summaryrefslogtreecommitdiff
path: root/src/im_format_jp2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/im_format_jp2.cpp')
-rw-r--r--src/im_format_jp2.cpp493
1 files changed, 493 insertions, 0 deletions
diff --git a/src/im_format_jp2.cpp b/src/im_format_jp2.cpp
new file mode 100644
index 0000000..16e2c60
--- /dev/null
+++ b/src/im_format_jp2.cpp
@@ -0,0 +1,493 @@
+/** \file
+ * \brief JP2 File Format
+ *
+ * See Copyright Notice in im_lib.h
+ * See libJaper Copyright Notice in jasper.h
+ * $Id: im_format_jp2.cpp,v 1.1 2008/10/17 06:10:16 scuri Exp $
+ */
+
+#include "im_format.h"
+#include "im_format_jp2.h"
+#include "im_util.h"
+#include "im_counter.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "jasper/jasper.h"
+#include "jpc/jpc_enc.h"
+#include "jp2/jp2_cod.h"
+
+extern "C"
+{
+ /* implemented in jas_binfile.c */
+ jas_stream_t *jas_binfile_open(const char *file_name, int is_new);
+}
+
+jas_seqent_t iJP2Bits2Int(jas_seqent_t v, int prec, int sgnd)
+{
+ v &= JAS_ONES(prec);
+ return (sgnd && (v & (1 << (prec - 1)))) ? (v - (1 << prec)) : v;
+}
+
+/* this is based on jas_image_readcmpt */
+template <class T>
+int iJP2ReadLine(jas_image_t *image, int row, int cmpno, T *data)
+{
+ jas_image_cmpt_t *cmpt = image->cmpts_[cmpno];
+
+ if (jas_stream_seek(cmpt->stream_, (cmpt->width_ * row) * cmpt->cps_, SEEK_SET) < 0)
+ return 0;
+
+ // this offset will convert from signed to unsigned
+ int offset = 0;
+ if (cmpt->sgnd_ && cmpt->prec_ > 1)
+ offset = 1 << (cmpt->prec_-1);
+
+ for (int j = 0; j < cmpt->width_; j++)
+ {
+ jas_seqent_t v = 0;
+
+ for (int k = 0; k < cmpt->cps_; k++)
+ {
+ int c = jas_stream_getc(cmpt->stream_);
+ if (c == EOF)
+ return 0;
+
+ v = (v << 8) | (c & 0xff);
+ }
+
+ v = iJP2Bits2Int(v, cmpt->prec_, cmpt->sgnd_);
+
+ *data++ = (T)(v + offset);
+ }
+
+ return 1;
+}
+
+uint_fast32_t iJP2Int2Bits(jas_seqent_t v, int prec, int sgnd)
+{
+ uint_fast32_t ret;
+ ret = ((sgnd && v < 0) ? ((1 << prec) + v) : v) & JAS_ONES(prec);
+ return ret;
+}
+
+/* this is based on jas_image_writecmpt */
+template <class T>
+int iJP2WriteLine(jas_image_t *image, int row, int cmpno, T *data)
+{
+ jas_image_cmpt_t *cmpt = image->cmpts_[cmpno];
+
+ if (jas_stream_seek(cmpt->stream_, (cmpt->width_ * row) * cmpt->cps_, SEEK_SET) < 0)
+ return 0;
+
+ for (int j = 0; j < cmpt->width_; j++)
+ {
+ jas_seqent_t v = iJP2Int2Bits(*data++, cmpt->prec_, cmpt->sgnd_);
+
+ for (int k = 0; k < cmpt->cps_; k++)
+ {
+ int c = (v >> (8 * (cmpt->cps_ - 1))) & 0xff;
+ if (jas_stream_putc(cmpt->stream_, (imbyte)c) == EOF)
+ return 0;
+
+ v <<= 8;
+ }
+ }
+
+ return 1;
+}
+
+static const char* iJP2CompTable[1] =
+{
+ "JPEG-2000",
+};
+
+class imFormatJP2: public imFormat
+{
+ int fmtid;
+ jas_stream_t *stream;
+ jas_image_t *image;
+
+public:
+ imFormatJP2()
+ :imFormat("JP2",
+ "JPEG-2000 JP2 File Format",
+ "*.jp2;*.jpc;*.j2c;*.j2k;",
+ iJP2CompTable,
+ 1,
+ 0)
+ {
+ image = 0;
+ }
+ ~imFormatJP2() {}
+
+ int Open(const char* file_name);
+ int New(const char* file_name);
+ void Close();
+ void* Handle(int index);
+ int ReadImageInfo(int index);
+ int ReadImageData(void* data);
+ int WriteImageInfo();
+ int WriteImageData(void* data);
+ int CanWrite(const char* compression, int color_mode, int data_type) const;
+};
+
+static char* ijp2_message = NULL;
+static int ijp2_abort = 0;
+static int ijp2_counter = -1;
+
+static void iJP2ProgressProc(int done, int total, char *descr)
+{
+ (void)descr;
+ if (done == 0)
+ {
+ imCounterTotal(ijp2_counter, total, ijp2_message);
+ ijp2_message = NULL;
+ }
+
+ if (!imCounterIncTo(ijp2_counter, done))
+ ijp2_abort = 1;
+}
+
+static int iJP2AbortProc(void)
+{
+ return ijp2_abort;
+}
+
+void imFormatRegisterJP2(void)
+{
+ // Jasper library initialization
+ jas_init();
+
+ jas_set_progress_proc((jas_progress_proc_t)iJP2ProgressProc);
+ jas_set_test_abort_proc((jas_test_abort_proc_t)iJP2AbortProc);
+
+ imFormatRegister(new imFormatJP2());
+}
+
+int imFormatJP2::Open(const char* file_name)
+{
+ this->stream = jas_binfile_open(file_name, 0);
+ if (this->stream == NULL)
+ return IM_ERR_OPEN;
+
+ this->fmtid = jas_image_getfmt(this->stream);
+ if (this->fmtid < 0)
+ {
+ jas_stream_close(this->stream);
+ return IM_ERR_FORMAT;
+ }
+
+ strcpy(this->compression, "JPEG-2000");
+ this->image_count = 1;
+
+ return IM_ERR_NONE;
+}
+
+int imFormatJP2::New(const char* file_name)
+{
+ this->stream = jas_binfile_open(file_name, 1);
+ if (this->stream == NULL)
+ return IM_ERR_OPEN;
+
+ strcpy(this->compression, "JPEG-2000");
+ this->image_count = 1;
+
+ return IM_ERR_NONE;
+}
+
+void imFormatJP2::Close()
+{
+ if (this->image)
+ jas_image_destroy(this->image);
+
+ jas_stream_close(this->stream);
+}
+
+void* imFormatJP2::Handle(int index)
+{
+ if (index == 0)
+ return (void*)this->stream->obj_;
+ else if (index == 1)
+ return (void*)this->image;
+ else if (index == 2)
+ return (void*)this->stream;
+ else
+ return NULL;
+}
+
+int imFormatJP2::ReadImageInfo(int index)
+{
+ (void)index;
+
+ // The counter is started because in Jasper all image reading is done here. BAD!
+ ijp2_counter = this->counter;
+ ijp2_abort = 0;
+ ijp2_message = "Reading JP2...";
+ this->image = jas_image_decode(this->stream, this->fmtid, 0);
+ ijp2_counter = -1;
+ if (!this->image)
+ return IM_ERR_ACCESS;
+
+ this->width = jas_image_width(this->image);
+ this->height = jas_image_height(this->image);
+
+ int clrspc_fam = jas_clrspc_fam(jas_image_clrspc(image));
+ switch(clrspc_fam)
+ {
+ case JAS_CLRSPC_FAM_GRAY:
+ this->file_color_mode = IM_GRAY;
+ break;
+ case JAS_CLRSPC_FAM_XYZ:
+ this->file_color_mode = IM_XYZ;
+ break;
+ case JAS_CLRSPC_FAM_RGB:
+ this->file_color_mode = IM_RGB;
+ break;
+ case JAS_CLRSPC_FAM_YCBCR:
+ this->file_color_mode = IM_YCBCR;
+ break;
+ case JAS_CLRSPC_FAM_LAB:
+ this->file_color_mode = IM_LAB;
+ break;
+ default:
+ return IM_ERR_DATA;
+ }
+
+ this->file_data_type = IM_BYTE;
+ int prec = jas_image_cmptprec(image, 0);
+ if (prec > 8)
+ this->file_data_type = IM_USHORT;
+
+ if (prec < 8)
+ this->convert_bpp = -prec; // just expand to 0-255
+
+ if (prec == 1 && this->file_color_mode == IM_GRAY)
+ this->file_color_mode = IM_BINARY;
+
+ int cmpno = jas_image_getcmptbytype(this->image, JAS_IMAGE_CT_OPACITY);
+ if (cmpno != -1)
+ this->file_color_mode |= IM_ALPHA;
+
+ this->file_color_mode |= IM_TOPDOWN;
+
+ if (image->metadata.count > 0)
+ {
+ imAttribTable* attrib_table = AttribTable();
+
+ // First write GeoTIFF data
+ jas_metadata_box_t *metabox = &image->metadata.boxes[JAS_IMAGE_BOX_GEO];
+ if (metabox->size>0 && metabox->buf)
+ attrib_table->Set("GeoTIFFBox", IM_BYTE, metabox->size, metabox->buf);
+
+ // Check if XMP is there
+ metabox = &image->metadata.boxes[JAS_IMAGE_BOX_XMP];
+ if (metabox->size>0 && metabox->buf)
+ attrib_table->Set("XMLPacket", IM_BYTE, metabox->size, metabox->buf);
+ }
+
+ return IM_ERR_NONE;
+}
+
+int imFormatJP2::WriteImageInfo()
+{
+ this->file_data_type = this->user_data_type;
+ this->file_color_mode = imColorModeSpace(this->user_color_mode);
+ this->file_color_mode |= IM_TOPDOWN;
+
+ int prec = 8;
+ if (this->file_data_type == IM_USHORT)
+ prec = 16;
+
+ jas_clrspc_t clrspc;
+ switch (imColorModeSpace(this->user_color_mode))
+ {
+ case IM_BINARY:
+ prec = 1;
+ case IM_GRAY:
+ clrspc = JAS_CLRSPC_SGRAY;
+ break;
+ case IM_RGB:
+ clrspc = JAS_CLRSPC_SRGB;
+ break;
+ case IM_XYZ:
+ clrspc = JAS_CLRSPC_CIEXYZ;
+ break;
+ case IM_LAB:
+ clrspc = JAS_CLRSPC_CIELAB;
+ break;
+ case IM_YCBCR:
+ clrspc = JAS_CLRSPC_SYCBCR;
+ break;
+ default:
+ return IM_ERR_DATA;
+ }
+
+ if (imColorModeHasAlpha(this->user_color_mode))
+ this->file_color_mode |= IM_ALPHA;
+
+ int numcmpts = imColorModeDepth(this->file_color_mode);
+
+ jas_image_cmptparm_t cmptparms[4];
+ for (int i = 0; i < numcmpts; i++)
+ {
+ jas_image_cmptparm_t* cmptparm = &cmptparms[i];
+
+ cmptparm->tlx = 0;
+ cmptparm->tly = 0;
+ cmptparm->hstep = 1;
+ cmptparm->vstep = 1;
+ cmptparm->width = this->width;
+ cmptparm->height = this->height;
+ cmptparm->prec = prec;
+ cmptparm->sgnd = 0;
+ }
+
+ this->image = jas_image_create(numcmpts, cmptparms, clrspc);
+ if (!this->image)
+ return IM_ERR_DATA;
+
+ if (this->image->metadata.count > 0)
+ {
+ const void* data;
+ int size;
+ imAttribTable* attrib_table = AttribTable();
+
+ // GeoTIFF first
+ data = attrib_table->Get("GeoTIFFBox", NULL, &size);
+ if (data)
+ {
+ jas_metadata_box_t *metabox = &image->metadata.boxes[JAS_IMAGE_BOX_GEO];
+ jas_box_alloc(metabox, size);
+ memcpy(metabox->buf, data, size);
+ memcpy(metabox->id, msi_uuid, sizeof(msi_uuid));
+ }
+
+ // Adobe XMP
+ data = attrib_table->Get("XMLPacket", NULL, &size);
+ {
+ jas_metadata_box_t *metabox = &image->metadata.boxes[JAS_IMAGE_BOX_XMP];
+ jas_box_alloc(metabox, size);
+ memcpy(metabox->buf, data, size);
+ memcpy(metabox->id, xmp_uuid, sizeof(xmp_uuid));
+ }
+ }
+
+ return IM_ERR_NONE;
+}
+
+int imFormatJP2::ReadImageData(void* data)
+{
+ int count = imFileLineBufferCount(this);
+
+ imCounterTotal(this->counter, count, NULL);
+
+ int alpha_plane = -1;
+ if (imColorModeHasAlpha(this->user_color_mode) && imColorModeHasAlpha(this->file_color_mode))
+ alpha_plane = imColorModeDepth(this->file_color_mode) - 1;
+
+ int row = 0, plane = 0;
+ for (int i = 0; i < count; i++)
+ {
+ int cmpno;
+ if (plane == alpha_plane)
+ cmpno = jas_image_getcmptbytype(image, JAS_IMAGE_CT_OPACITY);
+ else
+ cmpno = jas_image_getcmptbytype(image, JAS_IMAGE_CT_COLOR(plane));
+
+ if (cmpno == -1)
+ return IM_ERR_DATA;
+
+ int ret = 1;
+ if (this->file_data_type == IM_BYTE)
+ ret = iJP2ReadLine(image, row, cmpno, (imbyte*)this->line_buffer);
+ else
+ ret = iJP2ReadLine(image, row, cmpno, (imushort*)this->line_buffer);
+
+ if (!ret)
+ return IM_ERR_ACCESS;
+
+ imFileLineBufferRead(this, data, row, plane);
+
+ if (!imCounterInc(this->counter))
+ return IM_ERR_COUNTER;
+
+ imFileLineBufferInc(this, &row, &plane);
+ }
+
+ return IM_ERR_NONE;
+}
+
+int imFormatJP2::WriteImageData(void* data)
+{
+ int count = imFileLineBufferCount(this);
+ imCounterTotal(this->counter, count, "Writing JP2..."); /* first time count */
+
+ int depth = imColorModeDepth(this->file_color_mode);
+ if (imColorModeHasAlpha(this->user_color_mode) && imColorModeHasAlpha(this->file_color_mode))
+ {
+ jas_image_setcmpttype(image, depth-1, JAS_IMAGE_CT_OPACITY);
+ depth--;
+ }
+
+ for (int d = 0; d < depth; d++)
+ jas_image_setcmpttype(image, d, JAS_IMAGE_CT_COLOR(d));
+
+ int row = 0, plane = 0;
+ for (int i = 0; i < count; i++)
+ {
+ imFileLineBufferWrite(this, data, row, plane);
+
+ int ret = 1;
+ if (this->file_data_type == IM_BYTE)
+ ret = iJP2WriteLine(image, row, plane, (imbyte*)this->line_buffer);
+ else
+ ret = iJP2WriteLine(image, row, plane, (imushort*)this->line_buffer);
+
+ if (!imCounterInc(this->counter))
+ return IM_ERR_COUNTER;
+
+ imFileLineBufferInc(this, &row, &plane);
+ }
+
+ char outopts[512] = "";
+ imAttribTable* attrib_table = AttribTable();
+
+ float* ratio = (float*)attrib_table->Get("CompressionRatio");
+ if (ratio)
+ sprintf(outopts, "rate=%g", (double)(1.0 / *ratio));
+
+ // The counter continuous because in Jasper all image writing is done here. BAD!
+ ijp2_counter = this->counter;
+ ijp2_abort = 0;
+ ijp2_message = NULL; /* other counts */
+ int err = jas_image_encode(image, stream, 0 /*JP2 format always */, outopts);
+ ijp2_counter = -1;
+ if (err)
+ return IM_ERR_ACCESS;
+
+ jas_stream_flush(stream);
+
+ return IM_ERR_NONE;
+}
+
+int imFormatJP2::CanWrite(const char* compression, int color_mode, int data_type) const
+{
+ int color_space = imColorModeSpace(color_mode);
+
+ if (color_space == IM_MAP || color_space == IM_CMYK ||
+ color_space == IM_LUV)
+ return IM_ERR_DATA;
+
+ if (data_type != IM_BYTE && data_type != IM_USHORT)
+ return IM_ERR_DATA;
+
+ if (!compression || compression[0] == 0)
+ return IM_ERR_NONE;
+
+ if (!imStrEqual(compression, "JPEG-2000"))
+ return IM_ERR_COMPRESS;
+
+ return IM_ERR_NONE;
+}