blob: 77d383af97ec52212733b7e57d91a9c93dbe5cd0 [file] [log] [blame]
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001
2/*
3 * Copyright 2007 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10#include "SkImageDecoder.h"
11#include "SkImageEncoder.h"
12#include "SkJpegUtility.h"
13#include "SkColorPriv.h"
14#include "SkDither.h"
15#include "SkScaledBitmapSampler.h"
16#include "SkStream.h"
17#include "SkTemplates.h"
18#include "SkUtils.h"
19
20#include <stdio.h>
21extern "C" {
22 #include "jpeglib.h"
23 #include "jerror.h"
24}
25
26#ifdef SK_BUILD_FOR_ANDROID
27#include <cutils/properties.h>
28
29// Key to lookup the size of memory buffer set in system property
30static const char KEY_MEM_CAP[] = "ro.media.dec.jpeg.memcap";
31#endif
32
33// this enables timing code to report milliseconds for an encode
34//#define TIME_ENCODE
35//#define TIME_DECODE
36
37// this enables our rgb->yuv code, which is faster than libjpeg on ARM
38// disable for the moment, as we have some glitches when width != multiple of 4
39#define WE_CONVERT_TO_YUV
40
41//////////////////////////////////////////////////////////////////////////
42//////////////////////////////////////////////////////////////////////////
43
44class SkJPEGImageDecoder : public SkImageDecoder {
45public:
46 virtual Format getFormat() const {
47 return kJPEG_Format;
48 }
49
50protected:
51 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
52};
53
54//////////////////////////////////////////////////////////////////////////
55
56#include "SkTime.h"
57
58class AutoTimeMillis {
59public:
60 AutoTimeMillis(const char label[]) : fLabel(label) {
61 if (!fLabel) {
62 fLabel = "";
63 }
64 fNow = SkTime::GetMSecs();
65 }
66 ~AutoTimeMillis() {
67 SkDebugf("---- Time (ms): %s %d\n", fLabel, SkTime::GetMSecs() - fNow);
68 }
69private:
70 const char* fLabel;
71 SkMSec fNow;
72};
73
74/* Automatically clean up after throwing an exception */
75class JPEGAutoClean {
76public:
77 JPEGAutoClean(): cinfo_ptr(NULL) {}
78 ~JPEGAutoClean() {
79 if (cinfo_ptr) {
80 jpeg_destroy_decompress(cinfo_ptr);
81 }
82 }
83 void set(jpeg_decompress_struct* info) {
84 cinfo_ptr = info;
85 }
86private:
87 jpeg_decompress_struct* cinfo_ptr;
88};
89
90#ifdef SK_BUILD_FOR_ANDROID
91/* Check if the memory cap property is set.
92 If so, use the memory size for jpeg decode.
93*/
94static void overwrite_mem_buffer_size(j_decompress_ptr cinfo) {
95#ifdef ANDROID_LARGE_MEMORY_DEVICE
96 cinfo->mem->max_memory_to_use = 30 * 1024 * 1024;
97#else
98 cinfo->mem->max_memory_to_use = 5 * 1024 * 1024;
99#endif
100}
101#endif
102
103
104///////////////////////////////////////////////////////////////////////////////
105
106/* If we need to better match the request, we might examine the image and
107 output dimensions, and determine if the downsampling jpeg provided is
108 not sufficient. If so, we can recompute a modified sampleSize value to
109 make up the difference.
110
111 To skip this additional scaling, just set sampleSize = 1; below.
112 */
113static int recompute_sampleSize(int sampleSize,
114 const jpeg_decompress_struct& cinfo) {
115 return sampleSize * cinfo.output_width / cinfo.image_width;
116}
117
118static bool valid_output_dimensions(const jpeg_decompress_struct& cinfo) {
119 /* These are initialized to 0, so if they have non-zero values, we assume
120 they are "valid" (i.e. have been computed by libjpeg)
121 */
122 return cinfo.output_width != 0 && cinfo.output_height != 0;
123}
124
125static bool skip_src_rows(jpeg_decompress_struct* cinfo, void* buffer,
126 int count) {
127 for (int i = 0; i < count; i++) {
128 JSAMPLE* rowptr = (JSAMPLE*)buffer;
129 int row_count = jpeg_read_scanlines(cinfo, &rowptr, 1);
130 if (row_count != 1) {
131 return false;
132 }
133 }
134 return true;
135}
136
137// This guy exists just to aid in debugging, as it allows debuggers to just
138// set a break-point in one place to see all error exists.
139static bool return_false(const jpeg_decompress_struct& cinfo,
140 const SkBitmap& bm, const char msg[]) {
141#if 0
142 SkDebugf("libjpeg error %d <%s> from %s [%d %d]", cinfo.err->msg_code,
143 cinfo.err->jpeg_message_table[cinfo.err->msg_code], msg,
144 bm.width(), bm.height());
145#endif
146 return false; // must always return false
147}
148
149bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
150#ifdef TIME_DECODE
151 AutoTimeMillis atm("JPEG Decode");
152#endif
153
154 SkAutoMalloc srcStorage;
155 JPEGAutoClean autoClean;
156
157 jpeg_decompress_struct cinfo;
158 skjpeg_error_mgr sk_err;
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000159 skjpeg_source_mgr sk_stream(stream, this);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000160
161 cinfo.err = jpeg_std_error(&sk_err);
162 sk_err.error_exit = skjpeg_error_exit;
163
164 // All objects need to be instantiated before this setjmp call so that
165 // they will be cleaned up properly if an error occurs.
166 if (setjmp(sk_err.fJmpBuf)) {
167 return return_false(cinfo, *bm, "setjmp");
168 }
169
170 jpeg_create_decompress(&cinfo);
171 autoClean.set(&cinfo);
172
173#ifdef SK_BUILD_FOR_ANDROID
174 overwrite_mem_buffer_size(&cinfo);
175#endif
176
177 //jpeg_stdio_src(&cinfo, file);
178 cinfo.src = &sk_stream;
179
180 int status = jpeg_read_header(&cinfo, true);
181 if (status != JPEG_HEADER_OK) {
182 return return_false(cinfo, *bm, "read_header");
183 }
184
185 /* Try to fulfill the requested sampleSize. Since jpeg can do it (when it
186 can) much faster that we, just use their num/denom api to approximate
187 the size.
188 */
189 int sampleSize = this->getSampleSize();
190
191 cinfo.dct_method = JDCT_IFAST;
192 cinfo.scale_num = 1;
193 cinfo.scale_denom = sampleSize;
194
195 /* this gives about 30% performance improvement. In theory it may
196 reduce the visual quality, in practice I'm not seeing a difference
197 */
198 cinfo.do_fancy_upsampling = 0;
199
200 /* this gives another few percents */
201 cinfo.do_block_smoothing = 0;
202
203 /* default format is RGB */
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000204 cinfo.out_color_space = JCS_RGB;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000205
206 SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, false);
207 // only these make sense for jpegs
208 if (config != SkBitmap::kARGB_8888_Config &&
209 config != SkBitmap::kARGB_4444_Config &&
210 config != SkBitmap::kRGB_565_Config) {
211 config = SkBitmap::kARGB_8888_Config;
212 }
213
214#ifdef ANDROID_RGB
215 cinfo.dither_mode = JDITHER_NONE;
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000216 if (config == SkBitmap::kARGB_8888_Config) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000217 cinfo.out_color_space = JCS_RGBA_8888;
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000218 } else if (config == SkBitmap::kRGB_565_Config) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000219 cinfo.out_color_space = JCS_RGB_565;
220 if (this->getDitherImage()) {
221 cinfo.dither_mode = JDITHER_ORDERED;
222 }
223 }
224#endif
225
226 if (sampleSize == 1 && mode == SkImageDecoder::kDecodeBounds_Mode) {
227 bm->setConfig(config, cinfo.image_width, cinfo.image_height);
228 bm->setIsOpaque(true);
229 return true;
230 }
231
232 /* image_width and image_height are the original dimensions, available
233 after jpeg_read_header(). To see the scaled dimensions, we have to call
234 jpeg_start_decompress(), and then read output_width and output_height.
235 */
236 if (!jpeg_start_decompress(&cinfo)) {
237 /* If we failed here, we may still have enough information to return
238 to the caller if they just wanted (subsampled bounds). If sampleSize
239 was 1, then we would have already returned. Thus we just check if
240 we're in kDecodeBounds_Mode, and that we have valid output sizes.
241
242 One reason to fail here is that we have insufficient stream data
243 to complete the setup. However, output dimensions seem to get
244 computed very early, which is why this special check can pay off.
245 */
246 if (SkImageDecoder::kDecodeBounds_Mode == mode &&
247 valid_output_dimensions(cinfo)) {
248 SkScaledBitmapSampler smpl(cinfo.output_width, cinfo.output_height,
249 recompute_sampleSize(sampleSize, cinfo));
250 bm->setConfig(config, smpl.scaledWidth(), smpl.scaledHeight());
251 bm->setIsOpaque(true);
252 return true;
253 } else {
254 return return_false(cinfo, *bm, "start_decompress");
255 }
256 }
257 sampleSize = recompute_sampleSize(sampleSize, cinfo);
258
259 // should we allow the Chooser (if present) to pick a config for us???
260 if (!this->chooseFromOneChoice(config, cinfo.output_width,
261 cinfo.output_height)) {
262 return return_false(cinfo, *bm, "chooseFromOneChoice");
263 }
264
265#ifdef ANDROID_RGB
266 /* short-circuit the SkScaledBitmapSampler when possible, as this gives
267 a significant performance boost.
268 */
269 if (sampleSize == 1 &&
270 ((config == SkBitmap::kARGB_8888_Config &&
271 cinfo.out_color_space == JCS_RGBA_8888) ||
272 (config == SkBitmap::kRGB_565_Config &&
273 cinfo.out_color_space == JCS_RGB_565)))
274 {
275 bm->setConfig(config, cinfo.output_width, cinfo.output_height);
276 bm->setIsOpaque(true);
277 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
278 return true;
279 }
280 if (!this->allocPixelRef(bm, NULL)) {
281 return return_false(cinfo, *bm, "allocPixelRef");
282 }
283 SkAutoLockPixels alp(*bm);
284 JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels();
285 INT32 const bpr = bm->rowBytes();
286
287 while (cinfo.output_scanline < cinfo.output_height) {
288 int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
289 // if row_count == 0, then we didn't get a scanline, so abort.
290 // if we supported partial images, we might return true in this case
291 if (0 == row_count) {
292 return return_false(cinfo, *bm, "read_scanlines");
293 }
294 if (this->shouldCancelDecode()) {
295 return return_false(cinfo, *bm, "shouldCancelDecode");
296 }
297 rowptr += bpr;
298 }
299 jpeg_finish_decompress(&cinfo);
300 return true;
301 }
302#endif
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000303
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000304 // check for supported formats
305 SkScaledBitmapSampler::SrcConfig sc;
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000306 if (3 == cinfo.out_color_components && JCS_RGB == cinfo.out_color_space) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000307 sc = SkScaledBitmapSampler::kRGB;
308#ifdef ANDROID_RGB
309 } else if (JCS_RGBA_8888 == cinfo.out_color_space) {
310 sc = SkScaledBitmapSampler::kRGBX;
311 } else if (JCS_RGB_565 == cinfo.out_color_space) {
312 sc = SkScaledBitmapSampler::kRGB_565;
313#endif
314 } else if (1 == cinfo.out_color_components &&
315 JCS_GRAYSCALE == cinfo.out_color_space) {
316 sc = SkScaledBitmapSampler::kGray;
317 } else {
318 return return_false(cinfo, *bm, "jpeg colorspace");
319 }
320
321 SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height,
322 sampleSize);
323
324 bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000325 // jpegs are always opauqe (i.e. have no per-pixel alpha)
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000326 bm->setIsOpaque(true);
327
328 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
329 return true;
330 }
331 if (!this->allocPixelRef(bm, NULL)) {
332 return return_false(cinfo, *bm, "allocPixelRef");
333 }
334
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000335 SkAutoLockPixels alp(*bm);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000336 if (!sampler.begin(bm, sc, this->getDitherImage())) {
337 return return_false(cinfo, *bm, "sampler.begin");
338 }
339
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000340 uint8_t* srcRow = (uint8_t*)srcStorage.alloc(cinfo.output_width * 4);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000341
342 // Possibly skip initial rows [sampler.srcY0]
343 if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) {
344 return return_false(cinfo, *bm, "skip rows");
345 }
346
347 // now loop through scanlines until y == bm->height() - 1
348 for (int y = 0;; y++) {
349 JSAMPLE* rowptr = (JSAMPLE*)srcRow;
350 int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
351 if (0 == row_count) {
352 return return_false(cinfo, *bm, "read_scanlines");
353 }
354 if (this->shouldCancelDecode()) {
355 return return_false(cinfo, *bm, "shouldCancelDecode");
356 }
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000357
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000358 sampler.next(srcRow);
359 if (bm->height() - 1 == y) {
360 // we're done
361 break;
362 }
363
364 if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) {
365 return return_false(cinfo, *bm, "skip rows");
366 }
367 }
368
369 // we formally skip the rest, so we don't get a complaint from libjpeg
370 if (!skip_src_rows(&cinfo, srcRow,
371 cinfo.output_height - cinfo.output_scanline)) {
372 return return_false(cinfo, *bm, "skip rows");
373 }
374 jpeg_finish_decompress(&cinfo);
375
376// SkDebugf("------------------- bm2 size %d [%d %d] %d\n", bm->getSize(), bm->width(), bm->height(), bm->config());
377 return true;
378}
379
380///////////////////////////////////////////////////////////////////////////////
381
382#include "SkColorPriv.h"
383
384// taken from jcolor.c in libjpeg
385#if 0 // 16bit - precise but slow
386 #define CYR 19595 // 0.299
387 #define CYG 38470 // 0.587
388 #define CYB 7471 // 0.114
389
390 #define CUR -11059 // -0.16874
391 #define CUG -21709 // -0.33126
392 #define CUB 32768 // 0.5
393
394 #define CVR 32768 // 0.5
395 #define CVG -27439 // -0.41869
396 #define CVB -5329 // -0.08131
397
398 #define CSHIFT 16
399#else // 8bit - fast, slightly less precise
400 #define CYR 77 // 0.299
401 #define CYG 150 // 0.587
402 #define CYB 29 // 0.114
403
404 #define CUR -43 // -0.16874
405 #define CUG -85 // -0.33126
406 #define CUB 128 // 0.5
407
408 #define CVR 128 // 0.5
409 #define CVG -107 // -0.41869
410 #define CVB -21 // -0.08131
411
412 #define CSHIFT 8
413#endif
414
415static void rgb2yuv_32(uint8_t dst[], SkPMColor c) {
416 int r = SkGetPackedR32(c);
417 int g = SkGetPackedG32(c);
418 int b = SkGetPackedB32(c);
419
420 int y = ( CYR*r + CYG*g + CYB*b ) >> CSHIFT;
421 int u = ( CUR*r + CUG*g + CUB*b ) >> CSHIFT;
422 int v = ( CVR*r + CVG*g + CVB*b ) >> CSHIFT;
423
424 dst[0] = SkToU8(y);
425 dst[1] = SkToU8(u + 128);
426 dst[2] = SkToU8(v + 128);
427}
428
429static void rgb2yuv_4444(uint8_t dst[], U16CPU c) {
430 int r = SkGetPackedR4444(c);
431 int g = SkGetPackedG4444(c);
432 int b = SkGetPackedB4444(c);
433
434 int y = ( CYR*r + CYG*g + CYB*b ) >> (CSHIFT - 4);
435 int u = ( CUR*r + CUG*g + CUB*b ) >> (CSHIFT - 4);
436 int v = ( CVR*r + CVG*g + CVB*b ) >> (CSHIFT - 4);
437
438 dst[0] = SkToU8(y);
439 dst[1] = SkToU8(u + 128);
440 dst[2] = SkToU8(v + 128);
441}
442
443static void rgb2yuv_16(uint8_t dst[], U16CPU c) {
444 int r = SkGetPackedR16(c);
445 int g = SkGetPackedG16(c);
446 int b = SkGetPackedB16(c);
447
448 int y = ( 2*CYR*r + CYG*g + 2*CYB*b ) >> (CSHIFT - 2);
449 int u = ( 2*CUR*r + CUG*g + 2*CUB*b ) >> (CSHIFT - 2);
450 int v = ( 2*CVR*r + CVG*g + 2*CVB*b ) >> (CSHIFT - 2);
451
452 dst[0] = SkToU8(y);
453 dst[1] = SkToU8(u + 128);
454 dst[2] = SkToU8(v + 128);
455}
456
457///////////////////////////////////////////////////////////////////////////////
458
459typedef void (*WriteScanline)(uint8_t* SK_RESTRICT dst,
460 const void* SK_RESTRICT src, int width,
461 const SkPMColor* SK_RESTRICT ctable);
462
463static void Write_32_YUV(uint8_t* SK_RESTRICT dst,
464 const void* SK_RESTRICT srcRow, int width,
465 const SkPMColor*) {
466 const uint32_t* SK_RESTRICT src = (const uint32_t*)srcRow;
467 while (--width >= 0) {
468#ifdef WE_CONVERT_TO_YUV
469 rgb2yuv_32(dst, *src++);
470#else
471 uint32_t c = *src++;
472 dst[0] = SkGetPackedR32(c);
473 dst[1] = SkGetPackedG32(c);
474 dst[2] = SkGetPackedB32(c);
475#endif
476 dst += 3;
477 }
478}
479
480static void Write_4444_YUV(uint8_t* SK_RESTRICT dst,
481 const void* SK_RESTRICT srcRow, int width,
482 const SkPMColor*) {
483 const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)srcRow;
484 while (--width >= 0) {
485#ifdef WE_CONVERT_TO_YUV
486 rgb2yuv_4444(dst, *src++);
487#else
488 SkPMColor16 c = *src++;
489 dst[0] = SkPacked4444ToR32(c);
490 dst[1] = SkPacked4444ToG32(c);
491 dst[2] = SkPacked4444ToB32(c);
492#endif
493 dst += 3;
494 }
495}
496
497static void Write_16_YUV(uint8_t* SK_RESTRICT dst,
498 const void* SK_RESTRICT srcRow, int width,
499 const SkPMColor*) {
500 const uint16_t* SK_RESTRICT src = (const uint16_t*)srcRow;
501 while (--width >= 0) {
502#ifdef WE_CONVERT_TO_YUV
503 rgb2yuv_16(dst, *src++);
504#else
505 uint16_t c = *src++;
506 dst[0] = SkPacked16ToR32(c);
507 dst[1] = SkPacked16ToG32(c);
508 dst[2] = SkPacked16ToB32(c);
509#endif
510 dst += 3;
511 }
512}
513
514static void Write_Index_YUV(uint8_t* SK_RESTRICT dst,
515 const void* SK_RESTRICT srcRow, int width,
516 const SkPMColor* SK_RESTRICT ctable) {
517 const uint8_t* SK_RESTRICT src = (const uint8_t*)srcRow;
518 while (--width >= 0) {
519#ifdef WE_CONVERT_TO_YUV
520 rgb2yuv_32(dst, ctable[*src++]);
521#else
522 uint32_t c = ctable[*src++];
523 dst[0] = SkGetPackedR32(c);
524 dst[1] = SkGetPackedG32(c);
525 dst[2] = SkGetPackedB32(c);
526#endif
527 dst += 3;
528 }
529}
530
531static WriteScanline ChooseWriter(const SkBitmap& bm) {
532 switch (bm.config()) {
533 case SkBitmap::kARGB_8888_Config:
534 return Write_32_YUV;
535 case SkBitmap::kRGB_565_Config:
536 return Write_16_YUV;
537 case SkBitmap::kARGB_4444_Config:
538 return Write_4444_YUV;
539 case SkBitmap::kIndex8_Config:
540 return Write_Index_YUV;
541 default:
542 return NULL;
543 }
544}
545
546class SkJPEGImageEncoder : public SkImageEncoder {
547protected:
548 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) {
549#ifdef TIME_ENCODE
550 AutoTimeMillis atm("JPEG Encode");
551#endif
552
553 const WriteScanline writer = ChooseWriter(bm);
554 if (NULL == writer) {
555 return false;
556 }
557
558 SkAutoLockPixels alp(bm);
559 if (NULL == bm.getPixels()) {
560 return false;
561 }
562
563 jpeg_compress_struct cinfo;
564 skjpeg_error_mgr sk_err;
565 skjpeg_destination_mgr sk_wstream(stream);
566
567 // allocate these before set call setjmp
568 SkAutoMalloc oneRow;
569 SkAutoLockColors ctLocker;
570
571 cinfo.err = jpeg_std_error(&sk_err);
572 sk_err.error_exit = skjpeg_error_exit;
573 if (setjmp(sk_err.fJmpBuf)) {
574 return false;
575 }
576 jpeg_create_compress(&cinfo);
577
578 cinfo.dest = &sk_wstream;
579 cinfo.image_width = bm.width();
580 cinfo.image_height = bm.height();
581 cinfo.input_components = 3;
582#ifdef WE_CONVERT_TO_YUV
583 cinfo.in_color_space = JCS_YCbCr;
584#else
585 cinfo.in_color_space = JCS_RGB;
586#endif
587 cinfo.input_gamma = 1;
588
589 jpeg_set_defaults(&cinfo);
590 jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
591 cinfo.dct_method = JDCT_IFAST;
592
593 jpeg_start_compress(&cinfo, TRUE);
594
595 const int width = bm.width();
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000596 uint8_t* oneRowP = (uint8_t*)oneRow.alloc(width * 3);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000597
598 const SkPMColor* colors = ctLocker.lockColors(bm);
599 const void* srcRow = bm.getPixels();
600
601 while (cinfo.next_scanline < cinfo.image_height) {
602 JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
603
604 writer(oneRowP, srcRow, width, colors);
605 row_pointer[0] = oneRowP;
606 (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
607 srcRow = (const void*)((const char*)srcRow + bm.rowBytes());
608 }
609
610 jpeg_finish_compress(&cinfo);
611 jpeg_destroy_compress(&cinfo);
612
613 return true;
614 }
615};
616
617///////////////////////////////////////////////////////////////////////////////
618
619#include "SkTRegistry.h"
620
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000621static SkImageDecoder* DFactory(SkStream* stream) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000622 static const char gHeader[] = { 0xFF, 0xD8, 0xFF };
623 static const size_t HEADER_SIZE = sizeof(gHeader);
624
625 char buffer[HEADER_SIZE];
626 size_t len = stream->read(buffer, HEADER_SIZE);
627
628 if (len != HEADER_SIZE) {
629 return NULL; // can't read enough
630 }
631 if (memcmp(buffer, gHeader, HEADER_SIZE)) {
632 return NULL;
633 }
634 return SkNEW(SkJPEGImageDecoder);
635}
636
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000637static SkImageEncoder* EFactory(SkImageEncoder::Type t) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000638 return (SkImageEncoder::kJPEG_Type == t) ? SkNEW(SkJPEGImageEncoder) : NULL;
639}
640
robertphillips@google.com0a89c902012-03-20 15:13:04 +0000641static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(DFactory);
642static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(EFactory);