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