Flutter Engine
The Flutter Engine
SkPDFBitmap.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2015 Google Inc.
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
19#include "include/core/SkData.h"
24#include "include/core/SkSize.h"
33#include "modules/skcms/skcms.h"
34#include "src/core/SkTHash.h"
35#include "src/pdf/SkDeflate.h"
37#include "src/pdf/SkPDFTypes.h"
38#include "src/pdf/SkPDFUnion.h"
39
40#include <algorithm>
41#include <array>
42#include <cstring>
43#include <functional>
44#include <memory>
45#include <optional>
46#include <utility>
47
49 return codec.getEncodedInfo();
50}
51
52namespace {
53
54// write a single byte to a stream n times.
55void fill_stream(SkWStream* out, char value, size_t n) {
56 char buffer[4096];
57 memset(buffer, value, sizeof(buffer));
58 for (size_t i = 0; i < n / sizeof(buffer); ++i) {
59 out->write(buffer, sizeof(buffer));
60 }
61 out->write(buffer, n % sizeof(buffer));
62}
63
64/* It is necessary to average the color component of transparent
65 pixels with their surrounding neighbors since the PDF renderer may
66 separately re-sample the alpha and color channels when the image is
67 not displayed at its native resolution. Since an alpha of zero
68 gives no information about the color component, the pathological
69 case is a white image with sharp transparency bounds - the color
70 channel goes to black, and the should-be-transparent pixels are
71 rendered as grey because of the separate soft mask and color
72 resizing. e.g.: gm/bitmappremul.cpp */
73SkColor get_neighbor_avg_color(const SkPixmap& bm, int xOrig, int yOrig) {
75 unsigned r = 0, g = 0, b = 0, n = 0;
76 // Clamp the range to the edge of the bitmap.
77 int ymin = std::max(0, yOrig - 1);
78 int ymax = std::min(yOrig + 1, bm.height() - 1);
79 int xmin = std::max(0, xOrig - 1);
80 int xmax = std::min(xOrig + 1, bm.width() - 1);
81 for (int y = ymin; y <= ymax; ++y) {
82 const SkColor* scanline = bm.addr32(0, y);
83 for (int x = xmin; x <= xmax; ++x) {
84 SkColor color = scanline[x];
86 r += SkColorGetR(color);
87 g += SkColorGetG(color);
89 n++;
90 }
91 }
92 }
93 return n > 0 ? SkColorSetRGB(SkToU8(r / n), SkToU8(g / n), SkToU8(b / n))
95}
96
97enum class SkPDFStreamFormat { DCT, Flate, Uncompressed };
98
99template <typename T>
100void emit_image_stream(SkPDFDocument* doc,
102 T writeStream,
104 SkPDFUnion&& colorSpace,
106 int length,
107 SkPDFStreamFormat format) {
108 SkPDFDict pdfDict("XObject");
109 pdfDict.insertName("Subtype", "Image");
110 pdfDict.insertInt("Width", size.width());
111 pdfDict.insertInt("Height", size.height());
112 pdfDict.insertUnion("ColorSpace", std::move(colorSpace));
113 if (sMask) {
114 pdfDict.insertRef("SMask", sMask);
115 }
116 pdfDict.insertInt("BitsPerComponent", 8);
117 #ifdef SK_PDF_BASE85_BINARY
118 auto filters = SkPDFMakeArray();
119 filters->appendName("ASCII85Decode");
120 switch (format) {
121 case SkPDFStreamFormat::DCT: filters->appendName("DCTDecode"); break;
122 case SkPDFStreamFormat::Flate: filters->appendName("FlateDecode"); break;
123 case SkPDFStreamFormat::Uncompressed: break;
124 }
125 pdfDict.insertObject("Filter", std::move(filters));
126 #else
127 switch (format) {
128 case SkPDFStreamFormat::DCT: pdfDict.insertName("Filter", "DCTDecode"); break;
129 case SkPDFStreamFormat::Flate: pdfDict.insertName("Filter", "FlateDecode"); break;
130 case SkPDFStreamFormat::Uncompressed: break;
131 }
132 #endif
133 if (format == SkPDFStreamFormat::DCT) {
134 pdfDict.insertInt("ColorTransform", 0);
135 }
136 pdfDict.insertInt("Length", length);
137 doc->emitStream(pdfDict, std::move(writeStream), ref);
138}
139
140void do_deflated_alpha(const SkPixmap& pm, SkPDFDocument* doc, SkPDFIndirectReference ref) {
142 SkPDFStreamFormat format = compressionLevel == SkPDF::Metadata::CompressionLevel::None
143 ? SkPDFStreamFormat::Uncompressed
144 : SkPDFStreamFormat::Flate;
147 std::optional<SkDeflateWStream> deflateWStream;
148 if (format == SkPDFStreamFormat::Flate) {
149 deflateWStream.emplace(&buffer, SkToInt(compressionLevel));
150 stream = &*deflateWStream;
151 }
152 if (kAlpha_8_SkColorType == pm.colorType()) {
153 SkASSERT(pm.rowBytes() == (size_t)pm.width());
154 stream->write(pm.addr8(), pm.width() * pm.height());
155 } else {
158 SkASSERT(pm.rowBytes() == (size_t)pm.width() * 4);
159 const uint32_t* ptr = pm.addr32();
160 const uint32_t* stop = ptr + pm.height() * pm.width();
161
162 uint8_t byteBuffer[4092];
163 uint8_t* bufferStop = byteBuffer + std::size(byteBuffer);
164 uint8_t* dst = byteBuffer;
165 while (ptr != stop) {
166 *dst++ = 0xFF & ((*ptr++) >> SK_BGRA_A32_SHIFT);
167 if (dst == bufferStop) {
168 stream->write(byteBuffer, sizeof(byteBuffer));
169 dst = byteBuffer;
170 }
171 }
172 stream->write(byteBuffer, dst - byteBuffer);
173 }
174 if (deflateWStream) {
175 deflateWStream->finalize();
176 }
177
178 #ifdef SK_PDF_BASE85_BINARY
179 SkPDFUtils::Base85Encode(buffer.detachAsStream(), &buffer);
180 #endif
181 int length = SkToInt(buffer.bytesWritten());
182 emit_image_stream(doc, ref, [&buffer](SkWStream* stream) { buffer.writeToAndReset(stream); },
183 pm.info().dimensions(), SkPDFUnion::Name("DeviceGray"),
185}
186
187SkPDFUnion write_icc_profile(SkPDFDocument* doc, sk_sp<SkData>&& icc, int channels) {
188 SkPDFIndirectReference iccStreamRef;
189 {
190 static SkMutex iccProfileMapMutex;
191 SkAutoMutexExclusive lock(iccProfileMapMutex);
192
194 if (ref) {
195 iccStreamRef = *ref;
196 } else {
197 std::unique_ptr<SkPDFDict> iccStreamDict = SkPDFMakeDict();
198 iccStreamDict->insertInt("N", channels);
199 iccStreamRef = SkPDFStreamOut(std::move(iccStreamDict), SkMemoryStream::Make(icc), doc);
200 doc->fICCProfileMap.set(SkPDFIccProfileKey{icc, channels}, iccStreamRef);
201 }
202 }
203
204 std::unique_ptr<SkPDFArray> iccPDF = SkPDFMakeArray();
205 iccPDF->appendName("ICCBased");
206 iccPDF->appendRef(iccStreamRef);
207 return SkPDFUnion::Object(std::move(iccPDF));
208}
209
210void do_deflated_image(const SkPixmap& pm,
211 SkPDFDocument* doc,
212 bool isOpaque,
215 if (!isOpaque) {
216 sMask = doc->reserveRef();
217 }
219 SkPDFStreamFormat format = compressionLevel == SkPDF::Metadata::CompressionLevel::None
220 ? SkPDFStreamFormat::Uncompressed
221 : SkPDFStreamFormat::Flate;
224 std::optional<SkDeflateWStream> deflateWStream;
225 if (format == SkPDFStreamFormat::Flate) {
226 deflateWStream.emplace(&buffer, SkToInt(compressionLevel));
227 stream = &*deflateWStream;
228 }
229 SkPDFUnion colorSpace = SkPDFUnion::Name("DeviceGray");
230 int channels;
231 switch (pm.colorType()) {
233 channels = 1;
234 fill_stream(stream, '\x00', pm.width() * pm.height());
235 break;
237 channels = 1;
238 SkASSERT(sMask.fValue = -1);
239 SkASSERT(pm.rowBytes() == (size_t)pm.width());
240 stream->write(pm.addr8(), pm.width() * pm.height());
241 break;
242 default:
243 colorSpace = SkPDFUnion::Name("DeviceRGB");
244 channels = 3;
247 SkASSERT(pm.rowBytes() == (size_t)pm.width() * 4);
248 uint8_t byteBuffer[3072];
249 static_assert(std::size(byteBuffer) % 3 == 0, "");
250 uint8_t* bufferStop = byteBuffer + std::size(byteBuffer);
251 uint8_t* dst = byteBuffer;
252 for (int y = 0; y < pm.height(); ++y) {
253 const SkColor* src = pm.addr32(0, y);
254 for (int x = 0; x < pm.width(); ++x) {
255 SkColor color = *src++;
257 color = get_neighbor_avg_color(pm, x, y);
258 }
259 *dst++ = SkColorGetR(color);
260 *dst++ = SkColorGetG(color);
261 *dst++ = SkColorGetB(color);
262 if (dst == bufferStop) {
263 stream->write(byteBuffer, sizeof(byteBuffer));
264 dst = byteBuffer;
265 }
266 }
267 }
268 stream->write(byteBuffer, dst - byteBuffer);
269 }
270 if (deflateWStream) {
271 deflateWStream->finalize();
272 }
273
274 if (pm.colorSpace() && channels != 1) {
275 skcms_ICCProfile iccProfile;
276 pm.colorSpace()->toProfile(&iccProfile);
277 sk_sp<SkData> iccData = SkWriteICCProfile(&iccProfile, "");
278 colorSpace = write_icc_profile(doc, std::move(iccData), channels);
279 }
280
281 #ifdef SK_PDF_BASE85_BINARY
282 SkPDFUtils::Base85Encode(buffer.detachAsStream(), &buffer);
283 #endif
284 int length = SkToInt(buffer.bytesWritten());
285 emit_image_stream(doc, ref, [&buffer](SkWStream* stream) { buffer.writeToAndReset(stream); },
286 pm.info().dimensions(), std::move(colorSpace), sMask, length, format);
287 if (!isOpaque) {
288 do_deflated_alpha(pm, doc, sMask);
289 }
290}
291
292bool do_jpeg(sk_sp<SkData> data, SkColorSpace* imageColorSpace, SkPDFDocument* doc, SkISize size,
294 static constexpr const SkCodecs::Decoder decoders[] = {
296 };
297 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data, decoders);
298 if (!codec) {
299 return false;
300 }
301
302 SkISize jpegSize = codec->dimensions();
303 const SkEncodedInfo& encodedInfo = SkPDFBitmap::GetEncodedInfo(*codec);
304 SkEncodedInfo::Color jpegColorType = encodedInfo.color();
305 SkEncodedOrigin exifOrientation = codec->getOrigin();
306
307 bool yuv = jpegColorType == SkEncodedInfo::kYUV_Color;
308 bool goodColorType = yuv || jpegColorType == SkEncodedInfo::kGray_Color;
309 if (jpegSize != size // Safety check.
310 || !goodColorType
311 || kTopLeft_SkEncodedOrigin != exifOrientation) {
312 return false;
313 }
314 #ifdef SK_PDF_BASE85_BINARY
316 SkPDFUtils::Base85Encode(SkMemoryStream::MakeDirect(data->data(), data->size()), &buffer);
317 data = buffer.detachAsData();
318 #endif
319
320 int channels = yuv ? 3 : 1;
321 SkPDFUnion colorSpace = yuv ? SkPDFUnion::Name("DeviceRGB") : SkPDFUnion::Name("DeviceGray");
322 if (sk_sp<SkData> encodedIccProfileData = encodedInfo.profileData()) {
323 colorSpace = write_icc_profile(doc, std::move(encodedIccProfileData), channels);
324 } else if (const skcms_ICCProfile* codecIccProfile = codec->getICCProfile()) {
325 sk_sp<SkData> codecIccData = SkWriteICCProfile(codecIccProfile, "");
326 colorSpace = write_icc_profile(doc, std::move(codecIccData), channels);
327 } else if (imageColorSpace && channels != 1) {
328 skcms_ICCProfile imageIccProfile;
329 imageColorSpace->toProfile(&imageIccProfile);
330 sk_sp<SkData> imageIccData = SkWriteICCProfile(&imageIccProfile, "");
331 colorSpace = write_icc_profile(doc, std::move(imageIccData), channels);
332 }
333
334 emit_image_stream(doc, ref,
335 [&data](SkWStream* dst) { dst->write(data->data(), data->size()); },
336 jpegSize, std::move(colorSpace),
337 SkPDFIndirectReference(), SkToInt(data->size()), SkPDFStreamFormat::DCT);
338 return true;
339}
340
341SkBitmap to_pixels(const SkImage* image) {
342 SkBitmap bm;
343 int w = image->width(),
344 h = image->height();
345 switch (image->colorType()) {
348 break;
351 break;
352 default: {
353 // TODO: makeColorSpace(sRGB) or actually tag the images
355 bm.allocPixels(
357 }
358 }
359 // TODO: support GPU images in PDFs
360 if (!image->readPixels(nullptr, bm.pixmap(), 0, 0)) {
361 bm.eraseColor(SkColorSetARGB(0xFF, 0, 0, 0));
362 }
363 return bm;
364}
365
366void serialize_image(const SkImage* img,
367 int encodingQuality,
368 SkPDFDocument* doc,
370 SkASSERT(img);
371 SkASSERT(doc);
372 SkASSERT(encodingQuality >= 0);
373 SkISize dimensions = img->dimensions();
374
375 if (sk_sp<SkData> data = img->refEncodedData()) {
376 if (do_jpeg(std::move(data), img->colorSpace(), doc, dimensions, ref)) {
377 return;
378 }
379 }
380 SkBitmap bm = to_pixels(img);
381 const SkPixmap& pm = bm.pixmap();
382 bool isOpaque = pm.isOpaque() || pm.computeIsOpaque();
383 if (encodingQuality <= 100 && isOpaque) {
385 jOpts.fQuality = encodingQuality;
387 if (SkJpegEncoder::Encode(&stream, pm, jOpts)) {
388 if (do_jpeg(stream.detachAsData(), pm.colorSpace(), doc, dimensions, ref)) {
389 return;
390 }
391 }
392 }
393 do_deflated_image(pm, doc, isOpaque, ref);
394}
395
396} // namespace
397
399 SkPDFDocument* doc,
400 int encodingQuality) {
401 SkASSERT(img);
402 SkASSERT(doc);
404 if (SkExecutor* executor = doc->executor()) {
405 SkRef(img);
406 doc->incrementJobCount();
407 executor->add([img, encodingQuality, doc, ref]() {
408 serialize_image(img, encodingQuality, doc, ref);
409 SkSafeUnref(img);
410 doc->signalJobComplete();
411 });
412 return ref;
413 }
414 serialize_image(img, encodingQuality, doc, ref);
415 return ref;
416}
kUnpremul_SkAlphaType
SkAlphaType
Definition: SkAlphaType.h:26
@ kOpaque_SkAlphaType
pixel is opaque
Definition: SkAlphaType.h:28
#define SkASSERT(cond)
Definition: SkAssert.h:116
#define SK_BGRA_A32_SHIFT
Definition: SkColorPriv.h:70
@ kBGRA_8888_SkColorType
pixel with 8 bits for blue, green, red, alpha; in 32-bit word
Definition: SkColorType.h:26
@ 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
#define SkColorGetR(color)
Definition: SkColor.h:65
#define SkColorGetG(color)
Definition: SkColor.h:69
uint32_t SkColor
Definition: SkColor.h:37
#define SkColorSetRGB(r, g, b)
Definition: SkColor.h:57
constexpr SkColor SK_ColorTRANSPARENT
Definition: SkColor.h:99
static constexpr SkColor SkColorSetARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b)
Definition: SkColor.h:49
#define SkColorGetA(color)
Definition: SkColor.h:61
#define SkColorGetB(color)
Definition: SkColor.h:73
constexpr SkAlpha SK_AlphaTRANSPARENT
Definition: SkColor.h:89
SkEncodedOrigin
@ kTopLeft_SkEncodedOrigin
SK_API sk_sp< SkData > SkWriteICCProfile(const skcms_TransferFunction &, const skcms_Matrix3x3 &toXYZD50)
Definition: SkICC.cpp:682
SkPDFIndirectReference SkPDFSerializeImage(const SkImage *img, SkPDFDocument *doc, int encodingQuality)
SkPDFIndirectReference SkPDFStreamOut(std::unique_ptr< SkPDFDict > dict, std::unique_ptr< SkStreamAsset > content, SkPDFDocument *doc, SkPDFSteamCompressionEnabled compress)
Definition: SkPDFTypes.cpp:591
static std::unique_ptr< SkPDFDict > SkPDFMakeDict(const char *type=nullptr)
Definition: SkPDFTypes.h:185
static std::unique_ptr< SkPDFArray > SkPDFMakeArray(Args... args)
Definition: SkPDFTypes.h:125
static void SkSafeUnref(T *obj)
Definition: SkRefCnt.h:149
static T * SkRef(T *obj)
Definition: SkRefCnt.h:132
constexpr int SkToInt(S x)
Definition: SkTo.h:29
constexpr uint8_t SkToU8(S x)
Definition: SkTo.h:22
static sk_sp< SkData > serialize_image(const SkImage *image, SkSerialProcs procs)
void allocPixels(const SkImageInfo &info, size_t rowBytes)
Definition: SkBitmap.cpp:258
bool isOpaque() const
Definition: SkBitmap.h:324
const SkPixmap & pixmap() const
Definition: SkBitmap.h:133
void eraseColor(SkColor4f) const
Definition: SkBitmap.cpp:442
static std::unique_ptr< SkCodec > MakeFromData(sk_sp< SkData >, SkSpan< const SkCodecs::Decoder > decoders, SkPngChunkReader *=nullptr)
Definition: SkCodec.cpp:241
const SkEncodedInfo & getEncodedInfo() const
Definition: SkCodec.h:788
void toProfile(skcms_ICCProfile *) const
SkColorSpace * colorSpace() const
Definition: SkImage.cpp:156
SkISize dimensions() const
Definition: SkImage.h:297
bool readPixels(GrDirectContext *context, const SkImageInfo &dstInfo, void *dstPixels, size_t dstRowBytes, int srcX, int srcY, CachingHint cachingHint=kAllow_CachingHint) const
Definition: SkImage.cpp:42
int width() const
Definition: SkImage.h:285
SkColorType colorType() const
Definition: SkImage.cpp:152
int height() const
Definition: SkImage.h:291
sk_sp< SkData > refEncodedData() const
Definition: SkImage.cpp:214
sk_sp< SkColorSpace > refColorSpace() const
Definition: SkImage.cpp:158
static std::unique_ptr< SkMemoryStream > Make(sk_sp< SkData > data)
Definition: SkStream.cpp:314
static std::unique_ptr< SkMemoryStream > MakeDirect(const void *data, size_t length)
Definition: SkStream.cpp:310
static const SkEncodedInfo & GetEncodedInfo(SkCodec &)
Definition: SkPDFBitmap.cpp:48
SkExecutor * executor() const
void signalJobComplete()
const SkPDF::Metadata & metadata() const
void incrementJobCount()
skia_private::THashMap< SkPDFIccProfileKey, SkPDFIndirectReference, SkPDFIccProfileKey::Hash > fICCProfileMap
void emitStream(const SkPDFDict &dict, T writeStream, SkPDFIndirectReference ref)
SkPDFIndirectReference reserveRef()
static SkPDFUnion Object(std::unique_ptr< SkPDFObject >)
Definition: SkPDFTypes.cpp:353
static SkPDFUnion Name(const char *)
Definition: SkPDFTypes.cpp:325
const uint8_t * addr8() const
Definition: SkPixmap.h:326
bool computeIsOpaque() const
Definition: SkPixmap.cpp:577
const uint32_t * addr32() const
Definition: SkPixmap.h:352
size_t rowBytes() const
Definition: SkPixmap.h:145
int width() const
Definition: SkPixmap.h:160
SkColorType colorType() const
Definition: SkPixmap.h:173
bool isOpaque() const
Definition: SkPixmap.h:201
SkColorSpace * colorSpace() const
Definition: SkPixmap.cpp:61
const SkImageInfo & info() const
Definition: SkPixmap.h:135
int height() const
Definition: SkPixmap.h:166
SkAlphaType alphaType() const
Definition: SkPixmap.h:175
V * find(const K &key) const
Definition: SkTHash.h:494
V * set(K key, V val)
Definition: SkTHash.h:487
DlColor color
static bool b
uint8_t value
uint32_t uint32_t * format
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
size_t length
double y
double x
ImplicitString Name
Definition: DMSrcSink.h:38
constexpr SkCodecs::Decoder Decoder()
Definition: SkJpegDecoder.h:38
SK_API bool Encode(SkWStream *dst, const SkPixmap &src, const Options &options)
sk_sp< const SkImage > image
Definition: SkRecords.h:269
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
dst
Definition: cp.py:12
SkScalar w
SkScalar h
#define T
Definition: precompiler.cc:65
sk_sp< SkData > profileData() const
Color color() const
Definition: SkSize.h:16
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)
static SkImageInfo MakeA8(int width, int height)
enum SkPDF::Metadata::CompressionLevel fCompressionLevel
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63