diff --git a/core/app/utils/digikam_globals.cpp b/core/app/utils/digikam_globals.cpp index 112641f90b..626ecbc31f 100644 --- a/core/app/utils/digikam_globals.cpp +++ b/core/app/utils/digikam_globals.cpp @@ -1,294 +1,297 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-09-08 * Description : global macros, variables and flags * * Copyright (C) 2009-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 "digikam_globals.h" // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "drawdecoder.h" #include "rawcameradlg.h" // Windows includes #ifdef HAVE_DRMINGW # include #endif namespace Digikam { QShortcut* defineShortcut(QWidget* const w, const QKeySequence& key, const QObject* receiver, const char* slot) { QShortcut* const s = new QShortcut(w); s->setKey(key); s->setContext(Qt::WidgetWithChildrenShortcut); QObject::connect(s, SIGNAL(activated()), receiver, slot); return s; } QStringList supportedImageMimeTypes(QIODevice::OpenModeFlag mode, QString& allTypes) { QStringList formats; QList supported; switch(mode) { case QIODevice::ReadOnly: supported = QImageReader::supportedImageFormats(); break; case QIODevice::WriteOnly: supported = QImageWriter::supportedImageFormats(); break; case QIODevice::ReadWrite: supported = QImageWriter::supportedImageFormats() + QImageReader::supportedImageFormats(); break; default: qCDebug(DIGIKAM_GENERAL_LOG) << "Unsupported mode!"; break; } bool tiff = false; bool jpeg = false; #ifdef HAVE_JASPER bool jp2k = false; #endif // HAVE_JASPER foreach(const QByteArray& frm, supported) { if (QString::fromLatin1(frm).contains(QLatin1String("tif"), Qt::CaseInsensitive) || QString::fromLatin1(frm).contains(QLatin1String("tiff"), Qt::CaseInsensitive)) { tiff = true; continue; } if (QString::fromLatin1(frm).contains(QLatin1String("jpg"), Qt::CaseInsensitive) || QString::fromLatin1(frm).contains(QLatin1String("jpeg"), Qt::CaseInsensitive)) { jpeg = true; continue; } #ifdef HAVE_JASPER if (QString::fromLatin1(frm).contains(QLatin1String("jp2"), Qt::CaseInsensitive) || QString::fromLatin1(frm).contains(QLatin1String("j2k"), Qt::CaseInsensitive) || QString::fromLatin1(frm).contains(QLatin1String("jpx"), Qt::CaseInsensitive) || QString::fromLatin1(frm).contains(QLatin1String("jpc"), Qt::CaseInsensitive) || QString::fromLatin1(frm).contains(QLatin1String("pgx"), Qt::CaseInsensitive)) { jp2k = true; continue; } #endif // HAVE_JASPER formats.append(i18n("%1 Image (%2)", QString::fromLatin1(frm).toUpper(), QLatin1String("*.") + QLatin1String(frm))); allTypes.append(QString::fromLatin1("*.%1 ").arg(QLatin1String(frm))); } if (tiff) { formats.append(i18n("TIFF Image (*.tiff *.tif)")); allTypes.append(QLatin1String("*.tiff *.tif ")); } if (jpeg) { formats.append(i18n("JPEG Image (*.jpg *.jpeg *.jpe)")); allTypes.append(QLatin1String("*.jpg *.jpeg *.jpe ")); } #ifdef HAVE_JASPER if (jp2k) { formats.append(i18n("JPEG2000 Image (*.jp2 *.j2k *.jpx *.pgx)")); allTypes.append(QLatin1String("*.jp2 *.j2k *.jpx *.pgx ")); } #endif // HAVE_JASPER formats << i18n("Progressive Graphics file (*.pgf)"); allTypes.append(QLatin1String("*.pgf ")); + formats << i18n("High Efficiency Image Coding (*.heic)"); + allTypes.append(QLatin1String("*.heic ")); + if (mode != QIODevice::WriteOnly) { formats << i18n("Raw Images (%1)", QLatin1String(DRawDecoder::rawFiles())); allTypes.append(QLatin1String(DRawDecoder::rawFiles())); formats << i18n("All supported files (%1)", allTypes); } return formats; } void showRawCameraList() { RawCameraDlg* const dlg = new RawCameraDlg(qApp->activeWindow()); dlg->show(); } QProcessEnvironment adjustedEnvironmentForAppImage() { QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); // If we are running into AppImage bundle, switch env var to the right values. if (env.contains(QLatin1String("APPIMAGE_ORIGINAL_LD_LIBRARY_PATH")) && env.contains(QLatin1String("APPIMAGE_ORIGINAL_QT_PLUGIN_PATH")) && env.contains(QLatin1String("APPIMAGE_ORIGINAL_XDG_DATA_DIRS")) && env.contains(QLatin1String("APPIMAGE_ORIGINAL_PATH"))) { qCDebug(DIGIKAM_GENERAL_LOG) << "Adjusting environment variables for AppImage bundle"; if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_LD_LIBRARY_PATH")).isEmpty()) { env.insert(QLatin1String("LD_LIBRARY_PATH"), env.value(QLatin1String("APPIMAGE_ORIGINAL_LD_LIBRARY_PATH"))); } else { env.remove(QLatin1String("LD_LIBRARY_PATH")); } if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_QT_PLUGIN_PATH")).isEmpty()) { env.insert(QLatin1String("QT_PLUGIN_PATH"), env.value(QLatin1String("APPIMAGE_ORIGINAL_QT_PLUGIN_PATH"))); } else { env.remove(QLatin1String("QT_PLUGIN_PATH")); } if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_XDG_DATA_DIRS")).isEmpty()) { env.insert(QLatin1String("XDG_DATA_DIRS"), env.value(QLatin1String("APPIMAGE_ORIGINAL_XDG_DATA_DIRS"))); } else { env.remove(QLatin1String("XDG_DATA_DIRS")); } if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_PATH")).isEmpty()) { env.insert(QLatin1String("PATH"), env.value(QLatin1String("APPIMAGE_ORIGINAL_PATH"))); } else { env.remove(QLatin1String("PATH")); } } return env; } void tryInitDrMingw() { #ifdef HAVE_DRMINGW qCDebug(DIGIKAM_GENERAL_LOG) << "Loading DrMinGw run-time..."; wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QLatin1String("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { qCDebug(DIGIKAM_GENERAL_LOG) << "DrMinGw: cannot find crash handler dll."; return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { qCDebug(DIGIKAM_GENERAL_LOG) << "DrMinGw: cannot init crash handler dll."; return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { qCDebug(DIGIKAM_GENERAL_LOG) << "DrMinGw: cannot init customized crash file."; return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QLatin1String("\\digikam_crash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit().data()); qCDebug(DIGIKAM_GENERAL_LOG) << "DrMinGw run-time loaded."; qCDebug(DIGIKAM_GENERAL_LOG) << "DrMinGw crash-file will be located at: " << logFile; #endif // HAVE_DRMINGW } QString toolButtonStyleSheet() { return QLatin1String("QToolButton { padding: 1px; background-color: " " qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, " " stop: 0 rgba(100, 100, 100, 50%), " " stop: 1 rgba(170, 170, 170, 50%)); " "border: 1px solid rgba(170, 170, 170, 10%); } " "QToolButton:hover { border-color: white; } " "QToolButton:pressed { background-color: " " qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, " " stop: 0 rgba(40, 40, 40, 50%), " " stop: 1 rgba(90, 90, 90, 50%)); " "border-color: white; } " "QToolButton:checked { background-color: " " qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, " " stop: 0 rgba(40, 40, 40, 50%), " " stop: 1 rgba(90, 90, 90, 50%)); } " "QToolButton:disabled { background-color: " " qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, " " stop: 0 rgba(40, 40, 40, 50%), " " stop: 1 rgba(50, 50, 50, 50%)); }"); } } // namespace Digikam diff --git a/core/dplugins/dimg/heif/dimgheifloader.cpp b/core/dplugins/dimg/heif/dimgheifloader.cpp index 22cb99e581..9a44b8b0cb 100644 --- a/core/dplugins/dimg/heif/dimgheifloader.cpp +++ b/core/dplugins/dimg/heif/dimgheifloader.cpp @@ -1,649 +1,768 @@ /* ============================================================ * * 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. * * ============================================================ */ #include "dimgheifloader.h" // Qt includes #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "dimg.h" #include "dimgloaderobserver.h" #include "metaengine.h" namespace Digikam { DImgHEIFLoader::DImgHEIFLoader(DImg* const image) : DImgLoader(image) { m_hasAlpha = false; m_sixteenBit = false; m_observer = nullptr; } bool DImgHEIFLoader::load(const QString& filePath, DImgLoaderObserver* const observer) { m_observer = observer; // readMetadata(filePath, DImg::QIMAGE); NOTE: Exiv2 do not support HEIC yet FILE* const file = fopen(QFile::encodeName(filePath).constData(), "rb"); if (!file) { qWarning() << "Error: Could not open source file."; loadingFailed(); return false; } const int headerLen = 12; unsigned char header[headerLen]; if (fread(&header, headerLen, 1, file) != 1) { qWarning() << "Error: Could not parse magic identifier."; fclose(file); loadingFailed(); return false; } if ((memcmp(&header[4], "ftyp", 4) != 0) && (memcmp(&header[8], "heic", 4) != 0) && (memcmp(&header[8], "heix", 4) != 0) && (memcmp(&header[8], "mif1", 4) != 0)) { qWarning() << "Error: source file is not HEIC image."; fclose(file); loadingFailed(); return false; } fclose(file); if (observer) { observer->progressInfo(m_image, 0.1F); } // ------------------------------------------------------------------- // Initialize HEIF API. heif_item_id primary_image_id; struct heif_context* const heif_context = heif_context_alloc(); struct heif_error error = heif_context_read_from_file(heif_context, QFile::encodeName(filePath).constData(), NULL); if (!isHeifSuccess(&error)) { qWarning() << "Error: Could not read source file."; loadingFailed(); heif_context_free(heif_context); return false; } error = heif_context_get_primary_image_ID(heif_context, &primary_image_id); if (!isHeifSuccess(&error)) { qWarning() << "Error: Could not load image data."; loadingFailed(); heif_context_free(heif_context); return false; } return (readHEICImageByID(heif_context, primary_image_id)); } bool DImgHEIFLoader::hasAlpha() const { return m_hasAlpha; } bool DImgHEIFLoader::sixteenBit() const { return m_sixteenBit; } bool DImgHEIFLoader::isReadOnly() const { return false; } bool DImgHEIFLoader::isHeifSuccess(struct heif_error* const error) { if (error->code == 0) + { return true; + } - qWarning() << "Cannot read HEIF image:" << error->message; + qWarning() << "Error while processing HEIC image:" << error->message; return false; } bool DImgHEIFLoader::readHEICColorProfile(struct heif_image_handle* const image_handle) { #if LIBHEIF_NUMERIC_VERSION >= 0x01040000 size_t length = heif_image_handle_get_raw_color_profile_size(image_handle); if (length > 0) { // Read color profile. QByteArray profile; profile.resize(length); struct heif_error error = heif_image_handle_get_raw_color_profile(image_handle, profile.data()); if (error.code == 0) { qDebug() << "HEIC color profile found with size:" << length; imageSetIccProfile(IccProfile(profile)); return true; } } #endif // If ICC profile is null, check Exif metadata. checkExifWorkingColorSpace(); return true; } bool DImgHEIFLoader::readHEICMetadata(struct heif_image_handle* const image_handle) { heif_item_id dataIds[10]; QByteArray exif; QByteArray xmp; int count = heif_image_handle_get_list_of_metadata_block_IDs(image_handle, nullptr, dataIds, 10); if (count > 0) { for (int i = 0 ; i < count ; ++i) { if (QLatin1String(heif_image_handle_get_metadata_type(image_handle, dataIds[i])) == QLatin1String("Exif")) { // Read Exif chunk. size_t length = heif_image_handle_get_metadata_size(image_handle, dataIds[i]); QByteArray exifChunk; exifChunk.resize(length); struct heif_error error = heif_image_handle_get_metadata(image_handle, dataIds[i], exifChunk.data()); if ((error.code == 0) && (length > 4)) { // The first 4 bytes indicate the // offset to the start of the TIFF header of the Exif data. int skip = ((exifChunk.constData()[0] << 24) | (exifChunk.constData()[1] << 16) | (exifChunk.constData()[2] << 8) | exifChunk.constData()[3]) + 4; if (exifChunk.size() > skip) { // Copy the real exif data into the byte array qDebug() << "HEIC exif container found with size:" << length - skip; exif.append((char*)(exifChunk.data() + skip), exifChunk.size() - skip); } } } if ( (QLatin1String(heif_image_handle_get_metadata_type(image_handle, dataIds[i])) == QLatin1String("mime")) && (QLatin1String(heif_image_handle_get_metadata_content_type(image_handle, dataIds[i])) == QLatin1String("application/rdf+xml")) ) { // Read Xmp chunk. size_t length = heif_image_handle_get_metadata_size(image_handle, dataIds[i]); xmp.resize(length); struct heif_error error = heif_image_handle_get_metadata(image_handle, dataIds[i], xmp.data()); if ((error.code == 0)) { qDebug() << "HEIC xmp container found with size:" << length; } else { xmp = QByteArray(); } } } } if (!exif.isEmpty() || !xmp.isEmpty()) { MetaEngine meta; if (!exif.isEmpty()) meta.setExif(exif); if (!xmp.isEmpty()) meta.setXmp(xmp); m_image->setMetadata(meta.data()); return true; } return false; } bool DImgHEIFLoader::readHEICImageByID(struct heif_context* const heif_context, heif_item_id image_id) { struct heif_image* heif_image = nullptr; struct heif_image_handle* image_handle = nullptr; struct heif_error error = heif_context_get_image_handle(heif_context, image_id, &image_handle); if (!isHeifSuccess(&error)) { return false; } // NOTE: An HEIC image without ICC color profile or without metadata still valid. if (m_loadFlags & LoadICCData) { readHEICColorProfile(image_handle); readHEICMetadata(image_handle); } if (m_observer) { m_observer->progressInfo(m_image, 0.2F); } if (m_loadFlags & LoadImageData) { // Copy HEIF image into data structures. struct heif_decoding_options* const decode_options = heif_decoding_options_alloc(); decode_options->ignore_transformations = 1; m_hasAlpha = heif_image_handle_has_alpha_channel(image_handle); heif_chroma chroma = m_hasAlpha ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB; // Trace to check image size properties before decoding, as these values can be different. qDebug() << "HEIC image size: (" << heif_image_handle_get_width(image_handle) << "x" << heif_image_handle_get_height(image_handle) << ")"; error = heif_decode_image(image_handle, &heif_image, heif_colorspace_RGB, chroma, decode_options); if (!isHeifSuccess(&error)) { heif_image_handle_release(image_handle); return false; } if (m_observer) { m_observer->progressInfo(m_image, 0.3F); } heif_decoding_options_free(decode_options); int colorDepth = heif_image_get_bits_per_pixel(heif_image, heif_channel_interleaved); imageWidth() = heif_image_get_width(heif_image, heif_channel_interleaved); imageHeight() = heif_image_get_height(heif_image, heif_channel_interleaved); qDebug() << "Decoded HEIC image properties: size(" << imageWidth() << "x" << imageHeight() << "), Alpha:" << m_hasAlpha << ", Color depth :" << colorDepth; if (!QSize(imageWidth(), imageHeight()).isValid()) { heif_image_release(heif_image); heif_image_handle_release(image_handle); return false; } int stride = 0; uint8_t* const ptr = heif_image_get_plane(heif_image, heif_channel_interleaved, &stride); qDebug() << "HEIC data :" << ptr; qDebug() << "HEIC stride:" << stride; uchar* data = nullptr; if (colorDepth == 24 || // RGB colorDepth == 32) // RGBA { qDebug() << "Color bytes depth: 8"; m_sixteenBit = false; } else if (colorDepth == 48 || // RGB colorDepth == 64) // RGBA { qDebug() << "Color bytes depth: 16"; m_sixteenBit = true; } else { qWarning() << "Color bits depth: " << colorDepth << ": not supported!"; heif_image_release(heif_image); heif_image_handle_release(image_handle); return false; } if (m_sixteenBit) { data = new_failureTolerant(imageWidth(), imageHeight(), 8); // 16 bits/color/pixel } else { data = new_failureTolerant(imageWidth(), imageHeight(), 4); // 8 bits/color/pixel } if (m_observer) { m_observer->progressInfo(m_image, 0.4F); } uchar* dst = data; unsigned short* dst16 = reinterpret_cast(data); uchar* src = reinterpret_cast(ptr); unsigned short* src16 = reinterpret_cast(ptr); unsigned int checkPoint = 0; unsigned int size = imageHeight()*imageWidth(); for (unsigned int i = 0 ; i < size ; ++i) { if (!m_sixteenBit) // 8 bits image. { // Blue dst[0] = src[2]; // Green dst[1] = src[1]; // Red dst[2] = src[3]; // Alpha if (m_hasAlpha) { dst[3] = src[3]; src += 4; } else { dst[3] = 0xFF; src += 3; } dst += 4; } else // 16 bits image. { // Blue dst16[0] = src16[2]; // Green dst16[1] = src16[1]; // Red dst16[2] = src16[0]; // Alpha if (m_hasAlpha) { dst16[3] = src16[3]; src16 += 4; } else { dst16[3] = 0xFFFF; src16 += 3; } dst16 += 4; } if (m_observer && i >= checkPoint) { checkPoint += granularity(m_observer, i, 0.8F); if (!m_observer->continueQuery(m_image)) { heif_image_release(heif_image); heif_image_handle_release(image_handle); loadingFailed(); return false; } m_observer->progressInfo(m_image, 0.4 + (0.8 * (((float)i) / ((float)size)))); } } imageData() = data; imageSetAttribute(QLatin1String("format"), QLatin1String("HEIF")); imageSetAttribute(QLatin1String("originalColorModel"), DImg::RGB); imageSetAttribute(QLatin1String("originalBitDepth"), m_sixteenBit ? 16 : 8); imageSetAttribute(QLatin1String("originalSize"), QSize(imageWidth(), imageHeight())); } if (m_observer) { m_observer->progressInfo(m_image, 0.9F); } heif_image_release(heif_image); heif_image_handle_release(image_handle); return true; } bool DImgHEIFLoader::save(const QString& filePath, DImgLoaderObserver* const observer) { #ifdef HAVE_X265 m_observer = observer; // ------------------------------------------------------------------- // Open the file FILE* const f = fopen(QFile::encodeName(filePath).constData(), "wb"); if (!f) { qWarning() << "Cannot open target image file."; return false; } - // TODO + QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); + int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 50; + QVariant losslessAttr = imageGetAttribute(QLatin1String("lossless")); + bool lossless = losslessAttr.isValid() ? qualityAttr.toBool() : false; + heif_chroma chroma = imageHasAlpha() ? heif_chroma_interleaved_RGBA + : heif_chroma_interleaved_RGB; -#endif + // --- use standard HEVC encoder - return false; -} + struct heif_context* const ctx = heif_context_alloc(); -/* -KisImportExportErrorCode HeifExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) -{ - KisImageSP image = document->savingImage(); - const KoColorSpace *cs = image->colorSpace(); + if (!ctx) + { + qWarning() << "Cannot create HEIC context!"; + return false; + } + + struct heif_encoder* encoder = nullptr; + struct heif_error error = heif_context_get_encoder_for_format(ctx, heif_compression_HEVC, &encoder); - // Convert to 8 bits rgba on saving - if (cs->colorModelId() != RGBAColorModelID || cs->colorDepthId() != Integer8BitsColorDepthID) { - cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id()); - image->convertImageColorSpace(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); + if (!isHeifSuccess(&error)) + { + heif_context_free(ctx); + return false; } - int quality = configuration->getInt("quality", 50); - bool lossless = configuration->getBool("lossless", false); - bool has_alpha = configuration->getBool(KisImportExportFilter::ImageContainsTransparencyTag, false); + heif_encoder_set_lossy_quality(encoder, quality); + heif_encoder_set_lossless(encoder, lossless); + struct heif_image* image = nullptr; + error = heif_image_create(imageWidth(), imageHeight(), heif_colorspace_RGB, chroma, &image); - // If we want to add information from the document to the metadata, - // we should do that here. + if (!isHeifSuccess(&error)) + { + heif_encoder_release(encoder); + heif_context_free(ctx); + return false; + } - try { - // --- use standard HEVC encoder + error = heif_image_add_plane(image, heif_channel_R, imageWidth(), imageHeight(), imageBytesDepth()); - heif::Encoder encoder(heif_compression_HEVC); + if (!isHeifSuccess(&error)) + { + heif_encoder_release(encoder); + heif_context_free(ctx); + return false; + } - encoder.set_lossy_quality(quality); - encoder.set_lossless(lossless); + error = heif_image_add_plane(image, heif_channel_G, imageWidth(), imageHeight(), imageBytesDepth()); + if (!isHeifSuccess(&error)) + { + heif_encoder_release(encoder); + heif_context_free(ctx); + return false; + } + + error = heif_image_add_plane(image, heif_channel_B, imageWidth(), imageHeight(), imageBytesDepth()); + + if (!isHeifSuccess(&error)) + { + heif_encoder_release(encoder); + heif_context_free(ctx); + return false; + } + + if (imageHasAlpha()) + { + error = heif_image_add_plane(image, heif_channel_Alpha, imageWidth(), imageHeight(), imageBytesDepth()); + + if (!isHeifSuccess(&error)) + { + heif_encoder_release(encoder); + heif_context_free(ctx); + return false; + } + } + + int strideR = 0; + int strideG = 0; + int strideB = 0; + int strideA = 0; + + uint8_t* ptrR = heif_image_get_plane(image, heif_channel_R, &strideR); + uint8_t* ptrG = heif_image_get_plane(image, heif_channel_G, &strideG); + uint8_t* ptrB = heif_image_get_plane(image, heif_channel_B, &strideB); + + if (!ptrR || !ptrG || ! ptrB) + { + qWarning() << "Cannot get HEIC RGB planes!"; + heif_encoder_release(encoder); + heif_context_free(ctx); + return false; + } - // --- convert KisImage to HEIF image --- - int width = image->width(); - int height = image->height(); + uint8_t* ptrA = nullptr; - heif::Context ctx; + if (imageHasAlpha()) + { + ptrA = heif_image_get_plane(image, heif_channel_Alpha, &strideA); - heif::Image img; - img.create(width,height, heif_colorspace_RGB, heif_chroma_444); - img.add_plane(heif_channel_R, width,height, 8); - img.add_plane(heif_channel_G, width,height, 8); - img.add_plane(heif_channel_B, width,height, 8); + if (!ptrA) + { + qWarning() << "Cannot get HEIC Alpha plane!"; + heif_encoder_release(encoder); + heif_context_free(ctx); + return false; + } + } - uint8_t* ptrR {0}; - uint8_t* ptrG {0}; - uint8_t* ptrB {0}; - uint8_t* ptrA {0}; - int strideR,strideG,strideB,strideA; + qDebug() << "HEIC plane details:" + << "ptrR=" << ptrR << " strideR=" << strideR + << ", ptrG=" << ptrG << " strideG=" << strideG + << ", ptrB=" << ptrB << " strideB=" << strideB + << ", ptrA=" << ptrA << " strideA=" << strideA; + + uint checkpoint = 0; + unsigned short r = 0; + unsigned short g = 0; + unsigned short b = 0; + unsigned short a = 0; + unsigned char* data = imageData(); + unsigned char* pixel = nullptr; + + for (unsigned int y = 0 ; y < imageHeight() ; ++y) + { + if (m_observer && y == (long)checkpoint) + { + checkpoint += granularity(m_observer, imageHeight(), 0.8F); - ptrR = img.get_plane(heif_channel_R, &strideR); - ptrG = img.get_plane(heif_channel_G, &strideG); - ptrB = img.get_plane(heif_channel_B, &strideB); + if (!m_observer->continueQuery(m_image)) + { + heif_encoder_release(encoder); + heif_context_free(ctx); + return false; + } - if (has_alpha) { - img.add_plane(heif_channel_Alpha, width,height, 8); - ptrA = img.get_plane(heif_channel_Alpha, &strideA); + m_observer->progressInfo(m_image, 0.1 + (0.8 * (((float)y) / ((float)imageHeight())))); } - KisPaintDeviceSP pd = image->projection(); + for (unsigned int x = 0 ; x < imageWidth() ; ++x) + { + pixel = &data[((y * imageWidth()) + x) * imageBytesDepth()]; - for (int y=0; ycreateHLineIteratorNG(0, y, width); + 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]); - for (int x=0; x::red(it->rawData()); - ptrG[y*strideG+x] = KoBgrTraits::green(it->rawData()); - ptrB[y*strideB+x] = KoBgrTraits::blue(it->rawData()); + 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 (has_alpha) { - ptrA[y*strideA+x] = cs->opacityU8(it->rawData()); + if (imageHasAlpha()) + { + a = (unsigned short)(pixel[3]); } + } - it->nextPixel(); + ptrR[(y * strideR) + x] = r; + ptrG[(y * strideG) + x] = g; + ptrB[(y * strideB) + x] = b; + + if (imageHasAlpha()) + { + ptrA[(y * strideA) + x] = a; } } + } - // --- encode and write image + // --- encode and write image - heif::ImageHandle handle = ctx.encode_image(img, encoder); + struct heif_encoding_options* options = heif_encoding_options_alloc(); + options->save_alpha_channel = imageHasAlpha() ? 1 : 0; + struct heif_image_handle* hdl = nullptr; + error = heif_context_encode_image(ctx, image, encoder, options, &hdl); + if (!isHeifSuccess(&error)) + { + heif_encoding_options_free(options); + heif_image_handle_release(hdl); + heif_encoder_release(encoder); + heif_context_free(ctx); + return false; + } + heif_encoding_options_free(options); + heif_image_handle_release(hdl); + heif_encoder_release(encoder); - // --- add Exif / XMP metadata + // --- add Exif / XMP metadata +/* + KisExifInfoVisitor exivInfoVisitor; + exivInfoVisitor.visit(image->rootLayer().data()); - KisExifInfoVisitor exivInfoVisitor; - exivInfoVisitor.visit(image->rootLayer().data()); + QScopedPointer metaDataStore; + if (exivInfoVisitor.metaDataCount() == 1) { + metaDataStore.reset(new KisMetaData::Store(*exivInfoVisitor.exifInfo())); + } + else { + metaDataStore.reset(new KisMetaData::Store()); + } - QScopedPointer metaDataStore; - if (exivInfoVisitor.metaDataCount() == 1) { - metaDataStore.reset(new KisMetaData::Store(*exivInfoVisitor.exifInfo())); - } - else { - metaDataStore.reset(new KisMetaData::Store()); - } + if (!metaDataStore->empty()) { + { + KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); + QBuffer buffer; + exifIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else? + QByteArray data = buffer.data(); - if (!metaDataStore->empty()) { - { - KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); - QBuffer buffer; - exifIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else? - QByteArray data = buffer.data(); - - // Write the data to the file - ctx.add_exif_metadata(handle, data.constData(), data.size()); - } - { - KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); - QBuffer buffer; - xmpIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else? - QByteArray data = buffer.data(); - - // Write the data to the file - ctx.add_XMP_metadata(handle, data.constData(), data.size()); - } + // Write the data to the file + ctx.add_exif_metadata(handle, data.constData(), data.size()); } + { + KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); + QBuffer buffer; + xmpIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else? + QByteArray data = buffer.data(); + // Write the data to the file + ctx.add_XMP_metadata(handle, data.constData(), data.size()); + } + } +*/ - // --- write HEIF file + // --- write HEIF file - Writer_QIODevice writer(io); + error = heif_context_write_to_file(ctx, QFile::encodeName(filePath).constData()); - ctx.write(writer); - } - catch (heif::Error err) { - return setHeifError(document, err); + if (!isHeifSuccess(&error)) + { + heif_context_free(ctx); + return false; } - return ImportExportCodes::OK; + heif_context_free(ctx); + + imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("HEIC")); + saveMetadata(filePath); + +#endif + + return true; } -*/ } // namespace Digikam diff --git a/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.cpp b/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.cpp index 5af1f610d1..bc15e66990 100644 --- a/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.cpp +++ b/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader.cpp @@ -1,896 +1,898 @@ /* ============================================================ * * 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, DImg::JPEG); 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()); + 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; - unsigned short r, g, b, a = 0; - uint checkpoint = 0; + 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/tests/dimg/testdimgloader.cpp b/core/tests/dimg/testdimgloader.cpp index 4090e75259..a2129280c6 100644 --- a/core/tests/dimg/testdimgloader.cpp +++ b/core/tests/dimg/testdimgloader.cpp @@ -1,73 +1,73 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-10-23 * Description : a command line tool to test DImg image loader * * Copyright (C) 2012-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 #include #include // Local includes #include "metaengine.h" #include "dimg.h" #include "drawdecoding.h" #include "dpluginloader.h" using namespace Digikam; int main(int argc, char** argv) { QApplication app(argc, argv); if (argc != 2) { qDebug() << "testdimgloader - test DImg image loader"; qDebug() << "Usage: "; return -1; } MetaEngine::initializeExiv2(); DPluginLoader::instance()->init(); QFileInfo input(QString::fromUtf8(argv[1])); - QString outFilePath(input.baseName() + QLatin1String(".png")); + QString outFilePath(input.baseName() + QLatin1String(".heic")); DRawDecoderSettings settings; settings.halfSizeColorImage = false; settings.sixteenBitsImage = false; settings.RGBInterpolate4Colors = false; settings.RAWQuality = DRawDecoderSettings::BILINEAR; DImg img(input.filePath(), nullptr, DRawDecoding(settings)); if (!img.isNull()) { - img.save(outFilePath, QLatin1String("png")); + img.save(outFilePath, QLatin1String("heic")); } DPluginLoader::instance()->cleanUp(); MetaEngine::cleanupExiv2(); return 0; }