| /* | |
| * Copyright 2025 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/SkBmpRustCodec.h" | |
| #include "include/codec/SkCodecAnimation.h" | |
| #include "include/core/SkColorType.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkSpan.h" | |
| #include "include/core/SkStream.h" | |
| #include "include/private/SkAssert.h" | |
| #include "include/private/SkEncodedInfo.h" | |
| #include "include/private/SkTemplates.h" | |
| #include "rust/common/SkStreamAdapter.h" | |
| #include "rust/common/SpanUtils.h" | |
| #include "src/codec/SkSwizzler.h" | |
| #include "src/core/SkSafeMath.h" | |
| #include "src/core/SkStreamPriv.h" | |
| // Color type to use when creating the swizzler for xform | |
| // | |
| // Note that `bitsPerComponent` is always 8 for all supported BMP images. | |
| static constexpr SkColorType kXformSrcColorType = kRGBA_8888_SkColorType; | |
| // Static assertions to validate that Rust BmpColor/BmpAlpha enum values | |
| // match the corresponding SkEncodedInfo enum values. These assertions | |
| // ensure type-safe casting between the enums. | |
| static_assert(static_cast<int>(rust_bmp::BmpColor::RGB) == | |
| static_cast<int>(SkEncodedInfo::kRGB_Color), | |
| "BmpColor::RGB must match SkEncodedInfo::kRGB_Color"); | |
| static_assert(static_cast<int>(rust_bmp::BmpColor::RGBA) == | |
| static_cast<int>(SkEncodedInfo::kRGBA_Color), | |
| "BmpColor::RGBA must match SkEncodedInfo::kRGBA_Color"); | |
| static_assert(static_cast<int>(rust_bmp::BmpColor::BGR) == | |
| static_cast<int>(SkEncodedInfo::kBGR_Color), | |
| "BmpColor::BGR must match SkEncodedInfo::kBGR_Color"); | |
| static_assert(static_cast<int>(rust_bmp::BmpColor::BGRA) == | |
| static_cast<int>(SkEncodedInfo::kBGRA_Color), | |
| "BmpColor::BGRA must match SkEncodedInfo::kBGRA_Color"); | |
| static_assert(static_cast<int>(rust_bmp::BmpAlpha::Opaque) == | |
| static_cast<int>(SkEncodedInfo::kOpaque_Alpha), | |
| "BmpAlpha::Opaque must match SkEncodedInfo::kOpaque_Alpha"); | |
| static_assert(static_cast<int>(rust_bmp::BmpAlpha::Unpremul) == | |
| static_cast<int>(SkEncodedInfo::kUnpremul_Alpha), | |
| "BmpAlpha::Unpremul must match SkEncodedInfo::kUnpremul_Alpha"); | |
| static constexpr size_t kInMemoryReaderThreshold = 4 * 1024; | |
| namespace { | |
| // Helper function to map Rust DecodingResult to SkCodec::Result | |
| SkCodec::Result MapDecodingResult(rust_bmp::DecodingResult rustResult) { | |
| switch (rustResult) { | |
| case rust_bmp::DecodingResult::Success: | |
| return SkCodec::kSuccess; | |
| case rust_bmp::DecodingResult::FormatError: | |
| return SkCodec::kErrorInInput; | |
| case rust_bmp::DecodingResult::ParameterError: | |
| return SkCodec::kInvalidParameters; | |
| case rust_bmp::DecodingResult::UnsupportedFeature: | |
| return SkCodec::kUnimplemented; | |
| case rust_bmp::DecodingResult::IncompleteInput: | |
| return SkCodec::kIncompleteInput; | |
| case rust_bmp::DecodingResult::MemoryError: | |
| return SkCodec::kInternalError; | |
| case rust_bmp::DecodingResult::OtherError: | |
| return SkCodec::kErrorInInput; | |
| } | |
| SK_ABORT("Unexpected `rust_bmp::DecodingResult`: %d", static_cast<int>(rustResult)); | |
| } | |
| rust::Box<rust_bmp::ResultOfReader> NewReaderFromStream(SkStream* stream) { | |
| auto inputAdapter = std::make_unique<rust::stream::SkStreamAdapter>(stream); | |
| return rust_bmp::new_reader(std::move(inputAdapter)); | |
| } | |
| rust::Box<rust_bmp::ResultOfReader> NewReaderFromData(const SkData* data) { | |
| rust::Slice<const uint8_t> dataSlice(static_cast<const uint8_t*>(data->data()), | |
| data->size()); | |
| return rust_bmp::new_reader_from_data(dataSlice); | |
| } | |
| } // namespace | |
| std::unique_ptr<SkBmpRustCodec> SkBmpRustCodec::MakeFromStream(std::unique_ptr<SkStream> stream, | |
| Result* result, | |
| StreamType type) { | |
| Result resultStorage; | |
| if (!result) { | |
| result = &resultStorage; | |
| } | |
| if (!stream) { | |
| *result = (type == StreamType::kICO) ? kInvalidParameters : kInvalidInput; | |
| return nullptr; | |
| } | |
| rust::Box<rust_bmp::ResultOfReader> resultOfReader = NewReaderFromStream(stream.get()); | |
| return MakeFromStreamAndReader(std::move(stream), std::move(resultOfReader), type, | |
| /*inMemoryData=*/nullptr, result); | |
| } | |
| std::unique_ptr<SkBmpRustCodec> SkBmpRustCodec::MakeFromData(sk_sp<const SkData> data, | |
| Result* result) { | |
| Result resultStorage; | |
| if (!result) { | |
| result = &resultStorage; | |
| } | |
| if (!data) { | |
| *result = kInvalidInput; | |
| return nullptr; | |
| } | |
| if (!data->empty() && data->size() <= kInMemoryReaderThreshold) { | |
| rust::Box<rust_bmp::ResultOfReader> resultOfReader = NewReaderFromData(data.get()); | |
| std::unique_ptr<SkStream> stream = SkMemoryStream::Make(data); | |
| return MakeFromStreamAndReader(std::move(stream), std::move(resultOfReader), | |
| StreamType::kBMP, data, result); | |
| } | |
| return MakeFromStream(SkMemoryStream::Make(std::move(data)), result); | |
| } | |
| std::unique_ptr<SkBmpRustCodec> SkBmpRustCodec::MakeFromStreamAndReader( | |
| std::unique_ptr<SkStream> stream, | |
| rust::Box<rust_bmp::ResultOfReader> resultOfReader, | |
| StreamType streamType, | |
| sk_sp<const SkData> inMemoryData, | |
| Result* result) { | |
| SkASSERT_RELEASE(stream); | |
| SkASSERT_RELEASE(result); | |
| rust_bmp::DecodingResult rustResult = resultOfReader->err(); | |
| if (rustResult != rust_bmp::DecodingResult::Success) { | |
| *result = MapDecodingResult(rustResult); | |
| return nullptr; | |
| } | |
| rust::Box<rust_bmp::Reader> reader = resultOfReader->unwrap(); | |
| // In streaming mode, metadata might not be available yet if the stream | |
| // doesn't have enough data. Try to read metadata explicitly. ICO streams use | |
| // ICO-aware parsing: no file header, halved height, alpha via an AND mask. | |
| if (!reader->metadata_loaded()) { | |
| rust_bmp::DecodingResult metadataResult = (streamType == StreamType::kICO) | |
| ? reader->read_metadata_for_ico() | |
| : reader->read_metadata(); | |
| if (metadataResult == rust_bmp::DecodingResult::IncompleteInput) { | |
| // Need more data for metadata | |
| *result = kIncompleteInput; | |
| return nullptr; | |
| } else if (metadataResult != rust_bmp::DecodingResult::Success) { | |
| // Error during metadata read | |
| *result = MapDecodingResult(metadataResult); | |
| return nullptr; | |
| } | |
| // If read_metadata() returned Success, metadata must be loaded. | |
| SkASSERT_RELEASE(reader->metadata_loaded()); | |
| } | |
| return MakeFromReader(std::move(stream), std::move(reader), streamType, | |
| std::move(inMemoryData), result); | |
| } | |
| std::unique_ptr<SkBmpRustCodec> SkBmpRustCodec::MakeFromReader( | |
| std::unique_ptr<SkStream> stream, | |
| rust::Box<rust_bmp::Reader> reader, | |
| StreamType streamType, | |
| sk_sp<const SkData> inMemoryData, | |
| Result* result) { | |
| uint32_t width = reader->width(); | |
| uint32_t height = reader->height(); | |
| rust_bmp::BmpColor rustColor = reader->color(); | |
| rust_bmp::BmpAlpha rustAlpha = reader->alpha(); | |
| // BmpColor/BmpAlpha values match SkEncodedInfo enums (validated by static_assert). | |
| SkEncodedInfo::Color color = static_cast<SkEncodedInfo::Color>(rustColor); | |
| SkEncodedInfo::Alpha alpha = static_cast<SkEncodedInfo::Alpha>(rustAlpha); | |
| constexpr int kBitsPerComponent = 8; | |
| // Extract and parse ICC profile if present | |
| std::unique_ptr<SkCodecs::ColorProfile> colorProfile; | |
| rust::Vec<uint8_t> profileData = reader->icc_profile(); | |
| if (!profileData.empty()) { | |
| sk_sp<SkData> iccData = SkData::MakeWithCopy(profileData.data(), profileData.size()); | |
| colorProfile = SkCodecs::ColorProfile::MakeICCProfile(std::move(iccData)); | |
| } | |
| SkEncodedInfo encodedInfo = SkEncodedInfo::Make( | |
| width, | |
| height, | |
| color, | |
| alpha, | |
| kBitsPerComponent, | |
| std::move(colorProfile) | |
| ); | |
| // Pre-calculate srcRowBytes (width * bytesPerPixel) and check for overflow. | |
| // Also verify that the total image size (height * srcRowBytes) doesn't overflow. | |
| size_t bytesPerPixel; | |
| switch (color) { | |
| case SkEncodedInfo::kRGB_Color: | |
| case SkEncodedInfo::kBGR_Color: | |
| bytesPerPixel = 3; | |
| break; | |
| case SkEncodedInfo::kRGBA_Color: | |
| case SkEncodedInfo::kBGRA_Color: | |
| case SkEncodedInfo::kBGRX_Color: | |
| bytesPerPixel = 4; | |
| break; | |
| default: | |
| *result = kInvalidInput; | |
| return nullptr; | |
| } | |
| SkSafeMath safe; | |
| size_t srcRowBytes = safe.mul(safe.castTo<size_t>(width), bytesPerPixel); | |
| (void)safe.mul(safe.castTo<size_t>(height), srcRowBytes); | |
| if (!safe.ok()) { | |
| *result = kInternalError; | |
| return nullptr; | |
| } | |
| *result = kSuccess; | |
| return std::unique_ptr<SkBmpRustCodec>(new SkBmpRustCodec( | |
| std::move(encodedInfo), | |
| std::move(stream), | |
| std::move(reader), | |
| streamType, | |
| std::move(inMemoryData) | |
| )); | |
| } | |
| SkBmpRustCodec::SkBmpRustCodec(SkEncodedInfo&& encodedInfo, | |
| std::unique_ptr<SkStream> stream, | |
| rust::Box<rust_bmp::Reader> reader, | |
| StreamType streamType, | |
| sk_sp<const SkData> inMemoryData) | |
| : SkCodec(std::move(encodedInfo), skcms_PixelFormat_RGBA_8888, | |
| // TODO(crbug.com/370522089): Pass stream to SkCodec once SkCodec | |
| // avoids unnecessary rewinding (which forces re-reading entire stream). | |
| /* stream = */ nullptr) | |
| , fReader(std::move(reader)) | |
| , fPrivStream(std::move(stream)) | |
| , fInMemoryData(std::move(inMemoryData)) | |
| , fStreamType(streamType) { | |
| SkASSERT_RELEASE(fPrivStream); | |
| } | |
| SkBmpRustCodec::~SkBmpRustCodec() = default; | |
| sk_sp<const SkData> SkBmpRustCodec::getEncodedData() const { | |
| SkASSERT_RELEASE(fPrivStream); | |
| if (fInMemoryData) { | |
| return fInMemoryData; | |
| } | |
| sk_sp<const SkData> data = fPrivStream->getData(); | |
| if (data) { | |
| return data; | |
| } | |
| auto dStream = fPrivStream->duplicate(); | |
| if (!dStream->hasLength()) { | |
| return nullptr; | |
| } | |
| return SkData::MakeFromStream(dStream.get(), dStream->getLength()); | |
| } | |
| bool SkBmpRustCodec::onGetFrameInfo(int index, FrameInfo* info) const { | |
| if (index != 0) { | |
| return false; // BMP images only have one frame | |
| } | |
| if (info) { | |
| // BMP images are single-frame, so set frame metadata accordingly | |
| info->fRequiredFrame = SkCodec::kNoFrame; | |
| info->fDuration = 0; | |
| info->fFullyReceived = fReader->image_data_loaded(); | |
| info->fAlphaType = this->getInfo().alphaType(); | |
| info->fHasAlphaWithinBounds = info->fAlphaType != kOpaque_SkAlphaType; | |
| info->fDisposalMethod = SkCodecAnimation::DisposalMethod::kKeep; | |
| info->fBlend = SkCodecAnimation::Blend::kSrc; | |
| info->fFrameRect = SkIRect::MakeSize(this->dimensions()); | |
| } | |
| return true; | |
| } | |
| bool SkBmpRustCodec::onRewind() { | |
| // Clear any incremental decoding state | |
| fIncrementalDecodingState.reset(); | |
| if (!fPrivStream->rewind()) { | |
| return false; | |
| } | |
| rust::Box<rust_bmp::ResultOfReader> resultOfReader = [&]() { | |
| if (fInMemoryData) { | |
| return NewReaderFromData(fInMemoryData.get()); | |
| } | |
| return NewReaderFromStream(fPrivStream.get()); | |
| }(); | |
| if (resultOfReader->err() != rust_bmp::DecodingResult::Success) { | |
| return false; | |
| } | |
| fReader = resultOfReader->unwrap(); | |
| // Read metadata for the new reader (required before read_image_data can work). | |
| // Use ICO-aware metadata parsing if this codec was created from an ICO stream. | |
| if (!fReader->metadata_loaded()) { | |
| rust_bmp::DecodingResult metadataResult = (fStreamType == StreamType::kICO) | |
| ? fReader->read_metadata_for_ico() | |
| : fReader->read_metadata(); | |
| if (metadataResult != rust_bmp::DecodingResult::Success) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| SkCodec::Result SkBmpRustCodec::initializeSwizzler(const SkImageInfo& dstInfo, | |
| const Options& opts) { | |
| SkImageInfo swizzlerInfo = dstInfo; | |
| SkCodec::Options swizzlerOptions = opts; | |
| if (this->xformOnDecode()) { | |
| fXformBuffer.reset(new uint32_t[dstInfo.width()]); | |
| swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType); | |
| if (kPremul_SkAlphaType == dstInfo.alphaType()) { | |
| swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType); | |
| } | |
| swizzlerOptions.fZeroInitialized = kNo_ZeroInitialized; | |
| } | |
| fSwizzler = SkSwizzler::Make(this->getEncodedInfo(), nullptr, swizzlerInfo, swizzlerOptions); | |
| if (!fSwizzler) { | |
| // SkSwizzler doesn't support all possible destination color types (e.g., | |
| // kBGRA_10101010_XR_SkColorType). Return kInvalidConversion if the requested | |
| // destination format is not supported. | |
| return kInvalidConversion; | |
| } | |
| return kSuccess; | |
| } | |
| void SkBmpRustCodec::swizzleRow(const uint8_t* srcRow, void* dstRow) { | |
| if (this->xformOnDecode()) { | |
| // Use swizzler's width, which accounts for subsets | |
| const int32_t width = fSwizzler->swizzleWidth(); | |
| fSwizzler->swizzle(fXformBuffer.get(), srcRow); | |
| this->applyColorXform(dstRow, fXformBuffer.get(), width); | |
| } else { | |
| fSwizzler->swizzle(dstRow, srcRow); | |
| } | |
| } | |
| SkCodec::Result SkBmpRustCodec::onGetPixels(const SkImageInfo& info, | |
| void* dst, | |
| size_t dstRowStride, | |
| const Options& options, | |
| int* rowsDecoded) { | |
| Result result = this->initializeSwizzler(info, options); | |
| if (result != kSuccess) { | |
| return result; | |
| } | |
| return this->performFullDecode(info, dst, dstRowStride); | |
| } | |
| SkCodec::Result SkBmpRustCodec::performFullDecode(const SkImageInfo& dstInfo, | |
| void* dst, | |
| size_t dstRowStride) { | |
| // Reset the reader to start decoding from the beginning. | |
| fReader->reset_decode_state(); | |
| // Read all image data into the Rust decoder's internal buffer. | |
| // This may return IncompleteInput if not enough data is available. | |
| rust_bmp::DecodingResult readResult = fReader->read_image_data(); | |
| if (readResult != rust_bmp::DecodingResult::Success) { | |
| return MapDecodingResult(readResult); | |
| } | |
| const int32_t height = this->dimensions().height(); | |
| const size_t srcRowBytes = fReader->row_bytes(); | |
| // Get all rows and pixel data in a single call | |
| rust::Slice<const uint8_t> imageData; | |
| rust_bmp::DecodedRowsInfo rowsInfo = fReader->get_next_rows(imageData); | |
| if (imageData.empty()) { | |
| return kErrorInInput; | |
| } | |
| // Full decode should always produce all rows - if not, there's a bug. | |
| SkASSERT_RELEASE(rowsInfo.row_count == static_cast<uint32_t>(height)); | |
| SkSpan<const uint8_t> srcImage(imageData.data(), imageData.size()); | |
| SkSpan<uint8_t> dstImage(static_cast<uint8_t*>(dst), height * dstRowStride); | |
| // The image crate handles bottom-up vs top-down row ordering internally. | |
| // After full decode, the buffer is always in logical (top-to-bottom) order, | |
| // so we just copy rows sequentially without flipping. | |
| const uint32_t rowCount = rowsInfo.row_count; | |
| for (uint32_t y = 0; y < rowCount; ++y) { | |
| SkSpan<const uint8_t> srcRow = srcImage.subspan(y * srcRowBytes, srcRowBytes); | |
| SkSpan<uint8_t> dstRow = dstImage.subspan(y * dstRowStride, dstRowStride); | |
| this->swizzleRow(srcRow.data(), dstRow.data()); | |
| } | |
| return kSuccess; | |
| } | |
| SkCodec::Result SkBmpRustCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo, | |
| void* dst, | |
| size_t dstRowBytes, | |
| const Options& options) { | |
| // Validate and initialize swizzler | |
| Result result = this->initializeSwizzler(dstInfo, options); | |
| if (result != kSuccess) { | |
| return result; | |
| } | |
| // Reset the reader to start decoding from the beginning | |
| fReader->reset_decode_state(); | |
| // Initialize decoding state | |
| DecodingState state; | |
| // Set up destination buffer information | |
| const int32_t height = dstInfo.height(); | |
| SkSafeMath safe; | |
| size_t totalDstSize = safe.mul(safe.castTo<size_t>(height), dstRowBytes); | |
| if (!safe.ok()) { | |
| return kInternalError; | |
| } | |
| state.fDst = SkSpan(static_cast<uint8_t*>(dst), totalDstSize); | |
| state.fDstRowStride = dstRowBytes; | |
| // Store state for subsequent incremental decode calls | |
| fIncrementalDecodingState = std::move(state); | |
| return kSuccess; | |
| } | |
| SkCodec::Result SkBmpRustCodec::onIncrementalDecode(int* rowsDecoded) { | |
| if (!fIncrementalDecodingState.has_value()) { | |
| return kInvalidParameters; | |
| } | |
| Result result = this->incrementalDecode(*fIncrementalDecodingState, rowsDecoded); | |
| // Clean up state when decoding completes or encounters non-recoverable error | |
| if (result == kSuccess || result != kIncompleteInput) { | |
| fIncrementalDecodingState.reset(); | |
| } | |
| return result; | |
| } | |
| SkCodec::Result SkBmpRustCodec::incrementalDecode(DecodingState& state, int* rowsDecodedPtr) { | |
| const size_t srcRowBytes = fReader->row_bytes(); | |
| // Attempt to read image data. Returns IncompleteInput if more data needed, | |
| // Success when all data is loaded. | |
| rust_bmp::DecodingResult readResult = fReader->read_image_data(); | |
| if (readResult != rust_bmp::DecodingResult::Success && | |
| readResult != rust_bmp::DecodingResult::IncompleteInput) { | |
| return MapDecodingResult(readResult); | |
| } | |
| // Get NEW decoded rows from FFI and copy to destination. | |
| // FFI tracks progress - each call returns only newly decoded rows. | |
| rust::Slice<const uint8_t> imageData; | |
| rust_bmp::DecodedRowsInfo rowsInfo = fReader->get_next_rows(imageData); | |
| const uint32_t dstRowStart = rowsInfo.dst_row_start; | |
| const uint32_t rowCount = rowsInfo.row_count; | |
| if (rowCount > 0 && !imageData.empty()) { | |
| SkSpan<const uint8_t> srcImage(imageData.data(), imageData.size()); | |
| // Copy new rows: src row i -> dst row (dstRowStart + i) | |
| for (uint32_t i = 0; i < rowCount; ++i) { | |
| SkSpan<const uint8_t> srcRow = srcImage.subspan(i * srcRowBytes, srcRowBytes); | |
| uint32_t dstY = dstRowStart + i; | |
| void* dstRow = state.fDst.data() + (dstY * state.fDstRowStride); | |
| this->swizzleRow(srcRow.data(), dstRow); | |
| } | |
| state.fTotalRowsDecoded += rowCount; | |
| } | |
| if (rowsDecodedPtr) { | |
| // Report total rows decoded so far. | |
| // This is the cumulative count, regardless of row order (top-down or bottom-up). | |
| *rowsDecodedPtr = state.fTotalRowsDecoded; | |
| } | |
| return (readResult == rust_bmp::DecodingResult::Success) ? kSuccess : kIncompleteInput; | |
| } |