Skip to content

Commit

Permalink
MemoryImageResource{JPEG,PNG}. In-memory encoding and decoding!
Browse files Browse the repository at this point in the history
There's a lot of code duplication here, both between Disk* and Memory* and
among Memory*. I'll make Disk* share code after Memory* and the underlying
system gets a little more testing.
  • Loading branch information
novas0x2a committed Jan 26, 2011
1 parent 48f0157 commit 370bbe9
Show file tree
Hide file tree
Showing 16 changed files with 1,265 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -115,6 +115,7 @@ docs/workbook/workbook.toc
/src/vw/FileIO/tests/TestEndianness
/src/vw/FileIO/tests/TestExtTester
/src/vw/FileIO/tests/TestGDALFeatures
/src/vw/FileIO/tests/TestMemoryImageResource
/src/vw/FileIO/tests/nodata.tif
/src/vw/Geometry/tests/TestSpatialTree
/src/vw/Geometry/tests/TestSphere
Expand Down
265 changes: 265 additions & 0 deletions src/vw/FileIO/JpegIO.cc
@@ -0,0 +1,265 @@
#include <vw/FileIO/JpegIO.h>
#include <vw/Core/Exception.h>
#include <vw/Core/Log.h>

static void vw_jpeg_error_exit(j_common_ptr cinfo) {
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message)(cinfo, buffer);
int msg_code = cinfo->err->msg_code;
jpeg_destroy(cinfo);
if ( msg_code == JERR_NO_SOI )
vw::vw_throw( vw::ArgumentErr() << "JpegIO: Cannot open non-jpeg files.\n" );
else
vw::vw_throw( vw::IOErr() << "JpegIO error: " << buffer );
}


namespace vw {
namespace fileio {
namespace detail {

////////////////////////////////////////////////////////////////////////////////
// Base class functions
////////////////////////////////////////////////////////////////////////////////
void JpegIO::init_base(jpeg_error_mgr** mgr) {
*mgr = jpeg_std_error(&m_jerr);
m_jerr.error_exit = &vw_jpeg_error_exit;
}

////////////////////////////////////////////////////////////////////////////////
// Decompress
////////////////////////////////////////////////////////////////////////////////
JpegIODecompress::JpegIODecompress()
{
init_base(&m_ctx.err);
jpeg_create_decompress(&m_ctx);
}

JpegIODecompress::~JpegIODecompress() {
jpeg_destroy_decompress(&m_ctx);
}

bool JpegIODecompress::ready() const {
return m_ctx.output_scanline == 0;
}

void JpegIODecompress::read(uint8* buffer, size_t bufsize) {
VW_ASSERT(this->ready(), LogicErr() << "Cannot reread from a JpegIO reader");

jpeg_start_decompress(&m_ctx);
size_t skip = line_bytes();
VW_ASSERT(bufsize >= m_ctx.output_height * skip, LogicErr() << "Buffer is too small");

while (m_ctx.output_scanline < m_ctx.output_height) {
jpeg_read_scanlines(&m_ctx, &buffer, 1);
buffer += skip;
}
jpeg_finish_decompress(&m_ctx);
}

void JpegIODecompress::open() {
bind();
jpeg_read_header(&m_ctx, TRUE);
jpeg_calc_output_dimensions(&m_ctx);

m_fmt.cols = m_ctx.output_width;
m_fmt.rows = m_ctx.output_height;
m_fmt.channel_type = VW_CHANNEL_UINT8;

switch (m_ctx.output_components)
{
case 1:
m_fmt.pixel_format = VW_PIXEL_GRAY;
m_fmt.planes=1;
break;
case 2:
m_fmt.pixel_format = VW_PIXEL_GRAYA;
m_fmt.planes=1;
break;
case 3:
m_fmt.pixel_format = VW_PIXEL_RGB;
m_fmt.planes=1;
break;
case 4:
m_fmt.pixel_format = VW_PIXEL_RGBA;
m_fmt.planes=1; break;
default:
m_fmt.planes = m_ctx.output_components;
m_fmt.pixel_format = VW_PIXEL_SCALAR;
break;
}

m_cstride = m_ctx.output_components;
m_rstride = m_cstride * m_fmt.cols;
}

////////////////////////////////////////////////////////////////////////////////
// Compress
////////////////////////////////////////////////////////////////////////////////
JpegIOCompress::JpegIOCompress(const ImageFormat& fmt)
{
m_fmt = fmt;
init_base(&m_ctx.err);
jpeg_create_compress(&m_ctx);
}

JpegIOCompress::~JpegIOCompress() {
jpeg_destroy_compress(&m_ctx);
}

bool JpegIOCompress::ready() const {
return m_ctx.next_scanline == 0;
}

void JpegIOCompress::write(const uint8* data, size_t rows, size_t cols, size_t bufsize) {
VW_ASSERT(this->ready(), LogicErr() << "Cannot rewrite to a JpegIO writer");

size_t skip = cols * chan_bytes();
VW_ASSERT(bufsize >= rows * skip, LogicErr() << "Buffer is too small");

m_ctx.image_width = cols;
m_ctx.image_height = rows;
jpeg_start_compress(&m_ctx, TRUE);

JSAMPLE* jdata = reinterpret_cast<JSAMPLE*>(const_cast<uint8*>(data));
while (m_ctx.next_scanline < m_ctx.image_height) {
jpeg_write_scanlines(&m_ctx, &jdata, 1);
jdata += skip;
}
jpeg_finish_compress(&m_ctx);
}

void JpegIOCompress::open() {
bind();

switch (fmt().pixel_format) {
case VW_PIXEL_SCALAR:
m_ctx.input_components = fmt().planes ? fmt().planes : 1;
m_ctx.in_color_space = JCS_UNKNOWN;
break;
case VW_PIXEL_GRAYA:
vw_out(DebugMessage, "fileio") << "JpegIOCompress: Warning: alpha channel removed." << std::endl;;
case VW_PIXEL_GRAY:
m_ctx.input_components = 1;
m_ctx.in_color_space = JCS_GRAYSCALE;
break;
case VW_PIXEL_RGBA:
vw_out(DebugMessage, "fileio") << "JpegIOCompress: Warning: alpha channel removed." << std::endl;;
case VW_PIXEL_RGB:
m_ctx.input_components = 3;
m_ctx.in_color_space = JCS_RGB;
break;
default:
vw_throw( IOErr() << "JpegIOCompress: Unsupported pixel type (" << fmt().pixel_format << ")." );
}

m_cstride = m_ctx.input_components;

jpeg_set_defaults(&m_ctx);
jpeg_set_quality(&m_ctx, 95, TRUE); // XXX Make this settable
}

////////////////////////////////////////////////////////////////////////////////
// Src/Dest Helpers
////////////////////////////////////////////////////////////////////////////////

struct ptr_src_mgr {
struct jpeg_source_mgr pub;

static void init_source(j_decompress_ptr /*cinfo*/) {/* no work */}
static void term_source(j_decompress_ptr /*cinfo*/) {/* no work */}

static ::boolean fill_input_buffer(j_decompress_ptr /*cinfo*/) {
#if 0
// No more data to provide... this is what libjpeg does (insert a fake EOI).
static JOCTET buf[4];
buf[0] = (JOCTET)0xFF;
buf[1] = (JOCTET)JPEG_EOI;
cinfo->src->next_input_byte = buf;
cinfo->src->bytes_in_buffer = 2;
vw_log(WarningMessage) << "Damaged JPEG" << std::endl;
#endif
// I think we should be more strict.
vw_throw(IOErr() << "Damaged JPEG. No EOI? Cannot continue.");
return TRUE;
}

static void skip_input_data (j_decompress_ptr cinfo, long num_bytes_l)
{
VW_ASSERT(num_bytes_l >= 0, ArgumentErr() << "Cannot skip negative bytes");
size_t num_bytes = num_bytes_l;

if (num_bytes == 0)
return;

ptr_src_mgr *src = reinterpret_cast<ptr_src_mgr*>(cinfo->src);
VW_ASSERT(num_bytes <= src->pub.bytes_in_buffer, ArgumentErr() << "Cannot skip more bytes than are left");

src->pub.next_input_byte += num_bytes;
src->pub.bytes_in_buffer -= num_bytes;
}
};

struct vector_dest_mgr {
struct jpeg_destination_mgr pub;
// Make a guess that we're going to output more than 4k.
static const size_t BLOCK_SIZE = 4096;
std::vector<uint8>* vec;

static void init_destination (j_compress_ptr cinfo) {
vector_dest_mgr *dest = reinterpret_cast<vector_dest_mgr*>(cinfo->dest);
dest->vec->resize(BLOCK_SIZE);
dest->pub.free_in_buffer = BLOCK_SIZE;
dest->pub.next_output_byte = &(dest->vec->operator[](0));
}
static ::boolean empty_output_buffer (j_compress_ptr cinfo) {
vector_dest_mgr *dest = reinterpret_cast<vector_dest_mgr*>(cinfo->dest);
// resize gives us exactly what we ask for, but we actually want to do an
// amortized O(1) insert. So resize to twice the current capacity each
// time. Normally, size() starts at zero and this wouldn't work, but we
// initialize it to nonzero above.
dest->pub.free_in_buffer = dest->vec->size();
dest->vec->resize(dest->vec->capacity() * 2);
dest->pub.next_output_byte = &(dest->vec->operator[](dest->pub.free_in_buffer));
return true;
}
static void term_destination (j_compress_ptr cinfo) {
vector_dest_mgr *dest = reinterpret_cast<vector_dest_mgr*>(cinfo->dest);
// cut off the default-initialized tail
dest->vec->resize(dest->vec->size() - dest->pub.free_in_buffer);
}
};

void jpeg_ptr_src(j_decompress_ptr cinfo, const uint8* data, size_t size) {
VW_ASSERT(data, ArgumentErr() << "jpeg_ptr_src: Expected a non-null data ptr");
VW_ASSERT(size, ArgumentErr() << "jpeg_ptr_src: Expected a non-zero size");

ptr_src_mgr *src = reinterpret_cast<ptr_src_mgr*>(
(*cinfo->mem->alloc_small)(reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(ptr_src_mgr)));

src->pub.init_source = &ptr_src_mgr::init_source;
src->pub.fill_input_buffer = &ptr_src_mgr::fill_input_buffer;
src->pub.skip_input_data = &ptr_src_mgr::skip_input_data;
src->pub.resync_to_restart = ::jpeg_resync_to_restart;
src->pub.term_source = &ptr_src_mgr::term_source;
src->pub.bytes_in_buffer = size;
src->pub.next_input_byte = reinterpret_cast<JOCTET*>(const_cast<uint8*>(data));
cinfo->src = reinterpret_cast<jpeg_source_mgr*>(src);
}

void jpeg_vector_dest(j_compress_ptr cinfo, std::vector<uint8>* v) {
VW_ASSERT(v, ArgumentErr() << "std_vector_dest: Expected a non-null vector");

// allocate the mgr using libjpeg's current memory manager. It has the same
// lifetime as the j_compress_ptr
vector_dest_mgr *dest = reinterpret_cast<vector_dest_mgr*>(
(*cinfo->mem->alloc_small)(reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(vector_dest_mgr)));

dest->pub.init_destination = &vector_dest_mgr::init_destination;
dest->pub.empty_output_buffer = &vector_dest_mgr::empty_output_buffer;
dest->pub.term_destination = &vector_dest_mgr::term_destination;
dest->vec = v;
cinfo->dest = reinterpret_cast<jpeg_destination_mgr*>(dest);
}

}}} // namespace vw::fileio::detail
60 changes: 60 additions & 0 deletions src/vw/FileIO/JpegIO.h
@@ -0,0 +1,60 @@
#ifndef __VW_FILEIO_JPEGIO_H__
#define __VW_FILEIO_JPEGIO_H__

#include <vw/FileIO/ScanlineIO.h>

extern "C" {
#include <jpeglib.h>
#include <jerror.h>
}


namespace vw {
namespace fileio {
namespace detail {

// These classes exist to share code between the on-disk and in-memory versions
// of the relevant image resources. They are not intended for use by users
// (thus the detail namespace).
//
// Note: This code does not support suspending data sources! As the libjpeg
// docs say, if you don't know what that means, you don't need it.

class JpegIO {
private:
jpeg_error_mgr m_jerr;
protected:
void init_base(jpeg_error_mgr** mgr);
};

class JpegIODecompress : public JpegIO, public ScanlineReadBackend {
protected:
jpeg_decompress_struct m_ctx;
public:
JpegIODecompress();
virtual ~JpegIODecompress();

void open();
bool ready() const;
void read(uint8* data, size_t bufsize);
};

class JpegIOCompress : public JpegIO, public ScanlineWriteBackend {
protected:
jpeg_compress_struct m_ctx;
public:
// cols/rows/planes ignored in imageformat
JpegIOCompress(const ImageFormat& fmt);
virtual ~JpegIOCompress();

void open();
bool ready() const;
void write(const uint8* buffer, size_t rows, size_t cols, size_t bufsize);
};

void jpeg_ptr_src(j_decompress_ptr cinfo, const uint8* buffer, size_t size);
void jpeg_vector_dest(j_compress_ptr cinfo, std::vector<uint8>* v);

}}} // namespace vw::fileio::detail

#endif
12 changes: 8 additions & 4 deletions src/vw/FileIO/Makefile.am
Expand Up @@ -12,13 +12,13 @@
if MAKE_MODULE_FILEIO

if HAVE_PKG_PNG
png_headers = DiskImageResourcePNG.h
png_sources = DiskImageResourcePNG.cc
png_headers = DiskImageResourcePNG.h MemoryImageResourcePNG.h PngIO.h
png_sources = DiskImageResourcePNG.cc MemoryImageResourcePNG.cc PngIO.cc
endif

if HAVE_PKG_JPEG
jpeg_headers = DiskImageResourceJPEG.h
jpeg_sources = DiskImageResourceJPEG.cc
jpeg_headers = DiskImageResourceJPEG.h MemoryImageResourceJPEG.h JpegIO.h
jpeg_sources = DiskImageResourceJPEG.cc MemoryImageResourceJPEG.cc JpegIO.cc
endif

if HAVE_PKG_TIFF
Expand Down Expand Up @@ -48,7 +48,9 @@ include_HEADERS = \
DiskImageResourcePBM.h \
DiskImageResourcePDS.h \
DiskImageView.h \
MemoryImageResource.h \
KML.h \
ScanlineIO.h \
$(gdal_headers) \
$(hdf_headers) \
$(jpeg_headers) \
Expand All @@ -61,6 +63,8 @@ libvwFileIO_la_SOURCES = \
DiskImageResourcePBM.cc \
DiskImageResourcePDS.cc \
KML.cc \
MemoryImageResource.cc \
ScanlineIO.cc \
$(gdal_sources) \
$(hdf_sources) \
$(jpeg_sources) \
Expand Down

0 comments on commit 370bbe9

Please sign in to comment.