// vim: set tabstop=4 shiftwidth=4 noexpandtab: /* Gwenview - A simple image viewer for TDE 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if defined(HAVE_CONFIG_H) #include "config.h" #endif // System #include #include #include #include extern "C" { #include #include "transupp.h" } // TQt #include #include #include #include #include // KDE #include // Exiv2 #if defined(HAVE_EXIV2_EXIV2_HPP) #include #else #include #include #endif // Local #include "imageutils/imageutils.h" #include "imageutils/jpegcontent.h" #include "imageutils/jpegerrormanager.h" // Make sure an EXIV2_TEST_VERSION macro exists: #ifdef EXIV2_VERSION # ifndef EXIV2_TEST_VERSION # define EXIV2_TEST_VERSION(major,minor,patch) \ ( EXIV2_VERSION >= EXIV2_MAKE_VERSION(major,minor,patch) ) # endif #else # define EXIV2_TEST_VERSION(major,minor,patch) (false) #endif 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; 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 = 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; } return Orientation( it->toLong() ); } 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; } int res = it->toLong(); 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 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()) { #if (EXIV2_TEST_VERSION(0,17,91)) Exiv2::ExifThumbC thumb(d->mExifData); Exiv2::DataBuf const thumbnail = thumb.copy(); #else Exiv2::DataBuf thumbnail = d->mExifData.copyThumbnail(); #endif image.loadFromData(thumbnail.pData_, thumbnail.size_); } 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; } #if (EXIV2_TEST_VERSION(0,17,91)) Exiv2::ExifThumb thumb(d->mExifData); thumb.setJpegThumbnail((unsigned char*)array.data(), array.size()); #else d->mExifData.setJpegThumbnail((unsigned char*)array.data(), array.size()); #endif } 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; } 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()); TQDataStream stream(file); stream.writeRawBytes(d->mRawData.data(), d->mRawData.size()); // Make sure we are up to date loadFromData(d->mRawData); return true; } } // namespace