Coverage Report

Created: 2024-05-20 07:14

/src/skia/src/encode/SkJpegEncoderImpl.cpp
Line
Count
Source (jump to first uncovered line)
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 "src/encode/SkJpegEncoderImpl.h"
9
10
#include "include/core/SkAlphaType.h"
11
#include "include/core/SkBitmap.h"
12
#include "include/core/SkColorType.h"
13
#include "include/core/SkData.h"
14
#include "include/core/SkImageInfo.h"
15
#include "include/core/SkPixmap.h"
16
#include "include/core/SkRefCnt.h"
17
#include "include/core/SkStream.h"
18
#include "include/core/SkYUVAInfo.h"
19
#include "include/core/SkYUVAPixmaps.h"
20
#include "include/encode/SkEncoder.h"
21
#include "include/encode/SkJpegEncoder.h"
22
#include "include/private/base/SkAssert.h"
23
#include "include/private/base/SkNoncopyable.h"
24
#include "include/private/base/SkTemplates.h"
25
#include "src/base/SkMSAN.h"
26
#include "src/codec/SkJpegConstants.h"
27
#include "src/codec/SkJpegPriv.h"
28
#include "src/encode/SkImageEncoderFns.h"
29
#include "src/encode/SkImageEncoderPriv.h"
30
#include "src/encode/SkJPEGWriteUtility.h"
31
#include "src/image/SkImage_Base.h"
32
33
#include <csetjmp>
34
#include <cstdint>
35
#include <cstring>
36
#include <memory>
37
#include <utility>
38
39
class GrDirectContext;
40
class SkColorSpace;
41
class SkImage;
42
43
extern "C" {
44
#include "jpeglib.h"  // NO_G3_REWRITE
45
}
46
47
class SkJpegEncoderMgr final : SkNoncopyable {
48
public:
49
    /*
50
     * Create the decode manager
51
     * Does not take ownership of stream.
52
     */
53
4.67k
    static std::unique_ptr<SkJpegEncoderMgr> Make(SkWStream* stream) {
54
4.67k
        return std::unique_ptr<SkJpegEncoderMgr>(new SkJpegEncoderMgr(stream));
55
4.67k
    }
56
57
    bool initializeRGB(const SkImageInfo&,
58
                       const SkJpegEncoder::Options&,
59
                       const SkJpegMetadataEncoder::SegmentList&);
60
    bool initializeYUV(const SkYUVAPixmapInfo&,
61
                       const SkJpegEncoder::Options&,
62
                       const SkJpegMetadataEncoder::SegmentList&);
63
64
810k
    jpeg_compress_struct* cinfo() { return &fCInfo; }
65
66
9.35k
    skjpeg_error_mgr* errorMgr() { return &fErrMgr; }
67
68
805k
    transform_scanline_proc proc() const { return fProc; }
69
70
4.67k
    ~SkJpegEncoderMgr() { jpeg_destroy_compress(&fCInfo); }
71
72
private:
73
4.67k
    SkJpegEncoderMgr(SkWStream* stream) : fDstMgr(stream), fProc(nullptr) {
74
4.67k
        fCInfo.err = jpeg_std_error(&fErrMgr);
75
4.67k
        fErrMgr.error_exit = skjpeg_error_exit;
76
4.67k
        jpeg_create_compress(&fCInfo);
77
4.67k
        fCInfo.dest = &fDstMgr;
78
4.67k
    }
79
    void initializeCommon(const SkJpegEncoder::Options&, const SkJpegMetadataEncoder::SegmentList&);
80
81
    jpeg_compress_struct fCInfo;
82
    skjpeg_error_mgr fErrMgr;
83
    skjpeg_destination_mgr fDstMgr;
84
    transform_scanline_proc fProc;
85
};
86
87
bool SkJpegEncoderMgr::initializeRGB(const SkImageInfo& srcInfo,
88
                                     const SkJpegEncoder::Options& options,
89
4.67k
                                     const SkJpegMetadataEncoder::SegmentList& metadataSegments) {
90
4.67k
    auto chooseProc8888 = [&]() {
91
4.67k
        if (kUnpremul_SkAlphaType == srcInfo.alphaType() &&
92
4.67k
            options.fAlphaOption == SkJpegEncoder::AlphaOption::kBlendOnBlack) {
93
0
            return transform_scanline_to_premul_legacy;
94
0
        }
95
4.67k
        return (transform_scanline_proc) nullptr;
96
4.67k
    };
97
98
4.67k
    J_COLOR_SPACE jpegColorType = JCS_EXT_RGBA;
99
4.67k
    int numComponents = 0;
100
4.67k
    switch (srcInfo.colorType()) {
101
0
        case kRGBA_8888_SkColorType:
102
0
            fProc = chooseProc8888();
103
0
            jpegColorType = JCS_EXT_RGBA;
104
0
            numComponents = 4;
105
0
            break;
106
4.67k
        case kBGRA_8888_SkColorType:
107
4.67k
            fProc = chooseProc8888();
108
4.67k
            jpegColorType = JCS_EXT_BGRA;
109
4.67k
            numComponents = 4;
110
4.67k
            break;
111
0
        case kRGB_565_SkColorType:
112
0
            fProc = transform_scanline_565;
113
0
            jpegColorType = JCS_RGB;
114
0
            numComponents = 3;
115
0
            break;
116
0
        case kARGB_4444_SkColorType:
117
0
            if (SkJpegEncoder::AlphaOption::kBlendOnBlack == options.fAlphaOption) {
118
0
                return false;
119
0
            }
120
121
0
            fProc = transform_scanline_444;
122
0
            jpegColorType = JCS_RGB;
123
0
            numComponents = 3;
124
0
            break;
125
0
        case kGray_8_SkColorType:
126
0
        case kAlpha_8_SkColorType:
127
0
        case kR8_unorm_SkColorType:
128
0
            jpegColorType = JCS_GRAYSCALE;
129
0
            numComponents = 1;
130
0
            break;
131
0
        case kRGBA_F16_SkColorType:
132
0
            if (kUnpremul_SkAlphaType == srcInfo.alphaType() &&
133
0
                options.fAlphaOption == SkJpegEncoder::AlphaOption::kBlendOnBlack) {
134
0
                fProc = transform_scanline_F16_to_premul_8888;
135
0
            } else {
136
0
                fProc = transform_scanline_F16_to_8888;
137
0
            }
138
0
            jpegColorType = JCS_EXT_RGBA;
139
0
            numComponents = 4;
140
0
            break;
141
0
        default:
142
0
            return false;
143
4.67k
    }
144
145
4.67k
    fCInfo.image_width = srcInfo.width();
146
4.67k
    fCInfo.image_height = srcInfo.height();
147
4.67k
    fCInfo.in_color_space = jpegColorType;
148
4.67k
    fCInfo.input_components = numComponents;
149
4.67k
    jpeg_set_defaults(&fCInfo);
150
151
4.67k
    if (numComponents != 1) {
152
4.67k
        switch (options.fDownsample) {
153
4.67k
            case SkJpegEncoder::Downsample::k420:
154
4.67k
                SkASSERT(2 == fCInfo.comp_info[0].h_samp_factor);
155
4.67k
                SkASSERT(2 == fCInfo.comp_info[0].v_samp_factor);
156
4.67k
                SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor);
157
4.67k
                SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor);
158
4.67k
                SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor);
159
4.67k
                SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor);
160
4.67k
                break;
161
0
            case SkJpegEncoder::Downsample::k422:
162
0
                fCInfo.comp_info[0].h_samp_factor = 2;
163
0
                fCInfo.comp_info[0].v_samp_factor = 1;
164
0
                SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor);
165
0
                SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor);
166
0
                SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor);
167
0
                SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor);
168
0
                break;
169
0
            case SkJpegEncoder::Downsample::k444:
170
0
                fCInfo.comp_info[0].h_samp_factor = 1;
171
0
                fCInfo.comp_info[0].v_samp_factor = 1;
172
0
                SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor);
173
0
                SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor);
174
0
                SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor);
175
0
                SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor);
176
0
                break;
177
4.67k
        }
178
4.67k
    }
179
180
4.67k
    initializeCommon(options, metadataSegments);
181
4.67k
    return true;
182
4.67k
}
183
184
// Convert a row of an SkYUVAPixmaps to a row of Y,U,V triples.
185
// TODO(ccameron): This is horribly inefficient.
186
0
static void yuva_copy_row(const SkYUVAPixmaps& src, int row, uint8_t* dst) {
187
0
    int width = src.plane(0).width();
188
0
    switch (src.yuvaInfo().planeConfig()) {
189
0
        case SkYUVAInfo::PlaneConfig::kY_U_V: {
190
0
            auto [ssWidthU, ssHeightU] = src.yuvaInfo().planeSubsamplingFactors(1);
191
0
            auto [ssWidthV, ssHeightV] = src.yuvaInfo().planeSubsamplingFactors(2);
192
0
            const uint8_t* srcY = reinterpret_cast<const uint8_t*>(src.plane(0).addr(0, row));
193
0
            const uint8_t* srcU =
194
0
                    reinterpret_cast<const uint8_t*>(src.plane(1).addr(0, row / ssHeightU));
195
0
            const uint8_t* srcV =
196
0
                    reinterpret_cast<const uint8_t*>(src.plane(2).addr(0, row / ssHeightV));
197
0
            for (int col = 0; col < width; ++col) {
198
0
                dst[3 * col + 0] = srcY[col];
199
0
                dst[3 * col + 1] = srcU[col / ssWidthU];
200
0
                dst[3 * col + 2] = srcV[col / ssWidthV];
201
0
            }
202
0
            break;
203
0
        }
204
0
        case SkYUVAInfo::PlaneConfig::kY_UV: {
205
0
            auto [ssWidthUV, ssHeightUV] = src.yuvaInfo().planeSubsamplingFactors(1);
206
0
            const uint8_t* srcY = reinterpret_cast<const uint8_t*>(src.plane(0).addr(0, row));
207
0
            const uint8_t* srcUV =
208
0
                    reinterpret_cast<const uint8_t*>(src.plane(1).addr(0, row / ssHeightUV));
209
0
            for (int col = 0; col < width; ++col) {
210
0
                dst[3 * col + 0] = srcY[col];
211
0
                dst[3 * col + 1] = srcUV[2 * (col / ssWidthUV) + 0];
212
0
                dst[3 * col + 2] = srcUV[2 * (col / ssWidthUV) + 1];
213
0
            }
214
0
            break;
215
0
        }
216
0
        default:
217
0
            break;
218
0
    }
219
0
}
220
221
bool SkJpegEncoderMgr::initializeYUV(const SkYUVAPixmapInfo& srcInfo,
222
                                     const SkJpegEncoder::Options& options,
223
0
                                     const SkJpegMetadataEncoder::SegmentList& metadataSegments) {
224
0
    fCInfo.image_width = srcInfo.yuvaInfo().width();
225
0
    fCInfo.image_height = srcInfo.yuvaInfo().height();
226
0
    fCInfo.in_color_space = JCS_YCbCr;
227
0
    fCInfo.input_components = 3;
228
0
    jpeg_set_defaults(&fCInfo);
229
230
    // Support no color space conversion.
231
0
    if (srcInfo.yuvColorSpace() != kJPEG_Full_SkYUVColorSpace) {
232
0
        return false;
233
0
    }
234
235
    // Support only 8-bit data.
236
0
    switch (srcInfo.dataType()) {
237
0
        case SkYUVAPixmapInfo::DataType::kUnorm8:
238
0
            break;
239
0
        default:
240
0
            return false;
241
0
    }
242
243
    // Support only Y,U,V and Y,UV configurations (they are the only ones supported by
244
    // yuva_copy_row).
245
0
    switch (srcInfo.yuvaInfo().planeConfig()) {
246
0
        case SkYUVAInfo::PlaneConfig::kY_U_V:
247
0
        case SkYUVAInfo::PlaneConfig::kY_UV:
248
0
            break;
249
0
        default:
250
0
            return false;
251
0
    }
252
253
    // Specify to the encoder to use the same subsampling as the input image. The U and V planes
254
    // always have a sampling factor of 1.
255
0
    auto [ssHoriz, ssVert] = SkYUVAInfo::SubsamplingFactors(srcInfo.yuvaInfo().subsampling());
256
0
    fCInfo.comp_info[0].h_samp_factor = ssHoriz;
257
0
    fCInfo.comp_info[0].v_samp_factor = ssVert;
258
259
0
    initializeCommon(options, metadataSegments);
260
0
    return true;
261
0
}
262
263
void SkJpegEncoderMgr::initializeCommon(
264
        const SkJpegEncoder::Options& options,
265
4.67k
        const SkJpegMetadataEncoder::SegmentList& metadataSegments) {
266
    // Tells libjpeg-turbo to compute optimal Huffman coding tables
267
    // for the image.  This improves compression at the cost of
268
    // slower encode performance.
269
4.67k
    fCInfo.optimize_coding = TRUE;
270
271
4.67k
    jpeg_set_quality(&fCInfo, options.fQuality, TRUE);
272
4.67k
    jpeg_start_compress(&fCInfo, TRUE);
273
274
4.67k
    for (const auto& segment : metadataSegments) {
275
0
        jpeg_write_marker(&fCInfo,
276
0
                          segment.fMarker,
277
0
                          segment.fParameters->bytes(),
278
0
                          segment.fParameters->size());
279
0
    }
280
4.67k
}
281
282
std::unique_ptr<SkEncoder> SkJpegEncoderImpl::MakeYUV(
283
        SkWStream* dst,
284
        const SkYUVAPixmaps& srcYUVA,
285
        const SkColorSpace* srcYUVAColorSpace,
286
        const SkJpegEncoder::Options& options,
287
0
        const SkJpegMetadataEncoder::SegmentList& metadataSegments) {
288
0
    if (!srcYUVA.isValid()) {
289
0
        return nullptr;
290
0
    }
291
0
    std::unique_ptr<SkJpegEncoderMgr> encoderMgr = SkJpegEncoderMgr::Make(dst);
292
0
    skjpeg_error_mgr::AutoPushJmpBuf jmp(encoderMgr->errorMgr());
293
0
    if (setjmp(jmp)) {
294
0
        return nullptr;
295
0
    }
296
297
0
    if (!encoderMgr->initializeYUV(srcYUVA.pixmapsInfo(), options, metadataSegments)) {
298
0
        return nullptr;
299
0
    }
300
0
    return std::unique_ptr<SkJpegEncoderImpl>(
301
0
            new SkJpegEncoderImpl(std::move(encoderMgr), srcYUVA));
302
0
}
303
304
std::unique_ptr<SkEncoder> SkJpegEncoderImpl::MakeRGB(
305
        SkWStream* dst,
306
        const SkPixmap& src,
307
        const SkJpegEncoder::Options& options,
308
4.79k
        const SkJpegMetadataEncoder::SegmentList& metadataSegments) {
309
4.79k
    if (!SkPixmapIsValid(src)) {
310
122
        return nullptr;
311
122
    }
312
4.67k
    std::unique_ptr<SkJpegEncoderMgr> encoderMgr = SkJpegEncoderMgr::Make(dst);
313
4.67k
    skjpeg_error_mgr::AutoPushJmpBuf jmp(encoderMgr->errorMgr());
314
4.67k
    if (setjmp(jmp)) {
315
0
        return nullptr;
316
0
    }
317
318
4.67k
    if (!encoderMgr->initializeRGB(src.info(), options, metadataSegments)) {
319
0
        return nullptr;
320
0
    }
321
4.67k
    return std::unique_ptr<SkJpegEncoderImpl>(new SkJpegEncoderImpl(std::move(encoderMgr), src));
322
4.67k
}
323
324
SkJpegEncoderImpl::SkJpegEncoderImpl(std::unique_ptr<SkJpegEncoderMgr> encoderMgr,
325
                                     const SkPixmap& src)
326
        : SkEncoder(src,
327
                    encoderMgr->proc() ? encoderMgr->cinfo()->input_components * src.width() : 0)
328
4.67k
        , fEncoderMgr(std::move(encoderMgr)) {}
329
330
SkJpegEncoderImpl::SkJpegEncoderImpl(std::unique_ptr<SkJpegEncoderMgr> encoderMgr,
331
                                     const SkYUVAPixmaps& src)
332
        : SkEncoder(src.plane(0), encoderMgr->cinfo()->input_components * src.yuvaInfo().width())
333
        , fEncoderMgr(std::move(encoderMgr))
334
0
        , fSrcYUVA(src) {}
335
336
4.67k
SkJpegEncoderImpl::~SkJpegEncoderImpl() {}
337
338
4.67k
bool SkJpegEncoderImpl::onEncodeRows(int numRows) {
339
4.67k
    skjpeg_error_mgr::AutoPushJmpBuf jmp(fEncoderMgr->errorMgr());
340
4.67k
    if (setjmp(jmp)) {
341
0
        return false;
342
0
    }
343
344
4.67k
    if (fSrcYUVA) {
345
        // TODO(ccameron): Consider using jpeg_write_raw_data, to avoid having to re-pack the data.
346
0
        for (int i = 0; i < numRows; i++) {
347
0
            yuva_copy_row(*fSrcYUVA, fCurrRow + i, fStorage.get());
348
0
            JSAMPLE* jpegSrcRow = fStorage.get();
349
0
            jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1);
350
0
        }
351
4.67k
    } else {
352
4.67k
        const size_t srcBytes = SkColorTypeBytesPerPixel(fSrc.colorType()) * fSrc.width();
353
4.67k
        const size_t jpegSrcBytes = fEncoderMgr->cinfo()->input_components * fSrc.width();
354
4.67k
        const void* srcRow = fSrc.addr(0, fCurrRow);
355
805k
        for (int i = 0; i < numRows; i++) {
356
800k
            JSAMPLE* jpegSrcRow = (JSAMPLE*)(const_cast<void*>(srcRow));
357
800k
            if (fEncoderMgr->proc()) {
358
0
                sk_msan_assert_initialized(srcRow, SkTAddOffset<const void>(srcRow, srcBytes));
359
0
                fEncoderMgr->proc()((char*)fStorage.get(),
360
0
                                    (const char*)srcRow,
361
0
                                    fSrc.width(),
362
0
                                    fEncoderMgr->cinfo()->input_components);
363
0
                jpegSrcRow = fStorage.get();
364
0
                sk_msan_assert_initialized(jpegSrcRow,
365
0
                                           SkTAddOffset<const void>(jpegSrcRow, jpegSrcBytes));
366
800k
            } else {
367
                // Same as above, but this repetition allows determining whether a
368
                // proc was used when msan asserts.
369
800k
                sk_msan_assert_initialized(jpegSrcRow,
370
800k
                                           SkTAddOffset<const void>(jpegSrcRow, jpegSrcBytes));
371
800k
            }
372
373
800k
            jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1);
374
800k
            srcRow = SkTAddOffset<const void>(srcRow, fSrc.rowBytes());
375
800k
        }
376
4.67k
    }
377
378
4.67k
    fCurrRow += numRows;
379
4.67k
    if (fCurrRow == fSrc.height()) {
380
4.67k
        jpeg_finish_compress(fEncoderMgr->cinfo());
381
4.67k
    }
382
383
4.67k
    return true;
384
4.67k
}
385
386
namespace SkJpegEncoder {
387
388
4.79k
bool Encode(SkWStream* dst, const SkPixmap& src, const Options& options) {
389
4.79k
    auto encoder = Make(dst, src, options);
390
4.79k
    return encoder.get() && encoder->encodeRows(src.height());
391
4.79k
}
392
393
bool Encode(SkWStream* dst,
394
            const SkYUVAPixmaps& src,
395
            const SkColorSpace* srcColorSpace,
396
0
            const Options& options) {
397
0
    auto encoder = Make(dst, src, srcColorSpace, options);
398
0
    return encoder.get() && encoder->encodeRows(src.yuvaInfo().height());
399
0
}
400
401
0
sk_sp<SkData> Encode(GrDirectContext* ctx, const SkImage* img, const Options& options) {
402
0
    if (!img) {
403
0
        return nullptr;
404
0
    }
405
0
    SkBitmap bm;
406
0
    if (!as_IB(img)->getROPixels(ctx, &bm)) {
407
0
        return nullptr;
408
0
    }
409
0
    SkDynamicMemoryWStream stream;
410
0
    if (Encode(&stream, bm.pixmap(), options)) {
411
0
        return stream.detachAsData();
412
0
    }
413
0
    return nullptr;
414
0
}
415
416
4.79k
std::unique_ptr<SkEncoder> Make(SkWStream* dst, const SkPixmap& src, const Options& options) {
417
4.79k
    SkJpegMetadataEncoder::SegmentList metadataSegments;
418
4.79k
    SkJpegMetadataEncoder::AppendXMPStandard(metadataSegments, options.xmpMetadata);
419
4.79k
    SkJpegMetadataEncoder::AppendICC(metadataSegments, options, src.colorSpace());
420
4.79k
    return SkJpegEncoderImpl::MakeRGB(dst, src, options, metadataSegments);
421
4.79k
}
422
423
std::unique_ptr<SkEncoder> Make(SkWStream* dst,
424
                                const SkYUVAPixmaps& src,
425
                                const SkColorSpace* srcColorSpace,
426
0
                                const Options& options) {
427
0
    SkJpegMetadataEncoder::SegmentList metadataSegments;
428
0
    SkJpegMetadataEncoder::AppendXMPStandard(metadataSegments, options.xmpMetadata);
429
0
    SkJpegMetadataEncoder::AppendICC(metadataSegments, options, srcColorSpace);
430
0
    return SkJpegEncoderImpl::MakeYUV(dst, src, srcColorSpace, options, metadataSegments);
431
0
}
432
433
}  // namespace SkJpegEncoder
434
435
namespace SkJpegMetadataEncoder {
436
437
void AppendICC(SegmentList& segmentList,
438
               const SkJpegEncoder::Options& options,
439
4.79k
               const SkColorSpace* colorSpace) {
440
4.79k
    sk_sp<SkData> icc =
441
4.79k
            icc_from_color_space(colorSpace, options.fICCProfile, options.fICCProfileDescription);
442
4.79k
    if (!icc) {
443
4.79k
        return;
444
4.79k
    }
445
446
    // TODO(ccameron): This limits ICC profile size to a single segment's parameters (less than
447
    // 64k). Split larger profiles into more segments.
448
0
    SkDynamicMemoryWStream s;
449
0
    s.write(kICCSig, sizeof(kICCSig));
450
0
    s.write8(1);  // This is the first marker.
451
0
    s.write8(1);  // Out of one total markers.
452
0
    s.write(icc->data(), icc->size());
453
0
    segmentList.emplace_back(kICCMarker, s.detachAsData());
454
0
}
455
456
4.79k
void AppendXMPStandard(SegmentList& segmentList, const SkData* xmpMetadata) {
457
4.79k
    if (!xmpMetadata) {
458
4.79k
        return;
459
4.79k
    }
460
461
    // TODO(ccameron): Split this into a standard and extended XMP segment if needed.
462
0
    SkDynamicMemoryWStream s;
463
0
    s.write(kXMPStandardSig, sizeof(kXMPStandardSig));
464
0
    s.write(xmpMetadata->data(), xmpMetadata->size());
465
0
    segmentList.emplace_back(kXMPMarker, s.detachAsData());
466
0
}
467
468
}  // namespace SkJpegMetadataEncoder