/** \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.3 2010/04/08 13:19:00 scuri Exp $ */ #include "im_format.h" #include "im_format_jp2.h" #include "im_util.h" #include "im_counter.h" #include #include #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 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 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 imFileFormatJP2: public imFileFormatBase { int fmtid; jas_stream_t *stream; jas_image_t *image; public: imFileFormatJP2(const imFormat* _iformat): imFileFormatBase(_iformat), image(0) {} ~imFileFormatJP2() {} 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); }; 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) { } ~imFormatJP2() {} imFileFormatBase* Create(void) const { return new imFileFormatJP2(this); } 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 imFileFormatJP2::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 imFileFormatJP2::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 imFileFormatJP2::Close() { if (this->image) jas_image_destroy(this->image); jas_stream_close(this->stream); } void* imFileFormatJP2::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 imFileFormatJP2::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 imFileFormatJP2::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); if (data) { 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 imFileFormatJP2::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 imFileFormatJP2::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; }