blob: 914ceb7e8aa458a3ea4e011be2db27f73d0ead8a [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
scroggo@google.coma1a51542013-08-07 21:02:32 +000058#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.com11399402013-03-20 17:45:27 +000059class SkJPEGImageIndex {
60public:
61 SkJPEGImageIndex(SkStream* stream, SkImageDecoder* decoder)
scroggo@google.coma1a51542013-08-07 21:02:32 +000062 : fSrcMgr(stream, decoder)
63 , fInfoInitialized(false)
64 , fHuffmanCreated(false)
65 , fDecompressStarted(false)
66 {
67 SkDEBUGCODE(fReadHeaderSucceeded = false;)
68 }
djsollen@google.com11399402013-03-20 17:45:27 +000069
70 ~SkJPEGImageIndex() {
scroggo@google.coma1a51542013-08-07 21:02:32 +000071 if (fHuffmanCreated) {
72 fHuffmanCreated = false;
73 jpeg_destroy_huffman_index(&fHuffmanIndex);
74 }
75 if (fDecompressStarted) {
76 fDecompressStarted = false;
77 jpeg_finish_decompress(&fCInfo);
78 }
79 if (fInfoInitialized) {
80 this->destroyInfo();
81 }
djsollen@google.com11399402013-03-20 17:45:27 +000082 }
83
84 /**
scroggo@google.coma1a51542013-08-07 21:02:32 +000085 * Destroy the cinfo struct.
86 * After this call, if a huffman index was already built, it
87 * can be used after calling initializeInfoAndReadHeader
88 * again. Must not be called after startTileDecompress except
89 * in the destructor.
djsollen@google.com11399402013-03-20 17:45:27 +000090 */
scroggo@google.coma1a51542013-08-07 21:02:32 +000091 void destroyInfo() {
92 SkASSERT(fInfoInitialized);
93 SkASSERT(!fDecompressStarted);
94 fInfoInitialized = false;
95 jpeg_destroy_decompress(&fCInfo);
96 SkDEBUGCODE(fReadHeaderSucceeded = false;)
97 }
98
99 /**
100 * Initialize the cinfo struct.
101 * Calls jpeg_create_decompress, makes customizations, and
102 * finally calls jpeg_read_header. Returns true if jpeg_read_header
103 * returns JPEG_HEADER_OK.
104 * If cinfo was already initialized, destroyInfo must be called to
105 * destroy the old one. Must not be called after startTileDecompress.
106 */
107 bool initializeInfoAndReadHeader() {
108 SkASSERT(!fInfoInitialized && !fDecompressStarted);
djsollen@google.com11399402013-03-20 17:45:27 +0000109 jpeg_create_decompress(&fCInfo);
110 overwrite_mem_buffer_size(&fCInfo);
111 fCInfo.src = &fSrcMgr;
scroggo@google.coma1a51542013-08-07 21:02:32 +0000112 fInfoInitialized = true;
113 const bool success = (JPEG_HEADER_OK == jpeg_read_header(&fCInfo, true));
114 SkDEBUGCODE(fReadHeaderSucceeded = success;)
115 return success;
djsollen@google.com11399402013-03-20 17:45:27 +0000116 }
117
118 jpeg_decompress_struct* cinfo() { return &fCInfo; }
119
djsollen@google.com11399402013-03-20 17:45:27 +0000120 huffman_index* huffmanIndex() { return &fHuffmanIndex; }
scroggo@google.coma1a51542013-08-07 21:02:32 +0000121
122 /**
123 * Build the index to be used for tile based decoding.
124 * Must only be called after a successful call to
125 * initializeInfoAndReadHeader and must not be called more
126 * than once.
127 */
128 bool buildHuffmanIndex() {
129 SkASSERT(fReadHeaderSucceeded);
130 SkASSERT(!fHuffmanCreated);
131 jpeg_create_huffman_index(&fCInfo, &fHuffmanIndex);
132 fHuffmanCreated = true;
133 SkASSERT(1 == fCInfo.scale_num && 1 == fCInfo.scale_denom);
134 return jpeg_build_huffman_index(&fCInfo, &fHuffmanIndex);
135 }
136
137 /**
138 * Start tile based decoding. Must only be called after a
139 * successful call to buildHuffmanIndex, and must only be
140 * called once.
141 */
142 bool startTileDecompress() {
143 SkASSERT(fHuffmanCreated);
144 SkASSERT(fReadHeaderSucceeded);
145 SkASSERT(!fDecompressStarted);
146 if (jpeg_start_tile_decompress(&fCInfo)) {
147 fDecompressStarted = true;
148 return true;
149 }
150 return false;
151 }
djsollen@google.com11399402013-03-20 17:45:27 +0000152
153private:
154 skjpeg_source_mgr fSrcMgr;
155 jpeg_decompress_struct fCInfo;
djsollen@google.com11399402013-03-20 17:45:27 +0000156 huffman_index fHuffmanIndex;
scroggo@google.coma1a51542013-08-07 21:02:32 +0000157 bool fInfoInitialized;
158 bool fHuffmanCreated;
159 bool fDecompressStarted;
160 SkDEBUGCODE(bool fReadHeaderSucceeded;)
djsollen@google.com11399402013-03-20 17:45:27 +0000161};
scroggo@google.coma1a51542013-08-07 21:02:32 +0000162#endif
djsollen@google.com11399402013-03-20 17:45:27 +0000163
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000164class SkJPEGImageDecoder : public SkImageDecoder {
165public:
scroggo@google.coma1a51542013-08-07 21:02:32 +0000166#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.com11399402013-03-20 17:45:27 +0000167 SkJPEGImageDecoder() {
168 fImageIndex = NULL;
169 fImageWidth = 0;
170 fImageHeight = 0;
171 }
172
173 virtual ~SkJPEGImageDecoder() {
174 SkDELETE(fImageIndex);
175 }
scroggo@google.coma1a51542013-08-07 21:02:32 +0000176#endif
djsollen@google.com11399402013-03-20 17:45:27 +0000177
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000178 virtual Format getFormat() const {
179 return kJPEG_Format;
180 }
181
182protected:
scroggo@google.comd79277f2013-08-07 19:53:53 +0000183#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.com11399402013-03-20 17:45:27 +0000184 virtual bool onBuildTileIndex(SkStream *stream, int *width, int *height) SK_OVERRIDE;
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000185 virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& rect) SK_OVERRIDE;
djsollen@google.com11399402013-03-20 17:45:27 +0000186#endif
187 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
188
189private:
scroggo@google.coma1a51542013-08-07 21:02:32 +0000190#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.com11399402013-03-20 17:45:27 +0000191 SkJPEGImageIndex* fImageIndex;
192 int fImageWidth;
193 int fImageHeight;
scroggo@google.coma1a51542013-08-07 21:02:32 +0000194#endif
djsollen@google.com11399402013-03-20 17:45:27 +0000195
196 typedef SkImageDecoder INHERITED;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000197};
198
199//////////////////////////////////////////////////////////////////////////
200
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000201/* Automatically clean up after throwing an exception */
202class JPEGAutoClean {
203public:
204 JPEGAutoClean(): cinfo_ptr(NULL) {}
205 ~JPEGAutoClean() {
206 if (cinfo_ptr) {
207 jpeg_destroy_decompress(cinfo_ptr);
208 }
209 }
210 void set(jpeg_decompress_struct* info) {
211 cinfo_ptr = info;
212 }
213private:
214 jpeg_decompress_struct* cinfo_ptr;
215};
216
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000217///////////////////////////////////////////////////////////////////////////////
218
219/* If we need to better match the request, we might examine the image and
220 output dimensions, and determine if the downsampling jpeg provided is
221 not sufficient. If so, we can recompute a modified sampleSize value to
222 make up the difference.
223
224 To skip this additional scaling, just set sampleSize = 1; below.
225 */
226static int recompute_sampleSize(int sampleSize,
227 const jpeg_decompress_struct& cinfo) {
228 return sampleSize * cinfo.output_width / cinfo.image_width;
229}
230
231static bool valid_output_dimensions(const jpeg_decompress_struct& cinfo) {
232 /* These are initialized to 0, so if they have non-zero values, we assume
233 they are "valid" (i.e. have been computed by libjpeg)
234 */
djsollen@google.com11399402013-03-20 17:45:27 +0000235 return 0 != cinfo.output_width && 0 != cinfo.output_height;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000236}
237
djsollen@google.com11399402013-03-20 17:45:27 +0000238static bool skip_src_rows(jpeg_decompress_struct* cinfo, void* buffer, int count) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000239 for (int i = 0; i < count; i++) {
240 JSAMPLE* rowptr = (JSAMPLE*)buffer;
241 int row_count = jpeg_read_scanlines(cinfo, &rowptr, 1);
djsollen@google.com11399402013-03-20 17:45:27 +0000242 if (1 != row_count) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000243 return false;
244 }
245 }
246 return true;
247}
248
scroggo@google.comd79277f2013-08-07 19:53:53 +0000249#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.com11399402013-03-20 17:45:27 +0000250static bool skip_src_rows_tile(jpeg_decompress_struct* cinfo,
251 huffman_index *index, void* buffer, int count) {
252 for (int i = 0; i < count; i++) {
253 JSAMPLE* rowptr = (JSAMPLE*)buffer;
254 int row_count = jpeg_read_tile_scanline(cinfo, index, &rowptr);
255 if (1 != row_count) {
256 return false;
257 }
258 }
259 return true;
260}
261#endif
262
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000263// This guy exists just to aid in debugging, as it allows debuggers to just
264// set a break-point in one place to see all error exists.
265static bool return_false(const jpeg_decompress_struct& cinfo,
266 const SkBitmap& bm, const char msg[]) {
djsollen@google.com11399402013-03-20 17:45:27 +0000267#ifdef SK_DEBUG
scroggo@google.come8f34712013-07-01 14:44:54 +0000268 SkDebugf("libjpeg error %d <%s> from %s [%d %d]\n", cinfo.err->msg_code,
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000269 cinfo.err->jpeg_message_table[cinfo.err->msg_code], msg,
270 bm.width(), bm.height());
271#endif
272 return false; // must always return false
273}
274
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000275// Convert a scanline of CMYK samples to RGBX in place. Note that this
276// method moves the "scanline" pointer in its processing
277static void convert_CMYK_to_RGB(uint8_t* scanline, unsigned int width) {
278 // At this point we've received CMYK pixels from libjpeg. We
rmistry@google.comd6176b02012-08-23 18:14:13 +0000279 // perform a crude conversion to RGB (based on the formulae
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000280 // from easyrgb.com):
281 // CMYK -> CMY
282 // C = ( C * (1 - K) + K ) // for each CMY component
283 // CMY -> RGB
284 // R = ( 1 - C ) * 255 // for each RGB component
285 // Unfortunately we are seeing inverted CMYK so all the original terms
286 // are 1-. This yields:
287 // CMYK -> CMY
288 // C = ( (1-C) * (1 - (1-K) + (1-K) ) -> C = 1 - C*K
289 // The conversion from CMY->RGB remains the same
290 for (unsigned int x = 0; x < width; ++x, scanline += 4) {
291 scanline[0] = SkMulDiv255Round(scanline[0], scanline[3]);
292 scanline[1] = SkMulDiv255Round(scanline[1], scanline[3]);
293 scanline[2] = SkMulDiv255Round(scanline[2], scanline[3]);
294 scanline[3] = 255;
295 }
296}
297
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000298bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
299#ifdef TIME_DECODE
djsollen@google.com11399402013-03-20 17:45:27 +0000300 SkAutoTime atm("JPEG Decode");
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000301#endif
302
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000303 JPEGAutoClean autoClean;
304
305 jpeg_decompress_struct cinfo;
djsollen@google.com11399402013-03-20 17:45:27 +0000306 skjpeg_error_mgr errorManager;
scroggo@google.comd4c35652013-08-01 15:03:42 +0000307 skjpeg_source_mgr srcManager(stream, this);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000308
djsollen@google.com11399402013-03-20 17:45:27 +0000309 cinfo.err = jpeg_std_error(&errorManager);
310 errorManager.error_exit = skjpeg_error_exit;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000311
312 // All objects need to be instantiated before this setjmp call so that
313 // they will be cleaned up properly if an error occurs.
djsollen@google.com11399402013-03-20 17:45:27 +0000314 if (setjmp(errorManager.fJmpBuf)) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000315 return return_false(cinfo, *bm, "setjmp");
316 }
317
318 jpeg_create_decompress(&cinfo);
319 autoClean.set(&cinfo);
320
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000321 overwrite_mem_buffer_size(&cinfo);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000322
323 //jpeg_stdio_src(&cinfo, file);
djsollen@google.com11399402013-03-20 17:45:27 +0000324 cinfo.src = &srcManager;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000325
326 int status = jpeg_read_header(&cinfo, true);
327 if (status != JPEG_HEADER_OK) {
328 return return_false(cinfo, *bm, "read_header");
329 }
330
331 /* Try to fulfill the requested sampleSize. Since jpeg can do it (when it
332 can) much faster that we, just use their num/denom api to approximate
333 the size.
334 */
335 int sampleSize = this->getSampleSize();
336
scroggo@google.com3acd3fc2013-04-12 16:28:21 +0000337#ifdef DCT_IFAST_SUPPORTED
djsollen@google.com11399402013-03-20 17:45:27 +0000338 if (this->getPreferQualityOverSpeed()) {
339 cinfo.dct_method = JDCT_ISLOW;
340 } else {
341 cinfo.dct_method = JDCT_IFAST;
342 }
scroggo@google.com3acd3fc2013-04-12 16:28:21 +0000343#else
344 cinfo.dct_method = JDCT_ISLOW;
345#endif
djsollen@google.com11399402013-03-20 17:45:27 +0000346
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000347 cinfo.scale_num = 1;
348 cinfo.scale_denom = sampleSize;
349
350 /* this gives about 30% performance improvement. In theory it may
351 reduce the visual quality, in practice I'm not seeing a difference
352 */
353 cinfo.do_fancy_upsampling = 0;
354
355 /* this gives another few percents */
356 cinfo.do_block_smoothing = 0;
357
scroggo@google.comf698c822013-07-18 19:34:49 +0000358 SrcDepth srcDepth = k32Bit_SrcDepth;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000359 /* default format is RGB */
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000360 if (cinfo.jpeg_color_space == JCS_CMYK) {
361 // libjpeg cannot convert from CMYK to RGB - here we set up
362 // so libjpeg will give us CMYK samples back and we will
363 // later manually convert them to RGB
364 cinfo.out_color_space = JCS_CMYK;
scroggo@google.comf698c822013-07-18 19:34:49 +0000365 } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
366 cinfo.out_color_space = JCS_GRAYSCALE;
367 srcDepth = k8BitGray_SrcDepth;
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000368 } else {
369 cinfo.out_color_space = JCS_RGB;
370 }
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000371
scroggo@google.comf698c822013-07-18 19:34:49 +0000372 SkBitmap::Config config = this->getPrefConfig(srcDepth, false);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000373 // only these make sense for jpegs
scroggo@google.comf698c822013-07-18 19:34:49 +0000374 if (SkBitmap::kA8_Config == config) {
375 if (cinfo.jpeg_color_space != JCS_GRAYSCALE) {
376 // Converting from a non grayscale image to A8 is
377 // not currently supported.
378 config = SkBitmap::kARGB_8888_Config;
379 // Change the output from jpeg back to RGB.
380 cinfo.out_color_space = JCS_RGB;
381 }
382 } else if (config != SkBitmap::kARGB_8888_Config &&
383 config != SkBitmap::kARGB_4444_Config &&
384 config != SkBitmap::kRGB_565_Config) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000385 config = SkBitmap::kARGB_8888_Config;
386 }
387
388#ifdef ANDROID_RGB
389 cinfo.dither_mode = JDITHER_NONE;
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000390 if (SkBitmap::kARGB_8888_Config == config && JCS_CMYK != cinfo.out_color_space) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000391 cinfo.out_color_space = JCS_RGBA_8888;
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000392 } else if (SkBitmap::kRGB_565_Config == config && JCS_CMYK != cinfo.out_color_space) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000393 cinfo.out_color_space = JCS_RGB_565;
394 if (this->getDitherImage()) {
395 cinfo.dither_mode = JDITHER_ORDERED;
396 }
397 }
398#endif
399
djsollen@google.com11399402013-03-20 17:45:27 +0000400 if (1 == sampleSize && SkImageDecoder::kDecodeBounds_Mode == mode) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000401 bm->setConfig(config, cinfo.image_width, cinfo.image_height);
scroggo@google.com1f6b9952013-07-18 19:37:46 +0000402 bm->setIsOpaque(config != SkBitmap::kA8_Config);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000403 return true;
404 }
405
406 /* image_width and image_height are the original dimensions, available
407 after jpeg_read_header(). To see the scaled dimensions, we have to call
408 jpeg_start_decompress(), and then read output_width and output_height.
409 */
410 if (!jpeg_start_decompress(&cinfo)) {
411 /* If we failed here, we may still have enough information to return
412 to the caller if they just wanted (subsampled bounds). If sampleSize
413 was 1, then we would have already returned. Thus we just check if
414 we're in kDecodeBounds_Mode, and that we have valid output sizes.
415
416 One reason to fail here is that we have insufficient stream data
417 to complete the setup. However, output dimensions seem to get
418 computed very early, which is why this special check can pay off.
419 */
djsollen@google.com11399402013-03-20 17:45:27 +0000420 if (SkImageDecoder::kDecodeBounds_Mode == mode && valid_output_dimensions(cinfo)) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000421 SkScaledBitmapSampler smpl(cinfo.output_width, cinfo.output_height,
422 recompute_sampleSize(sampleSize, cinfo));
423 bm->setConfig(config, smpl.scaledWidth(), smpl.scaledHeight());
scroggo@google.com1f6b9952013-07-18 19:37:46 +0000424 bm->setIsOpaque(config != SkBitmap::kA8_Config);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000425 return true;
426 } else {
427 return return_false(cinfo, *bm, "start_decompress");
428 }
429 }
430 sampleSize = recompute_sampleSize(sampleSize, cinfo);
431
432 // should we allow the Chooser (if present) to pick a config for us???
djsollen@google.com11399402013-03-20 17:45:27 +0000433 if (!this->chooseFromOneChoice(config, cinfo.output_width, cinfo.output_height)) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000434 return return_false(cinfo, *bm, "chooseFromOneChoice");
435 }
436
djsollen@google.com11399402013-03-20 17:45:27 +0000437 SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height, sampleSize);
scroggo@google.combc69ce92013-07-09 15:45:14 +0000438 bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
scroggo@google.com1f6b9952013-07-18 19:37:46 +0000439 bm->setIsOpaque(config != SkBitmap::kA8_Config);
scroggo@google.combc69ce92013-07-09 15:45:14 +0000440 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
441 return true;
442 }
443 if (!this->allocPixelRef(bm, NULL)) {
444 return return_false(cinfo, *bm, "allocPixelRef");
djsollen@google.com11399402013-03-20 17:45:27 +0000445 }
446
447 SkAutoLockPixels alp(*bm);
448
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000449#ifdef ANDROID_RGB
450 /* short-circuit the SkScaledBitmapSampler when possible, as this gives
451 a significant performance boost.
452 */
453 if (sampleSize == 1 &&
rmistry@google.comd6176b02012-08-23 18:14:13 +0000454 ((config == SkBitmap::kARGB_8888_Config &&
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000455 cinfo.out_color_space == JCS_RGBA_8888) ||
rmistry@google.comd6176b02012-08-23 18:14:13 +0000456 (config == SkBitmap::kRGB_565_Config &&
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000457 cinfo.out_color_space == JCS_RGB_565)))
458 {
scroggo@google.combc69ce92013-07-09 15:45:14 +0000459 JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels();
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000460 INT32 const bpr = bm->rowBytes();
rmistry@google.comd6176b02012-08-23 18:14:13 +0000461
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000462 while (cinfo.output_scanline < cinfo.output_height) {
463 int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
464 // if row_count == 0, then we didn't get a scanline, so abort.
465 // if we supported partial images, we might return true in this case
466 if (0 == row_count) {
467 return return_false(cinfo, *bm, "read_scanlines");
468 }
469 if (this->shouldCancelDecode()) {
470 return return_false(cinfo, *bm, "shouldCancelDecode");
471 }
472 rowptr += bpr;
473 }
474 jpeg_finish_decompress(&cinfo);
475 return true;
476 }
477#endif
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000478
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000479 // check for supported formats
480 SkScaledBitmapSampler::SrcConfig sc;
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000481 if (JCS_CMYK == cinfo.out_color_space) {
482 // In this case we will manually convert the CMYK values to RGB
483 sc = SkScaledBitmapSampler::kRGBX;
484 } else if (3 == cinfo.out_color_components && JCS_RGB == cinfo.out_color_space) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000485 sc = SkScaledBitmapSampler::kRGB;
486#ifdef ANDROID_RGB
487 } else if (JCS_RGBA_8888 == cinfo.out_color_space) {
488 sc = SkScaledBitmapSampler::kRGBX;
489 } else if (JCS_RGB_565 == cinfo.out_color_space) {
490 sc = SkScaledBitmapSampler::kRGB_565;
491#endif
492 } else if (1 == cinfo.out_color_components &&
493 JCS_GRAYSCALE == cinfo.out_color_space) {
494 sc = SkScaledBitmapSampler::kGray;
495 } else {
496 return return_false(cinfo, *bm, "jpeg colorspace");
497 }
498
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000499 if (!sampler.begin(bm, sc, this->getDitherImage())) {
500 return return_false(cinfo, *bm, "sampler.begin");
501 }
502
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000503 // The CMYK work-around relies on 4 components per pixel here
djsollen@google.com11399402013-03-20 17:45:27 +0000504 SkAutoMalloc srcStorage(cinfo.output_width * 4);
505 uint8_t* srcRow = (uint8_t*)srcStorage.get();
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000506
507 // Possibly skip initial rows [sampler.srcY0]
508 if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) {
509 return return_false(cinfo, *bm, "skip rows");
510 }
511
512 // now loop through scanlines until y == bm->height() - 1
513 for (int y = 0;; y++) {
514 JSAMPLE* rowptr = (JSAMPLE*)srcRow;
515 int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
516 if (0 == row_count) {
517 return return_false(cinfo, *bm, "read_scanlines");
518 }
519 if (this->shouldCancelDecode()) {
520 return return_false(cinfo, *bm, "shouldCancelDecode");
521 }
robertphillips@google.com8570b5c2012-03-20 17:40:58 +0000522
523 if (JCS_CMYK == cinfo.out_color_space) {
524 convert_CMYK_to_RGB(srcRow, cinfo.output_width);
525 }
526
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000527 sampler.next(srcRow);
528 if (bm->height() - 1 == y) {
529 // we're done
530 break;
531 }
532
533 if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) {
534 return return_false(cinfo, *bm, "skip rows");
535 }
536 }
537
538 // we formally skip the rest, so we don't get a complaint from libjpeg
539 if (!skip_src_rows(&cinfo, srcRow,
540 cinfo.output_height - cinfo.output_scanline)) {
541 return return_false(cinfo, *bm, "skip rows");
542 }
543 jpeg_finish_decompress(&cinfo);
544
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000545 return true;
546}
547
scroggo@google.comd79277f2013-08-07 19:53:53 +0000548#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.com11399402013-03-20 17:45:27 +0000549bool SkJPEGImageDecoder::onBuildTileIndex(SkStream* stream, int *width, int *height) {
550
scroggo@google.coma1a51542013-08-07 21:02:32 +0000551 SkAutoTDelete<SkJPEGImageIndex> imageIndex(SkNEW_ARGS(SkJPEGImageIndex, (stream, this)));
djsollen@google.com11399402013-03-20 17:45:27 +0000552 jpeg_decompress_struct* cinfo = imageIndex->cinfo();
djsollen@google.com11399402013-03-20 17:45:27 +0000553
554 skjpeg_error_mgr sk_err;
555 cinfo->err = jpeg_std_error(&sk_err);
556 sk_err.error_exit = skjpeg_error_exit;
557
558 // All objects need to be instantiated before this setjmp call so that
559 // they will be cleaned up properly if an error occurs.
560 if (setjmp(sk_err.fJmpBuf)) {
561 return false;
562 }
563
564 // create the cinfo used to create/build the huffmanIndex
scroggo@google.coma1a51542013-08-07 21:02:32 +0000565 if (!imageIndex->initializeInfoAndReadHeader()) {
djsollen@google.com11399402013-03-20 17:45:27 +0000566 return false;
567 }
568
scroggo@google.coma1a51542013-08-07 21:02:32 +0000569 if (!imageIndex->buildHuffmanIndex()) {
djsollen@google.com11399402013-03-20 17:45:27 +0000570 return false;
571 }
572
573 // destroy the cinfo used to create/build the huffman index
scroggo@google.coma1a51542013-08-07 21:02:32 +0000574 imageIndex->destroyInfo();
djsollen@google.com11399402013-03-20 17:45:27 +0000575
576 // Init decoder to image decode mode
scroggo@google.coma1a51542013-08-07 21:02:32 +0000577 if (!imageIndex->initializeInfoAndReadHeader()) {
djsollen@google.com11399402013-03-20 17:45:27 +0000578 return false;
579 }
580
581 cinfo->out_color_space = JCS_RGBA_8888;
582 cinfo->do_fancy_upsampling = 0;
583 cinfo->do_block_smoothing = 0;
584
585 // instead of jpeg_start_decompress() we start a tiled decompress
scroggo@google.coma1a51542013-08-07 21:02:32 +0000586 if (!imageIndex->startTileDecompress()) {
587 return false;
588 }
djsollen@google.com11399402013-03-20 17:45:27 +0000589
scroggo@google.coma1a51542013-08-07 21:02:32 +0000590 SkASSERT(1 == cinfo->scale_num);
djsollen@google.com11399402013-03-20 17:45:27 +0000591 *height = cinfo->output_height;
592 *width = cinfo->output_width;
593 fImageWidth = *width;
594 fImageHeight = *height;
595
596 SkDELETE(fImageIndex);
scroggo@google.coma1a51542013-08-07 21:02:32 +0000597 fImageIndex = imageIndex.detach();
djsollen@google.com11399402013-03-20 17:45:27 +0000598
599 return true;
600}
601
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000602bool SkJPEGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
djsollen@google.com11399402013-03-20 17:45:27 +0000603 if (NULL == fImageIndex) {
604 return false;
605 }
606 jpeg_decompress_struct* cinfo = fImageIndex->cinfo();
607
608 SkIRect rect = SkIRect::MakeWH(fImageWidth, fImageHeight);
609 if (!rect.intersect(region)) {
610 // If the requested region is entirely outside the image return false
611 return false;
612 }
613
614
615 skjpeg_error_mgr errorManager;
616 cinfo->err = jpeg_std_error(&errorManager);
617 errorManager.error_exit = skjpeg_error_exit;
618 if (setjmp(errorManager.fJmpBuf)) {
619 return false;
620 }
621
622 int requestedSampleSize = this->getSampleSize();
623 cinfo->scale_denom = requestedSampleSize;
624
625 if (this->getPreferQualityOverSpeed()) {
626 cinfo->dct_method = JDCT_ISLOW;
627 } else {
628 cinfo->dct_method = JDCT_IFAST;
629 }
630
631 SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, false);
632 if (config != SkBitmap::kARGB_8888_Config &&
633 config != SkBitmap::kARGB_4444_Config &&
634 config != SkBitmap::kRGB_565_Config) {
635 config = SkBitmap::kARGB_8888_Config;
636 }
637
638 /* default format is RGB */
639 cinfo->out_color_space = JCS_RGB;
640
641#ifdef ANDROID_RGB
642 cinfo->dither_mode = JDITHER_NONE;
643 if (SkBitmap::kARGB_8888_Config == config) {
644 cinfo->out_color_space = JCS_RGBA_8888;
645 } else if (SkBitmap::kRGB_565_Config == config) {
646 cinfo->out_color_space = JCS_RGB_565;
647 if (this->getDitherImage()) {
648 cinfo->dither_mode = JDITHER_ORDERED;
649 }
650 }
651#endif
652
653 int startX = rect.fLeft;
654 int startY = rect.fTop;
655 int width = rect.width();
656 int height = rect.height();
657
658 jpeg_init_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(),
659 &startX, &startY, &width, &height);
660 int skiaSampleSize = recompute_sampleSize(requestedSampleSize, *cinfo);
661 int actualSampleSize = skiaSampleSize * (DCTSIZE / cinfo->min_DCT_scaled_size);
662
663 SkScaledBitmapSampler sampler(width, height, skiaSampleSize);
664
665 SkBitmap bitmap;
666 bitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
667 bitmap.setIsOpaque(true);
668
669 // Check ahead of time if the swap(dest, src) is possible or not.
670 // If yes, then we will stick to AllocPixelRef since it's cheaper with the
671 // swap happening. If no, then we will use alloc to allocate pixels to
672 // prevent garbage collection.
673 int w = rect.width() / actualSampleSize;
674 int h = rect.height() / actualSampleSize;
675 bool swapOnly = (rect == region) && bm->isNull() &&
676 (w == bitmap.width()) && (h == bitmap.height()) &&
677 ((startX - rect.x()) / actualSampleSize == 0) &&
678 ((startY - rect.y()) / actualSampleSize == 0);
679 if (swapOnly) {
680 if (!this->allocPixelRef(&bitmap, NULL)) {
681 return return_false(*cinfo, bitmap, "allocPixelRef");
682 }
683 } else {
684 if (!bitmap.allocPixels()) {
685 return return_false(*cinfo, bitmap, "allocPixels");
686 }
687 }
688
689 SkAutoLockPixels alp(bitmap);
690
691#ifdef ANDROID_RGB
692 /* short-circuit the SkScaledBitmapSampler when possible, as this gives
693 a significant performance boost.
694 */
695 if (skiaSampleSize == 1 &&
696 ((config == SkBitmap::kARGB_8888_Config &&
697 cinfo->out_color_space == JCS_RGBA_8888) ||
698 (config == SkBitmap::kRGB_565_Config &&
699 cinfo->out_color_space == JCS_RGB_565)))
700 {
701 JSAMPLE* rowptr = (JSAMPLE*)bitmap.getPixels();
702 INT32 const bpr = bitmap.rowBytes();
703 int rowTotalCount = 0;
704
705 while (rowTotalCount < height) {
706 int rowCount = jpeg_read_tile_scanline(cinfo,
707 fImageIndex->huffmanIndex(),
708 &rowptr);
709 // if row_count == 0, then we didn't get a scanline, so abort.
710 // if we supported partial images, we might return true in this case
711 if (0 == rowCount) {
712 return return_false(*cinfo, bitmap, "read_scanlines");
713 }
714 if (this->shouldCancelDecode()) {
715 return return_false(*cinfo, bitmap, "shouldCancelDecode");
716 }
717 rowTotalCount += rowCount;
718 rowptr += bpr;
719 }
720
721 if (swapOnly) {
722 bm->swap(bitmap);
723 } else {
724 cropBitmap(bm, &bitmap, actualSampleSize, region.x(), region.y(),
725 region.width(), region.height(), startX, startY);
726 }
727 return true;
728 }
729#endif
730
731 // check for supported formats
732 SkScaledBitmapSampler::SrcConfig sc;
733 if (JCS_CMYK == cinfo->out_color_space) {
734 // In this case we will manually convert the CMYK values to RGB
735 sc = SkScaledBitmapSampler::kRGBX;
736 } else if (3 == cinfo->out_color_components && JCS_RGB == cinfo->out_color_space) {
737 sc = SkScaledBitmapSampler::kRGB;
738#ifdef ANDROID_RGB
739 } else if (JCS_RGBA_8888 == cinfo->out_color_space) {
740 sc = SkScaledBitmapSampler::kRGBX;
741 } else if (JCS_RGB_565 == cinfo->out_color_space) {
742 sc = SkScaledBitmapSampler::kRGB_565;
743#endif
744 } else if (1 == cinfo->out_color_components &&
745 JCS_GRAYSCALE == cinfo->out_color_space) {
746 sc = SkScaledBitmapSampler::kGray;
747 } else {
748 return return_false(*cinfo, *bm, "jpeg colorspace");
749 }
750
751 if (!sampler.begin(&bitmap, sc, this->getDitherImage())) {
752 return return_false(*cinfo, bitmap, "sampler.begin");
753 }
754
755 // The CMYK work-around relies on 4 components per pixel here
756 SkAutoMalloc srcStorage(width * 4);
757 uint8_t* srcRow = (uint8_t*)srcStorage.get();
758
759 // Possibly skip initial rows [sampler.srcY0]
760 if (!skip_src_rows_tile(cinfo, fImageIndex->huffmanIndex(), srcRow, sampler.srcY0())) {
761 return return_false(*cinfo, bitmap, "skip rows");
762 }
763
764 // now loop through scanlines until y == bitmap->height() - 1
765 for (int y = 0;; y++) {
766 JSAMPLE* rowptr = (JSAMPLE*)srcRow;
767 int row_count = jpeg_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(), &rowptr);
768 if (0 == row_count) {
769 return return_false(*cinfo, bitmap, "read_scanlines");
770 }
771 if (this->shouldCancelDecode()) {
772 return return_false(*cinfo, bitmap, "shouldCancelDecode");
773 }
774
775 if (JCS_CMYK == cinfo->out_color_space) {
776 convert_CMYK_to_RGB(srcRow, width);
777 }
778
779 sampler.next(srcRow);
780 if (bitmap.height() - 1 == y) {
781 // we're done
782 break;
783 }
784
785 if (!skip_src_rows_tile(cinfo, fImageIndex->huffmanIndex(), srcRow,
786 sampler.srcDY() - 1)) {
787 return return_false(*cinfo, bitmap, "skip rows");
788 }
789 }
790 if (swapOnly) {
791 bm->swap(bitmap);
792 } else {
793 cropBitmap(bm, &bitmap, actualSampleSize, region.x(), region.y(),
794 region.width(), region.height(), startX, startY);
795 }
796 return true;
797}
798#endif
799
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000800///////////////////////////////////////////////////////////////////////////////
801
802#include "SkColorPriv.h"
803
804// taken from jcolor.c in libjpeg
805#if 0 // 16bit - precise but slow
806 #define CYR 19595 // 0.299
807 #define CYG 38470 // 0.587
808 #define CYB 7471 // 0.114
809
810 #define CUR -11059 // -0.16874
811 #define CUG -21709 // -0.33126
812 #define CUB 32768 // 0.5
813
814 #define CVR 32768 // 0.5
815 #define CVG -27439 // -0.41869
816 #define CVB -5329 // -0.08131
817
818 #define CSHIFT 16
819#else // 8bit - fast, slightly less precise
820 #define CYR 77 // 0.299
821 #define CYG 150 // 0.587
822 #define CYB 29 // 0.114
823
824 #define CUR -43 // -0.16874
825 #define CUG -85 // -0.33126
826 #define CUB 128 // 0.5
827
828 #define CVR 128 // 0.5
829 #define CVG -107 // -0.41869
830 #define CVB -21 // -0.08131
831
832 #define CSHIFT 8
833#endif
834
835static void rgb2yuv_32(uint8_t dst[], SkPMColor c) {
836 int r = SkGetPackedR32(c);
837 int g = SkGetPackedG32(c);
838 int b = SkGetPackedB32(c);
839
840 int y = ( CYR*r + CYG*g + CYB*b ) >> CSHIFT;
841 int u = ( CUR*r + CUG*g + CUB*b ) >> CSHIFT;
842 int v = ( CVR*r + CVG*g + CVB*b ) >> CSHIFT;
843
844 dst[0] = SkToU8(y);
845 dst[1] = SkToU8(u + 128);
846 dst[2] = SkToU8(v + 128);
847}
848
849static void rgb2yuv_4444(uint8_t dst[], U16CPU c) {
850 int r = SkGetPackedR4444(c);
851 int g = SkGetPackedG4444(c);
852 int b = SkGetPackedB4444(c);
853
854 int y = ( CYR*r + CYG*g + CYB*b ) >> (CSHIFT - 4);
855 int u = ( CUR*r + CUG*g + CUB*b ) >> (CSHIFT - 4);
856 int v = ( CVR*r + CVG*g + CVB*b ) >> (CSHIFT - 4);
857
858 dst[0] = SkToU8(y);
859 dst[1] = SkToU8(u + 128);
860 dst[2] = SkToU8(v + 128);
861}
862
863static void rgb2yuv_16(uint8_t dst[], U16CPU c) {
864 int r = SkGetPackedR16(c);
865 int g = SkGetPackedG16(c);
866 int b = SkGetPackedB16(c);
867
868 int y = ( 2*CYR*r + CYG*g + 2*CYB*b ) >> (CSHIFT - 2);
869 int u = ( 2*CUR*r + CUG*g + 2*CUB*b ) >> (CSHIFT - 2);
870 int v = ( 2*CVR*r + CVG*g + 2*CVB*b ) >> (CSHIFT - 2);
871
872 dst[0] = SkToU8(y);
873 dst[1] = SkToU8(u + 128);
874 dst[2] = SkToU8(v + 128);
875}
876
877///////////////////////////////////////////////////////////////////////////////
878
879typedef void (*WriteScanline)(uint8_t* SK_RESTRICT dst,
880 const void* SK_RESTRICT src, int width,
881 const SkPMColor* SK_RESTRICT ctable);
882
883static void Write_32_YUV(uint8_t* SK_RESTRICT dst,
884 const void* SK_RESTRICT srcRow, int width,
885 const SkPMColor*) {
886 const uint32_t* SK_RESTRICT src = (const uint32_t*)srcRow;
887 while (--width >= 0) {
888#ifdef WE_CONVERT_TO_YUV
889 rgb2yuv_32(dst, *src++);
890#else
891 uint32_t c = *src++;
892 dst[0] = SkGetPackedR32(c);
893 dst[1] = SkGetPackedG32(c);
894 dst[2] = SkGetPackedB32(c);
895#endif
896 dst += 3;
897 }
898}
899
900static void Write_4444_YUV(uint8_t* SK_RESTRICT dst,
901 const void* SK_RESTRICT srcRow, int width,
902 const SkPMColor*) {
903 const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)srcRow;
904 while (--width >= 0) {
905#ifdef WE_CONVERT_TO_YUV
906 rgb2yuv_4444(dst, *src++);
907#else
908 SkPMColor16 c = *src++;
909 dst[0] = SkPacked4444ToR32(c);
910 dst[1] = SkPacked4444ToG32(c);
911 dst[2] = SkPacked4444ToB32(c);
912#endif
913 dst += 3;
914 }
915}
916
917static void Write_16_YUV(uint8_t* SK_RESTRICT dst,
918 const void* SK_RESTRICT srcRow, int width,
919 const SkPMColor*) {
920 const uint16_t* SK_RESTRICT src = (const uint16_t*)srcRow;
921 while (--width >= 0) {
922#ifdef WE_CONVERT_TO_YUV
923 rgb2yuv_16(dst, *src++);
924#else
925 uint16_t c = *src++;
926 dst[0] = SkPacked16ToR32(c);
927 dst[1] = SkPacked16ToG32(c);
928 dst[2] = SkPacked16ToB32(c);
929#endif
930 dst += 3;
931 }
932}
933
934static void Write_Index_YUV(uint8_t* SK_RESTRICT dst,
935 const void* SK_RESTRICT srcRow, int width,
936 const SkPMColor* SK_RESTRICT ctable) {
937 const uint8_t* SK_RESTRICT src = (const uint8_t*)srcRow;
938 while (--width >= 0) {
939#ifdef WE_CONVERT_TO_YUV
940 rgb2yuv_32(dst, ctable[*src++]);
941#else
942 uint32_t c = ctable[*src++];
943 dst[0] = SkGetPackedR32(c);
944 dst[1] = SkGetPackedG32(c);
945 dst[2] = SkGetPackedB32(c);
946#endif
947 dst += 3;
948 }
949}
950
951static WriteScanline ChooseWriter(const SkBitmap& bm) {
952 switch (bm.config()) {
953 case SkBitmap::kARGB_8888_Config:
954 return Write_32_YUV;
955 case SkBitmap::kRGB_565_Config:
956 return Write_16_YUV;
957 case SkBitmap::kARGB_4444_Config:
958 return Write_4444_YUV;
959 case SkBitmap::kIndex8_Config:
960 return Write_Index_YUV;
961 default:
962 return NULL;
963 }
964}
965
966class SkJPEGImageEncoder : public SkImageEncoder {
967protected:
968 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) {
969#ifdef TIME_ENCODE
djsollen@google.com11399402013-03-20 17:45:27 +0000970 SkAutoTime atm("JPEG Encode");
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000971#endif
972
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000973 SkAutoLockPixels alp(bm);
974 if (NULL == bm.getPixels()) {
975 return false;
976 }
977
978 jpeg_compress_struct cinfo;
979 skjpeg_error_mgr sk_err;
980 skjpeg_destination_mgr sk_wstream(stream);
981
982 // allocate these before set call setjmp
983 SkAutoMalloc oneRow;
984 SkAutoLockColors ctLocker;
985
986 cinfo.err = jpeg_std_error(&sk_err);
987 sk_err.error_exit = skjpeg_error_exit;
988 if (setjmp(sk_err.fJmpBuf)) {
989 return false;
990 }
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000991
mtklein@google.com8d725b22013-07-24 16:20:05 +0000992 // Keep after setjmp or mark volatile.
993 const WriteScanline writer = ChooseWriter(bm);
994 if (NULL == writer) {
995 return false;
996 }
997
998 jpeg_create_compress(&cinfo);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000999 cinfo.dest = &sk_wstream;
1000 cinfo.image_width = bm.width();
1001 cinfo.image_height = bm.height();
1002 cinfo.input_components = 3;
1003#ifdef WE_CONVERT_TO_YUV
1004 cinfo.in_color_space = JCS_YCbCr;
1005#else
1006 cinfo.in_color_space = JCS_RGB;
1007#endif
1008 cinfo.input_gamma = 1;
1009
1010 jpeg_set_defaults(&cinfo);
1011 jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
scroggo@google.comb7decc52013-04-17 17:37:56 +00001012#ifdef DCT_IFAST_SUPPORTED
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001013 cinfo.dct_method = JDCT_IFAST;
scroggo@google.comb7decc52013-04-17 17:37:56 +00001014#endif
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001015
1016 jpeg_start_compress(&cinfo, TRUE);
1017
1018 const int width = bm.width();
robertphillips@google.com8570b5c2012-03-20 17:40:58 +00001019 uint8_t* oneRowP = (uint8_t*)oneRow.reset(width * 3);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001020
1021 const SkPMColor* colors = ctLocker.lockColors(bm);
1022 const void* srcRow = bm.getPixels();
1023
1024 while (cinfo.next_scanline < cinfo.image_height) {
1025 JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
1026
1027 writer(oneRowP, srcRow, width, colors);
1028 row_pointer[0] = oneRowP;
1029 (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
1030 srcRow = (const void*)((const char*)srcRow + bm.rowBytes());
1031 }
1032
1033 jpeg_finish_compress(&cinfo);
1034 jpeg_destroy_compress(&cinfo);
1035
1036 return true;
1037 }
1038};
1039
1040///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001041DEFINE_DECODER_CREATOR(JPEGImageDecoder);
1042DEFINE_ENCODER_CREATOR(JPEGImageEncoder);
1043///////////////////////////////////////////////////////////////////////////////
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001044
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001045static bool is_jpeg(SkStream* stream) {
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001046 static const unsigned char gHeader[] = { 0xFF, 0xD8, 0xFF };
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001047 static const size_t HEADER_SIZE = sizeof(gHeader);
1048
1049 char buffer[HEADER_SIZE];
1050 size_t len = stream->read(buffer, HEADER_SIZE);
1051
1052 if (len != HEADER_SIZE) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001053 return false; // can't read enough
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001054 }
1055 if (memcmp(buffer, gHeader, HEADER_SIZE)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001056 return false;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001057 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001058 return true;
1059}
1060
1061#include "SkTRegistry.h"
1062
1063static SkImageDecoder* sk_libjpeg_dfactory(SkStream* stream) {
1064 if (is_jpeg(stream)) {
1065 return SkNEW(SkJPEGImageDecoder);
1066 }
1067 return NULL;
1068}
1069
1070static SkImageDecoder::Format get_format_jpeg(SkStream* stream) {
1071 if (is_jpeg(stream)) {
1072 return SkImageDecoder::kJPEG_Format;
1073 }
1074 return SkImageDecoder::kUnknown_Format;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001075}
1076
robertphillips@google.com8570b5c2012-03-20 17:40:58 +00001077static SkImageEncoder* sk_libjpeg_efactory(SkImageEncoder::Type t) {
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001078 return (SkImageEncoder::kJPEG_Type == t) ? SkNEW(SkJPEGImageEncoder) : NULL;
1079}
1080
robertphillips@google.com8570b5c2012-03-20 17:40:58 +00001081
1082static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libjpeg_dfactory);
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001083static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_jpeg);
robertphillips@google.com8570b5c2012-03-20 17:40:58 +00001084static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libjpeg_efactory);