Commit 65ae8511a7f986433b5631460c963de96a5907f2
1 parent
fbac4725
Improve pdf-invert-images example
Showing
2 changed files
with
79 additions
and
40 deletions
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 | 2020-04-06 Jay Berkenbilt <ejb@ql.org> | 10 | 2020-04-06 Jay Berkenbilt <ejb@ql.org> |
| 2 | 11 | ||
| 3 | * 10.0.0: release | 12 | * 10.0.0: release |
examples/pdf-invert-images.cc
| @@ -21,28 +21,78 @@ void usage() | @@ -21,28 +21,78 @@ void usage() | ||
| 21 | } | 21 | } |
| 22 | 22 | ||
| 23 | // Derive a class from StreamDataProvider to provide updated stream | 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 | class ImageInverter: public QPDFObjectHandle::StreamDataProvider | 33 | class ImageInverter: public QPDFObjectHandle::StreamDataProvider |
| 32 | { | 34 | { |
| 33 | public: | 35 | public: |
| 36 | + ImageInverter(); | ||
| 34 | virtual ~ImageInverter() | 37 | virtual ~ImageInverter() |
| 35 | { | 38 | { |
| 36 | } | 39 | } |
| 37 | virtual void provideStreamData(int objid, int generation, | 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 | void | 96 | void |
| 47 | ImageInverter::provideStreamData(int objid, int generation, | 97 | ImageInverter::provideStreamData(int objid, int generation, |
| 48 | Pipeline* pipeline) | 98 | Pipeline* pipeline) |
| @@ -50,8 +100,9 @@ ImageInverter::provideStreamData(int objid, int generation, | @@ -50,8 +100,9 @@ ImageInverter::provideStreamData(int objid, int generation, | ||
| 50 | // Use the object and generation number supplied to look up the | 100 | // Use the object and generation number supplied to look up the |
| 51 | // image data. Then invert the image data and write the inverted | 101 | // image data. Then invert the image data and write the inverted |
| 52 | // data to the pipeline. | 102 | // data to the pipeline. |
| 103 | + QPDFObjGen og(objid, generation); | ||
| 53 | PointerHolder<Buffer> data = | 104 | PointerHolder<Buffer> data = |
| 54 | - this->image_data[QPDFObjGen(objid, generation)]; | 105 | + this->copied_images[og].getStreamData(qpdf_dl_all); |
| 55 | size_t size = data->getSize(); | 106 | size_t size = data->getSize(); |
| 56 | unsigned char* buf = data->getBuffer(); | 107 | unsigned char* buf = data->getBuffer(); |
| 57 | unsigned char ch; | 108 | unsigned char ch; |
| @@ -98,6 +149,9 @@ int main(int argc, char* argv[]) | @@ -98,6 +149,9 @@ int main(int argc, char* argv[]) | ||
| 98 | 149 | ||
| 99 | ImageInverter* inv = new ImageInverter; | 150 | ImageInverter* inv = new ImageInverter; |
| 100 | PointerHolder<QPDFObjectHandle::StreamDataProvider> p = inv; | 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 | // For each page... | 156 | // For each page... |
| 103 | std::vector<QPDFPageObjectHelper> pages = | 157 | std::vector<QPDFPageObjectHelper> pages = |
| @@ -126,38 +180,14 @@ int main(int argc, char* argv[]) | @@ -126,38 +180,14 @@ int main(int argc, char* argv[]) | ||
| 126 | // whether the image is filterable. Directly inspect | 180 | // whether the image is filterable. Directly inspect |
| 127 | // keys to determine the image type. | 181 | // keys to determine the image type. |
| 128 | if (image.pipeStreamData(0, qpdf_ef_compress, | 182 | if (image.pipeStreamData(0, qpdf_ef_compress, |
| 129 | - qpdf_dl_generalized) && | 183 | + qpdf_dl_all) && |
| 130 | color_space.isName() && | 184 | color_space.isName() && |
| 131 | bits_per_component.isInteger() && | 185 | bits_per_component.isInteger() && |
| 132 | (color_space.getName() == "/DeviceGray") && | 186 | (color_space.getName() == "/DeviceGray") && |
| 133 | (bits_per_component.getIntValue() == 8)) | 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 |