Commit 65ae8511a7f986433b5631460c963de96a5907f2

Authored by Jay Berkenbilt
1 parent fbac4725

Improve pdf-invert-images example

ChangeLog
  1 +2020-04-07 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Improve pdf-invert-images example to show a pattern of copying
  4 + streams into another QPDF object to enable a stream data provider
  5 + to access the original stream data.
  6 +
  7 + * Fix error that caused a compilation error with clang. Fixes
  8 + #424.
  9 +
1 10 2020-04-06 Jay Berkenbilt <ejb@ql.org>
2 11  
3 12 * 10.0.0: release
... ...
examples/pdf-invert-images.cc
... ... @@ -21,28 +21,78 @@ void usage()
21 21 }
22 22  
23 23 // Derive a class from StreamDataProvider to provide updated stream
24   -// data. The main purpose of using this object is to avoid having to
25   -// allocate memory up front for the objects. A real application might
26   -// use temporary files in order to avoid having to allocate all the
27   -// memory. Here, we're not going to worry about that since the goal
28   -// is really to show how to use this facility rather than to show the
29   -// best possible way to write an image inverter. This class still
30   -// illustrates dynamic creation of the new stream data.
  24 +// data. The main purpose of using this object is to avoid having to
  25 +// allocate memory up front for the objects. We want to replace the
  26 +// stream data with a function of the original stream data. In order
  27 +// to do this without actually holding all the images in memory, we
  28 +// create another QPDF object and copy the streams. Copying the
  29 +// streams doesn't actually copy the data. Internally, the qpdf
  30 +// library is holding onto the location of the stream data in the
  31 +// original file, which makes it possible for the StreamDataProvider
  32 +// to access it when it needs it.
31 33 class ImageInverter: public QPDFObjectHandle::StreamDataProvider
32 34 {
33 35 public:
  36 + ImageInverter();
34 37 virtual ~ImageInverter()
35 38 {
36 39 }
37 40 virtual void provideStreamData(int objid, int generation,
38   - Pipeline* pipeline);
  41 + Pipeline* pipeline) override;
39 42  
40   - // Map [og] = image object
41   - std::map<QPDFObjGen, QPDFObjectHandle> image_objects;
42   - // Map [og] = image data
43   - std::map<QPDFObjGen, PointerHolder<Buffer> > image_data;
  43 + void setSelfPh(PointerHolder<QPDFObjectHandle::StreamDataProvider>);
  44 + void registerImage(QPDFObjectHandle image);
  45 +
  46 + private:
  47 + QPDF other;
  48 + PointerHolder<QPDFObjectHandle::StreamDataProvider> self_ph;
  49 + // Map og in original to copied image
  50 + std::map<QPDFObjGen, QPDFObjectHandle> copied_images;
44 51 };
45 52  
  53 +ImageInverter::ImageInverter()
  54 +{
  55 + this->other.emptyPDF();
  56 +}
  57 +
  58 +void
  59 +ImageInverter::setSelfPh(PointerHolder<QPDFObjectHandle::StreamDataProvider> p)
  60 +{
  61 + // replaceStreamData requires a pointer holder to the stream data
  62 + // provider, but there's no way for us to generate one ourselves,
  63 + // so we have to have it handed to us.
  64 + this->self_ph = p;
  65 +}
  66 +
  67 +
  68 +void
  69 +ImageInverter::registerImage(QPDFObjectHandle image)
  70 +{
  71 + QPDFObjGen og(image.getObjGen());
  72 + // Store information about the images based on the object and
  73 + // generation number. Recall that a single image object may be
  74 + // used more than once, so no need to update the same stream
  75 + // multiple times.
  76 + if (this->copied_images.count(og) > 0)
  77 + {
  78 + return;
  79 + }
  80 + this->copied_images[og] = this->other.copyForeignObject(image);
  81 +
  82 + // Register our stream data provider for this stream. Future calls
  83 + // to getStreamData or pipeStreamData will use the new
  84 + // information. Provide null for both filter and decode
  85 + // parameters. Note that this does not mean the image data will be
  86 + // uncompressed when we write the file. By default, QPDFWriter
  87 + // will use /FlateDecode for anything that is uncompressed or
  88 + // filterable in the input QPDF object, so we don't have to deal
  89 + // with it explicitly here. We could explicitly use /DCTDecode and
  90 + // write through a DCT filter if we wanted.
  91 + image.replaceStreamData(this->self_ph,
  92 + QPDFObjectHandle::newNull(),
  93 + QPDFObjectHandle::newNull());
  94 +}
  95 +
46 96 void
47 97 ImageInverter::provideStreamData(int objid, int generation,
48 98 Pipeline* pipeline)
... ... @@ -50,8 +100,9 @@ ImageInverter::provideStreamData(int objid, int generation,
50 100 // Use the object and generation number supplied to look up the
51 101 // image data. Then invert the image data and write the inverted
52 102 // data to the pipeline.
  103 + QPDFObjGen og(objid, generation);
53 104 PointerHolder<Buffer> data =
54   - this->image_data[QPDFObjGen(objid, generation)];
  105 + this->copied_images[og].getStreamData(qpdf_dl_all);
55 106 size_t size = data->getSize();
56 107 unsigned char* buf = data->getBuffer();
57 108 unsigned char ch;
... ... @@ -98,6 +149,9 @@ int main(int argc, char* argv[])
98 149  
99 150 ImageInverter* inv = new ImageInverter;
100 151 PointerHolder<QPDFObjectHandle::StreamDataProvider> p = inv;
  152 + // We need to give ImageInverter the pointer holder that it
  153 + // needs to pass to replaceStreamData.
  154 + inv->setSelfPh(p);
101 155  
102 156 // For each page...
103 157 std::vector<QPDFPageObjectHelper> pages =
... ... @@ -126,38 +180,14 @@ int main(int argc, char* argv[])
126 180 // whether the image is filterable. Directly inspect
127 181 // keys to determine the image type.
128 182 if (image.pipeStreamData(0, qpdf_ef_compress,
129   - qpdf_dl_generalized) &&
  183 + qpdf_dl_all) &&
130 184 color_space.isName() &&
131 185 bits_per_component.isInteger() &&
132 186 (color_space.getName() == "/DeviceGray") &&
133 187 (bits_per_component.getIntValue() == 8))
134 188 {
135   - // Store information about the images based on the
136   - // object and generation number. Recall that a single
137   - // image object may be used more than once.
138   - QPDFObjGen og = image.getObjGen();
139   - if (inv->image_objects.count(og) == 0)
140   - {
141   - inv->image_objects[og] = image;
142   - inv->image_data[og] = image.getStreamData();
143   -
144   - // Register our stream data provider for this
145   - // stream. Future calls to getStreamData or
146   - // pipeStreamData will use the new
147   - // information. Provide null for both filter
148   - // and decode parameters. Note that this does
149   - // not mean the image data will be
150   - // uncompressed when we write the file. By
151   - // default, QPDFWriter will use /FlateDecode
152   - // for anything that is uncompressed or
153   - // filterable in the input QPDF object, so we
154   - // don't have to deal with it explicitly here.
155   - image.replaceStreamData(
156   - p,
157   - QPDFObjectHandle::newNull(),
158   - QPDFObjectHandle::newNull());
159   - }
160   - }
  189 + inv->registerImage(image);
  190 + }
161 191 }
162 192 }
163 193  
... ...