mirror of
https://github.com/qpdf/qpdf.git
synced 2025-01-03 07:12:28 +00:00
Implement Pl_DCT pipeline
Additional testing is added in later commits to be supported by additional changes in the library.
This commit is contained in:
parent
39ab004336
commit
ae90d2c485
70
include/qpdf/Pl_DCT.hh
Normal file
70
include/qpdf/Pl_DCT.hh
Normal file
@ -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__
|
204
libqpdf/Pl_DCT.cc
Normal file
204
libqpdf/Pl_DCT.cc
Normal file
@ -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();
|
||||||
|
}
|
@ -21,6 +21,7 @@ SRCS_libqpdf = \
|
|||||||
libqpdf/Pl_Buffer.cc \
|
libqpdf/Pl_Buffer.cc \
|
||||||
libqpdf/Pl_Concatenate.cc \
|
libqpdf/Pl_Concatenate.cc \
|
||||||
libqpdf/Pl_Count.cc \
|
libqpdf/Pl_Count.cc \
|
||||||
|
libqpdf/Pl_DCT.cc \
|
||||||
libqpdf/Pl_Discard.cc \
|
libqpdf/Pl_Discard.cc \
|
||||||
libqpdf/Pl_Flate.cc \
|
libqpdf/Pl_Flate.cc \
|
||||||
libqpdf/Pl_LZWDecoder.cc \
|
libqpdf/Pl_LZWDecoder.cc \
|
||||||
|
@ -4,6 +4,8 @@ BINS_libtests = \
|
|||||||
bits \
|
bits \
|
||||||
buffer \
|
buffer \
|
||||||
concatenate \
|
concatenate \
|
||||||
|
dct_compress \
|
||||||
|
dct_uncompress \
|
||||||
flate \
|
flate \
|
||||||
hex \
|
hex \
|
||||||
input_source \
|
input_source \
|
||||||
|
93
libtests/dct_compress.cc
Normal file
93
libtests/dct_compress.cc
Normal file
@ -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;
|
||||||
|
}
|
44
libtests/dct_uncompress.cc
Normal file
44
libtests/dct_uncompress.cc
Normal file
@ -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;
|
||||||
|
}
|
66
libtests/qtest/dct.test
Normal file
66
libtests/qtest/dct.test
Normal file
@ -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);
|
||||||
|
}
|
BIN
libtests/qtest/dct/rawdata
Normal file
BIN
libtests/qtest/dct/rawdata
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user