blob: 6a13fd642ba5f8946d77390d1d484d6ed0fe7066 [file] [log] [blame]
// Copyright 2025 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/fxcodec/png/skia_png_decoder.h"
#include <utility>
#include "core/fxcodec/cfx_codec_memory.h"
#include "core/fxcodec/png/png_decoder_delegate.h"
#include "core/fxcodec/progressive_decoder_context.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/check_op.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/notreached.h"
#include "core/fxcrt/span.h"
#include "core/fxcrt/unowned_ptr.h"
#include "third_party/skia/include/codec/SkCodec.h"
#include "third_party/skia/include/core/SkStream.h"
#ifdef PDF_ENABLE_RUST_PNG
#include "third_party/skia/include/codec/SkPngRustDecoder.h"
#else
#include "third_party/skia/include/codec/SkPngDecoder.h"
#endif
namespace fxcodec {
namespace {
sk_sp<SkColorSpace> GetTargetColorSpace(double gamma) {
skcms_ICCProfile profile;
skcms_Init(&profile);
skcms_TransferFunction fn;
fn.a = 1.0f;
fn.b = fn.c = fn.d = fn.e = fn.f = 0.0f;
fn.g = 1.0f / gamma;
skcms_SetTransferFunction(&profile, &fn);
skcms_Matrix3x3 to_xyzd50 = skcms_sRGB_profile()->toXYZD50;
skcms_SetXYZD50(&profile, &to_xyzd50);
return SkColorSpace::Make(profile);
}
// Implements/exposes `SkStream` API on top of `CFX_CodecMemory`.
//
// Note that the exposed API does *not* support seeking/rewinding, because this
// layer only has access to the `CFX_CodecMemory` pushed into `ContinueDecode`
// and can't reach for `IFX_SeekableReadStream` from which the bytes have been
// pulled into `CFX_CodecMemory`. This lack of support for seeking/rewinding
// requires avoiding calling any Skia APIs that may necessitate this
// functionality later (e.g. calling `SkCodec::getFrameCount` may attempt to
// read metadata of subsequent frames). This is not an issue today, because
// PDFium only needs to decode the first (or only) image / animation frame.
class CodecMemoryStream final : public SkStream {
public:
explicit CodecMemoryStream(RetainPtr<CFX_CodecMemory> codec_memory)
: codec_memory_(std::move(codec_memory)) {}
size_t read(void* buffer, size_t size) override {
// SAFETY: Relying on the caller to pass correct `buffer` and `size`.
uint8_t* bytes = static_cast<uint8_t*>(buffer);
auto byte_span = UNSAFE_BUFFERS(pdfium::span(bytes, size));
return codec_memory_->ReadBlock(byte_span);
}
bool isAtEnd() const override { return codec_memory_->IsEOF(); }
private:
RetainPtr<CFX_CodecMemory> const codec_memory_;
};
class SkiaPngContext final : public ProgressiveDecoderContext {
public:
// Caller needs to guarantee that `delegate` lives longer than
// `SkiaPngContext`.
explicit SkiaPngContext(PngDecoderDelegate* delegate) : delegate_(delegate) {}
~SkiaPngContext() override = default;
// Starts or resumes decoding `codec_memory`.
//
// Returns `false` upon failure. Returns `true` when either 1) the whole
// image has been successfully decoded or 2) the image has been partially
// decoded but decoding should be continued/retried when more input data
// is available.
//
// Communicates image metadata (once read/available) via
// `PngDecoderDelegate::PngReadHeader`. Writes decoded BGRA pixels to the
// buffer provided via `PngDecoderDelegate::PngAskImageBuf`.
bool ContinueDecode(RetainPtr<CFX_CodecMemory> codec_memory);
private:
enum class State {
// `decoder_` is null.
//
// This is the initial state.
kNoDecoder,
// `decoder_` is not null and `delegate_->PngReadHeader` succeeded
// and provided `target_gamma_`.
//
// `startIncrementalDecode` didn't run, or returned `kIncompleteInput`.
kGotDecoder,
// `decoder_` is not null, got `target_gamma_`, and `startIncrementalDecode`
// has already suceeded.
//
// `incrementalDecode` didn't run yet, or returned `kIncompleteInput`.
kStartedDecode,
// `decoder_` is null. All pixels have been decoded to
// `delegate_->PngAskImageBuf`.
//
// This is a terminal state.
kFinishedDecoding,
// `decoder_` is null. A non-recoverable (i.e. non-`kIncompleteInput`-kind)
// error has been encountered.
//
// This is a terminal state.
kError,
};
State state_ = State::kNoDecoder;
UnownedPtr<PngDecoderDelegate> const delegate_;
std::unique_ptr<SkCodec> decoder_;
double target_gamma_ = 0.0;
// `CFX_CodecMemory` received in `ContinueDecode` may get wrapped in
// `CodecMemoryStream` and become transitively owned by `decoder_`. This
// class retains `codec_memory_` to `CHECK` that all calls to `ContinueDecode`
// use the same `CFX_CodecMemory` - this helps to ensure that `decoder_` and
// `CodecMemoryStream` won't accidentally use stale input data if a future,
// hypothetical refactoring of `ProgressiveDecoder` changes how it manages
// `CFX_CodecMemory`.
RetainPtr<CFX_CodecMemory> codec_memory_;
};
bool SkiaPngContext::ContinueDecode(RetainPtr<CFX_CodecMemory> codec_memory) {
// `ProgressiveDecoder` guarantees that all calls to `ContinueDecode` use the
// same `codec_memory`. Therefore `SkiaPngContext` expects that
// `codec_memory` passed to `CodecMemoryStream` below remains the right one to
// use going forward.
if (!codec_memory_) {
codec_memory_ = codec_memory;
}
CHECK_EQ(&*codec_memory_, &*codec_memory);
switch (state_) {
case State::kNoDecoder: {
CHECK(!decoder_);
auto stream =
std::make_unique<CodecMemoryStream>(std::move(codec_memory));
SkCodec::Result result = SkCodec::kSuccess;
#ifdef PDF_ENABLE_RUST_PNG
decoder_ = SkPngRustDecoder::Decode(std::move(stream), &result);
#else
decoder_ = SkPngDecoder::Decode(std::move(stream), &result);
#endif
switch (result) {
case SkCodec::kSuccess: {
SkImageInfo info = decoder_->getInfo();
if (!delegate_->PngReadHeader(info.width(), info.height(),
&target_gamma_)) {
decoder_.reset();
state_ = State::kError;
return false;
}
state_ = State::kGotDecoder;
break; // continue decoding
}
case SkCodec::kIncompleteInput:
// Rewind to start from the beginning of input when retrying later.
// This will also prompt `ProgressiveDecoder::ReadMoreData` to grow
// the `codec_memory_` as needed.
codec_memory_->Seek(0);
return true; // retry when called later with more data
default:
decoder_.reset();
state_ = State::kError;
return false; // fatal error
}
break;
}
case State::kGotDecoder:
case State::kStartedDecode:
break;
case State::kFinishedDecoding:
case State::kError:
NOTREACHED();
}
if (state_ == State::kGotDecoder) {
SkImageInfo dst_info =
decoder_->getInfo()
.makeColorSpace(GetTargetColorSpace(target_gamma_))
.makeColorType(kBGRA_8888_SkColorType);
pdfium::span<uint8_t> dst_buffer = delegate_->PngAskImageBuf();
FX_SAFE_SIZE_T row_bytes = dst_buffer.size();
row_bytes /= dst_info.height();
SkCodec::Result result = decoder_->startIncrementalDecode(
dst_info, dst_buffer.data(), row_bytes.ValueOrDie());
switch (result) {
case SkCodec::kSuccess:
state_ = State::kStartedDecode;
break; // continue decoding
case SkCodec::kIncompleteInput:
return true; // retry when called later with more data
default:
decoder_.reset();
state_ = State::kError;
return false; // fatal error
}
}
CHECK_EQ(state_, State::kStartedDecode);
SkCodec::Result result = decoder_->incrementalDecode(nullptr);
switch (result) {
case SkCodec::kSuccess:
decoder_.reset();
delegate_->PngFinishedDecoding();
state_ = State::kFinishedDecoding;
return true; // finished decoding
case SkCodec::kIncompleteInput:
return true; // retry when called later with more data
default:
decoder_.reset();
state_ = State::kError;
return false; // fatal error
}
}
} // namespace
// static
std::unique_ptr<ProgressiveDecoderContext> SkiaPngDecoder::StartDecode(
PngDecoderDelegate* delegate) {
return std::make_unique<SkiaPngContext>(delegate);
}
// static
bool SkiaPngDecoder::ContinueDecode(ProgressiveDecoderContext* context,
RetainPtr<CFX_CodecMemory> codec_memory) {
auto* ctx = static_cast<SkiaPngContext*>(context);
return ctx->ContinueDecode(std::move(codec_memory));
}
} // namespace fxcodec