Commit ae90d2c485318beb8b4b938d09ffaf5c6f0a5e21

Authored by Jay Berkenbilt
1 parent 39ab0043

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 0 → 100644
  1 +// Copyright (c) 2005-2015 Jay Berkenbilt
  2 +//
  3 +// This file is part of qpdf. This software may be distributed under
  4 +// the terms of version 2 of the Artistic License which may be found
  5 +// in the source distribution. It is provided "as is" without express
  6 +// or implied warranty.
  7 +
  8 +#ifndef __PL_DCT_HH__
  9 +#define __PL_DCT_HH__
  10 +
  11 +#include <qpdf/Pipeline.hh>
  12 +#include <qpdf/Pl_Buffer.hh>
  13 +#include <jpeglib.h>
  14 +
  15 +class Pl_DCT: public Pipeline
  16 +{
  17 + public:
  18 + // Constructor for decompressing image data
  19 + QPDF_DLL
  20 + Pl_DCT(char const* identifier, Pipeline* next);
  21 +
  22 + class CompressConfig
  23 + {
  24 + public:
  25 + CompressConfig()
  26 + {
  27 + }
  28 + virtual ~CompressConfig()
  29 + {
  30 + }
  31 + virtual void apply(jpeg_compress_struct*) = 0;
  32 + };
  33 +
  34 + // Constructor for compressing image data
  35 + QPDF_DLL
  36 + Pl_DCT(char const* identifier, Pipeline* next,
  37 + JDIMENSION image_width,
  38 + JDIMENSION image_height,
  39 + int components,
  40 + J_COLOR_SPACE color_space,
  41 + CompressConfig* config_callback = 0);
  42 +
  43 + QPDF_DLL
  44 + virtual ~Pl_DCT();
  45 +
  46 + QPDF_DLL
  47 + virtual void write(unsigned char* data, size_t len);
  48 + QPDF_DLL
  49 + virtual void finish();
  50 +
  51 + private:
  52 + void compress(void* cinfo, PointerHolder<Buffer>);
  53 + void decompress(void* cinfo, PointerHolder<Buffer>);
  54 +
  55 + enum action_e { a_compress, a_decompress };
  56 +
  57 + action_e action;
  58 + Pl_Buffer buf;
  59 +
  60 + // Used for compression
  61 + JDIMENSION image_width;
  62 + JDIMENSION image_height;
  63 + int components;
  64 + J_COLOR_SPACE color_space;
  65 +
  66 + CompressConfig* config_callback;
  67 +
  68 +};
  69 +
  70 +#endif // __PL_DCT_HH__
... ...
libqpdf/Pl_DCT.cc 0 → 100644
  1 +#include <qpdf/Pl_DCT.hh>
  2 +
  3 +#include <qpdf/QUtil.hh>
  4 +#include <setjmp.h>
  5 +#include <string>
  6 +#include <stdexcept>
  7 +
  8 +#if BITS_IN_JSAMPLE != 8
  9 +# error "qpdf does not support libjpeg built with BITS_IN_JSAMPLE != 8"
  10 +#endif
  11 +
  12 +struct qpdf_jpeg_error_mgr
  13 +{
  14 + struct jpeg_error_mgr pub;
  15 + jmp_buf jmpbuf;
  16 + std::string msg;
  17 +};
  18 +
  19 +static void
  20 +error_handler(j_common_ptr cinfo)
  21 +{
  22 + qpdf_jpeg_error_mgr* jerr =
  23 + reinterpret_cast<qpdf_jpeg_error_mgr*>(cinfo->err);
  24 + char buf[JMSG_LENGTH_MAX];
  25 + (*cinfo->err->format_message)(cinfo, buf);
  26 + jerr->msg = buf;
  27 + longjmp(jerr->jmpbuf, 1);
  28 +}
  29 +
  30 +Pl_DCT::Pl_DCT(char const* identifier, Pipeline* next) :
  31 + Pipeline(identifier, next),
  32 + action(a_decompress),
  33 + buf("DCT compressed image")
  34 +{
  35 +}
  36 +
  37 +Pl_DCT::Pl_DCT(char const* identifier, Pipeline* next,
  38 + JDIMENSION image_width,
  39 + JDIMENSION image_height,
  40 + int components,
  41 + J_COLOR_SPACE color_space,
  42 + CompressConfig* config_callback) :
  43 + Pipeline(identifier, next),
  44 + action(a_compress),
  45 + buf("DCT uncompressed image"),
  46 + image_width(image_width),
  47 + image_height(image_height),
  48 + components(components),
  49 + color_space(color_space),
  50 + config_callback(config_callback)
  51 +{
  52 +}
  53 +
  54 +Pl_DCT::~Pl_DCT()
  55 +{
  56 +}
  57 +
  58 +void
  59 +Pl_DCT::write(unsigned char* data, size_t len)
  60 +{
  61 + this->buf.write(data, len);
  62 +}
  63 +
  64 +void
  65 +Pl_DCT::finish()
  66 +{
  67 + this->buf.finish();
  68 + PointerHolder<Buffer> b = this->buf.getBuffer();
  69 +
  70 + struct jpeg_compress_struct cinfo_compress;
  71 + struct jpeg_decompress_struct cinfo_decompress;
  72 + struct qpdf_jpeg_error_mgr jerr;
  73 +
  74 + cinfo_compress.err = jpeg_std_error(&(jerr.pub));
  75 + cinfo_decompress.err = jpeg_std_error(&(jerr.pub));
  76 + jerr.pub.error_exit = error_handler;
  77 +
  78 + bool error = false;
  79 + if (setjmp(jerr.jmpbuf) == 0)
  80 + {
  81 + if (this->action == a_compress)
  82 + {
  83 + compress(reinterpret_cast<void*>(&cinfo_compress), b);
  84 + }
  85 + else
  86 + {
  87 + decompress(reinterpret_cast<void*>(&cinfo_decompress), b);
  88 + }
  89 + }
  90 + else
  91 + {
  92 + error = true;
  93 + }
  94 +
  95 + if (this->action == a_compress)
  96 + {
  97 + jpeg_destroy_compress(&cinfo_compress);
  98 + }
  99 + if (this->action == a_decompress)
  100 + {
  101 + jpeg_destroy_decompress(&cinfo_decompress);
  102 + }
  103 + if (error)
  104 + {
  105 + throw std::runtime_error(jerr.msg);
  106 + }
  107 +}
  108 +
  109 +void
  110 +Pl_DCT::compress(void* cinfo_p, PointerHolder<Buffer> b)
  111 +{
  112 + struct jpeg_compress_struct* cinfo =
  113 + reinterpret_cast<jpeg_compress_struct*>(cinfo_p);
  114 +
  115 +#ifdef __GNUC__
  116 +# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406
  117 +# pragma GCC diagnostic push
  118 +# pragma GCC diagnostic ignored "-Wold-style-cast"
  119 +# endif
  120 +#endif
  121 + jpeg_create_compress(cinfo);
  122 +#ifdef __GNUC__
  123 +# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406
  124 +# pragma GCC diagnostic pop
  125 +# endif
  126 +#endif
  127 + unsigned char* outbuffer = 0;
  128 + unsigned long outsize = 0;
  129 + jpeg_mem_dest(cinfo, &outbuffer, &outsize);
  130 +
  131 + cinfo->image_width = this->image_width;
  132 + cinfo->image_height = this->image_height;
  133 + cinfo->input_components = this->components;
  134 + cinfo->in_color_space = this->color_space;
  135 + jpeg_set_defaults(cinfo);
  136 + if (this->config_callback)
  137 + {
  138 + this->config_callback->apply(cinfo);
  139 + }
  140 +
  141 + jpeg_start_compress(cinfo, TRUE);
  142 +
  143 + int width = cinfo->image_width * cinfo->input_components;
  144 + size_t expected_size =
  145 + cinfo->image_height * cinfo->image_width * cinfo->input_components;
  146 + if (b->getSize() != expected_size)
  147 + {
  148 + throw std::runtime_error(
  149 + "Pl_DCT: image buffer size = " +
  150 + QUtil::int_to_string(b->getSize()) + "; expected size = " +
  151 + QUtil::int_to_string(expected_size));
  152 + }
  153 + JSAMPROW row_pointer[1];
  154 + unsigned char* buffer = b->getBuffer();
  155 + while (cinfo->next_scanline < cinfo->image_height)
  156 + {
  157 + // We already verified that the buffer is big enough.
  158 + row_pointer[0] = &buffer[cinfo->next_scanline * width];
  159 + (void) jpeg_write_scanlines(cinfo, row_pointer, 1);
  160 + }
  161 + jpeg_finish_compress(cinfo);
  162 + this->getNext()->write(outbuffer, outsize);
  163 + this->getNext()->finish();
  164 +
  165 + free(outbuffer);
  166 +}
  167 +
  168 +void
  169 +Pl_DCT::decompress(void* cinfo_p, PointerHolder<Buffer> b)
  170 +{
  171 + struct jpeg_decompress_struct* cinfo =
  172 + reinterpret_cast<jpeg_decompress_struct*>(cinfo_p);
  173 +
  174 +#ifdef __GNUC__
  175 +# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406
  176 +# pragma GCC diagnostic push
  177 +# pragma GCC diagnostic ignored "-Wold-style-cast"
  178 +# endif
  179 +#endif
  180 + jpeg_create_decompress(cinfo);
  181 +#ifdef __GNUC__
  182 +# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406
  183 +# pragma GCC diagnostic pop
  184 +# endif
  185 +#endif
  186 + jpeg_mem_src(cinfo, b->getBuffer(), b->getSize());
  187 +
  188 + (void) jpeg_read_header(cinfo, TRUE);
  189 + (void) jpeg_calc_output_dimensions(cinfo);
  190 +
  191 + int width = cinfo->output_width * cinfo->output_components;
  192 + JSAMPARRAY buffer = (*cinfo->mem->alloc_sarray)
  193 + (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, width, 1);
  194 +
  195 + (void) jpeg_start_decompress(cinfo);
  196 + while (cinfo->output_scanline < cinfo->output_height)
  197 + {
  198 + (void) jpeg_read_scanlines(cinfo, buffer, 1);
  199 + this->getNext()->write(reinterpret_cast<unsigned char*>(buffer[0]),
  200 + width * sizeof(buffer[0][0]));
  201 + }
  202 + (void) jpeg_finish_decompress(cinfo);
  203 + this->getNext()->finish();
  204 +}
... ...
libqpdf/build.mk
... ... @@ -21,6 +21,7 @@ SRCS_libqpdf = \
21 21 libqpdf/Pl_Buffer.cc \
22 22 libqpdf/Pl_Concatenate.cc \
23 23 libqpdf/Pl_Count.cc \
  24 + libqpdf/Pl_DCT.cc \
24 25 libqpdf/Pl_Discard.cc \
25 26 libqpdf/Pl_Flate.cc \
26 27 libqpdf/Pl_LZWDecoder.cc \
... ...
libtests/build.mk
... ... @@ -4,6 +4,8 @@ BINS_libtests = \
4 4 bits \
5 5 buffer \
6 6 concatenate \
  7 + dct_compress \
  8 + dct_uncompress \
7 9 flate \
8 10 hex \
9 11 input_source \
... ...
libtests/dct_compress.cc 0 → 100644
  1 +#include <qpdf/Pl_DCT.hh>
  2 +#include <qpdf/Pl_StdioFile.hh>
  3 +#include <qpdf/QUtil.hh>
  4 +
  5 +#include <stdio.h>
  6 +#include <string.h>
  7 +#include <iostream>
  8 +#include <stdlib.h>
  9 +
  10 +static void usage()
  11 +{
  12 + std::cerr << "Usage: dct_compress infile outfile width height"
  13 + << " {rgb|cmyk|gray}" << std::endl;
  14 + exit(2);
  15 +}
  16 +
  17 +class Callback: public Pl_DCT::CompressConfig
  18 +{
  19 + public:
  20 + virtual ~Callback()
  21 + {
  22 + }
  23 + virtual void apply(jpeg_compress_struct*);
  24 + bool called = false;
  25 +};
  26 +
  27 +void Callback::apply(jpeg_compress_struct*)
  28 +{
  29 + this->called = true;
  30 +}
  31 +
  32 +int main(int argc, char* argv[])
  33 +{
  34 + if (argc != 6)
  35 + {
  36 + usage();
  37 + }
  38 +
  39 + char* infilename = argv[1];
  40 + char* outfilename = argv[2];
  41 + unsigned int width = atoi(argv[3]);
  42 + unsigned int height = atoi(argv[4]);
  43 + char* colorspace = argv[5];
  44 + J_COLOR_SPACE cs =
  45 + ((strcmp(colorspace, "rgb") == 0) ? JCS_RGB :
  46 + (strcmp(colorspace, "cmyk") == 0) ? JCS_CMYK :
  47 + (strcmp(colorspace, "gray") == 0) ? JCS_GRAYSCALE :
  48 + JCS_UNKNOWN);
  49 + int components = 0;
  50 + switch (cs)
  51 + {
  52 + case JCS_RGB:
  53 + components = 3;
  54 + break;
  55 + case JCS_CMYK:
  56 + components = 4;
  57 + break;
  58 + case JCS_GRAYSCALE:
  59 + components = 1;
  60 + break;
  61 + default:
  62 + usage();
  63 + break;
  64 + }
  65 +
  66 + FILE* infile = QUtil::safe_fopen(infilename, "rb");
  67 + FILE* outfile = QUtil::safe_fopen(outfilename, "wb");
  68 + Pl_StdioFile out("stdout", outfile);
  69 + unsigned char buf[100];
  70 + bool done = false;
  71 + Callback callback;
  72 + Pl_DCT dct("dct", &out, width, height, components, cs, &callback);
  73 + while (! done)
  74 + {
  75 + size_t len = fread(buf, 1, sizeof(buf), infile);
  76 + if (len <= 0)
  77 + {
  78 + done = true;
  79 + }
  80 + else
  81 + {
  82 + dct.write(buf, len);
  83 + }
  84 + }
  85 + dct.finish();
  86 + if (! callback.called)
  87 + {
  88 + std::cout << "Callback was not called" << std::endl;
  89 + }
  90 + fclose(infile);
  91 + fclose(outfile);
  92 + return 0;
  93 +}
... ...
libtests/dct_uncompress.cc 0 → 100644
  1 +#include <qpdf/Pl_DCT.hh>
  2 +#include <qpdf/Pl_StdioFile.hh>
  3 +#include <qpdf/QUtil.hh>
  4 +
  5 +#include <stdio.h>
  6 +#include <string.h>
  7 +#include <iostream>
  8 +#include <stdlib.h>
  9 +
  10 +int main(int argc, char* argv[])
  11 +{
  12 + if (argc != 3)
  13 + {
  14 + std::cerr << "Usage: dct_uncompress infile outfile"
  15 + << std::endl;
  16 + exit(2);
  17 + }
  18 +
  19 + char* infilename = argv[1];
  20 + char* outfilename = argv[2];
  21 +
  22 + FILE* infile = QUtil::safe_fopen(infilename, "rb");
  23 + FILE* outfile = QUtil::safe_fopen(outfilename, "wb");
  24 + Pl_StdioFile out("stdout", outfile);
  25 + unsigned char buf[100];
  26 + bool done = false;
  27 + Pl_DCT dct("dct", &out);
  28 + while (! done)
  29 + {
  30 + size_t len = fread(buf, 1, sizeof(buf), infile);
  31 + if (len <= 0)
  32 + {
  33 + done = true;
  34 + }
  35 + else
  36 + {
  37 + dct.write(buf, len);
  38 + }
  39 + }
  40 + dct.finish();
  41 + fclose(infile);
  42 + fclose(outfile);
  43 + return 0;
  44 +}
... ...
libtests/qtest/dct.test 0 → 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +use warnings;
  4 +use strict;
  5 +
  6 +chdir("dct") or die "chdir testdir failed: $!\n";
  7 +
  8 +require TestDriver;
  9 +
  10 +# This test suite does light verification of DCT by running some data
  11 +# through a round trip with one encoding system. The
  12 +# examples/pdf-create program also exercises DCT but does so more
  13 +# fully.
  14 +
  15 +my $td = new TestDriver('dct');
  16 +
  17 +cleanup();
  18 +
  19 +$td->runtest("compress",
  20 + {$td->COMMAND => "dct_compress rawdata a.jpg 400 256 gray"},
  21 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  22 +$td->runtest("decompress",
  23 + {$td->COMMAND => "dct_uncompress a.jpg out"},
  24 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  25 +# Compare
  26 +my @raw = get_data('rawdata');
  27 +my @processed = get_data('out');
  28 +my $checked_data = 0;
  29 +if ($td->runtest("bytes in data",
  30 + {$td->STRING => scalar(@processed)},
  31 + {$td->STRING => scalar(@raw)}))
  32 +{
  33 + my $mismatch = 0;
  34 + for (my $i = 0; $i < scalar(@raw); ++$i)
  35 + {
  36 + $checked_data = 1;
  37 + my $delta = abs(ord($raw[$i]) - ord($processed[$i]));
  38 + if ($delta > 10)
  39 + {
  40 + ++$mismatch;
  41 + }
  42 + }
  43 + $td->runtest("data is close enough",
  44 + {$td->STRING => $mismatch},
  45 + {$td->STRING => '0'});
  46 +}
  47 +
  48 +cleanup();
  49 +
  50 +$td->report(3 + $checked_data);
  51 +
  52 +sub cleanup
  53 +{
  54 + system("rm -f a.jpg out");
  55 +}
  56 +
  57 +sub get_data
  58 +{
  59 + my $file = shift;
  60 + local $/ = undef;
  61 + open(F, "<$file") || die;
  62 + binmode(F);
  63 + my $data = <F>;
  64 + close(F);
  65 + split('', $data);
  66 +}
... ...
libtests/qtest/dct/rawdata 0 → 100644
No preview for this file type