From ae90d2c485318beb8b4b938d09ffaf5c6f0a5e21 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Fri, 18 Aug 2017 19:52:53 -0400 Subject: [PATCH] Implement Pl_DCT pipeline Additional testing is added in later commits to be supported by additional changes in the library. --- include/qpdf/Pl_DCT.hh | 70 +++++++++++++ libqpdf/Pl_DCT.cc | 204 +++++++++++++++++++++++++++++++++++++ libqpdf/build.mk | 1 + libtests/build.mk | 2 + libtests/dct_compress.cc | 93 +++++++++++++++++ libtests/dct_uncompress.cc | 44 ++++++++ libtests/qtest/dct.test | 66 ++++++++++++ libtests/qtest/dct/rawdata | Bin 0 -> 102400 bytes 8 files changed, 480 insertions(+) create mode 100644 include/qpdf/Pl_DCT.hh create mode 100644 libqpdf/Pl_DCT.cc create mode 100644 libtests/dct_compress.cc create mode 100644 libtests/dct_uncompress.cc create mode 100644 libtests/qtest/dct.test create mode 100644 libtests/qtest/dct/rawdata 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 +#include +#include + +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); + void decompress(void* cinfo, PointerHolder); + + 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 + +#include +#include +#include +#include + +#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(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 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(&cinfo_compress), b); + } + else + { + decompress(reinterpret_cast(&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 b) +{ + struct jpeg_compress_struct* cinfo = + reinterpret_cast(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 b) +{ + struct jpeg_decompress_struct* cinfo = + reinterpret_cast(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(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(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 +#include +#include + +#include +#include +#include +#include + +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 +#include +#include + +#include +#include +#include +#include + +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 = ; + close(F); + split('', $data); +} diff --git a/libtests/qtest/dct/rawdata b/libtests/qtest/dct/rawdata new file mode 100644 index 0000000000000000000000000000000000000000..a359409831e0f595d8eb0602c5fb63e058bcf6ae GIT binary patch literal 102400 zcmeIuQ=%9K006-k+qP}nwr!`eZQHhO+g6*Tv2EMA#(wM^VFCg~8VDrRKwzN;f(SJb zRH%VqLJb5LY9NGA10jVP2qn}&XrTte2sIE^sDW@o4TKkJAc9Z>5rrCvB-B7;p$4J| zH4s&(foMVvL>FoxhEM}Bg&K$@)Ie;Z2I2@c5Lc*yctQ=t7iu7ZPy-2t8b~D6Kw_Z= zk_a`BRH%VuLJcGrY9NJB11W_XNF~%jYM}p8Ym>x zKw+T7ORH%VsLJbrbYM_Kr10{tTC?(WDX`u$n2sKbvsDW}q4U`vZpn^~X6@?n8 zB-B7a2I>ekP*ng}(}RH%VwLJc$*YM_Ns11*Ib_(!OLRzeN57HXi4Py=m+8fYifKzpGE zItVq;QK*4VLJf2lYM_fy16_q0=qA)accBJ)2sO}CsDWNW4fGajppQ@keT5q6C)7ZH zp#}yBH84=9fk8qI3>Io&h)@GVg&G(p)WC3|21W=qFjA<2Q9=!j7HVLOPy=Iy8W<m?qS~bfE@j2sJQMsDW8R4a^p5V2)4&bA=k1 zC)B`vp#~NRHLy^qfki?MEEZ~DiBJPeg&J5U)WC9~2380)uu`ajRYDD{7HVLPPy=g) z8dxXPzXxPy-)@8u%pCz-OTbz6dq&Rj7e)LJfQu XYT$=Z13!ft_$Ac9Z=nYM2sQ8*s5?Wd literal 0 HcmV?d00001