Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1034)

Side by Side Diff: src/codec/SkCodec_libbmp.cpp

Issue 947283002: Bmp Image Decoding (Closed) Base URL: https://skia.googlesource.com/skia.git@decode-leon-3
Patch Set: Tested bmp and swizzler design Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "SkCodec_libbmp.h"
9 #include "SkColorTable.h"
10 #include "SkEndian.h"
11 #include "SkSwizzler.h"
12 #include "SkStream.h"
13
14 /*
15 *
16 * Get a byte from the buffer
17 *
18 */
19 uint8_t get_byte(uint8_t* buffer, uint32_t i) {
20 return buffer[i];
21 }
22
23 /*
24 *
25 * Get a short from the buffer
26 *
27 */
28 uint16_t get_short(uint8_t* buffer, uint32_t i) {
29 uint16_t result;
30 memcpy(&result, &(buffer[i]), 2);
31 #ifdef SK_CPU_BENDIAN
scroggo 2015/02/25 17:22:42 This should not be indented. Same with other #ifde
32 return SkEndianSwap16(result);
33 #else
34 return result;
35 #endif
36 }
37
38 /*
39 *
40 * Get an int from the buffer
41 *
42 */
43 uint32_t get_int(uint8_t* buffer, uint32_t i) {
44 uint32_t result;
45 memcpy(&result, &(buffer[i]), 4);
46 #ifdef SK_CPU_BENDIAN
47 return SkEndianSwap32(result);
48 #else
49 return result;
50 #endif
51 }
52
53 /*
54 *
55 * Defines the version and type of the second bitmap header
56 *
57 */
58 enum BitmapHeaderType {
59 kInfoV1_BitmapHeaderType,
60 kInfoV2_BitmapHeaderType,
61 kInfoV3_BitmapHeaderType,
62 kInfoV4_BitmapHeaderType,
63 kInfoV5_BitmapHeaderType,
64 kOS2V1_BitmapHeaderType,
65 kOS2VX_BitmapHeaderType,
66 kUnknown_BitmapHeaderType
67 };
68
69 /*
70 *
71 * Possible bitmap compression types
72 *
73 */
74 enum BitmapCompressionMethod {
75 kNone_BitmapCompressionMethod = 0,
76 k8BitRLE_BitmapCompressionMethod = 1,
77 k4BitRLE_BitmapCompressionMethod = 2,
78 kBitMasks_BitmapCompressionMethod = 3,
79 kJpeg_BitmapCompressionMethod = 4,
80 kPng_BitmapCompressionMethod = 5,
81 kAlphaBitMasks_BitmapCompressionMethod = 6,
82 kCMYK_BitmapCompressionMethod = 11,
83 kCMYK8BitRLE_BitmapCompressionMethod = 12,
84 kCMYK4BitRLE_BitmapCompressionMethod = 13
85 };
86
87 /*
88 *
89 * Checks the start of the stream to see if the image is a bitmap
90 *
91 */
92 bool SkBmpCodec::IsBmp(SkStream* stream) {
93 const char bmpSig[] = { 'B', 'M' };
94 char buffer[sizeof(bmpSig)];
95 return stream->read(buffer, sizeof(bmpSig)) == sizeof(bmpSig) &&
96 !memcmp(buffer, bmpSig, sizeof(bmpSig));
97 }
98
99 /*
100 *
101 * Assumes IsBmp was called and returned true
102 * Creates a bitmap decoder
103 * Reads enough of the stream to determine the image format
104 *
105 */
106 SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) {
107 // Read the first header and the size of the second header
108 SkAutoTDeleteArray<uint8_t> hBuffer(new uint8_t[kBmpHeaderBytes + 4]);
scroggo 2015/02/25 17:22:42 You should use SkNEW_ARRAY (it can be found in SkP
109 if (stream->read(hBuffer.get(), kBmpHeaderBytes + 4) !=
msarett 2015/02/24 21:56:06 The size of the second header is technically the f
scroggo 2015/02/25 17:22:42 Why not have something like: const int kBmpHeader
110 kBmpHeaderBytes + 4) {
111 SkDebugf("Error: unable to read first bitmap header.\n");
112 return NULL;
113 }
114 //uint16_t signature = get_short(hBuffer, 0);
115 //uint32_t totalBytes = get_int(hBuffer, 2);
116 //uint32_t reserved = get_int(hBuffer, 6);
117 uint32_t offset = get_int(hBuffer.get(), 10);
scroggo 2015/02/25 17:22:42 Can these values be const? More importantly, keep
118 uint32_t infoBytes = get_int(hBuffer.get(), 14);
119 hBuffer.free();
120
121 // Read the second header
122 BitmapHeaderType headerType = kUnknown_BitmapHeaderType;
123 SkAutoTDeleteArray<uint8_t> iBuffer(new uint8_t[infoBytes - 4]);
scroggo 2015/02/25 17:22:41 Again, if you're using SkAutoTDeleteArray, which c
124 if (stream->read(iBuffer.get(), infoBytes - 4) != infoBytes - 4) {
125 SkDebugf("Error: unable to read second bitmap header.\n");
126 return NULL;
127 }
128
129 // If applicable, set default values for the header fields
130 uint16_t bitsPerPixel;
131 uint32_t compression = kNone_BitmapCompressionMethod;
132 uint32_t numColors = 0;
133 uint32_t bytesPerColor = 3;
134 int width, height;
135 if (infoBytes > kBmpOS2V1Bytes) {
136 // Check for the many partial versions of the OS 2 header
137 if ((infoBytes <= kBmpOS2V2Bytes && !(infoBytes & 3))
138 || 42 == infoBytes || 46 == infoBytes) {
139 headerType = kOS2VX_BitmapHeaderType;
140 }
141 // Check for versions of the Windows headers
142 switch (infoBytes) {
143 case kBmpInfoV1Bytes:
144 headerType = kInfoV1_BitmapHeaderType;
145 break;
146 case kBmpInfoV2Bytes:
147 headerType = kInfoV2_BitmapHeaderType;
148 break;
149 case kBmpInfoV3Bytes:
150 headerType = kInfoV3_BitmapHeaderType;
151 break;
152 case kBmpInfoV4Bytes:
153 headerType = kInfoV4_BitmapHeaderType;
154 break;
155 case kBmpInfoV5Bytes:
156 headerType = kInfoV5_BitmapHeaderType;
157 break;
158 default:
159 break;
160 }
161 width = get_int(iBuffer.get(), 0);
162 height = get_int(iBuffer.get(), 4);
163 //uint16_t planes = get_short(iBuffer, 8);
164 bitsPerPixel = get_short(iBuffer.get(), 10);
165 if (infoBytes - 4 >= 16) {
166 compression = get_int(iBuffer.get(), 12);
167 }
168 //uint32_t imageBytes = get_int(iBuffer, 16);
169 //uint32_t horizontalResolution = get_int(iBuffer, 20);
170 //uint32_t verticalResolution = get_int(iBuffer, 24);
171 if (infoBytes - 4 >= 32) {
172 numColors = get_int(iBuffer.get(), 28);
173 }
174 //uint32_t importantColors = get_int(iBuffer, infoBytes - 4, 32);
175 bytesPerColor = 4;
176 } else if (kBmpOS2V1Bytes == infoBytes) {
177 // This first OS 2 header has a unique format
178 headerType = kOS2V1_BitmapHeaderType;
179 width = (short) get_short(iBuffer.get(), 0);
scroggo 2015/02/25 17:22:42 Why did you cast to a short? It looks like width a
180 height = (short) get_short(iBuffer.get(), 2);
181 uint16_t planes = get_short(iBuffer.get(), 4);
182 bitsPerPixel = get_short(iBuffer.get(), 6);
183 compression = kNone_BitmapCompressionMethod;
184 numColors = 0;
185 bytesPerColor = 3;
186 } else {
187 SkDebugf("Error: second bitmap header size is invalid.\n");
188 return NULL;
189 }
190
191 // Check the header type
192 if (kUnknown_BitmapHeaderType == headerType) {
193 SkDebugf("Warning: bitmap header type may not be supported.\n");
scroggo 2015/02/25 17:22:41 What does this mean? (Also, it looks like it could
194 }
195
196 // Check for valid dimensions from header
197 bool inverted = true;
198 if (height < 0) {
199 height = -height;
200 inverted = false;
201 }
202 if (width <= 0 || width > kBmpMaxDim || !height || height > kBmpMaxDim) {
203 SkDebugf("Error: invalid bitmap dimensions.\n");
204 return NULL;
205 }
206
207 // Initialize bit masks
208 uint32_t redMask = 0, greenMask = 0, blueMask = 0, alphaMask = 0;
209 switch (bitsPerPixel) {
210 // Represent standard 16-bit format as bit masks (555)
scroggo 2015/02/25 17:22:42 nit: This would be clearer to me if it said someth
211 case 16:
212 redMask = 0x7C00;
213 greenMask = 0x03E0;
214 blueMask = 0x001F;
215 break;
216 default:
scroggo 2015/02/25 17:22:42 Do you plan to support other bitsPerPixel? If so,
msarett 2015/02/26 23:58:18 Other bits per pixel are supported. We just don't
217 break;
218 }
219
220 // Determine the format of the input
221 uint32_t maskBytes = 0;
222 BitmapInputFormat inputFormat = kUnknown_BitmapInputFormat;
223 switch (compression) {
224 case kNone_BitmapCompressionMethod:
225 inputFormat = kStandard_BitmapInputFormat;
226 // Always respect alpha mask in V4+
227 if (headerType == kInfoV4_BitmapHeaderType ||
228 headerType == kInfoV5_BitmapHeaderType) {
229 alphaMask = get_int(iBuffer.get(), 48);
scroggo 2015/02/25 17:22:41 compile assert that 48 is safe.
230 }
231 break;
232 case k8BitRLE_BitmapCompressionMethod:
233 if (bitsPerPixel != 8) {
234 SkDebugf("Warning: correcting invalid bitmap format.\n");
235 bitsPerPixel = 8;
236 }
237 inputFormat = k8BitRLE_BitmapInputFormat;
238 break;
239 case k4BitRLE_BitmapCompressionMethod:
240 if (bitsPerPixel != 4) {
241 SkDebugf("Warning: correcting invalid bitmap format.\n");
242 bitsPerPixel = 4;
243 }
244 inputFormat = k4BitRLE_BitmapInputFormat;
245 break;
246 case kAlphaBitMasks_BitmapCompressionMethod:
247 case kBitMasks_BitmapCompressionMethod:
248 // Load the masks
249 if (headerType == kInfoV1_BitmapHeaderType) {
250 SkAutoTDeleteArray<uint8_t> mBuffer(new uint8_t[kBmpMaskBytes]);
251 if (stream->read(mBuffer.get(), kBmpMaskBytes) !=
252 kBmpMaskBytes) {
253 SkDebugf("Error: unable to read bit masks.\n");
254 return NULL;
255 }
256 maskBytes = kBmpMaskBytes;
257 redMask = get_int(mBuffer.get(), 0);
258 greenMask = get_int(mBuffer.get(), 4);
259 blueMask = get_int(mBuffer.get(), 8);
260 mBuffer.free();
scroggo 2015/02/25 17:22:41 This call is unnecessary. mBuffer will go out of s
261 } else if (headerType == kInfoV2_BitmapHeaderType ||
scroggo 2015/02/25 17:22:42 Any reason not to make this a switch statement? -
262 headerType == kInfoV3_BitmapHeaderType) {
263 redMask = get_int(iBuffer.get(), 36);
264 greenMask = get_int(iBuffer.get(), 40);
265 blueMask = get_int(iBuffer.get(), 44);
266 } else if (headerType == kInfoV4_BitmapHeaderType ||
267 headerType == kInfoV5_BitmapHeaderType) {
268 redMask = get_int(iBuffer.get(), 36);
269 greenMask = get_int(iBuffer.get(), 40);
270 blueMask = get_int(iBuffer.get(), 44);
271 alphaMask = get_int(iBuffer.get(), 48);
272 } else if (headerType == kOS2VX_BitmapHeaderType) {
scroggo 2015/02/25 17:22:42 Is this a TODO? It seems like we could have return
msarett 2015/02/26 23:58:18 It is a TODO in the chromium code. It is pretty l
273 SkDebugf("Error: huffman compression format unsupported.\n");
274 return NULL;
275 } else {
276 SkDebugf("Error: invalid compression format for header.\n");
277 return NULL;
278 }
279 inputFormat = kBitMask_BitmapInputFormat;
280 break;
281 case kJpeg_BitmapCompressionMethod:
282 case kPng_BitmapCompressionMethod:
283 SkDebugf("Error: compression format not supported.\n");
284 return NULL;
285 case kCMYK_BitmapCompressionMethod:
286 case kCMYK8BitRLE_BitmapCompressionMethod:
287 case kCMYK4BitRLE_BitmapCompressionMethod:
288 SkDebugf("Error: CMYK not supported for bitmap decoding.\n");
289 return NULL;
290 default:
291 SkDebugf("Error: invalid format for bitmap decoding.\n");
292 return NULL;
293 }
294 iBuffer.free();
295
296 // Create mask array
297 uint32_t* masks = new uint32_t[4];
scroggo 2015/02/25 17:22:42 Instead of an array, what do you think of using a
298 masks[0] = redMask;
299 masks[1] = greenMask;
300 masks[2] = blueMask;
301 masks[3] = alphaMask;
302
303
304 // Check that the input format has been discovered
305 if (kUnknown_BitmapInputFormat == inputFormat) {
306 SkDebugf("Error: unknown bitmap input format.\n");
307 return NULL;
scroggo 2015/02/25 17:22:42 This will leak masks. If you used SkAutoTDeleteArr
308 }
309
310 // Verify the number of colors for the color table
311 if (bitsPerPixel < 16) {
312 int maxColors = 1 << bitsPerPixel;
scroggo 2015/02/25 17:22:42 nit: could be const.
313 // Zero is a default for maxColors
314 // Also set numColors to maxColors when input is too large
315 if (numColors <= 0 || numColors > maxColors) {
316 numColors = maxColors;
317 }
318 }
319
320 // Construct the color table
321 // Note that if bPP >= 16, there still may be a color table.
322 // In this case, the decoder does not index into it, instead it stores a
323 // list of colors intended for optimization.
scroggo 2015/02/25 17:22:41 What does this mean?
324 uint32_t colorBytes = numColors * bytesPerColor;
325 SkPMColor* colorTable = new SkPMColor[numColors];
326 if (numColors > 0) {
327 SkAutoTDeleteArray<uint8_t> cBuffer(new uint8_t[colorBytes]);
328 if (stream->read(cBuffer.get(), colorBytes) != colorBytes) {
329 SkDebugf("Error: unable to read color table.\n");
330 return NULL;
331 }
332 // We must respect the alpha channel for V4 and V5. However, if it is
333 // all zeros, we will display the image as opaque rather than
334 // transparent. This may require redoing some of the processing.
335 bool seenNonZeroAlpha = false;
336 for (uint32_t i = 0; i < numColors; i++) {
337 uint8_t blue = get_byte(cBuffer.get(), i*bytesPerColor);
338 uint8_t green = get_byte(cBuffer.get(), i*bytesPerColor + 1);
339 uint8_t red = get_byte(cBuffer.get(), i*bytesPerColor + 2);
340 uint8_t alpha = 0xFF;
341 if (headerType == kInfoV4_BitmapHeaderType ||
342 headerType == kInfoV5_BitmapHeaderType) {
343 alpha = (alphaMask >> 24) &
344 get_byte(cBuffer.get(), i*bytesPerColor + 3);
345 if (!alpha && !seenNonZeroAlpha) {
346 alpha = 0xFF;
347 } else {
348 // If we see a non-zero alpha, we restart the loop
349 seenNonZeroAlpha = true;
350 i = -1;
351 }
352 }
353 colorTable[i] = SkPreMultiplyColor(SkColorSetARGBInline(alpha,
354 red, green, blue));
355 }
356 cBuffer.free();
scroggo 2015/02/25 17:22:41 Again, this is unnecessary.
357 }
358
359 // Ensure that the stream now points to the start of the pixel array
360 uint32_t totalBytes = kBmpHeaderBytes + infoBytes + maskBytes +
361 numColors * bytesPerColor;
362 if (stream->skip(offset - totalBytes) != offset - totalBytes) {
363 SkDebugf("Error: unable to skip to image data.\n");
364 return NULL;
365 }
366 // Return the codec
367 // Use of image info for input format does not make sense given
368 // that the possible bitmap input formats do not match up with
369 // Skia color types. Instead we use ImageInfo for width and height,
370 // and other fields for input format information.
371 const SkImageInfo& imageInfo = SkImageInfo::Make(width, height,
372 kN32_SkColorType, kPremul_SkAlphaType);
373 return SkNEW_ARGS(SkBmpCodec, (imageInfo, stream, bitsPerPixel,
374 inputFormat, masks, colorTable, inverted));
375 }
376
377 /*
378 *
379 * Creates an instance of the decoder
380 * Called only by NewFromStream
381 *
382 */
383 SkBmpCodec::SkBmpCodec(const SkImageInfo& info, SkStream* stream,
384 const uint16_t bitsPerPixel,
385 const BitmapInputFormat inputFormat,
386 uint32_t* masks,
387 SkPMColor* colorTable,
388 bool inverted)
389 : INHERITED(info, stream)
390 , fBitsPerPixel(bitsPerPixel)
391 , fInputFormat(inputFormat)
392 , fBitMasks(masks)
393 , fColorTable(colorTable)
394 , fInverted(inverted)
395 {}
396
397 /*
398 *
399 * Clean up memory used by the decoder
400 * Currently, we are using autodelete types and there is no work to be done
401 *
402 */
403 SkBmpCodec::~SkBmpCodec() {}
scroggo 2015/02/25 17:22:42 This may just be a matter of preference, but I fee
404
405 /*
406 *
407 * Initiates the bitmap decode
408 *
409 */
410 SkCodec::Result SkBmpCodec::onGetPixels(const SkImageInfo& dstInfo,
411 void* dst, size_t dstRowBytes,
412 SkPMColor*, int*) {
413 // This version of the decoder does not support scaling
414 if (dstInfo.dimensions() != getOriginalInfo().dimensions()) {
415 SkDebugf("Error: scaling not supported.\n");
416 return kInvalidScale;
417 }
418
419 switch (fInputFormat) {
420 case k4BitRLE_BitmapInputFormat:
421 case k8BitRLE_BitmapInputFormat:
422 // TODO: Support RLE decoding
423 SkDebugf("RLE decoding not supported yet.\n");
424 return kUnimplemented;
425 case kBitMask_BitmapInputFormat:
426 case kStandard_BitmapInputFormat:
427 return decode(dstInfo, dst, dstRowBytes);
428 default:
429 SkDebugf("Error: unknown bitmap input format.\n");
430 return kInvalidInput;
431 }
432 }
433
434 /*
435 *
436 * Performs the bitmap decoding for standard and bit masks input format
437 *
438 */
439 SkCodec::Result SkBmpCodec::decode(const SkImageInfo& dstInfo,
440 void* dst, uint32_t dstRowBytes) {
441 // Set constant values
442 const int width = dstInfo.width();
443 const int height = dstInfo.height();
444 const uint32_t pixelsPerByte = 8 / fBitsPerPixel;
445 const uint32_t bytesPerPixel = fBitsPerPixel / 8;
446 const uint32_t unpaddedRowBytes = fBitsPerPixel < 16 ?
447 (width + pixelsPerByte - 1) / pixelsPerByte : width * bytesPerPixel;
448 const uint32_t paddedRowBytes = (unpaddedRowBytes + 3) & (~3);
449 const uint32_t alphaMask = fBitMasks.get()[3];
450
451 // Get swizzler configuration
452 SkSwizzler::SrcConfig config;
453 switch (fBitsPerPixel) {
454 case 1:
455 config = SkSwizzler::kIndex1;
456 break;
457 case 2:
458 config = SkSwizzler::kIndex2;
459 break;
460 case 4:
461 config = SkSwizzler::kIndex4;
462 break;
463 case 8:
464 config = SkSwizzler::kIndex8;
465 break;
466 case 16:
467 config = SkSwizzler::kMask16;
468 break;
469 case 24:
470 if (kBitMask_BitmapInputFormat == fInputFormat) {
471 config = SkSwizzler::kMask24;
472 } else {
473 config = SkSwizzler::kBGR;
474 }
475 break;
476 case 32:
477 if (kBitMask_BitmapInputFormat == fInputFormat) {
478 config = SkSwizzler::kMask32;
479 } else if (!alphaMask) {
480 config = SkSwizzler::kBGRX;
481 } else {
482 config = SkSwizzler::kBGRA;
483 }
484 break;
485 default:
486 SkDebugf("Error: invalid number of bits per pixel.\n");
scroggo 2015/02/25 17:22:42 We already knew this when creating the SkBmpCodec.
487 return kInvalidInput;
488 }
489
490 // If fixAlpha is false, it indicates that the image will be considered
491 // opaque. If fixAlpha is true, we will respect the value of the alpha
492 // channel if it is nonzero for any of the pixels. However, if it is
493 // always zero, we will consider the image opaque instead of transparent.
494 // This may require redoing some of the decoding.
495 bool fixAlpha = false;
496 if (alphaMask) {
497 fixAlpha = true;
498 }
499
500 // Create swizzler
501 SkSwizzler* swizzler = SkSwizzler::CreateSwizzler(config, fColorTable.get(),
502 dstInfo, dst, dstRowBytes, false, fBitMasks.get(), fixAlpha,
503 fInverted);
504
505 // Allocate space for a row buffer and a source for the swizzler
506 uint8_t* srcBuffer = new uint8_t[paddedRowBytes];
scroggo 2015/02/25 17:22:42 Use some form of auto deleter to avoid the memory
507
508 // Iterate over rows of the image
509 for (uint32_t row = 0; row < height; row++) {
510 // Read a row of the input
511 if (fStream->read(srcBuffer, paddedRowBytes) != paddedRowBytes) {
512 return kIncompleteInput;
513 }
514
515 // Decode the row in destination format
516 swizzler->next(srcBuffer);
517 }
518
519 // Clean up memory
520 delete [] srcBuffer;
521
522 // Finished decoding the entire image
523 return kSuccess;
524 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698