diff --git a/NEWS b/NEWS index 4065eb04cd..828b656248 100644 --- a/NEWS +++ b/NEWS @@ -1,64 +1,69 @@ digiKam 6.4.0 - Release date: 2019-??-?? ***************************************************************************************************** NEW FEATURES: General : new RawImport plugin interface to delegate Raw decoding function to extra engine with ImageEditor. General : new DImg plugin interface to externalize image loaders from core implementation. General : new HEIC image loader compatible with media generated by Apple devices. Import : add new option to convert on the fly to HEIC lossless format while downloading. ImageEditor: add new setting from setup dialog to select right Raw Import plugin. ImageEditor: add new clone tool to fix artifacts on image. ImageEditor: add new tool to import RAW image using UFRaw. ImageEditor: add new tool to import RAW image using RawTherapee. ImageEditor: add new tool to import RAW image using DarkTable. BQM : add new tool to convert to HEIC format. ***************************************************************************************************** BUGFIXES: 001 ==> 411587 - [digiKam] Crash when reopening Google Photos import wizard. 002 ==> 411578 - White Balance changes global luminosity. 003 ==> 411696 - Cannot launch digikam-6.3.0-x86-64.appimage in Kubuntu 14.04. 004 ==> 406503 - Histogram initializes greyer than white. 005 ==> 411714 - Correction of ticket 408881 (Restore default tools settings) is not enforced. 006 ==> 411702 - Video/picture Filter. 007 ==> 411726 - Geolocation not working. 008 ==> 387768 - File system corruption after renaming folder. 009 ==> 411808 - digiKam quit unexpectedly. 010 ==> 411880 - Existing file is count but not displayed. 011 ==> 411882 - I don't get image stack or panorama. 012 ==> 403269 - Stacked image and Panorama tools cannot find required binaries. 013 ==> 411651 - New tool to export photos to Canon Irista. 014 ==> 411927 - Crash during initial scan. 015 ==> 389652 - Interface freezes during initial scan. 016 ==> 389949 - Very slow startup [patch]. 017 ==> 316865 - SCAN : Add new option to don't scan file bigger than n Mb or stop scan if longer than n seconds. 018 ==> 370019 - Sidecar metadata not loading sporadically. 019 ==> 329353 - Make slow processing better. 020 ==> 392090 - While scanning collection the progess bar shows 0%. 021 ==> 411929 - Can not move or delete videos that were not played till the end. 022 ==> 392727 - Images are missing (Windows). 023 ==> 396559 - "digikam.dbengine: Database is locked." when scanning for new items. 024 ==> 411946 - Crash when saving captions to picture. 025 ==> 411902 - Interface icons become very large and unusable. 026 ==> 330168 - MYSQL : allow read only database. 027 ==> 351658 - Prevent to fill whole memory when all CPU cores are used to process Maintenance tools. 028 ==> 110920 - Support for removing complex objects from photos. 029 ==> 103332 - Blurring brushes for image correction. 030 ==> 150161 - UFRaw as tool for RAW. 032 ==> 411214 - digiKam git beta 6.4 eats huge memory. 033 ==> 181941 - Add an option to image editor to remember or not tools settings between sessions. 034 ==> 221571 - Integrate RawTherapee into digikam 035 ==> 341186 - Integrate with darktable. 036 ==> 412083 - Image previews scaled incorrectly on hidpi. 037 ==> 411612 - When using Tags Manager and then closing crashes digiKam. 038 ==> 412291 - Mac: Opening Preferences zooms the main window to unusable state. 039 ==> 412372 - Image editor view is expanded to 100% and shows only partial amount of picture. 040 ==> 412293 - Image editor Tools sidebar width not restored. 041 ==> 412425 - UI "breaks" / scales after opening any dialog. 042 ==> 412437 - Opening Preferences on MacOS 10.14.6 breaks resolution of main screen. 043 ==> 412442 - Website points to obsolete versions. 044 ==> 411027 - image files *.heic from iphones do not show up in digikam thumbnails although ther are find in MS file browser. 045 ==> 412574 - New images are not recognized when externally added to a DK folder. -046 ==> +046 ==> 195583 - FILEIO : read header metadata from JPEG2000 files with Exiv2. +047 ==> 116225 - JPEG compression quality should be shown in Properties. +048 ==> 405232 - digiKam crashes in Slideshow and Presentation. +049 ==> 412730 - Main window double resolution on retina after dialog is open. +050 ==> 412728 - MacOS: Image editor tools for colour adjustment incorrectly apply crop automatically and cut out most of the image. +051 ==> diff --git a/core/app/utils/digikam_globals.cpp b/core/app/utils/digikam_globals.cpp index e1402f17f9..6abbc15e16 100644 --- a/core/app/utils/digikam_globals.cpp +++ b/core/app/utils/digikam_globals.cpp @@ -1,287 +1,288 @@ /* ============================================================ * * 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; } foreach (const QByteArray& frm, supported) { if (QString::fromLatin1(frm).contains(QLatin1String("tif"), Qt::CaseInsensitive) || QString::fromLatin1(frm).contains(QLatin1String("tiff"), Qt::CaseInsensitive)) { continue; } if (QString::fromLatin1(frm).contains(QLatin1String("jpg"), Qt::CaseInsensitive) || QString::fromLatin1(frm).contains(QLatin1String("jpeg"), Qt::CaseInsensitive)) { 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)) { continue; } #endif // HAVE_JASPER #ifdef HAVE_X265 - if (QString::fromLatin1(frm).contains(QLatin1String("heic"), Qt::CaseInsensitive)) + if (QString::fromLatin1(frm).contains(QLatin1String("heic"), Qt::CaseInsensitive) || + QString::fromLatin1(frm).contains(QLatin1String("heif"), Qt::CaseInsensitive)) { continue; } #endif // HAVE_X265 formats.append(i18n("%1 Image (%2)", QString::fromLatin1(frm).toUpper(), QLatin1String("*.") + QLatin1String(frm))); allTypes.append(QString::fromLatin1("*.%1 ").arg(QLatin1String(frm))); } formats.append(i18n("TIFF Image (*.tiff *.tif)")); allTypes.append(QLatin1String("*.tiff *.tif ")); formats.append(i18n("JPEG Image (*.jpg *.jpeg *.jpe)")); allTypes.append(QLatin1String("*.jpg *.jpeg *.jpe ")); #ifdef HAVE_JASPER 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 ")); #ifdef HAVE_X265 - formats << i18n("High Efficiency Image Coding (*.heic)"); - allTypes.append(QLatin1String("*.heic ")); + formats << i18n("High Efficiency Image Coding (*.heic *.heif)"); + allTypes.append(QLatin1String("*.heic *.heif")); #endif // HAVE_X265 if (mode != QIODevice::WriteOnly) { formats << i18n("Raw Images (%1)", DRawDecoder::rawFiles()); allTypes.append(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 60b185bd5d..a34fd050b1 100644 --- a/core/dplugins/dimg/heif/dimgheifloader.cpp +++ b/core/dplugins/dimg/heif/dimgheifloader.cpp @@ -1,78 +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; + qWarning() << "Error while processing HEIF image:" << error->message; return false; } } // namespace Digikam diff --git a/core/dplugins/dimg/heif/dimgheifloader.h b/core/dplugins/dimg/heif/dimgheifloader.h index e9304302cd..34f7c1c663 100644 --- a/core/dplugins/dimg/heif/dimgheifloader.h +++ b/core/dplugins/dimg/heif/dimgheifloader.h @@ -1,95 +1,97 @@ /* ============================================================ * * 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 * https://github.com/GNOME/gimp/blob/master/plug-ins/common/file-heif.c * + * Specification: https://nokiatech.github.io/heif/technical.html + * * 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; bool hasAlpha() const override; bool sixteenBit() const override; 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); bool readHEICImageByHandle(struct heif_image_handle* image_handle, struct heif_image* heif_image); // 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/heif/dimgheifloader_load.cpp b/core/dplugins/dimg/heif/dimgheifloader_load.cpp index b27703daf0..dda07f2c31 100644 --- a/core/dplugins/dimg/heif/dimgheifloader_load.cpp +++ b/core/dplugins/dimg/heif/dimgheifloader_load.cpp @@ -1,530 +1,533 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-09-26 * Description : A HEIF IO file for DImg framework - load operations * * 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" // 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 { bool DImgHEIFLoader::load(const QString& filePath, DImgLoaderObserver* const observer) { m_observer = observer; readMetadata(filePath); 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."; + qWarning() << "Error: source file is not HEIF 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(), nullptr); 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::readHEICColorProfile(struct heif_image_handle* const image_handle) { #if LIBHEIF_NUMERIC_VERSION >= 0x01040000 switch (heif_image_handle_get_color_profile_type(image_handle)) { case heif_color_profile_type_not_present: break; case heif_color_profile_type_rICC: case heif_color_profile_type_prof: { 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; + qDebug() << "HEIF color profile found with size:" << length; imageSetIccProfile(IccProfile(profile)); return true; } } break; } default: // heif_color_profile_type_nclx - qWarning() << "Unknown HEIC color profile type discarded"; + qWarning() << "Unknown HEIF color profile type discarded"; break; } #else Q_UNUSED(image_handle); #endif // If ICC profile is null, check Exif metadata. if (checkExifWorkingColorSpace()) { return true; } return false; } 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; + qDebug() << "HEIF 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; + qDebug() << "HEIF 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 & LoadMetadata) { readHEICMetadata(image_handle); } if (m_loadFlags & LoadICCData) { readHEICColorProfile(image_handle); } if (m_observer) { m_observer->progressInfo(m_image, 0.2F); } if (m_loadFlags & LoadPreview) { heif_item_id thumbnail_ID = 0; int nThumbnails = heif_image_handle_get_list_of_thumbnail_IDs(image_handle, &thumbnail_ID, 1); - + if (nThumbnails > 0) { struct heif_image_handle* thumbnail_handle = nullptr; error = heif_image_handle_get_thumbnail(image_handle, thumbnail_ID, &thumbnail_handle); - + if (!isHeifSuccess(&error)) { heif_image_handle_release(image_handle); return false; } heif_image_handle_release(image_handle); - qDebug() << "HEIC preview found"; + qDebug() << "HEIF preview found in thumbnail chunk"; return readHEICImageByHandle(thumbnail_handle, heif_image); } } - + if (m_loadFlags & LoadImageData) { return readHEICImageByHandle(image_handle, heif_image); } - + heif_image_handle_release(image_handle); return true; } - + bool DImgHEIFLoader::readHEICImageByHandle(struct heif_image_handle* image_handle, struct heif_image* heif_image) { // Copy HEIF image into data structures. struct heif_error error; 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: (" + qDebug() << "HEIF 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); - heif_colorspace colorSpace = heif_image_get_colorspace(heif_image); - int colorDepth = heif_image_get_bits_per_pixel(heif_image, heif_channel_interleaved); + int colorModel = DImg::COLORMODELUNKNOWN; + int colorDepth = heif_image_get_bits_per_pixel_range(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(" + qDebug() << "Decoded HEIF 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 container:" << ptr; + qDebug() << "HEIF data container:" << ptr; qDebug() << "HEIC bytes per line:" << stride; if (!ptr || stride <= 0) { qWarning() << "HEIC data pixels information not valid!"; heif_image_release(heif_image); heif_image_handle_release(image_handle); return false; } - uchar* data = nullptr; + uchar* data = nullptr; + int colorMul = 1; // color multiplier + colorModel = DImg::RGB; - if (colorDepth == 24 || // RGB - colorDepth == 32) // RGBA + if (colorDepth == 8) { qDebug() << "Color bytes depth: 8"; m_sixteenBit = false; } - else if (colorDepth == 48 || // RGB - colorDepth == 64) // RGBA + else if ((colorDepth > 8) && (colorDepth <= 16)) { qDebug() << "Color bytes depth: 16"; m_sixteenBit = true; + colorMul = 16 - colorDepth; } else { qWarning() << "Color bits depth: " << colorDepth << ": not supported!"; heif_image_release(heif_image); heif_image_handle_release(image_handle); return false; } + qDebug() << "Color multiplier:" << colorMul; + 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 = nullptr; + unsigned short* dst16 = reinterpret_cast(data); uchar* src = nullptr; unsigned short* src16 = nullptr; unsigned int checkPoint = 0; for (unsigned int y = 0 ; y < imageHeight() ; ++y) { src = reinterpret_cast(ptr + (y * stride)); src16 = reinterpret_cast(src); for (unsigned int x = 0 ; x < imageWidth() ; ++x) { if (!m_sixteenBit) // 8 bits image. { // Blue dst[0] = src[2]; // Green dst[1] = src[1]; // Red - dst[2] = src[3]; + dst[2] = src[0]; // 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]; + dst16[0] = (unsigned short)(src16[2] << colorMul); // Green - dst16[1] = src16[1]; + dst16[1] = (unsigned short)(src16[1] << colorMul); // Red - dst16[2] = src16[0]; + dst16[2] = (unsigned short)(src16[0] << colorMul); // Alpha if (m_hasAlpha) { - dst16[3] = src16[3]; + dst16[3] = (unsigned short)(src16[3] << colorMul); src16 += 4; } else { dst16[3] = 0xFFFF; src16 += 3; } dst16 += 4; } } if (m_observer && y >= checkPoint) { checkPoint += granularity(m_observer, y, 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)y) / ((float)imageHeight())))); } } imageData() = data; imageSetAttribute(QLatin1String("format"), QLatin1String("HEIF")); - imageSetAttribute(QLatin1String("originalColorModel"), colorSpace); + imageSetAttribute(QLatin1String("originalColorModel"), colorModel); 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; } } // namespace Digikam diff --git a/core/dplugins/dimg/heif/dimgheifloader_save.cpp b/core/dplugins/dimg/heif/dimgheifloader_save.cpp index 616f901504..e2af13bd94 100644 --- a/core/dplugins/dimg/heif/dimgheifloader_save.cpp +++ b/core/dplugins/dimg/heif/dimgheifloader_save.cpp @@ -1,507 +1,505 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-09-26 * Description : A HEIF IO file for DImg framework - save operations * * 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" // Qt includes #include #include #include #include #include #include // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "dimg.h" #include "dimgloaderobserver.h" #include "metaengine.h" // libx265 includes #ifdef HAVE_X265 # include #endif namespace Digikam { int DImgHEIFLoader::x265MaxBitsDepth() { int maxOutputBitsDepth = -1; #ifdef HAVE_X265 for (int i = 16 ; i >= 8 ; i-=2) { qDebug() << "Check HEVC encoder for" << i << "bits encoding..."; const x265_api* const api = x265_api_get(i); if (api) { maxOutputBitsDepth = i; break; } } qDebug() << "HEVC encoder max bits depth:" << maxOutputBitsDepth; #endif if (maxOutputBitsDepth == -1) { qWarning() << "Cannot get max supported HEVC encoder bits depth!"; } return maxOutputBitsDepth; } bool DImgHEIFLoader::save(const QString& filePath, DImgLoaderObserver* const observer) { m_observer = observer; // ------------------------------------------------------------------- // Open the file FILE* const file = fopen(QFile::encodeName(filePath).constData(), "wb"); if (!file) { qWarning() << "Cannot open target image file."; return false; } fclose(file); QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 75; bool lossless = (quality == 0); // --- Determine libx265 encoder bits depth capability: 8=standard, 10, 12, or later 16. int maxOutputBitsDepth = x265MaxBitsDepth(); if (maxOutputBitsDepth == -1) { return false; } heif_chroma chroma; if (maxOutputBitsDepth > 8) // 16 bits image. { chroma = imageHasAlpha() ? heif_chroma_interleaved_RRGGBBAA_BE : heif_chroma_interleaved_RRGGBB_BE; } else { chroma = imageHasAlpha() ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB; } // --- use standard HEVC encoder qDebug() << "HEVC encoder setup..."; struct heif_context* const ctx = heif_context_alloc(); if (!ctx) { - qWarning() << "Cannot create HEIC context!"; + qWarning() << "Cannot create HEIF context!"; return false; } struct heif_encoder* encoder = nullptr; struct heif_error error = heif_context_get_encoder_for_format(ctx, heif_compression_HEVC, &encoder); if (!isHeifSuccess(&error)) { heif_context_free(ctx); return 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 (!isHeifSuccess(&error)) { heif_encoder_release(encoder); heif_context_free(ctx); return false; } // --- Save color profile before to create image data, as converting to color space can be processed at this stage. - qDebug() << "HEIC set color profile..."; + qDebug() << "HEIF set color profile..."; saveHEICColorProfile(image); // --- Add image data - qDebug() << "HEIC setup data plane..."; + qDebug() << "HEIF setup data plane..."; error = heif_image_add_plane(image, heif_channel_interleaved, imageWidth(), imageHeight(), maxOutputBitsDepth); if (!isHeifSuccess(&error)) { heif_encoder_release(encoder); heif_context_free(ctx); return false; } int stride = 0; uint8_t* const data = heif_image_get_plane(image, heif_channel_interleaved, &stride); if (!data || stride <= 0) { - qWarning() << "HEIC data pixels information not valid!"; + qWarning() << "HEIF data pixels information not valid!"; heif_encoder_release(encoder); heif_context_free(ctx); return false; } - qDebug() << "HEIC data container:" << data; - qDebug() << "HEIC bytes per line:" << stride; + qDebug() << "HEIF data container:" << data; + qDebug() << "HEIF bytes per line:" << stride; uint checkpoint = 0; unsigned char r = 0; unsigned char g = 0; unsigned char b = 0; unsigned char a = 0; unsigned char* src = nullptr; unsigned char* dst = nullptr; unsigned short r16 = 0; unsigned short g16 = 0; unsigned short b16 = 0; unsigned short a16 = 0; unsigned short* src16 = nullptr; unsigned short* dst16 = nullptr; int div16 = 16 - maxOutputBitsDepth; int mul8 = maxOutputBitsDepth - 8; int nbOutputBytesPerColor = (maxOutputBitsDepth > 8) ? (imageHasAlpha() ? 4 * 2 : 3 * 2) // output data stored on 16 bits : (imageHasAlpha() ? 4 : 3 ); // output data stored on 8 bits - qDebug() << "HEIC output bytes per color:" << nbOutputBytesPerColor; - qDebug() << "HEIC 16 to 8 bits coeff. :" << div16; - qDebug() << "HEIC 8 to 16 bits coeff. :" << mul8; + qDebug() << "HEIF output bytes per color:" << nbOutputBytesPerColor; + qDebug() << "HEIF 16 to 8 bits coeff. :" << div16; + qDebug() << "HEIF 8 to 16 bits coeff. :" << mul8; for (unsigned int y = 0 ; y < imageHeight() ; ++y) { src = &imageData()[(y * imageWidth()) * imageBytesDepth()]; src16 = reinterpret_cast(src); dst = reinterpret_cast(data + (y * stride)); dst16 = reinterpret_cast(dst); for (unsigned int x = 0 ; x < imageWidth() ; ++x) { if (imageSixteenBit()) // 16 bits source image. { b16 = src16[0]; g16 = src16[1]; r16 = src16[2]; if (imageHasAlpha()) { a16 = src16[3]; } if (maxOutputBitsDepth > 8) // From 16 bits to 10 bits or more. { dst16[0] = (unsigned short)(r16 >> div16); dst16[1] = (unsigned short)(g16 >> div16); dst16[2] = (unsigned short)(b16 >> div16); if (imageHasAlpha()) { dst16[3] = (unsigned short)(a16 >> div16); dst16 += 4; } else { dst16 += 3; } } else // From 16 bits to 8 bits. { dst[0] = (unsigned char)(r16 >> div16); dst[1] = (unsigned char)(g16 >> div16); dst[2] = (unsigned char)(b16 >> div16); if (imageHasAlpha()) { dst[3] = (unsigned char)(a16 >> div16); dst += 4; } else { dst += 3; } } src16 += 4; } else // 8 bits source image. { b = src[0]; g = src[1]; r = src[2]; if (imageHasAlpha()) { a = src[3]; } if (maxOutputBitsDepth > 8) // From 8 bits to 10 bits or more. { dst16[0] = (unsigned short)(r << mul8); dst16[1] = (unsigned short)(g << mul8); dst16[2] = (unsigned short)(b << mul8); if (imageHasAlpha()) { dst16[3] = (unsigned short)(a << mul8); dst16 += 4; } else { dst16 += 3; } } else // From 8 bits to 8 bits. { dst[0] = r; dst[1] = g; dst[2] = b; if (imageHasAlpha()) { dst[3] = a; dst += 4; } else { dst += 3; } } src += 4; } } if (m_observer && y == (long)checkpoint) { checkpoint += granularity(m_observer, imageHeight(), 0.8F); if (!m_observer->continueQuery(m_image)) { heif_encoder_release(encoder); heif_context_free(ctx); return false; } m_observer->progressInfo(m_image, 0.1 + (0.8 * (((float)y) / ((float)imageHeight())))); } } - qDebug() << "HEIC master image encoding..."; + qDebug() << "HEIF master image encoding..."; // --- encode and write master image struct heif_encoding_options* const options = heif_encoding_options_alloc(); options->save_alpha_channel = imageHasAlpha() ? 1 : 0; struct heif_image_handle* image_handle = nullptr; error = heif_context_encode_image(ctx, image, encoder, options, &image_handle); if (!isHeifSuccess(&error)) { heif_encoding_options_free(options); heif_image_handle_release(image_handle); heif_encoder_release(encoder); heif_context_free(ctx); return false; } // --- encode thumbnail // Note: Only encode preview for large image. // We will use the same preview size than DImg::prepareMetadataToSave() const int previewSize = 1280; if (qMin(imageWidth(), imageHeight()) > previewSize) { - qDebug() << "HEIC preview storage..."; + qDebug() << "HEIF preview storage in thumbnail chunk..."; struct heif_image_handle* thumbnail_handle = nullptr; error = heif_context_encode_thumbnail(ctx, image, image_handle, encoder, options, previewSize, &thumbnail_handle); if (!isHeifSuccess(&error)) { heif_encoding_options_free(options); heif_image_handle_release(image_handle); heif_encoder_release(encoder); heif_context_free(ctx); return false; } heif_image_handle_release(thumbnail_handle); } heif_encoding_options_free(options); heif_encoder_release(encoder); // --- Add Exif and XMP metadata - qDebug() << "HEIC metadata storage..."; + qDebug() << "HEIF metadata storage..."; saveHEICMetadata(ctx, image_handle); heif_image_handle_release(image_handle); - // --- TODO: Add thumnail image. - // --- write HEIF file - qDebug() << "HEIC flush to file..."; + qDebug() << "HEIF flush to file..."; error = heif_context_write_to_file(ctx, QFile::encodeName(filePath).constData()); if (!isHeifSuccess(&error)) { heif_context_free(ctx); return false; } heif_context_free(ctx); imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("HEIF")); saveMetadata(filePath); return true; } bool DImgHEIFLoader::saveHEICColorProfile(struct heif_image* const image) { #if LIBHEIF_NUMERIC_VERSION >= 0x01040000 QByteArray profile = m_image->getIccProfile().data(); if (!profile.isEmpty()) { // Save color profile. struct heif_error error = heif_image_set_raw_color_profile(image, "prof", // FIXME: detect string in profile data profile.data(), profile.size()); if (error.code != 0) { - qWarning() << "Cannot set HEIC color profile!"; + qWarning() << "Cannot set HEIF color profile!"; return false; } - qDebug() << "Stored HEIC color profile size:" << profile.size(); + qDebug() << "Stored HEIF color profile size:" << profile.size(); } #else Q_UNUSED(image_handle); #endif return true; } bool DImgHEIFLoader::saveHEICMetadata(struct heif_context* const heif_context, struct heif_image_handle* const image_handle) { MetaEngine meta(m_image->getMetadata()); if (!meta.hasExif() && !meta.hasXmp()) { return false; } QByteArray exif = meta.getExifEncoded(); QByteArray xmp = meta.getXmp(); struct heif_error error; if (!exif.isEmpty()) { error = heif_context_add_exif_metadata(heif_context, image_handle, exif.data(), exif.size()); if (error.code != 0) { - qWarning() << "Cannot store HEIC Exif metadata!"; + qWarning() << "Cannot store HEIF Exif metadata!"; return false; } - qDebug() << "Stored HEIC Exif data size:" << exif.size(); + qDebug() << "Stored HEIF Exif data size:" << exif.size(); } if (!xmp.isEmpty()) { error = heif_context_add_XMP_metadata(heif_context, image_handle, xmp.data(), xmp.size()); if (error.code != 0) { - qWarning() << "Cannot store HEIC Xmp metadata!"; + qWarning() << "Cannot store HEIF Xmp metadata!"; return false; } - qDebug() << "Stored HEIC Xmp data size:" << xmp.size(); + qDebug() << "Stored HEIF Xmp data size:" << xmp.size(); } return true; } } // namespace Digikam diff --git a/core/dplugins/dimg/heif/dimgheifplugin.cpp b/core/dplugins/dimg/heif/dimgheifplugin.cpp index 5eae4f295b..c5476d99cb 100644 --- a/core/dplugins/dimg/heif/dimgheifplugin.cpp +++ b/core/dplugins/dimg/heif/dimgheifplugin.cpp @@ -1,208 +1,214 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-09-22 * Description : HEIF DImg plugin. * * 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 "dimgheifplugin.h" // C++ includes #include // Qt includes #include #include // KDE includes #include // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "digikam_globals.h" #include "dimgheifloader.h" namespace DigikamHEIFDImgPlugin { DImgHEIFPlugin::DImgHEIFPlugin(QObject* const parent) : DPluginDImg(parent) { } DImgHEIFPlugin::~DImgHEIFPlugin() { } QString DImgHEIFPlugin::name() const { return i18n("HEIF loader"); } QString DImgHEIFPlugin::iid() const { return QLatin1String(DPLUGIN_IID); } QIcon DImgHEIFPlugin::icon() const { return QIcon::fromTheme(QLatin1String("image-x-generic")); } QString DImgHEIFPlugin::description() const { return i18n("An image loader based on Libheif codec"); } QString DImgHEIFPlugin::details() const { QString x265Notice = i18n("This library is not present on your system."); #ifdef HAVE_X265 int depth = DImgHEIFLoader::x265MaxBitsDepth(); if (depth != -1) { x265Notice = i18n("This library is available on your system with a maximum color depth " "support of %1 bits.", depth); } else { x265Notice = i18n("This library is available on your system but is not able to encode " "image with a suitable color depth."); } #endif return i18n("

This plugin permit to load and save image using Libheif codec.

" "

High Efficiency Image File Format (HEIF), also known as High Efficiency Image Coding (HEIC), " "is a file format for individual images and image sequences. It was developed by the " "Moving Picture Experts Group (MPEG) and it claims that twice as much information can be " "stored in a HEIF image as in a JPEG image of the same size, resulting in a better quality image. " "HEIF also supports animation, and is capable of storing more information than an animated GIF " "at a small fraction of the size.

" "

Encoding HEIC is relevant of optional libx265 codec. %1

" "

See " "High Efficiency Image File Format for details.

", x265Notice); } QList DImgHEIFPlugin::authors() const { return QList() << DPluginAuthor(QString::fromUtf8("Gilles Caulier"), QString::fromUtf8("caulier dot gilles at gmail dot com"), QString::fromUtf8("(C) 2019")) ; } void DImgHEIFPlugin::setup(QObject* const /*parent*/) { // Nothing to do } QMap DImgHEIFPlugin::extraAboutData() const { QMap map; map.insert(QLatin1String("HEIC"), i18n("High efficiency image coding")); + map.insert(QLatin1String("HEIF"), i18n("High efficiency image file format")); return map; } +bool DImgHEIFPlugin::previewSupported() const +{ + return true; +} + QString DImgHEIFPlugin::loaderName() const { return QLatin1String("HEIF"); } QString DImgHEIFPlugin::typeMimes() const { - return QLatin1String("HEIC"); + return QLatin1String("HEIC HEIF"); } bool DImgHEIFPlugin::canRead(const QString& filePath, bool magic) const { QFileInfo fileInfo(filePath); if (!fileInfo.exists()) { qCDebug(DIGIKAM_DIMG_LOG) << "File " << filePath << " does not exist"; return false; } // First simply check file extension if (!magic) { QString ext = fileInfo.suffix().toUpper(); return (ext == QLatin1String("HEIC")); } // In second, we trying to parse file header. FILE* const f = fopen(QFile::encodeName(filePath).constData(), "rb"); if (!f) { qCDebug(DIGIKAM_DIMG_LOG) << "Failed to open file " << filePath; return false; } const int headerLen = 12; unsigned char header[headerLen]; if (fread(&header, headerLen, 1, f) != 1) { qCDebug(DIGIKAM_DIMG_LOG) << "Failed to read header of file " << filePath; fclose(f); return false; } fclose(f); 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)) { return true; } return false; } bool DImgHEIFPlugin::canWrite(const QString& format) const { #ifdef HAVE_X265 if (format.toUpper() == QLatin1String("HEIC")) { return true; } #endif return false; } DImgLoader* DImgHEIFPlugin::loader(DImg* const image, const DRawDecoding&) const { return new DImgHEIFLoader(image); } } // namespace DigikamHEIFDImgPlugin diff --git a/core/dplugins/dimg/heif/dimgheifplugin.h b/core/dplugins/dimg/heif/dimgheifplugin.h index 14ec2ea1ad..5f9a7e4d53 100644 --- a/core/dplugins/dimg/heif/dimgheifplugin.h +++ b/core/dplugins/dimg/heif/dimgheifplugin.h @@ -1,74 +1,76 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-09-26 * Description : HEIF DImg plugin. * * 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. * * ============================================================ */ #ifndef DIGIKAM_DIMG_HEIF_PLUGIN_H #define DIGIKAM_DIMG_HEIF_PLUGIN_H // Qt includes #include #include // Local includes #include "dplugindimg.h" #include "dimg.h" #include "dimgloader.h" #define DPLUGIN_IID "org.kde.digikam.plugin.dimg.HEIF" using namespace Digikam; namespace DigikamHEIFDImgPlugin { class DImgHEIFPlugin : public DPluginDImg { Q_OBJECT Q_PLUGIN_METADATA(IID DPLUGIN_IID) Q_INTERFACES(Digikam::DPluginDImg) public: explicit DImgHEIFPlugin(QObject* const parent = nullptr); ~DImgHEIFPlugin(); QString name() const override; QString iid() const override; QIcon icon() const override; QString details() const override; QString description() const override; QList authors() const override; void setup(QObject* const) override; QMap extraAboutData() const override; + bool previewSupported() const override; + QString loaderName() const; QString typeMimes() const; bool canRead(const QString& filePath, bool magic) const; bool canWrite(const QString& format) const; DImgLoader* loader(DImg* const image, const DRawDecoding& rawSettings = DRawDecoding()) const; }; } // namespace DigikamHEIFDImgPlugin #endif // DIGIKAM_DIMG_HEIF_PLUGIN_H diff --git a/core/dplugins/dimg/imagemagick/dimgimagemagickplugin.cpp b/core/dplugins/dimg/imagemagick/dimgimagemagickplugin.cpp index 12fc637f09..e791d25489 100644 --- a/core/dplugins/dimg/imagemagick/dimgimagemagickplugin.cpp +++ b/core/dplugins/dimg/imagemagick/dimgimagemagickplugin.cpp @@ -1,333 +1,334 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-09-21 * Description : ImageMagick DImg plugin. * * 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 "dimgimagemagickplugin.h" // Image Magick includes #include #if MagickLibVersion < 0x700 # include #endif using namespace Magick; using namespace MagickCore; // Qt includes #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "digikam_globals.h" #include "dimgimagemagickloader.h" #include "drawdecoder.h" namespace DigikamImageMagickDImgPlugin { DImgImageMagickPlugin::DImgImageMagickPlugin(QObject* const parent) : DPluginDImg(parent) { MagickCoreGenesis((char*)NULL ,MagickFalse); } DImgImageMagickPlugin::~DImgImageMagickPlugin() { MagickCoreTerminus(); } QString DImgImageMagickPlugin::name() const { return i18n("ImageMagick loader"); } QString DImgImageMagickPlugin::iid() const { return QLatin1String(DPLUGIN_IID); } QIcon DImgImageMagickPlugin::icon() const { return QIcon::fromTheme(QLatin1String("image-x-generic")); } QString DImgImageMagickPlugin::description() const { return i18n("An image loader based on ImageMagick coders"); } QString DImgImageMagickPlugin::details() const { return i18n("

This plugin permit to load and save image using ImageMagick coders.

" "

ImageMagick is a free and open-source software suite for converting raster image and vector image files. " "It can read and write over 200 image file formats.

" "

See ImageMagick documentation for details.

" ); } QList DImgImageMagickPlugin::authors() const { return QList() << DPluginAuthor(QString::fromUtf8("Maik Qualmann"), QString::fromUtf8("metzpinguin at gmail dot com"), QString::fromUtf8("(C) 2019")) << DPluginAuthor(QString::fromUtf8("Gilles Caulier"), QString::fromUtf8("caulier dot gilles at gmail dot com"), QString::fromUtf8("(C) 2006-2019")) ; } void DImgImageMagickPlugin::setup(QObject* const /*parent*/) { // Nothing to do } QMap DImgImageMagickPlugin::extraAboutData() const { QString mimes = typeMimes(); QMap map; ExceptionInfo ex; size_t n = 0; const MagickInfo** inflst = GetMagickInfoList("*", &n, &ex); if (!inflst) { qWarning() << "ImageMagick coders list is null!"; return QMap(); } for (uint i = 0 ; i < n ; ++i) { const MagickInfo* inf = inflst[i]; if (inf) { QString mod = #if (MagickLibVersion >= 0x69A && defined(magick_module)) QString::fromLatin1(inf->magick_module).toUpper(); #else QString::fromLatin1(inf->module).toUpper(); #endif if (mimes.contains(mod)) { map.insert(mod, QLatin1String(inf->description)); } } } return map; } QString DImgImageMagickPlugin::loaderName() const { return QLatin1String("IMAGEMAGICK"); } QString DImgImageMagickPlugin::typeMimes() const { QStringList formats; ExceptionInfo ex; size_t n = 0; const MagickInfo** inflst = GetMagickInfoList("*", &n, &ex); if (!inflst) { qWarning() << "ImageMagick coders list is null!"; return QString(); } for (uint i = 0 ; i < n ; ++i) { const MagickInfo* inf = inflst[i]; if (inf && inf->decoder) { #if (MagickLibVersion >= 0x69A && defined(magick_module)) formats.append(QString::fromLatin1(inf->magick_module).toUpper()); #else formats.append(QString::fromLatin1(inf->module).toUpper()); #endif } } qDebug() << "ImageMagick support this formats:" << formats; formats.removeAll(QLatin1String("JPEG")); // JPEG file format formats.removeAll(QLatin1String("JPG")); // JPEG file format formats.removeAll(QLatin1String("JPE")); // JPEG file format formats.removeAll(QLatin1String("PNG")); formats.removeAll(QLatin1String("TIFF")); formats.removeAll(QLatin1String("TIF")); formats.removeAll(QLatin1String("PGF")); formats.removeAll(QLatin1String("JP2")); // JPEG2000 file format formats.removeAll(QLatin1String("JPX")); // JPEG2000 file format formats.removeAll(QLatin1String("JPC")); // JPEG2000 code stream formats.removeAll(QLatin1String("J2K")); // JPEG2000 code stream formats.removeAll(QLatin1String("PGX")); // JPEG2000 WM format formats.removeAll(QLatin1String("HEIC")); + formats.removeAll(QLatin1String("HEIF")); QString rawFilesExt = QString(DRawDecoder::rawFiles()).remove(QLatin1String("*.")).toUpper(); foreach (const QString& str, rawFilesExt.split(QLatin1Char(' '))) { formats.removeAll(str); // All Raw image formats } QString ret; foreach (const QString& str, formats) { if (!ret.contains(str)) { ret += QString::fromUtf8("%1 ").arg(str.toUpper()); } } return ret; } bool DImgImageMagickPlugin::canRead(const QString& filePath, bool magic) const { QFileInfo fileInfo(filePath); if (!fileInfo.exists()) { qCDebug(DIGIKAM_DIMG_LOG) << "File " << filePath << " does not exist"; return false; } if (!magic) { QString mimeType(QMimeDatabase().mimeTypeForFile(filePath).name()); // Ignore non image format. if ( mimeType.startsWith(QLatin1String("video/")) || mimeType.startsWith(QLatin1String("audio/")) ) { return false; } QString format = fileInfo.suffix().toUpper(); - QString blackList = QString(DRawDecoder::rawFiles()).remove(QLatin1String("*.")).toUpper(); // Ignore RAW files - blackList.append(QLatin1String(" JPEG JPG JPE PNG TIF TIFF PGF JP2 JPX JPC J2K PGX HEIC ")); // Ignore native loaders + QString blackList = QString(DRawDecoder::rawFiles()).remove(QLatin1String("*.")).toUpper(); // Ignore RAW files + blackList.append(QLatin1String(" JPEG JPG JPE PNG TIF TIFF PGF JP2 JPX JPC J2K PGX HEIC HEIF ")); // Ignore native loaders if (blackList.toUpper().contains(format)) { return false; } QStringList formats; ExceptionInfo ex; size_t n = 0; const MagickInfo** inflst = GetMagickInfoList("*", &n, &ex); if (!inflst) { qWarning() << "ImageMagick coders list is null!"; return false; } for (uint i = 0 ; i < n ; ++i) { const MagickInfo* inf = inflst[i]; if (inf && inf->decoder) { #if (MagickLibVersion >= 0x69A && defined(magick_module)) formats.append(QString::fromLatin1(inf->magick_module).toUpper()); #else formats.append(QString::fromLatin1(inf->module).toUpper()); #endif } } return (formats.contains(format)); } return false; } bool DImgImageMagickPlugin::canWrite(const QString& format) const { - QString blackList = QString(DRawDecoder::rawFiles()).remove(QLatin1String("*.")).toUpper(); // Ignore RAW files - blackList.append(QLatin1String(" JPEG JPG JPE PNG TIF TIFF PGF JP2 JPX JPC J2K PGX HEIC ")); // Ignore native loaders + QString blackList = QString(DRawDecoder::rawFiles()).remove(QLatin1String("*.")).toUpper(); // Ignore RAW files + blackList.append(QLatin1String(" JPEG JPG JPE PNG TIF TIFF PGF JP2 JPX JPC J2K PGX HEIC HEIF ")); // Ignore native loaders if (blackList.toUpper().contains(format)) { return false; } // NOTE: Native loaders support are previously black-listed. // For ex, if tiff is supported in write mode by ImageMagick it will never be handled. QStringList formats; ExceptionInfo ex; size_t n = 0; const MagickInfo** inflst = GetMagickInfoList("*", &n, &ex); if (!inflst) { qWarning() << "ImageMagick coders list is null!"; return false; } for (uint i = 0 ; i < n ; ++i) { const MagickInfo* inf = inflst[i]; if (inf && inf->encoder) { #if (MagickLibVersion >= 0x69A && defined(magick_module)) formats.append(QString::fromLatin1(inf->magick_module).toUpper()); #else formats.append(QString::fromLatin1(inf->module).toUpper()); #endif } } if (!formats.contains(format)) { return false; } return true; } DImgLoader* DImgImageMagickPlugin::loader(DImg* const image, const DRawDecoding&) const { return new DImgImageMagickLoader(image); } } // namespace DigikamImageMagickDImgPlugin diff --git a/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader_load.cpp b/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader_load.cpp index c112be4c05..f56ff2417a 100644 --- a/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader_load.cpp +++ b/core/dplugins/dimg/jpeg2000/dimgjpeg2000loader_load.cpp @@ -1,582 +1,583 @@ /* ============================================================ * * 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- load 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::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; + int colorModel = DImg::COLORMODELUNKNOWN; 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) + 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_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) + 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]); + + 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) + 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_; + jas_stream_memobj_t* const 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; } } // namespace DigikamJPEG2000DImgPlugin diff --git a/core/dplugins/dimg/png/dimgpngloader_load.cpp b/core/dplugins/dimg/png/dimgpngloader_load.cpp index 8785256080..801c628879 100644 --- a/core/dplugins/dimg/png/dimgpngloader_load.cpp +++ b/core/dplugins/dimg/png/dimgpngloader_load.cpp @@ -1,658 +1,663 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-11-01 * Description : a PNG image loader for DImg framework - load operations. * * Copyright (C) 2005-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. * * ============================================================ */ #define PNG_BYTES_TO_CHECK 4 #include "dimgpngloader.h" // C ANSI includes extern "C" { #include } // C++ includes #include #include // Qt includes #include #include #include // Local includes #include "metaengine.h" #include "digikam_debug.h" #include "digikam_config.h" #include "digikam_version.h" #include "dimgloaderobserver.h" // libPNG includes extern "C" { #include } #ifdef Q_OS_WIN void _ReadProc(struct png_struct_def* png_ptr, png_bytep data, png_size_t size) { FILE* const file_handle = (FILE*)png_get_io_ptr(png_ptr); fread(data, size, 1, file_handle); } #endif using namespace Digikam; namespace DigikamPNGDImgPlugin { #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 typedef png_bytep iCCP_data; #else typedef png_charp iCCP_data; #endif bool DImgPNGLoader::load(const QString& filePath, DImgLoaderObserver* const observer) { png_uint_32 w32, h32; int width, height; int bit_depth, color_type, interlace_type; FILE* f = nullptr; png_structp png_ptr = nullptr; png_infop info_ptr = nullptr; // To prevent cppcheck warnings. (void)f; (void)png_ptr; (void)info_ptr; readMetadata(filePath); // ------------------------------------------------------------------- // Open the file qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Opening file" << filePath; f = fopen(QFile::encodeName(filePath).constData(), "rb"); if (!f) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Cannot open image file."; loadingFailed(); return false; } unsigned char buf[PNG_BYTES_TO_CHECK]; size_t membersRead = fread(buf, 1, PNG_BYTES_TO_CHECK, f); #if PNG_LIBPNG_VER >= 10400 if ((membersRead != PNG_BYTES_TO_CHECK) || png_sig_cmp(buf, 0, PNG_BYTES_TO_CHECK)) #else if ((membersRead != PNG_BYTES_TO_CHECK) || !png_check_sig(buf, PNG_BYTES_TO_CHECK)) #endif { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Not a PNG image file."; fclose(f); loadingFailed(); return false; } rewind(f); // ------------------------------------------------------------------- // Initialize the internal structures png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png_ptr) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Invalid PNG image file structure."; fclose(f); loadingFailed(); return false; } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Cannot reading PNG image file structure."; png_destroy_read_struct(&png_ptr, nullptr, nullptr); fclose(f); loadingFailed(); return false; } // ------------------------------------------------------------------- // PNG error handling. If an error occurs during reading, libpng // will jump here // setjmp-save cleanup class Q_DECL_HIDDEN CleanupData { public: CleanupData() : data(nullptr), lines(nullptr), file(nullptr), cmod(0) { } ~CleanupData() { delete [] data; freeLines(); if (file) { fclose(file); } } void setData(uchar* const d) { data = d; } void setLines(uchar** const l) { lines = l; } void setFile(FILE* const f) { file = f; } void setSize(const QSize& s) { size = s; } void setColorModel(int c) { cmod = c; } void takeData() { data = nullptr; } void freeLines() { if (lines) { free(lines); } lines = nullptr; } uchar* data; uchar** lines; FILE* file; QSize size; int cmod; }; CleanupData* const cleanupData = new CleanupData; cleanupData->setFile(f); #if PNG_LIBPNG_VER >= 10400 if (setjmp(png_jmpbuf(png_ptr))) #else if (setjmp(png_ptr->jmpbuf)) #endif { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); if (!cleanupData->data || !cleanupData->size.isValid()) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Internal libPNG error during reading file. Process aborted!"; delete cleanupData; loadingFailed(); return false; } // We check only Exif metadata for ICC profile to prevent endless loop if (m_loadFlags & LoadICCData) { checkExifWorkingColorSpace(); } if (observer) { observer->progressInfo(m_image, 1.0); } imageWidth() = cleanupData->size.width(); imageHeight() = cleanupData->size.height(); imageData() = cleanupData->data; imageSetAttribute(QLatin1String("format"), QLatin1String("PNG")); imageSetAttribute(QLatin1String("originalColorModel"), cleanupData->cmod); imageSetAttribute(QLatin1String("originalBitDepth"), m_sixteenBit ? 16 : 8); imageSetAttribute(QLatin1String("originalSize"), cleanupData->size); cleanupData->takeData(); delete cleanupData; return true; } #ifdef Q_OS_WIN png_set_read_fn(png_ptr, f, _ReadProc); #else png_init_io(png_ptr, f); #endif // ------------------------------------------------------------------- // Read all PNG info up to image data png_read_info(png_ptr, info_ptr); - png_get_IHDR(png_ptr, info_ptr, (png_uint_32*)(&w32), - (png_uint_32*)(&h32), &bit_depth, &color_type, - &interlace_type, nullptr, nullptr); + png_get_IHDR(png_ptr, + info_ptr, + (png_uint_32*)(&w32), + (png_uint_32*)(&h32), + &bit_depth, + &color_type, + &interlace_type, + nullptr, + nullptr); width = (int)w32; height = (int)h32; int colorModel = DImg::COLORMODELUNKNOWN; m_sixteenBit = (bit_depth == 16); switch (color_type) { case PNG_COLOR_TYPE_RGB: // RGB m_hasAlpha = false; colorModel = DImg::RGB; break; case PNG_COLOR_TYPE_RGB_ALPHA: // RGBA m_hasAlpha = true; colorModel = DImg::RGB; break; case PNG_COLOR_TYPE_GRAY: // Grayscale m_hasAlpha = false; colorModel = DImg::GRAYSCALE; break; case PNG_COLOR_TYPE_GRAY_ALPHA: // Grayscale + Alpha m_hasAlpha = true; colorModel = DImg::GRAYSCALE; break; case PNG_COLOR_TYPE_PALETTE: // Indexed m_hasAlpha = false; colorModel = DImg::INDEXED; break; } cleanupData->setColorModel(colorModel); cleanupData->setSize(QSize(width, height)); uchar* data = nullptr; if (m_loadFlags & LoadImageData) { // TODO: Endianness: // You may notice that the code for little and big endian // below is now identical. This was found to work by PPC users. // If this proves right, all the conditional clauses can be removed. if (bit_depth == 16) { qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in 16 bits/color/pixel."; switch (color_type) { case PNG_COLOR_TYPE_RGB : // RGB qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_RGB"; png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); break; case PNG_COLOR_TYPE_RGB_ALPHA : // RGBA qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_RGB_ALPHA"; break; case PNG_COLOR_TYPE_GRAY : // Grayscale qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_GRAY"; png_set_gray_to_rgb(png_ptr); png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); break; case PNG_COLOR_TYPE_GRAY_ALPHA : // Grayscale + Alpha qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_GRAY_ALPHA"; png_set_gray_to_rgb(png_ptr); break; case PNG_COLOR_TYPE_PALETTE : // Indexed qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_PALETTE"; png_set_palette_to_rgb(png_ptr); png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); break; default: qCWarning(DIGIKAM_DIMG_LOG_PNG) << "PNG color type unknown."; delete cleanupData; loadingFailed(); return false; } } else { qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in >=8 bits/color/pixel."; png_set_packing(png_ptr); switch (color_type) { case PNG_COLOR_TYPE_RGB : // RGB qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_RGB"; png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); break; case PNG_COLOR_TYPE_RGB_ALPHA : // RGBA qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_RGB_ALPHA"; break; case PNG_COLOR_TYPE_GRAY : // Grayscale qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_GRAY"; #if PNG_LIBPNG_VER >= 10400 png_set_expand_gray_1_2_4_to_8(png_ptr); #else png_set_gray_1_2_4_to_8(png_ptr); #endif png_set_gray_to_rgb(png_ptr); png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); break; case PNG_COLOR_TYPE_GRAY_ALPHA : // Grayscale + alpha qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_GRAY_ALPHA"; png_set_gray_to_rgb(png_ptr); break; case PNG_COLOR_TYPE_PALETTE : // Indexed qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_PALETTE"; png_set_packing(png_ptr); png_set_palette_to_rgb(png_ptr); png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); break; default: qCWarning(DIGIKAM_DIMG_LOG_PNG) << "PNG color type unknown." << color_type; delete cleanupData; loadingFailed(); return false; } } if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(png_ptr); } png_set_bgr(png_ptr); //png_set_swap_alpha(png_ptr); if (observer) { observer->progressInfo(m_image, 0.1F); } // ------------------------------------------------------------------- // Get image data. // Call before png_read_update_info and png_start_read_image() // for non-interlaced images number_passes will be 1 int number_passes = png_set_interlace_handling(png_ptr); png_read_update_info(png_ptr, info_ptr); if (m_sixteenBit) { data = new_failureTolerant(width, height, 8); // 16 bits/color/pixel } else { data = new_failureTolerant(width, height, 4); // 8 bits/color/pixel } cleanupData->setData(data); uchar** lines = nullptr; (void)lines; // to prevent cppcheck warnings. lines = (uchar**)malloc(height * sizeof(uchar*)); cleanupData->setLines(lines); if (!data || !lines) { qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Cannot allocate memory to load PNG image data."; png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); delete cleanupData; loadingFailed(); return false; } - for (int i = 0; i < height; ++i) + for (int i = 0 ; i < height ; ++i) { if (m_sixteenBit) { lines[i] = data + (i * width * 8); } else { lines[i] = data + (i * width * 4); } } // The easy way to read the whole image // png_read_image(png_ptr, lines); // The other way to read images is row by row. Necessary for observer. // Now we need to deal with interlacing. - for (int pass = 0; pass < number_passes; ++pass) + for (int pass = 0 ; pass < number_passes ; ++pass) { - int y; int checkPoint = 0; - for (y = 0; y < height; ++y) + for (int y = 0 ; y < height ; ++y) { if (observer && y == checkPoint) { checkPoint += granularity(observer, height, 0.7F); if (!observer->continueQuery(m_image)) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); delete cleanupData; loadingFailed(); return false; } // use 10% - 80% for progress while reading rows observer->progressInfo(m_image, 0.1 + (0.7 * (((float)y) / ((float)height)))); } png_read_rows(png_ptr, lines + y, nullptr, 1); } } cleanupData->freeLines(); if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { // Swap bytes in 16 bits/color/pixel for DImg if (m_sixteenBit) { uchar ptr[8]; // One pixel to swap - for (int p = 0; p < width * height * 8; p += 8) + for (int p = 0 ; p < width * height * 8 ; p += 8) { memcpy(&ptr[0], &data[p], 8); // Current pixel - data[ p ] = ptr[1]; // Blue + data[ p ] = ptr[1]; // Blue data[p + 1] = ptr[0]; data[p + 2] = ptr[3]; // Green data[p + 3] = ptr[2]; data[p + 4] = ptr[5]; // Red data[p + 5] = ptr[4]; data[p + 6] = ptr[7]; // Alpha data[p + 7] = ptr[6]; } } } } if (observer) { observer->progressInfo(m_image, 0.9F); } // ------------------------------------------------------------------- // Read image ICC profile if (m_loadFlags & LoadICCData) { png_charp profile_name; iCCP_data profile_data = nullptr; png_uint_32 profile_size; int compression_type; png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &profile_size); if (profile_data != nullptr) { QByteArray profile_rawdata; profile_rawdata.resize(profile_size); memcpy(profile_rawdata.data(), profile_data, profile_size); imageSetIccProfile(IccProfile(profile_rawdata)); } else { // If ICC profile is null, check Exif metadata. checkExifWorkingColorSpace(); } } // ------------------------------------------------------------------- // Get embedded text data. png_text* text_ptr = nullptr; int num_comments = png_get_text(png_ptr, info_ptr, &text_ptr, nullptr); /* Standard Embedded text includes in PNG : Title Short (one line) title or caption for image Author Name of image's creator Description Description of image (possibly long) Copyright Copyright notice Creation Time Time of original image creation Software Software used to create the image Disclaimer Legal disclaimer Warning Warning of nature of content Source Device used to create the image Comment Miscellaneous comment; conversion from GIF comment Extra Raw profiles tag are used by ImageMagick and defines at this URL : http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-5.87/html/TagNames/PNG.html#TextualData */ if (m_loadFlags & LoadICCData) { for (int i = 0; i < num_comments; ++i) { // Check if we have a Raw profile embedded using ImageMagick technique. if (memcmp(text_ptr[i].key, "Raw profile type exif", 21) != 0 || memcmp(text_ptr[i].key, "Raw profile type APP1", 21) != 0 || memcmp(text_ptr[i].key, "Raw profile type iptc", 21) != 0) { imageSetEmbbededText(QLatin1String(text_ptr[i].key), QLatin1String(text_ptr[i].text)); qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Reading PNG Embedded text: key=" << text_ptr[i].key << " text=" << text_ptr[i].text; } } } // ------------------------------------------------------------------- if (m_loadFlags & LoadImageData) { png_read_end(png_ptr, info_ptr); } png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); cleanupData->takeData(); delete cleanupData; if (observer) { observer->progressInfo(m_image, 1.0); } imageWidth() = width; imageHeight() = height; imageData() = data; imageSetAttribute(QLatin1String("format"), QLatin1String("PNG")); imageSetAttribute(QLatin1String("originalColorModel"), colorModel); imageSetAttribute(QLatin1String("originalBitDepth"), bit_depth); imageSetAttribute(QLatin1String("originalSize"), QSize(width, height)); return true; } } // namespace DigikamPNGDImgPlugin diff --git a/core/dplugins/dimg/png/dimgpngloader_save.cpp b/core/dplugins/dimg/png/dimgpngloader_save.cpp index 702f6e723a..f01382daba 100644 --- a/core/dplugins/dimg/png/dimgpngloader_save.cpp +++ b/core/dplugins/dimg/png/dimgpngloader_save.cpp @@ -1,424 +1,424 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-11-01 * Description : a PNG image loader for DImg framework - save operations. * * Copyright (C) 2005-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 "dimgpngloader.h" // C ANSI includes extern "C" { #include } // C++ includes #include #include // Qt includes #include #include #include // Local includes #include "metaengine.h" #include "digikam_debug.h" #include "digikam_config.h" #include "digikam_version.h" #include "dimgloaderobserver.h" // libPNG includes extern "C" { #include } using namespace Digikam; namespace DigikamPNGDImgPlugin { #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 typedef png_bytep iCCP_data; #else typedef png_charp iCCP_data; #endif bool DImgPNGLoader::save(const QString& filePath, DImgLoaderObserver* const observer) { png_structp png_ptr; png_infop info_ptr; uint x, y, j; png_bytep row_ptr; png_color_8 sig_bit; FILE* f = nullptr; uchar* ptr = nullptr; uchar* data = nullptr; int quality = 75; int compression = 3; // Tp prevent cppcheck warnings. (void)f; (void)ptr; (void)data; (void)quality; (void)compression; // ------------------------------------------------------------------- // Open the file f = fopen(QFile::encodeName(filePath).constData(), "wb"); if (!f) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Cannot open target image file."; return false; } // ------------------------------------------------------------------- // Initialize the internal structures png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png_ptr) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Invalid target PNG image file structure."; fclose(f); return false; } info_ptr = png_create_info_struct(png_ptr); if (info_ptr == nullptr) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Cannot create PNG image file structure."; png_destroy_write_struct(&png_ptr, (png_infopp) nullptr); fclose(f); return false; } // ------------------------------------------------------------------- // PNG error handling. If an error occurs during writing, libpng // will jump here // setjmp-save cleanup class Q_DECL_HIDDEN CleanupData { public: CleanupData() : data(nullptr), f(nullptr) { } ~CleanupData() { delete [] data; if (f) { fclose(f); } } void setData(uchar* const d) { data = d; } void setFile(FILE* const file) { f = file; } uchar* data; FILE* f; }; CleanupData* const cleanupData = new CleanupData; cleanupData->setFile(f); #if PNG_LIBPNG_VER >= 10400 if (setjmp(png_jmpbuf(png_ptr))) #else if (setjmp(png_ptr->jmpbuf)) #endif { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Internal libPNG error during writing file. Process aborted!"; png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); delete cleanupData; return false; } png_init_io(png_ptr, f); png_set_bgr(png_ptr); //png_set_swap_alpha(png_ptr); if (imageHasAlpha()) { png_set_IHDR(png_ptr, info_ptr, imageWidth(), imageHeight(), imageBitsDepth(), PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); if (imageSixteenBit()) { data = new uchar[imageWidth() * 8 * sizeof(uchar)]; } else { data = new uchar[imageWidth() * 4 * sizeof(uchar)]; } } else { png_set_IHDR(png_ptr, info_ptr, imageWidth(), imageHeight(), imageBitsDepth(), PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); if (imageSixteenBit()) { data = new uchar[imageWidth() * 6 * sizeof(uchar)]; } else { data = new uchar[imageWidth() * 3 * sizeof(uchar)]; } } cleanupData->setData(data); sig_bit.red = imageBitsDepth(); sig_bit.green = imageBitsDepth(); sig_bit.blue = imageBitsDepth(); sig_bit.alpha = imageBitsDepth(); png_set_sBIT(png_ptr, info_ptr, &sig_bit); // ------------------------------------------------------------------- // Quality to convert to compression QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; qCDebug(DIGIKAM_DIMG_LOG_PNG) << "DImg quality level: " << quality; if (quality < 1) { quality = 1; } if (quality > 99) { quality = 99; } quality = quality / 10; compression = 9 - quality; if (compression < 0) { compression = 0; } if (compression > 9) { compression = 9; } qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG compression level: " << compression; png_set_compression_level(png_ptr, compression); // ------------------------------------------------------------------- // Write ICC profile. QByteArray profile_rawdata = m_image->getIccProfile().data(); if (!profile_rawdata.isEmpty()) { purgeExifWorkingColorSpace(); png_set_iCCP(png_ptr, info_ptr, (png_charp)("icc"), PNG_COMPRESSION_TYPE_BASE, (iCCP_data)profile_rawdata.data(), profile_rawdata.size()); } // ------------------------------------------------------------------- // Write embedded Text typedef QMap EmbeddedTextMap; EmbeddedTextMap map = imageEmbeddedText(); - for (EmbeddedTextMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it) + for (EmbeddedTextMap::const_iterator it = map.constBegin() ; it != map.constEnd() ; ++it) { if (it.key() != QLatin1String("Software") && it.key() != QLatin1String("Comment")) { - QByteArray key = it.key().toLatin1(); + QByteArray key = it.key().toLatin1(); QByteArray value = it.value().toLatin1(); png_text text; - text.key = key.data(); - text.text = value.data(); + text.key = key.data(); + text.text = value.data(); qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Writing PNG Embedded text: key=" << text.key << " text=" << text.text; text.compression = PNG_TEXT_COMPRESSION_zTXt; png_set_text(png_ptr, info_ptr, &(text), 1); } } // Update 'Software' text tag. - QString software = QLatin1String("digiKam "); + QString software = QLatin1String("digiKam "); software.append(digiKamVersion()); QString libpngver = QLatin1String(PNG_HEADER_VERSION_STRING); libpngver.replace(QLatin1Char('\n'), QLatin1Char(' ')); software.append(QString::fromLatin1(" (%1)").arg(libpngver)); QByteArray softwareAsAscii = software.toLatin1(); png_text text; text.key = (png_charp)("Software"); text.text = softwareAsAscii.data(); qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Writing PNG Embedded text: key=" << text.key << " text=" << text.text; text.compression = PNG_TEXT_COMPRESSION_zTXt; png_set_text(png_ptr, info_ptr, &(text), 1); if (observer) { observer->progressInfo(m_image, 0.2F); } // ------------------------------------------------------------------- // Write image data png_write_info(png_ptr, info_ptr); png_set_shift(png_ptr, &sig_bit); png_set_packing(png_ptr); ptr = imageData(); uint checkPoint = 0; - for (y = 0; y < imageHeight(); ++y) + for (y = 0 ; y < imageHeight() ; ++y) { if (observer && y == checkPoint) { checkPoint += granularity(observer, imageHeight(), 0.8F); if (!observer->continueQuery(m_image)) { png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); delete cleanupData; return false; } observer->progressInfo(m_image, 0.2 + (0.8 * (((float)y) / ((float)imageHeight())))); } j = 0; if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { - for (x = 0; x < imageWidth()*imageBytesDepth(); x += imageBytesDepth()) + for (x = 0 ; x < imageWidth()*imageBytesDepth() ; x += imageBytesDepth()) { if (imageSixteenBit()) { if (imageHasAlpha()) { data[j++] = ptr[x + 1]; // Blue - data[j++] = ptr[ x ]; + data[j++] = ptr[ x ]; data[j++] = ptr[x + 3]; // Green data[j++] = ptr[x + 2]; data[j++] = ptr[x + 5]; // Red data[j++] = ptr[x + 4]; data[j++] = ptr[x + 7]; // Alpha data[j++] = ptr[x + 6]; } else { data[j++] = ptr[x + 1]; // Blue - data[j++] = ptr[ x ]; + data[j++] = ptr[ x ]; data[j++] = ptr[x + 3]; // Green data[j++] = ptr[x + 2]; data[j++] = ptr[x + 5]; // Red data[j++] = ptr[x + 4]; } } else { if (imageHasAlpha()) { - data[j++] = ptr[ x ]; // Blue + data[j++] = ptr[ x ]; // Blue data[j++] = ptr[x + 1]; // Green data[j++] = ptr[x + 2]; // Red data[j++] = ptr[x + 3]; // Alpha } else { - data[j++] = ptr[ x ]; // Blue + data[j++] = ptr[ x ]; // Blue data[j++] = ptr[x + 1]; // Green data[j++] = ptr[x + 2]; // Red } } } } else { int bytes = (imageSixteenBit() ? 2 : 1) * (imageHasAlpha() ? 4 : 3); - for (x = 0; x < imageWidth()*imageBytesDepth(); x += imageBytesDepth()) + for (x = 0 ; x < imageWidth()*imageBytesDepth() ; x += imageBytesDepth()) { memcpy(data + j, ptr + x, bytes); j += bytes; } } row_ptr = (png_bytep) data; png_write_rows(png_ptr, &row_ptr, 1); ptr += (imageWidth() * imageBytesDepth()); } // ------------------------------------------------------------------- png_write_end(png_ptr, info_ptr); png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); delete cleanupData; imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("PNG")); saveMetadata(filePath); return true; } } // namespace DigikamPNGDImgPlugin diff --git a/core/dplugins/dimg/qimage/dimgqimageplugin.cpp b/core/dplugins/dimg/qimage/dimgqimageplugin.cpp index c26ce73a4d..cf86f28733 100644 --- a/core/dplugins/dimg/qimage/dimgqimageplugin.cpp +++ b/core/dplugins/dimg/qimage/dimgqimageplugin.cpp @@ -1,206 +1,207 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-09-20 * Description : QImage DImg plugin. * * 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 "dimgqimageplugin.h" // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "digikam_globals.h" #include "dimgqimageloader.h" #include "drawdecoder.h" namespace DigikamQImageDImgPlugin { DImgQImagePlugin::DImgQImagePlugin(QObject* const parent) : DPluginDImg(parent) { } DImgQImagePlugin::~DImgQImagePlugin() { } QString DImgQImagePlugin::name() const { return i18n("QImage loader"); } QString DImgQImagePlugin::iid() const { return QLatin1String(DPLUGIN_IID); } QIcon DImgQImagePlugin::icon() const { return QIcon::fromTheme(QLatin1String("image-x-generic")); } QString DImgQImagePlugin::description() const { return i18n("An image loader based on QImage plugins"); } QString DImgQImagePlugin::details() const { return i18n("

This plugin permit to load and save image using QImage plugins from Qt Framework.

" "

See Qt Framework documentation for details.

" ); } QList DImgQImagePlugin::authors() const { return QList() << DPluginAuthor(QString::fromUtf8("Renchi Raju"), QString::fromUtf8("renchi dot raju at gmail dot com"), QString::fromUtf8("(C) 2005")) << DPluginAuthor(QString::fromUtf8("Gilles Caulier"), QString::fromUtf8("caulier dot gilles at gmail dot com"), QString::fromUtf8("(C) 2006-2019")) ; } void DImgQImagePlugin::setup(QObject* const /*parent*/) { // Nothing to do } QString DImgQImagePlugin::loaderName() const { return QLatin1String("QIMAGE"); } QString DImgQImagePlugin::typeMimes() const { QList formats = QImageReader::supportedImageFormats(); qDebug(DIGIKAM_DIMG_LOG_QIMAGE) << "QImage support this formats:" << formats; formats.removeAll(QByteArray("JPEG")); // JPEG file format formats.removeAll(QByteArray("JPG")); // JPEG file format formats.removeAll(QByteArray("JPE")); // JPEG file format formats.removeAll(QByteArray("PNG")); formats.removeAll(QByteArray("TIFF")); formats.removeAll(QByteArray("TIF")); formats.removeAll(QByteArray("PGF")); formats.removeAll(QByteArray("JP2")); // JPEG2000 file format formats.removeAll(QByteArray("JPX")); // JPEG2000 file format formats.removeAll(QByteArray("JPC")); // JPEG2000 code stream formats.removeAll(QByteArray("J2K")); // JPEG2000 code stream formats.removeAll(QByteArray("PGX")); // JPEG2000 WM format formats.removeAll(QByteArray("HEIC")); + formats.removeAll(QByteArray("HEIF")); QString rawFilesExt = QString(DRawDecoder::rawFiles()).remove(QLatin1String("*.")).toUpper(); foreach (const QString& str, rawFilesExt.split(QLatin1Char(' '))) { formats.removeAll(str.toLatin1()); // All Raw image formats } QString ret; foreach (const QByteArray& ba, formats) { ret += QString::fromUtf8("%1 ").arg(QString::fromUtf8(ba).toUpper()); } return ret; } bool DImgQImagePlugin::canRead(const QString& filePath, bool magic) const { QFileInfo fileInfo(filePath); if (!fileInfo.exists()) { qCDebug(DIGIKAM_DIMG_LOG) << "File " << filePath << " does not exist"; return false; } if (!magic) { QString mimeType(QMimeDatabase().mimeTypeForFile(filePath).name()); // Ignore non image format. if ( mimeType.startsWith(QLatin1String("video/")) || mimeType.startsWith(QLatin1String("audio/")) ) { return false; } QString format = fileInfo.suffix().toUpper(); - QString blackList = QString(DRawDecoder::rawFiles()).remove(QLatin1String("*.")).toUpper(); // Ignore RAW files - blackList.append(QLatin1String(" JPEG JPG JPE PNG TIF TIFF PGF JP2 JPX JPC J2K PGX HEIC ")); // Ignore native loaders + QString blackList = QString(DRawDecoder::rawFiles()).remove(QLatin1String("*.")).toUpper(); // Ignore RAW files + blackList.append(QLatin1String(" JPEG JPG JPE PNG TIF TIFF PGF JP2 JPX JPC J2K PGX HEIC HEIF ")); // Ignore native loaders return (!blackList.toUpper().contains(format)); } return false; } bool DImgQImagePlugin::canWrite(const QString& format) const { - QString blackList = QString(DRawDecoder::rawFiles()).remove(QLatin1String("*.")).toUpper(); // Ignore RAW files - blackList.append(QLatin1String(" JPEG JPG JPE PNG TIF TIFF PGF JP2 JPX JPC J2K PGX HEIC ")); // Ignore native loaders + QString blackList = QString(DRawDecoder::rawFiles()).remove(QLatin1String("*.")).toUpper(); // Ignore RAW files + blackList.append(QLatin1String(" JPEG JPG JPE PNG TIF TIFF PGF JP2 JPX JPC J2K PGX HEIC HEIF")); // Ignore native loaders if (blackList.toUpper().contains(format)) { return false; } // NOTE: Native loaders support are previously black-listed. // For ex, if tiff is supported in write mode by QImage it will never be handled. QList formats = QImageWriter::supportedImageFormats(); foreach (const QByteArray& ba, formats) { if (QString::fromUtf8(ba).toUpper().contains(format.toUpper())) { return true; } } return false; } DImgLoader* DImgQImagePlugin::loader(DImg* const image, const DRawDecoding&) const { return new DImgQImageLoader(image); } } // namespace DigikamQImageDImgPlugin diff --git a/core/dplugins/dimg/tiff/dimgtiffloader_load.cpp b/core/dplugins/dimg/tiff/dimgtiffloader_load.cpp index 526066e0ff..0ca26cd878 100644 --- a/core/dplugins/dimg/tiff/dimgtiffloader_load.cpp +++ b/core/dplugins/dimg/tiff/dimgtiffloader_load.cpp @@ -1,717 +1,718 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-06-17 * Description : A TIFF IO file for DImg framework - load operations * * Copyright (C) 2005 by Renchi Raju * 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. * * ============================================================ */ // C ANSI includes extern "C" { #include } // C++ includes #include // Qt includes #include #include // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "dimgloaderobserver.h" #include "dimgtiffloader.h" //krazy:exclude=includes namespace DigikamTIFFDImgPlugin { bool DImgTIFFLoader::load(const QString& filePath, DImgLoaderObserver* const observer) { readMetadata(filePath); // ------------------------------------------------------------------- // TIFF error handling. If an errors/warnings occurs during reading, // libtiff will call these methods #ifdef Q_OS_WIN TIFFSetWarningHandler(NULL); #else TIFFSetWarningHandler(dimg_tiff_warning); #endif TIFFSetErrorHandler(dimg_tiff_error); // ------------------------------------------------------------------- // Open the file TIFF* const tif = TIFFOpen(QFile::encodeName(filePath).constData(), "r"); if (!tif) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Cannot open image file."; loadingFailed(); return false; } if (DIGIKAM_DIMG_LOG_TIFF().isDebugEnabled()) { TIFFPrintDirectory(tif, stdout, 0); } // ------------------------------------------------------------------- // Get image information. uint32 w, h; uint16 bits_per_sample; uint16 samples_per_pixel; uint16 photometric; uint16 planar_config; uint32 rows_per_strip; tsize_t strip_size; tstrip_t num_of_strips; TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGEWIDTH, &w); TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGELENGTH, &h); TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel); TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planar_config); if (TIFFGetFieldDefaulted(tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip) == 0 || rows_per_strip == 0) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "TIFF loader: Cannot handle non-stripped images. Loading file " << filePath; TIFFClose(tif); loadingFailed(); return false; } if (rows_per_strip > h) { rows_per_strip = h; } if ( bits_per_sample == 0 || samples_per_pixel == 0 || rows_per_strip == 0 // || rows_per_strip > h ) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "TIFF loader: Encountered invalid value in image." << endl << " bits_per_sample : " << bits_per_sample << endl << " samples_per_pixel : " << samples_per_pixel << endl << " rows_per_strip : " << rows_per_strip << endl << " h : " << h << endl << " Loading file : " << filePath; TIFFClose(tif); loadingFailed(); return false; } // TODO: check others TIFF color-spaces here. Actually, only RGB, PALETTE and MINISBLACK // have been tested. // Complete description of TIFFTAG_PHOTOMETRIC tag can be found at this url: // http://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric); if (photometric != PHOTOMETRIC_RGB && photometric != PHOTOMETRIC_PALETTE && photometric != PHOTOMETRIC_MINISWHITE && photometric != PHOTOMETRIC_MINISBLACK && ((photometric != PHOTOMETRIC_YCBCR) | (bits_per_sample != 8)) && ((photometric != PHOTOMETRIC_SEPARATED) | (bits_per_sample != 8)) && (m_loadFlags & LoadImageData)) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Can not handle image without RGB color-space: " << photometric; TIFFClose(tif); loadingFailed(); return false; } int colorModel = DImg::COLORMODELUNKNOWN; switch (photometric) { case PHOTOMETRIC_MINISWHITE: case PHOTOMETRIC_MINISBLACK: colorModel = DImg::GRAYSCALE; break; case PHOTOMETRIC_RGB: colorModel = DImg::RGB; break; case PHOTOMETRIC_PALETTE: colorModel = DImg::INDEXED; break; case PHOTOMETRIC_MASK: colorModel = DImg::MONOCHROME; break; case PHOTOMETRIC_SEPARATED: colorModel = DImg::CMYK; break; case PHOTOMETRIC_YCBCR: colorModel = DImg::YCBCR; break; case PHOTOMETRIC_CIELAB: case PHOTOMETRIC_ICCLAB: case PHOTOMETRIC_ITULAB: colorModel = DImg::CIELAB; break; case PHOTOMETRIC_LOGL: case PHOTOMETRIC_LOGLUV: colorModel = DImg::COLORMODELRAW; break; } if (samples_per_pixel == 4) { m_hasAlpha = true; } else { m_hasAlpha = false; } if (bits_per_sample == 16 || bits_per_sample == 32) { m_sixteenBit = true; } else { m_sixteenBit = false; } // ------------------------------------------------------------------- // Read image ICC profile if (m_loadFlags & LoadICCData) { uchar* profile_data = nullptr; uint32 profile_size; if (TIFFGetField(tif, TIFFTAG_ICCPROFILE, &profile_size, &profile_data)) { QByteArray profile_rawdata; profile_rawdata.resize(profile_size); memcpy(profile_rawdata.data(), profile_data, profile_size); imageSetIccProfile(IccProfile(profile_rawdata)); } else { // If ICC profile is null, check Exif metadata. checkExifWorkingColorSpace(); } } // ------------------------------------------------------------------- // Get image data. QScopedArrayPointer data; if (m_loadFlags & LoadImageData) { if (observer) { observer->progressInfo(m_image, 0.1F); } strip_size = TIFFStripSize(tif); num_of_strips = TIFFNumberOfStrips(tif); if (bits_per_sample == 16) // 16 bits image. { data.reset(new_failureTolerant(w, h, 8)); QScopedArrayPointer strip(new_failureTolerant(strip_size)); if (!data || strip.isNull()) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to allocate memory for TIFF image" << filePath; TIFFClose(tif); loadingFailed(); return false; } long offset = 0; long bytesRead = 0; uint checkpoint = 0; for (tstrip_t st = 0 ; st < num_of_strips ; ++st) { if (observer && st == checkpoint) { checkpoint += granularity(observer, num_of_strips, 0.8F); if (!observer->continueQuery(m_image)) { TIFFClose(tif); loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)st) / ((float)num_of_strips)))); } bytesRead = TIFFReadEncodedStrip(tif, st, strip.data(), strip_size); if (bytesRead == -1) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to read strip"; TIFFClose(tif); loadingFailed(); return false; } if ((planar_config == PLANARCONFIG_SEPARATE) && (st % (num_of_strips / samples_per_pixel)) == 0) { offset = 0; } ushort* stripPtr = reinterpret_cast(strip.data()); ushort* dataPtr = reinterpret_cast(data.data() + offset); ushort* p; // tiff data is read as BGR or ABGR or Greyscale if (samples_per_pixel == 1) // See bug #148400: Greyscale pictures only have _one_ sample per pixel { for (int i = 0 ; i < bytesRead / 2 ; ++i) { // We have to read two bytes for one pixel p = dataPtr; p[0] = *stripPtr; // RGB have to be set to the _same_ value p[1] = *stripPtr; p[2] = *stripPtr++; p[3] = 0xFFFF; // set alpha to 100% dataPtr += 4; } offset += bytesRead * 4; // The _byte_offset in the data array is, of course, four times bytesRead } else if ((samples_per_pixel == 3) && (planar_config == PLANARCONFIG_CONTIG)) { for (int i = 0 ; i < bytesRead / 6 ; ++i) { p = dataPtr; p[2] = *stripPtr++; p[1] = *stripPtr++; p[0] = *stripPtr++; p[3] = 0xFFFF; dataPtr += 4; } offset += bytesRead / 6 * 8; } else if ((samples_per_pixel == 3) && (planar_config == PLANARCONFIG_SEPARATE)) { for (int i = 0 ; i < bytesRead / 2 ; ++i) { p = dataPtr; switch ((st / (num_of_strips / samples_per_pixel))) { case 0: p[2] = *stripPtr++; p[3] = 0xFFFF; break; case 1: p[1] = *stripPtr++; break; case 2: p[0] = *stripPtr++; break; } dataPtr += 4; } offset += bytesRead / 2 * 8; } else if ((samples_per_pixel == 4) && (planar_config == PLANARCONFIG_CONTIG)) { for (int i = 0 ; i < bytesRead / 8 ; ++i) { p = dataPtr; p[2] = *stripPtr++; p[1] = *stripPtr++; p[0] = *stripPtr++; p[3] = *stripPtr++; dataPtr += 4; } offset += bytesRead; } else if ((samples_per_pixel == 4) && (planar_config == PLANARCONFIG_SEPARATE)) { for (int i = 0 ; i < bytesRead / 2 ; ++i) { p = dataPtr; switch ((st / (num_of_strips / samples_per_pixel))) { case 0: p[2] = *stripPtr++; break; case 1: p[1] = *stripPtr++; break; case 2: p[0] = *stripPtr++; break; case 3: p[3] = *stripPtr++; break; } dataPtr += 4; } offset += bytesRead / 2 * 8; } } } else if (bits_per_sample == 32) // 32 bits image. { data.reset(new_failureTolerant(w, h, 8)); QScopedArrayPointer strip(new_failureTolerant(strip_size)); if (!data || strip.isNull()) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to allocate memory for TIFF image" << filePath; TIFFClose(tif); loadingFailed(); return false; } long offset = 0; long bytesRead = 0; uint checkpoint = 0; float maxValue = 0.0; for (tstrip_t st = 0 ; st < num_of_strips ; ++st) { if (observer && !observer->continueQuery(m_image)) { TIFFClose(tif); loadingFailed(); return false; } bytesRead = TIFFReadEncodedStrip(tif, st, strip.data(), strip_size); if (bytesRead == -1) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to read strip"; TIFFClose(tif); loadingFailed(); return false; } float* stripPtr = reinterpret_cast(strip.data()); for (int i = 0 ; i < bytesRead / 4 ; ++i) { maxValue = qMax(maxValue, *stripPtr++); } } double factor = (maxValue > 10.0) ? log10(maxValue) * 1.5 : 1.0; double scale = (factor > 1.0) ? 0.75 : 1.0; if (factor > 1.0) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "TIFF image cannot be converted lossless from 32 to 16 bits" << filePath; } for (tstrip_t st = 0 ; st < num_of_strips ; ++st) { if (observer && st == checkpoint) { checkpoint += granularity(observer, num_of_strips, 0.8F); if (!observer->continueQuery(m_image)) { TIFFClose(tif); loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)st) / ((float)num_of_strips)))); } bytesRead = TIFFReadEncodedStrip(tif, st, strip.data(), strip_size); if (bytesRead == -1) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to read strip"; TIFFClose(tif); loadingFailed(); return false; } if ((planar_config == PLANARCONFIG_SEPARATE) && + (samples_per_pixel != 0) && (st % (num_of_strips / samples_per_pixel)) == 0) { offset = 0; } float* stripPtr = reinterpret_cast(strip.data()); ushort* dataPtr = reinterpret_cast(data.data() + offset); ushort* p = nullptr; if ((samples_per_pixel == 3) && (planar_config == PLANARCONFIG_CONTIG)) { for (int i = 0 ; i < bytesRead / 12 ; ++i) { p = dataPtr; p[2] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[1] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[0] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[3] = 0xFFFF; dataPtr += 4; } offset += bytesRead / 12 * 8; } else if ((samples_per_pixel == 3) && (planar_config == PLANARCONFIG_SEPARATE)) { for (int i = 0 ; i < bytesRead / 4 ; ++i) { p = dataPtr; - switch ((st / (num_of_strips / samples_per_pixel))) + switch ((st / (num_of_strips / (samples_per_pixel != 0) ? samples_per_pixel : 1))) { case 0: p[2] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[3] = 0xFFFF; break; case 1: p[1] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); break; case 2: p[0] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); break; } dataPtr += 4; } offset += bytesRead / 4 * 8; } else if ((samples_per_pixel == 4) && (planar_config == PLANARCONFIG_CONTIG)) { for (int i = 0 ; i < bytesRead / 16 ; ++i) { p = dataPtr; p[2] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[1] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[0] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[3] = (ushort)qBound(0.0, (double)*stripPtr++ * 65535.0, 65535.0); dataPtr += 4; } offset += bytesRead / 16 * 8; } else if ((samples_per_pixel == 4) && (planar_config == PLANARCONFIG_SEPARATE)) { for (int i = 0 ; i < bytesRead / 4 ; ++i) { p = dataPtr; switch ((st / (num_of_strips / samples_per_pixel))) { case 0: p[2] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); break; case 1: p[1] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); break; case 2: p[0] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); break; case 3: p[3] = (ushort)qBound(0.0, (double)*stripPtr++ * 65535.0, 65535.0); break; } dataPtr += 4; } offset += bytesRead / 4 * 8; } } } else // Non 16 or 32 bits images ==> get it on BGRA 8 bits. { data.reset(new_failureTolerant(w, h, 4)); QScopedArrayPointer strip(new_failureTolerant(w, rows_per_strip, 4)); if (!data || strip.isNull()) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to allocate memory for TIFF image" << filePath; TIFFClose(tif); loadingFailed(); return false; } long offset = 0; long pixelsRead = 0; // this is inspired by TIFFReadRGBAStrip, tif_getimage.c char emsg[1024] = ""; uint32 rows_to_read = 0; uint checkpoint = 0; TIFFRGBAImage img; // test whether libtiff can read format and initiate reading if (!TIFFRGBAImageOK(tif, emsg) || !TIFFRGBAImageBegin(&img, tif, 0, emsg)) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to set up RGBA reading of image, filename " << TIFFFileName(tif) << " error message from Libtiff: " << emsg; TIFFClose(tif); loadingFailed(); return false; } // libtiff cannot handle all possible orientations, it give weird results. // We rotate ourselves. (Bug 274865) img.req_orientation = img.orientation; // read strips from image: read rows_per_strip, so always start at beginning of a strip for (uint row = 0 ; row < h ; row += rows_per_strip) { if (observer && row >= checkpoint) { checkpoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { TIFFClose(tif); loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)row) / ((float)h)))); } img.row_offset = row; img.col_offset = 0; if (row + rows_per_strip > img.height) { rows_to_read = img.height - row; } else { rows_to_read = rows_per_strip; } // Read data if (TIFFRGBAImageGet(&img, reinterpret_cast(strip.data()), img.width, rows_to_read) == -1) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to read image data"; TIFFClose(tif); loadingFailed(); return false; } pixelsRead = rows_to_read * img.width; uchar* stripPtr = (uchar*)(strip.data()); uchar* dataPtr = (uchar*)(data.data() + offset); uchar* p = nullptr; // Reverse red and blue for (int i = 0 ; i < pixelsRead ; ++i) { p = dataPtr; p[2] = *stripPtr++; p[1] = *stripPtr++; p[0] = *stripPtr++; p[3] = *stripPtr++; dataPtr += 4; } offset += pixelsRead * 4; } TIFFRGBAImageEnd(&img); } } // ------------------------------------------------------------------- TIFFClose(tif); if (observer) { observer->progressInfo(m_image, 1.0); } imageWidth() = w; imageHeight() = h; imageData() = data.take(); imageSetAttribute(QLatin1String("format"), QLatin1String("TIFF")); imageSetAttribute(QLatin1String("originalColorModel"), colorModel); imageSetAttribute(QLatin1String("originalBitDepth"), bits_per_sample); imageSetAttribute(QLatin1String("originalSize"), QSize(w, h)); return true; } } // namespace DigikamTIFFDImgPlugin diff --git a/core/libs/database/coredb/coredbschemaupdater.cpp b/core/libs/database/coredb/coredbschemaupdater.cpp index eade72053c..cf9be3cf50 100644 --- a/core/libs/database/coredb/coredbschemaupdater.cpp +++ b/core/libs/database/coredb/coredbschemaupdater.cpp @@ -1,1594 +1,1594 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-04-16 * Description : Core database Schema updater * * Copyright (C) 2007-2012 by Marcel Wiesweg * 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 "coredbschemaupdater.h" // Qt includes #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "coredbbackend.h" #include "coredbtransaction.h" #include "coredbchecker.h" #include "collectionmanager.h" #include "collectionlocation.h" #include "collectionscanner.h" #include "itemquerybuilder.h" #include "collectionscannerobserver.h" #include "digikam_config.h" namespace Digikam { int CoreDbSchemaUpdater::schemaVersion() { return 10; } int CoreDbSchemaUpdater::filterSettingsVersion() { return 9; } int CoreDbSchemaUpdater::uniqueHashVersion() { return 2; } bool CoreDbSchemaUpdater::isUniqueHashUpToDate() { return CoreDbAccess().db()->getUniqueHashVersion() >= uniqueHashVersion(); } // -------------------------------------------------------------------------------------- class Q_DECL_HIDDEN CoreDbSchemaUpdater::Private { public: explicit Private() : setError(false), backend(nullptr), albumDB(nullptr), dbAccess(nullptr), observer(nullptr) { } bool setError; QVariant currentVersion; QVariant currentRequiredVersion; CoreDbBackend* backend; CoreDB* albumDB; DbEngineParameters parameters; // legacy CoreDbAccess* dbAccess; QString lastErrorMessage; InitializationObserver* observer; }; CoreDbSchemaUpdater::CoreDbSchemaUpdater(CoreDB* const albumDB, CoreDbBackend* const backend, DbEngineParameters parameters) : d(new Private) { d->backend = backend; d->albumDB = albumDB; d->parameters = parameters; } CoreDbSchemaUpdater::~CoreDbSchemaUpdater() { delete d; } void CoreDbSchemaUpdater::setCoreDbAccess(CoreDbAccess* const dbAccess) { d->dbAccess = dbAccess; } const QString CoreDbSchemaUpdater::getLastErrorMessage() { return d->lastErrorMessage; } bool CoreDbSchemaUpdater::update() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: running schema update"; bool success = startUpdates(); // cancelled? if (d->observer && !d->observer->continueQuery()) { return false; } // even on failure, try to set current version - it may have incremented setVersionSettings(); if (!success) { return false; } updateFilterSettings(); if (d->observer) { d->observer->finishedSchemaUpdate(InitializationObserver::UpdateSuccess); } return success; } void CoreDbSchemaUpdater::setVersionSettings() { if (d->currentVersion.isValid()) { d->albumDB->setSetting(QLatin1String("DBVersion"), QString::number(d->currentVersion.toInt())); } if (d->currentRequiredVersion.isValid()) { d->albumDB->setSetting(QLatin1String("DBVersionRequired"), QString::number(d->currentRequiredVersion.toInt())); } } static QVariant safeToVariant(const QString& s) { if (s.isEmpty()) { return QVariant(); } else { return s.toInt(); } } void CoreDbSchemaUpdater::readVersionSettings() { d->currentVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersion"))); d->currentRequiredVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersionRequired"))); } void CoreDbSchemaUpdater::setObserver(InitializationObserver* const observer) { d->observer = observer; } bool CoreDbSchemaUpdater::startUpdates() { if (!d->parameters.isSQLite()) { // Do we have sufficient privileges QStringList insufficientRights; CoreDbPrivilegesChecker checker(d->parameters); if (!checker.checkPrivileges(insufficientRights)) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: insufficient rights on database."; QString errorMsg = i18n("You have insufficient privileges on the database.\n" "Following privileges are not assigned to you:\n %1\n" "Check your privileges on the database and restart digiKam.", insufficientRights.join(QLatin1String(",\n"))); d->lastErrorMessage = errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } // First step: do we have an empty database? QStringList tables = d->backend->tables(); if (tables.contains(QLatin1String("Albums"), Qt::CaseInsensitive)) { // Find out schema version of db file readVersionSettings(); qCDebug(DIGIKAM_COREDB_LOG) << "Core database: have a structure version " << d->currentVersion.toInt(); // We absolutely require the DBVersion setting if (!d->currentVersion.isValid()) { // Something is damaged. Give up. qCDebug(DIGIKAM_COREDB_LOG) << "Core database: version not available! Giving up schema upgrading."; QString errorMsg = i18n("The database is not valid: " "the \"DBVersion\" setting does not exist. " "The current database schema version cannot be verified. " "Try to start with an empty database. "); d->lastErrorMessage=errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } // current version describes the current state of the schema in the db, // schemaVersion is the version required by the program. if (d->currentVersion.toInt() > schemaVersion()) { // trying to open a database with a more advanced than this CoreDbSchemaUpdater supports if (d->currentRequiredVersion.isValid() && d->currentRequiredVersion.toInt() <= schemaVersion()) { // version required may be less than current version return true; } else { QString errorMsg = i18n("The database has been used with a more recent version of digiKam " "and has been updated to a database schema which cannot be used with this version. " "(This means this digiKam version is too old, or the database format is too recent.) " "Please use the more recent version of digiKam that you used before. "); d->lastErrorMessage=errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } else { return makeUpdates(); } } else { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: no database file available"; // Legacy handling? // first test if there are older files that need to be upgraded. // This applies to "digikam.db" for 0.7 and "digikam3.db" for 0.8 and 0.9, // all only SQLite databases. // Version numbers used in this source file are a bit confused for the historic versions. // Version 1 is 0.6 (no db), Version 2 is 0.7 (SQLite 2), // Version 3 is 0.8-0.9, // Version 3 wrote the setting "DBVersion", "1", // Version 4 is 0.10, the digikam3.db file copied to digikam4.db, // no schema changes. // Version 4 writes "4", and from now on version x writes "x". // Version 5 includes the schema changes from 0.9 to 0.10 // Version 6 brought new tables for history and ImageTagProperties, with version 2.0 // Version 7 brought the VideoMetadata table with 3.0 if (d->parameters.isSQLite()) { QFileInfo currentDBFile(d->parameters.databaseNameCore); QFileInfo digikam3DB(currentDBFile.dir(), QLatin1String("digikam3.db")); if (digikam3DB.exists()) { if (!copyV3toV4(digikam3DB.filePath(), currentDBFile.filePath())) { return false; } // d->currentVersion is now 4; return makeUpdates(); } } // No legacy handling: start with a fresh db if (!createDatabase() || !createFilterSettings()) { QString errorMsg = i18n("Failed to create tables in database.\n ") + d->backend->lastError(); d->lastErrorMessage = errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } return true; } } bool CoreDbSchemaUpdater::beginWrapSchemaUpdateStep() { if (!d->backend->beginTransaction()) { QFileInfo currentDBFile(d->parameters.databaseNameCore); QString errorMsg = i18n("Failed to open a database transaction on your database file \"%1\". " "This is unusual. Please check that you can access the file and no " "other process has currently locked the file. " "If the problem persists you can get help from the digikam developers mailing list (see www.digikam.org/support). " "As well, please have a look at what digiKam prints on the console. ", QDir::toNativeSeparators(currentDBFile.filePath())); d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); return false; } return true; } bool CoreDbSchemaUpdater::endWrapSchemaUpdateStep(bool stepOperationSuccess, const QString& errorMsg) { if (!stepOperationSuccess) { d->backend->rollbackTransaction(); if (d->observer) { // error or cancelled? if (!d->observer->continueQuery()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update cancelled by user"; } else if (!d->setError) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } } return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt(); d->backend->commitTransaction(); return true; } bool CoreDbSchemaUpdater::makeUpdates() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: makeUpdates " << d->currentVersion.toInt() << " to " << schemaVersion(); if (d->currentVersion.toInt() < schemaVersion()) { if (d->currentVersion.toInt() < 5) { if (!beginWrapSchemaUpdateStep()) { return false; } // v4 was always SQLite QFileInfo currentDBFile(d->parameters.databaseNameCore); QString errorMsg = i18n("The schema updating process from version 4 to 6 failed, " "caused by an error that we did not expect. " "You can try to discard your old database and start with an empty one. " "(In this case, please move the database files " "\"%1\" and \"%2\" from the directory \"%3\"). " "More probably you will want to report this error to the digikam developers mailing list (see www.digikam.org/support). " "As well, please have a look at what digiKam prints on the console. ", QLatin1String("digikam3.db"), QLatin1String("digikam4.db"), QDir::toNativeSeparators(currentDBFile.dir().path())); if (!endWrapSchemaUpdateStep(updateV4toV7(), errorMsg)) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating v4 to v6"; // Still set these even in >= 1.4 because 0.10 - 1.3 may want to apply the updates if not set setLegacySettingEntries(); } // Incremental updates, starting from version 5 for (int v = d->currentVersion.toInt() ; v < schemaVersion() ; ++v) { int targetVersion = v + 1; if (!beginWrapSchemaUpdateStep()) { return false; } QString errorMsg = i18n("Failed to update the database schema from version %1 to version %2. " "Please read the error messages printed on the console and " "report this error as a bug at bugs.kde.org. ", d->currentVersion.toInt(), targetVersion); if (!endWrapSchemaUpdateStep(updateToVersion(targetVersion), errorMsg)) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt(); } // add future updates here } return true; } void CoreDbSchemaUpdater::defaultFilterSettings(QStringList& defaultItemFilter, QStringList& defaultVideoFilter, QStringList& defaultAudioFilter) { //NOTE for updating: //When changing anything here, just increment filterSettingsVersion() so that the changes take effect // https://en.wikipedia.org/wiki/Image_file_formats - defaultItemFilter << QLatin1String("jpg") << QLatin1String("jpeg") << QLatin1String("jpe") // JPEG - << QLatin1String("jp2") << QLatin1String("j2k") << QLatin1String("jpx") << QLatin1String("jpc") << QLatin1String("pgx") // JPEG-2000 - << QLatin1String("tif") << QLatin1String("tiff") // TIFF + defaultItemFilter << QLatin1String("jpg") << QLatin1String("jpeg") << QLatin1String("jpe") // JPEG + << QLatin1String("jp2") << QLatin1String("j2k") << QLatin1String("jpx") << QLatin1String("jpc") << QLatin1String("pgx") // JPEG-2000 + << QLatin1String("tif") << QLatin1String("tiff") // TIFF << QLatin1String("png") // PNG - << QLatin1String("gif") << QLatin1String("xpm") << QLatin1String("ppm") << QLatin1String("pnm") << QLatin1String("pgf") - << QLatin1String("bmp") << QLatin1String("pcx") - << QLatin1String("heic") + << QLatin1String("gif") << QLatin1String("xpm") << QLatin1String("ppm") << QLatin1String("pnm") << QLatin1String("pgf") + << QLatin1String("bmp") << QLatin1String("pcx") + << QLatin1String("heic") << QLatin1String("heif") << QLatin1String("webp"); // Raster graphics editor containers: https://en.wikipedia.org/wiki/Raster_graphics_editor defaultItemFilter << QLatin1String("xcf") << QLatin1String("psd") << QLatin1String("psb") << QLatin1String("kra") << QLatin1String("ora"); // Raw images: https://en.wikipedia.org/wiki/Raw_image_format defaultItemFilter << DRawDecoder::rawFilesList(); // Video files: https://en.wikipedia.org/wiki/Video_file_format defaultVideoFilter << QLatin1String("mpeg") << QLatin1String("mpg") << QLatin1String("mpo") << QLatin1String("mpe") << QLatin1String("mts") << QLatin1String("vob") // MPEG << QLatin1String("avi") << QLatin1String("divx") // RIFF << QLatin1String("wmv") << QLatin1String("wmf") << QLatin1String("asf") // ASF << QLatin1String("mp4") << QLatin1String("3gp") << QLatin1String("mov") << QLatin1String("3g2") << QLatin1String("m4v") << QLatin1String("m2v") // QuickTime << QLatin1String("mkv") << QLatin1String("webm") // Matroska << QLatin1String("mng"); // Animated PNG image // Audio files: https://en.wikipedia.org/wiki/Audio_file_format defaultAudioFilter << QLatin1String("ogg") << QLatin1String("oga") << QLatin1String("flac") << QLatin1String("wv") << QLatin1String("ape") // Linux audio << QLatin1String("mpc") << QLatin1String("au") // Linux audio << QLatin1String("m4b") << QLatin1String("aax") << QLatin1String("aa") // Book audio << QLatin1String("mp3") << QLatin1String("aac") // MPEG based audio << QLatin1String("m4a") << QLatin1String("m4p") << QLatin1String("caf") << QLatin1String("aiff") // Apple audio << QLatin1String("wma") << QLatin1String("wav"); // Windows audio } void CoreDbSchemaUpdater::defaultIgnoreDirectoryFilterSettings(QStringList& defaultIgnoreDirectoryFilter) { // NOTE: when update this section, // just increment filterSettingsVersion() so that the changes take effect defaultIgnoreDirectoryFilter << QLatin1String("@eaDir"); } bool CoreDbSchemaUpdater::createFilterSettings() { QStringList defaultItemFilter, defaultVideoFilter, defaultAudioFilter, defaultIgnoreDirectoryFilter; defaultFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter); defaultIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter); d->albumDB->setFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter); d->albumDB->setIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter); d->albumDB->setSetting(QLatin1String("FilterSettingsVersion"), QString::number(filterSettingsVersion())); d->albumDB->setSetting(QLatin1String("DcrawFilterSettingsVersion"), QString::number(DRawDecoder::rawFilesVersion())); return true; } bool CoreDbSchemaUpdater::updateFilterSettings() { QString filterVersion = d->albumDB->getSetting(QLatin1String("FilterSettingsVersion")); QString dcrawFilterVersion = d->albumDB->getSetting(QLatin1String("DcrawFilterSettingsVersion")); if (filterVersion.toInt() < filterSettingsVersion() || dcrawFilterVersion.toInt() < DRawDecoder::rawFilesVersion()) { createFilterSettings(); } return true; } bool CoreDbSchemaUpdater::createDatabase() { if ( createTables() && createIndices() && createTriggers()) { setLegacySettingEntries(); d->currentVersion = schemaVersion(); // if we start with the V2 hash, version 6 is required d->albumDB->setUniqueHashVersion(uniqueHashVersion()); d->currentRequiredVersion = schemaVersion(); /* // Digikam for database version 5 can work with version 6, though not using the new features d->currentRequiredVersion = 5; */ return true; } else { return false; } } bool CoreDbSchemaUpdater::createTables() { return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateDB"))); } bool CoreDbSchemaUpdater::createIndices() { // TODO: see which more indices are needed // create indices return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateIndices"))); } bool CoreDbSchemaUpdater::createTriggers() { return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateTriggers"))); } bool CoreDbSchemaUpdater::updateUniqueHash() { if (isUniqueHashUpToDate()) { return true; } readVersionSettings(); { CoreDbTransaction transaction; CoreDbAccess().db()->setUniqueHashVersion(uniqueHashVersion()); CollectionScanner scanner; scanner.setNeedFileCount(true); scanner.setUpdateHashHint(); if (d->observer) { d->observer->connectCollectionScanner(&scanner); scanner.setObserver(d->observer); } scanner.completeScan(); // earlier digikam does not know about the hash if (d->currentRequiredVersion.toInt() < 6) { d->currentRequiredVersion = 6; setVersionSettings(); } } return true; } bool CoreDbSchemaUpdater::performUpdateToVersion(const QString& actionName, int newVersion, int newRequiredVersion) { if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->moreSchemaUpdateSteps(1); } DbEngineAction updateAction = d->backend->getDBAction(actionName); if (updateAction.name.isNull()) { QString errorMsg = i18n("The database update action cannot be found. Please ensure that " "the dbconfig.xml file of the current version of digiKam is installed " "at the correct place. "); } if (!d->backend->execDBAction(updateAction)) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update to V" << newVersion << "failed!"; // resort to default error message, set above return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Updated schema to version %1.", newVersion)); } d->currentVersion = newVersion; // Digikam for database version 5 can work with version 6, though not using the new features // Note: We do not upgrade the uniqueHash d->currentRequiredVersion = newRequiredVersion; return true; } bool CoreDbSchemaUpdater::updateToVersion(int targetVersion) { if (d->currentVersion != targetVersion-1) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: updateToVersion performs only incremental updates. Called to update from" << d->currentVersion << "to" << targetVersion << ", aborting."; return false; } switch (targetVersion) { case 6: // Digikam for database version 5 can work with version 6, though not using the new features // Note: We do not upgrade the uniqueHash return performUpdateToVersion(QLatin1String("UpdateSchemaFromV5ToV6"), 6, 5); case 7: // Digikam for database version 5 and 6 can work with version 7, though not using the support for video files. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV6ToV7"), 7, 5); // NOTE: If you add a new update step, please check the d->currentVersion at the bottom of updateV4toV7 // If the update already comes with createTables, createTriggers, we don't need the extra update here case 8: // Digikam for database version 7 can work with version 8, now using COLLATE utf8_general_ci for MySQL. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 8, 5); case 9: // Digikam for database version 8 can work with version 9, now using COLLATE utf8_general_ci for MySQL. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 9, 5); case 10: // Digikam for database version 9 can work with version 10, remove ImageHaarMatrix table and add manualOrder column. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV9ToV10"), 10, 5); default: qCDebug(DIGIKAM_COREDB_LOG) << "Core database: unsupported update to version" << targetVersion; return false; } } bool CoreDbSchemaUpdater::copyV3toV4(const QString& digikam3DBPath, const QString& currentDBPath) { if (d->observer) { d->observer->moreSchemaUpdateSteps(2); } d->backend->close(); // We cannot use KIO here because KIO only works from the main thread QFile oldFile(digikam3DBPath); QFile newFile(currentDBPath); // QFile won't override. Remove the empty db file created when a non-existent file is opened newFile.remove(); if (!oldFile.copy(currentDBPath)) { QString errorMsg = i18n("Failed to copy the old database file (\"%1\") " "to its new location (\"%2\"). " "Error message: \"%3\". " "Please make sure that the file can be copied, " "or delete it.", digikam3DBPath, currentDBPath, oldFile.errorString()); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { d->observer->schemaUpdateProgress(i18n("Copied database file")); } if (!d->backend->open(d->parameters)) { QString errorMsg = i18n("The old database file (\"%1\") has been copied " "to the new location (\"%2\") but it cannot be opened. " "Please delete both files and try again, " "starting with an empty database. ", digikam3DBPath, currentDBPath); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { d->observer->schemaUpdateProgress(i18n("Opened new database file")); } d->currentVersion = 4; return true; } static QStringList cleanUserFilterString(const QString& filterString) { // splits by either ; or space, removes "*.", trims QStringList filterList; QString wildcard(QLatin1String("*.")); QChar dot(QLatin1Char('.')); Q_UNUSED(dot); QChar sep(QLatin1Char(';')); int i = filterString.indexOf( sep ); if ( i == -1 && filterString.indexOf(QLatin1Char(' ')) != -1 ) { sep = QChar(QLatin1Char(' ')); } QStringList sepList = filterString.split(sep, QString::SkipEmptyParts); foreach(const QString& f, sepList) { if (f.startsWith(wildcard)) { filterList << f.mid(2).trimmed().toLower(); } else { filterList << f.trimmed().toLower(); } } return filterList; } bool CoreDbSchemaUpdater::updateV4toV7() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database : running updateV4toV7"; if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->moreSchemaUpdateSteps(11); } // This update was introduced from digikam version 0.9 to digikam 0.10 // We operator on an SQLite3 database under a transaction (which will be rolled back on error) // --- Make space for new tables --- if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Albums RENAME TO AlbumsV3;"))) { return false; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Images RENAME TO ImagesV3;"))) { return false; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;"))) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: moved tables"; // --- Drop some triggers and indices --- // Don't check for errors here. The "IF EXISTS" clauses seem not supported in SQLite d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_album;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tag;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER insert_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER move_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP INDEX dir_index;")); d->backend->execSql(QString::fromUtf8("DROP INDEX tag_index;")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Prepared table creation")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: dropped triggers"; // --- Create new tables --- if (!createTables() || !createIndices()) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Created tables")); } // --- Populate AlbumRoots (from config) --- KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Album Settings")); QString albumLibraryPath = group.readEntry(QLatin1String("Album Path"), QString()); if (albumLibraryPath.isEmpty()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: Album library path from config file is empty. Aborting update."; QString errorMsg = i18n("No album library path has been found in the configuration file. " "Giving up the schema updating process. " "Please try with an empty database, or repair your configuration."); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } QUrl albumLibrary(QUrl::fromLocalFile(albumLibraryPath)); CollectionLocation location = CollectionManager::instance()->addLocation(albumLibrary, albumLibrary.fileName()); if (location.isNull()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: failure to create a collection location. Aborting update."; QString errorMsg = i18n("There was an error associating your albumLibraryPath (\"%1\") " "with a storage volume of your system. " "This problem may indicate that there is a problem with your installation. " "If you are working on Linux, check that HAL is installed and running. " "In any case, you can seek advice from the digikam developers mailing list (see www.digikam.org/support). " "The database updating process will now be aborted because we do not want " "to create a new database based on false assumptions from a broken installation.", albumLibraryPath); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Configured one album root")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: inserted album root"; // --- With the album root, populate albums --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Albums " " (id, albumRoot, relativePath, date, caption, collection, icon) " "SELECT id, ?, url, date, caption, collection, icon " " FROM AlbumsV3;" ), location.id()) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported albums")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated albums"; // --- Add images --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Images " " (id, album, name, status, category, modificationDate, fileSize, uniqueHash) " "SELECT id, dirid, name, ?, ?, NULL, NULL, NULL" " FROM ImagesV3;" ), DatabaseItem::Visible, DatabaseItem::UndefinedCategory) ) { return false; } if (!d->dbAccess->backend()->execSql(QString::fromUtf8( "REPLACE INTO ImageInformation (imageId) SELECT id FROM Images;")) ) { return false; } // remove orphan images that would not be removed by CollectionScanner d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE album NOT IN (SELECT id FROM Albums);")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported images information")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated Images"; // --- Port searches --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Searches " " (id, type, name, query) " "SELECT id, ?, name, url" " FROM SearchesV3;"), DatabaseSearch::LegacyUrlSearch) ) { return false; } SearchInfo::List sList = d->albumDB->scanSearches(); for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it) { QUrl url((*it).query); ItemQueryBuilder builder; QString query = builder.convertFromUrlToXml(url); QString name = (*it).name; if (name == i18n("Last Search")) { name = i18n("Last Search (0.9)"); } if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, name, query); } else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, name, query); } else { d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, name, query); } } // --- Create triggers --- if (!createTriggers()) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: created triggers"; // --- Populate name filters --- createFilterSettings(); // --- Set user settings from config --- QStringList defaultItemFilter, defaultVideoFilter, defaultAudioFilter; defaultFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter); QSet configItemFilter, configVideoFilter, configAudioFilter; configItemFilter = cleanUserFilterString(group.readEntry(QLatin1String("File Filter"), QString())).toSet(); configItemFilter += cleanUserFilterString(group.readEntry(QLatin1String("Raw File Filter"), QString())).toSet(); configVideoFilter = cleanUserFilterString(group.readEntry(QLatin1String("Movie File Filter"), QString())).toSet(); configAudioFilter = cleanUserFilterString(group.readEntry(QLatin1String("Audio File Filter"), QString())).toSet(); // remove those that are included in the default filter configItemFilter.subtract(defaultItemFilter.toSet()); configVideoFilter.subtract(defaultVideoFilter.toSet()); configAudioFilter.subtract(defaultAudioFilter.toSet()); d->albumDB->setUserFilterSettings(configItemFilter.toList(), configVideoFilter.toList(), configAudioFilter.toList()); qCDebug(DIGIKAM_COREDB_LOG) << "Core database: set initial filter settings with user settings" << configItemFilter; if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Initialized and imported file suffix filter")); } // --- do a full scan --- CollectionScanner scanner; if (d->observer) { d->observer->connectCollectionScanner(&scanner); scanner.setObserver(d->observer); } scanner.completeScan(); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Did the initial full scan")); } // --- Port date, comment and rating (_after_ the scan) --- // Port ImagesV3.date -> ImageInformation.creationDate if (!d->backend->execSql(QString::fromUtf8( "UPDATE ImageInformation SET " " creationDate=(SELECT datetime FROM ImagesV3 WHERE ImagesV3.id=ImageInformation.imageid) " "WHERE imageid IN (SELECT id FROM ImagesV3);") ) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported creation dates")); } // Port ImagesV3.comment to ItemComments // An author of NULL will inhibt the UNIQUE restriction to take effect (but #189080). Work around. d->backend->execSql(QString::fromUtf8( "DELETE FROM ImageComments WHERE " "type=? AND language=? AND author IS NULL " "AND imageid IN ( SELECT id FROM ImagesV3 ); "), (int)DatabaseComment::Comment, QLatin1String("x-default")); if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO ImageComments " " (imageid, type, language, comment) " "SELECT id, ?, ?, caption FROM ImagesV3;" ), (int)DatabaseComment::Comment, QLatin1String("x-default")) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported comments")); } // Port rating storage in ImageProperties to ItemInformation if (!d->backend->execSql(QString::fromUtf8( "UPDATE ImageInformation SET " " rating=(SELECT value FROM ImageProperties " " WHERE ImageInformation.imageid=ImageProperties.imageid AND ImageProperties.property=?) " "WHERE imageid IN (SELECT imageid FROM ImageProperties WHERE property=?);" ), QString::fromUtf8("Rating"), QString::fromUtf8("Rating")) ) { return false; } d->backend->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE property=?;"), QString::fromUtf8("Rating")); d->backend->execSql(QString::fromUtf8("UPDATE ImageInformation SET rating=0 WHERE rating<0;")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported ratings")); } // --- Drop old tables --- d->backend->execSql(QString::fromUtf8("DROP TABLE ImagesV3;")); d->backend->execSql(QString::fromUtf8("DROP TABLE AlbumsV3;")); d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;")); if (d->observer) { d->observer->schemaUpdateProgress(i18n("Dropped v3 tables")); } d->currentRequiredVersion = 5; d->currentVersion = 7; qCDebug(DIGIKAM_COREDB_LOG) << "Core database: returning true from updating to 5"; return true; } void CoreDbSchemaUpdater::setLegacySettingEntries() { d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true")); } // ---------- Legacy code ------------ void CoreDbSchemaUpdater::preAlpha010Update1() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update1")); if (!hasUpdate.isNull()) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;"))) { return; } if ( !d->backend->execSql( QString::fromUtf8( "CREATE TABLE IF NOT EXISTS Searches \n" " (id INTEGER PRIMARY KEY, \n" " type INTEGER, \n" " name TEXT NOT NULL, \n" " query TEXT NOT NULL);" ) )) { return; } if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Searches " " (id, type, name, query) " "SELECT id, ?, name, url" " FROM SearchesV3;"), DatabaseSearch::LegacyUrlSearch) ) { return; } SearchInfo::List sList = d->albumDB->scanSearches(); for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it) { QUrl url((*it).query); ItemQueryBuilder builder; QString query = builder.convertFromUrlToXml(url); if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, (*it).name, query); } else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, (*it).name, query); } else { d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, (*it).name, query); } } d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;")); d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true")); } void CoreDbSchemaUpdater::preAlpha010Update2() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update2")); if (!hasUpdate.isNull()) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImagePositions RENAME TO ItemPositionsTemp;"))) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImageMetadata RENAME TO ImageMetadataTemp;"))) { return; } d->backend->execSql( QString::fromUtf8("CREATE TABLE ItemPositions\n" " (imageid INTEGER PRIMARY KEY,\n" " latitude TEXT,\n" " latitudeNumber REAL,\n" " longitude TEXT,\n" " longitudeNumber REAL,\n" " altitude REAL,\n" " orientation REAL,\n" " tilt REAL,\n" " roll REAL,\n" " accuracy REAL,\n" " description TEXT);") ); d->backend->execSql(QString::fromUtf8("REPLACE INTO ImagePositions " " (imageid, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, accuracy, description) " "SELECT imageid, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, 0, description " " FROM ItemPositionsTemp;")); d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageMetadata\n" " (imageid INTEGER PRIMARY KEY,\n" " make TEXT,\n" " model TEXT,\n" " lens TEXT,\n" " aperture REAL,\n" " focalLength REAL,\n" " focalLength35 REAL,\n" " exposureTime REAL,\n" " exposureProgram INTEGER,\n" " exposureMode INTEGER,\n" " sensitivity INTEGER,\n" " flash INTEGER,\n" " whiteBalance INTEGER,\n" " whiteBalanceColorTemperature INTEGER,\n" " meteringMode INTEGER,\n" " subjectDistance REAL,\n" " subjectDistanceCategory INTEGER);") ); d->backend->execSql( QString::fromUtf8("INSERT INTO ImageMetadata " " (imageid, make, model, lens, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) " "SELECT imageid, make, model, NULL, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory " "FROM ImageMetadataTemp;")); d->backend->execSql(QString::fromUtf8("DROP TABLE ItemPositionsTemp;")); d->backend->execSql(QString::fromUtf8("DROP TABLE ImageMetadataTemp;")); d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true")); } void CoreDbSchemaUpdater::preAlpha010Update3() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update3")); if (!hasUpdate.isNull()) { return; } d->backend->execSql(QString::fromUtf8("DROP TABLE ItemCopyright;")); d->backend->execSql(QString::fromUtf8("CREATE TABLE ItemCopyright\n" " (imageid INTEGER,\n" " property TEXT,\n" " value TEXT,\n" " extraValue TEXT,\n" " UNIQUE(imageid, property, value, extraValue));") ); d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true")); } void CoreDbSchemaUpdater::beta010Update1() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update1")); if (!hasUpdate.isNull()) { return; } // if Image has been deleted d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;")); d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid=OLD.id;\n" " DELETE From ImageHaarMatrix\n " " WHERE imageid=OLD.id;\n" " DELETE From ItemInformation\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageMetadata\n " " WHERE imageid=OLD.id;\n" " DELETE From ItemPositions\n " " WHERE imageid=OLD.id;\n" " DELETE From ItemComments\n " " WHERE imageid=OLD.id;\n" " DELETE From ItemCopyright\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageProperties\n " " WHERE imageid=OLD.id;\n" " UPDATE Albums SET icon=null \n " " WHERE icon=OLD.id;\n" " UPDATE Tags SET icon=null \n " " WHERE icon=OLD.id;\n" "END;")); d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true")); } void CoreDbSchemaUpdater::beta010Update2() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update2")); if (!hasUpdate.isNull()) { return; } // force rescan and creation of ImageInformation entry for videos and audio d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE category=2 OR category=3;")); d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true")); } bool CoreDbSchemaUpdater::createTablesV3() { if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Albums\n" " (id INTEGER PRIMARY KEY,\n" " url TEXT NOT NULL UNIQUE,\n" " date DATE NOT NULL,\n" " caption TEXT,\n" " collection TEXT,\n" " icon INTEGER);") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Tags\n" " (id INTEGER PRIMARY KEY,\n" " pid INTEGER,\n" " name TEXT NOT NULL,\n" " icon INTEGER,\n" " iconkde TEXT,\n" " UNIQUE (name, pid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE TagsTree\n" " (id INTEGER NOT NULL,\n" " pid INTEGER NOT NULL,\n" " UNIQUE (id, pid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Images\n" " (id INTEGER PRIMARY KEY,\n" " name TEXT NOT NULL,\n" " dirid INTEGER NOT NULL,\n" " caption TEXT,\n" " datetime DATETIME,\n" " UNIQUE (name, dirid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageTags\n" " (imageid INTEGER NOT NULL,\n" " tagid INTEGER NOT NULL,\n" " UNIQUE (imageid, tagid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageProperties\n" " (imageid INTEGER NOT NULL,\n" " property TEXT NOT NULL,\n" " value TEXT NOT NULL,\n" " UNIQUE (imageid, property));") )) { return false; } if ( !d->backend->execSql( QString::fromUtf8("CREATE TABLE Searches \n" " (id INTEGER PRIMARY KEY, \n" " name TEXT NOT NULL UNIQUE, \n" " url TEXT NOT NULL);") ) ) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Settings \n" "(keyword TEXT NOT NULL UNIQUE,\n" " value TEXT);") )) { return false; } // TODO: see which more indices are needed // create indices d->backend->execSql(QString::fromUtf8("CREATE INDEX dir_index ON Images (dirid);")); d->backend->execSql(QString::fromUtf8("CREATE INDEX tag_index ON ImageTags (tagid);")); // create triggers // trigger: delete from Images/ImageTags/ImageProperties // if Album has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_album DELETE ON Albums\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n" " DELETE From ImageProperties\n" " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n" " DELETE FROM Images\n" " WHERE dirid = OLD.id;\n" "END;")); // trigger: delete from ImageTags/ImageProperties // if Image has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid=OLD.id;\n" " DELETE From ImageProperties\n " " WHERE imageid=OLD.id;\n" " UPDATE Albums SET icon=null \n " " WHERE icon=OLD.id;\n" " UPDATE Tags SET icon=null \n " " WHERE icon=OLD.id;\n" "END;")); // trigger: delete from ImageTags if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tag DELETE ON Tags\n" "BEGIN\n" " DELETE FROM ImageTags WHERE tagid=OLD.id;\n" "END;")); // trigger: insert into TagsTree if Tag has been added d->backend->execSql(QString::fromUtf8("CREATE TRIGGER insert_tagstree AFTER INSERT ON Tags\n" "BEGIN\n" " INSERT INTO TagsTree\n" " SELECT NEW.id, NEW.pid\n" " UNION\n" " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid;\n" "END;")); // trigger: delete from TagsTree if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tagstree DELETE ON Tags\n" "BEGIN\n" " DELETE FROM Tags\n" " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n" " DELETE FROM TagsTree\n" " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n" " DELETE FROM TagsTree\n" " WHERE id=OLD.id;\n" "END;")); // trigger: delete from TagsTree if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER move_tagstree UPDATE OF pid ON Tags\n" "BEGIN\n" " DELETE FROM TagsTree\n" " WHERE\n" " ((id = OLD.id)\n" " OR\n" " id IN (SELECT id FROM TagsTree WHERE pid=OLD.id))\n" " AND\n" " pid IN (SELECT pid FROM TagsTree WHERE id=OLD.id);\n" " INSERT INTO TagsTree\n" " SELECT NEW.id, NEW.pid\n" " UNION\n" " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid\n" " UNION\n" " SELECT id, NEW.pid FROM TagsTree WHERE pid=NEW.id\n" " UNION\n" " SELECT A.id, B.pid FROM TagsTree A, TagsTree B\n" " WHERE\n" " A.pid = NEW.id AND B.id = NEW.pid;\n" "END;")); return true; } } // namespace Digikam diff --git a/core/libs/dimg/dimg_fileio.cpp b/core/libs/dimg/dimg_fileio.cpp index 8fcc8542e4..3e8c713784 100644 --- a/core/libs/dimg/dimg_fileio.cpp +++ b/core/libs/dimg/dimg_fileio.cpp @@ -1,307 +1,317 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-06-14 * Description : digiKam 8/16 bits image management API * Files input output * * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * 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 "dimg.h" #include "dimg_p.h" namespace Digikam { bool DImg::loadItemInfo(const QString& filePath, bool loadMetadata, bool loadICCData, bool loadUniqueHash, bool loadImageHistory) { DImgLoader::LoadFlags loadFlags = DImgLoader::LoadItemInfo; if (loadMetadata) { loadFlags |= DImgLoader::LoadMetadata; } if (loadICCData) { loadFlags |= DImgLoader::LoadICCData; } if (loadUniqueHash) { loadFlags |= DImgLoader::LoadUniqueHash; } if (loadImageHistory) { loadFlags |= DImgLoader::LoadImageHistory; } return load(filePath, loadFlags, nullptr, DRawDecoding()); } bool DImg::load(const QString& filePath, DImgLoaderObserver* const observer, const DRawDecoding& rawDecodingSettings) { return load(filePath, DImgLoader::LoadAll, observer, rawDecodingSettings); } bool DImg::load(const QString& filePath, bool loadMetadata, bool loadICCData, bool loadUniqueHash, bool loadImageHistory, DImgLoaderObserver* const observer, const DRawDecoding& rawDecodingSettings) { DImgLoader::LoadFlags loadFlags = DImgLoader::LoadItemInfo | DImgLoader::LoadImageData; if (loadMetadata) { loadFlags |= DImgLoader::LoadMetadata; } if (loadICCData) { loadFlags |= DImgLoader::LoadICCData; } if (loadUniqueHash) { loadFlags |= DImgLoader::LoadUniqueHash; } if (loadImageHistory) { loadFlags |= DImgLoader::LoadImageHistory; } return load(filePath, loadFlags, observer, rawDecodingSettings); } bool DImg::load(const QString& filePath, int loadFlagsInt, DImgLoaderObserver* const observer, const DRawDecoding& rawDecodingSettings) { FORMAT format; QString name; DPluginDImg* plug = m_priv->pluginForFile(filePath, false, name, format); DImgLoader::LoadFlags loadFlags = (DImgLoader::LoadFlags)loadFlagsInt; setAttribute(QLatin1String("detectedFileFormat"), format); setAttribute(QLatin1String("originalFilePath"), filePath); FileReadLocker lock(filePath); // First step we check the file extension to find the right loader. if (plug) { if (observer && !observer->continueQuery(nullptr)) { return false; } + if (loadFlags & DImgLoader::LoadPreview && !plug->previewSupported()) + { + return false; + } + qCDebug(DIGIKAM_DIMG_LOG) << filePath << ":" << plug->loaderName() << "file identified"; DImgLoader* const loader = plug->loader(this, rawDecodingSettings); loader->setLoadFlags(loadFlags); if (loader->load(filePath, observer)) { m_priv->null = !loader->hasLoadedData(); m_priv->alpha = loader->hasAlpha(); m_priv->sixteenBit = loader->sixteenBit(); setAttribute(QLatin1String("isReadOnly"), loader->isReadOnly()); delete loader; return true; } delete loader; } if (observer && !observer->continueQuery(nullptr)) { return false; } plug = m_priv->pluginForFile(filePath, true, name, format); setAttribute(QLatin1String("detectedFileFormat"), format); // In the second step we check the magic bytes to find the right loader. if (plug) { if (observer && !observer->continueQuery(nullptr)) { return false; } + if (loadFlags & DImgLoader::LoadPreview && !plug->previewSupported()) + { + return false; + } + qCDebug(DIGIKAM_DIMG_LOG) << filePath << ":" << plug->loaderName() << "file identified (magic)"; DImgLoader* const loader = plug->loader(this, rawDecodingSettings); loader->setLoadFlags(loadFlags); if (loader->load(filePath, observer)) { m_priv->null = !loader->hasLoadedData(); m_priv->alpha = loader->hasAlpha(); m_priv->sixteenBit = loader->sixteenBit(); setAttribute(QLatin1String("isReadOnly"), loader->isReadOnly()); delete loader; return true; } delete loader; if (observer && observer->continueQuery(nullptr)) { qCWarning(DIGIKAM_DIMG_LOG) << filePath << ": Cannot load file !!!"; } return false; } qCWarning(DIGIKAM_DIMG_LOG) << filePath << ": Unknown image format !!!"; return false; } QString DImg::formatToMimeType(FORMAT frm) { QString format; switch (frm) { case (NONE): { return format; } case (JPEG): { format = QLatin1String("JPG"); break; } case (TIFF): { format = QLatin1String("TIF"); break; } case (PNG): { format = QLatin1String("PNG"); break; } case (JP2K): { format = QLatin1String("JP2"); break; } case (PGF): { format = QLatin1String("PGF"); break; } default: { // For QImage or ImageMagick based. break; } } return format; } bool DImg::save(const QString& filePath, FORMAT frm, DImgLoaderObserver* const observer) { if (isNull()) { return false; } return(save(filePath, formatToMimeType(frm), observer)); } bool DImg::save(const QString& filePath, const QString& format, DImgLoaderObserver* const observer) { qCDebug(DIGIKAM_DIMG_LOG) << "Saving to " << filePath << " with format: " << format; if (isNull()) { return false; } if (format.isEmpty()) { return false; } QString name; QString frm = format.toUpper(); setAttribute(QLatin1String("savedFilePath"), filePath); FileWriteLocker lock(filePath); DPluginDImg* const plug = m_priv->pluginForFormat(frm, name); DImg copyForSave = copy(); if (frm == QLatin1String("JPEG") || frm == QLatin1String("JPG") || frm == QLatin1String("JPE")) { // JPEG does not support transparency, so we shall provide an image without alpha channel. // This is only necessary if the image has an alpha channel, and there are actually transparent pixels if (hasTransparentPixels()) { copyForSave.removeAlphaChannel(); } } if (plug) { DImgLoader* const loader = plug->loader(©ForSave); copyForSave.setAttribute(QLatin1String("savedFormat-isReadOnly"), loader->isReadOnly()); bool ret = loader->save(filePath, observer); delete loader; return ret; } qCWarning(DIGIKAM_DIMG_LOG) << filePath << " : Unknown save format !!!"; return false; } DImg::FORMAT DImg::fileFormat(const QString& filePath) { FORMAT format; QString name; DImg::Private::pluginForFile(filePath, false, name, format); return format; } } // namespace Digikam diff --git a/core/libs/dimg/loaders/dimgloader.h b/core/libs/dimg/loaders/dimgloader.h index dda9194eed..3736a04ccf 100644 --- a/core/libs/dimg/loaders/dimgloader.h +++ b/core/libs/dimg/loaders/dimgloader.h @@ -1,208 +1,219 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-06-14 * Description : DImg image loader interface * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2005-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. * * ============================================================ */ #ifndef DIGIKAM_DIMG_LOADER_H #define DIGIKAM_DIMG_LOADER_H // C++ includes #include // Qt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "digikam_config.h" #include "digikam_export.h" #include "dimg.h" namespace Digikam { class DImgLoaderObserver; class DMetadata; class DImgLoader { public: + /** This is the list of loading modes usable by DImg image plugins + */ enum LoadFlag { - LoadItemInfo = 1, - LoadMetadata = 2, - LoadICCData = 4, - LoadImageData = 8, - LoadUniqueHash = 16, - LoadImageHistory = 32, - LoadPreview = 64, + // Load image information without image data + + LoadItemInfo = 1, /// Image info as width and height + LoadMetadata = 2, /// Image metadata + LoadICCData = 4, /// Image color profile + + LoadImageData = 8, /// Full image data + LoadUniqueHash = 16, /// Image unique hash + LoadImageHistory = 32, /// Image version history + + // Special mode to load reduced image data + + LoadPreview = 64, /// Load embeded preview image instead full size image + + // Helper to load all information, metadata and full image. + LoadAll = LoadItemInfo | LoadMetadata | LoadUniqueHash | LoadICCData | LoadImageData | LoadImageHistory }; Q_DECLARE_FLAGS(LoadFlags, LoadFlag) public: void setLoadFlags(LoadFlags flags); virtual ~DImgLoader(); virtual bool load(const QString& filePath, DImgLoaderObserver* const observer) = 0; virtual bool save(const QString& filePath, DImgLoaderObserver* const observer) = 0; virtual bool hasLoadedData() const; virtual bool hasAlpha() const = 0; virtual bool sixteenBit() const = 0; virtual bool isReadOnly() const = 0; static QByteArray uniqueHashV2(const QString& filePath, const DImg* const img = nullptr); static QByteArray uniqueHash(const QString& filePath, const DImg& img, bool loadMetadata); static HistoryImageId createHistoryImageId(const QString& filePath, const DImg& img, const DMetadata& metadata); static unsigned char* new_failureTolerant(size_t unsecureSize); static unsigned char* new_failureTolerant(quint64 w, quint64 h, uint typesPerPixel); static unsigned short* new_short_failureTolerant(size_t unsecureSize); static unsigned short* new_short_failureTolerant(quint64 w, quint64 h, uint typesPerPixel); /** Value returned : -1 : unsupported platform * 0 : parse failure from supported platform * 1 : parse done with success from supported platform */ static qint64 checkAllocation(qint64 fullSize); template static Type* new_failureTolerant(size_t unsecureSize); template static Type* new_failureTolerant(quint64 w, quint64 h, uint typesPerPixel); protected: explicit DImgLoader(DImg* const image); unsigned char*& imageData(); unsigned int& imageWidth(); unsigned int& imageHeight(); bool imageHasAlpha() const; bool imageSixteenBit() const; unsigned int imageNumBytes() const; int imageBitsDepth() const; int imageBytesDepth() const; void imageSetIccProfile(const IccProfile& profile); QVariant imageGetAttribute(const QString& key) const; void imageSetAttribute(const QString& key, const QVariant& value); QMap& imageEmbeddedText() const; QString imageGetEmbbededText(const QString& key) const; void imageSetEmbbededText(const QString& key, const QString& text); void loadingFailed(); bool checkExifWorkingColorSpace() const; void purgeExifWorkingColorSpace(); void storeColorProfileInMetadata(); virtual bool readMetadata(const QString& filePath); virtual bool saveMetadata(const QString& filePath); virtual int granularity(DImgLoaderObserver* const observer, int total, float progressSlice = 1.0); protected: DImg* m_image; LoadFlags m_loadFlags; private: DImgLoader(); }; // --------------------------------------------------------------------------------------------------- /// Allows safe multiplication of requested pixel number and bytes per pixel, avoiding particularly /// 32bit overflow and exceeding the size_t type template Q_INLINE_TEMPLATE Type* DImgLoader::new_failureTolerant(quint64 w, quint64 h, uint typesPerPixel) { quint64 requested = w * h * quint64(typesPerPixel); quint64 maximum = std::numeric_limits::max(); if (requested > maximum) { qCCritical(DIGIKAM_DIMG_LOG) << "Requested memory of" << requested*quint64(sizeof(Type)) << "is larger than size_t supported by platform."; return nullptr; } return new_failureTolerant(requested); } template Q_INLINE_TEMPLATE Type* DImgLoader::new_failureTolerant(size_t size) { qint64 res = checkAllocation(size); switch(res) { case 0: // parse failure from supported platform return nullptr; break; case -1: // unsupported platform // We will try to continue to allocate break; default: // parse done with success from supported platform break; } Type* reserved = nullptr; #ifdef Q_OS_WIN reserved = new Type[size]; if (reserved == nullptr) { qCCritical(DIGIKAM_DIMG_LOG) << "Failed to allocate chunk of memory of size" << size; } #else try { reserved = new Type[size]; } catch (std::bad_alloc& ex) { qCCritical(DIGIKAM_DIMG_LOG) << "Failed to allocate chunk of memory of size" << size << ex.what(); reserved = nullptr; } #endif return reserved; } Q_DECLARE_OPERATORS_FOR_FLAGS(DImgLoader::LoadFlags) } // namespace Digikam #endif // DIGIKAM_DIMG_LOADER_H diff --git a/core/libs/dimg/loaders/dimgloaderobserver.h b/core/libs/dimg/loaders/dimgloaderobserver.h index c14734a73e..97d02e755e 100644 --- a/core/libs/dimg/loaders/dimgloaderobserver.h +++ b/core/libs/dimg/loaders/dimgloaderobserver.h @@ -1,78 +1,81 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-01-03 * Description : DImgLoader observer interface * * Copyright (C) 2006-2012 by Marcel Wiesweg * * 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_LOADER_OBSERVER_H #define DIGIKAM_DIMG_LOADER_OBSERVER_H // Qt includes #include // Local includes #include "digikam_export.h" namespace Digikam { class DImg; class DIGIKAM_EXPORT DImgLoaderObserver { public: virtual ~DImgLoaderObserver() { }; - /** Posts progress information about image IO + /** + * Posts progress information about image IO */ virtual void progressInfo(DImg* const img, float progress) { Q_UNUSED(img); Q_UNUSED(progress); }; - /** Queries whether the image IO operation shall be continued + /** + * Queries whether the image IO operation shall be continued */ virtual bool continueQuery(DImg* const img) { Q_UNUSED(img); return true; }; - /** Return a relative value which determines the granularity, the frequency + /** + * Return a relative value which determines the granularity, the frequency * with which the DImgLoaderObserver is checked and progress is posted. * Standard is 1.0. Values < 1 mean less granularity (fewer checks), * values > 1 mean higher granularity (more checks). */ virtual float granularity() { return 1.0; }; }; -} // namespace Digikam +} // namespace Digikam #endif // DIGIKAM_DIMG_LOADER_OBSERVER_H diff --git a/core/libs/dplugins/core/dplugindimg.h b/core/libs/dplugins/core/dplugindimg.h index 8d0e76053a..fa8c15728a 100644 --- a/core/libs/dplugins/core/dplugindimg.h +++ b/core/libs/dplugins/core/dplugindimg.h @@ -1,112 +1,116 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2019-09-19 * Description : digiKam plugin definition for DImg image loader. * * 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. * * ============================================================ */ #ifndef DIGIKAM_DPLUGIN_DIMG_H #define DIGIKAM_DPLUGIN_DIMG_H // Local includes #include "dplugin.h" #include "dimgloader.h" #include "dpluginloader.h" #include "digikam_export.h" #include "drawdecoding.h" namespace Digikam { class DIGIKAM_EXPORT DPluginDImg : public DPlugin { Q_OBJECT public: /** * Constructor with optional parent object */ explicit DPluginDImg(QObject* const parent = nullptr); /** * Destructor */ ~DPluginDImg() override; public: /** This kind of plugin only provide one tool. */ int count() const override { return 1; }; /** This kind of plugin do not use a category. */ QStringList categories() const override { return QStringList(); }; /** This kind of plugin do not have GUI visibility attribute. */ void setVisible(bool) override {}; /** * Return the plugin interface identifier. */ QString ifaceIid() const override { return QLatin1String(DIGIKAM_DPLUGIN_DIMG_IID); }; /** This kind of plugin do not need to be configurable */ bool hasVisibilityProperty() const override { return false; }; /** With this kind of plugin, we will display the type-mimes list on about dialog. */ QMap extraAboutData() const override; QString extraAboutDataTitle() const override; public: /** Return a single capitalized word to identify the format supported by the loader. * Ex: jpeg => "JPG" ; tiff => "TIF", etc. */ virtual QString loaderName() const = 0; /** Return the list of white-listed type-mimes supported by the loader, * as a string of file-name suffix separated by spaces. * Ex: "jpeg jpg thm" */ virtual QString typeMimes() const = 0; + /** Return true if the loader can read a preview image. + */ + virtual bool previewSupported() const { return false; }; + /** Return true if source file path is supported by the loader and contents can be loaded. */ virtual bool canRead(const QString& filePath, bool magic) const = 0; /** Return true if target file format is supported by the loader and contents can be written. */ virtual bool canWrite(const QString& format) const = 0; /** Return the image loader instance for the DImg instance. */ virtual DImgLoader* loader(DImg* const image, const DRawDecoding& rawSettings = DRawDecoding()) const = 0; }; } // namespace Digikam Q_DECLARE_INTERFACE(Digikam::DPluginDImg, DIGIKAM_DPLUGIN_DIMG_IID) #endif // DIGIKAM_DPLUGIN_DIMG_H diff --git a/core/libs/fileactionmanager/fileworkeriface.cpp b/core/libs/fileactionmanager/fileworkeriface.cpp index f20a9bcb7d..655dc1b93f 100644 --- a/core/libs/fileactionmanager/fileworkeriface.cpp +++ b/core/libs/fileactionmanager/fileworkeriface.cpp @@ -1,398 +1,403 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-01-18 * Description : database worker interface * * Copyright (C) 2012 by Marcel Wiesweg * * 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 "fileworkeriface.h" // KDE includes #include // Local includes #include "digikam_debug.h" -#include "metaenginesettings.h" +#include "digikam_globals.h" #include "fileactionmngr_p.h" +#include "metaenginesettings.h" #include "itemattributeswatch.h" #include "iteminfotasksplitter.h" +#include "loadingcacheinterface.h" +#include "facetagseditor.h" #include "scancontroller.h" -#include "digikam_globals.h" #include "jpegutils.h" #include "dimg.h" -#include "facetagseditor.h" namespace Digikam { void FileActionMngrFileWorker::writeOrientationToFiles(FileActionItemInfoList infos, int orientation) { QStringList failedItems; foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } QString path = info.filePath(); DMetadata metadata(path); DMetadata::ImageOrientation o = (DMetadata::ImageOrientation)orientation; metadata.setItemOrientation(o); + LoadingCacheInterface::removeFromFileWatch(path); + if (!metadata.applyChanges()) { failedItems.append(info.name()); } else { emit imageDataChanged(path, true, true); QUrl url = QUrl::fromLocalFile(path); ItemAttributesWatch::instance()->fileMetadataChanged(url); } infos.writtenToOne(); } if (!failedItems.isEmpty()) { emit imageChangeFailed(i18n("Failed to revise Exif orientation these files:"), failedItems); } infos.finishedWriting(); } void FileActionMngrFileWorker::writeMetadataToFiles(FileActionItemInfoList infos) { d->startingToWrite(infos); ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { MetadataHub hub; if (state() == WorkerObject::Deactivating) { break; } hub.load(info); - QString filePath = info.filePath(); + QString path = info.filePath(); if (MetaEngineSettings::instance()->settings().useLazySync) { - hub.write(filePath, MetadataHub::WRITE_ALL); + hub.write(path, MetadataHub::WRITE_ALL); } else { ScanController::FileMetadataWrite writeScope(info); - writeScope.changed(hub.write(filePath, MetadataHub::WRITE_ALL)); + writeScope.changed(hub.write(path, MetadataHub::WRITE_ALL)); } // hub emits fileMetadataChanged infos.writtenToOne(); } ScanController::instance()->resumeCollectionScan(); infos.finishedWriting(); } void FileActionMngrFileWorker::writeMetadata(FileActionItemInfoList infos, int flags) { d->startingToWrite(infos); ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { MetadataHub hub; if (state() == WorkerObject::Deactivating) { break; } hub.load(info); // apply to file metadata if (MetaEngineSettings::instance()->settings().useLazySync) { hub.writeToMetadata(info, (MetadataHub::WriteComponents)flags); } else { ScanController::FileMetadataWrite writeScope(info); writeScope.changed(hub.writeToMetadata(info, (MetadataHub::WriteComponents)flags)); } // hub emits fileMetadataChanged infos.writtenToOne(); } ScanController::instance()->resumeCollectionScan(); infos.finishedWriting(); } void FileActionMngrFileWorker::transform(FileActionItemInfoList infos, int action) { d->startingToWrite(infos); QStringList failedItems; ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } QString path = info.filePath(); QString format = info.format(); MetaEngine::ImageOrientation currentOrientation = (MetaEngine::ImageOrientation)info.orientation(); bool isRaw = info.format().startsWith(QLatin1String("RAW")); bool rotateAsJpeg = false; bool rotateLossy = false; MetaEngineSettingsContainer::RotationBehaviorFlags behavior; behavior = MetaEngineSettings::instance()->settings().rotationBehavior; bool rotateByMetadata = (behavior & MetaEngineSettingsContainer::RotateByMetadataFlag); // Check if rotation by content, as desired, is feasible // We'll later check again if it was successful if (behavior & MetaEngineSettingsContainer::RotatingPixels) { if (format == QLatin1String("JPG") && JPEGUtils::isJpegImage(path)) { rotateAsJpeg = true; } if (behavior & MetaEngineSettingsContainer::RotateByLossyRotation) { DImg::FORMAT format = DImg::fileFormat(path); switch (format) { case DImg::JPEG: case DImg::PNG: case DImg::TIFF: case DImg::JP2K: case DImg::PGF: case DImg::HEIF: rotateLossy = true; default: break; } } } MetaEngineRotation matrix; matrix *= currentOrientation; matrix *= (MetaEngineRotation::TransformationAction)action; MetaEngine::ImageOrientation finalOrientation = matrix.exifOrientation(); bool rotatedPixels = false; + LoadingCacheInterface::removeFromFileWatch(path); + if (rotateAsJpeg) { JPEGUtils::JpegRotator rotator(path); rotator.setCurrentOrientation(currentOrientation); if (action == MetaEngineRotation::NoTransformation) { rotatedPixels = rotator.autoExifTransform(); } else { rotatedPixels = rotator.exifTransform((MetaEngineRotation::TransformationAction)action); } if (!rotatedPixels) { failedItems.append(info.name()); } } else if (rotateLossy) { // Non-JPEG image: DImg DImg image; if (!image.load(path)) { failedItems.append(info.name()); } else { if (action == MetaEngineRotation::NoTransformation) { image.rotateAndFlip(currentOrientation); } else { image.transform(action); } // TODO: Atomic operation!! // prepare metadata, including to reset Exif tag image.prepareMetadataToSave(path, image.format(), true); if (image.save(path, image.detectedFormat())) { rotatedPixels = true; } else { failedItems.append(info.name()); } } } adjustFaceRectangles(info, rotatedPixels, finalOrientation, currentOrientation); if (rotatedPixels) { // reset for DB. Metadata is already edited. finalOrientation = MetaEngine::ORIENTATION_NORMAL; } if (rotateByMetadata) { // Setting the rotation flag on Raws with embedded JPEG is a mess // Can apply to the RAW data, or to the embedded JPEG, or to both. if (!isRaw) { DMetadata metadata(path); metadata.setItemOrientation(finalOrientation); metadata.applyChanges(); } } if (!failedItems.contains(info.name())) { emit imageDataChanged(path, true, true); ItemAttributesWatch::instance()->fileMetadataChanged(info.fileUrl()); } // DB rotation ItemInfo(info).setOrientation(finalOrientation); infos.writtenToOne(); } if (!failedItems.isEmpty()) { emit imageChangeFailed(i18n("Failed to transform these files:"), failedItems); } infos.finishedWriting(); ScanController::instance()->resumeCollectionScan(); } void FileActionMngrFileWorker::adjustFaceRectangles(const ItemInfo& info, bool rotatedPixels, int newOrientation, int oldOrientation) { /** * Get all faces from database and rotate them */ QList facesList = FaceTagsEditor().databaseFaces(info.id()); if (facesList.isEmpty()) { return; } QSize newSize = info.dimensions(); QMultiMap adjustedFaces; foreach (const FaceTagsIface& dface, facesList) { QRect faceRect = dface.region().toRect(); QString name = FaceTags::faceNameForTag(dface.tagId()); TagRegion::reverseToOrientation(faceRect, oldOrientation, info.dimensions()); newSize = TagRegion::adjustToOrientation(faceRect, newOrientation, info.dimensions()); if (dface.tagId() == FaceTags::unknownPersonTagId()) { name.clear(); } adjustedFaces.insertMulti(name, faceRect); } /** * Delete all old faces and add rotated ones */ FaceTagsEditor().removeAllFaces(info.id()); QMultiMap::ConstIterator it = adjustedFaces.constBegin(); for ( ; it != adjustedFaces.constEnd() ; ++it) { TagRegion region(it.value()); if (it.key().isEmpty()) { int tagId = FaceTags::unknownPersonTagId(); FaceTagsIface face(FaceTagsIface::UnknownName, info.id(), tagId, region); FaceTagsEditor().addManually(face); } else { int tagId = FaceTags::getOrCreateTagForPerson(it.key()); if (!tagId) { qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to create a person tag for name" << it.key(); } FaceTagsEditor().add(info.id(), tagId, region, false); } } if (!rotatedPixels) { newSize = info.dimensions(); } /** * Write medatada */ MetadataHub hub; hub.load(info); // Adjusted newSize hub.loadFaceTags(info, newSize); hub.write(info.filePath(), MetadataHub::WRITE_ALL); } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata.cpp b/core/libs/metadataengine/dmetadata/dmetadata.cpp index 53d579776d..65d6f1d2fc 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata.cpp @@ -1,74 +1,75 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // Local includes #include "metaenginesettings.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { DMetadata::DMetadata() : MetaEngine() { registerMetadataSettings(); } DMetadata::DMetadata(const QString& filePath) : MetaEngine() { registerMetadataSettings(); load(filePath); } DMetadata::DMetadata(const MetaEngineData& data) : MetaEngine(data) { registerMetadataSettings(); } DMetadata::~DMetadata() { } void DMetadata::registerMetadataSettings() { setSettings(MetaEngineSettings::instance()->settings()); } void DMetadata::setSettings(const MetaEngineSettingsContainer& settings) { setUseXMPSidecar4Reading(settings.useXMPSidecar4Reading); + setUseCompatibleFileName(settings.useCompatibleFileName); setWriteRawFiles(settings.writeRawFiles); setMetadataWritingMode(settings.metadataWritingMode); setUpdateFileTimeStamp(settings.updateFileTimeStamp); } } // namespace Digikam diff --git a/core/libs/metadataengine/engine/metaengine.cpp b/core/libs/metadataengine/engine/metaengine.cpp index 3d995a64f9..f51962c487 100644 --- a/core/libs/metadataengine/engine/metaengine.cpp +++ b/core/libs/metadataengine/engine/metaengine.cpp @@ -1,306 +1,316 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface * Exiv2: http://www.exiv2.org * Exif : http://www.exif.org/Exif2-2.PDF * Iptc : http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf * Xmp : http://www.adobe.com/devnet/xmp/pdfs/xmp_specification.pdf * http://www.iptc.org/std/Iptc4xmpCore/1.0/specification/Iptc4xmpCore_1.0-spec-XMPSchema_8.pdf * Paper: http://www.metadataworkinggroup.com/pdf/mwg_guidance.pdf * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * 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 "metaengine.h" #include "metaengine_p.h" #include "metaengine_data_p.h" // Local includes #include "digikam_debug.h" #include "digikam_version.h" namespace Digikam { MetaEngine::MetaEngine() : d(new Private) { } MetaEngine::MetaEngine(const MetaEngine& metadata) : d(new Private) { d->copyPrivateData(metadata.d); } MetaEngine::MetaEngine(const MetaEngineData& data) : d(new Private) { setData(data); } MetaEngine::MetaEngine(const QString& filePath) : d(new Private) { load(filePath); } MetaEngine::~MetaEngine() { delete d; } MetaEngine& MetaEngine::operator=(const MetaEngine& metadata) { d->copyPrivateData(metadata.d); return *this; } //-- Statics methods ---------------------------------------------- bool MetaEngine::initializeExiv2() { #ifdef _XMP_SUPPORT_ if (!Exiv2::XmpParser::initialize()) return false; registerXmpNameSpace(QLatin1String("http://ns.adobe.com/lightroom/1.0/"), QLatin1String("lr")); registerXmpNameSpace(QLatin1String("https://www.digikam.org/ns/kipi/1.0/"), QLatin1String("kipi")); registerXmpNameSpace(QLatin1String("http://ns.microsoft.com/photo/1.2/"), QLatin1String("MP")); registerXmpNameSpace(QLatin1String("http://ns.acdsee.com/iptc/1.0/"), QLatin1String("acdsee")); registerXmpNameSpace(QLatin1String("http://www.video"), QLatin1String("video")); #endif // _XMP_SUPPORT_ return true; } bool MetaEngine::cleanupExiv2() { // Fix memory leak if Exiv2 support XMP. #ifdef _XMP_SUPPORT_ unregisterXmpNameSpace(QLatin1String("http://ns.adobe.com/lightroom/1.0/")); unregisterXmpNameSpace(QLatin1String("https://www.digikam.org/ns/kipi/1.0/")); unregisterXmpNameSpace(QLatin1String("http://ns.microsoft.com/photo/1.2/")); unregisterXmpNameSpace(QLatin1String("http://ns.acdsee.com/iptc/1.0/")); unregisterXmpNameSpace(QLatin1String("http://www.video")); Exiv2::XmpParser::terminate(); #endif // _XMP_SUPPORT_ return true; } bool MetaEngine::supportXmp() { #ifdef _XMP_SUPPORT_ return true; #else return false; #endif // _XMP_SUPPORT_ } bool MetaEngine::supportMetadataWritting(const QString& typeMime) { if (typeMime == QLatin1String("image/jpeg")) { return true; } else if (typeMime == QLatin1String("image/tiff")) { return true; } else if (typeMime == QLatin1String("image/png")) { return true; } else if (typeMime == QLatin1String("image/jp2")) { return true; } else if (typeMime == QLatin1String("image/x-raw")) { return true; } else if (typeMime == QLatin1String("image/pgf")) { return true; } return false; } QString MetaEngine::Exiv2Version() { #if EXIV2_TEST_VERSION(0,27,0) return QLatin1String(Exiv2::versionString().c_str()); #else return QLatin1String(Exiv2::version()); #endif } //-- General methods ---------------------------------------------- MetaEngineData MetaEngine::data() const { MetaEngineData data; data.d = d->data; return data; } void MetaEngine::setData(const MetaEngineData& data) { if (data.d) { d->data = data.d; } else { // MetaEngineData can have a null pointer, // but we never want a null pointer in Private. d->data->clear(); } } bool MetaEngine::loadFromData(const QByteArray& imgData) { if (imgData.isEmpty()) return false; QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((Exiv2::byte*)imgData.data(), imgData.size()); d->filePath.clear(); image->readMetadata(); // Size and mimetype --------------------------------- d->pixelSize = QSize(image->pixelWidth(), image->pixelHeight()); d->mimeType = QLatin1String(image->mimeType().c_str()); // Image comments --------------------------------- d->itemComments() = image->comment(); // Exif metadata ---------------------------------- d->exifMetadata() = image->exifData(); // Iptc metadata ---------------------------------- d->iptcMetadata() = image->iptcData(); #ifdef _XMP_SUPPORT_ // Xmp metadata ----------------------------------- d->xmpMetadata() = image->xmpData(); #endif // _XMP_SUPPORT_ return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot load metadata using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } bool MetaEngine::isEmpty() const { if (!hasComments() && !hasExif() && !hasIptc() && !hasXmp()) return true; return false; } QSize MetaEngine::getPixelSize() const { return d->pixelSize; } QString MetaEngine::getMimeType() const { return d->mimeType; } void MetaEngine::setWriteRawFiles(const bool on) { d->writeRawFiles = on; } bool MetaEngine::writeRawFiles() const { return d->writeRawFiles; } void MetaEngine::setUseXMPSidecar4Reading(const bool on) { d->useXMPSidecar4Reading = on; } bool MetaEngine::useXMPSidecar4Reading() const { return d->useXMPSidecar4Reading; } +void MetaEngine::setUseCompatibleFileName(const bool on) +{ + d->useCompatibleFileName = on; +} + +bool MetaEngine::useCompatibleFileName() const +{ + return d->useCompatibleFileName; +} + void MetaEngine::setMetadataWritingMode(const int mode) { d->metadataWritingMode = mode; } int MetaEngine::metadataWritingMode() const { return d->metadataWritingMode; } void MetaEngine::setUpdateFileTimeStamp(bool on) { d->updateFileTimeStamp = on; } bool MetaEngine::updateFileTimeStamp() const { return d->updateFileTimeStamp; } bool MetaEngine::setProgramId() const { QString version(digiKamVersion()); QLatin1String software("digiKam"); return setItemProgramId(software, version); } } // namespace Digikam diff --git a/core/libs/metadataengine/engine/metaengine.h b/core/libs/metadataengine/engine/metaengine.h index 5eed0f86c0..79f3cdfff5 100644 --- a/core/libs/metadataengine/engine/metaengine.h +++ b/core/libs/metadataengine/engine/metaengine.h @@ -1,1120 +1,1128 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface * Exiv2: http://www.exiv2.org * Exif : http://www.exif.org/Exif2-2.PDF * Iptc : http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf * Xmp : http://www.adobe.com/devnet/xmp/pdfs/xmp_specification.pdf * http://www.iptc.org/std/Iptc4xmpCore/1.0/specification/Iptc4xmpCore_1.0-spec-XMPSchema_8.pdf * Paper: http://www.metadataworkinggroup.com/pdf/mwg_guidance.pdf * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * 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_META_ENGINE_H #define DIGIKAM_META_ENGINE_H // QT includes #include #include #include #include #include #include #include #include #include #include // Local includes #include "metaengine_data.h" #include "digikam_export.h" namespace Digikam { class DIGIKAM_EXPORT MetaEngine { public: /** * The item metadata writing mode, between item file metadata and XMP sidecar file, depending on the context. * @sa MetadataWritingMode(), metadataWritingMode() */ enum MetadataWritingMode { /// Write metadata to item file only. WRITE_TO_FILE_ONLY = 0, /// Write metadata to sidecar file only. WRITE_TO_SIDECAR_ONLY = 1, /// Write metadata to item and sidecar files. WRITE_TO_SIDECAR_AND_FILE = 2, /// Write metadata to sidecar file only for read only items such as RAW files for example. WRITE_TO_SIDECAR_ONLY_FOR_READ_ONLY_FILES = 3 }; /** * The item color workspace values given by Exif metadata. */ enum ImageColorWorkSpace { WORKSPACE_UNSPECIFIED = 0, WORKSPACE_SRGB = 1, WORKSPACE_ADOBERGB = 2, WORKSPACE_UNCALIBRATED = 65535 }; /** * The item orientation values given by Exif metadata. */ enum ImageOrientation { ORIENTATION_UNSPECIFIED = 0, ORIENTATION_NORMAL = 1, ORIENTATION_HFLIP = 2, ORIENTATION_ROT_180 = 3, ORIENTATION_VFLIP = 4, ORIENTATION_ROT_90_HFLIP = 5, ORIENTATION_ROT_90 = 6, ORIENTATION_ROT_90_VFLIP = 7, ORIENTATION_ROT_270 = 8 }; /** * Xmp tag types, used by setXmpTag, only first three types are used */ enum XmpTagType { NormalTag = 0, ArrayBagTag = 1, StructureTag = 2, ArrayLangTag = 3, ArraySeqTag = 4 }; /** * A map used to store Tags Key and Tags Value. */ typedef QMap MetaDataMap; /** * A map used to store a list of Alternative Language values. * The map key is the language code following RFC3066 notation * (like "fr-FR" for French), and the map value the text. */ typedef QMap AltLangMap; /** * A map used to store Tags Key and a list of Tags properties : * - name, * - title, * - description. */ typedef QMap TagsMap; public: /** * Standard constructor. */ MetaEngine(); /** * Copy constructor. */ MetaEngine(const MetaEngine& metadata); /** * Constructor to load from parsed data. */ explicit MetaEngine(const MetaEngineData& data); /** * Contructor to Load Metadata from item file. */ explicit MetaEngine(const QString& filePath); /** * Standard destructor */ virtual ~MetaEngine(); /** * Create a copy of container */ MetaEngine& operator=(const MetaEngine& metadata); public: //----------------------------------------------------------------- /// @name Static methods //@{ /** Return true if Exiv2 library initialization is done properly. * This method must be called before using libMetaEngine with multithreading. * It initialize several non re-entrancy code from Adobe XMP SDK * See Bug #166424 for details. Call cleanupExiv2() to clean things up later. */ static bool initializeExiv2(); /** Return true if Exiv2 library memory allocations are cleaned properly. * This method must be called after using libMetaEngine with multithreading. * It cleans up memory used by Adobe XMP SDK * See Bug #166424 for details. */ static bool cleanupExiv2(); /** Return true if library can handle Xmp metadata */ static bool supportXmp(); /** Return true if library can write metadata to typeMime file format. */ static bool supportMetadataWritting(const QString& typeMime); /** Return a string version of Exiv2 release in format "major.minor.patch" */ static QString Exiv2Version(); //@} //----------------------------------------------------------------- /// @name General methods //@{ MetaEngineData data() const; void setData(const MetaEngineData& data); /** Load all metadata (Exif, Iptc, Xmp, and JFIF Comments) from a byte array. * Return true if metadata have been loaded successfully from item data. */ bool loadFromData(const QByteArray& imgData); /** Return 'true' if metadata container in memory as no Comments, Exif, Iptc, and Xmp. */ bool isEmpty() const; /** Returns the pixel size of the current item. This information is read from the file, * not from the metadata. The returned QSize is valid if the MetaEngine object was _constructed_ * by reading a file or item data; the information is not available when the object * was created from MetaEngineData. * Note that in the Exif or XMP metadata, there may be fields describing the item size. * These fields are not accessed by this method. * When replacing the metadata with setData(), the metadata may change; this information * always keeps referring to the file it was initially read from. */ QSize getPixelSize() const; /** Returns the mime type of this item. The information is read from the file; * see the docs for getPixelSize() to know when it is available. */ QString getMimeType() const; /** Enable or disable writing metadata operations to RAW tiff based files. * It requires Exiv2 0.18. By default RAW files are untouched. */ void setWriteRawFiles(const bool on); /** Return true if writing metadata operations on RAW tiff based files is enabled. * It's require at least Exiv2 0.18. */ bool writeRawFiles() const; - /** Enable or disable using XMP sidecar for reading metadata + /** Enable or disable using XMP sidecar for reading metadata. */ void setUseXMPSidecar4Reading(const bool on); /** Return true if using XMP sidecar for reading metadata is enabled. */ bool useXMPSidecar4Reading() const; + /** Enable or disable using compatible file name for sidecar files. + */ + void setUseCompatibleFileName(const bool on); + + /** Return true if using compatible file name for sidecar files. + */ + bool useCompatibleFileName() const; + /** Set metadata writing mode. * @param mode Metadata writing mode as defined by the #MetadataWritingMode enum. * @sa MetadataWritingMode, metadataWritingMode() */ void setMetadataWritingMode(const int mode); /** Return the metadata writing mode. * @returns Metadata writing mode as defined by the #MetadataWritingMode enum. * @sa MetadataWritingMode, setMetadataWritingMode() */ int metadataWritingMode() const; /** Enable or disable file timestamp updating when metadata are saved. * By default files timestamp are untouched. */ void setUpdateFileTimeStamp(bool on); /** Return true if file timestamp is updated when metadata are saved. */ bool updateFileTimeStamp() const; //@} //------------------------------------------------------------------- /// @name File I/O methods //@{ /** Set the file path of current item. */ void setFilePath(const QString& path); /** Return the file path of current item. */ QString getFilePath() const; /** Return the XMP Sidecar file path for a item file path. * If item file path do not include a file name or is empty, this function return a null string. */ - static QString sidecarFilePathForFile(const QString& path); + static QString sidecarFilePathForFile(const QString& path, bool useLR = false); /** Like sidecarFilePathForFile(), but works for local file path. */ static QString sidecarPath(const QString& path); /** Like sidecarFilePathForFile(), but works for remote URLs. */ static QUrl sidecarUrl(const QUrl& url); /** Gives a file url for a local path. */ static QUrl sidecarUrl(const QString& path); /** Performs a QFileInfo based check if the given local file has a sidecar. */ static bool hasSidecar(const QString& path); /** Load all metadata (Exif, Iptc, Xmp, and JFIF Comments) from a picture (JPEG, RAW, TIFF, PNG, * DNG, etc...). Return true if metadata have been loaded successfully from file. */ virtual bool load(const QString& filePath); /** Load metadata from a sidecar file and merge. * Return true if metadata have been loaded successfully from file. */ bool loadFromSidecarAndMerge(const QString& filePath); /** Save all metadata to a file. This one can be different than original picture to perform * transfert operation Return true if metadata have been saved into file. */ bool save(const QString& filePath, bool setVersion = false) const; /** The same than save() method, but it apply on current item. Return true if metadata * have been saved into file. */ bool applyChanges(bool setVersion = false) const; //@} //------------------------------------------------------------------- /// @name Metadata item information manipulation methods //@{ /** Set Program name and program version in Exif and Iptc Metadata. Return true if information * have been changed in metadata. */ bool setItemProgramId(const QString& program, const QString& version) const; /** Return the size of item in pixels using Exif tags. Return a null dimension if size cannot * be found. */ QSize getItemDimensions() const; /** Set the size of item in pixels in Exif tags. Return true if size have been changed * in metadata. */ bool setItemDimensions(const QSize& size) const; /** Return the item orientation set in Exif metadata. The makernotes of item are also parsed to * get this information. See ImageOrientation values for details. */ MetaEngine::ImageOrientation getItemOrientation() const; /** Set the Exif orientation tag of item. See ImageOrientation values for details * Return true if orientation have been changed in metadata. */ bool setItemOrientation(ImageOrientation orientation) const; /** Return the item color-space set in Exif metadata. The makernotes of item are also parsed to * get this information. See ImageColorWorkSpace values for details. */ MetaEngine::ImageColorWorkSpace getItemColorWorkSpace() const; /** Set the Exif color-space tag of item. See ImageColorWorkSpace values for details * Return true if work-space have been changed in metadata. */ bool setItemColorWorkSpace(ImageColorWorkSpace workspace) const; /** Return the time stamp of item. Exif information are check in first, IPTC in second * if item don't have Exif information. If no time stamp is found, a null date is returned. */ QDateTime getItemDateTime() const; /** Set the Exif and Iptc time stamp. If 'setDateTimeDigitized' parameter is true, the 'Digitalized' * time stamp is set, else only 'Created' time stamp is set. */ bool setImageDateTime(const QDateTime& dateTime, bool setDateTimeDigitized=false) const; /** Return the digitization time stamp of the item. First Exif information is checked, then IPTC. * If no digitization time stamp is found, getItemDateTime() is called if fallbackToCreationTime * is true, or a null QDateTime is returned if fallbackToCreationTime is false. */ QDateTime getDigitizationDateTime(bool fallbackToCreationTime=false) const; /** Return a QImage copy of Iptc preview image. Return a null item if preview cannot * be found. */ bool getItemPreview(QImage& preview) const; /** Set the Iptc preview image. The thumbnail item must have the right size before (64Kb max * with JPEG file, else 256Kb). Look Iptc specification for details. Return true if preview * have been changed in metadata. * Re-implement this method if you want to use another item file format than JPEG to * save preview. */ virtual bool setItemPreview(const QImage& preview) const; //@} //----------------------------------------------------------------- /// @name Comments manipulation methods //@{ /** Return 'true' if Comments can be written in file. */ static bool canWriteComment(const QString& filePath); /** Return 'true' if metadata container in memory as Comments. */ bool hasComments() const; /** Clear the Comments metadata container in memory. */ bool clearComments() const; /** Return a Qt byte array copy of Comments container get from current item. * Comments are JFIF section of JPEG images. Look Exiv2 API for more information. * Return a null Qt byte array if there is no Comments metadata in memory. */ QByteArray getComments() const; /** Return a Qt string object of Comments from current item decoded using * the 'detectEncodingAndDecode()' method. Return a null string if there is no * Comments metadata available. */ QString getCommentsDecoded() const; /** Set the Comments data using a Qt byte array. Return true if Comments metadata * have been changed in memory. */ bool setComments(const QByteArray& data) const; /** Language Alternative autodetection. Return a QString without language alternative * header. Header is saved into 'lang'. If no language alternative is founf, value is returned * as well and 'lang' is set to a null string. */ static QString detectLanguageAlt(const QString& value, QString& lang); //@} //----------------------------------------------------------------- /// @name Exif manipulation methods //@{ /** Return a map of all standard Exif tags supported by Exiv2. */ TagsMap getStdExifTagsList() const; /** Return a map of all non-standard Exif tags (makernotes) supported by Exiv2. */ TagsMap getMakernoteTagsList() const; /** Return 'true' if Exif can be written in file. */ static bool canWriteExif(const QString& filePath); /** Return 'true' if metadata container in memory as Exif. */ bool hasExif() const; /** Clear the Exif metadata container in memory. */ bool clearExif() const; /** Returns the exif data encoded to a QByteArray in a form suitable * for storage in a JPEG image. * Note that this encoding is a lossy operation. * * Set true 'addExifHeader' parameter to add an Exif header to Exif metadata. * Returns a null Qt byte array if there is no Exif metadata in memory. */ QByteArray getExifEncoded(bool addExifHeader=false) const; /** Set the Exif data using a Qt byte array. Return true if Exif metadata * have been changed in memory. */ bool setExif(const QByteArray& data) const; /** Return a QImage copy of Exif thumbnail image. Return a null image if thumbnail cannot * be found. The 'fixOrientation' parameter will rotate automatically the thumbnail if Exif * orientation tags information are attached with thumbnail. */ QImage getExifThumbnail(bool fixOrientation) const; /** Fix orientation of a QImage image accordingly with Exif orientation tag. * Return true if image is rotated, else false. */ bool rotateExifQImage(QImage& image, ImageOrientation orientation) const; /** Set the Exif Thumbnail image. The thumbnail image must have the right dimensions before. * Look Exif specification for details. Return true if thumbnail have been changed in metadata. */ bool setExifThumbnail(const QImage& thumb) const; /** Remove the Exif Thumbnail from the item */ bool removeExifThumbnail() const; /** Adds a JPEG thumbnail to a TIFF images. Use this instead of setExifThumbnail for TIFF images. */ bool setTiffThumbnail(const QImage& thumb) const; /** Return a QString copy of Exif user comments. Return a null string if user comments cannot * be found. */ QString getExifComment(bool readDescription = true) const; /** Set the Exif user comments from item. Look Exif specification for more details about this tag. * Return true if Exif user comments have been changed in metadata. */ bool setExifComment(const QString& comment, bool writeDescription = true) const; /** Get an Exif tags content like a string. If 'escapeCR' parameter is true, the CR characters * will be removed. If Exif tag cannot be found a null string is returned. */ QString getExifTagString(const char* exifTagName, bool escapeCR=true) const; /** Set an Exif tag content using a string. Return true if tag is set successfully. */ bool setExifTagString(const char* exifTagName, const QString& value) const; /** Get an Exif tag content like a long value. Return true if Exif tag be found. */ bool getExifTagLong(const char* exifTagName, long &val) const; /** Get an Exif tag content like a long value. Return true if Exif tag be found. */ bool getExifTagLong(const char* exifTagName, long &val, int component) const; /** Set an Exif tag content using a long value. Return true if tag is set successfully. */ bool setExifTagLong(const char* exifTagName, long val) const; /** Get the 'component' index of an Exif tags content like a rational value. * 'num' and 'den' are the numerator and the denominator of the rational value. * Return true if Exif tag be found. */ bool getExifTagRational(const char* exifTagName, long int& num, long int& den, int component=0) const; /** Set an Exif tag content using a rational value. * 'num' and 'den' are the numerator and the denominator of the rational value. * Return true if tag is set successfully. */ bool setExifTagRational(const char* exifTagName, long int num, long int den) const; /** Get an Exif tag content like a bytes array. Return an empty bytes array if Exif * tag cannot be found. */ QByteArray getExifTagData(const char* exifTagName) const; /** Set an Exif tag content using a bytes array. Return true if tag is set successfully. */ bool setExifTagData(const char* exifTagName, const QByteArray& data) const; /** Get an Exif tags content as a QVariant. Returns a null QVariant if the Exif * tag cannot be found. * For string and integer values the matching QVariant types will be used, * for date and time values QVariant::DateTime. * Rationals will be returned as QVariant::List with two integer QVariants (numerator, denominator) * if rationalAsListOfInts is true, as double if rationalAsListOfInts is false. * An exif tag of numerical type may contain more than one value; set component to the desired index. */ QVariant getExifTagVariant(const char* exifTagName, bool rationalAsListOfInts=true, bool escapeCR=true, int component=0) const; /** Set an Exif tag content using a QVariant. Returns true if tag is set successfully. * All types described for the getExifTagVariant() method are supported. * Calling with a QVariant of type ByteArray is equivalent to calling setExifTagData. * For the meaning of rationalWantSmallDenominator, see the documentation of the convertToRational methods. * Setting a value with multiple components is currently not supported. */ bool setExifTagVariant(const char* exifTagName, const QVariant& data, bool rationalWantSmallDenominator=true) const; /** Remove the Exif tag 'exifTagName' from Exif metadata. Return true if tag is * removed successfully or if no tag was present. */ bool removeExifTag(const char* exifTagName) const; /** Return the Exif Tag title or a null string. */ QString getExifTagTitle(const char* exifTagName); /** Return the Exif Tag description or a null string. */ QString getExifTagDescription(const char* exifTagName); /** Takes a QVariant value as it could have been retrieved by getExifTagVariant with the given exifTagName, * and returns its value properly converted to a string (including translations from Exiv2). * This is equivalent to calling getExifTagString directly. * If escapeCR is true CR characters will be removed from the result. */ QString createExifUserStringFromValue(const char* exifTagName, const QVariant& val, bool escapeCR=true); /** Return a map of Exif tags name/value found in metadata sorted by * Exif keys given by 'exifKeysFilter'. * * 'exifKeysFilter' is a QStringList of Exif keys. * For example, if you use the string list given below: * * "Iop" * "Thumbnail" * "Image" * "Photo" * * List can be empty to not filter output. * * ... this method will return a map of all Exif tags which : * * - include "Iop", or "Thumbnail", or "Image", or "Photo" in the Exif tag keys * if 'inverSelection' is false. * - not include "Iop", or "Thumbnail", or "Image", or "Photo" in the Exif tag keys * if 'inverSelection' is true. */ MetaEngine::MetaDataMap getExifTagsDataList(const QStringList& exifKeysFilter=QStringList(), bool invertSelection=false) const; //@} //------------------------------------------------------------- /// @name IPTC manipulation methods //@{ /** Return a map of all standard Iptc tags supported by Exiv2. */ MetaEngine::TagsMap getIptcTagsList() const; /** Return 'true' if Iptc can be written in file. */ static bool canWriteIptc(const QString& filePath); /** Return 'true' if metadata container in memory as Iptc. */ bool hasIptc() const; /** Clear the Iptc metadata container in memory. */ bool clearIptc() const; /** Return a Qt byte array copy of Iptc container get from current item. * Set true 'addIrbHeader' parameter to add an Irb header to Iptc metadata. * Return a null Qt byte array if there is no Iptc metadata in memory. */ QByteArray getIptc(bool addIrbHeader=false) const; /** Set the Iptc data using a Qt byte array. Return true if Iptc metadata * have been changed in memory. */ bool setIptc(const QByteArray& data) const; /** Get an Iptc tag content like a string. If 'escapeCR' parameter is true, the CR characters * will be removed. If Iptc tag cannot be found a null string is returned. */ QString getIptcTagString(const char* iptcTagName, bool escapeCR=true) const; /** Set an Iptc tag content using a string. Return true if tag is set successfully. */ bool setIptcTagString(const char* iptcTagName, const QString& value) const; /** Returns a strings list with of multiple Iptc tags from the item. Return an empty list if no tag is found. * Get the values of all IPTC tags with the given tag name in a string list. * (In Iptc, there can be multiple tags with the same name) * If the 'escapeCR' parameter is true, the CR characters * will be removed. * If no tag can be found an empty list is returned. */ QStringList getIptcTagsStringList(const char* iptcTagName, bool escapeCR=true) const; /** Set multiple Iptc tags contents using a strings list. 'maxSize' is the max characters size * of one entry. Return true if all tags have been set successfully. */ bool setIptcTagsStringList(const char* iptcTagName, int maxSize, const QStringList& oldValues, const QStringList& newValues) const; /** Get an Iptc tag content as a bytes array. Return an empty bytes array if Iptc * tag cannot be found. */ QByteArray getIptcTagData(const char* iptcTagName) const; /** Set an Iptc tag content using a bytes array. Return true if tag is set successfully. */ bool setIptcTagData(const char* iptcTagName, const QByteArray& data) const; /** Remove the all instance of Iptc tags 'iptcTagName' from Iptc metadata. Return true if all tags have been removed successfully (or none were present). */ bool removeIptcTag(const char* iptcTagName) const; /** Return the Iptc Tag title or a null string. */ QString getIptcTagTitle(const char* iptcTagName); /** Return the Iptc Tag description or a null string. */ QString getIptcTagDescription(const char* iptcTagName); /** Return a map of Iptc tags name/value found in metadata sorted by * Iptc keys given by 'iptcKeysFilter'. * * 'iptcKeysFilter' is a QStringList of Iptc keys. * For example, if you use the string list given below: * * "Envelope" * "Application2" * * List can be empty to not filter output. * * ... this method will return a map of all Iptc tags which : * * - include "Envelope", or "Application2" in the Iptc tag keys * if 'inverSelection' is false. * - not include "Envelope", or "Application2" in the Iptc tag keys * if 'inverSelection' is true. */ MetaEngine::MetaDataMap getIptcTagsDataList(const QStringList& iptcKeysFilter=QStringList(), bool invertSelection=false) const; /** Return a strings list of Iptc keywords from item. Return an empty list if no keyword are set. */ QStringList getIptcKeywords() const; /** Set Iptc keywords using a list of strings defined by 'newKeywords' parameter. Use 'getImageKeywords()' * method to set 'oldKeywords' parameter with existing keywords from item. The method will compare * all new keywords with all old keywords to prevent duplicate entries in item. Return true if keywords * have been changed in metadata. */ bool setIptcKeywords(const QStringList& oldKeywords, const QStringList& newKeywords) const; /** Return a strings list of Iptc subjects from item. Return an empty list if no subject are set. */ QStringList getIptcSubjects() const; /** Set Iptc subjects using a list of strings defined by 'newSubjects' parameter. Use 'getImageSubjects()' * method to set 'oldSubjects' parameter with existing subjects from item. The method will compare * all new subjects with all old subjects to prevent duplicate entries in item. Return true if subjects * have been changed in metadata. */ bool setIptcSubjects(const QStringList& oldSubjects, const QStringList& newSubjects) const; /** Return a strings list of Iptc sub-categories from item. Return an empty list if no sub-category * are set. */ QStringList getIptcSubCategories() const; /** Set Iptc sub-categories using a list of strings defined by 'newSubCategories' parameter. Use * 'getImageSubCategories()' method to set 'oldSubCategories' parameter with existing sub-categories * from item. The method will compare all new sub-categories with all old sub-categories to prevent * duplicate entries in item. Return true if sub-categories have been changed in metadata. */ bool setIptcSubCategories(const QStringList& oldSubCategories, const QStringList& newSubCategories) const; //@} //------------------------------------------------------------ /// @name XMP manipulation methods //@{ /** Return a map of all standard Xmp tags supported by Exiv2. */ MetaEngine::TagsMap getXmpTagsList() const; /** Return 'true' if Xmp can be written in file. */ static bool canWriteXmp(const QString& filePath); /** Return 'true' if metadata container in memory as Xmp. */ bool hasXmp() const; /** Clear the Xmp metadata container in memory. */ bool clearXmp() const; /** Return a Qt byte array copy of XMp container get from current item. * Return a null Qt byte array if there is no Xmp metadata in memory. */ QByteArray getXmp() const; /** Set the Xmp data using a Qt byte array. Return true if Xmp metadata * have been changed in memory. */ bool setXmp(const QByteArray& data) const; /** Get a Xmp tag content like a string. If 'escapeCR' parameter is true, the CR characters * will be removed. If Xmp tag cannot be found a null string is returned. */ QString getXmpTagString(const char* xmpTagName, bool escapeCR=true) const; /** Set a Xmp tag content using a string. Return true if tag is set successfully. */ bool setXmpTagString(const char* xmpTagName, const QString& value) const; /** Set a Xmp tag with a specific type. Return true if tag is set successfully. * This method only accept NormalTag, ArrayBagTag and StructureTag. * Other XmpTagTypes do nothing */ bool setXmpTagString(const char* xmpTagName, const QString& value, XmpTagType type) const; /** Return the Xmp Tag title or a null string. */ QString getXmpTagTitle(const char* xmpTagName); /** Return the Xmp Tag description or a null string. */ QString getXmpTagDescription(const char* xmpTagName); /** Return a map of Xmp tags name/value found in metadata sorted by * Xmp keys given by 'xmpKeysFilter'. * * 'xmpKeysFilter' is a QStringList of Xmp keys. * For example, if you use the string list given below: * * "dc" // Dubling Core schema. * "xmp" // Standard Xmp schema. * * List can be empty to not filter output. * * ... this method will return a map of all Xmp tags which : * * - include "dc", or "xmp" in the Xmp tag keys * if 'inverSelection' is false. * - not include "dc", or "xmp" in the Xmp tag keys * if 'inverSelection' is true. */ MetaEngine::MetaDataMap getXmpTagsDataList(const QStringList& xmpKeysFilter=QStringList(), bool invertSelection=false) const; /** Get all redondant Alternative Language Xmp tags content like a map. * See AltLangMap class description for details. * If 'escapeCR' parameter is true, the CR characters will be removed from strings. * If Xmp tag cannot be found a null string list is returned. */ MetaEngine::AltLangMap getXmpTagStringListLangAlt(const char* xmpTagName, bool escapeCR=true) const; /** Set an Alternative Language Xmp tag content using a map. See AltLangMap class * description for details. If tag already exist, it will be removed before. * Return true if tag is set successfully. */ bool setXmpTagStringListLangAlt(const char* xmpTagName, const MetaEngine::AltLangMap& values) const; /** Get a Xmp tag content like a string set with an alternative language * header 'langAlt' (like "fr-FR" for French - RFC3066 notation) * If 'escapeCR' parameter is true, the CR characters will be removed. * If Xmp tag cannot be found a null string is returned. */ QString getXmpTagStringLangAlt(const char* xmpTagName, const QString& langAlt, bool escapeCR) const; /** Set a Xmp tag content using a string with an alternative language header. 'langAlt' contain the * language alternative information (like "fr-FR" for French - RFC3066 notation) or is null to * set alternative language to default settings ("x-default"). * Return true if tag is set successfully. */ bool setXmpTagStringLangAlt(const char* xmpTagName, const QString& value, const QString& langAlt) const; /** Get a Xmp tag content like a sequence of strings. If 'escapeCR' parameter is true, the CR characters * will be removed from strings. If Xmp tag cannot be found a null string list is returned. */ QStringList getXmpTagStringSeq(const char* xmpTagName, bool escapeCR=true) const; /** Set a Xmp tag content using the sequence of strings 'seq'. * Return true if tag is set successfully. */ bool setXmpTagStringSeq(const char* xmpTagName, const QStringList& seq) const; /** Get a Xmp tag content like a bag of strings. If 'escapeCR' parameter is true, the CR characters * will be removed from strings. If Xmp tag cannot be found a null string list is returned. */ QStringList getXmpTagStringBag(const char* xmpTagName, bool escapeCR) const; /** Set a Xmp tag content using the bag of strings 'bag'. * Return true if tag is set successfully. */ bool setXmpTagStringBag(const char* xmpTagName, const QStringList& bag) const; /** Set an Xmp tag content using a list of strings defined by the 'entriesToAdd' parameter. * The existing entries are preserved. The method will compare * all new with all already existing entries to prevent duplicates in the item. * Return true if the entries have been added to metadata. */ bool addToXmpTagStringBag(const char* xmpTagName, const QStringList& entriesToAdd) const; /** Remove those Xmp tag entries that are listed in entriesToRemove from the entries in metadata. * Return true if tag entries are no longer contained in metadata. * All other entries are preserved. */ bool removeFromXmpTagStringBag(const char* xmpTagName, const QStringList& entriesToRemove) const; /** Get an Xmp tag content as a QVariant. Returns a null QVariant if the Xmp * tag cannot be found. * For string and integer values the matching QVariant types will be used, * for date and time values QVariant::DateTime. * Rationals will be returned as QVariant::List with two integer QVariants (numerator, denominator) * if rationalAsListOfInts is true, as double if rationalAsListOfInts is false. * Arrays (ordered, unordered, alternative) are returned as type StringList. * LangAlt values will have type Map (QMap) with the language * code as key and the contents as value, of type String. */ QVariant getXmpTagVariant(const char* xmpTagName, bool rationalAsListOfInts=true, bool stringEscapeCR=true) const; /** Return a strings list of Xmp keywords from item. Return an empty list if no keyword are set. */ QStringList getXmpKeywords() const; /** Set Xmp keywords using a list of strings defined by 'newKeywords' parameter. * The existing keywords from item are preserved. The method will compare * all new keywords with all already existing keywords to prevent duplicate entries in item. * Return true if keywords have been changed in metadata. */ bool setXmpKeywords(const QStringList& newKeywords) const; /** Remove those Xmp keywords that are listed in keywordsToRemove from the keywords in metadata. * Return true if keywords are no longer contained in metadata. */ bool removeXmpKeywords(const QStringList& keywordsToRemove); /** Return a strings list of Xmp subjects from item. Return an empty list if no subject are set. */ QStringList getXmpSubjects() const; /** Set Xmp subjects using a list of strings defined by 'newSubjects' parameter. * The existing subjects from item are preserved. The method will compare * all new subject with all already existing subject to prevent duplicate entries in item. * Return true if subjects have been changed in metadata. */ bool setXmpSubjects(const QStringList& newSubjects) const; /** Remove those Xmp subjects that are listed in subjectsToRemove from the subjects in metadata. * Return true if subjects are no longer contained in metadata. */ bool removeXmpSubjects(const QStringList& subjectsToRemove); /** Return a strings list of Xmp sub-categories from item. Return an empty list if no sub-category * are set. */ QStringList getXmpSubCategories() const; /** Set Xmp sub-categories using a list of strings defined by 'newSubCategories' parameter. * The existing sub-categories from item are preserved. The method will compare * all new sub-categories with all already existing sub-categories to prevent duplicate entries in item. * Return true if sub-categories have been changed in metadata. */ bool setXmpSubCategories(const QStringList& newSubCategories) const; /** Remove those Xmp sub-categories that are listed in categoriesToRemove from the sub-categories in metadata. * Return true if subjects are no longer contained in metadata. */ bool removeXmpSubCategories(const QStringList& categoriesToRemove); /** Remove the Xmp tag 'xmpTagName' from Xmp metadata. Return true if tag is * removed successfully or if no tag was present. */ bool removeXmpTag(const char* xmpTagName) const; /** Register a namespace which Exiv2 doesn't know yet. This is only needed * when new Xmp properties are added manually. 'uri' is the namespace url and prefix the * string used to construct new Xmp key (ex. "Xmp.digiKam.tagList"). * NOTE: If the Xmp metadata is read from an item, namespaces are decoded and registered * by Exiv2 at the same time. */ static bool registerXmpNameSpace(const QString& uri, const QString& prefix); /** Unregister a previously registered custom namespace */ static bool unregisterXmpNameSpace(const QString& uri); //@} //------------------------------------------------------------ /// @name GPS manipulation methods //@{ /** Make sure all static required GPS EXIF and XMP tags exist */ bool initializeGPSInfo(); /** Get all GPS location information set in item. Return true if all information can be found. */ bool getGPSInfo(double& altitude, double& latitude, double& longitude) const; /** Get GPS location information set in the item, in the GPSCoordinate format * as described in the XMP specification. Returns a null string in the information cannot be found. */ QString getGPSLatitudeString() const; QString getGPSLongitudeString() const; /** Get GPS location information set in the item, as a double floating point number as in degrees * where the sign determines the direction ref (North + / South - ; East + / West -). * Returns true if the information is available. */ bool getGPSLatitudeNumber(double* const latitude) const; bool getGPSLongitudeNumber(double* const longitude) const; /** Get GPS altitude information, in meters, relative to sea level (positive sign above sea level) */ bool getGPSAltitude(double* const altitude) const; /** Set all GPS location information into item. Return true if all information have been * changed in metadata. */ bool setGPSInfo(const double altitude, const double latitude, const double longitude); /** Set all GPS location information into item. Return true if all information have been * changed in metadata. If you do not want altitude to be set, pass a null pointer. */ bool setGPSInfo(const double* const altitude, const double latitude, const double longitude); /** Set all GPS location information into item. Return true if all information have been * changed in metadata. */ bool setGPSInfo(const double altitude, const QString& latitude, const QString& longitude); /** Remove all Exif tags relevant of GPS location information. Return true if all tags have been * removed successfully in metadata. */ bool removeGPSInfo(); /** This method converts 'number' to a rational value, returned in the 'numerator' and * 'denominator' parameters. Set the precision using 'rounding' parameter. * Use this method if you want to retrieve a most exact rational for a number * without further properties, without any requirements to the denominator. */ static void convertToRational(const double number, long int* const numerator, long int* const denominator, const int rounding); /** This method convert a 'number' to a rational value, returned in 'numerator' and * 'denominator' parameters. * This method will be able to retrieve a rational number from a double - if you * constructed your double with 1.0 / 4786.0, this method will retrieve 1 / 4786. * If your number is not expected to be rational, use the method above which is just as * exact with rounding = 4 and more exact with rounding > 4. */ static void convertToRationalSmallDenominator(const double number, long int* const numerator, long int* const denominator); /** Converts a GPS position stored as rationals in Exif to the form described * as GPSCoordinate in the XMP specification, either in the from "256,45,34N" or "256,45.566667N" */ static QString convertToGPSCoordinateString(const long int numeratorDegrees, const long int denominatorDegrees, const long int numeratorMinutes, const long int denominatorMinutes, const long int numeratorSeconds, long int denominatorSeconds, const char directionReference); /** Converts a GPS position stored as double floating point number in degrees to the form described * as GPSCoordinate in the XMP specification. */ static QString convertToGPSCoordinateString(const bool isLatitude, double coordinate); /** Converts a GPSCoordinate string as defined by XMP to three rationals and the direction reference. * Returns true if the conversion was successful. * If minutes is given in the fractional form, a denominator of 1000000 for the minutes will be used. */ static bool convertFromGPSCoordinateString(const QString& coordinate, long int* const numeratorDegrees, long int* const denominatorDegrees, long int* const numeratorMinutes, long int* const denominatorMinutes, long int* const numeratorSeconds, long int* const denominatorSeconds, char* const directionReference); /** Convert a GPSCoordinate string as defined by XMP to a double floating point number in degrees * where the sign determines the direction ref (North + / South - ; East + / West -). * Returns true if the conversion was successful. */ static bool convertFromGPSCoordinateString(const QString& gpsString, double* const coordinate); /** Converts a GPSCoordinate string to user presentable numbers, integer degrees and minutes and * double floating point seconds, and a direction reference ('N' or 'S', 'E' or 'W') */ static bool convertToUserPresentableNumbers(const QString& coordinate, int* const degrees, int* const minutes, double* const seconds, char* const directionReference); /** Converts a double floating point number to user presentable numbers, integer degrees and minutes and * double floating point seconds, and a direction reference ('N' or 'S', 'E' or 'W'). * The method needs to know for the direction reference * if the latitude or the longitude is meant by the double parameter. */ static void convertToUserPresentableNumbers(const bool isLatitude, double coordinate, int* const degrees, int* const minutes, double* const seconds, char* const directionReference); //@} protected: /** Set the Program Name and Program Version * information in Exif and Iptc metadata */ bool setProgramId() const; private: /** Internal container to store private members. Used to improve binary compatibility */ class Private; Private* const d; friend class MetaEnginePreviews; }; } // namespace Digikam #endif // DIGIKAM_META_ENGINE_H diff --git a/core/libs/metadataengine/engine/metaengine_fileio.cpp b/core/libs/metadataengine/engine/metaengine_fileio.cpp index 4630f544b2..b1be21353f 100644 --- a/core/libs/metadataengine/engine/metaengine_fileio.cpp +++ b/core/libs/metadataengine/engine/metaengine_fileio.cpp @@ -1,304 +1,304 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * File I/O methods * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * 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 "metaengine.h" #include "metaengine_p.h" // Local includes #include "digikam_debug.h" #include "digikam_version.h" namespace Digikam { void MetaEngine::setFilePath(const QString& path) { d->filePath = path; } QString MetaEngine::getFilePath() const { return d->filePath; } -QString MetaEngine::sidecarFilePathForFile(const QString& path) +QString MetaEngine::sidecarFilePathForFile(const QString& path, bool useLR) { if (path.isEmpty()) { return QString(); } QFileInfo info(path); QString pathForLR = path; pathForLR.chop(info.suffix().size()); pathForLR.append(QLatin1String("xmp")); - if (QFileInfo::exists(pathForLR)) + if (useLR || QFileInfo::exists(pathForLR)) { return pathForLR; } return path + QLatin1String(".xmp"); } QUrl MetaEngine::sidecarUrl(const QUrl& url) { return sidecarUrl(url.toLocalFile()); } QUrl MetaEngine::sidecarUrl(const QString& path) { return QUrl::fromLocalFile(sidecarFilePathForFile(path)); } QString MetaEngine::sidecarPath(const QString& path) { return sidecarFilePathForFile(path); } bool MetaEngine::hasSidecar(const QString& path) { return QFileInfo::exists(sidecarFilePathForFile(path)); } bool MetaEngine::load(const QString& filePath) { if (filePath.isEmpty()) { return false; } d->filePath = filePath; bool hasLoaded = false; QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::Image::AutoPtr image; image = Exiv2::ImageFactory::open((const char*)(QFile::encodeName(filePath)).constData()); image->readMetadata(); // Size and mimetype --------------------------------- d->pixelSize = QSize(image->pixelWidth(), image->pixelHeight()); d->mimeType = QLatin1String(image->mimeType().c_str()); // Image comments --------------------------------- d->itemComments() = image->comment(); // Exif metadata ---------------------------------- d->exifMetadata() = image->exifData(); // Iptc metadata ---------------------------------- d->iptcMetadata() = image->iptcData(); #ifdef _XMP_SUPPORT_ // Xmp metadata ----------------------------------- d->xmpMetadata() = image->xmpData(); #endif // _XMP_SUPPORT_ hasLoaded = true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromUtf8("Cannot load metadata from file %1").arg(getFilePath()), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } hasLoaded |= loadFromSidecarAndMerge(filePath); return hasLoaded; } bool MetaEngine::loadFromSidecarAndMerge(const QString& filePath) { if (filePath.isEmpty()) { return false; } d->filePath = filePath; bool hasLoaded = false; #ifdef _XMP_SUPPORT_ QMutexLocker lock(&s_metaEngineMutex); try { if (d->useXMPSidecar4Reading) { QString xmpSidecarPath = sidecarFilePathForFile(filePath); QFileInfo xmpSidecarFileInfo(xmpSidecarPath); Exiv2::Image::AutoPtr xmpsidecar; if (xmpSidecarFileInfo.exists() && xmpSidecarFileInfo.isReadable()) { // Read sidecar data xmpsidecar = Exiv2::ImageFactory::open(QFile::encodeName(xmpSidecarPath).constData()); xmpsidecar->readMetadata(); // Merge #if EXIV2_TEST_VERSION(0,27,99) d->loadSidecarData(std::move(xmpsidecar)); #else d->loadSidecarData(xmpsidecar); #endif hasLoaded = true; } } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromUtf8("Cannot load XMP sidecar from file %1").arg(getFilePath()), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #endif // _XMP_SUPPORT_ return hasLoaded; } bool MetaEngine::save(const QString& imageFilePath, bool setVersion) const { if (setVersion && !setProgramId()) { return false; } // If our image is really a symlink, we should follow the symlink so that // when we delete the file and rewrite it, we are honoring the symlink // (rather than just deleting it and putting a file there). // However, this may be surprising to the user when they are writing sidecar // files. They might expect them to show up where the symlink is. So, we // shouldn't follow the link when figuring out what the filename for the // sidecar should be. // Note, we are not yet handling the case where the sidecar itself is a // symlink. QString regularFilePath = imageFilePath; // imageFilePath might be a // symlink. Below we will change // regularFile to the pointed to // file if so. QFileInfo givenFileInfo(imageFilePath); if (givenFileInfo.isSymLink()) { qCDebug(DIGIKAM_METAENGINE_LOG) << "filePath" << imageFilePath << "is a symlink." << "Using target" << givenFileInfo.canonicalFilePath(); regularFilePath = givenFileInfo.canonicalFilePath();// Walk all the symlinks } // NOTE: see B.K.O #137770 & #138540 : never touch the file if is read only. QFileInfo finfo(regularFilePath); QFileInfo dinfo(finfo.path()); if (!dinfo.isWritable()) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Dir" << dinfo.filePath() << "is read-only. Metadata not saved."; return false; } bool writeToFile = false; bool writeToSidecar = false; bool writeToSidecarIfFileNotPossible = false; bool writtenToFile = false; bool writtenToSidecar = false; qCDebug(DIGIKAM_METAENGINE_LOG) << "MetaEngine::metadataWritingMode" << d->metadataWritingMode; switch(d->metadataWritingMode) { case WRITE_TO_SIDECAR_ONLY: writeToSidecar = true; break; case WRITE_TO_FILE_ONLY: writeToFile = true; break; case WRITE_TO_SIDECAR_AND_FILE: writeToFile = true; writeToSidecar = true; break; case WRITE_TO_SIDECAR_ONLY_FOR_READ_ONLY_FILES: writeToFile = true; writeToSidecarIfFileNotPossible = true; break; } if (writeToFile) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Will write Metadata to file" << finfo.absoluteFilePath(); writtenToFile = d->saveToFile(finfo); if (writtenToFile) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Metadata for file" << finfo.fileName() << "written to file."; } } if (writeToSidecar || (writeToSidecarIfFileNotPossible && !writtenToFile)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Will write XMP sidecar for file" << finfo.fileName(); writtenToSidecar = d->saveToXMPSidecar(regularFilePath); if (writtenToSidecar) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Metadata for file" << finfo.fileName() << "written to XMP sidecar."; } } return writtenToFile || writtenToSidecar; } bool MetaEngine::applyChanges(bool setVersion) const { if (d->filePath.isEmpty()) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Failed to apply changes: file path is empty!"; return false; } return save(d->filePath, setVersion); } } // namespace Digikam diff --git a/core/libs/metadataengine/engine/metaengine_p.cpp b/core/libs/metadataengine/engine/metaengine_p.cpp index 8fd1dc1cfb..f5daf86089 100644 --- a/core/libs/metadataengine/engine/metaengine_p.cpp +++ b/core/libs/metadataengine/engine/metaengine_p.cpp @@ -1,824 +1,827 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Internal private container. * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * 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 "metaengine_p.h" // C ANSI includes extern "C" { #include #ifndef Q_CC_MSVC # include #else # include #endif } // Qt includes #include #include // Local includes #include "digikam_debug.h" #include "metaengine_data_p.h" // Pragma directives to reduce warnings from Exiv2. #if defined(Q_CC_GNU) && !defined(Q_CC_CLANG) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #if defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif namespace Digikam { /** This mutex is used to protect all Exiv2 API calls when MetaEngine is used with multi-threads. */ QMutex s_metaEngineMutex(QMutex::Recursive); MetaEngine::Private::Private() : data(new MetaEngineData::Private) { QMutexLocker lock(&s_metaEngineMutex); writeRawFiles = false; updateFileTimeStamp = false; useXMPSidecar4Reading = false; + useCompatibleFileName = false; metadataWritingMode = WRITE_TO_FILE_ONLY; loadedFromSidecar = false; Exiv2::LogMsg::setHandler(MetaEngine::Private::printExiv2MessageHandler); } MetaEngine::Private::~Private() { } const Exiv2::ExifData& MetaEngine::Private::exifMetadata() const { return data.constData()->exifMetadata; } const Exiv2::IptcData& MetaEngine::Private::iptcMetadata() const { return data.constData()->iptcMetadata; } const std::string& MetaEngine::Private::itemComments() const { return data.constData()->imageComments; } Exiv2::ExifData& MetaEngine::Private::exifMetadata() { return data.data()->exifMetadata; } Exiv2::IptcData& MetaEngine::Private::iptcMetadata() { return data.data()->iptcMetadata; } std::string& MetaEngine::Private::itemComments() { return data.data()->imageComments; } #ifdef _XMP_SUPPORT_ const Exiv2::XmpData& MetaEngine::Private::xmpMetadata() const { return data.constData()->xmpMetadata; } Exiv2::XmpData& MetaEngine::Private::xmpMetadata() { return data.data()->xmpMetadata; } #endif void MetaEngine::Private::copyPrivateData(const Private* const other) { QMutexLocker lock(&s_metaEngineMutex); data = other->data; filePath = other->filePath; writeRawFiles = other->writeRawFiles; updateFileTimeStamp = other->updateFileTimeStamp; useXMPSidecar4Reading = other->useXMPSidecar4Reading; + useCompatibleFileName = other->useCompatibleFileName; metadataWritingMode = other->metadataWritingMode; } bool MetaEngine::Private::saveToXMPSidecar(const QFileInfo& finfo) const { - QString filePath = MetaEngine::sidecarFilePathForFile(finfo.filePath()); + QString filePath = MetaEngine::sidecarFilePathForFile(finfo.filePath(), + useCompatibleFileName); if (filePath.isEmpty()) { return false; } QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::Image::AutoPtr image; image = Exiv2::ImageFactory::create(Exiv2::ImageType::xmp, (const char*)(QFile::encodeName(filePath).constData())); #if EXIV2_TEST_VERSION(0,27,99) return saveOperations(finfo, std::move(image)); #else return saveOperations(finfo, image); #endif } catch(Exiv2::AnyError& e) { printExiv2ExceptionError(QLatin1String("Cannot save metadata to XMP sidecar using Exiv2 "), e); return false; } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; return false; } } bool MetaEngine::Private::saveToFile(const QFileInfo& finfo) const { if (!finfo.isWritable()) { qCDebug(DIGIKAM_METAENGINE_LOG) << "File" << finfo.fileName() << "is read only. Metadata not written."; return false; } QStringList rawTiffBasedSupported, rawTiffBasedNotSupported; // Raw files supported by Exiv2 0.26 rawTiffBasedSupported << QLatin1String("cr2") << QLatin1String("crw") << QLatin1String("dng") << QLatin1String("nef") << QLatin1String("pef") << QLatin1String("orf") << QLatin1String("srw"); // Raw files not supported by Exiv2 0.26 rawTiffBasedNotSupported << QLatin1String("3fr") << QLatin1String("arw") << QLatin1String("dcr") << QLatin1String("erf") << QLatin1String("k25") << QLatin1String("kdc") << QLatin1String("mos") << QLatin1String("raf") << QLatin1String("raw") << QLatin1String("sr2") << QLatin1String("srf") << QLatin1String("rw2"); QString ext = finfo.suffix().toLower(); if (rawTiffBasedNotSupported.contains(ext) || (!writeRawFiles && rawTiffBasedSupported.contains(ext))) { qCDebug(DIGIKAM_METAENGINE_LOG) << finfo.fileName() << "is a TIFF based RAW file, " << "writing to such a file is disabled by current settings."; return false; } /* if (rawTiffBasedNotSupported.contains(ext)) { qCDebug(DIGIKAM_METAENGINE_LOG) << finfo.fileName() << "is TIFF based RAW file not yet supported. Metadata not saved."; return false; } if (rawTiffBasedSupported.contains(ext) && !writeRawFiles) { qCDebug(DIGIKAM_METAENGINE_LOG) << finfo.fileName() << "is TIFF based RAW file supported but writing mode is disabled. " << "Metadata not saved."; return false; } qCDebug(DIGIKAM_METAENGINE_LOG) << "File Extension: " << ext << " is supported for writing mode"; bool ret = false; */ QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::Image::AutoPtr image; image = Exiv2::ImageFactory::open((const char*)(QFile::encodeName(finfo.filePath()).constData())); #if EXIV2_TEST_VERSION(0,27,99) return saveOperations(finfo, std::move(image)); #else return saveOperations(finfo, image); #endif } catch(Exiv2::AnyError& e) { printExiv2ExceptionError(QLatin1String("Cannot save metadata to image using Exiv2 "), e); return false; } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; return false; } } bool MetaEngine::Private::saveOperations(const QFileInfo& finfo, Exiv2::Image::AutoPtr image) const { QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::AccessMode mode; bool wroteComment = false; bool wroteEXIF = false; bool wroteIPTC = false; bool wroteXMP = false; // We need to load target file metadata to merge with new one. It's mandatory with TIFF format: // like all tiff file structure is based on Exif. image->readMetadata(); // Image Comments --------------------------------- mode = image->checkMode(Exiv2::mdComment); if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)) { image->setComment(itemComments()); wroteComment = true; } qCDebug(DIGIKAM_METAENGINE_LOG) << "wroteComment: " << wroteComment; // Exif metadata ---------------------------------- mode = image->checkMode(Exiv2::mdExif); if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)) { if (image->mimeType() == "image/tiff") { Exiv2::ExifData orgExif = image->exifData(); Exiv2::ExifData newExif; QStringList untouchedTags; // With tiff image we cannot overwrite whole Exif data as well, because // image data are stored in Exif container. We need to take a care about // to not lost image data. untouchedTags << QLatin1String("Exif.Image.ImageWidth"); untouchedTags << QLatin1String("Exif.Image.ImageLength"); untouchedTags << QLatin1String("Exif.Image.BitsPerSample"); untouchedTags << QLatin1String("Exif.Image.Compression"); untouchedTags << QLatin1String("Exif.Image.PhotometricInterpretation"); untouchedTags << QLatin1String("Exif.Image.FillOrder"); untouchedTags << QLatin1String("Exif.Image.SamplesPerPixel"); untouchedTags << QLatin1String("Exif.Image.StripOffsets"); untouchedTags << QLatin1String("Exif.Image.RowsPerStrip"); untouchedTags << QLatin1String("Exif.Image.StripByteCounts"); untouchedTags << QLatin1String("Exif.Image.XResolution"); untouchedTags << QLatin1String("Exif.Image.YResolution"); untouchedTags << QLatin1String("Exif.Image.PlanarConfiguration"); untouchedTags << QLatin1String("Exif.Image.ResolutionUnit"); for (Exiv2::ExifData::const_iterator it = orgExif.begin() ; it != orgExif.end() ; ++it) { if (untouchedTags.contains(QLatin1String(it->key().c_str()))) { newExif[it->key().c_str()] = orgExif[it->key().c_str()]; } } Exiv2::ExifData readedExif = exifMetadata(); for (Exiv2::ExifData::const_iterator it = readedExif.begin() ; it != readedExif.end() ; ++it) { if (!untouchedTags.contains(QLatin1String(it->key().c_str()))) { newExif[it->key().c_str()] = readedExif[it->key().c_str()]; } } image->setExifData(newExif); } else { image->setExifData(exifMetadata()); } wroteEXIF = true; } qCDebug(DIGIKAM_METAENGINE_LOG) << "wroteEXIF: " << wroteEXIF; // Iptc metadata ---------------------------------- mode = image->checkMode(Exiv2::mdIptc); if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)) { image->setIptcData(iptcMetadata()); wroteIPTC = true; } qCDebug(DIGIKAM_METAENGINE_LOG) << "wroteIPTC: " << wroteIPTC; // Xmp metadata ----------------------------------- mode = image->checkMode(Exiv2::mdXmp); if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)) { #ifdef _XMP_SUPPORT_ image->setXmpData(xmpMetadata()); wroteXMP = true; #endif } qCDebug(DIGIKAM_METAENGINE_LOG) << "wroteXMP: " << wroteXMP; if (!wroteComment && !wroteEXIF && !wroteIPTC && !wroteXMP) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Writing metadata is not supported for file" << finfo.fileName(); return false; } else if (!wroteEXIF || !wroteIPTC || !wroteXMP) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Support for writing metadata is limited for file" << finfo.fileName(); } if (!updateFileTimeStamp) { // Don't touch access and modification timestamp of file. QT_STATBUF st; struct utimbuf ut; int ret = QT_STAT(QFile::encodeName(filePath).constData(), &st); if (ret == 0) { ut.modtime = st.st_mtime; ut.actime = st.st_atime; } image->writeMetadata(); if (ret == 0) { ::utime(QFile::encodeName(filePath).constData(), &ut); } qCDebug(DIGIKAM_METAENGINE_LOG) << "File time stamp restored"; } else { image->writeMetadata(); } return true; } catch(Exiv2::AnyError& e) { printExiv2ExceptionError(QLatin1String("Cannot save metadata using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } void MetaEngine::Private::printExiv2ExceptionError(const QString& msg, Exiv2::AnyError& e) { std::string s(e.what()); qCCritical(DIGIKAM_METAENGINE_LOG) << msg.toLatin1().constData() << " (Error #" << e.code() << ": " << s.c_str(); } void MetaEngine::Private::printExiv2MessageHandler(int lvl, const char* msg) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Exiv2 (" << lvl << ") : " << msg; } QString MetaEngine::Private::convertCommentValue(const Exiv2::Exifdatum& exifDatum) const { QMutexLocker lock(&s_metaEngineMutex); try { std::string comment; std::string charset; comment = exifDatum.toString(); // libexiv2 will prepend "charset=\"SomeCharset\" " if charset is specified // Before conversion to QString, we must know the charset, so we stay with std::string for a while if (comment.length() > 8 && comment.substr(0, 8) == "charset=") { // the prepended charset specification is followed by a blank std::string::size_type pos = comment.find_first_of(' '); if (pos != std::string::npos) { // extract string between the = and the blank charset = comment.substr(8, pos-8); // get the rest of the string after the charset specification comment = comment.substr(pos+1); } } if (charset == "\"Unicode\"") { return QString::fromUtf8(comment.data()); } else if (charset == "\"Jis\"") { QTextCodec* const codec = QTextCodec::codecForName("JIS7"); if (codec) { const char* tmp = comment.c_str(); if (tmp) { return codec->toUnicode(tmp); } } return QLatin1String(""); } else if (charset == "\"Ascii\"") { return QLatin1String(comment.c_str()); } else { return detectEncodingAndDecode(comment); } } catch(Exiv2::AnyError& e) { printExiv2ExceptionError(QLatin1String("Cannot convert Comment using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return QString(); } QString MetaEngine::Private::detectEncodingAndDecode(const std::string& value) const { // For charset autodetection, we could use sophisticated code // (Mozilla chardet, KHTML's autodetection, QTextCodec::codecForContent), // but that is probably too much. // We check for UTF8, Local encoding and ASCII. // Look like KEncodingDetector class can provide a full implementation for encoding detection. if (value.empty()) { return QString(); } if (isUtf8(value.c_str())) { return QString::fromUtf8(value.c_str()); } // Utf8 has a pretty unique byte pattern. // Thats not true for ASCII, it is not possible // to reliably autodetect different ISO-8859 charsets. // So we can use either local encoding, or latin1. QString localString = QString::fromLocal8Bit(value.c_str()); // To fix broken JFIF comment, replace non printable // characters from the string. See bug 39617 for (int i = 0 ; i < localString.size() ; ++i) { if (!localString.at(i).isPrint() && (localString.at(i) != QLatin1Char('\n')) && (localString.at(i) != QLatin1Char('\r'))) { localString[i] = QLatin1Char('_'); } } return localString; } bool MetaEngine::Private::isUtf8(const char* const buffer) const { int i, n; unsigned char c; bool gotone = false; if (!buffer) { return true; } // character never appears in text #define F 0 // character appears in plain ASCII text #define T 1 // character appears in ISO-8859 text #define I 2 // character appears in non-ISO extended ASCII (Mac, IBM PC) #define X 3 static const unsigned char text_chars[256] = { // BEL BS HT LF FF CR F, F, F, F, F, F, F, T, T, T, T, F, T, T, F, F, // 0x0X // ESC F, F, F, F, F, F, F, F, F, F, F, T, F, F, F, F, // 0x1X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x2X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x3X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x4X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x5X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x6X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F, // 0x7X // NEL X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, // 0x8X X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, // 0x9X I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xaX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xbX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xcX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xdX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xeX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I // 0xfX }; for (i = 0 ; (c = buffer[i]) ; ++i) { if ((c & 0x80) == 0) { // 0xxxxxxx is plain ASCII // Even if the whole file is valid UTF-8 sequences, // still reject it if it uses weird control characters. if (text_chars[c] != T) { return false; } } else if ((c & 0x40) == 0) { // 10xxxxxx never 1st byte return false; } else { // 11xxxxxx begins UTF-8 int following = 0; if ((c & 0x20) == 0) { // 110xxxxx following = 1; } else if ((c & 0x10) == 0) { // 1110xxxx following = 2; } else if ((c & 0x08) == 0) { // 11110xxx following = 3; } else if ((c & 0x04) == 0) { // 111110xx following = 4; } else if ((c & 0x02) == 0) { // 1111110x following = 5; } else { return false; } for (n = 0 ; n < following ; ++n) { i++; if (!(c = buffer[i])) { goto done; } if ((c & 0x80) == 0 || (c & 0x40)) { return false; } } gotone = true; } } done: return gotone; // don't claim it's UTF-8 if it's all 7-bit. } #undef F #undef T #undef I #undef X int MetaEngine::Private::getXMPTagsListFromPrefix(const QString& pf, MetaEngine::TagsMap& tagsMap) const { int i = 0; #ifdef _XMP_SUPPORT_ QMutexLocker lock(&s_metaEngineMutex); try { QList tags; tags << Exiv2::XmpProperties::propertyList(pf.toLatin1().data()); for (QList::iterator it = tags.begin() ; it != tags.end() ; ++it) { while ( (*it) && !QString::fromLatin1((*it)->name_).isNull() ) { QString key = QLatin1String( Exiv2::XmpKey( pf.toLatin1().data(), (*it)->name_ ).key().c_str() ); QStringList values; values << QLatin1String((*it)->name_) << QLatin1String((*it)->title_) << QLatin1String((*it)->desc_); tagsMap.insert(key, values); ++(*it); i++; } } } catch(Exiv2::AnyError& e) { printExiv2ExceptionError(QLatin1String("Cannot get Xmp tags list using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(pf); Q_UNUSED(tagsMap); #endif // _XMP_SUPPORT_ return i; } #ifdef _XMP_SUPPORT_ void MetaEngine::Private::loadSidecarData(Exiv2::Image::AutoPtr xmpsidecar) { // Having a sidecar is a special situation. // The sidecar data often "dominates", see in particular bug 309058 for important aspects: // If a field is removed from the sidecar, we must ignore (older) data for this field in the file. // First: Ignore file XMP, only use sidecar XMP xmpMetadata() = xmpsidecar->xmpData(); loadedFromSidecar = true; // EXIF // Four groups of properties are mapped between EXIF and XMP: // Date/Time, Description, Copyright, Creator // A few more tags are defined "writeback" tags in the XMP specification, the sidecar value therefore overrides the Exif value. // The rest is kept side-by-side. // (to understand, remember that the xmpsidecar's Exif data is actually XMP data mapped back to Exif) // Description, Copyright and Creator is dominated by the sidecar: Remove file Exif fields, if field not in XMP. ExifMetaEngineMergeHelper exifDominatedHelper; exifDominatedHelper << QLatin1String("Exif.Image.ImageDescription") << QLatin1String("Exif.Photo.UserComment") << QLatin1String("Exif.Image.Copyright") << QLatin1String("Exif.Image.Artist"); exifDominatedHelper.exclusiveMerge(xmpsidecar->exifData(), exifMetadata()); // Date/Time and "the few more" from the XMP spec are handled as writeback // Note that Date/Time mapping is slightly contradictory in latest specs. ExifMetaEngineMergeHelper exifWritebackHelper; exifWritebackHelper << QLatin1String("Exif.Image.DateTime") << QLatin1String("Exif.Image.DateTime") << QLatin1String("Exif.Photo.DateTimeOriginal") << QLatin1String("Exif.Photo.DateTimeDigitized") << QLatin1String("Exif.Image.Orientation") << QLatin1String("Exif.Image.XResolution") << QLatin1String("Exif.Image.YResolution") << QLatin1String("Exif.Image.ResolutionUnit") << QLatin1String("Exif.Image.Software") << QLatin1String("Exif.Photo.RelatedSoundFile"); exifWritebackHelper.mergeFields(xmpsidecar->exifData(), exifMetadata()); // IPTC // These fields cover almost all relevant IPTC data and are defined in the XMP specification for reconciliation. IptcMetaEngineMergeHelper iptcDominatedHelper; iptcDominatedHelper << QLatin1String("Iptc.Application2.ObjectName") << QLatin1String("Iptc.Application2.Urgency") << QLatin1String("Iptc.Application2.Category") << QLatin1String("Iptc.Application2.SuppCategory") << QLatin1String("Iptc.Application2.Keywords") << QLatin1String("Iptc.Application2.SubLocation") << QLatin1String("Iptc.Application2.SpecialInstructions") << QLatin1String("Iptc.Application2.Byline") << QLatin1String("Iptc.Application2.BylineTitle") << QLatin1String("Iptc.Application2.City") << QLatin1String("Iptc.Application2.ProvinceState") << QLatin1String("Iptc.Application2.CountryCode") << QLatin1String("Iptc.Application2.CountryName") << QLatin1String("Iptc.Application2.TransmissionReference") << QLatin1String("Iptc.Application2.Headline") << QLatin1String("Iptc.Application2.Credit") << QLatin1String("Iptc.Application2.Source") << QLatin1String("Iptc.Application2.Copyright") << QLatin1String("Iptc.Application2.Caption") << QLatin1String("Iptc.Application2.Writer"); iptcDominatedHelper.exclusiveMerge(xmpsidecar->iptcData(), iptcMetadata()); IptcMetaEngineMergeHelper iptcWritebackHelper; iptcWritebackHelper << QLatin1String("Iptc.Application2.DateCreated") << QLatin1String("Iptc.Application2.TimeCreated") << QLatin1String("Iptc.Application2.DigitizationDate") << QLatin1String("Iptc.Application2.DigitizationTime"); iptcWritebackHelper.mergeFields(xmpsidecar->iptcData(), iptcMetadata()); /* * TODO: Exiv2 (referring to 0.23) does not correctly synchronize all times values as given below. * Time values and their synchronization: * Original Date/Time – Creation date of the intellectual content (e.g. the photograph), rather than the creatio*n date of the content being shown Exif DateTimeOriginal (36867, 0x9003) and SubSecTimeOriginal (37521, 0x9291) IPTC DateCreated (IIM 2:55, 0x0237) and TimeCreated (IIM 2:60, 0x023C) XMP (photoshop:DateCreated) * Digitized Date/Time – Creation date of the digital representation Exif DateTimeDigitized (36868, 0x9004) and SubSecTimeDigitized (37522, 0x9292) IPTC DigitalCreationDate (IIM 2:62, 0x023E) and DigitalCreationTime (IIM 2:63, 0x023F) XMP (xmp:CreateDate) * Modification Date/Time – Modification date of the digital image file Exif DateTime (306, 0x132) and SubSecTime (37520, 0x9290) XMP (xmp:ModifyDate) */ } #endif // _XMP_SUPPORT_ } // namespace Digikam // Restore warnings #if defined(Q_CC_GNU) && !defined(Q_CC_CLANG) # pragma GCC diagnostic pop #endif #if defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif diff --git a/core/libs/metadataengine/engine/metaengine_p.h b/core/libs/metadataengine/engine/metaengine_p.h index 694a5d0487..80de21b9d1 100644 --- a/core/libs/metadataengine/engine/metaengine_p.h +++ b/core/libs/metadataengine/engine/metaengine_p.h @@ -1,198 +1,199 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Internal private container. * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * 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_META_ENGINE_PRIVATE_H #define DIGIKAM_META_ENGINE_PRIVATE_H #include "metaengine.h" // C++ includes #include #include #include #include #include #include #include #include // Qt includes #include #include #include #include #include #include // Exiv2 includes ------------------------------------------------------- // NOTE: All Exiv2 header must be stay there to not expose external source code to Exiv2 API // and reduce Exiv2 dependency to client code. #if defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif // The pragmas are required to be able to catch exceptions thrown by libexiv2: // See http://gcc.gnu.org/wiki/Visibility, the section about c++ exceptions. // They are needed for all libexiv2 versions that do not care about visibility. #ifdef Q_CC_GNU # pragma GCC visibility push(default) #endif #include #include #include #include #include #include #include #include #include #include #include #include // Check if Exiv2 support XMP #ifdef EXV_HAVE_XMP_TOOLKIT # define _XMP_SUPPORT_ 1 #endif #ifndef EXIV2_TEST_VERSION # define EXIV2_TEST_VERSION(major,minor,patch) \ ( EXIV2_VERSION >= EXIV2_MAKE_VERSION(major,minor,patch) ) #endif #if EXIV2_TEST_VERSION(0,27,99) # define AutoPtr UniquePtr #endif // With exiv2 > 0.20.0, all makernote header files have been removed to increase binary compatibility. // See Exiv2 bugzilla entry http://dev.exiv2.org/issues/719 // and wiki topic http://dev.exiv2.org/boards/3/topics/583 #ifdef Q_CC_GNU # pragma GCC visibility pop #endif // End of Exiv2 headers ------------------------------------------------------ // Local includes #include "metaengine_mergehelper.h" namespace Digikam { extern QMutex s_metaEngineMutex; // -------------------------------------------------------------------------- class Q_DECL_HIDDEN MetaEngine::Private { public: explicit Private(); virtual ~Private(); void copyPrivateData(const Private* const other); bool saveToXMPSidecar(const QFileInfo& finfo) const; bool saveToFile(const QFileInfo& finfo) const; bool saveOperations(const QFileInfo& finfo, Exiv2::Image::AutoPtr image) const; /** Wrapper method to convert a Comments content to a QString. */ QString convertCommentValue(const Exiv2::Exifdatum& exifDatum) const; /** Charset autodetection to convert a string to a QString. */ QString detectEncodingAndDecode(const std::string& value) const; /** UTF8 autodetection from a string. */ bool isUtf8(const char* const buffer) const; int getXMPTagsListFromPrefix(const QString& pf, MetaEngine::TagsMap& tagsMap) const; const Exiv2::ExifData& exifMetadata() const; const Exiv2::IptcData& iptcMetadata() const; const std::string& itemComments() const; Exiv2::ExifData& exifMetadata(); Exiv2::IptcData& iptcMetadata(); std::string& itemComments(); #ifdef _XMP_SUPPORT_ const Exiv2::XmpData& xmpMetadata() const; Exiv2::XmpData& xmpMetadata(); void loadSidecarData(Exiv2::Image::AutoPtr xmpsidecar); #endif public: /** Generic method to print the Exiv2 C++ Exception error message from 'e'. * 'msg' string is printed using qDebug rules. */ static void printExiv2ExceptionError(const QString& msg, Exiv2::AnyError& e); /** Generic method to print debug message from Exiv2. * 'msg' string is printed using qDebug rules. 'lvl' is the debug level of Exiv2 message. */ static void printExiv2MessageHandler(int lvl, const char* msg); public: bool writeRawFiles; bool updateFileTimeStamp; bool useXMPSidecar4Reading; + bool useCompatibleFileName; /// A mode from #MetadataWritingMode enum. int metadataWritingMode; /// XMP, and parts of EXIF/IPTC, were loaded from an XMP sidecar file bool loadedFromSidecar; QString filePath; QSize pixelSize; QString mimeType; QSharedDataPointer data; }; } // namespace Digikam #if defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif #endif // DIGIKAM_META_ENGINE_PRIVATE_H diff --git a/core/libs/metadataengine/engine/metaenginesettingscontainer.cpp b/core/libs/metadataengine/engine/metaenginesettingscontainer.cpp index 82a48df133..e02afdbc96 100644 --- a/core/libs/metadataengine/engine/metaenginesettingscontainer.cpp +++ b/core/libs/metadataengine/engine/metaenginesettingscontainer.cpp @@ -1,192 +1,197 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-08-20 * Description : MetaEngine Settings Container. * * Copyright (C) 2010-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 "metaenginesettingscontainer.h" // Qt includes #include // KDE includes #include // Local includes #include "metaenginesettings.h" namespace Digikam { MetaEngineSettingsContainer::MetaEngineSettingsContainer() : exifRotate(true), exifSetOrientation(true), saveComments(false), saveDateTime(false), savePickLabel(false), saveColorLabel(false), saveRating(false), saveTemplate(false), saveTags(false), saveFaceTags(false), writeRawFiles(false), updateFileTimeStamp(true), rescanImageIfModified(false), clearMetadataIfRescan(false), useXMPSidecar4Reading(false), + useCompatibleFileName(false), useLazySync(false), metadataWritingMode(MetaEngine::WRITE_TO_FILE_ONLY), rotationBehavior(RotatingFlags | RotateByLosslessRotation), sidecarExtensions(QStringList()) { } MetaEngineSettingsContainer::~MetaEngineSettingsContainer() { } void MetaEngineSettingsContainer::readFromConfig(KConfigGroup& group) { exifRotate = group.readEntry("EXIF Rotate", true); exifSetOrientation = group.readEntry("EXIF Set Orientation", true); saveTags = group.readEntry("Save Tags", false); saveTemplate = group.readEntry("Save Template", false); saveFaceTags = group.readEntry("Save FaceTags", false); saveComments = group.readEntry("Save EXIF Comments", false); saveDateTime = group.readEntry("Save Date Time", false); savePickLabel = group.readEntry("Save Pick Label", false); saveColorLabel = group.readEntry("Save Color Label", false); saveRating = group.readEntry("Save Rating", false); writeRawFiles = group.readEntry("Write Metadata To RAW Files", false); useXMPSidecar4Reading = group.readEntry("Use XMP Sidecar For Reading", false); + useCompatibleFileName = group.readEntry("Use Compatible File Name", false); metadataWritingMode = (MetaEngine::MetadataWritingMode) group.readEntry("Metadata Writing Mode", (int)MetaEngine::WRITE_TO_FILE_ONLY); updateFileTimeStamp = group.readEntry("Update File Timestamp", true); rescanImageIfModified = group.readEntry("Rescan File If Modified", false); clearMetadataIfRescan = group.readEntry("Clear Metadata If Rescan", false); useLazySync = group.readEntry("Use Lazy Synchronization", false); rotationBehavior = NoRotation; sidecarExtensions = group.readEntry("Custom Sidecar Extensions", QStringList()); if (group.readEntry("Rotate By Internal Flag", true)) { rotationBehavior |= RotateByInternalFlag; } if (group.readEntry("Rotate By Metadata Flag", true)) { rotationBehavior |= RotateByMetadataFlag; } if (group.readEntry("Rotate Contents Lossless", true)) { rotationBehavior |= RotateByLosslessRotation; } if (group.readEntry("Rotate Contents Lossy", false)) { rotationBehavior |= RotateByLossyRotation; } } void MetaEngineSettingsContainer::writeToConfig(KConfigGroup& group) const { group.writeEntry("EXIF Rotate", exifRotate); group.writeEntry("EXIF Set Orientation", exifSetOrientation); group.writeEntry("Save Tags", saveTags); group.writeEntry("Save Template", saveTemplate); group.writeEntry("Save FaceTags", saveFaceTags); group.writeEntry("Save EXIF Comments", saveComments); group.writeEntry("Save Date Time", saveDateTime); group.writeEntry("Save Pick Label", savePickLabel); group.writeEntry("Save Color Label", saveColorLabel); group.writeEntry("Save Rating", saveRating); group.writeEntry("Write Metadata To RAW Files", writeRawFiles); group.writeEntry("Use XMP Sidecar For Reading", useXMPSidecar4Reading); + group.writeEntry("Use Compatible File Name", useCompatibleFileName); group.writeEntry("Metadata Writing Mode", (int)metadataWritingMode); group.writeEntry("Update File Timestamp", updateFileTimeStamp); group.writeEntry("Rescan File If Modified", rescanImageIfModified); group.writeEntry("Clear Metadata If Rescan", clearMetadataIfRescan); group.writeEntry("Rotate By Internal Flag", bool(rotationBehavior & RotateByInternalFlag)); group.writeEntry("Rotate By Metadata Flag", bool(rotationBehavior & RotateByMetadataFlag)); group.writeEntry("Rotate Contents Lossless", bool(rotationBehavior & RotateByLosslessRotation)); group.writeEntry("Rotate Contents Lossy", bool(rotationBehavior & RotateByLossyRotation)); group.writeEntry("Use Lazy Synchronization", useLazySync); group.writeEntry("Custom Sidecar Extensions", sidecarExtensions); } QDebug operator<<(QDebug dbg, const MetaEngineSettingsContainer& inf) { dbg.nospace() << "[MetaEngineSettingsContainer] exifRotate(" << inf.exifRotate << "), "; dbg.nospace() << "exifSetOrientation(" << inf.exifSetOrientation << "), "; dbg.nospace() << "saveComments(" << inf.saveComments << "), "; dbg.nospace() << "saveDateTime(" << inf.saveDateTime << "), "; dbg.nospace() << "savePickLabel(" << inf.saveColorLabel << "), "; dbg.nospace() << "saveColorLabel(" << inf.savePickLabel << "), "; dbg.nospace() << "saveRating(" << inf.saveRating << "), "; dbg.nospace() << "saveTemplate(" << inf.saveTemplate << "), "; dbg.nospace() << "saveTags(" << inf.saveTags << "), "; dbg.nospace() << "saveFaceTags(" << inf.saveFaceTags << "), "; dbg.nospace() << "writeRawFiles(" << inf.writeRawFiles << "), "; dbg.nospace() << "updateFileTimeStamp(" << inf.updateFileTimeStamp << "), "; dbg.nospace() << "rescanImageIfModified(" << inf.rescanImageIfModified << "), "; dbg.nospace() << "clearMetadataIfRescan(" << inf.clearMetadataIfRescan << "), "; dbg.nospace() << "useXMPSidecar4Reading(" << inf.useXMPSidecar4Reading << "), "; + dbg.nospace() << "useCompatibleFileName(" + << inf.useCompatibleFileName << "), "; dbg.nospace() << "useLazySync(" << inf.useLazySync << "), "; dbg.nospace() << "metadataWritingMode(" << inf.metadataWritingMode << "), "; dbg.nospace() << "rotationBehavior(" << inf.rotationBehavior << "), "; dbg.nospace() << "sidecarExtensions(" << inf.sidecarExtensions << ")"; return dbg.space(); } } // namespace Digikam diff --git a/core/libs/metadataengine/engine/metaenginesettingscontainer.h b/core/libs/metadataengine/engine/metaenginesettingscontainer.h index d696a8857a..262e2a13fd 100644 --- a/core/libs/metadataengine/engine/metaenginesettingscontainer.h +++ b/core/libs/metadataengine/engine/metaenginesettingscontainer.h @@ -1,121 +1,122 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-08-20 * Description : MetaEngine Settings Container. * * Copyright (C) 2010-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. * * ============================================================ */ #ifndef DIGIKAM_META_ENGINE_SETTINGS_CONTAINER_H #define DIGIKAM_META_ENGINE_SETTINGS_CONTAINER_H // Qt includes #include #include // Local includes #include "digikam_export.h" #include "metaengine.h" class QStringList; class KConfigGroup; namespace Digikam { /** * The class MetaEngineSettingsContainer encapsulates all metadata related settings. * NOTE: this allows supply changed arguments to MetadataHub without changing the global settings. */ class DIGIKAM_EXPORT MetaEngineSettingsContainer { public: /** * Describes the allowed and desired operation when rotating a picture. * The modes are in escalating order and describe if an operation is allowed. * What is actually done will be governed by what is possible: * 1) RAW files cannot by rotated by content, setting the metadata may be problematic * 2) Read-Only files cannot edited, neither content nor metadata * 3) Writable files will have lossy compression * 4) Only JPEG and PGF offer lossless rotation * Using a contents-based rotation always implies resetting the flag. */ enum RotationBehaviorFlag { NoRotation = 0, RotateByInternalFlag = 1 << 0, RotateByMetadataFlag = 1 << 1, RotateByLosslessRotation = 1 << 2, RotateByLossyRotation = 1 << 3, RotatingFlags = RotateByInternalFlag | RotateByMetadataFlag, RotatingPixels = RotateByLosslessRotation | RotateByLossyRotation }; Q_DECLARE_FLAGS(RotationBehaviorFlags, RotationBehaviorFlag) public: explicit MetaEngineSettingsContainer(); ~MetaEngineSettingsContainer(); public: void readFromConfig(KConfigGroup& group); void writeToConfig(KConfigGroup& group) const; public: bool exifRotate; bool exifSetOrientation; bool saveComments; bool saveDateTime; bool savePickLabel; bool saveColorLabel; bool saveRating; bool saveTemplate; bool saveTags; bool saveFaceTags; bool writeRawFiles; bool updateFileTimeStamp; bool rescanImageIfModified; bool clearMetadataIfRescan; bool useXMPSidecar4Reading; + bool useCompatibleFileName; bool useLazySync; MetaEngine::MetadataWritingMode metadataWritingMode; RotationBehaviorFlags rotationBehavior; QStringList sidecarExtensions; }; //! qDebug() stream operator. Writes property @a inf to the debug output in a nicely formatted way. DIGIKAM_EXPORT QDebug operator<<(QDebug dbg, const MetaEngineSettingsContainer& inf); } // namespace Digikam Q_DECLARE_OPERATORS_FOR_FLAGS(Digikam::MetaEngineSettingsContainer::RotationBehaviorFlags) #endif // DIGIKAM_META_ENGINE_SETTINGS_CONTAINER_H diff --git a/core/libs/threadimageio/fileio/loadingcache.cpp b/core/libs/threadimageio/fileio/loadingcache.cpp index 015fee33a1..55c5b15fd8 100644 --- a/core/libs/threadimageio/fileio/loadingcache.cpp +++ b/core/libs/threadimageio/fileio/loadingcache.cpp @@ -1,528 +1,527 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-01-11 * Description : shared image loading and caching * * Copyright (C) 2005-2011 by Marcel Wiesweg * * 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 "loadingcache.h" // Qt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "iccsettings.h" #include "kmemoryinfo.h" #include "dmetadata.h" #include "thumbnailsize.h" namespace Digikam { class Q_DECL_HIDDEN LoadingCache::Private { public: explicit Private(LoadingCache* const q) : q(q) { // Note: Don't make the mutex recursive, we need to use a wait condition on it watch = nullptr; } void mapImageFilePath(const QString& filePath, const QString& cacheKey); void mapThumbnailFilePath(const QString& filePath, const QString& cacheKey); void cleanUpImageFilePathHash(); void cleanUpThumbnailFilePathHash(); LoadingCacheFileWatch* fileWatch() const; public: QCache imageCache; QCache thumbnailImageCache; QCache thumbnailPixmapCache; QMultiMap imageFilePathHash; QMultiMap thumbnailFilePathHash; QMap loadingDict; QMutex mutex; QWaitCondition condVar; LoadingCacheFileWatch* watch; LoadingCache* q; }; LoadingCacheFileWatch* LoadingCache::Private::fileWatch() const { // install default watch if no watch is set yet if (!watch) { q->setFileWatch(new ClassicLoadingCacheFileWatch); } return watch; } void LoadingCache::Private::mapImageFilePath(const QString& filePath, const QString& cacheKey) { if (imageFilePathHash.size() > 5*imageCache.size()) { cleanUpImageFilePathHash(); } imageFilePathHash.insert(filePath, cacheKey); } void LoadingCache::Private::mapThumbnailFilePath(const QString& filePath, const QString& cacheKey) { if (thumbnailFilePathHash.size() > 5*(thumbnailImageCache.size() + thumbnailPixmapCache.size())) { cleanUpThumbnailFilePathHash(); } thumbnailFilePathHash.insert(filePath, cacheKey); } void LoadingCache::Private::cleanUpImageFilePathHash() { // Remove all entries from hash whose value is no longer a key in the cache QSet keys = imageCache.keys().toSet(); QMultiMap::iterator it; for (it = imageFilePathHash.begin() ; it != imageFilePathHash.end() ; ) { if (!keys.contains(it.value())) { it = imageFilePathHash.erase(it); } else { ++it; } } } void LoadingCache::Private::cleanUpThumbnailFilePathHash() { QSet keys; keys += thumbnailImageCache.keys().toSet(); keys += thumbnailPixmapCache.keys().toSet(); QMultiMap::iterator it; for (it = thumbnailFilePathHash.begin() ; it != thumbnailFilePathHash.end() ; ) { if (!keys.contains(it.value())) { it = thumbnailFilePathHash.erase(it); } else { ++it; } } } LoadingCache* LoadingCache::m_instance = nullptr; LoadingCache* LoadingCache::cache() { if (!m_instance) { m_instance = new LoadingCache; } return m_instance; } void LoadingCache::cleanUp() { delete m_instance; } LoadingCache::LoadingCache() : d(new Private(this)) { KMemoryInfo memory = KMemoryInfo::currentInfo(); setCacheSize(qBound(60, int(memory.megabytes(KMemoryInfo::TotalRam)*0.05), 200)); setThumbnailCacheSize(5, 100); // the pixmap number should not be based on system memory, it's graphics memory // good place to call it here as LoadingCache is a singleton qRegisterMetaType("LoadingDescription"); qRegisterMetaType("DImg"); qRegisterMetaType("DMetadata"); connect(IccSettings::instance(), SIGNAL(settingsChanged(ICCSettingsContainer,ICCSettingsContainer)), this, SLOT(iccSettingsChanged(ICCSettingsContainer,ICCSettingsContainer))); } LoadingCache::~LoadingCache() { delete d->watch; delete d; m_instance = nullptr; } DImg* LoadingCache::retrieveImage(const QString& cacheKey) const { return d->imageCache[cacheKey]; } bool LoadingCache::putImage(const QString& cacheKey, const DImg& img, const QString& filePath) const { int cost = img.numBytes(); bool successfulyInserted = d->imageCache.insert(cacheKey, new DImg(img), cost); if (successfulyInserted && !filePath.isEmpty()) { d->mapImageFilePath(filePath, cacheKey); d->fileWatch()->addedImage(filePath); } return successfulyInserted; } void LoadingCache::removeImage(const QString& cacheKey) { d->imageCache.remove(cacheKey); } void LoadingCache::removeImages() { d->imageCache.clear(); } bool LoadingCache::isCacheable(const DImg& img) const { // return whether image fits in cache return (uint)d->imageCache.maxCost() >= img.numBytes(); } void LoadingCache::addLoadingProcess(LoadingProcess* const process) { d->loadingDict[process->cacheKey()] = process; } LoadingProcess* LoadingCache::retrieveLoadingProcess(const QString& cacheKey) const { return d->loadingDict.value(cacheKey); } void LoadingCache::removeLoadingProcess(LoadingProcess* const process) { d->loadingDict.remove(process->cacheKey()); } void LoadingCache::notifyNewLoadingProcess(LoadingProcess* const process, const LoadingDescription& description) { for (QMap::const_iterator it = d->loadingDict.constBegin() ; it != d->loadingDict.constEnd() ; ++it) { it.value()->notifyNewLoadingProcess(process, description); } } void LoadingCache::setCacheSize(int megabytes) { qCDebug(DIGIKAM_GENERAL_LOG) << "Allowing a cache size of" << megabytes << "MB"; d->imageCache.setMaxCost(megabytes * 1024 * 1024); } // --- Thumbnails ---- const QImage* LoadingCache::retrieveThumbnail(const QString& cacheKey) const { return d->thumbnailImageCache[cacheKey]; } const QPixmap* LoadingCache::retrieveThumbnailPixmap(const QString& cacheKey) const { return d->thumbnailPixmapCache[cacheKey]; } bool LoadingCache::hasThumbnailPixmap(const QString& cacheKey) const { return d->thumbnailPixmapCache.contains(cacheKey); } void LoadingCache::putThumbnail(const QString& cacheKey, const QImage& thumb, const QString& filePath) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) int cost = thumb.sizeInBytes(); #else int cost = thumb.byteCount(); #endif if (d->thumbnailImageCache.insert(cacheKey, new QImage(thumb), cost)) { d->mapThumbnailFilePath(filePath, cacheKey); d->fileWatch()->addedThumbnail(filePath); } } void LoadingCache::putThumbnail(const QString& cacheKey, const QPixmap& thumb, const QString& filePath) { int cost = thumb.width() * thumb.height() * thumb.depth() / 8; if (d->thumbnailPixmapCache.insert(cacheKey, new QPixmap(thumb), cost)) { d->mapThumbnailFilePath(filePath, cacheKey); d->fileWatch()->addedThumbnail(filePath); } } void LoadingCache::removeThumbnail(const QString& cacheKey) { d->thumbnailImageCache.remove(cacheKey); d->thumbnailPixmapCache.remove(cacheKey); } void LoadingCache::removeThumbnails() { d->thumbnailImageCache.clear(); d->thumbnailPixmapCache.clear(); } void LoadingCache::setThumbnailCacheSize(int numberOfQImages, int numberOfQPixmaps) { d->thumbnailImageCache.setMaxCost(numberOfQImages * ThumbnailSize::maxThumbsSize() * ThumbnailSize::maxThumbsSize() * 4); d->thumbnailPixmapCache.setMaxCost(numberOfQPixmaps * ThumbnailSize::maxThumbsSize() * ThumbnailSize::maxThumbsSize() * QPixmap::defaultDepth() / 8); } void LoadingCache::setFileWatch(LoadingCacheFileWatch* const watch) { delete d->watch; d->watch = watch; d->watch->m_cache = this; } +void LoadingCache::removeFromFileWatch(const QString& filePath) +{ + d->fileWatch()->removeFile(filePath); +} + QStringList LoadingCache::imageFilePathsInCache() const { d->cleanUpImageFilePathHash(); return d->imageFilePathHash.uniqueKeys(); } QStringList LoadingCache::thumbnailFilePathsInCache() const { d->cleanUpThumbnailFilePathHash(); return d->thumbnailFilePathHash.uniqueKeys(); } void LoadingCache::notifyFileChanged(const QString& filePath, bool notify) { QList keys = d->imageFilePathHash.values(filePath); foreach (const QString& cacheKey, keys) { - if (d->imageCache.remove(cacheKey) && notify) - { - emit fileChanged(filePath, cacheKey); - } + d->imageCache.remove(cacheKey); } keys = d->thumbnailFilePathHash.values(filePath); foreach (const QString& cacheKey, keys) { - bool removedImage = d->thumbnailImageCache.remove(cacheKey); - bool removedPixmap = d->thumbnailPixmapCache.remove(cacheKey); - - if ((removedImage || removedPixmap) && notify) - { - emit fileChanged(filePath, cacheKey); - } + d->thumbnailImageCache.remove(cacheKey); + d->thumbnailPixmapCache.remove(cacheKey); } if (notify) { emit fileChanged(filePath); } } void LoadingCache::iccSettingsChanged(const ICCSettingsContainer& current, const ICCSettingsContainer& previous) { if (current.enableCM != previous.enableCM || current.useManagedPreviews != previous.useManagedPreviews || current.monitorProfile != previous.monitorProfile) { LoadingCache::CacheLock lock(this); removeImages(); removeThumbnails(); } } //--------------------------------------------------------------------------------------------------- LoadingCacheFileWatch::~LoadingCacheFileWatch() { if (m_cache) { LoadingCache::CacheLock lock(m_cache); if (m_cache->d->watch == this) { m_cache->d->watch = nullptr; } } } void LoadingCacheFileWatch::notifyFileChanged(const QString& filePath) { if (m_cache) { LoadingCache::CacheLock lock(m_cache); m_cache->notifyFileChanged(filePath); } } +void LoadingCacheFileWatch::removeFile(const QString&) +{ + // default: do nothing +} + void LoadingCacheFileWatch::addedImage(const QString&) { // default: do nothing } void LoadingCacheFileWatch::addedThumbnail(const QString&) { // default: do nothing } //--------------------------------------------------------------------------------------------------- ClassicLoadingCacheFileWatch::ClassicLoadingCacheFileWatch() { if (thread() != QCoreApplication::instance()->thread()) { moveToThread(QCoreApplication::instance()->thread()); } m_watch = new QFileSystemWatcher; connect(m_watch, SIGNAL(fileChanged(QString)), this, SLOT(slotFileDirty(QString))); // Make sure the signal gets here directly from the event loop. // If putImage is called from the main thread, with CacheLock, // a deadlock would result (mutex is not recursive) connect(this, SIGNAL(signalUpdateDirWatch()), this, SLOT(slotUpdateDirWatch()), Qt::QueuedConnection); - } ClassicLoadingCacheFileWatch::~ClassicLoadingCacheFileWatch() { delete m_watch; } +void ClassicLoadingCacheFileWatch::removeFile(const QString& filePath) +{ + m_watch->removePath(filePath); +} + void ClassicLoadingCacheFileWatch::addedImage(const QString& filePath) { Q_UNUSED(filePath) // schedule update of file watch // QFileSystemWatch can only be accessed from main thread! emit signalUpdateDirWatch(); } void ClassicLoadingCacheFileWatch::addedThumbnail(const QString& filePath) { Q_UNUSED(filePath); // ignore, we do not watch thumbnails } void ClassicLoadingCacheFileWatch::slotFileDirty(const QString& path) { // Signal comes from main thread - qCDebug(DIGIKAM_GENERAL_LOG) << "LoadingCache slotFileDirty " << path; + qCDebug(DIGIKAM_GENERAL_LOG) << "LoadingCache slotFileDirty:" << path; // This method acquires a lock itself notifyFileChanged(path); // No need for locking here, we are in main thread m_watch->removePath(path); - m_watchedFiles.remove(path); } void ClassicLoadingCacheFileWatch::slotUpdateDirWatch() { // Event comes from main thread, we need to lock ourselves. LoadingCache::CacheLock lock(m_cache); // get a list of files in cache that need watch - QSet toBeAdded; - QSet toBeRemoved = m_watchedFiles; - QList filePaths = m_cache->imageFilePathsInCache(); + QStringList watchedFiles = m_watch->files(); + QList filePaths = m_cache->imageFilePathsInCache(); - foreach (const QString& m_watchPath, filePaths) + foreach (const QString& path, watchedFiles) { - if (!m_watchPath.isEmpty()) + if (!path.isEmpty()) { - if (!m_watchedFiles.contains(m_watchPath)) + if (!filePaths.contains(path)) { - toBeAdded.insert(m_watchPath); + m_watch->removePath(path); } - - toBeRemoved.remove(m_watchPath); } } - foreach (const QString& watchedItem, toBeRemoved) - { - //qCDebug(DIGIKAM_GENERAL_LOG) << "removing watch for " << *it; - m_watch->removePath(watchedItem); - m_watchedFiles.remove(watchedItem); - } - - foreach (const QString& watchedItem, toBeAdded) + foreach (const QString& path, filePaths) { - //qCDebug(DIGIKAM_GENERAL_LOG) << "adding watch for " << *it; - m_watch->addPath(watchedItem); - m_watchedFiles.insert(watchedItem); + if (!path.isEmpty()) + { + if (!watchedFiles.contains(path)) + { + m_watch->addPath(path); + } + } } } //--------------------------------------------------------------------------------------------------- LoadingCache::CacheLock::CacheLock(LoadingCache* const cache) : m_cache(cache) { m_cache->d->mutex.lock(); } LoadingCache::CacheLock::~CacheLock() { m_cache->d->mutex.unlock(); } void LoadingCache::CacheLock::wakeAll() { // obviously the mutex is locked when this function is called m_cache->d->condVar.wakeAll(); } void LoadingCache::CacheLock::timedWait() { // same as above, the mutex is certainly locked m_cache->d->condVar.wait(&m_cache->d->mutex, 1000); } } // namespace Digikam diff --git a/core/libs/threadimageio/fileio/loadingcache.h b/core/libs/threadimageio/fileio/loadingcache.h index 3dd079e6db..26cc95642c 100644 --- a/core/libs/threadimageio/fileio/loadingcache.h +++ b/core/libs/threadimageio/fileio/loadingcache.h @@ -1,313 +1,312 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-01-11 * Description : shared image loading and caching * * Copyright (C) 2005-2011 by Marcel Wiesweg * * 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_LOADING_CACHE_H #define DIGIKAM_LOADING_CACHE_H // Qt includes #include #include #include // Local includes #include "dimg.h" #include "loadsavethread.h" #include "digikam_export.h" namespace Digikam { class ICCSettingsContainer; class LoadingProcessListener { public: virtual ~LoadingProcessListener() {}; virtual bool querySendNotifyEvent() const = 0; virtual void setResult(const LoadingDescription& loadingDescription, const DImg& img) = 0; virtual LoadSaveNotifier* loadSaveNotifier() const = 0; virtual LoadSaveThread::AccessMode accessMode() = 0; }; // -------------------------------------------------------------------------------------------------------------- class LoadingProcess { public: virtual ~LoadingProcess() {}; virtual bool completed() const = 0; virtual QString filePath() const = 0; virtual QString cacheKey() const = 0; virtual void addListener(LoadingProcessListener* const listener) = 0; virtual void removeListener(LoadingProcessListener* const listener) = 0; virtual void notifyNewLoadingProcess(LoadingProcess* const process, const LoadingDescription& description) = 0; }; // -------------------------------------------------------------------------------------------------------------- class DIGIKAM_EXPORT LoadingCacheFileWatch { public: virtual ~LoadingCacheFileWatch(); /// Called by the thread when a new entry is added to the cache + virtual void removeFile(const QString& filePath); virtual void addedImage(const QString& filePath); virtual void addedThumbnail(const QString& filePath); protected: /** * Convenience method. * Call this to tell the cache to remove stored images for filePath from the cache. * Calling this method is fast, you do not need to check if the file is contained in the cache. * Do not hold the CacheLock when calling this method. */ void notifyFileChanged(const QString& filePath); protected: friend class LoadingCache; class LoadingCache* m_cache; }; // -------------------------------------------------------------------------------------------------------------- class DIGIKAM_EXPORT ClassicLoadingCacheFileWatch : public QObject, public LoadingCacheFileWatch { Q_OBJECT /** Reference implementation */ public: ClassicLoadingCacheFileWatch(); ~ClassicLoadingCacheFileWatch(); + virtual void removeFile(const QString& filePath) override; virtual void addedImage(const QString& filePath) override; virtual void addedThumbnail(const QString& filePath) override; private Q_SLOTS: void slotFileDirty(const QString& path); void slotUpdateDirWatch(); Q_SIGNALS: void signalUpdateDirWatch(); protected: QFileSystemWatcher* m_watch; - QSet m_watchedFiles; }; // -------------------------------------------------------------------------------------------------------------- class DIGIKAM_EXPORT LoadingCache : public QObject { Q_OBJECT public: static LoadingCache* cache(); static void cleanUp(); virtual ~LoadingCache(); /// !! All methods of LoadingCache shall only be called when a CacheLock is held !! class DIGIKAM_EXPORT CacheLock { public: explicit CacheLock(LoadingCache* const cache); ~CacheLock(); void wakeAll(); void timedWait(); private: LoadingCache* m_cache; }; /** * Retrieves an image for the given string from the cache, * or 0 if no image is found. */ DImg* retrieveImage(const QString& cacheKey) const; /// Returns whether the given DImg fits in the cache. bool isCacheable(const DImg& img) const; /** Put image into for given string into the cache. * Returns true if image has been put in the cache, false otherwise. * Ownership of the DImg instance is passed to the cache. * When it cannot be put in the cache it is deleted. * The third parameter specifies a file path that will be watched. * If this file changes, the object will be removed from the cache. */ bool putImage(const QString& cacheKey, const DImg& img, const QString& filePath) const; /** * Remove entries for the given cacheKey from the cache */ void removeImage(const QString& cacheKey); /** * Remove all entries from the cache */ void removeImages(); // ------- Loading process management ----------------------------------- /** * Find the loading process for given cacheKey, or 0 if not found */ LoadingProcess* retrieveLoadingProcess(const QString& cacheKey) const; /** * Add a loading process to the list. Only one loading process * for the same cache key is registered at a time. */ void addLoadingProcess(LoadingProcess* const process); /** * Remove loading process for given cache key */ void removeLoadingProcess(LoadingProcess* const process); /** * Notify all currently registered loading processes */ void notifyNewLoadingProcess(LoadingProcess* const process, const LoadingDescription& description); /** * Sets the cache size in megabytes. * The thumbnail cache is not affected and setThumbnailCacheSize takes the maximum number. */ void setCacheSize(int megabytes); // ------- Thumbnail cache ----------------------------------- /// The LoadingCache support both the caching of QImage and QPixmap objects. /// QPixmaps can only be accessed from the main thread, so the tasks cannot access this cache. /** * Retrieves a thumbnail for the given filePath from the thumbnail cache, * or a 0 if the thumbnail is not found. */ const QImage* retrieveThumbnail(const QString& cacheKey) const; const QPixmap* retrieveThumbnailPixmap(const QString& cacheKey) const; bool hasThumbnailPixmap(const QString& cacheKey) const; /** * Puts a thumbnail into the thumbnail cache. */ void putThumbnail(const QString& cacheKey, const QImage& thumb, const QString& filePath); void putThumbnail(const QString& cacheKey, const QPixmap& thumb, const QString& filePath); /** * Remove the thumbnail for the given file path from the thumbnail cache */ void removeThumbnail(const QString& cacheKey); /** * Remove all thumbnails */ void removeThumbnails(); /** * Sets the size of the thumbnail cache * @param numberOfQImages The maximum number of thumbnails of max possible size in QImage format that will be cached. If the size of the images is smaller, a larger number will be cached. * @param numberOfQPixmaps The maximum number of thumbnails of max possible size in QPixmap format that will be cached. If the size of the images is smaller, a larger number will be cached. * Note: The main cache is unaffected by this method, * and setCacheSize takes megabytes as parameter. * Note: A good caching strategy will be to set one of the numbers to 0 * Default values: (0, 100) */ void setThumbnailCacheSize(int numberOfQImages, int numberOfQPixmaps); // ------- File Watch Management ----------------------------------- /** * Sets a LoadingCacheFileWatch to watch the files contained in this cache. * Ownership of this object is transferred to the cache. */ void setFileWatch(LoadingCacheFileWatch* const watch); + /** + * Remove file from LoadingCacheFileWatch. + */ + void removeFromFileWatch(const QString& filePath); + /** * Returns a list of all possible file paths in cache. */ QStringList imageFilePathsInCache() const; QStringList thumbnailFilePathsInCache() const; /** * Remove all entries from cache that were loaded from filePath. * Emits relevant signals if notify = true. */ void notifyFileChanged(const QString& filePath, bool notify = true); Q_SIGNALS: /** * This signal is emitted when the cache is notified that a file was changed. * There is no information in this signal if the file was ever contained in the cache. * The signal may be emitted under CacheLock. Strongly consider a queued connection. */ void fileChanged(const QString& filePath); - /** - * This signal is emitted when the cache is notified that a file was changed, - * and the given cache key was removed from the cache. - * The signal may be emitted under CacheLock. Strongly consider a queued connection. - */ - void fileChanged(const QString& filePath, const QString& cacheKey); - private Q_SLOTS: void iccSettingsChanged(const ICCSettingsContainer& current, const ICCSettingsContainer& previous); private: LoadingCache(); friend class LoadingCacheFileWatch; friend class CacheLock; private: static LoadingCache* m_instance; class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_LOADING_CACHE_H diff --git a/core/libs/threadimageio/fileio/loadingcacheinterface.cpp b/core/libs/threadimageio/fileio/loadingcacheinterface.cpp index aa1e5c2c1e..b11bf1ac0a 100644 --- a/core/libs/threadimageio/fileio/loadingcacheinterface.cpp +++ b/core/libs/threadimageio/fileio/loadingcacheinterface.cpp @@ -1,92 +1,100 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-06 * Description : shared image loading and caching * * Copyright (C) 2005-2011 by Marcel Wiesweg * * 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 "loadingcacheinterface.h" // Local includes #include "loadingcache.h" namespace Digikam { void LoadingCacheInterface::initialize() { LoadingCache::cache(); } void LoadingCacheInterface::cleanUp() { LoadingCache::cleanUp(); } void LoadingCacheInterface::fileChanged(const QString& filePath, bool notify) { LoadingCache* const cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); cache->notifyFileChanged(filePath, notify); } void LoadingCacheInterface::connectToSignalFileChanged(QObject* const object, const char* slot) { LoadingCache* const cache = LoadingCache::cache(); QObject::connect(cache, SIGNAL(fileChanged(QString)), object, slot, Qt::QueuedConnection); // make it a queued connection because the signal is emitted when the CacheLock is held! } void LoadingCacheInterface::cleanCache() { LoadingCache* const cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); cache->removeImages(); } void LoadingCacheInterface::cleanThumbnailCache() { LoadingCache* const cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); cache->removeThumbnails(); } +void LoadingCacheInterface::removeFromFileWatch(const QString& filePath) +{ + LoadingCache* const cache = LoadingCache::cache(); + LoadingCache::CacheLock lock(cache); + + cache->removeFromFileWatch(filePath); +} + void LoadingCacheInterface::putImage(const QString& filePath, const DImg& img) { LoadingCache* const cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); if (cache->isCacheable(img)) { cache->putImage(filePath, img, filePath); } } void LoadingCacheInterface::setCacheOptions(int cacheSize) { LoadingCache* const cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); cache->setCacheSize(cacheSize); } } // namespace Digikam diff --git a/core/libs/threadimageio/fileio/loadingcacheinterface.h b/core/libs/threadimageio/fileio/loadingcacheinterface.h index 224de31899..a545170ff6 100644 --- a/core/libs/threadimageio/fileio/loadingcacheinterface.h +++ b/core/libs/threadimageio/fileio/loadingcacheinterface.h @@ -1,87 +1,92 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-06 * Description : shared image loading and caching * * Copyright (C) 2005-2011 by Marcel Wiesweg * * 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_LOADING_CACHE_INTERFACE_H #define DIGIKAM_LOADING_CACHE_INTERFACE_H // Qt includes #include // Local includes #include "digikam_export.h" #include "dimg.h" namespace Digikam { class DIGIKAM_EXPORT LoadingCacheInterface { public: static void initialize(); /** clean up cache at shutdown */ static void cleanUp(); /** * Remove an image from the cache * because it may have changed on disk */ static void fileChanged(const QString& filePath, bool notify = true); /** * Connect the given object/slot to the signal * void fileChanged(const QString& filePath); * which is emitted when the cache gains knowledge about a possible * change of this file on disk. */ static void connectToSignalFileChanged(QObject* const object, const char* slot); /** * remove all images from the cache * (e.g. when loading settings changed) * Does not affect thumbnails. */ static void cleanCache(); /** * Remove all thumbnails from the thumbnail cache. * Does not affect main image cache. */ static void cleanThumbnailCache(); + /** + * Remove file from LoadingCacheFileWatch. + */ + static void removeFromFileWatch(const QString& filePath); + /** add a copy of the image to cache */ static void putImage(const QString& filePath, const DImg& img); /** * Set cache size in Megabytes. * Set to 0 to disable caching. */ static void setCacheOptions(int cacheSize); }; } // namespace Digikam #endif // DIGIKAM_LOADING_CACHE_INTERFACE_H diff --git a/core/libs/threadimageio/preview/previewtask.cpp b/core/libs/threadimageio/preview/previewtask.cpp index 06d433583a..e3fa45d96a 100644 --- a/core/libs/threadimageio/preview/previewtask.cpp +++ b/core/libs/threadimageio/preview/previewtask.cpp @@ -1,584 +1,584 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-12-26 * Description : Multithreaded loader for previews * * Copyright (C) 2006-2011 by Marcel Wiesweg * 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 "previewtask.h" // Qt includes #include #include #include // Local includes #include "dimgloader.h" #include "drawdecoder.h" #include "digikam_debug.h" #include "dmetadata.h" #include "jpegutils.h" #include "metaenginesettings.h" #include "previewloadthread.h" namespace Digikam { void PreviewLoadingTask::execute() { if (m_loadingTaskStatus == LoadingTaskStatusStopping) { return; } // Check if preview is in cache first. LoadingCache* const cache = LoadingCache::cache(); { LoadingCache::CacheLock lock(cache); // find possible cached images DImg* cachedImg = nullptr; QStringList lookupKeys = m_loadingDescription.lookupCacheKeys(); // lookupCacheKeys returns "best first". Prepend the cache key to make the list "fastest first": // Scaling a full version takes longer! lookupKeys.prepend(m_loadingDescription.cacheKey()); foreach (const QString& key, lookupKeys) { if ((cachedImg = cache->retrieveImage(key))) { if (m_loadingDescription.needCheckRawDecoding()) { if (cachedImg->rawDecodingSettings() == m_loadingDescription.rawDecodingSettings) { break; } else { cachedImg = nullptr; } } else { break; } } } if (cachedImg) { // image is found in image cache, loading is successful m_img = *cachedImg; } else { // find possible running loading process m_usedProcess = nullptr; for (QStringList::const_iterator it = lookupKeys.constBegin() ; it != lookupKeys.constEnd() ; ++it) { if ((m_usedProcess = cache->retrieveLoadingProcess(*it))) { break; } } if (m_usedProcess) { // Other process is right now loading this image. // Add this task to the list of listeners and // attach this thread to the other thread, wait until loading // has finished. m_usedProcess->addListener(this); // break loop when either the loading has completed, or this task is being stopped while (m_loadingTaskStatus != LoadingTaskStatusStopping && m_usedProcess && !m_usedProcess->completed()) { lock.timedWait(); } // remove listener from process if (m_usedProcess) { m_usedProcess->removeListener(this); } // wake up the process which is waiting until all listeners have removed themselves lock.wakeAll(); // set to 0, as checked in setStatus m_usedProcess = nullptr; // m_img is now set to the result } else { // Neither in cache, nor currently loading in different thread. // Load it here and now, add this LoadingProcess to cache list. cache->addLoadingProcess(this); // Add this to the list of listeners addListener(this); // for use in setStatus m_usedProcess = this; // Notify other processes that we are now loading this image. // They might be interested - see notifyNewLoadingProcess below cache->notifyNewLoadingProcess(this, m_loadingDescription); } } } if (continueQuery(&m_img) && m_img.isNull()) { // Preview is not in cache, we will load image from file. DImg::FORMAT format = DImg::fileFormat(m_loadingDescription.filePath); m_fromRawEmbeddedPreview = false; if (format == DImg::RAW) { MetaEnginePreviews previews(m_loadingDescription.filePath); // Check original image size using Exiv2. QSize originalSize = previews.originalSize(); // If not valid, get original size from LibRaw if (!originalSize.isValid()) { DRawInfo container; if (DRawDecoder::rawFileIdentify(container, m_loadingDescription.filePath)) { originalSize = container.imageSize; } } switch (m_loadingDescription.previewParameters.previewSettings.quality) { case PreviewSettings::FastPreview: case PreviewSettings::FastButLargePreview: { // Size calculations int sizeLimit = -1; int bestSize = qMax(originalSize.width(), originalSize.height()); // for RAWs, the alternative is the half preview, so best size is already originalSize / 2 bestSize /= 2; if (m_loadingDescription.previewParameters.previewSettings.quality == PreviewSettings::FastButLargePreview) { sizeLimit = qMin(m_loadingDescription.previewParameters.size, bestSize); } if (loadExiv2Preview(previews, sizeLimit)) { break; } if (loadLibRawPreview(sizeLimit)) { break; } loadHalfSizeRaw(); break; } case PreviewSettings::HighQualityPreview: { switch (m_loadingDescription.previewParameters.previewSettings.rawLoading) { case PreviewSettings::RawPreviewAutomatic: { // If we find a preview that is larger than half size (which is what we get from half-size original data), we take it int acceptableSize = qMax(lround(originalSize.width() * 0.48), lround(originalSize.height() * 0.48)); if (loadExiv2Preview(previews, acceptableSize)) { break; } if (loadLibRawPreview(acceptableSize)) { break; } loadHalfSizeRaw(); break; } case PreviewSettings::RawPreviewFromEmbeddedPreview: { if (loadExiv2Preview(previews)) { break; } if (loadLibRawPreview()) { break; } loadHalfSizeRaw(); break; } case PreviewSettings::RawPreviewFromRawHalfSize: { loadHalfSizeRaw(); break; } } } } // So far, everything loaded QImage. Convert to DImg. convertQImageToDImg(); } else // Non-RAW images { qCDebug(DIGIKAM_GENERAL_LOG) << "Try to get preview from" << m_loadingDescription.filePath; qCDebug(DIGIKAM_GENERAL_LOG) << "Preview quality: " << m_loadingDescription.previewParameters.previewSettings.quality; bool isFast = (m_loadingDescription.previewParameters.previewSettings.quality == PreviewSettings::FastPreview); switch (m_loadingDescription.previewParameters.previewSettings.quality) { case PreviewSettings::FastPreview: case PreviewSettings::FastButLargePreview: { if (isFast && loadImagePreview(m_loadingDescription.previewParameters.size)) { convertQImageToDImg(); break; } // Set a hint to try to load a JPEG or PGF with the fast scale-before-decoding method if (isFast) { m_img.setAttribute(QLatin1String("scaledLoadingSize"), m_loadingDescription.previewParameters.size); } m_img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); break; } case PreviewSettings::HighQualityPreview: { m_img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); break; } } } if (continueQuery(&m_img) && !m_img.isNull() && MetaEngineSettings::instance()->settings().exifRotate) { LoadSaveThread::exifRotate(m_img, m_loadingDescription.filePath); } { LoadingCache::CacheLock lock(cache); // Put valid image into cache of loaded images if (continueQuery(&m_img) && !m_img.isNull()) { cache->putImage(m_loadingDescription.cacheKey(), m_img, m_loadingDescription.filePath); } // remove this from the list of loading processes in cache cache->removeLoadingProcess(this); } { LoadingCache::CacheLock lock(cache); // indicate that loading has finished so that listeners can stop waiting m_completed = true; // dispatch image to all listeners, including this for (int i = 0 ; i < m_listeners.count() ; ++i) { LoadingProcessListener* const l = m_listeners.at(i); LoadSaveNotifier* const notifier = l->loadSaveNotifier(); if (l->accessMode() == LoadSaveThread::AccessModeReadWrite) { // If a listener requested ReadWrite access, it gets a deep copy. // DImg is explicitly shared. l->setResult(m_loadingDescription, m_img.copy()); } else { l->setResult(m_loadingDescription, m_img); } if (notifier) { notifier->imageLoaded(m_loadingDescription, m_img); } } // remove myself from list of listeners removeListener(this); // wake all listeners waiting on cache condVar, so that they remove themselves lock.wakeAll(); // wait until all listeners have removed themselves while (m_listeners.count() != 0) { lock.timedWait(); } // set to 0, as checked in setStatus m_usedProcess = nullptr; } } // following the golden rule to avoid deadlocks, do this when CacheLock is not held if (continueQuery(&m_img) && !m_img.isNull()) { // The image from the cache may or may not be rotated and post processed. // exifRotate() and postProcess() will detect if work is needed. // We check before to find out if we need to provide a deep copy const bool needExifRotate = MetaEngineSettings::instance()->settings().exifRotate && !LoadSaveThread::wasExifRotated(m_img); const bool needImageScale = needToScale(); const bool needPostProcess = needsPostProcessing(); const bool needConvertToEightBit = m_loadingDescription.previewParameters.previewSettings.convertToEightBit; if (accessMode() == LoadSaveThread::AccessModeReadWrite || needExifRotate || needImageScale || needPostProcess || needConvertToEightBit) { m_img.detach(); } if (needImageScale) { QSize scaledSize = m_img.size(); scaledSize.scale(m_loadingDescription.previewParameters.size, m_loadingDescription.previewParameters.size, Qt::KeepAspectRatio); m_img = m_img.smoothScale(scaledSize.width(), scaledSize.height()); } if (needConvertToEightBit) { m_img.convertToEightBit(); } if (needExifRotate) { LoadSaveThread::exifRotate(m_img, m_loadingDescription.filePath); } if (needPostProcess) { postProcess(); } } else if (continueQuery(&m_img)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot extract preview for" << m_loadingDescription.filePath; } else { m_img = DImg(); } if (m_thread) { m_thread->taskHasFinished(); m_thread->imageLoaded(m_loadingDescription, m_img); } } bool PreviewLoadingTask::needToScale() { switch (m_loadingDescription.previewParameters.previewSettings.quality) { case PreviewSettings::FastPreview: if (m_loadingDescription.previewParameters.size > 0) { int maxSize = qMax(m_img.width(), m_img.height()); int acceptableUpperSize = lround(1.25 * (double)m_loadingDescription.previewParameters.size); return (maxSize >= acceptableUpperSize); } break; case PreviewSettings::FastButLargePreview: case PreviewSettings::HighQualityPreview: break; } return false; } // -- Exif/IPTC preview extraction using Exiv2 -------------------------------------------------------- bool PreviewLoadingTask::loadExiv2Preview(MetaEnginePreviews& previews, int sizeLimit) { if (previews.isEmpty() || !continueQuery(&m_img)) { return false; } if (sizeLimit == -1 || qMax(previews.width(), previews.height()) >= sizeLimit) { m_qimage = previews.image(); if (!m_qimage.isNull()) { m_fromRawEmbeddedPreview = true; return true; } } return false; } bool PreviewLoadingTask::loadLibRawPreview(int sizeLimit) { if (!continueQuery(&m_img)) { return false; } QImage rawPreview; DRawDecoder::loadEmbeddedPreview(rawPreview, m_loadingDescription.filePath); if (!rawPreview.isNull() && (sizeLimit == -1 || qMax(rawPreview.width(), rawPreview.height()) >= sizeLimit)) { m_qimage = rawPreview; m_fromRawEmbeddedPreview = true; return true; } return false; } bool PreviewLoadingTask::loadHalfSizeRaw() { if (!continueQuery(&m_img)) { return false; } DRawDecoder::loadHalfPreview(m_qimage, m_loadingDescription.filePath); return (!m_qimage.isNull()); } void PreviewLoadingTask::convertQImageToDImg() { if (!continueQuery(&m_img)) { return; } // convert from QImage m_img = DImg(m_qimage); DImg::FORMAT format = DImg::fileFormat(m_loadingDescription.filePath); m_img.setAttribute(QLatin1String("detectedFileFormat"), format); m_img.setAttribute(QLatin1String("originalFilePath"), m_loadingDescription.filePath); DMetadata metadata(m_loadingDescription.filePath); QSize orgSize = metadata.getPixelSize(); if (format == DImg::RAW && LoadSaveThread::infoProvider()) { orgSize = LoadSaveThread::infoProvider()->dimensionsHint(m_loadingDescription.filePath); } // Set the ratio of width and height of the // original size to the same ratio of the loaded image. // Because a half RAW preview was probably already rotated. if (format == DImg::RAW && !m_fromRawEmbeddedPreview) { if ((m_img.width() < m_img.height() && orgSize.width() > orgSize.height()) || (m_img.width() > m_img.height() && orgSize.width() < orgSize.height())) { orgSize.transpose(); } } m_img.setAttribute(QLatin1String("originalSize"), orgSize); m_img.setMetadata(metadata.data()); // mark as embedded preview (for Exif rotation) if (m_fromRawEmbeddedPreview) { m_img.setAttribute(QLatin1String("fromRawEmbeddedPreview"), true); // If we loaded the embedded preview, the Exif of the RAW indicates // the color space of the preview (see bug 195950 for NEF files) m_img.setIccProfile(metadata.getIccProfile()); } // free memory m_qimage = QImage(); } bool PreviewLoadingTask::loadImagePreview(int sizeLimit) { DMetadata metadata(m_loadingDescription.filePath); QImage previewImage; if (metadata.getItemPreview(previewImage)) { if (sizeLimit == -1 || qMax(previewImage.width(), previewImage.height()) > sizeLimit) { m_qimage = previewImage; return true; } } - + qDebug(DIGIKAM_GENERAL_LOG) << "Try to load DImg preview from:" << m_loadingDescription.filePath; DImg img; DImgLoader::LoadFlags loadFlags = DImgLoader::LoadItemInfo | DImgLoader::LoadMetadata | DImgLoader::LoadICCData | DImgLoader::LoadPreview; - if (img.load(m_loadingDescription.filePath, loadFlags, nullptr)) + if (img.load(m_loadingDescription.filePath, loadFlags, this)) { if (sizeLimit == -1 || qMax(img.width(), img.height()) > (uint)sizeLimit) { m_qimage = img.copyQImage(); return true; } } return false; } } // namespace Digikam diff --git a/core/libs/widgets/files/filesaveoptionsbox.cpp b/core/libs/widgets/files/filesaveoptionsbox.cpp index d2be7befb0..52ce81af42 100644 --- a/core/libs/widgets/files/filesaveoptionsbox.cpp +++ b/core/libs/widgets/files/filesaveoptionsbox.cpp @@ -1,281 +1,281 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-08-02 * Description : a stack of widgets to set image file save * options into image editor. * * Copyright (C) 2007-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 "filesaveoptionsbox.h" // Qt includes #include #include #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_debug.h" #include "digikam_config.h" #include "jpegsettings.h" #include "pngsettings.h" #include "tiffsettings.h" #include "pgfsettings.h" #ifdef HAVE_JASPER # include "jp2ksettings.h" #endif // HAVE_JASPER #ifdef HAVE_X265 # include "heifsettings.h" #endif // HAVE_X265 namespace Digikam { class Q_DECL_HIDDEN FileSaveOptionsBox::Private { public: explicit Private() : noneOptions(nullptr), noneGrid(nullptr), labelNone(nullptr), JPEGOptions(nullptr), PNGOptions(nullptr), TIFFOptions(nullptr), #ifdef HAVE_JASPER JPEG2000Options(nullptr), #endif // HAVE_JASPER #ifdef HAVE_X265 HEIFOptions(nullptr), #endif // HAVE_X265 PGFOptions(nullptr) { } QWidget* noneOptions; QGridLayout* noneGrid; QLabel* labelNone; JPEGSettings* JPEGOptions; PNGSettings* PNGOptions; TIFFSettings* TIFFOptions; #ifdef HAVE_JASPER JP2KSettings* JPEG2000Options; #endif // HAVE_JASPER #ifdef HAVE_X265 HEIFSettings* HEIFOptions; #endif // HAVE_X265 PGFSettings* PGFOptions; }; FileSaveOptionsBox::FileSaveOptionsBox(QWidget* const parent) : QStackedWidget(parent), d(new Private) { setAttribute(Qt::WA_DeleteOnClose); //-- NONE Settings ------------------------------------------------------ d->noneOptions = new QWidget(this); d->noneGrid = new QGridLayout(d->noneOptions); d->noneGrid->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); d->noneOptions->setLayout(d->noneGrid); d->labelNone = new QLabel(i18n("No options available"), d->noneOptions); d->noneGrid->addWidget(d->labelNone, 0, 0, 0, 1); //-- JPEG Settings ------------------------------------------------------ d->JPEGOptions = new JPEGSettings(this); //-- PNG Settings ------------------------------------------------------- d->PNGOptions = new PNGSettings(this); //-- TIFF Settings ------------------------------------------------------ d->TIFFOptions = new TIFFSettings(this); //-- JPEG 2000 Settings ------------------------------------------------- #ifdef HAVE_JASPER d->JPEG2000Options = new JP2KSettings(this); #endif // HAVE_JASPER //-- PGF Settings ------------------------------------------------- d->PGFOptions = new PGFSettings(this); //-- HEIF Settings ------------------------------------------------- #ifdef HAVE_X265 d->HEIFOptions = new HEIFSettings(this); #endif // HAVE_X265 //----------------------------------------------------------------------- insertWidget(DImg::NONE, d->noneOptions); insertWidget(DImg::JPEG, d->JPEGOptions); insertWidget(DImg::PNG, d->PNGOptions); insertWidget(DImg::TIFF, d->TIFFOptions); #ifdef HAVE_JASPER insertWidget(DImg::JP2K, d->JPEG2000Options); #endif // HAVE_JASPER insertWidget(DImg::PGF, d->PGFOptions); #ifdef HAVE_X265 insertWidget(DImg::HEIF, d->HEIFOptions); #endif // HAVE_X265 //----------------------------------------------------------------------- readSettings(); } FileSaveOptionsBox::~FileSaveOptionsBox() { delete d; } void FileSaveOptionsBox::setImageFileFormat(const QString& ext) { qCDebug(DIGIKAM_WIDGETS_LOG) << "Format selected: " << ext; setCurrentIndex(discoverFormat(ext, DImg::NONE)); } DImg::FORMAT FileSaveOptionsBox::discoverFormat(const QString& filename, DImg::FORMAT fallback) { qCDebug(DIGIKAM_WIDGETS_LOG) << "Trying to discover format based on filename '" << filename << "', fallback = " << fallback; QStringList splitParts = filename.split(QLatin1Char('.')); QString ext; if (splitParts.size() < 2) { qCDebug(DIGIKAM_WIDGETS_LOG) << "filename '" << filename << "' does not contain an extension separated by a point."; ext = filename; } else { ext = splitParts.at(splitParts.size() - 1); } ext = ext.toUpper(); DImg::FORMAT format = fallback; if (ext.contains(QLatin1String("JPEG")) || ext.contains(QLatin1String("JPG")) || ext.contains(QLatin1String("JPE"))) { format = DImg::JPEG; } else if (ext.contains(QLatin1String("PNG"))) { format = DImg::PNG; } else if (ext.contains(QLatin1String("TIFF")) || ext.contains(QLatin1String("TIF"))) { format = DImg::TIFF; } #ifdef HAVE_JASPER else if (ext.contains(QLatin1String("JP2")) || ext.contains(QLatin1String("JPX")) || ext.contains(QLatin1String("JPC")) || ext.contains(QLatin1String("PGX")) || ext.contains(QLatin1String("J2K"))) { format = DImg::JP2K; } #endif // HAVE_JASPER #ifdef HAVE_X265 - else if (ext.contains(QLatin1String("HEIC"))) + else if (ext.contains(QLatin1String("HEIC")) || ext.contains(QLatin1String("HEIF"))) { format = DImg::HEIF; } #endif // HAVE_X265 else if (ext.contains(QLatin1String("PGF"))) { format = DImg::PGF; } else { qCWarning(DIGIKAM_WIDGETS_LOG) << "Using fallback format " << fallback; } qCDebug(DIGIKAM_WIDGETS_LOG) << "Discovered format: " << format; return format; } void FileSaveOptionsBox::applySettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("ImageViewer Settings"); group.writeEntry(QLatin1String("JPEGCompression"), d->JPEGOptions->getCompressionValue()); group.writeEntry(QLatin1String("JPEGSubSampling"), d->JPEGOptions->getSubSamplingValue()); group.writeEntry(QLatin1String("PNGCompression"), d->PNGOptions->getCompressionValue()); group.writeEntry(QLatin1String("TIFFCompression"), d->TIFFOptions->getCompression()); #ifdef HAVE_JASPER group.writeEntry(QLatin1String("JPEG2000Compression"), d->JPEG2000Options->getCompressionValue()); group.writeEntry(QLatin1String("JPEG2000LossLess"), d->JPEG2000Options->getLossLessCompression()); #endif // HAVE_JASPER group.writeEntry(QLatin1String("PGFCompression"), d->PGFOptions->getCompressionValue()); group.writeEntry(QLatin1String("PGFLossLess"), d->PGFOptions->getLossLessCompression()); #ifdef HAVE_X265 group.writeEntry(QLatin1String("HEIFCompression"), d->HEIFOptions->getCompressionValue()); group.writeEntry(QLatin1String("HEIFLossLess"), d->HEIFOptions->getLossLessCompression()); #endif // HAVE_X265 config->sync(); } void FileSaveOptionsBox::readSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("ImageViewer Settings"); d->JPEGOptions->setCompressionValue( group.readEntry(QLatin1String("JPEGCompression"), 75) ); d->JPEGOptions->setSubSamplingValue( group.readEntry(QLatin1String("JPEGSubSampling"), 1) ); // Medium subsampling d->PNGOptions->setCompressionValue( group.readEntry(QLatin1String("PNGCompression"), 9) ); d->TIFFOptions->setCompression( group.readEntry(QLatin1String("TIFFCompression"), false) ); #ifdef HAVE_JASPER d->JPEG2000Options->setCompressionValue( group.readEntry(QLatin1String("JPEG2000Compression"), 75) ); d->JPEG2000Options->setLossLessCompression( group.readEntry(QLatin1String("JPEG2000LossLess"), true) ); #endif // HAVE_JASPER d->PGFOptions->setCompressionValue( group.readEntry(QLatin1String("PGFCompression"), 3) ); d->PGFOptions->setLossLessCompression( group.readEntry(QLatin1String("PGFLossLess"), true) ); #ifdef HAVE_X265 d->HEIFOptions->setCompressionValue( group.readEntry(QLatin1String("HEIFCompression"), 75) ); d->HEIFOptions->setLossLessCompression( group.readEntry(QLatin1String("HEIFLossLess"), true) ); #endif // HAVE_X265 } } // namespace Digikam diff --git a/core/showfoto/main/showfoto.cpp b/core/showfoto/main/showfoto.cpp index 54b3548e46..9adb6817ac 100644 --- a/core/showfoto/main/showfoto.cpp +++ b/core/showfoto/main/showfoto.cpp @@ -1,896 +1,897 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-11-22 * Description : stand alone digiKam image editor * * Copyright (C) 2004-2019 by Gilles Caulier * Copyright (C) 2006-2012 by Marcel Wiesweg * Copyright (C) 2009-2011 by Andi Clemens * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2005-2006 by Tom Albers * Copyright (C) 2008 by Arnd Baecker * Copyright (C) 2013-2015 by Mohamed_Anwer * * 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 "showfoto.h" #include "showfoto_p.h" namespace ShowFoto { ShowFoto::ShowFoto(const QList& urlList) : Digikam::EditorWindow(QLatin1String("Showfoto")), d(new Private) { setXMLFile(QLatin1String("showfotoui5.rc")); m_nonDestructive = false; // Show splash-screen at start up. KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); if (group.readEntry(QLatin1String("ShowSplash"), true) && !qApp->isSessionRestored()) { d->splash = new Digikam::DSplashScreen(); d->splash->show(); } // Setup loading cache and thumbnails interface. Digikam::LoadingCacheInterface::initialize(); Digikam::MetaEngineSettings::instance(); d->thumbLoadThread = new Digikam::ThumbnailLoadThread(); d->thumbLoadThread->setThumbnailSize(Digikam::ThumbnailSize::Huge); d->thumbLoadThread->setSendSurrogatePixmap(true); // Check ICC profiles repository availability if (d->splash) { d->splash->setMessage(i18n("Checking ICC repository...")); } d->validIccPath = Digikam::SetupICC::iccRepositoryIsValid(); // Populate Themes if (d->splash) { d->splash->setMessage(i18n("Loading themes...")); } Digikam::ThemeManager::instance(); // Load plugins if (d->splash) { d->splash->setMessage(i18n("Load Plugins...")); } DPluginLoader* const dpl = Digikam::DPluginLoader::instance(); dpl->init(); // -- Build the GUI ----------------------------------- setupUserArea(); setupActions(); setupStatusBar(); createGUI(xmlFile()); registerPluginsActions(); cleanupActions(); // Create tool selection view setupSelectToolsAction(); // Create context menu. setupContextMenu(); // Make signals/slots connections setupConnections(); // Disable all actions toggleActions(false); // -- Read settings -------------------------------- readSettings(); applySettings(); setAutoSaveSettings(configGroupName(), true); d->rightSideBar->loadState(); //-------------------------------------------------- d->thumbBarDock->reInitialize(); // -- Load current items --------------------------- slotDroppedUrls(urlList, false); } ShowFoto::~ShowFoto() { delete m_canvas; m_canvas = nullptr; Digikam::ThumbnailLoadThread::cleanUp(); Digikam::LoadingCacheInterface::cleanUp(); Digikam::DPluginLoader::instance()->cleanUp(); delete d->model; delete d->filterModel; delete d->thumbBar; delete d->rightSideBar; delete d->thumbLoadThread; delete d; } bool ShowFoto::queryClose() { // wait if a save operation is currently running if (!waitForSavingToComplete()) { return false; } if (!d->thumbBar->currentInfo().isNull() && !promptUserSave(d->thumbBar->currentUrl())) { return false; } saveSettings(); return true; } void ShowFoto::show() { // Remove Splashscreen. if (d->splash) { d->splash->finish(this); delete d->splash; d->splash = nullptr; } // Display application window. QMainWindow::show(); // Report errors from ICC repository path. KSharedConfig::Ptr config = KSharedConfig::openConfig(); if (!d->validIccPath) { QString message = i18n("

The ICC profile path seems to be invalid.

" "

If you want to set it now, select \"Yes\", otherwise " "select \"No\". In this case, \"Color Management\" feature " "will be disabled until you solve this issue

"); if (QMessageBox::warning(this, qApp->applicationName(), message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { if (!setup(true)) { KConfigGroup group = config->group(QLatin1String("Color Management")); group.writeEntry(QLatin1String("EnableCM"), false); config->sync(); } } else { KConfigGroup group = config->group(QLatin1String("Color Management")); group.writeEntry(QLatin1String("EnableCM"), false); config->sync(); } } } void ShowFoto::slotOpenFile() { if (!d->thumbBar->currentInfo().isNull() && !promptUserSave(d->thumbBar->currentUrl())) { return; } QList urls = Digikam::ImageDialog::getImageURLs(this, d->lastOpenedDirectory); if (urls.count() > 1) { d->infoList.clear(); d->model->clearShowfotoItemInfos(); openUrls(urls); emit signalInfoList(d->infoList); slotOpenUrl(d->thumbBar->currentInfo()); toggleNavigation(1); } else if (urls.count() == 1) { d->infoList.clear(); d->model->clearShowfotoItemInfos(); openFolder(urls.first().adjusted(QUrl::RemoveFilename)); emit signalInfoList(d->infoList); slotOpenUrl(d->thumbBar->findItemByUrl(urls.first())); d->thumbBar->setCurrentUrl(urls.first()); } } void ShowFoto::slotOpenFolder() { if (!d->thumbBar->currentInfo().isNull() && !promptUserSave(d->thumbBar->currentUrl())) { return; } QUrl url = DFileDialog::getExistingDirectoryUrl(this, i18n("Open Images From Folder"), d->lastOpenedDirectory); if (!url.isEmpty()) { d->infoList.clear(); d->model->clearShowfotoItemInfos(); openFolder(url); emit signalInfoList(d->infoList); slotOpenUrl(d->thumbBar->currentInfo()); toggleNavigation(1); } } void ShowFoto::openUrls(const QList &urls) { if (urls.isEmpty()) { return; } ShowfotoItemInfo iteminfo; DMetadata meta; for (QList::const_iterator it = urls.constBegin() ; it != urls.constEnd() ; ++it) { QFileInfo fi((*it).toLocalFile()); iteminfo.name = fi.fileName(); iteminfo.mime = fi.suffix(); iteminfo.size = fi.size(); iteminfo.folder = fi.path(); iteminfo.url = QUrl::fromLocalFile(fi.filePath()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) iteminfo.dtime = fi.birthTime(); #else iteminfo.dtime = fi.created(); #endif meta.load(fi.filePath()); iteminfo.ctime = meta.getItemDateTime(); iteminfo.width = meta.getItemDimensions().width(); iteminfo.height = meta.getItemDimensions().height(); iteminfo.photoInfo = meta.getPhotographInformation(); if (!d->infoList.contains(iteminfo)) d->infoList << iteminfo; } } void ShowFoto::openFolder(const QUrl& url) { if (!url.isValid() || !url.isLocalFile()) { return; } d->lastOpenedDirectory = url; // Parse image IO mime types registration to get files filter pattern. QString filter; QStringList mimeTypes = supportedImageMimeTypes(QIODevice::ReadOnly, filter); QString patterns = filter.toLower(); patterns.append (QLatin1Char(' ')); patterns.append (filter.toUpper()); qCDebug(DIGIKAM_SHOWFOTO_LOG) << "patterns=" << patterns; // Get all image files from directory. QDir dir(url.toLocalFile(), patterns); dir.setFilter(QDir::Files); if (!dir.exists()) { return; } QApplication::setOverrideCursor(Qt::WaitCursor); QFileInfoList fileinfolist = dir.entryInfoList(); if (fileinfolist.isEmpty()) { QApplication::restoreOverrideCursor(); return; } QFileInfoList::const_iterator fi; ShowfotoItemInfo iteminfo; DMetadata meta; // And open all items in image editor. for (fi = fileinfolist.constBegin() ; fi != fileinfolist.constEnd() ; ++fi) { iteminfo.name = (*fi).fileName(); iteminfo.mime = (*fi).suffix(); iteminfo.size = (*fi).size(); iteminfo.folder = (*fi).path(); iteminfo.url = QUrl::fromLocalFile((*fi).filePath()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) iteminfo.dtime = (*fi).birthTime(); #else iteminfo.dtime = (*fi).created(); #endif meta.load((*fi).filePath()); iteminfo.ctime = meta.getItemDateTime(); iteminfo.width = meta.getItemDimensions().width(); iteminfo.height = meta.getItemDimensions().height(); iteminfo.photoInfo = meta.getPhotographInformation(); if (!d->infoList.contains(iteminfo)) d->infoList << iteminfo; } QApplication::restoreOverrideCursor(); } void ShowFoto::slotDroppedUrls(const QList& droppedUrls, bool dropped) { if (droppedUrls.isEmpty()) { return; } QList imagesUrls; QList foldersUrls; foreach (const QUrl& drop, droppedUrls) { if (drop.isValid()) { QFileInfo info(drop.toLocalFile()); QString ext(info.suffix().toUpper()); QUrl url(QUrl::fromLocalFile(info.canonicalFilePath())); // Add extra check of the image extensions that are still // unknown in older Qt versions or have an application mime type. if (QMimeDatabase().mimeTypeForUrl(url).name().startsWith(QLatin1String("image/")) || ext == QLatin1String("HEIC") || + ext == QLatin1String("HEIF") || ext == QLatin1String("KRA")) { imagesUrls << url; } if (info.isDir()) { foldersUrls << url; } } } if (!imagesUrls.isEmpty()) { openUrls(imagesUrls); } if (!foldersUrls.isEmpty()) { foreach (const QUrl& fUrl, foldersUrls) { openFolder(fUrl); } } if (!d->infoList.isEmpty()) { if (!dropped && foldersUrls.isEmpty() && imagesUrls.count() == 1) { openFolder(imagesUrls.first().adjusted(QUrl::RemoveFilename)); d->model->clearShowfotoItemInfos(); emit signalInfoList(d->infoList); slotOpenUrl(d->thumbBar->findItemByUrl(imagesUrls.first())); d->thumbBar->setCurrentUrl(imagesUrls.first()); return; } d->model->clearShowfotoItemInfos(); emit signalInfoList(d->infoList); slotOpenUrl(d->thumbBar->currentInfo()); } else { QMessageBox::information(this, qApp->applicationName(), i18n("There is no dropped item to process.")); qCWarning(DIGIKAM_SHOWFOTO_LOG) << "infolist is empty.."; } } void ShowFoto::slotOpenUrl(const ShowfotoItemInfo& info) { if (d->thumbBar->currentInfo().isNull()) { return; } QString localFile; if (info.url.isLocalFile()) { // file protocol. We do not need the network localFile = info.url.toLocalFile(); } else { QMessageBox::critical(this, i18n("Error Loading File"), i18n("Failed to load file: %1\n" "Remote file handling is not supported", info.url.fileName())); return; } d->currentLoadedUrl = info.url; m_canvas->load(localFile, m_IOFileSettings); //TODO : add preload here like in ImageWindow::slotLoadCurrent() ??? } void ShowFoto::slotShowfotoItemInfoActivated(const ShowfotoItemInfo& info) { if (!d->thumbBar->currentInfo().isNull() && !promptUserSave(d->currentLoadedUrl)) { d->thumbBar->setCurrentUrl(d->currentLoadedUrl); return; } slotOpenUrl(info); } Digikam::ThumbBarDock* ShowFoto::thumbBar() const { return d->thumbBarDock; } Digikam::Sidebar* ShowFoto::rightSideBar() const { return (dynamic_cast(d->rightSideBar)); } void ShowFoto::slotChanged() { QString mpixels; QSize dims(m_canvas->imageWidth(), m_canvas->imageHeight()); mpixels.setNum(dims.width()*dims.height()/1000000.0, 'f', 2); QString str = (!dims.isValid()) ? i18nc("unknown image dimensions", "Unknown") : i18nc("%1 width, %2 height, %3 mpixels", "%1x%2 (%3Mpx)", dims.width(),dims.height(),mpixels); m_resLabel->setAdjustedText(str); if (!d->thumbBar->currentInfo().isNull()) { if (d->thumbBar->currentUrl().isValid()) { QRect sel = m_canvas->getSelectedArea(); Digikam::DImg* const img = m_canvas->interface()->getImg(); d->rightSideBar->itemChanged(d->thumbBar->currentUrl(), sel, img); } } } void ShowFoto::slotUpdateItemInfo() { d->itemsNb = d->thumbBar->showfotoItemInfos().size(); int index = 0; QString text; if (d->itemsNb > 0) { index = 1; for (int i = 0 ; i < d->itemsNb ; ++i) { QUrl url = d->thumbBar->showfotoItemInfos().at(i).url; if (url.matches(d->thumbBar->currentUrl(), QUrl::None)) { break; } ++index; } text = i18nc(" ( of )", "%1 (%2 of %3)", d->thumbBar->currentInfo().name, index, d->itemsNb); setCaption(QDir::toNativeSeparators(d->thumbBar->currentUrl() .adjusted(QUrl::RemoveFilename).toLocalFile())); } else { text = QLatin1String(""); setCaption(QLatin1String("")); } m_nameLabel->setText(text); toggleNavigation(index); } void ShowFoto::slotFirst() { if (!d->thumbBar->currentInfo().isNull() && !promptUserSave(d->thumbBar->currentUrl())) { return; } d->thumbBar->toFirstIndex(); d->thumbBar->setCurrentInfo(d->thumbBar->showfotoItemInfos().first()); slotOpenUrl(d->thumbBar->showfotoItemInfos().first()); } void ShowFoto::slotLast() { if (!d->thumbBar->currentInfo().isNull() && !promptUserSave(d->thumbBar->currentUrl())) { return; } d->thumbBar->toLastIndex(); d->thumbBar->setCurrentInfo(d->thumbBar->showfotoItemInfos().last()); slotOpenUrl(d->thumbBar->showfotoItemInfos().last()); } void ShowFoto::slotForward() { if (!d->thumbBar->currentInfo().isNull() && !promptUserSave(d->thumbBar->currentUrl())) { return; } bool currentIsNull = d->thumbBar->currentInfo().isNull(); if (!currentIsNull) { d->thumbBar->toNextIndex(); slotOpenUrl(d->thumbBar->currentInfo()); } } void ShowFoto::slotBackward() { if (!d->thumbBar->currentInfo().isNull() && !promptUserSave(d->thumbBar->currentUrl())) { return; } bool currentIsNull = d->thumbBar->currentInfo().isNull(); if (!currentIsNull) { d->thumbBar->toPreviousIndex(); slotOpenUrl(d->thumbBar->currentInfo()); } } void ShowFoto::slotPrepareToLoad() { Digikam::EditorWindow::slotPrepareToLoad(); // Here we enable specific actions on showfoto. d->openFilesInFolderAction->setEnabled(true); d->fileOpenAction->setEnabled(true); } void ShowFoto::slotLoadingStarted(const QString& filename) { Digikam::EditorWindow::slotLoadingStarted(filename); // Here we disable specific actions on showfoto. d->openFilesInFolderAction->setEnabled(false); d->fileOpenAction->setEnabled(false); } void ShowFoto::slotLoadingFinished(const QString& filename, bool success) { Digikam::EditorWindow::slotLoadingFinished(filename, success); // Here we re-enable specific actions on showfoto. d->openFilesInFolderAction->setEnabled(true); d->fileOpenAction->setEnabled(true); } void ShowFoto::slotSavingStarted(const QString& filename) { Digikam::EditorWindow::slotSavingStarted(filename); // Here we disable specific actions on showfoto. d->openFilesInFolderAction->setEnabled(false); d->fileOpenAction->setEnabled(false); } void ShowFoto::moveFile() { /* * moveFile() -> moveLocalFile() -> movingSaveFileFinished() * | | * finishSaving(true) save...IsComplete() */ qCDebug(DIGIKAM_SHOWFOTO_LOG) << m_savingContext.destinationURL << m_savingContext.destinationURL.isLocalFile(); if (m_savingContext.destinationURL.isLocalFile()) { qCDebug(DIGIKAM_SHOWFOTO_LOG) << "moving a local file"; EditorWindow::moveFile(); } else { QMessageBox::critical(this, i18n("Error Saving File"), i18n("Failed to save file: %1", i18n("Remote file handling is not supported"))); } } void ShowFoto::finishSaving(bool success) { Digikam::EditorWindow::finishSaving(success); // Here we re-enable specific actions on showfoto. d->openFilesInFolderAction->setEnabled(true); d->fileOpenAction->setEnabled(true); } void ShowFoto::saveIsComplete() { Digikam::LoadingCacheInterface::putImage(m_savingContext.destinationURL.toLocalFile(), m_canvas->currentImage()); //d->thumbBar->invalidateThumb(d->currentItem); // Pop-up a message to bring user when save is done. Digikam::DNotificationWrapper(QLatin1String("editorsavefilecompleted"), i18n("Image saved successfully"), this, windowTitle()); resetOrigin(); } void ShowFoto::saveAsIsComplete() { resetOriginSwitchFile(); /* Digikam::LoadingCacheInterface::putImage(m_savingContext.destinationURL.toLocalFile(), m_canvas->currentImage()); // Add the file to the list of thumbbar images if it's not there already Digikam::ThumbBarItem* foundItem = d->thumbBar->findItemByUrl(m_savingContext.destinationURL); d->thumbBar->invalidateThumb(foundItem); qCDebug(DIGIKAM_SHOWFOTO_LOG) << wantedUrls; if (!foundItem) { foundItem = new Digikam::ThumbBarItem(d->thumbBar, m_savingContext.destinationURL); } // shortcut slotOpenUrl d->thumbBar->blockSignals(true); d->thumbBar->setSelected(foundItem); d->thumbBar->blockSignals(false); d->currentItem = foundItem; slotUpdateItemInfo(); // Pop-up a message to bring user when save is done. Digikam::DNotificationWrapper("editorsavefilecompleted", i18n("Image saved successfully"), this, windowTitle()); */ } void ShowFoto::saveVersionIsComplete() { } QUrl ShowFoto::saveDestinationUrl() { if (d->thumbBar->currentInfo().isNull()) { qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot return the url of the image to save " << "because no image is selected."; return QUrl(); } return d->thumbBar->currentUrl(); } bool ShowFoto::save() { if (d->thumbBar->currentInfo().isNull()) { qCWarning(DIGIKAM_GENERAL_LOG) << "This should not happen"; return true; } startingSave(d->currentLoadedUrl); return true; } bool ShowFoto::saveAs() { if (d->thumbBar->currentInfo().isNull()) { qCWarning(DIGIKAM_GENERAL_LOG) << "This should not happen"; return false; } return (startingSaveAs(d->currentLoadedUrl)); } void ShowFoto::slotDeleteCurrentItem() { QUrl urlCurrent(d->thumbBar->currentUrl()); QString warnMsg(i18n("About to delete file \"%1\"\nAre you sure?", urlCurrent.fileName())); if (QMessageBox::warning(this, qApp->applicationName(), warnMsg, QMessageBox::Apply | QMessageBox::Abort) != QMessageBox::Apply) { return; } else { bool ret = QFile::remove(urlCurrent.toLocalFile()); if (!ret) { QMessageBox::critical(this, qApp->applicationName(), i18n("Cannot delete \"%1\"", urlCurrent.fileName())); return; } // No error, remove item in thumbbar. d->model->removeIndex(d->thumbBar->currentIndex()); // Disable menu actions and SideBar if no current image. d->itemsNb = d->thumbBar->showfotoItemInfos().size(); if (d->itemsNb == 0) { slotUpdateItemInfo(); toggleActions(false); m_canvas->load(QString(), m_IOFileSettings); emit signalNoCurrentItem(); } else { // If there is an image after the deleted one, make that selected. slotOpenUrl(d->thumbBar->currentInfo()); } } } void ShowFoto::slotRevert() { if (!promptUserSave(d->thumbBar->currentUrl())) { return; } m_canvas->slotRestore(); } bool ShowFoto::saveNewVersion() { return false; } bool ShowFoto::saveCurrentVersion() { return false; } bool ShowFoto::saveNewVersionAs() { return false; } bool ShowFoto::saveNewVersionInFormat(const QString&) { return false; } void ShowFoto::slotSetupMetadataFilters(int tab) { Setup::execMetadataFilters(this, tab+1); } void ShowFoto::slotAddedDropedItems(QDropEvent* e) { QList list = e->mimeData()->urls(); QList urls; foreach (const QUrl& url, list) { QFileInfo fi(url.toLocalFile()); if (fi.exists()) { urls.append(url); } } e->accept(); if (!urls.isEmpty()) { slotDroppedUrls(urls, true); } } void ShowFoto::slotFileWithDefaultApplication() { Digikam::DFileOperations::openFilesWithDefaultApplication(QList() << d->thumbBar->currentUrl()); } void ShowFoto::slotOpenWith(QAction* action) { openWith(d->thumbBar->currentUrl(), action); } DInfoInterface* ShowFoto::infoIface(DPluginAction* const) { DMetaInfoIface* const iface = new DMetaInfoIface(this, d->thumbBar->urls()); connect(iface, SIGNAL(signalItemChanged(QUrl)), this, SLOT(slotChanged())); connect(iface, SIGNAL(signalImportedImage(QUrl)), this, SLOT(slotImportedImagefromScanner(QUrl))); return iface; } } // namespace ShowFoto #include "moc_showfoto.cpp" diff --git a/core/utilities/setup/metadata/setupmetadata.cpp b/core/utilities/setup/metadata/setupmetadata.cpp index c7cbd39275..5c9c1cb41f 100644 --- a/core/utilities/setup/metadata/setupmetadata.cpp +++ b/core/utilities/setup/metadata/setupmetadata.cpp @@ -1,898 +1,909 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2003-08-03 * Description : setup Metadata tab. * * Copyright (C) 2003-2004 by Ralf Holzer * Copyright (C) 2003-2019 by Gilles Caulier * Copyright (C) 2009-2012 by Marcel Wiesweg * Copyright (C) 2017 by Simon Frei * * 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 "setupmetadata.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "advancedmetadatatab.h" #include "applicationsettings.h" #include "dactivelabel.h" #include "digikam_config.h" #include "digikam_debug.h" #include "metaengine.h" #include "metadatapanel.h" #include "metaenginesettings.h" #include "setuputils.h" namespace Digikam { class Q_DECL_HIDDEN SetupMetadata::Private { public: explicit Private() : exifAutoRotateOriginal(false), exifAutoRotateShowedInfo(false), clearMetadataShowedInfo(false), fieldsGroup(nullptr), readWriteGroup(nullptr), rotationGroup(nullptr), rotationAdvGroup(nullptr), saveTagsBox(nullptr), saveCommentsBox(nullptr), saveRatingBox(nullptr), savePickLabelBox(nullptr), saveColorLabelBox(nullptr), saveDateTimeBox(nullptr), saveTemplateBox(nullptr), saveFaceTags(nullptr), useLazySync(nullptr), writeRawFilesBox(nullptr), writeXMPSidecarBox(nullptr), readXMPSidecarBox(nullptr), + sidecarFileNameBox(nullptr), updateFileTimeStampBox(nullptr), rescanImageIfModifiedBox(nullptr), clearMetadataIfRescanBox(nullptr), writingModeCombo(nullptr), rotateByFlag(nullptr), rotateByContents(nullptr), allowRotateByMetadata(nullptr), allowLossyRotate(nullptr), exifRotateBox(nullptr), exifSetOrientationBox(nullptr), saveToBalooBox(nullptr), readFromBalooBox(nullptr), tab(nullptr), displaySubTab(nullptr), tagsCfgPanel(nullptr), advTab(nullptr), extensionsEdit(nullptr) { } bool exifAutoRotateOriginal; bool exifAutoRotateShowedInfo; bool clearMetadataShowedInfo; QGroupBox* fieldsGroup; QGroupBox* readWriteGroup; QGroupBox* rotationGroup; QGroupBox* rotationAdvGroup; QCheckBox* saveTagsBox; QCheckBox* saveCommentsBox; QCheckBox* saveRatingBox; QCheckBox* savePickLabelBox; QCheckBox* saveColorLabelBox; QCheckBox* saveDateTimeBox; QCheckBox* saveTemplateBox; QCheckBox* saveFaceTags; QCheckBox* useLazySync; QCheckBox* writeRawFilesBox; QCheckBox* writeXMPSidecarBox; QCheckBox* readXMPSidecarBox; + QCheckBox* sidecarFileNameBox; QCheckBox* updateFileTimeStampBox; QCheckBox* rescanImageIfModifiedBox; QCheckBox* clearMetadataIfRescanBox; QComboBox* writingModeCombo; QRadioButton* rotateByFlag; QRadioButton* rotateByContents; QCheckBox* allowRotateByMetadata; QCheckBox* allowLossyRotate; QCheckBox* exifRotateBox; QCheckBox* exifSetOrientationBox; QCheckBox* saveToBalooBox; QCheckBox* readFromBalooBox; QTabWidget* tab; QTabWidget* displaySubTab; MetadataPanel* tagsCfgPanel; AdvancedMetadataTab* advTab; QLineEdit* extensionsEdit; }; SetupMetadata::SetupMetadata(QWidget* const parent) : QScrollArea(parent), d(new Private) { d->tab = new QTabWidget(viewport()); setWidget(d->tab); setWidgetResizable(true); QWidget* const panel = new QWidget; QVBoxLayout* const mainLayout = new QVBoxLayout; // -------------------------------------------------------- d->fieldsGroup = new QGroupBox; QGridLayout* const fieldsLayout = new QGridLayout; d->fieldsGroup->setWhatsThis(xi18nc("@info:whatsthis", "In addition to the pixel content, image files usually " "contain a variety of metadata. A lot of the parameters you can use " "in digiKam to manage files, such as rating or comment, can be written " "to the files' metadata. " "Storing in metadata allows one to preserve this information " "when moving or sending the files to different systems.")); QLabel* const fieldsIconLabel = new QLabel; fieldsIconLabel->setPixmap(QIcon::fromTheme(QLatin1String("format-list-unordered")).pixmap(32)); QLabel* const fieldsLabel = new QLabel(i18nc("@label", "Write This Information to the Metadata")); d->saveTagsBox = new QCheckBox; d->saveTagsBox->setText(i18nc("@option:check", "Image tags")); d->saveTagsBox->setWhatsThis(i18nc("@info:whatsthis", "Turn on this option to store the item tags " "in the XMP and IPTC tags.")); d->saveCommentsBox = new QCheckBox; d->saveCommentsBox->setText(i18nc("@option:check", "Captions and title")); d->saveCommentsBox->setWhatsThis(i18nc("@info:whatsthis", "Turn on this option to store item caption and title " "in the JFIF Comment section, the EXIF tag, the XMP tag, " "and the IPTC tag.")); d->saveRatingBox = new QCheckBox; d->saveRatingBox->setText(i18nc("@option:check", "Rating")); d->saveRatingBox->setWhatsThis(i18nc("@info:whatsthis", "Turn on this option to store the item rating " "in the EXIF tag and the XMP tags.")); d->savePickLabelBox = new QCheckBox; d->savePickLabelBox->setText(i18nc("@option:check", "Pick label")); d->savePickLabelBox->setWhatsThis(i18nc("@info:whatsthis", "Turn on this option to store the item pick label " "in the XMP tags.")); d->saveColorLabelBox = new QCheckBox; d->saveColorLabelBox->setText(i18nc("@option:check", "Color label")); d->saveColorLabelBox->setWhatsThis(i18nc("@info:whatsthis", "Turn on this option to store the item color label " "in the XMP tags.")); d->saveDateTimeBox = new QCheckBox; d->saveDateTimeBox->setText(i18nc("@option:check", "Timestamps")); d->saveDateTimeBox->setWhatsThis(i18nc("@info:whatsthis", "Turn on this option to store the item date and time " "in the EXIF, XMP, and IPTC tags.")); d->saveTemplateBox = new QCheckBox; d->saveTemplateBox->setText(i18nc("@option:check", "Metadata templates (Copyright etc.)")); d->saveTemplateBox->setWhatsThis(i18nc("@info:whatsthis", "Turn on this option to store the metadata " "template in the XMP and the IPTC tags. " "You can set template values to Template setup page.")); d->saveFaceTags = new QCheckBox; d->saveFaceTags->setText(i18nc("@option:check", "Face Tags (including face areas)")); d->saveFaceTags->setWhatsThis(i18nc("@info:whatsthis", "Turn on this option to store face tags " "with face rectangles in the XMP tags.")); fieldsLayout->addWidget(fieldsIconLabel, 0, 0, 2, 3); fieldsLayout->addWidget(fieldsLabel, 0, 1, 2, 3); fieldsLayout->addWidget(d->saveTagsBox, 2, 0, 1, 3); fieldsLayout->addWidget(d->saveCommentsBox, 3, 0, 1, 3); fieldsLayout->addWidget(d->saveRatingBox, 4, 0, 1, 3); fieldsLayout->addWidget(d->savePickLabelBox, 5, 0, 1, 3); fieldsLayout->addWidget(d->saveColorLabelBox, 6, 0, 1, 3); fieldsLayout->addWidget(d->saveDateTimeBox, 7, 0, 1, 3); fieldsLayout->addWidget(d->saveTemplateBox, 8, 0, 1, 3); fieldsLayout->addWidget(d->saveFaceTags, 9 ,0, 1, 3); fieldsLayout->setColumnStretch(3, 10); d->fieldsGroup->setLayout(fieldsLayout); // -------------------------------------------------------- d->readWriteGroup = new QGroupBox; QGridLayout* const readWriteLayout = new QGridLayout; QLabel* const readWriteIconLabel = new QLabel; readWriteIconLabel->setPixmap(QIcon::fromTheme(QLatin1String("document-open")).pixmap(32)); QLabel* const readWriteLabel = new QLabel(i18nc("@label", "Reading and Writing Metadata")); d->useLazySync = new QCheckBox; d->useLazySync->setText(i18nc("@option:check", "Use lazy synchronization")); d->useLazySync->setWhatsThis(i18nc("@info:whatsthis", "Instead of synchronizing metadata, just schedule it for synchronization." "Synchronization can be done later by triggering the apply pending, or at digikam exit")); d->writeRawFilesBox = new QCheckBox; d->writeRawFilesBox->setText(i18nc("@option:check", "If possible write Metadata to RAW files (experimental)")); d->writeRawFilesBox->setWhatsThis(i18nc("@info:whatsthis", "Turn on this option to write metadata into RAW TIFF/EP files. " "This feature requires the Exiv2 shared library, version >= 0.18.0. It is still " "experimental, and is disabled by default.")); d->writeRawFilesBox->setEnabled(MetaEngine::supportMetadataWritting(QLatin1String("image/x-raw"))); d->updateFileTimeStampBox = new QCheckBox; d->updateFileTimeStampBox->setText(i18nc("@option:check", "&Update file modification timestamp when files are modified")); d->updateFileTimeStampBox->setWhatsThis(i18nc("@info:whatsthis", "Turn off this option to not update file timestamps when files are changed as " "when you update metadata or image data. Note: disabling this option can " "introduce some dysfunctions with applications which use file timestamps " "properties to detect file modifications automatically.")); d->rescanImageIfModifiedBox = new QCheckBox; d->rescanImageIfModifiedBox->setText(i18nc("@option:check", "&Rescan file when files are modified")); d->rescanImageIfModifiedBox->setWhatsThis(i18nc("@info:whatsthis", "Turning this option on, will force digiKam to rescan files that has been " "modified outside digiKam. If a file has changed it is file size or if " "the last modified timestamp has changed, a rescan of that " "file will be performed when digiKam starts.")); d->clearMetadataIfRescanBox = new QCheckBox; d->clearMetadataIfRescanBox->setText(i18nc("@option:check", "&Clean up the metadata from the database when rescan files")); d->clearMetadataIfRescanBox->setWhatsThis(i18nc("@info:whatsthis", "Turning this option on, will force digiKam to delete the file metadata " "contained in the database before the file is rescanned. WARNING: " "if your metadata has been written to the database only and not " "to the file or sidecar, you will be able to lose inserted " "metadata such as tags, keywords, or geographic coordinates.")); readWriteLayout->addWidget(readWriteIconLabel, 0, 0, 2, 3); readWriteLayout->addWidget(readWriteLabel, 0, 1, 2, 3); readWriteLayout->addWidget(d->useLazySync, 2, 0, 1, 3); readWriteLayout->addWidget(d->writeRawFilesBox, 3, 0, 1, 3); readWriteLayout->addWidget(d->updateFileTimeStampBox, 4, 0, 1, 3); readWriteLayout->addWidget(d->rescanImageIfModifiedBox, 5, 0, 1, 3); readWriteLayout->addWidget(d->clearMetadataIfRescanBox, 6, 0, 1, 3); readWriteLayout->setColumnStretch(3, 10); d->readWriteGroup->setLayout(readWriteLayout); // -------------------------------------------------------- QFrame* const infoBox = new QFrame; QGridLayout* const infoBoxGrid = new QGridLayout; infoBox->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); DActiveLabel* const exiv2LogoLabel = new DActiveLabel(QUrl(QLatin1String("http://www.exiv2.org")), QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/logo-exiv2.png")), infoBox); exiv2LogoLabel->setWhatsThis(i18n("Visit Exiv2 project website")); QLabel* const explanation = new QLabel(infoBox); explanation->setOpenExternalLinks(true); explanation->setWordWrap(true); QString txt; txt.append(i18n("

EXIF - " "a standard used by most digital cameras today to store technical " "information (like aperture and shutter speed) about an image.

")); txt.append(i18n("

IPTC - " "an older standard used in digital photography to store " "photographer information in images.

")); if (MetaEngine::supportXmp()) txt.append(i18n("

XMP - " "a new standard used in digital photography, designed to replace IPTC.

")); explanation->setText(txt); infoBoxGrid->addWidget(exiv2LogoLabel, 0, 0, 1, 1); infoBoxGrid->addWidget(explanation, 0, 1, 1, 2); infoBoxGrid->setColumnStretch(1, 10); infoBoxGrid->setRowStretch(1, 10); infoBoxGrid->setSpacing(0); infoBox->setLayout(infoBoxGrid); // -------------------------------------------------------- mainLayout->addWidget(d->fieldsGroup); mainLayout->addWidget(d->readWriteGroup); mainLayout->addWidget(infoBox); panel->setLayout(mainLayout); d->tab->insertTab(Behavior, panel, i18nc("@title:tab", "Behavior")); // -------------------------------------------------------- QWidget* const rotationPanel = new QWidget(d->tab); QVBoxLayout* const rotationLayout = new QVBoxLayout; d->rotationGroup = new QGroupBox; QGridLayout* const rotationGroupLayout = new QGridLayout; QLabel* const rotationExplanation = new QLabel(i18nc("@label", "When rotating a file")); QLabel* const rotationIcon = new QLabel; rotationIcon->setPixmap(QIcon::fromTheme(QLatin1String("transform-rotate")).pixmap(32)); d->rotateByFlag = new QRadioButton(i18nc("@option:radio", "Rotate by only setting a flag")); d->rotateByContents = new QRadioButton(i18nc("@option:radio", "Rotate by changing the content if possible")); d->allowLossyRotate = new QCheckBox(i18nc("@option:check", "Even allow lossy rotation if necessary")); d->allowRotateByMetadata = new QCheckBox(i18nc("@option:check", "Write flag to metadata if possible")); connect(d->rotateByContents, SIGNAL(toggled(bool)), d->allowLossyRotate, SLOT(setEnabled(bool))); d->rotateByFlag->setChecked(false); d->rotateByContents->setChecked(false); d->allowLossyRotate->setEnabled(false); d->allowLossyRotate->setChecked(false); d->allowRotateByMetadata->setChecked(true); d->rotateByFlag->setToolTip(i18nc("@info:tooltip", "Rotate files only by changing a flag, not touching the pixel data")); d->rotateByFlag->setWhatsThis(xi18nc("@info:whatsthis", "A file can be rotated in two ways: " "You can change the contents, rearranging the individual pixels of the image data. " "Or you can set a flag that the file is to be rotated before it is shown. " "Select this option if you always want to set only a flag. " "This is less obtrusive, but requires support if the file is accessed with another software. " "Ensure to allow setting the flag in the metadata if you want to share your files " "outside digiKam.")); d->rotateByContents->setToolTip(i18nc("@info:tooltip", "If possible rotate files by changing the pixel data")); d->rotateByContents->setWhatsThis(xi18nc("@info:whatsthis", "A file can be rotated in two ways: " "You can change the contents, rearranging the individual pixels of the image data. " "Or you can set a flag that the file is to be rotated before it is shown. " "Select this option if you want the file to be rotated by changing the content. " "This is a lossless operation for JPEG files. For other formats it is a lossy operation, " "which you need to enable explicitly. " "It is not support for RAW and other read-only formats, " "which will be rotated by flag only.")); d->allowLossyRotate->setToolTip(i18nc("@info:tooltip", "Rotate files by changing the pixel data even if the operation will incur quality loss")); d->allowLossyRotate->setWhatsThis(i18nc("@info:whatsthis", "For some file formats which apply lossy compression, " "data will be lost each time the content is rotated. " "Check this option to allow lossy rotation. " "If not enabled, these files will be rotated by flag.")); d->allowRotateByMetadata->setToolTip(i18nc("@info:tooltip", "When rotating a file by setting a flag, also change this flag in the file's metadata")); d->allowRotateByMetadata->setWhatsThis(i18nc("@info:whatsthis", "File metadata typically contains a flag describing " "that a file shall be shown rotated. " "Enable this option to allow editing this field. ")); rotationGroupLayout->addWidget(rotationIcon, 0, 0, 1, 1); rotationGroupLayout->addWidget(rotationExplanation, 0, 1, 1, 2); rotationGroupLayout->addWidget(d->rotateByFlag, 1, 0, 1, 3); rotationGroupLayout->addWidget(d->rotateByContents, 2, 0, 1, 3); rotationGroupLayout->addWidget(d->allowLossyRotate, 3, 2, 1, 1); rotationGroupLayout->addWidget(d->allowRotateByMetadata, 4, 0, 1, 3); rotationGroupLayout->setColumnStretch(3, 10); d->rotationGroup->setLayout(rotationGroupLayout); // -------------------------------------------------------- d->rotationAdvGroup = new QGroupBox; QGridLayout* const rotationAdvLayout = new QGridLayout; QLabel* const rotationAdvExpl = new QLabel(i18nc("@label", "Rotate actions")); QLabel* const rotationAdvIcon = new QLabel; rotationAdvIcon->setPixmap(QIcon::fromTheme(QLatin1String("configure")).pixmap(32)); d->exifRotateBox = new QCheckBox; d->exifRotateBox->setText(i18n("Show images/thumbnails &rotated according to orientation tag.")); d->exifSetOrientationBox = new QCheckBox; d->exifSetOrientationBox->setText(i18n("Set orientation tag to normal after rotate/flip.")); rotationAdvLayout->addWidget(rotationAdvIcon, 0, 0, 1, 1); rotationAdvLayout->addWidget(rotationAdvExpl, 0, 1, 1, 1); rotationAdvLayout->addWidget(d->exifRotateBox, 1, 0, 1, 3); rotationAdvLayout->addWidget(d->exifSetOrientationBox, 2, 0, 1, 3); rotationAdvLayout->setColumnStretch(2, 10); d->rotationAdvGroup->setLayout(rotationAdvLayout); // -------------------------------------------------------- QLabel* const rotationNote = new QLabel(i18n("Note: These settings affect the album view " "and not the image editor. The image editor always " "changes the image data during the rotation.")); rotationNote->setWordWrap(true); rotationNote->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); // -------------------------------------------------------- rotationLayout->addWidget(d->rotationGroup); rotationLayout->addWidget(d->rotationAdvGroup); rotationLayout->addWidget(rotationNote); rotationLayout->addStretch(); rotationPanel->setLayout(rotationLayout); d->tab->insertTab(Rotation, rotationPanel, i18nc("@title:tab", "Rotation")); // -------------------------------------------------------- QWidget* const displayPanel = new QWidget; QGridLayout* const displayLayout = new QGridLayout; QLabel* const displayLabel = new QLabel(i18nc("@info:label", "Select Metadata Fields to Be Displayed")); QLabel* const displayIcon = new QLabel; displayIcon->setPixmap(QIcon::fromTheme(QLatin1String("view-list-tree")).pixmap(32)); d->displaySubTab = new QTabWidget; d->tagsCfgPanel = new MetadataPanel(d->displaySubTab); displayLayout->addWidget(displayIcon, 0, 0); displayLayout->addWidget(displayLabel, 0, 1); displayLayout->addWidget(d->displaySubTab, 1, 0, 1, 3); displayLayout->setColumnStretch(2, 1); displayPanel->setLayout(displayLayout); d->tab->insertTab(Display, displayPanel, i18nc("@title:tab", "Views")); // -------------------------------------------------------- #ifdef HAVE_KFILEMETADATA QWidget* const balooPanel = new QWidget(d->tab); QVBoxLayout* const balooLayout = new QVBoxLayout(balooPanel); QGroupBox* const balooGroup = new QGroupBox(i18n("Baloo Desktop Search"), balooPanel); QVBoxLayout* const gLayout3 = new QVBoxLayout(balooGroup); d->saveToBalooBox = new QCheckBox; d->saveToBalooBox->setText(i18n("Store metadata from digiKam in Baloo")); d->saveToBalooBox->setWhatsThis(i18n("Turn on this option to push rating, comments and tags " "from digiKam into the Baloo storage")); d->readFromBalooBox = new QCheckBox; d->readFromBalooBox->setText(i18n("Read metadata from Baloo")); d->readFromBalooBox->setWhatsThis(i18n("Turn on this option if you want to apply changes to " "rating, comments and tags made in Baloo to digiKam's metadata storage. " "Please note that image metadata will not be edited automatically.")); gLayout3->addWidget(d->saveToBalooBox); gLayout3->addWidget(d->readFromBalooBox); d->tab->insertTab(Baloo, balooPanel, i18nc("@title:tab", "Baloo")); // -------------------------------------------------------- QFrame* const balooBox = new QFrame(balooPanel); QGridLayout* const balooGrid = new QGridLayout(balooBox); balooBox->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); QLabel* const balooLogoLabel = new QLabel; balooLogoLabel->setPixmap(QIcon::fromTheme(QLatin1String("baloo")).pixmap(48)); QLabel* const balooExplanation = new QLabel(balooBox); balooExplanation->setOpenExternalLinks(true); balooExplanation->setWordWrap(true); QString balootxt; balootxt.append(i18n("

Baloo " "provides the basis to handle all kinds of metadata on the KDE desktop in a generic fashion. " "It allows you to tag, rate and comment your files in KDE applications like Dolphin.

" "

Please set here if you want to synchronize the metadata stored by digiKam desktop-wide with the " "Baloo Desktop Search.

")); balooExplanation->setText(balootxt); balooGrid->addWidget(balooLogoLabel, 0, 0, 1, 1); balooGrid->addWidget(balooExplanation, 0, 1, 1, 2); balooGrid->setColumnStretch(1, 10); balooGrid->setSpacing(0); // -------------------------------------------------------- balooLayout->addWidget(balooGroup); balooLayout->addWidget(balooBox); //balooLayout->addWidget(d->resyncButton, 0, Qt::AlignRight); balooLayout->addStretch(); #endif // HAVE_KFILEMETADATA //--------------Advanced Metadata Configuration -------------- d->advTab = new AdvancedMetadataTab(this); d->tab->insertTab(AdvancedConfig, d->advTab, i18nc("@title:tab", "Advanced")); //------------------------Sidecars------------------------- QWidget* const sidecarsPanel = new QWidget(d->tab); QVBoxLayout* const sidecarsLayout = new QVBoxLayout(sidecarsPanel); // -------------------------------------------------------- QGroupBox* rwSidecarsGroup = new QGroupBox; QGridLayout* const rwSidecarsLayout = new QGridLayout; QLabel* const rwSidecarsLabel = new QLabel(i18nc("@label", "Reading and Writing to Sidecars")); d->readXMPSidecarBox = new QCheckBox; d->readXMPSidecarBox->setText(i18nc("@option:check", "Read from sidecar files")); d->readXMPSidecarBox->setWhatsThis(i18nc("@info:whatsthis", "Turn on this option to read metadata from XMP sidecar files when reading metadata.")); d->readXMPSidecarBox->setEnabled(MetaEngine::supportXmp()); d->writeXMPSidecarBox = new QCheckBox; d->writeXMPSidecarBox->setText(i18nc("@option:check", "Write to sidecar files")); d->writeXMPSidecarBox->setWhatsThis(i18nc("@info:whatsthis", "Turn on this option to save, as specified, metadata to XMP sidecar files.")); d->writeXMPSidecarBox->setEnabled(MetaEngine::supportXmp()); d->writingModeCombo = new QComboBox; d->writingModeCombo->addItem(i18n("Write to XMP sidecar for read-only item only"), MetaEngine::WRITE_TO_SIDECAR_ONLY_FOR_READ_ONLY_FILES); d->writingModeCombo->addItem(i18n("Write to XMP sidecar only"), MetaEngine::WRITE_TO_SIDECAR_ONLY); d->writingModeCombo->addItem(i18n("Write to item and XMP Sidecar"), MetaEngine::WRITE_TO_SIDECAR_AND_FILE); d->writingModeCombo->setToolTip(i18nc("@info:tooltip", "Specify the exact mode of XMP sidecar writing")); d->writingModeCombo->setEnabled(false); + d->sidecarFileNameBox = new QCheckBox; + d->sidecarFileNameBox->setText(i18nc("@option:check", "Use compatible file name (BASENAME.xmp) for sidecar files")); + d->sidecarFileNameBox->setWhatsThis(i18nc("@info:whatsthis", + "Turn on this option to create the sidecar files in a compatible " + "file name format of many commercial programs.")); + connect(d->writeXMPSidecarBox, SIGNAL(toggled(bool)), d->writingModeCombo, SLOT(setEnabled(bool))); rwSidecarsLayout->addWidget(rwSidecarsLabel, 0, 0, 1, 3); rwSidecarsLayout->addWidget(d->readXMPSidecarBox, 1, 0, 1, 3); rwSidecarsLayout->addWidget(d->writeXMPSidecarBox, 2, 0, 1, 3); rwSidecarsLayout->addWidget(d->writingModeCombo, 3, 1, 1, 2); + rwSidecarsLayout->addWidget(d->sidecarFileNameBox, 4, 0, 1, 3); rwSidecarsLayout->setColumnStretch(3, 10); rwSidecarsGroup->setLayout(rwSidecarsLayout); // -------------------------------------------------------- QGroupBox* const extensionsGroup = new QGroupBox(sidecarsPanel); QGridLayout* const extensionsGrid = new QGridLayout(extensionsGroup); QLabel* extensionsGroupLabel = new QLabel( i18n("

Add file types to be recognised as sidecars.

" "

digiKam (optionally) writes metadata to *.xmp sidecar " "files. Other programs might use different types, which " "can be specified below. digiKam will neither display these " "nor read from or write to them. But whenever a matching album " "item (e.g. \"image.dng\" for \"image.dng.pp3\") is renamed, " "moved, copied or deleted, the same operation will be done " "on these sidecar files.

" "

Multiple extensions must be separated by a semicolon " "or a space.

")); extensionsGroupLabel->setWordWrap(true); QLabel* const extensionsLogo = new QLabel(extensionsGroup); extensionsLogo->setPixmap(QIcon::fromTheme(QLatin1String("text-x-texinfo")).pixmap(48)); d->extensionsEdit = new QLineEdit(extensionsGroup); d->extensionsEdit->setWhatsThis(i18n("

Here you can add extra extensions " "of sidecars files to be processed alongside " "regular items. These files will not be visible, " "but regarded as an extension of the main file. " "Just write \"xyz abc\" to support files with " "the *.xyz and *.abc extensions. The internally " "used sidecars type *.xmp is always included.

")); d->extensionsEdit->setClearButtonEnabled(true); d->extensionsEdit->setPlaceholderText(i18n("Enter additional sidecars file extensions.")); QLabel* const extensionsLabel = new QLabel(extensionsGroup); extensionsLabel->setText(i18n("Additional &sidecar file extensions")); extensionsLabel->setBuddy(d->extensionsEdit); extensionsGrid->addWidget(extensionsGroupLabel, 0, 0, 1, -1); extensionsGrid->addWidget(extensionsLogo, 1, 0, 2, 1); extensionsGrid->addWidget(extensionsLabel, 1, 1, 1, -1); extensionsGrid->addWidget(d->extensionsEdit, 2, 1, 1, -1); extensionsGrid->setColumnStretch(1, 10); // -------------------------------------------------------- sidecarsLayout->addWidget(rwSidecarsGroup); sidecarsLayout->addWidget(extensionsGroup); sidecarsLayout->addStretch(); d->tab->insertTab(Sidecars, sidecarsPanel, i18nc("@title:tab", "Sidecars")); // -------------------------------------------------------- readSettings(); connect(d->exifRotateBox, SIGNAL(toggled(bool)), this, SLOT(slotExifAutoRotateToggled(bool))); connect(d->clearMetadataIfRescanBox, SIGNAL(toggled(bool)), this, SLOT(slotClearMetadataToggled(bool))); connect(d->writeRawFilesBox, SIGNAL(toggled(bool)), this, SLOT(slotWriteRawFilesToggled(bool))); } SetupMetadata::~SetupMetadata() { delete d; } void SetupMetadata::setActiveMainTab(MetadataTab tab) { d->tab->setCurrentIndex(tab); } void SetupMetadata::setActiveSubTab(int tab) { d->displaySubTab->setCurrentIndex(tab); } void SetupMetadata::applySettings() { MetaEngineSettings* const mSettings = MetaEngineSettings::instance(); if (!mSettings) { return; } MetaEngineSettingsContainer set; set.rotationBehavior = MetaEngineSettingsContainer::RotateByInternalFlag; if (d->allowRotateByMetadata->isChecked()) { set.rotationBehavior |= MetaEngineSettingsContainer::RotateByMetadataFlag; } if (d->rotateByContents->isChecked()) { set.rotationBehavior |= MetaEngineSettingsContainer::RotateByLosslessRotation; if (d->allowLossyRotate->isChecked()) { set.rotationBehavior |= MetaEngineSettingsContainer::RotateByLossyRotation; } } set.exifRotate = d->exifRotateBox->isChecked(); set.exifSetOrientation = d->exifSetOrientationBox->isChecked(); set.saveComments = d->saveCommentsBox->isChecked(); set.saveDateTime = d->saveDateTimeBox->isChecked(); set.savePickLabel = d->savePickLabelBox->isChecked(); set.saveColorLabel = d->saveColorLabelBox->isChecked(); set.saveRating = d->saveRatingBox->isChecked(); set.saveTags = d->saveTagsBox->isChecked(); set.saveTemplate = d->saveTemplateBox->isChecked(); set.saveFaceTags = d->saveFaceTags->isChecked(); set.useLazySync = d->useLazySync->isChecked(); set.writeRawFiles = d->writeRawFilesBox->isChecked(); set.useXMPSidecar4Reading = d->readXMPSidecarBox->isChecked(); + set.useCompatibleFileName = d->sidecarFileNameBox->isChecked(); if (d->writeXMPSidecarBox->isChecked()) { set.metadataWritingMode = (MetaEngine::MetadataWritingMode) d->writingModeCombo->itemData(d->writingModeCombo->currentIndex()).toInt(); } else { set.metadataWritingMode = MetaEngine::WRITE_TO_FILE_ONLY; } set.updateFileTimeStamp = d->updateFileTimeStampBox->isChecked(); set.rescanImageIfModified = d->rescanImageIfModifiedBox->isChecked(); set.clearMetadataIfRescan = d->clearMetadataIfRescanBox->isChecked(); set.sidecarExtensions = cleanUserFilterString(d->extensionsEdit->text()); set.sidecarExtensions.removeAll(QLatin1String("xmp")); set.sidecarExtensions.removeDuplicates(); mSettings->setSettings(set); #ifdef HAVE_KFILEMETADATA ApplicationSettings* const aSettings = ApplicationSettings::instance(); if (!aSettings) { return; } aSettings->setSyncDigikamToBaloo(d->saveToBalooBox->isChecked()); aSettings->setSyncBalooToDigikam(d->readFromBalooBox->isChecked()); aSettings->saveSettings(); #endif // HAVE_KFILEMETADATA d->tagsCfgPanel->applySettings(); d->advTab->applySettings(); } void SetupMetadata::readSettings() { MetaEngineSettings* const mSettings = MetaEngineSettings::instance(); if (!mSettings) { return; } MetaEngineSettingsContainer set = mSettings->settings(); if (set.rotationBehavior & MetaEngineSettingsContainer::RotatingPixels) { d->rotateByContents->setChecked(true); } else { d->rotateByFlag->setChecked(true); } d->allowRotateByMetadata->setChecked(set.rotationBehavior & MetaEngineSettingsContainer::RotateByMetadataFlag); d->allowLossyRotate->setChecked(set.rotationBehavior & MetaEngineSettingsContainer::RotateByLossyRotation); d->exifAutoRotateOriginal = set.exifRotate; d->exifRotateBox->setChecked(d->exifAutoRotateOriginal); d->exifSetOrientationBox->setChecked(set.exifSetOrientation); d->saveTagsBox->setChecked(set.saveTags); d->saveCommentsBox->setChecked(set.saveComments); d->saveRatingBox->setChecked(set.saveRating); d->savePickLabelBox->setChecked(set.savePickLabel); d->saveColorLabelBox->setChecked(set.saveColorLabel); d->saveDateTimeBox->setChecked(set.saveDateTime); d->saveTemplateBox->setChecked(set.saveTemplate); d->saveFaceTags->setChecked(set.saveFaceTags); d->useLazySync->setChecked(set.useLazySync); d->writeRawFilesBox->setChecked(set.writeRawFiles); d->readXMPSidecarBox->setChecked(set.useXMPSidecar4Reading); + d->sidecarFileNameBox->setChecked(set.useCompatibleFileName); d->updateFileTimeStampBox->setChecked(set.updateFileTimeStamp); d->rescanImageIfModifiedBox->setChecked(set.rescanImageIfModified); d->clearMetadataIfRescanBox->setChecked(set.clearMetadataIfRescan); if (set.metadataWritingMode == MetaEngine::WRITE_TO_FILE_ONLY) { d->writeXMPSidecarBox->setChecked(false); } else { d->writeXMPSidecarBox->setChecked(true); d->writingModeCombo->setCurrentIndex(d->writingModeCombo->findData(set.metadataWritingMode)); } d->extensionsEdit->setText(set.sidecarExtensions.join(QLatin1Char(' '))); #ifdef HAVE_KFILEMETADATA ApplicationSettings* const aSettings = ApplicationSettings::instance(); if (!aSettings) { return; } d->saveToBalooBox->setChecked(aSettings->getSyncDigikamToBaloo()); d->readFromBalooBox->setChecked(aSettings->getSyncBalooToDigikam()); #endif // HAVE_KFILEMETADATA } bool SetupMetadata::exifAutoRotateHasChanged() const { return d->exifAutoRotateOriginal != d->exifRotateBox->isChecked(); } void SetupMetadata::slotExifAutoRotateToggled(bool b) { // Show info if rotation was switched off, and only once. if (!b && !d->exifAutoRotateShowedInfo && exifAutoRotateHasChanged()) { d->exifAutoRotateShowedInfo = true; QMessageBox::information(this, qApp->applicationName(), i18nc("@info", "Switching off exif auto rotation will most probably show " "your images in a wrong orientation, so only change this " "option if you explicitly require this.")); } } void SetupMetadata::slotClearMetadataToggled(bool b) { // Show info if delete metadata from the database was switched on, and only once. if (b && !d->clearMetadataShowedInfo) { d->clearMetadataShowedInfo = true; QMessageBox::information(this, qApp->applicationName(), i18nc("@info", "Switching on this option and your metadata has been written to the " "database only and not to the file or sidecar, you will be able to " "lose inserted metadata such as tags, keywords, or geographic " "coordinates.")); } } void SetupMetadata::slotWriteRawFilesToggled(bool b) { // Show info if write metadata to raw files was switched on if (b) { QApplication::beep(); QPointer msgBox = new QMessageBox(QMessageBox::Warning, qApp->applicationName(), i18n("

Do you really want to enable metadata writing to RAW files?

" "

DigiKam delegates this task to the Exiv2 library. With different RAW " "formats, problems are known which can lead to the destruction of RAW " "files. If you decide to do so, make a backup of your RAW files.

" "

We strongly recommend not to enable this option.

"), QMessageBox::Yes | QMessageBox::No, this); msgBox->button(QMessageBox::Yes)->setText(i18n("Yes I understand")); msgBox->setDefaultButton(QMessageBox::No); int result = msgBox->exec(); delete msgBox; if (result == QMessageBox::Yes) { QPointer msgBox = new QMessageBox(QMessageBox::Warning, qApp->applicationName(), i18n("You would rather disable writing metadata to RAW files?"), QMessageBox::Yes | QMessageBox::No, this); int result = msgBox->exec(); delete msgBox; if (result == QMessageBox::No) { return; } } d->writeRawFilesBox->setChecked(false); } } } // namespace Digikam diff --git a/project/bundles/3rdparty/ext_libicu/CMakeLists.txt b/project/bundles/3rdparty/ext_libicu/CMakeLists.txt index 90310b6f9b..4c0c52c1cf 100644 --- a/project/bundles/3rdparty/ext_libicu/CMakeLists.txt +++ b/project/bundles/3rdparty/ext_libicu/CMakeLists.txt @@ -1,27 +1,30 @@ # Script to build libicu for digiKam bundle. # # Copyright (c) 2015-2019, Gilles Caulier, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # -SET(EXTPREFIX_libicu "${EXTPREFIX}") +SET(EXTPREFIX_libicu "${EXTPREFIX}/local") ExternalProject_Add(ext_libicu DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://github.com/unicode-org/icu/archive/release-64-2.tar.gz URL_MD5 6bab6095784e0d1ede06d6d27df29bf2 - INSTALL_DIR ${EXTPREFIX_libicu} - - CONFIGURE_COMMAND cd /icu4c/source && ./configure --prefix ${EXTPREFIX_libicu} --enable-tests=no --enable-samples=no + CONFIGURE_COMMAND cd /icu4c/source && + ./configure --prefix ${EXTPREFIX_libicu} + --enable-tests=no + --enable-samples=no + --with-data-packaging=library + --disable-renaming BUILD_COMMAND cd /icu4c/source && $(MAKE) INSTALL_COMMAND cd /icu4c/source && $(MAKE) install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ALWAYS 0 ) diff --git a/project/bundles/3rdparty/ext_qt/CMakeLists.txt b/project/bundles/3rdparty/ext_qt/CMakeLists.txt index 14bbf672a5..c475c0473f 100644 --- a/project/bundles/3rdparty/ext_qt/CMakeLists.txt +++ b/project/bundles/3rdparty/ext_qt/CMakeLists.txt @@ -1,98 +1,97 @@ # Script to build Qt for digiKam bundle. # # Copyright (c) 2015-2019, Gilles Caulier, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # SET(EXTPREFIX_qt "${EXTPREFIX}") IF(NOT ENABLE_QTWEBENGINE) SET(DROP_QTWEBENGINE_DEPS -skip qtwebengine # No need Chromium browser support (QtWebkit instead) -skip qtwebchannel # QtWebChannel support ==> QWebEngine dependency -skip qtquickcontrols # QtQuick support ==> QWebEngine dependency ) ENDIF() ExternalProject_Add(ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/official_releases/qt/5.13/5.13.1/single/qt-everywhere-src-5.13.1.tar.xz URL_MD5 d66b1da335d0c25325fdf493e9044c38 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/qt-icu-hack.patch && ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/qt-appimage-support.patch - CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto' - CONFIGURE_COMMAND /configure + ICU_LIBS="-I${EXTPREFIX}/local/ -L${EXTPREFIX}/local/lib -licui18n -licuuc -licudata" -prefix ${EXTPREFIX_qt} -verbose -release -opensource -confirm-license -sql-sqlite # Compile Sqlite SQL plugin -sql-mysql # Compile Mysql SQL plugin -iconv # International string conversion + -icu -fontconfig -system-freetype # Use system font rendering lib https://doc.qt.io/qt-5/qtgui-attribution-freetype.html -openssl-linked # hard link ssl libraries -nomake tests # Do not build test codes -nomake examples # Do not build basis example codes -no-compile-examples # Do not build extra example codes -no-qml-debug - -no-icu # Locale support disabled (International Component Unicode) -no-mtdev -no-journald -no-syslog -no-tslib -no-directfb -no-linuxfb -no-libproxy -no-pch -qt-zlib -qt-pcre -qt-harfbuzz -qt-xcb -skip qt3d # 3D core -skip qtactiveqt # No need ActiveX support -skip qtandroidextras # For embeded devices only -skip qtcharts # No need data models charts support -skip qtconnectivity # For embeded devices only -skip qtdatavis3d # no need 3D data visualizations support -skip qtdoc # No need documentation -skip qtgamepad # No need gamepad hardware support. -skip qtgraphicaleffects # No need Advanced graphical effects in GUI -skip qtlocation # No need geolocation -skip qtlottie # No need Adobe QtQuick After Effect animations integration -skip qtmacextras # For MacOS devices only -skip qtmultimedia # No need multimedia support (replaced by QtAV+ffmpeg) -skip qtnetworkauth # No need network authentification support. -skip qtpurchasing # No need in-app purchase of products support -skip qtquickcontrols2 # QtQuick support for QML -skip qtremoteobjects # No need sharing QObject properties between processes support -skip qtscript # No need scripting (deprecated) -skip qtscxml # No need SCXML state machines support -skip qtsensors # For embeded devices only -skip qtserialbus # No need serial bus support -skip qtserialport # No need serial port support -skip qtspeech # No need speech synthesis support -skip qttranslations # No need translation tools. -skip qtvirtualkeyboard # No need virtual keyboard support -skip qtwayland # Specific to Linux -skip qtwebglplugin # No need browser OpenGL extention support -skip qtwebsockets # No need websocket support -skip qtwebview # QML extension for QWebEngine -skip qtwinextras # For Windows devices only ${DROP_QTWEBENGINE_DEPS} # Exemple of code to install only one Qt module for hacking purpose (aka qtbase here) #BUILD_COMMAND cd && $(MAKE) module-qtbase #INSTALL_COMMAND cd && $(MAKE) module-qtbase-install_subtargets UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ALWAYS 0 ) diff --git a/project/bundles/appimage/01-build-host.sh b/project/bundles/appimage/01-build-host.sh index e09675cf2f..f500f9b95d 100755 --- a/project/bundles/appimage/01-build-host.sh +++ b/project/bundles/appimage/01-build-host.sh @@ -1,203 +1,203 @@ #!/bin/bash # Script to build a Linux Host installation to compile an AppImage bundle of digiKam. # This script must be run as sudo # # Copyright (c) 2015-2019, Gilles Caulier, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # # Halt and catch errors set -eE trap 'PREVIOUS_COMMAND=$THIS_COMMAND; THIS_COMMAND=$BASH_COMMAND' DEBUG trap 'echo "FAILED COMMAND: $PREVIOUS_COMMAND"' ERR ################################################################################################# # Manage script traces to log file mkdir -p ./logs exec > >(tee ./logs/build-host.full.log) 2>&1 ################################################################################################# echo "01-build-host.sh : build a Linux host installation to compile an AppImage bundle." echo "---------------------------------------------------------------------------------" ################################################################################################# # Pre-processing checks . ./config.sh . ./common.sh ChecksRunAsRoot StartScript ChecksCPUCores HostAdjustments RegisterRemoteServers ORIG_WD="`pwd`" ################################################################################################# echo -e "---------- Update Linux Host\n" urpmi --auto --auto-update ################################################################################################# if [[ "$(arch)" = "x86_64" ]] ; then LIBSUFFIX=lib64 else LIBSUFFIX=lib fi echo -e "---------- Install New Development Packages\n" # Packages for base dependencies and Qt5. urpmi --auto \ wget \ tar \ bzip2 \ gettext \ git \ subversion \ libtool \ which \ fuse \ automake \ cmake \ gcc-c++ \ patch \ libdrm-devel \ libxcb \ libxcb-devel \ xcb-util-keysyms-devel \ xcb-util-devel \ xkeyboard-config \ xscreensaver \ gperf \ ruby \ bison \ flex \ zlib-devel \ expat-devel \ fuse-devel \ glibc-devel \ mysql-devel \ eigen3-devel \ openssl-devel \ cppunit-devel \ libstdc++-devel \ libxml2-devel \ libstdc++-devel \ lcms2-devel \ glibc-devel \ libudev-devel \ sqlite-devel \ libexif-devel \ libxslt-devel \ xz-devel \ lz4-devel \ inotify-tools-devel \ openssl-devel \ cups-devel \ openal-soft-devel \ libical-devel \ libcap-devel \ fontconfig-devel \ freetype-devel \ patchelf \ dpkg \ python \ ruby \ ruby-devel \ sqlite3-devel \ ffmpeg-devel \ boost-devel \ gphoto2-devel \ sane-backends \ jasper-devel \ ${LIBSUFFIX}xkbcommon-devel \ ${LIBSUFFIX}sane1-devel \ ${LIBSUFFIX}xcb-util1 \ ${LIBSUFFIX}xi-devel \ ${LIBSUFFIX}xtst-devel \ ${LIBSUFFIX}xrandr-devel \ ${LIBSUFFIX}xcursor-devel \ ${LIBSUFFIX}xcomposite-devel \ ${LIBSUFFIX}xrender-devel \ ${LIBSUFFIX}mesagl1-devel \ ${LIBSUFFIX}mesaglu1-devel \ ${LIBSUFFIX}mesaegl1-devel \ ${LIBSUFFIX}mesaegl1 \ ${LIBSUFFIX}ltdl-devel \ ${LIBSUFFIX}glib2.0-devel \ ${LIBSUFFIX}usb1.0-devel \ ${LIBSUFFIX}jpeg-devel \ ${LIBSUFFIX}png-devel \ ${LIBSUFFIX}tiff-devel \ - ${LIBSUFFIX}icu-devel \ ${LIBSUFFIX}lqr-devel \ ${LIBSUFFIX}fftw-devel \ - ${LIBSUFFIX}curl-devel \ + ${LIBSUFFIX}curl-devel \ ${LIBSUFFIX}magick-devel ################################################################################################# echo -e "---------- Clean-up Old Packages\n" # Remove system based devel package to prevent conflict with new one. urpme --auto --force ${LIBSUFFIX}qt5core5 || true ################################################################################################# echo -e "---------- Prepare Linux host to Compile Extra Dependencies\n" # Workaround for: On Mageia 6, .pc files in /usr/lib/pkgconfig are not recognized # However, this is where .pc files get installed when bulding libraries... (FIXME) # I found this by comparing the output of librevenge's "make install" command # between Ubuntu and CentOS 6 ln -sf /usr/share/pkgconfig /usr/lib/pkgconfig # Make sure we build from the /, parts of this script depends on that. We also need to run as root... cd / # Create the build dir for the 3rdparty deps if [ ! -d $BUILDING_DIR ] ; then mkdir $BUILDING_DIR fi if [ ! -d $DOWNLOAD_DIR ] ; then mkdir $DOWNLOAD_DIR fi ################################################################################################# cd $BUILDING_DIR rm -rf $BUILDING_DIR/* || true cmake $ORIG_WD/../3rdparty \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DINSTALL_ROOT=/usr \ -DEXTERNALS_DOWNLOAD_DIR=$DOWNLOAD_DIR \ -DENABLE_QTWEBENGINE=$DK_QTWEBENGINE # Low level libraries and Qt5 dependencies # NOTE: The order to compile each component here is very important. +cmake --build . --config RelWithDebInfo --target ext_libicu -- -j$CPU_CORES cmake --build . --config RelWithDebInfo --target ext_qt -- -j$CPU_CORES # depend of tiff, png, jpeg if [[ $DK_QTWEBENGINE = 0 ]] ; then cmake --build . --config RelWithDebInfo --target ext_qtwebkit -- -j$CPU_CORES # depend of Qt and libicu fi cmake --build . --config RelWithDebInfo --target ext_qtav -- -j$CPU_CORES # depend of qt and ffmpeg cmake --build . --config RelWithDebInfo --target ext_exiv2 -- -j$CPU_CORES cmake --build . --config RelWithDebInfo --target ext_opencv -- -j$CPU_CORES cmake --build . --config RelWithDebInfo --target ext_lensfun -- -j$CPU_CORES ################################################################################################# TerminateScript