blob: e6884eb7e8fe44435b665eaad479a8ba0a31a661 [file] [log] [blame]
// Copyright 2018 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/bmp/cfx_bmpdecompressor.h"
#include <stdint.h>
#include <algorithm>
#include <limits>
#include <utility>
#include "core/fxcodec/bmp/cfx_bmpcontext.h"
#include "core/fxcodec/cfx_codec_memory.h"
#include "core/fxcrt/byteorder.h"
#include "core/fxcrt/data_vector.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/numerics/safe_math.h"
#include "core/fxcrt/span_util.h"
#include "core/fxge/calculate_pitch.h"
namespace fxcodec {
namespace {
#define BMP_PAL_ENCODE(a, r, g, b) \
(((uint32_t)(a) << 24) | ((r) << 16) | ((g) << 8) | (b))
constexpr size_t kBmpCoreHeaderSize = 12;
constexpr size_t kBmpInfoHeaderSize = 40;
static_assert(sizeof(BmpCoreHeader) == kBmpCoreHeaderSize,
"BmpCoreHeader has wrong size");
static_assert(sizeof(BmpInfoHeader) == kBmpInfoHeaderSize,
"BmpInfoHeader has wrong size");
constexpr uint16_t kBmpSignature = 0x4D42;
constexpr uint8_t kRleMarker = 0;
constexpr uint8_t kRleEol = 0;
constexpr uint8_t kRleEoi = 1;
constexpr uint8_t kRleDelta = 2;
constexpr uint32_t kBmpRgb = 0L;
constexpr uint32_t kBmpRle8 = 1L;
constexpr uint32_t kBmpRle4 = 2L;
constexpr uint32_t kBmpBitfields = 3L;
// Limit of image dimension. Use the same limit as the JBIG2 codecs.
constexpr uint32_t kBmpMaxImageDimension = 65535;
uint8_t HalfRoundUp(uint8_t value) {
uint16_t value16 = value;
return static_cast<uint8_t>((value16 + 1) / 2);
}
} // namespace
CFX_BmpDecompressor::CFX_BmpDecompressor(const CFX_BmpContext* context)
: context_(context) {}
CFX_BmpDecompressor::~CFX_BmpDecompressor() = default;
void CFX_BmpDecompressor::ReadNextScanline() {
uint32_t row = img_tb_flag_ ? row_num_ : (height_ - 1 - row_num_);
context_->m_pDelegate->BmpReadScanline(row, out_row_buffer_);
++row_num_;
}
bool CFX_BmpDecompressor::GetDataPosition(uint32_t rcd_pos) {
return context_->m_pDelegate->BmpInputImagePositionBuf(rcd_pos);
}
BmpDecoder::Status CFX_BmpDecompressor::ReadHeader() {
if (decode_status_ == DecodeStatus::kHeader) {
BmpDecoder::Status status = ReadBmpHeader();
if (status != BmpDecoder::Status::kSuccess)
return status;
}
if (decode_status_ != DecodeStatus::kPal)
return BmpDecoder::Status::kSuccess;
if (compress_flag_ == kBmpBitfields)
return ReadBmpBitfields();
return ReadBmpPalette();
}
BmpDecoder::Status CFX_BmpDecompressor::ReadBmpHeader() {
BmpFileHeader bmp_header;
if (!ReadAllOrNone(
pdfium::as_writable_bytes(pdfium::span_from_ref(bmp_header)))) {
return BmpDecoder::Status::kContinue;
}
bmp_header.bfType = fxcrt::FromLE16(bmp_header.bfType);
data_offset_ = fxcrt::FromLE32(bmp_header.bfOffBits);
data_size_ = fxcrt::FromLE32(bmp_header.bfSize);
if (bmp_header.bfType != kBmpSignature)
return BmpDecoder::Status::kFail;
size_t pos = input_buffer_->GetPosition();
if (!ReadAllOrNone(
pdfium::as_writable_bytes(pdfium::span_from_ref(img_ifh_size_)))) {
return BmpDecoder::Status::kContinue;
}
if (!input_buffer_->Seek(pos))
return BmpDecoder::Status::kFail;
img_ifh_size_ = fxcrt::FromLE32(img_ifh_size_);
pal_type_ = PalType::kNew;
BmpDecoder::Status status = ReadBmpHeaderIfh();
if (status != BmpDecoder::Status::kSuccess)
return status;
return ReadBmpHeaderDimensions();
}
BmpDecoder::Status CFX_BmpDecompressor::ReadBmpHeaderIfh() {
if (img_ifh_size_ == kBmpCoreHeaderSize) {
pal_type_ = PalType::kOld;
BmpCoreHeader bmp_core_header;
if (!ReadAllOrNone(pdfium::as_writable_bytes(
pdfium::span_from_ref(bmp_core_header)))) {
return BmpDecoder::Status::kContinue;
}
width_ = fxcrt::FromLE16(bmp_core_header.bcWidth);
height_ = fxcrt::FromLE16(bmp_core_header.bcHeight);
bit_counts_ = fxcrt::FromLE16(bmp_core_header.bcBitCount);
compress_flag_ = kBmpRgb;
img_tb_flag_ = false;
return BmpDecoder::Status::kSuccess;
}
if (img_ifh_size_ == kBmpInfoHeaderSize) {
BmpInfoHeader bmp_info_header;
if (!ReadAllOrNone(pdfium::as_writable_bytes(
pdfium::span_from_ref(bmp_info_header)))) {
return BmpDecoder::Status::kContinue;
}
width_ = fxcrt::FromLE32(bmp_info_header.biWidth);
bit_counts_ = fxcrt::FromLE16(bmp_info_header.biBitCount);
compress_flag_ = fxcrt::FromLE32(bmp_info_header.biCompression);
color_used_ = fxcrt::FromLE32(bmp_info_header.biClrUsed);
dpi_x_ =
static_cast<int32_t>(fxcrt::FromLE32(bmp_info_header.biXPelsPerMeter));
dpi_y_ =
static_cast<int32_t>(fxcrt::FromLE32(bmp_info_header.biYPelsPerMeter));
int32_t signed_height = fxcrt::FromLE32(bmp_info_header.biHeight);
if (!SetHeight(signed_height)) {
return BmpDecoder::Status::kFail;
}
return BmpDecoder::Status::kSuccess;
}
if (img_ifh_size_ <= sizeof(BmpInfoHeader))
return BmpDecoder::Status::kFail;
FX_SAFE_SIZE_T new_pos = input_buffer_->GetPosition();
BmpInfoHeader bmp_info_header;
if (!ReadAllOrNone(
pdfium::as_writable_bytes(pdfium::span_from_ref(bmp_info_header)))) {
return BmpDecoder::Status::kContinue;
}
new_pos += img_ifh_size_;
if (!new_pos.IsValid())
return BmpDecoder::Status::kFail;
if (!input_buffer_->Seek(new_pos.ValueOrDie()))
return BmpDecoder::Status::kContinue;
width_ = fxcrt::FromLE32(bmp_info_header.biWidth);
bit_counts_ = fxcrt::FromLE16(bmp_info_header.biBitCount);
compress_flag_ = fxcrt::FromLE32(bmp_info_header.biCompression);
color_used_ = fxcrt::FromLE32(bmp_info_header.biClrUsed);
dpi_x_ = fxcrt::FromLE32(bmp_info_header.biXPelsPerMeter);
dpi_y_ = fxcrt::FromLE32(bmp_info_header.biYPelsPerMeter);
int32_t signed_height = fxcrt::FromLE32(bmp_info_header.biHeight);
if (!SetHeight(signed_height)) {
return BmpDecoder::Status::kFail;
}
uint16_t bi_planes = fxcrt::FromLE16(bmp_info_header.biPlanes);
if (compress_flag_ != kBmpRgb || bi_planes != 1 || color_used_ != 0) {
return BmpDecoder::Status::kFail;
}
return BmpDecoder::Status::kSuccess;
}
BmpDecoder::Status CFX_BmpDecompressor::ReadBmpHeaderDimensions() {
if (width_ > kBmpMaxImageDimension || height_ > kBmpMaxImageDimension ||
compress_flag_ > kBmpBitfields) {
return BmpDecoder::Status::kFail;
}
switch (bit_counts_) {
case 1:
case 4:
case 8:
case 16:
case 24: {
if (color_used_ > 1U << bit_counts_)
return BmpDecoder::Status::kFail;
break;
}
case 32:
break;
default:
return BmpDecoder::Status::kFail;
}
std::optional<uint32_t> stride = fxge::CalculatePitch32(bit_counts_, width_);
if (!stride.has_value())
return BmpDecoder::Status::kFail;
src_row_bytes_ = stride.value();
switch (bit_counts_) {
case 1:
case 4:
case 8:
stride = fxge::CalculatePitch32(8, width_);
if (!stride.has_value())
return BmpDecoder::Status::kFail;
out_row_bytes_ = stride.value();
components_ = 1;
break;
case 16:
case 24:
stride = fxge::CalculatePitch32(24, width_);
if (!stride.has_value())
return BmpDecoder::Status::kFail;
out_row_bytes_ = stride.value();
components_ = 3;
break;
case 32:
out_row_bytes_ = src_row_bytes_;
components_ = 4;
break;
}
out_row_buffer_.clear();
if (out_row_bytes_ <= 0)
return BmpDecoder::Status::kFail;
out_row_buffer_.resize(out_row_bytes_);
SaveDecodingStatus(DecodeStatus::kPal);
return BmpDecoder::Status::kSuccess;
}
BmpDecoder::Status CFX_BmpDecompressor::ReadBmpBitfields() {
if (bit_counts_ != 16 && bit_counts_ != 32)
return BmpDecoder::Status::kFail;
uint32_t masks[3];
if (!ReadAllOrNone(pdfium::as_writable_bytes(pdfium::make_span(masks))))
return BmpDecoder::Status::kContinue;
mask_red_ = fxcrt::FromLE32(masks[0]);
mask_green_ = fxcrt::FromLE32(masks[1]);
mask_blue_ = fxcrt::FromLE32(masks[2]);
if (mask_red_ & mask_green_ || mask_red_ & mask_blue_ ||
mask_green_ & mask_blue_) {
return BmpDecoder::Status::kFail;
}
header_offset_ = std::max(header_offset_, 26 + img_ifh_size_);
SaveDecodingStatus(DecodeStatus::kDataPre);
return BmpDecoder::Status::kSuccess;
}
BmpDecoder::Status CFX_BmpDecompressor::ReadBmpPalette() {
if (bit_counts_ == 16) {
mask_red_ = 0x7C00;
mask_green_ = 0x03E0;
mask_blue_ = 0x001F;
}
pal_num_ = 0;
if (bit_counts_ < 16) {
pal_num_ = 1 << bit_counts_;
if (color_used_ != 0)
pal_num_ = color_used_;
size_t src_pal_size = pal_num_ * PaletteChannelCount();
DataVector<uint8_t> src_pal(src_pal_size);
uint8_t* src_pal_data = src_pal.data();
if (!ReadAllOrNone(src_pal))
return BmpDecoder::Status::kContinue;
palette_.resize(pal_num_);
int32_t src_pal_index = 0;
if (pal_type_ == PalType::kOld) {
while (src_pal_index < pal_num_) {
palette_[src_pal_index++] = BMP_PAL_ENCODE(
0x00, src_pal_data[2], src_pal_data[1], src_pal_data[0]);
src_pal_data += 3;
}
} else {
while (src_pal_index < pal_num_) {
palette_[src_pal_index++] = BMP_PAL_ENCODE(
src_pal_data[3], src_pal_data[2], src_pal_data[1], src_pal_data[0]);
src_pal_data += 4;
}
}
}
header_offset_ = std::max(
header_offset_, 14 + img_ifh_size_ + pal_num_ * PaletteChannelCount());
SaveDecodingStatus(DecodeStatus::kDataPre);
return BmpDecoder::Status::kSuccess;
}
bool CFX_BmpDecompressor::ValidateFlag() const {
switch (compress_flag_) {
case kBmpRgb:
case kBmpBitfields:
case kBmpRle8:
case kBmpRle4:
return true;
default:
return false;
}
}
BmpDecoder::Status CFX_BmpDecompressor::DecodeImage() {
if (decode_status_ == DecodeStatus::kDataPre) {
// In order to tolerate certain corrupt BMP files, use the header offset if
// the data offset would point into the header.
data_offset_ = std::max(header_offset_, data_offset_);
input_buffer_->Seek(input_buffer_->GetSize());
if (!GetDataPosition(data_offset_)) {
decode_status_ = DecodeStatus::kTail;
return BmpDecoder::Status::kFail;
}
row_num_ = 0;
SaveDecodingStatus(DecodeStatus::kData);
}
if (decode_status_ != DecodeStatus::kData || !ValidateFlag())
return BmpDecoder::Status::kFail;
switch (compress_flag_) {
case kBmpRgb:
case kBmpBitfields:
return DecodeRGB();
case kBmpRle8:
return DecodeRLE8();
case kBmpRle4:
return DecodeRLE4();
default:
return BmpDecoder::Status::kFail;
}
}
bool CFX_BmpDecompressor::ValidateColorIndex(uint8_t val) const {
return val < pal_num_;
}
BmpDecoder::Status CFX_BmpDecompressor::DecodeRGB() {
DataVector<uint8_t> dest_buf(src_row_bytes_);
while (row_num_ < height_) {
size_t idx = 0;
if (!ReadAllOrNone(dest_buf))
return BmpDecoder::Status::kContinue;
SaveDecodingStatus(DecodeStatus::kData);
switch (bit_counts_) {
case 1: {
for (uint32_t col = 0; col < width_; ++col) {
uint8_t index =
dest_buf[col >> 3] & (0x80 >> (col % 8)) ? 0x01 : 0x00;
if (!ValidateColorIndex(index))
return BmpDecoder::Status::kFail;
out_row_buffer_[idx++] = index;
}
break;
}
case 4: {
for (uint32_t col = 0; col < width_; ++col) {
uint8_t index = (col & 0x01) ? (dest_buf[col >> 1] & 0x0F)
: ((dest_buf[col >> 1] & 0xF0) >> 4);
if (!ValidateColorIndex(index))
return BmpDecoder::Status::kFail;
out_row_buffer_[idx++] = index;
}
break;
}
case 8: {
for (uint32_t col = 0; col < width_; ++col) {
uint8_t index = dest_buf[col];
if (!ValidateColorIndex(index))
return BmpDecoder::Status::kFail;
out_row_buffer_[idx++] = index;
}
break;
}
case 16: {
auto buf =
fxcrt::reinterpret_span<uint16_t>(pdfium::make_span(dest_buf));
uint8_t blue_bits = 0;
uint8_t green_bits = 0;
uint8_t red_bits = 0;
for (int32_t i = 0; i < 16; i++) {
if ((mask_blue_ >> i) & 0x01)
blue_bits++;
if ((mask_green_ >> i) & 0x01)
green_bits++;
if ((mask_red_ >> i) & 0x01)
red_bits++;
}
green_bits += blue_bits;
red_bits += green_bits;
if (blue_bits > 8 || green_bits < 8 || red_bits < 8)
return BmpDecoder::Status::kContinue;
blue_bits = 8 - blue_bits;
green_bits -= 8;
red_bits -= 8;
for (uint32_t col = 0; col < width_; ++col) {
buf.front() = fxcrt::FromLE16(buf.front());
out_row_buffer_[idx++] =
static_cast<uint8_t>((buf.front() & mask_blue_) << blue_bits);
out_row_buffer_[idx++] =
static_cast<uint8_t>((buf.front() & mask_green_) >> green_bits);
out_row_buffer_[idx++] =
static_cast<uint8_t>((buf.front() & mask_red_) >> red_bits);
buf = buf.subspan(1);
}
break;
}
case 24:
case 32:
// TODO(crbug.com/pdfium/1901): Apply bitfields.
fxcrt::spancpy(pdfium::make_span(out_row_buffer_),
pdfium::make_span(dest_buf).first(src_row_bytes_));
idx += src_row_bytes_;
break;
}
ReadNextScanline();
}
SaveDecodingStatus(DecodeStatus::kTail);
return BmpDecoder::Status::kSuccess;
}
BmpDecoder::Status CFX_BmpDecompressor::DecodeRLE8() {
uint8_t first_part;
col_num_ = 0;
while (true) {
if (!ReadAllOrNone(pdfium::span_from_ref(first_part))) {
return BmpDecoder::Status::kContinue;
}
switch (first_part) {
case kRleMarker: {
if (!ReadAllOrNone(pdfium::span_from_ref(first_part))) {
return BmpDecoder::Status::kContinue;
}
switch (first_part) {
case kRleEol: {
if (row_num_ >= height_) {
SaveDecodingStatus(DecodeStatus::kTail);
return BmpDecoder::Status::kFail;
}
ReadNextScanline();
col_num_ = 0;
fxcrt::spanset(pdfium::make_span(out_row_buffer_), 0);
SaveDecodingStatus(DecodeStatus::kData);
continue;
}
case kRleEoi: {
if (row_num_ < height_)
ReadNextScanline();
SaveDecodingStatus(DecodeStatus::kTail);
return BmpDecoder::Status::kSuccess;
}
case kRleDelta: {
uint8_t delta[2];
if (!ReadAllOrNone(delta))
return BmpDecoder::Status::kContinue;
col_num_ += delta[0];
size_t bmp_row_num__next = row_num_ + delta[1];
if (col_num_ >= out_row_bytes_ || bmp_row_num__next >= height_)
return BmpDecoder::Status::kFail;
while (row_num_ < bmp_row_num__next) {
fxcrt::spanset(pdfium::make_span(out_row_buffer_), 0);
ReadNextScanline();
}
break;
}
default: {
int32_t avail_size =
pdfium::checked_cast<int32_t>(out_row_bytes_ - col_num_);
if (!avail_size || static_cast<int32_t>(first_part) > avail_size)
return BmpDecoder::Status::kFail;
size_t second_part_size =
first_part & 1 ? first_part + 1 : first_part;
DataVector<uint8_t> second_part(second_part_size);
if (!ReadAllOrNone(second_part))
return BmpDecoder::Status::kContinue;
fxcrt::spancpy(pdfium::make_span(out_row_buffer_).subspan(col_num_),
pdfium::make_span(second_part).first(first_part));
for (size_t i = col_num_; i < col_num_ + first_part; ++i) {
if (!ValidateColorIndex(out_row_buffer_[i]))
return BmpDecoder::Status::kFail;
}
col_num_ += first_part;
}
}
break;
}
default: {
int32_t avail_size =
pdfium::checked_cast<int32_t>(out_row_bytes_ - col_num_);
if (!avail_size || static_cast<int32_t>(first_part) > avail_size)
return BmpDecoder::Status::kFail;
uint8_t second_part;
if (!ReadAllOrNone(pdfium::span_from_ref(second_part))) {
return BmpDecoder::Status::kContinue;
}
fxcrt::spanset(
pdfium::make_span(out_row_buffer_).subspan(col_num_, first_part),
second_part);
if (!ValidateColorIndex(out_row_buffer_[col_num_]))
return BmpDecoder::Status::kFail;
col_num_ += first_part;
}
}
}
}
BmpDecoder::Status CFX_BmpDecompressor::DecodeRLE4() {
uint8_t first_part;
col_num_ = 0;
while (true) {
if (!ReadAllOrNone(pdfium::span_from_ref(first_part))) {
return BmpDecoder::Status::kContinue;
}
switch (first_part) {
case kRleMarker: {
if (!ReadAllOrNone(pdfium::span_from_ref(first_part))) {
return BmpDecoder::Status::kContinue;
}
switch (first_part) {
case kRleEol: {
if (row_num_ >= height_) {
SaveDecodingStatus(DecodeStatus::kTail);
return BmpDecoder::Status::kFail;
}
ReadNextScanline();
col_num_ = 0;
fxcrt::spanset(pdfium::make_span(out_row_buffer_), 0);
SaveDecodingStatus(DecodeStatus::kData);
continue;
}
case kRleEoi: {
if (row_num_ < height_)
ReadNextScanline();
SaveDecodingStatus(DecodeStatus::kTail);
return BmpDecoder::Status::kSuccess;
}
case kRleDelta: {
uint8_t delta[2];
if (!ReadAllOrNone(delta))
return BmpDecoder::Status::kContinue;
col_num_ += delta[0];
size_t bmp_row_num__next = row_num_ + delta[1];
if (col_num_ >= out_row_bytes_ || bmp_row_num__next >= height_)
return BmpDecoder::Status::kFail;
while (row_num_ < bmp_row_num__next) {
fxcrt::spanset(pdfium::make_span(out_row_buffer_), 0);
ReadNextScanline();
}
break;
}
default: {
int32_t avail_size =
pdfium::checked_cast<int32_t>(out_row_bytes_ - col_num_);
if (!avail_size)
return BmpDecoder::Status::kFail;
uint8_t size = HalfRoundUp(first_part);
if (static_cast<int32_t>(first_part) > avail_size) {
if (size + (col_num_ >> 1) > src_row_bytes_)
return BmpDecoder::Status::kFail;
first_part = avail_size - 1;
}
size_t second_part_size = size & 1 ? size + 1 : size;
DataVector<uint8_t> second_part(second_part_size);
uint8_t* second_part_data = second_part.data();
if (!ReadAllOrNone(second_part))
return BmpDecoder::Status::kContinue;
for (uint8_t i = 0; i < first_part; i++) {
uint8_t color = (i & 0x01) ? (*second_part_data++ & 0x0F)
: (*second_part_data & 0xF0) >> 4;
if (!ValidateColorIndex(color))
return BmpDecoder::Status::kFail;
out_row_buffer_[col_num_++] = color;
}
}
}
break;
}
default: {
int32_t avail_size =
pdfium::checked_cast<int32_t>(out_row_bytes_ - col_num_);
if (!avail_size)
return BmpDecoder::Status::kFail;
if (static_cast<int32_t>(first_part) > avail_size) {
uint8_t size = HalfRoundUp(first_part);
if (size + (col_num_ >> 1) > src_row_bytes_)
return BmpDecoder::Status::kFail;
first_part = avail_size - 1;
}
uint8_t second_part;
if (!ReadAllOrNone(pdfium::span_from_ref(second_part))) {
return BmpDecoder::Status::kContinue;
}
for (uint8_t i = 0; i < first_part; i++) {
uint8_t second_byte = second_part;
second_byte =
i & 0x01 ? (second_byte & 0x0F) : (second_byte & 0xF0) >> 4;
if (!ValidateColorIndex(second_byte))
return BmpDecoder::Status::kFail;
out_row_buffer_[col_num_++] = second_byte;
}
}
}
}
}
bool CFX_BmpDecompressor::ReadAllOrNone(pdfium::span<uint8_t> buf) {
if (!input_buffer_)
return false;
size_t original_position = input_buffer_->GetPosition();
size_t read = input_buffer_->ReadBlock(buf);
if (read < buf.size()) {
input_buffer_->Seek(original_position);
return false;
}
return true;
}
void CFX_BmpDecompressor::SaveDecodingStatus(DecodeStatus status) {
decode_status_ = status;
}
void CFX_BmpDecompressor::SetInputBuffer(
RetainPtr<CFX_CodecMemory> codec_memory) {
input_buffer_ = std::move(codec_memory);
}
FX_FILESIZE CFX_BmpDecompressor::GetAvailInput() const {
if (!input_buffer_)
return 0;
return input_buffer_->GetSize() - input_buffer_->GetPosition();
}
bool CFX_BmpDecompressor::SetHeight(int32_t signed_height) {
if (signed_height >= 0) {
height_ = signed_height;
return true;
}
if (signed_height != std::numeric_limits<int>::min()) {
height_ = -signed_height;
img_tb_flag_ = true;
return true;
}
return false;
}
} // namespace fxcodec