mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 10:58:58 +00:00
Improve pdf-invert-images example
This commit is contained in:
parent
fbac472510
commit
65ae8511a7
@ -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
|
||||
|
@ -22,27 +22,77 @@ 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.
|
||||
// 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,37 +180,13 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user