diff --git a/README b/README
index 98fb2532..48c8be4c 100644
--- a/README
+++ b/README
@@ -57,7 +57,7 @@ See COPYING.FCGI for licensing information for these libraries.
REQUIREMENTS
------------
Requirements: libtiff, zlib and the IJG JPEG development libraries.
-Optional: libmemcached (for Memcached) and Kakadu (for JPEG2000)
+Optional: libmemcached (for Memcached) and Kakadu or OpenJPEG (for JPEG2000)
Plus, of course, an fcgi-enabled web server. The server has been successfully
tested on the following servers:
@@ -115,6 +115,19 @@ following parameters to the ./configure command
+OPTIONAL LIBRARIES: OPENJPEG
+----------------------------
+IIPImage is able to decode JPEG2000 images via the free software OpenJPEG
+(https://github.com/uclouvain/openjpeg). This is still much slower than
+decoding via the Kakadu SDK, and it is also highly experimental.
+
+It is also possible to use both the Kakadu SDK and OpenJPEG.
+Then the Kakadu SDK will be used by default.
+OpenJPEG can be enabled by setting the environment variable USE_OPENJPEG
+to any value larger than 0, for example with USE_OPENJPEG=1.
+
+
+
INSTALLATION
------------
Simply copy the executable called iipsrv.fcgi in the src subdirectory into
diff --git a/configure.ac b/configure.ac
index 4f87bb36..dba86c10 100644
--- a/configure.ac
+++ b/configure.ac
@@ -205,6 +205,44 @@ AM_CONDITIONAL( [ENABLE_MODULES], [test x$modules = xtrue] )
AC_SUBST(DL_LIBS)
+#************************************************************
+
+# Check for OpenJPEG JPEG2000 library
+
+AC_ARG_WITH( openjpeg,
+ [ --with-openjpeg=DIR location of the openjpeg source files],
+ openjpeg_path=$withval)
+
+
+AC_CHECK_FILE($openjpeg_path/src/lib/openjp2/openjpeg.h,
+ AC_MSG_RESULT([configure: Found OpenJPEG sources. Will compile JPEG2000 support]); OPENJPEG=true,
+ AC_MSG_RESULT([configure: No OpenJPEG JPEG2000 Sources Found]); OPENJPEG=false
+)
+
+if test "x$OPENJPEG" = xtrue; then
+ INCLUDES="$INCLUDES -I$openjpeg_path/src/lib/openjp2/"
+ LIBS="$LIBS $openjpeg_path/bin/libopenjp2.so -lpthread"
+else
+ PKG_CHECK_MODULES([OPENJPEG], [libopenjp2], [OPENJPEG=true], [OPENJPEG=false])
+ if test "x$OPENJPEG" = xtrue; then
+ INCLUDES="$INCLUDES $OPENJPEG_CFLAGS"
+ LIBS="$LIBS $OPENJPEG_LIBS"
+ fi
+fi
+
+if test "x$OPENJPEG" = xtrue; then
+ AC_DEFINE(HAVE_OPENJPEG)
+ EXTRAS="OpenJPEGImage.o"
+fi
+
+AM_CONDITIONAL([ENABLE_OPENJPEG], [test x$OPENJPEG = xtrue])
+
+AC_SUBST(INCLUDES)
+AC_SUBST(EXTRAS)
+
+AC_SUBST(LIBS)
+
+
#************************************************************
# Check for Kakadu JPEG2000 library
@@ -346,6 +384,7 @@ Options Enabled:
---------------
Memcached: ${MEMCACHED}
JPEG2000 (Kakadu): ${KAKADU}
+ JPEG2000 (OpenJPEG): ${OPENJPEG}
])
# PNG Output: ${PNG}
diff --git a/src/Environment.h b/src/Environment.h
index 8144d7c9..ac40606d 100644
--- a/src/Environment.h
+++ b/src/Environment.h
@@ -129,6 +129,15 @@ class Environment {
return layers;
}
+ static bool getUseOpenJPEG(){
+ char* envpara = getenv( "USE_OPENJPEG" );
+ int value;
+ if( envpara ) value = atoi( envpara );
+ else value = 0;
+
+ return (value > 0);
+ }
+
static std::string getFileSystemPrefix(){
char* envpara = getenv( "FILESYSTEM_PREFIX" );
diff --git a/src/FIF.cc b/src/FIF.cc
index d35b8f4a..1e61dc33 100644
--- a/src/FIF.cc
+++ b/src/FIF.cc
@@ -29,6 +29,10 @@
#include "KakaduImage.h"
#endif
+#ifdef HAVE_OPENJPEG
+#include "OpenJPEGImage.h"
+#endif
+
#define MAXIMAGECACHE 1000 // Max number of items in image cache
@@ -120,10 +124,20 @@ void FIF::run( Session* session, const string& src ){
if( session->loglevel >= 2 ) *(session->logfile) << "FIF :: TIFF image detected" << endl;
*session->image = new TPTImage( test );
}
-#ifdef HAVE_KAKADU
+#if defined(HAVE_KAKADU) || defined(HAVE_OPENJPEG)
else if( format == JPEG2000 ){
- if( session->loglevel >= 2 ) *(session->logfile) << "FIF :: JPEG2000 image detected" << endl;
- *session->image = new KakaduImage( test );
+ if( session->loglevel >= 2 )
+ *(session->logfile) << "FIF :: JPEG2000 image detected" << endl;
+#if defined(HAVE_OPENJPEG)
+ if( session->useOpenJPEG )
+ *session->image = new OpenJPEGImage( test );
+#endif
+#if defined(HAVE_KAKADU)
+#if defined(HAVE_OPENJPEG)
+ else
+#endif
+ *session->image = new KakaduImage( test );
+#endif
}
#endif
else throw string( "Unsupported image type: " + argument );
@@ -145,7 +159,7 @@ void FIF::run( Session* session, const string& src ){
throw string( "Unsupported image type: " + imtype );
}
else{
- // Construct our dynamic loading image decoder
+ // Construct our dynamic loading image decoder
session->image = new DSOImage( test );
(*session->image)->Load( (*mod_it).second );
diff --git a/src/Main.cc b/src/Main.cc
index c159327e..c46e52a9 100644
--- a/src/Main.cc
+++ b/src/Main.cc
@@ -256,6 +256,15 @@ int main( int argc, char *argv[] )
string filesystem_prefix = Environment::getFileSystemPrefix();
+#if !defined(HAVE_OPENJPEG)
+ bool useOpenJPEG = false;
+#elif defined(HAVE_KAKADU)
+ bool useOpenJPEG = Environment::getUseOpenJPEG();
+#else
+ bool useOpenJPEG = true;
+#endif
+
+
// Set up our watermark object
Watermark watermark( Environment::getWatermark(),
Environment::getWatermarkOpacity(),
@@ -290,7 +299,10 @@ int main( int argc, char *argv[] )
else logfile << max_layers << endl;
}
#ifdef HAVE_KAKADU
- logfile << "Setting up JPEG2000 support via Kakadu SDK" << endl;
+ if(!useOpenJPEG) logfile << "Setting up JPEG2000 support via Kakadu SDK" << endl;
+#endif
+#ifdef HAVE_OPENJPEG
+ if(useOpenJPEG) logfile << "Setting up JPEG2000 support via OpenJPEG" << endl;
#endif
}
@@ -473,6 +485,7 @@ int main( int argc, char *argv[] )
session.out = &writer;
session.watermark = &watermark;
session.headers.clear();
+ session.useOpenJPEG = useOpenJPEG;
char* header = NULL;
diff --git a/src/Makefile.am b/src/Makefile.am
index c21c3d4b..e1516761 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -13,6 +13,10 @@ if ENABLE_KAKADU
iipsrv_fcgi_LDADD += KakaduImage.o
endif
+if ENABLE_OPENJPEG
+iipsrv_fcgi_LDADD += OpenJPEGImage.o
+endif
+
#if ENABLE_PNG
#iipsrv_fcgi_LDADD += PNGCompressor.o PTL.o
#endif
@@ -21,7 +25,7 @@ if ENABLE_MODULES
iipsrv_fcgi_LDADD += DSOImage.o
endif
-EXTRA_iipsrv_fcgi_SOURCES = DSOImage.h DSOImage.cc KakaduImage.h KakaduImage.cc Main.cc
+EXTRA_iipsrv_fcgi_SOURCES = DSOImage.h DSOImage.cc KakaduImage.h KakaduImage.cc Main.cc OpenJPEGImage.h OpenJPEGImage.cc
iipsrv_fcgi_SOURCES = \
IIPImage.h \
diff --git a/src/OpenJPEGImage.cc b/src/OpenJPEGImage.cc
new file mode 100644
index 00000000..c453488b
--- /dev/null
+++ b/src/OpenJPEGImage.cc
@@ -0,0 +1,538 @@
+/* IIP Server: OpenJPEG JPEG2000 handler
+
+ Copyright (C) 2015 Moravian Library in Brno (http://www.mzk.cz/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+//#define DEBUG 1
+
+#include "OpenJPEGImage.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+using namespace std;
+
+/************************************************************************/
+/* callbacks */
+/************************************************************************/
+// These are called by OpenJPEG library.
+// This driver then decides to ignore the messages or to process them.
+// While not in debug mode errors are processed but warnings and info messages are omitted
+
+static void error_callback(const char* msg, void* /*client_data*/)
+{
+ stringstream ss;
+ ss << "ERROR :: OpenJPEG core :: " << msg;
+ throw file_error(ss.str());
+}
+
+static void warning_callback(const char* msg, void* /*client_data*/)
+{
+#ifdef DEBUG
+ logfile << "WARNING :: OpenJPEG core :: " << msg << flush;
+#endif
+}
+
+static void info_callback(const char* msg, void* /*client_data*/)
+{
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG core :: " << msg << flush;
+#endif
+}
+
+/************************************************************************/
+/* openImage() */
+/************************************************************************/
+// Opens file with image and calls loadImageInfo()
+
+void OpenJPEGImage::openImage() throw(file_error)
+{
+#ifdef DEBUG
+ Timer timer;
+ timer.start();
+ logfile << "INFO :: OpenJPEG :: openImage() :: started" << endl
+ << flush;
+#endif
+
+ filename = getFileName(currentX, currentY); // Get file name
+ updateTimestamp(filename); // Check if our image has been modified
+
+ loadImageInfo(currentX, currentY);
+ isSet = true; // Image is opened and info is set
+
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG :: openImage() :: " << timer.getTime() << " microseconds" << endl
+ << flush;
+#endif
+}
+
+/************************************************************************/
+/* closeImage() */
+/************************************************************************/
+// Closes file with image
+
+void OpenJPEGImage::closeImage()
+{
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG :: closeImage() :: started" << endl
+ << flush;
+#endif
+
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG :: closeImage() :: ended" << endl
+ << flush;
+#endif
+}
+
+/************************************************************************/
+/* loadImageInfo() */
+/************************************************************************/
+// Saves important image information to IIPImage and OpenJPEGImage variables
+
+void OpenJPEGImage::loadImageInfo(int /*seq*/, int /*ang*/) throw(file_error)
+{
+#ifdef DEBUG
+ Timer timer;
+ timer.start();
+ logfile << "INFO :: OpenJPEG :: loadImageInfo() :: started" << endl
+ << flush;
+#endif
+
+ static opj_image_t* l_image; // Image structure
+ static opj_stream_t* l_stream; // File stream
+ static opj_codec_t* l_codec; // Handle to a decompressor
+
+ class Finally {
+ // This class makes sure that the resources are deallocated properly
+ public:
+ ~Finally()
+ {
+ opj_end_decompress(l_codec, l_stream);
+ opj_stream_destroy(l_stream);
+ opj_destroy_codec(l_codec);
+ opj_image_destroy(l_image);
+ l_codec = NULL;
+ l_stream = NULL;
+ l_image = NULL;
+ }
+ };
+ Finally finally; // Allocated on stack, destructor is called on both successful and exceptional scope exit
+
+ l_codec = opj_create_decompress(OPJ_CODEC_JP2); // Create decompress codec
+
+ // Set callback handlers. OPJ library then passes information, warnings and errors to specified methods.
+ opj_set_info_handler(l_codec, info_callback, 00);
+ opj_set_warning_handler(l_codec, warning_callback, 00);
+ opj_set_error_handler(l_codec, error_callback, 00);
+
+ opj_dparameters_t parameters; // Set default decoder parameters
+ opj_set_default_decoder_parameters(¶meters);
+ if (!opj_setup_decoder(l_codec, ¶meters)) {
+ throw file_error("ERROR :: OpenJPEG :: openImage() :: opj_setup_decoder() failed"); // Setup decoder
+ }
+
+ if (!(l_stream = opj_stream_create_default_file_stream(filename.c_str(), 1))) {
+ throw file_error("ERROR :: OpenJPEG :: openImage() :: opj_stream_create_default_file_stream() failed"); // Create stream
+ }
+
+ if (!opj_read_header(l_stream, l_codec, &l_image)) {
+ throw file_error("ERROR :: OpenJPEG :: openImage() :: opj_read_header() failed"); // Read main header
+ }
+
+ opj_codestream_info_v2_t* cst_info = opj_get_cstr_info(l_codec); // Get info structure
+ image_tile_width = cst_info->tdx; // Save image tile width - tile width that this image operates with
+ image_tile_height = cst_info->tdy; // Save image tile height
+ numResolutions = cst_info->m_default_tile_info.tccp_info[0].numresolutions; // Save number of resolution levels in image
+ max_layers = cst_info->m_default_tile_info.numlayers; // Save number of layers
+#ifdef DEBUG
+ logfile << "OpenJPEG :: " << max_layers << " quality layers detected" << endl
+ << flush;
+#endif
+ opj_destroy_cstr_info(&cst_info); // We already read everything we needed from info structure
+
+ // Check whether image parameters make sense
+ if (l_image->x1 <= l_image->x0 || l_image->y1 <= l_image->y0 ||
+ l_image->numcomps == 0 ||
+ l_image->comps[0].w != l_image->x1 - l_image->x0 ||
+ l_image->comps[0].h != l_image->y1 - l_image->y0) {
+ throw file_error("ERROR :: OpenJPEG :: loadImageInfo() :: Could not handle that image");
+ }
+
+ // Check for 420 format
+ if (l_image->color_space != OPJ_CLRSPC_SRGB && l_image->numcomps == 3 &&
+ l_image->comps[1].w == l_image->comps[0].w / 2 &&
+ l_image->comps[1].h == l_image->comps[0].h / 2 &&
+ l_image->comps[2].w == l_image->comps[0].w / 2 &&
+ l_image->comps[2].h == l_image->comps[0].h / 2) {
+ throw file_error("ERROR :: OpenJPEG :: loadImageInfo() :: 420 format detected.");
+ }
+
+ // Check whether component parameters make sense
+ for (int i = 2; i <= (int)l_image->numcomps; ++i) {
+ if (l_image->comps[i - 1].w != l_image->comps[0].w || l_image->comps[i - 1].h != l_image->comps[0].h) {
+ throw file_error("ERROR :: OpenJPEG :: loadImageInfo() :: Could not handle that image");
+ }
+ }
+
+ // Save color space
+ switch ((channels = l_image->numcomps)) {
+ case 3:
+ colourspace = sRGB;
+ break;
+ case 1:
+ colourspace = GREYSCALE;
+ break;
+ default:
+ throw file_error("ERROR :: OpenJPEG :: Unsupported color space");
+ }
+
+ bpc = l_image->comps[0].prec; // Save bit depth
+ sgnd = l_image->comps[0].sgnd; // Save whether the data are signed
+
+ // Save first resolution level
+ image_widths.push_back((raster_width = l_image->x1 - l_image->x0));
+ image_heights.push_back((raster_height = l_image->y1 - l_image->y0));
+
+ unsigned int tmp_w = raster_width;
+ unsigned int tmp_h = raster_height;
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG :: Resolution : " << tmp_w << "x" << tmp_h << endl
+ << flush;
+#endif
+ unsigned short i = 1;
+ // Save other resolution levels
+ for (; tmp_w > tile_width || tmp_h > tile_height; ++i) {
+ image_widths.push_back((tmp_w /= 2));
+ image_heights.push_back((tmp_h /= 2));
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG :: Resolution : " << tmp_w << "x" << tmp_h << endl
+ << flush;
+#endif
+ }
+
+ // Generate virtual resolutions if necessary
+ if (i > numResolutions) {
+ virtual_levels = i - numResolutions;
+#ifdef DEBUG
+ logfile << "WARNING :: OpenJPEG :: Insufficient resolution levels in JPEG2000 stream. Will generate " << virtual_levels << " extra levels dynamically." << endl;
+#endif
+ }
+
+ numResolutions = i; // Set number of resolutions to (number of picture resolutions + number of virtual resolutions)
+
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG :: loadImageInfo() :: " << timer.getTime() << " microseconds" << endl
+ << flush;
+#endif
+}
+
+/************************************************************************/
+/* getTile() - Get an individual tile */
+/************************************************************************/
+// Get one individual tile from the opened picture
+
+RawTile OpenJPEGImage::getTile(int seq, int ang, unsigned int res, int layers,
+ unsigned int tile) throw(file_error)
+{
+#ifdef DEBUG
+ Timer timer;
+ timer.start();
+ logfile << "INFO :: OpenJPEG :: getTile() :: started" << endl
+ << flush;
+#endif
+
+ // Check whether requested resolution exists in the picture.
+ if (res > numResolutions) {
+ throw file_error("ERROR :: OpenJPEG :: getTile() :: Asked for non-existent resolution");
+ }
+
+ // Reverse the resolution number - resolutions in IIPImage are stored in reversed order
+ int vipsres = (numResolutions - 1) - res;
+
+ unsigned int rem_x = image_widths[vipsres] % tile_width; // Get the width and height for last row and column tiles
+ unsigned int rem_y = image_heights[vipsres] % tile_height; // that means remaining pixels, which do not form a whole tile
+
+ unsigned int ntlx = (image_widths[vipsres] / tile_width) + (rem_x == 0 ? 0 : 1); // Calculate the number of tiles in each direction
+ unsigned int ntly = (image_heights[vipsres] / tile_height) + (rem_y == 0 ? 0 : 1);
+
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG :: getTile() :: asked for: " << endl
+ << "\tTile width: " << tile_width << endl
+ << "\tTile height: " << tile_height << endl
+ << "\tResolution: " << res << ", meaning " << vipsres << " for OpenJPEG driver" << endl
+ << "\tResolution: " << image_widths[vipsres] << "x" << image_heights[vipsres] << endl
+ << "\tTile index: " << tile << endl
+ << "\tTiles available: " << ntlx << "x" << ntly << endl
+ << "\tRemaining tile width in last column: " << rem_x << endl
+ << "\tRemaining tile height in bottom row: " << rem_y << endl
+ << flush;
+#endif
+
+ // Check whether requested tile exists.
+ if (tile >= ntlx * ntly) {
+ throw file_error("ERROR :: OpenJPEG :: getTile() :: Asked for non-existent tile");
+ }
+
+ unsigned int tw = tile_width, th = tile_height;
+
+ // Alter the tile size if it's in the last column
+ if ((tile % ntlx == ntlx - 1) && rem_x != 0) {
+ tw = rem_x;
+ }
+
+ // Alter the tile size if it's in the bottom row
+ if ((tile / ntlx == ntly - 1) && rem_y != 0) {
+ th = rem_y;
+ }
+
+ // Calculate the pixel offsets for this tile
+ int xoffset = (tile % ntlx) * tile_width;
+ int yoffset = (tile / ntlx) * tile_height;
+
+#ifdef DEBUG
+ logfile << "\tFinal tile size requested: " << tw << "x" << th << " @" << channels << endl
+ << flush;
+#endif
+
+ // Create Rawtile object and initialize it
+ RawTile rawtile(tile, res, seq, ang, tw, th, channels, 8);
+ rawtile.data = new unsigned char[tw * th * channels];
+ rawtile.dataLength = tw * th * channels;
+ rawtile.filename = getImagePath();
+ rawtile.timestamp = timestamp;
+
+ // Set the number of layers to half of the number of detected layers if we have not set the layers parameter manually
+ if (layers <= 0) {
+ layers = ceil(max_layers / 2.0);
+ }
+
+ // Process the tile - save data to rawfile.data
+ // We can decode a single tile only if requested size equals tile size defined in opened image
+ // If tile sizes defined in IIPImage and opened image do not match, indexes of tiles we ask OPJ library for do not match either
+ // In case of this tile size inconsistency, seventh parameter becomes -1. This means that OpenJPEG library will process the request as
+ // a request for region, not a tile. OPJ library will then select tiles that need to be decoded in order to decode requested region.
+ process(tw, th, xoffset, yoffset, res, layers,
+ (image_tile_width == tw && image_tile_height == th) ? tile : -1, rawtile.data);
+
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG :: getTile() :: " << timer.getTime() << " microseconds" << endl
+ << flush;
+#endif
+
+ return rawtile;
+}
+
+/************************************************************************/
+/* getRegion() - Get an entire region and not just a tile */
+/************************************************************************/
+// Gets selected region from opened picture
+
+void OpenJPEGImage::getRegion(int /*seq*/, int /*ang*/, unsigned int res,
+ int layers, int x, int y,
+ unsigned int w, unsigned int h,
+ unsigned char* buf) throw(file_error)
+{
+#ifdef DEBUG
+ Timer timer;
+ timer.start();
+ logfile << "INFO :: OpenJPEG :: getRegion() :: started" << endl
+ << flush;
+#endif
+
+ // Check if desired resolution exists
+ if (res > numResolutions) {
+ throw file_error("ERROR :: OpenJPEG :: getRegion() :: Asked for non-existent resolution");
+ }
+
+ // Check layer request
+ if (layers <= 0) {
+ layers = ceil(max_layers / 2.0);
+ }
+
+ // Reverse resolution level number
+ int vipsres = (numResolutions - 1) - res;
+
+ // Check if specified region is valid for this picture
+ if ((x + w) > image_widths[vipsres] || (y + h) > image_heights[vipsres]) {
+ throw file_error("ERROR :: OpenJPEG :: getRegion() :: Asked for region out of raster size");
+ }
+
+ // Get the region
+ process(w, h, x, y, res, layers, -1, buf);
+
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG :: getRegion() :: " << timer.getTime() << " microseconds" << endl
+ << flush;
+#endif
+}
+
+/************************************************************************/
+/* process() - Main processing function */
+/************************************************************************/
+// Core method for recovering tiles and regions from opened picture via OPJ library
+
+void OpenJPEGImage::process(unsigned int tw, unsigned int th,
+ unsigned int xoffset, unsigned int yoffset,
+ unsigned int res, int layers, int tile,
+ void* d) throw(file_error)
+{
+ static opj_image_t* out_image; // Decoded image
+ static opj_stream_t* l_stream; // File stream
+ static opj_codec_t* l_codec; // Handle to a decompressor
+
+ unsigned int factor = 1; // Downsampling factor - set it to default value
+ int vipsres = (numResolutions - 1) - res; // Reverse resolution number
+
+ if (res < virtual_levels) {
+ // Handle virtual resolutions
+ // Once we enter this scope it means we need to provide a virtual resolution
+ // Factor 2 means half of the smallest original resolution, factor 4 means quarter of it and so on...
+ // Can not be negative or zero - see above condition. Is always >= 2
+ factor = 2 * (virtual_levels - res);
+ // Take care of offsets as well
+ xoffset *= factor;
+ yoffset *= factor;
+ // We need to decode bigger region than requested. We are going to downsample it later, thus gain the desired size again
+ tw *= factor;
+ th *= factor;
+ // Set our resolution level back to the smallest original resolution
+ vipsres = numResolutions - 1 - virtual_levels;
+ }
+
+ class Finally {
+ // This class makes sure that the resources are deallocated properly
+ public:
+ ~Finally()
+ {
+ opj_end_decompress(l_codec, l_stream);
+ opj_stream_destroy(l_stream);
+ opj_destroy_codec(l_codec);
+ opj_image_destroy(out_image);
+ l_codec = NULL;
+ l_stream = NULL;
+ out_image = NULL;
+ }
+ };
+ // Allocated on stack, destructor is called on both successful and exceptional scope exit
+ Finally finally;
+
+ l_codec = opj_create_decompress(OPJ_CODEC_JP2); // Create decompress codec
+ opj_set_info_handler(l_codec, info_callback, 00); // Set callback handlers
+ opj_set_warning_handler(l_codec, warning_callback, 00);
+ opj_set_error_handler(l_codec, error_callback, 00);
+
+ if (!(l_stream = opj_stream_create_default_file_stream(filename.c_str(), 1))) {
+ // Create stream
+ throw file_error("ERROR :: OpenJPEG :: process() :: opj_stream_create_default_file_stream() failed");
+ }
+
+ opj_dparameters_t params;
+ params.cp_layer = layers; // Set quality layers
+ params.cp_reduce = 0;
+ if (!opj_setup_decoder(l_codec, ¶ms)) {
+ // Setup layers
+ throw file_error("ERROR :: OpenJPEG :: process() :: opj_setup_decoder() failed");
+ }
+
+ if (!opj_read_header(l_stream, l_codec, &out_image)) {
+ // Read main header
+ throw file_error("ERROR :: OpenJPEG :: process() :: opj_read_header() failed");
+ }
+ if (!opj_set_decoded_resolution_factor(l_codec, vipsres)) {
+ // Setup resolution
+ throw file_error("ERROR :: OpenJPEG :: process() :: opj_set_decoded_resolution_factor() failed");
+ }
+#ifdef DEBUG
+ Timer timer;
+ timer.start();
+ logfile << "INFO :: OpenJPEG :: process() :: Decoding started" << endl
+ << flush;
+#endif
+ if (tile < 0) {
+ // In this scope we decode a region - OpenJPEG library selects tiles that need to be decoded itself
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG :: process() :: Decoding a region (not a single tile)" << endl
+ << flush;
+#endif
+ // Tell OpenJPEG what region we want to decode
+ if (!opj_set_decode_area(l_codec, out_image, xoffset, yoffset, xoffset + tw, yoffset + th)) {
+ throw file_error("ERROR :: OpenJPEG :: process() :: opj_set_decode_area() failed");
+ }
+ // Decode region from image
+ if (!opj_decode(l_codec, l_stream, out_image)) {
+ throw file_error("ERROR :: OpenJPEG :: process() :: opj_decode() failed");
+ }
+ }
+ // Get a single tile if possible
+ else if (!opj_get_decoded_tile(l_codec, l_stream, out_image, tile)) {
+ throw file_error("ERROR :: OpenJPEG :: process() :: opj_get_decoded_tile() failed");
+ }
+
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG :: process() :: Decoding took " << timer.getTime() << " microseconds" << endl
+ << "INFO :: OpenJPEG :: process() :: Decoded image info: " << endl
+ << "\tPrecision: " << out_image->comps[0].prec << endl
+ << "\tBPP: " << out_image->comps[0].bpc << endl
+ << "\tSigned: " << out_image->comps[0].sgnd << endl
+ << "\tXOFF: " << out_image->comps[0].x0 << endl
+ << "\tYOFF: " << out_image->comps[0].y0 << endl
+ << "\tXSIZE: " << out_image->comps[0].w << endl
+ << "\tYSIZE: " << out_image->comps[0].h << endl
+ << "\tRESNO: " << out_image->comps[0].resno_decoded << endl
+ << "INFO :: OpenJPEG :: process() :: Copying image data started" << endl
+ << flush;
+ timer.start();
+#endif
+
+ // Now we need to pass decoded data to the buffer we received as a parameter (actually we received just a reference to it)
+ // Create new pointer to the buffer as unsigned char pointer
+ unsigned char* p_buffer = (unsigned char*)d;
+ // We are unable to perform pointer arithmetics on void pointers
+ unsigned int buffer_write_pos = 0; // Write position indicator
+ unsigned int h_pos = 0;
+ unsigned int w_pos;
+ unsigned int xy_position = 0; // Another position indicators
+ unsigned int color_comp; // Current color component indicator
+ for (; h_pos < th; h_pos += factor) {
+ // Vertical cycle - takes care of pixel columns
+ for (w_pos = 0; w_pos < tw; w_pos += factor) {
+ // Horizontal cycle - takes care of pixel rows
+ xy_position = (tw * h_pos) + w_pos; // Calculate exact position (pixel index)
+ for (color_comp = 0; color_comp < channels; ++color_comp) {
+ // For each color component in that pixel
+ // Get component data from opj_image structure which contains decoded tile/region
+ OPJ_INT32 int_data = out_image->comps[color_comp].data[xy_position];
+ // Copy first byte from the integer we saved to buffer
+ p_buffer[buffer_write_pos + color_comp] = int_data & 0x000000ff;
+ //(int_data & 0x0000ff00) >> 8; --second byte
+ //(int_data & 0x00ff0000) >> 16; --third byte
+ //(int_data & 0xff000000) >> 24; --fourth byte
+ // Just a first byte from OPJ integer is needed for 8-bit depth images
+ }
+ // Increment write position indicator for each pixel's component we have written to the buffer
+ buffer_write_pos += channels;
+ }
+ }
+
+#ifdef DEBUG
+ logfile << "INFO :: OpenJPEG :: process() :: Copying image data took " << timer.getTime() << " microseconds" << endl
+ << flush;
+#endif
+}
diff --git a/src/OpenJPEGImage.h b/src/OpenJPEGImage.h
new file mode 100644
index 00000000..6ebcd51b
--- /dev/null
+++ b/src/OpenJPEGImage.h
@@ -0,0 +1,165 @@
+/* IIP Server: OpenJPEG JPEG2000 handler
+
+ Copyright (C) 2015 Moravian Library in Brno (http://www.mzk.cz/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef _OPENJPEGIMAGE_H
+#define _OPENJPEGIMAGE_H
+
+#include "IIPImage.h"
+
+#define TILESIZE 256
+
+extern std::ofstream logfile;
+
+// Image class for JPEG 2000 Images:
+// Inherits from IIPImage. Uses the OpenJPEG library.
+class OpenJPEGImage : public IIPImage {
+
+private:
+ std::string filename; // Compressed source file
+
+ unsigned int raster_width; // Image size
+ unsigned int raster_height;
+
+ unsigned int image_tile_width; // Tile size defined in the image
+ unsigned int image_tile_height;
+
+ int sgnd; // Whether the data are signed
+
+ unsigned int max_layers; // Quality layers
+
+ unsigned int virtual_levels; // How many virtual levels we need to generate
+
+ /**
+ Main processing function
+ \param tw width of region
+ \param th height of region
+ \param xoffset x coordinate
+ \param yoffset y coordinate
+ \param res resolution
+ \param layers number of quality levels to decode
+ \param tile specific tile to decode (-1 if deconding a region)
+ \param d buffer to fill
+*/
+ void process(unsigned int tw, unsigned int th,
+ unsigned int xoffset, unsigned int yoffset, unsigned int res,
+ int layers, int tile, void* d) throw(file_error);
+
+public:
+ /**
+ Constructor
+ */
+ OpenJPEGImage()
+ : IIPImage()
+ {
+ image_tile_width = 0;
+ image_tile_height = 0;
+ tile_width = TILESIZE;
+ tile_height = TILESIZE;
+ raster_width = 0;
+ raster_height = 0;
+ sgnd = 0;
+ numResolutions = 0;
+ virtual_levels = 0;
+ };
+
+ /**
+ Constructor
+ \param path image path
+ */
+ OpenJPEGImage(const std::string& path)
+ : IIPImage(path)
+ {
+ image_tile_width = 0;
+ image_tile_height = 0;
+ tile_width = TILESIZE;
+ tile_height = TILESIZE;
+ raster_width = 0;
+ raster_height = 0;
+ sgnd = 0;
+ numResolutions = 0;
+ virtual_levels = 0;
+ };
+
+ /**
+ Copy Constructor
+ \param image IIPImage object
+ */
+ OpenJPEGImage(const IIPImage& image)
+ : IIPImage(image)
+ {
+ tile_width = TILESIZE;
+ tile_height = TILESIZE;
+ numResolutions = image.numResolutions;
+ virtual_levels = 0;
+ };
+
+ /**
+ Destructor
+ */
+ ~OpenJPEGImage()
+ {
+ closeImage();
+ };
+
+ /**
+ Overloaded function for opening a JP2 image
+ */
+ void openImage() throw(file_error);
+
+ /**
+ Overloaded function for loading JP2 image information
+ \param x horizontal sequence angle
+ \param y vertical sequence angle
+ */
+ void loadImageInfo(int x, int y) throw(file_error);
+
+ /**
+ Overloaded function for closing a JP2 image
+ */
+ void closeImage();
+
+ /**
+ Overloaded function for getting a particular tile
+ \param x horizontal sequence angle
+ \param y vertical sequence angle
+ \param r resolution
+ \param l number of quality layers to decode
+ \param t tile number
+ */
+ RawTile getTile(int x, int y, unsigned int r, int l,
+ unsigned int t) throw(file_error);
+
+ /**
+ Overloaded function for returning a region from image
+ \param ha horizontal angle
+ \param va vertical angle
+ \param r resolution
+ \param l number of quality layers to decode
+ \param x x coordinate
+ \param y y coordinate
+ \param w width of region
+ \param h height of region
+ \param b buffer to fill
+ \return a RawTile object
+ */
+ void getRegion(int ha, int va, unsigned int r, int l,
+ int x, int y, unsigned int w, unsigned int h,
+ unsigned char* b) throw(file_error);
+};
+
+#endif
diff --git a/src/Task.h b/src/Task.h
index 339b303f..80277e1f 100644
--- a/src/Task.h
+++ b/src/Task.h
@@ -72,6 +72,7 @@ struct Session {
IIPResponse* response;
Watermark* watermark;
int loglevel;
+ bool useOpenJPEG;
std::ofstream* logfile;
std::map headers;