blob: 76f89a3d0002f6067a257e572729b22d54e92d98 [file]
/*
* Copyright 2026 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "experimental/rust_jpeg/decoder/impl/SkJpegRustCodec.h"
#include <algorithm>
#include <array>
#include <limits>
#include "include/codec/SkCodecAnimation.h"
#include "include/core/SkColorType.h"
#include "include/core/SkData.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkSpan.h"
#include "include/core/SkStream.h"
#include "include/core/SkYUVAInfo.h"
#include "include/core/SkYUVAPixmaps.h"
#include "include/private/SkAssert.h"
#include "include/private/SkEncodedInfo.h"
#include "include/private/SkFeatures.h"
#include "include/private/SkGainmapInfo.h"
#include "include/private/SkJpegMetadataDecoder.h"
#include "include/private/SkTemplates.h"
#include "include/private/SkTo.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"
#include "include/private/SkExif.h"
#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
#include "include/core/SkColorSpace.h"
#include "include/private/SkXmp.h"
#include "modules/skcms/skcms.h"
#include "src/codec/SkJpegConstants.h"
#include "src/codec/SkJpegXmp.h"
#endif // SK_CODEC_DECODES_JPEG_GAINMAPS
static constexpr SkColorType kXformSrcColorType = kRGBA_8888_SkColorType;
// Static assertions to validate that Rust JpegColor/JpegAlpha enum values
// match the corresponding SkEncodedInfo enum values.
static_assert(static_cast<int>(rust_jpeg::JpegColor::RGB) ==
static_cast<int>(SkEncodedInfo::kRGB_Color),
"JpegColor::RGB must match SkEncodedInfo::kRGB_Color");
static_assert(static_cast<int>(rust_jpeg::JpegColor::Grayscale) ==
static_cast<int>(SkEncodedInfo::kGray_Color),
"JpegColor::Grayscale must match SkEncodedInfo::kGray_Color");
static_assert(static_cast<int>(rust_jpeg::JpegColor::InvertedCMYK) ==
static_cast<int>(SkEncodedInfo::kInvertedCMYK_Color),
"JpegColor::InvertedCMYK must match SkEncodedInfo::kInvertedCMYK_Color");
static_assert(static_cast<int>(rust_jpeg::JpegAlpha::Opaque) ==
static_cast<int>(SkEncodedInfo::kOpaque_Alpha),
"JpegAlpha::Opaque must match SkEncodedInfo::kOpaque_Alpha");
namespace {
// zune-jpeg can emit one deferred interpolation group plus eight regular groups per vertical
// sampling factor. JPEG sampling factors are at most 4, so a stripe is at most (8 + 1) * 4 rows.
constexpr int kMaxScanlineBatchRows = 36;
constexpr rust_jpeg::PackedOutputFormat NativeN32OutputFormat() {
#if SK_PMCOLOR_BYTE_ORDER(B, G, R, A)
return rust_jpeg::PackedOutputFormat::BGRA;
#else
static_assert(SK_PMCOLOR_BYTE_ORDER(R, G, B, A), "Unexpected SK_PMCOLOR_BYTE_ORDER");
return rust_jpeg::PackedOutputFormat::RGBA;
#endif
}
uint64_t StreamLengthOrZero(const SkStream& stream) {
if (!stream.hasLength()) {
return 0;
}
return static_cast<uint64_t>(stream.getLength());
}
SkCodec::Result MapDecodingResult(rust_jpeg::DecodingResult rustResult) {
switch (rustResult) {
case rust_jpeg::DecodingResult::Success:
return SkCodec::kSuccess;
case rust_jpeg::DecodingResult::FormatError:
return SkCodec::kErrorInInput;
case rust_jpeg::DecodingResult::ParameterError:
return SkCodec::kInvalidParameters;
case rust_jpeg::DecodingResult::UnsupportedFeature:
return SkCodec::kUnimplemented;
case rust_jpeg::DecodingResult::IncompleteInput:
return SkCodec::kIncompleteInput;
case rust_jpeg::DecodingResult::MemoryError:
return SkCodec::kInternalError;
case rust_jpeg::DecodingResult::OtherError:
return SkCodec::kErrorInInput;
}
SK_ABORT("Unexpected `rust_jpeg::DecodingResult`: %d", static_cast<int>(rustResult));
}
bool GetYUVAInfo(const rust_jpeg::Reader& reader,
SkISize dimensions,
SkEncodedOrigin origin,
const SkYUVAPixmapInfo::SupportedDataTypes* supportedDataTypes,
SkYUVAPixmapInfo* yuvaPixmapInfo) {
if (reader.raw_component_count() != 3) {
return false;
}
std::array<rust_jpeg::RawPlaneInfo, 3> planes = {
reader.raw_plane_info(0), reader.raw_plane_info(1), reader.raw_plane_info(2)};
if (planes[0].component_id != 1 || planes[1].component_id != 2 || planes[2].component_id != 3 ||
planes[1].horizontal_sampling_factor != 1 || planes[1].vertical_sampling_factor != 1 ||
planes[2].horizontal_sampling_factor != 1 || planes[2].vertical_sampling_factor != 1) {
return false;
}
const uint32_t hY = planes[0].horizontal_sampling_factor;
const uint32_t vY = planes[0].vertical_sampling_factor;
SkYUVAInfo::Subsampling subsampling;
if (hY == 1 && vY == 1) {
subsampling = SkYUVAInfo::Subsampling::k444;
} else if (hY == 2 && vY == 1) {
subsampling = SkYUVAInfo::Subsampling::k422;
} else if (hY == 2 && vY == 2) {
subsampling = SkYUVAInfo::Subsampling::k420;
} else if (hY == 1 && vY == 2) {
subsampling = SkYUVAInfo::Subsampling::k440;
} else if (hY == 4 && vY == 1) {
subsampling = SkYUVAInfo::Subsampling::k411;
} else if (hY == 4 && vY == 2) {
subsampling = SkYUVAInfo::Subsampling::k410;
} else {
return false;
}
const auto ceilDiv = [](uint32_t value, uint32_t divisor) {
return value / divisor + (value % divisor != 0);
};
const uint32_t width = static_cast<uint32_t>(dimensions.width());
const uint32_t height = static_cast<uint32_t>(dimensions.height());
if (planes[0].width != width || planes[0].height != height ||
planes[1].width != ceilDiv(width, hY) || planes[2].width != ceilDiv(width, hY) ||
planes[1].height != ceilDiv(height, vY) || planes[2].height != ceilDiv(height, vY)) {
return false;
}
for (const auto& plane : planes) {
if (plane.width == 0 || plane.height == 0 || plane.row_bytes < plane.width) {
return false;
}
}
constexpr auto kConfig = SkYUVAInfo::PlaneConfig::kY_U_V;
constexpr auto kDataType = SkYUVAPixmapInfo::DataType::kUnorm8;
if (supportedDataTypes && !supportedDataTypes->supported(kConfig, kDataType)) {
return false;
}
if (yuvaPixmapInfo) {
const SkYUVAInfo yuvaInfo(dimensions,
kConfig,
subsampling,
kJPEG_Full_SkYUVColorSpace,
origin,
SkYUVAInfo::Siting::kCentered,
SkYUVAInfo::Siting::kCentered);
SkColorType colorTypes[SkYUVAPixmapInfo::kMaxPlanes] = {
kAlpha_8_SkColorType, kAlpha_8_SkColorType, kAlpha_8_SkColorType};
size_t rowBytes[SkYUVAPixmapInfo::kMaxPlanes] = {
planes[0].row_bytes, planes[1].row_bytes, planes[2].row_bytes};
*yuvaPixmapInfo = SkYUVAPixmapInfo(yuvaInfo, colorTypes, rowBytes);
if (!yuvaPixmapInfo->isValid()) {
return false;
}
}
return true;
}
} // namespace
std::unique_ptr<SkJpegRustCodec> SkJpegRustCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
Result* result,
bool rawInputComplete) {
Result resultStorage;
if (!result) {
result = &resultStorage;
}
if (!stream) {
*result = kInvalidInput;
return nullptr;
}
auto inputAdapter = std::make_unique<rust::stream::SkStreamAdapter>(stream.get());
rust::Box<rust_jpeg::Reader> reader =
rust_jpeg::new_reader(
std::move(inputAdapter), StreamLengthOrZero(*stream), rawInputComplete);
if (reader->initialization_result() != rust_jpeg::DecodingResult::Success) {
*result = MapDecodingResult(reader->initialization_result());
return nullptr;
}
if (!reader->metadata_loaded()) {
rust_jpeg::DecodingResult metadataResult = reader->read_metadata();
if (metadataResult != rust_jpeg::DecodingResult::Success) {
*result = MapDecodingResult(metadataResult);
return nullptr;
}
SkASSERT_RELEASE(reader->metadata_loaded());
}
uint32_t width = reader->width();
uint32_t height = reader->height();
rust_jpeg::JpegColor rustColor = reader->color();
rust_jpeg::JpegAlpha rustAlpha = reader->alpha();
SkEncodedInfo::Color color = static_cast<SkEncodedInfo::Color>(rustColor);
SkEncodedInfo::Alpha alpha = static_cast<SkEncodedInfo::Alpha>(rustAlpha);
constexpr int kBitsPerComponent = 8;
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));
}
// Validate ICC profile data space against the image color space,
// matching the existing C++ JPEG codec's behavior. Drop profiles
// whose data space is incompatible.
if (colorProfile) {
auto dataSpace = colorProfile->dataSpace();
switch (color) {
case SkEncodedInfo::kInvertedCMYK_Color:
if (dataSpace != SkCodecs::ColorProfile::DataSpace::kCMYK) {
colorProfile = nullptr;
}
break;
case SkEncodedInfo::kGray_Color:
if (dataSpace != SkCodecs::ColorProfile::DataSpace::kGray &&
dataSpace != SkCodecs::ColorProfile::DataSpace::kRGB) {
colorProfile = nullptr;
}
break;
default:
if (dataSpace != SkCodecs::ColorProfile::DataSpace::kRGB) {
colorProfile = nullptr;
}
break;
}
}
// Parse orientation from EXIF data using SkExif::Parse (shared with C++ codec).
SkEncodedOrigin orientation = kDefault_SkEncodedOrigin;
{
rust::Vec<uint8_t> exifBytes = reader->exif_data();
if (!exifBytes.empty()) {
auto exifData = SkData::MakeWithCopy(exifBytes.data(), exifBytes.size());
SkExif::Metadata exif;
SkExif::Parse(exif, exifData.get());
if (exif.fOrigin.has_value()) {
orientation = exif.fOrigin.value();
}
}
}
SkEncodedInfo encodedInfo = SkEncodedInfo::Make(
width, height, color, alpha, kBitsPerComponent, std::move(colorProfile));
// Guard against overflow: reject images whose raw pixel buffer would
// exceed size_t range.
size_t bytesPerPixel;
switch (color) {
case SkEncodedInfo::kRGB_Color:
bytesPerPixel = 3;
break;
case SkEncodedInfo::kGray_Color:
bytesPerPixel = 1;
break;
case SkEncodedInfo::kInvertedCMYK_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<SkJpegRustCodec>(new SkJpegRustCodec(
std::move(encodedInfo), std::move(stream), std::move(reader), orientation));
}
SkJpegRustCodec::SkJpegRustCodec(SkEncodedInfo&& encodedInfo,
std::unique_ptr<SkStream> stream,
rust::Box<rust_jpeg::Reader> reader,
SkEncodedOrigin origin)
: SkCodec(std::move(encodedInfo),
skcms_PixelFormat_RGBA_8888,
/* stream = */ nullptr,
origin)
, fPrivStream(std::move(stream))
, fReader(std::move(reader)) {
SkASSERT_RELEASE(fPrivStream);
}
SkJpegRustCodec::~SkJpegRustCodec() = default;
sk_sp<const SkData> SkJpegRustCodec::getEncodedData() const {
SkASSERT_RELEASE(fPrivStream);
sk_sp<const SkData> data = fPrivStream->getData();
if (data) {
return data;
}
auto dStream = fPrivStream->duplicate();
if (!dStream || !dStream->hasLength()) {
return nullptr;
}
return SkData::MakeFromStream(dStream.get(), dStream->getLength());
}
bool SkJpegRustCodec::onGetFrameInfo(int index, FrameInfo* info) const {
if (index != 0) {
return false;
}
if (info) {
info->fRequiredFrame = SkCodec::kNoFrame;
info->fDuration = 0;
info->fFullyReceived = fReader->image_data_loaded();
info->fAlphaType = this->getInfo().alphaType();
info->fHasAlphaWithinBounds = false; // JPEG never has alpha
info->fDisposalMethod = SkCodecAnimation::DisposalMethod::kKeep;
info->fBlend = SkCodecAnimation::Blend::kSrc;
info->fFrameRect = SkIRect::MakeSize(this->dimensions());
}
return true;
}
bool SkJpegRustCodec::onGetGainmapInfo(SkGainmapInfo* info,
std::unique_ptr<SkStream>* gainmapImageStream) {
#ifndef SK_CODEC_DECODES_JPEG_GAINMAPS
return false;
#else
// Segment scanning and MPF TIFF/IFD parsing happen in Rust. Higher-level
// gainmap detection (XMP, ISO 21496-1) uses C++ SkJpegMetadataDecoder.
if (!fReader->segments_scanned()) {
return false;
}
if (!fReader->has_mpf()) {
return false;
}
// Two lambdas needed: CXX exposes Reader and EmbeddedJpegScanner methods
// with different prefixes in a flat namespace.
auto buildSegmentsFromReader =
[](const rust_jpeg::Reader& reader) -> std::vector<SkJpegMetadataDecoder::Segment> {
std::vector<SkJpegMetadataDecoder::Segment> segs;
const uint32_t count = reader.segment_count();
segs.reserve(count);
for (uint32_t i = 0; i < count; ++i) {
rust_jpeg::JpegSegmentInfo si = reader.segment_info(i);
if (si.parameter_length <= 2)
continue;
rust::Vec<uint8_t> pd = reader.segment_data(i);
if (pd.empty())
continue;
segs.emplace_back(si.marker, SkData::MakeWithCopy(pd.data(), pd.size()));
}
return segs;
};
auto buildSegmentsFromEmbedded = [](const rust_jpeg::EmbeddedJpegScanner& scanner)
-> std::vector<SkJpegMetadataDecoder::Segment> {
std::vector<SkJpegMetadataDecoder::Segment> segs;
const uint32_t count = scanner.embedded_segment_count();
segs.reserve(count);
for (uint32_t i = 0; i < count; ++i) {
rust_jpeg::JpegSegmentInfo si = scanner.embedded_segment_info(i);
if (si.parameter_length <= 2) continue;
rust::Vec<uint8_t> pd = scanner.embedded_segment_data(i);
if (pd.empty()) continue;
segs.emplace_back(si.marker, SkData::MakeWithCopy(pd.data(), pd.size()));
}
return segs;
};
auto baseMetadata = SkJpegMetadataDecoder::Make(buildSegmentsFromReader(*fReader));
if (!baseMetadata) {
return false;
}
SkExif::Metadata baseExif;
SkExif::Parse(baseExif, baseMetadata->getExifMetadata(/*copyData=*/false).get());
bool isoGainmapPresent = SkGainmapInfo::ParseVersion(
baseMetadata->getISOGainmapMetadata(/*copyData=*/false).get());
std::vector<sk_sp<const SkData> > baseApp1Params;
{
const uint32_t segCount = fReader->segment_count();
for (uint32_t i = 0; i < segCount; ++i) {
rust_jpeg::JpegSegmentInfo si = fReader->segment_info(i);
if (si.marker == kXMPMarker && si.parameter_length > 2) {
rust::Vec<uint8_t> pd = fReader->segment_data(i);
if (!pd.empty()) {
baseApp1Params.push_back(SkData::MakeWithCopy(pd.data(), pd.size()));
}
}
}
}
auto baseXmp = SkJpegMakeXmp(baseApp1Params);
bool adobeGainmapPresent = baseXmp && baseXmp->getGainmapInfoAdobe(nullptr);
sk_sp<const SkData> encodedData = this->getEncodedData();
if (!encodedData) {
return false;
}
const uint32_t mpfSegOff = fReader->mpf_segment_offset();
const uint32_t mpfCount = fReader->mpf_image_count();
// MPF entries are zero-based. Entry 0 is the primary image, so inspect
// secondary images in [1, mpfCount).
for (uint32_t mpIdx = 1; mpIdx < mpfCount; ++mpIdx) {
rust_jpeg::MpfImageEntry entry = fReader->mpf_image_entry(mpIdx);
// 2 (marker) + 2 (param length) + 4 (MPF signature).
constexpr size_t kMpfHeaderSize = 8;
SkSafeMath safe;
size_t imageOffset = safe.add(safe.add(static_cast<size_t>(mpfSegOff), kMpfHeaderSize),
static_cast<size_t>(entry.data_offset));
size_t imageSize = entry.size;
size_t imageEnd = safe.add(imageOffset, imageSize);
if (!safe.ok() || imageEnd > encodedData->size()) {
continue;
}
rust::Slice<const uint8_t> embSlice(encodedData->bytes() + imageOffset, imageSize);
auto embScanner = rust_jpeg::scan_embedded_jpeg(embSlice);
if (embScanner->embedded_had_error()) {
continue;
}
auto embMetadata = SkJpegMetadataDecoder::Make(buildSegmentsFromEmbedded(*embScanner));
if (!embMetadata) {
continue;
}
SkGainmapInfo gainmapInfo;
bool foundGainmap = false;
if (isoGainmapPresent) {
foundGainmap = SkGainmapInfo::Parse(
embMetadata->getISOGainmapMetadata(/*copyData=*/false).get(), gainmapInfo);
if (foundGainmap && gainmapInfo.fGainmapMathColorSpace) {
// Use the gain map image's ICC color space for gainmap math.
sk_sp<SkColorSpace> imageColorSpace;
auto iccData = embMetadata->getICCProfileData(/*copyData=*/false);
skcms_ICCProfile iccProfile;
if (iccData && skcms_Parse(iccData->data(), iccData->size(), &iccProfile)) {
imageColorSpace = SkColorSpace::Make(iccProfile);
}
gainmapInfo.fGainmapMathColorSpace = std::move(imageColorSpace);
}
}
if (!foundGainmap) {
std::vector<sk_sp<const SkData> > embApp1Params;
const uint32_t embSegCount = embScanner->embedded_segment_count();
for (uint32_t j = 0; j < embSegCount; ++j) {
rust_jpeg::JpegSegmentInfo si = embScanner->embedded_segment_info(j);
if (si.marker == kXMPMarker && si.parameter_length > 2) {
rust::Vec<uint8_t> pd = embScanner->embedded_segment_data(j);
if (!pd.empty()) {
embApp1Params.push_back(SkData::MakeWithCopy(pd.data(), pd.size()));
}
}
}
auto embXmp = SkJpegMakeXmp(embApp1Params);
if (embXmp) {
if (adobeGainmapPresent) {
foundGainmap = embXmp->getGainmapInfoAdobe(&gainmapInfo);
}
if (!foundGainmap && baseExif.fHdrHeadroom.has_value()) {
foundGainmap = embXmp->getGainmapInfoApple(baseExif.fHdrHeadroom.value(),
&gainmapInfo);
}
}
}
if (foundGainmap) {
*info = gainmapInfo;
*gainmapImageStream = SkMemoryStream::Make(
SkData::MakeWithCopy(encodedData->bytes() + imageOffset, imageSize));
return true;
}
}
return false;
#endif // SK_CODEC_DECODES_JPEG_GAINMAPS
}
bool SkJpegRustCodec::onGetGainmapCodec(SkGainmapInfo* info,
std::unique_ptr<SkCodec>* gainmapCodec) {
std::unique_ptr<SkStream> stream;
if (!this->onGetGainmapInfo(info, &stream)) {
return false;
}
if (gainmapCodec) {
Result result;
*gainmapCodec = SkCodec::MakeFromStream(std::move(stream), &result);
if (!*gainmapCodec) {
return false;
}
}
return true;
}
bool SkJpegRustCodec::onRewind() {
fIncrementalDecodingState.reset();
fScanlineBuffer.clear();
fSwizzleRowCount = 0;
fDirectPackedOutput = false;
if (!fPrivStream->rewind()) {
return false;
}
auto inputAdapter = std::make_unique<rust::stream::SkStreamAdapter>(fPrivStream.get());
const bool rawInputComplete = fReader->raw_input_complete();
fReader = rust_jpeg::new_reader(
std::move(inputAdapter), StreamLengthOrZero(*fPrivStream), rawInputComplete);
if (fReader->initialization_result() != rust_jpeg::DecodingResult::Success) {
return false;
}
if (!fReader->metadata_loaded()) {
rust_jpeg::DecodingResult metadataResult = fReader->read_metadata();
if (metadataResult != rust_jpeg::DecodingResult::Success) {
return false;
}
}
return true;
}
bool SkJpegRustCodec::onQueryYUVAInfo(
const SkYUVAPixmapInfo::SupportedDataTypes& supportedDataTypes,
SkYUVAPixmapInfo* yuvaPixmapInfo) const {
return GetYUVAInfo(
*fReader, this->dimensions(), this->getOrigin(), &supportedDataTypes, yuvaPixmapInfo);
}
SkCodec::Result SkJpegRustCodec::onGetYUVAPlanes(const SkYUVAPixmaps& yuvaPixmaps) {
SkYUVAPixmapInfo expectedInfo;
if (!GetYUVAInfo(*fReader, this->dimensions(), this->getOrigin(), nullptr, &expectedInfo) ||
yuvaPixmaps.numPlanes() != 3 || yuvaPixmaps.yuvaInfo() != expectedInfo.yuvaInfo() ||
yuvaPixmaps.dataType() != SkYUVAPixmapInfo::DataType::kUnorm8) {
return kInvalidInput;
}
const auto& planes = yuvaPixmaps.planes();
for (int index = 0; index < 3; ++index) {
if (planes[index].colorType() != kAlpha_8_SkColorType ||
planes[index].dimensions() != expectedInfo.planeInfo(index).dimensions() ||
planes[index].rowBytes() < static_cast<size_t>(planes[index].width()) ||
planes[index].rowBytes() > std::numeric_limits<uint32_t>::max() ||
!planes[index].writable_addr()) {
return kInvalidInput;
}
}
fReader->reset_decode_state();
std::array<uint32_t, 3> outputRows = {};
while (true) {
std::array<rust::Slice<uint8_t>, 3> destinations;
for (int index = 0; index < 3; ++index) {
const SkPixmap& plane = planes[index];
const size_t planeHeight = static_cast<size_t>(plane.height());
if (outputRows[index] > planeHeight) {
return kErrorInInput;
}
const size_t remainingRows = planeHeight - outputRows[index];
SkSafeMath safe;
const size_t addressOffset = safe.mul(outputRows[index], plane.rowBytes());
const size_t remainingBytes =
remainingRows == 0 ? 0
: safe.add(safe.mul(remainingRows - 1, plane.rowBytes()),
static_cast<size_t>(plane.width()));
if (!safe.ok()) {
return kInternalError;
}
auto* address = remainingRows == 0
? static_cast<uint8_t*>(plane.writable_addr())
: static_cast<uint8_t*>(plane.writable_addr()) + addressOffset;
destinations[index] = rust::Slice<uint8_t>(address, remainingBytes);
}
rust_jpeg::RawOutputResult result =
fReader->read_raw_imcu_row(destinations[0],
SkToU32(planes[0].rowBytes()),
destinations[1],
SkToU32(planes[1].rowBytes()),
destinations[2],
SkToU32(planes[2].rowBytes()));
switch (result.status) {
case rust_jpeg::OutputStatus::Ready: {
const std::array<uint32_t, 3> rows = {result.y_rows, result.u_rows, result.v_rows};
bool madeProgress = false;
for (int index = 0; index < 3; ++index) {
const uint32_t planeHeight = static_cast<uint32_t>(planes[index].height());
if (outputRows[index] > planeHeight ||
rows[index] > planeHeight - outputRows[index]) {
return kErrorInInput;
}
outputRows[index] += rows[index];
madeProgress |= rows[index] != 0;
}
if (!madeProgress) {
return kErrorInInput;
}
break;
}
case rust_jpeg::OutputStatus::NeedMoreInput:
return kIncompleteInput;
case rust_jpeg::OutputStatus::Complete:
for (int index = 0; index < 3; ++index) {
if (outputRows[index] != static_cast<uint32_t>(planes[index].height())) {
return kIncompleteInput;
}
}
return kSuccess;
case rust_jpeg::OutputStatus::Error:
return MapDecodingResult(result.result);
default:
return kInternalError;
}
}
}
SkCodec::Result SkJpegRustCodec::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) {
return kInvalidConversion;
}
return kSuccess;
}
void SkJpegRustCodec::swizzleRow(const uint8_t* srcRow, void* dstRow) {
++fSwizzleRowCount;
if (this->xformOnDecode()) {
const int32_t width = fSwizzler->swizzleWidth();
fSwizzler->swizzle(fXformBuffer.get(), srcRow);
this->applyColorXform(dstRow, fXformBuffer.get(), width);
} else {
fSwizzler->swizzle(dstRow, srcRow);
}
}
SkCodec::Result SkJpegRustCodec::onGetPixels(const SkImageInfo& info,
void* dst,
size_t dstRowStride,
const Options& options,
int* rowsDecoded) {
fReader->reset_decode_state();
fSwizzleRowCount = 0;
fDirectPackedOutput = this->shouldUseDirectPackedOutput(info, options);
Result result = fDirectPackedOutput ? kSuccess : this->initializeSwizzler(info, options);
if (result != kSuccess) {
return result;
}
int decodedRows = 0;
result = this->performFullDecode(info, dst, dstRowStride, &decodedRows);
fLastFullDecodeRows = decodedRows;
if (rowsDecoded) {
*rowsDecoded = decodedRows;
}
return result;
}
bool SkJpegRustCodec::shouldUseDirectPackedOutput(const SkImageInfo& dstInfo,
const Options& options) const {
// Limit this optimization to x86 pending performance validation on other targets.
// The reader capability below checks the JPEG color space, not target performance;
// zune-jpeg's packed output is not restricted to x86.
#if defined(SK_CPU_X86)
return !fDisableDirectPackedOutputForTesting && !options.fSubset &&
dstInfo.dimensions() == this->dimensions() && dstInfo.colorType() == kN32_SkColorType &&
dstInfo.alphaType() != kUnknown_SkAlphaType && !this->xformOnDecode() &&
fReader->supports_streaming_full_decode() && fReader->supports_direct_packed_output();
#else
return false;
#endif
}
SkCodec::Result SkJpegRustCodec::performFullDecode(const SkImageInfo& dstInfo,
void* dst,
size_t dstRowStride,
int* rowsDecoded) {
if (fReader->supports_streaming_full_decode()) {
// Baseline and extended sequential JPEGs share the explicit scanline
// implementation. Keep both Rust and C++ staging bounded to an MCU-scale
// batch rather than retaining an additional packed frame.
Result startResult = this->startScanlineSession();
if (startResult != kSuccess) {
return startResult;
}
ScanlineDecodeResult decodeResult =
this->decodeScanlines(dst, dstInfo.height(), dstRowStride);
if (rowsDecoded) {
*rowsDecoded = decodeResult.fRowsDecoded;
}
if (decodeResult.fResult != kSuccess) {
return decodeResult.fResult;
}
rust_jpeg::ScanlineOutputResult finishResult = fReader->finish_scanline_output();
switch (finishResult.status) {
case rust_jpeg::OutputStatus::Complete:
case rust_jpeg::OutputStatus::NeedMoreInput:
return decodeResult.fRowsDecoded == dstInfo.height() ? kSuccess : kIncompleteInput;
case rust_jpeg::OutputStatus::Error:
return MapDecodingResult(finishResult.result);
case rust_jpeg::OutputStatus::Ready:
default:
return kInternalError;
}
}
// Progressive JPEGs are measurably faster through zune-jpeg's packed
// full-image decoder. This is also the fallback for scanline-incompatible
// encodings such as DNL.
rust_jpeg::DecodingResult readResult = fReader->read_image_data();
if (readResult != rust_jpeg::DecodingResult::Success) {
return MapDecodingResult(readResult);
}
const size_t srcRowBytes = fReader->row_bytes();
rust::Slice<const uint8_t> imageData;
rust_jpeg::DecodedRowsInfo rowsInfo = fReader->get_next_rows(imageData);
if (imageData.empty()) {
return kErrorInInput;
}
SkSpan<const uint8_t> srcImage(imageData.data(), imageData.size());
SkSafeMath safe;
size_t totalDstSize = safe.mul(safe.castTo<size_t>(dstInfo.height()), dstRowStride);
if (!safe.ok()) {
return kInternalError;
}
SkSpan<uint8_t> dstImage(static_cast<uint8_t*>(dst), totalDstSize);
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());
}
if (rowsDecoded) {
*rowsDecoded = static_cast<int>(rowCount);
}
return (rowCount == static_cast<uint32_t>(this->dimensions().height())) ? kSuccess
: kIncompleteInput;
}
SkCodec::Result SkJpegRustCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
void* dst,
size_t dstRowBytes,
const Options& options) {
fSwizzleRowCount = 0;
fDirectPackedOutput = false;
if (dstRowBytes < dstInfo.minRowBytes()) {
return kInvalidParameters;
}
if (options.fSubset) {
return kInvalidParameters;
}
Result result = this->initializeSwizzler(dstInfo, options);
if (result != kSuccess) {
return result;
}
fReader->reset_decode_state();
DecodingState state;
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;
fIncrementalDecodingState = std::move(state);
return kSuccess;
}
SkCodec::Result SkJpegRustCodec::onIncrementalDecode(int* rowsDecoded) {
if (!fIncrementalDecodingState.has_value()) {
return kInvalidParameters;
}
Result result = this->incrementalDecode(*fIncrementalDecodingState, rowsDecoded);
if (result != kIncompleteInput) {
fIncrementalDecodingState.reset();
}
return result;
}
SkCodec::Result SkJpegRustCodec::incrementalDecode(DecodingState& state, int* rowsDecodedPtr) {
const size_t srcRowBytes = fReader->row_bytes();
rust_jpeg::DecodingResult readResult = fReader->read_incremental_image_data();
if (readResult != rust_jpeg::DecodingResult::Success &&
readResult != rust_jpeg::DecodingResult::IncompleteInput) {
return MapDecodingResult(readResult);
}
rust::Slice<const uint8_t> imageData;
rust_jpeg::DecodedRowsInfo rowsInfo = fReader->get_next_rows(imageData);
const uint32_t dstRowStart = rowsInfo.dst_row_start;
const uint32_t rowCount = rowsInfo.row_count;
if (rowsInfo.is_preview) {
// Progressive previews replace the initialized frame from row zero.
// SkCodec permits rowsDecoded to include initialized but unfinished rows.
if (dstRowStart != 0) {
return kErrorInInput;
}
}
uint32_t copiedRows = 0;
if (rowCount > 0) {
const uint32_t height = static_cast<uint32_t>(this->dimensions().height());
if (srcRowBytes == 0 || imageData.empty() || dstRowStart >= height ||
rowCount > height - dstRowStart) {
return kErrorInInput;
}
SkSafeMath safe;
const size_t requiredSrcBytes = safe.mul(safe.castTo<size_t>(rowCount), srcRowBytes);
if (!safe.ok() || imageData.size() < requiredSrcBytes) {
return kErrorInInput;
}
SkSpan<const uint8_t> srcImage(imageData.data(), imageData.size());
for (uint32_t i = 0; i < rowCount; ++i) {
const uint32_t dstY = dstRowStart + i;
const size_t srcOffset = safe.mul(safe.castTo<size_t>(i), srcRowBytes);
const size_t dstOffset = safe.mul(safe.castTo<size_t>(dstY), state.fDstRowStride);
if (!safe.ok()) {
return kInternalError;
}
SkSpan<const uint8_t> srcRow = srcImage.subspan(srcOffset, srcRowBytes);
SkSpan<uint8_t> dstRow = state.fDst.subspan(dstOffset, state.fDstRowStride);
this->swizzleRow(srcRow.data(), dstRow.data());
copiedRows++;
}
state.fTotalRowsInitialized =
std::max(state.fTotalRowsInitialized, static_cast<int>(dstRowStart + copiedRows));
}
if (rowsDecodedPtr) {
*rowsDecodedPtr = state.fTotalRowsInitialized;
}
return readResult == rust_jpeg::DecodingResult::Success &&
state.fTotalRowsInitialized >= this->dimensions().height()
? kSuccess
: kIncompleteInput;
}
SkCodec::Result SkJpegRustCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
const Options& options) {
// zune-jpeg currently emits full-frame rows and has no scaled-IDCT or
// jpeg_crop_scanline equivalent. SkCodec rejects scaled dimensions before
// this hook; reject horizontal subsets explicitly instead of claiming them.
if (options.fSubset) {
return kUnimplemented;
}
fSwizzleRowCount = 0;
fDirectPackedOutput = this->shouldUseDirectPackedOutput(dstInfo, options);
Result result = fDirectPackedOutput ? kSuccess : this->initializeSwizzler(dstInfo, options);
if (result != kSuccess) {
return result;
}
fReader->reset_decode_state();
fScanlineBuffer.clear();
return this->startScanlineSession();
}
SkCodec::Result SkJpegRustCodec::startScanlineSession() {
const rust_jpeg::PackedOutputFormat format =
fDirectPackedOutput ? NativeN32OutputFormat() : rust_jpeg::PackedOutputFormat::Source;
rust_jpeg::ScanlineOutputResult start = fReader->start_scanline_output(format);
switch (start.status) {
case rust_jpeg::OutputStatus::Ready:
case rust_jpeg::OutputStatus::Complete:
return kSuccess;
case rust_jpeg::OutputStatus::NeedMoreInput:
return kIncompleteInput;
case rust_jpeg::OutputStatus::Error:
return MapDecodingResult(start.result);
default:
return kInternalError;
}
}
SkJpegRustCodec::ScanlineDecodeResult SkJpegRustCodec::decodeScanlines(void* dst,
int countLines,
size_t dstRowBytes) {
if (!dst || countLines <= 0 || dstRowBytes < this->dstInfo().minRowBytes()) {
return {kInvalidParameters, 0};
}
if (fDirectPackedOutput) {
return this->decodeDirectScanlines(dst, countLines, dstRowBytes);
}
const size_t srcRowBytes = fReader->row_bytes();
SkSafeMath safe;
const size_t bufferSize =
safe.mul(srcRowBytes, safe.castTo<size_t>(std::min(countLines, kMaxScanlineBatchRows)));
if (!safe.ok()) {
return {kInternalError, 0};
}
fScanlineBuffer.resize(bufferSize);
int totalRows = 0;
while (totalRows < countLines) {
const int requestedRows = std::min(countLines - totalRows, kMaxScanlineBatchRows);
const size_t requestedBytes = srcRowBytes * static_cast<size_t>(requestedRows);
SkSpan<uint8_t> batch(fScanlineBuffer.data(), requestedBytes);
rust_jpeg::ScanlineOutputResult result =
fReader->read_scanlines(rust::Slice<uint8_t>(batch.data(), batch.size()),
static_cast<uint32_t>(srcRowBytes));
switch (result.status) {
case rust_jpeg::OutputStatus::Ready:
if (result.rows == 0 || result.rows > static_cast<uint32_t>(requestedRows)) {
return {kErrorInInput, totalRows};
}
break;
case rust_jpeg::OutputStatus::NeedMoreInput:
return {kIncompleteInput, totalRows};
case rust_jpeg::OutputStatus::Complete:
return {totalRows == countLines ? kSuccess : kIncompleteInput, totalRows};
case rust_jpeg::OutputStatus::Error:
return {MapDecodingResult(result.result), totalRows};
default:
return {kInternalError, totalRows};
}
auto* dstBytes = static_cast<uint8_t*>(dst);
for (uint32_t row = 0; row < result.rows; ++row) {
SkSafeMath rowSafe;
const size_t dstRow = rowSafe.add(rowSafe.castTo<size_t>(totalRows), row);
const size_t dstOffset = rowSafe.mul(dstRow, dstRowBytes);
const size_t srcOffset = rowSafe.mul(row, srcRowBytes);
if (!rowSafe.ok()) {
return {kInternalError, totalRows};
}
this->swizzleRow(fScanlineBuffer.data() + srcOffset, dstBytes + dstOffset);
}
totalRows += static_cast<int>(result.rows);
}
return {kSuccess, totalRows};
}
SkJpegRustCodec::ScanlineDecodeResult SkJpegRustCodec::decodeDirectScanlines(void* dst,
int countLines,
size_t dstRowBytes) {
const size_t logicalRowBytes = this->dstInfo().minRowBytes();
if (logicalRowBytes == 0 || logicalRowBytes != fReader->row_bytes() ||
dstRowBytes < logicalRowBytes) {
return {kInvalidParameters, 0};
}
fScanlineBuffer.clear();
int totalRows = 0;
auto* dstBytes = static_cast<uint8_t*>(dst);
while (totalRows < countLines) {
const int remainingRows = countLines - totalRows;
int requestedRows = std::min(remainingRows, kMaxScanlineBatchRows);
size_t outputStride = dstRowBytes;
if (requestedRows == remainingRows && dstRowBytes != logicalRowBytes) {
// A caller may allocate no trailing padding after the final row. Keep the last row out
// of a padded batch so the Rust slice never extends beyond the caller's allocation.
if (requestedRows > 1) {
--requestedRows;
} else {
outputStride = logicalRowBytes;
}
}
SkSafeMath batchSafe;
const size_t dstOffset = batchSafe.mul(batchSafe.castTo<size_t>(totalRows), dstRowBytes);
const size_t requestedBytes =
batchSafe.mul(batchSafe.castTo<size_t>(requestedRows), outputStride);
const uint32_t rustOutputStride = batchSafe.castTo<uint32_t>(outputStride);
if (!batchSafe.ok()) {
return {kInternalError, totalRows};
}
rust_jpeg::ScanlineOutputResult result = fReader->read_scanlines(
rust::Slice<uint8_t>(dstBytes + dstOffset, requestedBytes), rustOutputStride);
switch (result.status) {
case rust_jpeg::OutputStatus::Ready:
if (result.rows == 0 || result.rows > SkToU32(requestedRows)) {
return {kErrorInInput, totalRows};
}
totalRows += SkToInt(result.rows);
break;
case rust_jpeg::OutputStatus::NeedMoreInput:
return {kIncompleteInput, totalRows};
case rust_jpeg::OutputStatus::Complete:
return {totalRows == countLines ? kSuccess : kIncompleteInput, totalRows};
case rust_jpeg::OutputStatus::Error:
return {MapDecodingResult(result.result), totalRows};
default:
return {kInternalError, totalRows};
}
}
return {kSuccess, totalRows};
}
int SkJpegRustCodec::onGetScanlines(void* dst, int countLines, size_t dstRowBytes) {
return this->decodeScanlines(dst, countLines, dstRowBytes).fRowsDecoded;
}
bool SkJpegRustCodec::onSkipScanlines(int countLines) {
SkASSERT_RELEASE(countLines >= 0); // Already checked in SkCodec::skipScanlines.
uint32_t skipped = 0;
while (skipped < static_cast<uint32_t>(countLines)) {
rust_jpeg::ScanlineOutputResult result =
fReader->skip_scanlines(static_cast<uint32_t>(countLines) - skipped);
if (result.status != rust_jpeg::OutputStatus::Ready || result.rows == 0) {
return false;
}
if (result.rows > static_cast<uint32_t>(countLines) - skipped) {
return false;
}
skipped += result.rows;
}
return true;
}