comparison mupdf-source/thirdparty/zint/backend/png.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 /* png.c - Handles output to PNG file */
2 /*
3 libzint - the open source barcode library
4 Copyright (C) 2009-2024 Robin Stuart <rstuart114@gmail.com>
5
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9
10 1. Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in the
14 documentation and/or other materials provided with the distribution.
15 3. Neither the name of the project nor the names of its contributors
16 may be used to endorse or promote products derived from this software
17 without specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
23 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 SUCH DAMAGE.
30 */
31 /* SPDX-License-Identifier: BSD-3-Clause */
32
33 #ifndef ZINT_NO_PNG
34
35 #include <errno.h>
36 #include <math.h>
37 #include <stdio.h>
38 #include <png.h>
39 #include <zlib.h>
40 #include <setjmp.h>
41 #include "common.h"
42 #include "filemem.h"
43 #include "output.h"
44
45 /* Note using "wpng_" prefix not "png_" (except for `png_pixel_plot()`) to avoid clashing with libpng */
46
47 /* Note if change this need to change "backend/tests/test_png.c" definition also */
48 struct wpng_error_type {
49 struct zint_symbol *symbol;
50 jmp_buf jmpbuf;
51 };
52
53 static void wpng_error_handler(png_structp png_ptr, png_const_charp msg) {
54 struct wpng_error_type *wpng_error_ptr;
55
56 wpng_error_ptr = (struct wpng_error_type *) png_get_error_ptr(png_ptr);
57 if (wpng_error_ptr == NULL) {
58 /* we are completely hosed now */
59 fprintf(stderr, "Error 636: libpng error: %s\n", msg ? msg : "<NULL>");
60 fprintf(stderr, "Error 637: jmpbuf not recoverable, terminating\n");
61 fflush(stderr);
62 return; /* libpng will call abort() */
63 }
64 errtxtf(0, wpng_error_ptr->symbol, 635, "libpng error: %s", msg ? msg : "<NULL>");
65 longjmp(wpng_error_ptr->jmpbuf, 1);
66 }
67
68 #ifdef ZINT_TEST /* Wrapper for direct testing */
69 INTERNAL void wpng_error_handler_test(png_structp png_ptr, png_const_charp msg) {
70 wpng_error_handler(png_ptr, msg);
71 }
72 #endif
73
74 /* libpng write callback */
75 static void wpng_write(png_structp png_ptr, png_bytep ptr, size_t size) {
76 struct filemem *fmp = (struct filemem *) png_get_io_ptr(png_ptr);
77 (void) fm_write(ptr, 1, size, fmp);
78 }
79
80 /* libpng flush callback */
81 static void wpng_flush(png_structp png_ptr) {
82 struct filemem *fmp = (struct filemem *) png_get_io_ptr(png_ptr);
83 (void) fm_flush(fmp);
84 }
85
86 /* Guestimate best compression strategy */
87 static int wpng_guess_compression_strategy(struct zint_symbol *symbol, const unsigned char *pixelbuf) {
88 (void)pixelbuf;
89
90 /* TODO: Do properly */
91
92 /* It seems the best choice for typical barcode pngs is one of Z_DEFAULT_STRATEGY and Z_FILTERED */
93
94 /* Some guesses */
95 if (symbol->symbology == BARCODE_MAXICODE) {
96 return Z_DEFAULT_STRATEGY;
97 }
98 if (symbol->symbology == BARCODE_AZTEC && symbol->bitmap_width <= 30) {
99 return Z_DEFAULT_STRATEGY;
100 }
101
102 /* Z_FILTERED seems to work better for slightly more barcodes+data so default to that */
103 return Z_FILTERED;
104 }
105
106 INTERNAL int png_pixel_plot(struct zint_symbol *symbol, const unsigned char *pixelbuf) {
107 struct wpng_error_type wpng_error;
108 struct filemem fm;
109 struct filemem *const fmp = &fm;
110 png_structp png_ptr;
111 png_infop info_ptr;
112 int i;
113 int row, column;
114 png_color bg, fg;
115 unsigned char bg_alpha, fg_alpha;
116 unsigned char map[128];
117 png_color palette[32];
118 int num_palette;
119 unsigned char trans_alpha[32];
120 int num_trans; /* Note initialize below to avoid gcc -Wclobbered warning due to `longjmp()` */
121 int bit_depth;
122 int compression_strategy;
123 const unsigned char *pb;
124 unsigned char *outdata = (unsigned char *) z_alloca(symbol->bitmap_width);
125
126 wpng_error.symbol = symbol;
127
128 (void) out_colour_get_rgb(symbol->fgcolour, &fg.red, &fg.green, &fg.blue, &fg_alpha);
129 (void) out_colour_get_rgb(symbol->bgcolour, &bg.red, &bg.green, &bg.blue, &bg_alpha);
130
131 num_trans = 0;
132 if (symbol->symbology == BARCODE_ULTRA) {
133 static const unsigned char ultra_chars[8] = { 'W', 'C', 'B', 'M', 'R', 'Y', 'G', 'K' };
134 for (i = 0; i < 8; i++) {
135 map[ultra_chars[i]] = i;
136 out_colour_char_to_rgb(ultra_chars[i], &palette[i].red, &palette[i].green, &palette[i].blue);
137 if (fg_alpha != 0xff) {
138 trans_alpha[i] = fg_alpha;
139 }
140 }
141 num_palette = 8;
142 if (fg_alpha != 0xff) {
143 num_trans = 8;
144 }
145
146 /* For Ultracode, have foreground only if have bind/box */
147 if (symbol->border_width > 0 && (symbol->output_options & (BARCODE_BIND | BARCODE_BOX | BARCODE_BIND_TOP))) {
148 /* Check whether can re-use black */
149 if (fg.red == 0 && fg.green == 0 && fg.blue == 0) {
150 map['1'] = 7; /* Re-use black */
151 } else {
152 map['1'] = num_palette;
153 palette[num_palette++] = fg;
154 if (fg_alpha != 0xff) {
155 trans_alpha[num_trans++] = fg_alpha;
156 }
157 }
158 }
159
160 /* For Ultracode, have background only if have whitespace/quiet zones */
161 if (symbol->whitespace_width > 0 || symbol->whitespace_height > 0
162 || ((symbol->output_options & BARCODE_QUIET_ZONES)
163 && !(symbol->output_options & BARCODE_NO_QUIET_ZONES))) {
164 /* Check whether can re-use white */
165 if (bg.red == 0xff && bg.green == 0xff && bg.blue == 0xff && bg_alpha == fg_alpha) {
166 map['0'] = 0; /* Re-use white */
167 } else {
168 if (bg_alpha == 0xff || fg_alpha != 0xff) {
169 /* No alpha or have foreground alpha - add to end */
170 map['0'] = num_palette;
171 palette[num_palette++] = bg;
172 } else {
173 /* Alpha and no foreground alpha - add to front & move white to end */
174 png_color white = palette[0]; /* Take copy */
175 map['0'] = 0;
176 palette[0] = bg;
177 map['W'] = num_palette;
178 palette[num_palette++] = white;
179 }
180 if (bg_alpha != 0xff) {
181 trans_alpha[num_trans++] = bg_alpha;
182 }
183 }
184 }
185 } else {
186 int bg_idx = 0, fg_idx = 1;
187 /* Do alphas first so can swop indexes if background not alpha */
188 if (bg_alpha != 0xff) {
189 trans_alpha[num_trans++] = bg_alpha;
190 }
191 if (fg_alpha != 0xff) {
192 trans_alpha[num_trans++] = fg_alpha;
193 if (num_trans == 1) {
194 /* Only foreground has alpha so swop indexes - saves a byte! */
195 bg_idx = 1;
196 fg_idx = 0;
197 }
198 }
199
200 map['0'] = bg_idx;
201 palette[bg_idx] = bg;
202 map['1'] = fg_idx;
203 palette[fg_idx] = fg;
204 num_palette = 2;
205 }
206
207 if (num_palette <= 2) {
208 bit_depth = 1;
209 } else {
210 bit_depth = 4;
211 }
212
213 /* Open output file in binary mode */
214 if (!fm_open(fmp, symbol, "wb")) {
215 return errtxtf(ZINT_ERROR_FILE_ACCESS, symbol, 632, "Could not open PNG output file (%1$d: %2$s)", fmp->err,
216 strerror(fmp->err));
217 }
218
219 /* Set up error handling routine as proc() above */
220 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, &wpng_error, wpng_error_handler, NULL);
221 if (!png_ptr) {
222 (void) fm_close(fmp, symbol);
223 return errtxt(ZINT_ERROR_MEMORY, symbol, 633, "Insufficient memory for PNG write structure buffer");
224 }
225
226 info_ptr = png_create_info_struct(png_ptr);
227 if (!info_ptr) {
228 png_destroy_write_struct(&png_ptr, NULL);
229 (void) fm_close(fmp, symbol);
230 return errtxt(ZINT_ERROR_MEMORY, symbol, 634, "Insufficient memory for PNG info structure buffer");
231 }
232
233 /* catch jumping here */
234 if (setjmp(wpng_error.jmpbuf)) {
235 png_destroy_write_struct(&png_ptr, &info_ptr);
236 (void) fm_close(fmp, symbol);
237 return ZINT_ERROR_MEMORY;
238 }
239
240 /* Set our output functions */
241 png_set_write_fn(png_ptr, fmp, wpng_write, wpng_flush);
242
243 /* set compression */
244 png_set_compression_level(png_ptr, 9);
245
246 /* Compression strategy can make a difference */
247 compression_strategy = wpng_guess_compression_strategy(symbol, pixelbuf);
248 if (compression_strategy != Z_DEFAULT_STRATEGY) {
249 png_set_compression_strategy(png_ptr, compression_strategy);
250 }
251
252 if (symbol->dpmm) {
253 int resolution = (int) roundf(stripf(symbol->dpmm * 1000.0f)); /* pixels per metre */
254 png_set_pHYs(png_ptr, info_ptr, resolution, resolution, PNG_RESOLUTION_METER);
255 }
256
257 /* set Header block */
258 png_set_IHDR(png_ptr, info_ptr, symbol->bitmap_width, symbol->bitmap_height,
259 bit_depth, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
260 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
261
262 png_set_PLTE(png_ptr, info_ptr, palette, num_palette);
263 if (num_trans) {
264 png_set_tRNS(png_ptr, info_ptr, trans_alpha, num_trans, NULL);
265 }
266
267 /* write all chunks up to (but not including) first IDAT */
268 png_write_info(png_ptr, info_ptr);
269
270 /* Pixel Plotting */
271 pb = pixelbuf;
272 if (bit_depth == 1) {
273 for (row = 0; row < symbol->bitmap_height; row++) {
274 if (row && memcmp(pb, pb - symbol->bitmap_width, symbol->bitmap_width) == 0) {
275 pb += symbol->bitmap_width;
276 } else {
277 unsigned char *image_data = outdata;
278 for (column = 0; column < symbol->bitmap_width; column += 8, image_data++) {
279 unsigned char byte = 0;
280 for (i = 0; i < 8 && column + i < symbol->bitmap_width; i++, pb++) {
281 byte |= map[*pb] << (7 - i);
282 }
283 *image_data = byte;
284 }
285 }
286 /* write row contents to file */
287 png_write_row(png_ptr, outdata);
288 }
289 } else { /* Bit depth 4 */
290 for (row = 0; row < symbol->bitmap_height; row++) {
291 if (row && memcmp(pb, pb - symbol->bitmap_width, symbol->bitmap_width) == 0) {
292 pb += symbol->bitmap_width;
293 } else {
294 unsigned char *image_data = outdata;
295 for (column = 0; column < symbol->bitmap_width; column += 2, image_data++) {
296 unsigned char byte = map[*pb++] << 4;
297 if (column + 1 < symbol->bitmap_width) {
298 byte |= map[*pb++];
299 }
300 *image_data = byte;
301 }
302 }
303 /* write row contents to file */
304 png_write_row(png_ptr, outdata);
305 }
306 }
307
308 /* End the file */
309 png_write_end(png_ptr, NULL);
310
311 /* make sure we have disengaged */
312 png_destroy_write_struct(&png_ptr, &info_ptr);
313
314 if (fm_error(fmp)) {
315 errtxtf(0, symbol, 638, "Incomplete write of PNG output (%1$d: %2$s)", fmp->err, strerror(fmp->err));
316 (void) fm_close(fmp, symbol);
317 return ZINT_ERROR_FILE_WRITE;
318 }
319
320 if (!fm_close(fmp, symbol)) {
321 return errtxtf(ZINT_ERROR_FILE_WRITE, symbol, 960, "Failure on closing PNG output file (%1$d: %2$s)",
322 fmp->err, strerror(fmp->err));
323 }
324
325 return 0;
326 }
327 /* vim: set ts=4 sw=4 et : */
328 #else
329 #if defined(__clang__)
330 /* Suppresses clang-tidy-18 "clang-diagnostic-empty-translation-unit" */
331 typedef int wpng_make_clang_tidy_compilers_happy;
332 #endif
333 #endif /* ZINT_NO_PNG */