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