diff options
Diffstat (limited to 'src/im_format_jp2.cpp')
-rw-r--r-- | src/im_format_jp2.cpp | 493 |
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; +} |