diff options
Diffstat (limited to 'examples/pdf-invert-images.cc')
-rw-r--r-- | examples/pdf-invert-images.cc | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/examples/pdf-invert-images.cc b/examples/pdf-invert-images.cc new file mode 100644 index 00000000..e84181bc --- /dev/null +++ b/examples/pdf-invert-images.cc @@ -0,0 +1,172 @@ +#include <iostream> +#include <string.h> +#include <stdlib.h> +#include <qpdf/QPDF.hh> +#include <qpdf/QUtil.hh> +#include <qpdf/Buffer.hh> +#include <qpdf/QPDFWriter.hh> + +static char const* whoami = 0; + +void usage() +{ + std::cerr << "Usage: " << whoami << " infile.pdf outfile.pdf [in-password]" + << std::endl + << "Invert some images in infile.pdf;" + << " write output to outfile.pdf" << std::endl; + exit(2); +} + +// 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. +class ImageInverter: public QPDFObjectHandle::StreamDataProvider +{ + public: + virtual ~ImageInverter() + { + } + virtual void provideStreamData(int objid, int generation, + Pipeline* pipeline); + + // Map [obj][gen] = image object + std::map<int, std::map<int, QPDFObjectHandle> > image_objects; + // Map [obj][gen] = image data + std::map<int, std::map<int, PointerHolder<Buffer> > > image_data; +}; + +void +ImageInverter::provideStreamData(int objid, int generation, + Pipeline* pipeline) +{ + // 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. + PointerHolder<Buffer> data = this->image_data[objid][generation]; + size_t size = data.getPointer()->getSize(); + unsigned char* buf = data.getPointer()->getBuffer(); + unsigned char ch; + for (size_t i = 0; i < size; ++i) + { + ch = (unsigned char)0xff - buf[i]; + pipeline->write(&ch, 1); + } + pipeline->finish(); +} + +int main(int argc, char* argv[]) +{ + whoami = QUtil::getWhoami(argv[0]); + + // For libtool's sake.... + if (strncmp(whoami, "lt-", 3) == 0) + { + whoami += 3; + } + + if (! ((argc == 3) || (argc == 4))) + { + usage(); + } + + char const* infilename = argv[1]; + char const* outfilename = argv[2]; + char const* password = (argc == 4) ? argv[3] : ""; + + try + { + QPDF qpdf; + qpdf.processFile(infilename, password); + + ImageInverter* inv = new ImageInverter; + PointerHolder<QPDFObjectHandle::StreamDataProvider> p = inv; + + // For each page... + std::vector<QPDFObjectHandle> pages = qpdf.getAllPages(); + for (std::vector<QPDFObjectHandle>::iterator iter = pages.begin(); + iter != pages.end(); ++iter) + { + QPDFObjectHandle& page = *iter; + // Get all images on the page. + std::map<std::string, QPDFObjectHandle> images = + page.getPageImages(); + for (std::map<std::string, QPDFObjectHandle>::iterator iter = + images.begin(); + iter != images.end(); ++iter) + { + QPDFObjectHandle& image = (*iter).second; + QPDFObjectHandle image_dict = image.getDict(); + QPDFObjectHandle color_space = + image_dict.getKey("/ColorSpace"); + QPDFObjectHandle bits_per_component = + image_dict.getKey("/BitsPerComponent"); + + // For our example, we can only work with images 8-bit + // grayscale images that we can fully decode. Use + // pipeStreamData with a null pipeline to determine + // whether the image is filterable. Directly inspect + // keys to determine the image type. + if (image.pipeStreamData(0, true, false, false) && + 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. + int objid = image.getObjectID(); + int gen = image.getGeneration(); + if (inv->image_objects[objid].count(gen) == 0) + { + inv->image_objects[objid][gen] = image; + inv->image_data[objid][gen] = 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->image_data[objid][gen].getPointer()-> + getSize()); + } + } + } + } + + // Write out a new file + QPDFWriter w(qpdf, outfilename); + if (QUtil::get_env("IN_TESTSUITE")) + { + // For the test suite, uncompress streams and use static + // IDs. + w.setStaticID(true); + } + w.write(); + std::cout << whoami << ": new file written to " << outfilename + << std::endl; + } + catch (std::exception &e) + { + std::cerr << whoami << " processing file " << infilename << ": " + << e.what() << std::endl; + exit(2); + } + + return 0; +} |