| /* | |
| * 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/encode/SkPngEncoderBase.h" | |
| #include <utility> | |
| #include "include/core/SkAlphaType.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkColorType.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkPixmap.h" | |
| #include "include/core/SkSpan.h" | |
| #include "include/private/SkAssert.h" | |
| #include "include/private/SkEncodedInfo.h" | |
| #include "include/private/SkTemplates.h" | |
| #include "src/core/SkConvertPixels.h" | |
| #include "src/core/SkImageInfoPriv.h" | |
| #include "src/core/SkMSAN.h" | |
| #include "src/core/SkSafeMath.h" | |
| #include "src/encode/SkImageEncoderFns.h" | |
| namespace { | |
| SkEncodedInfo makeInfo(const SkImageInfo& srcInfo, | |
| SkEncodedInfo::Color color, | |
| int bitsPerComponent) { | |
| SkEncodedInfo::Alpha alpha = | |
| color == SkEncodedInfo::kGray_Color || color == SkEncodedInfo::kRGB_Color | |
| ? SkEncodedInfo::kOpaque_Alpha | |
| : SkEncodedInfo::kUnpremul_Alpha; | |
| return SkEncodedInfo::Make(srcInfo.width(), srcInfo.height(), color, alpha, bitsPerComponent); | |
| } | |
| SkEncodedInfo makeGray8Info(const SkImageInfo& srcInfo) { | |
| return makeInfo(srcInfo, SkEncodedInfo::kGray_Color, 8); | |
| } | |
| SkEncodedInfo makeGrayAlpha8Info(const SkImageInfo& srcInfo) { | |
| return makeInfo(srcInfo, SkEncodedInfo::kGrayAlpha_Color, 8); | |
| } | |
| SkEncodedInfo makeRgba8Info(const SkImageInfo& srcInfo) { | |
| return makeInfo(srcInfo, SkEncodedInfo::kRGBA_Color, 8); | |
| } | |
| SkEncodedInfo makeRgba16Info(const SkImageInfo& srcInfo) { | |
| return makeInfo(srcInfo, SkEncodedInfo::kRGBA_Color, 16); | |
| } | |
| SkPngEncoderBase::TargetInfo makeTargetInfo(SkEncodedInfo dstInfo, const SkImageInfo& srcImageInfo, | |
| SkColorType dstCT, SkAlphaType dstAT) { | |
| SkASSERT(dstCT != kAlpha_8_SkColorType); | |
| SkImageInfo dstRowInfo = SkImageInfo::Make(srcImageInfo.width(), 1, dstCT, dstAT); | |
| return SkPngEncoderBase::TargetInfo {srcImageInfo.makeWH(srcImageInfo.width(), 1), | |
| dstRowInfo, | |
| std::move(dstInfo), | |
| dstRowInfo.minRowBytes(),}; | |
| } | |
| std::optional<SkPngEncoderBase::TargetInfo> makeAlpha8TargetInfo(SkEncodedInfo dstInfo) { | |
| // `static_cast<size_t>`(dstInfo.bitsPerPixel())` uses trustworthy, bounded | |
| // data as input - no need to use `SkSafeMath` for this part. | |
| SkASSERT(dstInfo.bitsPerComponent() == 8 || dstInfo.bitsPerComponent() == 16); | |
| SkASSERT(dstInfo.bitsPerPixel() <= (16 * 4)); | |
| size_t bitsPerPixel = static_cast<size_t>(dstInfo.bitsPerPixel()); | |
| SkASSERT((bitsPerPixel % 8) == 0); | |
| size_t bytesPerPixel = bitsPerPixel / 8; | |
| SkSafeMath safe; | |
| size_t dstRowSize = safe.mul(safe.castTo<size_t>(dstInfo.width()), bytesPerPixel); | |
| if (!safe.ok()) { | |
| return std::nullopt; | |
| } | |
| return SkPngEncoderBase::TargetInfo{std::nullopt, std::nullopt, | |
| std::move(dstInfo), dstRowSize}; | |
| } | |
| } // namespace | |
| // static | |
| std::optional<SkPngEncoderBase::TargetInfo> SkPngEncoderBase::getTargetInfo( | |
| const SkImageInfo& srcInfo) { | |
| SkColorType srcCT = srcInfo.colorType(); | |
| SkAlphaType srcAT = srcInfo.alphaType(); | |
| int numChannels = SkColorTypeNumChannels(srcCT); | |
| switch(numChannels) { | |
| case 1: { | |
| uint32_t srcChannelFlags = SkColorTypeChannelFlags(srcCT); | |
| if (srcChannelFlags == kGray_SkColorChannelFlag) { | |
| SkASSERT(srcInfo.isOpaque()); | |
| return makeTargetInfo(makeGray8Info(srcInfo), srcInfo, srcCT, srcAT); | |
| } | |
| // We support encoding kAlpha_8_SkColorType to GrayAlpha images and just ignore gray. | |
| // Otherwise, there is no sensible way to encode alpha only images. | |
| if (srcCT == kAlpha_8_SkColorType) { | |
| return makeAlpha8TargetInfo(makeGrayAlpha8Info(srcInfo)); | |
| } | |
| break; | |
| } | |
| case 3: { | |
| SkASSERT(srcInfo.isOpaque()); | |
| if (srcAT == kUnknown_SkAlphaType) { | |
| SkDEBUGFAIL("unknown alpha type"); | |
| return std::nullopt; | |
| } | |
| int maxBitsPerChannel = SkColorTypeMaxBitsPerChannel(srcCT); | |
| if (maxBitsPerChannel <= 8) { | |
| return makeTargetInfo(makeRgba8Info(srcInfo), | |
| srcInfo, | |
| kRGB_888x_SkColorType, | |
| kOpaque_SkAlphaType); | |
| } else if (maxBitsPerChannel <= 32) { | |
| return makeTargetInfo(makeRgba16Info(srcInfo), | |
| srcInfo, | |
| kR16G16B16A16_unorm_SkColorType, | |
| kOpaque_SkAlphaType); | |
| } | |
| break; | |
| } | |
| case 4: { | |
| if (srcAT == kUnknown_SkAlphaType) { | |
| SkDEBUGFAIL("unknown alpha type"); | |
| return std::nullopt; | |
| } | |
| int maxBitsPerChannel = SkColorTypeMaxBitsPerChannel(srcCT); | |
| if (maxBitsPerChannel <= 8) { | |
| if (srcAT == kOpaque_SkAlphaType) { | |
| return makeTargetInfo(makeRgba8Info(srcInfo), | |
| srcInfo, | |
| kRGB_888x_SkColorType, | |
| kOpaque_SkAlphaType); | |
| } | |
| return makeTargetInfo(makeRgba8Info(srcInfo), | |
| srcInfo, | |
| kRGBA_8888_SkColorType, | |
| kUnpremul_SkAlphaType); | |
| } else if (maxBitsPerChannel <= 32) { | |
| if (srcAT == kOpaque_SkAlphaType) { | |
| return makeTargetInfo(makeRgba16Info(srcInfo), | |
| srcInfo, | |
| kR16G16B16A16_unorm_SkColorType, | |
| kOpaque_SkAlphaType); | |
| } | |
| return makeTargetInfo(makeRgba16Info(srcInfo), | |
| srcInfo, | |
| kR16G16B16A16_unorm_SkColorType, | |
| kUnpremul_SkAlphaType); | |
| } | |
| } | |
| break; | |
| } | |
| return std::nullopt; | |
| } | |
| SkPngEncoderBase::SkPngEncoderBase(TargetInfo targetInfo, const SkPixmap& src) | |
| : SkEncoder(src, targetInfo.fDstRowSize), fTargetInfo(std::move(targetInfo)) { | |
| SkASSERT(src.colorType() == kAlpha_8_SkColorType | |
| || (fTargetInfo.fSrcRowInfo && fTargetInfo.fDstRowInfo)); | |
| } | |
| bool SkPngEncoderBase::onEncodeRows(int numRows) { | |
| // https://www.w3.org/TR/png-3/#11IHDR says that "zero is an invalid value" | |
| // for width and height. | |
| if (fSrc.width() == 0 || fSrc.height() == 0) { | |
| return false; | |
| } | |
| if (numRows < 0) { | |
| return false; | |
| } | |
| while (numRows > 0) { | |
| if (fCurrRow == fSrc.height()) { | |
| return false; | |
| } | |
| const void* srcRow = fSrc.addr(0, fCurrRow); | |
| sk_msan_assert_initialized(srcRow, | |
| (const uint8_t*)srcRow + (fSrc.width() << fSrc.shiftPerPixel())); | |
| if (fSrc.colorType() == kAlpha_8_SkColorType) { | |
| // This is a special case where we store kAlpha_8 images as GrayAlpha in png. | |
| transform_scanline_A8_to_GrayAlpha((char*)fStorage.get(), | |
| (const char*)srcRow, | |
| fSrc.width(), | |
| SkColorTypeBytesPerPixel(fSrc.colorType())); | |
| } else { | |
| SkASSERT(fSrc.width() == fTargetInfo.fSrcRowInfo->width()); | |
| if (!SkConvertPixels(fTargetInfo.fDstRowInfo.value(), | |
| (void*)fStorage.get(), | |
| fTargetInfo.fDstRowSize, | |
| fTargetInfo.fSrcRowInfo.value(), | |
| srcRow, | |
| fTargetInfo.fSrcRowInfo->minRowBytes())) { | |
| return false; | |
| } | |
| } | |
| SkSpan<const uint8_t> rowToEncode(fStorage.get(), fTargetInfo.fDstRowSize); | |
| if (!this->onEncodeRow(rowToEncode)) { | |
| return false; | |
| } | |
| fCurrRow++; | |
| numRows--; | |
| } | |
| if (fCurrRow == fSrc.height() && !fFinishedEncoding) { | |
| fFinishedEncoding = true; | |
| return this->onFinishEncoding(); | |
| } | |
| return true; | |
| } |