| // Copyright 2016 PDFium Authors. All rights reserved. |
| // 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/fpdfapi/page/cpdf_image.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "constants/stream_dict_common.h" |
| #include "core/fpdfapi/page/cpdf_dib.h" |
| #include "core/fpdfapi/page/cpdf_page.h" |
| #include "core/fpdfapi/parser/cpdf_array.h" |
| #include "core/fpdfapi/parser/cpdf_boolean.h" |
| #include "core/fpdfapi/parser/cpdf_dictionary.h" |
| #include "core/fpdfapi/parser/cpdf_document.h" |
| #include "core/fpdfapi/parser/cpdf_name.h" |
| #include "core/fpdfapi/parser/cpdf_number.h" |
| #include "core/fpdfapi/parser/cpdf_reference.h" |
| #include "core/fpdfapi/parser/cpdf_stream.h" |
| #include "core/fpdfapi/parser/cpdf_string.h" |
| #include "core/fxcodec/jpeg/jpegmodule.h" |
| #include "core/fxcrt/fx_memory_wrappers.h" |
| #include "core/fxcrt/fx_stream.h" |
| #include "core/fxge/dib/cfx_dibitmap.h" |
| #include "core/fxge/dib/fx_dib.h" |
| #include "third_party/base/check.h" |
| #include "third_party/base/numerics/safe_conversions.h" |
| |
| // static |
| bool CPDF_Image::IsValidJpegComponent(int32_t comps) { |
| return comps == 1 || comps == 3 || comps == 4; |
| } |
| |
| // static |
| bool CPDF_Image::IsValidJpegBitsPerComponent(int32_t bpc) { |
| return bpc == 1 || bpc == 2 || bpc == 4 || bpc == 8 || bpc == 16; |
| } |
| |
| CPDF_Image::CPDF_Image(CPDF_Document* pDoc) : m_pDocument(pDoc) { |
| DCHECK(m_pDocument); |
| } |
| |
| CPDF_Image::CPDF_Image(CPDF_Document* pDoc, RetainPtr<CPDF_Stream> pStream) |
| : m_bIsInline(true), m_pDocument(pDoc), m_pStream(std::move(pStream)) { |
| DCHECK(m_pDocument); |
| FinishInitialization(m_pStream->GetDict()); |
| } |
| |
| CPDF_Image::CPDF_Image(CPDF_Document* pDoc, uint32_t dwStreamObjNum) |
| : m_pDocument(pDoc), |
| m_pStream(ToStream(pDoc->GetIndirectObject(dwStreamObjNum))) { |
| DCHECK(m_pDocument); |
| FinishInitialization(m_pStream->GetDict()); |
| } |
| |
| CPDF_Image::~CPDF_Image() = default; |
| |
| void CPDF_Image::FinishInitialization(CPDF_Dictionary* pStreamDict) { |
| m_pOC.Reset(pStreamDict->GetDictFor("OC")); |
| m_bIsMask = !pStreamDict->KeyExist("ColorSpace") || |
| pStreamDict->GetIntegerFor("ImageMask"); |
| m_bInterpolate = !!pStreamDict->GetIntegerFor("Interpolate"); |
| m_Height = pStreamDict->GetIntegerFor("Height"); |
| m_Width = pStreamDict->GetIntegerFor("Width"); |
| } |
| |
| void CPDF_Image::ConvertStreamToIndirectObject() { |
| if (!m_pStream->IsInline()) |
| return; |
| |
| m_pDocument->AddIndirectObject(m_pStream); |
| } |
| |
| CPDF_Dictionary* CPDF_Image::GetDict() const { |
| return m_pStream ? m_pStream->GetDict() : nullptr; |
| } |
| |
| RetainPtr<CPDF_Dictionary> CPDF_Image::InitJPEG( |
| pdfium::span<uint8_t> src_span) { |
| Optional<JpegModule::JpegImageInfo> info_opt = JpegModule::LoadInfo(src_span); |
| if (!info_opt.has_value()) |
| return nullptr; |
| |
| const JpegModule::JpegImageInfo& info = info_opt.value(); |
| if (!IsValidJpegComponent(info.num_components) || |
| !IsValidJpegBitsPerComponent(info.bits_per_components)) { |
| return nullptr; |
| } |
| |
| RetainPtr<CPDF_Dictionary> pDict = |
| CreateXObjectImageDict(info.width, info.height); |
| const char* csname = nullptr; |
| if (info.num_components == 1) { |
| csname = "DeviceGray"; |
| } else if (info.num_components == 3) { |
| csname = "DeviceRGB"; |
| } else if (info.num_components == 4) { |
| csname = "DeviceCMYK"; |
| CPDF_Array* pDecode = pDict->SetNewFor<CPDF_Array>("Decode"); |
| for (int n = 0; n < 4; n++) { |
| pDecode->AppendNew<CPDF_Number>(1); |
| pDecode->AppendNew<CPDF_Number>(0); |
| } |
| } |
| pDict->SetNewFor<CPDF_Name>("ColorSpace", csname); |
| pDict->SetNewFor<CPDF_Number>("BitsPerComponent", info.bits_per_components); |
| pDict->SetNewFor<CPDF_Name>("Filter", "DCTDecode"); |
| if (!info.color_transform) { |
| CPDF_Dictionary* pParms = |
| pDict->SetNewFor<CPDF_Dictionary>(pdfium::stream::kDecodeParms); |
| pParms->SetNewFor<CPDF_Number>("ColorTransform", 0); |
| } |
| m_bIsMask = false; |
| m_Width = info.width; |
| m_Height = info.height; |
| if (!m_pStream) |
| m_pStream = pdfium::MakeRetain<CPDF_Stream>(); |
| return pDict; |
| } |
| |
| void CPDF_Image::SetJpegImage(const RetainPtr<IFX_SeekableReadStream>& pFile) { |
| uint32_t size = pdfium::base::checked_cast<uint32_t>(pFile->GetSize()); |
| if (!size) |
| return; |
| |
| uint32_t dwEstimateSize = std::min(size, 8192U); |
| std::vector<uint8_t, FxAllocAllocator<uint8_t>> data(dwEstimateSize); |
| if (!pFile->ReadBlockAtOffset(data.data(), 0, dwEstimateSize)) |
| return; |
| |
| RetainPtr<CPDF_Dictionary> pDict = InitJPEG(data); |
| if (!pDict && size > dwEstimateSize) { |
| data.resize(size); |
| if (pFile->ReadBlockAtOffset(data.data(), 0, size)) |
| pDict = InitJPEG(data); |
| } |
| if (!pDict) |
| return; |
| |
| m_pStream->InitStreamFromFile(pFile, std::move(pDict)); |
| } |
| |
| void CPDF_Image::SetJpegImageInline( |
| const RetainPtr<IFX_SeekableReadStream>& pFile) { |
| uint32_t size = pdfium::base::checked_cast<uint32_t>(pFile->GetSize()); |
| if (!size) |
| return; |
| |
| std::vector<uint8_t, FxAllocAllocator<uint8_t>> data(size); |
| if (!pFile->ReadBlockAtOffset(data.data(), 0, size)) |
| return; |
| |
| RetainPtr<CPDF_Dictionary> pDict = InitJPEG(data); |
| if (!pDict) |
| return; |
| |
| m_pStream->InitStream(data, std::move(pDict)); |
| } |
| |
| void CPDF_Image::SetImage(const RetainPtr<CFX_DIBitmap>& pBitmap) { |
| int32_t BitmapWidth = pBitmap->GetWidth(); |
| int32_t BitmapHeight = pBitmap->GetHeight(); |
| if (BitmapWidth < 1 || BitmapHeight < 1) |
| return; |
| |
| RetainPtr<CPDF_Dictionary> pDict = |
| CreateXObjectImageDict(BitmapWidth, BitmapHeight); |
| const int32_t bpp = pBitmap->GetBPP(); |
| size_t dest_pitch = 0; |
| bool bCopyWithoutAlpha = true; |
| if (bpp == 1) { |
| int32_t reset_a = 0; |
| int32_t reset_r = 0; |
| int32_t reset_g = 0; |
| int32_t reset_b = 0; |
| int32_t set_a = 0; |
| int32_t set_r = 0; |
| int32_t set_g = 0; |
| int32_t set_b = 0; |
| if (!pBitmap->IsMaskFormat()) { |
| std::tie(reset_a, reset_r, reset_g, reset_b) = |
| ArgbDecode(pBitmap->GetPaletteArgb(0)); |
| std::tie(set_a, set_r, set_g, set_b) = |
| ArgbDecode(pBitmap->GetPaletteArgb(1)); |
| } |
| if (set_a == 0 || reset_a == 0) { |
| pDict->SetNewFor<CPDF_Boolean>("ImageMask", true); |
| if (reset_a == 0) { |
| CPDF_Array* pArray = pDict->SetNewFor<CPDF_Array>("Decode"); |
| pArray->AppendNew<CPDF_Number>(1); |
| pArray->AppendNew<CPDF_Number>(0); |
| } |
| } else { |
| CPDF_Array* pCS = pDict->SetNewFor<CPDF_Array>("ColorSpace"); |
| pCS->AppendNew<CPDF_Name>("Indexed"); |
| pCS->AppendNew<CPDF_Name>("DeviceRGB"); |
| pCS->AppendNew<CPDF_Number>(1); |
| ByteString ct; |
| { |
| // Span's lifetime must end before ReleaseBuffer() below. |
| pdfium::span<char> pBuf = ct.GetBuffer(6); |
| pBuf[0] = static_cast<char>(reset_r); |
| pBuf[1] = static_cast<char>(reset_g); |
| pBuf[2] = static_cast<char>(reset_b); |
| pBuf[3] = static_cast<char>(set_r); |
| pBuf[4] = static_cast<char>(set_g); |
| pBuf[5] = static_cast<char>(set_b); |
| } |
| ct.ReleaseBuffer(6); |
| pCS->AppendNew<CPDF_String>(ct, true); |
| } |
| pDict->SetNewFor<CPDF_Number>("BitsPerComponent", 1); |
| dest_pitch = (BitmapWidth + 7) / 8; |
| } else if (bpp == 8) { |
| size_t palette_size = pBitmap->GetRequiredPaletteSize(); |
| if (palette_size > 0) { |
| DCHECK(palette_size <= 256); |
| CPDF_Array* pCS = m_pDocument->NewIndirect<CPDF_Array>(); |
| pCS->AppendNew<CPDF_Name>("Indexed"); |
| pCS->AppendNew<CPDF_Name>("DeviceRGB"); |
| pCS->AppendNew<CPDF_Number>(static_cast<int>(palette_size - 1)); |
| std::unique_ptr<uint8_t, FxFreeDeleter> pColorTable( |
| FX_Alloc2D(uint8_t, palette_size, 3)); |
| uint8_t* ptr = pColorTable.get(); |
| for (size_t i = 0; i < palette_size; i++) { |
| uint32_t argb = pBitmap->GetPaletteArgb(i); |
| ptr[0] = FXARGB_R(argb); |
| ptr[1] = FXARGB_G(argb); |
| ptr[2] = FXARGB_B(argb); |
| ptr += 3; |
| } |
| auto pNewDict = m_pDocument->New<CPDF_Dictionary>(); |
| CPDF_Stream* pCTS = m_pDocument->NewIndirect<CPDF_Stream>( |
| std::move(pColorTable), palette_size * 3, std::move(pNewDict)); |
| pCS->AppendNew<CPDF_Reference>(m_pDocument.Get(), pCTS->GetObjNum()); |
| pDict->SetNewFor<CPDF_Reference>("ColorSpace", m_pDocument.Get(), |
| pCS->GetObjNum()); |
| } else { |
| pDict->SetNewFor<CPDF_Name>("ColorSpace", "DeviceGray"); |
| } |
| pDict->SetNewFor<CPDF_Number>("BitsPerComponent", 8); |
| dest_pitch = BitmapWidth; |
| } else { |
| pDict->SetNewFor<CPDF_Name>("ColorSpace", "DeviceRGB"); |
| pDict->SetNewFor<CPDF_Number>("BitsPerComponent", 8); |
| dest_pitch = BitmapWidth * 3; |
| bCopyWithoutAlpha = false; |
| } |
| |
| RetainPtr<CFX_DIBitmap> pMaskBitmap; |
| if (pBitmap->IsAlphaFormat()) |
| pMaskBitmap = pBitmap->CloneAlphaMask(); |
| |
| if (pMaskBitmap) { |
| int32_t maskWidth = pMaskBitmap->GetWidth(); |
| int32_t maskHeight = pMaskBitmap->GetHeight(); |
| std::unique_ptr<uint8_t, FxFreeDeleter> mask_buf; |
| int32_t mask_size = 0; |
| RetainPtr<CPDF_Dictionary> pMaskDict = |
| CreateXObjectImageDict(maskWidth, maskHeight); |
| pMaskDict->SetNewFor<CPDF_Name>("ColorSpace", "DeviceGray"); |
| pMaskDict->SetNewFor<CPDF_Number>("BitsPerComponent", 8); |
| if (pMaskBitmap->GetFormat() != FXDIB_Format::k1bppMask) { |
| mask_buf.reset(FX_AllocUninit2D(uint8_t, maskHeight, maskWidth)); |
| mask_size = maskHeight * maskWidth; // Safe since checked alloc returned. |
| for (int32_t a = 0; a < maskHeight; a++) { |
| memcpy(mask_buf.get() + a * maskWidth, pMaskBitmap->GetScanline(a), |
| maskWidth); |
| } |
| } |
| pMaskDict->SetNewFor<CPDF_Number>("Length", mask_size); |
| CPDF_Stream* pNewStream = m_pDocument->NewIndirect<CPDF_Stream>( |
| std::move(mask_buf), mask_size, std::move(pMaskDict)); |
| pDict->SetNewFor<CPDF_Reference>("SMask", m_pDocument.Get(), |
| pNewStream->GetObjNum()); |
| } |
| |
| uint8_t* src_buf = pBitmap->GetBuffer(); |
| int32_t src_pitch = pBitmap->GetPitch(); |
| std::unique_ptr<uint8_t, FxFreeDeleter> dest_buf( |
| FX_Alloc2D(uint8_t, dest_pitch, BitmapHeight)); |
| // Safe as checked alloc returned. |
| size_t dest_size = dest_pitch * BitmapHeight; |
| auto dest_span = pdfium::make_span(dest_buf.get(), dest_size); |
| size_t dest_span_offset = 0; |
| if (bCopyWithoutAlpha) { |
| for (int32_t i = 0; i < BitmapHeight; i++) { |
| memcpy(&dest_span[dest_span_offset], src_buf, dest_pitch); |
| dest_span_offset += dest_pitch; |
| src_buf += src_pitch; |
| } |
| } else { |
| int32_t src_offset = 0; |
| for (int32_t row = 0; row < BitmapHeight; row++) { |
| size_t dest_span_row_offset = dest_span_offset; |
| src_offset = row * src_pitch; |
| for (int32_t column = 0; column < BitmapWidth; column++) { |
| float alpha = 1; |
| dest_span[dest_span_row_offset] = |
| static_cast<uint8_t>(src_buf[src_offset + 2] * alpha); |
| dest_span[dest_span_row_offset + 1] = |
| static_cast<uint8_t>(src_buf[src_offset + 1] * alpha); |
| dest_span[dest_span_row_offset + 2] = |
| static_cast<uint8_t>(src_buf[src_offset] * alpha); |
| dest_span_row_offset += 3; |
| src_offset += bpp == 24 ? 3 : 4; |
| } |
| |
| dest_span_offset += dest_pitch; |
| } |
| } |
| if (!m_pStream) |
| m_pStream = pdfium::MakeRetain<CPDF_Stream>(); |
| |
| m_pStream->InitStream(dest_span, std::move(pDict)); |
| m_bIsMask = pBitmap->IsMaskFormat(); |
| m_Width = BitmapWidth; |
| m_Height = BitmapHeight; |
| } |
| |
| void CPDF_Image::ResetCache(CPDF_Page* pPage) { |
| RetainPtr<CPDF_Image> pHolder(this); |
| pPage->GetRenderCache()->ResetBitmapForImage(pHolder); |
| } |
| |
| RetainPtr<CFX_DIBBase> CPDF_Image::LoadDIBBase() const { |
| auto source = pdfium::MakeRetain<CPDF_DIB>(); |
| if (!source->Load(m_pDocument.Get(), m_pStream.Get())) |
| return nullptr; |
| |
| if (!source->IsJBigImage()) |
| return source; |
| |
| CPDF_DIB::LoadState ret = CPDF_DIB::LoadState::kContinue; |
| while (ret == CPDF_DIB::LoadState::kContinue) |
| ret = source->ContinueLoadDIBBase(nullptr); |
| return ret == CPDF_DIB::LoadState::kSuccess ? source : nullptr; |
| } |
| |
| RetainPtr<CFX_DIBBase> CPDF_Image::DetachBitmap() { |
| return std::move(m_pDIBBase); |
| } |
| |
| RetainPtr<CFX_DIBBase> CPDF_Image::DetachMask() { |
| return std::move(m_pMask); |
| } |
| |
| bool CPDF_Image::StartLoadDIBBase(const CPDF_Dictionary* pFormResource, |
| const CPDF_Dictionary* pPageResource, |
| bool bStdCS, |
| CPDF_ColorSpace::Family GroupFamily, |
| bool bLoadMask) { |
| auto source = pdfium::MakeRetain<CPDF_DIB>(); |
| CPDF_DIB::LoadState ret = source->StartLoadDIBBase( |
| m_pDocument.Get(), m_pStream.Get(), true, pFormResource, pPageResource, |
| bStdCS, GroupFamily, bLoadMask); |
| if (ret == CPDF_DIB::LoadState::kFail) { |
| m_pDIBBase.Reset(); |
| return false; |
| } |
| m_pDIBBase = source; |
| if (ret == CPDF_DIB::LoadState::kContinue) |
| return true; |
| |
| m_pMask = source->DetachMask(); |
| m_MatteColor = source->GetMatteColor(); |
| return false; |
| } |
| |
| bool CPDF_Image::Continue(PauseIndicatorIface* pPause) { |
| RetainPtr<CPDF_DIB> pSource = m_pDIBBase.As<CPDF_DIB>(); |
| CPDF_DIB::LoadState ret = pSource->ContinueLoadDIBBase(pPause); |
| if (ret == CPDF_DIB::LoadState::kContinue) |
| return true; |
| |
| if (ret == CPDF_DIB::LoadState::kSuccess) { |
| m_pMask = pSource->DetachMask(); |
| m_MatteColor = pSource->GetMatteColor(); |
| } else { |
| m_pDIBBase.Reset(); |
| } |
| return false; |
| } |
| |
| RetainPtr<CPDF_Dictionary> CPDF_Image::CreateXObjectImageDict(int width, |
| int height) { |
| auto dict = m_pDocument->New<CPDF_Dictionary>(); |
| dict->SetNewFor<CPDF_Name>("Type", "XObject"); |
| dict->SetNewFor<CPDF_Name>("Subtype", "Image"); |
| dict->SetNewFor<CPDF_Number>("Width", width); |
| dict->SetNewFor<CPDF_Number>("Height", height); |
| return dict; |
| } |