blob: aac0b4b5cdeac2ec79e4a1fa24c9610e83323a11 [file] [log] [blame]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001/*
2 * Copyright 2007, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "SkImageDecoder.h"
reed@android.comb08eb2b2009-01-06 20:16:26 +000018#include "SkImageEncoder.h"
reed@android.com6f598152010-01-21 15:34:19 +000019#include "SkJpegUtility.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000020#include "SkColorPriv.h"
21#include "SkDither.h"
22#include "SkScaledBitmapSampler.h"
23#include "SkStream.h"
24#include "SkTemplates.h"
25#include "SkUtils.h"
26
27#include <stdio.h>
28extern "C" {
29 #include "jpeglib.h"
30 #include "jerror.h"
31}
32
reed@android.com12d16252009-10-19 15:51:15 +000033#ifdef ANDROID
34#include <cutils/properties.h>
35
36// Key to lookup the size of memory buffer set in system property
37static const char KEY_MEM_CAP[] = "ro.media.dec.jpeg.memcap";
38#endif
39
reed@android.com8a1c16f2008-12-17 15:59:43 +000040// this enables timing code to report milliseconds for an encode
41//#define TIME_ENCODE
42//#define TIME_DECODE
43
44// this enables our rgb->yuv code, which is faster than libjpeg on ARM
45// disable for the moment, as we have some glitches when width != multiple of 4
46#define WE_CONVERT_TO_YUV
47
48//////////////////////////////////////////////////////////////////////////
49//////////////////////////////////////////////////////////////////////////
50
51class SkJPEGImageDecoder : public SkImageDecoder {
52public:
53 virtual Format getFormat() const {
54 return kJPEG_Format;
55 }
56
57protected:
reed@android.com3f1f06a2010-03-03 21:04:12 +000058 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +000059};
60
reed@android.com8a1c16f2008-12-17 15:59:43 +000061//////////////////////////////////////////////////////////////////////////
62
63#include "SkTime.h"
64
65class AutoTimeMillis {
66public:
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 }
76private:
77 const char* fLabel;
78 SkMSec fNow;
79};
80
reed@android.com8a1c16f2008-12-17 15:59:43 +000081/* Automatically clean up after throwing an exception */
82class JPEGAutoClean {
83public:
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 }
93private:
94 jpeg_decompress_struct* cinfo_ptr;
95};
96
reed@android.com12d16252009-10-19 15:51:15 +000097#ifdef ANDROID
98/* Check if the memory cap property is set.
99 If so, use the memory size for jpeg decode.
100*/
101static void overwrite_mem_buffer_size(j_decompress_ptr cinfo) {
reed@android.com2295c632010-03-04 21:38:50 +0000102#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
reed@android.com12d16252009-10-19 15:51:15 +0000107}
108#endif
109
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110
111///////////////////////////////////////////////////////////////////////////////
112
reed@android.combc7d2fb2010-02-05 15:43:07 +0000113/* 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 */
120static int recompute_sampleSize(int sampleSize,
121 const jpeg_decompress_struct& cinfo) {
122 return sampleSize * cinfo.output_width / cinfo.image_width;
123}
124
125static 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
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132static 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.
146static 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
reed@android.com3f1f06a2010-03-03 21:04:12 +0000156bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157#ifdef TIME_DECODE
158 AutoTimeMillis atm("JPEG Decode");
159#endif
160
161 SkAutoMalloc srcStorage;
162 JPEGAutoClean autoClean;
163
164 jpeg_decompress_struct cinfo;
reed@android.com6f598152010-01-21 15:34:19 +0000165 skjpeg_error_mgr sk_err;
166 skjpeg_source_mgr sk_stream(stream, this);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167
168 cinfo.err = jpeg_std_error(&sk_err);
reed@android.com6f598152010-01-21 15:34:19 +0000169 sk_err.error_exit = skjpeg_error_exit;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170
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
reed@android.com12d16252009-10-19 15:51:15 +0000180#ifdef ANDROID
181 overwrite_mem_buffer_size(&cinfo);
182#endif
183
reed@android.com8a1c16f2008-12-17 15:59:43 +0000184 //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
reed@android.com3f1f06a2010-03-03 21:04:12 +0000213 SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, false);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214 // 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) {
djsollen@google.com57f49692011-02-23 20:46:31 +0000226 cinfo.out_color_space = JCS_RGB_565;
227 if (this->getDitherImage()) {
228 cinfo.dither_mode = JDITHER_ORDERED;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000229 }
230 }
231#endif
232
reed@android.com9ce22572009-07-16 14:50:54 +0000233 if (sampleSize == 1 && mode == SkImageDecoder::kDecodeBounds_Mode) {
234 bm->setConfig(config, cinfo.image_width, cinfo.image_height);
235 bm->setIsOpaque(true);
236 return true;
237 }
238
reed@android.com8a1c16f2008-12-17 15:59:43 +0000239 /* image_width and image_height are the original dimensions, available
240 after jpeg_read_header(). To see the scaled dimensions, we have to call
241 jpeg_start_decompress(), and then read output_width and output_height.
242 */
243 if (!jpeg_start_decompress(&cinfo)) {
reed@android.combc7d2fb2010-02-05 15:43:07 +0000244 /* If we failed here, we may still have enough information to return
245 to the caller if they just wanted (subsampled bounds). If sampleSize
246 was 1, then we would have already returned. Thus we just check if
247 we're in kDecodeBounds_Mode, and that we have valid output sizes.
248
249 One reason to fail here is that we have insufficient stream data
250 to complete the setup. However, output dimensions seem to get
251 computed very early, which is why this special check can pay off.
252 */
253 if (SkImageDecoder::kDecodeBounds_Mode == mode &&
254 valid_output_dimensions(cinfo)) {
255 SkScaledBitmapSampler smpl(cinfo.output_width, cinfo.output_height,
256 recompute_sampleSize(sampleSize, cinfo));
257 bm->setConfig(config, smpl.scaledWidth(), smpl.scaledHeight());
258 bm->setIsOpaque(true);
259 return true;
260 } else {
261 return return_false(cinfo, *bm, "start_decompress");
262 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263 }
reed@android.combc7d2fb2010-02-05 15:43:07 +0000264 sampleSize = recompute_sampleSize(sampleSize, cinfo);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000265
266 // should we allow the Chooser (if present) to pick a config for us???
267 if (!this->chooseFromOneChoice(config, cinfo.output_width,
268 cinfo.output_height)) {
269 return return_false(cinfo, *bm, "chooseFromOneChoice");
270 }
271
272#ifdef ANDROID_RGB
273 /* short-circuit the SkScaledBitmapSampler when possible, as this gives
274 a significant performance boost.
275 */
276 if (sampleSize == 1 &&
277 ((config == SkBitmap::kARGB_8888_Config &&
278 cinfo.out_color_space == JCS_RGBA_8888) ||
279 (config == SkBitmap::kRGB_565_Config &&
280 cinfo.out_color_space == JCS_RGB_565)))
281 {
282 bm->setConfig(config, cinfo.output_width, cinfo.output_height);
283 bm->setIsOpaque(true);
284 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
285 return true;
286 }
287 if (!this->allocPixelRef(bm, NULL)) {
288 return return_false(cinfo, *bm, "allocPixelRef");
289 }
290 SkAutoLockPixels alp(*bm);
291 JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels();
292 INT32 const bpr = bm->rowBytes();
293
294 while (cinfo.output_scanline < cinfo.output_height) {
295 int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
296 // if row_count == 0, then we didn't get a scanline, so abort.
297 // if we supported partial images, we might return true in this case
298 if (0 == row_count) {
299 return return_false(cinfo, *bm, "read_scanlines");
300 }
301 if (this->shouldCancelDecode()) {
302 return return_false(cinfo, *bm, "shouldCancelDecode");
303 }
304 rowptr += bpr;
305 }
306 jpeg_finish_decompress(&cinfo);
307 return true;
308 }
309#endif
310
311 // check for supported formats
312 SkScaledBitmapSampler::SrcConfig sc;
313 if (3 == cinfo.out_color_components && JCS_RGB == cinfo.out_color_space) {
314 sc = SkScaledBitmapSampler::kRGB;
315#ifdef ANDROID_RGB
316 } else if (JCS_RGBA_8888 == cinfo.out_color_space) {
317 sc = SkScaledBitmapSampler::kRGBX;
djsollen@google.com57f49692011-02-23 20:46:31 +0000318 } else if (JCS_RGB_565 == cinfo.out_color_space) {
319 sc = SkScaledBitmapSampler::kRGB_565;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000320#endif
321 } else if (1 == cinfo.out_color_components &&
322 JCS_GRAYSCALE == cinfo.out_color_space) {
323 sc = SkScaledBitmapSampler::kGray;
324 } else {
325 return return_false(cinfo, *bm, "jpeg colorspace");
326 }
327
328 SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height,
329 sampleSize);
330
331 bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
332 // jpegs are always opauqe (i.e. have no per-pixel alpha)
333 bm->setIsOpaque(true);
334
335 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
336 return true;
337 }
338 if (!this->allocPixelRef(bm, NULL)) {
339 return return_false(cinfo, *bm, "allocPixelRef");
340 }
341
342 SkAutoLockPixels alp(*bm);
343 if (!sampler.begin(bm, sc, this->getDitherImage())) {
344 return return_false(cinfo, *bm, "sampler.begin");
345 }
346
347 uint8_t* srcRow = (uint8_t*)srcStorage.alloc(cinfo.output_width * 4);
348
349 // Possibly skip initial rows [sampler.srcY0]
350 if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) {
351 return return_false(cinfo, *bm, "skip rows");
352 }
353
354 // now loop through scanlines until y == bm->height() - 1
355 for (int y = 0;; y++) {
356 JSAMPLE* rowptr = (JSAMPLE*)srcRow;
357 int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
358 if (0 == row_count) {
359 return return_false(cinfo, *bm, "read_scanlines");
360 }
361 if (this->shouldCancelDecode()) {
362 return return_false(cinfo, *bm, "shouldCancelDecode");
363 }
364
365 sampler.next(srcRow);
366 if (bm->height() - 1 == y) {
367 // we're done
368 break;
369 }
370
371 if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) {
372 return return_false(cinfo, *bm, "skip rows");
373 }
374 }
375
376 // we formally skip the rest, so we don't get a complaint from libjpeg
377 if (!skip_src_rows(&cinfo, srcRow,
378 cinfo.output_height - cinfo.output_scanline)) {
379 return return_false(cinfo, *bm, "skip rows");
380 }
381 jpeg_finish_decompress(&cinfo);
382
383// SkDebugf("------------------- bm2 size %d [%d %d] %d\n", bm->getSize(), bm->width(), bm->height(), bm->config());
384 return true;
385}
386
387///////////////////////////////////////////////////////////////////////////////
388
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389#include "SkColorPriv.h"
390
391// taken from jcolor.c in libjpeg
392#if 0 // 16bit - precise but slow
393 #define CYR 19595 // 0.299
394 #define CYG 38470 // 0.587
395 #define CYB 7471 // 0.114
396
397 #define CUR -11059 // -0.16874
398 #define CUG -21709 // -0.33126
399 #define CUB 32768 // 0.5
400
401 #define CVR 32768 // 0.5
402 #define CVG -27439 // -0.41869
403 #define CVB -5329 // -0.08131
404
405 #define CSHIFT 16
406#else // 8bit - fast, slightly less precise
407 #define CYR 77 // 0.299
408 #define CYG 150 // 0.587
409 #define CYB 29 // 0.114
410
411 #define CUR -43 // -0.16874
412 #define CUG -85 // -0.33126
413 #define CUB 128 // 0.5
414
415 #define CVR 128 // 0.5
416 #define CVG -107 // -0.41869
417 #define CVB -21 // -0.08131
418
419 #define CSHIFT 8
420#endif
421
422static void rgb2yuv_32(uint8_t dst[], SkPMColor c) {
423 int r = SkGetPackedR32(c);
424 int g = SkGetPackedG32(c);
425 int b = SkGetPackedB32(c);
426
427 int y = ( CYR*r + CYG*g + CYB*b ) >> CSHIFT;
428 int u = ( CUR*r + CUG*g + CUB*b ) >> CSHIFT;
429 int v = ( CVR*r + CVG*g + CVB*b ) >> CSHIFT;
430
431 dst[0] = SkToU8(y);
432 dst[1] = SkToU8(u + 128);
433 dst[2] = SkToU8(v + 128);
434}
435
436static void rgb2yuv_4444(uint8_t dst[], U16CPU c) {
437 int r = SkGetPackedR4444(c);
438 int g = SkGetPackedG4444(c);
439 int b = SkGetPackedB4444(c);
440
441 int y = ( CYR*r + CYG*g + CYB*b ) >> (CSHIFT - 4);
442 int u = ( CUR*r + CUG*g + CUB*b ) >> (CSHIFT - 4);
443 int v = ( CVR*r + CVG*g + CVB*b ) >> (CSHIFT - 4);
444
445 dst[0] = SkToU8(y);
446 dst[1] = SkToU8(u + 128);
447 dst[2] = SkToU8(v + 128);
448}
449
450static void rgb2yuv_16(uint8_t dst[], U16CPU c) {
451 int r = SkGetPackedR16(c);
452 int g = SkGetPackedG16(c);
453 int b = SkGetPackedB16(c);
454
455 int y = ( 2*CYR*r + CYG*g + 2*CYB*b ) >> (CSHIFT - 2);
456 int u = ( 2*CUR*r + CUG*g + 2*CUB*b ) >> (CSHIFT - 2);
457 int v = ( 2*CVR*r + CVG*g + 2*CVB*b ) >> (CSHIFT - 2);
458
459 dst[0] = SkToU8(y);
460 dst[1] = SkToU8(u + 128);
461 dst[2] = SkToU8(v + 128);
462}
463
464///////////////////////////////////////////////////////////////////////////////
465
466typedef void (*WriteScanline)(uint8_t* SK_RESTRICT dst,
467 const void* SK_RESTRICT src, int width,
468 const SkPMColor* SK_RESTRICT ctable);
469
470static void Write_32_YUV(uint8_t* SK_RESTRICT dst,
471 const void* SK_RESTRICT srcRow, int width,
472 const SkPMColor*) {
473 const uint32_t* SK_RESTRICT src = (const uint32_t*)srcRow;
474 while (--width >= 0) {
475#ifdef WE_CONVERT_TO_YUV
476 rgb2yuv_32(dst, *src++);
477#else
478 uint32_t c = *src++;
479 dst[0] = SkGetPackedR32(c);
480 dst[1] = SkGetPackedG32(c);
481 dst[2] = SkGetPackedB32(c);
482#endif
483 dst += 3;
484 }
485}
486
487static void Write_4444_YUV(uint8_t* SK_RESTRICT dst,
488 const void* SK_RESTRICT srcRow, int width,
489 const SkPMColor*) {
490 const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)srcRow;
491 while (--width >= 0) {
492#ifdef WE_CONVERT_TO_YUV
493 rgb2yuv_4444(dst, *src++);
494#else
495 SkPMColor16 c = *src++;
496 dst[0] = SkPacked4444ToR32(c);
497 dst[1] = SkPacked4444ToG32(c);
498 dst[2] = SkPacked4444ToB32(c);
499#endif
500 dst += 3;
501 }
502}
503
504static void Write_16_YUV(uint8_t* SK_RESTRICT dst,
505 const void* SK_RESTRICT srcRow, int width,
506 const SkPMColor*) {
507 const uint16_t* SK_RESTRICT src = (const uint16_t*)srcRow;
508 while (--width >= 0) {
509#ifdef WE_CONVERT_TO_YUV
510 rgb2yuv_16(dst, *src++);
511#else
512 uint16_t c = *src++;
513 dst[0] = SkPacked16ToR32(c);
514 dst[1] = SkPacked16ToG32(c);
515 dst[2] = SkPacked16ToB32(c);
516#endif
517 dst += 3;
518 }
519}
520
521static void Write_Index_YUV(uint8_t* SK_RESTRICT dst,
522 const void* SK_RESTRICT srcRow, int width,
523 const SkPMColor* SK_RESTRICT ctable) {
524 const uint8_t* SK_RESTRICT src = (const uint8_t*)srcRow;
525 while (--width >= 0) {
526#ifdef WE_CONVERT_TO_YUV
527 rgb2yuv_32(dst, ctable[*src++]);
528#else
529 uint32_t c = ctable[*src++];
530 dst[0] = SkGetPackedR32(c);
531 dst[1] = SkGetPackedG32(c);
532 dst[2] = SkGetPackedB32(c);
533#endif
534 dst += 3;
535 }
536}
537
538static WriteScanline ChooseWriter(const SkBitmap& bm) {
539 switch (bm.config()) {
540 case SkBitmap::kARGB_8888_Config:
541 return Write_32_YUV;
542 case SkBitmap::kRGB_565_Config:
543 return Write_16_YUV;
544 case SkBitmap::kARGB_4444_Config:
545 return Write_4444_YUV;
546 case SkBitmap::kIndex8_Config:
547 return Write_Index_YUV;
548 default:
549 return NULL;
550 }
551}
552
reed@android.com8a1c16f2008-12-17 15:59:43 +0000553class SkJPEGImageEncoder : public SkImageEncoder {
554protected:
555 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) {
556#ifdef TIME_ENCODE
557 AutoTimeMillis atm("JPEG Encode");
558#endif
559
560 const WriteScanline writer = ChooseWriter(bm);
561 if (NULL == writer) {
562 return false;
563 }
564
565 SkAutoLockPixels alp(bm);
566 if (NULL == bm.getPixels()) {
567 return false;
568 }
569
570 jpeg_compress_struct cinfo;
reed@android.com6f598152010-01-21 15:34:19 +0000571 skjpeg_error_mgr sk_err;
572 skjpeg_destination_mgr sk_wstream(stream);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573
574 // allocate these before set call setjmp
575 SkAutoMalloc oneRow;
576 SkAutoLockColors ctLocker;
577
578 cinfo.err = jpeg_std_error(&sk_err);
reed@android.com6f598152010-01-21 15:34:19 +0000579 sk_err.error_exit = skjpeg_error_exit;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000580 if (setjmp(sk_err.fJmpBuf)) {
581 return false;
582 }
583 jpeg_create_compress(&cinfo);
584
585 cinfo.dest = &sk_wstream;
586 cinfo.image_width = bm.width();
587 cinfo.image_height = bm.height();
588 cinfo.input_components = 3;
589#ifdef WE_CONVERT_TO_YUV
590 cinfo.in_color_space = JCS_YCbCr;
591#else
592 cinfo.in_color_space = JCS_RGB;
593#endif
594 cinfo.input_gamma = 1;
595
596 jpeg_set_defaults(&cinfo);
597 jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
598 cinfo.dct_method = JDCT_IFAST;
599
600 jpeg_start_compress(&cinfo, TRUE);
601
602 const int width = bm.width();
603 uint8_t* oneRowP = (uint8_t*)oneRow.alloc(width * 3);
604
605 const SkPMColor* colors = ctLocker.lockColors(bm);
606 const void* srcRow = bm.getPixels();
607
608 while (cinfo.next_scanline < cinfo.image_height) {
609 JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
610
611 writer(oneRowP, srcRow, width, colors);
612 row_pointer[0] = oneRowP;
613 (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
614 srcRow = (const void*)((const char*)srcRow + bm.rowBytes());
615 }
616
617 jpeg_finish_compress(&cinfo);
618 jpeg_destroy_compress(&cinfo);
619
620 return true;
621 }
622};
623
reed@android.com00bf85a2009-01-22 13:04:56 +0000624///////////////////////////////////////////////////////////////////////////////
625
626#include "SkTRegistry.h"
627
628static SkImageDecoder* DFactory(SkStream* stream) {
629 static const char gHeader[] = { 0xFF, 0xD8, 0xFF };
630 static const size_t HEADER_SIZE = sizeof(gHeader);
631
632 char buffer[HEADER_SIZE];
633 size_t len = stream->read(buffer, HEADER_SIZE);
634
635 if (len != HEADER_SIZE) {
636 return NULL; // can't read enough
637 }
638 if (memcmp(buffer, gHeader, HEADER_SIZE)) {
639 return NULL;
640 }
641 return SkNEW(SkJPEGImageDecoder);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000642}
643
reed@android.com00bf85a2009-01-22 13:04:56 +0000644static SkImageEncoder* EFactory(SkImageEncoder::Type t) {
645 return (SkImageEncoder::kJPEG_Type == t) ? SkNEW(SkJPEGImageEncoder) : NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646}
647
reed@android.com00bf85a2009-01-22 13:04:56 +0000648static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(DFactory);
649static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(EFactory);