summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt20
-rw-r--r--config.h.cmake4
-rw-r--r--kimgio/CMakeLists.txt7
-rw-r--r--kimgio/jp2.cpp323
4 files changed, 331 insertions, 23 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 41a4ed3ad..c2923f1c5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -105,7 +105,7 @@ option( WITH_CUPS "Enable CUPS support" ON )
option( WITH_IMAGETOPS_BINARY "Enable installation of imagetops binary" ${WITH_ALL_OPTIONS} )
option( WITH_LUA "Enable LUA support" ${WITH_ALL_OPTIONS} )
option( WITH_TIFF "Enable tiff support" ${WITH_ALL_OPTIONS} )
-option( WITH_JASPER "Enable jasper (jpeg2k) support" ${WITH_ALL_OPTIONS} )
+option( WITH_OPENJPEG "Enable openjpeg (jpeg2k) support" ${WITH_ALL_OPTIONS} )
option( WITH_WEBP "Enable WebP support" ${WITH_ALL_OPTIONS} )
option( WITH_OPENEXR "Enable openexr support" ${WITH_ALL_OPTIONS} )
option( WITH_UTEMPTER "Use utempter for utmp management" ${WITH_ALL_OPTIONS} )
@@ -881,16 +881,16 @@ if( WITH_TIFF )
endif( WITH_TIFF )
-##### check for jasper ##########################
-
-if( WITH_JASPER )
- find_package( Jasper )
- if( NOT JASPER_FOUND )
- message(FATAL_ERROR "\njasper are requested, but not found on your system" )
- endif( NOT JASPER_FOUND )
- set( HAVE_JASPER 1 )
-endif( WITH_JASPER )
+##### check for openjpeg ##########################
+if( WITH_OPENJPEG )
+ pkg_search_module( OPENJPEG libopenjp2 )
+ if( NOT OPENJPEG_FOUND )
+ tde_message_fatal( "JPEG-2000 support requested, but openjpeg was not found on your system")
+ endif()
+ message( STATUS "JPEG-2000 support enabled" )
+ set( HAVE_OPENJPEG 1 )
+endif( WITH_OPENJPEG )
##### check for webp ############################
diff --git a/config.h.cmake b/config.h.cmake
index fa6580cf3..bba918f12 100644
--- a/config.h.cmake
+++ b/config.h.cmake
@@ -300,8 +300,8 @@
/* Define to 1 if you have the <inttypes.h> header file. */
#cmakedefine HAVE_INTTYPES_H 1
-/* Define if you have jasper */
-#cmakedefine HAVE_JASPER 1
+/* Define if you have openjpeg */
+#cmakedefine HAVE_OPENJPEG 1
/* Defines if your system has the libart library */
#cmakedefine HAVE_LIBART 1
diff --git a/kimgio/CMakeLists.txt b/kimgio/CMakeLists.txt
index cd013e588..80c20f7a6 100644
--- a/kimgio/CMakeLists.txt
+++ b/kimgio/CMakeLists.txt
@@ -16,6 +16,7 @@ include_directories(
${CMAKE_BINARY_DIR}
${CMAKE_BINARY_DIR}/tdecore
${CMAKE_SOURCE_DIR}/tdecore
+ ${OPENJPEG_INCLUDE_DIRS}
)
link_directories(
@@ -75,11 +76,11 @@ tde_add_kpart( ${target}
##### kimg_jp2 ##################################
-if( JASPER_FOUND )
+if( OPENJPEG_FOUND )
set( target kimg_jp2 )
tde_add_kpart( ${target}
SOURCES jp2.cpp
- LINK tdecore-shared ${JASPER_LIBRARIES}
+ LINK tdecore-shared ${OPENJPEG_LIBRARIES}
DESTINATION ${PLUGIN_INSTALL_DIR}
)
tde_create_translated_desktop(
@@ -87,7 +88,7 @@ if( JASPER_FOUND )
DESTINATION ${SERVICES_INSTALL_DIR}
PO_DIR mimetypes
)
-endif( JASPER_FOUND )
+endif( OPENJPEG_FOUND )
##### kimg_pcx ##################################
diff --git a/kimgio/jp2.cpp b/kimgio/jp2.cpp
index ff64f9263..948eef3fd 100644
--- a/kimgio/jp2.cpp
+++ b/kimgio/jp2.cpp
@@ -1,7 +1,8 @@
// This library is distributed under the conditions of the GNU LGPL.
#include "config.h"
-#ifdef HAVE_JASPER
+#ifdef HAVE_OPENJPEG
+
#include <unistd.h>
#include "jp2.h"
@@ -15,18 +16,322 @@
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
+
+#include <openjpeg.h>
+
+#include <kdebug.h>
#include <tdetempfile.h>
#include <tqcolor.h>
#include <tqcstring.h>
#include <tqfile.h>
#include <tqimage.h>
+#include <tqmemarray.h>
+
+/*
+ * JPEG-2000 Plugin for KImageIO.
+ *
+ * Current limitations:
+ * - Only reads sRGB/Grayscale images (doesn't convert colorspace).
+ * - Doesn't support writing images.
+ * - Doesn't support OPJ_CODEC_J2K.
+ * - Doesn't support subsampling.
+ * - Doesn't read ICC profiles.
+ * - Doesn't write images.
+ *
+ * Improvements:
+ * - kimgio_jp2_read_srgba/kimgio_jp2_read_grayscale could be merged.
+ *
+ * The API documentation is rather poor, so good references on how to use OpenJPEG
+ * are the tools provided by OpenJPEG, such as 'opj_decompress':
+ * https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/opj_decompress.c
+ * https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/opj_compress.c
+ */
+
+// kdDebug category
+constexpr int kCategory = 399;
+
+struct KIMGJP2Wrapper
+{
+ public:
+ opj_codec_t *codec { nullptr };
+ opj_image_t *image { nullptr };
+ opj_stream_t *stream { nullptr };
+ KTempFile tempFile;
+
+ ~KIMGJP2Wrapper()
+ {
+ if (stream)
+ {
+ opj_stream_destroy(stream);
+ }
+ if (image)
+ {
+ opj_image_destroy(image);
+ }
+ if (codec)
+ {
+ opj_destroy_codec(codec);
+ }
+ }
+};
+
+static void kimgio_jp2_err_handler(const char *message, void *data)
+{
+ kdError(kCategory) << "Error decoding JP2 image: " << message;
+}
+
+static void kimgio_jp2_info_handler(const char *message, void *data)
+{
+ // Reports status, e.g.: Main header has been correctly decoded.
+ // kdDebug(kCategory) << "JP2 decoding message: " << message;
+}
+
+static void kimgio_jp2_warn_handler(const char *message, void *data)
+{
+ kdWarning(kCategory) << "Warning decoding JP2 image: " << message;
+}
+
+static void kimgio_jp2_read_srgba(TQImage &image, const KIMGJP2Wrapper &jp2)
+{
+ const int height = image.height();
+ const int width = image.width();
+
+ unsigned char alphaMask = 0x0;
+
+ for (int row = 0; row < height; row++)
+ {
+ for (int col = 0; col < width; col++)
+ {
+ const int offset = row * width + col;
+ OPJ_INT32 r, g, b, a = 0xFF;
+
+ // Convert to unsigned
+ r = jp2.image->comps[0].data[offset];
+ r += (jp2.image->comps[0].sgnd ? 1 << (jp2.image->comps[0].prec - 1) : 0);
+
+ g = jp2.image->comps[1].data[offset];
+ g += (jp2.image->comps[0].sgnd ? 1 << (jp2.image->comps[1].prec - 1) : 0);
+
+ b = jp2.image->comps[2].data[offset];
+ b += (jp2.image->comps[2].sgnd ? 1 << (jp2.image->comps[2].prec - 1) : 0);
+
+ if (jp2.image->numcomps > 3 && jp2.image->comps[3].alpha)
+ {
+ a = jp2.image->comps[3].data[offset];
+ a += (jp2.image->comps[3].sgnd ? 1 << (jp2.image->comps[3].prec - 1) : 0);
+ }
+
+ image.setPixel(col, row, tqRgba(r, g, b, a));
+ alphaMask |= (255 - a);
+ }
+ }
+
+ if (alphaMask != 0x0)
+ {
+ image.setAlphaBuffer(true);
+ }
+}
+
+static void kimgio_jp2_read_grayscale(TQImage &image, const KIMGJP2Wrapper &jp2)
+{
+ const int height = image.height();
+ const int width = image.width();
+
+ unsigned char alphaMask = 0x0;
+
+ for (int row = 0; row < height; row++)
+ {
+ for (int col = 0; col < width; col++)
+ {
+ const int offset = row * width + col;
+ OPJ_INT32 g, a = 0xFF;
+
+ // Convert to unsigned
+ g = jp2.image->comps[0].data[offset];
+ g += (jp2.image->comps[0].sgnd ? (1 << jp2.image->comps[0].prec - 1) : 0);
+
+ if (jp2.image->numcomps > 1 && jp2.image->comps[1].alpha)
+ {
+ a = jp2.image->comps[1].data[offset];
+ a = (jp2.image->comps[1].sgnd ? (1 << jp2.image->comps[1].prec - 1) : 0);
+ }
+
+ image.setPixel(col, row, tqRgba(g, g, g, a));
+ alphaMask |= (255 - a);
+ }
+ }
+
+ if (alphaMask != 0x0)
+ {
+ image.setAlphaBuffer(true);
+ }
+}
+
+static const char *colorspaceToString(COLOR_SPACE clr)
+{
+ switch (clr)
+ {
+ case OPJ_CLRSPC_SRGB:
+ {
+ return "sRGB";
+ }
+ case OPJ_CLRSPC_GRAY:
+ {
+ return "sRGB Grayscale";
+ }
+ case OPJ_CLRSPC_SYCC:
+ {
+ return "YUV";
+ }
+ case OPJ_CLRSPC_EYCC:
+ {
+ return "YCbCr";
+ }
+ case OPJ_CLRSPC_CMYK:
+ {
+ return "CMYK";
+ }
+ case OPJ_CLRSPC_UNSPECIFIED:
+ {
+ return "Unspecified";
+ }
+ default:
+ {
+ return "Unknown";
+ }
+ }
+}
-// dirty, but avoids a warning because jasper.h includes jas_config.h.
-#undef PACKAGE
-#undef VERSION
-#include <jasper/jasper.h>
+TDE_EXPORT void kimgio_jp2_read(TQImageIO* io)
+{
+ KIMGJP2Wrapper jp2;
+ opj_dparameters_t parameters;
+
+ if (auto tqfile = dynamic_cast<TQFile *>(io->ioDevice()))
+ {
+ jp2.stream = opj_stream_create_default_file_stream(tqfile->name().local8Bit().data(), OPJ_TRUE);
+ }
+ else
+ {
+ // 4096 (=4k) is a common page size.
+ constexpr int pageSize = 4096;
+
+ // Use a temporary file, since TQSocket::size() reports bytes
+ // available to read *now*, not the file size.
+ if (jp2.tempFile.status() != 0)
+ {
+ kdError(kCategory) << "Failed to create temporary file for non-TQFile IO" << endl;
+ return;
+ }
+ jp2.tempFile.setAutoDelete(true);
+
+ TQFile *tempFile = jp2.tempFile.file();
+ TQByteArray b(pageSize);
+ TQ_LONG bytesRead;
+
+ // 0 or -1 is EOF / error
+ while ((bytesRead = io->ioDevice()->readBlock(b.data(), pageSize)) > 0)
+ {
+ if ((tempFile->writeBlock(b.data(), bytesRead)) == -1)
+ {
+ break;
+ }
+ }
-// code taken in parts from JasPer's jiv.c
+ // flush everything out to disk
+ tempFile->flush();
+ jp2.stream = opj_stream_create_default_file_stream(tempFile->name().local8Bit().data(), OPJ_TRUE);
+ }
+
+ if (nullptr == jp2.stream)
+ {
+ kdError(kCategory) << "Failed to create input stream for JP2" << endl;
+ io->setStatus(IO_ResourceError);
+ return;
+ }
+
+ jp2.codec = opj_create_decompress(OPJ_CODEC_JP2);
+ if (nullptr == jp2.codec)
+ {
+ kdError(kCategory) << "Unable to create decompressor for JP2" << endl;
+ io->setStatus(IO_ResourceError);
+ return;
+ }
+ opj_set_error_handler(jp2.codec, kimgio_jp2_err_handler, nullptr);
+ opj_set_info_handler(jp2.codec, kimgio_jp2_info_handler, nullptr);
+ opj_set_warning_handler(jp2.codec, kimgio_jp2_warn_handler, nullptr);
+
+ opj_set_default_decoder_parameters(&parameters);
+ if (OPJ_FALSE == opj_setup_decoder(jp2.codec, &parameters))
+ {
+ kdError(kCategory) << "Failed to setup decoder for JP2" << endl;
+ io->setStatus(IO_ResourceError);
+ return;
+ }
+
+ if (OPJ_FALSE == opj_read_header(jp2.stream, jp2.codec, &jp2.image))
+ {
+ kdError(kCategory) << "Failed to read JP2 header" << endl;
+ io->setStatus(IO_ReadError);
+ return;
+ }
+
+ if (OPJ_FALSE == opj_decode(jp2.codec, jp2.stream, jp2.image))
+ {
+ kdError(kCategory) << "Failed to decode JP2 image" << endl;
+ io->setStatus(IO_ReadError);
+ return;
+ }
+
+ if (OPJ_FALSE == opj_end_decompress(jp2.codec, jp2.stream))
+ {
+ kdError(kCategory) << "Failed to decode JP2 image ending" << endl;
+ io->setStatus(IO_ReadError);
+ return;
+ }
+
+ OPJ_UINT32 width = jp2.image->x1 - jp2.image->x0;
+ OPJ_UINT32 height = jp2.image->y1 - jp2.image->y0;
+ TQImage image(width, height, 32);
+
+ switch (jp2.image->color_space)
+ {
+ case OPJ_CLRSPC_SRGB:
+ {
+ kimgio_jp2_read_srgba(image, jp2);
+ break;
+ }
+ case OPJ_CLRSPC_GRAY:
+ {
+ kimgio_jp2_read_grayscale(image, jp2);
+ break;
+ }
+ default:
+ {
+ kdError(kCategory) << "Unsupported colorspace detected: "
+ << colorspaceToString(jp2.image->color_space)
+ << endl;
+ io->setStatus(IO_ReadError);
+ return;
+ }
+ }
+
+ io->setImage(image);
+ io->setStatus(IO_Ok);
+}
+
+
+static void kimgio_jp2_write_handler(void *buffer, OPJ_SIZE_T buffer_size, void *user_data)
+{
+ // TODO(mio):
+}
+
+TDE_EXPORT void kimgio_jp2_write(TQImageIO *io)
+{
+ kdDebug(kCategory) << "Writing JP2 with OpenJPEG is not supported yet." << endl;
+}
+
+/*
#define DEFAULT_RATE 0.10
#define MAXCMPTS 256
@@ -192,7 +497,7 @@ create_image( const TQImage& qi )
cmptparms[i].sgnd = false;
}
- jas_image_t* ji = jas_image_create( 3 /* number components */, cmptparms, JAS_CLRSPC_UNKNOWN );
+ jas_image_t* ji = jas_image_create( 3 /* number components *//*, cmptparms, JAS_CLRSPC_UNKNOWN );
delete[] cmptparms;
// returning 0 is ok
@@ -324,5 +629,7 @@ kimgio_jp2_write( TQImageIO* io )
io->setStatus( IO_Ok );
} // kimgio_jp2_write
-#endif // HAVE_JASPER
+*/
+
+#endif // HAVE_OPENJPEG