| /* |
| * Copyright 2024 Google LLC. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/codec/SkPngCodecBase.h" |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <tuple> |
| #include <utility> |
| |
| #include "include/codec/SkCodec.h" |
| #include "include/codec/SkEncodedImageFormat.h" |
| #include "include/core/SkAlphaType.h" |
| #include "include/core/SkColor.h" |
| #include "include/core/SkColorType.h" |
| #include "include/core/SkImageInfo.h" |
| #include "include/core/SkRect.h" |
| #include "include/core/SkStream.h" |
| #include "include/private/SkEncodedInfo.h" |
| #include "include/private/base/SkAssert.h" |
| #include "include/private/base/SkSpan_impl.h" |
| #include "modules/skcms/skcms.h" |
| #include "src/codec/SkCodecPriv.h" |
| #include "src/codec/SkColorPalette.h" |
| #include "src/codec/SkSwizzler.h" |
| #include "src/core/SkMemset.h" |
| #include "src/core/SkSwizzlePriv.h" |
| |
| namespace { |
| |
| constexpr SkColorType kXformSrcColorType = kRGBA_8888_SkColorType; |
| |
| inline bool needs_premul(SkAlphaType dstAT, SkEncodedInfo::Alpha encodedAlpha) { |
| return kPremul_SkAlphaType == dstAT && SkEncodedInfo::kUnpremul_Alpha == encodedAlpha; |
| } |
| |
| skcms_PixelFormat ToPixelFormat(const SkEncodedInfo& info) { |
| // We use kRGB and kRGBA formats because color PNGs are always RGB or RGBA. |
| if (16 == info.bitsPerComponent()) { |
| if (SkEncodedInfo::kRGBA_Color == info.color()) { |
| return skcms_PixelFormat_RGBA_16161616BE; |
| } else if (SkEncodedInfo::kRGB_Color == info.color()) { |
| return skcms_PixelFormat_RGB_161616BE; |
| } |
| } else if (SkEncodedInfo::kGray_Color == info.color()) { |
| return skcms_PixelFormat_G_8; |
| } |
| |
| return skcms_PixelFormat_RGBA_8888; |
| } |
| |
| } // namespace |
| |
| SkPngCodecBase::~SkPngCodecBase() = default; |
| |
| // static |
| bool SkPngCodecBase::isCompatibleColorProfileAndType(const SkEncodedInfo::ICCProfile* profile, |
| SkEncodedInfo::Color color) { |
| if (profile) { |
| switch (profile->profile()->data_color_space) { |
| case skcms_Signature_CMYK: |
| return false; |
| case skcms_Signature_Gray: |
| if (SkEncodedInfo::kGray_Color != color && |
| SkEncodedInfo::kGrayAlpha_Color != color) { |
| return false; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| return true; |
| } |
| |
| SkPngCodecBase::SkPngCodecBase(SkEncodedInfo&& encodedInfo, |
| std::unique_ptr<SkStream> stream, |
| SkEncodedOrigin origin) |
| : SkCodec(std::move(encodedInfo), ToPixelFormat(encodedInfo), std::move(stream), origin) {} |
| |
| SkEncodedImageFormat SkPngCodecBase::onGetEncodedFormat() const { |
| return SkEncodedImageFormat::kPNG; |
| } |
| |
| SkCodec::Result SkPngCodecBase::initializeXforms(const SkImageInfo& dstInfo, |
| const Options& options, |
| int frameWidth) { |
| if (frameWidth != dstInfo.width() && options.fSubset) { |
| return kInvalidParameters; |
| } |
| fXformWidth = frameWidth; |
| |
| { |
| size_t encodedBitsPerPixel = static_cast<size_t>(getEncodedInfo().bitsPerPixel()); |
| |
| // We assume that `frameWidth` and `bitsPerPixel` have been already sanitized |
| // earlier (and that the multiplication and addition below won't overflow). |
| SkASSERT_RELEASE(0 < frameWidth); |
| SkASSERT_RELEASE(frameWidth < 0xFFFFFF); |
| SkASSERT_RELEASE(encodedBitsPerPixel < 128); |
| |
| size_t encodedBitsPerRow = static_cast<size_t>(frameWidth) * encodedBitsPerPixel; |
| fEncodedRowBytes = (encodedBitsPerRow + 7) / 8; // Round up to the next byte. |
| |
| size_t dstBytesPerPixel = dstInfo.bytesPerPixel(); |
| fDstRowBytes = static_cast<size_t>(frameWidth) * dstBytesPerPixel; |
| } |
| |
| // Reset fSwizzler and this->colorXform(). We can't do this in onRewind() because the |
| // interlaced scanline decoder may need to rewind. |
| fSwizzler.reset(nullptr); |
| |
| // If skcms directly supports the encoded PNG format, we should skip format |
| // conversion in the swizzler (or skip swizzling altogether). |
| bool skipFormatConversion = false; |
| switch (this->getEncodedInfo().color()) { |
| case SkEncodedInfo::kRGB_Color: |
| if (this->getEncodedInfo().bitsPerComponent() != 16) { |
| break; |
| } |
| [[fallthrough]]; |
| case SkEncodedInfo::kRGBA_Color: |
| case SkEncodedInfo::kGray_Color: |
| skipFormatConversion = this->colorXform(); |
| break; |
| default: |
| break; |
| } |
| |
| if (skipFormatConversion && !options.fSubset) { |
| fXformMode = kColorOnly_XformMode; |
| } else { |
| if (SkEncodedInfo::kPalette_Color == this->getEncodedInfo().color()) { |
| if (!this->createColorTable(dstInfo)) { |
| return kInvalidInput; |
| } |
| } |
| |
| Result result = |
| this->initializeSwizzler(dstInfo, options, skipFormatConversion, frameWidth); |
| if (result != kSuccess) { |
| return result; |
| } |
| } |
| |
| this->allocateStorage(dstInfo); |
| |
| // We can't call `initializeXformParams` here, because `swizzleWidth` may |
| // change *after* `onStartIncrementalDecode` |
| // (`SkSampledCodec::sampledDecode` first [transitively] calls |
| // `onStartIncrementalDecode` and *then* `SkSwizzler::onSetSampleX`). |
| |
| return kSuccess; |
| } |
| |
| void SkPngCodecBase::initializeXformParams() { |
| if (fXformMode == kSwizzleColor_XformMode) { |
| fXformWidth = this->swizzler()->swizzleWidth(); |
| } |
| } |
| |
| void SkPngCodecBase::allocateStorage(const SkImageInfo& dstInfo) { |
| switch (fXformMode) { |
| case kSwizzleOnly_XformMode: |
| break; |
| case kColorOnly_XformMode: |
| // Intentional fall through. A swizzler hasn't been created yet, but one will |
| // be created later if we are sampling. We'll go ahead and allocate |
| // enough memory to swizzle if necessary. |
| case kSwizzleColor_XformMode: { |
| const int bitsPerPixel = this->getEncodedInfo().bitsPerPixel(); |
| |
| // If we have more than 8-bits (per component) of precision, we will keep that |
| // extra precision. Otherwise, we will swizzle to RGBA_8888 before transforming. |
| const size_t bytesPerPixel = (bitsPerPixel > 32) ? bitsPerPixel / 8 : 4; |
| const size_t colorXformBytes = dstInfo.width() * bytesPerPixel; |
| fStorage.reset(colorXformBytes); |
| break; |
| } |
| } |
| } |
| |
| SkCodec::Result SkPngCodecBase::initializeSwizzler(const SkImageInfo& dstInfo, |
| const Options& options, |
| bool skipFormatConversion, |
| int frameWidth) { |
| SkImageInfo swizzlerInfo = dstInfo; |
| Options swizzlerOptions = options; |
| fXformMode = kSwizzleOnly_XformMode; |
| if (this->colorXform() && this->xformOnDecode()) { |
| if (SkEncodedInfo::kGray_Color == this->getEncodedInfo().color()) { |
| swizzlerInfo = swizzlerInfo.makeColorType(kGray_8_SkColorType); |
| } else { |
| swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType); |
| } |
| if (kPremul_SkAlphaType == dstInfo.alphaType()) { |
| swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType); |
| } |
| |
| fXformMode = kSwizzleColor_XformMode; |
| |
| // Here, we swizzle into temporary memory, which is not zero initialized. |
| // FIXME (msarett): |
| // Is this a problem? |
| swizzlerOptions.fZeroInitialized = kNo_ZeroInitialized; |
| } |
| |
| SkIRect frameRect = SkIRect::MakeWH(frameWidth, 1); |
| const SkIRect* frameRectPtr = nullptr; |
| if (options.fSubset) { |
| SkASSERT_RELEASE(frameWidth == dstInfo.width()); |
| } else { |
| frameRectPtr = &frameRect; |
| } |
| |
| if (skipFormatConversion) { |
| // We cannot skip format conversion when there is a color table. |
| SkASSERT_RELEASE(!fColorTable); |
| int srcBPP = 0; |
| switch (this->getEncodedInfo().color()) { |
| case SkEncodedInfo::kRGB_Color: |
| SkASSERT_RELEASE(this->getEncodedInfo().bitsPerComponent() == 16); |
| srcBPP = 6; |
| break; |
| case SkEncodedInfo::kRGBA_Color: |
| srcBPP = this->getEncodedInfo().bitsPerComponent() / 2; |
| break; |
| case SkEncodedInfo::kGray_Color: |
| srcBPP = 1; |
| break; |
| default: |
| SkASSERT_RELEASE(false); |
| break; |
| } |
| fSwizzler = SkSwizzler::MakeSimple(srcBPP, swizzlerInfo, swizzlerOptions, frameRectPtr); |
| } else { |
| const SkPMColor* colors = SkCodecPriv::GetColorPtr(fColorTable.get()); |
| fSwizzler = SkSwizzler::Make( |
| this->getEncodedInfo(), colors, swizzlerInfo, swizzlerOptions, frameRectPtr); |
| } |
| |
| return !!fSwizzler ? kSuccess : kUnimplemented; |
| } |
| |
| SkSampler* SkPngCodecBase::getSampler(bool createIfNecessary) { |
| if (fSwizzler || !createIfNecessary) { |
| return fSwizzler.get(); |
| } |
| |
| // Ok to ignore `initializeSwizzler`'s result, because if it fails, then |
| // `fSwizzler` will be `nullptr` and we want to return `nullptr` upon |
| // failure. |
| std::ignore = this->initializeSwizzler( |
| this->dstInfo(), this->options(), true, this->dstInfo().width()); |
| |
| return fSwizzler.get(); |
| } |
| |
| void SkPngCodecBase::applyXformRow(SkSpan<uint8_t> dstRow, SkSpan<const uint8_t> srcRow) { |
| SkASSERT_RELEASE(dstRow.size() >= fDstRowBytes); |
| SkASSERT_RELEASE(srcRow.size() >= fEncodedRowBytes); |
| applyXformRow(dstRow.data(), srcRow.data()); |
| } |
| |
| void SkPngCodecBase::applyXformRow(void* dstRow, const uint8_t* srcRow) { |
| switch (fXformMode) { |
| case kSwizzleOnly_XformMode: |
| fSwizzler->swizzle(dstRow, srcRow); |
| break; |
| case kColorOnly_XformMode: |
| this->applyColorXform(dstRow, srcRow, fXformWidth); |
| break; |
| case kSwizzleColor_XformMode: |
| fSwizzler->swizzle(fStorage.get(), srcRow); |
| this->applyColorXform(dstRow, fStorage.get(), fXformWidth); |
| break; |
| } |
| } |
| |
| // Note: SkColorPalette claims to store SkPMColors, which is not necessarily the case here. |
| bool SkPngCodecBase::createColorTable(const SkImageInfo& dstInfo) { |
| if (fDstInfoOfPreviousColorTableCreation.has_value() && |
| *fDstInfoOfPreviousColorTableCreation == dstInfo) { |
| return !!fColorTable; |
| } |
| fColorTable.reset(); |
| fDstInfoOfPreviousColorTableCreation = dstInfo; |
| |
| std::optional<SkSpan<const PaletteColorEntry>> maybePlteChunk = this->onTryGetPlteChunk(); |
| if (!maybePlteChunk.has_value()) { |
| return false; |
| } |
| const PaletteColorEntry* palette = maybePlteChunk->data(); |
| constexpr size_t kMaxCountOfPaletteEntries = 256; |
| size_t numColors = maybePlteChunk->size(); |
| numColors = std::min(numColors, kMaxCountOfPaletteEntries); |
| |
| // Contents depend on tableColorType and our choice of if/when to premultiply: |
| // { kPremul, kUnpremul, kOpaque } x { RGBA, BGRA } |
| SkPMColor colorTable[kMaxCountOfPaletteEntries]; |
| SkColorType tableColorType = this->colorXform() ? kXformSrcColorType : dstInfo.colorType(); |
| |
| std::optional<SkSpan<const uint8_t>> maybeTrnsChunk = this->onTryGetTrnsChunk(); |
| const uint8_t* alphas = nullptr; |
| size_t numColorsWithAlpha = 0; |
| if (maybeTrnsChunk.has_value()) { |
| alphas = maybeTrnsChunk->data(); |
| numColorsWithAlpha = maybeTrnsChunk->size(); |
| numColorsWithAlpha = std::min(numColorsWithAlpha, numColors); |
| } |
| |
| bool shouldApplyColorXformToColorTable = this->colorXform() && !this->xformOnDecode(); |
| if (alphas) { |
| bool premultiply = !shouldApplyColorXformToColorTable && |
| needs_premul(dstInfo.alphaType(), this->getEncodedInfo().alpha()); |
| |
| // Choose which function to use to create the color table. If the final destination's |
| // colortype is unpremultiplied, the color table will store unpremultiplied colors. |
| SkCodecPriv::PackColorProc proc = |
| SkCodecPriv::ChoosePackColorProc(premultiply, tableColorType); |
| |
| for (size_t i = 0; i < numColorsWithAlpha; i++) { |
| // We don't have a function in SkOpts that combines a set of alphas with a set |
| // of RGBs. We could write one, but it's hardly worth it, given that this |
| // is such a small fraction of the total decode time. |
| colorTable[i] = proc(alphas[i], palette->red, palette->green, palette->blue); |
| palette++; |
| } |
| } |
| |
| if (numColorsWithAlpha < numColors) { |
| // The optimized code depends on a 3-byte png_color struct with the colors |
| // in RGB order. These checks make sure it is safe to use. |
| static_assert(3 == sizeof(PaletteColorEntry)); |
| static_assert(offsetof(PaletteColorEntry, red) == 0); |
| static_assert(offsetof(PaletteColorEntry, green) == 1); |
| static_assert(offsetof(PaletteColorEntry, blue) == 2); |
| |
| if (SkCodecPriv::IsRGBA(tableColorType)) { |
| SkOpts::RGB_to_RGB1(colorTable + numColorsWithAlpha, |
| (const uint8_t*)palette, |
| numColors - numColorsWithAlpha); |
| } else { |
| SkOpts::RGB_to_BGR1(colorTable + numColorsWithAlpha, |
| (const uint8_t*)palette, |
| numColors - numColorsWithAlpha); |
| } |
| } |
| |
| if (shouldApplyColorXformToColorTable) { |
| this->applyColorXform(colorTable, colorTable, numColors); |
| } |
| |
| // Pad the color table with the last color in the table (or black) in the case that |
| // invalid pixel indices exceed the number of colors in the table. |
| const size_t maxColors = static_cast<size_t>(1) << this->getEncodedInfo().bitsPerComponent(); |
| if (numColors < maxColors) { |
| SkPMColor lastColor = numColors > 0 ? colorTable[numColors - 1] : SK_ColorBLACK; |
| SkOpts::memset32(colorTable + numColors, lastColor, maxColors - numColors); |
| } |
| |
| fColorTable.reset(new SkColorPalette(colorTable, maxColors)); |
| return true; |
| } |