Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* |
michael@0 | 2 | * Copyright 2007 The Android Open Source Project |
michael@0 | 3 | * |
michael@0 | 4 | * Use of this source code is governed by a BSD-style license that can be |
michael@0 | 5 | * found in the LICENSE file. |
michael@0 | 6 | */ |
michael@0 | 7 | |
michael@0 | 8 | |
michael@0 | 9 | #include "SkImageDecoder.h" |
michael@0 | 10 | #include "SkImageEncoder.h" |
michael@0 | 11 | #include "SkJpegUtility.h" |
michael@0 | 12 | #include "SkColorPriv.h" |
michael@0 | 13 | #include "SkDither.h" |
michael@0 | 14 | #include "SkScaledBitmapSampler.h" |
michael@0 | 15 | #include "SkStream.h" |
michael@0 | 16 | #include "SkTemplates.h" |
michael@0 | 17 | #include "SkTime.h" |
michael@0 | 18 | #include "SkUtils.h" |
michael@0 | 19 | #include "SkRTConf.h" |
michael@0 | 20 | #include "SkRect.h" |
michael@0 | 21 | #include "SkCanvas.h" |
michael@0 | 22 | |
michael@0 | 23 | |
michael@0 | 24 | #include <stdio.h> |
michael@0 | 25 | extern "C" { |
michael@0 | 26 | #include "jpeglib.h" |
michael@0 | 27 | #include "jerror.h" |
michael@0 | 28 | } |
michael@0 | 29 | |
michael@0 | 30 | // These enable timing code that report milliseconds for an encoding/decoding |
michael@0 | 31 | //#define TIME_ENCODE |
michael@0 | 32 | //#define TIME_DECODE |
michael@0 | 33 | |
michael@0 | 34 | // this enables our rgb->yuv code, which is faster than libjpeg on ARM |
michael@0 | 35 | #define WE_CONVERT_TO_YUV |
michael@0 | 36 | |
michael@0 | 37 | // If ANDROID_RGB is defined by in the jpeg headers it indicates that jpeg offers |
michael@0 | 38 | // support for two additional formats (1) JCS_RGBA_8888 and (2) JCS_RGB_565. |
michael@0 | 39 | |
michael@0 | 40 | #if defined(SK_DEBUG) |
michael@0 | 41 | #define DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_WARNINGS false |
michael@0 | 42 | #define DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_ERRORS false |
michael@0 | 43 | #else // !defined(SK_DEBUG) |
michael@0 | 44 | #define DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_WARNINGS true |
michael@0 | 45 | #define DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_ERRORS true |
michael@0 | 46 | #endif // defined(SK_DEBUG) |
michael@0 | 47 | SK_CONF_DECLARE(bool, c_suppressJPEGImageDecoderWarnings, |
michael@0 | 48 | "images.jpeg.suppressDecoderWarnings", |
michael@0 | 49 | DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_WARNINGS, |
michael@0 | 50 | "Suppress most JPG warnings when calling decode functions."); |
michael@0 | 51 | SK_CONF_DECLARE(bool, c_suppressJPEGImageDecoderErrors, |
michael@0 | 52 | "images.jpeg.suppressDecoderErrors", |
michael@0 | 53 | DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_ERRORS, |
michael@0 | 54 | "Suppress most JPG error messages when decode " |
michael@0 | 55 | "function fails."); |
michael@0 | 56 | |
michael@0 | 57 | ////////////////////////////////////////////////////////////////////////// |
michael@0 | 58 | ////////////////////////////////////////////////////////////////////////// |
michael@0 | 59 | |
michael@0 | 60 | static void overwrite_mem_buffer_size(jpeg_decompress_struct* cinfo) { |
michael@0 | 61 | #ifdef SK_BUILD_FOR_ANDROID |
michael@0 | 62 | /* Check if the device indicates that it has a large amount of system memory |
michael@0 | 63 | * if so, increase the memory allocation to 30MB instead of the default 5MB. |
michael@0 | 64 | */ |
michael@0 | 65 | #ifdef ANDROID_LARGE_MEMORY_DEVICE |
michael@0 | 66 | cinfo->mem->max_memory_to_use = 30 * 1024 * 1024; |
michael@0 | 67 | #else |
michael@0 | 68 | cinfo->mem->max_memory_to_use = 5 * 1024 * 1024; |
michael@0 | 69 | #endif |
michael@0 | 70 | #endif // SK_BUILD_FOR_ANDROID |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | ////////////////////////////////////////////////////////////////////////// |
michael@0 | 74 | ////////////////////////////////////////////////////////////////////////// |
michael@0 | 75 | |
michael@0 | 76 | static void do_nothing_emit_message(jpeg_common_struct*, int) { |
michael@0 | 77 | /* do nothing */ |
michael@0 | 78 | } |
michael@0 | 79 | static void do_nothing_output_message(j_common_ptr) { |
michael@0 | 80 | /* do nothing */ |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | static void initialize_info(jpeg_decompress_struct* cinfo, skjpeg_source_mgr* src_mgr) { |
michael@0 | 84 | SkASSERT(cinfo != NULL); |
michael@0 | 85 | SkASSERT(src_mgr != NULL); |
michael@0 | 86 | jpeg_create_decompress(cinfo); |
michael@0 | 87 | overwrite_mem_buffer_size(cinfo); |
michael@0 | 88 | cinfo->src = src_mgr; |
michael@0 | 89 | /* To suppress warnings with a SK_DEBUG binary, set the |
michael@0 | 90 | * environment variable "skia_images_jpeg_suppressDecoderWarnings" |
michael@0 | 91 | * to "true". Inside a program that links to skia: |
michael@0 | 92 | * SK_CONF_SET("images.jpeg.suppressDecoderWarnings", true); */ |
michael@0 | 93 | if (c_suppressJPEGImageDecoderWarnings) { |
michael@0 | 94 | cinfo->err->emit_message = &do_nothing_emit_message; |
michael@0 | 95 | } |
michael@0 | 96 | /* To suppress error messages with a SK_DEBUG binary, set the |
michael@0 | 97 | * environment variable "skia_images_jpeg_suppressDecoderErrors" |
michael@0 | 98 | * to "true". Inside a program that links to skia: |
michael@0 | 99 | * SK_CONF_SET("images.jpeg.suppressDecoderErrors", true); */ |
michael@0 | 100 | if (c_suppressJPEGImageDecoderErrors) { |
michael@0 | 101 | cinfo->err->output_message = &do_nothing_output_message; |
michael@0 | 102 | } |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | #ifdef SK_BUILD_FOR_ANDROID |
michael@0 | 106 | class SkJPEGImageIndex { |
michael@0 | 107 | public: |
michael@0 | 108 | SkJPEGImageIndex(SkStreamRewindable* stream, SkImageDecoder* decoder) |
michael@0 | 109 | : fSrcMgr(stream, decoder) |
michael@0 | 110 | , fInfoInitialized(false) |
michael@0 | 111 | , fHuffmanCreated(false) |
michael@0 | 112 | , fDecompressStarted(false) |
michael@0 | 113 | { |
michael@0 | 114 | SkDEBUGCODE(fReadHeaderSucceeded = false;) |
michael@0 | 115 | } |
michael@0 | 116 | |
michael@0 | 117 | ~SkJPEGImageIndex() { |
michael@0 | 118 | if (fHuffmanCreated) { |
michael@0 | 119 | // Set to false before calling the libjpeg function, in case |
michael@0 | 120 | // the libjpeg function calls longjmp. Our setjmp handler may |
michael@0 | 121 | // attempt to delete this SkJPEGImageIndex, thus entering this |
michael@0 | 122 | // destructor again. Setting fHuffmanCreated to false first |
michael@0 | 123 | // prevents an infinite loop. |
michael@0 | 124 | fHuffmanCreated = false; |
michael@0 | 125 | jpeg_destroy_huffman_index(&fHuffmanIndex); |
michael@0 | 126 | } |
michael@0 | 127 | if (fDecompressStarted) { |
michael@0 | 128 | // Like fHuffmanCreated, set to false before calling libjpeg |
michael@0 | 129 | // function to prevent potential infinite loop. |
michael@0 | 130 | fDecompressStarted = false; |
michael@0 | 131 | jpeg_finish_decompress(&fCInfo); |
michael@0 | 132 | } |
michael@0 | 133 | if (fInfoInitialized) { |
michael@0 | 134 | this->destroyInfo(); |
michael@0 | 135 | } |
michael@0 | 136 | } |
michael@0 | 137 | |
michael@0 | 138 | /** |
michael@0 | 139 | * Destroy the cinfo struct. |
michael@0 | 140 | * After this call, if a huffman index was already built, it |
michael@0 | 141 | * can be used after calling initializeInfoAndReadHeader |
michael@0 | 142 | * again. Must not be called after startTileDecompress except |
michael@0 | 143 | * in the destructor. |
michael@0 | 144 | */ |
michael@0 | 145 | void destroyInfo() { |
michael@0 | 146 | SkASSERT(fInfoInitialized); |
michael@0 | 147 | SkASSERT(!fDecompressStarted); |
michael@0 | 148 | // Like fHuffmanCreated, set to false before calling libjpeg |
michael@0 | 149 | // function to prevent potential infinite loop. |
michael@0 | 150 | fInfoInitialized = false; |
michael@0 | 151 | jpeg_destroy_decompress(&fCInfo); |
michael@0 | 152 | SkDEBUGCODE(fReadHeaderSucceeded = false;) |
michael@0 | 153 | } |
michael@0 | 154 | |
michael@0 | 155 | /** |
michael@0 | 156 | * Initialize the cinfo struct. |
michael@0 | 157 | * Calls jpeg_create_decompress, makes customizations, and |
michael@0 | 158 | * finally calls jpeg_read_header. Returns true if jpeg_read_header |
michael@0 | 159 | * returns JPEG_HEADER_OK. |
michael@0 | 160 | * If cinfo was already initialized, destroyInfo must be called to |
michael@0 | 161 | * destroy the old one. Must not be called after startTileDecompress. |
michael@0 | 162 | */ |
michael@0 | 163 | bool initializeInfoAndReadHeader() { |
michael@0 | 164 | SkASSERT(!fInfoInitialized && !fDecompressStarted); |
michael@0 | 165 | initialize_info(&fCInfo, &fSrcMgr); |
michael@0 | 166 | fInfoInitialized = true; |
michael@0 | 167 | const bool success = (JPEG_HEADER_OK == jpeg_read_header(&fCInfo, true)); |
michael@0 | 168 | SkDEBUGCODE(fReadHeaderSucceeded = success;) |
michael@0 | 169 | return success; |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | jpeg_decompress_struct* cinfo() { return &fCInfo; } |
michael@0 | 173 | |
michael@0 | 174 | huffman_index* huffmanIndex() { return &fHuffmanIndex; } |
michael@0 | 175 | |
michael@0 | 176 | /** |
michael@0 | 177 | * Build the index to be used for tile based decoding. |
michael@0 | 178 | * Must only be called after a successful call to |
michael@0 | 179 | * initializeInfoAndReadHeader and must not be called more |
michael@0 | 180 | * than once. |
michael@0 | 181 | */ |
michael@0 | 182 | bool buildHuffmanIndex() { |
michael@0 | 183 | SkASSERT(fReadHeaderSucceeded); |
michael@0 | 184 | SkASSERT(!fHuffmanCreated); |
michael@0 | 185 | jpeg_create_huffman_index(&fCInfo, &fHuffmanIndex); |
michael@0 | 186 | SkASSERT(1 == fCInfo.scale_num && 1 == fCInfo.scale_denom); |
michael@0 | 187 | fHuffmanCreated = jpeg_build_huffman_index(&fCInfo, &fHuffmanIndex); |
michael@0 | 188 | return fHuffmanCreated; |
michael@0 | 189 | } |
michael@0 | 190 | |
michael@0 | 191 | /** |
michael@0 | 192 | * Start tile based decoding. Must only be called after a |
michael@0 | 193 | * successful call to buildHuffmanIndex, and must only be |
michael@0 | 194 | * called once. |
michael@0 | 195 | */ |
michael@0 | 196 | bool startTileDecompress() { |
michael@0 | 197 | SkASSERT(fHuffmanCreated); |
michael@0 | 198 | SkASSERT(fReadHeaderSucceeded); |
michael@0 | 199 | SkASSERT(!fDecompressStarted); |
michael@0 | 200 | if (jpeg_start_tile_decompress(&fCInfo)) { |
michael@0 | 201 | fDecompressStarted = true; |
michael@0 | 202 | return true; |
michael@0 | 203 | } |
michael@0 | 204 | return false; |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | private: |
michael@0 | 208 | skjpeg_source_mgr fSrcMgr; |
michael@0 | 209 | jpeg_decompress_struct fCInfo; |
michael@0 | 210 | huffman_index fHuffmanIndex; |
michael@0 | 211 | bool fInfoInitialized; |
michael@0 | 212 | bool fHuffmanCreated; |
michael@0 | 213 | bool fDecompressStarted; |
michael@0 | 214 | SkDEBUGCODE(bool fReadHeaderSucceeded;) |
michael@0 | 215 | }; |
michael@0 | 216 | #endif |
michael@0 | 217 | |
michael@0 | 218 | class SkJPEGImageDecoder : public SkImageDecoder { |
michael@0 | 219 | public: |
michael@0 | 220 | #ifdef SK_BUILD_FOR_ANDROID |
michael@0 | 221 | SkJPEGImageDecoder() { |
michael@0 | 222 | fImageIndex = NULL; |
michael@0 | 223 | fImageWidth = 0; |
michael@0 | 224 | fImageHeight = 0; |
michael@0 | 225 | } |
michael@0 | 226 | |
michael@0 | 227 | virtual ~SkJPEGImageDecoder() { |
michael@0 | 228 | SkDELETE(fImageIndex); |
michael@0 | 229 | } |
michael@0 | 230 | #endif |
michael@0 | 231 | |
michael@0 | 232 | virtual Format getFormat() const { |
michael@0 | 233 | return kJPEG_Format; |
michael@0 | 234 | } |
michael@0 | 235 | |
michael@0 | 236 | protected: |
michael@0 | 237 | #ifdef SK_BUILD_FOR_ANDROID |
michael@0 | 238 | virtual bool onBuildTileIndex(SkStreamRewindable *stream, int *width, int *height) SK_OVERRIDE; |
michael@0 | 239 | virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& rect) SK_OVERRIDE; |
michael@0 | 240 | #endif |
michael@0 | 241 | virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE; |
michael@0 | 242 | |
michael@0 | 243 | private: |
michael@0 | 244 | #ifdef SK_BUILD_FOR_ANDROID |
michael@0 | 245 | SkJPEGImageIndex* fImageIndex; |
michael@0 | 246 | int fImageWidth; |
michael@0 | 247 | int fImageHeight; |
michael@0 | 248 | #endif |
michael@0 | 249 | |
michael@0 | 250 | /** |
michael@0 | 251 | * Determine the appropriate bitmap config and out_color_space based on |
michael@0 | 252 | * both the preference of the caller and the jpeg_color_space on the |
michael@0 | 253 | * jpeg_decompress_struct passed in. |
michael@0 | 254 | * Must be called after jpeg_read_header. |
michael@0 | 255 | */ |
michael@0 | 256 | SkBitmap::Config getBitmapConfig(jpeg_decompress_struct*); |
michael@0 | 257 | |
michael@0 | 258 | typedef SkImageDecoder INHERITED; |
michael@0 | 259 | }; |
michael@0 | 260 | |
michael@0 | 261 | ////////////////////////////////////////////////////////////////////////// |
michael@0 | 262 | |
michael@0 | 263 | /* Automatically clean up after throwing an exception */ |
michael@0 | 264 | class JPEGAutoClean { |
michael@0 | 265 | public: |
michael@0 | 266 | JPEGAutoClean(): cinfo_ptr(NULL) {} |
michael@0 | 267 | ~JPEGAutoClean() { |
michael@0 | 268 | if (cinfo_ptr) { |
michael@0 | 269 | jpeg_destroy_decompress(cinfo_ptr); |
michael@0 | 270 | } |
michael@0 | 271 | } |
michael@0 | 272 | void set(jpeg_decompress_struct* info) { |
michael@0 | 273 | cinfo_ptr = info; |
michael@0 | 274 | } |
michael@0 | 275 | private: |
michael@0 | 276 | jpeg_decompress_struct* cinfo_ptr; |
michael@0 | 277 | }; |
michael@0 | 278 | |
michael@0 | 279 | /////////////////////////////////////////////////////////////////////////////// |
michael@0 | 280 | |
michael@0 | 281 | /* If we need to better match the request, we might examine the image and |
michael@0 | 282 | output dimensions, and determine if the downsampling jpeg provided is |
michael@0 | 283 | not sufficient. If so, we can recompute a modified sampleSize value to |
michael@0 | 284 | make up the difference. |
michael@0 | 285 | |
michael@0 | 286 | To skip this additional scaling, just set sampleSize = 1; below. |
michael@0 | 287 | */ |
michael@0 | 288 | static int recompute_sampleSize(int sampleSize, |
michael@0 | 289 | const jpeg_decompress_struct& cinfo) { |
michael@0 | 290 | return sampleSize * cinfo.output_width / cinfo.image_width; |
michael@0 | 291 | } |
michael@0 | 292 | |
michael@0 | 293 | static bool valid_output_dimensions(const jpeg_decompress_struct& cinfo) { |
michael@0 | 294 | /* These are initialized to 0, so if they have non-zero values, we assume |
michael@0 | 295 | they are "valid" (i.e. have been computed by libjpeg) |
michael@0 | 296 | */ |
michael@0 | 297 | return 0 != cinfo.output_width && 0 != cinfo.output_height; |
michael@0 | 298 | } |
michael@0 | 299 | |
michael@0 | 300 | static bool skip_src_rows(jpeg_decompress_struct* cinfo, void* buffer, int count) { |
michael@0 | 301 | for (int i = 0; i < count; i++) { |
michael@0 | 302 | JSAMPLE* rowptr = (JSAMPLE*)buffer; |
michael@0 | 303 | int row_count = jpeg_read_scanlines(cinfo, &rowptr, 1); |
michael@0 | 304 | if (1 != row_count) { |
michael@0 | 305 | return false; |
michael@0 | 306 | } |
michael@0 | 307 | } |
michael@0 | 308 | return true; |
michael@0 | 309 | } |
michael@0 | 310 | |
michael@0 | 311 | #ifdef SK_BUILD_FOR_ANDROID |
michael@0 | 312 | static bool skip_src_rows_tile(jpeg_decompress_struct* cinfo, |
michael@0 | 313 | huffman_index *index, void* buffer, int count) { |
michael@0 | 314 | for (int i = 0; i < count; i++) { |
michael@0 | 315 | JSAMPLE* rowptr = (JSAMPLE*)buffer; |
michael@0 | 316 | int row_count = jpeg_read_tile_scanline(cinfo, index, &rowptr); |
michael@0 | 317 | if (1 != row_count) { |
michael@0 | 318 | return false; |
michael@0 | 319 | } |
michael@0 | 320 | } |
michael@0 | 321 | return true; |
michael@0 | 322 | } |
michael@0 | 323 | #endif |
michael@0 | 324 | |
michael@0 | 325 | // This guy exists just to aid in debugging, as it allows debuggers to just |
michael@0 | 326 | // set a break-point in one place to see all error exists. |
michael@0 | 327 | static bool return_false(const jpeg_decompress_struct& cinfo, |
michael@0 | 328 | const SkBitmap& bm, const char caller[]) { |
michael@0 | 329 | if (!(c_suppressJPEGImageDecoderErrors)) { |
michael@0 | 330 | char buffer[JMSG_LENGTH_MAX]; |
michael@0 | 331 | cinfo.err->format_message((const j_common_ptr)&cinfo, buffer); |
michael@0 | 332 | SkDebugf("libjpeg error %d <%s> from %s [%d %d]\n", |
michael@0 | 333 | cinfo.err->msg_code, buffer, caller, bm.width(), bm.height()); |
michael@0 | 334 | } |
michael@0 | 335 | return false; // must always return false |
michael@0 | 336 | } |
michael@0 | 337 | |
michael@0 | 338 | // Convert a scanline of CMYK samples to RGBX in place. Note that this |
michael@0 | 339 | // method moves the "scanline" pointer in its processing |
michael@0 | 340 | static void convert_CMYK_to_RGB(uint8_t* scanline, unsigned int width) { |
michael@0 | 341 | // At this point we've received CMYK pixels from libjpeg. We |
michael@0 | 342 | // perform a crude conversion to RGB (based on the formulae |
michael@0 | 343 | // from easyrgb.com): |
michael@0 | 344 | // CMYK -> CMY |
michael@0 | 345 | // C = ( C * (1 - K) + K ) // for each CMY component |
michael@0 | 346 | // CMY -> RGB |
michael@0 | 347 | // R = ( 1 - C ) * 255 // for each RGB component |
michael@0 | 348 | // Unfortunately we are seeing inverted CMYK so all the original terms |
michael@0 | 349 | // are 1-. This yields: |
michael@0 | 350 | // CMYK -> CMY |
michael@0 | 351 | // C = ( (1-C) * (1 - (1-K) + (1-K) ) -> C = 1 - C*K |
michael@0 | 352 | // The conversion from CMY->RGB remains the same |
michael@0 | 353 | for (unsigned int x = 0; x < width; ++x, scanline += 4) { |
michael@0 | 354 | scanline[0] = SkMulDiv255Round(scanline[0], scanline[3]); |
michael@0 | 355 | scanline[1] = SkMulDiv255Round(scanline[1], scanline[3]); |
michael@0 | 356 | scanline[2] = SkMulDiv255Round(scanline[2], scanline[3]); |
michael@0 | 357 | scanline[3] = 255; |
michael@0 | 358 | } |
michael@0 | 359 | } |
michael@0 | 360 | |
michael@0 | 361 | /** |
michael@0 | 362 | * Common code for setting the error manager. |
michael@0 | 363 | */ |
michael@0 | 364 | static void set_error_mgr(jpeg_decompress_struct* cinfo, skjpeg_error_mgr* errorManager) { |
michael@0 | 365 | SkASSERT(cinfo != NULL); |
michael@0 | 366 | SkASSERT(errorManager != NULL); |
michael@0 | 367 | cinfo->err = jpeg_std_error(errorManager); |
michael@0 | 368 | errorManager->error_exit = skjpeg_error_exit; |
michael@0 | 369 | } |
michael@0 | 370 | |
michael@0 | 371 | /** |
michael@0 | 372 | * Common code for turning off upsampling and smoothing. Turning these |
michael@0 | 373 | * off helps performance without showing noticable differences in the |
michael@0 | 374 | * resulting bitmap. |
michael@0 | 375 | */ |
michael@0 | 376 | static void turn_off_visual_optimizations(jpeg_decompress_struct* cinfo) { |
michael@0 | 377 | SkASSERT(cinfo != NULL); |
michael@0 | 378 | /* this gives about 30% performance improvement. In theory it may |
michael@0 | 379 | reduce the visual quality, in practice I'm not seeing a difference |
michael@0 | 380 | */ |
michael@0 | 381 | cinfo->do_fancy_upsampling = 0; |
michael@0 | 382 | |
michael@0 | 383 | /* this gives another few percents */ |
michael@0 | 384 | cinfo->do_block_smoothing = 0; |
michael@0 | 385 | } |
michael@0 | 386 | |
michael@0 | 387 | /** |
michael@0 | 388 | * Common code for setting the dct method. |
michael@0 | 389 | */ |
michael@0 | 390 | static void set_dct_method(const SkImageDecoder& decoder, jpeg_decompress_struct* cinfo) { |
michael@0 | 391 | SkASSERT(cinfo != NULL); |
michael@0 | 392 | #ifdef DCT_IFAST_SUPPORTED |
michael@0 | 393 | if (decoder.getPreferQualityOverSpeed()) { |
michael@0 | 394 | cinfo->dct_method = JDCT_ISLOW; |
michael@0 | 395 | } else { |
michael@0 | 396 | cinfo->dct_method = JDCT_IFAST; |
michael@0 | 397 | } |
michael@0 | 398 | #else |
michael@0 | 399 | cinfo->dct_method = JDCT_ISLOW; |
michael@0 | 400 | #endif |
michael@0 | 401 | } |
michael@0 | 402 | |
michael@0 | 403 | SkBitmap::Config SkJPEGImageDecoder::getBitmapConfig(jpeg_decompress_struct* cinfo) { |
michael@0 | 404 | SkASSERT(cinfo != NULL); |
michael@0 | 405 | |
michael@0 | 406 | SrcDepth srcDepth = k32Bit_SrcDepth; |
michael@0 | 407 | if (JCS_GRAYSCALE == cinfo->jpeg_color_space) { |
michael@0 | 408 | srcDepth = k8BitGray_SrcDepth; |
michael@0 | 409 | } |
michael@0 | 410 | |
michael@0 | 411 | SkBitmap::Config config = this->getPrefConfig(srcDepth, /*hasAlpha*/ false); |
michael@0 | 412 | switch (config) { |
michael@0 | 413 | case SkBitmap::kA8_Config: |
michael@0 | 414 | // Only respect A8 config if the original is grayscale, |
michael@0 | 415 | // in which case we will treat the grayscale as alpha |
michael@0 | 416 | // values. |
michael@0 | 417 | if (cinfo->jpeg_color_space != JCS_GRAYSCALE) { |
michael@0 | 418 | config = SkBitmap::kARGB_8888_Config; |
michael@0 | 419 | } |
michael@0 | 420 | break; |
michael@0 | 421 | case SkBitmap::kARGB_8888_Config: |
michael@0 | 422 | // Fall through. |
michael@0 | 423 | case SkBitmap::kARGB_4444_Config: |
michael@0 | 424 | // Fall through. |
michael@0 | 425 | case SkBitmap::kRGB_565_Config: |
michael@0 | 426 | // These are acceptable destination configs. |
michael@0 | 427 | break; |
michael@0 | 428 | default: |
michael@0 | 429 | // Force all other configs to 8888. |
michael@0 | 430 | config = SkBitmap::kARGB_8888_Config; |
michael@0 | 431 | break; |
michael@0 | 432 | } |
michael@0 | 433 | |
michael@0 | 434 | switch (cinfo->jpeg_color_space) { |
michael@0 | 435 | case JCS_CMYK: |
michael@0 | 436 | // Fall through. |
michael@0 | 437 | case JCS_YCCK: |
michael@0 | 438 | // libjpeg cannot convert from CMYK or YCCK to RGB - here we set up |
michael@0 | 439 | // so libjpeg will give us CMYK samples back and we will later |
michael@0 | 440 | // manually convert them to RGB |
michael@0 | 441 | cinfo->out_color_space = JCS_CMYK; |
michael@0 | 442 | break; |
michael@0 | 443 | case JCS_GRAYSCALE: |
michael@0 | 444 | if (SkBitmap::kA8_Config == config) { |
michael@0 | 445 | cinfo->out_color_space = JCS_GRAYSCALE; |
michael@0 | 446 | break; |
michael@0 | 447 | } |
michael@0 | 448 | // The data is JCS_GRAYSCALE, but the caller wants some sort of RGB |
michael@0 | 449 | // config. Fall through to set to the default. |
michael@0 | 450 | default: |
michael@0 | 451 | cinfo->out_color_space = JCS_RGB; |
michael@0 | 452 | break; |
michael@0 | 453 | } |
michael@0 | 454 | return config; |
michael@0 | 455 | } |
michael@0 | 456 | |
michael@0 | 457 | #ifdef ANDROID_RGB |
michael@0 | 458 | /** |
michael@0 | 459 | * Based on the config and dither mode, adjust out_color_space and |
michael@0 | 460 | * dither_mode of cinfo. |
michael@0 | 461 | */ |
michael@0 | 462 | static void adjust_out_color_space_and_dither(jpeg_decompress_struct* cinfo, |
michael@0 | 463 | SkBitmap::Config config, |
michael@0 | 464 | const SkImageDecoder& decoder) { |
michael@0 | 465 | SkASSERT(cinfo != NULL); |
michael@0 | 466 | cinfo->dither_mode = JDITHER_NONE; |
michael@0 | 467 | if (JCS_CMYK == cinfo->out_color_space) { |
michael@0 | 468 | return; |
michael@0 | 469 | } |
michael@0 | 470 | switch(config) { |
michael@0 | 471 | case SkBitmap::kARGB_8888_Config: |
michael@0 | 472 | cinfo->out_color_space = JCS_RGBA_8888; |
michael@0 | 473 | break; |
michael@0 | 474 | case SkBitmap::kRGB_565_Config: |
michael@0 | 475 | cinfo->out_color_space = JCS_RGB_565; |
michael@0 | 476 | if (decoder.getDitherImage()) { |
michael@0 | 477 | cinfo->dither_mode = JDITHER_ORDERED; |
michael@0 | 478 | } |
michael@0 | 479 | break; |
michael@0 | 480 | default: |
michael@0 | 481 | break; |
michael@0 | 482 | } |
michael@0 | 483 | } |
michael@0 | 484 | #endif |
michael@0 | 485 | |
michael@0 | 486 | |
michael@0 | 487 | /** |
michael@0 | 488 | Sets all pixels in given bitmap to SK_ColorWHITE for all rows >= y. |
michael@0 | 489 | Used when decoding fails partway through reading scanlines to fill |
michael@0 | 490 | remaining lines. */ |
michael@0 | 491 | static void fill_below_level(int y, SkBitmap* bitmap) { |
michael@0 | 492 | SkIRect rect = SkIRect::MakeLTRB(0, y, bitmap->width(), bitmap->height()); |
michael@0 | 493 | SkCanvas canvas(*bitmap); |
michael@0 | 494 | canvas.clipRect(SkRect::Make(rect)); |
michael@0 | 495 | canvas.drawColor(SK_ColorWHITE); |
michael@0 | 496 | } |
michael@0 | 497 | |
michael@0 | 498 | /** |
michael@0 | 499 | * Get the config and bytes per pixel of the source data. Return |
michael@0 | 500 | * whether the data is supported. |
michael@0 | 501 | */ |
michael@0 | 502 | static bool get_src_config(const jpeg_decompress_struct& cinfo, |
michael@0 | 503 | SkScaledBitmapSampler::SrcConfig* sc, |
michael@0 | 504 | int* srcBytesPerPixel) { |
michael@0 | 505 | SkASSERT(sc != NULL && srcBytesPerPixel != NULL); |
michael@0 | 506 | if (JCS_CMYK == cinfo.out_color_space) { |
michael@0 | 507 | // In this case we will manually convert the CMYK values to RGB |
michael@0 | 508 | *sc = SkScaledBitmapSampler::kRGBX; |
michael@0 | 509 | // The CMYK work-around relies on 4 components per pixel here |
michael@0 | 510 | *srcBytesPerPixel = 4; |
michael@0 | 511 | } else if (3 == cinfo.out_color_components && JCS_RGB == cinfo.out_color_space) { |
michael@0 | 512 | *sc = SkScaledBitmapSampler::kRGB; |
michael@0 | 513 | *srcBytesPerPixel = 3; |
michael@0 | 514 | #ifdef ANDROID_RGB |
michael@0 | 515 | } else if (JCS_RGBA_8888 == cinfo.out_color_space) { |
michael@0 | 516 | *sc = SkScaledBitmapSampler::kRGBX; |
michael@0 | 517 | *srcBytesPerPixel = 4; |
michael@0 | 518 | } else if (JCS_RGB_565 == cinfo.out_color_space) { |
michael@0 | 519 | *sc = SkScaledBitmapSampler::kRGB_565; |
michael@0 | 520 | *srcBytesPerPixel = 2; |
michael@0 | 521 | #endif |
michael@0 | 522 | } else if (1 == cinfo.out_color_components && |
michael@0 | 523 | JCS_GRAYSCALE == cinfo.out_color_space) { |
michael@0 | 524 | *sc = SkScaledBitmapSampler::kGray; |
michael@0 | 525 | *srcBytesPerPixel = 1; |
michael@0 | 526 | } else { |
michael@0 | 527 | return false; |
michael@0 | 528 | } |
michael@0 | 529 | return true; |
michael@0 | 530 | } |
michael@0 | 531 | |
michael@0 | 532 | bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { |
michael@0 | 533 | #ifdef TIME_DECODE |
michael@0 | 534 | SkAutoTime atm("JPEG Decode"); |
michael@0 | 535 | #endif |
michael@0 | 536 | |
michael@0 | 537 | JPEGAutoClean autoClean; |
michael@0 | 538 | |
michael@0 | 539 | jpeg_decompress_struct cinfo; |
michael@0 | 540 | skjpeg_source_mgr srcManager(stream, this); |
michael@0 | 541 | |
michael@0 | 542 | skjpeg_error_mgr errorManager; |
michael@0 | 543 | set_error_mgr(&cinfo, &errorManager); |
michael@0 | 544 | |
michael@0 | 545 | // All objects need to be instantiated before this setjmp call so that |
michael@0 | 546 | // they will be cleaned up properly if an error occurs. |
michael@0 | 547 | if (setjmp(errorManager.fJmpBuf)) { |
michael@0 | 548 | return return_false(cinfo, *bm, "setjmp"); |
michael@0 | 549 | } |
michael@0 | 550 | |
michael@0 | 551 | initialize_info(&cinfo, &srcManager); |
michael@0 | 552 | autoClean.set(&cinfo); |
michael@0 | 553 | |
michael@0 | 554 | int status = jpeg_read_header(&cinfo, true); |
michael@0 | 555 | if (status != JPEG_HEADER_OK) { |
michael@0 | 556 | return return_false(cinfo, *bm, "read_header"); |
michael@0 | 557 | } |
michael@0 | 558 | |
michael@0 | 559 | /* Try to fulfill the requested sampleSize. Since jpeg can do it (when it |
michael@0 | 560 | can) much faster that we, just use their num/denom api to approximate |
michael@0 | 561 | the size. |
michael@0 | 562 | */ |
michael@0 | 563 | int sampleSize = this->getSampleSize(); |
michael@0 | 564 | |
michael@0 | 565 | set_dct_method(*this, &cinfo); |
michael@0 | 566 | |
michael@0 | 567 | SkASSERT(1 == cinfo.scale_num); |
michael@0 | 568 | cinfo.scale_denom = sampleSize; |
michael@0 | 569 | |
michael@0 | 570 | turn_off_visual_optimizations(&cinfo); |
michael@0 | 571 | |
michael@0 | 572 | const SkBitmap::Config config = this->getBitmapConfig(&cinfo); |
michael@0 | 573 | |
michael@0 | 574 | #ifdef ANDROID_RGB |
michael@0 | 575 | adjust_out_color_space_and_dither(&cinfo, config, *this); |
michael@0 | 576 | #endif |
michael@0 | 577 | |
michael@0 | 578 | if (1 == sampleSize && SkImageDecoder::kDecodeBounds_Mode == mode) { |
michael@0 | 579 | // Assume an A8 bitmap is not opaque to avoid the check of each |
michael@0 | 580 | // individual pixel. It is very unlikely to be opaque, since |
michael@0 | 581 | // an opaque A8 bitmap would not be very interesting. |
michael@0 | 582 | // Otherwise, a jpeg image is opaque. |
michael@0 | 583 | return bm->setConfig(config, cinfo.image_width, cinfo.image_height, 0, |
michael@0 | 584 | SkBitmap::kA8_Config == config ? |
michael@0 | 585 | kPremul_SkAlphaType : kOpaque_SkAlphaType); |
michael@0 | 586 | } |
michael@0 | 587 | |
michael@0 | 588 | /* image_width and image_height are the original dimensions, available |
michael@0 | 589 | after jpeg_read_header(). To see the scaled dimensions, we have to call |
michael@0 | 590 | jpeg_start_decompress(), and then read output_width and output_height. |
michael@0 | 591 | */ |
michael@0 | 592 | if (!jpeg_start_decompress(&cinfo)) { |
michael@0 | 593 | /* If we failed here, we may still have enough information to return |
michael@0 | 594 | to the caller if they just wanted (subsampled bounds). If sampleSize |
michael@0 | 595 | was 1, then we would have already returned. Thus we just check if |
michael@0 | 596 | we're in kDecodeBounds_Mode, and that we have valid output sizes. |
michael@0 | 597 | |
michael@0 | 598 | One reason to fail here is that we have insufficient stream data |
michael@0 | 599 | to complete the setup. However, output dimensions seem to get |
michael@0 | 600 | computed very early, which is why this special check can pay off. |
michael@0 | 601 | */ |
michael@0 | 602 | if (SkImageDecoder::kDecodeBounds_Mode == mode && valid_output_dimensions(cinfo)) { |
michael@0 | 603 | SkScaledBitmapSampler smpl(cinfo.output_width, cinfo.output_height, |
michael@0 | 604 | recompute_sampleSize(sampleSize, cinfo)); |
michael@0 | 605 | // Assume an A8 bitmap is not opaque to avoid the check of each |
michael@0 | 606 | // individual pixel. It is very unlikely to be opaque, since |
michael@0 | 607 | // an opaque A8 bitmap would not be very interesting. |
michael@0 | 608 | // Otherwise, a jpeg image is opaque. |
michael@0 | 609 | return bm->setConfig(config, smpl.scaledWidth(), smpl.scaledHeight(), |
michael@0 | 610 | 0, SkBitmap::kA8_Config == config ? |
michael@0 | 611 | kPremul_SkAlphaType : kOpaque_SkAlphaType); |
michael@0 | 612 | } else { |
michael@0 | 613 | return return_false(cinfo, *bm, "start_decompress"); |
michael@0 | 614 | } |
michael@0 | 615 | } |
michael@0 | 616 | sampleSize = recompute_sampleSize(sampleSize, cinfo); |
michael@0 | 617 | |
michael@0 | 618 | // should we allow the Chooser (if present) to pick a config for us??? |
michael@0 | 619 | if (!this->chooseFromOneChoice(config, cinfo.output_width, cinfo.output_height)) { |
michael@0 | 620 | return return_false(cinfo, *bm, "chooseFromOneChoice"); |
michael@0 | 621 | } |
michael@0 | 622 | |
michael@0 | 623 | SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height, sampleSize); |
michael@0 | 624 | // Assume an A8 bitmap is not opaque to avoid the check of each |
michael@0 | 625 | // individual pixel. It is very unlikely to be opaque, since |
michael@0 | 626 | // an opaque A8 bitmap would not be very interesting. |
michael@0 | 627 | // Otherwise, a jpeg image is opaque. |
michael@0 | 628 | bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight(), 0, |
michael@0 | 629 | SkBitmap::kA8_Config != config ? kOpaque_SkAlphaType : kPremul_SkAlphaType); |
michael@0 | 630 | if (SkImageDecoder::kDecodeBounds_Mode == mode) { |
michael@0 | 631 | return true; |
michael@0 | 632 | } |
michael@0 | 633 | if (!this->allocPixelRef(bm, NULL)) { |
michael@0 | 634 | return return_false(cinfo, *bm, "allocPixelRef"); |
michael@0 | 635 | } |
michael@0 | 636 | |
michael@0 | 637 | SkAutoLockPixels alp(*bm); |
michael@0 | 638 | |
michael@0 | 639 | #ifdef ANDROID_RGB |
michael@0 | 640 | /* short-circuit the SkScaledBitmapSampler when possible, as this gives |
michael@0 | 641 | a significant performance boost. |
michael@0 | 642 | */ |
michael@0 | 643 | if (sampleSize == 1 && |
michael@0 | 644 | ((config == SkBitmap::kARGB_8888_Config && |
michael@0 | 645 | cinfo.out_color_space == JCS_RGBA_8888) || |
michael@0 | 646 | (config == SkBitmap::kRGB_565_Config && |
michael@0 | 647 | cinfo.out_color_space == JCS_RGB_565))) |
michael@0 | 648 | { |
michael@0 | 649 | JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels(); |
michael@0 | 650 | INT32 const bpr = bm->rowBytes(); |
michael@0 | 651 | |
michael@0 | 652 | while (cinfo.output_scanline < cinfo.output_height) { |
michael@0 | 653 | int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1); |
michael@0 | 654 | if (0 == row_count) { |
michael@0 | 655 | // if row_count == 0, then we didn't get a scanline, |
michael@0 | 656 | // so return early. We will return a partial image. |
michael@0 | 657 | fill_below_level(cinfo.output_scanline, bm); |
michael@0 | 658 | cinfo.output_scanline = cinfo.output_height; |
michael@0 | 659 | break; // Skip to jpeg_finish_decompress() |
michael@0 | 660 | } |
michael@0 | 661 | if (this->shouldCancelDecode()) { |
michael@0 | 662 | return return_false(cinfo, *bm, "shouldCancelDecode"); |
michael@0 | 663 | } |
michael@0 | 664 | rowptr += bpr; |
michael@0 | 665 | } |
michael@0 | 666 | jpeg_finish_decompress(&cinfo); |
michael@0 | 667 | return true; |
michael@0 | 668 | } |
michael@0 | 669 | #endif |
michael@0 | 670 | |
michael@0 | 671 | // check for supported formats |
michael@0 | 672 | SkScaledBitmapSampler::SrcConfig sc; |
michael@0 | 673 | int srcBytesPerPixel; |
michael@0 | 674 | |
michael@0 | 675 | if (!get_src_config(cinfo, &sc, &srcBytesPerPixel)) { |
michael@0 | 676 | return return_false(cinfo, *bm, "jpeg colorspace"); |
michael@0 | 677 | } |
michael@0 | 678 | |
michael@0 | 679 | if (!sampler.begin(bm, sc, *this)) { |
michael@0 | 680 | return return_false(cinfo, *bm, "sampler.begin"); |
michael@0 | 681 | } |
michael@0 | 682 | |
michael@0 | 683 | SkAutoMalloc srcStorage(cinfo.output_width * srcBytesPerPixel); |
michael@0 | 684 | uint8_t* srcRow = (uint8_t*)srcStorage.get(); |
michael@0 | 685 | |
michael@0 | 686 | // Possibly skip initial rows [sampler.srcY0] |
michael@0 | 687 | if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) { |
michael@0 | 688 | return return_false(cinfo, *bm, "skip rows"); |
michael@0 | 689 | } |
michael@0 | 690 | |
michael@0 | 691 | // now loop through scanlines until y == bm->height() - 1 |
michael@0 | 692 | for (int y = 0;; y++) { |
michael@0 | 693 | JSAMPLE* rowptr = (JSAMPLE*)srcRow; |
michael@0 | 694 | int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1); |
michael@0 | 695 | if (0 == row_count) { |
michael@0 | 696 | // if row_count == 0, then we didn't get a scanline, |
michael@0 | 697 | // so return early. We will return a partial image. |
michael@0 | 698 | fill_below_level(y, bm); |
michael@0 | 699 | cinfo.output_scanline = cinfo.output_height; |
michael@0 | 700 | break; // Skip to jpeg_finish_decompress() |
michael@0 | 701 | } |
michael@0 | 702 | if (this->shouldCancelDecode()) { |
michael@0 | 703 | return return_false(cinfo, *bm, "shouldCancelDecode"); |
michael@0 | 704 | } |
michael@0 | 705 | |
michael@0 | 706 | if (JCS_CMYK == cinfo.out_color_space) { |
michael@0 | 707 | convert_CMYK_to_RGB(srcRow, cinfo.output_width); |
michael@0 | 708 | } |
michael@0 | 709 | |
michael@0 | 710 | sampler.next(srcRow); |
michael@0 | 711 | if (bm->height() - 1 == y) { |
michael@0 | 712 | // we're done |
michael@0 | 713 | break; |
michael@0 | 714 | } |
michael@0 | 715 | |
michael@0 | 716 | if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) { |
michael@0 | 717 | return return_false(cinfo, *bm, "skip rows"); |
michael@0 | 718 | } |
michael@0 | 719 | } |
michael@0 | 720 | |
michael@0 | 721 | // we formally skip the rest, so we don't get a complaint from libjpeg |
michael@0 | 722 | if (!skip_src_rows(&cinfo, srcRow, |
michael@0 | 723 | cinfo.output_height - cinfo.output_scanline)) { |
michael@0 | 724 | return return_false(cinfo, *bm, "skip rows"); |
michael@0 | 725 | } |
michael@0 | 726 | jpeg_finish_decompress(&cinfo); |
michael@0 | 727 | |
michael@0 | 728 | return true; |
michael@0 | 729 | } |
michael@0 | 730 | |
michael@0 | 731 | #ifdef SK_BUILD_FOR_ANDROID |
michael@0 | 732 | bool SkJPEGImageDecoder::onBuildTileIndex(SkStreamRewindable* stream, int *width, int *height) { |
michael@0 | 733 | |
michael@0 | 734 | SkAutoTDelete<SkJPEGImageIndex> imageIndex(SkNEW_ARGS(SkJPEGImageIndex, (stream, this))); |
michael@0 | 735 | jpeg_decompress_struct* cinfo = imageIndex->cinfo(); |
michael@0 | 736 | |
michael@0 | 737 | skjpeg_error_mgr sk_err; |
michael@0 | 738 | set_error_mgr(cinfo, &sk_err); |
michael@0 | 739 | |
michael@0 | 740 | // All objects need to be instantiated before this setjmp call so that |
michael@0 | 741 | // they will be cleaned up properly if an error occurs. |
michael@0 | 742 | if (setjmp(sk_err.fJmpBuf)) { |
michael@0 | 743 | return false; |
michael@0 | 744 | } |
michael@0 | 745 | |
michael@0 | 746 | // create the cinfo used to create/build the huffmanIndex |
michael@0 | 747 | if (!imageIndex->initializeInfoAndReadHeader()) { |
michael@0 | 748 | return false; |
michael@0 | 749 | } |
michael@0 | 750 | |
michael@0 | 751 | if (!imageIndex->buildHuffmanIndex()) { |
michael@0 | 752 | return false; |
michael@0 | 753 | } |
michael@0 | 754 | |
michael@0 | 755 | // destroy the cinfo used to create/build the huffman index |
michael@0 | 756 | imageIndex->destroyInfo(); |
michael@0 | 757 | |
michael@0 | 758 | // Init decoder to image decode mode |
michael@0 | 759 | if (!imageIndex->initializeInfoAndReadHeader()) { |
michael@0 | 760 | return false; |
michael@0 | 761 | } |
michael@0 | 762 | |
michael@0 | 763 | // FIXME: This sets cinfo->out_color_space, which we may change later |
michael@0 | 764 | // based on the config in onDecodeSubset. This should be fine, since |
michael@0 | 765 | // jpeg_init_read_tile_scanline will check out_color_space again after |
michael@0 | 766 | // that change (when it calls jinit_color_deconverter). |
michael@0 | 767 | (void) this->getBitmapConfig(cinfo); |
michael@0 | 768 | |
michael@0 | 769 | turn_off_visual_optimizations(cinfo); |
michael@0 | 770 | |
michael@0 | 771 | // instead of jpeg_start_decompress() we start a tiled decompress |
michael@0 | 772 | if (!imageIndex->startTileDecompress()) { |
michael@0 | 773 | return false; |
michael@0 | 774 | } |
michael@0 | 775 | |
michael@0 | 776 | SkASSERT(1 == cinfo->scale_num); |
michael@0 | 777 | fImageWidth = cinfo->output_width; |
michael@0 | 778 | fImageHeight = cinfo->output_height; |
michael@0 | 779 | |
michael@0 | 780 | if (width) { |
michael@0 | 781 | *width = fImageWidth; |
michael@0 | 782 | } |
michael@0 | 783 | if (height) { |
michael@0 | 784 | *height = fImageHeight; |
michael@0 | 785 | } |
michael@0 | 786 | |
michael@0 | 787 | SkDELETE(fImageIndex); |
michael@0 | 788 | fImageIndex = imageIndex.detach(); |
michael@0 | 789 | |
michael@0 | 790 | return true; |
michael@0 | 791 | } |
michael@0 | 792 | |
michael@0 | 793 | bool SkJPEGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) { |
michael@0 | 794 | if (NULL == fImageIndex) { |
michael@0 | 795 | return false; |
michael@0 | 796 | } |
michael@0 | 797 | jpeg_decompress_struct* cinfo = fImageIndex->cinfo(); |
michael@0 | 798 | |
michael@0 | 799 | SkIRect rect = SkIRect::MakeWH(fImageWidth, fImageHeight); |
michael@0 | 800 | if (!rect.intersect(region)) { |
michael@0 | 801 | // If the requested region is entirely outside the image return false |
michael@0 | 802 | return false; |
michael@0 | 803 | } |
michael@0 | 804 | |
michael@0 | 805 | |
michael@0 | 806 | skjpeg_error_mgr errorManager; |
michael@0 | 807 | set_error_mgr(cinfo, &errorManager); |
michael@0 | 808 | |
michael@0 | 809 | if (setjmp(errorManager.fJmpBuf)) { |
michael@0 | 810 | return false; |
michael@0 | 811 | } |
michael@0 | 812 | |
michael@0 | 813 | int requestedSampleSize = this->getSampleSize(); |
michael@0 | 814 | cinfo->scale_denom = requestedSampleSize; |
michael@0 | 815 | |
michael@0 | 816 | set_dct_method(*this, cinfo); |
michael@0 | 817 | |
michael@0 | 818 | const SkBitmap::Config config = this->getBitmapConfig(cinfo); |
michael@0 | 819 | #ifdef ANDROID_RGB |
michael@0 | 820 | adjust_out_color_space_and_dither(cinfo, config, *this); |
michael@0 | 821 | #endif |
michael@0 | 822 | |
michael@0 | 823 | int startX = rect.fLeft; |
michael@0 | 824 | int startY = rect.fTop; |
michael@0 | 825 | int width = rect.width(); |
michael@0 | 826 | int height = rect.height(); |
michael@0 | 827 | |
michael@0 | 828 | jpeg_init_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(), |
michael@0 | 829 | &startX, &startY, &width, &height); |
michael@0 | 830 | int skiaSampleSize = recompute_sampleSize(requestedSampleSize, *cinfo); |
michael@0 | 831 | int actualSampleSize = skiaSampleSize * (DCTSIZE / cinfo->min_DCT_scaled_size); |
michael@0 | 832 | |
michael@0 | 833 | SkScaledBitmapSampler sampler(width, height, skiaSampleSize); |
michael@0 | 834 | |
michael@0 | 835 | SkBitmap bitmap; |
michael@0 | 836 | bitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight()); |
michael@0 | 837 | // Assume an A8 bitmap is not opaque to avoid the check of each |
michael@0 | 838 | // individual pixel. It is very unlikely to be opaque, since |
michael@0 | 839 | // an opaque A8 bitmap would not be very interesting. |
michael@0 | 840 | // Otherwise, a jpeg image is opaque. |
michael@0 | 841 | bitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight(), 0, |
michael@0 | 842 | config == SkBitmap::kA8_Config ? kPremul_SkAlphaType : |
michael@0 | 843 | kOpaque_SkAlphaType); |
michael@0 | 844 | |
michael@0 | 845 | // Check ahead of time if the swap(dest, src) is possible or not. |
michael@0 | 846 | // If yes, then we will stick to AllocPixelRef since it's cheaper with the |
michael@0 | 847 | // swap happening. If no, then we will use alloc to allocate pixels to |
michael@0 | 848 | // prevent garbage collection. |
michael@0 | 849 | int w = rect.width() / actualSampleSize; |
michael@0 | 850 | int h = rect.height() / actualSampleSize; |
michael@0 | 851 | bool swapOnly = (rect == region) && bm->isNull() && |
michael@0 | 852 | (w == bitmap.width()) && (h == bitmap.height()) && |
michael@0 | 853 | ((startX - rect.x()) / actualSampleSize == 0) && |
michael@0 | 854 | ((startY - rect.y()) / actualSampleSize == 0); |
michael@0 | 855 | if (swapOnly) { |
michael@0 | 856 | if (!this->allocPixelRef(&bitmap, NULL)) { |
michael@0 | 857 | return return_false(*cinfo, bitmap, "allocPixelRef"); |
michael@0 | 858 | } |
michael@0 | 859 | } else { |
michael@0 | 860 | if (!bitmap.allocPixels()) { |
michael@0 | 861 | return return_false(*cinfo, bitmap, "allocPixels"); |
michael@0 | 862 | } |
michael@0 | 863 | } |
michael@0 | 864 | |
michael@0 | 865 | SkAutoLockPixels alp(bitmap); |
michael@0 | 866 | |
michael@0 | 867 | #ifdef ANDROID_RGB |
michael@0 | 868 | /* short-circuit the SkScaledBitmapSampler when possible, as this gives |
michael@0 | 869 | a significant performance boost. |
michael@0 | 870 | */ |
michael@0 | 871 | if (skiaSampleSize == 1 && |
michael@0 | 872 | ((config == SkBitmap::kARGB_8888_Config && |
michael@0 | 873 | cinfo->out_color_space == JCS_RGBA_8888) || |
michael@0 | 874 | (config == SkBitmap::kRGB_565_Config && |
michael@0 | 875 | cinfo->out_color_space == JCS_RGB_565))) |
michael@0 | 876 | { |
michael@0 | 877 | JSAMPLE* rowptr = (JSAMPLE*)bitmap.getPixels(); |
michael@0 | 878 | INT32 const bpr = bitmap.rowBytes(); |
michael@0 | 879 | int rowTotalCount = 0; |
michael@0 | 880 | |
michael@0 | 881 | while (rowTotalCount < height) { |
michael@0 | 882 | int rowCount = jpeg_read_tile_scanline(cinfo, |
michael@0 | 883 | fImageIndex->huffmanIndex(), |
michael@0 | 884 | &rowptr); |
michael@0 | 885 | // if rowCount == 0, then we didn't get a scanline, so abort. |
michael@0 | 886 | // onDecodeSubset() relies on onBuildTileIndex(), which |
michael@0 | 887 | // needs a complete image to succeed. |
michael@0 | 888 | if (0 == rowCount) { |
michael@0 | 889 | return return_false(*cinfo, bitmap, "read_scanlines"); |
michael@0 | 890 | } |
michael@0 | 891 | if (this->shouldCancelDecode()) { |
michael@0 | 892 | return return_false(*cinfo, bitmap, "shouldCancelDecode"); |
michael@0 | 893 | } |
michael@0 | 894 | rowTotalCount += rowCount; |
michael@0 | 895 | rowptr += bpr; |
michael@0 | 896 | } |
michael@0 | 897 | |
michael@0 | 898 | if (swapOnly) { |
michael@0 | 899 | bm->swap(bitmap); |
michael@0 | 900 | } else { |
michael@0 | 901 | cropBitmap(bm, &bitmap, actualSampleSize, region.x(), region.y(), |
michael@0 | 902 | region.width(), region.height(), startX, startY); |
michael@0 | 903 | } |
michael@0 | 904 | return true; |
michael@0 | 905 | } |
michael@0 | 906 | #endif |
michael@0 | 907 | |
michael@0 | 908 | // check for supported formats |
michael@0 | 909 | SkScaledBitmapSampler::SrcConfig sc; |
michael@0 | 910 | int srcBytesPerPixel; |
michael@0 | 911 | |
michael@0 | 912 | if (!get_src_config(*cinfo, &sc, &srcBytesPerPixel)) { |
michael@0 | 913 | return return_false(*cinfo, *bm, "jpeg colorspace"); |
michael@0 | 914 | } |
michael@0 | 915 | |
michael@0 | 916 | if (!sampler.begin(&bitmap, sc, *this)) { |
michael@0 | 917 | return return_false(*cinfo, bitmap, "sampler.begin"); |
michael@0 | 918 | } |
michael@0 | 919 | |
michael@0 | 920 | SkAutoMalloc srcStorage(width * srcBytesPerPixel); |
michael@0 | 921 | uint8_t* srcRow = (uint8_t*)srcStorage.get(); |
michael@0 | 922 | |
michael@0 | 923 | // Possibly skip initial rows [sampler.srcY0] |
michael@0 | 924 | if (!skip_src_rows_tile(cinfo, fImageIndex->huffmanIndex(), srcRow, sampler.srcY0())) { |
michael@0 | 925 | return return_false(*cinfo, bitmap, "skip rows"); |
michael@0 | 926 | } |
michael@0 | 927 | |
michael@0 | 928 | // now loop through scanlines until y == bitmap->height() - 1 |
michael@0 | 929 | for (int y = 0;; y++) { |
michael@0 | 930 | JSAMPLE* rowptr = (JSAMPLE*)srcRow; |
michael@0 | 931 | int row_count = jpeg_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(), &rowptr); |
michael@0 | 932 | // if row_count == 0, then we didn't get a scanline, so abort. |
michael@0 | 933 | // onDecodeSubset() relies on onBuildTileIndex(), which |
michael@0 | 934 | // needs a complete image to succeed. |
michael@0 | 935 | if (0 == row_count) { |
michael@0 | 936 | return return_false(*cinfo, bitmap, "read_scanlines"); |
michael@0 | 937 | } |
michael@0 | 938 | if (this->shouldCancelDecode()) { |
michael@0 | 939 | return return_false(*cinfo, bitmap, "shouldCancelDecode"); |
michael@0 | 940 | } |
michael@0 | 941 | |
michael@0 | 942 | if (JCS_CMYK == cinfo->out_color_space) { |
michael@0 | 943 | convert_CMYK_to_RGB(srcRow, width); |
michael@0 | 944 | } |
michael@0 | 945 | |
michael@0 | 946 | sampler.next(srcRow); |
michael@0 | 947 | if (bitmap.height() - 1 == y) { |
michael@0 | 948 | // we're done |
michael@0 | 949 | break; |
michael@0 | 950 | } |
michael@0 | 951 | |
michael@0 | 952 | if (!skip_src_rows_tile(cinfo, fImageIndex->huffmanIndex(), srcRow, |
michael@0 | 953 | sampler.srcDY() - 1)) { |
michael@0 | 954 | return return_false(*cinfo, bitmap, "skip rows"); |
michael@0 | 955 | } |
michael@0 | 956 | } |
michael@0 | 957 | if (swapOnly) { |
michael@0 | 958 | bm->swap(bitmap); |
michael@0 | 959 | } else { |
michael@0 | 960 | cropBitmap(bm, &bitmap, actualSampleSize, region.x(), region.y(), |
michael@0 | 961 | region.width(), region.height(), startX, startY); |
michael@0 | 962 | } |
michael@0 | 963 | return true; |
michael@0 | 964 | } |
michael@0 | 965 | #endif |
michael@0 | 966 | |
michael@0 | 967 | /////////////////////////////////////////////////////////////////////////////// |
michael@0 | 968 | |
michael@0 | 969 | #include "SkColorPriv.h" |
michael@0 | 970 | |
michael@0 | 971 | // taken from jcolor.c in libjpeg |
michael@0 | 972 | #if 0 // 16bit - precise but slow |
michael@0 | 973 | #define CYR 19595 // 0.299 |
michael@0 | 974 | #define CYG 38470 // 0.587 |
michael@0 | 975 | #define CYB 7471 // 0.114 |
michael@0 | 976 | |
michael@0 | 977 | #define CUR -11059 // -0.16874 |
michael@0 | 978 | #define CUG -21709 // -0.33126 |
michael@0 | 979 | #define CUB 32768 // 0.5 |
michael@0 | 980 | |
michael@0 | 981 | #define CVR 32768 // 0.5 |
michael@0 | 982 | #define CVG -27439 // -0.41869 |
michael@0 | 983 | #define CVB -5329 // -0.08131 |
michael@0 | 984 | |
michael@0 | 985 | #define CSHIFT 16 |
michael@0 | 986 | #else // 8bit - fast, slightly less precise |
michael@0 | 987 | #define CYR 77 // 0.299 |
michael@0 | 988 | #define CYG 150 // 0.587 |
michael@0 | 989 | #define CYB 29 // 0.114 |
michael@0 | 990 | |
michael@0 | 991 | #define CUR -43 // -0.16874 |
michael@0 | 992 | #define CUG -85 // -0.33126 |
michael@0 | 993 | #define CUB 128 // 0.5 |
michael@0 | 994 | |
michael@0 | 995 | #define CVR 128 // 0.5 |
michael@0 | 996 | #define CVG -107 // -0.41869 |
michael@0 | 997 | #define CVB -21 // -0.08131 |
michael@0 | 998 | |
michael@0 | 999 | #define CSHIFT 8 |
michael@0 | 1000 | #endif |
michael@0 | 1001 | |
michael@0 | 1002 | static void rgb2yuv_32(uint8_t dst[], SkPMColor c) { |
michael@0 | 1003 | int r = SkGetPackedR32(c); |
michael@0 | 1004 | int g = SkGetPackedG32(c); |
michael@0 | 1005 | int b = SkGetPackedB32(c); |
michael@0 | 1006 | |
michael@0 | 1007 | int y = ( CYR*r + CYG*g + CYB*b ) >> CSHIFT; |
michael@0 | 1008 | int u = ( CUR*r + CUG*g + CUB*b ) >> CSHIFT; |
michael@0 | 1009 | int v = ( CVR*r + CVG*g + CVB*b ) >> CSHIFT; |
michael@0 | 1010 | |
michael@0 | 1011 | dst[0] = SkToU8(y); |
michael@0 | 1012 | dst[1] = SkToU8(u + 128); |
michael@0 | 1013 | dst[2] = SkToU8(v + 128); |
michael@0 | 1014 | } |
michael@0 | 1015 | |
michael@0 | 1016 | static void rgb2yuv_4444(uint8_t dst[], U16CPU c) { |
michael@0 | 1017 | int r = SkGetPackedR4444(c); |
michael@0 | 1018 | int g = SkGetPackedG4444(c); |
michael@0 | 1019 | int b = SkGetPackedB4444(c); |
michael@0 | 1020 | |
michael@0 | 1021 | int y = ( CYR*r + CYG*g + CYB*b ) >> (CSHIFT - 4); |
michael@0 | 1022 | int u = ( CUR*r + CUG*g + CUB*b ) >> (CSHIFT - 4); |
michael@0 | 1023 | int v = ( CVR*r + CVG*g + CVB*b ) >> (CSHIFT - 4); |
michael@0 | 1024 | |
michael@0 | 1025 | dst[0] = SkToU8(y); |
michael@0 | 1026 | dst[1] = SkToU8(u + 128); |
michael@0 | 1027 | dst[2] = SkToU8(v + 128); |
michael@0 | 1028 | } |
michael@0 | 1029 | |
michael@0 | 1030 | static void rgb2yuv_16(uint8_t dst[], U16CPU c) { |
michael@0 | 1031 | int r = SkGetPackedR16(c); |
michael@0 | 1032 | int g = SkGetPackedG16(c); |
michael@0 | 1033 | int b = SkGetPackedB16(c); |
michael@0 | 1034 | |
michael@0 | 1035 | int y = ( 2*CYR*r + CYG*g + 2*CYB*b ) >> (CSHIFT - 2); |
michael@0 | 1036 | int u = ( 2*CUR*r + CUG*g + 2*CUB*b ) >> (CSHIFT - 2); |
michael@0 | 1037 | int v = ( 2*CVR*r + CVG*g + 2*CVB*b ) >> (CSHIFT - 2); |
michael@0 | 1038 | |
michael@0 | 1039 | dst[0] = SkToU8(y); |
michael@0 | 1040 | dst[1] = SkToU8(u + 128); |
michael@0 | 1041 | dst[2] = SkToU8(v + 128); |
michael@0 | 1042 | } |
michael@0 | 1043 | |
michael@0 | 1044 | /////////////////////////////////////////////////////////////////////////////// |
michael@0 | 1045 | |
michael@0 | 1046 | typedef void (*WriteScanline)(uint8_t* SK_RESTRICT dst, |
michael@0 | 1047 | const void* SK_RESTRICT src, int width, |
michael@0 | 1048 | const SkPMColor* SK_RESTRICT ctable); |
michael@0 | 1049 | |
michael@0 | 1050 | static void Write_32_YUV(uint8_t* SK_RESTRICT dst, |
michael@0 | 1051 | const void* SK_RESTRICT srcRow, int width, |
michael@0 | 1052 | const SkPMColor*) { |
michael@0 | 1053 | const uint32_t* SK_RESTRICT src = (const uint32_t*)srcRow; |
michael@0 | 1054 | while (--width >= 0) { |
michael@0 | 1055 | #ifdef WE_CONVERT_TO_YUV |
michael@0 | 1056 | rgb2yuv_32(dst, *src++); |
michael@0 | 1057 | #else |
michael@0 | 1058 | uint32_t c = *src++; |
michael@0 | 1059 | dst[0] = SkGetPackedR32(c); |
michael@0 | 1060 | dst[1] = SkGetPackedG32(c); |
michael@0 | 1061 | dst[2] = SkGetPackedB32(c); |
michael@0 | 1062 | #endif |
michael@0 | 1063 | dst += 3; |
michael@0 | 1064 | } |
michael@0 | 1065 | } |
michael@0 | 1066 | |
michael@0 | 1067 | static void Write_4444_YUV(uint8_t* SK_RESTRICT dst, |
michael@0 | 1068 | const void* SK_RESTRICT srcRow, int width, |
michael@0 | 1069 | const SkPMColor*) { |
michael@0 | 1070 | const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)srcRow; |
michael@0 | 1071 | while (--width >= 0) { |
michael@0 | 1072 | #ifdef WE_CONVERT_TO_YUV |
michael@0 | 1073 | rgb2yuv_4444(dst, *src++); |
michael@0 | 1074 | #else |
michael@0 | 1075 | SkPMColor16 c = *src++; |
michael@0 | 1076 | dst[0] = SkPacked4444ToR32(c); |
michael@0 | 1077 | dst[1] = SkPacked4444ToG32(c); |
michael@0 | 1078 | dst[2] = SkPacked4444ToB32(c); |
michael@0 | 1079 | #endif |
michael@0 | 1080 | dst += 3; |
michael@0 | 1081 | } |
michael@0 | 1082 | } |
michael@0 | 1083 | |
michael@0 | 1084 | static void Write_16_YUV(uint8_t* SK_RESTRICT dst, |
michael@0 | 1085 | const void* SK_RESTRICT srcRow, int width, |
michael@0 | 1086 | const SkPMColor*) { |
michael@0 | 1087 | const uint16_t* SK_RESTRICT src = (const uint16_t*)srcRow; |
michael@0 | 1088 | while (--width >= 0) { |
michael@0 | 1089 | #ifdef WE_CONVERT_TO_YUV |
michael@0 | 1090 | rgb2yuv_16(dst, *src++); |
michael@0 | 1091 | #else |
michael@0 | 1092 | uint16_t c = *src++; |
michael@0 | 1093 | dst[0] = SkPacked16ToR32(c); |
michael@0 | 1094 | dst[1] = SkPacked16ToG32(c); |
michael@0 | 1095 | dst[2] = SkPacked16ToB32(c); |
michael@0 | 1096 | #endif |
michael@0 | 1097 | dst += 3; |
michael@0 | 1098 | } |
michael@0 | 1099 | } |
michael@0 | 1100 | |
michael@0 | 1101 | static void Write_Index_YUV(uint8_t* SK_RESTRICT dst, |
michael@0 | 1102 | const void* SK_RESTRICT srcRow, int width, |
michael@0 | 1103 | const SkPMColor* SK_RESTRICT ctable) { |
michael@0 | 1104 | const uint8_t* SK_RESTRICT src = (const uint8_t*)srcRow; |
michael@0 | 1105 | while (--width >= 0) { |
michael@0 | 1106 | #ifdef WE_CONVERT_TO_YUV |
michael@0 | 1107 | rgb2yuv_32(dst, ctable[*src++]); |
michael@0 | 1108 | #else |
michael@0 | 1109 | uint32_t c = ctable[*src++]; |
michael@0 | 1110 | dst[0] = SkGetPackedR32(c); |
michael@0 | 1111 | dst[1] = SkGetPackedG32(c); |
michael@0 | 1112 | dst[2] = SkGetPackedB32(c); |
michael@0 | 1113 | #endif |
michael@0 | 1114 | dst += 3; |
michael@0 | 1115 | } |
michael@0 | 1116 | } |
michael@0 | 1117 | |
michael@0 | 1118 | static WriteScanline ChooseWriter(const SkBitmap& bm) { |
michael@0 | 1119 | switch (bm.config()) { |
michael@0 | 1120 | case SkBitmap::kARGB_8888_Config: |
michael@0 | 1121 | return Write_32_YUV; |
michael@0 | 1122 | case SkBitmap::kRGB_565_Config: |
michael@0 | 1123 | return Write_16_YUV; |
michael@0 | 1124 | case SkBitmap::kARGB_4444_Config: |
michael@0 | 1125 | return Write_4444_YUV; |
michael@0 | 1126 | case SkBitmap::kIndex8_Config: |
michael@0 | 1127 | return Write_Index_YUV; |
michael@0 | 1128 | default: |
michael@0 | 1129 | return NULL; |
michael@0 | 1130 | } |
michael@0 | 1131 | } |
michael@0 | 1132 | |
michael@0 | 1133 | class SkJPEGImageEncoder : public SkImageEncoder { |
michael@0 | 1134 | protected: |
michael@0 | 1135 | virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) { |
michael@0 | 1136 | #ifdef TIME_ENCODE |
michael@0 | 1137 | SkAutoTime atm("JPEG Encode"); |
michael@0 | 1138 | #endif |
michael@0 | 1139 | |
michael@0 | 1140 | SkAutoLockPixels alp(bm); |
michael@0 | 1141 | if (NULL == bm.getPixels()) { |
michael@0 | 1142 | return false; |
michael@0 | 1143 | } |
michael@0 | 1144 | |
michael@0 | 1145 | jpeg_compress_struct cinfo; |
michael@0 | 1146 | skjpeg_error_mgr sk_err; |
michael@0 | 1147 | skjpeg_destination_mgr sk_wstream(stream); |
michael@0 | 1148 | |
michael@0 | 1149 | // allocate these before set call setjmp |
michael@0 | 1150 | SkAutoMalloc oneRow; |
michael@0 | 1151 | SkAutoLockColors ctLocker; |
michael@0 | 1152 | |
michael@0 | 1153 | cinfo.err = jpeg_std_error(&sk_err); |
michael@0 | 1154 | sk_err.error_exit = skjpeg_error_exit; |
michael@0 | 1155 | if (setjmp(sk_err.fJmpBuf)) { |
michael@0 | 1156 | return false; |
michael@0 | 1157 | } |
michael@0 | 1158 | |
michael@0 | 1159 | // Keep after setjmp or mark volatile. |
michael@0 | 1160 | const WriteScanline writer = ChooseWriter(bm); |
michael@0 | 1161 | if (NULL == writer) { |
michael@0 | 1162 | return false; |
michael@0 | 1163 | } |
michael@0 | 1164 | |
michael@0 | 1165 | jpeg_create_compress(&cinfo); |
michael@0 | 1166 | cinfo.dest = &sk_wstream; |
michael@0 | 1167 | cinfo.image_width = bm.width(); |
michael@0 | 1168 | cinfo.image_height = bm.height(); |
michael@0 | 1169 | cinfo.input_components = 3; |
michael@0 | 1170 | #ifdef WE_CONVERT_TO_YUV |
michael@0 | 1171 | cinfo.in_color_space = JCS_YCbCr; |
michael@0 | 1172 | #else |
michael@0 | 1173 | cinfo.in_color_space = JCS_RGB; |
michael@0 | 1174 | #endif |
michael@0 | 1175 | cinfo.input_gamma = 1; |
michael@0 | 1176 | |
michael@0 | 1177 | jpeg_set_defaults(&cinfo); |
michael@0 | 1178 | jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); |
michael@0 | 1179 | #ifdef DCT_IFAST_SUPPORTED |
michael@0 | 1180 | cinfo.dct_method = JDCT_IFAST; |
michael@0 | 1181 | #endif |
michael@0 | 1182 | |
michael@0 | 1183 | jpeg_start_compress(&cinfo, TRUE); |
michael@0 | 1184 | |
michael@0 | 1185 | const int width = bm.width(); |
michael@0 | 1186 | uint8_t* oneRowP = (uint8_t*)oneRow.reset(width * 3); |
michael@0 | 1187 | |
michael@0 | 1188 | const SkPMColor* colors = ctLocker.lockColors(bm); |
michael@0 | 1189 | const void* srcRow = bm.getPixels(); |
michael@0 | 1190 | |
michael@0 | 1191 | while (cinfo.next_scanline < cinfo.image_height) { |
michael@0 | 1192 | JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ |
michael@0 | 1193 | |
michael@0 | 1194 | writer(oneRowP, srcRow, width, colors); |
michael@0 | 1195 | row_pointer[0] = oneRowP; |
michael@0 | 1196 | (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); |
michael@0 | 1197 | srcRow = (const void*)((const char*)srcRow + bm.rowBytes()); |
michael@0 | 1198 | } |
michael@0 | 1199 | |
michael@0 | 1200 | jpeg_finish_compress(&cinfo); |
michael@0 | 1201 | jpeg_destroy_compress(&cinfo); |
michael@0 | 1202 | |
michael@0 | 1203 | return true; |
michael@0 | 1204 | } |
michael@0 | 1205 | }; |
michael@0 | 1206 | |
michael@0 | 1207 | /////////////////////////////////////////////////////////////////////////////// |
michael@0 | 1208 | DEFINE_DECODER_CREATOR(JPEGImageDecoder); |
michael@0 | 1209 | DEFINE_ENCODER_CREATOR(JPEGImageEncoder); |
michael@0 | 1210 | /////////////////////////////////////////////////////////////////////////////// |
michael@0 | 1211 | |
michael@0 | 1212 | static bool is_jpeg(SkStreamRewindable* stream) { |
michael@0 | 1213 | static const unsigned char gHeader[] = { 0xFF, 0xD8, 0xFF }; |
michael@0 | 1214 | static const size_t HEADER_SIZE = sizeof(gHeader); |
michael@0 | 1215 | |
michael@0 | 1216 | char buffer[HEADER_SIZE]; |
michael@0 | 1217 | size_t len = stream->read(buffer, HEADER_SIZE); |
michael@0 | 1218 | |
michael@0 | 1219 | if (len != HEADER_SIZE) { |
michael@0 | 1220 | return false; // can't read enough |
michael@0 | 1221 | } |
michael@0 | 1222 | if (memcmp(buffer, gHeader, HEADER_SIZE)) { |
michael@0 | 1223 | return false; |
michael@0 | 1224 | } |
michael@0 | 1225 | return true; |
michael@0 | 1226 | } |
michael@0 | 1227 | |
michael@0 | 1228 | |
michael@0 | 1229 | static SkImageDecoder* sk_libjpeg_dfactory(SkStreamRewindable* stream) { |
michael@0 | 1230 | if (is_jpeg(stream)) { |
michael@0 | 1231 | return SkNEW(SkJPEGImageDecoder); |
michael@0 | 1232 | } |
michael@0 | 1233 | return NULL; |
michael@0 | 1234 | } |
michael@0 | 1235 | |
michael@0 | 1236 | static SkImageDecoder::Format get_format_jpeg(SkStreamRewindable* stream) { |
michael@0 | 1237 | if (is_jpeg(stream)) { |
michael@0 | 1238 | return SkImageDecoder::kJPEG_Format; |
michael@0 | 1239 | } |
michael@0 | 1240 | return SkImageDecoder::kUnknown_Format; |
michael@0 | 1241 | } |
michael@0 | 1242 | |
michael@0 | 1243 | static SkImageEncoder* sk_libjpeg_efactory(SkImageEncoder::Type t) { |
michael@0 | 1244 | return (SkImageEncoder::kJPEG_Type == t) ? SkNEW(SkJPEGImageEncoder) : NULL; |
michael@0 | 1245 | } |
michael@0 | 1246 | |
michael@0 | 1247 | static SkImageDecoder_DecodeReg gDReg(sk_libjpeg_dfactory); |
michael@0 | 1248 | static SkImageDecoder_FormatReg gFormatReg(get_format_jpeg); |
michael@0 | 1249 | static SkImageEncoder_EncodeReg gEReg(sk_libjpeg_efactory); |