summaryrefslogtreecommitdiffstats
path: root/src/imageutils/jpegcontent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/imageutils/jpegcontent.cpp')
-rw-r--r--src/imageutils/jpegcontent.cpp666
1 files changed, 666 insertions, 0 deletions
diff --git a/src/imageutils/jpegcontent.cpp b/src/imageutils/jpegcontent.cpp
new file mode 100644
index 0000000..b44b14d
--- /dev/null
+++ b/src/imageutils/jpegcontent.cpp
@@ -0,0 +1,666 @@
+// vim: set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aurélien Gâteau
+
+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
+of the License, 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.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+// System
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+extern "C" {
+#include <jpeglib.h>
+#include "transupp.h"
+}
+
+// Qt
+#include <qbuffer.h>
+#include <qfile.h>
+#include <qimage.h>
+#include <qmap.h>
+#include <qwmatrix.h>
+
+// KDE
+#include <kdebug.h>
+
+// Exiv2
+#include <exiv2/exif.hpp>
+#include <exiv2/image.hpp>
+
+// Local
+#include "imageutils/imageutils.h"
+#include "imageutils/jpegcontent.h"
+#include "imageutils/jpegerrormanager.h"
+
+namespace ImageUtils {
+
+const int INMEM_DST_DELTA=4096;
+
+
+//------------------------------------------
+//
+// In-memory data source manager for libjpeg
+//
+//------------------------------------------
+struct inmem_src_mgr : public jpeg_source_mgr {
+ QByteArray* mInput;
+};
+
+void inmem_init_source(j_decompress_ptr cinfo) {
+ inmem_src_mgr* src=(inmem_src_mgr*)(cinfo->src);
+ src->next_input_byte=(const JOCTET*)( src->mInput->data() );
+ src->bytes_in_buffer=src->mInput->size();
+}
+
+/**
+ * If this function is called, it means the JPEG file is broken. We feed the
+ * decoder with fake EOI has specified in the libjpeg documentation.
+ */
+int inmem_fill_input_buffer(j_decompress_ptr cinfo) {
+ static JOCTET fakeEOI[2]={ JOCTET(0xFF), JOCTET(JPEG_EOI)};
+ kdWarning() << k_funcinfo << " Image is incomplete" << endl;
+ cinfo->src->next_input_byte=fakeEOI;
+ cinfo->src->bytes_in_buffer=2;
+ return true;
+}
+
+void inmem_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+ if (num_bytes<=0) return;
+ Q_ASSERT(num_bytes>=long(cinfo->src->bytes_in_buffer));
+ cinfo->src->next_input_byte+=num_bytes;
+ cinfo->src->bytes_in_buffer-=num_bytes;
+}
+
+void inmem_term_source(j_decompress_ptr /*cinfo*/) {
+}
+
+
+//-----------------------------------------------
+//
+// In-memory data destination manager for libjpeg
+//
+//-----------------------------------------------
+struct inmem_dest_mgr : public jpeg_destination_mgr {
+ QByteArray* mOutput;
+
+ void dump() {
+ kdDebug() << "dest_mgr:\n";
+ kdDebug() << "- next_output_byte: " << next_output_byte << endl;
+ kdDebug() << "- free_in_buffer: " << free_in_buffer << endl;
+ kdDebug() << "- output size: " << mOutput->size() << endl;
+ }
+};
+
+void inmem_init_destination(j_compress_ptr cinfo) {
+ inmem_dest_mgr* dest=(inmem_dest_mgr*)(cinfo->dest);
+ if (dest->mOutput->size()==0) {
+ bool result=dest->mOutput->resize(INMEM_DST_DELTA);
+ Q_ASSERT(result);
+ }
+ dest->free_in_buffer=dest->mOutput->size();
+ dest->next_output_byte=(JOCTET*)(dest->mOutput->data() );
+}
+
+int inmem_empty_output_buffer(j_compress_ptr cinfo) {
+ inmem_dest_mgr* dest=(inmem_dest_mgr*)(cinfo->dest);
+ bool result=dest->mOutput->resize(dest->mOutput->size() + INMEM_DST_DELTA);
+ Q_ASSERT(result);
+ dest->next_output_byte=(JOCTET*)( dest->mOutput->data() + dest->mOutput->size() - INMEM_DST_DELTA );
+ dest->free_in_buffer=INMEM_DST_DELTA;
+
+ return true;
+}
+
+void inmem_term_destination(j_compress_ptr cinfo) {
+ inmem_dest_mgr* dest=(inmem_dest_mgr*)(cinfo->dest);
+ int finalSize=dest->next_output_byte - (JOCTET*)(dest->mOutput->data());
+ Q_ASSERT(finalSize>=0);
+ dest->mOutput->resize(finalSize);
+}
+
+
+//---------------------
+//
+// JPEGContent::Private
+//
+//---------------------
+struct JPEGContent::Private {
+ QByteArray mRawData;
+ QSize mSize;
+ QString mComment;
+ QString mAperture;
+ QString mExposureTime;
+ QString mFocalLength;
+ QString mIso;
+
+ bool mPendingTransformation;
+ QWMatrix mTransformMatrix;
+ Exiv2::ExifData mExifData;
+
+ Private() {
+ mPendingTransformation = false;
+ }
+
+ void setupInmemSource(j_decompress_ptr cinfo) {
+ Q_ASSERT(!cinfo->src);
+ inmem_src_mgr* src = (inmem_src_mgr*)
+ (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
+ sizeof(inmem_src_mgr));
+ cinfo->src=(struct jpeg_source_mgr*)(src);
+
+ src->init_source=inmem_init_source;
+ src->fill_input_buffer=inmem_fill_input_buffer;
+ src->skip_input_data=inmem_skip_input_data;
+ src->resync_to_restart=jpeg_resync_to_restart;
+ src->term_source=inmem_term_source;
+
+ src->mInput=&mRawData;
+ }
+
+
+ void setupInmemDestination(j_compress_ptr cinfo, QByteArray* outputData) {
+ Q_ASSERT(!cinfo->dest);
+ inmem_dest_mgr* dest = (inmem_dest_mgr*)
+ (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
+ sizeof(inmem_dest_mgr));
+ cinfo->dest=(struct jpeg_destination_mgr*)(dest);
+
+ dest->init_destination=inmem_init_destination;
+ dest->empty_output_buffer=inmem_empty_output_buffer;
+ dest->term_destination=inmem_term_destination;
+
+ dest->mOutput=outputData;
+ }
+ bool readSize() {
+ struct jpeg_decompress_struct srcinfo;
+ jpeg_saved_marker_ptr mark;
+
+ // Init JPEG structs
+ JPEGErrorManager errorManager;
+
+ // Initialize the JPEG decompression object
+ srcinfo.err = &errorManager;
+ jpeg_create_decompress(&srcinfo);
+ if (setjmp(errorManager.jmp_buffer)) {
+ kdError() << k_funcinfo << "libjpeg fatal error\n";
+ return false;
+ }
+
+ // Specify data source for decompression
+ setupInmemSource(&srcinfo);
+
+ // Read the header
+ jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL);
+ int result=jpeg_read_header(&srcinfo, true);
+ if (result!=JPEG_HEADER_OK) {
+ kdError() << "Could not read jpeg header\n";
+ jpeg_destroy_decompress(&srcinfo);
+ return false;
+ }
+ mSize=QSize(srcinfo.image_width, srcinfo.image_height);
+
+ jpeg_destroy_decompress(&srcinfo);
+ return true;
+ }
+};
+
+
+//------------
+//
+// JPEGContent
+//
+//------------
+JPEGContent::JPEGContent() {
+ d=new JPEGContent::Private();
+}
+
+
+JPEGContent::~JPEGContent() {
+ delete d;
+}
+
+
+bool JPEGContent::load(const QString& path) {
+ QFile file(path);
+ if (!file.open(IO_ReadOnly)) {
+ kdError() << "Could not open '" << path << "' for reading\n";
+ return false;
+ }
+ return loadFromData(file.readAll());
+}
+
+
+bool JPEGContent::loadFromData(const QByteArray& data) {
+ d->mPendingTransformation = false;
+ d->mTransformMatrix.reset();
+
+ d->mRawData = data;
+ if (d->mRawData.size()==0) {
+ kdError() << "No data\n";
+ return false;
+ }
+
+ if (!d->readSize()) return false;
+
+ Exiv2::Image::AutoPtr image;
+ try {
+ image = Exiv2::ImageFactory::open((unsigned char*)data.data(), data.size());
+ image->readMetadata();
+ } catch (Exiv2::Error&) {
+ kdError() << "Could not load image with Exiv2\n";
+ return false;
+ }
+
+ d->mExifData = image->exifData();
+ d->mComment = QString::fromUtf8( image->comment().c_str() );
+
+ d->mAperture=aperture();
+ d->mExposureTime=exposureTime();
+ d->mIso=iso();
+ d->mFocalLength=iso();
+
+ // Adjust the size according to the orientation
+ switch (orientation()) {
+ case TRANSPOSE:
+ case ROT_90:
+ case TRANSVERSE:
+ case ROT_270:
+ d->mSize.transpose();
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+
+Orientation JPEGContent::orientation() const {
+ Exiv2::ExifKey key("Exif.Image.Orientation");
+ Exiv2::ExifData::iterator it = d->mExifData.findKey(key);
+ if (it == d->mExifData.end()) {
+ return NOT_AVAILABLE;
+ }
+ return Orientation( it->toLong() );
+}
+
+
+int JPEGContent::dotsPerMeterX() const {
+ return dotsPerMeter("XResolution");
+}
+
+
+int JPEGContent::dotsPerMeterY() const {
+ return dotsPerMeter("YResolution");
+}
+
+
+int JPEGContent::dotsPerMeter(const QString& keyName) const {
+ Exiv2::ExifKey keyResUnit("Exif.Image.ResolutionUnit");
+ Exiv2::ExifData::iterator it = d->mExifData.findKey(keyResUnit);
+ if (it == d->mExifData.end()) {
+ return 0;
+ }
+ int res = it->toLong();
+ QString keyVal = "Exif.Image." + keyName;
+ Exiv2::ExifKey keyResolution(keyVal.ascii());
+ it = d->mExifData.findKey(keyResolution);
+ if (it == d->mExifData.end()) {
+ return 0;
+ }
+ // The unit for measuring XResolution and YResolution. The same unit is used for both XResolution and YResolution.
+ // If the image resolution in unknown, 2 (inches) is designated.
+ // Default = 2
+ // 2 = inches
+ // 3 = centimeters
+ // Other = reserved
+ const float INCHESPERMETER = (100. / 2.54);
+ Exiv2::Rational r = it->toRational();
+ if (r.second == 0) {
+ // a rational with 0 as second will make hang toLong() conversion
+ r.second = 1;
+ }
+ switch (res) {
+ case 3: // dots per cm
+ return int(float(r.first) * 100 / float(r.second));
+ default: // dots per inch
+ return int(float(r.first) * INCHESPERMETER / float(r.second));
+ }
+
+ return 0;
+}
+
+
+void JPEGContent::resetOrientation() {
+ Exiv2::ExifKey key("Exif.Image.Orientation");
+ Exiv2::ExifData::iterator it = d->mExifData.findKey(key);
+ if (it == d->mExifData.end()) {
+ return;
+ }
+
+ *it = uint16_t(ImageUtils::NORMAL);
+}
+
+
+QSize JPEGContent::size() const {
+ return d->mSize;
+}
+
+
+QString JPEGContent::comment() const {
+ return d->mComment;
+}
+
+QString JPEGContent::getExifInformation(const QString exifkey) const {
+ QString ret;
+
+ Exiv2::ExifKey key(exifkey.latin1());
+ Exiv2::ExifData::iterator it = d->mExifData.findKey(key);
+
+ if (it != d->mExifData.end()) {
+ std::ostringstream outputString;
+ outputString << *it;
+ ret=QString(outputString.str().c_str());
+ }
+ else {
+ ret="n/a";
+ }
+ return ret;
+}
+
+QString JPEGContent::aperture() const {
+ d->mAperture=getExifInformation("Exif.Photo.FNumber");
+ return d->mAperture;
+}
+
+QString JPEGContent::exposureTime() const {
+ d->mExposureTime=getExifInformation("Exif.Photo.ExposureTime");
+ return d->mExposureTime;
+}
+
+QString JPEGContent::iso() const {
+ d->mIso=getExifInformation("Exif.Photo.ISOSpeedRatings");
+ return d->mIso;
+}
+
+QString JPEGContent::focalLength() const {
+ d->mFocalLength=getExifInformation("Exif.Photo.FocalLength");
+ return d->mFocalLength;
+}
+
+void JPEGContent::setComment(const QString& comment) {
+ d->mComment = comment;
+}
+
+static QWMatrix createRotMatrix(int angle) {
+ QWMatrix matrix;
+ matrix.rotate(angle);
+ return matrix;
+}
+
+
+static QWMatrix createScaleMatrix(int dx, int dy) {
+ QWMatrix matrix;
+ matrix.scale(dx, dy);
+ return matrix;
+}
+
+
+
+struct OrientationInfo {
+ OrientationInfo() {}
+ OrientationInfo(Orientation o, QWMatrix m, JXFORM_CODE j)
+ : orientation(o), matrix(m), jxform(j) {}
+
+ Orientation orientation;
+ QWMatrix matrix;
+ JXFORM_CODE jxform;
+};
+typedef QValueList<OrientationInfo> OrientationInfoList;
+
+static const OrientationInfoList& orientationInfoList() {
+ static OrientationInfoList list;
+ if (list.size() == 0) {
+ QWMatrix rot90 = createRotMatrix(90);
+ QWMatrix hflip = createScaleMatrix(-1, 1);
+ QWMatrix vflip = createScaleMatrix(1, -1);
+
+ list
+ << OrientationInfo(NOT_AVAILABLE, QWMatrix(), JXFORM_NONE)
+ << OrientationInfo(NORMAL, QWMatrix(), JXFORM_NONE)
+ << OrientationInfo(HFLIP, hflip, JXFORM_FLIP_H)
+ << OrientationInfo(ROT_180, createRotMatrix(180), JXFORM_ROT_180)
+ << OrientationInfo(VFLIP, vflip, JXFORM_FLIP_V)
+ << OrientationInfo(TRANSPOSE, hflip * rot90, JXFORM_TRANSPOSE)
+ << OrientationInfo(ROT_90, rot90, JXFORM_ROT_90)
+ << OrientationInfo(TRANSVERSE, vflip * rot90, JXFORM_TRANSVERSE)
+ << OrientationInfo(ROT_270, createRotMatrix(270), JXFORM_ROT_270)
+ ;
+ }
+ return list;
+}
+
+
+void JPEGContent::transform(Orientation orientation) {
+ if (orientation != NOT_AVAILABLE && orientation != NORMAL) {
+ d->mPendingTransformation = true;
+ OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end());
+ for (; it!=end; ++it) {
+ if ( (*it).orientation == orientation ) {
+ d->mTransformMatrix = (*it).matrix * d->mTransformMatrix;
+ break;
+ }
+ }
+ if (it == end) {
+ kdWarning() << k_funcinfo << "Could not find matrix for orientation\n";
+ }
+ }
+}
+
+
+#if 0
+static void dumpMatrix(const QWMatrix& matrix) {
+ kdDebug() << "matrix | " << matrix.m11() << ", " << matrix.m12() << " |\n";
+ kdDebug() << " | " << matrix.m21() << ", " << matrix.m22() << " |\n";
+ kdDebug() << " ( " << matrix.dx() << ", " << matrix.dy() << " )\n";
+}
+#endif
+
+
+static bool matricesAreSame(const QWMatrix& m1, const QWMatrix& m2, double tolerance) {
+ return fabs( m1.m11() - m2.m11() ) < tolerance
+ && fabs( m1.m12() - m2.m12() ) < tolerance
+ && fabs( m1.m21() - m2.m21() ) < tolerance
+ && fabs( m1.m22() - m2.m22() ) < tolerance
+ && fabs( m1.dx() - m2.dx() ) < tolerance
+ && fabs( m1.dy() - m2.dy() ) < tolerance;
+}
+
+
+static JXFORM_CODE findJxform(const QWMatrix& matrix) {
+ OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end());
+ for (; it!=end; ++it) {
+ if ( matricesAreSame( (*it).matrix, matrix, 0.001) ) {
+ return (*it).jxform;
+ }
+ }
+ kdWarning() << "findJxform: failed\n";
+ return JXFORM_NONE;
+}
+
+
+void JPEGContent::applyPendingTransformation() {
+ if (d->mRawData.size()==0) {
+ kdError() << "No data loaded\n";
+ return;
+ }
+
+ // The following code is inspired by jpegtran.c from the libjpeg
+
+ // Init JPEG structs
+ struct jpeg_decompress_struct srcinfo;
+ struct jpeg_compress_struct dstinfo;
+ jvirt_barray_ptr * src_coef_arrays;
+ jvirt_barray_ptr * dst_coef_arrays;
+
+ // Initialize the JPEG decompression object
+ JPEGErrorManager srcErrorManager;
+ srcinfo.err = &srcErrorManager;
+ jpeg_create_decompress(&srcinfo);
+ if (setjmp(srcErrorManager.jmp_buffer)) {
+ kdError() << k_funcinfo << "libjpeg error in src\n";
+ return;
+ }
+
+ // Initialize the JPEG compression object
+ JPEGErrorManager dstErrorManager;
+ dstinfo.err = &dstErrorManager;
+ jpeg_create_compress(&dstinfo);
+ if (setjmp(dstErrorManager.jmp_buffer)) {
+ kdError() << k_funcinfo << "libjpeg error in dst\n";
+ return;
+ }
+
+ // Specify data source for decompression
+ d->setupInmemSource(&srcinfo);
+
+ // Enable saving of extra markers that we want to copy
+ jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL);
+
+ (void) jpeg_read_header(&srcinfo, TRUE);
+
+ // Init transformation
+ jpeg_transform_info transformoption;
+ transformoption.transform = findJxform(d->mTransformMatrix);
+ transformoption.force_grayscale = false;
+ transformoption.trim = false;
+ jtransform_request_workspace(&srcinfo, &transformoption);
+
+ /* Read source file as DCT coefficients */
+ src_coef_arrays = jpeg_read_coefficients(&srcinfo);
+
+ /* Initialize destination compression parameters from source values */
+ jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
+
+ /* Adjust destination parameters if required by transform options;
+ * also find out which set of coefficient arrays will hold the output.
+ */
+ dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo,
+ src_coef_arrays,
+ &transformoption);
+
+ /* Specify data destination for compression */
+ QByteArray output;
+ output.resize(d->mRawData.size());
+ d->setupInmemDestination(&dstinfo, &output);
+
+ /* Start compressor (note no image data is actually written here) */
+ jpeg_write_coefficients(&dstinfo, dst_coef_arrays);
+
+ /* Copy to the output file any extra markers that we want to preserve */
+ jcopy_markers_execute(&srcinfo, &dstinfo, JCOPYOPT_ALL);
+
+ /* Execute image transformation, if any */
+ jtransform_execute_transformation(&srcinfo, &dstinfo,
+ src_coef_arrays,
+ &transformoption);
+
+ /* Finish compression and release memory */
+ jpeg_finish_compress(&dstinfo);
+ jpeg_destroy_compress(&dstinfo);
+ (void) jpeg_finish_decompress(&srcinfo);
+ jpeg_destroy_decompress(&srcinfo);
+
+ // Set rawData to our new JPEG
+ d->mRawData = output;
+}
+
+
+QImage JPEGContent::thumbnail() const {
+ QImage image;
+ if (!d->mExifData.empty()) {
+ Exiv2::DataBuf thumbnail = d->mExifData.copyThumbnail();
+ image.loadFromData(thumbnail.pData_, thumbnail.size_);
+ }
+ return image;
+}
+
+
+void JPEGContent::setThumbnail(const QImage& thumbnail) {
+ if (d->mExifData.empty()) {
+ return;
+ }
+
+ QByteArray array;
+ QBuffer buffer(array);
+ buffer.open(IO_WriteOnly);
+ QImageIO iio(&buffer, "JPEG");
+ iio.setImage(thumbnail);
+ if (!iio.write()) {
+ kdError() << "Could not write thumbnail\n";
+ return;
+ }
+
+ d->mExifData.setJpegThumbnail((unsigned char*)array.data(), array.size());
+}
+
+
+bool JPEGContent::save(const QString& path) {
+ QFile file(path);
+ if (!file.open(IO_WriteOnly)) {
+ kdError() << "Could not open '" << path << "' for writing\n";
+ return false;
+ }
+
+ return save(&file);
+}
+
+
+bool JPEGContent::save(QFile* file) {
+ if (d->mRawData.size()==0) {
+ kdError() << "No data to store in '" << file->name() << "'\n";
+ return false;
+ }
+
+ if (d->mPendingTransformation) {
+ applyPendingTransformation();
+ d->mPendingTransformation = false;
+ }
+
+ Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((unsigned char*)d->mRawData.data(), d->mRawData.size());
+
+ // Store Exif info
+ image->setExifData(d->mExifData);
+ image->setComment(d->mComment.utf8().data());
+ image->writeMetadata();
+
+ // Update mRawData
+ Exiv2::BasicIo& io = image->io();
+ d->mRawData.resize(io.size());
+ io.read((unsigned char*)d->mRawData.data(), io.size());
+
+ QDataStream stream(file);
+ stream.writeRawBytes(d->mRawData.data(), d->mRawData.size());
+
+ // Make sure we are up to date
+ loadFromData(d->mRawData);
+ return true;
+}
+
+
+} // namespace