Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript generate transparent 1X1 pixel in dataURL format

I would like to know a way to generate a single pixel in JavaScript converting it to base64. The ideal function would be:

function createPixel(hexColor, opacity){
   //...Calculate
   return base64DataURL;
}

I am not very familiar with image processing. Any format is fine (png, gif etc). I would like to use this to overlay background images, (yes I could use rgba css3 but I am trying to place it with a background-image only on one element so i am not overlaying an element on top of another to achieve the effect).

Thanks in advance.

Edit: I would like not to use canvas, I am sure you can use canvas to get the base64 dataURL but I am sure it is not as fast as a string manipulation. Also I am not worried about converting an image into base64 but more interested in creating the image.

like image 587
user654628 Avatar asked Apr 30 '11 23:04

user654628


4 Answers

4+ years later, here's a simpler solution that generates a standard GIF so actually works in browsers (I couldn't get the PEM solution to work in anything) and is up to an order of magnitude faster than PEM/canvas. Only downside is GIF doesn't support alpha opacity - but that can be controlled via CSS.

It's based on this JSFiddle (unknown beautiful author), but with basic optimisation - reused keyStr and accepts both hex string ('#FF0000') and hex literal (0xFF0000) - latter much faster (thanks icktoofay).

<html>

<body onload="Test()">
  <script>
    function Test() {
      var img = new Image;
      img.src = createPixelGIF(0xff0000); // generate a red pixel data URI
      img.height = 100;
      img.width = 100; // optional: add dimensions
      document.body.appendChild(img); // add to the page
    }

    // ROUTINES =============

    var createPixelGIF = (function() {

      var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

      return function createPixelGIF(hexColor) {
        return "data:image/gif;base64,R0lGODlhAQABAPAA" + encodeHex(hexColor) + "/yH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
      }

      function encodeHex(hexColor) {
        var rgb;
        if (typeof hexColor == 'string') {
          var s = hexColor.substring(1, 7);
          if (s.length < 6) s = s[0] + s[0] + s[1] + s[1] + s[2] + s[2];
          rgb = [parseInt(s[0] + s[1], 16), parseInt(s[2] + s[3], 16), parseInt(s[4] + s[5], 16)];
        } else
          rgb = [(hexColor & (0xFF << 16)) >> 16, (hexColor & (0xFF << 8)) >> 8, hexColor & 0xFF];

        return encodeRGB(rgb[0], rgb[1], rgb[2]);
      }

      function encodeRGB(r, g, b) {
        return encode_triplet(0, r, g) + encode_triplet(b, 255, 255);
      }

      function encode_triplet(e1, e2, e3) {
        enc1 = e1 >> 2;
        enc2 = ((e1 & 3) << 4) | (e2 >> 4);
        enc3 = ((e2 & 15) << 2) | (e3 >> 6);
        enc4 = e3 & 63;
        return keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
      }

    })();
  </script>
</body>

</html>

Updated JSPerf results: http://jsperf.com/base64image/4 (code above is "createPixelGIF2"). You'll see I tried further optimisation (3+4), but it seems JS is happier with stack operations than hard-to-read combo functions :)

I also added an updated test for the canvas method, which for some reason excluded the instantiation of the canvas object - the biggest performance drag that would be seen in real world use.

like image 92
Ben Johnson Avatar answered Nov 15 '22 17:11

Ben Johnson


Here's a fully cross-browser-compatible implementation using <canvas> (demo @ jsfiddle).

var canvas = document.createElement('canvas');

// http://code.google.com/p/explorercanvas/wiki/Instructions#Dynamically_created_elements
if (!canvas.getContext) G_vmlCanvasManager.initElement(canvas);

var ctx = canvas.getContext('2d');
canvas.width = 1;
canvas.height = 1;

// for simplicity, assume the input is in rgba format
function createPixel(r, g, b, a) {
    ctx.fillStyle = 'rgba(' + [r,g,b,a].join() + ')';
    ctx.fillRect(0,0,1,1);
    // 'data:image/png;base64,'.length => 22
    return canvas.toDataURL('image/png','').substring(22);
}

I was curious to see how this stacks up against icktoofay's answer, performance-wise. Note, this will have to use excanvas for IE <9, which means that performance will almost certainly be worse there (but what's new).

Check out the jsperf: http://jsperf.com/base64image

like image 21
Matt Ball Avatar answered Nov 15 '22 15:11

Matt Ball


function createPlaceholder(w, h) {
    var img = document.createElement('img');
    img.setAttribute('style', 'width:'+w+'px;height:'+h+'px;border:none;display:block');
    img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';
    return img;
}

(for transparent pixel)

like image 4
jaboja Avatar answered Nov 15 '22 16:11

jaboja


Try this. It uses a somewhat esoteric image format (PAM), but you said any format is fine, and it really does work! It's not optimized or anything, so it's probably pretty slow, but hey, it works.

Edit: Okay, I optimized it a little...

var createPixel=(function() {
    var table=[];
    for(var i=0;i<26;i++) {
        table.push(String.fromCharCode("A".charCodeAt(0)+i));
    }
    for(var i=0;i<26;i++) {
        table.push(String.fromCharCode("a".charCodeAt(0)+i));
    }
    for(var i=0;i<10;i++) {
        table.push(i.toString(10));
    }
    table.push("+");
    table.push("/");
    function b64encode(x) {
        var bits=[];
        for(var i=0;i<x.length;i++) {
            var byte=x.charCodeAt(i);
            for(var j=7;j>=0;j--) {
                bits.push(byte&(1<<j));
            }
        }
        var output=[];
        for(var i=0;i<bits.length;i+=6) {
            var section=bits.slice(i, i+6).map(
                function(bit) { return bit?1:0; });
            var required=6-section.length;
            while(section.length<6) section.push(0);
            section=(section[0]<<5)|
                (section[1]<<4)|
                (section[2]<<3)|
                (section[3]<<2)|
                (section[4]<<1)|
                section[5];
            output.push(table[section]);
            if(required==2) {
                output.push('=');
            }else if(required==4) {
                output.push('==');
            }
        }
        output=output.join("");
        return output;
    }
    if(window.btoa) {
        b64encode=window.btoa;
    }
    return function createPixel(hexColor, opacity) {
        var colorTuple=[
            (hexColor&(0xFF<<16))>>16,
            (hexColor&(0xFF<<8))>>8,
            hexColor&0xFF,
            Math.floor(opacity*255)
        ];
        var data="P7\nWIDTH 1\nHEIGHT 1\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n";
        colorTuple.forEach(function(tupleElement) {
            data+=String.fromCharCode(tupleElement);
        });
        var base64DataURL="data:image/pam;base64,"+b64encode(data);
        return base64DataURL;
    }
})();

...but really, canvas should work fine.

like image 2
icktoofay Avatar answered Nov 15 '22 15:11

icktoofay