1 // Copyright 2014 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7 #include "public/fpdf_edit.h"
8
9 #include <math.h>
10 #include <algorithm>
11 #include <memory>
12 #include <utility>
13
14 #include "core/fpdfapi/page/cpdf_dib.h"
15 #include "core/fpdfapi/page/cpdf_image.h"
16 #include "core/fpdfapi/page/cpdf_imageobject.h"
17 #include "core/fpdfapi/page/cpdf_page.h"
18 #include "core/fpdfapi/page/cpdf_pageobject.h"
19 #include "core/fpdfapi/parser/cpdf_array.h"
20 #include "core/fpdfapi/parser/cpdf_dictionary.h"
21 #include "core/fpdfapi/parser/cpdf_name.h"
22 #include "core/fpdfapi/parser/cpdf_stream.h"
23 #include "core/fpdfapi/parser/cpdf_stream_acc.h"
24 #include "core/fpdfapi/render/cpdf_imagerenderer.h"
25 #include "core/fpdfapi/render/cpdf_rendercontext.h"
26 #include "core/fpdfapi/render/cpdf_renderstatus.h"
27 #include "core/fxcrt/compiler_specific.h"
28 #include "core/fxcrt/notreached.h"
29 #include "core/fxcrt/stl_util.h"
30 #include "core/fxge/cfx_defaultrenderdevice.h"
31 #include "core/fxge/dib/cfx_dibitmap.h"
32 #include "fpdfsdk/cpdfsdk_customaccess.h"
33 #include "fpdfsdk/cpdfsdk_helpers.h"
34
35 namespace {
36
37 // These checks ensure the consistency of colorspace values across core/ and
38 // public/.
39 static_assert(static_cast<int>(CPDF_ColorSpace::Family::kDeviceGray) ==
40 FPDF_COLORSPACE_DEVICEGRAY,
41 "kDeviceGray value mismatch");
42 static_assert(static_cast<int>(CPDF_ColorSpace::Family::kDeviceRGB) ==
43 FPDF_COLORSPACE_DEVICERGB,
44 "kDeviceRGB value mismatch");
45 static_assert(static_cast<int>(CPDF_ColorSpace::Family::kDeviceCMYK) ==
46 FPDF_COLORSPACE_DEVICECMYK,
47 "kDeviceCMYK value mismatch");
48 static_assert(static_cast<int>(CPDF_ColorSpace::Family::kCalGray) ==
49 FPDF_COLORSPACE_CALGRAY,
50 "kCalGray value mismatch");
51 static_assert(static_cast<int>(CPDF_ColorSpace::Family::kCalRGB) ==
52 FPDF_COLORSPACE_CALRGB,
53 "kCalRGB value mismatch");
54 static_assert(static_cast<int>(CPDF_ColorSpace::Family::kLab) ==
55 FPDF_COLORSPACE_LAB,
56 "kLab value mismatch");
57 static_assert(static_cast<int>(CPDF_ColorSpace::Family::kICCBased) ==
58 FPDF_COLORSPACE_ICCBASED,
59 "kICCBased value mismatch");
60 static_assert(static_cast<int>(CPDF_ColorSpace::Family::kSeparation) ==
61 FPDF_COLORSPACE_SEPARATION,
62 "kSeparation value mismatch");
63 static_assert(static_cast<int>(CPDF_ColorSpace::Family::kDeviceN) ==
64 FPDF_COLORSPACE_DEVICEN,
65 "kDeviceN value mismatch");
66 static_assert(static_cast<int>(CPDF_ColorSpace::Family::kIndexed) ==
67 FPDF_COLORSPACE_INDEXED,
68 "kIndexed value mismatch");
69 static_assert(static_cast<int>(CPDF_ColorSpace::Family::kPattern) ==
70 FPDF_COLORSPACE_PATTERN,
71 "kPattern value mismatch");
72
MakeSeekableReadStream(FPDF_FILEACCESS * pFileAccess)73 RetainPtr<IFX_SeekableReadStream> MakeSeekableReadStream(
74 FPDF_FILEACCESS* pFileAccess) {
75 return pdfium::MakeRetain<CPDFSDK_CustomAccess>(pFileAccess);
76 }
77
CPDFImageObjectFromFPDFPageObject(FPDF_PAGEOBJECT image_object)78 CPDF_ImageObject* CPDFImageObjectFromFPDFPageObject(
79 FPDF_PAGEOBJECT image_object) {
80 CPDF_PageObject* pPageObject = CPDFPageObjectFromFPDFPageObject(image_object);
81 return pPageObject ? pPageObject->AsImage() : nullptr;
82 }
83
LoadJpegHelper(FPDF_PAGE * pages,int count,FPDF_PAGEOBJECT image_object,FPDF_FILEACCESS * file_access,bool inline_jpeg)84 bool LoadJpegHelper(FPDF_PAGE* pages,
85 int count,
86 FPDF_PAGEOBJECT image_object,
87 FPDF_FILEACCESS* file_access,
88 bool inline_jpeg) {
89 CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object);
90 if (!pImgObj)
91 return false;
92
93 if (!file_access)
94 return false;
95
96 if (pages) {
97 for (int index = 0; index < count; index++) {
98 CPDF_Page* pPage = CPDFPageFromFPDFPage(UNSAFE_TODO(pages[index]));
99 if (pPage) {
100 pImgObj->GetImage()->ResetCache(pPage);
101 }
102 }
103 }
104
105 RetainPtr<IFX_SeekableReadStream> pFile = MakeSeekableReadStream(file_access);
106 if (inline_jpeg)
107 pImgObj->GetImage()->SetJpegImageInline(std::move(pFile));
108 else
109 pImgObj->GetImage()->SetJpegImage(std::move(pFile));
110
111 pImgObj->SetDirty(true);
112 return true;
113 }
114
115 } // namespace
116
117 FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV
FPDFPageObj_NewImageObj(FPDF_DOCUMENT document)118 FPDFPageObj_NewImageObj(FPDF_DOCUMENT document) {
119 CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
120 if (!pDoc)
121 return nullptr;
122
123 auto pImageObj = std::make_unique<CPDF_ImageObject>();
124 pImageObj->SetImage(pdfium::MakeRetain<CPDF_Image>(pDoc));
125
126 // Caller takes ownership.
127 return FPDFPageObjectFromCPDFPageObject(pImageObj.release());
128 }
129
130 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFImageObj_LoadJpegFile(FPDF_PAGE * pages,int count,FPDF_PAGEOBJECT image_object,FPDF_FILEACCESS * file_access)131 FPDFImageObj_LoadJpegFile(FPDF_PAGE* pages,
132 int count,
133 FPDF_PAGEOBJECT image_object,
134 FPDF_FILEACCESS* file_access) {
135 return LoadJpegHelper(pages, count, image_object, file_access, false);
136 }
137
138 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFImageObj_LoadJpegFileInline(FPDF_PAGE * pages,int count,FPDF_PAGEOBJECT image_object,FPDF_FILEACCESS * file_access)139 FPDFImageObj_LoadJpegFileInline(FPDF_PAGE* pages,
140 int count,
141 FPDF_PAGEOBJECT image_object,
142 FPDF_FILEACCESS* file_access) {
143 return LoadJpegHelper(pages, count, image_object, file_access, true);
144 }
145
146 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFImageObj_SetMatrix(FPDF_PAGEOBJECT image_object,double a,double b,double c,double d,double e,double f)147 FPDFImageObj_SetMatrix(FPDF_PAGEOBJECT image_object,
148 double a,
149 double b,
150 double c,
151 double d,
152 double e,
153 double f) {
154 CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object);
155 if (!pImgObj)
156 return false;
157
158 pImgObj->SetImageMatrix(CFX_Matrix(
159 static_cast<float>(a), static_cast<float>(b), static_cast<float>(c),
160 static_cast<float>(d), static_cast<float>(e), static_cast<float>(f)));
161 pImgObj->SetDirty(true);
162 return true;
163 }
164
165 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFImageObj_SetBitmap(FPDF_PAGE * pages,int count,FPDF_PAGEOBJECT image_object,FPDF_BITMAP bitmap)166 FPDFImageObj_SetBitmap(FPDF_PAGE* pages,
167 int count,
168 FPDF_PAGEOBJECT image_object,
169 FPDF_BITMAP bitmap) {
170 CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object);
171 if (!pImgObj) {
172 return false;
173 }
174
175 RetainPtr<CFX_DIBitmap> holder(CFXDIBitmapFromFPDFBitmap(bitmap));
176 if (!holder) {
177 return false;
178 }
179 CHECK(!holder->IsPremultiplied());
180
181 if (pages) {
182 for (int index = 0; index < count; index++) {
183 CPDF_Page* pPage = CPDFPageFromFPDFPage(UNSAFE_TODO(pages[index]));
184 if (pPage) {
185 pImgObj->GetImage()->ResetCache(pPage);
186 }
187 }
188 }
189
190 pImgObj->GetImage()->SetImage(holder);
191 pImgObj->CalcBoundingBox();
192 pImgObj->SetDirty(true);
193 return true;
194 }
195
196 FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV
FPDFImageObj_GetBitmap(FPDF_PAGEOBJECT image_object)197 FPDFImageObj_GetBitmap(FPDF_PAGEOBJECT image_object) {
198 CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object);
199 if (!pImgObj)
200 return nullptr;
201
202 RetainPtr<CPDF_Image> pImg = pImgObj->GetImage();
203 if (!pImg)
204 return nullptr;
205
206 RetainPtr<CFX_DIBBase> pSource = pImg->LoadDIBBase();
207 if (!pSource)
208 return nullptr;
209
210 // If the source image has a representation of 1 bit per pixel, or if the
211 // source image has a color palette, convert it to a representation that does
212 // not have a color palette, as there is no public API to access the palette.
213 //
214 // Otherwise, convert the source image to a bitmap directly,
215 // retaining its color representation.
216 //
217 // Only return FPDF_BITMAPs in formats that FPDFBitmap_CreateEx() would
218 // return.
219 enum class ConversionOp {
220 kRealize,
221 kConvertTo8bppRgb,
222 kConvertToRgb,
223 };
224
225 ConversionOp op;
226 switch (pSource->GetFormat()) {
227 case FXDIB_Format::k1bppMask:
228 case FXDIB_Format::k8bppMask: {
229 // Masks do not have palettes, so they can be safely converted to
230 // `FXDIB_Format::k8bppRgb`.
231 CHECK(!pSource->HasPalette());
232 op = ConversionOp::kConvertTo8bppRgb;
233 break;
234 }
235 case FXDIB_Format::k1bppRgb: {
236 // If there is a palette, then convert to `FXDIB_Format::kBgr` to avoid
237 // creating a bitmap with a palette.
238 op = pSource->HasPalette() ? ConversionOp::kConvertToRgb
239 : ConversionOp::kConvertTo8bppRgb;
240 break;
241 }
242 case FXDIB_Format::k8bppRgb:
243 case FXDIB_Format::kBgra:
244 case FXDIB_Format::kBgr:
245 case FXDIB_Format::kBgrx: {
246 // If there is a palette, then convert to `FXDIB_Format::kBgr` to avoid
247 // creating a bitmap with a palette.
248 op = pSource->HasPalette() ? ConversionOp::kConvertToRgb
249 : ConversionOp::kRealize;
250 break;
251 }
252 case FXDIB_Format::kInvalid: {
253 NOTREACHED_NORETURN();
254 }
255 #if defined(PDF_USE_SKIA)
256 case FXDIB_Format::kBgraPremul: {
257 // TODO(crbug.com/355676038): Consider adding support for
258 // `FXDIB_Format::kBgraPremul`
259 NOTREACHED_NORETURN();
260 }
261 #endif
262 }
263
264 RetainPtr<CFX_DIBitmap> pBitmap;
265 switch (op) {
266 case ConversionOp::kRealize:
267 pBitmap = pSource->Realize();
268 break;
269 case ConversionOp::kConvertTo8bppRgb:
270 pBitmap = pSource->ConvertTo(FXDIB_Format::k8bppRgb);
271 break;
272 case ConversionOp::kConvertToRgb:
273 pBitmap = pSource->ConvertTo(FXDIB_Format::kBgr);
274 break;
275 }
276 if (!pBitmap) {
277 return nullptr;
278 }
279
280 CHECK(!pBitmap->HasPalette());
281 CHECK(!pBitmap->IsPremultiplied());
282
283 // Caller takes ownership.
284 return FPDFBitmapFromCFXDIBitmap(pBitmap.Leak());
285 }
286
287 FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV
FPDFImageObj_GetRenderedBitmap(FPDF_DOCUMENT document,FPDF_PAGE page,FPDF_PAGEOBJECT image_object)288 FPDFImageObj_GetRenderedBitmap(FPDF_DOCUMENT document,
289 FPDF_PAGE page,
290 FPDF_PAGEOBJECT image_object) {
291 CPDF_Document* doc = CPDFDocumentFromFPDFDocument(document);
292 if (!doc)
293 return nullptr;
294
295 CPDF_Page* optional_page = CPDFPageFromFPDFPage(page);
296 if (optional_page && optional_page->GetDocument() != doc)
297 return nullptr;
298
299 CPDF_ImageObject* image = CPDFImageObjectFromFPDFPageObject(image_object);
300 if (!image)
301 return nullptr;
302
303 // Create |result_bitmap|.
304 const CFX_Matrix& image_matrix = image->matrix();
305 float output_width = std::ceil(hypotf(image_matrix.a, image_matrix.c));
306 float output_height = std::ceil(hypotf(image_matrix.b, image_matrix.d));
307 auto result_bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
308 if (!result_bitmap->Create(static_cast<int>(output_width),
309 static_cast<int>(output_height),
310 FXDIB_Format::kBgra)) {
311 return nullptr;
312 }
313
314 // Set up all the rendering code.
315 RetainPtr<CPDF_Dictionary> page_resources =
316 optional_page ? optional_page->GetMutablePageResources() : nullptr;
317 CPDF_RenderContext context(doc, std::move(page_resources),
318 /*pPageCache=*/nullptr);
319 CFX_DefaultRenderDevice device;
320 device.Attach(result_bitmap);
321 CPDF_RenderStatus status(&context, &device);
322 CPDF_ImageRenderer renderer(&status);
323
324 // Need to first flip the image, as expected by |renderer|.
325 CFX_Matrix render_matrix(1, 0, 0, -1, 0, output_height);
326
327 // Then take |image_matrix|'s offset into account.
328 float min_x = image_matrix.e + std::min(image_matrix.a, image_matrix.c);
329 float min_y = image_matrix.f + std::min(image_matrix.b, image_matrix.d);
330 render_matrix.Translate(-min_x, min_y);
331
332 // Do the actual rendering.
333 bool should_continue = renderer.Start(image, render_matrix, /*bStdCS=*/false);
334 while (should_continue) {
335 should_continue = renderer.Continue(/*pPause=*/nullptr);
336 }
337
338 if (!renderer.GetResult())
339 return nullptr;
340
341 CHECK(!result_bitmap->IsPremultiplied());
342
343 // Caller takes ownership.
344 return FPDFBitmapFromCFXDIBitmap(result_bitmap.Leak());
345 }
346
347 FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFImageObj_GetImageDataDecoded(FPDF_PAGEOBJECT image_object,void * buffer,unsigned long buflen)348 FPDFImageObj_GetImageDataDecoded(FPDF_PAGEOBJECT image_object,
349 void* buffer,
350 unsigned long buflen) {
351 CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object);
352 if (!pImgObj)
353 return 0;
354
355 RetainPtr<CPDF_Image> pImg = pImgObj->GetImage();
356 if (!pImg)
357 return 0;
358
359 RetainPtr<const CPDF_Stream> pImgStream = pImg->GetStream();
360 if (!pImgStream)
361 return 0;
362
363 // SAFETY: caller ensures `buffer` points to at least `buflen` bytes.
364 return DecodeStreamMaybeCopyAndReturnLength(
365 std::move(pImgStream),
366 UNSAFE_BUFFERS(pdfium::make_span(static_cast<uint8_t*>(buffer),
367 static_cast<size_t>(buflen))));
368 }
369
370 FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFImageObj_GetImageDataRaw(FPDF_PAGEOBJECT image_object,void * buffer,unsigned long buflen)371 FPDFImageObj_GetImageDataRaw(FPDF_PAGEOBJECT image_object,
372 void* buffer,
373 unsigned long buflen) {
374 CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object);
375 if (!pImgObj)
376 return 0;
377
378 RetainPtr<CPDF_Image> pImg = pImgObj->GetImage();
379 if (!pImg)
380 return 0;
381
382 RetainPtr<const CPDF_Stream> pImgStream = pImg->GetStream();
383 if (!pImgStream)
384 return 0;
385
386 // SAFETY: caller ensures `buffer` points to at least `buflen` bytes.
387 return GetRawStreamMaybeCopyAndReturnLength(
388 std::move(pImgStream),
389 UNSAFE_BUFFERS(pdfium::make_span(static_cast<uint8_t*>(buffer),
390 static_cast<size_t>(buflen))));
391 }
392
393 FPDF_EXPORT int FPDF_CALLCONV
FPDFImageObj_GetImageFilterCount(FPDF_PAGEOBJECT image_object)394 FPDFImageObj_GetImageFilterCount(FPDF_PAGEOBJECT image_object) {
395 CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object);
396 if (!pImgObj)
397 return 0;
398
399 RetainPtr<CPDF_Image> pImg = pImgObj->GetImage();
400 if (!pImg)
401 return 0;
402
403 RetainPtr<const CPDF_Dictionary> pDict = pImg->GetDict();
404 if (!pDict)
405 return 0;
406
407 RetainPtr<const CPDF_Object> pFilter = pDict->GetDirectObjectFor("Filter");
408 if (!pFilter)
409 return 0;
410
411 if (pFilter->IsArray())
412 return fxcrt::CollectionSize<int>(*pFilter->AsArray());
413
414 if (pFilter->IsName())
415 return 1;
416
417 return 0;
418 }
419
420 FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFImageObj_GetImageFilter(FPDF_PAGEOBJECT image_object,int index,void * buffer,unsigned long buflen)421 FPDFImageObj_GetImageFilter(FPDF_PAGEOBJECT image_object,
422 int index,
423 void* buffer,
424 unsigned long buflen) {
425 if (index < 0 || index >= FPDFImageObj_GetImageFilterCount(image_object))
426 return 0;
427
428 CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object);
429 RetainPtr<const CPDF_Dictionary> pDict =
430 pObj->AsImage()->GetImage()->GetDict();
431 RetainPtr<const CPDF_Object> pFilter = pDict->GetDirectObjectFor("Filter");
432 ByteString bsFilter = pFilter->IsName()
433 ? pFilter->AsName()->GetString()
434 : pFilter->AsArray()->GetByteStringAt(index);
435
436 // SAFETY: required from caller.
437 return NulTerminateMaybeCopyAndReturnLength(
438 bsFilter, UNSAFE_BUFFERS(SpanFromFPDFApiArgs(buffer, buflen)));
439 }
440
441 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFImageObj_GetImageMetadata(FPDF_PAGEOBJECT image_object,FPDF_PAGE page,FPDF_IMAGEOBJ_METADATA * metadata)442 FPDFImageObj_GetImageMetadata(FPDF_PAGEOBJECT image_object,
443 FPDF_PAGE page,
444 FPDF_IMAGEOBJ_METADATA* metadata) {
445 CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object);
446 if (!pImgObj || !metadata)
447 return false;
448
449 RetainPtr<CPDF_Image> pImg = pImgObj->GetImage();
450 if (!pImg)
451 return false;
452
453 metadata->marked_content_id =
454 pImgObj->GetContentMarks()->GetMarkedContentID();
455
456 const int nPixelWidth = pImg->GetPixelWidth();
457 const int nPixelHeight = pImg->GetPixelHeight();
458 metadata->width = nPixelWidth;
459 metadata->height = nPixelHeight;
460
461 const float nWidth = pImgObj->GetRect().Width();
462 const float nHeight = pImgObj->GetRect().Height();
463 constexpr int nPointsPerInch = 72;
464 if (nWidth != 0 && nHeight != 0) {
465 metadata->horizontal_dpi = nPixelWidth / nWidth * nPointsPerInch;
466 metadata->vertical_dpi = nPixelHeight / nHeight * nPointsPerInch;
467 }
468
469 metadata->bits_per_pixel = 0;
470 metadata->colorspace = FPDF_COLORSPACE_UNKNOWN;
471
472 CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
473 if (!pPage || !pPage->GetDocument() || !pImg->GetStream())
474 return true;
475
476 // A cross-document image may have come from the embedder.
477 if (pPage->GetDocument() != pImg->GetDocument())
478 return false;
479
480 RetainPtr<CPDF_DIB> pSource = pImg->CreateNewDIB();
481 CPDF_DIB::LoadState ret = pSource->StartLoadDIBBase(
482 false, nullptr, pPage->GetPageResources().Get(), false,
483 CPDF_ColorSpace::Family::kUnknown, false, {0, 0});
484 if (ret == CPDF_DIB::LoadState::kFail)
485 return true;
486
487 metadata->bits_per_pixel = pSource->GetBPP();
488 if (pSource->GetColorSpace()) {
489 metadata->colorspace =
490 static_cast<int>(pSource->GetColorSpace()->GetFamily());
491 }
492 return true;
493 }
494
495 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFImageObj_GetImagePixelSize(FPDF_PAGEOBJECT image_object,unsigned int * width,unsigned int * height)496 FPDFImageObj_GetImagePixelSize(FPDF_PAGEOBJECT image_object,
497 unsigned int* width,
498 unsigned int* height) {
499 CPDF_ImageObject* pImgObj = CPDFImageObjectFromFPDFPageObject(image_object);
500 if (!pImgObj || !width || !height) {
501 return false;
502 }
503
504 RetainPtr<CPDF_Image> pImg = pImgObj->GetImage();
505 if (!pImg) {
506 return false;
507 }
508
509 *width = pImg->GetPixelWidth();
510 *height = pImg->GetPixelHeight();
511 return true;
512 }
513