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