1/**
2 * @author shaozilee
3 *
4 * Bmp format decoder,support 1bit 4bit 8bit 24bit bmp
5 *
6 */
7
8function BmpDecoder(buffer,is_with_alpha) {
9  this.pos = 0;
10  this.buffer = buffer;
11  this.is_with_alpha = !!is_with_alpha;
12  //Header should be BM
13  if (this.buffer[0] != 66 && this.buffer[1] != 77) throw new Error("Invalid BMP File");
14  this.pos += 2;
15  this.parseHeader();
16  this.parseBGR();
17}
18
19BmpDecoder.prototype.parseHeader = function() {
20  var b =  this.buffer;
21  this.fileSize = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
22  this.pos += 4;
23  this.reserved = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
24  this.pos += 4;
25  this.offset = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
26  this.pos += 4;
27  this.headerSize = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
28  this.pos += 4;
29  this.width = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
30  this.pos += 4;
31  this.height = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
32  this.pos += 4;
33  this.planes = (b[this.pos+1] << 8) | b[this.pos];
34  this.pos += 2;
35  this.bitPP = (b[this.pos+1] << 8) | b[this.pos];
36  this.pos += 2;
37  this.compress = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
38  this.pos += 4;
39  this.rawSize = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
40  this.pos += 4;
41  this.hr = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
42  this.pos += 4;
43  this.vr = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
44  this.pos += 4;
45  this.colors = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
46  this.pos += 4;
47  this.importantColors = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
48  this.pos += 4;
49
50  if(this.bitPP === 16 && this.is_with_alpha){
51    this.bitPP = 15
52  };
53  if (this.bitPP < 15) {
54    var len = this.colors === 0 ? 1 << this.bitPP : this.colors;
55    this.palette = new Array(len);
56    for (var i = 0; i < len; i++) {
57      var blue = this.buffer[this.pos++];
58      var green = this.buffer[this.pos++];
59      var red = this.buffer[this.pos++];
60      var quad = this.buffer[this.pos++];
61      this.palette[i] = {
62        red: red,
63        green: green,
64        blue: blue,
65        quad: quad
66      };
67    }
68  }
69
70}
71
72BmpDecoder.prototype.parseBGR = function() {
73  this.pos = this.offset;
74  try {
75    var bitn = "bit" + this.bitPP;
76
77    var canvas = document.createElement("canvas");
78    var ctx = canvas.getContext("2d");
79	var imageData = ctx.createImageData(this.width, this.height);
80	this.imageData = imageData;
81    this.data = imageData.data;
82
83    this[bitn]();
84  } catch (e) {
85    console.log("bit decode error:" + e);
86  }
87
88};
89
90BmpDecoder.prototype.bit1 = function() {
91  var xlen = Math.ceil(this.width / 8);
92  var mode = xlen%4;
93  for (var y = this.height - 1; y >= 0; y--) {
94    for (var x = 0; x < xlen; x++) {
95      var b = this.buffer[this.pos++];
96      var location = y * this.width * 4 + x*8*4;
97      for (var i = 0; i < 8; i++) {
98        if(x*8+i<this.width){
99          var rgb = this.palette[((b>>(7-i))&0x1)];
100          this.data[location+i*4] = rgb.red;
101          this.data[location+i*4 + 1] = rgb.green;
102          this.data[location+i*4 + 2] = rgb.blue;
103          this.data[location+i*4 + 3] = 0xFF;
104        }else{
105          break;
106        }
107      }
108    }
109
110    if (mode != 0){
111      this.pos+=(4 - mode);
112    }
113  }
114};
115
116BmpDecoder.prototype.bit4 = function() {
117  var xlen = Math.ceil(this.width/2);
118  var mode = xlen%4;
119  for (var y = this.height - 1; y >= 0; y--) {
120    for (var x = 0; x < xlen; x++) {
121      var b = this.buffer[this.pos++];//this.buffer.readUInt8(this.pos++);
122      var location = y * this.width * 4 + x*2*4;
123
124      var before = b>>4;
125      var after = b&0x0F;
126
127      var rgb = this.palette[before];
128      this.data[location] = rgb.red;
129      this.data[location + 1] = rgb.green;
130      this.data[location + 2] = rgb.blue;
131      this.data[location + 3] = 0xFF;
132
133      if(x*2+1>=this.width)break;
134
135      rgb = this.palette[after];
136      this.data[location+4] = rgb.red;
137      this.data[location+4 + 1] = rgb.green;
138      this.data[location+4 + 2] = rgb.blue;
139      this.data[location+4 + 3] = 0xFF;
140    }
141
142    if (mode != 0){
143      this.pos+=(4 - mode);
144    }
145  }
146
147};
148
149BmpDecoder.prototype.bit8 = function() {
150  var mode = this.width%4;
151  for (var y = this.height - 1; y >= 0; y--) {
152    for (var x = 0; x < this.width; x++) {
153      var b = this.buffer[this.pos++];
154      var location = y * this.width * 4 + x*4;
155      if(b < this.palette.length) {
156        var rgb = this.palette[b];
157        this.data[location] = rgb.red;
158        this.data[location + 1] = rgb.green;
159        this.data[location + 2] = rgb.blue;
160        this.data[location + 3] = 0xFF;
161      } else {
162        this.data[location] = 0xFF;
163        this.data[location + 1] = 0xFF;
164        this.data[location + 2] = 0xFF;
165        this.data[location + 3] = 0xFF;
166      }
167    }
168    if (mode != 0){
169      this.pos+=(4 - mode);
170    }
171  }
172};
173
174//Currently not used!
175BmpDecoder.prototype.bit15 = function() {
176  //FIXED BUG, padding is based on number of bytes not the width
177  var dif_w = (this.width * 2) % 4;
178  if (dif_w != 0) {
179	  dif_w = 4 - dif_w;
180  }
181  var _11111 = parseInt("11111", 2),_1_5 = _11111;
182  for (var y = this.height - 1; y >= 0; y--) {
183    for (var x = 0; x < this.width; x++) {
184
185      var B = (this.buffer[this.pos+1] << 8) | this.buffer[this.pos];
186      this.pos+=2;
187      var blue = (B & _1_5) / _1_5 * 255 | 0;
188      var green = (B >> 5 & _1_5 ) / _1_5 * 255 | 0;
189      var red = (B >> 10 & _1_5) / _1_5 * 255 | 0;
190      var alpha = (B>>15)?0xFF:0x00;
191
192      var location = y * this.width * 4 + x * 4;
193      this.data[location] = red;
194      this.data[location + 1] = green;
195      this.data[location + 2] = blue;
196      this.data[location + 3] = alpha;
197    }
198    //skip extra bytes
199    this.pos += dif_w;
200  }
201};
202
203//TODO support other RGB masks, e.g., RGB565
204BmpDecoder.prototype.bit16 = function() {
205  //FIXED BUG, padding is based on number of bytes not the width
206  var dif_w = (this.width * 2) % 4;
207  if (dif_w != 0) {
208	  dif_w = 4 - dif_w;
209  }
210  var _11111 = parseInt("11111", 2),_1_5 = _11111;
211  var _111111 = parseInt("111111", 2),_1_6 = _111111;
212  for (var y = this.height - 1; y >= 0; y--) {
213    for (var x = 0; x < this.width; x++) {
214
215      var B = (this.buffer[this.pos+1] << 8) | this.buffer[this.pos];
216      this.pos+=2;
217      var alpha = 0xFF;
218      var blue = (B & _1_5) / _1_5 * 255 | 0;
219      var green = (B >> 5 & _1_5) / _1_5 * 255 | 0;
220      var red = (B >> 10 & _1_5) / _1_5 * 255 | 0;
221
222      var location = y * this.width * 4 + x * 4;
223      this.data[location] = red;
224      this.data[location + 1] = green;
225      this.data[location + 2] = blue;
226      this.data[location + 3] = alpha;
227    }
228    //skip extra bytes
229    this.pos += dif_w;
230  }
231};
232
233BmpDecoder.prototype.bit24 = function() {
234  //when height > 0
235  //FIXED BUG, padding is based on number of bytes not the width
236  var dif_w = ((this.width * 3) % 4);
237  if (dif_w != 0) {
238	  dif_w = 4 - dif_w;
239  }
240  for (var y = this.height - 1; y >= 0; y--) {
241    for (var x = 0; x < this.width; x++) {
242      var blue = this.buffer[this.pos++];
243      var green = this.buffer[this.pos++];
244      var red = this.buffer[this.pos++];
245      var location = y * this.width * 4 + x * 4;
246      this.data[location] = red;
247      this.data[location + 1] = green;
248      this.data[location + 2] = blue;
249      this.data[location + 3] = 0xFF;
250    }
251    //skip extra bytes
252    this.pos += dif_w;
253  }
254
255};
256
257/**
258 * add 32bit decode func
259 * @author soubok
260 */
261BmpDecoder.prototype.bit32 = function() {
262  //when height > 0
263  for (var y = this.height - 1; y >= 0; y--) {
264    for (var x = 0; x < this.width; x++) {
265      var blue = this.buffer[this.pos++];
266      var green = this.buffer[this.pos++];
267      var red = this.buffer[this.pos++];
268      var alpha = this.buffer[this.pos++];
269      var location = y * this.width * 4 + x * 4;
270      //FIXED BUG alpha is the last byte in image data
271      this.data[location] = red;
272      this.data[location + 1] = green;
273      this.data[location + 2] = blue;
274      this.data[location + 3] = alpha;
275    }
276    //FIXED BUG no padding is needed for 32 bit images "the length of the rows IS a multiple of four bytes"
277  }
278
279};
280
281BmpDecoder.prototype.getData = function() {
282  return this.data;
283};