comparison mupdf-source/source/pdf/pdf-image-rewriter.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 // Copyright (C) 2004-2025 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 #include "mupdf/pdf.h"
25
26 #include "../fitz/pixmap-imp.h"
27
28 #include <string.h>
29 #include <math.h>
30
31 typedef struct
32 {
33 int num;
34 int gen;
35 float dpi;
36 } image_details;
37
38 typedef struct
39 {
40 int max;
41 int len;
42 int *uimg;
43 } image_list;
44
45 typedef struct
46 {
47 int max;
48 int len;
49 image_details *img;
50 } unique_image_list;
51
52 typedef struct
53 {
54 image_list list;
55 unique_image_list uilist;
56 pdf_image_rewriter_options *opts;
57 int which;
58 } image_info;
59
60 static float
61 dpi_from_ctm(fz_matrix ctm, int w, int h)
62 {
63 float expx = sqrtf(ctm.a * ctm.a + ctm.b * ctm.b);
64 float expy = sqrtf(ctm.c * ctm.c + ctm.d * ctm.d);
65 float dpix = w * 72.0f / (expx == 0 ? 1 : expx);
66 float dpiy = h * 72.0f / (expy == 0 ? 1 : expy);
67
68 if (dpix > dpiy)
69 return dpiy;
70
71 return dpix;
72 }
73
74 static void
75 gather_image_rewrite(fz_context *ctx, void *opaque, fz_image **image, fz_matrix ctm, pdf_obj *im_obj)
76 {
77 image_info *info = (image_info *)opaque;
78 image_list *ilist = &info->list;
79 unique_image_list *uilist = &info->uilist;
80 int i, num, gen;
81 float dpi;
82
83 if (im_obj == NULL)
84 return; /* Inline image, don't need to pregather that. */
85
86 num = pdf_to_num(ctx, im_obj);
87 gen = pdf_to_gen(ctx, im_obj);
88
89 dpi = dpi_from_ctm(ctm, (*image)->w, (*image)->h);
90
91 /* Find this in the unique image list */
92 for (i = 0; i < uilist->len; i++)
93 {
94 /* Found one already. Keep the smaller of the dpi's. */
95 if (uilist->img[i].num == num &&
96 uilist->img[i].gen == gen)
97 {
98 if (dpi < uilist->img[i].dpi)
99 uilist->img[i].dpi = dpi;
100 break;
101 }
102 }
103
104 if (i == uilist->len)
105 {
106 /* Need to add a new unique image. */
107 if (uilist->max == uilist->len)
108 {
109 int max2 = uilist->max * 2;
110 if (max2 == 0)
111 max2 = 32; /* Arbitrary */
112 uilist->img = fz_realloc(ctx, uilist->img, max2 * sizeof(*uilist->img));
113 uilist->max = max2;
114 }
115
116 uilist->img[uilist->len].num = num;
117 uilist->img[uilist->len].gen = gen;
118 uilist->img[uilist->len].dpi = dpi;
119 uilist->len++;
120 }
121
122 /* Now we need to add an entry in the unique image list saying that entry n in
123 * that list corresponds to the ith unique image. */
124 if (ilist->max == ilist->len)
125 {
126 int max2 = ilist->max * 2;
127 if (max2 == 0)
128 max2 = 32; /* Arbitrary */
129 ilist->uimg = fz_realloc(ctx, ilist->uimg, max2 * sizeof(*ilist->uimg));
130 ilist->max = max2;
131 }
132
133 ilist->uimg[ilist->len++] = i;
134 }
135
136 typedef enum
137 {
138 IMAGE_COLOR = 0,
139 IMAGE_GRAY,
140 IMAGE_BITONAL
141 } image_type;
142
143 static image_type
144 classify_pixmap(fz_context *ctx, fz_pixmap *pix)
145 {
146 /* For now, spots means color. In future we could check to
147 * see if all the spots were 0? */
148 if (pix->s)
149 return IMAGE_COLOR;
150
151 if (fz_colorspace_is_gray(ctx, pix->colorspace))
152 {
153 int n = pix->n;
154 int h = pix->h;
155 ptrdiff_t span_step = pix->stride - pix->w * n;
156 const unsigned char *s = pix->samples;
157
158 /* Loop until we know it's not bitonal */
159 if (pix->alpha)
160 {
161 while (h--)
162 {
163 int w = pix->w;
164
165 while (w--)
166 {
167 if (s[1] == 0)
168 {
169 /* If alpha is zero, other components don't matter. */
170 }
171 else if (s[0] != 0 && s[0] != 255)
172 return IMAGE_GRAY;
173 s += 2;
174 }
175 s += span_step;
176 }
177 return IMAGE_BITONAL;
178 }
179 else
180 {
181 while (h--)
182 {
183 int w = pix->w;
184
185 while (w--)
186 {
187 if (s[0] != 0 && s[0] != 255)
188 return IMAGE_GRAY;
189 s++;
190 }
191 s += span_step;
192 }
193 return IMAGE_BITONAL;
194 }
195 }
196 else if (fz_colorspace_is_rgb(ctx, pix->colorspace))
197 {
198 int n = pix->n;
199 int h = pix->h;
200 int w;
201 ptrdiff_t span_step = pix->stride - pix->w * n;
202 const unsigned char *s = pix->samples;
203
204 /* Is this safe, cos of profiles? */
205 if (pix->alpha)
206 {
207 /* Loop until we know it's not bitonal */
208 while (h--)
209 {
210 w = pix->w;
211 while (w--)
212 {
213 if (s[3] == 0)
214 {
215 /* If alpha is zero, other components don't matter. */
216 }
217 else if (s[0] == s[1] && s[0] == s[2])
218 {
219 /* Plausibly gray */
220 if (s[0] != 0 && s[0] != 255)
221 goto rgba_not_bitonal; /* But not bitonal */
222 if (s[3] != 0 && s[3] != 255)
223 goto rgba_not_bitonal;
224 }
225 else
226 return IMAGE_COLOR;
227 s += n;
228 }
229 s += span_step;
230 }
231 return IMAGE_BITONAL;
232
233 /* Loop until we know it's not gray */
234 while (h--)
235 {
236 w = pix->w;
237
238 while (w--)
239 {
240 if (s[3] == 0)
241 {
242 /* If alpha is zero, other components don't matter. */
243 }
244 else if (s[0] != s[1] || s[0] != s[2])
245 return IMAGE_COLOR;
246 rgba_not_bitonal:
247 s += n;
248 }
249 s += span_step;
250 }
251 return IMAGE_GRAY;
252 }
253 else
254 {
255 /* Loop until we know it's not bitonal */
256 while (h--)
257 {
258 w = pix->w;
259 while (w--)
260 {
261 if (s[0] == s[1] && s[0] == s[2])
262 {
263 if (s[0] != 0 && s[0] != 255)
264 goto rgb_not_bitonal;
265 }
266 else
267 return IMAGE_COLOR;
268 s += n;
269 }
270 s += span_step;
271 }
272 return IMAGE_BITONAL;
273
274 /* Loop until we know it's not gray */
275 while (h--)
276 {
277 w = pix->w;
278
279 while (w--)
280 {
281 if (s[0] != s[1] || s[0] != s[2])
282 return IMAGE_COLOR;
283 rgb_not_bitonal:
284 s += n;
285 }
286 s += span_step;
287 }
288 return IMAGE_GRAY;
289 }
290 }
291 else if (fz_colorspace_is_cmyk(ctx, pix->colorspace))
292 {
293 int n = pix->n;
294 int h = pix->h;
295 int w;
296 ptrdiff_t span_step = pix->stride - pix->w * n;
297 const unsigned char *s = pix->samples;
298
299 if (pix->alpha)
300 {
301 /* Loop until we know it's not bitonal */
302 while (h--)
303 {
304 w = pix->w;
305
306 while (w--)
307 {
308 if (s[4] == 0)
309 {
310 /* If alpha is 0, other components don't matter. */
311 }
312 else if (s[0] == 0 && s[1] == 0 && s[2] == 0)
313 {
314 if (s[3] != 0 && s[3] != 255)
315 goto cmyka_not_bitonal;
316 }
317 else
318 return IMAGE_COLOR;
319 s += 5;
320 }
321 s += span_step;
322 }
323 return IMAGE_GRAY;
324
325 /* Loop until we know it's not gray */
326 while (h--)
327 {
328 w = pix->w;
329
330 while (w--)
331 {
332 if (s[4] == 0)
333 {
334 /* If alpha is 0, other components don't matter. */
335 }
336 else if (s[0] != 0 || s[1] != 0 || s[2] != 0)
337 return IMAGE_COLOR;
338 cmyka_not_bitonal:
339 s += 5;
340 }
341 s += span_step;
342 }
343 return IMAGE_GRAY;
344 }
345 else
346 {
347 /* Loop until we know it's not bitonal */
348 while (h--)
349 {
350 w = pix->w;
351
352 while (w--)
353 {
354 if (s[0] == 0 && s[1] == 0 && s[2] != 0)
355 {
356 if (s[3] != 0 && s[3] != 255)
357 goto cmyk_not_bitonal;
358 }
359 else
360 return IMAGE_COLOR;
361 s += 4;
362 }
363 s += span_step;
364 }
365 return IMAGE_GRAY;
366
367 /* Loop until we know it's not gray */
368 while (h--)
369 {
370 w = pix->w;
371
372 while (w--)
373 {
374 if (s[0] != 0 || s[1] != 0 || s[2] != 0)
375 return IMAGE_COLOR;
376 cmyk_not_bitonal:
377 s += 4;
378 }
379 s += span_step;
380 }
381 return IMAGE_GRAY;
382 }
383 }
384 return IMAGE_COLOR;
385 }
386
387 static fz_pixmap *
388 resample(fz_context *ctx, fz_pixmap *src, int method, float from_dpi, float to_dpi)
389 {
390 int w2 = src->w;
391 int h2 = src->h;
392 int w = (int)(w2 * to_dpi / from_dpi + 0.5f);
393 int h = (int)(h2 * to_dpi / from_dpi + 0.5f);
394 int factor;
395
396 /* Allow for us shrinking an image to 0.*/
397 assert(w >= 0 && h >= 0);
398 if (w == 0)
399 w = 1;
400 if (h == 0)
401 h = 1;
402
403 /* Allow for the possibility that we might only want to make such a tiny change
404 * in dpi that the image doesn't really resize. */
405 if (w >= w2 && h >= h2)
406 return NULL;
407
408 if (method == FZ_SUBSAMPLE_BICUBIC)
409 {
410 fz_irect clip = { 0, 0, w, h };
411 return fz_scale_pixmap(ctx, src, 0, 0, w, h, &clip);
412 }
413
414 factor = 0;
415 while (1)
416 {
417 int w3 = (w2+1)/2;
418 int h3 = (h2+1)/2;
419 if (w3 <= w || h3 <= h)
420 break;
421 factor++;
422 w2 = w3;
423 h2 = h3;
424 }
425
426 fz_subsample_pixmap(ctx, src, factor);
427
428 return fz_keep_pixmap(ctx, src);
429 }
430
431 static fz_compressed_buffer *
432 fz_recompress_image_as_jpeg(fz_context *ctx, fz_pixmap *pix, const char *quality, fz_colorspace **cs)
433 {
434 fz_compressed_buffer *cbuf = NULL;
435 fz_pixmap *rgb = NULL;
436 int q = fz_atoi(quality);
437
438 if (q == 0)
439 q = 75; /* Default quality */
440
441 if (!pix->colorspace)
442 return NULL;
443
444 if (!fz_colorspace_is_cmyk(ctx, pix->colorspace) &&
445 !fz_colorspace_is_gray(ctx, pix->colorspace) &&
446 !fz_colorspace_is_rgb(ctx, pix->colorspace))
447 {
448 /* We're going to need to convert colorspace. */
449 /* It's not gray, so we need a color space - pick rgb. */
450 pix = rgb = fz_convert_pixmap(ctx, pix, fz_device_rgb(ctx), NULL, NULL, fz_default_color_params, 0);
451 *cs = fz_device_rgb(ctx);
452 }
453
454 fz_var(cbuf);
455
456 fz_try(ctx)
457 {
458 cbuf = fz_new_compressed_buffer(ctx);
459 cbuf->buffer = fz_new_buffer_from_pixmap_as_jpeg(ctx, pix, fz_default_color_params, q, 0);
460 cbuf->params.type = FZ_IMAGE_JPEG;
461 cbuf->params.u.jpeg.color_transform = -2;
462 }
463 fz_always(ctx)
464 {
465 if (rgb)
466 fz_drop_pixmap(ctx, rgb);
467 }
468 fz_catch(ctx)
469 {
470 fz_drop_compressed_buffer(ctx, cbuf);
471 fz_rethrow(ctx);
472 }
473
474 return cbuf;
475 }
476
477 static fz_compressed_buffer *
478 fz_recompress_image_as_j2k(fz_context *ctx, fz_pixmap *pix, const char *quality)
479 {
480 fz_compressed_buffer *cbuf = fz_new_compressed_buffer(ctx);
481 fz_output *out = NULL;
482 int q = fz_atoi(quality);
483
484 if (q <= 0)
485 q = 80; /* Default 1:20 compression */
486 if (q > 100)
487 q = 100;
488
489 fz_var(out);
490
491 fz_try(ctx)
492 {
493 cbuf->buffer = fz_new_buffer(ctx, 1024);
494 out = fz_new_output_with_buffer(ctx, cbuf->buffer);
495
496 fz_write_pixmap_as_jpx(ctx, out, pix, q);
497 cbuf->params.type = FZ_IMAGE_JPX;
498 cbuf->params.u.jpx.smask_in_data = 0;
499 }
500 fz_always(ctx)
501 {
502 fz_drop_output(ctx, out);
503 }
504 fz_catch(ctx)
505 {
506 fz_drop_compressed_buffer(ctx, cbuf);
507 fz_rethrow(ctx);
508 }
509
510 return cbuf;
511 }
512
513 static fz_compressed_buffer *
514 fz_recompress_image_as_flate(fz_context *ctx, fz_pixmap *pix, const char *quality)
515 {
516 fz_compressed_buffer *cbuf = fz_new_compressed_buffer(ctx);
517 fz_output *out = NULL;
518 fz_output *out2 = NULL;
519 int h = pix->h;
520 size_t n = (size_t) pix->w * pix->n;
521 const unsigned char *samp = pix->samples;
522 ptrdiff_t str = pix->stride;
523 int q = fz_atoi(quality);
524
525 /* Notionally, it's 0-100 */
526 q /= 11;
527 if (q > FZ_DEFLATE_BEST)
528 q = FZ_DEFLATE_BEST;
529 if (q <= 0)
530 q = FZ_DEFLATE_DEFAULT;
531
532 fz_var(out);
533 fz_var(out2);
534
535 fz_try(ctx)
536 {
537 cbuf->buffer = fz_new_buffer(ctx, 1024);
538 out = fz_new_output_with_buffer(ctx, cbuf->buffer);
539 out2 = fz_new_deflate_output(ctx, out, q, 0);
540
541 while (h--)
542 {
543 fz_write_data(ctx, out2, samp, n);
544 samp += str;
545 }
546
547 fz_close_output(ctx, out2);
548 fz_drop_output(ctx, out2);
549 out2 = NULL;
550 fz_close_output(ctx, out);
551 }
552 fz_always(ctx)
553 {
554 fz_drop_output(ctx, out2);
555 fz_drop_output(ctx, out);
556 }
557 fz_catch(ctx)
558 {
559 fz_drop_compressed_buffer(ctx, cbuf);
560 fz_rethrow(ctx);
561 }
562
563 cbuf->params.type = FZ_IMAGE_FLATE;
564 cbuf->params.u.flate.bpc = 8;
565 cbuf->params.u.flate.colors = 0;
566 cbuf->params.u.flate.predictor = 0;
567 cbuf->params.u.flate.columns = 0;
568
569 return cbuf;
570 }
571
572 static fz_compressed_buffer *
573 fz_recompress_image_as_fax(fz_context *ctx, fz_pixmap *pix)
574 {
575 /* FIXME: Should get default colorspaces from the doc! */
576 fz_default_colorspaces *defcs = fz_new_default_colorspaces(ctx);
577 fz_compressed_buffer *cbuf = NULL;
578 fz_halftone *ht = NULL;
579 fz_bitmap *bmp = NULL;
580 fz_buffer *inv_buffer = NULL;
581
582 fz_var(ht);
583 fz_var(bmp);
584 fz_var(cbuf);
585 fz_var(inv_buffer);
586
587 fz_keep_pixmap(ctx, pix);
588 fz_try(ctx)
589 {
590
591 /* Convert to alphaless grey */
592 if (pix->n != 1)
593 {
594 fz_pixmap *pix2 = fz_convert_pixmap(ctx, pix, fz_device_gray(ctx), NULL, defcs, fz_default_color_params, 0);
595
596 fz_drop_pixmap(ctx, pix);
597 pix = pix2;
598 }
599
600 /* Convert to a bitmap */
601 ht = fz_default_halftone(ctx, 1);
602
603 bmp = fz_new_bitmap_from_pixmap(ctx, pix, ht);
604
605 cbuf = fz_new_compressed_buffer(ctx);
606 cbuf->buffer = fz_compress_ccitt_fax_g4(ctx, bmp->samples, bmp->w, bmp->h, bmp->stride);
607 cbuf->params.type = FZ_IMAGE_FAX;
608 cbuf->params.u.fax.k = -1;
609 cbuf->params.u.fax.columns = pix->w;
610 cbuf->params.u.fax.rows = pix->h;
611
612 fz_invert_bitmap(ctx, bmp);
613 inv_buffer = fz_compress_ccitt_fax_g4(ctx, bmp->samples, bmp->w, bmp->h, bmp->stride);
614
615 /* cbuf->buffer requires "/BlackIs1 true ", so it needs to beats the inverted one by
616 * at least 15 bytes, or we'll use the inverted one. */
617 if (cbuf->buffer->len + 15 < inv_buffer->len)
618 {
619 cbuf->params.u.fax.black_is_1 = 1;
620 }
621 else
622 {
623 fz_drop_buffer(ctx, cbuf->buffer);
624 cbuf->buffer = inv_buffer;
625 inv_buffer = NULL;
626 }
627 }
628 fz_always(ctx)
629 {
630 fz_drop_bitmap(ctx, bmp);
631 fz_drop_halftone(ctx, ht);
632 fz_drop_pixmap(ctx, pix);
633 fz_drop_buffer(ctx, inv_buffer);
634 fz_drop_default_colorspaces(ctx, defcs);
635 }
636 fz_catch(ctx)
637 {
638 fz_drop_compressed_buffer(ctx, cbuf);
639 fz_rethrow(ctx);
640 }
641
642 return cbuf;
643 }
644
645 static int method_from_fmt(int fmt)
646 {
647 switch (fmt)
648 {
649 case FZ_IMAGE_JPEG:
650 return FZ_RECOMPRESS_JPEG;
651 case FZ_IMAGE_JPX:
652 return FZ_RECOMPRESS_J2K;
653 case FZ_IMAGE_FAX:
654 return FZ_RECOMPRESS_FAX;
655 }
656 return FZ_RECOMPRESS_LOSSLESS;
657 }
658
659 static fz_image *
660 recompress_image(fz_context *ctx, fz_pixmap *pix, int type, int fmt, int method, const char *quality, fz_image *oldimg)
661 {
662 int interpolate = oldimg->interpolate;
663 fz_compressed_buffer *cbuf = NULL;
664 fz_colorspace *cs = pix->colorspace;
665 int bpc = 8;
666
667 if (method == FZ_RECOMPRESS_NEVER)
668 return NULL;
669
670 if (method == FZ_RECOMPRESS_SAME)
671 method = method_from_fmt(fmt);
672
673 if (method == FZ_RECOMPRESS_J2K)
674 cbuf = fz_recompress_image_as_j2k(ctx, pix, quality);
675 if (method == FZ_RECOMPRESS_JPEG)
676 cbuf = fz_recompress_image_as_jpeg(ctx, pix, quality, &cs);
677 if (method == FZ_RECOMPRESS_FAX)
678 {
679 cbuf = fz_recompress_image_as_fax(ctx, pix);
680 if (cbuf)
681 {
682 bpc = 1;
683 cs = fz_device_gray(ctx);
684 }
685 }
686 if (cbuf == NULL)
687 cbuf = fz_recompress_image_as_flate(ctx, pix, quality);
688
689 if (cbuf == NULL)
690 return NULL;
691
692 /* fz_new_image_from_compressed_buffer takes ownership of compressed buffer, even
693 * in failure case. */
694 return fz_new_image_from_compressed_buffer(ctx, pix->w, pix->h, bpc, cs, pix->xres, pix->yres, interpolate, 0, NULL, NULL, cbuf, oldimg->mask);
695 }
696
697 static void
698 do_image_rewrite(fz_context *ctx, void *opaque, fz_image **image, fz_matrix ctm, pdf_obj *im_obj)
699 {
700 image_info *info = (image_info *)opaque;
701 image_list *ilist = &info->list;
702 unique_image_list *uilist = &info->uilist;
703 float dpi;
704 fz_pixmap *pix;
705 fz_pixmap *newpix = NULL;
706 image_type type;
707 int fmt = fz_compressed_image_type(ctx, *image);
708 int lossy = fz_is_lossy_image(ctx, *image);
709 size_t orig_len = pdf_dict_get_int64(ctx, im_obj, PDF_NAME(Length));
710
711 /* FIXME: We don't recompress im_obj->mask! */
712
713 /* Can't recompress colorkeyed images, currently. */
714 if ((*image)->use_colorkey)
715 return;
716 /* Can't recompress scalable images. */
717 if ((*image)->scalable)
718 return;
719
720 /* Can't rewrite separation ones, currently, as we can't pdf_add_image a separation image. */
721 if (fz_colorspace_is_indexed(ctx, (*image)->colorspace) &&
722 fz_colorspace_is_device_n(ctx, (*image)->colorspace->u.indexed.base))
723 return;
724 if (fz_colorspace_is_device_n(ctx, (*image)->colorspace))
725 return;
726
727 if (im_obj == NULL)
728 dpi = dpi_from_ctm(ctm, (*image)->w, (*image)->h);
729 else
730 dpi = uilist->img[ilist->uimg[info->which++]].dpi;
731
732 /* What sort of image is this? */
733 pix = fz_get_pixmap_from_image(ctx, *image, NULL, NULL, NULL, NULL);
734 type = classify_pixmap(ctx, pix);
735
736 fz_var(newpix);
737
738 fz_try(ctx)
739 {
740 fz_image *newimg = NULL;
741
742 if (type == IMAGE_BITONAL &&
743 info->opts->bitonal_image_recompress_method != FZ_RECOMPRESS_NEVER &&
744 info->opts->bitonal_image_subsample_threshold != 0 &&
745 dpi > info->opts->bitonal_image_subsample_threshold)
746 {
747 /* Resample a bitonal image. */
748 newpix = resample(ctx, pix, info->opts->bitonal_image_subsample_method, dpi, info->opts->bitonal_image_subsample_to);
749 }
750 else if (type == IMAGE_COLOR && lossy &&
751 info->opts->color_lossy_image_recompress_method != FZ_RECOMPRESS_NEVER &&
752 info->opts->color_lossy_image_subsample_threshold != 0 &&
753 dpi > info->opts->color_lossy_image_subsample_threshold)
754 {
755 /* Resample a lossily encoded color image. */
756 newpix = resample(ctx, pix, info->opts->color_lossy_image_subsample_method, dpi, info->opts->color_lossy_image_subsample_to);
757 }
758 else if (type == IMAGE_COLOR && !lossy &&
759 info->opts->color_lossless_image_recompress_method != FZ_RECOMPRESS_NEVER &&
760 info->opts->color_lossless_image_subsample_threshold != 0 &&
761 dpi > info->opts->color_lossless_image_subsample_threshold)
762 {
763 /* Resample a losslessly color image. */
764 newpix = resample(ctx, pix, info->opts->color_lossless_image_subsample_method, dpi, info->opts->color_lossless_image_subsample_to);
765 }
766 else if (type == IMAGE_GRAY && lossy &&
767 info->opts->gray_lossy_image_recompress_method != FZ_RECOMPRESS_NEVER &&
768 info->opts->gray_lossy_image_subsample_threshold != 0 &&
769 dpi > info->opts->gray_lossy_image_subsample_threshold)
770 {
771 /* Resample a lossily encoded gray image. */
772 newpix = resample(ctx, pix, info->opts->gray_lossy_image_subsample_method, dpi, info->opts->gray_lossy_image_subsample_to);
773 }
774 else if (type == IMAGE_GRAY && !lossy &&
775 info->opts->gray_lossless_image_recompress_method != FZ_RECOMPRESS_NEVER &&
776 info->opts->gray_lossless_image_subsample_threshold != 0 &&
777 dpi > info->opts->gray_lossless_image_subsample_threshold)
778 {
779 /* Resample a losslessly encoded gray image. */
780 newpix = resample(ctx, pix, info->opts->gray_lossless_image_subsample_method, dpi, info->opts->gray_lossless_image_subsample_to);
781 }
782
783 if (newpix)
784 {
785 /* We've scaled (or otherwise converted the image). So it needs to be compressed. */
786 if (type == IMAGE_COLOR)
787 {
788 if (lossy)
789 newimg = recompress_image(ctx, newpix, type, fmt, info->opts->color_lossy_image_recompress_method, info->opts->color_lossy_image_recompress_quality, *image);
790 else
791 newimg = recompress_image(ctx, newpix, type, fmt, info->opts->color_lossless_image_recompress_method, info->opts->color_lossless_image_recompress_quality, *image);
792 }
793 else if (type == IMAGE_GRAY)
794 {
795 if (lossy)
796 newimg = recompress_image(ctx, newpix, type, fmt, info->opts->gray_lossy_image_recompress_method, info->opts->gray_lossy_image_recompress_quality, *image);
797 else
798 newimg = recompress_image(ctx, newpix, type, fmt, info->opts->gray_lossless_image_recompress_method, info->opts->gray_lossless_image_recompress_quality, *image);
799 }
800 else if (type == IMAGE_BITONAL)
801 newimg = recompress_image(ctx, newpix, type, fmt, info->opts->bitonal_image_recompress_method, info->opts->bitonal_image_recompress_quality, *image);
802 }
803 else if (type == IMAGE_COLOR)
804 {
805 if (lossy)
806 newimg = recompress_image(ctx, pix, type, fmt, info->opts->color_lossy_image_recompress_method, info->opts->color_lossy_image_recompress_quality, *image);
807 else
808 newimg = recompress_image(ctx, pix, type, fmt, info->opts->color_lossless_image_recompress_method, info->opts->color_lossless_image_recompress_quality, *image);
809 }
810 else if (type == IMAGE_GRAY)
811 {
812 if (lossy)
813 newimg = recompress_image(ctx, pix, type, fmt, info->opts->gray_lossy_image_recompress_method, info->opts->gray_lossy_image_recompress_quality, *image);
814 else
815 newimg = recompress_image(ctx, pix, type, fmt, info->opts->gray_lossless_image_recompress_method, info->opts->gray_lossless_image_recompress_quality, *image);
816 }
817 else if (type == IMAGE_BITONAL)
818 {
819 newimg = recompress_image(ctx, pix, type, fmt, info->opts->bitonal_image_recompress_method, info->opts->bitonal_image_recompress_quality, *image);
820 }
821
822 if (newimg)
823 {
824 /* fz_image_size gives us the uncompressed size for losslessly compressed images
825 * as the image holds the uncompressed buffer. But orig_len will be 0 for inline
826 * images. So we have to combine the two. */
827 size_t oldsize = fz_image_size(ctx, *image);
828 size_t newsize = fz_image_size(ctx, newimg);
829 if (orig_len != 0)
830 oldsize = orig_len;
831 if (oldsize <= newsize)
832 {
833 /* Old one was smaller! Don't mess with it. */
834 fz_drop_image(ctx, newimg);
835 }
836 else
837 {
838 fz_drop_image(ctx, *image);
839 *image = newimg;
840 }
841 }
842 }
843 fz_always(ctx)
844 {
845 fz_drop_pixmap(ctx, newpix);
846 fz_drop_pixmap(ctx, pix);
847 }
848 fz_catch(ctx)
849 {
850 fz_rethrow(ctx);
851 }
852 }
853
854 static void
855 gather_image_info(fz_context *ctx, pdf_document *doc, int page_num, image_info *info)
856 {
857 pdf_page *page = pdf_load_page(ctx, doc, page_num);
858 pdf_filter_options options = { 0 };
859 pdf_filter_factory list[2] = { 0 };
860 pdf_color_filter_options copts = { 0 };
861 pdf_annot *annot;
862
863 copts.opaque = info;
864 copts.color_rewrite = NULL;
865 copts.image_rewrite = gather_image_rewrite;
866 copts.shade_rewrite = NULL;
867 options.filters = list;
868 options.recurse = 1;
869 options.no_update = 1;
870 list[0].filter = pdf_new_color_filter;
871 list[0].options = &copts;
872
873 fz_try(ctx)
874 {
875 pdf_filter_page_contents(ctx, doc, page, &options);
876
877 for (annot = pdf_first_annot(ctx, page); annot != NULL; annot = pdf_next_annot(ctx, annot))
878 pdf_filter_annot_contents(ctx, doc, annot, &options);
879 for (annot = pdf_first_widget(ctx, page); annot != NULL; annot = pdf_next_annot(ctx, annot))
880 pdf_filter_annot_contents(ctx, doc, annot, &options);
881 }
882 fz_always(ctx)
883 fz_drop_page(ctx, &page->super);
884 fz_catch(ctx)
885 fz_rethrow(ctx);
886 }
887
888 static void
889 rewrite_image_info(fz_context *ctx, pdf_document *doc, int page_num, image_info *info)
890 {
891 pdf_page *page = pdf_load_page(ctx, doc, page_num);
892 pdf_filter_options options = { 0 };
893 pdf_filter_factory list[2] = { 0 };
894 pdf_color_filter_options copts = { 0 };
895 pdf_annot *annot;
896
897 copts.opaque = info;
898 copts.color_rewrite = NULL;
899 copts.image_rewrite = do_image_rewrite;
900 copts.shade_rewrite = NULL;
901 options.filters = list;
902 options.recurse = 1;
903 list[0].filter = pdf_new_color_filter;
904 list[0].options = &copts;
905
906 fz_try(ctx)
907 {
908 pdf_filter_page_contents(ctx, doc, page, &options);
909
910 for (annot = pdf_first_annot(ctx, page); annot != NULL; annot = pdf_next_annot(ctx, annot))
911 pdf_filter_annot_contents(ctx, doc, annot, &options);
912 for (annot = pdf_first_widget(ctx, page); annot != NULL; annot = pdf_next_annot(ctx, annot))
913 pdf_filter_annot_contents(ctx, doc, annot, &options);
914 }
915 fz_always(ctx)
916 fz_drop_page(ctx, &page->super);
917 fz_catch(ctx)
918 fz_rethrow(ctx);
919 }
920
921 void pdf_rewrite_images(fz_context *ctx, pdf_document *doc, pdf_image_rewriter_options *opts)
922 {
923 int i;
924 int n = pdf_count_pages(ctx, doc);
925 image_info info = { 0 };
926
927 info.opts = opts;
928
929 /* If nothing to do, do nothing! */
930 if (opts->bitonal_image_subsample_threshold == 0 &&
931 opts->gray_lossless_image_subsample_threshold == 0 &&
932 opts->gray_lossy_image_subsample_threshold == 0 &&
933 opts->color_lossless_image_subsample_threshold == 0 &&
934 opts->color_lossy_image_subsample_threshold == 0 &&
935 opts->bitonal_image_recompress_method == FZ_RECOMPRESS_NEVER &&
936 opts->color_lossy_image_recompress_method == FZ_RECOMPRESS_NEVER &&
937 opts->color_lossless_image_recompress_method == FZ_RECOMPRESS_NEVER &&
938 opts->gray_lossy_image_recompress_method == FZ_RECOMPRESS_NEVER &&
939 opts->gray_lossless_image_recompress_method == FZ_RECOMPRESS_NEVER)
940 return;
941
942 /* Pass 1: Gather information */
943 for (i = 0; i < n; i++)
944 {
945 gather_image_info(ctx, doc, i, &info);
946 }
947
948 /* Pass 2: Resample as required */
949 for (i = 0; i < n; i++)
950 {
951 rewrite_image_info(ctx, doc, i, &info);
952 }
953
954 fz_free(ctx, info.list.uimg);
955 fz_free(ctx, info.uilist.img);
956 }