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