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