blob: d4047f197356a15b09bb02cd2a36089caf0d6df8 [file] [log] [blame]
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001/*
2 * Copyright 2007 The Android Open Source Project
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
9#include "SkImageDecoder.h"
10#include "SkImageEncoder.h"
11#include "SkJpegUtility.h"
12#include "SkColorPriv.h"
13#include "SkDither.h"
14#include "SkScaledBitmapSampler.h"
15#include "SkStream.h"
16#include "SkTemplates.h"
djsollen@google.com11399402013-03-20 17:45:27 +000017#include "SkTime.h"
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000018#include "SkUtils.h"
djsollen@google.com11399402013-03-20 17:45:27 +000019#include "SkRect.h"
20#include "SkCanvas.h"
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000021
22#include <stdio.h>
23extern "C" {
24 #include "jpeglib.h"
25 #include "jerror.h"
26}
27
djsollen@google.com11399402013-03-20 17:45:27 +000028// These enable timing code that report milliseconds for an encoding/decoding
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000029//#define TIME_ENCODE
30//#define TIME_DECODE
31
32// this enables our rgb->yuv code, which is faster than libjpeg on ARM
33// disable for the moment, as we have some glitches when width != multiple of 4
34#define WE_CONVERT_TO_YUV
35
djsollen@google.com11399402013-03-20 17:45:27 +000036// If ANDROID_RGB is defined by in the jpeg headers it indicates that jpeg offers
37// support for two additional formats (1) JCS_RGBA_8888 and (2) JCS_RGB_565.
38
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000039//////////////////////////////////////////////////////////////////////////
40//////////////////////////////////////////////////////////////////////////
41
djsollen@google.com11399402013-03-20 17:45:27 +000042static void overwrite_mem_buffer_size(j_decompress_ptr cinfo) {
43#ifdef SK_BUILD_FOR_ANDROID
44 /* Check if the device indicates that it has a large amount of system memory
45 * if so, increase the memory allocation to 30MB instead of the default 5MB.
46 */
47#ifdef ANDROID_LARGE_MEMORY_DEVICE
48 cinfo->mem->max_memory_to_use = 30 * 1024 * 1024;
49#else
50 cinfo->mem->max_memory_to_use = 5 * 1024 * 1024;
51#endif
52#endif // SK_BUILD_FOR_ANDROID
53}
54
55//////////////////////////////////////////////////////////////////////////
56//////////////////////////////////////////////////////////////////////////
57
58class SkJPEGImageIndex {
59public:
60 SkJPEGImageIndex(SkStream* stream, SkImageDecoder* decoder)
scroggo@google.comd4c35652013-08-01 15:03:42 +000061 : fSrcMgr(stream, decoder) {}
djsollen@google.com11399402013-03-20 17:45:27 +000062
63 ~SkJPEGImageIndex() {
scroggo@google.comd79277f2013-08-07 19:53:53 +000064#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.com11399402013-03-20 17:45:27 +000065 jpeg_destroy_huffman_index(&fHuffmanIndex);
66#endif
67 jpeg_finish_decompress(&fCInfo);
68 jpeg_destroy_decompress(&fCInfo);
69 }
70
71 /**
72 * Init the cinfo struct using libjpeg and apply any necessary
73 * customizations.
74 */
75 void initializeInfo() {
76 jpeg_create_decompress(&fCInfo);
77 overwrite_mem_buffer_size(&fCInfo);
78 fCInfo.src = &fSrcMgr;
79 }
80
81 jpeg_decompress_struct* cinfo() { return &fCInfo; }
82
scroggo@google.comd79277f2013-08-07 19:53:53 +000083#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.com11399402013-03-20 17:45:27 +000084 huffman_index* huffmanIndex() { return &fHuffmanIndex; }
85#endif
86
87private:
88 skjpeg_source_mgr fSrcMgr;
89 jpeg_decompress_struct fCInfo;
scroggo@google.comd79277f2013-08-07 19:53:53 +000090#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.com11399402013-03-20 17:45:27 +000091 huffman_index fHuffmanIndex;
92#endif
93};
94
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000095class SkJPEGImageDecoder : public SkImageDecoder {
96public:
djsollen@google.com11399402013-03-20 17:45:27 +000097 SkJPEGImageDecoder() {
98 fImageIndex = NULL;
99 fImageWidth = 0;
100 fImageHeight = 0;
101 }
102
103 virtual ~SkJPEGImageDecoder() {
104 SkDELETE(fImageIndex);
105 }
106
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000107 virtual Format getFormat() const {
108 return kJPEG_Format;
109 }
110
111protected:
scroggo@google.comd79277f2013-08-07 19:53:53 +0000112#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.com11399402013-03-20 17:45:27 +0000113 virtual bool onBuildTileIndex(SkStream *stream, int *width, int *height) SK_OVERRIDE;
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000114 virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& rect) SK_OVERRIDE;
djsollen@google.com11399402013-03-20 17:45:27 +0000115#endif
116 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
117
118private:
119 SkJPEGImageIndex* fImageIndex;
120 int fImageWidth;
121 int fImageHeight;
122
123 typedef SkImageDecoder INHERITED;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000124};
125
126//////////////////////////////////////////////////////////////////////////
127
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000128/* Automatically clean up after throwing an exception */
129class JPEGAutoClean {
130public:
131 JPEGAutoClean(): cinfo_ptr(NULL) {}
132 ~JPEGAutoClean() {
133 if (cinfo_ptr) {
134 jpeg_destroy_decompress(cinfo_ptr);
135 }
136 }
137 void set(jpeg_decompress_struct* info) {
138 cinfo_ptr = info;
139 }
140private:
141 jpeg_decompress_struct* cinfo_ptr;
142};
143
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000144///////////////////////////////////////////////////////////////////////////////
145
146/* If we need to better match the request, we might examine the image and
147 output dimensions, and determine if the downsampling jpeg provided is
148 not sufficient. If so, we can recompute a modified sampleSize value to
149 make up the difference.
150
151 To skip this additional scaling, just set sampleSize = 1; below.
152 */
153static int recompute_sampleSize(int sampleSize,
154 const jpeg_decompress_struct& cinfo) {
155 return sampleSize * cinfo.output_width / cinfo.image_width;
156}
157
158static bool valid_output_dimensions(const jpeg_decompress_struct& cinfo) {
159 /* These are initialized to 0, so if they have non-zero values, we assume
160 they are "valid" (i.e. have been computed by libjpeg)
161 */
djsollen@google.com11399402013-03-20 17:45:27 +0000162 return 0 != cinfo.output_width && 0 != cinfo.output_height;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000163}
164
djsollen@google.com11399402013-03-20 17:45:27 +0000165static bool skip_src_rows(jpeg_decompress_struct* cinfo, void* buffer, int count) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000166 for (int i = 0; i < count; i++) {
167 JSAMPLE* rowptr = (JSAMPLE*)buffer;
168 int row_count = jpeg_read_scanlines(cinfo, &rowptr, 1);
djsollen@google.com11399402013-03-20 17:45:27 +0000169 if (1 != row_count) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000170 return false;
171 }
172 }
173 return true;
174}
175
scroggo@google.comd79277f2013-08-07 19:53:53 +0000176#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.com11399402013-03-20 17:45:27 +0000177static bool skip_src_rows_tile(jpeg_decompress_struct* cinfo,
178 huffman_index *index, void* buffer, int count) {
179 for (int i = 0; i < count; i++) {
180 JSAMPLE* rowptr = (JSAMPLE*)buffer;
181 int row_count = jpeg_read_tile_scanline(cinfo, index, &rowptr);
182 if (1 != row_count) {
183 return false;
184 }
185 }
186 return true;
187}
188#endif
189
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000190// This guy exists just to aid in debugging, as it allows debuggers to just
191// set a break-point in one place to see all error exists.
192static bool return_false(const jpeg_decompress_struct& cinfo,
193 const SkBitmap& bm, const char msg[]) {
djsollen@google.com11399402013-03-20 17:45:27 +0000194#ifdef SK_DEBUG
scroggo@google.come8f34712013-07-01 14:44:54 +0000195 SkDebugf("libjpeg error %d <%s> from %s [%d %d]\n", cinfo.err->msg_code,
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000196 cinfo.err->jpeg_message_table[cinfo.err->msg_code], msg,
197 bm.width(), bm.height());
198#endif
199 return false; // must always return false
200}
201
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000202// Convert a scanline of CMYK samples to RGBX in place. Note that this
203// method moves the "scanline" pointer in its processing
204static void convert_CMYK_to_RGB(uint8_t* scanline, unsigned int width) {
205 // At this point we've received CMYK pixels from libjpeg. We
rmistry@google.comd6176b02012-08-23 18:14:13 +0000206 // perform a crude conversion to RGB (based on the formulae
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000207 // from easyrgb.com):
208 // CMYK -> CMY
209 // C = ( C * (1 - K) + K ) // for each CMY component
210 // CMY -> RGB
211 // R = ( 1 - C ) * 255 // for each RGB component
212 // Unfortunately we are seeing inverted CMYK so all the original terms
213 // are 1-. This yields:
214 // CMYK -> CMY
215 // C = ( (1-C) * (1 - (1-K) + (1-K) ) -> C = 1 - C*K
216 // The conversion from CMY->RGB remains the same
217 for (unsigned int x = 0; x < width; ++x, scanline += 4) {
218 scanline[0] = SkMulDiv255Round(scanline[0], scanline[3]);
219 scanline[1] = SkMulDiv255Round(scanline[1], scanline[3]);
220 scanline[2] = SkMulDiv255Round(scanline[2], scanline[3]);
221 scanline[3] = 255;
222 }
223}
224
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000225bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
226#ifdef TIME_DECODE
djsollen@google.com11399402013-03-20 17:45:27 +0000227 SkAutoTime atm("JPEG Decode");
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000228#endif
229
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000230 JPEGAutoClean autoClean;
231
232 jpeg_decompress_struct cinfo;
djsollen@google.com11399402013-03-20 17:45:27 +0000233 skjpeg_error_mgr errorManager;
scroggo@google.comd4c35652013-08-01 15:03:42 +0000234 skjpeg_source_mgr srcManager(stream, this);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000235
djsollen@google.com11399402013-03-20 17:45:27 +0000236 cinfo.err = jpeg_std_error(&errorManager);
237 errorManager.error_exit = skjpeg_error_exit;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000238
239 // All objects need to be instantiated before this setjmp call so that
240 // they will be cleaned up properly if an error occurs.
djsollen@google.com11399402013-03-20 17:45:27 +0000241 if (setjmp(errorManager.fJmpBuf)) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000242 return return_false(cinfo, *bm, "setjmp");
243 }
244
245 jpeg_create_decompress(&cinfo);
246 autoClean.set(&cinfo);
247
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000248 overwrite_mem_buffer_size(&cinfo);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000249
250 //jpeg_stdio_src(&cinfo, file);
djsollen@google.com11399402013-03-20 17:45:27 +0000251 cinfo.src = &srcManager;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000252
253 int status = jpeg_read_header(&cinfo, true);
254 if (status != JPEG_HEADER_OK) {
255 return return_false(cinfo, *bm, "read_header");
256 }
257
258 /* Try to fulfill the requested sampleSize. Since jpeg can do it (when it
259 can) much faster that we, just use their num/denom api to approximate
260 the size.
261 */
262 int sampleSize = this->getSampleSize();
263
scroggo@google.com3acd3fc2013-04-12 16:28:21 +0000264#ifdef DCT_IFAST_SUPPORTED
djsollen@google.com11399402013-03-20 17:45:27 +0000265 if (this->getPreferQualityOverSpeed()) {
266 cinfo.dct_method = JDCT_ISLOW;
267 } else {
268 cinfo.dct_method = JDCT_IFAST;
269 }
scroggo@google.com3acd3fc2013-04-12 16:28:21 +0000270#else
271 cinfo.dct_method = JDCT_ISLOW;
272#endif
djsollen@google.com11399402013-03-20 17:45:27 +0000273
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000274 cinfo.scale_num = 1;
275 cinfo.scale_denom = sampleSize;
276
277 /* this gives about 30% performance improvement. In theory it may
278 reduce the visual quality, in practice I'm not seeing a difference
279 */
280 cinfo.do_fancy_upsampling = 0;
281
282 /* this gives another few percents */
283 cinfo.do_block_smoothing = 0;
284
scroggo@google.comf698c822013-07-18 19:34:49 +0000285 SrcDepth srcDepth = k32Bit_SrcDepth;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000286 /* default format is RGB */
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000287 if (cinfo.jpeg_color_space == JCS_CMYK) {
288 // libjpeg cannot convert from CMYK to RGB - here we set up
289 // so libjpeg will give us CMYK samples back and we will
290 // later manually convert them to RGB
291 cinfo.out_color_space = JCS_CMYK;
scroggo@google.comf698c822013-07-18 19:34:49 +0000292 } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
293 cinfo.out_color_space = JCS_GRAYSCALE;
294 srcDepth = k8BitGray_SrcDepth;
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000295 } else {
296 cinfo.out_color_space = JCS_RGB;
297 }
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000298
scroggo@google.comf698c822013-07-18 19:34:49 +0000299 SkBitmap::Config config = this->getPrefConfig(srcDepth, false);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000300 // only these make sense for jpegs
scroggo@google.comf698c822013-07-18 19:34:49 +0000301 if (SkBitmap::kA8_Config == config) {
302 if (cinfo.jpeg_color_space != JCS_GRAYSCALE) {
303 // Converting from a non grayscale image to A8 is
304 // not currently supported.
305 config = SkBitmap::kARGB_8888_Config;
306 // Change the output from jpeg back to RGB.
307 cinfo.out_color_space = JCS_RGB;
308 }
309 } else if (config != SkBitmap::kARGB_8888_Config &&
310 config != SkBitmap::kARGB_4444_Config &&
311 config != SkBitmap::kRGB_565_Config) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000312 config = SkBitmap::kARGB_8888_Config;
313 }
314
315#ifdef ANDROID_RGB
316 cinfo.dither_mode = JDITHER_NONE;
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000317 if (SkBitmap::kARGB_8888_Config == config && JCS_CMYK != cinfo.out_color_space) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000318 cinfo.out_color_space = JCS_RGBA_8888;
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000319 } else if (SkBitmap::kRGB_565_Config == config && JCS_CMYK != cinfo.out_color_space) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000320 cinfo.out_color_space = JCS_RGB_565;
321 if (this->getDitherImage()) {
322 cinfo.dither_mode = JDITHER_ORDERED;
323 }
324 }
325#endif
326
djsollen@google.com11399402013-03-20 17:45:27 +0000327 if (1 == sampleSize && SkImageDecoder::kDecodeBounds_Mode == mode) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000328 bm->setConfig(config, cinfo.image_width, cinfo.image_height);
scroggo@google.com1f6b9952013-07-18 19:37:46 +0000329 bm->setIsOpaque(config != SkBitmap::kA8_Config);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000330 return true;
331 }
332
333 /* image_width and image_height are the original dimensions, available
334 after jpeg_read_header(). To see the scaled dimensions, we have to call
335 jpeg_start_decompress(), and then read output_width and output_height.
336 */
337 if (!jpeg_start_decompress(&cinfo)) {
338 /* If we failed here, we may still have enough information to return
339 to the caller if they just wanted (subsampled bounds). If sampleSize
340 was 1, then we would have already returned. Thus we just check if
341 we're in kDecodeBounds_Mode, and that we have valid output sizes.
342
343 One reason to fail here is that we have insufficient stream data
344 to complete the setup. However, output dimensions seem to get
345 computed very early, which is why this special check can pay off.
346 */
djsollen@google.com11399402013-03-20 17:45:27 +0000347 if (SkImageDecoder::kDecodeBounds_Mode == mode && valid_output_dimensions(cinfo)) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000348 SkScaledBitmapSampler smpl(cinfo.output_width, cinfo.output_height,
349 recompute_sampleSize(sampleSize, cinfo));
350 bm->setConfig(config, smpl.scaledWidth(), smpl.scaledHeight());
scroggo@google.com1f6b9952013-07-18 19:37:46 +0000351 bm->setIsOpaque(config != SkBitmap::kA8_Config);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000352 return true;
353 } else {
354 return return_false(cinfo, *bm, "start_decompress");
355 }
356 }
357 sampleSize = recompute_sampleSize(sampleSize, cinfo);
358
359 // should we allow the Chooser (if present) to pick a config for us???
djsollen@google.com11399402013-03-20 17:45:27 +0000360 if (!this->chooseFromOneChoice(config, cinfo.output_width, cinfo.output_height)) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000361 return return_false(cinfo, *bm, "chooseFromOneChoice");
362 }
363
djsollen@google.com11399402013-03-20 17:45:27 +0000364 SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height, sampleSize);
scroggo@google.combc69ce92013-07-09 15:45:14 +0000365 bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
scroggo@google.com1f6b9952013-07-18 19:37:46 +0000366 bm->setIsOpaque(config != SkBitmap::kA8_Config);
scroggo@google.combc69ce92013-07-09 15:45:14 +0000367 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
368 return true;
369 }
370 if (!this->allocPixelRef(bm, NULL)) {
371 return return_false(cinfo, *bm, "allocPixelRef");
djsollen@google.com11399402013-03-20 17:45:27 +0000372 }
373
374 SkAutoLockPixels alp(*bm);
375
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000376#ifdef ANDROID_RGB
377 /* short-circuit the SkScaledBitmapSampler when possible, as this gives
378 a significant performance boost.
379 */
380 if (sampleSize == 1 &&
rmistry@google.comd6176b02012-08-23 18:14:13 +0000381 ((config == SkBitmap::kARGB_8888_Config &&
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000382 cinfo.out_color_space == JCS_RGBA_8888) ||
rmistry@google.comd6176b02012-08-23 18:14:13 +0000383 (config == SkBitmap::kRGB_565_Config &&
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000384 cinfo.out_color_space == JCS_RGB_565)))
385 {
scroggo@google.combc69ce92013-07-09 15:45:14 +0000386 JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels();
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000387 INT32 const bpr = bm->rowBytes();
rmistry@google.comd6176b02012-08-23 18:14:13 +0000388
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000389 while (cinfo.output_scanline < cinfo.output_height) {
390 int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
391 // if row_count == 0, then we didn't get a scanline, so abort.
392 // if we supported partial images, we might return true in this case
393 if (0 == row_count) {
394 return return_false(cinfo, *bm, "read_scanlines");
395 }
396 if (this->shouldCancelDecode()) {
397 return return_false(cinfo, *bm, "shouldCancelDecode");
398 }
399 rowptr += bpr;
400 }
401 jpeg_finish_decompress(&cinfo);
402 return true;
403 }
404#endif
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000405
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000406 // check for supported formats
407 SkScaledBitmapSampler::SrcConfig sc;
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000408 if (JCS_CMYK == cinfo.out_color_space) {
409 // In this case we will manually convert the CMYK values to RGB
410 sc = SkScaledBitmapSampler::kRGBX;
411 } else if (3 == cinfo.out_color_components && JCS_RGB == cinfo.out_color_space) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000412 sc = SkScaledBitmapSampler::kRGB;
413#ifdef ANDROID_RGB
414 } else if (JCS_RGBA_8888 == cinfo.out_color_space) {
415 sc = SkScaledBitmapSampler::kRGBX;
416 } else if (JCS_RGB_565 == cinfo.out_color_space) {
417 sc = SkScaledBitmapSampler::kRGB_565;
418#endif
419 } else if (1 == cinfo.out_color_components &&
420 JCS_GRAYSCALE == cinfo.out_color_space) {
421 sc = SkScaledBitmapSampler::kGray;
422 } else {
423 return return_false(cinfo, *bm, "jpeg colorspace");
424 }
425
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000426 if (!sampler.begin(bm, sc, this->getDitherImage())) {
427 return return_false(cinfo, *bm, "sampler.begin");
428 }
429
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000430 // The CMYK work-around relies on 4 components per pixel here
djsollen@google.com11399402013-03-20 17:45:27 +0000431 SkAutoMalloc srcStorage(cinfo.output_width * 4);
432 uint8_t* srcRow = (uint8_t*)srcStorage.get();
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000433
434 // Possibly skip initial rows [sampler.srcY0]
435 if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) {
436 return return_false(cinfo, *bm, "skip rows");
437 }
438
439 // now loop through scanlines until y == bm->height() - 1
440 for (int y = 0;; y++) {
441 JSAMPLE* rowptr = (JSAMPLE*)srcRow;
442 int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
443 if (0 == row_count) {
444 return return_false(cinfo, *bm, "read_scanlines");
445 }
446 if (this->shouldCancelDecode()) {
447 return return_false(cinfo, *bm, "shouldCancelDecode");
448 }
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000449
450 if (JCS_CMYK == cinfo.out_color_space) {
451 convert_CMYK_to_RGB(srcRow, cinfo.output_width);
452 }
453
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000454 sampler.next(srcRow);
455 if (bm->height() - 1 == y) {
456 // we're done
457 break;
458 }
459
460 if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) {
461 return return_false(cinfo, *bm, "skip rows");
462 }
463 }
464
465 // we formally skip the rest, so we don't get a complaint from libjpeg
466 if (!skip_src_rows(&cinfo, srcRow,
467 cinfo.output_height - cinfo.output_scanline)) {
468 return return_false(cinfo, *bm, "skip rows");
469 }
470 jpeg_finish_decompress(&cinfo);
471
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000472 return true;
473}
474
scroggo@google.comd79277f2013-08-07 19:53:53 +0000475#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.com11399402013-03-20 17:45:27 +0000476bool SkJPEGImageDecoder::onBuildTileIndex(SkStream* stream, int *width, int *height) {
477
478 SkJPEGImageIndex* imageIndex = SkNEW_ARGS(SkJPEGImageIndex, (stream, this));
479 jpeg_decompress_struct* cinfo = imageIndex->cinfo();
480 huffman_index* huffmanIndex = imageIndex->huffmanIndex();
481
482 skjpeg_error_mgr sk_err;
483 cinfo->err = jpeg_std_error(&sk_err);
484 sk_err.error_exit = skjpeg_error_exit;
485
486 // All objects need to be instantiated before this setjmp call so that
487 // they will be cleaned up properly if an error occurs.
488 if (setjmp(sk_err.fJmpBuf)) {
489 return false;
490 }
491
492 // create the cinfo used to create/build the huffmanIndex
493 imageIndex->initializeInfo();
494 cinfo->do_fancy_upsampling = 0;
495 cinfo->do_block_smoothing = 0;
496
497 int status = jpeg_read_header(cinfo, true);
498 if (JPEG_HEADER_OK != status) {
499 SkDELETE(imageIndex);
500 return false;
501 }
502
503 jpeg_create_huffman_index(cinfo, huffmanIndex);
504 cinfo->scale_num = 1;
505 cinfo->scale_denom = 1;
506 if (!jpeg_build_huffman_index(cinfo, huffmanIndex)) {
507 SkDELETE(imageIndex);
508 return false;
509 }
510
511 // destroy the cinfo used to create/build the huffman index
512 jpeg_destroy_decompress(cinfo);
513
514 // Init decoder to image decode mode
515 imageIndex->initializeInfo();
516
517 status = jpeg_read_header(cinfo, true);
518 if (JPEG_HEADER_OK != status) {
519 SkDELETE(imageIndex);
520 return false;
521 }
522
523 cinfo->out_color_space = JCS_RGBA_8888;
524 cinfo->do_fancy_upsampling = 0;
525 cinfo->do_block_smoothing = 0;
526
527 // instead of jpeg_start_decompress() we start a tiled decompress
528 jpeg_start_tile_decompress(cinfo);
529
530 cinfo->scale_num = 1;
531 *height = cinfo->output_height;
532 *width = cinfo->output_width;
533 fImageWidth = *width;
534 fImageHeight = *height;
535
536 SkDELETE(fImageIndex);
537 fImageIndex = imageIndex;
538
539 return true;
540}
541
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000542bool SkJPEGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
djsollen@google.com11399402013-03-20 17:45:27 +0000543 if (NULL == fImageIndex) {
544 return false;
545 }
546 jpeg_decompress_struct* cinfo = fImageIndex->cinfo();
547
548 SkIRect rect = SkIRect::MakeWH(fImageWidth, fImageHeight);
549 if (!rect.intersect(region)) {
550 // If the requested region is entirely outside the image return false
551 return false;
552 }
553
554
555 skjpeg_error_mgr errorManager;
556 cinfo->err = jpeg_std_error(&errorManager);
557 errorManager.error_exit = skjpeg_error_exit;
558 if (setjmp(errorManager.fJmpBuf)) {
559 return false;
560 }
561
562 int requestedSampleSize = this->getSampleSize();
563 cinfo->scale_denom = requestedSampleSize;
564
565 if (this->getPreferQualityOverSpeed()) {
566 cinfo->dct_method = JDCT_ISLOW;
567 } else {
568 cinfo->dct_method = JDCT_IFAST;
569 }
570
571 SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, false);
572 if (config != SkBitmap::kARGB_8888_Config &&
573 config != SkBitmap::kARGB_4444_Config &&
574 config != SkBitmap::kRGB_565_Config) {
575 config = SkBitmap::kARGB_8888_Config;
576 }
577
578 /* default format is RGB */
579 cinfo->out_color_space = JCS_RGB;
580
581#ifdef ANDROID_RGB
582 cinfo->dither_mode = JDITHER_NONE;
583 if (SkBitmap::kARGB_8888_Config == config) {
584 cinfo->out_color_space = JCS_RGBA_8888;
585 } else if (SkBitmap::kRGB_565_Config == config) {
586 cinfo->out_color_space = JCS_RGB_565;
587 if (this->getDitherImage()) {
588 cinfo->dither_mode = JDITHER_ORDERED;
589 }
590 }
591#endif
592
593 int startX = rect.fLeft;
594 int startY = rect.fTop;
595 int width = rect.width();
596 int height = rect.height();
597
598 jpeg_init_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(),
599 &startX, &startY, &width, &height);
600 int skiaSampleSize = recompute_sampleSize(requestedSampleSize, *cinfo);
601 int actualSampleSize = skiaSampleSize * (DCTSIZE / cinfo->min_DCT_scaled_size);
602
603 SkScaledBitmapSampler sampler(width, height, skiaSampleSize);
604
605 SkBitmap bitmap;
606 bitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
607 bitmap.setIsOpaque(true);
608
609 // Check ahead of time if the swap(dest, src) is possible or not.
610 // If yes, then we will stick to AllocPixelRef since it's cheaper with the
611 // swap happening. If no, then we will use alloc to allocate pixels to
612 // prevent garbage collection.
613 int w = rect.width() / actualSampleSize;
614 int h = rect.height() / actualSampleSize;
615 bool swapOnly = (rect == region) && bm->isNull() &&
616 (w == bitmap.width()) && (h == bitmap.height()) &&
617 ((startX - rect.x()) / actualSampleSize == 0) &&
618 ((startY - rect.y()) / actualSampleSize == 0);
619 if (swapOnly) {
620 if (!this->allocPixelRef(&bitmap, NULL)) {
621 return return_false(*cinfo, bitmap, "allocPixelRef");
622 }
623 } else {
624 if (!bitmap.allocPixels()) {
625 return return_false(*cinfo, bitmap, "allocPixels");
626 }
627 }
628
629 SkAutoLockPixels alp(bitmap);
630
631#ifdef ANDROID_RGB
632 /* short-circuit the SkScaledBitmapSampler when possible, as this gives
633 a significant performance boost.
634 */
635 if (skiaSampleSize == 1 &&
636 ((config == SkBitmap::kARGB_8888_Config &&
637 cinfo->out_color_space == JCS_RGBA_8888) ||
638 (config == SkBitmap::kRGB_565_Config &&
639 cinfo->out_color_space == JCS_RGB_565)))
640 {
641 JSAMPLE* rowptr = (JSAMPLE*)bitmap.getPixels();
642 INT32 const bpr = bitmap.rowBytes();
643 int rowTotalCount = 0;
644
645 while (rowTotalCount < height) {
646 int rowCount = jpeg_read_tile_scanline(cinfo,
647 fImageIndex->huffmanIndex(),
648 &rowptr);
649 // if row_count == 0, then we didn't get a scanline, so abort.
650 // if we supported partial images, we might return true in this case
651 if (0 == rowCount) {
652 return return_false(*cinfo, bitmap, "read_scanlines");
653 }
654 if (this->shouldCancelDecode()) {
655 return return_false(*cinfo, bitmap, "shouldCancelDecode");
656 }
657 rowTotalCount += rowCount;
658 rowptr += bpr;
659 }
660
661 if (swapOnly) {
662 bm->swap(bitmap);
663 } else {
664 cropBitmap(bm, &bitmap, actualSampleSize, region.x(), region.y(),
665 region.width(), region.height(), startX, startY);
666 }
667 return true;
668 }
669#endif
670
671 // check for supported formats
672 SkScaledBitmapSampler::SrcConfig sc;
673 if (JCS_CMYK == cinfo->out_color_space) {
674 // In this case we will manually convert the CMYK values to RGB
675 sc = SkScaledBitmapSampler::kRGBX;
676 } else if (3 == cinfo->out_color_components && JCS_RGB == cinfo->out_color_space) {
677 sc = SkScaledBitmapSampler::kRGB;
678#ifdef ANDROID_RGB
679 } else if (JCS_RGBA_8888 == cinfo->out_color_space) {
680 sc = SkScaledBitmapSampler::kRGBX;
681 } else if (JCS_RGB_565 == cinfo->out_color_space) {
682 sc = SkScaledBitmapSampler::kRGB_565;
683#endif
684 } else if (1 == cinfo->out_color_components &&
685 JCS_GRAYSCALE == cinfo->out_color_space) {
686 sc = SkScaledBitmapSampler::kGray;
687 } else {
688 return return_false(*cinfo, *bm, "jpeg colorspace");
689 }
690
691 if (!sampler.begin(&bitmap, sc, this->getDitherImage())) {
692 return return_false(*cinfo, bitmap, "sampler.begin");
693 }
694
695 // The CMYK work-around relies on 4 components per pixel here
696 SkAutoMalloc srcStorage(width * 4);
697 uint8_t* srcRow = (uint8_t*)srcStorage.get();
698
699 // Possibly skip initial rows [sampler.srcY0]
700 if (!skip_src_rows_tile(cinfo, fImageIndex->huffmanIndex(), srcRow, sampler.srcY0())) {
701 return return_false(*cinfo, bitmap, "skip rows");
702 }
703
704 // now loop through scanlines until y == bitmap->height() - 1
705 for (int y = 0;; y++) {
706 JSAMPLE* rowptr = (JSAMPLE*)srcRow;
707 int row_count = jpeg_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(), &rowptr);
708 if (0 == row_count) {
709 return return_false(*cinfo, bitmap, "read_scanlines");
710 }
711 if (this->shouldCancelDecode()) {
712 return return_false(*cinfo, bitmap, "shouldCancelDecode");
713 }
714
715 if (JCS_CMYK == cinfo->out_color_space) {
716 convert_CMYK_to_RGB(srcRow, width);
717 }
718
719 sampler.next(srcRow);
720 if (bitmap.height() - 1 == y) {
721 // we're done
722 break;
723 }
724
725 if (!skip_src_rows_tile(cinfo, fImageIndex->huffmanIndex(), srcRow,
726 sampler.srcDY() - 1)) {
727 return return_false(*cinfo, bitmap, "skip rows");
728 }
729 }
730 if (swapOnly) {
731 bm->swap(bitmap);
732 } else {
733 cropBitmap(bm, &bitmap, actualSampleSize, region.x(), region.y(),
734 region.width(), region.height(), startX, startY);
735 }
736 return true;
737}
738#endif
739
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000740///////////////////////////////////////////////////////////////////////////////
741
742#include "SkColorPriv.h"
743
744// taken from jcolor.c in libjpeg
745#if 0 // 16bit - precise but slow
746 #define CYR 19595 // 0.299
747 #define CYG 38470 // 0.587
748 #define CYB 7471 // 0.114
749
750 #define CUR -11059 // -0.16874
751 #define CUG -21709 // -0.33126
752 #define CUB 32768 // 0.5
753
754 #define CVR 32768 // 0.5
755 #define CVG -27439 // -0.41869
756 #define CVB -5329 // -0.08131
757
758 #define CSHIFT 16
759#else // 8bit - fast, slightly less precise
760 #define CYR 77 // 0.299
761 #define CYG 150 // 0.587
762 #define CYB 29 // 0.114
763
764 #define CUR -43 // -0.16874
765 #define CUG -85 // -0.33126
766 #define CUB 128 // 0.5
767
768 #define CVR 128 // 0.5
769 #define CVG -107 // -0.41869
770 #define CVB -21 // -0.08131
771
772 #define CSHIFT 8
773#endif
774
775static void rgb2yuv_32(uint8_t dst[], SkPMColor c) {
776 int r = SkGetPackedR32(c);
777 int g = SkGetPackedG32(c);
778 int b = SkGetPackedB32(c);
779
780 int y = ( CYR*r + CYG*g + CYB*b ) >> CSHIFT;
781 int u = ( CUR*r + CUG*g + CUB*b ) >> CSHIFT;
782 int v = ( CVR*r + CVG*g + CVB*b ) >> CSHIFT;
783
784 dst[0] = SkToU8(y);
785 dst[1] = SkToU8(u + 128);
786 dst[2] = SkToU8(v + 128);
787}
788
789static void rgb2yuv_4444(uint8_t dst[], U16CPU c) {
790 int r = SkGetPackedR4444(c);
791 int g = SkGetPackedG4444(c);
792 int b = SkGetPackedB4444(c);
793
794 int y = ( CYR*r + CYG*g + CYB*b ) >> (CSHIFT - 4);
795 int u = ( CUR*r + CUG*g + CUB*b ) >> (CSHIFT - 4);
796 int v = ( CVR*r + CVG*g + CVB*b ) >> (CSHIFT - 4);
797
798 dst[0] = SkToU8(y);
799 dst[1] = SkToU8(u + 128);
800 dst[2] = SkToU8(v + 128);
801}
802
803static void rgb2yuv_16(uint8_t dst[], U16CPU c) {
804 int r = SkGetPackedR16(c);
805 int g = SkGetPackedG16(c);
806 int b = SkGetPackedB16(c);
807
808 int y = ( 2*CYR*r + CYG*g + 2*CYB*b ) >> (CSHIFT - 2);
809 int u = ( 2*CUR*r + CUG*g + 2*CUB*b ) >> (CSHIFT - 2);
810 int v = ( 2*CVR*r + CVG*g + 2*CVB*b ) >> (CSHIFT - 2);
811
812 dst[0] = SkToU8(y);
813 dst[1] = SkToU8(u + 128);
814 dst[2] = SkToU8(v + 128);
815}
816
817///////////////////////////////////////////////////////////////////////////////
818
819typedef void (*WriteScanline)(uint8_t* SK_RESTRICT dst,
820 const void* SK_RESTRICT src, int width,
821 const SkPMColor* SK_RESTRICT ctable);
822
823static void Write_32_YUV(uint8_t* SK_RESTRICT dst,
824 const void* SK_RESTRICT srcRow, int width,
825 const SkPMColor*) {
826 const uint32_t* SK_RESTRICT src = (const uint32_t*)srcRow;
827 while (--width >= 0) {
828#ifdef WE_CONVERT_TO_YUV
829 rgb2yuv_32(dst, *src++);
830#else
831 uint32_t c = *src++;
832 dst[0] = SkGetPackedR32(c);
833 dst[1] = SkGetPackedG32(c);
834 dst[2] = SkGetPackedB32(c);
835#endif
836 dst += 3;
837 }
838}
839
840static void Write_4444_YUV(uint8_t* SK_RESTRICT dst,
841 const void* SK_RESTRICT srcRow, int width,
842 const SkPMColor*) {
843 const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)srcRow;
844 while (--width >= 0) {
845#ifdef WE_CONVERT_TO_YUV
846 rgb2yuv_4444(dst, *src++);
847#else
848 SkPMColor16 c = *src++;
849 dst[0] = SkPacked4444ToR32(c);
850 dst[1] = SkPacked4444ToG32(c);
851 dst[2] = SkPacked4444ToB32(c);
852#endif
853 dst += 3;
854 }
855}
856
857static void Write_16_YUV(uint8_t* SK_RESTRICT dst,
858 const void* SK_RESTRICT srcRow, int width,
859 const SkPMColor*) {
860 const uint16_t* SK_RESTRICT src = (const uint16_t*)srcRow;
861 while (--width >= 0) {
862#ifdef WE_CONVERT_TO_YUV
863 rgb2yuv_16(dst, *src++);
864#else
865 uint16_t c = *src++;
866 dst[0] = SkPacked16ToR32(c);
867 dst[1] = SkPacked16ToG32(c);
868 dst[2] = SkPacked16ToB32(c);
869#endif
870 dst += 3;
871 }
872}
873
874static void Write_Index_YUV(uint8_t* SK_RESTRICT dst,
875 const void* SK_RESTRICT srcRow, int width,
876 const SkPMColor* SK_RESTRICT ctable) {
877 const uint8_t* SK_RESTRICT src = (const uint8_t*)srcRow;
878 while (--width >= 0) {
879#ifdef WE_CONVERT_TO_YUV
880 rgb2yuv_32(dst, ctable[*src++]);
881#else
882 uint32_t c = ctable[*src++];
883 dst[0] = SkGetPackedR32(c);
884 dst[1] = SkGetPackedG32(c);
885 dst[2] = SkGetPackedB32(c);
886#endif
887 dst += 3;
888 }
889}
890
891static WriteScanline ChooseWriter(const SkBitmap& bm) {
892 switch (bm.config()) {
893 case SkBitmap::kARGB_8888_Config:
894 return Write_32_YUV;
895 case SkBitmap::kRGB_565_Config:
896 return Write_16_YUV;
897 case SkBitmap::kARGB_4444_Config:
898 return Write_4444_YUV;
899 case SkBitmap::kIndex8_Config:
900 return Write_Index_YUV;
901 default:
902 return NULL;
903 }
904}
905
906class SkJPEGImageEncoder : public SkImageEncoder {
907protected:
908 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) {
909#ifdef TIME_ENCODE
djsollen@google.com11399402013-03-20 17:45:27 +0000910 SkAutoTime atm("JPEG Encode");
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000911#endif
912
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000913 SkAutoLockPixels alp(bm);
914 if (NULL == bm.getPixels()) {
915 return false;
916 }
917
918 jpeg_compress_struct cinfo;
919 skjpeg_error_mgr sk_err;
920 skjpeg_destination_mgr sk_wstream(stream);
921
922 // allocate these before set call setjmp
923 SkAutoMalloc oneRow;
924 SkAutoLockColors ctLocker;
925
926 cinfo.err = jpeg_std_error(&sk_err);
927 sk_err.error_exit = skjpeg_error_exit;
928 if (setjmp(sk_err.fJmpBuf)) {
929 return false;
930 }
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000931
mtklein@google.com8d725b22013-07-24 16:20:05 +0000932 // Keep after setjmp or mark volatile.
933 const WriteScanline writer = ChooseWriter(bm);
934 if (NULL == writer) {
935 return false;
936 }
937
938 jpeg_create_compress(&cinfo);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000939 cinfo.dest = &sk_wstream;
940 cinfo.image_width = bm.width();
941 cinfo.image_height = bm.height();
942 cinfo.input_components = 3;
943#ifdef WE_CONVERT_TO_YUV
944 cinfo.in_color_space = JCS_YCbCr;
945#else
946 cinfo.in_color_space = JCS_RGB;
947#endif
948 cinfo.input_gamma = 1;
949
950 jpeg_set_defaults(&cinfo);
951 jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
scroggo@google.comb7decc52013-04-17 17:37:56 +0000952#ifdef DCT_IFAST_SUPPORTED
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000953 cinfo.dct_method = JDCT_IFAST;
scroggo@google.comb7decc52013-04-17 17:37:56 +0000954#endif
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000955
956 jpeg_start_compress(&cinfo, TRUE);
957
958 const int width = bm.width();
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000959 uint8_t* oneRowP = (uint8_t*)oneRow.reset(width * 3);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000960
961 const SkPMColor* colors = ctLocker.lockColors(bm);
962 const void* srcRow = bm.getPixels();
963
964 while (cinfo.next_scanline < cinfo.image_height) {
965 JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
966
967 writer(oneRowP, srcRow, width, colors);
968 row_pointer[0] = oneRowP;
969 (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
970 srcRow = (const void*)((const char*)srcRow + bm.rowBytes());
971 }
972
973 jpeg_finish_compress(&cinfo);
974 jpeg_destroy_compress(&cinfo);
975
976 return true;
977 }
978};
979
980///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +0000981DEFINE_DECODER_CREATOR(JPEGImageDecoder);
982DEFINE_ENCODER_CREATOR(JPEGImageEncoder);
983///////////////////////////////////////////////////////////////////////////////
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000984
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000985static bool is_jpeg(SkStream* stream) {
robertphillips@google.comec51cb82012-03-23 18:13:47 +0000986 static const unsigned char gHeader[] = { 0xFF, 0xD8, 0xFF };
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000987 static const size_t HEADER_SIZE = sizeof(gHeader);
988
989 char buffer[HEADER_SIZE];
990 size_t len = stream->read(buffer, HEADER_SIZE);
991
992 if (len != HEADER_SIZE) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000993 return false; // can't read enough
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000994 }
995 if (memcmp(buffer, gHeader, HEADER_SIZE)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000996 return false;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000997 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000998 return true;
999}
1000
1001#include "SkTRegistry.h"
1002
1003static SkImageDecoder* sk_libjpeg_dfactory(SkStream* stream) {
1004 if (is_jpeg(stream)) {
1005 return SkNEW(SkJPEGImageDecoder);
1006 }
1007 return NULL;
1008}
1009
1010static SkImageDecoder::Format get_format_jpeg(SkStream* stream) {
1011 if (is_jpeg(stream)) {
1012 return SkImageDecoder::kJPEG_Format;
1013 }
1014 return SkImageDecoder::kUnknown_Format;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001015}
1016
robertphillips@google.com8570b5c2012-03-20 17:40:58 +00001017static SkImageEncoder* sk_libjpeg_efactory(SkImageEncoder::Type t) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001018 return (SkImageEncoder::kJPEG_Type == t) ? SkNEW(SkJPEGImageEncoder) : NULL;
1019}
1020
robertphillips@google.com8570b5c2012-03-20 17:40:58 +00001021
1022static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libjpeg_dfactory);
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001023static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_jpeg);
robertphillips@google.com8570b5c2012-03-20 17:40:58 +00001024static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libjpeg_efactory);