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