/** * @author shaozilee * * Bmp format decoder,support 1bit 4bit 8bit 24bit bmp * */ function BmpDecoder(buffer,is_with_alpha) { this.pos = 0; this.buffer = buffer; this.is_with_alpha = !!is_with_alpha; //Header should be BM if (this.buffer[0] != 66 && this.buffer[1] != 77) throw new Error("Invalid BMP File"); this.pos += 2; this.parseHeader(); this.parseBGR(); } BmpDecoder.prototype.parseHeader = function() { var b = this.buffer; this.fileSize = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; this.pos += 4; this.reserved = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; this.pos += 4; this.offset = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; this.pos += 4; this.headerSize = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; this.pos += 4; this.width = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; this.pos += 4; this.height = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; this.pos += 4; this.planes = (b[this.pos+1] << 8) | b[this.pos]; this.pos += 2; this.bitPP = (b[this.pos+1] << 8) | b[this.pos]; this.pos += 2; this.compress = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; this.pos += 4; this.rawSize = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; this.pos += 4; this.hr = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; this.pos += 4; this.vr = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; this.pos += 4; this.colors = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; this.pos += 4; this.importantColors = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; 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.buffer[this.pos++]; var green = this.buffer[this.pos++]; var red = this.buffer[this.pos++]; var quad = this.buffer[this.pos++]; this.palette[i] = { red: red, green: green, blue: blue, quad: quad }; } } } BmpDecoder.prototype.parseBGR = function() { this.pos = this.offset; try { var bitn = "bit" + this.bitPP; var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); var imageData = ctx.createImageData(this.width, this.height); this.imageData = imageData; this.data = imageData.data; 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; for (var y = this.height - 1; y >= 0; y--) { for (var x = 0; x < xlen; x++) { var b = this.buffer[this.pos++]; var location = y * this.width * 4 + x*8*4; for (var i = 0; i < 8; i++) { if(x*8+i>(7-i))&0x1)]; this.data[location+i*4] = rgb.red; this.data[location+i*4 + 1] = rgb.green; this.data[location+i*4 + 2] = rgb.blue; 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--) { for (var x = 0; x < xlen; x++) { var b = this.buffer[this.pos++];//this.buffer.readUInt8(this.pos++); var location = y * this.width * 4 + x*2*4; var before = b>>4; var after = b&0x0F; var rgb = this.palette[before]; this.data[location] = rgb.red; this.data[location + 1] = rgb.green; this.data[location + 2] = rgb.blue; this.data[location + 3] = 0xFF; if(x*2+1>=this.width)break; rgb = this.palette[after]; this.data[location+4] = rgb.red; this.data[location+4 + 1] = rgb.green; this.data[location+4 + 2] = rgb.blue; 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--) { for (var x = 0; x < this.width; x++) { var b = this.buffer[this.pos++]; var location = y * 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); } } }; //Currently not used! BmpDecoder.prototype.bit15 = function() { //FIXED BUG, padding is based on number of bytes not the width var dif_w = (this.width * 2) % 4; if (dif_w != 0) { dif_w = 4 - dif_w; } var _11111 = parseInt("11111", 2),_1_5 = _11111; for (var y = this.height - 1; y >= 0; y--) { for (var x = 0; x < this.width; x++) { var B = (this.buffer[this.pos+1] << 8) | this.buffer[this.pos]; 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 = y * 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; } }; //TODO support other RGB masks, e.g., RGB565 BmpDecoder.prototype.bit16 = function() { //FIXED BUG, padding is based on number of bytes not the width var dif_w = (this.width * 2) % 4; if (dif_w != 0) { dif_w = 4 - dif_w; } 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--) { for (var x = 0; x < this.width; x++) { var B = (this.buffer[this.pos+1] << 8) | this.buffer[this.pos]; this.pos+=2; var alpha = 0xFF; 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 location = y * 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 //FIXED BUG, padding is based on number of bytes not the width var dif_w = ((this.width * 3) % 4); if (dif_w != 0) { dif_w = 4 - dif_w; } for (var y = this.height - 1; y >= 0; y--) { for (var x = 0; x < this.width; x++) { var blue = this.buffer[this.pos++]; var green = this.buffer[this.pos++]; var red = this.buffer[this.pos++]; var location = y * 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 += dif_w; } }; /** * add 32bit decode func * @author soubok */ BmpDecoder.prototype.bit32 = function() { //when height > 0 for (var y = this.height - 1; y >= 0; y--) { for (var x = 0; x < this.width; x++) { var blue = this.buffer[this.pos++]; var green = this.buffer[this.pos++]; var red = this.buffer[this.pos++]; var alpha = this.buffer[this.pos++]; var location = y * this.width * 4 + x * 4; //FIXED BUG alpha is the last byte in image data this.data[location] = red; this.data[location + 1] = green; this.data[location + 2] = blue; this.data[location + 3] = alpha; } //FIXED BUG no padding is needed for 32 bit images "the length of the rows IS a multiple of four bytes" } }; BmpDecoder.prototype.getData = function() { return this.data; };