/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % JJJ PPPP 222 % % J P P 2 2 % % J PPPP 22 % % J J P 2 % % JJ P 22222 % % % % % % Read/Write JPEG-2000 Image Format % % % % Cristy % % Nathan Brown % % June 2001 % % % % % % Copyright 1999-2014 ImageMagick Studio LLC, a non-profit organization % % dedicated to making software imaging solutions freely available. % % % % You may not use this file except in compliance with the License. You may % % obtain a copy of the License at % % % % http://www.imagemagick.org/script/license.php % % % % Unless required by applicable law or agreed to in writing, software % % distributed under the License is distributed on an "AS IS" BASIS, % % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % % See the License for the specific language governing permissions and % % limitations under the License. % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % */ /* Include declarations. */ #include "magick/studio.h" #include "magick/artifact.h" #include "magick/attribute.h" #include "magick/blob.h" #include "magick/blob-private.h" #include "magick/cache.h" #include "magick/colorspace.h" #include "magick/colorspace-private.h" #include "magick/color.h" #include "magick/color-private.h" #include "magick/exception.h" #include "magick/exception-private.h" #include "magick/image.h" #include "magick/image-private.h" #include "magick/list.h" #include "magick/magick.h" #include "magick/memory_.h" #include "magick/monitor.h" #include "magick/monitor-private.h" #include "magick/option.h" #include "magick/pixel-accessor.h" #include "magick/profile.h" #include "magick/quantum-private.h" #include "magick/static.h" #include "magick/statistic.h" #include "magick/string_.h" #include "magick/module.h" #if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) #include #endif /* Forward declarations. */ #if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) static MagickBooleanType WriteJP2Image(const ImageInfo *,Image *); #endif /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s J 2 K % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsJ2K() returns MagickTrue if the image format type, identified by the % magick string, is J2K. % % The format of the IsJ2K method is: % % MagickBooleanType IsJP2(const unsigned char *magick,const size_t length) % % A description of each parameter follows: % % o magick: compare image format pattern against these bytes. % % o length: Specifies the length of the magick string. % */ static MagickBooleanType IsJ2K(const unsigned char *magick,const size_t length) { if (length < 4) return(MagickFalse); if (memcmp(magick,"\xff\x4f\xff\x51",4) == 0) return(MagickTrue); return(MagickFalse); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s J P 2 % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsJP2() returns MagickTrue if the image format type, identified by the % magick string, is JP2. % % The format of the IsJP2 method is: % % MagickBooleanType IsJP2(const unsigned char *magick,const size_t length) % % A description of each parameter follows: % % o magick: compare image format pattern against these bytes. % % o length: Specifies the length of the magick string. % */ static MagickBooleanType IsJP2(const unsigned char *magick,const size_t length) { if (length < 12) return(MagickFalse); if (memcmp(magick,"\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a",12) == 0) return(MagickTrue); if (memcmp(magick,"\x0d\x0a\x87\x0a",12) == 0) return(MagickTrue); return(MagickFalse); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s J P C % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsJPC()() returns MagickTrue if the image format type, identified by the % magick string, is JPC. % % The format of the IsJPC method is: % % MagickBooleanType IsJPC(const unsigned char *magick,const size_t length) % % A description of each parameter follows: % % o magick: compare image format pattern against these bytes. % % o length: Specifies the length of the magick string. % */ static MagickBooleanType IsJPC(const unsigned char *magick,const size_t length) { if (length < 12) return(MagickFalse); if (memcmp(magick,"\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a",12) == 0) return(MagickTrue); if (memcmp(magick,"\x0d\x0a\x87\x0a",12) == 0) return(MagickTrue); return(MagickFalse); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e a d J P 2 I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ReadJP2Image() reads a JPEG 2000 Image file (JP2) or JPEG 2000 % codestream (JPC) image file and returns it. It allocates the memory % necessary for the new Image structure and returns a pointer to the new % image or set of images. % % JP2 support is originally written by Nathan Brown, nathanbrown@letu.edu. % % The format of the ReadJP2Image method is: % % Image *ReadJP2Image(const ImageInfo *image_info, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image_info: the image info. % % o exception: return any errors or warnings in this structure. % */ #if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) static void JP2ErrorHandler(const char *message,void *client_data) { ExceptionInfo *exception; exception=(ExceptionInfo *) client_data; (void) ThrowMagickException(exception,GetMagickModule(),CoderError, message,"`%s'","OpenJP2"); } static OPJ_SIZE_T JP2ReadHandler(void *buffer,OPJ_SIZE_T length,void *context) { Image *image; ssize_t count; image=(Image *) context; count=ReadBlob(image,(ssize_t) length,(unsigned char *) buffer); if (count == 0) return(-1); return((OPJ_SIZE_T) count); } static OPJ_BOOL JP2SeekHandler(OPJ_OFF_T offset,void *context) { Image *image; image=(Image *) context; return(SeekBlob(image,offset,SEEK_SET) == 0 ? 1 : 0); } static OPJ_OFF_T JP2SkipHandler(OPJ_OFF_T length,void *context) { Image *image; image=(Image *) context; if (DiscardBlobBytes(image,(size_t) length) == MagickFalse) return(0); return(length); } static void JP2WarningHandler(const char *message,void *client_data) { ExceptionInfo *exception; exception=(ExceptionInfo *) client_data; (void) ThrowMagickException(exception,GetMagickModule(),CoderWarning, message,"`%s'","OpenJP2"); } static OPJ_SIZE_T JP2WriteHandler(void *buffer,OPJ_SIZE_T length,void *context) { Image *image; ssize_t count; image=(Image *) context; count=WriteBlob(image,(ssize_t) length,(unsigned char *) buffer); return((OPJ_SIZE_T) count); } static Image *ReadJP2Image(const ImageInfo *image_info,ExceptionInfo *exception) { Image *image; int factor, jp2_status; MagickBooleanType status; opj_codec_t *jp2_codec; opj_codestream_index_t *codestream_index = (opj_codestream_index_t *) NULL; opj_dparameters_t parameters; opj_image_t *jp2_image; opj_stream_t *jp2_stream; register ssize_t i; ssize_t y; /* Open image file. */ assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickSignature); if (image_info->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s", image_info->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); image=AcquireImage(image_info); status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); if (status == MagickFalse) { image=DestroyImageList(image); return((Image *) NULL); } /* Initialize JP2 codec. */ if (LocaleCompare(image_info->magick,"JPT") == 0) jp2_codec=opj_create_decompress(OPJ_CODEC_JPT); else if (LocaleCompare(image_info->magick,"J2K") == 0) jp2_codec=opj_create_decompress(OPJ_CODEC_J2K); else jp2_codec=opj_create_decompress(OPJ_CODEC_JP2); opj_set_warning_handler(jp2_codec,JP2WarningHandler,exception); opj_set_error_handler(jp2_codec,JP2ErrorHandler,exception); opj_set_default_decoder_parameters(¶meters); option=GetImageOption(image_info,"jp2:reduce-factor"); if (option != (const char *) NULL) parameters.cp_reduce=StringToInteger(option); option=GetImageOption(image_info,"jp2:quality-layers"); if (option != (const char *) NULL) parameters.cp_layer=StringToInteger(option); if (opj_setup_decoder(jp2_codec,¶meters) == 0) { opj_destroy_codec(jp2_codec); ThrowReaderException(DelegateError,"UnableToManageJP2Stream"); } jp2_stream=opj_stream_create(1024,1); opj_stream_set_read_function(jp2_stream,JP2ReadHandler); opj_stream_set_write_function(jp2_stream,JP2WriteHandler); opj_stream_set_seek_function(jp2_stream,JP2SeekHandler); opj_stream_set_skip_function(jp2_stream,JP2SkipHandler); opj_stream_set_user_data(jp2_stream,image); opj_stream_set_user_data_length(jp2_stream,GetBlobSize(image)); if (opj_read_header(jp2_stream,jp2_codec,&jp2_image) == 0) { opj_stream_set_user_data(jp2_stream,NULL); opj_stream_destroy_v3(jp2_stream); opj_destroy_codec(jp2_codec); ThrowReaderException(DelegateError,"UnableToDecodeImageFile"); } factor=0; if (opj_set_decoded_resolution_factor(jp2_codec,factor) == 0) { opj_stream_set_user_data(jp2_stream,NULL); opj_stream_destroy_v3(jp2_stream); opj_destroy_codec(jp2_codec); opj_image_destroy(jp2_image); ThrowReaderException(DelegateError,"UnableToDecodeImageFile"); } jp2_status=opj_set_decode_area(jp2_codec,jp2_image,parameters.DA_x0, parameters.DA_y0,parameters.DA_x1,parameters.DA_y1); if (jp2_status == 0) { opj_stream_set_user_data(jp2_stream,NULL); opj_stream_destroy_v3(jp2_stream); opj_destroy_codec(jp2_codec); opj_image_destroy(jp2_image); ThrowReaderException(DelegateError,"UnableToDecodeImageFile"); } if ((opj_decode(jp2_codec,jp2_stream,jp2_image) == 0) || (opj_end_decompress(jp2_codec,jp2_stream) == 0)) { opj_stream_set_user_data(jp2_stream,NULL); opj_stream_destroy_v3(jp2_stream); opj_destroy_codec(jp2_codec); opj_image_destroy(jp2_image); ThrowReaderException(DelegateError,"UnableToDecodeImageFile"); } opj_stream_set_user_data(jp2_stream,NULL); opj_stream_destroy_v3(jp2_stream); for (i=0; i < (ssize_t) jp2_image->numcomps; i++) { if ((jp2_image->comps[i].dx == 0) || (jp2_image->comps[i].dy == 0)) { opj_stream_set_user_data(jp2_stream,NULL); opj_destroy_codec(jp2_codec); opj_image_destroy(jp2_image); ThrowReaderException(CoderError,"IrregularChannelGeometryNotSupported") } } /* Convert JP2 image. */ image->columns=(size_t) jp2_image->comps[0].w; image->rows=(size_t) jp2_image->comps[0].h; image->compression=JPEG2000Compression; if (jp2_image->numcomps <= 2) { SetImageColorspace(image,GRAYColorspace); if (jp2_image->numcomps > 1) image->matte=MagickTrue; } if (jp2_image->numcomps > 3) image->matte=MagickTrue; for (i=0; i < (ssize_t) jp2_image->numcomps; i++) { if ((jp2_image->comps[i].dx == 0) || (jp2_image->comps[i].dy == 0)) { opj_stream_set_user_data(jp2_stream,NULL); opj_destroy_codec(jp2_codec); opj_image_destroy(jp2_image); ThrowReaderException(CoderError,"IrregularChannelGeometryNotSupported") } if ((jp2_image->comps[i].dx > 1) || (jp2_image->comps[i].dy > 1)) image->colorspace=YUVColorspace; } if (jp2_image->icc_profile_buf != (unsigned char *) NULL) { StringInfo *profile; profile=BlobToStringInfo(jp2_image->icc_profile_buf, jp2_image->icc_profile_len); if (profile != (StringInfo *) NULL) SetImageProfile(image,"icc",profile); } for (y=0; y < (ssize_t) image->rows; y++) { register PixelPacket *restrict q; register ssize_t x; q=GetAuthenticPixels(image,0,y,image->columns,1,exception); if (q == (PixelPacket *) NULL) break; for (x=0; x < (ssize_t) image->columns; x++) { register ssize_t i; for (i=0; i < (ssize_t) jp2_image->numcomps; i++) { double pixel, scale; scale=QuantumRange/(double) ((1UL << jp2_image->comps[i].prec)-1); pixel=scale*(jp2_image->comps[i].data[y/jp2_image->comps[i].dy* image->columns/jp2_image->comps[i].dx+x/jp2_image->comps[i].dx]+ (jp2_image->comps[i].sgnd ? 1UL << (jp2_image->comps[i].prec-1) : 0)); switch (i) { case 0: { SetPixelRed(image,ClampToQuantum(pixel),q); SetPixelGreen(image,ClampToQuantum(pixel),q); SetPixelBlue(image,ClampToQuantum(pixel),q); SetPixelAlpha(image,OpaqueAlpha,q); break; } case 1: { if (jp2_image->numcomps == 2) { SetPixelAlpha(image,ClampToQuantum(pixel),q); break; } SetPixelGreen(image,ClampToQuantum(pixel),q); break; } case 2: { SetPixelBlue(image,ClampToQuantum(pixel),q); break; } case 3: { SetPixelAlpha(image,ClampToQuantum(pixel),q); break; } } } q+=GetPixelChannels(image); } if (SyncAuthenticPixels(image,exception) == MagickFalse) break; status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y, image->rows); if (status == MagickFalse) break; } /* Free resources. */ opj_destroy_codec(jp2_codec); opj_image_destroy(jp2_image); opj_destroy_cstr_index(&codestream_index); return(GetFirstImageInList(image)); } #endif /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e g i s t e r J P 2 I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % RegisterJP2Image() adds attributes for the JP2 image format to the list of % supported formats. The attributes include the image format tag, a method % method to read and/or write the format, whether the format supports the % saving of more than one frame to the same file or blob, whether the format % supports native in-memory I/O, and a brief description of the format. % % The format of the RegisterJP2Image method is: % % size_t RegisterJP2Image(void) % */ ModuleExport size_t RegisterJP2Image(void) { MagickInfo *entry; entry=SetMagickInfo("JP2"); entry->description=ConstantString("JPEG-2000 File Format Syntax"); entry->mime_type=ConstantString("image/jp2"); entry->module=ConstantString("JP2"); entry->magick=(IsImageFormatHandler *) IsJP2; entry->adjoin=MagickFalse; entry->seekable_stream=MagickTrue; entry->thread_support=NoThreadSupport; #if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) entry->decoder=(DecodeImageHandler *) ReadJP2Image; entry->encoder=(EncodeImageHandler *) WriteJP2Image; #endif (void) RegisterMagickInfo(entry); entry=SetMagickInfo("J2K"); entry->description=ConstantString("JPEG-2000 Code Stream Syntax"); entry->mime_type=ConstantString("image/jp2"); entry->module=ConstantString("JP2"); entry->magick=(IsImageFormatHandler *) IsJ2K; entry->adjoin=MagickFalse; entry->seekable_stream=MagickTrue; entry->thread_support=NoThreadSupport; #if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) entry->decoder=(DecodeImageHandler *) ReadJP2Image; entry->encoder=(EncodeImageHandler *) WriteJP2Image; #endif (void) RegisterMagickInfo(entry); entry=SetMagickInfo("JPT"); entry->description=ConstantString("JPEG-2000 File Format Syntax"); entry->mime_type=ConstantString("image/jp2"); entry->module=ConstantString("JP2"); entry->magick=(IsImageFormatHandler *) IsJP2; entry->adjoin=MagickFalse; entry->seekable_stream=MagickTrue; entry->thread_support=NoThreadSupport; #if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) entry->decoder=(DecodeImageHandler *) ReadJP2Image; entry->encoder=(EncodeImageHandler *) WriteJP2Image; #endif (void) RegisterMagickInfo(entry); entry=SetMagickInfo("JPC"); entry->description=ConstantString("JPEG-2000 Code Stream Syntax"); entry->mime_type=ConstantString("image/jp2"); entry->module=ConstantString("JP2"); entry->magick=(IsImageFormatHandler *) IsJPC; entry->adjoin=MagickFalse; entry->seekable_stream=MagickTrue; entry->thread_support=NoThreadSupport; #if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) entry->decoder=(DecodeImageHandler *) ReadJP2Image; entry->encoder=(EncodeImageHandler *) WriteJP2Image; #endif (void) RegisterMagickInfo(entry); return(MagickImageCoderSignature); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % U n r e g i s t e r J P 2 I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % UnregisterJP2Image() removes format registrations made by the JP2 module % from the list of supported formats. % % The format of the UnregisterJP2Image method is: % % UnregisterJP2Image(void) % */ ModuleExport void UnregisterJP2Image(void) { (void) UnregisterMagickInfo("JPC"); (void) UnregisterMagickInfo("JPT"); (void) UnregisterMagickInfo("JP2"); (void) UnregisterMagickInfo("J2K"); } #if defined(MAGICKCORE_LIBOPENJP2_DELEGATE) /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % W r i t e J P 2 I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % WriteJP2Image() writes an image in the JPEG 2000 image format. % % JP2 support originally written by Nathan Brown, nathanbrown@letu.edu % % The format of the WriteJP2Image method is: % % MagickBooleanType WriteJP2Image(const ImageInfo *image_info,Image *image) % % A description of each parameter follows. % % o image_info: the image info. % % o image: The image. % */ static MagickBooleanType WriteJP2Image(const ImageInfo *image_info,Image *image) { MagickBooleanType status; /* Open image file. */ assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickSignature); assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception); if (status == MagickFalse) return(status); /* Initialize JPEG 2000 API. */ if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse) (void) TransformImageColorspace(image,sRGBColorspace); if (status != MagickFalse) ThrowWriterException(DelegateError,"UnableToEncodeImageFile"); return(MagickTrue); } #endif