Advertisement
RReverser

BMPImage class from Javascript BMP Parser

May 16th, 2012
366
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. function BMPImage(data) {
  2.     this.parser = new jParser(data, BMPImage.structure);
  3.     this.meta = this.parser.parse('header');
  4. }
  5.  
  6. BMPImage.structure = {
  7.     bgr: {
  8.         b: 'uint8',
  9.         g: 'uint8',
  10.         r: 'uint8'
  11.     },
  12.     bgra: {
  13.         b: 'uint8',
  14.         g: 'uint8',
  15.         r: 'uint8',
  16.         a: 'uint8'
  17.     },
  18.     size: {
  19.         horz: 'uint32',
  20.         vert: 'uint32'
  21.     },
  22.     header: {
  23.         // bitmap "magic" signature
  24.         signature: function() {
  25.             var magic = this.parse(['string', 2]);
  26.             if (magic != 'BM') {
  27.                 throw new TypeError('Sorry, but only Windows BMP files are supported.');
  28.             }
  29.             return magic;
  30.         },
  31.         // full file size
  32.         fileSize: 'uint32',
  33.         // reserved
  34.         reserved: 'uint32',
  35.         // offset of bitmap data
  36.         dataOffset: 'uint32',
  37.         // size of DIB header
  38.         dibHeaderSize: 'uint32',
  39.         // image dimensions
  40.         size: 'size',
  41.         // color planes count (equals 1)
  42.         planesCount: 'uint16',
  43.         // color depth (bits per pixel)
  44.         bpp: 'uint16',
  45.         // compression type
  46.         compression: 'uint32',
  47.         // size of bitmap data
  48.         dataSize: 'uint32',
  49.         // resolutions (pixels per meter)
  50.         resolution: 'size',
  51.         // total color count
  52.         colorsCount: function() { return this.parse('uint32') || Math.pow(2, this.current.bpp) /* (1 << bpp) not applicable for 32bpp */ },
  53.         // count of colors that are required for displaying image
  54.         importantColorsCount: function() { return this.parse('uint32') || this.current.colorsCount },
  55.         // color palette (mandatory for <=8bpp images)
  56.         palette: [
  57.             'array',
  58.             function() {
  59.                 var color = this.parse('bgr');
  60.                 // align to 4 bytes
  61.                 this.skip(1);
  62.                 return color;
  63.             },
  64.             function() { return this.current.bpp <= 8 ? this.current.colorsCount : 0 }
  65.         ],
  66.         // color masks (needed for 16bpp images)
  67.         mask: {
  68.             r: 'uint32',
  69.             g: 'uint32',
  70.             b: 'uint32'
  71.         }
  72.     }
  73. }
  74.  
  75. BMPImage.prototype.drawToCanvas = function(canvas) {
  76.     canvas.width = this.meta.size.horz;
  77.     canvas.height = this.meta.size.vert;
  78.     this.drawToContext(canvas.getContext('2d'));
  79. }
  80.  
  81. BMPImage.prototype.drawToContext = function(context) {
  82.     if (this.meta.compression && this.meta.compression != 3) {
  83.         throw new TypeError('Sorry, but RLE compressed images are not supported.');
  84.     }
  85.  
  86. // seek to bitmap data start
  87. this.parser.seek(this.meta.dataOffset);
  88.  
  89.     var
  90.         // saving image sizes
  91.         size = this.meta.size,
  92.         // creating image data
  93.         imgData = context.createImageData(size.horz, size.vert),
  94.         // initializing bitmask for extracting particular bit ranges
  95.         bitMask = ~ (-1 << this.meta.bpp),
  96.         // color bit offset for <8bpp images
  97.         bitNumber,
  98.         // color index (should be stored between iterations for <8bpp images)
  99.         colorIndex;
  100.  
  101.     // timer start
  102.     var drawStartedTime = Date.now();
  103.  
  104.     // iterating over resulting bitmap bottom-to-top, left-to-right
  105.     for (var y = size.vert - 1; y > 0; y--) {
  106.         // calculating image data offset for row [y]
  107.         var dataPos = 4 * y * size.horz;
  108.  
  109.         // iterating over row pixels
  110.         for (var x = 0; x < size.horz; x++) {
  111.             var color;
  112.  
  113.             switch (this.meta.bpp) {
  114.                 case 1:
  115.                 case 2:
  116.                 case 4:
  117.                     // extracting bit ranges from highest to lowest (and moving to next byte when finished inside current)
  118.                     if (!bitNumber) {
  119.                         bitNumber = 8;
  120.                         colorIndex = this.parser.view.getUint8();
  121.                     }
  122.                     bitNumber -= this.meta.bpp;
  123.                     color = this.meta.palette[(colorIndex >> bitNumber) & bitMask];
  124.                     break;
  125.  
  126.                 case 8:
  127.                     // simply taking color by it's index
  128.                     color = this.meta.palette[this.parser.view.getUint8()];
  129.                     break;
  130.  
  131.                 case 16:
  132.                     // extracting RGB values using 5-6-5 encoding scheme and given masks
  133.                     colorIndex = this.parser.view.getUint16();
  134.                     color = {
  135.                         b: (colorIndex & this.meta.mask.b) << 3,
  136.                         g: (colorIndex & this.meta.mask.g) >> 3,
  137.                         r: (colorIndex & this.meta.mask.r) >> 8
  138.                     };
  139.                     break;
  140.  
  141.                 case 24:
  142.                     // reading RGB color
  143.                     color = this.parser.parse('bgr');
  144.                     break;
  145.  
  146.                 case 32:
  147.                     // reading RGBA color
  148.                     color = this.parser.parse('bgra');
  149.                     break;
  150.  
  151.                 default:
  152.                     throw new TypeError('Sorry, but ' + this.meta.bpp + 'bpp images are not supported.');
  153.             }
  154.  
  155.             // putting resulting RGBA values to image data
  156.             imgData.data[dataPos++] = color.r;
  157.             imgData.data[dataPos++] = color.g;
  158.             imgData.data[dataPos++] = color.b;
  159.             imgData.data[dataPos++] = color.a || 255;
  160.         }
  161.  
  162.         // padding new row's alignment to 4 bytes
  163.         var offsetOverhead = (this.parser.tell() - this.meta.dataOffset) % 4;
  164.         if (offsetOverhead) {
  165.             this.parser.skip(4 - offsetOverhead);
  166.             bitNumber = 0;
  167.         }
  168.     }
  169.  
  170.     var drawTime = Date.now() - drawStartedTime;
  171.  
  172.     this.meta.profiler = {
  173.         // timer stop
  174.         time: drawTime,
  175.         // calculating speed as pixels per millisecond
  176.         speed: (size.horz * size.vert) / drawTime
  177.     }
  178.  
  179.     // putting image data to given canvas context
  180.     context.putImageData(imgData, 0, 0);
  181. }
  182.  
  183. BMPImage.readFrom = function(source, callback) {
  184.     function callbackImg(data) { callback.call(new BMPImage(data)) }
  185.  
  186.     if (source instanceof File) {
  187.         // reading image from File instance
  188.  
  189.         var reader = new FileReader;
  190.         reader.onload = function() { callbackImg(this.result) }
  191.         reader.readAsArrayBuffer(source);
  192.     } else {
  193.         // reading image with AJAX request
  194.  
  195.         var xhr = new XMLHttpRequest;
  196.         xhr.open('GET', source, true);
  197.  
  198.         // new browsers (XMLHttpRequest2-compliant)
  199.         if ('responseType' in xhr) {
  200.             xhr.responseType = 'arraybuffer';
  201.         }
  202.         // old browsers (XMLHttpRequest-compliant)
  203.         else if ('overrideMimeType' in xhr) {
  204.             xhr.overrideMimeType('text/plain; charset=x-user-defined');
  205.         }
  206.         // IE9 (Microsoft.XMLHTTP-compliant)
  207.         else {
  208.             xhr.setRequestHeader('Accept-Charset', 'x-user-defined');
  209.         }
  210.  
  211.         xhr.onload = function() {
  212.             if (this.status != 200) {
  213.                 throw new Error(this.statusText);
  214.             }
  215.             // emulating response field for IE9
  216.             if (!('response' in this)) {
  217.                 this.response = new VBArray(this.responseBody).toArray().map(String.fromCharCode).join('');
  218.             }
  219.             callbackImg(this.response);
  220.         }
  221.  
  222.         xhr.send();
  223.     }
  224. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement