diff --git a/ChangeLog b/ChangeLog index d74264fd..d9790438 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2020-04-07 Jay Berkenbilt + + * Improve pdf-invert-images example to show a pattern of copying + streams into another QPDF object to enable a stream data provider + to access the original stream data. + + * Fix error that caused a compilation error with clang. Fixes + #424. + 2020-04-06 Jay Berkenbilt * 10.0.0: release diff --git a/examples/pdf-invert-images.cc b/examples/pdf-invert-images.cc index a149c495..37af1b73 100644 --- a/examples/pdf-invert-images.cc +++ b/examples/pdf-invert-images.cc @@ -21,28 +21,78 @@ void usage() } // Derive a class from StreamDataProvider to provide updated stream -// data. The main purpose of using this object is to avoid having to -// allocate memory up front for the objects. A real application might -// use temporary files in order to avoid having to allocate all the -// memory. Here, we're not going to worry about that since the goal -// is really to show how to use this facility rather than to show the -// best possible way to write an image inverter. This class still -// illustrates dynamic creation of the new stream data. +// data. The main purpose of using this object is to avoid having to +// allocate memory up front for the objects. We want to replace the +// stream data with a function of the original stream data. In order +// to do this without actually holding all the images in memory, we +// create another QPDF object and copy the streams. Copying the +// streams doesn't actually copy the data. Internally, the qpdf +// library is holding onto the location of the stream data in the +// original file, which makes it possible for the StreamDataProvider +// to access it when it needs it. class ImageInverter: public QPDFObjectHandle::StreamDataProvider { public: + ImageInverter(); virtual ~ImageInverter() { } virtual void provideStreamData(int objid, int generation, - Pipeline* pipeline); + Pipeline* pipeline) override; - // Map [og] = image object - std::map image_objects; - // Map [og] = image data - std::map > image_data; + void setSelfPh(PointerHolder); + void registerImage(QPDFObjectHandle image); + + private: + QPDF other; + PointerHolder self_ph; + // Map og in original to copied image + std::map copied_images; }; +ImageInverter::ImageInverter() +{ + this->other.emptyPDF(); +} + +void +ImageInverter::setSelfPh(PointerHolder p) +{ + // replaceStreamData requires a pointer holder to the stream data + // provider, but there's no way for us to generate one ourselves, + // so we have to have it handed to us. + this->self_ph = p; +} + + +void +ImageInverter::registerImage(QPDFObjectHandle image) +{ + QPDFObjGen og(image.getObjGen()); + // Store information about the images based on the object and + // generation number. Recall that a single image object may be + // used more than once, so no need to update the same stream + // multiple times. + if (this->copied_images.count(og) > 0) + { + return; + } + this->copied_images[og] = this->other.copyForeignObject(image); + + // Register our stream data provider for this stream. Future calls + // to getStreamData or pipeStreamData will use the new + // information. Provide null for both filter and decode + // parameters. Note that this does not mean the image data will be + // uncompressed when we write the file. By default, QPDFWriter + // will use /FlateDecode for anything that is uncompressed or + // filterable in the input QPDF object, so we don't have to deal + // with it explicitly here. We could explicitly use /DCTDecode and + // write through a DCT filter if we wanted. + image.replaceStreamData(this->self_ph, + QPDFObjectHandle::newNull(), + QPDFObjectHandle::newNull()); +} + void ImageInverter::provideStreamData(int objid, int generation, Pipeline* pipeline) @@ -50,8 +100,9 @@ ImageInverter::provideStreamData(int objid, int generation, // Use the object and generation number supplied to look up the // image data. Then invert the image data and write the inverted // data to the pipeline. + QPDFObjGen og(objid, generation); PointerHolder data = - this->image_data[QPDFObjGen(objid, generation)]; + this->copied_images[og].getStreamData(qpdf_dl_all); size_t size = data->getSize(); unsigned char* buf = data->getBuffer(); unsigned char ch; @@ -98,6 +149,9 @@ int main(int argc, char* argv[]) ImageInverter* inv = new ImageInverter; PointerHolder p = inv; + // We need to give ImageInverter the pointer holder that it + // needs to pass to replaceStreamData. + inv->setSelfPh(p); // For each page... std::vector pages = @@ -126,38 +180,14 @@ int main(int argc, char* argv[]) // whether the image is filterable. Directly inspect // keys to determine the image type. if (image.pipeStreamData(0, qpdf_ef_compress, - qpdf_dl_generalized) && + qpdf_dl_all) && color_space.isName() && bits_per_component.isInteger() && (color_space.getName() == "/DeviceGray") && (bits_per_component.getIntValue() == 8)) { - // Store information about the images based on the - // object and generation number. Recall that a single - // image object may be used more than once. - QPDFObjGen og = image.getObjGen(); - if (inv->image_objects.count(og) == 0) - { - inv->image_objects[og] = image; - inv->image_data[og] = image.getStreamData(); - - // Register our stream data provider for this - // stream. Future calls to getStreamData or - // pipeStreamData will use the new - // information. Provide null for both filter - // and decode parameters. Note that this does - // not mean the image data will be - // uncompressed when we write the file. By - // default, QPDFWriter will use /FlateDecode - // for anything that is uncompressed or - // filterable in the input QPDF object, so we - // don't have to deal with it explicitly here. - image.replaceStreamData( - p, - QPDFObjectHandle::newNull(), - QPDFObjectHandle::newNull()); - } - } + inv->registerImage(image); + } } }