| // 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 "public/fpdf_edit.h" | |
| #include <math.h> | |
| #include <algorithm> | |
| #include <memory> | |
| #include <utility> | |
| #include "core/fpdfapi/page/cpdf_dib.h" | |
| #include "core/fpdfapi/page/cpdf_docpagedata.h" | |
| #include "core/fpdfapi/page/cpdf_iccprofile.h" | |
| #include "core/fpdfapi/page/cpdf_image.h" | |
| #include "core/fpdfapi/page/cpdf_imageobject.h" | |
| #include "core/fpdfapi/page/cpdf_page.h" | |
| #include "core/fpdfapi/page/cpdf_pageobject.h" | |
| #include "core/fpdfapi/parser/cpdf_array.h" | |
| #include "core/fpdfapi/parser/cpdf_dictionary.h" | |
| #include "core/fpdfapi/parser/cpdf_name.h" | |
| #include "core/fpdfapi/parser/cpdf_stream.h" | |
| #include "core/fpdfapi/parser/cpdf_stream_acc.h" | |
| #include "core/fpdfapi/render/cpdf_imagerenderer.h" | |
| #include "core/fpdfapi/render/cpdf_rendercontext.h" | |
| #include "core/fpdfapi/render/cpdf_renderstatus.h" | |
| #include "core/fxcrt/compiler_specific.h" | |
| #include "core/fxcrt/notreached.h" | |
| #include "core/fxcrt/stl_util.h" | |
| #include "core/fxcrt/to_underlying.h" | |
| #include "core/fxge/cfx_renderdevice.h" | |
| #include "core/fxge/dib/cfx_dibitmap.h" | |
| #include "fpdfsdk/cpdfsdk_customaccess.h" | |
| #include "fpdfsdk/cpdfsdk_helpers.h" | |
| namespace { | |
| // These checks ensure the consistency of colorspace values across core/ and | |
| // public/. | |
| static_assert(fxcrt::to_underlying(CPDF_ColorSpace::Family::kDeviceGray) == | |
| FPDF_COLORSPACE_DEVICEGRAY, | |
| "kDeviceGray value mismatch"); | |
| static_assert(fxcrt::to_underlying(CPDF_ColorSpace::Family::kDeviceRGB) == | |
| FPDF_COLORSPACE_DEVICERGB, | |
| "kDeviceRGB value mismatch"); | |
| static_assert(fxcrt::to_underlying(CPDF_ColorSpace::Family::kDeviceCMYK) == | |
| FPDF_COLORSPACE_DEVICECMYK, | |
| "kDeviceCMYK value mismatch"); | |
| static_assert(fxcrt::to_underlying(CPDF_ColorSpace::Family::kCalGray) == | |
| FPDF_COLORSPACE_CALGRAY, | |
| "kCalGray value mismatch"); | |
| static_assert(fxcrt::to_underlying(CPDF_ColorSpace::Family::kCalRGB) == | |
| FPDF_COLORSPACE_CALRGB, | |
| "kCalRGB value mismatch"); | |
| static_assert(fxcrt::to_underlying(CPDF_ColorSpace::Family::kLab) == | |
| FPDF_COLORSPACE_LAB, | |
| "kLab value mismatch"); | |
| static_assert(fxcrt::to_underlying(CPDF_ColorSpace::Family::kICCBased) == | |
| FPDF_COLORSPACE_ICCBASED, | |
| "kICCBased value mismatch"); | |
| static_assert(fxcrt::to_underlying(CPDF_ColorSpace::Family::kSeparation) == | |
| FPDF_COLORSPACE_SEPARATION, | |
| "kSeparation value mismatch"); | |
| static_assert(fxcrt::to_underlying(CPDF_ColorSpace::Family::kDeviceN) == | |
| FPDF_COLORSPACE_DEVICEN, | |
| "kDeviceN value mismatch"); | |
| static_assert(fxcrt::to_underlying(CPDF_ColorSpace::Family::kIndexed) == | |
| FPDF_COLORSPACE_INDEXED, | |
| "kIndexed value mismatch"); | |
| static_assert(fxcrt::to_underlying(CPDF_ColorSpace::Family::kPattern) == | |
| FPDF_COLORSPACE_PATTERN, | |
| "kPattern value mismatch"); | |
| RetainPtr<IFX_SeekableReadStream> MakeSeekableReadStream( | |
| FPDF_FILEACCESS* pFileAccess) { | |
| return pdfium::MakeRetain<CPDFSDK_CustomAccess>(pFileAccess); | |
| } | |
| CPDF_ImageObject* CPDFImageObjectFromFPDFPageObject( | |
| FPDF_PAGEOBJECT image_object) { | |
| CPDF_PageObject* pPageObject = CPDFPageObjectFromFPDFPageObject(image_object); | |
| return pPageObject ? pPageObject->AsImage() : nullptr; | |
| } | |
| bool LoadJpegHelper(FPDF_PAGE* pages, | |
| int count, | |
| FPDF_PAGEOBJECT image_object, | |
| FPDF_FILEACCESS* file_access, | |
| bool inline_jpeg) { | |
| CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object); | |
| if (!pImgObj) { | |
| return false; | |
| } | |
| if (!file_access) { | |
| return false; | |
| } | |
| if (pages) { | |
| for (int index = 0; index < count; index++) { | |
| CPDF_Page* pPage = CPDFPageFromFPDFPage(UNSAFE_TODO(pages[index])); | |
| if (pPage) { | |
| pImgObj->GetImage()->ResetCache(pPage); | |
| } | |
| } | |
| } | |
| RetainPtr<IFX_SeekableReadStream> pFile = MakeSeekableReadStream(file_access); | |
| if (inline_jpeg) { | |
| pImgObj->GetImage()->SetJpegImageInline(std::move(pFile)); | |
| } else { | |
| pImgObj->GetImage()->SetJpegImage(std::move(pFile)); | |
| } | |
| pImgObj->SetDirty(true); | |
| return true; | |
| } | |
| } // namespace | |
| FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV | |
| FPDFPageObj_NewImageObj(FPDF_DOCUMENT document) { | |
| CPDF_Document* doc = CPDFDocumentFromFPDFDocument(document); | |
| if (!doc) { | |
| return nullptr; | |
| } | |
| auto pImageObj = std::make_unique<CPDF_ImageObject>(); | |
| pImageObj->SetImage(pdfium::MakeRetain<CPDF_Image>(doc)); | |
| // Caller takes ownership. | |
| return FPDFPageObjectFromCPDFPageObject(pImageObj.release()); | |
| } | |
| FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV | |
| FPDFImageObj_LoadJpegFile(FPDF_PAGE* pages, | |
| int count, | |
| FPDF_PAGEOBJECT image_object, | |
| FPDF_FILEACCESS* file_access) { | |
| return LoadJpegHelper(pages, count, image_object, file_access, false); | |
| } | |
| FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV | |
| FPDFImageObj_LoadJpegFileInline(FPDF_PAGE* pages, | |
| int count, | |
| FPDF_PAGEOBJECT image_object, | |
| FPDF_FILEACCESS* file_access) { | |
| return LoadJpegHelper(pages, count, image_object, file_access, true); | |
| } | |
| FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV | |
| FPDFImageObj_SetMatrix(FPDF_PAGEOBJECT image_object, | |
| double a, | |
| double b, | |
| double c, | |
| double d, | |
| double e, | |
| double f) { | |
| CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object); | |
| if (!pImgObj) { | |
| return false; | |
| } | |
| pImgObj->SetImageMatrix(CFX_Matrix( | |
| static_cast<float>(a), static_cast<float>(b), static_cast<float>(c), | |
| static_cast<float>(d), static_cast<float>(e), static_cast<float>(f))); | |
| pImgObj->SetDirty(true); | |
| return true; | |
| } | |
| FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV | |
| FPDFImageObj_SetBitmap(FPDF_PAGE* pages, | |
| int count, | |
| FPDF_PAGEOBJECT image_object, | |
| FPDF_BITMAP bitmap) { | |
| CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object); | |
| if (!pImgObj) { | |
| return false; | |
| } | |
| RetainPtr<CFX_DIBitmap> holder(CFXDIBitmapFromFPDFBitmap(bitmap)); | |
| if (!holder) { | |
| return false; | |
| } | |
| ValidateBitmapPremultiplyState(holder); | |
| if (pages) { | |
| for (int index = 0; index < count; index++) { | |
| CPDF_Page* pPage = CPDFPageFromFPDFPage(UNSAFE_TODO(pages[index])); | |
| if (pPage) { | |
| pImgObj->GetImage()->ResetCache(pPage); | |
| } | |
| } | |
| } | |
| pImgObj->GetImage()->SetImage(holder); | |
| pImgObj->CalcBoundingBox(); | |
| pImgObj->SetDirty(true); | |
| return true; | |
| } | |
| FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV | |
| FPDFImageObj_GetBitmap(FPDF_PAGEOBJECT image_object) { | |
| CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object); | |
| if (!pImgObj) { | |
| return nullptr; | |
| } | |
| RetainPtr<CPDF_Image> pImg = pImgObj->GetImage(); | |
| if (!pImg) { | |
| return nullptr; | |
| } | |
| RetainPtr<CFX_DIBBase> pSource = pImg->LoadDIBBase(); | |
| if (!pSource) { | |
| return nullptr; | |
| } | |
| // If the source image has a representation of 1 bit per pixel, or if the | |
| // source image has a color palette, convert it to a representation that does | |
| // not have a color palette, as there is no public API to access the palette. | |
| // | |
| // Otherwise, convert the source image to a bitmap directly, | |
| // retaining its color representation. | |
| // | |
| // Only return FPDF_BITMAPs in formats that FPDFBitmap_CreateEx() would | |
| // return. | |
| enum class ConversionOp { | |
| kRealize, | |
| kConvertTo8bppRgb, | |
| kConvertToRgb, | |
| }; | |
| ConversionOp op; | |
| switch (pSource->GetFormat()) { | |
| case FXDIB_Format::k1bppMask: | |
| case FXDIB_Format::k8bppMask: { | |
| // Masks do not have palettes, so they can be safely converted to | |
| // `FXDIB_Format::k8bppRgb`. | |
| CHECK(!pSource->HasPalette()); | |
| op = ConversionOp::kConvertTo8bppRgb; | |
| break; | |
| } | |
| case FXDIB_Format::k1bppRgb: { | |
| // If there is a palette, then convert to `FXDIB_Format::kBgr` to avoid | |
| // creating a bitmap with a palette. | |
| op = pSource->HasPalette() ? ConversionOp::kConvertToRgb | |
| : ConversionOp::kConvertTo8bppRgb; | |
| break; | |
| } | |
| case FXDIB_Format::k8bppRgb: | |
| case FXDIB_Format::kBgra: | |
| case FXDIB_Format::kBgr: | |
| case FXDIB_Format::kBgrx: { | |
| // If there is a palette, then convert to `FXDIB_Format::kBgr` to avoid | |
| // creating a bitmap with a palette. | |
| op = pSource->HasPalette() ? ConversionOp::kConvertToRgb | |
| : ConversionOp::kRealize; | |
| break; | |
| } | |
| case FXDIB_Format::kInvalid: { | |
| NOTREACHED(); | |
| } | |
| #if defined(PDF_USE_SKIA) | |
| case FXDIB_Format::kBgraPremul: { | |
| // TODO(crbug.com/355676038): Consider adding support for | |
| // `FXDIB_Format::kBgraPremul` | |
| NOTREACHED(); | |
| } | |
| #endif | |
| } | |
| RetainPtr<CFX_DIBitmap> pBitmap; | |
| switch (op) { | |
| case ConversionOp::kRealize: | |
| pBitmap = pSource->Realize(); | |
| break; | |
| case ConversionOp::kConvertTo8bppRgb: | |
| pBitmap = pSource->ConvertTo(FXDIB_Format::k8bppRgb); | |
| break; | |
| case ConversionOp::kConvertToRgb: | |
| pBitmap = pSource->ConvertTo(FXDIB_Format::kBgr); | |
| break; | |
| } | |
| if (!pBitmap) { | |
| return nullptr; | |
| } | |
| CHECK(!pBitmap->HasPalette()); | |
| ValidateBitmapPremultiplyState(pBitmap); | |
| // Caller takes ownership. | |
| return FPDFBitmapFromCFXDIBitmap(pBitmap.Leak()); | |
| } | |
| FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV | |
| FPDFImageObj_GetRenderedBitmap(FPDF_DOCUMENT document, | |
| FPDF_PAGE page, | |
| FPDF_PAGEOBJECT image_object) { | |
| CPDF_Document* doc = CPDFDocumentFromFPDFDocument(document); | |
| if (!doc) { | |
| return nullptr; | |
| } | |
| CPDF_Page* optional_page = CPDFPageFromFPDFPage(page); | |
| if (optional_page && optional_page->GetDocument() != doc) { | |
| return nullptr; | |
| } | |
| CPDF_ImageObject* image = CPDFImageObjectFromFPDFPageObject(image_object); | |
| if (!image) { | |
| return nullptr; | |
| } | |
| // Create |result_bitmap|. | |
| const CFX_Matrix& image_matrix = image->matrix(); | |
| float output_width = std::ceil(hypotf(image_matrix.a, image_matrix.c)); | |
| float output_height = std::ceil(hypotf(image_matrix.b, image_matrix.d)); | |
| auto result_bitmap = pdfium::MakeRetain<CFX_DIBitmap>(); | |
| if (!result_bitmap->Create(static_cast<int>(output_width), | |
| static_cast<int>(output_height), | |
| FXDIB_Format::kBgra)) { | |
| return nullptr; | |
| } | |
| // Set up all the rendering code. | |
| RetainPtr<CPDF_Dictionary> page_resources = | |
| optional_page ? optional_page->GetMutablePageResources() : nullptr; | |
| CPDF_RenderContext context(doc, std::move(page_resources), | |
| /*pPageCache=*/nullptr); | |
| std::unique_ptr<CFX_RenderDevice> device = | |
| CFX_RenderDevice::CreateForBitmap(result_bitmap); | |
| if (!device) { | |
| return nullptr; | |
| } | |
| CPDF_RenderStatus status(&context, device.get()); | |
| CPDF_ImageRenderer renderer(&status); | |
| // Need to first flip the image, as expected by |renderer|. | |
| const CFX_Matrix render_matrix(1, 0, 0, -1, 0, output_height); | |
| // Then take |image_matrix|'s offset into account. | |
| float min_x = image_matrix.e + std::min(image_matrix.a, image_matrix.c); | |
| float min_y = image_matrix.f + std::min(image_matrix.b, image_matrix.d); | |
| CFX_Matrix image_render_matrix = render_matrix; | |
| image_render_matrix.Translate(-min_x, min_y); | |
| // Apply the clip path if there is one. | |
| if (image->clip_path().HasRef()) { | |
| CFX_FloatRect output_rect(0, 0, output_width, output_height); | |
| CFX_FloatRect original_image_rect = | |
| image->original_matrix().TransformRect(CFX_FloatRect(0, 0, 1, 1)); | |
| CFX_Matrix clip_render_matrix; | |
| clip_render_matrix.MatchRect(output_rect, original_image_rect); | |
| clip_render_matrix.Concat(render_matrix); | |
| status.ProcessClipPath(image->clip_path(), clip_render_matrix); | |
| } | |
| // Do the actual rendering. | |
| bool should_continue = | |
| renderer.Start(image, image_render_matrix, /*bStdCS=*/false); | |
| while (should_continue) { | |
| should_continue = renderer.Continue(/*pPause=*/nullptr); | |
| } | |
| if (!renderer.GetResult()) { | |
| return nullptr; | |
| } | |
| ValidateBitmapPremultiplyState(result_bitmap); | |
| // Caller takes ownership. | |
| return FPDFBitmapFromCFXDIBitmap(result_bitmap.Leak()); | |
| } | |
| FPDF_EXPORT unsigned long FPDF_CALLCONV | |
| FPDFImageObj_GetImageDataDecoded(FPDF_PAGEOBJECT image_object, | |
| void* buffer, | |
| unsigned long buflen) { | |
| CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object); | |
| if (!pImgObj) { | |
| return 0; | |
| } | |
| RetainPtr<CPDF_Image> pImg = pImgObj->GetImage(); | |
| if (!pImg) { | |
| return 0; | |
| } | |
| RetainPtr<const CPDF_Stream> pImgStream = pImg->GetStream(); | |
| if (!pImgStream) { | |
| return 0; | |
| } | |
| // SAFETY: caller ensures `buffer` points to at least `buflen` bytes. | |
| return DecodeStreamMaybeCopyAndReturnLength( | |
| std::move(pImgStream), | |
| UNSAFE_BUFFERS(pdfium::span(static_cast<uint8_t*>(buffer), | |
| static_cast<size_t>(buflen)))); | |
| } | |
| FPDF_EXPORT unsigned long FPDF_CALLCONV | |
| FPDFImageObj_GetImageDataRaw(FPDF_PAGEOBJECT image_object, | |
| void* buffer, | |
| unsigned long buflen) { | |
| CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object); | |
| if (!pImgObj) { | |
| return 0; | |
| } | |
| RetainPtr<CPDF_Image> pImg = pImgObj->GetImage(); | |
| if (!pImg) { | |
| return 0; | |
| } | |
| RetainPtr<const CPDF_Stream> pImgStream = pImg->GetStream(); | |
| if (!pImgStream) { | |
| return 0; | |
| } | |
| // SAFETY: caller ensures `buffer` points to at least `buflen` bytes. | |
| return GetRawStreamMaybeCopyAndReturnLength( | |
| std::move(pImgStream), | |
| UNSAFE_BUFFERS(pdfium::span(static_cast<uint8_t*>(buffer), | |
| static_cast<size_t>(buflen)))); | |
| } | |
| FPDF_EXPORT int FPDF_CALLCONV | |
| FPDFImageObj_GetImageFilterCount(FPDF_PAGEOBJECT image_object) { | |
| CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object); | |
| if (!pImgObj) { | |
| return 0; | |
| } | |
| RetainPtr<CPDF_Image> pImg = pImgObj->GetImage(); | |
| if (!pImg) { | |
| return 0; | |
| } | |
| RetainPtr<const CPDF_Dictionary> dict = pImg->GetDict(); | |
| if (!dict) { | |
| return 0; | |
| } | |
| RetainPtr<const CPDF_Object> pFilter = dict->GetDirectObjectFor("Filter"); | |
| if (!pFilter) { | |
| return 0; | |
| } | |
| if (pFilter->IsArray()) { | |
| return fxcrt::CollectionSize<int>(*pFilter->AsArray()); | |
| } | |
| if (pFilter->IsName()) { | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| FPDF_EXPORT unsigned long FPDF_CALLCONV | |
| FPDFImageObj_GetImageFilter(FPDF_PAGEOBJECT image_object, | |
| int index, | |
| void* buffer, | |
| unsigned long buflen) { | |
| if (index < 0 || index >= FPDFImageObj_GetImageFilterCount(image_object)) { | |
| return 0; | |
| } | |
| CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); | |
| RetainPtr<const CPDF_Dictionary> dict = | |
| pObj->AsImage()->GetImage()->GetDict(); | |
| RetainPtr<const CPDF_Object> pFilter = dict->GetDirectObjectFor("Filter"); | |
| ByteString bsFilter = pFilter->IsName() | |
| ? pFilter->AsName()->GetString() | |
| : pFilter->AsArray()->GetByteStringAt(index); | |
| // SAFETY: required from caller. | |
| return NulTerminateMaybeCopyAndReturnLength( | |
| bsFilter, UNSAFE_BUFFERS(SpanFromFPDFApiArgs(buffer, buflen))); | |
| } | |
| FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV | |
| FPDFImageObj_GetImageMetadata(FPDF_PAGEOBJECT image_object, | |
| FPDF_PAGE page, | |
| FPDF_IMAGEOBJ_METADATA* metadata) { | |
| CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object); | |
| if (!pImgObj || !metadata) { | |
| return false; | |
| } | |
| RetainPtr<CPDF_Image> pImg = pImgObj->GetImage(); | |
| if (!pImg) { | |
| return false; | |
| } | |
| metadata->marked_content_id = | |
| pImgObj->GetContentMarks()->GetMarkedContentID(); | |
| const int nPixelWidth = pImg->GetPixelWidth(); | |
| const int nPixelHeight = pImg->GetPixelHeight(); | |
| metadata->width = nPixelWidth; | |
| metadata->height = nPixelHeight; | |
| const float nWidth = pImgObj->GetRect().Width(); | |
| const float nHeight = pImgObj->GetRect().Height(); | |
| static constexpr int nPointsPerInch = 72; | |
| if (nWidth != 0 && nHeight != 0) { | |
| metadata->horizontal_dpi = nPixelWidth / nWidth * nPointsPerInch; | |
| metadata->vertical_dpi = nPixelHeight / nHeight * nPointsPerInch; | |
| } | |
| metadata->bits_per_pixel = 0; | |
| metadata->colorspace = FPDF_COLORSPACE_UNKNOWN; | |
| CPDF_Page* pPage = CPDFPageFromFPDFPage(page); | |
| if (!pPage || !pPage->GetDocument() || !pImg->GetStream()) { | |
| return true; | |
| } | |
| // A cross-document image may have come from the embedder. | |
| if (pPage->GetDocument() != pImg->GetDocument()) { | |
| return false; | |
| } | |
| RetainPtr<CPDF_DIB> pSource = pImg->CreateNewDIB(); | |
| CPDF_DIB::LoadState ret = pSource->StartLoadDIBBase( | |
| false, nullptr, pPage->GetPageResources().Get(), false, | |
| CPDF_ColorSpace::Family::kUnknown, false, {0, 0}); | |
| if (ret == CPDF_DIB::LoadState::kFail) { | |
| return true; | |
| } | |
| metadata->bits_per_pixel = pSource->GetBPP(); | |
| if (pSource->GetColorSpace()) { | |
| metadata->colorspace = | |
| static_cast<int>(pSource->GetColorSpace()->GetFamily()); | |
| } | |
| return true; | |
| } | |
| FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV | |
| FPDFImageObj_GetImagePixelSize(FPDF_PAGEOBJECT image_object, | |
| unsigned int* width, | |
| unsigned int* height) { | |
| CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object); | |
| if (!pImgObj || !width || !height) { | |
| return false; | |
| } | |
| RetainPtr<CPDF_Image> pImg = pImgObj->GetImage(); | |
| if (!pImg) { | |
| return false; | |
| } | |
| *width = pImg->GetPixelWidth(); | |
| *height = pImg->GetPixelHeight(); | |
| return true; | |
| } | |
| FPDF_BOOL FPDF_CALLCONV | |
| FPDFImageObj_GetIccProfileDataDecoded(FPDF_PAGEOBJECT image_object, | |
| FPDF_PAGE page, | |
| uint8_t* buffer, | |
| size_t buflen, | |
| size_t* out_buflen) { | |
| CPDF_ImageObject* image_obj = CPDFImageObjectFromFPDFPageObject(image_object); | |
| CPDF_Page* pdf_page = CPDFPageFromFPDFPage(page); | |
| if (!image_obj || !pdf_page || !out_buflen) { | |
| return false; | |
| } | |
| RetainPtr<CPDF_Image> image = image_obj->GetImage(); | |
| if (!image) { | |
| return false; | |
| } | |
| const CPDF_Stream* stream = image->GetStream(); | |
| if (!stream) { | |
| return false; | |
| } | |
| RetainPtr<const CPDF_Dictionary> stream_dict = stream->GetDict(); | |
| if (!stream_dict) { | |
| return false; | |
| } | |
| RetainPtr<const CPDF_Object> color_space_obj = | |
| stream_dict->GetDirectObjectFor("ColorSpace"); | |
| RetainPtr<const CPDF_Dictionary> page_resources = | |
| pdf_page->GetPageResources(); | |
| if (!page_resources) { | |
| return false; | |
| } | |
| CPDF_Document* document = pdf_page->GetDocument(); | |
| if (!document) { | |
| return false; | |
| } | |
| auto* doc_data = CPDF_DocPageData::FromDocument(document); | |
| if (!doc_data) { | |
| return true; | |
| } | |
| RetainPtr<CPDF_ColorSpace> color_space = | |
| doc_data->GetColorSpace(color_space_obj.Get(), page_resources); | |
| if (!color_space) { | |
| return false; | |
| } | |
| RetainPtr<CPDF_IccProfile> icc_profile = color_space->GetIccProfile(); | |
| if (!icc_profile || !icc_profile->IsValid()) { | |
| return false; | |
| } | |
| RetainPtr<const CPDF_StreamAcc> stream_acc = icc_profile->GetStreamAcc(); | |
| pdfium::span<const uint8_t> data = stream_acc->GetSpan(); | |
| *out_buflen = data.size(); | |
| if (!buffer || buflen < *out_buflen) { | |
| return true; | |
| } | |
| // SAFETY: required from caller. | |
| auto result_span = UNSAFE_BUFFERS(SpanFromFPDFApiArgs(buffer, buflen)); | |
| fxcrt::spancpy(result_span, data); | |
| return true; | |
| } |