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