blob: 383456f3dfbbaae184e2a6c988de54f70c014d1b [file] [log] [blame]
// Copyright 2014 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
#include "core/fxcodec/png/libpng_png_decoder.h"
#include <setjmp.h>
#include <string.h>
#include "core/fxcodec/cfx_codec_memory.h"
#include "core/fxcodec/fx_codec.h"
#include "core/fxcodec/fx_codec_def.h"
#include "core/fxcodec/png/png_decoder_delegate.h"
#include "core/fxcodec/progressive_decoder_context.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/unowned_ptr.h"
#ifdef USE_SYSTEM_LIBPNG
#include <png.h>
#else
#include "third_party/libpng/png.h"
#endif
#ifdef PDF_ENABLE_RUST_PNG
#error "If Rust PNG is enabled, then `libpng` should not be used."
#endif
#define PNG_ERROR_SIZE 256
using PngDecoderDelegate = fxcodec::PngDecoderDelegate;
class CPngContext final : public ProgressiveDecoderContext {
public:
explicit CPngContext(PngDecoderDelegate* pDelegate);
~CPngContext() override;
png_structp png_ = nullptr;
png_infop info_ = nullptr;
UnownedPtr<PngDecoderDelegate> const delegate_;
char last_error_[PNG_ERROR_SIZE] = {};
png_uint_32 height_ = 0;
int number_of_passes_ = 0;
};
extern "C" {
void _png_error_data(png_structp png_ptr, png_const_charp error_msg) {
if (png_get_error_ptr(png_ptr)) {
UNSAFE_TODO(strncpy(static_cast<char*>(png_get_error_ptr(png_ptr)),
error_msg, PNG_ERROR_SIZE - 1));
}
longjmp(png_jmpbuf(png_ptr), 1);
}
void _png_warning_data(png_structp png_ptr, png_const_charp error_msg) {}
void _png_get_header_func(png_structp png_ptr, png_infop info_ptr) {
auto* pContext =
reinterpret_cast<CPngContext*>(png_get_progressive_ptr(png_ptr));
if (!pContext) {
return;
}
png_uint_32 width = 0;
png_uint_32 height = 0;
int bits_per_component = 0;
int libpng_color_type = 0;
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bits_per_component,
&libpng_color_type, nullptr, nullptr, nullptr);
if (bits_per_component > 8) {
png_set_strip_16(png_ptr);
} else if (bits_per_component < 8) {
png_set_expand_gray_1_2_4_to_8(png_ptr);
}
if (libpng_color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png_ptr);
}
pContext->number_of_passes_ = png_set_interlace_handling(png_ptr);
pContext->height_ = height;
double gamma = 1.0;
if (!pContext->delegate_->PngReadHeader(width, height, &gamma)) {
// Note that `png_error` function is marked as `PNG_NORETURN`.
png_error(pContext->png_, "Read Header Callback Error");
}
int intent;
if (png_get_sRGB(png_ptr, info_ptr, &intent)) {
png_set_gamma(png_ptr, gamma, 0.45455);
} else {
double image_gamma;
if (png_get_gAMA(png_ptr, info_ptr, &image_gamma)) {
png_set_gamma(png_ptr, gamma, image_gamma);
} else {
png_set_gamma(png_ptr, gamma, 0.45455);
}
}
if (!(libpng_color_type & PNG_COLOR_MASK_COLOR)) {
png_set_gray_to_rgb(png_ptr);
}
png_set_bgr(png_ptr);
if (!(libpng_color_type & PNG_COLOR_MASK_ALPHA)) {
png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
}
png_read_update_info(png_ptr, info_ptr);
}
void _png_get_end_func(png_structp png_ptr, png_infop info_ptr) {}
void _png_get_row_func(png_structp png_ptr,
png_bytep new_row,
png_uint_32 row_num,
int pass) {
auto* pContext =
reinterpret_cast<CPngContext*>(png_get_progressive_ptr(png_ptr));
if (!pContext) {
return;
}
pdfium::span<uint8_t> dst_buf =
pContext->delegate_->PngAskScanlineBuf(row_num);
CHECK(!dst_buf.empty());
png_progressive_combine_row(png_ptr, dst_buf.data(), new_row);
if ((pass == (pContext->number_of_passes_ - 1)) &&
(row_num == (pContext->height_ - 1))) {
pContext->delegate_->PngFinishedDecoding();
}
}
int _png_set_read_and_error_fns(png_structrp png_ptr,
void* user_ctx,
char* error_buf) {
if (setjmp(png_jmpbuf(png_ptr))) {
return 0;
}
png_set_progressive_read_fn(png_ptr, user_ctx, _png_get_header_func,
_png_get_row_func, _png_get_end_func);
png_set_error_fn(png_ptr, error_buf, _png_error_data, _png_warning_data);
return 1;
}
int _png_continue_decode(png_structrp png_ptr,
png_inforp info_ptr,
uint8_t* buffer,
size_t size) {
if (setjmp(png_jmpbuf(png_ptr))) {
return 0;
}
png_process_data(png_ptr, info_ptr, buffer, size);
return 1;
}
} // extern "C"
CPngContext::CPngContext(PngDecoderDelegate* pDelegate)
: delegate_(pDelegate) {}
CPngContext::~CPngContext() {
png_destroy_read_struct(png_ ? &png_ : nullptr, info_ ? &info_ : nullptr,
nullptr);
}
namespace fxcodec {
// static
std::unique_ptr<ProgressiveDecoderContext> LibpngPngDecoder::StartDecode(
PngDecoderDelegate* pDelegate) {
auto p = std::make_unique<CPngContext>(pDelegate);
p->png_ =
png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!p->png_) {
return nullptr;
}
p->info_ = png_create_info_struct(p->png_);
if (!p->info_) {
return nullptr;
}
if (!_png_set_read_and_error_fns(p->png_, p.get(), p->last_error_)) {
return nullptr;
}
return p;
}
// static
bool LibpngPngDecoder::ContinueDecode(ProgressiveDecoderContext* pContext,
RetainPtr<CFX_CodecMemory> codec_memory) {
auto* ctx = static_cast<CPngContext*>(pContext);
pdfium::span<uint8_t> src_buf = codec_memory->GetUnconsumedSpan();
bool result = _png_continue_decode(ctx->png_, ctx->info_, src_buf.data(),
src_buf.size());
// `libpng` always consumes all the data from `src_buf`, so
// advance/seek `codec_memory` to the end of the buffer.
codec_memory->Seek(codec_memory->GetSize());
CHECK(codec_memory->GetUnconsumedSpan().empty());
return result;
}
} // namespace fxcodec