// Copyright (C) 2004-2023 Artifex Software, Inc. // // This file is part of MuPDF. // // MuPDF is free software: you can redistribute it and/or modify it under the // terms of the GNU Affero General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more // details. // // You should have received a copy of the GNU Affero General Public License // along with MuPDF. If not, see // // Alternative licensing terms are available from the licensor. // For commercial licensing, see or contact // Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, // CA 94945, U.S.A., +1(415)492-9861, for further information. /* PDF recoloring tool. */ #include "mupdf/fitz.h" #include "mupdf/pdf.h" #include #include #include static fz_colorspace *outcs = NULL; static pdf_obj *outcs_obj = NULL; static void color_rewrite(fz_context *ctx, void *opaque, pdf_obj **cs_obj, int *n, float color[FZ_MAX_COLORS]) { fz_colorspace *cs; float cols[4] = { 0 }; if (pdf_name_eq(ctx, *cs_obj, PDF_NAME(Pattern))) return; if (pdf_name_eq(ctx, pdf_dict_get(ctx, *cs_obj, PDF_NAME(Type)), PDF_NAME(Pattern))) return; if (*n != 0) { cs = pdf_load_colorspace(ctx, *cs_obj); fz_try(ctx) { fz_convert_color(ctx, cs, color, outcs, cols, NULL, fz_default_color_params); } fz_always(ctx) fz_drop_colorspace(ctx, cs); fz_catch(ctx) fz_rethrow(ctx); *n = outcs->n; } pdf_drop_obj(ctx, *cs_obj); *cs_obj = outcs_obj; memcpy(color, cols, sizeof(color[0])*4); } static void image_rewrite(fz_context *ctx, void *opaque, fz_image **image) { fz_image *orig = *image; fz_pixmap *pix; fz_colorspace* dst_cs; dst_cs = outcs; pix = fz_get_unscaled_pixmap_from_image(ctx, orig); if (pix->colorspace != dst_cs) { fz_pixmap *pix2 = fz_convert_pixmap(ctx, pix, dst_cs, NULL, NULL, fz_default_color_params, 1); fz_drop_pixmap(ctx, pix); pix = pix2; } *image = fz_new_image_from_pixmap(ctx, pix, orig->mask); fz_drop_pixmap(ctx, pix); fz_drop_image(ctx, orig); } static void vertex_rewrite(fz_context *ctx, void *opaque, fz_colorspace *dst_cs, float *d, fz_colorspace *src_cs, const float *s) { fz_convert_color(ctx, src_cs, s, outcs, d, NULL, fz_default_color_params); } static pdf_recolor_vertex * shade_rewrite(fz_context *ctx, void *opaque, fz_colorspace *src_cs, fz_colorspace **dst_cs) { *dst_cs = outcs; return vertex_rewrite; } static void rewrite_page_streams(fz_context *ctx, pdf_document *doc, int page_num) { pdf_page *page = pdf_load_page(ctx, doc, page_num); pdf_filter_options options = { 0 }; pdf_filter_factory list[2] = { 0 }; pdf_color_filter_options copts = { 0 }; pdf_annot *annot; copts.opaque = NULL; copts.color_rewrite = color_rewrite; copts.image_rewrite = image_rewrite; copts.shade_rewrite = shade_rewrite; options.filters = list; options.recurse = 1; list[0].filter = pdf_new_color_filter; list[0].options = &copts; fz_try(ctx) { pdf_filter_page_contents(ctx, doc, page, &options); for (annot = pdf_first_annot(ctx, page); annot != NULL; annot = pdf_next_annot(ctx, annot)) pdf_filter_annot_contents(ctx, doc, annot, &options); } fz_always(ctx) fz_drop_page(ctx, &page->super); fz_catch(ctx) fz_rethrow(ctx); } static int usage(void) { fprintf(stderr, "usage: mutool recolor [options] \n"); fprintf(stderr, "\t-c -\tOutput colorspace (gray(default), rgb, cmyk)\n"); fprintf(stderr, "\t-r\tRemove OutputIntent(s)\n"); fprintf(stderr, "\t-o -\tOutput file\n"); return 1; } int pdfrecolor_main(int argc, char **argv) { fz_context *ctx = NULL; pdf_document *pdf = NULL; fz_document *doc = NULL; pdf_write_options opts = pdf_default_write_options; int n, i, c; char *infile = NULL; char *outputfile = NULL; int code = EXIT_SUCCESS; const char *colorspace = NULL; int remove_oi = 0; while ((c = fz_getopt(argc, argv, "c:o:r")) != -1) { switch (c) { default: return usage(); // color convert case 'c': colorspace = fz_optarg; break; case 'o': outputfile = fz_optarg; break; case 'r': remove_oi = 1; break; } } if (fz_optind == argc || !outputfile) return usage(); infile = argv[fz_optind]; if (colorspace == NULL || !strcmp(colorspace, "gray")) colorspace = "gray"; else if (!strcmp(colorspace, "rgb")) {} else if (!strcmp(colorspace, "cmyk")) {} else { fprintf(stderr, "Unknown colorspace\n"); return usage(); } /* Set up the options for the file saving. */ #if 1 opts.do_compress = 1; opts.do_compress_images = 1; opts.do_compress_fonts = 1; opts.do_garbage = 3; #else opts.do_compress = 0; opts.do_pretty = 1; opts.do_compress = 0; opts.do_compress_images = 1; opts.do_compress_fonts = 0; opts.do_garbage = 0; opts.do_clean = 1; #endif /* Create a MuPDF library context. */ ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); if (!ctx) { fprintf(stderr, "Could not create global context.\n"); return EXIT_FAILURE; } if (!strcmp(colorspace, "gray")) { outcs = fz_device_gray(ctx); outcs_obj = PDF_NAME(DeviceGray); } else if (!strcmp(colorspace, "rgb")) { outcs = fz_device_rgb(ctx); outcs_obj = PDF_NAME(DeviceRGB); } else if (!strcmp(colorspace, "cmyk")) { outcs = fz_device_cmyk(ctx); outcs_obj = PDF_NAME(DeviceCMYK); } /* Register the document handlers (only really need PDF, but this is * the simplest way. */ fz_register_document_handlers(ctx); fz_try(ctx) { /* Load the input document. */ doc = fz_open_document(ctx, infile); /* Get a PDF specific pointer, and count the pages. */ pdf = pdf_document_from_fz_document(ctx, doc); n = fz_count_pages(ctx, doc); for (i = 0; i < n; i++) rewrite_page_streams(ctx, pdf, i); if (remove_oi) pdf_dict_del(ctx, pdf_dict_get(ctx, pdf_trailer(ctx, pdf), PDF_NAME(Root)), PDF_NAME(OutputIntents)); pdf_save_document(ctx, pdf, outputfile, &opts); } fz_always(ctx) { fz_drop_document(ctx, doc); } fz_catch(ctx) { code = EXIT_FAILURE; } fz_drop_context(ctx); return code; }