blob: eaea77e0e0ebe1fe0bc365d2b2ae155748c6ddf8 [file] [log] [blame]
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001/*
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
Hal Canarydb683012016-11-23 08:55:18 -07008#include "SkImageEncoderPriv.h"
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00009
Hal Canary1fcc4042016-11-30 17:07:59 -050010#ifdef SK_HAS_JPEG_LIBRARY
11
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000012#include "SkColorPriv.h"
Mike Klein5e819ca2017-06-12 13:07:22 -040013#include "SkColorSpace_Base.h"
Matt Sarett5df93de2017-03-22 21:52:47 +000014#include "SkImageEncoderFns.h"
Matt Sarett26b44df2017-05-02 16:04:56 -040015#include "SkImageInfoPriv.h"
16#include "SkJpegEncoder.h"
Hal Canarydb683012016-11-23 08:55:18 -070017#include "SkJPEGWriteUtility.h"
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000018#include "SkStream.h"
19#include "SkTemplates.h"
halcanary@google.comfed30372013-10-04 12:46:45 +000020
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000021#include <stdio.h>
Hal Canarydb683012016-11-23 08:55:18 -070022
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000023extern "C" {
24 #include "jpeglib.h"
25 #include "jerror.h"
26}
27
Matt Sarettc367d032017-05-05 11:13:26 -040028class SkJpegEncoderMgr final : SkNoncopyable {
Matt Sarett26b44df2017-05-02 16:04:56 -040029public:
30
31 /*
32 * Create the decode manager
33 * Does not take ownership of stream
34 */
35 static std::unique_ptr<SkJpegEncoderMgr> Make(SkWStream* stream) {
36 return std::unique_ptr<SkJpegEncoderMgr>(new SkJpegEncoderMgr(stream));
37 }
38
Matt Sarett2e61b182017-05-09 12:46:50 -040039 bool setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options);
Matt Sarett26b44df2017-05-02 16:04:56 -040040
41 jpeg_compress_struct* cinfo() { return &fCInfo; }
42
43 jmp_buf& jmpBuf() { return fErrMgr.fJmpBuf; }
44
45 transform_scanline_proc proc() const { return fProc; }
46
47 ~SkJpegEncoderMgr() {
48 jpeg_destroy_compress(&fCInfo);
49 }
50
51private:
52
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
Matt Sarett2e61b182017-05-09 12:46:50 -040069bool 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
Matt Sarett26b44df2017-05-02 16:04:56 -040089 J_COLOR_SPACE jpegColorType = JCS_EXT_RGBA;
90 int numComponents = 0;
91 switch (srcInfo.colorType()) {
Matt Sarette95941f2017-01-27 18:16:40 -050092 case kRGBA_8888_SkColorType:
Matt Sarett2e61b182017-05-09 12:46:50 -040093 fProc = chooseProc8888();
Matt Sarett26b44df2017-05-02 16:04:56 -040094 jpegColorType = JCS_EXT_RGBA;
95 numComponents = 4;
96 break;
Matt Sarette95941f2017-01-27 18:16:40 -050097 case kBGRA_8888_SkColorType:
Matt Sarett2e61b182017-05-09 12:46:50 -040098 fProc = chooseProc8888();
Matt Sarett26b44df2017-05-02 16:04:56 -040099 jpegColorType = JCS_EXT_BGRA;
100 numComponents = 4;
101 break;
reed6c225732014-06-09 19:52:07 -0700102 case kRGB_565_SkColorType:
Matt Sarett26b44df2017-05-02 16:04:56 -0400103 fProc = transform_scanline_565;
104 jpegColorType = JCS_RGB;
105 numComponents = 3;
106 break;
reed6c225732014-06-09 19:52:07 -0700107 case kARGB_4444_SkColorType:
Matt Sarett2e61b182017-05-09 12:46:50 -0400108 if (SkJpegEncoder::AlphaOption::kBlendOnBlack == options.fAlphaOption) {
109 return false;
110 }
111
Matt Sarett26b44df2017-05-02 16:04:56 -0400112 fProc = transform_scanline_444;
113 jpegColorType = JCS_RGB;
114 numComponents = 3;
115 break;
reed6c225732014-06-09 19:52:07 -0700116 case kIndex_8_SkColorType:
Matt Sarett2e61b182017-05-09 12:46:50 -0400117 if (SkJpegEncoder::AlphaOption::kBlendOnBlack == options.fAlphaOption) {
118 return false;
119 }
120
Matt Sarett26b44df2017-05-02 16:04:56 -0400121 fProc = transform_scanline_index8_opaque;
122 jpegColorType = JCS_RGB;
123 numComponents = 3;
124 break;
Matt Sarette95941f2017-01-27 18:16:40 -0500125 case kGray_8_SkColorType:
Matt Sarett26b44df2017-05-02 16:04:56 -0400126 SkASSERT(srcInfo.isOpaque());
127 jpegColorType = JCS_GRAYSCALE;
128 numComponents = 1;
129 break;
Matt Sarette95941f2017-01-27 18:16:40 -0500130 case kRGBA_F16_SkColorType:
Matt Sarett2e61b182017-05-09 12:46:50 -0400131 if (!srcInfo.colorSpace() || !srcInfo.colorSpace()->gammaIsLinear() ||
132 SkTransferFunctionBehavior::kRespect != options.fBlendBehavior) {
Matt Sarette95941f2017-01-27 18:16:40 -0500133 return false;
134 }
135
Matt Sarett2e61b182017-05-09 12:46:50 -0400136 if (kUnpremul_SkAlphaType != srcInfo.alphaType() ||
137 SkJpegEncoder::AlphaOption::kIgnore == options.fAlphaOption)
138 {
139 fProc = transform_scanline_F16_to_8888;
140 } else {
141 fProc = transform_scanline_F16_to_premul_8888;
142 }
Matt Sarett26b44df2017-05-02 16:04:56 -0400143 jpegColorType = JCS_EXT_RGBA;
144 numComponents = 4;
145 break;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000146 default:
Matt Sarette95941f2017-01-27 18:16:40 -0500147 return false;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000148 }
Matt Sarette95941f2017-01-27 18:16:40 -0500149
Matt Sarett26b44df2017-05-02 16:04:56 -0400150 fCInfo.image_width = srcInfo.width();
151 fCInfo.image_height = srcInfo.height();
152 fCInfo.in_color_space = jpegColorType;
153 fCInfo.input_components = numComponents;
154 jpeg_set_defaults(&fCInfo);
Hal Canary1fcc4042016-11-30 17:07:59 -0500155
Matt Sarettfe319082017-05-09 14:02:10 -0400156 if (kGray_8_SkColorType != srcInfo.colorType()) {
157 switch (options.fDownsample) {
158 case SkJpegEncoder::Downsample::k420:
159 SkASSERT(2 == fCInfo.comp_info[0].h_samp_factor);
160 SkASSERT(2 == fCInfo.comp_info[0].v_samp_factor);
161 SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor);
162 SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor);
163 SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor);
164 SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor);
165 break;
166 case SkJpegEncoder::Downsample::k422:
167 fCInfo.comp_info[0].h_samp_factor = 2;
168 fCInfo.comp_info[0].v_samp_factor = 1;
169 fCInfo.comp_info[1].h_samp_factor = 1;
170 fCInfo.comp_info[1].v_samp_factor = 1;
171 fCInfo.comp_info[2].h_samp_factor = 1;
172 fCInfo.comp_info[2].v_samp_factor = 1;
173 break;
174 case SkJpegEncoder::Downsample::k444:
175 fCInfo.comp_info[0].h_samp_factor = 1;
176 fCInfo.comp_info[0].v_samp_factor = 1;
177 fCInfo.comp_info[1].h_samp_factor = 1;
178 fCInfo.comp_info[1].v_samp_factor = 1;
179 fCInfo.comp_info[2].h_samp_factor = 1;
180 fCInfo.comp_info[2].v_samp_factor = 1;
181 break;
182 }
183 }
184
Hal Canary1fcc4042016-11-30 17:07:59 -0500185 // Tells libjpeg-turbo to compute optimal Huffman coding tables
186 // for the image. This improves compression at the cost of
187 // slower encode performance.
Matt Sarett26b44df2017-05-02 16:04:56 -0400188 fCInfo.optimize_coding = TRUE;
189 return true;
190}
Hal Canary1fcc4042016-11-30 17:07:59 -0500191
Matt Sarett6a4dc662017-05-11 09:32:59 -0400192std::unique_ptr<SkEncoder> SkJpegEncoder::Make(SkWStream* dst, const SkPixmap& src,
193 const Options& options) {
Matt Sarett2e61b182017-05-09 12:46:50 -0400194 if (!SkPixmapIsValid(src, options.fBlendBehavior)) {
Matt Sarett26b44df2017-05-02 16:04:56 -0400195 return nullptr;
196 }
197
198 std::unique_ptr<SkJpegEncoderMgr> encoderMgr = SkJpegEncoderMgr::Make(dst);
199 if (setjmp(encoderMgr->jmpBuf())) {
200 return nullptr;
201 }
202
Matt Sarett2e61b182017-05-09 12:46:50 -0400203 if (!encoderMgr->setParams(src.info(), options)) {
Matt Sarett26b44df2017-05-02 16:04:56 -0400204 return nullptr;
205 }
206
207 jpeg_set_quality(encoderMgr->cinfo(), options.fQuality, TRUE);
208 jpeg_start_compress(encoderMgr->cinfo(), TRUE);
209
Mike Klein5e819ca2017-06-12 13:07:22 -0400210 if (SkColorSpace* cs = src.colorSpace()) {
211 sk_sp<SkColorSpace> owned;
212 if (src.colorType() == kRGBA_F16_SkColorType) {
213 // We'll be converting to 8-bit sRGB, so we'd better tag it that way.
214 owned = as_CSB(src.colorSpace())->makeSRGBGamma();
215 cs = owned.get();
216 }
217
218 sk_sp<SkData> icc = icc_from_color_space(*cs);
Matt Sarett5df93de2017-03-22 21:52:47 +0000219 if (icc) {
220 // Create a contiguous block of memory with the icc signature followed by the profile.
221 sk_sp<SkData> markerData =
222 SkData::MakeUninitialized(kICCMarkerHeaderSize + icc->size());
223 uint8_t* ptr = (uint8_t*) markerData->writable_data();
224 memcpy(ptr, kICCSig, sizeof(kICCSig));
225 ptr += sizeof(kICCSig);
226 *ptr++ = 1; // This is the first marker.
227 *ptr++ = 1; // Out of one total markers.
228 memcpy(ptr, icc->data(), icc->size());
229
Matt Sarett26b44df2017-05-02 16:04:56 -0400230 jpeg_write_marker(encoderMgr->cinfo(), kICCMarker, markerData->bytes(),
231 markerData->size());
Matt Sarett5df93de2017-03-22 21:52:47 +0000232 }
233 }
234
Matt Sarettc367d032017-05-05 11:13:26 -0400235 return std::unique_ptr<SkJpegEncoder>(new SkJpegEncoder(std::move(encoderMgr), src));
Matt Sarett26b44df2017-05-02 16:04:56 -0400236}
237
Matt Sarettc367d032017-05-05 11:13:26 -0400238SkJpegEncoder::SkJpegEncoder(std::unique_ptr<SkJpegEncoderMgr> encoderMgr, const SkPixmap& src)
239 : INHERITED(src, encoderMgr->proc() ? encoderMgr->cinfo()->input_components*src.width() : 0)
240 , fEncoderMgr(std::move(encoderMgr))
Matt Sarett26b44df2017-05-02 16:04:56 -0400241{}
242
Matt Sarettc367d032017-05-05 11:13:26 -0400243SkJpegEncoder::~SkJpegEncoder() {}
Matt Sarett26b44df2017-05-02 16:04:56 -0400244
Matt Sarettc367d032017-05-05 11:13:26 -0400245bool SkJpegEncoder::onEncodeRows(int numRows) {
Matt Sarett26b44df2017-05-02 16:04:56 -0400246 if (setjmp(fEncoderMgr->jmpBuf())) {
Matt Sarett26b44df2017-05-02 16:04:56 -0400247 return false;
248 }
249
250 const void* srcRow = fSrc.addr(0, fCurrRow);
251 const SkPMColor* colors = fSrc.ctable() ? fSrc.ctable()->readColors() : nullptr;
252 for (int i = 0; i < numRows; i++) {
Matt Sarette95941f2017-01-27 18:16:40 -0500253 JSAMPLE* jpegSrcRow = (JSAMPLE*) srcRow;
Matt Sarett26b44df2017-05-02 16:04:56 -0400254 if (fEncoderMgr->proc()) {
255 fEncoderMgr->proc()((char*)fStorage.get(), (const char*)srcRow, fSrc.width(),
256 fEncoderMgr->cinfo()->input_components, colors);
257 jpegSrcRow = fStorage.get();
Matt Sarette95941f2017-01-27 18:16:40 -0500258 }
Hal Canary1fcc4042016-11-30 17:07:59 -0500259
Matt Sarett26b44df2017-05-02 16:04:56 -0400260 jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1);
261 srcRow = SkTAddOffset<const void>(srcRow, fSrc.rowBytes());
Hal Canary1fcc4042016-11-30 17:07:59 -0500262 }
263
Matt Sarett26b44df2017-05-02 16:04:56 -0400264 fCurrRow += numRows;
265 if (fCurrRow == fSrc.height()) {
266 jpeg_finish_compress(fEncoderMgr->cinfo());
267 }
Hal Canary1fcc4042016-11-30 17:07:59 -0500268
269 return true;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000270}
Matt Sarett26b44df2017-05-02 16:04:56 -0400271
272bool SkJpegEncoder::Encode(SkWStream* dst, const SkPixmap& src, const Options& options) {
273 auto encoder = SkJpegEncoder::Make(dst, src, options);
274 return encoder.get() && encoder->encodeRows(src.height());
275}
276
Hal Canary1fcc4042016-11-30 17:07:59 -0500277#endif