diff --git a/core/libs/dimg/loaders/dimgloader.cpp b/core/libs/dimg/loaders/dimgloader.cpp index a008b851e0..5db8a0f8e0 100644 --- a/core/libs/dimg/loaders/dimgloader.cpp +++ b/core/libs/dimg/loaders/dimgloader.cpp @@ -1,441 +1,443 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-06-14 * Description : DImg image loader interface * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2005-2018 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 "dimgloader.h" // C++ includes #include #include #include // Qt includes #include #include #include // Local includes #include "digikam_debug.h" #include "dimg_p.h" #include "dmetadata.h" #include "dimgloaderobserver.h" #include "kmemoryinfo.h" namespace Digikam { +QMutex s_dimgLoaderMutex(QMutex::Recursive); + DImgLoader::DImgLoader(DImg* const image) : m_image(image) { m_loadFlags = LoadAll; } DImgLoader::~DImgLoader() { } void DImgLoader::setLoadFlags(LoadFlags flags) { m_loadFlags = flags; } bool DImgLoader::hasLoadedData() const { return (m_loadFlags & LoadImageData) && m_image->m_priv->data; } int DImgLoader::granularity(DImgLoaderObserver* const observer, int total, float progressSlice) { // Splits expect total value into the chunks where checks shall occur // and combines this with a possible correction factor from observer. // Progress slice is the part of 100% concerned with the current granularity // (E.g. in a loop only the values from 10% to 90% are used, then progressSlice is 0.8) // Current default is 1/20, that is progress info every 5% int granularity = 0; if (observer) { granularity = (int)((total / (20 * progressSlice)) / observer->granularity()); } return granularity ? granularity : 1; } unsigned char*& DImgLoader::imageData() { return m_image->m_priv->data; } unsigned int& DImgLoader::imageWidth() { return m_image->m_priv->width; } unsigned int& DImgLoader::imageHeight() { return m_image->m_priv->height; } bool DImgLoader::imageHasAlpha() const { return m_image->hasAlpha(); } bool DImgLoader::imageSixteenBit() const { return m_image->sixteenBit(); } int DImgLoader::imageBitsDepth() const { return m_image->bitsDepth(); } int DImgLoader::imageBytesDepth() const { return m_image->bytesDepth(); } void DImgLoader::imageSetIccProfile(const IccProfile& profile) { m_image->setIccProfile(profile); } QVariant DImgLoader::imageGetAttribute(const QString& key) const { return m_image->attribute(key); } QString DImgLoader::imageGetEmbbededText(const QString& key) const { return m_image->embeddedText(key); } void DImgLoader::imageSetAttribute(const QString& key, const QVariant& value) { m_image->setAttribute(key, value); } QMap& DImgLoader::imageEmbeddedText() const { return m_image->m_priv->embeddedText; } void DImgLoader::imageSetEmbbededText(const QString& key, const QString& text) { m_image->setEmbeddedText(key, text); } void DImgLoader::loadingFailed() { if (m_image->m_priv->data) { delete [] m_image->m_priv->data; } m_image->m_priv->data = 0; m_image->m_priv->width = 0; m_image->m_priv->height = 0; } qint64 DImgLoader::checkAllocation(qint64 fullSize) { if ((quint64)fullSize >= std::numeric_limits::max()) { qCWarning(DIGIKAM_DIMG_LOG) << "Cannot allocate buffer of size" << fullSize; return 0; } // Do extra check if allocating serious amounts of memory. // At the time of writing (2011), I consider 100 MB as "serious". if (fullSize > (qint64)(100 * 1024 * 1024)) { KMemoryInfo memory = KMemoryInfo::currentInfo(); int res = memory.isValid(); if (res == -1) { qCWarning(DIGIKAM_DIMG_LOG) << "Not a recognized platform to get memory information"; return -1; } else if (res == 0) { qCWarning(DIGIKAM_DIMG_LOG) << "Error to get physical memory information form a recognized platform"; return 0; } qint64 available = memory.bytes(KMemoryInfo::AvailableMemory); if (fullSize > available) { qCWarning(DIGIKAM_DIMG_LOG) << "Not enough memory to allocate buffer of size " << fullSize; qCWarning(DIGIKAM_DIMG_LOG) << "Available memory size is " << available; return 0; } } return fullSize; } bool DImgLoader::readMetadata(const QString& filePath, DImg::FORMAT /*ff*/) { if (!((m_loadFlags & LoadMetadata) || (m_loadFlags & LoadUniqueHash) || (m_loadFlags & LoadImageHistory))) { return false; } DMetadata metaDataFromFile; if (!metaDataFromFile.load(filePath)) { m_image->setMetadata(MetaEngineData()); return false; } m_image->setMetadata(metaDataFromFile.data()); if (m_loadFlags & LoadImageHistory) { DImageHistory history = DImageHistory::fromXml(metaDataFromFile.getItemHistory()); HistoryImageId id = createHistoryImageId(filePath, *m_image, metaDataFromFile); id.m_type = HistoryImageId::Current; history << id; m_image->setItemHistory(history); imageSetAttribute(QLatin1String("originalImageHistory"), QVariant::fromValue(history)); } return true; } // copied from imagescanner.cpp static QDateTime creationDateFromFilesystem(const QFileInfo& info) { // creation date is not what it seems on Unix QDateTime ctime = info.created(); QDateTime mtime = info.lastModified(); if (ctime.isNull()) { return mtime; } if (mtime.isNull()) { return ctime; } return qMin(ctime, mtime); } HistoryImageId DImgLoader::createHistoryImageId(const QString& filePath, const DImg& image, const DMetadata& metadata) { QFileInfo file(filePath); if (!file.exists()) { return HistoryImageId(); } HistoryImageId id(metadata.getItemUniqueId()); QDateTime dt = metadata.getItemDateTime(); if (dt.isNull()) { dt = creationDateFromFilesystem(file); } id.setCreationDate(dt); id.setFileName(file.fileName()); id.setPath(file.path()); id.setUniqueHash(QString::fromUtf8(uniqueHashV2(filePath, &image)), file.size()); return id; } bool DImgLoader::saveMetadata(const QString& filePath) { DMetadata metaDataToFile(filePath); metaDataToFile.setData(m_image->getMetadata()); return metaDataToFile.applyChanges(true); } bool DImgLoader::checkExifWorkingColorSpace() const { DMetadata metaData(m_image->getMetadata()); IccProfile profile = metaData.getIccProfile(); if (!profile.isNull()) { m_image->setIccProfile(profile); return true; } return false; } void DImgLoader::storeColorProfileInMetadata() { IccProfile profile = m_image->getIccProfile(); if (profile.isNull()) { return; } DMetadata metaData(m_image->getMetadata()); metaData.setIccProfile(profile); m_image->setMetadata(metaData.data()); } void DImgLoader::purgeExifWorkingColorSpace() { DMetadata meta(m_image->getMetadata()); meta.removeExifColorSpace(); m_image->setMetadata(meta.data()); } QByteArray DImgLoader::uniqueHashV2(const QString& filePath, const DImg* const img) { QFile file(filePath); if (!file.open(QIODevice::Unbuffered | QIODevice::ReadOnly)) { return QByteArray(); } QCryptographicHash md5(QCryptographicHash::Md5); // Specified size: 100 kB; but limit to file size const qint64 specifiedSize = 100 * 1024; // 100 kB qint64 size = qMin(file.size(), specifiedSize); if (size) { QScopedArrayPointer databuf(new char[size]); int read; // Read first 100 kB if ((read = file.read(databuf.data(), size)) > 0) { md5.addData(databuf.data(), read); } // Read last 100 kB file.seek(file.size() - size); if ((read = file.read(databuf.data(), size)) > 0) { md5.addData(databuf.data(), read); } } file.close(); QByteArray hash = md5.result().toHex(); if (img && !hash.isNull()) { const_cast(img)->setAttribute(QString::fromUtf8("uniqueHashV2"), hash); } return hash; } QByteArray DImgLoader::uniqueHash(const QString& filePath, const DImg& img, bool loadMetadata) { QByteArray bv; if (loadMetadata) { DMetadata metaDataFromFile(filePath); bv = metaDataFromFile.getExifEncoded(); } else { DMetadata metaDataFromImage(img.getMetadata()); bv = metaDataFromImage.getExifEncoded(); } // Create the unique ID QCryptographicHash md5(QCryptographicHash::Md5); // First, read the Exif data into the hash md5.addData(bv); // Second, read in the first 8KB of the file QFile qfile(filePath); char databuf[8192]; QByteArray hash; if (qfile.open(QIODevice::Unbuffered | QIODevice::ReadOnly)) { int readlen = 0; if ((readlen = qfile.read(databuf, 8192)) > 0) { QByteArray size = 0; md5.addData(databuf, readlen); md5.addData(size.setNum(qfile.size())); hash = md5.result().toHex(); } qfile.close(); } if (!hash.isNull()) { const_cast(img).setAttribute(QLatin1String("uniqueHash"), hash); } return hash; } unsigned char* DImgLoader::new_failureTolerant(size_t unsecureSize) { return new_failureTolerant(unsecureSize); } unsigned char* DImgLoader::new_failureTolerant(quint64 w, quint64 h, uint typesPerPixel) { return new_failureTolerant(w, h, typesPerPixel); } unsigned short* DImgLoader::new_short_failureTolerant(size_t unsecureSize) { return new_failureTolerant(unsecureSize); } unsigned short* DImgLoader::new_short_failureTolerant(quint64 w, quint64 h, uint typesPerPixel) { return new_failureTolerant(w, h, typesPerPixel); } } // namespace Digikam diff --git a/core/libs/dimg/loaders/dimgloader.h b/core/libs/dimg/loaders/dimgloader.h index 8eb0b3a083..7732399eea 100644 --- a/core/libs/dimg/loaders/dimgloader.h +++ b/core/libs/dimg/loaders/dimgloader.h @@ -1,196 +1,199 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-06-14 * Description : DImg image loader interface * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2005-2018 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 +#include +#include // Local includes #include "digikam_debug.h" #include "digikam_export.h" #include "dimg.h" namespace Digikam { +extern QMutex s_dimgLoaderMutex; + class DImgLoaderObserver; class DMetadata; class DImgLoader { public: enum LoadFlag { LoadItemInfo = 1, LoadMetadata = 2, LoadICCData = 4, LoadImageData = 8, LoadUniqueHash = 16, LoadImageHistory = 32, 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 = 0); 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; 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, DImg::FORMAT ff); 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 0; } 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 0; break; case -1: // unsupported platform // We will try to continue to allocate break; default: // parse done with success from supported platform break; } Type* reserved = 0; 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 = 0; } return reserved; } Q_DECLARE_OPERATORS_FOR_FLAGS(DImgLoader::LoadFlags) } // namespace Digikam #endif // DIGIKAM_DIMG_LOADER_H diff --git a/core/libs/dimg/loaders/jpegloader.cpp b/core/libs/dimg/loaders/jpegloader.cpp index b5347a40ee..8127019d9e 100644 --- a/core/libs/dimg/loaders/jpegloader.cpp +++ b/core/libs/dimg/loaders/jpegloader.cpp @@ -1,977 +1,979 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-06-14 * Description : A JPEG IO file for DImg framework * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2005-2018 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 XMD_H #include "jpegloader.h" // C ANSI includes extern "C" { #include "iccjpeg.h" } // Qt includes #include #include // Local includes #include "digikam_config.h" #include "dimg.h" #include "digikam_debug.h" #include "dimgloaderobserver.h" #ifdef Q_OS_WIN #include "jpegwin.h" #endif namespace Digikam { // To manage Errors/Warnings handling provide by libjpeg void JPEGLoader::dimg_jpeg_error_exit(j_common_ptr cinfo) { dimg_jpeg_error_mgr* const myerr = static_cast(cinfo->err); char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << buffer; longjmp(myerr->setjmp_buffer, 1); } void JPEGLoader::dimg_jpeg_emit_message(j_common_ptr cinfo, int msg_level) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); qCDebug(DIGIKAM_DIMG_LOG_JPEG) << buffer << " (" << msg_level << ")"; } void JPEGLoader::dimg_jpeg_output_message(j_common_ptr cinfo) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); qCDebug(DIGIKAM_DIMG_LOG_JPEG) << buffer; } JPEGLoader::JPEGLoader(DImg* const image) : DImgLoader(image) { } bool JPEGLoader::load(const QString& filePath, DImgLoaderObserver* const observer) { + QMutexLocker lock(&s_dimgLoaderMutex); + readMetadata(filePath, DImg::JPEG); FILE* const file = fopen(QFile::encodeName(filePath).constData(), "rb"); if (!file) { loadingFailed(); return false; } unsigned char header[2]; if (fread(&header, 2, 1, file) != 1) { fclose(file); loadingFailed(); return false; } unsigned char jpegID[] = { 0xFF, 0xD8 }; if (memcmp(header, jpegID, 2) != 0) { // not a jpeg file fclose(file); loadingFailed(); return false; } rewind(file); struct jpeg_decompress_struct cinfo; struct dimg_jpeg_error_mgr jerr; // ------------------------------------------------------------------- // JPEG error handling. cinfo.err = jpeg_std_error(&jerr); cinfo.err->error_exit = dimg_jpeg_error_exit; cinfo.err->emit_message = dimg_jpeg_emit_message; cinfo.err->output_message = dimg_jpeg_output_message; // setjmp-save cleanup class Q_DECL_HIDDEN CleanupData { public: CleanupData() { data = 0; dest = 0; file = 0; cmod = 0; } ~CleanupData() { delete [] data; delete [] dest; if (file) { fclose(file); } } void setData(uchar* const d) { data = d; } void setDest(uchar* const d) { dest = d; } void setFile(FILE* const f) { file = f; } void setSize(const QSize& s) { size = s; } void setColorModel(int c) { cmod = c; } void deleteData() { delete [] data; data = 0; } void takeDest() { dest = 0; } public: uchar* data; uchar* dest; FILE* file; QSize size; int cmod; }; CleanupData* const cleanupData = new CleanupData; cleanupData->setFile(file); // If an error occurs during reading, libjpeg will jump here if (setjmp(jerr.setjmp_buffer)) { jpeg_destroy_decompress(&cinfo); if (!cleanupData->dest || !cleanupData->size.isValid()) { 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->dest; imageSetAttribute(QLatin1String("format"), QLatin1String("JPG")); imageSetAttribute(QLatin1String("originalColorModel"), cleanupData->cmod); imageSetAttribute(QLatin1String("originalBitDepth"), 8); imageSetAttribute(QLatin1String("originalSize"), cleanupData->size); cleanupData->takeDest(); delete cleanupData; return true; } // ------------------------------------------------------------------- // Find out if we do the fast-track loading with reduced size. Jpeg specific. int scaledLoadingSize = 0; QVariant attribute = imageGetAttribute(QLatin1String("scaledLoadingSize")); if (attribute.isValid()) { scaledLoadingSize = attribute.toInt(); } // ------------------------------------------------------------------- // Set JPEG decompressor instance jpeg_create_decompress(&cinfo); bool startedDecompress = false; #ifdef Q_OS_WIN QFile inFile(filePath); QByteArray buffer; if (inFile.open(QIODevice::ReadOnly)) { buffer = inFile.readAll(); inFile.close(); } JPEGUtils::jpeg_memory_src(&cinfo, (JOCTET*)buffer.data(), buffer.size()); #else jpeg_stdio_src(&cinfo, file); #endif // Recording ICC profile marker (from iccjpeg.c) if (m_loadFlags & LoadICCData) { setup_read_icc_profile(&cinfo); } // read image information jpeg_read_header(&cinfo, boolean(true)); // read dimension (nominal values from header) int w = cinfo.image_width; int h = cinfo.image_height; QSize originalSize(w, h); // Libjpeg handles the following conversions: // YCbCr => GRAYSCALE, YCbCr => RGB, GRAYSCALE => RGB, YCCK => CMYK // So we cannot get RGB from CMYK or YCCK, CMYK conversion is handled below int colorModel = DImg::COLORMODELUNKNOWN; switch (cinfo.jpeg_color_space) { case JCS_UNKNOWN: // perhaps jpeg_read_header did some guessing, leave value unchanged colorModel = DImg::COLORMODELUNKNOWN; break; case JCS_GRAYSCALE: cinfo.out_color_space = JCS_RGB; colorModel = DImg::GRAYSCALE; break; case JCS_RGB: cinfo.out_color_space = JCS_RGB; colorModel = DImg::RGB; break; case JCS_YCbCr: cinfo.out_color_space = JCS_RGB; colorModel = DImg::YCBCR; break; case JCS_CMYK: case JCS_YCCK: cinfo.out_color_space = JCS_CMYK; colorModel = DImg::CMYK; break; default: break; } cleanupData->setColorModel(colorModel); // ------------------------------------------------------------------- // Load image data. uchar* dest = 0; if (m_loadFlags & LoadImageData) { // set decompression parameters cinfo.do_fancy_upsampling = boolean(true); cinfo.do_block_smoothing = boolean(false); // handle scaled loading if (scaledLoadingSize) { int imgSize = qMax(cinfo.image_width, cinfo.image_height); // libjpeg supports 1/1, 1/2, 1/4, 1/8 int scale = 1; while (scaledLoadingSize* scale * 2 <= imgSize) { scale *= 2; } if (scale > 8) { scale = 8; } //cinfo.scale_num = 1; //cinfo.scale_denom = scale; cinfo.scale_denom *= scale; } // initialize decompression if (!startedDecompress) { jpeg_start_decompress(&cinfo); startedDecompress = true; } // some pseudo-progress if (observer) { observer->progressInfo(m_image, 0.1F); } // re-read dimension (scaling included) w = cinfo.output_width; h = cinfo.output_height; // ------------------------------------------------------------------- // Get scanlines uchar* ptr = 0, *data = 0, *line[16]; uchar* ptr2 = 0; int x, y, l, i, scans; // int count; // int prevy; if (cinfo.rec_outbuf_height > 16) { jpeg_destroy_decompress(&cinfo); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "Height of JPEG scanline buffer out of range!"; delete cleanupData; loadingFailed(); return false; } // We only take RGB with 1 or 3 components, or CMYK with 4 components if (!( (cinfo.out_color_space == JCS_RGB && (cinfo.output_components == 3 || cinfo.output_components == 1)) || (cinfo.out_color_space == JCS_CMYK && cinfo.output_components == 4) )) { jpeg_destroy_decompress(&cinfo); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "JPEG colorspace (" << cinfo.out_color_space << ") or Number of JPEG color components (" << cinfo.output_components << ") unsupported!"; delete cleanupData; loadingFailed(); return false; } data = new_failureTolerant(w * 16 * cinfo.output_components); cleanupData->setData(data); if (!data) { jpeg_destroy_decompress(&cinfo); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "Cannot allocate memory!"; delete cleanupData; loadingFailed(); return false; } dest = new_failureTolerant(w, h, 4); cleanupData->setSize(QSize(w, h)); cleanupData->setDest(dest); if (!dest) { jpeg_destroy_decompress(&cinfo); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "Cannot allocate memory!"; delete cleanupData; loadingFailed(); return false; } ptr2 = dest; // count = 0; // prevy = 0; if (cinfo.output_components == 3) { for (i = 0; i < cinfo.rec_outbuf_height; ++i) { line[i] = data + (i * w * 3); } int checkPoint = 0; for (l = 0; l < h; l += cinfo.rec_outbuf_height) { // use 0-10% and 90-100% for pseudo-progress if (observer && l >= checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { jpeg_destroy_decompress(&cinfo); delete cleanupData; loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)l) / ((float)h)))); } jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height); scans = cinfo.rec_outbuf_height; if ((h - l) < scans) { scans = h - l; } ptr = data; for (y = 0; y < scans; ++y) { for (x = 0; x < w; ++x) { ptr2[3] = 0xFF; ptr2[2] = ptr[0]; ptr2[1] = ptr[1]; ptr2[0] = ptr[2]; ptr += 3; ptr2 += 4; } } } } else if (cinfo.output_components == 1) { for (i = 0; i < cinfo.rec_outbuf_height; ++i) { line[i] = data + (i * w); } int checkPoint = 0; for (l = 0; l < h; l += cinfo.rec_outbuf_height) { if (observer && l >= checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { jpeg_destroy_decompress(&cinfo); delete cleanupData; loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)l) / ((float)h)))); } jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height); scans = cinfo.rec_outbuf_height; if ((h - l) < scans) { scans = h - l; } ptr = data; for (y = 0; y < scans; ++y) { for (x = 0; x < w; ++x) { ptr2[3] = 0xFF; ptr2[2] = ptr[0]; ptr2[1] = ptr[0]; ptr2[0] = ptr[0]; ptr ++; ptr2 += 4; } } } } else // CMYK { for (i = 0; i < cinfo.rec_outbuf_height; ++i) { line[i] = data + (i * w * 4); } int checkPoint = 0; for (l = 0; l < h; l += cinfo.rec_outbuf_height) { // use 0-10% and 90-100% for pseudo-progress if (observer && l >= checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { jpeg_destroy_decompress(&cinfo); delete cleanupData; loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)l) / ((float)h)))); } jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height); scans = cinfo.rec_outbuf_height; if ((h - l) < scans) { scans = h - l; } ptr = data; for (y = 0; y < scans; ++y) { for (x = 0; x < w; ++x) { // Inspired by Qt's JPEG loader int k = ptr[3]; ptr2[3] = 0xFF; ptr2[2] = k * ptr[0] / 255; ptr2[1] = k * ptr[1] / 255; ptr2[0] = k * ptr[2] / 255; ptr += 4; ptr2 += 4; } } } } // clean up cleanupData->deleteData(); } // ------------------------------------------------------------------- // Read image ICC profile if (m_loadFlags & LoadICCData) { if (!startedDecompress) { jpeg_start_decompress(&cinfo); startedDecompress = true; } JOCTET* profile_data = NULL; uint profile_size = 0; read_icc_profile(&cinfo, &profile_data, &profile_size); if (profile_data != NULL) { QByteArray profile_rawdata; profile_rawdata.resize(profile_size); memcpy(profile_rawdata.data(), profile_data, profile_size); imageSetIccProfile(IccProfile(profile_rawdata)); free(profile_data); } else { // If ICC profile is null, check Exif metadata. checkExifWorkingColorSpace(); } } // ------------------------------------------------------------------- if (startedDecompress) { jpeg_finish_decompress(&cinfo); } jpeg_destroy_decompress(&cinfo); // ------------------------------------------------------------------- cleanupData->takeDest(); delete cleanupData; if (observer) { observer->progressInfo(m_image, 1.0); } imageWidth() = w; imageHeight() = h; imageData() = dest; imageSetAttribute(QLatin1String("format"), QLatin1String("JPG")); imageSetAttribute(QLatin1String("originalColorModel"), colorModel); imageSetAttribute(QLatin1String("originalBitDepth"), 8); imageSetAttribute(QLatin1String("originalSize"), originalSize); return true; } bool JPEGLoader::save(const QString& filePath, DImgLoaderObserver* const observer) { FILE* const file = fopen(QFile::encodeName(filePath).constData(), "wb"); if (!file) { return false; } struct jpeg_compress_struct cinfo; struct dimg_jpeg_error_mgr jerr; // ------------------------------------------------------------------- // JPEG error handling. cinfo.err = jpeg_std_error(&jerr); cinfo.err->error_exit = dimg_jpeg_error_exit; cinfo.err->emit_message = dimg_jpeg_emit_message; cinfo.err->output_message = dimg_jpeg_output_message; // setjmp-save cleanup class Q_DECL_HIDDEN CleanupData { public: CleanupData() : line(0), f(0) { } ~CleanupData() { deleteLine(); if (f) { fclose(f); } } void setLine(uchar* const l) { line = l; } void setFile(FILE* const file) { f = file; } void deleteLine() { delete [] line; line = 0; } public: uchar* line; FILE* f; }; CleanupData* const cleanupData = new CleanupData; cleanupData->setFile(file); // If an error occurs during writing, libjpeg will jump here if (setjmp(jerr.setjmp_buffer)) { jpeg_destroy_compress(&cinfo); delete cleanupData; return false; } // ------------------------------------------------------------------- // Set JPEG compressor instance jpeg_create_compress(&cinfo); jpeg_stdio_dest(&cinfo, file); uint& w = imageWidth(); uint& h = imageHeight(); unsigned char*& data = imageData(); // Size of image. cinfo.image_width = w; cinfo.image_height = h; // Color components of image in RGB. cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; if (quality < 0) { quality = 90; } if (quality > 100) { quality = 100; } QVariant subSamplingAttr = imageGetAttribute(QLatin1String("subsampling")); int subsampling = subSamplingAttr.isValid() ? subSamplingAttr.toInt() : 1; // Medium jpeg_set_defaults(&cinfo); // bug #149578: set horizontal and vertical chroma subsampling factor to encoder. // See this page for details: http://en.wikipedia.org/wiki/Chroma_subsampling switch (subsampling) { case 1: // 2x1, 1x1, 1x1 (4:2:2) { qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG chroma-subsampling 4:2:2"; cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; break; } case 2: // 2x2, 1x1, 1x1 (4:2:0) { qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG chroma-subsampling 4:2:0"; cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; break; } case 3: // 4x1, 1x1, 1x1 (4:1:1) { qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG chroma-subsampling 4:1:1"; cinfo.comp_info[0].h_samp_factor = 4; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; break; } default: // 1x1, 1x1, 1x1 (4:4:4) { qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG chroma-subsampling 4:4:4"; cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; break; } } jpeg_set_quality(&cinfo, quality, boolean(true)); jpeg_start_compress(&cinfo, boolean(true)); qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG quality compression value: " << quality; if (observer) { observer->progressInfo(m_image, 0.1F); } // ------------------------------------------------------------------- // Write ICC profile. QByteArray profile_rawdata = m_image->getIccProfile().data(); if (!profile_rawdata.isEmpty()) { purgeExifWorkingColorSpace(); write_icc_profile(&cinfo, (JOCTET*)profile_rawdata.data(), profile_rawdata.size()); } if (observer) { observer->progressInfo(m_image, 0.2F); } // ------------------------------------------------------------------- // Write Image data. uchar* line = new uchar[w * 3]; uchar* dstPtr = 0; uint checkPoint = 0; cleanupData->setLine(line); if (!imageSixteenBit()) // 8 bits image. { uchar* srcPtr = data; for (uint j = 0; j < h; ++j) { if (observer && j == checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { jpeg_destroy_compress(&cinfo); delete cleanupData; return false; } // use 0-20% for pseudo-progress, now fill 20-100% observer->progressInfo(m_image, 0.2 + (0.8 * (((float)j) / ((float)h)))); } dstPtr = line; for (uint i = 0; i < w; ++i) { dstPtr[2] = srcPtr[0]; // Blue dstPtr[1] = srcPtr[1]; // Green dstPtr[0] = srcPtr[2]; // Red srcPtr += 4; dstPtr += 3; } jpeg_write_scanlines(&cinfo, &line, 1); } } else { unsigned short* srcPtr = reinterpret_cast(data); for (uint j = 0; j < h; ++j) { if (observer && j == checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { jpeg_destroy_compress(&cinfo); delete cleanupData; return false; } // use 0-20% for pseudo-progress, now fill 20-100% observer->progressInfo(m_image, 0.2 + (0.8 * (((float)j) / ((float)h)))); } dstPtr = line; for (uint i = 0; i < w; ++i) { dstPtr[2] = (srcPtr[0] * 255UL) / 65535UL; // Blue dstPtr[1] = (srcPtr[1] * 255UL) / 65535UL; // Green dstPtr[0] = (srcPtr[2] * 255UL) / 65535UL; // Red srcPtr += 4; dstPtr += 3; } jpeg_write_scanlines(&cinfo, &line, 1); } } cleanupData->deleteLine(); // ------------------------------------------------------------------- jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); delete cleanupData; imageSetAttribute(QLatin1String("savedformat"), QLatin1String("JPG")); saveMetadata(filePath); return true; } bool JPEGLoader::hasAlpha() const { return false; } bool JPEGLoader::sixteenBit() const { return false; } bool JPEGLoader::isReadOnly() const { return false; } } // namespace Digikam