|
|
|
|
/*
|
|
|
|
|
Gwenview - A simple image viewer for TDE
|
|
|
|
|
Copyright 2000-2004 Aur<EFBFBD>lien G<EFBFBD>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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
|
|
// System
|
|
|
|
|
#include <math.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
extern "C" {
|
|
|
|
|
#include <jpeglib.h>
|
|
|
|
|
#include "transupp.h"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TQt
|
|
|
|
|
#include <tqbuffer.h>
|
|
|
|
|
#include <tqfile.h>
|
|
|
|
|
#include <tqimage.h>
|
|
|
|
|
#include <tqmap.h>
|
|
|
|
|
#include <tqwmatrix.h>
|
|
|
|
|
|
|
|
|
|
// KDE
|
|
|
|
|
#include <kdebug.h>
|
|
|
|
|
|
|
|
|
|
// Exiv2
|
|
|
|
|
#if defined(HAVE_EXIV2_EXIV2_HPP)
|
|
|
|
|
#include <exiv2/exiv2.hpp>
|
|
|
|
|
#else
|
|
|
|
|
#include <exiv2/exif.hpp>
|
|
|
|
|
#include <exiv2/image.hpp>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
|
TQByteArray* 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 {
|
|
|
|
|
TQByteArray* 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 {
|
|
|
|
|
TQByteArray mRawData;
|
|
|
|
|
TQSize mSize;
|
|
|
|
|
TQString mComment;
|
|
|
|
|
TQString mAperture;
|
|
|
|
|
TQString mExposureTime;
|
|
|
|
|
TQString mFocalLength;
|
|
|
|
|
TQString mIso;
|
|
|
|
|
|
|
|
|
|
bool mPendingTransformation;
|
|
|
|
|
TQWMatrix 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, TQByteArray* 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=TQSize(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 TQString& path) {
|
|
|
|
|
TQFile 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 TQByteArray& 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;
|
|
|
|
|
|
|
|
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
|
|
|
Exiv2::Image::UniquePtr image;
|
|
|
|
|
#else
|
|
|
|
|
Exiv2::Image::AutoPtr image;
|
|
|
|
|
#endif
|
|
|
|
|
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 = TQString::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;
|
|
|
|
|
}
|
|
|
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
|
|
|
return Orientation( it->toInt64() );
|
|
|
|
|
#else
|
|
|
|
|
return Orientation( it->toLong() );
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int JPEGContent::dotsPerMeterX() const {
|
|
|
|
|
return dotsPerMeter("XResolution");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int JPEGContent::dotsPerMeterY() const {
|
|
|
|
|
return dotsPerMeter("YResolution");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int JPEGContent::dotsPerMeter(const TQString& keyName) const {
|
|
|
|
|
Exiv2::ExifKey keyResUnit("Exif.Image.ResolutionUnit");
|
|
|
|
|
Exiv2::ExifData::iterator it = d->mExifData.findKey(keyResUnit);
|
|
|
|
|
if (it == d->mExifData.end()) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
|
|
|
int64_t res = it->toInt64();
|
|
|
|
|
#else
|
|
|
|
|
long res = it->toLong();
|
|
|
|
|
#endif
|
|
|
|
|
TQString 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TQSize JPEGContent::size() const {
|
|
|
|
|
return d->mSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TQString JPEGContent::comment() const {
|
|
|
|
|
return d->mComment;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TQString JPEGContent::getExifInformation(const TQString exifkey) const {
|
|
|
|
|
TQString 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=TQString(outputString.str().c_str());
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
ret="n/a";
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TQString JPEGContent::aperture() const {
|
|
|
|
|
d->mAperture=getExifInformation("Exif.Photo.FNumber");
|
|
|
|
|
return d->mAperture;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TQString JPEGContent::exposureTime() const {
|
|
|
|
|
d->mExposureTime=getExifInformation("Exif.Photo.ExposureTime");
|
|
|
|
|
return d->mExposureTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TQString JPEGContent::iso() const {
|
|
|
|
|
d->mIso=getExifInformation("Exif.Photo.ISOSpeedRatings");
|
|
|
|
|
return d->mIso;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TQString JPEGContent::focalLength() const {
|
|
|
|
|
d->mFocalLength=getExifInformation("Exif.Photo.FocalLength");
|
|
|
|
|
return d->mFocalLength;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void JPEGContent::setComment(const TQString& comment) {
|
|
|
|
|
d->mComment = comment;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static TQWMatrix createRotMatrix(int angle) {
|
|
|
|
|
TQWMatrix matrix;
|
|
|
|
|
matrix.rotate(angle);
|
|
|
|
|
return matrix;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static TQWMatrix createScaleMatrix(int dx, int dy) {
|
|
|
|
|
TQWMatrix matrix;
|
|
|
|
|
matrix.scale(dx, dy);
|
|
|
|
|
return matrix;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct OrientationInfo {
|
|
|
|
|
OrientationInfo() {}
|
|
|
|
|
OrientationInfo(Orientation o, TQWMatrix m, JXFORM_CODE j)
|
|
|
|
|
: orientation(o), matrix(m), jxform(j) {}
|
|
|
|
|
|
|
|
|
|
Orientation orientation;
|
|
|
|
|
TQWMatrix matrix;
|
|
|
|
|
JXFORM_CODE jxform;
|
|
|
|
|
};
|
|
|
|
|
typedef TQValueList<OrientationInfo> OrientationInfoList;
|
|
|
|
|
|
|
|
|
|
static const OrientationInfoList& orientationInfoList() {
|
|
|
|
|
static OrientationInfoList list;
|
|
|
|
|
if (list.size() == 0) {
|
|
|
|
|
TQWMatrix rot90 = createRotMatrix(90);
|
|
|
|
|
TQWMatrix hflip = createScaleMatrix(-1, 1);
|
|
|
|
|
TQWMatrix vflip = createScaleMatrix(1, -1);
|
|
|
|
|
|
|
|
|
|
list
|
|
|
|
|
<< OrientationInfo(NOT_AVAILABLE, TQWMatrix(), JXFORM_NONE)
|
|
|
|
|
<< OrientationInfo(NORMAL, TQWMatrix(), 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 TQWMatrix& 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 TQWMatrix& m1, const TQWMatrix& 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 TQWMatrix& 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;
|
|
|
|
|
memset(&transformoption, 0, sizeof(jpeg_transform_info));
|
|
|
|
|
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 */
|
|
|
|
|
TQByteArray 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TQImage JPEGContent::thumbnail() const {
|
|
|
|
|
TQImage image;
|
|
|
|
|
if (!d->mExifData.empty()) {
|
|
|
|
|
Exiv2::ExifThumbC thumb(d->mExifData);
|
|
|
|
|
Exiv2::DataBuf const thumbnail = thumb.copy();
|
|
|
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
|
|
|
image.loadFromData(thumbnail.c_data(), thumbnail.size());
|
|
|
|
|
#else
|
|
|
|
|
image.loadFromData(thumbnail.pData_, thumbnail.size_);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
return image;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void JPEGContent::setThumbnail(const TQImage& thumbnail) {
|
|
|
|
|
if (d->mExifData.empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TQByteArray array;
|
|
|
|
|
TQBuffer buffer(array);
|
|
|
|
|
buffer.open(IO_WriteOnly);
|
|
|
|
|
TQImageIO iio(&buffer, "JPEG");
|
|
|
|
|
iio.setImage(thumbnail);
|
|
|
|
|
if (!iio.write()) {
|
|
|
|
|
kdError() << "Could not write thumbnail\n";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Exiv2::ExifThumb thumb(d->mExifData);
|
|
|
|
|
thumb.setJpegThumbnail((unsigned char*)array.data(), array.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool JPEGContent::save(const TQString& path) {
|
|
|
|
|
TQFile file(path);
|
|
|
|
|
if (!file.open(IO_WriteOnly)) {
|
|
|
|
|
kdError() << "Could not open '" << path << "' for writing\n";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return save(&file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool JPEGContent::save(TQFile* file) {
|
|
|
|
|
if (d->mRawData.size()==0) {
|
|
|
|
|
kdError() << "No data to store in '" << file->name() << "'\n";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (d->mPendingTransformation) {
|
|
|
|
|
applyPendingTransformation();
|
|
|
|
|
d->mPendingTransformation = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if (EXIV2_TEST_VERSION(0,28,0))
|
|
|
|
|
Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open((unsigned char*)d->mRawData.data(), d->mRawData.size());
|
|
|
|
|
#else
|
|
|
|
|
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((unsigned char*)d->mRawData.data(), d->mRawData.size());
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// 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());
|
|
|
|
|
|
|
|
|
|
TQDataStream stream(file);
|
|
|
|
|
stream.writeRawBytes(d->mRawData.data(), d->mRawData.size());
|
|
|
|
|
|
|
|
|
|
// Make sure we are up to date
|
|
|
|
|
loadFromData(d->mRawData);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace
|