aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/qpdf/Pl_DCT.hh70
-rw-r--r--libqpdf/Pl_DCT.cc204
-rw-r--r--libqpdf/build.mk1
-rw-r--r--libtests/build.mk2
-rw-r--r--libtests/dct_compress.cc93
-rw-r--r--libtests/dct_uncompress.cc44
-rw-r--r--libtests/qtest/dct.test66
-rw-r--r--libtests/qtest/dct/rawdatabin0 -> 102400 bytes
8 files changed, 480 insertions, 0 deletions
diff --git a/include/qpdf/Pl_DCT.hh b/include/qpdf/Pl_DCT.hh
new file mode 100644
index 00000000..b7415394
--- /dev/null
+++ b/include/qpdf/Pl_DCT.hh
@@ -0,0 +1,70 @@
+// Copyright (c) 2005-2015 Jay Berkenbilt
+//
+// This file is part of qpdf. This software may be distributed under
+// the terms of version 2 of the Artistic License which may be found
+// in the source distribution. It is provided "as is" without express
+// or implied warranty.
+
+#ifndef __PL_DCT_HH__
+#define __PL_DCT_HH__
+
+#include <qpdf/Pipeline.hh>
+#include <qpdf/Pl_Buffer.hh>
+#include <jpeglib.h>
+
+class Pl_DCT: public Pipeline
+{
+ public:
+ // Constructor for decompressing image data
+ QPDF_DLL
+ Pl_DCT(char const* identifier, Pipeline* next);
+
+ class CompressConfig
+ {
+ public:
+ CompressConfig()
+ {
+ }
+ virtual ~CompressConfig()
+ {
+ }
+ virtual void apply(jpeg_compress_struct*) = 0;
+ };
+
+ // Constructor for compressing image data
+ QPDF_DLL
+ Pl_DCT(char const* identifier, Pipeline* next,
+ JDIMENSION image_width,
+ JDIMENSION image_height,
+ int components,
+ J_COLOR_SPACE color_space,
+ CompressConfig* config_callback = 0);
+
+ QPDF_DLL
+ virtual ~Pl_DCT();
+
+ QPDF_DLL
+ virtual void write(unsigned char* data, size_t len);
+ QPDF_DLL
+ virtual void finish();
+
+ private:
+ void compress(void* cinfo, PointerHolder<Buffer>);
+ void decompress(void* cinfo, PointerHolder<Buffer>);
+
+ enum action_e { a_compress, a_decompress };
+
+ action_e action;
+ Pl_Buffer buf;
+
+ // Used for compression
+ JDIMENSION image_width;
+ JDIMENSION image_height;
+ int components;
+ J_COLOR_SPACE color_space;
+
+ CompressConfig* config_callback;
+
+};
+
+#endif // __PL_DCT_HH__
diff --git a/libqpdf/Pl_DCT.cc b/libqpdf/Pl_DCT.cc
new file mode 100644
index 00000000..e9ecccd9
--- /dev/null
+++ b/libqpdf/Pl_DCT.cc
@@ -0,0 +1,204 @@
+#include <qpdf/Pl_DCT.hh>
+
+#include <qpdf/QUtil.hh>
+#include <setjmp.h>
+#include <string>
+#include <stdexcept>
+
+#if BITS_IN_JSAMPLE != 8
+# error "qpdf does not support libjpeg built with BITS_IN_JSAMPLE != 8"
+#endif
+
+struct qpdf_jpeg_error_mgr
+{
+ struct jpeg_error_mgr pub;
+ jmp_buf jmpbuf;
+ std::string msg;
+};
+
+static void
+error_handler(j_common_ptr cinfo)
+{
+ qpdf_jpeg_error_mgr* jerr =
+ reinterpret_cast<qpdf_jpeg_error_mgr*>(cinfo->err);
+ char buf[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buf);
+ jerr->msg = buf;
+ longjmp(jerr->jmpbuf, 1);
+}
+
+Pl_DCT::Pl_DCT(char const* identifier, Pipeline* next) :
+ Pipeline(identifier, next),
+ action(a_decompress),
+ buf("DCT compressed image")
+{
+}
+
+Pl_DCT::Pl_DCT(char const* identifier, Pipeline* next,
+ JDIMENSION image_width,
+ JDIMENSION image_height,
+ int components,
+ J_COLOR_SPACE color_space,
+ CompressConfig* config_callback) :
+ Pipeline(identifier, next),
+ action(a_compress),
+ buf("DCT uncompressed image"),
+ image_width(image_width),
+ image_height(image_height),
+ components(components),
+ color_space(color_space),
+ config_callback(config_callback)
+{
+}
+
+Pl_DCT::~Pl_DCT()
+{
+}
+
+void
+Pl_DCT::write(unsigned char* data, size_t len)
+{
+ this->buf.write(data, len);
+}
+
+void
+Pl_DCT::finish()
+{
+ this->buf.finish();
+ PointerHolder<Buffer> b = this->buf.getBuffer();
+
+ struct jpeg_compress_struct cinfo_compress;
+ struct jpeg_decompress_struct cinfo_decompress;
+ struct qpdf_jpeg_error_mgr jerr;
+
+ cinfo_compress.err = jpeg_std_error(&(jerr.pub));
+ cinfo_decompress.err = jpeg_std_error(&(jerr.pub));
+ jerr.pub.error_exit = error_handler;
+
+ bool error = false;
+ if (setjmp(jerr.jmpbuf) == 0)
+ {
+ if (this->action == a_compress)
+ {
+ compress(reinterpret_cast<void*>(&cinfo_compress), b);
+ }
+ else
+ {
+ decompress(reinterpret_cast<void*>(&cinfo_decompress), b);
+ }
+ }
+ else
+ {
+ error = true;
+ }
+
+ if (this->action == a_compress)
+ {
+ jpeg_destroy_compress(&cinfo_compress);
+ }
+ if (this->action == a_decompress)
+ {
+ jpeg_destroy_decompress(&cinfo_decompress);
+ }
+ if (error)
+ {
+ throw std::runtime_error(jerr.msg);
+ }
+}
+
+void
+Pl_DCT::compress(void* cinfo_p, PointerHolder<Buffer> b)
+{
+ struct jpeg_compress_struct* cinfo =
+ reinterpret_cast<jpeg_compress_struct*>(cinfo_p);
+
+#ifdef __GNUC__
+# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wold-style-cast"
+# endif
+#endif
+ jpeg_create_compress(cinfo);
+#ifdef __GNUC__
+# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406
+# pragma GCC diagnostic pop
+# endif
+#endif
+ unsigned char* outbuffer = 0;
+ unsigned long outsize = 0;
+ jpeg_mem_dest(cinfo, &outbuffer, &outsize);
+
+ cinfo->image_width = this->image_width;
+ cinfo->image_height = this->image_height;
+ cinfo->input_components = this->components;
+ cinfo->in_color_space = this->color_space;
+ jpeg_set_defaults(cinfo);
+ if (this->config_callback)
+ {
+ this->config_callback->apply(cinfo);
+ }
+
+ jpeg_start_compress(cinfo, TRUE);
+
+ int width = cinfo->image_width * cinfo->input_components;
+ size_t expected_size =
+ cinfo->image_height * cinfo->image_width * cinfo->input_components;
+ if (b->getSize() != expected_size)
+ {
+ throw std::runtime_error(
+ "Pl_DCT: image buffer size = " +
+ QUtil::int_to_string(b->getSize()) + "; expected size = " +
+ QUtil::int_to_string(expected_size));
+ }
+ JSAMPROW row_pointer[1];
+ unsigned char* buffer = b->getBuffer();
+ while (cinfo->next_scanline < cinfo->image_height)
+ {
+ // We already verified that the buffer is big enough.
+ row_pointer[0] = &buffer[cinfo->next_scanline * width];
+ (void) jpeg_write_scanlines(cinfo, row_pointer, 1);
+ }
+ jpeg_finish_compress(cinfo);
+ this->getNext()->write(outbuffer, outsize);
+ this->getNext()->finish();
+
+ free(outbuffer);
+}
+
+void
+Pl_DCT::decompress(void* cinfo_p, PointerHolder<Buffer> b)
+{
+ struct jpeg_decompress_struct* cinfo =
+ reinterpret_cast<jpeg_decompress_struct*>(cinfo_p);
+
+#ifdef __GNUC__
+# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wold-style-cast"
+# endif
+#endif
+ jpeg_create_decompress(cinfo);
+#ifdef __GNUC__
+# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406
+# pragma GCC diagnostic pop
+# endif
+#endif
+ jpeg_mem_src(cinfo, b->getBuffer(), b->getSize());
+
+ (void) jpeg_read_header(cinfo, TRUE);
+ (void) jpeg_calc_output_dimensions(cinfo);
+
+ int width = cinfo->output_width * cinfo->output_components;
+ JSAMPARRAY buffer = (*cinfo->mem->alloc_sarray)
+ (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, width, 1);
+
+ (void) jpeg_start_decompress(cinfo);
+ while (cinfo->output_scanline < cinfo->output_height)
+ {
+ (void) jpeg_read_scanlines(cinfo, buffer, 1);
+ this->getNext()->write(reinterpret_cast<unsigned char*>(buffer[0]),
+ width * sizeof(buffer[0][0]));
+ }
+ (void) jpeg_finish_decompress(cinfo);
+ this->getNext()->finish();
+}
diff --git a/libqpdf/build.mk b/libqpdf/build.mk
index 9a8652a6..75d0054e 100644
--- a/libqpdf/build.mk
+++ b/libqpdf/build.mk
@@ -21,6 +21,7 @@ SRCS_libqpdf = \
libqpdf/Pl_Buffer.cc \
libqpdf/Pl_Concatenate.cc \
libqpdf/Pl_Count.cc \
+ libqpdf/Pl_DCT.cc \
libqpdf/Pl_Discard.cc \
libqpdf/Pl_Flate.cc \
libqpdf/Pl_LZWDecoder.cc \
diff --git a/libtests/build.mk b/libtests/build.mk
index 7977c8c5..baa34125 100644
--- a/libtests/build.mk
+++ b/libtests/build.mk
@@ -4,6 +4,8 @@ BINS_libtests = \
bits \
buffer \
concatenate \
+ dct_compress \
+ dct_uncompress \
flate \
hex \
input_source \
diff --git a/libtests/dct_compress.cc b/libtests/dct_compress.cc
new file mode 100644
index 00000000..ea5d9c33
--- /dev/null
+++ b/libtests/dct_compress.cc
@@ -0,0 +1,93 @@
+#include <qpdf/Pl_DCT.hh>
+#include <qpdf/Pl_StdioFile.hh>
+#include <qpdf/QUtil.hh>
+
+#include <stdio.h>
+#include <string.h>
+#include <iostream>
+#include <stdlib.h>
+
+static void usage()
+{
+ std::cerr << "Usage: dct_compress infile outfile width height"
+ << " {rgb|cmyk|gray}" << std::endl;
+ exit(2);
+}
+
+class Callback: public Pl_DCT::CompressConfig
+{
+ public:
+ virtual ~Callback()
+ {
+ }
+ virtual void apply(jpeg_compress_struct*);
+ bool called = false;
+};
+
+void Callback::apply(jpeg_compress_struct*)
+{
+ this->called = true;
+}
+
+int main(int argc, char* argv[])
+{
+ if (argc != 6)
+ {
+ usage();
+ }
+
+ char* infilename = argv[1];
+ char* outfilename = argv[2];
+ unsigned int width = atoi(argv[3]);
+ unsigned int height = atoi(argv[4]);
+ char* colorspace = argv[5];
+ J_COLOR_SPACE cs =
+ ((strcmp(colorspace, "rgb") == 0) ? JCS_RGB :
+ (strcmp(colorspace, "cmyk") == 0) ? JCS_CMYK :
+ (strcmp(colorspace, "gray") == 0) ? JCS_GRAYSCALE :
+ JCS_UNKNOWN);
+ int components = 0;
+ switch (cs)
+ {
+ case JCS_RGB:
+ components = 3;
+ break;
+ case JCS_CMYK:
+ components = 4;
+ break;
+ case JCS_GRAYSCALE:
+ components = 1;
+ break;
+ default:
+ usage();
+ break;
+ }
+
+ FILE* infile = QUtil::safe_fopen(infilename, "rb");
+ FILE* outfile = QUtil::safe_fopen(outfilename, "wb");
+ Pl_StdioFile out("stdout", outfile);
+ unsigned char buf[100];
+ bool done = false;
+ Callback callback;
+ Pl_DCT dct("dct", &out, width, height, components, cs, &callback);
+ while (! done)
+ {
+ size_t len = fread(buf, 1, sizeof(buf), infile);
+ if (len <= 0)
+ {
+ done = true;
+ }
+ else
+ {
+ dct.write(buf, len);
+ }
+ }
+ dct.finish();
+ if (! callback.called)
+ {
+ std::cout << "Callback was not called" << std::endl;
+ }
+ fclose(infile);
+ fclose(outfile);
+ return 0;
+}
diff --git a/libtests/dct_uncompress.cc b/libtests/dct_uncompress.cc
new file mode 100644
index 00000000..1ab1ae21
--- /dev/null
+++ b/libtests/dct_uncompress.cc
@@ -0,0 +1,44 @@
+#include <qpdf/Pl_DCT.hh>
+#include <qpdf/Pl_StdioFile.hh>
+#include <qpdf/QUtil.hh>
+
+#include <stdio.h>
+#include <string.h>
+#include <iostream>
+#include <stdlib.h>
+
+int main(int argc, char* argv[])
+{
+ if (argc != 3)
+ {
+ std::cerr << "Usage: dct_uncompress infile outfile"
+ << std::endl;
+ exit(2);
+ }
+
+ char* infilename = argv[1];
+ char* outfilename = argv[2];
+
+ FILE* infile = QUtil::safe_fopen(infilename, "rb");
+ FILE* outfile = QUtil::safe_fopen(outfilename, "wb");
+ Pl_StdioFile out("stdout", outfile);
+ unsigned char buf[100];
+ bool done = false;
+ Pl_DCT dct("dct", &out);
+ while (! done)
+ {
+ size_t len = fread(buf, 1, sizeof(buf), infile);
+ if (len <= 0)
+ {
+ done = true;
+ }
+ else
+ {
+ dct.write(buf, len);
+ }
+ }
+ dct.finish();
+ fclose(infile);
+ fclose(outfile);
+ return 0;
+}
diff --git a/libtests/qtest/dct.test b/libtests/qtest/dct.test
new file mode 100644
index 00000000..f3b28581
--- /dev/null
+++ b/libtests/qtest/dct.test
@@ -0,0 +1,66 @@
+#!/usr/bin/env perl
+require 5.008;
+use warnings;
+use strict;
+
+chdir("dct") or die "chdir testdir failed: $!\n";
+
+require TestDriver;
+
+# This test suite does light verification of DCT by running some data
+# through a round trip with one encoding system. The
+# examples/pdf-create program also exercises DCT but does so more
+# fully.
+
+my $td = new TestDriver('dct');
+
+cleanup();
+
+$td->runtest("compress",
+ {$td->COMMAND => "dct_compress rawdata a.jpg 400 256 gray"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+$td->runtest("decompress",
+ {$td->COMMAND => "dct_uncompress a.jpg out"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+# Compare
+my @raw = get_data('rawdata');
+my @processed = get_data('out');
+my $checked_data = 0;
+if ($td->runtest("bytes in data",
+ {$td->STRING => scalar(@processed)},
+ {$td->STRING => scalar(@raw)}))
+{
+ my $mismatch = 0;
+ for (my $i = 0; $i < scalar(@raw); ++$i)
+ {
+ $checked_data = 1;
+ my $delta = abs(ord($raw[$i]) - ord($processed[$i]));
+ if ($delta > 10)
+ {
+ ++$mismatch;
+ }
+ }
+ $td->runtest("data is close enough",
+ {$td->STRING => $mismatch},
+ {$td->STRING => '0'});
+}
+
+cleanup();
+
+$td->report(3 + $checked_data);
+
+sub cleanup
+{
+ system("rm -f a.jpg out");
+}
+
+sub get_data
+{
+ my $file = shift;
+ local $/ = undef;
+ open(F, "<$file") || die;
+ binmode(F);
+ my $data = <F>;
+ close(F);
+ split('', $data);
+}
diff --git a/libtests/qtest/dct/rawdata b/libtests/qtest/dct/rawdata
new file mode 100644
index 00000000..a3594098
--- /dev/null
+++ b/libtests/qtest/dct/rawdata
Binary files differ