xref: /aosp_15_r20/external/skia/src/codec/SkPngCodecBase.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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