libs/BMPDecoder.js

/**
 * @author shaozilee
 *
 * Bmp format decoder,support 1bit 4bit 8bit 24bit bmp
 *
 */

import { console } from "./console.js";

function BmpDecoder(buffer, is_with_alpha) {
  this.pos = 0;
  this.buffer = buffer;
  this.datav = new DataView(buffer.buffer);
  this.is_with_alpha = !!is_with_alpha;
  this.bottom_up = true;
  this.flag =
    String.fromCharCode(this.buffer[0]) + String.fromCharCode(this.buffer[1]);
  this.pos += 2;
  if (["BM", "BA", "CI", "CP", "IC", "PT"].indexOf(this.flag) === -1)
    throw new Error("Invalid BMP File");
  this.parseHeader();
  this.parseBGR();
}

BmpDecoder.prototype.parseHeader = function() {
  this.fileSize = this.datav.getUint32(this.pos, true);
  this.pos += 4;
  this.reserved = this.datav.getUint32(this.pos, true);
  this.pos += 4;
  this.offset = this.datav.getUint32(this.pos, true);
  this.pos += 4;
  this.headerSize = this.datav.getUint32(this.pos, true);
  this.pos += 4;
  this.width = this.datav.getUint32(this.pos, true);
  this.pos += 4;
  this.height = this.datav.getInt32(this.pos, true);
  this.pos += 4;
  this.planes = this.datav.getUint16(this.pos, true);
  this.pos += 2;
  this.bitPP = this.datav.getUint16(this.pos, true);
  this.pos += 2;
  this.compress = this.datav.getUint32(this.pos, true);
  this.pos += 4;
  this.rawSize = this.datav.getUint32(this.pos, true);
  this.pos += 4;
  this.hr = this.datav.getUint32(this.pos, true);
  this.pos += 4;
  this.vr = this.datav.getUint32(this.pos, true);
  this.pos += 4;
  this.colors = this.datav.getUint32(this.pos, true);
  this.pos += 4;
  this.importantColors = this.datav.getUint32(this.pos, true);
  this.pos += 4;

  if (this.bitPP === 16 && this.is_with_alpha) {
    this.bitPP = 15;
  }
  if (this.bitPP < 15) {
    var len = this.colors === 0 ? 1 << this.bitPP : this.colors;
    this.palette = new Array(len);
    for (var i = 0; i < len; i++) {
      var blue = this.datav.getUint8(this.pos++, true);
      var green = this.datav.getUint8(this.pos++, true);
      var red = this.datav.getUint8(this.pos++, true);
      var quad = this.datav.getUint8(this.pos++, true);
      this.palette[i] = {
        red: red,
        green: green,
        blue: blue,
        quad: quad
      };
    }
  }
  if (this.height < 0) {
    this.height *= -1;
    this.bottom_up = false;
  }
};

BmpDecoder.prototype.parseBGR = function() {
  this.pos = this.offset;
  try {
    var bitn = "bit" + this.bitPP;
    var len = this.width * this.height * 4;
    this.data = new Uint8Array(len);

    this[bitn]();
  } catch (e) {
    console.log("bit decode error:" + e);
  }
};

BmpDecoder.prototype.bit1 = function() {
  var xlen = Math.ceil(this.width / 8);
  var mode = xlen % 4;
  var y;
  for (y = this.height - 1; y >= 0; y--) {
    var line = this.bottom_up ? y : this.height - 1 - y;
    for (var x = 0; x < xlen; x++) {
      var b = this.datav.getUint8(this.pos++, true);
      var location = line * this.width * 4 + x * 8 * 4;
      for (var i = 0; i < 8; i++) {
        if (x * 8 + i < this.width) {
          var rgb = this.palette[(b >> (7 - i)) & 0x1];
          this.data[location + i * 4] = rgb.blue;
          this.data[location + i * 4 + 1] = rgb.green;
          this.data[location + i * 4 + 2] = rgb.red;
          this.data[location + i * 4 + 3] = 0xff;
        } else {
          break;
        }
      }
    }

    if (mode !== 0) {
      this.pos += 4 - mode;
    }
  }
};

BmpDecoder.prototype.bit4 = function() {
  var xlen = Math.ceil(this.width / 2);
  var mode = xlen % 4;
  for (var y = this.height - 1; y >= 0; y--) {
    var line = this.bottom_up ? y : this.height - 1 - y;
    for (var x = 0; x < xlen; x++) {
      var b = this.datav.getUint8(this.pos++, true);
      var location = line * this.width * 4 + x * 2 * 4;

      var before = b >> 4;
      var after = b & 0x0f;

      var rgb = this.palette[before];
      this.data[location] = rgb.blue;
      this.data[location + 1] = rgb.green;
      this.data[location + 2] = rgb.red;
      this.data[location + 3] = 0xff;

      if (x * 2 + 1 >= this.width) break;

      rgb = this.palette[after];
      this.data[location + 4] = rgb.blue;
      this.data[location + 4 + 1] = rgb.green;
      this.data[location + 4 + 2] = rgb.red;
      this.data[location + 4 + 3] = 0xff;
    }

    if (mode !== 0) {
      this.pos += 4 - mode;
    }
  }
};

BmpDecoder.prototype.bit8 = function() {
  var mode = this.width % 4;
  for (var y = this.height - 1; y >= 0; y--) {
    var line = this.bottom_up ? y : this.height - 1 - y;
    for (var x = 0; x < this.width; x++) {
      var b = this.datav.getUint8(this.pos++, true);
      var location = line * this.width * 4 + x * 4;
      if (b < this.palette.length) {
        var rgb = this.palette[b];
        this.data[location] = rgb.red;
        this.data[location + 1] = rgb.green;
        this.data[location + 2] = rgb.blue;
        this.data[location + 3] = 0xff;
      } else {
        this.data[location] = 0xff;
        this.data[location + 1] = 0xff;
        this.data[location + 2] = 0xff;
        this.data[location + 3] = 0xff;
      }
    }
    if (mode !== 0) {
      this.pos += 4 - mode;
    }
  }
};

BmpDecoder.prototype.bit15 = function() {
  var dif_w = this.width % 3;
  var _11111 = parseInt("11111", 2),
    _1_5 = _11111;
  for (var y = this.height - 1; y >= 0; y--) {
    var line = this.bottom_up ? y : this.height - 1 - y;
    for (var x = 0; x < this.width; x++) {
      var B = this.datav.getUint16(this.pos, true);
      this.pos += 2;
      var blue = (((B & _1_5) / _1_5) * 255) | 0;
      var green = ((((B >> 5) & _1_5) / _1_5) * 255) | 0;
      var red = ((((B >> 10) & _1_5) / _1_5) * 255) | 0;
      var alpha = B >> 15 ? 0xff : 0x00;

      var location = line * this.width * 4 + x * 4;
      this.data[location] = red;
      this.data[location + 1] = green;
      this.data[location + 2] = blue;
      this.data[location + 3] = alpha;
    }
    //skip extra bytes
    this.pos += dif_w;
  }
};

BmpDecoder.prototype.bit16 = function() {
  var dif_w = this.width % 3;
  var _11111 = parseInt("11111", 2),
    _1_5 = _11111;
  var _111111 = parseInt("111111", 2),
    _1_6 = _111111;
  for (var y = this.height - 1; y >= 0; y--) {
    var line = this.bottom_up ? y : this.height - 1 - y;
    for (var x = 0; x < this.width; x++) {
      var B = this.datav.getUint16(this.pos, true);
      this.pos += 2;
      var alpha = 0xff;
      var blue = (((B & _1_5) / _1_5) * 255) | 0;
      var green = ((((B >> 5) & _1_6) / _1_6) * 255) | 0;
      var red = (((B >> 11) / _1_5) * 255) | 0;

      var location = line * this.width * 4 + x * 4;
      this.data[location] = red;
      this.data[location + 1] = green;
      this.data[location + 2] = blue;
      this.data[location + 3] = alpha;
    }
    //skip extra bytes
    this.pos += dif_w;
  }
};

BmpDecoder.prototype.bit24 = function() {
  //when height > 0
  for (var y = this.height - 1; y >= 0; y--) {
    var line = this.bottom_up ? y : this.height - 1 - y;
    for (var x = 0; x < this.width; x++) {
      var blue = this.datav.getUint8(this.pos++, true);
      var green = this.datav.getUint8(this.pos++, true);
      var red = this.datav.getUint8(this.pos++, true);
      var location = line * this.width * 4 + x * 4;
      this.data[location] = red;
      this.data[location + 1] = green;
      this.data[location + 2] = blue;
      this.data[location + 3] = 0xff;
    }
    //skip extra bytes
    this.pos += this.width % 4;
  }
};

/**
 * add 32bit decode func
 * @author soubok
 */
BmpDecoder.prototype.bit32 = function() {
  //when height > 0
  for (var y = this.height - 1; y >= 0; y--) {
    var line = this.bottom_up ? y : this.height - 1 - y;
    for (var x = 0; x < this.width; x++) {
      var blue = this.datav.getUint8(this.pos++, true);
      var green = this.datav.getUint8(this.pos++, true);
      var red = this.datav.getUint8(this.pos++, true);
      var alpha = this.datav.getUint8(this.pos++, true);
      var location = line * this.width * 4 + x * 4;
      this.data[location] = red;
      this.data[location + 1] = green;
      this.data[location + 2] = blue;
      this.data[location + 3] = alpha;
    }
    //skip extra bytes
    //this.pos += (this.width % 4);
  }
};

BmpDecoder.prototype.getData = function() {
  return this.data;
};

export { BmpDecoder };