| /* | |
| * Copyright 2015 The Android Open Source Project | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #ifndef SkCodecPriv_DEFINED | |
| #define SkCodecPriv_DEFINED | |
| #include "include/codec/SkCodec.h" | |
| #include "include/codec/SkEncodedOrigin.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkSpan.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/private/SkEncodedInfo.h" | |
| #include "include/private/SkMalloc.h" | |
| #include "include/private/SkTemplates.h" | |
| #include "modules/skcms/skcms.h" | |
| #include "src/codec/SkColorPalette.h" | |
| #include "src/core/SkColorData.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <memory> | |
| #if !defined(SK_BUILD_FOR_ANDROID) | |
| #include <ranges> | |
| #endif | |
| #include <string_view> | |
| #include <utility> | |
| #ifdef SK_PRINT_CODEC_MESSAGES | |
| #define SkCodecPrintf SkDebugf | |
| #else | |
| #define SkCodecPrintf(...) | |
| #endif | |
| namespace SkCodecs { | |
| bool HasDecoder(std::string_view id); | |
| // All color profile information from an SkCodec. | |
| // TODO(https://issues.skia.org/issues/464217864): This should include skhdr::Metadata. | |
| class ColorProfile { | |
| public: | |
| // Create a ColorProfile from an SkData for an ICC profile. | |
| // Returns nullptr if the data fails to parse. This will use | |
| // either MakeICCProfileWithSkCMS or MakeICCProfileWithRust | |
| // depending on the build configuration. | |
| static std::unique_ptr<ColorProfile> MakeICCProfile(sk_sp<const SkData>); | |
| // Implementation of MakeICCProfile that uses skcms to parse. | |
| // This should only be called by tests. | |
| static std::unique_ptr<ColorProfile> MakeICCProfileWithSkCMS(sk_sp<const SkData>); | |
| // Create a ColorProfile from an SkColorSpace. Returns nullptr if the color space is | |
| // nullptr. | |
| static std::unique_ptr<ColorProfile> Make(sk_sp<SkColorSpace>); | |
| // Create a ColorProfile from the specified matrix and transfer curves. This will always | |
| // succeed. | |
| static std::unique_ptr<ColorProfile> Make(const skcms_TransferFunction& trfn, | |
| const skcms_Matrix3x3& toXYZD50); | |
| // Create a ColorProfile with the specified CICP values. This will always succeed, even if | |
| // the values are unrecognized. | |
| static std::unique_ptr<ColorProfile> MakeCICP(uint8_t color_primaries, | |
| uint8_t transfer_characteristics, | |
| uint8_t matrix_coefficients, | |
| uint8_t full_range_flag); | |
| // Create a copy of this. | |
| std::unique_ptr<ColorProfile> clone() const; | |
| // Return the color data space for the profile. | |
| enum class DataSpace { | |
| kRGB, // skcms_Signature_RGB | |
| kCMYK, // skcms_Signature_CMYK | |
| kGray, // skcms_Signature_Gray | |
| kOther, // all other values | |
| }; | |
| DataSpace dataSpace() const; | |
| // Return the color space that this profile represents exactly. Return nullptr if this profile | |
| // cannot be represented as an SkColorSpace. | |
| sk_sp<SkColorSpace> getExactColorSpace() const; | |
| // Return the color space that Android uses for this profile (see implementation for details). | |
| // Will not return nullptr. | |
| sk_sp<SkColorSpace> getAndroidOutputColorSpace() const; | |
| // TODO(https://issues.skia.org/issues/464217864): Remove direct access to the | |
| // skcms_ICCProfile and change data() to be a serialize() function. | |
| const skcms_ICCProfile* profile() const { return &fProfile; } | |
| sk_sp<const SkData> data() const { return fData; } | |
| private: | |
| ColorProfile(const skcms_ICCProfile&, sk_sp<const SkData> = nullptr); | |
| #if defined(SK_CODEC_COLOR_PROFILE_PARSE_WITH_RUST) | |
| friend std::unique_ptr<ColorProfile> MakeICCProfileWithRust(sk_sp<const SkData>); | |
| #endif | |
| skcms_ICCProfile fProfile; | |
| sk_sp<const SkData> fData; | |
| // Opaque handle that keeps alive any externally-allocated data that | |
| // fProfile's raw pointers (e.g. A2B grid/curve tables) reference. | |
| std::shared_ptr<void> fRetainedData; | |
| }; | |
| } // namespace SkCodecs | |
| class SkCodecPriv final { | |
| public: | |
| static const SkEncodedInfo& GetEncodedInfo(const SkCodec* codec) { | |
| SkASSERT(codec); | |
| return codec->getEncodedInfo(); | |
| } | |
| static bool SelectXformFormat(SkColorType colorType, | |
| bool forColorTable, | |
| skcms_PixelFormat* outFormat); | |
| // FIXME: Consider sharing with dm, nanbench, and tools. | |
| static float GetScaleFromSampleSize(int sampleSize) { return 1.0f / ((float)sampleSize); } | |
| static bool IsValidSubset(const SkIRect& subset, const SkISize& imageDims) { | |
| return SkIRect::MakeSize(imageDims).contains(subset); | |
| } | |
| /* | |
| * returns a scaled dimension based on the original dimension and the sampleSize | |
| * NOTE: we round down here for scaled dimension to match the behavior of SkImageDecoder | |
| */ | |
| static int GetSampledDimension(int srcDimension, int sampleSize) { | |
| if (sampleSize > srcDimension) { | |
| return 1; | |
| } | |
| if (sampleSize == 0) { | |
| return 0; | |
| } | |
| return srcDimension / sampleSize; | |
| } | |
| /* | |
| * Returns the first coordinate that we will keep during a scaled decode. | |
| * The output can be interpreted as an x-coordinate or a y-coordinate. | |
| * | |
| * This does not need to be called and is not called when sampleFactor == 1. | |
| */ | |
| static int GetStartCoord(int sampleFactor) { return sampleFactor / 2; } | |
| /* | |
| * Given a coordinate in the original image, this returns the corresponding | |
| * coordinate in the scaled image. This function is meaningless if | |
| * IsCoordNecessary returns false. | |
| * The output can be interpreted as an x-coordinate or a y-coordinate. | |
| * | |
| * This does not need to be called and is not called when sampleFactor == 1. | |
| */ | |
| static int GetDstCoord(int srcCoord, int sampleFactor) { return srcCoord / sampleFactor; } | |
| /* | |
| * When scaling, we will discard certain y-coordinates (rows) and | |
| * x-coordinates (columns). This function returns true if we should keep the | |
| * coordinate and false otherwise. | |
| * The inputs may be x-coordinates or y-coordinates. | |
| * | |
| * This does not need to be called and is not called when sampleFactor == 1. | |
| */ | |
| static bool IsCoordNecessary(int srcCoord, int sampleFactor, int scaledDim) { | |
| // Get the first coordinate that we want to keep | |
| int startCoord = GetStartCoord(sampleFactor); | |
| // Return false on edge cases | |
| if (srcCoord < startCoord || GetDstCoord(srcCoord, sampleFactor) >= scaledDim) { | |
| return false; | |
| } | |
| // Every sampleFactor rows are necessary | |
| return ((srcCoord - startCoord) % sampleFactor) == 0; | |
| } | |
| static bool ValidAlpha(SkAlphaType dstAlpha, bool srcIsOpaque) { | |
| if (kUnknown_SkAlphaType == dstAlpha) { | |
| return false; | |
| } | |
| if (srcIsOpaque) { | |
| if (kOpaque_SkAlphaType != dstAlpha) { | |
| SkCodecPrintf( | |
| "Warning: an opaque image should be decoded as opaque " | |
| "- it is being decoded as non-opaque, which will draw slower\n"); | |
| } | |
| return true; | |
| } | |
| return dstAlpha != kOpaque_SkAlphaType; | |
| } | |
| /* | |
| * If there is a color table, get a pointer to the colors, otherwise return nullptr | |
| */ | |
| static const SkPMColor* GetColorPtr(SkColorPalette* colorTable) { | |
| return nullptr != colorTable ? colorTable->readColors() : nullptr; | |
| } | |
| /* | |
| * Compute row bytes for an image using pixels per byte | |
| */ | |
| static size_t ComputeRowBytesPixelsPerByte(int width, uint32_t pixelsPerByte) { | |
| return (width + pixelsPerByte - 1) / pixelsPerByte; | |
| } | |
| /* | |
| * Compute row bytes for an image using bytes per pixel | |
| */ | |
| static size_t ComputeRowBytesBytesPerPixel(int width, uint32_t bytesPerPixel) { | |
| return width * bytesPerPixel; | |
| } | |
| /* | |
| * Compute row bytes for an image | |
| */ | |
| static size_t ComputeRowBytes(int width, uint32_t bitsPerPixel) { | |
| if (bitsPerPixel < 16) { | |
| SkASSERT(0 == 8 % bitsPerPixel); | |
| const uint32_t pixelsPerByte = 8 / bitsPerPixel; | |
| return ComputeRowBytesPixelsPerByte(width, pixelsPerByte); | |
| } else { | |
| SkASSERT(0 == bitsPerPixel % 8); | |
| const uint32_t bytesPerPixel = bitsPerPixel / 8; | |
| return ComputeRowBytesBytesPerPixel(width, bytesPerPixel); | |
| } | |
| } | |
| /* | |
| * Get a byte from a buffer | |
| * This method is unsafe, the caller is responsible for performing a check | |
| */ | |
| static uint8_t UnsafeGetByte(const uint8_t* buffer, uint32_t i) { return buffer[i]; } | |
| /* | |
| * Get a short from a buffer | |
| * This method is unsafe, the caller is responsible for performing a check | |
| */ | |
| static uint16_t UnsafeGetShort(const uint8_t* buffer, uint32_t i) { | |
| uint16_t result; | |
| memcpy(&result, &(buffer[i]), 2); | |
| #ifdef SK_CPU_BENDIAN | |
| return SkEndianSwap16(result); | |
| #else | |
| return result; | |
| #endif | |
| } | |
| /* | |
| * Get an int from a buffer | |
| * This method is unsafe, the caller is responsible for performing a check | |
| */ | |
| static uint32_t UnsafeGetInt(const uint8_t* buffer, uint32_t i) { | |
| uint32_t result; | |
| memcpy(&result, &(buffer[i]), 4); | |
| #ifdef SK_CPU_BENDIAN | |
| return SkEndianSwap32(result); | |
| #else | |
| return result; | |
| #endif | |
| } | |
| /* | |
| * @param data Buffer to read bytes from | |
| * @param isLittleEndian Output parameter | |
| * Indicates if the data is little endian | |
| * Is unaffected on false returns | |
| */ | |
| static bool IsValidEndianMarker(const uint8_t* data, bool* isLittleEndian) { | |
| // II indicates Intel (little endian) and MM indicates motorola (big endian). | |
| if (('I' != data[0] || 'I' != data[1]) && ('M' != data[0] || 'M' != data[1])) { | |
| return false; | |
| } | |
| *isLittleEndian = ('I' == data[0]); | |
| return true; | |
| } | |
| static uint16_t GetEndianShort(const uint8_t* data, bool littleEndian) { | |
| if (littleEndian) { | |
| return (data[1] << 8) | (data[0]); | |
| } | |
| return (data[0] << 8) | (data[1]); | |
| } | |
| static uint32_t GetEndianInt(const uint8_t* data, bool littleEndian) { | |
| if (littleEndian) { | |
| return (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | (data[0]); | |
| } | |
| return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]); | |
| } | |
| static SkPMColor PremultiplyARGBasRGBA(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { | |
| if (a != 255) { | |
| r = SkMulDiv255Round(r, a); | |
| g = SkMulDiv255Round(g, a); | |
| b = SkMulDiv255Round(b, a); | |
| } | |
| return SkPackARGB_as_RGBA(a, r, g, b); | |
| } | |
| static SkPMColor PremultiplyARGBasBGRA(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { | |
| if (a != 255) { | |
| r = SkMulDiv255Round(r, a); | |
| g = SkMulDiv255Round(g, a); | |
| b = SkMulDiv255Round(b, a); | |
| } | |
| return SkPackARGB_as_BGRA(a, r, g, b); | |
| } | |
| static bool IsRGBA(SkColorType colorType) { | |
| #ifdef SK_PMCOLOR_IS_RGBA | |
| return (kBGRA_8888_SkColorType != colorType); | |
| #else | |
| return (kRGBA_8888_SkColorType == colorType); | |
| #endif | |
| } | |
| // Method for coverting to a 32 bit pixel. | |
| using PackColorProc = uint32_t (*)(U8CPU a, U8CPU r, U8CPU g, U8CPU b); | |
| static PackColorProc ChoosePackColorProc(bool isPremul, SkColorType colorType) { | |
| bool isRGBA = IsRGBA(colorType); | |
| if (isPremul) { | |
| if (isRGBA) { | |
| return &PremultiplyARGBasRGBA; | |
| } else { | |
| return &PremultiplyARGBasBGRA; | |
| } | |
| } else { | |
| if (isRGBA) { | |
| return &SkPackARGB_as_RGBA; | |
| } else { | |
| return &SkPackARGB_as_BGRA; | |
| } | |
| } | |
| } | |
| static sk_sp<const SkData> GetEncodedData(const SkCodec* codec) { | |
| SkASSERT(codec); | |
| return codec->getEncodedData(); | |
| } | |
| static bool NeedsRewind(const SkCodec* codec) { | |
| SkASSERT(codec); | |
| return codec->fNeedsRewind; | |
| } | |
| // A move-only heap-allocated byte buffer tracked against SkCodec's decode memory budget. | |
| class BudgetedBuffer { | |
| public: | |
| BudgetedBuffer() = default; | |
| BudgetedBuffer(BudgetedBuffer&& other) noexcept | |
| : fStorage(std::move(other.fStorage)), fSize(std::exchange(other.fSize, 0)) {} | |
| BudgetedBuffer& operator=(BudgetedBuffer&& other) noexcept { | |
| if (this != &other) { | |
| fStorage = std::move(other.fStorage); | |
| fSize = std::exchange(other.fSize, 0); | |
| } | |
| return *this; | |
| } | |
| BudgetedBuffer(const BudgetedBuffer&) = delete; | |
| BudgetedBuffer& operator=(const BudgetedBuffer&) = delete; | |
| // Checks that `numBytes` fits within `codec`'s decode memory budget and allocates | |
| // storage (zero-initialized by default). The buffer must be empty before calling | |
| // this method (call `reset()` first if reusing a buffer). Returns `kOutOfMemory` if | |
| // the budget is exceeded, or `kInternalError` if allocation fails. | |
| [[nodiscard]] SkCodec::Result allocateFromBudget(SkCodec* codec, | |
| size_t numBytes, | |
| bool zeroInit = true) { | |
| SkASSERT_RELEASE(codec); | |
| SkASSERT_RELEASE(this->empty()); | |
| if (numBytes == 0) { | |
| return SkCodec::kSuccess; | |
| } | |
| if (!codec->allocateFromBudget(numBytes)) { | |
| return SkCodec::kOutOfMemory; | |
| } | |
| void* buffer = zeroInit ? sk_calloc_canfail(numBytes) : sk_malloc_canfail(numBytes); | |
| if (!buffer) { | |
| return SkCodec::kInternalError; | |
| } | |
| fStorage.reset(static_cast<uint8_t*>(buffer)); | |
| fSize = numBytes; | |
| return SkCodec::kSuccess; | |
| } | |
| void reset() { | |
| fStorage.reset(); | |
| fSize = 0; | |
| } | |
| uint8_t* data() { return fStorage.get(); } | |
| const uint8_t* data() const { return fStorage.get(); } | |
| size_t size() const { return fSize; } | |
| bool empty() const { return fSize == 0; } | |
| uint8_t* begin() { return fStorage.get(); } | |
| const uint8_t* begin() const { return fStorage.get(); } | |
| uint8_t* end() { return fStorage.get() + fSize; } | |
| const uint8_t* end() const { return fStorage.get() + fSize; } | |
| SkSpan<uint8_t> span() { return SkSpan(fStorage.get(), fSize); } | |
| SkSpan<const uint8_t> span() const { return SkSpan(fStorage.get(), fSize); } | |
| private: | |
| std::unique_ptr<uint8_t, SkFunctionObject<sk_free>> fStorage; | |
| size_t fSize = 0; | |
| }; | |
| #if !defined(SK_BUILD_FOR_ANDROID) | |
| static_assert(std::ranges::contiguous_range<BudgetedBuffer>); | |
| static_assert(std::ranges::contiguous_range<const BudgetedBuffer>); | |
| static_assert(std::ranges::sized_range<BudgetedBuffer>); | |
| static_assert(std::ranges::sized_range<const BudgetedBuffer>); | |
| #endif | |
| }; | |
| #endif // SkCodecPriv_DEFINED |