File indexing completed on 2024-04-28 04:18:51
0001 // vim: set tabstop=4 shiftwidth=4 expandtab: 0002 /* 0003 Gwenview: an image viewer 0004 Copyright 2007 Aurélien Gâteau <agateau@kde.org> 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License 0008 as published by the Free Software Foundation; either version 2 0009 of the License, or (at your option) any later version. 0010 0011 This program is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 GNU General Public License for more details. 0015 0016 You should have received a copy of the GNU General Public License 0017 along with this program; if not, write to the Free Software 0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0019 0020 */ 0021 #include "jpegcontent.h" 0022 0023 // System 0024 #include <cmath> 0025 #include <cstdio> 0026 #include <cstdlib> 0027 #include <cstring> 0028 0029 extern "C" { 0030 #include <jpeglib.h> // Must be included before transupp.h 0031 0032 #include "transupp.h" 0033 } 0034 0035 // Qt 0036 #include <QBuffer> 0037 #include <QFile> 0038 #include <QImage> 0039 #include <QImageWriter> 0040 #include <QTransform> 0041 0042 // KF 0043 #include <KLocalizedString> 0044 0045 // Exiv2 0046 #include <exiv2/exiv2.hpp> 0047 0048 // Local 0049 #include "exiv2imageloader.h" 0050 #include "gwenview_lib_debug.h" 0051 #include "gwenviewconfig.h" 0052 #include "imageutils.h" 0053 #include "iodevicejpegsourcemanager.h" 0054 #include "jpegerrormanager.h" 0055 0056 namespace Gwenview 0057 { 0058 const int INMEM_DST_DELTA = 4096; 0059 0060 //----------------------------------------------- 0061 // 0062 // In-memory data destination manager for libjpeg 0063 // 0064 //----------------------------------------------- 0065 struct inmem_dest_mgr : public jpeg_destination_mgr { 0066 QByteArray *mOutput; 0067 0068 void dump() 0069 { 0070 qCDebug(GWENVIEW_LIB_LOG) << "dest_mgr:\n"; 0071 qCDebug(GWENVIEW_LIB_LOG) << "- next_output_byte: " << next_output_byte; 0072 qCDebug(GWENVIEW_LIB_LOG) << "- free_in_buffer: " << free_in_buffer; 0073 qCDebug(GWENVIEW_LIB_LOG) << "- output size: " << mOutput->size(); 0074 } 0075 }; 0076 0077 void inmem_init_destination(j_compress_ptr cinfo) 0078 { 0079 auto dest = (inmem_dest_mgr *)(cinfo->dest); 0080 if (dest->mOutput->size() == 0) { 0081 dest->mOutput->resize(INMEM_DST_DELTA); 0082 } 0083 dest->free_in_buffer = dest->mOutput->size(); 0084 dest->next_output_byte = (JOCTET *)(dest->mOutput->data()); 0085 } 0086 0087 boolean inmem_empty_output_buffer(j_compress_ptr cinfo) 0088 { 0089 auto dest = (inmem_dest_mgr *)(cinfo->dest); 0090 dest->mOutput->resize(dest->mOutput->size() + INMEM_DST_DELTA); 0091 dest->next_output_byte = (JOCTET *)(dest->mOutput->data() + dest->mOutput->size() - INMEM_DST_DELTA); 0092 dest->free_in_buffer = INMEM_DST_DELTA; 0093 0094 return true; 0095 } 0096 0097 void inmem_term_destination(j_compress_ptr cinfo) 0098 { 0099 auto dest = (inmem_dest_mgr *)(cinfo->dest); 0100 int finalSize = dest->next_output_byte - (JOCTET *)(dest->mOutput->data()); 0101 Q_ASSERT(finalSize >= 0); 0102 dest->mOutput->resize(finalSize); 0103 } 0104 0105 //--------------------- 0106 // 0107 // JpegContent::Private 0108 // 0109 //--------------------- 0110 struct JpegContent::Private { 0111 // JpegContent usually stores the image pixels as compressed JPEG data in 0112 // mRawData. However if the image is set with setImage() because the user 0113 // performed a lossy image manipulation, mRawData is cleared and the image 0114 // pixels are kept in mImage until updateRawDataFromImage() is called. 0115 QImage mImage; 0116 0117 // Store the input file, keep it open readOnly. This allows the file to be memory mapped 0118 // (i.e. mRawData may point to mFile.map()) rather than completely read on load. Postpone 0119 // QFile::readAll() as long as possible (currently in save()). 0120 QFile mFile; 0121 QByteArray mRawData; 0122 0123 QSize mSize; 0124 QString mComment; 0125 bool mPendingTransformation; 0126 QTransform mTransformMatrix; 0127 Exiv2::ExifData mExifData; 0128 QString mErrorString; 0129 0130 Private() 0131 { 0132 mPendingTransformation = false; 0133 } 0134 0135 void setupInmemDestination(j_compress_ptr cinfo, QByteArray *outputData) 0136 { 0137 Q_ASSERT(!cinfo->dest); 0138 auto dest = (inmem_dest_mgr *)(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(inmem_dest_mgr)); 0139 cinfo->dest = (struct jpeg_destination_mgr *)(dest); 0140 0141 dest->init_destination = inmem_init_destination; 0142 dest->empty_output_buffer = inmem_empty_output_buffer; 0143 dest->term_destination = inmem_term_destination; 0144 0145 dest->mOutput = outputData; 0146 } 0147 bool readSize() 0148 { 0149 struct jpeg_decompress_struct srcinfo; 0150 0151 // Init JPEG structs 0152 JPEGErrorManager errorManager; 0153 0154 // Initialize the JPEG decompression object 0155 srcinfo.err = &errorManager; 0156 jpeg_create_decompress(&srcinfo); 0157 if (setjmp(errorManager.jmp_buffer)) { 0158 qCCritical(GWENVIEW_LIB_LOG) << "libjpeg fatal error\n"; 0159 return false; 0160 } 0161 0162 // Specify data source for decompression 0163 QBuffer buffer(&mRawData); 0164 buffer.open(QIODevice::ReadOnly); 0165 IODeviceJpegSourceManager::setup(&srcinfo, &buffer); 0166 0167 // Read the header 0168 jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL); 0169 int result = jpeg_read_header(&srcinfo, true); 0170 if (result != JPEG_HEADER_OK) { 0171 qCCritical(GWENVIEW_LIB_LOG) << "Could not read jpeg header\n"; 0172 jpeg_destroy_decompress(&srcinfo); 0173 return false; 0174 } 0175 mSize = QSize(srcinfo.image_width, srcinfo.image_height); 0176 0177 jpeg_destroy_decompress(&srcinfo); 0178 return true; 0179 } 0180 0181 bool updateRawDataFromImage() 0182 { 0183 QBuffer buffer; 0184 QImageWriter writer(&buffer, "jpeg"); 0185 writer.setQuality(GwenviewConfig::jPEGQuality()); 0186 if (!writer.write(mImage)) { 0187 mErrorString = writer.errorString(); 0188 return false; 0189 } 0190 mRawData = buffer.data(); 0191 mImage = QImage(); 0192 return true; 0193 } 0194 }; 0195 0196 //------------ 0197 // 0198 // JpegContent 0199 // 0200 //------------ 0201 JpegContent::JpegContent() 0202 { 0203 d = new JpegContent::Private(); 0204 } 0205 0206 JpegContent::~JpegContent() 0207 { 0208 delete d; 0209 } 0210 0211 bool JpegContent::load(const QString &path) 0212 { 0213 if (d->mFile.isOpen()) { 0214 d->mFile.unmap(reinterpret_cast<unsigned char *>(d->mRawData.data())); 0215 d->mFile.close(); 0216 d->mRawData.clear(); 0217 } 0218 0219 d->mFile.setFileName(path); 0220 if (!d->mFile.open(QIODevice::ReadOnly)) { 0221 qCCritical(GWENVIEW_LIB_LOG) << "Could not open '" << path << "' for reading\n"; 0222 return false; 0223 } 0224 0225 QByteArray rawData; 0226 uchar *mappedFile = d->mFile.map(0, d->mFile.size(), QFileDevice::MapPrivateOption); 0227 if (mappedFile == nullptr) { 0228 // process' mapping limit exceeded, file is sealed or filesystem doesn't support it, etc. 0229 qCDebug(GWENVIEW_LIB_LOG) << "Could not mmap '" << path << "', falling back to QFile::readAll()\n"; 0230 0231 rawData = d->mFile.readAll(); 0232 // all read in, no need to keep it open 0233 d->mFile.close(); 0234 } else { 0235 rawData = QByteArray::fromRawData(reinterpret_cast<char *>(mappedFile), d->mFile.size()); 0236 } 0237 0238 return loadFromData(rawData); 0239 } 0240 0241 bool JpegContent::loadFromData(const QByteArray &data) 0242 { 0243 std::unique_ptr<Exiv2::Image> image; 0244 Exiv2ImageLoader loader; 0245 if (!loader.load(data)) { 0246 qCCritical(GWENVIEW_LIB_LOG) << "Could not load image with Exiv2, reported error:" << loader.errorMessage(); 0247 } 0248 image.reset(loader.popImage().release()); 0249 0250 return loadFromData(data, image.get()); 0251 } 0252 0253 bool JpegContent::loadFromData(const QByteArray &data, Exiv2::Image *exiv2Image) 0254 { 0255 d->mPendingTransformation = false; 0256 d->mTransformMatrix.reset(); 0257 0258 d->mRawData = data; 0259 if (d->mRawData.size() == 0) { 0260 qCCritical(GWENVIEW_LIB_LOG) << "No data\n"; 0261 return false; 0262 } 0263 0264 if (!d->readSize()) 0265 return false; 0266 0267 d->mExifData = exiv2Image->exifData(); 0268 d->mComment = QString::fromUtf8(exiv2Image->comment().c_str()); 0269 0270 if (!GwenviewConfig::applyExifOrientation()) { 0271 return true; 0272 } 0273 0274 // Adjust the size according to the orientation 0275 switch (orientation()) { 0276 case TRANSPOSE: 0277 case ROT_90: 0278 case TRANSVERSE: 0279 case ROT_270: 0280 d->mSize.transpose(); 0281 break; 0282 default: 0283 break; 0284 } 0285 0286 return true; 0287 } 0288 0289 QByteArray JpegContent::rawData() const 0290 { 0291 return d->mRawData; 0292 } 0293 0294 Orientation JpegContent::orientation() const 0295 { 0296 Exiv2::ExifKey key("Exif.Image.Orientation"); 0297 auto it = d->mExifData.findKey(key); 0298 0299 // We do the same checks as in libexiv2's src/crwimage.cpp: 0300 // https://github.com/Exiv2/exiv2/blob/0d397b95c7b4a10819c0ea0f36fa20943e6a4ea5/src/crwimage.cpp#L1336 0301 if (it == d->mExifData.end() || it->count() == 0 || it->typeId() != Exiv2::unsignedShort) { 0302 return NOT_AVAILABLE; 0303 } 0304 #if EXIV2_TEST_VERSION(0, 28, 0) 0305 return Orientation(it->toUint32()); 0306 #else 0307 return Orientation(it->toLong()); 0308 #endif 0309 } 0310 0311 int JpegContent::dotsPerMeterX() const 0312 { 0313 return dotsPerMeter(QStringLiteral("XResolution")); 0314 } 0315 0316 int JpegContent::dotsPerMeterY() const 0317 { 0318 return dotsPerMeter(QStringLiteral("YResolution")); 0319 } 0320 0321 int JpegContent::dotsPerMeter(const QString &keyName) const 0322 { 0323 Exiv2::ExifKey keyResUnit("Exif.Image.ResolutionUnit"); 0324 auto it = d->mExifData.findKey(keyResUnit); 0325 if (it == d->mExifData.end()) { 0326 return 0; 0327 } 0328 #if EXIV2_TEST_VERSION(0, 28, 0) 0329 int res = it->toUint32(); 0330 #else 0331 int res = it->toLong(); 0332 #endif 0333 QString keyVal = QStringLiteral("Exif.Image.") + keyName; 0334 Exiv2::ExifKey keyResolution(keyVal.toLocal8Bit().data()); 0335 it = d->mExifData.findKey(keyResolution); 0336 if (it == d->mExifData.end()) { 0337 return 0; 0338 } 0339 // The unit for measuring XResolution and YResolution. The same unit is used for both XResolution and YResolution. 0340 // If the image resolution in unknown, 2 (inches) is designated. 0341 // Default = 2 0342 // 2 = inches 0343 // 3 = centimeters 0344 // Other = reserved 0345 const float INCHESPERMETER = (100. / 2.54); 0346 switch (res) { 0347 case 3: // dots per cm 0348 #if EXIV2_TEST_VERSION(0, 28, 0) 0349 return int(it->toUint32() * 100); 0350 #else 0351 return int(it->toLong() * 100); 0352 #endif 0353 default: // dots per inch 0354 #if EXIV2_TEST_VERSION(0, 28, 0) 0355 return int(it->toUint32() * INCHESPERMETER); 0356 #else 0357 return int(it->toLong() * INCHESPERMETER); 0358 #endif 0359 } 0360 0361 return 0; 0362 } 0363 0364 void JpegContent::resetOrientation() 0365 { 0366 Exiv2::ExifKey key("Exif.Image.Orientation"); 0367 auto it = d->mExifData.findKey(key); 0368 if (it == d->mExifData.end()) { 0369 return; 0370 } 0371 0372 *it = uint16_t(NORMAL); 0373 } 0374 0375 QSize JpegContent::size() const 0376 { 0377 return d->mSize; 0378 } 0379 0380 QString JpegContent::comment() const 0381 { 0382 return d->mComment; 0383 } 0384 0385 void JpegContent::setComment(const QString &comment) 0386 { 0387 d->mComment = comment; 0388 } 0389 0390 static QTransform createRotMatrix(int angle) 0391 { 0392 QTransform matrix; 0393 matrix.rotate(angle); 0394 return matrix; 0395 } 0396 0397 static QTransform createScaleMatrix(int dx, int dy) 0398 { 0399 QTransform matrix; 0400 matrix.scale(dx, dy); 0401 return matrix; 0402 } 0403 0404 struct OrientationInfo { 0405 OrientationInfo() 0406 : orientation(NOT_AVAILABLE) 0407 , jxform(JXFORM_NONE) 0408 { 0409 } 0410 0411 OrientationInfo(Orientation o, const QTransform &m, JXFORM_CODE j) 0412 : orientation(o) 0413 , matrix(m) 0414 , jxform(j) 0415 { 0416 } 0417 0418 Orientation orientation; 0419 QTransform matrix; 0420 JXFORM_CODE jxform; 0421 }; 0422 using OrientationInfoList = QList<OrientationInfo>; 0423 0424 static const OrientationInfoList &orientationInfoList() 0425 { 0426 static OrientationInfoList list; 0427 if (list.size() == 0) { 0428 QTransform rot90 = createRotMatrix(90); 0429 QTransform hflip = createScaleMatrix(-1, 1); 0430 QTransform vflip = createScaleMatrix(1, -1); 0431 0432 list << OrientationInfo() << OrientationInfo(NORMAL, QTransform(), JXFORM_NONE) << OrientationInfo(HFLIP, hflip, JXFORM_FLIP_H) 0433 << OrientationInfo(ROT_180, createRotMatrix(180), JXFORM_ROT_180) << OrientationInfo(VFLIP, vflip, JXFORM_FLIP_V) 0434 << OrientationInfo(TRANSPOSE, hflip * rot90, JXFORM_TRANSPOSE) << OrientationInfo(ROT_90, rot90, JXFORM_ROT_90) 0435 << OrientationInfo(TRANSVERSE, vflip * rot90, JXFORM_TRANSVERSE) << OrientationInfo(ROT_270, createRotMatrix(270), JXFORM_ROT_270); 0436 } 0437 return list; 0438 } 0439 0440 void JpegContent::transform(Orientation orientation) 0441 { 0442 if (orientation != NOT_AVAILABLE && orientation != NORMAL) { 0443 d->mPendingTransformation = true; 0444 OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end()); 0445 for (; it != end; ++it) { 0446 if ((*it).orientation == orientation) { 0447 d->mTransformMatrix = (*it).matrix * d->mTransformMatrix; 0448 break; 0449 } 0450 } 0451 if (it == end) { 0452 qCWarning(GWENVIEW_LIB_LOG) << "Could not find matrix for orientation\n"; 0453 } 0454 } 0455 } 0456 0457 #if 0 0458 static void dumpMatrix(const QTransform& matrix) 0459 { 0460 qCDebug(GWENVIEW_LIB_LOG) << "matrix | " << matrix.m11() << ", " << matrix.m12() << " |\n"; 0461 qCDebug(GWENVIEW_LIB_LOG) << " | " << matrix.m21() << ", " << matrix.m22() << " |\n"; 0462 qCDebug(GWENVIEW_LIB_LOG) << " ( " << matrix.dx() << ", " << matrix.dy() << " )\n"; 0463 } 0464 #endif 0465 0466 static bool matricesAreSame(const QTransform &m1, const QTransform &m2, double tolerance) 0467 { 0468 return fabs(m1.m11() - m2.m11()) < tolerance && fabs(m1.m12() - m2.m12()) < tolerance && fabs(m1.m21() - m2.m21()) < tolerance 0469 && fabs(m1.m22() - m2.m22()) < tolerance && fabs(m1.dx() - m2.dx()) < tolerance && fabs(m1.dy() - m2.dy()) < tolerance; 0470 } 0471 0472 static JXFORM_CODE findJxform(const QTransform &matrix) 0473 { 0474 OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end()); 0475 for (; it != end; ++it) { 0476 if (matricesAreSame((*it).matrix, matrix, 0.001)) { 0477 return (*it).jxform; 0478 } 0479 } 0480 qCWarning(GWENVIEW_LIB_LOG) << "findJxform: failed\n"; 0481 return JXFORM_NONE; 0482 } 0483 0484 void JpegContent::applyPendingTransformation() 0485 { 0486 if (d->mRawData.size() == 0) { 0487 qCCritical(GWENVIEW_LIB_LOG) << "No data loaded\n"; 0488 return; 0489 } 0490 0491 // The following code is inspired by jpegtran.c from the libjpeg 0492 0493 // Init JPEG structs 0494 struct jpeg_decompress_struct srcinfo; 0495 struct jpeg_compress_struct dstinfo; 0496 jvirt_barray_ptr *src_coef_arrays; 0497 jvirt_barray_ptr *dst_coef_arrays; 0498 0499 // Initialize the JPEG decompression object 0500 JPEGErrorManager srcErrorManager; 0501 srcinfo.err = &srcErrorManager; 0502 jpeg_create_decompress(&srcinfo); 0503 if (setjmp(srcErrorManager.jmp_buffer)) { 0504 qCCritical(GWENVIEW_LIB_LOG) << "libjpeg error in src\n"; 0505 return; 0506 } 0507 0508 // Initialize the JPEG compression object 0509 JPEGErrorManager dstErrorManager; 0510 dstinfo.err = &dstErrorManager; 0511 jpeg_create_compress(&dstinfo); 0512 if (setjmp(dstErrorManager.jmp_buffer)) { 0513 qCCritical(GWENVIEW_LIB_LOG) << "libjpeg error in dst\n"; 0514 return; 0515 } 0516 0517 // Specify data source for decompression 0518 QBuffer buffer(&d->mRawData); 0519 buffer.open(QIODevice::ReadOnly); 0520 IODeviceJpegSourceManager::setup(&srcinfo, &buffer); 0521 0522 // Enable saving of extra markers that we want to copy 0523 jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL); 0524 0525 (void)jpeg_read_header(&srcinfo, true); 0526 0527 // Init transformation 0528 jpeg_transform_info transformoption; 0529 memset(&transformoption, 0, sizeof(jpeg_transform_info)); 0530 transformoption.transform = findJxform(d->mTransformMatrix); 0531 jtransform_request_workspace(&srcinfo, &transformoption); 0532 0533 /* Read source file as DCT coefficients */ 0534 src_coef_arrays = jpeg_read_coefficients(&srcinfo); 0535 0536 /* Initialize destination compression parameters from source values */ 0537 jpeg_copy_critical_parameters(&srcinfo, &dstinfo); 0538 0539 /* Adjust destination parameters if required by transform options; 0540 * also find out which set of coefficient arrays will hold the output. 0541 */ 0542 dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo, src_coef_arrays, &transformoption); 0543 0544 /* Specify data destination for compression */ 0545 QByteArray output; 0546 output.resize(d->mRawData.size()); 0547 d->setupInmemDestination(&dstinfo, &output); 0548 0549 /* Start compressor (note no image data is actually written here) */ 0550 jpeg_write_coefficients(&dstinfo, dst_coef_arrays); 0551 0552 /* Copy to the output file any extra markers that we want to preserve */ 0553 jcopy_markers_execute(&srcinfo, &dstinfo, JCOPYOPT_ALL); 0554 0555 /* Execute image transformation, if any */ 0556 jtransform_execute_transformation(&srcinfo, &dstinfo, src_coef_arrays, &transformoption); 0557 0558 /* Finish compression and release memory */ 0559 jpeg_finish_compress(&dstinfo); 0560 jpeg_destroy_compress(&dstinfo); 0561 (void)jpeg_finish_decompress(&srcinfo); 0562 jpeg_destroy_decompress(&srcinfo); 0563 0564 // Set rawData to our new JPEG 0565 d->mRawData = output; 0566 } 0567 0568 QImage JpegContent::thumbnail() const 0569 { 0570 QImage image; 0571 if (!d->mExifData.empty()) { 0572 #if (EXIV2_TEST_VERSION(0, 17, 91)) 0573 Exiv2::ExifThumbC thumb(d->mExifData); 0574 Exiv2::DataBuf thumbnail = thumb.copy(); 0575 #else 0576 Exiv2::DataBuf thumbnail = d->mExifData.copyThumbnail(); 0577 #endif 0578 #if (EXIV2_TEST_VERSION(0, 28, 0)) 0579 image.loadFromData(thumbnail.data(), thumbnail.size()); 0580 #else 0581 image.loadFromData(thumbnail.pData_, thumbnail.size_); 0582 #endif 0583 0584 auto it = d->mExifData.findKey(Exiv2::ExifKey("Exif.Canon.ThumbnailImageValidArea")); 0585 // ensure ThumbnailImageValidArea actually specifies a rectangle, i.e. there must be 4 coordinates 0586 if (it != d->mExifData.end() && it->count() == 4) { 0587 #if EXIV2_TEST_VERSION(0, 28, 0) 0588 QRect validArea(QPoint(it->toUint32(0), it->toUint32(2)), QPoint(it->toUint32(1), it->toUint32(3))); 0589 #else 0590 QRect validArea(QPoint(it->toLong(0), it->toLong(2)), QPoint(it->toLong(1), it->toLong(3))); 0591 #endif 0592 image = image.copy(validArea); 0593 } else { 0594 // Unfortunately, Sony does not provide an exif tag that specifies the valid area of the 0595 // embedded thumbnail. Need to derive it from the size of the preview image instead. 0596 it = d->mExifData.findKey(Exiv2::ExifKey("Exif.Sony1.PreviewImageSize")); 0597 if (it != d->mExifData.end() && it->count() == 2) { 0598 #if EXIV2_TEST_VERSION(0, 28, 0) 0599 const long prevHeight = it->toUint32(0); 0600 const long prevWidth = it->toUint32(1); 0601 #else 0602 const long prevHeight = it->toLong(0); 0603 const long prevWidth = it->toLong(1); 0604 #endif 0605 0606 if (image.width() > 0 && prevWidth > 0) { 0607 const double scale = prevWidth / image.width(); 0608 0609 // the embedded thumb only needs to be cropped vertically 0610 const long validThumbAreaHeight = ceil(prevHeight / scale); 0611 const long totalHeightOfBlackArea = image.height() - validThumbAreaHeight; 0612 // black bars on top and bottom should be equal in height 0613 const long offsetFromTop = totalHeightOfBlackArea / 2; 0614 0615 const QRect validArea(QPoint(0, offsetFromTop), QSize(image.width(), validThumbAreaHeight)); 0616 image = image.copy(validArea); 0617 } 0618 } 0619 } 0620 0621 Orientation o = orientation(); 0622 if (GwenviewConfig::applyExifOrientation() && o != NORMAL && o != NOT_AVAILABLE) { 0623 image = image.transformed(ImageUtils::transformMatrix(o)); 0624 } 0625 } 0626 return image; 0627 } 0628 0629 void JpegContent::setThumbnail(const QImage &thumbnail) 0630 { 0631 if (d->mExifData.empty()) { 0632 return; 0633 } 0634 0635 QByteArray array; 0636 QBuffer buffer(&array); 0637 buffer.open(QIODevice::WriteOnly); 0638 QImageWriter writer(&buffer, "JPEG"); 0639 if (!writer.write(thumbnail)) { 0640 qCCritical(GWENVIEW_LIB_LOG) << "Could not write thumbnail\n"; 0641 return; 0642 } 0643 0644 #if (EXIV2_TEST_VERSION(0, 17, 91)) 0645 Exiv2::ExifThumb thumb(d->mExifData); 0646 thumb.setJpegThumbnail((unsigned char *)array.data(), array.size()); 0647 #else 0648 d->mExifData.setJpegThumbnail((unsigned char *)array.data(), array.size()); 0649 #endif 0650 } 0651 0652 bool JpegContent::save(const QString &path) 0653 { 0654 // we need to take ownership of the input file's data 0655 // if the input file is still open, data is still only mem-mapped 0656 if (d->mFile.isOpen()) { 0657 // backup the mmap() pointer 0658 auto mappedFile = reinterpret_cast<unsigned char *>(d->mRawData.data()); 0659 // read the file to memory 0660 d->mRawData = d->mFile.readAll(); 0661 d->mFile.unmap(mappedFile); 0662 d->mFile.close(); 0663 } 0664 0665 QFile file(path); 0666 if (!file.open(QIODevice::WriteOnly)) { 0667 d->mErrorString = i18nc("@info", "Could not open file for writing."); 0668 return false; 0669 } 0670 0671 return save(&file); 0672 } 0673 0674 bool JpegContent::save(QIODevice *device) 0675 { 0676 if (!d->mImage.isNull()) { 0677 if (!d->updateRawDataFromImage()) { 0678 return false; 0679 } 0680 } 0681 0682 if (d->mRawData.size() == 0) { 0683 d->mErrorString = i18nc("@info", "No data to store."); 0684 return false; 0685 } 0686 0687 if (d->mPendingTransformation) { 0688 applyPendingTransformation(); 0689 d->mPendingTransformation = false; 0690 } 0691 0692 std::unique_ptr<Exiv2::Image> image; 0693 image.reset(Exiv2::ImageFactory::open((unsigned char *)d->mRawData.data(), d->mRawData.size()).release()); 0694 0695 // Store Exif info 0696 image->setExifData(d->mExifData); 0697 image->setComment(d->mComment.toUtf8().toStdString()); 0698 image->writeMetadata(); 0699 0700 // Update mRawData 0701 Exiv2::BasicIo &io = image->io(); 0702 d->mRawData.resize(io.size()); 0703 io.read((unsigned char *)d->mRawData.data(), io.size()); 0704 0705 QDataStream stream(device); 0706 stream.writeRawData(d->mRawData.data(), d->mRawData.size()); 0707 0708 // Make sure we are up to date 0709 loadFromData(d->mRawData); 0710 return true; 0711 } 0712 0713 QString JpegContent::errorString() const 0714 { 0715 return d->mErrorString; 0716 } 0717 0718 void JpegContent::setImage(const QImage &image) 0719 { 0720 d->mRawData.clear(); 0721 d->mImage = image; 0722 d->mSize = image.size(); 0723 d->mExifData["Exif.Photo.PixelXDimension"] = image.width(); 0724 d->mExifData["Exif.Photo.PixelYDimension"] = image.height(); 0725 resetOrientation(); 0726 0727 d->mPendingTransformation = false; 0728 d->mTransformMatrix = QTransform(); 0729 } 0730 0731 } // namespace