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