1//========================================================================
2//
3// ImageEmbeddingUtils.cc
4//
5// Copyright (C) 2021 Georgiy Sgibnev <georgiy@sgibnev.com>. Work sponsored by lab50.net.
6// Copyright (C) 2021, 2022 Albert Astals Cid <aacid@kde.org>
7// Copyright (C) 2021 Marco Genasci <fedeliallalinea@gmail.com>
8// Copyright (C) 2023 Jordan Abrahams-Whitehead <ajordanr@google.com>
9// Copyright (C) 2024 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
10//
11// This file is licensed under the GPLv2 or later
12//
13//========================================================================
14
15#include <config.h>
16
17#include <memory>
18#ifdef ENABLE_LIBJPEG
19# include <cstdio>
20extern "C" {
21# include <jpeglib.h>
22}
23# include <csetjmp>
24#endif
25#ifdef ENABLE_LIBPNG
26# include <png.h>
27#endif
28
29#include "ImageEmbeddingUtils.h"
30#include "goo/gmem.h"
31#include "goo/GooCheckedOps.h"
32#include "Object.h"
33#include "Array.h"
34#include "Error.h"
35#include "PDFDoc.h"
36
37namespace ImageEmbeddingUtils {
38
39static const uint8_t PNG_MAGIC_NUM[] = { 0x89, 0x50, 0x4e, 0x47 };
40static const uint8_t JPEG_MAGIC_NUM[] = { 0xff, 0xd8, 0xff };
41static const uint8_t JPEG2000_MAGIC_NUM[] = { 0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20 };
42static const Goffset MAX_MAGIC_NUM_SIZE = sizeof(JPEG2000_MAGIC_NUM);
43
44static bool checkMagicNum(const uint8_t *fileContent, const uint8_t *magicNum, const uint8_t size)
45{
46 return (memcmp(s1: fileContent, s2: magicNum, n: size) == 0);
47}
48
49// Transforms an image to XObject.
50class ImageEmbedder
51{
52protected:
53 static constexpr const char *DEVICE_GRAY = "DeviceGray";
54 static constexpr const char *DEVICE_RGB = "DeviceRGB";
55
56 int m_width;
57 int m_height;
58
59 ImageEmbedder(const int width, const int height) : m_width(width), m_height(height) { }
60
61 // Creates an object of type XObject. You own the returned ptr.
62 static Dict *createImageDict(XRef *xref, const char *colorSpace, const int width, const int height, const int bitsPerComponent)
63 {
64 Dict *imageDict = new Dict(xref);
65 imageDict->add(key: "Type", val: Object(objName, "XObject"));
66 imageDict->add(key: "Subtype", val: Object(objName, "Image"));
67 imageDict->add(key: "ColorSpace", val: Object(objName, colorSpace));
68 imageDict->add(key: "Width", val: Object(width));
69 imageDict->add(key: "Height", val: Object(height));
70 imageDict->add(key: "BitsPerComponent", val: Object(bitsPerComponent));
71 return imageDict;
72 }
73
74public:
75 ImageEmbedder() = delete;
76 ImageEmbedder(const ImageEmbedder &) = delete;
77 ImageEmbedder &operator=(const ImageEmbedder &) = delete;
78 virtual ~ImageEmbedder();
79
80 // Call it only once.
81 // Returns ref to a new object or Ref::INVALID.
82 virtual Ref embedImage(XRef *xref) = 0;
83};
84
85ImageEmbedder::~ImageEmbedder() { }
86
87#ifdef ENABLE_LIBPNG
88// Transforms a PNG image to XObject.
89class PngEmbedder : public ImageEmbedder
90{
91 // LibpngInputStream is a simple replacement for GInputStream.
92 // Used with png_set_read_fn().
93 class LibpngInputStream
94 {
95 std::unique_ptr<uint8_t[]> m_fileContent;
96 uint8_t *m_iterator;
97 png_size_t m_remainingSize;
98
99 void read(png_bytep out, const png_size_t size)
100 {
101 const png_size_t fixedSize = (m_remainingSize >= size) ? size : m_remainingSize;
102 memcpy(dest: out, src: m_iterator, n: fixedSize);
103 m_iterator += fixedSize;
104 m_remainingSize -= fixedSize;
105 }
106
107 public:
108 LibpngInputStream(std::unique_ptr<uint8_t[]> &&fileContent, const Goffset size) : m_fileContent(std::move(fileContent)), m_iterator(m_fileContent.get()), m_remainingSize(size) { }
109 LibpngInputStream() = delete;
110 LibpngInputStream(const LibpngInputStream &) = delete;
111 LibpngInputStream &operator=(const LibpngInputStream &) = delete;
112 ~LibpngInputStream() = default;
113
114 // Pass this static function to png_set_read_fn().
115 static void readCallback(png_structp png, png_bytep out, png_size_t size)
116 {
117 LibpngInputStream *stream = (LibpngInputStream *)png_get_io_ptr(png_ptr: png);
118 if (stream) {
119 stream->read(out, size);
120 }
121 }
122 };
123
124 png_structp m_png;
125 png_infop m_info;
126 LibpngInputStream *m_stream;
127 const png_byte m_type;
128 const bool m_hasAlpha;
129 // Number of color channels.
130 const png_byte m_n;
131 // Number of color channels excluding alpha channel. Should be 1 or 3.
132 const png_byte m_nWithoutAlpha;
133 // Shold be 8 or 16.
134 const png_byte m_bitDepth;
135 // Should be 1 or 2.
136 const png_byte m_byteDepth;
137
138 PngEmbedder(png_structp png, png_infop info, LibpngInputStream *stream)
139 : ImageEmbedder(png_get_image_width(png_ptr: png, info_ptr: info), png_get_image_height(png_ptr: png, info_ptr: info)),
140 m_png(png),
141 m_info(info),
142 m_stream(stream),
143 m_type(png_get_color_type(png_ptr: m_png, info_ptr: m_info)),
144 m_hasAlpha(m_type & PNG_COLOR_MASK_ALPHA),
145 m_n(png_get_channels(png_ptr: m_png, info_ptr: m_info)),
146 m_nWithoutAlpha(m_hasAlpha ? m_n - 1 : m_n),
147 m_bitDepth(png_get_bit_depth(png_ptr: m_png, info_ptr: m_info)),
148 m_byteDepth(m_bitDepth / 8)
149 {
150 }
151
152 // Reads pixels into mainBuffer (RGB/gray channels) and maskBuffer (alpha channel).
153 void readPixels(png_bytep mainBuffer, png_bytep maskBuffer)
154 {
155 // Read pixels from m_png.
156 const int rowSize = png_get_rowbytes(png_ptr: m_png, info_ptr: m_info);
157 png_bytepp pixels = new png_bytep[m_height];
158 for (int y = 0; y < m_height; y++) {
159 pixels[y] = new png_byte[rowSize];
160 }
161 png_read_image(png_ptr: m_png, image: pixels);
162
163 // Copy pixels into mainBuffer and maskBuffer.
164 const png_byte pixelSizeWithoutAlpha = m_nWithoutAlpha * m_byteDepth;
165 for (int y = 0; y < m_height; y++) {
166 png_bytep row = pixels[y];
167 for (int x = 0; x < m_width; x++) {
168 memcpy(dest: mainBuffer, src: row, n: pixelSizeWithoutAlpha);
169 mainBuffer += pixelSizeWithoutAlpha;
170 row += pixelSizeWithoutAlpha;
171 if (m_hasAlpha) {
172 memcpy(dest: maskBuffer, src: row, n: m_byteDepth);
173 maskBuffer += m_byteDepth;
174 row += m_byteDepth;
175 }
176 }
177 }
178
179 // Cleanup.
180 for (int y = 0; y < m_height; y++) {
181 delete[] pixels[y];
182 }
183 delete[] pixels;
184 }
185
186 // Supportive function for create().
187 // We don't want to deal with palette images.
188 // We don't want to deal with 1/2/4-bit samples.
189 static void fixPng(png_structp png, png_infop info)
190 {
191 const png_byte colorType = png_get_color_type(png_ptr: png, info_ptr: info);
192 const png_byte bitDepth = png_get_bit_depth(png_ptr: png, info_ptr: info);
193
194 bool updateRequired = false;
195 if (colorType == PNG_COLOR_TYPE_PALETTE) {
196 png_set_palette_to_rgb(png_ptr: png);
197 updateRequired = true;
198 }
199 if ((colorType == PNG_COLOR_TYPE_GRAY) && (bitDepth < 8)) {
200 png_set_expand_gray_1_2_4_to_8(png_ptr: png);
201 updateRequired = true;
202 }
203 if (png_get_valid(png_ptr: png, info_ptr: info, PNG_INFO_tRNS)) {
204 png_set_tRNS_to_alpha(png_ptr: png);
205 updateRequired = true;
206 }
207 if (bitDepth < 8) {
208 png_set_packing(png_ptr: png);
209 updateRequired = true;
210 }
211 if (updateRequired) {
212 png_read_update_info(png_ptr: png, info_ptr: info);
213 }
214 }
215
216public:
217 PngEmbedder() = delete;
218 PngEmbedder(const PngEmbedder &) = delete;
219 PngEmbedder &operator=(const PngEmbedder &) = delete;
220 ~PngEmbedder() override
221 {
222 png_destroy_read_struct(png_ptr_ptr: &m_png, info_ptr_ptr: &m_info, end_info_ptr_ptr: nullptr);
223 delete m_stream;
224 }
225
226 Ref embedImage(XRef *xref) override;
227
228 static std::unique_ptr<ImageEmbedder> create(std::unique_ptr<uint8_t[]> &&fileContent, const Goffset fileSize)
229 {
230 png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, error_ptr: nullptr, error_fn: nullptr, warn_fn: nullptr);
231 if (png == nullptr) {
232 error(category: errInternal, pos: -1, msg: "Couldn't load PNG. png_create_read_struct() failed");
233 return nullptr;
234 }
235 png_infop info = png_create_info_struct(png_ptr: png);
236 if (info == nullptr) {
237 error(category: errInternal, pos: -1, msg: "Couldn't load PNG. png_create_info_struct() failed");
238 png_destroy_read_struct(png_ptr_ptr: &png, info_ptr_ptr: nullptr, end_info_ptr_ptr: nullptr);
239 return nullptr;
240 }
241 if (setjmp(png_jmpbuf(png))) {
242 error(category: errInternal, pos: -1, msg: "Couldn't load PNG. Failed to set up error handling for reading PNG");
243 png_destroy_read_struct(png_ptr_ptr: &png, info_ptr_ptr: &info, end_info_ptr_ptr: nullptr);
244 return nullptr;
245 }
246
247 LibpngInputStream *stream = new LibpngInputStream(std::move(fileContent), fileSize);
248 png_set_read_fn(png_ptr: png, io_ptr: stream, read_data_fn: LibpngInputStream::readCallback);
249 png_read_info(png_ptr: png, info_ptr: info);
250 fixPng(png, info);
251 const png_byte bitDepth = png_get_bit_depth(png_ptr: png, info_ptr: info);
252 if ((bitDepth != 8) && (bitDepth != 16)) {
253 error(category: errInternal, pos: -1, msg: "Couldn't load PNG. Fixing bit depth failed");
254 png_destroy_read_struct(png_ptr_ptr: &png, info_ptr_ptr: &info, end_info_ptr_ptr: nullptr);
255 delete stream;
256 return nullptr;
257 }
258 return std::unique_ptr<ImageEmbedder>(new PngEmbedder(png, info, stream));
259 }
260};
261
262Ref PngEmbedder::embedImage(XRef *xref)
263{
264 // Read pixels.
265 Goffset area;
266 if (checkedMultiply(x: static_cast<Goffset>(m_width), y: static_cast<Goffset>(m_height), z: &area)) {
267 error(category: errIO, pos: -1, msg: "PngEmbedder::embedImage: width * height overflows Goffset");
268 return Ref::INVALID();
269 }
270 Goffset maskBufferSize;
271 static_assert(sizeof(Goffset) >= sizeof(m_byteDepth));
272 if (checkedMultiply(x: area, y: static_cast<Goffset>(m_byteDepth), z: &maskBufferSize)) {
273 error(category: errIO, pos: -1, msg: "PngEmbedder::embedImage: width * height * m_byteDepth overflows Goffset");
274 return Ref::INVALID();
275 }
276 Goffset mainBufferSize;
277 static_assert(sizeof(Goffset) >= sizeof(m_nWithoutAlpha));
278 if (checkedMultiply(x: maskBufferSize, y: static_cast<Goffset>(m_nWithoutAlpha), z: &mainBufferSize)) {
279 error(category: errIO, pos: -1, msg: "PngEmbedder::embedImage: width * height * m_byteDepth * m_nWithoutAlpha overflows Goffset");
280 return Ref::INVALID();
281 }
282 png_bytep mainBuffer = (png_bytep)gmalloc(size: mainBufferSize);
283 png_bytep maskBuffer = (m_hasAlpha) ? (png_bytep)gmalloc(size: maskBufferSize) : nullptr;
284 readPixels(mainBuffer, maskBuffer);
285
286 // Create a mask XObject and a main XObject.
287 const char *colorSpace = ((m_type == PNG_COLOR_TYPE_GRAY) || (m_type == PNG_COLOR_TYPE_GRAY_ALPHA)) ? DEVICE_GRAY : DEVICE_RGB;
288 Dict *baseImageDict = createImageDict(xref, colorSpace, width: m_width, height: m_height, bitsPerComponent: m_bitDepth);
289 if (m_hasAlpha) {
290 Dict *maskImageDict = createImageDict(xref, colorSpace: DEVICE_GRAY, width: m_width, height: m_height, bitsPerComponent: m_bitDepth);
291 Ref maskImageRef = xref->addStreamObject(dict: maskImageDict, buffer: maskBuffer, bufferSize: maskBufferSize, compression: StreamCompression::Compress);
292 baseImageDict->add(key: "SMask", val: Object(maskImageRef));
293 }
294 return xref->addStreamObject(dict: baseImageDict, buffer: mainBuffer, bufferSize: mainBufferSize, compression: StreamCompression::Compress);
295}
296#endif
297
298#ifdef ENABLE_LIBJPEG
299
300struct JpegErrorManager
301{
302 jpeg_error_mgr pub;
303 jmp_buf setjmpBuffer;
304};
305
306// Note: an address of pub is equal to an address of a JpegErrorManager instance.
307static void jpegExitErrorHandler(j_common_ptr info)
308{
309 JpegErrorManager *errorManager = (JpegErrorManager *)info->err;
310 (*errorManager->pub.output_message)(info);
311 // Jump to the setjmp point.
312 longjmp(env: errorManager->setjmpBuffer, val: 1);
313}
314
315// Transforms a JPEG image to XObject.
316class JpegEmbedder : public ImageEmbedder
317{
318 std::unique_ptr<uint8_t[]> m_fileContent;
319 Goffset m_fileSize;
320
321 JpegEmbedder(const int width, const int height, std::unique_ptr<uint8_t[]> &&fileContent, const Goffset fileSize) : ImageEmbedder(width, height), m_fileContent(std::move(fileContent)), m_fileSize(fileSize) { }
322
323public:
324 JpegEmbedder() = delete;
325 JpegEmbedder(const JpegEmbedder &) = delete;
326 JpegEmbedder &operator=(const JpegEmbedder &) = delete;
327 ~JpegEmbedder() override = default;
328
329 Ref embedImage(XRef *xref) override;
330
331 static std::unique_ptr<ImageEmbedder> create(std::unique_ptr<uint8_t[]> &&fileContent, const Goffset fileSize)
332 {
333 jpeg_decompress_struct info;
334 JpegErrorManager errorManager;
335 info.err = jpeg_std_error(err: &errorManager.pub);
336 errorManager.pub.error_exit = jpegExitErrorHandler;
337 if (setjmp(errorManager.setjmpBuffer)) {
338 // The setjmp point.
339 jpeg_destroy_decompress(cinfo: &info);
340 error(category: errInternal, pos: -1, msg: "libjpeg failed to process the file");
341 return nullptr;
342 }
343
344 jpeg_create_decompress(&info);
345 // fileSize is guaranteed to be in the range 0..int max by the checks in embed()
346 // jpeg_mem_src takes an unsigned long in the 3rd parameter
347 jpeg_mem_src(cinfo: &info, inbuffer: fileContent.get(), insize: static_cast<unsigned long>(fileSize));
348 jpeg_read_header(cinfo: &info, TRUE);
349 jpeg_start_decompress(cinfo: &info);
350 auto result = std::unique_ptr<ImageEmbedder>(new JpegEmbedder(info.output_width, info.output_height, std::move(fileContent), fileSize));
351 jpeg_abort_decompress(cinfo: &info);
352 jpeg_destroy_decompress(cinfo: &info);
353 return result;
354 }
355};
356
357Ref JpegEmbedder::embedImage(XRef *xref)
358{
359 if (m_fileContent == nullptr) {
360 return Ref::INVALID();
361 }
362 Dict *baseImageDict = createImageDict(xref, colorSpace: DEVICE_RGB, width: m_width, height: m_height, bitsPerComponent: 8);
363 baseImageDict->add(key: "Filter", val: Object(objName, "DCTDecode"));
364 Ref baseImageRef = xref->addStreamObject(dict: baseImageDict, buffer: m_fileContent.release(), bufferSize: m_fileSize, compression: StreamCompression::None);
365 return baseImageRef;
366}
367#endif
368
369Ref embed(XRef *xref, const GooFile &imageFile)
370{
371 // Load the image file.
372 const Goffset fileSize = imageFile.size();
373 if (fileSize < 0) {
374 error(category: errIO, pos: -1, msg: "Image file size could not be calculated");
375 return Ref::INVALID();
376 }
377 // GooFile::read only takes an integer so for now we don't support huge images
378 if (fileSize > std::numeric_limits<int>::max()) {
379 error(category: errIO, pos: -1, msg: "file size too big");
380 return Ref::INVALID();
381 }
382 std::unique_ptr<uint8_t[]> fileContent = std::make_unique<uint8_t[]>(num: fileSize);
383 const int bytesRead = imageFile.read(buf: (char *)fileContent.get(), n: static_cast<int>(fileSize), offset: 0);
384 if ((bytesRead != fileSize) || (fileSize < MAX_MAGIC_NUM_SIZE)) {
385 error(category: errIO, pos: -1, msg: "Couldn't load the image file");
386 return Ref::INVALID();
387 }
388
389 std::unique_ptr<ImageEmbedder> embedder;
390 if (checkMagicNum(fileContent: fileContent.get(), magicNum: PNG_MAGIC_NUM, size: sizeof(PNG_MAGIC_NUM))) {
391#ifdef ENABLE_LIBPNG
392 embedder = PngEmbedder::create(fileContent: std::move(fileContent), fileSize);
393#else
394 error(errUnimplemented, -1, "PNG format is not supported");
395#endif
396 } else if (checkMagicNum(fileContent: fileContent.get(), magicNum: JPEG_MAGIC_NUM, size: sizeof(JPEG_MAGIC_NUM))) {
397#ifdef ENABLE_LIBJPEG
398 embedder = JpegEmbedder::create(fileContent: std::move(fileContent), fileSize);
399#else
400 error(errUnimplemented, -1, "JPEG format is not supported");
401#endif
402 } else if (checkMagicNum(fileContent: fileContent.get(), magicNum: JPEG2000_MAGIC_NUM, size: sizeof(JPEG2000_MAGIC_NUM))) {
403 // TODO: implement JPEG2000 support using libopenjpeg2.
404 error(category: errUnimplemented, pos: -1, msg: "JPEG2000 format is not supported");
405 return Ref::INVALID();
406 } else {
407 error(category: errUnimplemented, pos: -1, msg: "Image format is not supported");
408 return Ref::INVALID();
409 }
410
411 if (!embedder) {
412 return Ref::INVALID();
413 }
414 return embedder->embedImage(xref);
415}
416
417Ref embed(XRef *xref, const std::string &imagePath)
418{
419 std::unique_ptr<GooFile> imageFile(GooFile::open(fileName: imagePath));
420 if (!imageFile) {
421 error(category: errIO, pos: -1, msg: "Couldn't open {0:s}", imagePath.c_str());
422 return Ref::INVALID();
423 }
424 return embed(xref, imageFile: *imageFile);
425}
426
427}
428

source code of poppler/poppler/ImageEmbeddingUtils.cc