Improve pdf-invert-images example

This commit is contained in:
Jay Berkenbilt 2020-04-07 18:11:00 -04:00
parent fbac472510
commit 65ae8511a7
2 changed files with 79 additions and 40 deletions

View File

@ -1,3 +1,12 @@
2020-04-07 Jay Berkenbilt <ejb@ql.org>
* 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 <ejb@ql.org>
* 10.0.0: release

View File

@ -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<QPDFObjGen, QPDFObjectHandle> image_objects;
// Map [og] = image data
std::map<QPDFObjGen, PointerHolder<Buffer> > image_data;
void setSelfPh(PointerHolder<QPDFObjectHandle::StreamDataProvider>);
void registerImage(QPDFObjectHandle image);
private:
QPDF other;
PointerHolder<QPDFObjectHandle::StreamDataProvider> self_ph;
// Map og in original to copied image
std::map<QPDFObjGen, QPDFObjectHandle> copied_images;
};
ImageInverter::ImageInverter()
{
this->other.emptyPDF();
}
void
ImageInverter::setSelfPh(PointerHolder<QPDFObjectHandle::StreamDataProvider> 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<Buffer> 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<QPDFObjectHandle::StreamDataProvider> 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<QPDFPageObjectHelper> 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);
}
}
}