Commit ee2aad438164da5f3fd9ff3f3eacbaad81bbebcc

Authored by Jay Berkenbilt
1 parent 6f3b76b6

Add CLI flags for image optimization

ChangeLog
1 1 2019-01-04 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * Add new option --optimize-images, which recompresses every image
  4 + using DCT (JPEG) compression as long as the image is not already
  5 + compressed with lossy compression and recompressing the image
  6 + reduces its size. The additional options --oi-min-width,
  7 + --oi-min-height, and --oi-min-area prevent recompression of images
  8 + whose width, height, or pixel area (width * height) are below a
  9 + specified threshold.
  10 +
3 11 * Add new option --collate. When specified, the semantics of
4 12 --pages change from concatenation to collation. See the manual for
5 13 a more detailed discussion. Fixes #259.
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -860,6 +860,7 @@ class QPDFObjectHandle
860 860 // do nothing. Objects read normally from the file have
861 861 // descriptions. See comments on setObjectDescription for
862 862 // additional details.
  863 + QPDF_DLL
863 864 void warnIfPossible(std::string const& warning,
864 865 bool throw_if_no_description = false);
865 866  
... ...
qpdf/qpdf.cc
... ... @@ -11,6 +11,8 @@
11 11 #include <qpdf/FileInputSource.hh>
12 12 #include <qpdf/Pl_StdioFile.hh>
13 13 #include <qpdf/Pl_Discard.hh>
  14 +#include <qpdf/Pl_DCT.hh>
  15 +#include <qpdf/Pl_Count.hh>
14 16 #include <qpdf/PointerHolder.hh>
15 17  
16 18 #include <qpdf/QPDF.hh>
... ... @@ -127,6 +129,10 @@ struct Options
127 129 collate(false),
128 130 json(false),
129 131 check(false),
  132 + optimize_images(false),
  133 + oi_min_width(128),
  134 + oi_min_height(128),
  135 + oi_min_area(16384),
130 136 require_outfile(true),
131 137 infilename(0),
132 138 outfilename(0)
... ... @@ -208,6 +214,10 @@ struct Options
208 214 std::set<std::string> json_keys;
209 215 std::set<std::string> json_objects;
210 216 bool check;
  217 + bool optimize_images;
  218 + size_t oi_min_width;
  219 + size_t oi_min_height;
  220 + size_t oi_min_area;
211 221 std::vector<PageSpec> page_specs;
212 222 std::map<std::string, RotationSpec> rotations;
213 223 bool require_outfile;
... ... @@ -602,6 +612,10 @@ class ArgParser
602 612 void argJsonKey(char* parameter);
603 613 void argJsonObject(char* parameter);
604 614 void argCheck();
  615 + void argOptimizeImages();
  616 + void argOiMinWidth(char* paramter);
  617 + void argOiMinHeight(char* paramter);
  618 + void argOiMinArea(char* paramter);
605 619 void arg40Print(char* parameter);
606 620 void arg40Modify(char* parameter);
607 621 void arg40Extract(char* parameter);
... ... @@ -816,6 +830,13 @@ ArgParser::initOptionTable()
816 830 (*t)["json-object"] = oe_requiredParameter(
817 831 &ArgParser::argJsonObject, "trailer|obj[,gen]");
818 832 (*t)["check"] = oe_bare(&ArgParser::argCheck);
  833 + (*t)["optimize-images"] = oe_bare(&ArgParser::argOptimizeImages);
  834 + (*t)["oi-min-width"] = oe_requiredParameter(
  835 + &ArgParser::argOiMinWidth, "minimum-width");
  836 + (*t)["oi-min-height"] = oe_requiredParameter(
  837 + &ArgParser::argOiMinHeight, "minimum-height");
  838 + (*t)["oi-min-area"] = oe_requiredParameter(
  839 + &ArgParser::argOiMinArea, "minimum-area");
819 840  
820 841 t = &this->encrypt40_option_table;
821 842 (*t)["--"] = oe_bare(&ArgParser::argEndEncrypt);
... ... @@ -1417,6 +1438,30 @@ ArgParser::argCheck()
1417 1438 }
1418 1439  
1419 1440 void
  1441 +ArgParser::argOptimizeImages()
  1442 +{
  1443 + o.optimize_images = true;
  1444 +}
  1445 +
  1446 +void
  1447 +ArgParser::argOiMinWidth(char* parameter)
  1448 +{
  1449 + o.oi_min_width = QUtil::string_to_int(parameter);
  1450 +}
  1451 +
  1452 +void
  1453 +ArgParser::argOiMinHeight(char* parameter)
  1454 +{
  1455 + o.oi_min_height = QUtil::string_to_int(parameter);
  1456 +}
  1457 +
  1458 +void
  1459 +ArgParser::argOiMinArea(char* parameter)
  1460 +{
  1461 + o.oi_min_area = QUtil::string_to_int(parameter);
  1462 +}
  1463 +
  1464 +void
1420 1465 ArgParser::arg40Print(char* parameter)
1421 1466 {
1422 1467 o.r2_print = (strcmp(parameter, "y") == 0);
... ... @@ -1911,6 +1956,10 @@ familiar with the PDF file format or who are PDF developers.\n\
1911 1956 contents including those for interactive form\n\
1912 1957 fields; may also want --generate-appearances\n\
1913 1958 --generate-appearances generate appearance streams for form fields\n\
  1959 +--optimize-images compress images with DCT (JPEG) when advantageous\n\
  1960 +--oi-min-width=w do not optimize images whose width is below w\n\
  1961 +--oi-min-height=h do not optimize images whose height is below h\n\
  1962 +--oi-min-area=a do not optimize images whose pixel count is below a\n\
1914 1963 --qdf turns on \"QDF mode\" (below)\n\
1915 1964 --linearize-pass1=file write intermediate pass of linearized file\n\
1916 1965 for debugging\n\
... ... @@ -3403,9 +3452,202 @@ static void do_inspection(QPDF&amp; pdf, Options&amp; o)
3403 3452 }
3404 3453 }
3405 3454  
  3455 +class ImageOptimizer: public QPDFObjectHandle::StreamDataProvider
  3456 +{
  3457 + public:
  3458 + ImageOptimizer(Options& o, QPDFObjectHandle& image);
  3459 + virtual ~ImageOptimizer()
  3460 + {
  3461 + }
  3462 + virtual void provideStreamData(int objid, int generation,
  3463 + Pipeline* pipeline);
  3464 + PointerHolder<Pipeline> makePipeline(
  3465 + std::string const& description, Pipeline* next);
  3466 + bool evaluate(std::string const& description);
  3467 +
  3468 + private:
  3469 + Options& o;
  3470 + QPDFObjectHandle image;
  3471 +};
  3472 +
  3473 +ImageOptimizer::ImageOptimizer(Options& o, QPDFObjectHandle& image) :
  3474 + o(o),
  3475 + image(image)
  3476 +{
  3477 +}
  3478 +
  3479 +PointerHolder<Pipeline>
  3480 +ImageOptimizer::makePipeline(std::string const& description, Pipeline* next)
  3481 +{
  3482 + PointerHolder<Pipeline> result;
  3483 + QPDFObjectHandle dict = image.getDict();
  3484 + QPDFObjectHandle w_obj = dict.getKey("/Width");
  3485 + QPDFObjectHandle h_obj = dict.getKey("/Height");
  3486 + QPDFObjectHandle colorspace_obj = dict.getKey("/ColorSpace");
  3487 + QPDFObjectHandle components_obj = dict.getKey("/BitsPerComponent");
  3488 + if (! (w_obj.isInteger() &&
  3489 + h_obj.isInteger() &&
  3490 + colorspace_obj.isName() &&
  3491 + components_obj.isInteger()))
  3492 + {
  3493 + if (o.verbose && (! description.empty()))
  3494 + {
  3495 + std::cout << whoami << ": " << description
  3496 + << ": not optimizing because image dictionary"
  3497 + << " is missing required keys" << std::endl;
  3498 + }
  3499 + return result;
  3500 + }
  3501 + JDIMENSION w = w_obj.getIntValue();
  3502 + JDIMENSION h = h_obj.getIntValue();
  3503 + std::string colorspace = colorspace_obj.getName();
  3504 + int components = 0;
  3505 + J_COLOR_SPACE cs = JCS_UNKNOWN;
  3506 + if (colorspace == "/DeviceRGB")
  3507 + {
  3508 + components = 3;
  3509 + cs = JCS_RGB;
  3510 + }
  3511 + else if (colorspace == "/DeviceGray")
  3512 + {
  3513 + components = 1;
  3514 + cs = JCS_GRAYSCALE;
  3515 + }
  3516 + else if (colorspace == "/DeviceCMYK")
  3517 + {
  3518 + components = 4;
  3519 + cs = JCS_CMYK;
  3520 + }
  3521 + else
  3522 + {
  3523 + if (o.verbose && (! description.empty()))
  3524 + {
  3525 + std::cout << whoami << ": " << description
  3526 + << ": not optimizing because of unsupported"
  3527 + << " image parameters" << std::endl;
  3528 + }
  3529 + return result;
  3530 + }
  3531 + if (((o.oi_min_width > 0) && (w <= o.oi_min_width)) ||
  3532 + ((o.oi_min_height > 0) && (h <= o.oi_min_height)) ||
  3533 + ((o.oi_min_area > 0) && ((w * h) <= o.oi_min_area)))
  3534 + {
  3535 + QTC::TC("qpdf", "qpdf image optimize too small");
  3536 + if (o.verbose && (! description.empty()))
  3537 + {
  3538 + std::cout << whoami << ": " << description
  3539 + << ": not optimizing because of image"
  3540 + << " is smaller than requested minimum dimensions"
  3541 + << std::endl;
  3542 + }
  3543 + return result;
  3544 + }
  3545 +
  3546 + result = new Pl_DCT("jpg", next, w, h, components, cs);
  3547 + return result;
  3548 +}
  3549 +
  3550 +bool
  3551 +ImageOptimizer::evaluate(std::string const& description)
  3552 +{
  3553 + Pl_Discard d;
  3554 + Pl_Count c("count", &d);
  3555 + PointerHolder<Pipeline> p = makePipeline(description, &c);
  3556 + if (p.getPointer() == 0)
  3557 + {
  3558 + // message issued by makePipeline
  3559 + return false;
  3560 + }
  3561 + if (! image.pipeStreamData(p.getPointer(), 0, qpdf_dl_specialized,
  3562 + true, false))
  3563 + {
  3564 + QTC::TC("qpdf", "qpdf image optimize no pipeline");
  3565 + if (o.verbose)
  3566 + {
  3567 + std::cout << whoami << ": " << description
  3568 + << ": not optimizing because unable to decode data"
  3569 + << " or data already uses DCT"
  3570 + << std::endl;
  3571 + }
  3572 + return false;
  3573 + }
  3574 + long long orig_length = image.getDict().getKey("/Length").getIntValue();
  3575 + if (c.getCount() >= orig_length)
  3576 + {
  3577 + QTC::TC("qpdf", "qpdf image optimize no shink");
  3578 + if (o.verbose)
  3579 + {
  3580 + std::cout << whoami << ": " << description
  3581 + << ": not optimizing because DCT compression does not"
  3582 + << " reduce image size" << std::endl;
  3583 + }
  3584 + return false;
  3585 + }
  3586 + if (o.verbose)
  3587 + {
  3588 + std::cout << whoami << ": " << description
  3589 + << ": optimizing image reduces size from "
  3590 + << orig_length << " to " << c.getCount()
  3591 + << std::endl;
  3592 + }
  3593 + return true;
  3594 +}
  3595 +
  3596 +void
  3597 +ImageOptimizer::provideStreamData(int, int, Pipeline* pipeline)
  3598 +{
  3599 + PointerHolder<Pipeline> p = makePipeline("", pipeline);
  3600 + if (p.getPointer() == 0)
  3601 + {
  3602 + // Should not be possible
  3603 + image.warnIfPossible("unable to create pipeline after previous"
  3604 + " success; image data will be lost");
  3605 + pipeline->finish();
  3606 + return;
  3607 + }
  3608 + image.pipeStreamData(p.getPointer(), 0, qpdf_dl_specialized,
  3609 + false, false);
  3610 +}
  3611 +
3406 3612 static void handle_transformations(QPDF& pdf, Options& o)
3407 3613 {
3408 3614 QPDFPageDocumentHelper dh(pdf);
  3615 + if (o.optimize_images)
  3616 + {
  3617 + int pageno = 0;
  3618 + std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
  3619 + for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
  3620 + iter != pages.end(); ++iter)
  3621 + {
  3622 + ++pageno;
  3623 + QPDFPageObjectHelper& ph(*iter);
  3624 + QPDFObjectHandle page = ph.getObjectHandle();
  3625 + std::map<std::string, QPDFObjectHandle> images =
  3626 + ph.getPageImages();
  3627 + for (std::map<std::string, QPDFObjectHandle>::iterator iter =
  3628 + images.begin();
  3629 + iter != images.end(); ++iter)
  3630 + {
  3631 + std::string name = (*iter).first;
  3632 + QPDFObjectHandle& image = (*iter).second;
  3633 + ImageOptimizer* io = new ImageOptimizer(o, image);
  3634 + PointerHolder<QPDFObjectHandle::StreamDataProvider> sdp(io);
  3635 + if (io->evaluate("image " + name + " on page " +
  3636 + QUtil::int_to_string(pageno)))
  3637 + {
  3638 + QPDFObjectHandle new_image =
  3639 + QPDFObjectHandle::newStream(&pdf);
  3640 + new_image.replaceDict(image.getDict().shallowCopy());
  3641 + new_image.replaceStreamData(
  3642 + sdp,
  3643 + QPDFObjectHandle::newName("/DCTDecode"),
  3644 + QPDFObjectHandle::newNull());
  3645 + page.getKey("/Resources").getKey("/XObject").replaceKey(
  3646 + name, new_image);
  3647 + }
  3648 + }
  3649 + }
  3650 + }
3409 3651 if (o.generate_appearances)
3410 3652 {
3411 3653 QPDFAcroFormDocumentHelper afdh(pdf);
... ...
qpdf/qpdf.testcov
... ... @@ -402,3 +402,6 @@ QPDFFormFieldObjectHelper list found 0
402 402 QPDFFormFieldObjectHelper list first too low 0
403 403 QPDFFormFieldObjectHelper list last too high 0
404 404 QPDF detected dangling ref 0
  405 +qpdf image optimize no pipeline 0
  406 +qpdf image optimize no shink 0
  407 +qpdf image optimize too small 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -1851,6 +1851,43 @@ $td-&gt;runtest(&quot;get data&quot;,
1851 1851  
1852 1852 show_ntests();
1853 1853 # ----------
  1854 +$td->notify("--- Image Optimization ---");
  1855 +my @image_opt = (
  1856 + ['image-streams', 'image-streams', ''],
  1857 + ['small-images', 'defaults', ''],
  1858 + ['small-images', 'min-width',
  1859 + '--oi-min-width=150 --oi-min-height=0 --oi-min-area=0'],
  1860 + ['small-images', 'min-height',
  1861 + '--oi-min-width=0 --oi-min-height=150 --oi-min-area=0'],
  1862 + ['small-images', 'min-area',
  1863 + '--oi-min-width=0 --oi-min-height=0 --oi-min-area=30000'],
  1864 + ['small-images', 'min-area-all',
  1865 + '--oi-min-width=0 --oi-min-height=0 --oi-min-area=30000'],
  1866 + );
  1867 +
  1868 +$n_tests += 2 * scalar(@image_opt);
  1869 +
  1870 +foreach my $d (@image_opt)
  1871 +{
  1872 + my ($f, $description, $args) = @$d;
  1873 +
  1874 + $td->runtest("optimize images: $description",
  1875 + {$td->COMMAND =>
  1876 + "qpdf --static-id --optimize-images --verbose" .
  1877 + " $args $f.pdf a.pdf",
  1878 + $td->FILTER => "perl filter-optimize-images.pl"},
  1879 + {$td->FILE => "optimize-images-$description.out",
  1880 + $td->EXIT_STATUS => 0},
  1881 + $td->NORMALIZE_NEWLINES);
  1882 + $td->runtest("check json: $description",
  1883 + {$td->COMMAND => "qpdf --json --json-key=pages a.pdf"},
  1884 + {$td->FILE => "optimize-images-$description-json.out",
  1885 + $td->EXIT_STATUS => 0},
  1886 + $td->NORMALIZE_NEWLINES);
  1887 +}
  1888 +
  1889 +show_ntests();
  1890 +# ----------
1854 1891 $td->notify("--- Preserve unreferenced objects ---");
1855 1892 $n_tests += 6;
1856 1893  
... ...
qpdf/qtest/qpdf/filter-optimize-images.pl 0 → 100644
  1 +use strict;
  2 +use warnings;
  3 +
  4 +while (<>)
  5 +{
  6 + s/(reduces size from \d+ to )\d+/$1.../;
  7 + print;
  8 +}
... ...
qpdf/qtest/qpdf/optimize-images-defaults-json.out 0 → 100644
  1 +{
  2 + "pages": [
  3 + {
  4 + "contents": [
  5 + "4 0 R"
  6 + ],
  7 + "images": [
  8 + {
  9 + "bitspercomponent": 8,
  10 + "colorspace": "/DeviceGray",
  11 + "decodeparms": [
  12 + null
  13 + ],
  14 + "filter": [
  15 + "/FlateDecode"
  16 + ],
  17 + "filterable": true,
  18 + "height": 200,
  19 + "name": "/Im1",
  20 + "object": "6 0 R",
  21 + "width": 100
  22 + },
  23 + {
  24 + "bitspercomponent": 8,
  25 + "colorspace": "/DeviceGray",
  26 + "decodeparms": [
  27 + null
  28 + ],
  29 + "filter": [
  30 + "/FlateDecode"
  31 + ],
  32 + "filterable": true,
  33 + "height": 100,
  34 + "name": "/Im2",
  35 + "object": "7 0 R",
  36 + "width": 200
  37 + },
  38 + {
  39 + "bitspercomponent": 8,
  40 + "colorspace": "/DeviceGray",
  41 + "decodeparms": [
  42 + null
  43 + ],
  44 + "filter": [
  45 + "/FlateDecode"
  46 + ],
  47 + "filterable": true,
  48 + "height": 200,
  49 + "name": "/Im3",
  50 + "object": "8 0 R",
  51 + "width": 200
  52 + }
  53 + ],
  54 + "label": null,
  55 + "object": "3 0 R",
  56 + "outlines": [],
  57 + "pageposfrom1": 1
  58 + }
  59 + ],
  60 + "parameters": {
  61 + "decodelevel": "generalized"
  62 + },
  63 + "version": 1
  64 +}
... ...
qpdf/qtest/qpdf/optimize-images-defaults.out 0 → 100644
  1 +qpdf: image /Im1 on page 1: not optimizing because of image is smaller than requested minimum dimensions
  2 +qpdf: image /Im2 on page 1: not optimizing because of image is smaller than requested minimum dimensions
  3 +qpdf: image /Im3 on page 1: not optimizing because DCT compression does not reduce image size
  4 +qpdf: wrote file a.pdf
... ...
qpdf/qtest/qpdf/optimize-images-image-streams-json.out 0 → 100644
  1 +{
  2 + "pages": [
  3 + {
  4 + "contents": [
  5 + "12 0 R"
  6 + ],
  7 + "images": [
  8 + {
  9 + "bitspercomponent": 8,
  10 + "colorspace": "/DeviceCMYK",
  11 + "decodeparms": [
  12 + null
  13 + ],
  14 + "filter": [
  15 + "/DCTDecode"
  16 + ],
  17 + "filterable": false,
  18 + "height": 480,
  19 + "name": "/Im1",
  20 + "object": "14 0 R",
  21 + "width": 400
  22 + }
  23 + ],
  24 + "label": null,
  25 + "object": "3 0 R",
  26 + "outlines": [],
  27 + "pageposfrom1": 1
  28 + },
  29 + {
  30 + "contents": [
  31 + "15 0 R"
  32 + ],
  33 + "images": [
  34 + {
  35 + "bitspercomponent": 8,
  36 + "colorspace": "/DeviceCMYK",
  37 + "decodeparms": [
  38 + null
  39 + ],
  40 + "filter": [
  41 + "/DCTDecode"
  42 + ],
  43 + "filterable": false,
  44 + "height": 480,
  45 + "name": "/Im1",
  46 + "object": "16 0 R",
  47 + "width": 400
  48 + }
  49 + ],
  50 + "label": null,
  51 + "object": "4 0 R",
  52 + "outlines": [],
  53 + "pageposfrom1": 2
  54 + },
  55 + {
  56 + "contents": [
  57 + "17 0 R"
  58 + ],
  59 + "images": [
  60 + {
  61 + "bitspercomponent": 8,
  62 + "colorspace": "/DeviceCMYK",
  63 + "decodeparms": [
  64 + null
  65 + ],
  66 + "filter": [
  67 + "/DCTDecode"
  68 + ],
  69 + "filterable": false,
  70 + "height": 480,
  71 + "name": "/Im1",
  72 + "object": "18 0 R",
  73 + "width": 400
  74 + }
  75 + ],
  76 + "label": null,
  77 + "object": "5 0 R",
  78 + "outlines": [],
  79 + "pageposfrom1": 3
  80 + },
  81 + {
  82 + "contents": [
  83 + "19 0 R"
  84 + ],
  85 + "images": [
  86 + {
  87 + "bitspercomponent": 8,
  88 + "colorspace": "/DeviceRGB",
  89 + "decodeparms": [
  90 + null
  91 + ],
  92 + "filter": [
  93 + "/DCTDecode"
  94 + ],
  95 + "filterable": false,
  96 + "height": 480,
  97 + "name": "/Im1",
  98 + "object": "20 0 R",
  99 + "width": 400
  100 + }
  101 + ],
  102 + "label": null,
  103 + "object": "6 0 R",
  104 + "outlines": [],
  105 + "pageposfrom1": 4
  106 + },
  107 + {
  108 + "contents": [
  109 + "21 0 R"
  110 + ],
  111 + "images": [
  112 + {
  113 + "bitspercomponent": 8,
  114 + "colorspace": "/DeviceRGB",
  115 + "decodeparms": [
  116 + null
  117 + ],
  118 + "filter": [
  119 + "/DCTDecode"
  120 + ],
  121 + "filterable": false,
  122 + "height": 480,
  123 + "name": "/Im1",
  124 + "object": "22 0 R",
  125 + "width": 400
  126 + }
  127 + ],
  128 + "label": null,
  129 + "object": "7 0 R",
  130 + "outlines": [],
  131 + "pageposfrom1": 5
  132 + },
  133 + {
  134 + "contents": [
  135 + "23 0 R"
  136 + ],
  137 + "images": [
  138 + {
  139 + "bitspercomponent": 8,
  140 + "colorspace": "/DeviceRGB",
  141 + "decodeparms": [
  142 + null
  143 + ],
  144 + "filter": [
  145 + "/DCTDecode"
  146 + ],
  147 + "filterable": false,
  148 + "height": 480,
  149 + "name": "/Im1",
  150 + "object": "24 0 R",
  151 + "width": 400
  152 + }
  153 + ],
  154 + "label": null,
  155 + "object": "8 0 R",
  156 + "outlines": [],
  157 + "pageposfrom1": 6
  158 + },
  159 + {
  160 + "contents": [
  161 + "25 0 R"
  162 + ],
  163 + "images": [
  164 + {
  165 + "bitspercomponent": 8,
  166 + "colorspace": "/DeviceGray",
  167 + "decodeparms": [
  168 + null
  169 + ],
  170 + "filter": [
  171 + "/DCTDecode"
  172 + ],
  173 + "filterable": false,
  174 + "height": 480,
  175 + "name": "/Im1",
  176 + "object": "26 0 R",
  177 + "width": 400
  178 + }
  179 + ],
  180 + "label": null,
  181 + "object": "9 0 R",
  182 + "outlines": [],
  183 + "pageposfrom1": 7
  184 + },
  185 + {
  186 + "contents": [
  187 + "27 0 R"
  188 + ],
  189 + "images": [
  190 + {
  191 + "bitspercomponent": 8,
  192 + "colorspace": "/DeviceGray",
  193 + "decodeparms": [
  194 + null
  195 + ],
  196 + "filter": [
  197 + "/DCTDecode"
  198 + ],
  199 + "filterable": false,
  200 + "height": 480,
  201 + "name": "/Im1",
  202 + "object": "28 0 R",
  203 + "width": 400
  204 + }
  205 + ],
  206 + "label": null,
  207 + "object": "10 0 R",
  208 + "outlines": [],
  209 + "pageposfrom1": 8
  210 + },
  211 + {
  212 + "contents": [
  213 + "29 0 R"
  214 + ],
  215 + "images": [
  216 + {
  217 + "bitspercomponent": 8,
  218 + "colorspace": "/DeviceGray",
  219 + "decodeparms": [
  220 + null
  221 + ],
  222 + "filter": [
  223 + "/DCTDecode"
  224 + ],
  225 + "filterable": false,
  226 + "height": 480,
  227 + "name": "/Im1",
  228 + "object": "30 0 R",
  229 + "width": 400
  230 + }
  231 + ],
  232 + "label": null,
  233 + "object": "11 0 R",
  234 + "outlines": [],
  235 + "pageposfrom1": 9
  236 + }
  237 + ],
  238 + "parameters": {
  239 + "decodelevel": "generalized"
  240 + },
  241 + "version": 1
  242 +}
... ...
qpdf/qtest/qpdf/optimize-images-image-streams.out 0 → 100644
  1 +qpdf: image /Im1 on page 1: optimizing image reduces size from 768000 to ...
  2 +qpdf: image /Im1 on page 2: not optimizing because unable to decode data or data already uses DCT
  3 +qpdf: image /Im1 on page 3: optimizing image reduces size from 768998 to ...
  4 +qpdf: image /Im1 on page 4: optimizing image reduces size from 576000 to ...
  5 +qpdf: image /Im1 on page 5: not optimizing because unable to decode data or data already uses DCT
  6 +qpdf: image /Im1 on page 6: optimizing image reduces size from 641497 to ...
  7 +qpdf: image /Im1 on page 7: optimizing image reduces size from 192000 to ...
  8 +qpdf: image /Im1 on page 8: not optimizing because unable to decode data or data already uses DCT
  9 +qpdf: image /Im1 on page 9: optimizing image reduces size from 3001 to ...
  10 +qpdf: wrote file a.pdf
... ...
qpdf/qtest/qpdf/optimize-images-min-area-all-json.out 0 → 100644
  1 +{
  2 + "pages": [
  3 + {
  4 + "contents": [
  5 + "4 0 R"
  6 + ],
  7 + "images": [
  8 + {
  9 + "bitspercomponent": 8,
  10 + "colorspace": "/DeviceGray",
  11 + "decodeparms": [
  12 + null
  13 + ],
  14 + "filter": [
  15 + "/FlateDecode"
  16 + ],
  17 + "filterable": true,
  18 + "height": 200,
  19 + "name": "/Im1",
  20 + "object": "6 0 R",
  21 + "width": 100
  22 + },
  23 + {
  24 + "bitspercomponent": 8,
  25 + "colorspace": "/DeviceGray",
  26 + "decodeparms": [
  27 + null
  28 + ],
  29 + "filter": [
  30 + "/FlateDecode"
  31 + ],
  32 + "filterable": true,
  33 + "height": 100,
  34 + "name": "/Im2",
  35 + "object": "7 0 R",
  36 + "width": 200
  37 + },
  38 + {
  39 + "bitspercomponent": 8,
  40 + "colorspace": "/DeviceGray",
  41 + "decodeparms": [
  42 + null
  43 + ],
  44 + "filter": [
  45 + "/FlateDecode"
  46 + ],
  47 + "filterable": true,
  48 + "height": 200,
  49 + "name": "/Im3",
  50 + "object": "8 0 R",
  51 + "width": 200
  52 + }
  53 + ],
  54 + "label": null,
  55 + "object": "3 0 R",
  56 + "outlines": [],
  57 + "pageposfrom1": 1
  58 + }
  59 + ],
  60 + "parameters": {
  61 + "decodelevel": "generalized"
  62 + },
  63 + "version": 1
  64 +}
... ...
qpdf/qtest/qpdf/optimize-images-min-area-all.out 0 → 100644
  1 +qpdf: image /Im1 on page 1: not optimizing because of image is smaller than requested minimum dimensions
  2 +qpdf: image /Im2 on page 1: not optimizing because of image is smaller than requested minimum dimensions
  3 +qpdf: image /Im3 on page 1: not optimizing because DCT compression does not reduce image size
  4 +qpdf: wrote file a.pdf
... ...
qpdf/qtest/qpdf/optimize-images-min-area-json.out 0 → 100644
  1 +{
  2 + "pages": [
  3 + {
  4 + "contents": [
  5 + "4 0 R"
  6 + ],
  7 + "images": [
  8 + {
  9 + "bitspercomponent": 8,
  10 + "colorspace": "/DeviceGray",
  11 + "decodeparms": [
  12 + null
  13 + ],
  14 + "filter": [
  15 + "/FlateDecode"
  16 + ],
  17 + "filterable": true,
  18 + "height": 200,
  19 + "name": "/Im1",
  20 + "object": "6 0 R",
  21 + "width": 100
  22 + },
  23 + {
  24 + "bitspercomponent": 8,
  25 + "colorspace": "/DeviceGray",
  26 + "decodeparms": [
  27 + null
  28 + ],
  29 + "filter": [
  30 + "/FlateDecode"
  31 + ],
  32 + "filterable": true,
  33 + "height": 100,
  34 + "name": "/Im2",
  35 + "object": "7 0 R",
  36 + "width": 200
  37 + },
  38 + {
  39 + "bitspercomponent": 8,
  40 + "colorspace": "/DeviceGray",
  41 + "decodeparms": [
  42 + null
  43 + ],
  44 + "filter": [
  45 + "/FlateDecode"
  46 + ],
  47 + "filterable": true,
  48 + "height": 200,
  49 + "name": "/Im3",
  50 + "object": "8 0 R",
  51 + "width": 200
  52 + }
  53 + ],
  54 + "label": null,
  55 + "object": "3 0 R",
  56 + "outlines": [],
  57 + "pageposfrom1": 1
  58 + }
  59 + ],
  60 + "parameters": {
  61 + "decodelevel": "generalized"
  62 + },
  63 + "version": 1
  64 +}
... ...
qpdf/qtest/qpdf/optimize-images-min-area.out 0 → 100644
  1 +qpdf: image /Im1 on page 1: not optimizing because of image is smaller than requested minimum dimensions
  2 +qpdf: image /Im2 on page 1: not optimizing because of image is smaller than requested minimum dimensions
  3 +qpdf: image /Im3 on page 1: not optimizing because DCT compression does not reduce image size
  4 +qpdf: wrote file a.pdf
... ...
qpdf/qtest/qpdf/optimize-images-min-height-json.out 0 → 100644
  1 +{
  2 + "pages": [
  3 + {
  4 + "contents": [
  5 + "4 0 R"
  6 + ],
  7 + "images": [
  8 + {
  9 + "bitspercomponent": 8,
  10 + "colorspace": "/DeviceGray",
  11 + "decodeparms": [
  12 + null
  13 + ],
  14 + "filter": [
  15 + "/DCTDecode"
  16 + ],
  17 + "filterable": false,
  18 + "height": 200,
  19 + "name": "/Im1",
  20 + "object": "6 0 R",
  21 + "width": 100
  22 + },
  23 + {
  24 + "bitspercomponent": 8,
  25 + "colorspace": "/DeviceGray",
  26 + "decodeparms": [
  27 + null
  28 + ],
  29 + "filter": [
  30 + "/FlateDecode"
  31 + ],
  32 + "filterable": true,
  33 + "height": 100,
  34 + "name": "/Im2",
  35 + "object": "7 0 R",
  36 + "width": 200
  37 + },
  38 + {
  39 + "bitspercomponent": 8,
  40 + "colorspace": "/DeviceGray",
  41 + "decodeparms": [
  42 + null
  43 + ],
  44 + "filter": [
  45 + "/FlateDecode"
  46 + ],
  47 + "filterable": true,
  48 + "height": 200,
  49 + "name": "/Im3",
  50 + "object": "8 0 R",
  51 + "width": 200
  52 + }
  53 + ],
  54 + "label": null,
  55 + "object": "3 0 R",
  56 + "outlines": [],
  57 + "pageposfrom1": 1
  58 + }
  59 + ],
  60 + "parameters": {
  61 + "decodelevel": "generalized"
  62 + },
  63 + "version": 1
  64 +}
... ...
qpdf/qtest/qpdf/optimize-images-min-height.out 0 → 100644
  1 +qpdf: image /Im1 on page 1: optimizing image reduces size from 20000 to ...
  2 +qpdf: image /Im2 on page 1: not optimizing because of image is smaller than requested minimum dimensions
  3 +qpdf: image /Im3 on page 1: not optimizing because DCT compression does not reduce image size
  4 +qpdf: wrote file a.pdf
... ...
qpdf/qtest/qpdf/optimize-images-min-width-json.out 0 → 100644
  1 +{
  2 + "pages": [
  3 + {
  4 + "contents": [
  5 + "4 0 R"
  6 + ],
  7 + "images": [
  8 + {
  9 + "bitspercomponent": 8,
  10 + "colorspace": "/DeviceGray",
  11 + "decodeparms": [
  12 + null
  13 + ],
  14 + "filter": [
  15 + "/FlateDecode"
  16 + ],
  17 + "filterable": true,
  18 + "height": 200,
  19 + "name": "/Im1",
  20 + "object": "6 0 R",
  21 + "width": 100
  22 + },
  23 + {
  24 + "bitspercomponent": 8,
  25 + "colorspace": "/DeviceGray",
  26 + "decodeparms": [
  27 + null
  28 + ],
  29 + "filter": [
  30 + "/DCTDecode"
  31 + ],
  32 + "filterable": false,
  33 + "height": 100,
  34 + "name": "/Im2",
  35 + "object": "7 0 R",
  36 + "width": 200
  37 + },
  38 + {
  39 + "bitspercomponent": 8,
  40 + "colorspace": "/DeviceGray",
  41 + "decodeparms": [
  42 + null
  43 + ],
  44 + "filter": [
  45 + "/FlateDecode"
  46 + ],
  47 + "filterable": true,
  48 + "height": 200,
  49 + "name": "/Im3",
  50 + "object": "8 0 R",
  51 + "width": 200
  52 + }
  53 + ],
  54 + "label": null,
  55 + "object": "3 0 R",
  56 + "outlines": [],
  57 + "pageposfrom1": 1
  58 + }
  59 + ],
  60 + "parameters": {
  61 + "decodelevel": "generalized"
  62 + },
  63 + "version": 1
  64 +}
... ...
qpdf/qtest/qpdf/optimize-images-min-width.out 0 → 100644
  1 +qpdf: image /Im1 on page 1: not optimizing because of image is smaller than requested minimum dimensions
  2 +qpdf: image /Im2 on page 1: optimizing image reduces size from 20000 to ...
  3 +qpdf: image /Im3 on page 1: not optimizing because DCT compression does not reduce image size
  4 +qpdf: wrote file a.pdf
... ...
qpdf/qtest/qpdf/small-images.pdf 0 → 100644
No preview for this file type