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
22extern "C" {
23    #include "jpeglib.h"
24    #include "jerror.h"
25}
26
27class SkJpegEncoderMgr final : SkNoncopyable {
28public:
29
30    /*
31     * Create the decode manager
32     * Does not take ownership of stream
33     */
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
40    jpeg_compress_struct* cinfo() { return &fCInfo; }
41
42    skjpeg_error_mgr* errorMgr() { return &fErrMgr; }
43
44    transform_scanline_proc proc() const { return fProc; }
45
46    ~SkJpegEncoderMgr() {
47        jpeg_destroy_compress(&fCInfo);
48    }
49
50private:
51
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
68bool 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
182std::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
220SkJpegEncoder::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
225SkJpegEncoder::~SkJpegEncoder() {}
226
227bool 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
254bool 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