Flutter Engine
The Flutter Engine
SkJpegEncoderImpl.cpp
Go to the documentation of this file.
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
9
13#include "include/core/SkData.h"
25#include "src/base/SkMSAN.h"
32
33#include <csetjmp>
34#include <cstdint>
35#include <cstring>
36#include <memory>
37#include <utility>
38
39class GrDirectContext;
40class SkColorSpace;
41class SkImage;
42
43extern "C" {
44#include "jpeglib.h" // NO_G3_REWRITE
45}
46
48public:
49 /*
50 * Create the decode manager
51 * Does not take ownership of stream.
52 */
53 static std::unique_ptr<SkJpegEncoderMgr> Make(SkWStream* stream) {
54 return std::unique_ptr<SkJpegEncoderMgr>(new SkJpegEncoderMgr(stream));
55 }
56
57 bool initializeRGB(const SkImageInfo&,
63
64 jpeg_compress_struct* cinfo() { return &fCInfo; }
65
66 skjpeg_error_mgr* errorMgr() { return &fErrMgr; }
67
68 transform_scanline_proc proc() const { return fProc; }
69
70 ~SkJpegEncoderMgr() { jpeg_destroy_compress(&fCInfo); }
71
72private:
73 SkJpegEncoderMgr(SkWStream* stream) : fDstMgr(stream), fProc(nullptr) {
74 fCInfo.err = jpeg_std_error(&fErrMgr);
75 fErrMgr.error_exit = skjpeg_error_exit;
76 jpeg_create_compress(&fCInfo);
77 fCInfo.dest = &fDstMgr;
78 }
79 void initializeCommon(const SkJpegEncoder::Options&, const SkJpegMetadataEncoder::SegmentList&);
80
81 jpeg_compress_struct fCInfo;
82 skjpeg_error_mgr fErrMgr;
85};
86
89 const SkJpegMetadataEncoder::SegmentList& metadataSegments) {
90 auto chooseProc8888 = [&]() {
91 if (kUnpremul_SkAlphaType == srcInfo.alphaType() &&
94 }
95 return (transform_scanline_proc) nullptr;
96 };
97
98 J_COLOR_SPACE jpegColorType = JCS_EXT_RGBA;
99 int numComponents = 0;
100 switch (srcInfo.colorType()) {
102 fProc = chooseProc8888();
103 jpegColorType = JCS_EXT_RGBA;
104 numComponents = 4;
105 break;
107 fProc = chooseProc8888();
108 jpegColorType = JCS_EXT_BGRA;
109 numComponents = 4;
110 break;
113 jpegColorType = JCS_RGB;
114 numComponents = 3;
115 break;
118 return false;
119 }
120
122 jpegColorType = JCS_RGB;
123 numComponents = 3;
124 break;
128 jpegColorType = JCS_GRAYSCALE;
129 numComponents = 1;
130 break;
132 if (kUnpremul_SkAlphaType == srcInfo.alphaType() &&
135 } else {
137 }
138 jpegColorType = JCS_EXT_RGBA;
139 numComponents = 4;
140 break;
141 default:
142 return false;
143 }
144
145 fCInfo.image_width = srcInfo.width();
146 fCInfo.image_height = srcInfo.height();
147 fCInfo.in_color_space = jpegColorType;
148 fCInfo.input_components = numComponents;
149 jpeg_set_defaults(&fCInfo);
150
151 if (numComponents != 1) {
152 switch (options.fDownsample) {
154 SkASSERT(2 == fCInfo.comp_info[0].h_samp_factor);
155 SkASSERT(2 == fCInfo.comp_info[0].v_samp_factor);
156 SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor);
157 SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor);
158 SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor);
159 SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor);
160 break;
162 fCInfo.comp_info[0].h_samp_factor = 2;
163 fCInfo.comp_info[0].v_samp_factor = 1;
164 SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor);
165 SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor);
166 SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor);
167 SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor);
168 break;
170 fCInfo.comp_info[0].h_samp_factor = 1;
171 fCInfo.comp_info[0].v_samp_factor = 1;
172 SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor);
173 SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor);
174 SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor);
175 SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor);
176 break;
177 }
178 }
179
180 initializeCommon(options, metadataSegments);
181 return true;
182}
183
184// Convert a row of an SkYUVAPixmaps to a row of Y,U,V triples.
185// TODO(ccameron): This is horribly inefficient.
186static void yuva_copy_row(const SkYUVAPixmaps& src, int row, uint8_t* dst) {
187 int width = src.plane(0).width();
188 switch (src.yuvaInfo().planeConfig()) {
190 auto [ssWidthU, ssHeightU] = src.yuvaInfo().planeSubsamplingFactors(1);
191 auto [ssWidthV, ssHeightV] = src.yuvaInfo().planeSubsamplingFactors(2);
192 const uint8_t* srcY = reinterpret_cast<const uint8_t*>(src.plane(0).addr(0, row));
193 const uint8_t* srcU =
194 reinterpret_cast<const uint8_t*>(src.plane(1).addr(0, row / ssHeightU));
195 const uint8_t* srcV =
196 reinterpret_cast<const uint8_t*>(src.plane(2).addr(0, row / ssHeightV));
197 for (int col = 0; col < width; ++col) {
198 dst[3 * col + 0] = srcY[col];
199 dst[3 * col + 1] = srcU[col / ssWidthU];
200 dst[3 * col + 2] = srcV[col / ssWidthV];
201 }
202 break;
203 }
205 auto [ssWidthUV, ssHeightUV] = src.yuvaInfo().planeSubsamplingFactors(1);
206 const uint8_t* srcY = reinterpret_cast<const uint8_t*>(src.plane(0).addr(0, row));
207 const uint8_t* srcUV =
208 reinterpret_cast<const uint8_t*>(src.plane(1).addr(0, row / ssHeightUV));
209 for (int col = 0; col < width; ++col) {
210 dst[3 * col + 0] = srcY[col];
211 dst[3 * col + 1] = srcUV[2 * (col / ssWidthUV) + 0];
212 dst[3 * col + 2] = srcUV[2 * (col / ssWidthUV) + 1];
213 }
214 break;
215 }
216 default:
217 break;
218 }
219}
220
223 const SkJpegMetadataEncoder::SegmentList& metadataSegments) {
224 fCInfo.image_width = srcInfo.yuvaInfo().width();
225 fCInfo.image_height = srcInfo.yuvaInfo().height();
226 fCInfo.in_color_space = JCS_YCbCr;
227 fCInfo.input_components = 3;
228 jpeg_set_defaults(&fCInfo);
229
230 // Support no color space conversion.
231 if (srcInfo.yuvColorSpace() != kJPEG_Full_SkYUVColorSpace) {
232 return false;
233 }
234
235 // Support only 8-bit data.
236 switch (srcInfo.dataType()) {
238 break;
239 default:
240 return false;
241 }
242
243 // Support only Y,U,V and Y,UV configurations (they are the only ones supported by
244 // yuva_copy_row).
245 switch (srcInfo.yuvaInfo().planeConfig()) {
248 break;
249 default:
250 return false;
251 }
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 auto [ssHoriz, ssVert] = SkYUVAInfo::SubsamplingFactors(srcInfo.yuvaInfo().subsampling());
256 fCInfo.comp_info[0].h_samp_factor = ssHoriz;
257 fCInfo.comp_info[0].v_samp_factor = ssVert;
258
259 initializeCommon(options, metadataSegments);
260 return true;
261}
262
263void SkJpegEncoderMgr::initializeCommon(
265 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 fCInfo.optimize_coding = TRUE;
270
271 jpeg_set_quality(&fCInfo, options.fQuality, TRUE);
272 jpeg_start_compress(&fCInfo, TRUE);
273
274 for (const auto& segment : metadataSegments) {
275 jpeg_write_marker(&fCInfo,
276 segment.fMarker,
277 segment.fParameters->bytes(),
278 segment.fParameters->size());
279 }
280}
281
282std::unique_ptr<SkEncoder> SkJpegEncoderImpl::MakeYUV(
283 SkWStream* dst,
284 const SkYUVAPixmaps& srcYUVA,
285 const SkColorSpace* srcYUVAColorSpace,
287 const SkJpegMetadataEncoder::SegmentList& metadataSegments) {
288 if (!srcYUVA.isValid()) {
289 return nullptr;
290 }
291 std::unique_ptr<SkJpegEncoderMgr> encoderMgr = SkJpegEncoderMgr::Make(dst);
292 skjpeg_error_mgr::AutoPushJmpBuf jmp(encoderMgr->errorMgr());
293 if (setjmp(jmp)) {
294 return nullptr;
295 }
296
297 if (!encoderMgr->initializeYUV(srcYUVA.pixmapsInfo(), options, metadataSegments)) {
298 return nullptr;
299 }
300 return std::unique_ptr<SkJpegEncoderImpl>(
301 new SkJpegEncoderImpl(std::move(encoderMgr), srcYUVA));
302}
303
304std::unique_ptr<SkEncoder> SkJpegEncoderImpl::MakeRGB(
305 SkWStream* dst,
306 const SkPixmap& src,
308 const SkJpegMetadataEncoder::SegmentList& metadataSegments) {
309 if (!SkPixmapIsValid(src)) {
310 return nullptr;
311 }
312 std::unique_ptr<SkJpegEncoderMgr> encoderMgr = SkJpegEncoderMgr::Make(dst);
313 skjpeg_error_mgr::AutoPushJmpBuf jmp(encoderMgr->errorMgr());
314 if (setjmp(jmp)) {
315 return nullptr;
316 }
317
318 if (!encoderMgr->initializeRGB(src.info(), options, metadataSegments)) {
319 return nullptr;
320 }
321 return std::unique_ptr<SkJpegEncoderImpl>(new SkJpegEncoderImpl(std::move(encoderMgr), src));
322}
323
324SkJpegEncoderImpl::SkJpegEncoderImpl(std::unique_ptr<SkJpegEncoderMgr> encoderMgr,
325 const SkPixmap& src)
326 : SkEncoder(src,
327 encoderMgr->proc() ? encoderMgr->cinfo()->input_components * src.width() : 0)
328 , fEncoderMgr(std::move(encoderMgr)) {}
329
330SkJpegEncoderImpl::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 , fSrcYUVA(src) {}
335
337
339 skjpeg_error_mgr::AutoPushJmpBuf jmp(fEncoderMgr->errorMgr());
340 if (setjmp(jmp)) {
341 return false;
342 }
343
344 if (fSrcYUVA) {
345 // TODO(ccameron): Consider using jpeg_write_raw_data, to avoid having to re-pack the data.
346 for (int i = 0; i < numRows; i++) {
347 yuva_copy_row(*fSrcYUVA, fCurrRow + i, fStorage.get());
348 JSAMPLE* jpegSrcRow = fStorage.get();
349 jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1);
350 }
351 } else {
352 const size_t srcBytes = SkColorTypeBytesPerPixel(fSrc.colorType()) * fSrc.width();
353 const size_t jpegSrcBytes = fEncoderMgr->cinfo()->input_components * fSrc.width();
354 const void* srcRow = fSrc.addr(0, fCurrRow);
355 for (int i = 0; i < numRows; i++) {
356 JSAMPLE* jpegSrcRow = (JSAMPLE*)(const_cast<void*>(srcRow));
357 if (fEncoderMgr->proc()) {
358 sk_msan_assert_initialized(srcRow, SkTAddOffset<const void>(srcRow, srcBytes));
359 fEncoderMgr->proc()((char*)fStorage.get(),
360 (const char*)srcRow,
361 fSrc.width(),
362 fEncoderMgr->cinfo()->input_components);
363 jpegSrcRow = fStorage.get();
365 SkTAddOffset<const void>(jpegSrcRow, jpegSrcBytes));
366 } else {
367 // Same as above, but this repetition allows determining whether a
368 // proc was used when msan asserts.
370 SkTAddOffset<const void>(jpegSrcRow, jpegSrcBytes));
371 }
372
373 jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1);
374 srcRow = SkTAddOffset<const void>(srcRow, fSrc.rowBytes());
375 }
376 }
377
378 fCurrRow += numRows;
379 if (fCurrRow == fSrc.height()) {
380 jpeg_finish_compress(fEncoderMgr->cinfo());
381 }
382
383 return true;
384}
385
386namespace SkJpegEncoder {
387
388bool Encode(SkWStream* dst, const SkPixmap& src, const Options& options) {
389 auto encoder = Make(dst, src, options);
390 return encoder.get() && encoder->encodeRows(src.height());
391}
392
394 const SkYUVAPixmaps& src,
395 const SkColorSpace* srcColorSpace,
396 const Options& options) {
397 auto encoder = Make(dst, src, srcColorSpace, options);
398 return encoder.get() && encoder->encodeRows(src.yuvaInfo().height());
399}
400
401sk_sp<SkData> Encode(GrDirectContext* ctx, const SkImage* img, const Options& options) {
402 if (!img) {
403 return nullptr;
404 }
405 SkBitmap bm;
406 if (!as_IB(img)->getROPixels(ctx, &bm)) {
407 return nullptr;
408 }
410 if (Encode(&stream, bm.pixmap(), options)) {
411 return stream.detachAsData();
412 }
413 return nullptr;
414}
415
416std::unique_ptr<SkEncoder> Make(SkWStream* dst, const SkPixmap& src, const Options& options) {
417 SkJpegMetadataEncoder::SegmentList metadataSegments;
418 SkJpegMetadataEncoder::AppendXMPStandard(metadataSegments, options.xmpMetadata);
419 SkJpegMetadataEncoder::AppendICC(metadataSegments, options, src.colorSpace());
420 return SkJpegEncoderImpl::MakeRGB(dst, src, options, metadataSegments);
421}
422
423std::unique_ptr<SkEncoder> Make(SkWStream* dst,
424 const SkYUVAPixmaps& src,
425 const SkColorSpace* srcColorSpace,
426 const Options& options) {
427 SkJpegMetadataEncoder::SegmentList metadataSegments;
428 SkJpegMetadataEncoder::AppendXMPStandard(metadataSegments, options.xmpMetadata);
429 SkJpegMetadataEncoder::AppendICC(metadataSegments, options, srcColorSpace);
430 return SkJpegEncoderImpl::MakeYUV(dst, src, srcColorSpace, options, metadataSegments);
431}
432
433} // namespace SkJpegEncoder
434
436
437void AppendICC(SegmentList& segmentList,
439 const SkColorSpace* colorSpace) {
440 sk_sp<SkData> icc =
441 icc_from_color_space(colorSpace, options.fICCProfile, options.fICCProfileDescription);
442 if (!icc) {
443 return;
444 }
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.
449 s.write(kICCSig, sizeof(kICCSig));
450 s.write8(1); // This is the first marker.
451 s.write8(1); // Out of one total markers.
452 s.write(icc->data(), icc->size());
453 segmentList.emplace_back(kICCMarker, s.detachAsData());
454}
455
456void AppendXMPStandard(SegmentList& segmentList, const SkData* xmpMetadata) {
457 if (!xmpMetadata) {
458 return;
459 }
460
461 // TODO(ccameron): Split this into a standard and extended XMP segment if needed.
463 s.write(kXMPStandardSig, sizeof(kXMPStandardSig));
464 s.write(xmpMetadata->data(), xmpMetadata->size());
465 segmentList.emplace_back(kXMPMarker, s.detachAsData());
466}
467
468} // namespace SkJpegMetadataEncoder
const char * options
kUnpremul_SkAlphaType
#define SkASSERT(cond)
Definition: SkAssert.h:116
@ kR8_unorm_SkColorType
Definition: SkColorType.h:54
@ kARGB_4444_SkColorType
pixel with 4 bits for alpha, red, green, blue; in 16-bit word
Definition: SkColorType.h:23
@ kBGRA_8888_SkColorType
pixel with 8 bits for blue, green, red, alpha; in 32-bit word
Definition: SkColorType.h:26
@ kRGBA_F16_SkColorType
pixel with half floats for red, green, blue, alpha;
Definition: SkColorType.h:38
@ kAlpha_8_SkColorType
pixel with alpha in 8-bit byte
Definition: SkColorType.h:21
@ kGray_8_SkColorType
pixel with grayscale level in 8-bit byte
Definition: SkColorType.h:35
@ kRGB_565_SkColorType
pixel with 5 bits red, 6 bits green, 5 bits blue, in 16-bit word
Definition: SkColorType.h:22
@ kRGBA_8888_SkColorType
pixel with 8 bits for red, green, blue, alpha; in 32-bit word
Definition: SkColorType.h:24
static void transform_scanline_to_premul_legacy(char *dst, const char *src, int width, int)
static void transform_scanline_F16_to_8888(char *dst, const char *src, int width, int)
void(* transform_scanline_proc)(char *dst, const char *src, int width, int bpp)
static void transform_scanline_565(char *dst, const char *src, int width, int)
static void transform_scanline_F16_to_premul_8888(char *dst, const char *src, int width, int)
static void transform_scanline_444(char *dst, const char *src, int width, int)
static sk_sp< SkData > icc_from_color_space(const SkColorSpace *cs, const skcms_ICCProfile *profile, const char *profile_description)
static bool SkPixmapIsValid(const SkPixmap &src)
SK_API int SkColorTypeBytesPerPixel(SkColorType ct)
Definition: SkImageInfo.cpp:16
@ kJPEG_Full_SkYUVColorSpace
describes full range
Definition: SkImageInfo.h:69
static SkImage_Base * as_IB(SkImage *image)
Definition: SkImage_Base.h:201
void skjpeg_error_exit(j_common_ptr cinfo)
static constexpr uint8_t kICCSig[]
static constexpr uint32_t kICCMarker
static constexpr uint32_t kXMPMarker
static constexpr uint8_t kXMPStandardSig[]
static void yuva_copy_row(const SkYUVAPixmaps &src, int row, uint8_t *dst)
static void sk_msan_assert_initialized(const void *begin, const void *end)
Definition: SkMSAN.h:24
const SkPixmap & pixmap() const
Definition: SkBitmap.h:133
Definition: SkData.h:25
const void * data() const
Definition: SkData.h:37
size_t size() const
Definition: SkData.h:30
skia_private::AutoTMalloc< uint8_t > fStorage
Definition: SkEncoder.h:60
int fCurrRow
Definition: SkEncoder.h:59
const SkPixmap & fSrc
Definition: SkEncoder.h:58
static std::unique_ptr< SkEncoder > MakeRGB(SkWStream *dst, const SkPixmap &src, const SkJpegEncoder::Options &options, const SkJpegMetadataEncoder::SegmentList &metadata)
bool onEncodeRows(int numRows) override
static std::unique_ptr< SkEncoder > MakeYUV(SkWStream *dst, const SkYUVAPixmaps &srcYUVA, const SkColorSpace *srcYUVAColorSpace, const SkJpegEncoder::Options &options, const SkJpegMetadataEncoder::SegmentList &metadata)
skjpeg_error_mgr * errorMgr()
bool initializeRGB(const SkImageInfo &, const SkJpegEncoder::Options &, const SkJpegMetadataEncoder::SegmentList &)
jpeg_compress_struct * cinfo()
transform_scanline_proc proc() const
static std::unique_ptr< SkJpegEncoderMgr > Make(SkWStream *stream)
bool initializeYUV(const SkYUVAPixmapInfo &, const SkJpegEncoder::Options &, const SkJpegMetadataEncoder::SegmentList &)
size_t rowBytes() const
Definition: SkPixmap.h:145
int width() const
Definition: SkPixmap.h:160
SkColorType colorType() const
Definition: SkPixmap.h:173
const void * addr() const
Definition: SkPixmap.h:153
int height() const
Definition: SkPixmap.h:166
int width() const
Definition: SkYUVAInfo.h:172
@ kY_U_V
Plane 0: Y, Plane 1: U, Plane 2: V.
@ kY_UV
Plane 0: Y, Plane 1: UV.
int height() const
Definition: SkYUVAInfo.h:173
PlaneConfig planeConfig() const
Definition: SkYUVAInfo.h:160
static std::tuple< int, int > SubsamplingFactors(Subsampling)
Definition: SkYUVAInfo.cpp:29
Subsampling subsampling() const
Definition: SkYUVAInfo.h:161
SkYUVColorSpace yuvColorSpace() const
const SkYUVAInfo & yuvaInfo() const
@ kUnorm8
8 bit unsigned normalized
DataType dataType() const
SkYUVAPixmapInfo pixmapsInfo() const
bool isValid() const
struct MyStruct s
SK_API std::unique_ptr< SkEncoder > Make(SkWStream *dst, const SkPixmap &src, const Options &options)
SK_API bool Encode(SkWStream *dst, const SkPixmap &src, const Options &options)
void AppendICC(SegmentList &segmentList, const SkJpegEncoder::Options &options, const SkColorSpace *colorSpace)
void AppendXMPStandard(SegmentList &segmentList, const SkData *xmpMetadata)
std::vector< Segment > SegmentList
dst
Definition: cp.py:12
Definition: ref_ptr.h:256
int32_t width
int width() const
Definition: SkImageInfo.h:365
SkAlphaType alphaType() const
Definition: SkImageInfo.h:375
SkColorType colorType() const
Definition: SkImageInfo.h:373
int height() const
Definition: SkImageInfo.h:371