diff --git a/core/dplugins/dimg/heif/dimgheifloader.cpp b/core/dplugins/dimg/heif/dimgheifloader.cpp index c046d082cb..60b185bd5d 100644 --- a/core/dplugins/dimg/heif/dimgheifloader.cpp +++ b/core/dplugins/dimg/heif/dimgheifloader.cpp @@ -1,74 +1,78 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-09-26 * Description : A HEIF IO file for DImg framework * * Copyright (C) 2019 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dimgheifloader.h" // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "dimg.h" #include "dimgloaderobserver.h" namespace Digikam { DImgHEIFLoader::DImgHEIFLoader(DImg* const image) : DImgLoader(image) { m_hasAlpha = false; m_sixteenBit = false; m_observer = nullptr; } +DImgHEIFLoader::~DImgHEIFLoader() +{ +} + bool DImgHEIFLoader::hasAlpha() const { return m_hasAlpha; } bool DImgHEIFLoader::sixteenBit() const { return m_sixteenBit; } bool DImgHEIFLoader::isReadOnly() const { #ifdef HAVE_X265 return false; #else return true; #endif } bool DImgHEIFLoader::isHeifSuccess(struct heif_error* const error) { if (error->code == 0) { return true; } qWarning() << "Error while processing HEIC image:" << error->message; return false; } } // namespace Digikam diff --git a/core/dplugins/dimg/heif/dimgheifloader.h b/core/dplugins/dimg/heif/dimgheifloader.h index b64db2c262..4ec1c70040 100644 --- a/core/dplugins/dimg/heif/dimgheifloader.h +++ b/core/dplugins/dimg/heif/dimgheifloader.h @@ -1,89 +1,90 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-09-26 * Description : A HEIF IO file for DImg framework * * Copyright (C) 2019 by Gilles Caulier * * Other HEIF loader implementions: * https://github.com/KDE/krita/tree/master/plugins/impex/heif * https://github.com/jakar/qt-heif-image-plugin * https://github.com/ImageMagick/ImageMagick/blob/master/coders/heic.c * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_DIMG_HEIF_LOADER_H #define DIGIKAM_DIMG_HEIF_LOADER_H // Local includes #include "dimg.h" #include "dimgloader.h" #include "digikam_export.h" #include "heif.h" using namespace Digikam; namespace Digikam { class DIGIKAM_EXPORT DImgHEIFLoader : public DImgLoader { public: explicit DImgHEIFLoader(DImg* const image); + ~DImgHEIFLoader(); bool load(const QString& filePath, DImgLoaderObserver* const observer) override; bool save(const QString& filePath, DImgLoaderObserver* const observer) override; virtual bool hasAlpha() const override; virtual bool sixteenBit() const override; virtual bool isReadOnly() const override; /** * Determine libx265 encoder bits depth capability: 8=standard, 10, 12, or more. * Return -1 if encoder instance is not found. */ static int x265MaxBitsDepth(); private: bool isHeifSuccess(struct heif_error* const error); // Read operations bool readHEICColorProfile(struct heif_image_handle* const image_handle); bool readHEICMetadata(struct heif_image_handle* const image_handle); bool readHEICImageByID(struct heif_context* const heif_context, heif_item_id image_id); // Save operations bool saveHEICColorProfile(struct heif_image* const image); bool saveHEICMetadata(struct heif_context* const heif_context, struct heif_image_handle* const image_handle); private: bool m_sixteenBit; bool m_hasAlpha; DImgLoaderObserver* m_observer; }; } // namespace Digikam #endif // DIGIKAM_DIMG_HEIF_LOADER_H diff --git a/core/dplugins/dimg/jpeg2000/CMakeLists.txt b/core/dplugins/dimg/jpeg2000/CMakeLists.txt index 5b0df617ae..fb254d8257 100644 --- a/core/dplugins/dimg/jpeg2000/CMakeLists.txt +++ b/core/dplugins/dimg/jpeg2000/CMakeLists.txt @@ -1,25 +1,27 @@ # # Copyright (c) 2015-2019 by Gilles Caulier, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. include(MacroDPlugins) include_directories($ $ $ $ $ ) set(dimgjpeg2000plugin_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/dimgjpeg2000plugin.cpp ${CMAKE_CURRENT_SOURCE_DIR}/dimgjpeg2000loader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dimgjpeg2000loader_load.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dimgjpeg2000loader_save.cpp ) DIGIKAM_ADD_DIMG_PLUGIN(NAME JPEG2000 SOURCES ${dimgjpeg2000plugin_SRCS} DEPENDS ${JASPER_LIBRARIES} ) diff --git a/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.cpp b/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.cpp index d1654ce31e..b576c8b112 100644 --- a/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.cpp +++ b/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.cpp @@ -1,898 +1,91 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-06-14 * Description : A JPEG-2000 IO file for DImg framework * * Copyright (C) 2006-2019 by Gilles Caulier * - * This implementation use Jasper API - * library : http://www.ece.uvic.ca/~mdadams/jasper - * Other JPEG-2000 encoder-decoder : http://www.openjpeg.org - * - * Others Linux JPEG-2000 Loader implementation: - * https://github.com/ImageMagick/ImageMagick/blob/master/coders/jp2.c - * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dimgjpeg2000loader.h" // Qt includes #include #include #include // Local includes #include "digikam_config.h" #include "dimg.h" #include "digikam_debug.h" #include "dimgloaderobserver.h" #include "dmetadata.h" // Jasper includes #ifndef Q_CC_MSVC extern "C" { #endif #if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wshift-negative-value" #endif #include #if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif #ifndef Q_CC_MSVC } #endif namespace DigikamJPEG2000DImgPlugin { DImgJPEG2000Loader::DImgJPEG2000Loader(DImg* const image) : DImgLoader(image) { m_hasAlpha = false; m_sixteenBit = false; } -bool DImgJPEG2000Loader::load(const QString& filePath, DImgLoaderObserver* const observer) -{ - readMetadata(filePath); - - FILE* const file = fopen(QFile::encodeName(filePath).constData(), "rb"); - - if (!file) - { - loadingFailed(); - return false; - } - - unsigned char header[9]; - - if (fread(&header, 9, 1, file) != 1) - { - fclose(file); - loadingFailed(); - return false; - } - - fclose(file); - - unsigned char jp2ID[5] = { 0x6A, 0x50, 0x20, 0x20, 0x0D, }; - unsigned char jpcID[2] = { 0xFF, 0x4F }; - - if (memcmp(&header[4], &jp2ID, 5) != 0 && - memcmp(&header, &jpcID, 2) != 0) - { - // not a jpeg2000 file - loadingFailed(); - return false; - } - - imageSetAttribute(QLatin1String("format"), QLatin1String("JP2")); - - if (!(m_loadFlags & LoadImageData) && !(m_loadFlags & LoadICCData)) - { - // libjasper will load the full image in memory already when calling jas_image_decode. - // This is bad when scanning. See bugs 215458 and 195583. - // FIXME: Use Exiv2 to extract this info - DMetadata metadata(filePath); - QSize size = metadata.getItemDimensions(); - - if (size.isValid()) - { - imageWidth() = size.width(); - imageHeight() = size.height(); - } - - return true; - } - - // ------------------------------------------------------------------- - // Initialize JPEG 2000 API. - - long i, x, y; - int components[4]; - unsigned int maximum_component_depth, scale[4], x_step[4], y_step[4]; - unsigned long number_components; - - jas_image_t* jp2_image = nullptr; - jas_stream_t* jp2_stream = nullptr; - jas_matrix_t* pixels[4]; - - int init = jas_init(); - - if (init != 0) - { - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to init JPEG2000 decoder"; - loadingFailed(); - return false; - } - - jp2_stream = jas_stream_fopen(QFile::encodeName(filePath).constData(), "rb"); - - if (jp2_stream == nullptr) - { - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to open JPEG2000 stream"; - loadingFailed(); - return false; - } - - int fmt = jas_image_strtofmt(QByteArray("jp2").data()); - jp2_image = jas_image_decode(jp2_stream, fmt, nullptr); - - if (jp2_image == nullptr) - { - jas_stream_close(jp2_stream); - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to decode JPEG2000 image"; - loadingFailed(); - return false; - } - - jas_stream_close(jp2_stream); - - // some pseudo-progress - if (observer) - { - observer->progressInfo(m_image, 0.1F); - } - - // ------------------------------------------------------------------- - // Check color space. - - int colorModel; - - switch (jas_clrspc_fam(jas_image_clrspc(jp2_image))) - { - case JAS_CLRSPC_FAM_RGB: - { - components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_R); - components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_G); - components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_B); - - if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0)) - { - jas_image_destroy(jp2_image); - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JPEG2000 image : Missing Image Channel"; - loadingFailed(); - return false; - } - - number_components = 3; - components[3] = jas_image_getcmptbytype(jp2_image, 3); - - if (components[3] > 0) - { - m_hasAlpha = true; - ++number_components; - } - - colorModel = DImg::RGB; - break; - } - - case JAS_CLRSPC_FAM_GRAY: - { - components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_GRAY_Y); - - if (components[0] < 0) - { - jas_image_destroy(jp2_image); - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JP2000 image : Missing Image Channel"; - loadingFailed(); - return false; - } - - number_components = 1; - colorModel = DImg::GRAYSCALE; - break; - } - - case JAS_CLRSPC_FAM_YCBCR: - { - components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_Y); - components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CB); - components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CR); - - if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0)) - { - jas_image_destroy(jp2_image); - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JP2000 image : Missing Image Channel"; - loadingFailed(); - return false; - } - - number_components = 3; - components[3] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_UNKNOWN); - - if (components[3] > 0) - { - m_hasAlpha = true; - ++number_components; - } - - // FIXME : image->colorspace=YCbCrColorspace; - colorModel = DImg::YCBCR; - break; - } - - default: - { - jas_image_destroy(jp2_image); - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JP2000 image : Colorspace Model Is Not Supported"; - loadingFailed(); - return false; - } - } - - // ------------------------------------------------------------------- - // Check image geometry. - - imageWidth() = jas_image_width(jp2_image); - imageHeight() = jas_image_height(jp2_image); - - for (i = 0; i < (long)number_components; ++i) - { - if ((((jas_image_cmptwidth(jp2_image, components[i])* - jas_image_cmpthstep(jp2_image, components[i])) != (long)imageWidth())) || - (((jas_image_cmptheight(jp2_image, components[i])* - jas_image_cmptvstep(jp2_image, components[i])) != (long)imageHeight())) || - (jas_image_cmpttlx(jp2_image, components[i]) != 0) || - (jas_image_cmpttly(jp2_image, components[i]) != 0) || - (jas_image_cmptsgnd(jp2_image, components[i]) != false)) - { - jas_image_destroy(jp2_image); - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JPEG2000 image : Irregular Channel Geometry Not Supported"; - loadingFailed(); - return false; - } - - x_step[i] = jas_image_cmpthstep(jp2_image, components[i]); - y_step[i] = jas_image_cmptvstep(jp2_image, components[i]); - } - - // ------------------------------------------------------------------- - // Get image format. - - maximum_component_depth = 0; - - for (i = 0; i < (long)number_components; ++i) - { - maximum_component_depth = qMax((long)jas_image_cmptprec(jp2_image, components[i]), - (long)maximum_component_depth); - pixels[i] = jas_matrix_create(1, ((unsigned int)imageWidth()) / x_step[i]); - - if (!pixels[i]) - { - jas_image_destroy(jp2_image); - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error decoding JPEG2000 image data : Memory Allocation Failed"; - loadingFailed(); - return false; - } - } - - if (maximum_component_depth > 8) - { - m_sixteenBit = true; - } - - for (i = 0 ; i < (long)number_components ; ++i) - { - scale[i] = 1; - int prec = jas_image_cmptprec(jp2_image, components[i]); - - if (m_sixteenBit && prec < 16) - { - scale[i] = (1 << (16 - jas_image_cmptprec(jp2_image, components[i]))); - } - } - - // ------------------------------------------------------------------- - // Get image data. - - QScopedArrayPointer data; - - if (m_loadFlags & LoadImageData) - { - if (m_sixteenBit) // 16 bits image. - { - data.reset(new_failureTolerant(imageWidth(), imageHeight(), 8)); - } - else - { - data.reset(new_failureTolerant(imageWidth(), imageHeight(), 4)); - } - - if (!data) - { - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error decoding JPEG2000 image data : Memory Allocation Failed"; - jas_image_destroy(jp2_image); - - for (i = 0 ; i < (long)number_components ; ++i) - { - jas_matrix_destroy(pixels[i]); - } - - jas_cleanup(); - loadingFailed(); - return false; - } - - uint checkPoint = 0; - uchar* dst = data.data(); - unsigned short* dst16 = reinterpret_cast(data.data()); - - for (y = 0 ; y < (long)imageHeight() ; ++y) - { - for (i = 0 ; i < (long)number_components; ++i) - { - int ret = jas_image_readcmpt(jp2_image, (short)components[i], 0, - ((unsigned int) y) / y_step[i], - ((unsigned int) imageWidth()) / x_step[i], - 1, pixels[i]); - - if (ret != 0) - { - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error decoding JPEG2000 image data"; - jas_image_destroy(jp2_image); - - for (i = 0 ; i < (long)number_components ; ++i) - { - jas_matrix_destroy(pixels[i]); - } - - jas_cleanup(); - loadingFailed(); - return false; - } - } - - switch (number_components) - { - case 1: // Grayscale. - { - if (!m_sixteenBit) // 8 bits image. - { - for (x = 0 ; x < (long)imageWidth() ; ++x) - { - dst[0] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); - dst[1] = dst[0]; - dst[2] = dst[0]; - dst[3] = 0xFF; - - dst += 4; - } - } - else // 16 bits image. - { - for (x = 0 ; x < (long)imageWidth() ; ++x) - { - dst16[0] = (unsigned short)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); - dst16[1] = dst16[0]; - dst16[2] = dst16[0]; - dst16[3] = 0xFFFF; - - dst16 += 4; - } - } - - break; - } - - case 3: // RGB. - { - if (!m_sixteenBit) // 8 bits image. - { - for (x = 0 ; x < (long)imageWidth() ; ++x) - { - // Blue - dst[0] = (uchar)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); - // Green - dst[1] = (uchar)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); - // Red - dst[2] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); - // Alpha - dst[3] = 0xFF; - - dst += 4; - } - } - else // 16 bits image. - { - for (x = 0 ; x < (long)imageWidth() ; ++x) - { - // Blue - dst16[0] = (unsigned short)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); - // Green - dst16[1] = (unsigned short)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); - // Red - dst16[2] = (unsigned short)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); - // Alpha - dst16[3] = 0xFFFF; - - dst16 += 4; - } - } - - break; - } - - case 4: // RGBA. - { - if (!m_sixteenBit) // 8 bits image. - { - for (x = 0 ; x < (long)imageWidth() ; ++x) - { - // Blue - dst[0] = (uchar)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); - // Green - dst[1] = (uchar)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); - // Red - dst[2] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); - // Alpha - dst[3] = (uchar)(scale[3] * jas_matrix_getv(pixels[3], x / x_step[3])); - - dst += 4; - } - } - else // 16 bits image. - { - for (x = 0 ; x < (long)imageWidth() ; ++x) - { - // Blue - dst16[0] = (unsigned short)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); - // Green - dst16[1] = (unsigned short)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); - // Red - dst16[2] = (unsigned short)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); - // Alpha - dst16[3] = (unsigned short)(scale[3] * jas_matrix_getv(pixels[3], x / x_step[3])); - - dst16 += 4; - } - } - - break; - } - } - - // use 0-10% and 90-100% for pseudo-progress - if (observer && y >= (long)checkPoint) - { - checkPoint += granularity(observer, y, 0.8F); - - if (!observer->continueQuery(m_image)) - { - jas_image_destroy(jp2_image); - - for (i = 0 ; i < (long)number_components ; ++i) - { - jas_matrix_destroy(pixels[i]); - } - - jas_cleanup(); - - loadingFailed(); - return false; - } - - observer->progressInfo(m_image, 0.1 + (0.8 * (((float)y) / ((float)imageHeight())))); - } - } - } - - // ------------------------------------------------------------------- - // Get ICC color profile. - - if (m_loadFlags & LoadICCData) - { - jas_iccprof_t* icc_profile = nullptr; - jas_stream_t* icc_stream = nullptr; - jas_cmprof_t* cm_profile = nullptr; - - // To prevent cppcheck warnings. - (void)icc_profile; - (void)icc_stream; - (void)cm_profile; - - cm_profile = jas_image_cmprof(jp2_image); - - if (cm_profile != nullptr) - { - icc_profile = jas_iccprof_createfromcmprof(cm_profile); - } - - if (icc_profile != nullptr) - { - icc_stream = jas_stream_memopen(nullptr, 0); - - if (icc_stream != nullptr) - { - if (jas_iccprof_save(icc_profile, icc_stream) == 0) - { - if (jas_stream_flush(icc_stream) == 0) - { - jas_stream_memobj_t* blob = (jas_stream_memobj_t*) icc_stream->obj_; - QByteArray profile_rawdata; - profile_rawdata.resize(blob->len_); - memcpy(profile_rawdata.data(), blob->buf_, blob->len_); - imageSetIccProfile(IccProfile(profile_rawdata)); - jas_stream_close(icc_stream); - } - } - } - } - else - { - // If ICC profile is null, check Exif metadata. - checkExifWorkingColorSpace(); - } - } - - if (observer) - { - observer->progressInfo(m_image, 1.0); - } - - imageData() = data.take(); - imageSetAttribute(QLatin1String("format"), QLatin1String("JP2")); - imageSetAttribute(QLatin1String("originalColorModel"), colorModel); - imageSetAttribute(QLatin1String("originalBitDepth"), maximum_component_depth); - imageSetAttribute(QLatin1String("originalSize"), QSize(imageWidth(), imageHeight())); - - jas_image_destroy(jp2_image); - - for (i = 0 ; i < (long)number_components ; ++i) - { - jas_matrix_destroy(pixels[i]); - } - - jas_cleanup(); - - return true; -} - -bool DImgJPEG2000Loader::save(const QString& filePath, DImgLoaderObserver* const observer) +DImgJPEG2000Loader::~DImgJPEG2000Loader() { - FILE* const file = fopen(QFile::encodeName(filePath).constData(), "wb"); - - if (!file) - { - return false; - } - - fclose(file); - - // ------------------------------------------------------------------- - // Initialize JPEG 2000 API. - - long i, x, y; - unsigned long number_components; - - jas_image_t* jp2_image = nullptr; - jas_stream_t* jp2_stream = nullptr; - jas_matrix_t* pixels[4]; - jas_image_cmptparm_t component_info[4]; - - int init = jas_init(); - - if (init != 0) - { - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to init JPEG2000 decoder"; - return false; - } - - jp2_stream = jas_stream_fopen(QFile::encodeName(filePath).constData(), "wb"); - - if (jp2_stream == nullptr) - { - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to open JPEG2000 stream"; - return false; - } - - number_components = imageHasAlpha() ? 4 : 3; - - for (i = 0 ; i < (long)number_components ; ++i) - { - component_info[i].tlx = 0; - component_info[i].tly = 0; - component_info[i].hstep = 1; - component_info[i].vstep = 1; - component_info[i].width = imageWidth(); - component_info[i].height = imageHeight(); - component_info[i].prec = imageBitsDepth(); - component_info[i].sgnd = false; - } - - jp2_image = jas_image_create(number_components, component_info, JAS_CLRSPC_UNKNOWN); - - if (jp2_image == nullptr) - { - jas_stream_close(jp2_stream); - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to create JPEG2000 image"; - return false; - } - - if (observer) - { - observer->progressInfo(m_image, 0.1F); - } - - // ------------------------------------------------------------------- - // Check color space. - - if (number_components >= 3) // RGB & RGBA - { - // Alpha Channel - if (number_components == 4) - { - jas_image_setcmpttype(jp2_image, 3, JAS_IMAGE_CT_OPACITY); - } - - jas_image_setclrspc(jp2_image, JAS_CLRSPC_SRGB); - jas_image_setcmpttype(jp2_image, 0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R)); - jas_image_setcmpttype(jp2_image, 1, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G)); - jas_image_setcmpttype(jp2_image, 2, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B)); - } - - // ------------------------------------------------------------------- - // Set ICC color profile. - - // FIXME : doesn't work yet! - - jas_cmprof_t* cm_profile = nullptr; - jas_iccprof_t* icc_profile = nullptr; - - // To prevent cppcheck warnings. - (void)cm_profile; - (void)icc_profile; - - QByteArray profile_rawdata = m_image->getIccProfile().data(); - icc_profile = jas_iccprof_createfrombuf((uchar*)profile_rawdata.data(), profile_rawdata.size()); - - if (icc_profile != nullptr) - { - cm_profile = jas_cmprof_createfromiccprof(icc_profile); - - if (cm_profile != nullptr) - { - jas_image_setcmprof(jp2_image, cm_profile); - //enable when it works: purgeExifWorkingColorSpace(); - } - } - - // workaround: - storeColorProfileInMetadata(); - - // ------------------------------------------------------------------- - // Convert to JPEG 2000 pixels. - - for (i = 0 ; i < (long)number_components ; ++i) - { - pixels[i] = jas_matrix_create(1, (unsigned int)imageWidth()); - - if (pixels[i] == nullptr) - { - for (x = 0 ; x < i ; ++x) - { - jas_matrix_destroy(pixels[x]); - } - - jas_image_destroy(jp2_image); - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error encoding JPEG2000 image data : Memory Allocation Failed"; - return false; - } - } - - unsigned char* data = imageData(); - unsigned char* pixel = nullptr; - unsigned short r = 0; - unsigned short g = 0; - unsigned short b = 0; - unsigned short a = 0; - uint checkpoint = 0; - - for (y = 0 ; y < (long)imageHeight() ; ++y) - { - if (observer && y == (long)checkpoint) - { - checkpoint += granularity(observer, imageHeight(), 0.8F); - - if (!observer->continueQuery(m_image)) - { - jas_image_destroy(jp2_image); - - for (i = 0 ; i < (long)number_components ; ++i) - { - jas_matrix_destroy(pixels[i]); - } - - jas_cleanup(); - - return false; - } - - observer->progressInfo(m_image, 0.1 + (0.8 * (((float)y) / ((float)imageHeight())))); - } - - for (x = 0 ; x < (long)imageWidth() ; ++x) - { - pixel = &data[((y * imageWidth()) + x) * imageBytesDepth()]; - - if (imageSixteenBit()) // 16 bits image. - { - b = (unsigned short)(pixel[0] + 256 * pixel[1]); - g = (unsigned short)(pixel[2] + 256 * pixel[3]); - r = (unsigned short)(pixel[4] + 256 * pixel[5]); - - if (imageHasAlpha()) - { - a = (unsigned short)(pixel[6] + 256 * pixel[7]); - } - } - else // 8 bits image. - { - b = (unsigned short)pixel[0]; - g = (unsigned short)pixel[1]; - r = (unsigned short)pixel[2]; - - if (imageHasAlpha()) - { - a = (unsigned short)(pixel[3]); - } - } - - jas_matrix_setv(pixels[0], x, r); - jas_matrix_setv(pixels[1], x, g); - jas_matrix_setv(pixels[2], x, b); - - if (number_components > 3) - { - jas_matrix_setv(pixels[3], x, a); - } - } - - for (i = 0 ; i < (long)number_components ; ++i) - { - int ret = jas_image_writecmpt(jp2_image, (short) i, 0, (unsigned int)y, - (unsigned int)imageWidth(), 1, pixels[i]); - - if (ret != 0) - { - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error encoding JPEG2000 image data"; - - jas_image_destroy(jp2_image); - - for (i = 0 ; i < (long)number_components ; ++i) - { - jas_matrix_destroy(pixels[i]); - } - - jas_cleanup(); - return false; - } - } - } - - QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); - int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; - - if (quality < 0) - { - quality = 90; - } - - if (quality > 100) - { - quality = 100; - } - - // optstr: - // - rate=#B => the resulting file size is about # bytes - // - rate=0.0 .. 1.0 => the resulting file size is about the factor times - // the uncompressed size - // use sprintf for locale-aware string - char rateBuffer[16]; - sprintf(rateBuffer, "rate=%.2g", (quality / 100.0)); - - qCDebug(DIGIKAM_DIMG_LOG_JP2K) << "JPEG2000 quality: " << quality; - qCDebug(DIGIKAM_DIMG_LOG_JP2K) << "JPEG2000 " << rateBuffer; - - int fmt = jas_image_strtofmt(QByteArray("jp2").data()); - int ret = jas_image_encode(jp2_image, jp2_stream, fmt, rateBuffer); - - if (ret != 0) - { - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to encode JPEG2000 image"; - - jas_image_destroy(jp2_image); - jas_stream_close(jp2_stream); - - for (i = 0 ; i < (long)number_components ; ++i) - { - jas_matrix_destroy(pixels[i]); - } - - jas_cleanup(); - - return false; - } - - if (observer) - { - observer->progressInfo(m_image, 1.0); - } - - jas_image_destroy(jp2_image); - jas_stream_close(jp2_stream); - - for (i = 0 ; i < (long)number_components ; ++i) - { - jas_matrix_destroy(pixels[i]); - } - - jas_cleanup(); - - imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("JP2")); - saveMetadata(filePath); - - return true; } bool DImgJPEG2000Loader::hasAlpha() const { return m_hasAlpha; } bool DImgJPEG2000Loader::sixteenBit() const { return m_sixteenBit; } bool DImgJPEG2000Loader::isReadOnly() const { return false; } } // namespace DigikamJPEG2000DImgPlugin diff --git a/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.h b/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.h index c6030c82c1..ee08823b33 100644 --- a/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.h +++ b/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.h @@ -1,60 +1,64 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-06-14 * Description : A JPEG-2000 IO file for DImg framework * * Copyright (C) 2006-2019 by Gilles Caulier * + * Others Linux JPEG-2000 Loader implementation: + * https://github.com/ImageMagick/ImageMagick/blob/master/coders/jp2.c + * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_DIMG_JPEG_2000_LOADER_H #define DIGIKAM_DIMG_JPEG_2000_LOADER_H // Local includes #include "dimg.h" #include "dimgloader.h" #include "digikam_export.h" using namespace Digikam; namespace DigikamJPEG2000DImgPlugin { class DIGIKAM_EXPORT DImgJPEG2000Loader : public DImgLoader { public: explicit DImgJPEG2000Loader(DImg* const image); + ~DImgJPEG2000Loader(); bool load(const QString& filePath, DImgLoaderObserver* const observer) override; bool save(const QString& filePath, DImgLoaderObserver* const observer) override; virtual bool hasAlpha() const override; virtual bool sixteenBit() const override; virtual bool isReadOnly() const override; private: bool m_sixteenBit; bool m_hasAlpha; }; } // namespace DigikamJPEG2000DImgPlugin #endif // DIGIKAM_DIMG_JPEG_2000_LOADER_H diff --git a/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.cpp b/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader_load.cpp similarity index 67% copy from core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.cpp copy to core/dplugins/dimg/jpeg2000/dimgjpeg2000loader_load.cpp index d1654ce31e..c112be4c05 100644 --- a/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.cpp +++ b/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader_load.cpp @@ -1,898 +1,582 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-06-14 - * Description : A JPEG-2000 IO file for DImg framework + * Description : A JPEG-2000 IO file for DImg framework- load operations * * Copyright (C) 2006-2019 by Gilles Caulier * - * This implementation use Jasper API - * library : http://www.ece.uvic.ca/~mdadams/jasper - * Other JPEG-2000 encoder-decoder : http://www.openjpeg.org - * - * Others Linux JPEG-2000 Loader implementation: - * https://github.com/ImageMagick/ImageMagick/blob/master/coders/jp2.c - * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dimgjpeg2000loader.h" // Qt includes #include #include #include // Local includes #include "digikam_config.h" #include "dimg.h" #include "digikam_debug.h" #include "dimgloaderobserver.h" #include "dmetadata.h" // Jasper includes #ifndef Q_CC_MSVC extern "C" { #endif #if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wshift-negative-value" #endif #include #if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif #ifndef Q_CC_MSVC } #endif namespace DigikamJPEG2000DImgPlugin { -DImgJPEG2000Loader::DImgJPEG2000Loader(DImg* const image) - : DImgLoader(image) -{ - m_hasAlpha = false; - m_sixteenBit = false; -} - bool DImgJPEG2000Loader::load(const QString& filePath, DImgLoaderObserver* const observer) { readMetadata(filePath); FILE* const file = fopen(QFile::encodeName(filePath).constData(), "rb"); if (!file) { loadingFailed(); return false; } unsigned char header[9]; if (fread(&header, 9, 1, file) != 1) { fclose(file); loadingFailed(); return false; } fclose(file); unsigned char jp2ID[5] = { 0x6A, 0x50, 0x20, 0x20, 0x0D, }; unsigned char jpcID[2] = { 0xFF, 0x4F }; if (memcmp(&header[4], &jp2ID, 5) != 0 && memcmp(&header, &jpcID, 2) != 0) { // not a jpeg2000 file loadingFailed(); return false; } imageSetAttribute(QLatin1String("format"), QLatin1String("JP2")); if (!(m_loadFlags & LoadImageData) && !(m_loadFlags & LoadICCData)) { // libjasper will load the full image in memory already when calling jas_image_decode. // This is bad when scanning. See bugs 215458 and 195583. // FIXME: Use Exiv2 to extract this info DMetadata metadata(filePath); QSize size = metadata.getItemDimensions(); if (size.isValid()) { imageWidth() = size.width(); imageHeight() = size.height(); } return true; } // ------------------------------------------------------------------- // Initialize JPEG 2000 API. long i, x, y; int components[4]; unsigned int maximum_component_depth, scale[4], x_step[4], y_step[4]; unsigned long number_components; jas_image_t* jp2_image = nullptr; jas_stream_t* jp2_stream = nullptr; jas_matrix_t* pixels[4]; int init = jas_init(); if (init != 0) { qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to init JPEG2000 decoder"; loadingFailed(); return false; } jp2_stream = jas_stream_fopen(QFile::encodeName(filePath).constData(), "rb"); if (jp2_stream == nullptr) { qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to open JPEG2000 stream"; loadingFailed(); return false; } int fmt = jas_image_strtofmt(QByteArray("jp2").data()); jp2_image = jas_image_decode(jp2_stream, fmt, nullptr); if (jp2_image == nullptr) { jas_stream_close(jp2_stream); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to decode JPEG2000 image"; loadingFailed(); return false; } jas_stream_close(jp2_stream); // some pseudo-progress if (observer) { observer->progressInfo(m_image, 0.1F); } // ------------------------------------------------------------------- // Check color space. int colorModel; switch (jas_clrspc_fam(jas_image_clrspc(jp2_image))) { case JAS_CLRSPC_FAM_RGB: { components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_R); components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_G); components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_B); if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0)) { jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JPEG2000 image : Missing Image Channel"; loadingFailed(); return false; } number_components = 3; components[3] = jas_image_getcmptbytype(jp2_image, 3); if (components[3] > 0) { m_hasAlpha = true; ++number_components; } colorModel = DImg::RGB; break; } case JAS_CLRSPC_FAM_GRAY: { components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_GRAY_Y); if (components[0] < 0) { jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JP2000 image : Missing Image Channel"; loadingFailed(); return false; } number_components = 1; colorModel = DImg::GRAYSCALE; break; } case JAS_CLRSPC_FAM_YCBCR: { components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_Y); components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CB); components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CR); if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0)) { jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JP2000 image : Missing Image Channel"; loadingFailed(); return false; } number_components = 3; components[3] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_UNKNOWN); if (components[3] > 0) { m_hasAlpha = true; ++number_components; } // FIXME : image->colorspace=YCbCrColorspace; colorModel = DImg::YCBCR; break; } default: { jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JP2000 image : Colorspace Model Is Not Supported"; loadingFailed(); return false; } } // ------------------------------------------------------------------- // Check image geometry. imageWidth() = jas_image_width(jp2_image); imageHeight() = jas_image_height(jp2_image); for (i = 0; i < (long)number_components; ++i) { if ((((jas_image_cmptwidth(jp2_image, components[i])* jas_image_cmpthstep(jp2_image, components[i])) != (long)imageWidth())) || (((jas_image_cmptheight(jp2_image, components[i])* jas_image_cmptvstep(jp2_image, components[i])) != (long)imageHeight())) || (jas_image_cmpttlx(jp2_image, components[i]) != 0) || (jas_image_cmpttly(jp2_image, components[i]) != 0) || (jas_image_cmptsgnd(jp2_image, components[i]) != false)) { jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JPEG2000 image : Irregular Channel Geometry Not Supported"; loadingFailed(); return false; } x_step[i] = jas_image_cmpthstep(jp2_image, components[i]); y_step[i] = jas_image_cmptvstep(jp2_image, components[i]); } // ------------------------------------------------------------------- // Get image format. maximum_component_depth = 0; for (i = 0; i < (long)number_components; ++i) { maximum_component_depth = qMax((long)jas_image_cmptprec(jp2_image, components[i]), (long)maximum_component_depth); pixels[i] = jas_matrix_create(1, ((unsigned int)imageWidth()) / x_step[i]); if (!pixels[i]) { jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error decoding JPEG2000 image data : Memory Allocation Failed"; loadingFailed(); return false; } } if (maximum_component_depth > 8) { m_sixteenBit = true; } for (i = 0 ; i < (long)number_components ; ++i) { scale[i] = 1; int prec = jas_image_cmptprec(jp2_image, components[i]); if (m_sixteenBit && prec < 16) { scale[i] = (1 << (16 - jas_image_cmptprec(jp2_image, components[i]))); } } // ------------------------------------------------------------------- // Get image data. QScopedArrayPointer data; if (m_loadFlags & LoadImageData) { if (m_sixteenBit) // 16 bits image. { data.reset(new_failureTolerant(imageWidth(), imageHeight(), 8)); } else { data.reset(new_failureTolerant(imageWidth(), imageHeight(), 4)); } if (!data) { qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error decoding JPEG2000 image data : Memory Allocation Failed"; jas_image_destroy(jp2_image); for (i = 0 ; i < (long)number_components ; ++i) { jas_matrix_destroy(pixels[i]); } jas_cleanup(); loadingFailed(); return false; } uint checkPoint = 0; uchar* dst = data.data(); unsigned short* dst16 = reinterpret_cast(data.data()); for (y = 0 ; y < (long)imageHeight() ; ++y) { for (i = 0 ; i < (long)number_components; ++i) { int ret = jas_image_readcmpt(jp2_image, (short)components[i], 0, ((unsigned int) y) / y_step[i], ((unsigned int) imageWidth()) / x_step[i], 1, pixels[i]); if (ret != 0) { qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error decoding JPEG2000 image data"; jas_image_destroy(jp2_image); for (i = 0 ; i < (long)number_components ; ++i) { jas_matrix_destroy(pixels[i]); } jas_cleanup(); loadingFailed(); return false; } } switch (number_components) { case 1: // Grayscale. { if (!m_sixteenBit) // 8 bits image. { for (x = 0 ; x < (long)imageWidth() ; ++x) { dst[0] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); dst[1] = dst[0]; dst[2] = dst[0]; dst[3] = 0xFF; dst += 4; } } else // 16 bits image. { for (x = 0 ; x < (long)imageWidth() ; ++x) { dst16[0] = (unsigned short)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); dst16[1] = dst16[0]; dst16[2] = dst16[0]; dst16[3] = 0xFFFF; dst16 += 4; } } break; } case 3: // RGB. { if (!m_sixteenBit) // 8 bits image. { for (x = 0 ; x < (long)imageWidth() ; ++x) { // Blue dst[0] = (uchar)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); // Green dst[1] = (uchar)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); // Red dst[2] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); // Alpha dst[3] = 0xFF; dst += 4; } } else // 16 bits image. { for (x = 0 ; x < (long)imageWidth() ; ++x) { // Blue dst16[0] = (unsigned short)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); // Green dst16[1] = (unsigned short)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); // Red dst16[2] = (unsigned short)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); // Alpha dst16[3] = 0xFFFF; dst16 += 4; } } break; } case 4: // RGBA. { if (!m_sixteenBit) // 8 bits image. { for (x = 0 ; x < (long)imageWidth() ; ++x) { // Blue dst[0] = (uchar)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); // Green dst[1] = (uchar)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); // Red dst[2] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); // Alpha dst[3] = (uchar)(scale[3] * jas_matrix_getv(pixels[3], x / x_step[3])); dst += 4; } } else // 16 bits image. { for (x = 0 ; x < (long)imageWidth() ; ++x) { // Blue dst16[0] = (unsigned short)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); // Green dst16[1] = (unsigned short)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); // Red dst16[2] = (unsigned short)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); // Alpha dst16[3] = (unsigned short)(scale[3] * jas_matrix_getv(pixels[3], x / x_step[3])); dst16 += 4; } } break; } } // use 0-10% and 90-100% for pseudo-progress if (observer && y >= (long)checkPoint) { checkPoint += granularity(observer, y, 0.8F); if (!observer->continueQuery(m_image)) { jas_image_destroy(jp2_image); for (i = 0 ; i < (long)number_components ; ++i) { jas_matrix_destroy(pixels[i]); } jas_cleanup(); loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)y) / ((float)imageHeight())))); } } } // ------------------------------------------------------------------- // Get ICC color profile. if (m_loadFlags & LoadICCData) { jas_iccprof_t* icc_profile = nullptr; jas_stream_t* icc_stream = nullptr; jas_cmprof_t* cm_profile = nullptr; // To prevent cppcheck warnings. (void)icc_profile; (void)icc_stream; (void)cm_profile; cm_profile = jas_image_cmprof(jp2_image); if (cm_profile != nullptr) { icc_profile = jas_iccprof_createfromcmprof(cm_profile); } if (icc_profile != nullptr) { icc_stream = jas_stream_memopen(nullptr, 0); if (icc_stream != nullptr) { if (jas_iccprof_save(icc_profile, icc_stream) == 0) { if (jas_stream_flush(icc_stream) == 0) { jas_stream_memobj_t* blob = (jas_stream_memobj_t*) icc_stream->obj_; QByteArray profile_rawdata; profile_rawdata.resize(blob->len_); memcpy(profile_rawdata.data(), blob->buf_, blob->len_); imageSetIccProfile(IccProfile(profile_rawdata)); jas_stream_close(icc_stream); } } } } else { // If ICC profile is null, check Exif metadata. checkExifWorkingColorSpace(); } } if (observer) { observer->progressInfo(m_image, 1.0); } imageData() = data.take(); imageSetAttribute(QLatin1String("format"), QLatin1String("JP2")); imageSetAttribute(QLatin1String("originalColorModel"), colorModel); imageSetAttribute(QLatin1String("originalBitDepth"), maximum_component_depth); imageSetAttribute(QLatin1String("originalSize"), QSize(imageWidth(), imageHeight())); jas_image_destroy(jp2_image); for (i = 0 ; i < (long)number_components ; ++i) { jas_matrix_destroy(pixels[i]); } jas_cleanup(); return true; } -bool DImgJPEG2000Loader::save(const QString& filePath, DImgLoaderObserver* const observer) -{ - FILE* const file = fopen(QFile::encodeName(filePath).constData(), "wb"); - - if (!file) - { - return false; - } - - fclose(file); - - // ------------------------------------------------------------------- - // Initialize JPEG 2000 API. - - long i, x, y; - unsigned long number_components; - - jas_image_t* jp2_image = nullptr; - jas_stream_t* jp2_stream = nullptr; - jas_matrix_t* pixels[4]; - jas_image_cmptparm_t component_info[4]; - - int init = jas_init(); - - if (init != 0) - { - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to init JPEG2000 decoder"; - return false; - } - - jp2_stream = jas_stream_fopen(QFile::encodeName(filePath).constData(), "wb"); - - if (jp2_stream == nullptr) - { - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to open JPEG2000 stream"; - return false; - } - - number_components = imageHasAlpha() ? 4 : 3; - - for (i = 0 ; i < (long)number_components ; ++i) - { - component_info[i].tlx = 0; - component_info[i].tly = 0; - component_info[i].hstep = 1; - component_info[i].vstep = 1; - component_info[i].width = imageWidth(); - component_info[i].height = imageHeight(); - component_info[i].prec = imageBitsDepth(); - component_info[i].sgnd = false; - } - - jp2_image = jas_image_create(number_components, component_info, JAS_CLRSPC_UNKNOWN); - - if (jp2_image == nullptr) - { - jas_stream_close(jp2_stream); - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to create JPEG2000 image"; - return false; - } - - if (observer) - { - observer->progressInfo(m_image, 0.1F); - } - - // ------------------------------------------------------------------- - // Check color space. - - if (number_components >= 3) // RGB & RGBA - { - // Alpha Channel - if (number_components == 4) - { - jas_image_setcmpttype(jp2_image, 3, JAS_IMAGE_CT_OPACITY); - } - - jas_image_setclrspc(jp2_image, JAS_CLRSPC_SRGB); - jas_image_setcmpttype(jp2_image, 0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R)); - jas_image_setcmpttype(jp2_image, 1, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G)); - jas_image_setcmpttype(jp2_image, 2, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B)); - } - - // ------------------------------------------------------------------- - // Set ICC color profile. - - // FIXME : doesn't work yet! - - jas_cmprof_t* cm_profile = nullptr; - jas_iccprof_t* icc_profile = nullptr; - - // To prevent cppcheck warnings. - (void)cm_profile; - (void)icc_profile; - - QByteArray profile_rawdata = m_image->getIccProfile().data(); - icc_profile = jas_iccprof_createfrombuf((uchar*)profile_rawdata.data(), profile_rawdata.size()); - - if (icc_profile != nullptr) - { - cm_profile = jas_cmprof_createfromiccprof(icc_profile); - - if (cm_profile != nullptr) - { - jas_image_setcmprof(jp2_image, cm_profile); - //enable when it works: purgeExifWorkingColorSpace(); - } - } - - // workaround: - storeColorProfileInMetadata(); - - // ------------------------------------------------------------------- - // Convert to JPEG 2000 pixels. - - for (i = 0 ; i < (long)number_components ; ++i) - { - pixels[i] = jas_matrix_create(1, (unsigned int)imageWidth()); - - if (pixels[i] == nullptr) - { - for (x = 0 ; x < i ; ++x) - { - jas_matrix_destroy(pixels[x]); - } - - jas_image_destroy(jp2_image); - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error encoding JPEG2000 image data : Memory Allocation Failed"; - return false; - } - } - - unsigned char* data = imageData(); - unsigned char* pixel = nullptr; - unsigned short r = 0; - unsigned short g = 0; - unsigned short b = 0; - unsigned short a = 0; - uint checkpoint = 0; - - for (y = 0 ; y < (long)imageHeight() ; ++y) - { - if (observer && y == (long)checkpoint) - { - checkpoint += granularity(observer, imageHeight(), 0.8F); - - if (!observer->continueQuery(m_image)) - { - jas_image_destroy(jp2_image); - - for (i = 0 ; i < (long)number_components ; ++i) - { - jas_matrix_destroy(pixels[i]); - } - - jas_cleanup(); - - return false; - } - - observer->progressInfo(m_image, 0.1 + (0.8 * (((float)y) / ((float)imageHeight())))); - } - - for (x = 0 ; x < (long)imageWidth() ; ++x) - { - pixel = &data[((y * imageWidth()) + x) * imageBytesDepth()]; - - if (imageSixteenBit()) // 16 bits image. - { - b = (unsigned short)(pixel[0] + 256 * pixel[1]); - g = (unsigned short)(pixel[2] + 256 * pixel[3]); - r = (unsigned short)(pixel[4] + 256 * pixel[5]); - - if (imageHasAlpha()) - { - a = (unsigned short)(pixel[6] + 256 * pixel[7]); - } - } - else // 8 bits image. - { - b = (unsigned short)pixel[0]; - g = (unsigned short)pixel[1]; - r = (unsigned short)pixel[2]; - - if (imageHasAlpha()) - { - a = (unsigned short)(pixel[3]); - } - } - - jas_matrix_setv(pixels[0], x, r); - jas_matrix_setv(pixels[1], x, g); - jas_matrix_setv(pixels[2], x, b); - - if (number_components > 3) - { - jas_matrix_setv(pixels[3], x, a); - } - } - - for (i = 0 ; i < (long)number_components ; ++i) - { - int ret = jas_image_writecmpt(jp2_image, (short) i, 0, (unsigned int)y, - (unsigned int)imageWidth(), 1, pixels[i]); - - if (ret != 0) - { - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error encoding JPEG2000 image data"; - - jas_image_destroy(jp2_image); - - for (i = 0 ; i < (long)number_components ; ++i) - { - jas_matrix_destroy(pixels[i]); - } - - jas_cleanup(); - return false; - } - } - } - - QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); - int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; - - if (quality < 0) - { - quality = 90; - } - - if (quality > 100) - { - quality = 100; - } - - // optstr: - // - rate=#B => the resulting file size is about # bytes - // - rate=0.0 .. 1.0 => the resulting file size is about the factor times - // the uncompressed size - // use sprintf for locale-aware string - char rateBuffer[16]; - sprintf(rateBuffer, "rate=%.2g", (quality / 100.0)); - - qCDebug(DIGIKAM_DIMG_LOG_JP2K) << "JPEG2000 quality: " << quality; - qCDebug(DIGIKAM_DIMG_LOG_JP2K) << "JPEG2000 " << rateBuffer; - - int fmt = jas_image_strtofmt(QByteArray("jp2").data()); - int ret = jas_image_encode(jp2_image, jp2_stream, fmt, rateBuffer); - - if (ret != 0) - { - qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to encode JPEG2000 image"; - - jas_image_destroy(jp2_image); - jas_stream_close(jp2_stream); - - for (i = 0 ; i < (long)number_components ; ++i) - { - jas_matrix_destroy(pixels[i]); - } - - jas_cleanup(); - - return false; - } - - if (observer) - { - observer->progressInfo(m_image, 1.0); - } - - jas_image_destroy(jp2_image); - jas_stream_close(jp2_stream); - - for (i = 0 ; i < (long)number_components ; ++i) - { - jas_matrix_destroy(pixels[i]); - } - - jas_cleanup(); - - imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("JP2")); - saveMetadata(filePath); - - return true; -} - -bool DImgJPEG2000Loader::hasAlpha() const -{ - return m_hasAlpha; -} - -bool DImgJPEG2000Loader::sixteenBit() const -{ - return m_sixteenBit; -} - -bool DImgJPEG2000Loader::isReadOnly() const -{ - return false; -} - } // namespace DigikamJPEG2000DImgPlugin diff --git a/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader_save.cpp b/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader_save.cpp new file mode 100644 index 0000000000..1aaa76e90f --- /dev/null +++ b/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader_save.cpp @@ -0,0 +1,352 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * https://www.digikam.org + * + * Date : 2006-06-14 + * Description : A JPEG-2000 IO file for DImg framework - save operations + * + * Copyright (C) 2006-2019 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#include "dimgjpeg2000loader.h" + +// Qt includes + +#include +#include +#include + +// Local includes + +#include "digikam_config.h" +#include "dimg.h" +#include "digikam_debug.h" +#include "dimgloaderobserver.h" +#include "dmetadata.h" + +// Jasper includes + +#ifndef Q_CC_MSVC +extern "C" +{ +#endif + +#if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wshift-negative-value" +#endif + +#include + +#if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG) +# pragma clang diagnostic pop +#endif + +#ifndef Q_CC_MSVC +} +#endif + +namespace DigikamJPEG2000DImgPlugin +{ + +bool DImgJPEG2000Loader::save(const QString& filePath, DImgLoaderObserver* const observer) +{ + FILE* const file = fopen(QFile::encodeName(filePath).constData(), "wb"); + + if (!file) + { + return false; + } + + fclose(file); + + // ------------------------------------------------------------------- + // Initialize JPEG 2000 API. + + long i, x, y; + unsigned long number_components; + + jas_image_t* jp2_image = nullptr; + jas_stream_t* jp2_stream = nullptr; + jas_matrix_t* pixels[4]; + jas_image_cmptparm_t component_info[4]; + + int init = jas_init(); + + if (init != 0) + { + qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to init JPEG2000 decoder"; + return false; + } + + jp2_stream = jas_stream_fopen(QFile::encodeName(filePath).constData(), "wb"); + + if (jp2_stream == nullptr) + { + qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to open JPEG2000 stream"; + return false; + } + + number_components = imageHasAlpha() ? 4 : 3; + + for (i = 0 ; i < (long)number_components ; ++i) + { + component_info[i].tlx = 0; + component_info[i].tly = 0; + component_info[i].hstep = 1; + component_info[i].vstep = 1; + component_info[i].width = imageWidth(); + component_info[i].height = imageHeight(); + component_info[i].prec = imageBitsDepth(); + component_info[i].sgnd = false; + } + + jp2_image = jas_image_create(number_components, component_info, JAS_CLRSPC_UNKNOWN); + + if (jp2_image == nullptr) + { + jas_stream_close(jp2_stream); + qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to create JPEG2000 image"; + return false; + } + + if (observer) + { + observer->progressInfo(m_image, 0.1F); + } + + // ------------------------------------------------------------------- + // Check color space. + + if (number_components >= 3) // RGB & RGBA + { + // Alpha Channel + if (number_components == 4) + { + jas_image_setcmpttype(jp2_image, 3, JAS_IMAGE_CT_OPACITY); + } + + jas_image_setclrspc(jp2_image, JAS_CLRSPC_SRGB); + jas_image_setcmpttype(jp2_image, 0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R)); + jas_image_setcmpttype(jp2_image, 1, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G)); + jas_image_setcmpttype(jp2_image, 2, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B)); + } + + // ------------------------------------------------------------------- + // Set ICC color profile. + + // FIXME : doesn't work yet! + + jas_cmprof_t* cm_profile = nullptr; + jas_iccprof_t* icc_profile = nullptr; + + // To prevent cppcheck warnings. + (void)cm_profile; + (void)icc_profile; + + QByteArray profile_rawdata = m_image->getIccProfile().data(); + icc_profile = jas_iccprof_createfrombuf((uchar*)profile_rawdata.data(), profile_rawdata.size()); + + if (icc_profile != nullptr) + { + cm_profile = jas_cmprof_createfromiccprof(icc_profile); + + if (cm_profile != nullptr) + { + jas_image_setcmprof(jp2_image, cm_profile); + //enable when it works: purgeExifWorkingColorSpace(); + } + } + + // workaround: + storeColorProfileInMetadata(); + + // ------------------------------------------------------------------- + // Convert to JPEG 2000 pixels. + + for (i = 0 ; i < (long)number_components ; ++i) + { + pixels[i] = jas_matrix_create(1, (unsigned int)imageWidth()); + + if (pixels[i] == nullptr) + { + for (x = 0 ; x < i ; ++x) + { + jas_matrix_destroy(pixels[x]); + } + + jas_image_destroy(jp2_image); + qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error encoding JPEG2000 image data : Memory Allocation Failed"; + return false; + } + } + + unsigned char* data = imageData(); + unsigned char* pixel = nullptr; + unsigned short r = 0; + unsigned short g = 0; + unsigned short b = 0; + unsigned short a = 0; + uint checkpoint = 0; + + for (y = 0 ; y < (long)imageHeight() ; ++y) + { + if (observer && y == (long)checkpoint) + { + checkpoint += granularity(observer, imageHeight(), 0.8F); + + if (!observer->continueQuery(m_image)) + { + jas_image_destroy(jp2_image); + + for (i = 0 ; i < (long)number_components ; ++i) + { + jas_matrix_destroy(pixels[i]); + } + + jas_cleanup(); + + return false; + } + + observer->progressInfo(m_image, 0.1 + (0.8 * (((float)y) / ((float)imageHeight())))); + } + + for (x = 0 ; x < (long)imageWidth() ; ++x) + { + pixel = &data[((y * imageWidth()) + x) * imageBytesDepth()]; + + if (imageSixteenBit()) // 16 bits image. + { + b = (unsigned short)(pixel[0] + 256 * pixel[1]); + g = (unsigned short)(pixel[2] + 256 * pixel[3]); + r = (unsigned short)(pixel[4] + 256 * pixel[5]); + + if (imageHasAlpha()) + { + a = (unsigned short)(pixel[6] + 256 * pixel[7]); + } + } + else // 8 bits image. + { + b = (unsigned short)pixel[0]; + g = (unsigned short)pixel[1]; + r = (unsigned short)pixel[2]; + + if (imageHasAlpha()) + { + a = (unsigned short)(pixel[3]); + } + } + + jas_matrix_setv(pixels[0], x, r); + jas_matrix_setv(pixels[1], x, g); + jas_matrix_setv(pixels[2], x, b); + + if (number_components > 3) + { + jas_matrix_setv(pixels[3], x, a); + } + } + + for (i = 0 ; i < (long)number_components ; ++i) + { + int ret = jas_image_writecmpt(jp2_image, (short) i, 0, (unsigned int)y, + (unsigned int)imageWidth(), 1, pixels[i]); + + if (ret != 0) + { + qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error encoding JPEG2000 image data"; + + jas_image_destroy(jp2_image); + + for (i = 0 ; i < (long)number_components ; ++i) + { + jas_matrix_destroy(pixels[i]); + } + + jas_cleanup(); + return false; + } + } + } + + QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); + int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; + + if (quality < 0) + { + quality = 90; + } + + if (quality > 100) + { + quality = 100; + } + + // optstr: + // - rate=#B => the resulting file size is about # bytes + // - rate=0.0 .. 1.0 => the resulting file size is about the factor times + // the uncompressed size + // use sprintf for locale-aware string + char rateBuffer[16]; + sprintf(rateBuffer, "rate=%.2g", (quality / 100.0)); + + qCDebug(DIGIKAM_DIMG_LOG_JP2K) << "JPEG2000 quality: " << quality; + qCDebug(DIGIKAM_DIMG_LOG_JP2K) << "JPEG2000 " << rateBuffer; + + int fmt = jas_image_strtofmt(QByteArray("jp2").data()); + int ret = jas_image_encode(jp2_image, jp2_stream, fmt, rateBuffer); + + if (ret != 0) + { + qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to encode JPEG2000 image"; + + jas_image_destroy(jp2_image); + jas_stream_close(jp2_stream); + + for (i = 0 ; i < (long)number_components ; ++i) + { + jas_matrix_destroy(pixels[i]); + } + + jas_cleanup(); + + return false; + } + + if (observer) + { + observer->progressInfo(m_image, 1.0); + } + + jas_image_destroy(jp2_image); + jas_stream_close(jp2_stream); + + for (i = 0 ; i < (long)number_components ; ++i) + { + jas_matrix_destroy(pixels[i]); + } + + jas_cleanup(); + + imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("JP2")); + saveMetadata(filePath); + + return true; +} + +} // namespace DigikamJPEG2000DImgPlugin