📄 jpeg.cpp
字号:
// jpeg.cpp -- Thatcher Ulrich <tu@tulrich.com> 2002
// This source code has been donated to the Public Domain. Do
// whatever you want with it.
// Wrapper for jpeg file operations. The actual work is done by the
// IJG jpeg lib.
#include "base/utility.h"
#include "base/jpeg.h"
#include "base/tu_file.h"
#include <stdio.h>
#if TU_CONFIG_LINK_TO_JPEGLIB
extern "C" {
#include <jpeglib.h>
}
namespace jpeg
{
// jpeglib data source constructors, for using tu_file* instead
// of stdio for jpeg IO.
void setup_rw_source(jpeg_decompress_struct* cinfo, tu_file* instream);
void setup_rw_dest(jpeg_compress_struct* cinfo, tu_file* outstream);
// Helper object for reading jpeg image data. Basically a thin
static const int IO_BUF_SIZE = 4096;
// A jpeglib source manager that reads from a tu_file. Paraphrased
// from IJG jpeglib jdatasrc.c.
struct rw_source
{
struct jpeg_source_mgr m_pub; /* public fields */
tu_file* m_in_stream; /* source stream */
bool m_start_of_file; /* have we gotten any data yet? */
JOCTET m_buffer[IO_BUF_SIZE]; /* start of buffer */
rw_source(tu_file* in)
:
m_in_stream(in),
m_start_of_file(true)
// Constructor. The caller is responsible for closing the input stream
// after it's done using us.
{
// fill in function pointers...
m_pub.init_source = init_source;
m_pub.fill_input_buffer = fill_input_buffer;
m_pub.skip_input_data = skip_input_data;
m_pub.resync_to_restart = jpeg_resync_to_restart; // use default method
m_pub.term_source = term_source;
m_pub.bytes_in_buffer = 0;
m_pub.next_input_byte = NULL;
}
static void init_source(j_decompress_ptr cinfo)
{
rw_source* src = (rw_source*) cinfo->src;
src->m_start_of_file = true;
}
static boolean fill_input_buffer(j_decompress_ptr cinfo)
// Read data into our input buffer. Client calls this
// when it needs more data from the file.
{
rw_source* src = (rw_source*) cinfo->src;
size_t bytes_read = src->m_in_stream->read_bytes(src->m_buffer, IO_BUF_SIZE);
if (bytes_read <= 0) {
// Is the file completely empty?
if (src->m_start_of_file) {
// Treat this as a fatal error.
throw "empty jpeg source stream.";
}
// warn("jpeg end-of-stream");
// Insert a fake EOI marker.
src->m_buffer[0] = (JOCTET) 0xFF;
src->m_buffer[1] = (JOCTET) JPEG_EOI;
bytes_read = 2;
}
// Hack to work around SWF bug: sometimes data
// starts with FFD9FFD8, when it should be
// FFD8FFD9!
if (src->m_start_of_file && bytes_read >= 4)
{
if (src->m_buffer[0] == 0xFF
&& src->m_buffer[1] == 0xD9
&& src->m_buffer[2] == 0xFF
&& src->m_buffer[3] == 0xD8)
{
src->m_buffer[1] = 0xD8;
src->m_buffer[3] = 0xD9;
}
}
// Expose buffer state to clients.
src->m_pub.next_input_byte = src->m_buffer;
src->m_pub.bytes_in_buffer = bytes_read;
src->m_start_of_file = false;
return TRUE;
}
static void skip_input_data(j_decompress_ptr cinfo, long num_bytes)
// Called by client when it wants to advance past some
// uninteresting data.
{
rw_source* src = (rw_source*) cinfo->src;
// According to jpeg docs, large skips are
// infrequent. So let's just do it the simple
// way.
if (num_bytes > 0) {
while (num_bytes > (long) src->m_pub.bytes_in_buffer) {
num_bytes -= (long) src->m_pub.bytes_in_buffer;
fill_input_buffer(cinfo);
}
// Handle remainder.
src->m_pub.next_input_byte += (size_t) num_bytes;
src->m_pub.bytes_in_buffer -= (size_t) num_bytes;
}
}
static void term_source(j_decompress_ptr cinfo)
// Terminate the source. Make sure we get deleted.
{
/*rw_source* src = (rw_source*) cinfo->src;
assert(src);
// @@ it's kind of bogus to be deleting here
// -- term_source happens at the end of
// reading an image, but we're probably going
// to want to init a source and use it to read
// many images, without reallocating our
// buffer.
delete src;
cinfo->src = NULL;*/
}
void discard_partial_buffer()
{
// Discard existing bytes in our buffer.
m_pub.bytes_in_buffer = 0;
m_pub.next_input_byte = NULL;
}
};
void setup_rw_source(jpeg_decompress_struct* cinfo, tu_file* instream)
// Set up the given decompress object to read from the given
// stream.
{
// assert(cinfo->src == NULL);
cinfo->src = (jpeg_source_mgr*) (new rw_source(instream));
}
// A jpeglib destination manager that writes to a tu_file.
// Paraphrased from IJG jpeglib jdatadst.c.
struct rw_dest
{
struct jpeg_destination_mgr m_pub; /* public fields */
tu_file* m_out_stream; /* source stream */
JOCTET m_buffer[IO_BUF_SIZE]; /* start of buffer */
rw_dest(tu_file* out)
:
m_out_stream(out)
// Constructor. The caller is responsible for closing
// the output stream after it's done using us.
{
// fill in function pointers...
m_pub.init_destination = init_destination;
m_pub.empty_output_buffer = empty_output_buffer;
m_pub.term_destination = term_destination;
m_pub.next_output_byte = m_buffer;
m_pub.free_in_buffer = IO_BUF_SIZE;
}
static void init_destination(j_compress_ptr cinfo)
{
rw_dest* dest = (rw_dest*) cinfo->dest;
assert(dest);
dest->m_pub.next_output_byte = dest->m_buffer;
dest->m_pub.free_in_buffer = IO_BUF_SIZE;
}
static boolean empty_output_buffer(j_compress_ptr cinfo)
// Write the output buffer into the stream.
{
rw_dest* dest = (rw_dest*) cinfo->dest;
assert(dest);
if (dest->m_out_stream->write_bytes(dest->m_buffer, IO_BUF_SIZE) != IO_BUF_SIZE)
{
// Error.
// @@ bah, exceptions suck. TODO consider alternatives.
throw "jpeg::rw_dest couldn't write data.";
}
dest->m_pub.next_output_byte = dest->m_buffer;
dest->m_pub.free_in_buffer = IO_BUF_SIZE;
return TRUE;
}
static void term_destination(j_compress_ptr cinfo)
// Terminate the destination. Flush any leftover
// data, and make sure we get deleted.
{
rw_dest* dest = (rw_dest*) cinfo->dest;
assert(dest);
// Write any remaining data.
int datacount = IO_BUF_SIZE - dest->m_pub.free_in_buffer;
if (datacount > 0) {
if (dest->m_out_stream->write_bytes(dest->m_buffer, datacount) != datacount)
{
// Error.
throw "jpeg::rw_dest::term_destination couldn't write data.";
}
}
// Clean ourselves up.
delete dest;
cinfo->dest = NULL;
}
};
void setup_rw_dest(j_compress_ptr cinfo, tu_file* outstream)
// Set up the given compress object to write to the given
// output stream.
{
cinfo->dest = (jpeg_destination_mgr*) (new rw_dest(outstream));
}
//
// Error handler
//
void jpeg_error_exit(j_common_ptr cinfo)
// Called when jpeglib has a fatal error.
{
assert(0);
(*cinfo->err->output_message) (cinfo);
tu_error_exit(1, "internal error in jpeglib");
}
static void setup_jpeg_err(jpeg_error_mgr* jerr)
// Set up some error handlers for the jpeg lib.
{
// Set up defaults.
jpeg_std_error(jerr);
jerr->error_exit = jpeg_error_exit;
}
//
// wrappers
//
struct input_impl : public input
// Bascially this is a thin wrapper around jpeg_decompress
// object.
{
// State needed for input.
struct jpeg_decompress_struct m_cinfo;
struct jpeg_error_mgr m_jerr;
bool m_compressor_opened;
enum SWF_DEFINE_BITS_JPEG2 { SWF_JPEG2 };
enum SWF_DEFINE_BITS_JPEG2_HEADER_ONLY { SWF_JPEG2_HEADER_ONLY };
input_impl(tu_file* in)
:
m_compressor_opened(false)
// Constructor. Read the header data from in, and
// prepare to read data.
{
setup_jpeg_err(&m_jerr);
m_cinfo.err = &m_jerr;
// Initialize decompression object.
jpeg_create_decompress(&m_cinfo);
setup_rw_source(&m_cinfo, in);
start_image();
}
input_impl(SWF_DEFINE_BITS_JPEG2_HEADER_ONLY e, tu_file* in)
:
m_compressor_opened(false)
// The SWF file format stores JPEG images with the
// encoding tables separate from the image data. This
// constructor reads the encoding table only and keeps
// them in this object. You need to call
// start_image() and finish_image() around any calls
// to get_width/height/components and read_scanline.
{
setup_jpeg_err(&m_jerr);
m_cinfo.err = &m_jerr;
// Initialize decompression object.
jpeg_create_decompress(&m_cinfo);
setup_rw_source(&m_cinfo, in);
// Read the encoding tables.
jpeg_read_header(&m_cinfo, FALSE);
// Don't start reading any image data!
// App does that manually using start_image.
}
~input_impl()
// Destructor. Clean up our jpeg reader state.
{
finish_image();
rw_source* src = (rw_source*) m_cinfo.src;
delete src;
m_cinfo.src = NULL;
jpeg_destroy_decompress(&m_cinfo);
}
void discard_partial_buffer()
// Discard any data sitting in our input buffer. Use
// this before/after reading headers or partial image
// data, to avoid screwing up future reads.
{
rw_source* src = (rw_source*) m_cinfo.src;
// We only have to discard the input buffer after reading the tables.
if (src)
{
src->discard_partial_buffer();
}
}
void start_image()
// This is something you can do with "abbreviated"
// streams; i.e. if you constructed this inputter
// using (SWF_JPEG2_HEADER_ONLY) to just load the
// tables, or if you called finish_image() and want to
// load another image using the existing tables.
{
assert(m_compressor_opened == false);
// Now, read the image header.
jpeg_read_header(&m_cinfo, TRUE);
jpeg_start_decompress(&m_cinfo);
m_compressor_opened = true;
}
void finish_image()
{
if (m_compressor_opened)
{
jpeg_finish_decompress(&m_cinfo);
m_compressor_opened = false;
}
}
int get_height() const
// Return the height of the image. Take the data from our m_cinfo struct.
{
assert(m_compressor_opened);
return m_cinfo.output_height;
}
int get_width() const
// Return the width of the image. Take the data from our m_cinfo struct.
{
assert(m_compressor_opened);
return m_cinfo.output_width;
}
int get_components() const
// Return number of components (i.e. == 3 for RGB
// data). The size of the data for a scanline is
// get_width() * get_components().
{
assert(m_compressor_opened);
return m_cinfo.output_components;
}
void read_scanline(unsigned char* rgb_data)
// Read a scanline's worth of image data into the
// given buffer. The amount of data read is
// get_width() * get_components().
{
assert(m_compressor_opened);
assert(m_cinfo.output_scanline < m_cinfo.output_height);
int lines_read = jpeg_read_scanlines(&m_cinfo, &rgb_data, 1);
assert(lines_read == 1);
lines_read = lines_read; // avoid warning in NDEBUG
}
};
/*static*/ input* input::create(tu_file* in)
// Create and return a jpeg-input object that will read from the
// given input stream.
{
return new input_impl(in);
}
/*static*/ input* input::create_swf_jpeg2_header_only(tu_file* in)
// Read SWF JPEG2-style header. App needs to call
// start_image() before loading any image data. Multiple
// images can be loaded by bracketing within
// start_image()/finish_image() pairs.
{
return new input_impl(input_impl::SWF_JPEG2_HEADER_ONLY, in);
}
// Default destructor.
input::~input() {}
struct output_impl : public output
// Basically this is a thin wrapper around jpeg_compress
// object.
{
// State needed for output.
struct jpeg_compress_struct m_cinfo;
struct jpeg_error_mgr m_jerr;
output_impl(tu_file* out, int width, int height, int quality)
// Constructor. Read the header data from in, and
// prepare to read data.
{
m_cinfo.err = jpeg_std_error(&m_jerr);
// Initialize decompression object.
jpeg_create_compress(&m_cinfo);
setup_rw_dest(&m_cinfo, out);
m_cinfo.image_width = width;
m_cinfo.image_height = height;
m_cinfo.input_components = 3;
m_cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&m_cinfo);
jpeg_set_quality(&m_cinfo, quality, TRUE);
jpeg_start_compress(&m_cinfo, TRUE);
}
~output_impl()
// Destructor. Clean up our jpeg reader state.
{
jpeg_finish_compress(&m_cinfo);
/*
rw_dest* src = (rw_source*) m_cinfo.dest;
delete dest;
m_cinfo.dest = NULL;
*/
jpeg_destroy_compress(&m_cinfo);
}
void write_scanline(unsigned char* rgb_data)
// Write out a single scanline.
{
jpeg_write_scanlines(&m_cinfo, &rgb_data, 1);
}
};
/*static*/ output* output::create(tu_file* in, int width, int height, int quality)
// Create and return a jpeg-input object that will read from the
// given input stream.
{
return new output_impl(in, width, height, quality);
}
// Default constructor.
output::~output() {}
}
#else // not TU_CONFIG_LINK_TO_JPEGLIB
namespace jpeg
{
/*static*/ input* input::create(tu_file* in)
{
return NULL;
}
/*static*/ input* input::create_swf_jpeg2_header_only(tu_file* in)
{
return NULL;
}
/*static*/ output* output::create(tu_file* out, int width, int height, int quality)
{
return NULL;
}
}
#endif // not TU_CONFIG_LINK_TO_JPEGLIB
// Local Variables:
// mode: C++
// c-basic-offset: 8
// tab-width: 8
// indent-tabs-mode: t
// End:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -