| // ========================================================== |
| // JPEG XR Loader & Writer |
| // |
| // Design and implementation by |
| // - Herve Drolon (drolon@infonie.fr) |
| // |
| // This file is part of FreeImage 3 |
| // |
| // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY |
| // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES |
| // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE |
| // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED |
| // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT |
| // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY |
| // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL |
| // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER |
| // THIS DISCLAIMER. |
| // |
| // Use at your own risk! |
| // ========================================================== |
| |
| #include "FreeImage.h" |
| #include "Utilities.h" |
| #include "../Metadata/FreeImageTag.h" |
| |
| #include "../LibJXR/jxrgluelib/JXRGlue.h" |
| |
| // ========================================================== |
| // Plugin Interface |
| // ========================================================== |
| |
| static int s_format_id; |
| |
| // ========================================================== |
| // FreeImageIO interface (I/O streaming functions) |
| // ========================================================== |
| |
| /** |
| JXR wrapper for FreeImage I/O handle |
| */ |
| typedef struct tagFreeImageJXRIO { |
| FreeImageIO *io; |
| fi_handle handle; |
| } FreeImageJXRIO; |
| |
| static ERR |
| _jxr_io_Read(WMPStream* pWS, void* pv, size_t cb) { |
| FreeImageJXRIO *fio = (FreeImageJXRIO*)pWS->state.pvObj; |
| return (fio->io->read_proc(pv, (unsigned)cb, 1, fio->handle) == 1) ? WMP_errSuccess : WMP_errFileIO; |
| } |
| |
| static ERR |
| _jxr_io_Write(WMPStream* pWS, const void* pv, size_t cb) { |
| FreeImageJXRIO *fio = (FreeImageJXRIO*)pWS->state.pvObj; |
| if(0 != cb) { |
| return (fio->io->write_proc((void*)pv, (unsigned)cb, 1, fio->handle) == 1) ? WMP_errSuccess : WMP_errFileIO; |
| } |
| return WMP_errFileIO; |
| } |
| |
| static ERR |
| _jxr_io_SetPos(WMPStream* pWS, size_t offPos) { |
| FreeImageJXRIO *fio = (FreeImageJXRIO*)pWS->state.pvObj; |
| return (fio->io->seek_proc(fio->handle, (long)offPos, SEEK_SET) == 0) ? WMP_errSuccess : WMP_errFileIO; |
| } |
| |
| static ERR |
| _jxr_io_GetPos(WMPStream* pWS, size_t* poffPos) { |
| FreeImageJXRIO *fio = (FreeImageJXRIO*)pWS->state.pvObj; |
| long lOff = fio->io->tell_proc(fio->handle); |
| if(lOff == -1) { |
| return WMP_errFileIO; |
| } |
| *poffPos = (size_t)lOff; |
| return WMP_errSuccess; |
| } |
| |
| static Bool |
| _jxr_io_EOS(WMPStream* pWS) { |
| FreeImageJXRIO *fio = (FreeImageJXRIO*)pWS->state.pvObj; |
| long currentPos = fio->io->tell_proc(fio->handle); |
| fio->io->seek_proc(fio->handle, 0, SEEK_END); |
| long fileRemaining = fio->io->tell_proc(fio->handle) - currentPos; |
| fio->io->seek_proc(fio->handle, currentPos, SEEK_SET); |
| return (fileRemaining > 0); |
| } |
| |
| static ERR |
| _jxr_io_Close(WMPStream** ppWS) { |
| WMPStream *pWS = *ppWS; |
| // HACK : we use fMem to avoid a stream destruction by the library |
| // because FreeImage MUST HAVE the ownership of the stream |
| // see _jxr_io_Create |
| if(pWS && pWS->fMem) { |
| free(pWS); |
| *ppWS = NULL; |
| } |
| return WMP_errSuccess; |
| } |
| |
| static ERR |
| _jxr_io_Create(WMPStream **ppWS, FreeImageJXRIO *jxr_io) { |
| *ppWS = (WMPStream*)calloc(1, sizeof(**ppWS)); |
| if(*ppWS) { |
| WMPStream *pWS = *ppWS; |
| |
| pWS->state.pvObj = jxr_io; |
| pWS->Close = _jxr_io_Close; |
| pWS->EOS = _jxr_io_EOS; |
| pWS->Read = _jxr_io_Read; |
| pWS->Write = _jxr_io_Write; |
| pWS->SetPos = _jxr_io_SetPos; |
| pWS->GetPos = _jxr_io_GetPos; |
| |
| // HACK : we use fMem to avoid a stream destruction by the library |
| // because FreeImage MUST HAVE the ownership of the stream |
| // see _jxr_io_Close |
| pWS->fMem = FALSE; |
| |
| return WMP_errSuccess; |
| } |
| return WMP_errOutOfMemory; |
| } |
| |
| // ========================================================== |
| // JPEG XR Error handling |
| // ========================================================== |
| |
| static const char* |
| JXR_ErrorMessage(const int error) { |
| switch(error) { |
| case WMP_errNotYetImplemented: |
| case WMP_errAbstractMethod: |
| return "Not yet implemented"; |
| case WMP_errOutOfMemory: |
| return "Out of memory"; |
| case WMP_errFileIO: |
| return "File I/O error"; |
| case WMP_errBufferOverflow: |
| return "Buffer overflow"; |
| case WMP_errInvalidParameter: |
| return "Invalid parameter"; |
| case WMP_errInvalidArgument: |
| return "Invalid argument"; |
| case WMP_errUnsupportedFormat: |
| return "Unsupported format"; |
| case WMP_errIncorrectCodecVersion: |
| return "Incorrect codec version"; |
| case WMP_errIndexNotFound: |
| return "Format converter: Index not found"; |
| case WMP_errOutOfSequence: |
| return "Metadata: Out of sequence"; |
| case WMP_errMustBeMultipleOf16LinesUntilLastCall: |
| return "Must be multiple of 16 lines until last call"; |
| case WMP_errPlanarAlphaBandedEncRequiresTempFile: |
| return "Planar alpha banded encoder requires temp files"; |
| case WMP_errAlphaModeCannotBeTranscoded: |
| return "Alpha mode cannot be transcoded"; |
| case WMP_errIncorrectCodecSubVersion: |
| return "Incorrect codec subversion"; |
| case WMP_errFail: |
| case WMP_errNotInitialized: |
| default: |
| return "Invalid instruction - please contact the FreeImage team"; |
| } |
| } |
| |
| // ========================================================== |
| // Helper functions & macro |
| // ========================================================== |
| |
| #define JXR_CHECK(error_code) \ |
| if(error_code < 0) { \ |
| const char *error_message = JXR_ErrorMessage(error_code); \ |
| throw error_message; \ |
| } |
| |
| // -------------------------------------------------------------------------- |
| |
| /** |
| Input conversions natively understood by FreeImage |
| @see GetNativePixelFormat |
| */ |
| typedef struct tagJXRInputConversion { |
| BITDEPTH_BITS bdBitDepth; |
| U32 cbitUnit; |
| FREE_IMAGE_TYPE image_type; |
| unsigned red_mask; |
| unsigned green_mask; |
| unsigned blue_mask; |
| } JXRInputConversion; |
| |
| /** |
| Conversion table for native FreeImage formats |
| @see GetNativePixelFormat |
| */ |
| static JXRInputConversion s_FreeImagePixelInfo[] = { |
| // 1-bit bitmap |
| { BD_1, 1, FIT_BITMAP, 0, 0, 0 }, |
| // 8-, 24-, 32-bit bitmap |
| { BD_8, 8, FIT_BITMAP, 0, 0, 0 }, |
| { BD_8, 24, FIT_BITMAP, 0, 0, 0 }, |
| { BD_8, 32, FIT_BITMAP, 0, 0, 0 }, |
| // 16-bit RGB 565 |
| { BD_565, 16, FIT_BITMAP, FI16_565_RED_MASK, FI16_565_GREEN_MASK, FI16_565_BLUE_MASK }, |
| // 16-bit RGB 555 |
| { BD_5, 16, FIT_BITMAP, FI16_555_RED_MASK, FI16_555_GREEN_MASK, FI16_555_BLUE_MASK }, |
| // 16-bit greyscale, RGB16, RGBA16 bitmap |
| { BD_16, 16, FIT_UINT16, 0, 0, 0 }, |
| { BD_16, 48, FIT_RGB16, 0, 0, 0 }, |
| { BD_16, 64, FIT_RGBA16, 0, 0, 0 }, |
| // 32-bit float, RGBF, RGBAF bitmap |
| { BD_32F, 32, FIT_FLOAT, 0, 0, 0 }, |
| { BD_32F, 96, FIT_RGBF, 0, 0, 0 }, |
| { BD_32F, 128, FIT_RGBAF, 0, 0, 0 } |
| }; |
| |
| /** |
| Scan input pixelInfo specifications and return the equivalent FreeImage info for loading |
| @param pixelInfo Image specifications |
| @param out_guid_format (returned value) output pixel format |
| @param image_type (returned value) Image type |
| @param bpp (returned value) Image bit depth |
| @param red_mask (returned value) RGB mask |
| @param green_mask (returned value) RGB mask |
| @param blue_mask (returned value) RGB mask |
| @return Returns WMP_errSuccess if successful, returns WMP_errFail otherwise |
| @see GetInputPixelFormat |
| */ |
| static ERR |
| GetNativePixelFormat(const PKPixelInfo *pixelInfo, PKPixelFormatGUID *out_guid_format, FREE_IMAGE_TYPE *image_type, unsigned *bpp, unsigned *red_mask, unsigned *green_mask, unsigned *blue_mask) { |
| const unsigned s_FreeImagePixelInfoSize = (unsigned)sizeof(s_FreeImagePixelInfo) / sizeof(*(s_FreeImagePixelInfo)); |
| |
| for(unsigned i = 0; i < s_FreeImagePixelInfoSize; i++) { |
| if(pixelInfo->bdBitDepth == s_FreeImagePixelInfo[i].bdBitDepth) { |
| if(pixelInfo->cbitUnit == s_FreeImagePixelInfo[i].cbitUnit) { |
| // found ! now get dst image format specifications |
| memcpy(out_guid_format, pixelInfo->pGUIDPixFmt, sizeof(PKPixelFormatGUID)); |
| *image_type = s_FreeImagePixelInfo[i].image_type; |
| *bpp = s_FreeImagePixelInfo[i].cbitUnit; |
| *red_mask = s_FreeImagePixelInfo[i].red_mask; |
| *green_mask = s_FreeImagePixelInfo[i].green_mask; |
| *blue_mask = s_FreeImagePixelInfo[i].blue_mask; |
| return WMP_errSuccess; |
| } |
| } |
| } |
| |
| // not found : need pixel format conversion |
| return WMP_errFail; |
| } |
| |
| /** |
| Scan input file guid format and return the equivalent FreeImage info & target guid format for loading |
| @param pDecoder Decoder handle |
| @param guid_format (returned value) Output pixel format |
| @param image_type (returned value) Image type |
| @param bpp (returned value) Image bit depth |
| @param red_mask (returned value) RGB mask |
| @param green_mask (returned value) RGB mask |
| @param blue_mask (returned value) RGB mask |
| @return Returns TRUE if successful, returns FALSE otherwise |
| */ |
| static ERR |
| GetInputPixelFormat(PKImageDecode *pDecoder, PKPixelFormatGUID *guid_format, FREE_IMAGE_TYPE *image_type, unsigned *bpp, unsigned *red_mask, unsigned *green_mask, unsigned *blue_mask) { |
| ERR error_code = 0; // error code as returned by the interface |
| PKPixelInfo pixelInfo; // image specifications |
| |
| try { |
| // get input file pixel format ... |
| PKPixelFormatGUID pguidSourcePF; |
| error_code = pDecoder->GetPixelFormat(pDecoder, &pguidSourcePF); |
| JXR_CHECK(error_code); |
| pixelInfo.pGUIDPixFmt = &pguidSourcePF; |
| // ... check for a supported format and get the format specifications |
| error_code = PixelFormatLookup(&pixelInfo, LOOKUP_FORWARD); |
| JXR_CHECK(error_code); |
| |
| // search for an equivalent native FreeImage format |
| error_code = GetNativePixelFormat(&pixelInfo, guid_format, image_type, bpp, red_mask, green_mask, blue_mask); |
| |
| if(error_code != WMP_errSuccess) { |
| // try to find a suitable conversion function ... |
| const PKPixelFormatGUID *ppguidTargetPF = NULL; // target pixel format |
| unsigned iIndex = 0; // first available conversion function |
| do { |
| error_code = PKFormatConverter_EnumConversions(&pguidSourcePF, iIndex, &ppguidTargetPF); |
| if(error_code == WMP_errSuccess) { |
| // found a conversion function, is the converted format a native FreeImage format ? |
| pixelInfo.pGUIDPixFmt = ppguidTargetPF; |
| error_code = PixelFormatLookup(&pixelInfo, LOOKUP_FORWARD); |
| JXR_CHECK(error_code); |
| error_code = GetNativePixelFormat(&pixelInfo, guid_format, image_type, bpp, red_mask, green_mask, blue_mask); |
| if(error_code == WMP_errSuccess) { |
| break; |
| } |
| } |
| // try next conversion function |
| iIndex++; |
| } while(error_code != WMP_errIndexNotFound); |
| |
| } |
| |
| return (error_code == WMP_errSuccess) ? WMP_errSuccess : WMP_errUnsupportedFormat; |
| |
| } catch(...) { |
| return error_code; |
| } |
| } |
| |
| // -------------------------------------------------------------------------- |
| |
| /** |
| Scan input dib format and return the equivalent PKPixelFormatGUID format for saving |
| @param dib Image to be saved |
| @param guid_format (returned value) GUID format |
| @param bHasAlpha (returned value) TRUE if an alpha layer is present |
| @return Returns TRUE if successful, returns FALSE otherwise |
| */ |
| static ERR |
| GetOutputPixelFormat(FIBITMAP *dib, PKPixelFormatGUID *guid_format, BOOL *bHasAlpha) { |
| const FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib); |
| const unsigned bpp = FreeImage_GetBPP(dib); |
| const FREE_IMAGE_COLOR_TYPE color_type = FreeImage_GetColorType(dib); |
| |
| *guid_format = GUID_PKPixelFormatDontCare; |
| *bHasAlpha = FALSE; |
| |
| switch(image_type) { |
| case FIT_BITMAP: // standard image : 1-, 4-, 8-, 16-, 24-, 32-bit |
| switch(bpp) { |
| case 1: |
| // assume FIC_MINISBLACK |
| if(color_type == FIC_MINISBLACK) { |
| *guid_format = GUID_PKPixelFormatBlackWhite; |
| } |
| break; |
| case 8: |
| // assume FIC_MINISBLACK |
| if(color_type == FIC_MINISBLACK) { |
| *guid_format = GUID_PKPixelFormat8bppGray; |
| } |
| break; |
| case 16: |
| if ((FreeImage_GetRedMask(dib) == FI16_565_RED_MASK) && (FreeImage_GetGreenMask(dib) == FI16_565_GREEN_MASK) && (FreeImage_GetBlueMask(dib) == FI16_565_BLUE_MASK)) { |
| *guid_format = GUID_PKPixelFormat16bppRGB565; |
| } else { |
| // includes case where all the masks are 0 |
| *guid_format = GUID_PKPixelFormat16bppRGB555; |
| } |
| break; |
| #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR |
| case 24: |
| *guid_format = GUID_PKPixelFormat24bppBGR; |
| break; |
| case 32: |
| *guid_format = GUID_PKPixelFormat32bppBGRA; |
| *bHasAlpha = TRUE; |
| break; |
| #elif FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB |
| case 24: |
| *guid_format = GUID_PKPixelFormat24bppRGB; |
| break; |
| case 32: |
| *guid_format = GUID_PKPixelFormat32bppRGBA; |
| *bHasAlpha = TRUE; |
| break; |
| #endif |
| case 4: |
| default: |
| // not supported |
| break; |
| } |
| break; |
| case FIT_UINT16: // array of unsigned short : unsigned 16-bit |
| *guid_format = GUID_PKPixelFormat16bppGray; |
| break; |
| case FIT_FLOAT: // array of float : 32-bit IEEE floating point |
| *guid_format = GUID_PKPixelFormat32bppGrayFloat; |
| break; |
| case FIT_RGB16: // 48-bit RGB image : 3 x 16-bit |
| *guid_format = GUID_PKPixelFormat48bppRGB; |
| break; |
| case FIT_RGBA16: // 64-bit RGBA image : 4 x 16-bit |
| *guid_format = GUID_PKPixelFormat64bppRGBA; |
| *bHasAlpha = TRUE; |
| break; |
| case FIT_RGBF: // 96-bit RGB float image : 3 x 32-bit IEEE floating point |
| *guid_format = GUID_PKPixelFormat96bppRGBFloat; |
| break; |
| case FIT_RGBAF: // 128-bit RGBA float image : 4 x 32-bit IEEE floating point |
| *guid_format = GUID_PKPixelFormat128bppRGBAFloat; |
| *bHasAlpha = TRUE; |
| break; |
| |
| case FIT_INT16: // array of short : signed 16-bit |
| case FIT_UINT32: // array of unsigned long : unsigned 32-bit |
| case FIT_INT32: // array of long : signed 32-bit |
| case FIT_DOUBLE: // array of double : 64-bit IEEE floating point |
| case FIT_COMPLEX: // array of FICOMPLEX : 2 x 64-bit IEEE floating point |
| |
| default: |
| // unsupported format |
| break; |
| } |
| |
| return (*guid_format != GUID_PKPixelFormatDontCare) ? WMP_errSuccess : WMP_errUnsupportedFormat; |
| } |
| |
| // ========================================================== |
| // Metadata loading |
| // ========================================================== |
| |
| /** |
| Read a JPEG-XR IFD as a buffer |
| @see ReadMetadata |
| */ |
| static ERR |
| ReadProfile(WMPStream* pStream, unsigned cbByteCount, unsigned uOffset, BYTE **ppbProfile) { |
| // (re-)allocate profile buffer |
| BYTE *pbProfile = *ppbProfile; |
| pbProfile = (BYTE*)realloc(pbProfile, cbByteCount); |
| if(!pbProfile) { |
| return WMP_errOutOfMemory; |
| } |
| // read the profile |
| if(WMP_errSuccess == pStream->SetPos(pStream, uOffset)) { |
| if(WMP_errSuccess == pStream->Read(pStream, pbProfile, cbByteCount)) { |
| *ppbProfile = pbProfile; |
| return WMP_errSuccess; |
| } |
| } |
| return WMP_errFileIO; |
| } |
| |
| /** |
| Convert a DPKPROPVARIANT to a FITAG, then store the tag as FIMD_EXIF_MAIN |
| @see ReadDescriptiveMetadata |
| */ |
| static BOOL |
| ReadPropVariant(WORD tag_id, const DPKPROPVARIANT & varSrc, FIBITMAP *dib) { |
| DWORD dwSize; |
| |
| if(varSrc.vt == DPKVT_EMPTY) { |
| return FALSE; |
| } |
| |
| // get the tag key |
| TagLib& s = TagLib::instance(); |
| const char *key = s.getTagFieldName(TagLib::EXIF_MAIN, tag_id, NULL); |
| if(!key) { |
| return FALSE; |
| } |
| |
| // create a tag |
| FITAG *tag = FreeImage_CreateTag(); |
| if(tag) { |
| // set tag ID |
| FreeImage_SetTagID(tag, tag_id); |
| // set tag type, count, length and value |
| switch (varSrc.vt) { |
| case DPKVT_LPSTR: |
| FreeImage_SetTagType(tag, FIDT_ASCII); |
| dwSize = (DWORD)strlen(varSrc.VT.pszVal) + 1; |
| FreeImage_SetTagCount(tag, dwSize); |
| FreeImage_SetTagLength(tag, dwSize); |
| FreeImage_SetTagValue(tag, varSrc.VT.pszVal); |
| break; |
| |
| case DPKVT_LPWSTR: |
| FreeImage_SetTagType(tag, FIDT_UNDEFINED); |
| dwSize = (DWORD)(sizeof(U16) * (wcslen((wchar_t *) varSrc.VT.pwszVal) + 1)); // +1 for NULL term |
| FreeImage_SetTagCount(tag, dwSize); |
| FreeImage_SetTagLength(tag, dwSize); |
| FreeImage_SetTagValue(tag, varSrc.VT.pwszVal); |
| break; |
| |
| case DPKVT_UI2: |
| FreeImage_SetTagType(tag, FIDT_SHORT); |
| FreeImage_SetTagCount(tag, 1); |
| FreeImage_SetTagLength(tag, 2); |
| FreeImage_SetTagValue(tag, &varSrc.VT.uiVal); |
| break; |
| |
| case DPKVT_UI4: |
| FreeImage_SetTagType(tag, FIDT_LONG); |
| FreeImage_SetTagCount(tag, 1); |
| FreeImage_SetTagLength(tag, 4); |
| FreeImage_SetTagValue(tag, &varSrc.VT.ulVal); |
| break; |
| |
| default: |
| assert(FALSE); // This case is not handled |
| break; |
| } |
| // get the tag desctiption |
| const char *description = s.getTagDescription(TagLib::EXIF_MAIN, tag_id); |
| FreeImage_SetTagDescription(tag, description); |
| |
| // store the tag |
| FreeImage_SetMetadata(FIMD_EXIF_MAIN, dib, key, tag); |
| |
| FreeImage_DeleteTag(tag); |
| } |
| return TRUE; |
| } |
| |
| /** |
| Read JPEG-XR descriptive metadata and store as EXIF_MAIN metadata |
| @see ReadPropVariant |
| */ |
| static ERR |
| ReadDescriptiveMetadata(PKImageDecode *pID, FIBITMAP *dib) { |
| // get Exif TIFF metadata |
| const DESCRIPTIVEMETADATA *pDescMetadata = &pID->WMP.sDescMetadata; |
| // convert metadata to FITAG and store into the EXIF_MAIN metadata model |
| ReadPropVariant(WMP_tagImageDescription, pDescMetadata->pvarImageDescription, dib); |
| ReadPropVariant(WMP_tagCameraMake, pDescMetadata->pvarCameraMake, dib); |
| ReadPropVariant(WMP_tagCameraModel, pDescMetadata->pvarCameraModel, dib); |
| ReadPropVariant(WMP_tagSoftware, pDescMetadata->pvarSoftware, dib); |
| ReadPropVariant(WMP_tagDateTime, pDescMetadata->pvarDateTime, dib); |
| ReadPropVariant(WMP_tagArtist, pDescMetadata->pvarArtist, dib); |
| ReadPropVariant(WMP_tagCopyright, pDescMetadata->pvarCopyright, dib); |
| ReadPropVariant(WMP_tagRatingStars, pDescMetadata->pvarRatingStars, dib); |
| ReadPropVariant(WMP_tagRatingValue, pDescMetadata->pvarRatingValue, dib); |
| ReadPropVariant(WMP_tagCaption, pDescMetadata->pvarCaption, dib); |
| ReadPropVariant(WMP_tagDocumentName, pDescMetadata->pvarDocumentName, dib); |
| ReadPropVariant(WMP_tagPageName, pDescMetadata->pvarPageName, dib); |
| ReadPropVariant(WMP_tagPageNumber, pDescMetadata->pvarPageNumber, dib); |
| ReadPropVariant(WMP_tagHostComputer, pDescMetadata->pvarHostComputer, dib); |
| return WMP_errSuccess; |
| } |
| |
| /** |
| Read ICC, XMP, Exif, Exif-GPS, IPTC, descriptive (i.e. Exif-TIFF) metadata |
| @see ReadProfile, ReadDescriptiveMetadata |
| */ |
| static ERR |
| ReadMetadata(PKImageDecode *pID, FIBITMAP *dib) { |
| ERR error_code = 0; // error code as returned by the interface |
| size_t currentPos = 0; // current stream position |
| |
| WMPStream *pStream = pID->pStream; |
| WmpDEMisc *wmiDEMisc = &pID->WMP.wmiDEMisc; |
| BYTE *pbProfile = NULL; |
| |
| try { |
| // save current position |
| error_code = pStream->GetPos(pStream, ¤tPos); |
| JXR_CHECK(error_code); |
| |
| // ICC profile |
| if(0 != wmiDEMisc->uColorProfileByteCount) { |
| unsigned cbByteCount = wmiDEMisc->uColorProfileByteCount; |
| unsigned uOffset = wmiDEMisc->uColorProfileOffset; |
| error_code = ReadProfile(pStream, cbByteCount, uOffset, &pbProfile); |
| JXR_CHECK(error_code); |
| FreeImage_CreateICCProfile(dib, pbProfile, cbByteCount); |
| } |
| |
| // XMP metadata |
| if(0 != wmiDEMisc->uXMPMetadataByteCount) { |
| unsigned cbByteCount = wmiDEMisc->uXMPMetadataByteCount; |
| unsigned uOffset = wmiDEMisc->uXMPMetadataOffset; |
| error_code = ReadProfile(pStream, cbByteCount, uOffset, &pbProfile); |
| JXR_CHECK(error_code); |
| // store the tag as XMP |
| FITAG *tag = FreeImage_CreateTag(); |
| if(tag) { |
| FreeImage_SetTagLength(tag, cbByteCount); |
| FreeImage_SetTagCount(tag, cbByteCount); |
| FreeImage_SetTagType(tag, FIDT_ASCII); |
| FreeImage_SetTagValue(tag, pbProfile); |
| FreeImage_SetTagKey(tag, g_TagLib_XMPFieldName); |
| FreeImage_SetMetadata(FIMD_XMP, dib, FreeImage_GetTagKey(tag), tag); |
| FreeImage_DeleteTag(tag); |
| } |
| } |
| |
| // IPTC metadata |
| if(0 != wmiDEMisc->uIPTCNAAMetadataByteCount) { |
| unsigned cbByteCount = wmiDEMisc->uIPTCNAAMetadataByteCount; |
| unsigned uOffset = wmiDEMisc->uIPTCNAAMetadataOffset; |
| error_code = ReadProfile(pStream, cbByteCount, uOffset, &pbProfile); |
| JXR_CHECK(error_code); |
| // decode the IPTC profile |
| read_iptc_profile(dib, pbProfile, cbByteCount); |
| } |
| |
| // Exif metadata |
| if(0 != wmiDEMisc->uEXIFMetadataByteCount) { |
| unsigned cbByteCount = wmiDEMisc->uEXIFMetadataByteCount; |
| unsigned uOffset = wmiDEMisc->uEXIFMetadataOffset; |
| error_code = ReadProfile(pStream, cbByteCount, uOffset, &pbProfile); |
| JXR_CHECK(error_code); |
| // decode the Exif profile |
| jpegxr_read_exif_profile(dib, pbProfile, cbByteCount, uOffset); |
| } |
| |
| // Exif-GPS metadata |
| if(0 != wmiDEMisc->uGPSInfoMetadataByteCount) { |
| unsigned cbByteCount = wmiDEMisc->uGPSInfoMetadataByteCount; |
| unsigned uOffset = wmiDEMisc->uGPSInfoMetadataOffset; |
| error_code = ReadProfile(pStream, cbByteCount, uOffset, &pbProfile); |
| JXR_CHECK(error_code); |
| // decode the Exif-GPS profile |
| jpegxr_read_exif_gps_profile(dib, pbProfile, cbByteCount, uOffset); |
| } |
| |
| // free profile buffer |
| free(pbProfile); |
| // restore initial position |
| error_code = pID->pStream->SetPos(pID->pStream, currentPos); |
| JXR_CHECK(error_code); |
| |
| // as a LAST STEP, read descriptive metadata |
| // these metadata overwrite possible identical Exif-TIFF metadata |
| // that could have been read inside the Exif IFD |
| |
| return ReadDescriptiveMetadata(pID, dib); |
| |
| } catch(...) { |
| // free profile buffer |
| free(pbProfile); |
| if(currentPos) { |
| // restore initial position |
| pStream->SetPos(pStream, currentPos); |
| } |
| return error_code; |
| } |
| } |
| |
| // ========================================================== |
| // Metadata saving |
| // ========================================================== |
| |
| /** |
| Convert a FITAG (coming from FIMD_EXIF_MAIN) to a DPKPROPVARIANT. |
| No allocation is needed here, the function just copy pointers when needed. |
| @see WriteDescriptiveMetadata |
| */ |
| static BOOL |
| WritePropVariant(FIBITMAP *dib, WORD tag_id, DPKPROPVARIANT & varDst) { |
| FITAG *tag = NULL; |
| |
| TagLib& s = TagLib::instance(); |
| |
| // clear output DPKPROPVARIANT |
| varDst.vt = DPKVT_EMPTY; |
| |
| // given the tag id, get the tag key |
| const char *key = s.getTagFieldName(TagLib::EXIF_MAIN, tag_id, NULL); |
| // then, get the tag info |
| if(!FreeImage_GetMetadata(FIMD_EXIF_MAIN, dib, key, &tag)) { |
| return FALSE; |
| } |
| |
| // set the tag value |
| switch(FreeImage_GetTagType(tag)) { |
| case FIDT_ASCII: |
| varDst.vt = DPKVT_LPSTR; |
| varDst.VT.pszVal = (char*)FreeImage_GetTagValue(tag); |
| break; |
| case FIDT_BYTE: |
| case FIDT_UNDEFINED: |
| varDst.vt = DPKVT_LPWSTR; |
| varDst.VT.pwszVal = (U16*)FreeImage_GetTagValue(tag); |
| break; |
| case FIDT_SHORT: |
| varDst.vt = DPKVT_UI2; |
| varDst.VT.uiVal = *((U16*)FreeImage_GetTagValue(tag)); |
| break; |
| case FIDT_LONG: |
| varDst.vt = DPKVT_UI4; |
| varDst.VT.ulVal = *((U32*)FreeImage_GetTagValue(tag)); |
| break; |
| default: |
| break; |
| } |
| |
| return TRUE; |
| } |
| |
| /** |
| Write EXIF_MAIN metadata to JPEG-XR descriptive metadata |
| @see WritePropVariant |
| */ |
| static ERR |
| WriteDescriptiveMetadata(PKImageEncode *pIE, FIBITMAP *dib) { |
| ERR error_code = 0; // error code as returned by the interface |
| DESCRIPTIVEMETADATA DescMetadata; |
| |
| // fill the DESCRIPTIVEMETADATA structure (use pointers to arrays when needed) |
| WritePropVariant(dib, WMP_tagImageDescription, DescMetadata.pvarImageDescription); |
| WritePropVariant(dib, WMP_tagCameraMake, DescMetadata.pvarCameraMake); |
| WritePropVariant(dib, WMP_tagCameraModel, DescMetadata.pvarCameraModel); |
| WritePropVariant(dib, WMP_tagSoftware, DescMetadata.pvarSoftware); |
| WritePropVariant(dib, WMP_tagDateTime, DescMetadata.pvarDateTime); |
| WritePropVariant(dib, WMP_tagArtist, DescMetadata.pvarArtist); |
| WritePropVariant(dib, WMP_tagCopyright, DescMetadata.pvarCopyright); |
| WritePropVariant(dib, WMP_tagRatingStars, DescMetadata.pvarRatingStars); |
| WritePropVariant(dib, WMP_tagRatingValue, DescMetadata.pvarRatingValue); |
| WritePropVariant(dib, WMP_tagCaption, DescMetadata.pvarCaption); |
| WritePropVariant(dib, WMP_tagDocumentName, DescMetadata.pvarDocumentName); |
| WritePropVariant(dib, WMP_tagPageName, DescMetadata.pvarPageName); |
| WritePropVariant(dib, WMP_tagPageNumber, DescMetadata.pvarPageNumber); |
| WritePropVariant(dib, WMP_tagHostComputer, DescMetadata.pvarHostComputer); |
| |
| // copy the structure to the encoder |
| error_code = pIE->SetDescriptiveMetadata(pIE, &DescMetadata); |
| |
| // no need to free anything here |
| return error_code; |
| } |
| |
| /** |
| Write ICC, XMP, Exif, Exif-GPS, IPTC, descriptive (i.e. Exif-TIFF) metadata |
| */ |
| static ERR |
| WriteMetadata(PKImageEncode *pIE, FIBITMAP *dib) { |
| ERR error_code = 0; // error code as returned by the interface |
| BYTE *profile = NULL; |
| unsigned profile_size = 0; |
| |
| try { |
| // write ICC profile |
| { |
| FIICCPROFILE *iccProfile = FreeImage_GetICCProfile(dib); |
| if(iccProfile->data) { |
| error_code = pIE->SetColorContext(pIE, (U8*)iccProfile->data, iccProfile->size); |
| JXR_CHECK(error_code); |
| } |
| } |
| |
| // write descriptive metadata |
| if(FreeImage_GetMetadataCount(FIMD_EXIF_MAIN, dib)) { |
| error_code = WriteDescriptiveMetadata(pIE, dib); |
| JXR_CHECK(error_code); |
| } |
| |
| // write IPTC metadata |
| if(FreeImage_GetMetadataCount(FIMD_IPTC, dib)) { |
| // create a binary profile |
| if(write_iptc_profile(dib, &profile, &profile_size)) { |
| // write the profile |
| error_code = PKImageEncode_SetIPTCNAAMetadata_WMP(pIE, profile, profile_size); |
| JXR_CHECK(error_code); |
| // release profile |
| free(profile); |
| profile = NULL; |
| } |
| } |
| |
| // write XMP metadata |
| { |
| FITAG *tag_xmp = NULL; |
| if(FreeImage_GetMetadata(FIMD_XMP, dib, g_TagLib_XMPFieldName, &tag_xmp)) { |
| error_code = PKImageEncode_SetXMPMetadata_WMP(pIE, (BYTE*)FreeImage_GetTagValue(tag_xmp), FreeImage_GetTagLength(tag_xmp)); |
| JXR_CHECK(error_code); |
| } |
| } |
| |
| // write Exif metadata |
| { |
| if(tiff_get_ifd_profile(dib, FIMD_EXIF_EXIF, &profile, &profile_size)) { |
| error_code = PKImageEncode_SetEXIFMetadata_WMP(pIE, profile, profile_size); |
| JXR_CHECK(error_code); |
| // release profile |
| free(profile); |
| profile = NULL; |
| } |
| } |
| |
| // write Exif GPS metadata |
| { |
| if(tiff_get_ifd_profile(dib, FIMD_EXIF_GPS, &profile, &profile_size)) { |
| error_code = PKImageEncode_SetGPSInfoMetadata_WMP(pIE, profile, profile_size); |
| JXR_CHECK(error_code); |
| // release profile |
| free(profile); |
| profile = NULL; |
| } |
| } |
| |
| return WMP_errSuccess; |
| |
| } catch(...) { |
| free(profile); |
| return error_code; |
| } |
| } |
| |
| |
| |
| // ========================================================== |
| // Quantization tables (Y, U, V, YHP, UHP, VHP), |
| // optimized for PSNR |
| // ========================================================== |
| |
| static const int DPK_QPS_420[11][6] = { // for 8 bit only |
| { 66, 65, 70, 72, 72, 77 }, |
| { 59, 58, 63, 64, 63, 68 }, |
| { 52, 51, 57, 56, 56, 61 }, |
| { 48, 48, 54, 51, 50, 55 }, |
| { 43, 44, 48, 46, 46, 49 }, |
| { 37, 37, 42, 38, 38, 43 }, |
| { 26, 28, 31, 27, 28, 31 }, |
| { 16, 17, 22, 16, 17, 21 }, |
| { 10, 11, 13, 10, 10, 13 }, |
| { 5, 5, 6, 5, 5, 6 }, |
| { 2, 2, 3, 2, 2, 2 } |
| }; |
| |
| static const int DPK_QPS_8[12][6] = { |
| { 67, 79, 86, 72, 90, 98 }, |
| { 59, 74, 80, 64, 83, 89 }, |
| { 53, 68, 75, 57, 76, 83 }, |
| { 49, 64, 71, 53, 70, 77 }, |
| { 45, 60, 67, 48, 67, 74 }, |
| { 40, 56, 62, 42, 59, 66 }, |
| { 33, 49, 55, 35, 51, 58 }, |
| { 27, 44, 49, 28, 45, 50 }, |
| { 20, 36, 42, 20, 38, 44 }, |
| { 13, 27, 34, 13, 28, 34 }, |
| { 7, 17, 21, 8, 17, 21 }, // Photoshop 100% |
| { 2, 5, 6, 2, 5, 6 } |
| }; |
| |
| static const int DPK_QPS_16[11][6] = { |
| { 197, 203, 210, 202, 207, 213 }, |
| { 174, 188, 193, 180, 189, 196 }, |
| { 152, 167, 173, 156, 169, 174 }, |
| { 135, 152, 157, 137, 153, 158 }, |
| { 119, 137, 141, 119, 138, 142 }, |
| { 102, 120, 125, 100, 120, 124 }, |
| { 82, 98, 104, 79, 98, 103 }, |
| { 60, 76, 81, 58, 76, 81 }, |
| { 39, 52, 58, 36, 52, 58 }, |
| { 16, 27, 33, 14, 27, 33 }, |
| { 5, 8, 9, 4, 7, 8 } |
| }; |
| |
| static const int DPK_QPS_16f[11][6] = { |
| { 148, 177, 171, 165, 187, 191 }, |
| { 133, 155, 153, 147, 172, 181 }, |
| { 114, 133, 138, 130, 157, 167 }, |
| { 97, 118, 120, 109, 137, 144 }, |
| { 76, 98, 103, 85, 115, 121 }, |
| { 63, 86, 91, 62, 96, 99 }, |
| { 46, 68, 71, 43, 73, 75 }, |
| { 29, 48, 52, 27, 48, 51 }, |
| { 16, 30, 35, 14, 29, 34 }, |
| { 8, 14, 17, 7, 13, 17 }, |
| { 3, 5, 7, 3, 5, 6 } |
| }; |
| |
| static const int DPK_QPS_32f[11][6] = { |
| { 194, 206, 209, 204, 211, 217 }, |
| { 175, 187, 196, 186, 193, 205 }, |
| { 157, 170, 177, 167, 180, 190 }, |
| { 133, 152, 156, 144, 163, 168 }, |
| { 116, 138, 142, 117, 143, 148 }, |
| { 98, 120, 123, 96, 123, 126 }, |
| { 80, 99, 102, 78, 99, 102 }, |
| { 65, 79, 84, 63, 79, 84 }, |
| { 48, 61, 67, 45, 60, 66 }, |
| { 27, 41, 46, 24, 40, 45 }, |
| { 3, 22, 24, 2, 21, 22 } |
| }; |
| |
| // ========================================================== |
| // Plugin Implementation |
| // ========================================================== |
| |
| static const char * DLL_CALLCONV |
| Format() { |
| return "JPEG-XR"; |
| } |
| |
| static const char * DLL_CALLCONV |
| Description() { |
| return "JPEG XR image format"; |
| } |
| |
| static const char * DLL_CALLCONV |
| Extension() { |
| return "jxr,wdp,hdp"; |
| } |
| |
| static const char * DLL_CALLCONV |
| RegExpr() { |
| return NULL; |
| } |
| |
| static const char * DLL_CALLCONV |
| MimeType() { |
| return "image/vnd.ms-photo"; |
| } |
| |
| static BOOL DLL_CALLCONV |
| Validate(FreeImageIO *io, fi_handle handle) { |
| BYTE jxr_signature[3] = { 0x49, 0x49, 0xBC }; |
| BYTE signature[3] = { 0, 0, 0 }; |
| |
| io->read_proc(&signature, 1, 3, handle); |
| |
| return (memcmp(jxr_signature, signature, 3) == 0); |
| } |
| |
| static BOOL DLL_CALLCONV |
| SupportsExportDepth(int depth) { |
| return ( |
| (depth == 1) || |
| (depth == 8) || |
| (depth == 16) || |
| (depth == 24) || |
| (depth == 32) |
| ); |
| } |
| |
| static BOOL DLL_CALLCONV |
| SupportsExportType(FREE_IMAGE_TYPE type) { |
| return ( |
| (type == FIT_BITMAP) || |
| (type == FIT_UINT16) || |
| (type == FIT_RGB16) || |
| (type == FIT_RGBA16) || |
| (type == FIT_FLOAT) || |
| (type == FIT_RGBF) || |
| (type == FIT_RGBAF) |
| ); |
| } |
| |
| static BOOL DLL_CALLCONV |
| SupportsICCProfiles() { |
| return TRUE; |
| } |
| |
| static BOOL DLL_CALLCONV |
| SupportsNoPixels() { |
| return TRUE; |
| } |
| |
| // ========================================================== |
| // Open & Close |
| // ========================================================== |
| |
| static void * DLL_CALLCONV |
| Open(FreeImageIO *io, fi_handle handle, BOOL read) { |
| WMPStream *pStream = NULL; // stream interface |
| if(io && handle) { |
| // allocate the FreeImageIO stream wrapper |
| FreeImageJXRIO *jxr_io = (FreeImageJXRIO*)malloc(sizeof(FreeImageJXRIO)); |
| if(jxr_io) { |
| jxr_io->io = io; |
| jxr_io->handle = handle; |
| // create a JXR stream wrapper |
| if(_jxr_io_Create(&pStream, jxr_io) != WMP_errSuccess) { |
| free(jxr_io); |
| return NULL; |
| } |
| } |
| } |
| return pStream; |
| } |
| |
| static void DLL_CALLCONV |
| Close(FreeImageIO *io, fi_handle handle, void *data) { |
| WMPStream *pStream = (WMPStream*)data; |
| if(pStream) { |
| // free the FreeImageIO stream wrapper |
| FreeImageJXRIO *jxr_io = (FreeImageJXRIO*)pStream->state.pvObj; |
| free(jxr_io); |
| // free the JXR stream wrapper |
| pStream->fMem = TRUE; |
| _jxr_io_Close(&pStream); |
| } |
| } |
| |
| // ========================================================== |
| // Load |
| // ========================================================== |
| |
| /** |
| Set decoder parameters |
| @param pDecoder Decoder handle |
| @param flags FreeImage load flags |
| */ |
| static void |
| SetDecoderParameters(PKImageDecode *pDecoder, int flags) { |
| // load image & alpha for formats with alpha |
| pDecoder->WMP.wmiSCP.uAlphaMode = 2; |
| // more options to come ... |
| } |
| |
| /** |
| Copy or convert & copy decoded pixels into the dib |
| @param pDecoder Decoder handle |
| @param out_guid_format Target guid format |
| @param dib Output dib |
| @param width Image width |
| @param height Image height |
| @return Returns 0 if successful, returns ERR otherwise |
| */ |
| static ERR |
| CopyPixels(PKImageDecode *pDecoder, PKPixelFormatGUID out_guid_format, FIBITMAP *dib, int width, int height) { |
| PKFormatConverter *pConverter = NULL; // pixel format converter |
| ERR error_code = 0; // error code as returned by the interface |
| BYTE *pb = NULL; // local buffer used for pixel format conversion |
| |
| // image dimensions |
| const PKRect rect = {0, 0, width, height}; |
| |
| try { |
| // get input file pixel format ... |
| PKPixelFormatGUID in_guid_format; |
| error_code = pDecoder->GetPixelFormat(pDecoder, &in_guid_format); |
| JXR_CHECK(error_code); |
| |
| // is a format conversion needed ? |
| |
| if(IsEqualGUID(out_guid_format, in_guid_format)) { |
| // no conversion, load bytes "as is" ... |
| |
| // get a pointer to dst pixel data |
| BYTE *dib_bits = FreeImage_GetBits(dib); |
| |
| // get dst pitch (count of BYTE for stride) |
| const unsigned cbStride = FreeImage_GetPitch(dib); |
| |
| // decode and copy bits to dst array |
| error_code = pDecoder->Copy(pDecoder, &rect, dib_bits, cbStride); |
| JXR_CHECK(error_code); |
| } |
| else { |
| // we need to use the conversion API ... |
| |
| // allocate the pixel format converter |
| error_code = PKCodecFactory_CreateFormatConverter(&pConverter); |
| JXR_CHECK(error_code); |
| |
| // set the conversion function |
| error_code = pConverter->Initialize(pConverter, pDecoder, NULL, out_guid_format); |
| JXR_CHECK(error_code); |
| |
| // get the maximum stride |
| unsigned cbStride = 0; |
| { |
| PKPixelInfo pPIFrom; |
| PKPixelInfo pPITo; |
| |
| pPIFrom.pGUIDPixFmt = &in_guid_format; |
| error_code = PixelFormatLookup(&pPIFrom, LOOKUP_FORWARD); |
| JXR_CHECK(error_code); |
| |
| pPITo.pGUIDPixFmt = &out_guid_format; |
| error_code = PixelFormatLookup(&pPITo, LOOKUP_FORWARD); |
| JXR_CHECK(error_code); |
| |
| unsigned cbStrideFrom = ((pPIFrom.cbitUnit + 7) >> 3) * width; |
| unsigned cbStrideTo = ((pPITo.cbitUnit + 7) >> 3) * width; |
| cbStride = MAX(cbStrideFrom, cbStrideTo); |
| } |
| |
| // allocate a local decoder / encoder buffer |
| error_code = PKAllocAligned((void **) &pb, cbStride * height, 128); |
| JXR_CHECK(error_code); |
| |
| // copy / convert pixels |
| error_code = pConverter->Copy(pConverter, &rect, pb, cbStride); |
| JXR_CHECK(error_code); |
| |
| // now copy pixels into the dib |
| const size_t line_size = FreeImage_GetLine(dib); |
| for(int y = 0; y < height; y++) { |
| BYTE *src_bits = (BYTE*)(pb + y * cbStride); |
| BYTE *dst_bits = (BYTE*)FreeImage_GetScanLine(dib, y); |
| memcpy(dst_bits, src_bits, line_size); |
| } |
| |
| // free the local buffer |
| PKFreeAligned((void **) &pb); |
| |
| // free the pixel format converter |
| PKFormatConverter_Release(&pConverter); |
| } |
| |
| // FreeImage DIB are upside-down relative to usual graphic conventions |
| FreeImage_FlipVertical(dib); |
| |
| // post-processing ... |
| // ------------------- |
| |
| // swap RGB as needed |
| |
| #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR |
| if(IsEqualGUID(out_guid_format, GUID_PKPixelFormat24bppRGB) || IsEqualGUID(out_guid_format, GUID_PKPixelFormat32bppRGB)) { |
| SwapRedBlue32(dib); |
| } |
| #elif FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB |
| if(IsEqualGUID(out_guid_format, GUID_PKPixelFormat24bppBGR) || IsEqualGUID(out_guid_format, GUID_PKPixelFormat32bppBGR)) { |
| SwapRedBlue32(dib); |
| } |
| #endif |
| |
| return WMP_errSuccess; |
| |
| } catch(...) { |
| // free the local buffer |
| PKFreeAligned((void **) &pb); |
| // free the pixel format converter |
| PKFormatConverter_Release(&pConverter); |
| |
| return error_code; |
| } |
| } |
| |
| // -------------------------------------------------------------------------- |
| |
| static FIBITMAP * DLL_CALLCONV |
| Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { |
| PKImageDecode *pDecoder = NULL; // decoder interface |
| ERR error_code = 0; // error code as returned by the interface |
| PKPixelFormatGUID guid_format; // loaded pixel format (== input file pixel format if no conversion needed) |
| |
| FREE_IMAGE_TYPE image_type = FIT_UNKNOWN; // input image type |
| unsigned bpp = 0; // input image bit depth |
| FIBITMAP *dib = NULL; |
| |
| // get the I/O stream wrapper |
| WMPStream *pDecodeStream = (WMPStream*)data; |
| |
| if(!handle || !pDecodeStream) { |
| return NULL; |
| } |
| |
| BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS; |
| |
| try { |
| int width, height; // image dimensions (in pixels) |
| |
| // create a JXR decoder interface and initialize function pointers with *_WMP functions |
| error_code = PKImageDecode_Create_WMP(&pDecoder); |
| JXR_CHECK(error_code); |
| |
| // attach the stream to the decoder ... |
| // ... then read the image container and the metadata |
| error_code = pDecoder->Initialize(pDecoder, pDecodeStream); |
| JXR_CHECK(error_code); |
| |
| // set decoder parameters |
| SetDecoderParameters(pDecoder, flags); |
| |
| // get dst image format specifications |
| unsigned red_mask = 0, green_mask = 0, blue_mask = 0; |
| error_code = GetInputPixelFormat(pDecoder, &guid_format, &image_type, &bpp, &red_mask, &green_mask, &blue_mask); |
| JXR_CHECK(error_code); |
| |
| // get image dimensions |
| pDecoder->GetSize(pDecoder, &width, &height); |
| |
| // allocate dst image |
| { |
| dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, bpp, red_mask, green_mask, blue_mask); |
| if(!dib) { |
| throw FI_MSG_ERROR_DIB_MEMORY; |
| } |
| if(FreeImage_GetBPP(dib) == 1) { |
| // BD_1 - build a FIC_MINISBLACK palette |
| RGBQUAD *pal = FreeImage_GetPalette(dib); |
| pal[0].rgbRed = pal[0].rgbGreen = pal[0].rgbBlue = 0; |
| pal[1].rgbRed = pal[1].rgbGreen = pal[1].rgbBlue = 255; |
| } |
| } |
| |
| // get image resolution |
| { |
| float resX, resY; // image resolution (in dots per inch) |
| // convert from English units, i.e. dots per inch to universal units, i.e. dots per meter |
| pDecoder->GetResolution(pDecoder, &resX, &resY); |
| FreeImage_SetDotsPerMeterX(dib, (unsigned)(resX / 0.0254F + 0.5F)); |
| FreeImage_SetDotsPerMeterY(dib, (unsigned)(resY / 0.0254F + 0.5F)); |
| } |
| |
| // get metadata & ICC profile |
| error_code = ReadMetadata(pDecoder, dib); |
| JXR_CHECK(error_code); |
| |
| if(header_only) { |
| // header only mode ... |
| |
| // free the decoder |
| pDecoder->Release(&pDecoder); |
| assert(pDecoder == NULL); |
| |
| return dib; |
| } |
| |
| // copy pixels into the dib, perform pixel conversion if needed |
| error_code = CopyPixels(pDecoder, guid_format, dib, width, height); |
| JXR_CHECK(error_code); |
| |
| // free the decoder |
| pDecoder->Release(&pDecoder); |
| assert(pDecoder == NULL); |
| |
| return dib; |
| |
| } catch (const char *message) { |
| // unload the dib |
| FreeImage_Unload(dib); |
| // free the decoder |
| pDecoder->Release(&pDecoder); |
| |
| if(NULL != message) { |
| FreeImage_OutputMessageProc(s_format_id, message); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| // ========================================================== |
| // Save |
| // ========================================================== |
| |
| /** |
| Configure compression parameters |
| |
| ImageQuality Q (BD==1) Q (BD==8) Q (BD==16) Q (BD==32F) Subsample Overlap |
| [0.0, 0.4] 8-IQ*5 (see table) (see table) (see table) 4:4:4 2 |
| (0.4, 0.8) 8-IQ*5 (see table) (see table) (see table) 4:4:4 1 |
| [0.8, 1.0) 8-IQ*5 (see table) (see table) (see table) 4:4:4 1 |
| [1.0, 1.0] 1 1 1 1 4:4:4 0 |
| |
| @param wmiSCP Encoder parameters |
| @param pixelInfo Image specifications |
| @param fltImageQuality Image output quality in [0..1), 1 means lossless |
| */ |
| static void |
| SetCompression(CWMIStrCodecParam *wmiSCP, const PKPixelInfo *pixelInfo, float fltImageQuality) { |
| if(fltImageQuality < 1.0F) { |
| // overlap |
| if(fltImageQuality >= 0.5F) { |
| wmiSCP->olOverlap = OL_ONE; |
| } else { |
| wmiSCP->olOverlap = OL_TWO; |
| } |
| // chroma sub-sampling |
| if(fltImageQuality >= 0.5F || pixelInfo->uBitsPerSample > 8) { |
| wmiSCP->cfColorFormat = YUV_444; |
| } else { |
| wmiSCP->cfColorFormat = YUV_420; |
| } |
| |
| // bit depth |
| if(pixelInfo->bdBitDepth == BD_1) { |
| wmiSCP->uiDefaultQPIndex = (U8)(8 - 5.0F * fltImageQuality + 0.5F); |
| } |
| else { |
| // remap [0.8, 0.866, 0.933, 1.0] to [0.8, 0.9, 1.0, 1.1] |
| // to use 8-bit DPK QP table (0.933 == Photoshop JPEG 100) |
| if(fltImageQuality > 0.8F && pixelInfo->bdBitDepth == BD_8 && wmiSCP->cfColorFormat != YUV_420 && wmiSCP->cfColorFormat != YUV_422) { |
| fltImageQuality = 0.8F + (fltImageQuality - 0.8F) * 1.5F; |
| } |
| |
| const int qi = (int) (10.0F * fltImageQuality); |
| const float qf = 10.0F * fltImageQuality - (float)qi; |
| |
| const int *pQPs = |
| (wmiSCP->cfColorFormat == YUV_420 || wmiSCP->cfColorFormat == YUV_422) ? |
| DPK_QPS_420[qi] : |
| (pixelInfo->bdBitDepth == BD_8 ? DPK_QPS_8[qi] : |
| (pixelInfo->bdBitDepth == BD_16 ? DPK_QPS_16[qi] : |
| (pixelInfo->bdBitDepth == BD_16F ? DPK_QPS_16f[qi] : |
| DPK_QPS_32f[qi]))); |
| |
| wmiSCP->uiDefaultQPIndex = (U8) (0.5F + (float) pQPs[0] * (1.0F - qf) + (float) (pQPs + 6)[0] * qf); |
| wmiSCP->uiDefaultQPIndexU = (U8) (0.5F + (float) pQPs[1] * (1.0F - qf) + (float) (pQPs + 6)[1] * qf); |
| wmiSCP->uiDefaultQPIndexV = (U8) (0.5F + (float) pQPs[2] * (1.0F - qf) + (float) (pQPs + 6)[2] * qf); |
| wmiSCP->uiDefaultQPIndexYHP = (U8) (0.5F + (float) pQPs[3] * (1.0F - qf) + (float) (pQPs + 6)[3] * qf); |
| wmiSCP->uiDefaultQPIndexUHP = (U8) (0.5F + (float) pQPs[4] * (1.0F - qf) + (float) (pQPs + 6)[4] * qf); |
| wmiSCP->uiDefaultQPIndexVHP = (U8) (0.5F + (float) pQPs[5] * (1.0F - qf) + (float) (pQPs + 6)[5] * qf); |
| } |
| } // fltImageQuality < 1.0F |
| else { |
| // lossless mode |
| wmiSCP->uiDefaultQPIndex = 1; |
| } |
| } |
| |
| /** |
| Set encoder parameters |
| @param wmiSCP Encoder parameters |
| @param pixelInfo Image specifications |
| @param flags FreeImage save flags |
| @param bHasAlpha TRUE if an alpha layer is present |
| */ |
| static void |
| SetEncoderParameters(CWMIStrCodecParam *wmiSCP, const PKPixelInfo *pixelInfo, int flags, BOOL bHasAlpha) { |
| float fltImageQuality = 1.0F; |
| |
| // all values have been set to zero by the API |
| // update default values for some attributes |
| wmiSCP->cfColorFormat = YUV_444; // color format |
| wmiSCP->bdBitDepth = BD_LONG; // internal bit depth |
| wmiSCP->bfBitstreamFormat = SPATIAL; // compressed image data in spatial order |
| wmiSCP->bProgressiveMode = FALSE; // sequential mode |
| wmiSCP->olOverlap = OL_ONE; // single level overlap processing |
| wmiSCP->cNumOfSliceMinus1H = 0; // # of horizontal slices |
| wmiSCP->cNumOfSliceMinus1V = 0; // # of vertical slices |
| wmiSCP->sbSubband = SB_ALL; // keep all subbands |
| wmiSCP->uAlphaMode = 0; // 0:no alpha 1: alpha only else: something + alpha |
| wmiSCP->uiDefaultQPIndex = 1; // quantization for grey or rgb layer(s), 1: lossless |
| wmiSCP->uiDefaultQPIndexAlpha = 1; // quantization for alpha layer, 1: lossless |
| |
| // process the flags |
| // ----------------- |
| |
| // progressive mode |
| if((flags & JXR_PROGRESSIVE) == JXR_PROGRESSIVE) { |
| // turn on progressive mode (instead of sequential mode) |
| wmiSCP->bProgressiveMode = TRUE; |
| } |
| |
| // quality in [0.01 - 1.0), 1.0 means lossless - default is 0.80 |
| int quality = flags & 0x7F; |
| if(quality == 0) { |
| // defaut to 0.80 |
| fltImageQuality = 0.8F; |
| } else if((flags & JXR_LOSSLESS) == JXR_LOSSLESS) { |
| fltImageQuality = 1.0F; |
| } else { |
| quality = (quality >= 100) ? 100 : quality; |
| fltImageQuality = quality / 100.0F; |
| } |
| SetCompression(wmiSCP, pixelInfo, fltImageQuality); |
| |
| // alpha compression |
| if(bHasAlpha) { |
| wmiSCP->uAlphaMode = 2; // encode with a planar alpha channel |
| } |
| } |
| |
| // -------------------------------------------------------------------------- |
| |
| static BOOL DLL_CALLCONV |
| Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) { |
| BOOL bIsFlipped = FALSE; // FreeImage DIB are upside-down relative to usual graphic conventions |
| PKPixelFormatGUID guid_format; // image format |
| PKPixelInfo pixelInfo; // image specifications |
| BOOL bHasAlpha = FALSE; // is alpha layer present ? |
| |
| PKImageEncode *pEncoder = NULL; // encoder interface |
| ERR error_code = 0; // error code as returned by the interface |
| |
| // get the I/O stream wrapper |
| WMPStream *pEncodeStream = (WMPStream*)data; |
| |
| if(!dib || !handle || !pEncodeStream) { |
| return FALSE; |
| } |
| |
| try { |
| // get image dimensions |
| unsigned width = FreeImage_GetWidth(dib); |
| unsigned height = FreeImage_GetHeight(dib); |
| |
| // check JPEG-XR limits |
| if((width < MB_WIDTH_PIXEL) || (height < MB_HEIGHT_PIXEL)) { |
| FreeImage_OutputMessageProc(s_format_id, "Unsupported image size: width x height = %d x %d", width, height); |
| throw (const char*)NULL; |
| } |
| |
| // get output pixel format |
| error_code = GetOutputPixelFormat(dib, &guid_format, &bHasAlpha); |
| JXR_CHECK(error_code); |
| pixelInfo.pGUIDPixFmt = &guid_format; |
| error_code = PixelFormatLookup(&pixelInfo, LOOKUP_FORWARD); |
| JXR_CHECK(error_code); |
| |
| // create a JXR encoder interface and initialize function pointers with *_WMP functions |
| error_code = PKImageEncode_Create_WMP(&pEncoder); |
| JXR_CHECK(error_code); |
| |
| // attach the stream to the encoder and set all encoder parameters to zero ... |
| error_code = pEncoder->Initialize(pEncoder, pEncodeStream, &pEncoder->WMP.wmiSCP, sizeof(CWMIStrCodecParam)); |
| JXR_CHECK(error_code); |
| |
| // ... then configure the encoder |
| SetEncoderParameters(&pEncoder->WMP.wmiSCP, &pixelInfo, flags, bHasAlpha); |
| |
| // set pixel format |
| pEncoder->SetPixelFormat(pEncoder, guid_format); |
| |
| // set image size |
| pEncoder->SetSize(pEncoder, width, height); |
| |
| // set resolution (convert from universal units to English units) |
| float resX = (float)(unsigned)(0.5F + 0.0254F * FreeImage_GetDotsPerMeterX(dib)); |
| float resY = (float)(unsigned)(0.5F + 0.0254F * FreeImage_GetDotsPerMeterY(dib)); |
| pEncoder->SetResolution(pEncoder, resX, resY); |
| |
| // set metadata |
| WriteMetadata(pEncoder, dib); |
| |
| // write metadata & pixels |
| // ----------------------- |
| |
| // dib coordinates are upside-down relative to usual conventions |
| bIsFlipped = FreeImage_FlipVertical(dib); |
| |
| // get a pointer to dst pixel data |
| BYTE *dib_bits = FreeImage_GetBits(dib); |
| |
| // get dst pitch (count of BYTE for stride) |
| const unsigned cbStride = FreeImage_GetPitch(dib); |
| |
| // write metadata + pixels on output |
| error_code = pEncoder->WritePixels(pEncoder, height, dib_bits, cbStride); |
| JXR_CHECK(error_code); |
| |
| // recover dib coordinates |
| FreeImage_FlipVertical(dib); |
| |
| // free the encoder |
| pEncoder->Release(&pEncoder); |
| assert(pEncoder == NULL); |
| |
| return TRUE; |
| |
| } catch (const char *message) { |
| if(bIsFlipped) { |
| // recover dib coordinates |
| FreeImage_FlipVertical(dib); |
| } |
| if(pEncoder) { |
| // free the encoder |
| pEncoder->Release(&pEncoder); |
| assert(pEncoder == NULL); |
| } |
| if(NULL != message) { |
| FreeImage_OutputMessageProc(s_format_id, message); |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| // ========================================================== |
| // Init |
| // ========================================================== |
| |
| void DLL_CALLCONV |
| InitJXR(Plugin *plugin, int format_id) { |
| s_format_id = format_id; |
| |
| plugin->format_proc = Format; |
| plugin->description_proc = Description; |
| plugin->extension_proc = Extension; |
| plugin->regexpr_proc = RegExpr; |
| plugin->open_proc = Open; |
| plugin->close_proc = Close; |
| plugin->pagecount_proc = NULL; |
| plugin->pagecapability_proc = NULL; |
| plugin->load_proc = Load; |
| plugin->save_proc = Save; |
| plugin->validate_proc = Validate; |
| plugin->mime_proc = MimeType; |
| plugin->supports_export_bpp_proc = SupportsExportDepth; |
| plugin->supports_export_type_proc = SupportsExportType; |
| plugin->supports_icc_profiles_proc = SupportsICCProfiles; |
| plugin->supports_no_pixels_proc = SupportsNoPixels; |
| } |
| |