blob: ed523bb05f404afac6d5ba1f44031dac332178f7 [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) {
226 if (sampleSize == 1) {
227 // SkScaledBitmapSampler can't handle RGB_565 yet,
228 // so don't even try.
229 cinfo.out_color_space = JCS_RGB_565;
230 if (this->getDitherImage()) {
231 cinfo.dither_mode = JDITHER_ORDERED;
232 }
233 }
234 }
235#endif
236
reed@android.com9ce22572009-07-16 14:50:54 +0000237 if (sampleSize == 1 && mode == SkImageDecoder::kDecodeBounds_Mode) {
238 bm->setConfig(config, cinfo.image_width, cinfo.image_height);
239 bm->setIsOpaque(true);
240 return true;
241 }
242
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243 /* image_width and image_height are the original dimensions, available
244 after jpeg_read_header(). To see the scaled dimensions, we have to call
245 jpeg_start_decompress(), and then read output_width and output_height.
246 */
247 if (!jpeg_start_decompress(&cinfo)) {
reed@android.combc7d2fb2010-02-05 15:43:07 +0000248 /* If we failed here, we may still have enough information to return
249 to the caller if they just wanted (subsampled bounds). If sampleSize
250 was 1, then we would have already returned. Thus we just check if
251 we're in kDecodeBounds_Mode, and that we have valid output sizes.
252
253 One reason to fail here is that we have insufficient stream data
254 to complete the setup. However, output dimensions seem to get
255 computed very early, which is why this special check can pay off.
256 */
257 if (SkImageDecoder::kDecodeBounds_Mode == mode &&
258 valid_output_dimensions(cinfo)) {
259 SkScaledBitmapSampler smpl(cinfo.output_width, cinfo.output_height,
260 recompute_sampleSize(sampleSize, cinfo));
261 bm->setConfig(config, smpl.scaledWidth(), smpl.scaledHeight());
262 bm->setIsOpaque(true);
263 return true;
264 } else {
265 return return_false(cinfo, *bm, "start_decompress");
266 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000267 }
reed@android.combc7d2fb2010-02-05 15:43:07 +0000268 sampleSize = recompute_sampleSize(sampleSize, cinfo);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000269
270 // should we allow the Chooser (if present) to pick a config for us???
271 if (!this->chooseFromOneChoice(config, cinfo.output_width,
272 cinfo.output_height)) {
273 return return_false(cinfo, *bm, "chooseFromOneChoice");
274 }
275
276#ifdef ANDROID_RGB
277 /* short-circuit the SkScaledBitmapSampler when possible, as this gives
278 a significant performance boost.
279 */
280 if (sampleSize == 1 &&
281 ((config == SkBitmap::kARGB_8888_Config &&
282 cinfo.out_color_space == JCS_RGBA_8888) ||
283 (config == SkBitmap::kRGB_565_Config &&
284 cinfo.out_color_space == JCS_RGB_565)))
285 {
286 bm->setConfig(config, cinfo.output_width, cinfo.output_height);
287 bm->setIsOpaque(true);
288 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
289 return true;
290 }
291 if (!this->allocPixelRef(bm, NULL)) {
292 return return_false(cinfo, *bm, "allocPixelRef");
293 }
294 SkAutoLockPixels alp(*bm);
295 JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels();
296 INT32 const bpr = bm->rowBytes();
297
298 while (cinfo.output_scanline < cinfo.output_height) {
299 int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
300 // if row_count == 0, then we didn't get a scanline, so abort.
301 // if we supported partial images, we might return true in this case
302 if (0 == row_count) {
303 return return_false(cinfo, *bm, "read_scanlines");
304 }
305 if (this->shouldCancelDecode()) {
306 return return_false(cinfo, *bm, "shouldCancelDecode");
307 }
308 rowptr += bpr;
309 }
310 jpeg_finish_decompress(&cinfo);
311 return true;
312 }
313#endif
314
315 // check for supported formats
316 SkScaledBitmapSampler::SrcConfig sc;
317 if (3 == cinfo.out_color_components && JCS_RGB == cinfo.out_color_space) {
318 sc = SkScaledBitmapSampler::kRGB;
319#ifdef ANDROID_RGB
320 } else if (JCS_RGBA_8888 == cinfo.out_color_space) {
321 sc = SkScaledBitmapSampler::kRGBX;
322 //} else if (JCS_RGB_565 == cinfo.out_color_space) {
323 // sc = SkScaledBitmapSampler::kRGB_565;
324#endif
325 } else if (1 == cinfo.out_color_components &&
326 JCS_GRAYSCALE == cinfo.out_color_space) {
327 sc = SkScaledBitmapSampler::kGray;
328 } else {
329 return return_false(cinfo, *bm, "jpeg colorspace");
330 }
331
332 SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height,
333 sampleSize);
334
335 bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
336 // jpegs are always opauqe (i.e. have no per-pixel alpha)
337 bm->setIsOpaque(true);
338
339 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
340 return true;
341 }
342 if (!this->allocPixelRef(bm, NULL)) {
343 return return_false(cinfo, *bm, "allocPixelRef");
344 }
345
346 SkAutoLockPixels alp(*bm);
347 if (!sampler.begin(bm, sc, this->getDitherImage())) {
348 return return_false(cinfo, *bm, "sampler.begin");
349 }
350
351 uint8_t* srcRow = (uint8_t*)srcStorage.alloc(cinfo.output_width * 4);
352
353 // Possibly skip initial rows [sampler.srcY0]
354 if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) {
355 return return_false(cinfo, *bm, "skip rows");
356 }
357
358 // now loop through scanlines until y == bm->height() - 1
359 for (int y = 0;; y++) {
360 JSAMPLE* rowptr = (JSAMPLE*)srcRow;
361 int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
362 if (0 == row_count) {
363 return return_false(cinfo, *bm, "read_scanlines");
364 }
365 if (this->shouldCancelDecode()) {
366 return return_false(cinfo, *bm, "shouldCancelDecode");
367 }
368
369 sampler.next(srcRow);
370 if (bm->height() - 1 == y) {
371 // we're done
372 break;
373 }
374
375 if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) {
376 return return_false(cinfo, *bm, "skip rows");
377 }
378 }
379
380 // we formally skip the rest, so we don't get a complaint from libjpeg
381 if (!skip_src_rows(&cinfo, srcRow,
382 cinfo.output_height - cinfo.output_scanline)) {
383 return return_false(cinfo, *bm, "skip rows");
384 }
385 jpeg_finish_decompress(&cinfo);
386
387// SkDebugf("------------------- bm2 size %d [%d %d] %d\n", bm->getSize(), bm->width(), bm->height(), bm->config());
388 return true;
389}
390
391///////////////////////////////////////////////////////////////////////////////
392
reed@android.com8a1c16f2008-12-17 15:59:43 +0000393#include "SkColorPriv.h"
394
395// taken from jcolor.c in libjpeg
396#if 0 // 16bit - precise but slow
397 #define CYR 19595 // 0.299
398 #define CYG 38470 // 0.587
399 #define CYB 7471 // 0.114
400
401 #define CUR -11059 // -0.16874
402 #define CUG -21709 // -0.33126
403 #define CUB 32768 // 0.5
404
405 #define CVR 32768 // 0.5
406 #define CVG -27439 // -0.41869
407 #define CVB -5329 // -0.08131
408
409 #define CSHIFT 16
410#else // 8bit - fast, slightly less precise
411 #define CYR 77 // 0.299
412 #define CYG 150 // 0.587
413 #define CYB 29 // 0.114
414
415 #define CUR -43 // -0.16874
416 #define CUG -85 // -0.33126
417 #define CUB 128 // 0.5
418
419 #define CVR 128 // 0.5
420 #define CVG -107 // -0.41869
421 #define CVB -21 // -0.08131
422
423 #define CSHIFT 8
424#endif
425
426static void rgb2yuv_32(uint8_t dst[], SkPMColor c) {
427 int r = SkGetPackedR32(c);
428 int g = SkGetPackedG32(c);
429 int b = SkGetPackedB32(c);
430
431 int y = ( CYR*r + CYG*g + CYB*b ) >> CSHIFT;
432 int u = ( CUR*r + CUG*g + CUB*b ) >> CSHIFT;
433 int v = ( CVR*r + CVG*g + CVB*b ) >> CSHIFT;
434
435 dst[0] = SkToU8(y);
436 dst[1] = SkToU8(u + 128);
437 dst[2] = SkToU8(v + 128);
438}
439
440static void rgb2yuv_4444(uint8_t dst[], U16CPU c) {
441 int r = SkGetPackedR4444(c);
442 int g = SkGetPackedG4444(c);
443 int b = SkGetPackedB4444(c);
444
445 int y = ( CYR*r + CYG*g + CYB*b ) >> (CSHIFT - 4);
446 int u = ( CUR*r + CUG*g + CUB*b ) >> (CSHIFT - 4);
447 int v = ( CVR*r + CVG*g + CVB*b ) >> (CSHIFT - 4);
448
449 dst[0] = SkToU8(y);
450 dst[1] = SkToU8(u + 128);
451 dst[2] = SkToU8(v + 128);
452}
453
454static void rgb2yuv_16(uint8_t dst[], U16CPU c) {
455 int r = SkGetPackedR16(c);
456 int g = SkGetPackedG16(c);
457 int b = SkGetPackedB16(c);
458
459 int y = ( 2*CYR*r + CYG*g + 2*CYB*b ) >> (CSHIFT - 2);
460 int u = ( 2*CUR*r + CUG*g + 2*CUB*b ) >> (CSHIFT - 2);
461 int v = ( 2*CVR*r + CVG*g + 2*CVB*b ) >> (CSHIFT - 2);
462
463 dst[0] = SkToU8(y);
464 dst[1] = SkToU8(u + 128);
465 dst[2] = SkToU8(v + 128);
466}
467
468///////////////////////////////////////////////////////////////////////////////
469
470typedef void (*WriteScanline)(uint8_t* SK_RESTRICT dst,
471 const void* SK_RESTRICT src, int width,
472 const SkPMColor* SK_RESTRICT ctable);
473
474static void Write_32_YUV(uint8_t* SK_RESTRICT dst,
475 const void* SK_RESTRICT srcRow, int width,
476 const SkPMColor*) {
477 const uint32_t* SK_RESTRICT src = (const uint32_t*)srcRow;
478 while (--width >= 0) {
479#ifdef WE_CONVERT_TO_YUV
480 rgb2yuv_32(dst, *src++);
481#else
482 uint32_t c = *src++;
483 dst[0] = SkGetPackedR32(c);
484 dst[1] = SkGetPackedG32(c);
485 dst[2] = SkGetPackedB32(c);
486#endif
487 dst += 3;
488 }
489}
490
491static void Write_4444_YUV(uint8_t* SK_RESTRICT dst,
492 const void* SK_RESTRICT srcRow, int width,
493 const SkPMColor*) {
494 const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)srcRow;
495 while (--width >= 0) {
496#ifdef WE_CONVERT_TO_YUV
497 rgb2yuv_4444(dst, *src++);
498#else
499 SkPMColor16 c = *src++;
500 dst[0] = SkPacked4444ToR32(c);
501 dst[1] = SkPacked4444ToG32(c);
502 dst[2] = SkPacked4444ToB32(c);
503#endif
504 dst += 3;
505 }
506}
507
508static void Write_16_YUV(uint8_t* SK_RESTRICT dst,
509 const void* SK_RESTRICT srcRow, int width,
510 const SkPMColor*) {
511 const uint16_t* SK_RESTRICT src = (const uint16_t*)srcRow;
512 while (--width >= 0) {
513#ifdef WE_CONVERT_TO_YUV
514 rgb2yuv_16(dst, *src++);
515#else
516 uint16_t c = *src++;
517 dst[0] = SkPacked16ToR32(c);
518 dst[1] = SkPacked16ToG32(c);
519 dst[2] = SkPacked16ToB32(c);
520#endif
521 dst += 3;
522 }
523}
524
525static void Write_Index_YUV(uint8_t* SK_RESTRICT dst,
526 const void* SK_RESTRICT srcRow, int width,
527 const SkPMColor* SK_RESTRICT ctable) {
528 const uint8_t* SK_RESTRICT src = (const uint8_t*)srcRow;
529 while (--width >= 0) {
530#ifdef WE_CONVERT_TO_YUV
531 rgb2yuv_32(dst, ctable[*src++]);
532#else
533 uint32_t c = ctable[*src++];
534 dst[0] = SkGetPackedR32(c);
535 dst[1] = SkGetPackedG32(c);
536 dst[2] = SkGetPackedB32(c);
537#endif
538 dst += 3;
539 }
540}
541
542static WriteScanline ChooseWriter(const SkBitmap& bm) {
543 switch (bm.config()) {
544 case SkBitmap::kARGB_8888_Config:
545 return Write_32_YUV;
546 case SkBitmap::kRGB_565_Config:
547 return Write_16_YUV;
548 case SkBitmap::kARGB_4444_Config:
549 return Write_4444_YUV;
550 case SkBitmap::kIndex8_Config:
551 return Write_Index_YUV;
552 default:
553 return NULL;
554 }
555}
556
reed@android.com8a1c16f2008-12-17 15:59:43 +0000557class SkJPEGImageEncoder : public SkImageEncoder {
558protected:
559 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) {
560#ifdef TIME_ENCODE
561 AutoTimeMillis atm("JPEG Encode");
562#endif
563
564 const WriteScanline writer = ChooseWriter(bm);
565 if (NULL == writer) {
566 return false;
567 }
568
569 SkAutoLockPixels alp(bm);
570 if (NULL == bm.getPixels()) {
571 return false;
572 }
573
574 jpeg_compress_struct cinfo;
reed@android.com6f598152010-01-21 15:34:19 +0000575 skjpeg_error_mgr sk_err;
576 skjpeg_destination_mgr sk_wstream(stream);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577
578 // allocate these before set call setjmp
579 SkAutoMalloc oneRow;
580 SkAutoLockColors ctLocker;
581
582 cinfo.err = jpeg_std_error(&sk_err);
reed@android.com6f598152010-01-21 15:34:19 +0000583 sk_err.error_exit = skjpeg_error_exit;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000584 if (setjmp(sk_err.fJmpBuf)) {
585 return false;
586 }
587 jpeg_create_compress(&cinfo);
588
589 cinfo.dest = &sk_wstream;
590 cinfo.image_width = bm.width();
591 cinfo.image_height = bm.height();
592 cinfo.input_components = 3;
593#ifdef WE_CONVERT_TO_YUV
594 cinfo.in_color_space = JCS_YCbCr;
595#else
596 cinfo.in_color_space = JCS_RGB;
597#endif
598 cinfo.input_gamma = 1;
599
600 jpeg_set_defaults(&cinfo);
601 jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
602 cinfo.dct_method = JDCT_IFAST;
603
604 jpeg_start_compress(&cinfo, TRUE);
605
606 const int width = bm.width();
607 uint8_t* oneRowP = (uint8_t*)oneRow.alloc(width * 3);
608
609 const SkPMColor* colors = ctLocker.lockColors(bm);
610 const void* srcRow = bm.getPixels();
611
612 while (cinfo.next_scanline < cinfo.image_height) {
613 JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
614
615 writer(oneRowP, srcRow, width, colors);
616 row_pointer[0] = oneRowP;
617 (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
618 srcRow = (const void*)((const char*)srcRow + bm.rowBytes());
619 }
620
621 jpeg_finish_compress(&cinfo);
622 jpeg_destroy_compress(&cinfo);
623
624 return true;
625 }
626};
627
reed@android.com00bf85a2009-01-22 13:04:56 +0000628///////////////////////////////////////////////////////////////////////////////
629
630#include "SkTRegistry.h"
631
632static SkImageDecoder* DFactory(SkStream* stream) {
633 static const char gHeader[] = { 0xFF, 0xD8, 0xFF };
634 static const size_t HEADER_SIZE = sizeof(gHeader);
635
636 char buffer[HEADER_SIZE];
637 size_t len = stream->read(buffer, HEADER_SIZE);
638
639 if (len != HEADER_SIZE) {
640 return NULL; // can't read enough
641 }
642 if (memcmp(buffer, gHeader, HEADER_SIZE)) {
643 return NULL;
644 }
645 return SkNEW(SkJPEGImageDecoder);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646}
647
reed@android.com00bf85a2009-01-22 13:04:56 +0000648static SkImageEncoder* EFactory(SkImageEncoder::Type t) {
649 return (SkImageEncoder::kJPEG_Type == t) ? SkNEW(SkJPEGImageEncoder) : NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650}
651
reed@android.com00bf85a2009-01-22 13:04:56 +0000652static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(DFactory);
653static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(EFactory);