• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "SkImageEncoderPriv.h"
9 
10 #ifdef SK_HAS_JPEG_LIBRARY
11 
12 #include "SkColorPriv.h"
13 #include "SkColorSpace_Base.h"
14 #include "SkImageEncoderFns.h"
15 #include "SkImageInfoPriv.h"
16 #include "SkJpegEncoder.h"
17 #include "SkJPEGWriteUtility.h"
18 #include "SkStream.h"
19 #include "SkTemplates.h"
20 
21 #include <stdio.h>
22 
23 extern "C" {
24     #include "jpeglib.h"
25     #include "jerror.h"
26 }
27 
28 class SkJpegEncoderMgr final : SkNoncopyable {
29 public:
30 
31     /*
32      * Create the decode manager
33      * Does not take ownership of stream
34      */
Make(SkWStream * stream)35     static std::unique_ptr<SkJpegEncoderMgr> Make(SkWStream* stream) {
36         return std::unique_ptr<SkJpegEncoderMgr>(new SkJpegEncoderMgr(stream));
37     }
38 
39     bool setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options);
40 
cinfo()41     jpeg_compress_struct* cinfo() { return &fCInfo; }
42 
jmpBuf()43     jmp_buf& jmpBuf() { return fErrMgr.fJmpBuf; }
44 
proc() const45     transform_scanline_proc proc() const { return fProc; }
46 
~SkJpegEncoderMgr()47     ~SkJpegEncoderMgr() {
48         jpeg_destroy_compress(&fCInfo);
49     }
50 
51 private:
52 
SkJpegEncoderMgr(SkWStream * stream)53     SkJpegEncoderMgr(SkWStream* stream)
54         : fDstMgr(stream)
55         , fProc(nullptr)
56     {
57         fCInfo.err = jpeg_std_error(&fErrMgr);
58         fErrMgr.error_exit = skjpeg_error_exit;
59         jpeg_create_compress(&fCInfo);
60         fCInfo.dest = &fDstMgr;
61     }
62 
63     jpeg_compress_struct    fCInfo;
64     skjpeg_error_mgr        fErrMgr;
65     skjpeg_destination_mgr  fDstMgr;
66     transform_scanline_proc fProc;
67 };
68 
setParams(const SkImageInfo & srcInfo,const SkJpegEncoder::Options & options)69 bool SkJpegEncoderMgr::setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options)
70 {
71     auto chooseProc8888 = [&]() {
72         if (kUnpremul_SkAlphaType != srcInfo.alphaType() ||
73             SkJpegEncoder::AlphaOption::kIgnore == options.fAlphaOption)
74         {
75             return (transform_scanline_proc) nullptr;
76         }
77 
78         // Note that kRespect mode is only supported with sRGB or linear transfer functions.
79         // The legacy code path is incidentally correct when the transfer function is linear.
80         const bool isSRGBTransferFn = srcInfo.gammaCloseToSRGB() &&
81                 (SkTransferFunctionBehavior::kRespect == options.fBlendBehavior);
82         if (isSRGBTransferFn) {
83             return transform_scanline_to_premul_linear;
84         } else {
85             return transform_scanline_to_premul_legacy;
86         }
87     };
88 
89     J_COLOR_SPACE jpegColorType = JCS_EXT_RGBA;
90     int numComponents = 0;
91     switch (srcInfo.colorType()) {
92         case kRGBA_8888_SkColorType:
93             fProc = chooseProc8888();
94             jpegColorType = JCS_EXT_RGBA;
95             numComponents = 4;
96             break;
97         case kBGRA_8888_SkColorType:
98             fProc = chooseProc8888();
99             jpegColorType = JCS_EXT_BGRA;
100             numComponents = 4;
101             break;
102         case kRGB_565_SkColorType:
103             fProc = transform_scanline_565;
104             jpegColorType = JCS_RGB;
105             numComponents = 3;
106             break;
107         case kARGB_4444_SkColorType:
108             if (SkJpegEncoder::AlphaOption::kBlendOnBlack == options.fAlphaOption) {
109                 return false;
110             }
111 
112             fProc = transform_scanline_444;
113             jpegColorType = JCS_RGB;
114             numComponents = 3;
115             break;
116         case kGray_8_SkColorType:
117             SkASSERT(srcInfo.isOpaque());
118             jpegColorType = JCS_GRAYSCALE;
119             numComponents = 1;
120             break;
121         case kRGBA_F16_SkColorType:
122             if (!srcInfo.colorSpace() || !srcInfo.colorSpace()->gammaIsLinear() ||
123                     SkTransferFunctionBehavior::kRespect != options.fBlendBehavior) {
124                 return false;
125             }
126 
127             if (kUnpremul_SkAlphaType != srcInfo.alphaType() ||
128                 SkJpegEncoder::AlphaOption::kIgnore == options.fAlphaOption)
129             {
130                 fProc = transform_scanline_F16_to_8888;
131             } else {
132                 fProc = transform_scanline_F16_to_premul_8888;
133             }
134             jpegColorType = JCS_EXT_RGBA;
135             numComponents = 4;
136             break;
137         default:
138             return false;
139     }
140 
141     fCInfo.image_width = srcInfo.width();
142     fCInfo.image_height = srcInfo.height();
143     fCInfo.in_color_space = jpegColorType;
144     fCInfo.input_components = numComponents;
145     jpeg_set_defaults(&fCInfo);
146 
147     if (kGray_8_SkColorType != srcInfo.colorType()) {
148         switch (options.fDownsample) {
149             case SkJpegEncoder::Downsample::k420:
150                 SkASSERT(2 == fCInfo.comp_info[0].h_samp_factor);
151                 SkASSERT(2 == fCInfo.comp_info[0].v_samp_factor);
152                 SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor);
153                 SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor);
154                 SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor);
155                 SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor);
156                 break;
157             case SkJpegEncoder::Downsample::k422:
158                 fCInfo.comp_info[0].h_samp_factor = 2;
159                 fCInfo.comp_info[0].v_samp_factor = 1;
160                 fCInfo.comp_info[1].h_samp_factor = 1;
161                 fCInfo.comp_info[1].v_samp_factor = 1;
162                 fCInfo.comp_info[2].h_samp_factor = 1;
163                 fCInfo.comp_info[2].v_samp_factor = 1;
164                 break;
165             case SkJpegEncoder::Downsample::k444:
166                 fCInfo.comp_info[0].h_samp_factor = 1;
167                 fCInfo.comp_info[0].v_samp_factor = 1;
168                 fCInfo.comp_info[1].h_samp_factor = 1;
169                 fCInfo.comp_info[1].v_samp_factor = 1;
170                 fCInfo.comp_info[2].h_samp_factor = 1;
171                 fCInfo.comp_info[2].v_samp_factor = 1;
172                 break;
173         }
174     }
175 
176     // Tells libjpeg-turbo to compute optimal Huffman coding tables
177     // for the image.  This improves compression at the cost of
178     // slower encode performance.
179     fCInfo.optimize_coding = TRUE;
180     return true;
181 }
182 
Make(SkWStream * dst,const SkPixmap & src,const Options & options)183 std::unique_ptr<SkEncoder> SkJpegEncoder::Make(SkWStream* dst, const SkPixmap& src,
184                                                const Options& options) {
185     if (!SkPixmapIsValid(src, options.fBlendBehavior)) {
186         return nullptr;
187     }
188 
189     std::unique_ptr<SkJpegEncoderMgr> encoderMgr = SkJpegEncoderMgr::Make(dst);
190     if (setjmp(encoderMgr->jmpBuf())) {
191         return nullptr;
192     }
193 
194     if (!encoderMgr->setParams(src.info(), options)) {
195         return nullptr;
196     }
197 
198     jpeg_set_quality(encoderMgr->cinfo(), options.fQuality, TRUE);
199     jpeg_start_compress(encoderMgr->cinfo(), TRUE);
200 
201     sk_sp<SkData> icc = icc_from_color_space(src.info());
202     if (icc) {
203         // Create a contiguous block of memory with the icc signature followed by the profile.
204         sk_sp<SkData> markerData =
205                 SkData::MakeUninitialized(kICCMarkerHeaderSize + icc->size());
206         uint8_t* ptr = (uint8_t*) markerData->writable_data();
207         memcpy(ptr, kICCSig, sizeof(kICCSig));
208         ptr += sizeof(kICCSig);
209         *ptr++ = 1; // This is the first marker.
210         *ptr++ = 1; // Out of one total markers.
211         memcpy(ptr, icc->data(), icc->size());
212 
213         jpeg_write_marker(encoderMgr->cinfo(), kICCMarker, markerData->bytes(), markerData->size());
214     }
215 
216     return std::unique_ptr<SkJpegEncoder>(new SkJpegEncoder(std::move(encoderMgr), src));
217 }
218 
SkJpegEncoder(std::unique_ptr<SkJpegEncoderMgr> encoderMgr,const SkPixmap & src)219 SkJpegEncoder::SkJpegEncoder(std::unique_ptr<SkJpegEncoderMgr> encoderMgr, const SkPixmap& src)
220     : INHERITED(src, encoderMgr->proc() ? encoderMgr->cinfo()->input_components*src.width() : 0)
221     , fEncoderMgr(std::move(encoderMgr))
222 {}
223 
~SkJpegEncoder()224 SkJpegEncoder::~SkJpegEncoder() {}
225 
onEncodeRows(int numRows)226 bool SkJpegEncoder::onEncodeRows(int numRows) {
227     if (setjmp(fEncoderMgr->jmpBuf())) {
228         return false;
229     }
230 
231     const void* srcRow = fSrc.addr(0, fCurrRow);
232     for (int i = 0; i < numRows; i++) {
233         JSAMPLE* jpegSrcRow = (JSAMPLE*) srcRow;
234         if (fEncoderMgr->proc()) {
235             fEncoderMgr->proc()((char*)fStorage.get(), (const char*)srcRow, fSrc.width(),
236                                 fEncoderMgr->cinfo()->input_components, nullptr);
237             jpegSrcRow = fStorage.get();
238         }
239 
240         jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1);
241         srcRow = SkTAddOffset<const void>(srcRow, fSrc.rowBytes());
242     }
243 
244     fCurrRow += numRows;
245     if (fCurrRow == fSrc.height()) {
246         jpeg_finish_compress(fEncoderMgr->cinfo());
247     }
248 
249     return true;
250 }
251 
Encode(SkWStream * dst,const SkPixmap & src,const Options & options)252 bool SkJpegEncoder::Encode(SkWStream* dst, const SkPixmap& src, const Options& options) {
253     auto encoder = SkJpegEncoder::Make(dst, src, options);
254     return encoder.get() && encoder->encodeRows(src.height());
255 }
256 
257 #endif
258