/src/mupdf/source/fitz/output-pnm.c
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (C) 2004-2023 Artifex Software, Inc. |
2 | | // |
3 | | // This file is part of MuPDF. |
4 | | // |
5 | | // MuPDF is free software: you can redistribute it and/or modify it under the |
6 | | // terms of the GNU Affero General Public License as published by the Free |
7 | | // Software Foundation, either version 3 of the License, or (at your option) |
8 | | // any later version. |
9 | | // |
10 | | // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY |
11 | | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
12 | | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more |
13 | | // details. |
14 | | // |
15 | | // You should have received a copy of the GNU Affero General Public License |
16 | | // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> |
17 | | // |
18 | | // Alternative licensing terms are available from the licensor. |
19 | | // For commercial licensing, see <https://www.artifex.com/> or contact |
20 | | // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
21 | | // CA 94129, USA, for further information. |
22 | | |
23 | | #include "mupdf/fitz.h" |
24 | | |
25 | | /* |
26 | | * Write pixmap to PNM file (without alpha channel) |
27 | | */ |
28 | | static void |
29 | | pnm_write_header(fz_context *ctx, fz_band_writer *writer, fz_colorspace *cs) |
30 | 0 | { |
31 | 0 | fz_output *out = writer->out; |
32 | 0 | int w = writer->w; |
33 | 0 | int h = writer->h; |
34 | 0 | int n = writer->n; |
35 | 0 | int alpha = writer->alpha; |
36 | |
|
37 | 0 | if (writer->s != 0) |
38 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "PNM writer cannot cope with spot colors"); |
39 | 0 | if (cs && !fz_colorspace_is_gray(ctx, cs) && !fz_colorspace_is_rgb(ctx, cs)) |
40 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "pixmap must be grayscale or rgb to write as pnm"); |
41 | | |
42 | | /* Treat alpha only as greyscale */ |
43 | 0 | if (n == 1 && alpha) |
44 | 0 | alpha = 0; |
45 | 0 | n -= alpha; |
46 | |
|
47 | 0 | if (alpha) |
48 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "PNM writer cannot cope with alpha"); |
49 | | |
50 | 0 | if (n == 1) |
51 | 0 | fz_write_printf(ctx, out, "P5\n"); |
52 | 0 | if (n == 3) |
53 | 0 | fz_write_printf(ctx, out, "P6\n"); |
54 | 0 | fz_write_printf(ctx, out, "%d %d\n", w, h); |
55 | 0 | fz_write_printf(ctx, out, "255\n"); |
56 | 0 | } |
57 | | |
58 | | static void |
59 | | pnm_write_band(fz_context *ctx, fz_band_writer *writer, int stride, int band_start, int band_height, const unsigned char *p) |
60 | 0 | { |
61 | 0 | fz_output *out = writer->out; |
62 | 0 | int w = writer->w; |
63 | 0 | int h = writer->h; |
64 | 0 | int n = writer->n; |
65 | 0 | int len; |
66 | 0 | int end = band_start + band_height; |
67 | |
|
68 | 0 | if (n != 1 && n != 3) |
69 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "pixmap must be grayscale or rgb to write as pnm"); |
70 | | |
71 | 0 | if (!out) |
72 | 0 | return; |
73 | | |
74 | 0 | if (end > h) |
75 | 0 | end = h; |
76 | 0 | end -= band_start; |
77 | | |
78 | | /* Tests show that writing single bytes out at a time |
79 | | * is appallingly slow. We get a huge improvement |
80 | | * by collating stuff into buffers first. */ |
81 | |
|
82 | 0 | while (end--) |
83 | 0 | { |
84 | 0 | len = w; |
85 | 0 | while (len) |
86 | 0 | { |
87 | 0 | int num_written = len; |
88 | |
|
89 | 0 | switch (n) |
90 | 0 | { |
91 | 0 | case 1: |
92 | | /* No collation required */ |
93 | 0 | fz_write_data(ctx, out, p, num_written); |
94 | 0 | p += num_written; |
95 | 0 | break; |
96 | 0 | case 3: |
97 | 0 | fz_write_data(ctx, out, p, num_written*3); |
98 | 0 | p += num_written*3; |
99 | 0 | break; |
100 | 0 | } |
101 | 0 | len -= num_written; |
102 | 0 | } |
103 | 0 | p += stride - w*n; |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | | fz_band_writer *fz_new_pnm_band_writer(fz_context *ctx, fz_output *out) |
108 | 0 | { |
109 | 0 | fz_band_writer *writer = fz_new_band_writer(ctx, fz_band_writer, out); |
110 | |
|
111 | 0 | writer->header = pnm_write_header; |
112 | 0 | writer->band = pnm_write_band; |
113 | |
|
114 | 0 | return writer; |
115 | 0 | } |
116 | | |
117 | | void |
118 | | fz_write_pixmap_as_pnm(fz_context *ctx, fz_output *out, fz_pixmap *pixmap) |
119 | 0 | { |
120 | 0 | fz_band_writer *writer = fz_new_pnm_band_writer(ctx, out); |
121 | 0 | fz_try(ctx) |
122 | 0 | { |
123 | 0 | fz_write_header(ctx, writer, pixmap->w, pixmap->h, pixmap->n, pixmap->alpha, 0, 0, 0, pixmap->colorspace, pixmap->seps); |
124 | 0 | fz_write_band(ctx, writer, pixmap->stride, pixmap->h, pixmap->samples); |
125 | 0 | fz_close_band_writer(ctx, writer); |
126 | 0 | } |
127 | 0 | fz_always(ctx) |
128 | 0 | fz_drop_band_writer(ctx, writer); |
129 | 0 | fz_catch(ctx) |
130 | 0 | fz_rethrow(ctx); |
131 | 0 | } |
132 | | |
133 | | void |
134 | | fz_save_pixmap_as_pnm(fz_context *ctx, fz_pixmap *pixmap, const char *filename) |
135 | 0 | { |
136 | 0 | fz_band_writer *writer = NULL; |
137 | 0 | fz_output *out = fz_new_output_with_path(ctx, filename, 0); |
138 | |
|
139 | 0 | fz_var(writer); |
140 | |
|
141 | 0 | fz_try(ctx) |
142 | 0 | { |
143 | 0 | writer = fz_new_pnm_band_writer(ctx, out); |
144 | 0 | fz_write_header(ctx, writer, pixmap->w, pixmap->h, pixmap->n, pixmap->alpha, 0, 0, 0, pixmap->colorspace, pixmap->seps); |
145 | 0 | fz_write_band(ctx, writer, pixmap->stride, pixmap->h, pixmap->samples); |
146 | 0 | fz_close_band_writer(ctx, writer); |
147 | 0 | fz_close_output(ctx, out); |
148 | 0 | } |
149 | 0 | fz_always(ctx) |
150 | 0 | { |
151 | 0 | fz_drop_band_writer(ctx, writer); |
152 | 0 | fz_drop_output(ctx, out); |
153 | 0 | } |
154 | 0 | fz_catch(ctx) |
155 | 0 | fz_rethrow(ctx); |
156 | 0 | } |
157 | | |
158 | | /* |
159 | | * Write pixmap to PAM file (with or without alpha channel) |
160 | | */ |
161 | | |
162 | | static void |
163 | | pam_write_header(fz_context *ctx, fz_band_writer *writer, fz_colorspace *cs) |
164 | 0 | { |
165 | 0 | fz_output *out = writer->out; |
166 | 0 | int w = writer->w; |
167 | 0 | int h = writer->h; |
168 | 0 | int n = writer->n; |
169 | 0 | int alpha = writer->alpha; |
170 | |
|
171 | 0 | if (writer->s != 0) |
172 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "PAM writer cannot cope with spot colors"); |
173 | | |
174 | 0 | fz_write_printf(ctx, out, "P7\n"); |
175 | 0 | fz_write_printf(ctx, out, "WIDTH %d\n", w); |
176 | 0 | fz_write_printf(ctx, out, "HEIGHT %d\n", h); |
177 | 0 | fz_write_printf(ctx, out, "DEPTH %d\n", n); |
178 | 0 | fz_write_printf(ctx, out, "MAXVAL 255\n"); |
179 | |
|
180 | 0 | n -= alpha; |
181 | |
|
182 | 0 | if (n == 0 && alpha) fz_write_printf(ctx, out, "TUPLTYPE GRAYSCALE\n"); |
183 | 0 | else if (n == 1 && !alpha && fz_colorspace_is_gray(ctx, cs)) fz_write_printf(ctx, out, "TUPLTYPE GRAYSCALE\n"); |
184 | 0 | else if (n == 1 && alpha && fz_colorspace_is_gray(ctx, cs)) fz_write_printf(ctx, out, "TUPLTYPE GRAYSCALE_ALPHA\n"); |
185 | 0 | else if (n == 3 && !alpha && fz_colorspace_is_rgb(ctx, cs)) fz_write_printf(ctx, out, "TUPLTYPE RGB\n"); |
186 | 0 | else if (n == 3 && alpha && fz_colorspace_is_rgb(ctx, cs)) fz_write_printf(ctx, out, "TUPLTYPE RGB_ALPHA\n"); |
187 | 0 | else if (n == 4 && !alpha && fz_colorspace_is_cmyk(ctx, cs)) fz_write_printf(ctx, out, "TUPLTYPE CMYK\n"); |
188 | 0 | else if (n == 4 && alpha && fz_colorspace_is_cmyk(ctx, cs)) fz_write_printf(ctx, out, "TUPLTYPE CMYK_ALPHA\n"); |
189 | 0 | else |
190 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "pixmap must be alpha only, gray, rgb, or cmyk"); |
191 | 0 | fz_write_printf(ctx, out, "ENDHDR\n"); |
192 | 0 | } |
193 | | |
194 | | static void |
195 | | pam_write_band(fz_context *ctx, fz_band_writer *writer, int stride, int band_start, int band_height, const unsigned char *sp) |
196 | 0 | { |
197 | 0 | fz_output *out = writer->out; |
198 | 0 | int w = writer->w; |
199 | 0 | int h = writer->h; |
200 | 0 | int n = writer->n; |
201 | 0 | int alpha = writer->alpha; |
202 | 0 | int x, y; |
203 | 0 | int end = band_start + band_height; |
204 | |
|
205 | 0 | if (!out) |
206 | 0 | return; |
207 | | |
208 | 0 | if (end > h) |
209 | 0 | end = h; |
210 | 0 | end -= band_start; |
211 | |
|
212 | 0 | if (alpha) |
213 | 0 | { |
214 | | /* Buffer must be a multiple of 2, 3 and 5 at least. */ |
215 | | /* Also, for the generic case, it must be bigger than FZ_MAX_COLORS */ |
216 | 0 | char buffer[2*3*4*5*6]; |
217 | 0 | char *b = buffer; |
218 | 0 | stride -= n * w; |
219 | 0 | switch (n) |
220 | 0 | { |
221 | 0 | case 2: |
222 | 0 | for (y = 0; y < end; y++) |
223 | 0 | { |
224 | 0 | for (x = 0; x < w; x++) |
225 | 0 | { |
226 | 0 | int a = sp[1]; |
227 | 0 | *b++ = a ? (sp[0] * 255 + (a>>1))/a : 0; |
228 | 0 | *b++ = a; |
229 | 0 | sp += 2; |
230 | 0 | if (b == &buffer[sizeof(buffer)]) |
231 | 0 | { |
232 | 0 | fz_write_data(ctx, out, buffer, sizeof(buffer)); |
233 | 0 | b = buffer; |
234 | 0 | } |
235 | 0 | } |
236 | 0 | sp += stride; |
237 | 0 | } |
238 | 0 | if (b != buffer) |
239 | 0 | fz_write_data(ctx, out, buffer, b - buffer); |
240 | 0 | break; |
241 | 0 | case 4: |
242 | 0 | for (y = 0; y < end; y++) |
243 | 0 | { |
244 | 0 | for (x = 0; x < w; x++) |
245 | 0 | { |
246 | 0 | int a = sp[3]; |
247 | 0 | int inva = a ? 256 * 255 / a : 0; |
248 | 0 | *b++ = (sp[0] * inva + 128)>>8; |
249 | 0 | *b++ = (sp[1] * inva + 128)>>8; |
250 | 0 | *b++ = (sp[2] * inva + 128)>>8; |
251 | 0 | *b++ = a; |
252 | 0 | sp += 4; |
253 | 0 | if (b == &buffer[sizeof(buffer)]) |
254 | 0 | { |
255 | 0 | fz_write_data(ctx, out, buffer, sizeof(buffer)); |
256 | 0 | b = buffer; |
257 | 0 | } |
258 | 0 | } |
259 | 0 | sp += stride; |
260 | 0 | } |
261 | 0 | if (b != buffer) |
262 | 0 | fz_write_data(ctx, out, buffer, b - buffer); |
263 | 0 | break; |
264 | 0 | case 5: |
265 | 0 | for (y = 0; y < end; y++) |
266 | 0 | { |
267 | 0 | for (x = 0; x < w; x++) |
268 | 0 | { |
269 | 0 | int a = sp[4]; |
270 | 0 | int inva = a ? 256 * 255 / a : 0; |
271 | 0 | *b++ = (sp[0] * inva + 128)>>8; |
272 | 0 | *b++ = (sp[1] * inva + 128)>>8; |
273 | 0 | *b++ = (sp[2] * inva + 128)>>8; |
274 | 0 | *b++ = (sp[3] * inva + 128)>>8; |
275 | 0 | *b++ = a; |
276 | 0 | sp += 5; |
277 | 0 | if (b == &buffer[sizeof(buffer)]) |
278 | 0 | { |
279 | 0 | fz_write_data(ctx, out, buffer, sizeof(buffer)); |
280 | 0 | b = buffer; |
281 | 0 | } |
282 | 0 | } |
283 | 0 | sp += stride; |
284 | 0 | } |
285 | 0 | if (b != buffer) |
286 | 0 | fz_write_data(ctx, out, buffer, b - buffer); |
287 | 0 | break; |
288 | 0 | default: |
289 | 0 | for (y = 0; y < end; y++) |
290 | 0 | { |
291 | 0 | for (x = 0; x < w; x++) |
292 | 0 | { |
293 | 0 | int a = sp[n-1]; |
294 | 0 | int inva = a ? 256 * 255 / a : 0; |
295 | 0 | int k; |
296 | 0 | for (k = 0; k < n-1; k++) |
297 | 0 | *b++ = (*sp++ * inva + 128)>>8; |
298 | 0 | *b++ = a; |
299 | 0 | sp++; |
300 | 0 | if (b >= &buffer[sizeof(buffer)] - n) |
301 | 0 | { |
302 | 0 | fz_write_data(ctx, out, buffer, b - buffer); |
303 | 0 | b = buffer; |
304 | 0 | } |
305 | 0 | } |
306 | 0 | sp += stride; |
307 | 0 | } |
308 | 0 | if (b != buffer) |
309 | 0 | fz_write_data(ctx, out, buffer, b - buffer); |
310 | 0 | break; |
311 | 0 | } |
312 | 0 | } |
313 | 0 | else |
314 | 0 | for (y = 0; y < end; y++) |
315 | 0 | { |
316 | 0 | fz_write_data(ctx, out, sp, (size_t)w * n); |
317 | 0 | sp += stride; |
318 | 0 | } |
319 | 0 | } |
320 | | |
321 | | fz_band_writer *fz_new_pam_band_writer(fz_context *ctx, fz_output *out) |
322 | 0 | { |
323 | 0 | fz_band_writer *writer = fz_new_band_writer(ctx, fz_band_writer, out); |
324 | |
|
325 | 0 | writer->header = pam_write_header; |
326 | 0 | writer->band = pam_write_band; |
327 | |
|
328 | 0 | return writer; |
329 | 0 | } |
330 | | |
331 | | void |
332 | | fz_write_pixmap_as_pam(fz_context *ctx, fz_output *out, fz_pixmap *pixmap) |
333 | 0 | { |
334 | 0 | fz_band_writer *writer = fz_new_pam_band_writer(ctx, out); |
335 | 0 | fz_try(ctx) |
336 | 0 | { |
337 | 0 | fz_write_header(ctx, writer, pixmap->w, pixmap->h, pixmap->n, pixmap->alpha, 0, 0, 0, pixmap->colorspace, pixmap->seps); |
338 | 0 | fz_write_band(ctx, writer, pixmap->stride, pixmap->h, pixmap->samples); |
339 | 0 | fz_close_band_writer(ctx, writer); |
340 | 0 | } |
341 | 0 | fz_always(ctx) |
342 | 0 | fz_drop_band_writer(ctx, writer); |
343 | 0 | fz_catch(ctx) |
344 | 0 | fz_rethrow(ctx); |
345 | 0 | } |
346 | | |
347 | | void |
348 | | fz_save_pixmap_as_pam(fz_context *ctx, fz_pixmap *pixmap, const char *filename) |
349 | 0 | { |
350 | 0 | fz_band_writer *writer = NULL; |
351 | 0 | fz_output *out = fz_new_output_with_path(ctx, filename, 0); |
352 | |
|
353 | 0 | fz_var(writer); |
354 | |
|
355 | 0 | fz_try(ctx) |
356 | 0 | { |
357 | 0 | writer = fz_new_pam_band_writer(ctx, out); |
358 | 0 | fz_write_header(ctx, writer, pixmap->w, pixmap->h, pixmap->n, pixmap->alpha, 0, 0, 0, pixmap->colorspace, pixmap->seps); |
359 | 0 | fz_write_band(ctx, writer, pixmap->stride, pixmap->h, pixmap->samples); |
360 | 0 | fz_close_band_writer(ctx, writer); |
361 | 0 | fz_close_output(ctx, out); |
362 | 0 | } |
363 | 0 | fz_always(ctx) |
364 | 0 | { |
365 | 0 | fz_drop_band_writer(ctx, writer); |
366 | 0 | fz_drop_output(ctx, out); |
367 | 0 | } |
368 | 0 | fz_catch(ctx) |
369 | 0 | fz_rethrow(ctx); |
370 | 0 | } |
371 | | |
372 | | static fz_buffer * |
373 | | buffer_from_pixmap(fz_context *ctx, fz_pixmap *pix, fz_color_params color_params, int drop, |
374 | | void (*do_write)(fz_context *ctx, fz_output *out, fz_pixmap *pix)) |
375 | 0 | { |
376 | 0 | fz_buffer *buf = NULL; |
377 | 0 | fz_output *out = NULL; |
378 | |
|
379 | 0 | fz_var(buf); |
380 | 0 | fz_var(out); |
381 | |
|
382 | 0 | fz_try(ctx) |
383 | 0 | { |
384 | 0 | buf = fz_new_buffer(ctx, 1024); |
385 | 0 | out = fz_new_output_with_buffer(ctx, buf); |
386 | 0 | do_write(ctx, out, pix); |
387 | 0 | fz_close_output(ctx, out); |
388 | 0 | } |
389 | 0 | fz_always(ctx) |
390 | 0 | { |
391 | 0 | if (drop) |
392 | 0 | fz_drop_pixmap(ctx, pix); |
393 | 0 | fz_drop_output(ctx, out); |
394 | 0 | } |
395 | 0 | fz_catch(ctx) |
396 | 0 | { |
397 | 0 | fz_drop_buffer(ctx, buf); |
398 | 0 | fz_rethrow(ctx); |
399 | 0 | } |
400 | 0 | return buf; |
401 | 0 | } |
402 | | |
403 | | fz_buffer * |
404 | | fz_new_buffer_from_image_as_pnm(fz_context *ctx, fz_image *image, fz_color_params color_params) |
405 | 0 | { |
406 | 0 | fz_pixmap *pix = fz_get_pixmap_from_image(ctx, image, NULL, NULL, NULL, NULL); |
407 | 0 | return buffer_from_pixmap(ctx, pix, color_params, 1, fz_write_pixmap_as_pnm); |
408 | 0 | } |
409 | | |
410 | | fz_buffer * |
411 | | fz_new_buffer_from_pixmap_as_pnm(fz_context *ctx, fz_pixmap *pix, fz_color_params color_params) |
412 | 0 | { |
413 | 0 | return buffer_from_pixmap(ctx, pix, color_params, 0, fz_write_pixmap_as_pnm); |
414 | 0 | } |
415 | | |
416 | | fz_buffer * |
417 | | fz_new_buffer_from_image_as_pam(fz_context *ctx, fz_image *image, fz_color_params color_params) |
418 | 0 | { |
419 | 0 | fz_pixmap *pix = fz_get_pixmap_from_image(ctx, image, NULL, NULL, NULL, NULL); |
420 | 0 | return buffer_from_pixmap(ctx, pix, color_params, 1, fz_write_pixmap_as_pam); |
421 | 0 | } |
422 | | |
423 | | fz_buffer * |
424 | | fz_new_buffer_from_pixmap_as_pam(fz_context *ctx, fz_pixmap *pix, fz_color_params color_params) |
425 | 0 | { |
426 | 0 | return buffer_from_pixmap(ctx, pix, color_params, 0, fz_write_pixmap_as_pam); |
427 | 0 | } |