1 /*
2 * Copyright 2024 Google LLC.
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/codec/SkPngCodecBase.h"
9
10 #include <cstddef>
11 #include <tuple>
12 #include <utility>
13
14 #include "include/codec/SkCodec.h"
15 #include "include/codec/SkEncodedImageFormat.h"
16 #include "include/core/SkAlphaType.h"
17 #include "include/core/SkColor.h"
18 #include "include/core/SkColorType.h"
19 #include "include/core/SkImageInfo.h"
20 #include "include/core/SkRect.h"
21 #include "include/core/SkStream.h"
22 #include "include/private/SkEncodedInfo.h"
23 #include "include/private/base/SkAssert.h"
24 #include "include/private/base/SkSpan_impl.h"
25 #include "modules/skcms/skcms.h"
26 #include "src/codec/SkCodecPriv.h"
27 #include "src/codec/SkColorPalette.h"
28 #include "src/codec/SkSwizzler.h"
29 #include "src/core/SkMemset.h"
30 #include "src/core/SkSwizzlePriv.h"
31
32 namespace {
33
34 constexpr SkColorType kXformSrcColorType = kRGBA_8888_SkColorType;
35
needs_premul(SkAlphaType dstAT,SkEncodedInfo::Alpha encodedAlpha)36 inline bool needs_premul(SkAlphaType dstAT, SkEncodedInfo::Alpha encodedAlpha) {
37 return kPremul_SkAlphaType == dstAT && SkEncodedInfo::kUnpremul_Alpha == encodedAlpha;
38 }
39
ToPixelFormat(const SkEncodedInfo & info)40 skcms_PixelFormat ToPixelFormat(const SkEncodedInfo& info) {
41 // We use kRGB and kRGBA formats because color PNGs are always RGB or RGBA.
42 if (16 == info.bitsPerComponent()) {
43 if (SkEncodedInfo::kRGBA_Color == info.color()) {
44 return skcms_PixelFormat_RGBA_16161616BE;
45 } else if (SkEncodedInfo::kRGB_Color == info.color()) {
46 return skcms_PixelFormat_RGB_161616BE;
47 }
48 } else if (SkEncodedInfo::kGray_Color == info.color()) {
49 return skcms_PixelFormat_G_8;
50 }
51
52 return skcms_PixelFormat_RGBA_8888;
53 }
54
55 } // namespace
56
57 SkPngCodecBase::~SkPngCodecBase() = default;
58
59 // static
isCompatibleColorProfileAndType(const SkEncodedInfo::ICCProfile * profile,SkEncodedInfo::Color color)60 bool SkPngCodecBase::isCompatibleColorProfileAndType(const SkEncodedInfo::ICCProfile* profile,
61 SkEncodedInfo::Color color) {
62 if (profile) {
63 switch (profile->profile()->data_color_space) {
64 case skcms_Signature_CMYK:
65 return false;
66 case skcms_Signature_Gray:
67 if (SkEncodedInfo::kGray_Color != color &&
68 SkEncodedInfo::kGrayAlpha_Color != color) {
69 return false;
70 }
71 break;
72 default:
73 break;
74 }
75 }
76
77 return true;
78 }
79
SkPngCodecBase(SkEncodedInfo && encodedInfo,std::unique_ptr<SkStream> stream)80 SkPngCodecBase::SkPngCodecBase(SkEncodedInfo&& encodedInfo, std::unique_ptr<SkStream> stream)
81 : SkCodec(std::move(encodedInfo), ToPixelFormat(encodedInfo), std::move(stream)) {}
82
onGetEncodedFormat() const83 SkEncodedImageFormat SkPngCodecBase::onGetEncodedFormat() const {
84 return SkEncodedImageFormat::kPNG;
85 }
86
initializeXforms(const SkImageInfo & dstInfo,const Options & options,int frameWidth)87 SkCodec::Result SkPngCodecBase::initializeXforms(const SkImageInfo& dstInfo,
88 const Options& options,
89 int frameWidth) {
90 if (frameWidth != dstInfo.width() && options.fSubset) {
91 return kInvalidParameters;
92 }
93 fXformWidth = frameWidth;
94
95 {
96 size_t encodedBitsPerPixel = static_cast<size_t>(getEncodedInfo().bitsPerPixel());
97
98 // We assume that `frameWidth` and `bitsPerPixel` have been already sanitized
99 // earlier (and that the multiplication and addition below won't overflow).
100 SkASSERT(0 < frameWidth);
101 SkASSERT(frameWidth < 0xFFFFFF);
102 SkASSERT(encodedBitsPerPixel < 128);
103
104 size_t encodedBitsPerRow = static_cast<size_t>(frameWidth) * encodedBitsPerPixel;
105 fEncodedRowBytes = (encodedBitsPerRow + 7) / 8; // Round up to the next byte.
106
107 #if defined(SK_DEBUG)
108 size_t dstBytesPerPixel = dstInfo.bytesPerPixel();
109 fDstRowBytes = static_cast<size_t>(frameWidth) * dstBytesPerPixel;
110 #endif
111 }
112
113 // Reset fSwizzler and this->colorXform(). We can't do this in onRewind() because the
114 // interlaced scanline decoder may need to rewind.
115 fSwizzler.reset(nullptr);
116
117 // If skcms directly supports the encoded PNG format, we should skip format
118 // conversion in the swizzler (or skip swizzling altogether).
119 bool skipFormatConversion = false;
120 switch (this->getEncodedInfo().color()) {
121 case SkEncodedInfo::kRGB_Color:
122 if (this->getEncodedInfo().bitsPerComponent() != 16) {
123 break;
124 }
125 [[fallthrough]];
126 case SkEncodedInfo::kRGBA_Color:
127 case SkEncodedInfo::kGray_Color:
128 skipFormatConversion = this->colorXform();
129 break;
130 default:
131 break;
132 }
133
134 if (skipFormatConversion && !options.fSubset) {
135 fXformMode = kColorOnly_XformMode;
136 } else {
137 if (SkEncodedInfo::kPalette_Color == this->getEncodedInfo().color()) {
138 if (!this->createColorTable(dstInfo)) {
139 return kInvalidInput;
140 }
141 }
142
143 Result result =
144 this->initializeSwizzler(dstInfo, options, skipFormatConversion, frameWidth);
145 if (result != kSuccess) {
146 return result;
147 }
148 }
149
150 this->allocateStorage(dstInfo);
151
152 // We can't call `initializeXformParams` here, because `swizzleWidth` may
153 // change *after* `onStartIncrementalDecode`
154 // (`SkSampledCodec::sampledDecode` first [transitively] calls
155 // `onStartIncrementalDecode` and *then* `SkSwizzler::onSetSampleX`).
156
157 return kSuccess;
158 }
159
initializeXformParams()160 void SkPngCodecBase::initializeXformParams() {
161 if (fXformMode == kSwizzleColor_XformMode) {
162 fXformWidth = this->swizzler()->swizzleWidth();
163 }
164 }
165
allocateStorage(const SkImageInfo & dstInfo)166 void SkPngCodecBase::allocateStorage(const SkImageInfo& dstInfo) {
167 switch (fXformMode) {
168 case kSwizzleOnly_XformMode:
169 break;
170 case kColorOnly_XformMode:
171 // Intentional fall through. A swizzler hasn't been created yet, but one will
172 // be created later if we are sampling. We'll go ahead and allocate
173 // enough memory to swizzle if necessary.
174 case kSwizzleColor_XformMode: {
175 const int bitsPerPixel = this->getEncodedInfo().bitsPerPixel();
176
177 // If we have more than 8-bits (per component) of precision, we will keep that
178 // extra precision. Otherwise, we will swizzle to RGBA_8888 before transforming.
179 const size_t bytesPerPixel = (bitsPerPixel > 32) ? bitsPerPixel / 8 : 4;
180 const size_t colorXformBytes = dstInfo.width() * bytesPerPixel;
181 fStorage.reset(colorXformBytes);
182 break;
183 }
184 }
185 }
186
initializeSwizzler(const SkImageInfo & dstInfo,const Options & options,bool skipFormatConversion,int frameWidth)187 SkCodec::Result SkPngCodecBase::initializeSwizzler(const SkImageInfo& dstInfo,
188 const Options& options,
189 bool skipFormatConversion,
190 int frameWidth) {
191 SkImageInfo swizzlerInfo = dstInfo;
192 Options swizzlerOptions = options;
193 fXformMode = kSwizzleOnly_XformMode;
194 if (this->colorXform() && this->xformOnDecode()) {
195 if (SkEncodedInfo::kGray_Color == this->getEncodedInfo().color()) {
196 swizzlerInfo = swizzlerInfo.makeColorType(kGray_8_SkColorType);
197 } else {
198 swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType);
199 }
200 if (kPremul_SkAlphaType == dstInfo.alphaType()) {
201 swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType);
202 }
203
204 fXformMode = kSwizzleColor_XformMode;
205
206 // Here, we swizzle into temporary memory, which is not zero initialized.
207 // FIXME (msarett):
208 // Is this a problem?
209 swizzlerOptions.fZeroInitialized = kNo_ZeroInitialized;
210 }
211
212 SkIRect frameRect = SkIRect::MakeWH(frameWidth, 1);
213 const SkIRect* frameRectPtr = nullptr;
214 if (options.fSubset) {
215 SkASSERT(frameWidth == dstInfo.width());
216 } else {
217 frameRectPtr = &frameRect;
218 }
219
220 if (skipFormatConversion) {
221 // We cannot skip format conversion when there is a color table.
222 SkASSERT(!fColorTable);
223 int srcBPP = 0;
224 switch (this->getEncodedInfo().color()) {
225 case SkEncodedInfo::kRGB_Color:
226 SkASSERT(this->getEncodedInfo().bitsPerComponent() == 16);
227 srcBPP = 6;
228 break;
229 case SkEncodedInfo::kRGBA_Color:
230 srcBPP = this->getEncodedInfo().bitsPerComponent() / 2;
231 break;
232 case SkEncodedInfo::kGray_Color:
233 srcBPP = 1;
234 break;
235 default:
236 SkASSERT(false);
237 break;
238 }
239 fSwizzler = SkSwizzler::MakeSimple(srcBPP, swizzlerInfo, swizzlerOptions, frameRectPtr);
240 } else {
241 const SkPMColor* colors = get_color_ptr(fColorTable.get());
242 fSwizzler = SkSwizzler::Make(
243 this->getEncodedInfo(), colors, swizzlerInfo, swizzlerOptions, frameRectPtr);
244 }
245
246 return !!fSwizzler ? kSuccess : kUnimplemented;
247 }
248
getSampler(bool createIfNecessary)249 SkSampler* SkPngCodecBase::getSampler(bool createIfNecessary) {
250 if (fSwizzler || !createIfNecessary) {
251 return fSwizzler.get();
252 }
253
254 // Ok to ignore `initializeSwizzler`'s result, because if it fails, then
255 // `fSwizzler` will be `nullptr` and we want to return `nullptr` upon
256 // failure.
257 std::ignore = this->initializeSwizzler(
258 this->dstInfo(), this->options(), true, this->dstInfo().width());
259
260 return fSwizzler.get();
261 }
262
applyXformRow(SkSpan<uint8_t> dstRow,SkSpan<const uint8_t> srcRow)263 void SkPngCodecBase::applyXformRow(SkSpan<uint8_t> dstRow, SkSpan<const uint8_t> srcRow) {
264 SkASSERT(dstRow.size() >= fDstRowBytes);
265 SkASSERT(srcRow.size() >= fEncodedRowBytes);
266 applyXformRow(dstRow.data(), srcRow.data());
267 }
268
applyXformRow(void * dstRow,const uint8_t * srcRow)269 void SkPngCodecBase::applyXformRow(void* dstRow, const uint8_t* srcRow) {
270 switch (fXformMode) {
271 case kSwizzleOnly_XformMode:
272 fSwizzler->swizzle(dstRow, srcRow);
273 break;
274 case kColorOnly_XformMode:
275 this->applyColorXform(dstRow, srcRow, fXformWidth);
276 break;
277 case kSwizzleColor_XformMode:
278 fSwizzler->swizzle(fStorage.get(), srcRow);
279 this->applyColorXform(dstRow, fStorage.get(), fXformWidth);
280 break;
281 }
282 }
283
284 // Note: SkColorPalette claims to store SkPMColors, which is not necessarily the case here.
createColorTable(const SkImageInfo & dstInfo)285 bool SkPngCodecBase::createColorTable(const SkImageInfo& dstInfo) {
286 std::optional<SkSpan<const PaletteColorEntry>> maybePlteChunk = this->onTryGetPlteChunk();
287 if (!maybePlteChunk.has_value()) {
288 return false;
289 }
290 const PaletteColorEntry* palette = maybePlteChunk->data();
291 size_t numColors = maybePlteChunk->size();
292
293 // Contents depend on tableColorType and our choice of if/when to premultiply:
294 // { kPremul, kUnpremul, kOpaque } x { RGBA, BGRA }
295 SkPMColor colorTable[256];
296 SkColorType tableColorType = this->colorXform() ? kXformSrcColorType : dstInfo.colorType();
297
298 std::optional<SkSpan<const uint8_t>> maybeTrnsChunk = this->onTryGetTrnsChunk();
299 const uint8_t* alphas = nullptr;
300 size_t numColorsWithAlpha = 0;
301 if (maybeTrnsChunk.has_value()) {
302 alphas = maybeTrnsChunk->data();
303 numColorsWithAlpha = maybeTrnsChunk->size();
304 }
305
306 if (alphas) {
307 bool premultiply = needs_premul(dstInfo.alphaType(), this->getEncodedInfo().alpha());
308
309 // Choose which function to use to create the color table. If the final destination's
310 // colortype is unpremultiplied, the color table will store unpremultiplied colors.
311 PackColorProc proc = choose_pack_color_proc(premultiply, tableColorType);
312
313 for (size_t i = 0; i < numColorsWithAlpha; i++) {
314 // We don't have a function in SkOpts that combines a set of alphas with a set
315 // of RGBs. We could write one, but it's hardly worth it, given that this
316 // is such a small fraction of the total decode time.
317 colorTable[i] = proc(alphas[i], palette->red, palette->green, palette->blue);
318 palette++;
319 }
320 }
321
322 if (numColorsWithAlpha < numColors) {
323 // The optimized code depends on a 3-byte png_color struct with the colors
324 // in RGB order. These checks make sure it is safe to use.
325 static_assert(3 == sizeof(PaletteColorEntry));
326 static_assert(offsetof(PaletteColorEntry, red) == 0);
327 static_assert(offsetof(PaletteColorEntry, green) == 1);
328 static_assert(offsetof(PaletteColorEntry, blue) == 2);
329
330 if (is_rgba(tableColorType)) {
331 SkOpts::RGB_to_RGB1(colorTable + numColorsWithAlpha,
332 (const uint8_t*)palette,
333 numColors - numColorsWithAlpha);
334 } else {
335 SkOpts::RGB_to_BGR1(colorTable + numColorsWithAlpha,
336 (const uint8_t*)palette,
337 numColors - numColorsWithAlpha);
338 }
339 }
340
341 if (this->colorXform() && !this->xformOnDecode()) {
342 this->applyColorXform(colorTable, colorTable, numColors);
343 }
344
345 // Pad the color table with the last color in the table (or black) in the case that
346 // invalid pixel indices exceed the number of colors in the table.
347 const size_t maxColors = static_cast<size_t>(1) << this->getEncodedInfo().bitsPerComponent();
348 if (numColors < maxColors) {
349 SkPMColor lastColor = numColors > 0 ? colorTable[numColors - 1] : SK_ColorBLACK;
350 SkOpts::memset32(colorTable + numColors, lastColor, maxColors - numColors);
351 }
352
353 fColorTable.reset(new SkColorPalette(colorTable, maxColors));
354 return true;
355 }
356