diff options
Diffstat (limited to 'src/libs/dimg/loaders')
29 files changed, 5902 insertions, 0 deletions
diff --git a/src/libs/dimg/loaders/Makefile.am b/src/libs/dimg/loaders/Makefile.am new file mode 100644 index 00000000..62e96fe8 --- /dev/null +++ b/src/libs/dimg/loaders/Makefile.am @@ -0,0 +1,21 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libdimgloaders.la + +libdimgloaders_la_SOURCES = dimgloader.cpp pngloader.cpp jpegloader.cpp tiffloader.cpp \ + rawloader.cpp ppmloader.cpp qimageloader.cpp iccjpeg.c \ + jp2kloader.cpp jpegsettings.cpp pngsettings.cpp \ + tiffsettings.cpp jp2ksettings.cpp + +libdimgloaders_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) \ + $(LIBJPEG) $(LIB_TIFF) $(LIB_PNG) $(LIB_JASPER) + +INCLUDES = $(all_includes) -I$(top_srcdir)/src/libs/dimg \ + -I$(top_srcdir)/src/libs/dimg/filters \ + -I$(top_srcdir)/src/libs/curves \ + -I$(top_srcdir)/src/libs/levels \ + -I$(top_srcdir)/src/libs/histogram \ + -I$(top_srcdir)/src/libs/whitebalance \ + -I$(top_srcdir)/src/libs/dmetadata \ + -I$(top_srcdir)/src/digikam \ + $(LIBKEXIV2_CFLAGS) $(LIBKDCRAW_CFLAGS) diff --git a/src/libs/dimg/loaders/README b/src/libs/dimg/loaders/README new file mode 100644 index 00000000..b77e9c4c --- /dev/null +++ b/src/libs/dimg/loaders/README @@ -0,0 +1,42 @@ +--------------------------------------------------------------------------- + +Native DIMG Loaders status (Gilles Caulier - 2006-11-29) + +Format Read Write ICC MetaData Thumb 8bits 16bits depency Remarks + +JPG Done Done Done Done TODO yes N.A libjpeg Metadata are EXIF/IPTC/XMP/ICC profil +PNG Done Done Done Done N.A yes yes libpng Metadata are EXIF/IPTC/XMP/ICC profil +TIF/EP Done Done Done Done TODO yes yes libtiff Metadata are EXIF/IPTC/XMP/ICC profil +RAW Done N.A N.A Done Done yes yes dcraw Metadata are EXIF +PPM Done TODO N.A N.A N.A yes yes none +JPEG2K Done Done Done TODO N.A yes yes libjasper Metadata are EXIF/XMP/ICC profil + +Others file formats are supported only in 8 bits/color/pixel using TQImage/kimgio. +QT3.x + KDE 3.4.x support these formats : + +Format Read Write Remarks + +PSD yes no Photoshop file format +EXR yes no OpenEXR (libopenexr) +XCF yes no Gimp file format +PBM yes yes +PGM yes yes +PPM no yes +TGA yes yes +PCX yes yes +BMP yes yes Win32 bitmap format +RGB yes yes +XBM yes yes +XPM yes yes +EPS yes yes +DDS yes no +ICO yes no Win32 icon format +MNG yes no +GIF yes no + +--------------------------------------------------------------------------- + +TODO : + +Add PCD support using http://linux.bytesex.org/fbida/libpcd.html + diff --git a/src/libs/dimg/loaders/dimgloader.cpp b/src/libs/dimg/loaders/dimgloader.cpp new file mode 100644 index 00000000..615b11e1 --- /dev/null +++ b/src/libs/dimg/loaders/dimgloader.cpp @@ -0,0 +1,200 @@ +/* ============================================================ + * + * 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 <[email protected]> + * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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. + * + * ============================================================ */ + +// KDE includes. + +#include <kstandarddirs.h> + +// Local includes. + +#include "ddebug.h" +#include "dimgprivate.h" +#include "dmetadata.h" +#include "dimgloaderobserver.h" +#include "dimgloader.h" + +namespace Digikam +{ + +DImgLoader::DImgLoader(DImg* image) + : m_image(image) +{ +} + +int DImgLoader::granularity(DImgLoaderObserver *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() +{ + return m_image->hasAlpha(); +} + +bool DImgLoader::imageSixteenBit() +{ + return m_image->sixteenBit(); +} + +int DImgLoader::imageBitsDepth() +{ + return m_image->bitsDepth(); +} + +int DImgLoader::imageBytesDepth() +{ + return m_image->bytesDepth(); +} + +TQMap<int, TQByteArray>& DImgLoader::imageMetaData() +{ + return m_image->m_priv->metaData; +} + +TQVariant DImgLoader::imageGetAttribute(const TQString& key) +{ + return m_image->attribute(key); +} + +TQString DImgLoader::imageGetEmbbededText(const TQString& key) +{ + return m_image->embeddedText(key); +} + +void DImgLoader::imageSetAttribute(const TQString& key, const TQVariant& value) +{ + m_image->setAttribute(key, value); +} + +TQMap<TQString, TQString>& DImgLoader::imageEmbeddedText() +{ + return m_image->m_priv->embeddedText; +} + +void DImgLoader::imageSetEmbbededText(const TQString& key, const TQString& text) +{ + m_image->setEmbeddedText(key, text); +} + +bool DImgLoader::readMetadata(const TQString& filePath, DImg::FORMAT /*ff*/) +{ + TQMap<int, TQByteArray>& imageMetadata = imageMetaData(); + imageMetadata.clear(); + + DMetadata metaDataFromFile(filePath); + if (!metaDataFromFile.load(filePath)) + return false; + + // Do not insert null data into metaData map: + // Even if byte array is null, if there is a key in the map, it will + // be interpreted as "There was data, so write it again to the file". + if (!metaDataFromFile.getComments().isNull()) + imageMetadata.insert(DImg::COM, metaDataFromFile.getComments()); + if (!metaDataFromFile.getExif().isNull()) + imageMetadata.insert(DImg::EXIF, metaDataFromFile.getExif()); + if (!metaDataFromFile.getIptc().isNull()) + imageMetadata.insert(DImg::IPTC, metaDataFromFile.getIptc()); + + return true; +} + +bool DImgLoader::saveMetadata(const TQString& filePath) +{ + DMetadata metaDataToFile(filePath); + metaDataToFile.setComments(m_image->getComments()); + metaDataToFile.setExif(m_image->getExif()); + metaDataToFile.setIptc(m_image->getIptc()); + return metaDataToFile.applyChanges(); +} + +bool DImgLoader::checkExifWorkingColorSpace() +{ + DMetadata metaData; + metaData.setExif(m_image->getExif()); + + // Check if Exif data contains an ICC color profile. + TQByteArray profile = metaData.getExifTagData("Exif.Image.InterColorProfile"); + if (!profile.isNull()) + { + DDebug() << "Found an ICC profile in Exif metadata" << endl; + m_image->setICCProfil(profile); + return true; + } + + // Else check the Exif color-space tag and use a default profiles available in digiKam. + TDEGlobal::dirs()->addResourceType("profiles", TDEGlobal::dirs()->kde_default("data") + "digikam/profiles"); + + switch(metaData.getImageColorWorkSpace()) + { + case DMetadata::WORKSPACE_SRGB: + { + TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "srgb-d65.icm"); + m_image->getICCProfilFromFile(directory + "srgb-d65.icm"); + DDebug() << "Exif color-space tag is sRGB. Using default sRGB ICC profile." << endl; + return true; + break; + } + + case DMetadata::WORKSPACE_ADOBERGB: + { + TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "adobergb.icm"); + m_image->getICCProfilFromFile(directory + "adobergb.icm"); + DDebug() << "Exif color-space tag is AdobeRGB. Using default AdobeRGB ICC profile." << endl; + return true; + break; + } + + default: + break; + } + + return false; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/dimgloader.h b/src/libs/dimg/loaders/dimgloader.h new file mode 100644 index 00000000..39025888 --- /dev/null +++ b/src/libs/dimg/loaders/dimgloader.h @@ -0,0 +1,97 @@ +/* ============================================================ + * + * 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 <[email protected]> + * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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 DIMGLOADER_H +#define DIMGLOADER_H + +// TQt includes. + +#include <tqmap.h> +#include <tqstring.h> +#include <tqcstring.h> +#include <tqvariant.h> + +// Local includes. + +#include "dimg.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class DImgLoaderObserver; + +class DIGIKAM_EXPORT DImgLoader +{ +public: + + virtual ~DImgLoader() {}; + + virtual bool load(const TQString& filePath, DImgLoaderObserver *observer) = 0; + virtual bool save(const TQString& filePath, DImgLoaderObserver *observer) = 0; + + virtual bool hasAlpha() const = 0; + virtual bool sixteenBit() const = 0; + virtual bool isReadOnly() const = 0; + +protected: + + DImgLoader(DImg* image); + + unsigned char*& imageData(); + unsigned int& imageWidth(); + unsigned int& imageHeight(); + + bool imageHasAlpha(); + bool imageSixteenBit(); + + int imageBitsDepth(); + int imageBytesDepth(); + + TQMap<int, TQByteArray>& imageMetaData(); + TQVariant imageGetAttribute(const TQString& key); + void imageSetAttribute(const TQString& key, const TQVariant& value); + + TQMap<TQString, TQString>& imageEmbeddedText(); + TQString imageGetEmbbededText(const TQString& key); + void imageSetEmbbededText(const TQString& key, const TQString& text); + + virtual bool readMetadata(const TQString& filePath, DImg::FORMAT ff); + virtual bool saveMetadata(const TQString& filePath); + virtual int granularity(DImgLoaderObserver *observer, int total, float progressSlice = 1.0); + + bool checkExifWorkingColorSpace(); + +protected: + + DImg *m_image; + +private: + + DImgLoader(); +}; + +} // NameSpace Digikam + +#endif /* DIMGLOADER_H */ diff --git a/src/libs/dimg/loaders/dimgloaderobserver.h b/src/libs/dimg/loaders/dimgloaderobserver.h new file mode 100644 index 00000000..ea83bede --- /dev/null +++ b/src/libs/dimg/loaders/dimgloaderobserver.h @@ -0,0 +1,67 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-03 + * Description : DImgLoader observer interface + * + * Copyright (C) 2006-2007 by Marcel Wiesweg <[email protected]> + * + * 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 DIMGLOADEROBSERVER_H +#define DIMGLOADEROBSERVER_H + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DImg; + +class DIGIKAM_EXPORT DImgLoaderObserver +{ + +public: + // posts progress information about image IO + virtual void progressInfo(const DImg *, float /*progress*/) + {}; + + // queries whether the image IO operation shall be continued + virtual bool continueQuery(const DImg *) + { return true; }; + + // Return a relative value which determines the granularity, the frequency + // with which the DImgLoaderObserver is checked and progress is posted. + // Standard is 1.0. Values < 1 mean less granularity (fewer checks), + // values > 1 mean higher granularity (more checks). + virtual float granularity() + { return 1.0; }; + + // This is a hack needed to prevent hanging when a TDEProcess-based loader (raw loader) + // is waiting for the process to finish, but the main thread is waiting + // for the thread to finish and no TDEProcess events are delivered. + // Remove when porting to TQt4. + virtual bool isShuttingDown() + { return false; } + + virtual ~DImgLoaderObserver(){}; +}; + +} // namespace Digikam + +#endif // DIMGLOADEROBSERVER_H diff --git a/src/libs/dimg/loaders/iccjpeg.c b/src/libs/dimg/loaders/iccjpeg.c new file mode 100644 index 00000000..fefa9509 --- /dev/null +++ b/src/libs/dimg/loaders/iccjpeg.c @@ -0,0 +1,270 @@ +/* + * Little cms + * Copyright (C) 1998-2004 Marti Maria + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * iccprofile.c + * + * This file provides code to read and write International Color Consortium + * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has + * defined a standard format for including such data in JPEG "APP2" markers. + * The code given here does not know anything about the internal structure + * of the ICC profile data; it just knows how to put the profile data into + * a JPEG file being written, or get it back out when reading. + * + * This code depends on new features added to the IJG JPEG library as of + * IJG release 6b; it will not compile or work with older IJG versions. + * + * NOTE: this code would need surgery to work on 16-bit-int machines + * with ICC profiles exceeding 64K bytes in size. If you need to do that, + * change all the "unsigned int" variables to "INT32". You'll also need + * to find a malloc() replacement that can allocate more than 64K. + */ + +#include "iccjpeg.h" +#include <stdlib.h> /* define malloc() */ + + +/* + * Since an ICC profile can be larger than the maximum size of a JPEG marker + * (64K), we need provisions to split it into multiple markers. The format + * defined by the ICC specifies one or more APP2 markers containing the + * following data: + * Identifying string ASCII "ICC_PROFILE\0" (12 bytes) + * Marker sequence number 1 for first APP2, 2 for next, etc (1 byte) + * Number of markers Total number of APP2's used (1 byte) + * Profile data (remainder of APP2 data) + * Decoders should use the marker sequence numbers to reassemble the profile, + * rather than assuming that the APP2 markers appear in the correct sequence. + */ + +#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ +#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ +#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ +#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) + + +/* + * This routine writes the given ICC profile data into a JPEG file. + * It *must* be called AFTER calling jpeg_start_compress() and BEFORE + * the first call to jpeg_write_scanlines(). + * (This ordering ensures that the APP2 marker(s) will appear after the + * SOI and JFIF or Adobe markers, but before all else.) + */ + +void +write_icc_profile (j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len) +{ + unsigned int num_markers; /* total number of markers we'll write */ + int cur_marker = 1; /* per spec, counting starts at 1 */ + unsigned int length; /* number of bytes to write in this marker */ + + /* Calculate the number of markers we'll need, rounding up of course */ + num_markers = icc_data_len / MAX_DATA_BYTES_IN_MARKER; + if (num_markers * MAX_DATA_BYTES_IN_MARKER != icc_data_len) + num_markers++; + + while (icc_data_len > 0) { + /* length of profile to put in this marker */ + length = icc_data_len; + if (length > MAX_DATA_BYTES_IN_MARKER) + length = MAX_DATA_BYTES_IN_MARKER; + icc_data_len -= length; + + /* Write the JPEG marker header (APP2 code and marker length) */ + jpeg_write_m_header(cinfo, ICC_MARKER, + (unsigned int) (length + ICC_OVERHEAD_LEN)); + + /* Write the marker identifying string "ICC_PROFILE" (null-terminated). + * We code it in this less-than-transparent way so that the code works + * even if the local character set is not ASCII. + */ + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x5F); + jpeg_write_m_byte(cinfo, 0x50); + jpeg_write_m_byte(cinfo, 0x52); + jpeg_write_m_byte(cinfo, 0x4F); + jpeg_write_m_byte(cinfo, 0x46); + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x4C); + jpeg_write_m_byte(cinfo, 0x45); + jpeg_write_m_byte(cinfo, 0x0); + + /* Add the sequencing info */ + jpeg_write_m_byte(cinfo, cur_marker); + jpeg_write_m_byte(cinfo, (int) num_markers); + + /* Add the profile data */ + while (length--) { + jpeg_write_m_byte(cinfo, *icc_data_ptr); + icc_data_ptr++; + } + cur_marker++; + } +} + + +/* + * Prepare for reading an ICC profile + */ + +void +setup_read_icc_profile (j_decompress_ptr cinfo) +{ + /* Tell the library to keep any APP2 data it may find */ + jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF); +} + + +/* + * Handy subroutine to test whether a saved marker is an ICC profile marker. + */ + +static boolean +marker_is_icc (jpeg_saved_marker_ptr marker) +{ + return + marker->marker == ICC_MARKER && + marker->data_length >= ICC_OVERHEAD_LEN && + /* verify the identifying string */ + GETJOCTET(marker->data[0]) == 0x49 && + GETJOCTET(marker->data[1]) == 0x43 && + GETJOCTET(marker->data[2]) == 0x43 && + GETJOCTET(marker->data[3]) == 0x5F && + GETJOCTET(marker->data[4]) == 0x50 && + GETJOCTET(marker->data[5]) == 0x52 && + GETJOCTET(marker->data[6]) == 0x4F && + GETJOCTET(marker->data[7]) == 0x46 && + GETJOCTET(marker->data[8]) == 0x49 && + GETJOCTET(marker->data[9]) == 0x4C && + GETJOCTET(marker->data[10]) == 0x45 && + GETJOCTET(marker->data[11]) == 0x0; +} + + +/* + * See if there was an ICC profile in the JPEG file being read; + * if so, reassemble and return the profile data. + * + * TRUE is returned if an ICC profile was found, FALSE if not. + * If TRUE is returned, *icc_data_ptr is set to point to the + * returned data, and *icc_data_len is set to its length. + * + * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc() + * and must be freed by the caller with free() when the caller no longer + * needs it. (Alternatively, we could write this routine to use the + * IJG library's memory allocator, so that the data would be freed implicitly + * at jpeg_finish_decompress() time. But it seems likely that many apps + * will prefer to have the data stick around after decompression finishes.) + * + * NOTE: if the file contains invalid ICC APP2 markers, we just silently + * return FALSE. You might want to issue an error message instead. + */ + +boolean +read_icc_profile (j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len) +{ + jpeg_saved_marker_ptr marker; + int num_markers = 0; + int seq_no; + JOCTET *icc_data; + unsigned int total_length; +#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */ + char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */ + unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */ + unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */ + + *icc_data_ptr = NULL; /* avoid confusion if FALSE return */ + *icc_data_len = 0; + + /* This first pass over the saved markers discovers whether there are + * any ICC markers and verifies the consistency of the marker numbering. + */ + + for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) + marker_present[seq_no] = 0; + + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + if (num_markers == 0) + num_markers = GETJOCTET(marker->data[13]); + else if (num_markers != GETJOCTET(marker->data[13])) + return FALSE; /* inconsistent num_markers fields */ + seq_no = GETJOCTET(marker->data[12]); + if (seq_no <= 0 || seq_no > num_markers) + return FALSE; /* bogus sequence number */ + if (marker_present[seq_no]) + return FALSE; /* duplicate sequence numbers */ + marker_present[seq_no] = 1; + data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN; + } + } + + if (num_markers == 0) + return FALSE; + + /* Check for missing markers, count total space needed, + * compute offset of each marker's part of the data. + */ + + total_length = 0; + for (seq_no = 1; seq_no <= num_markers; seq_no++) { + if (marker_present[seq_no] == 0) + return FALSE; /* missing sequence number */ + data_offset[seq_no] = total_length; + total_length += data_length[seq_no]; + } + + if (total_length <= 0) + return FALSE; /* found only empty markers? */ + + /* Allocate space for assembled data */ + icc_data = (JOCTET *) malloc(total_length * sizeof(JOCTET)); + if (icc_data == NULL) + return FALSE; /* oops, out of memory */ + + /* and fill it in */ + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + JOCTET FAR *src_ptr; + JOCTET *dst_ptr; + unsigned int length; + seq_no = GETJOCTET(marker->data[12]); + dst_ptr = icc_data + data_offset[seq_no]; + src_ptr = marker->data + ICC_OVERHEAD_LEN; + length = data_length[seq_no]; + while (length--) { + *dst_ptr++ = *src_ptr++; + } + } + } + + *icc_data_ptr = icc_data; + *icc_data_len = total_length; + + return TRUE; +} diff --git a/src/libs/dimg/loaders/iccjpeg.h b/src/libs/dimg/loaders/iccjpeg.h new file mode 100644 index 00000000..2aab4196 --- /dev/null +++ b/src/libs/dimg/loaders/iccjpeg.h @@ -0,0 +1,101 @@ +/* + * Little cms + * Copyright (C) 1998-2004 Marti Maria + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * + * iccprofile.h + * + * This file provides code to read and write International Color Consortium + * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has + * defined a standard format for including such data in JPEG "APP2" markers. + * The code given here does not know anything about the internal structure + * of the ICC profile data; it just knows how to put the profile data into + * a JPEG file being written, or get it back out when reading. + * + * This code depends on new features added to the IJG JPEG library as of + * IJG release 6b; it will not compile or work with older IJG versions. + * + * NOTE: this code would need surgery to work on 16-bit-int machines + * with ICC profiles exceeding 64K bytes in size. See iccprofile.c + * for details. + */ + +#ifndef ICCJPEG_H +#define ICCJPEG_H + +#include <stdio.h> /* needed to define "FILE", "NULL" */ +#include <jpeglib.h> + + +/** + * This routine writes the given ICC profile data into a JPEG file. + * It *must* be called AFTER calling jpeg_start_compress() and BEFORE + * the first call to jpeg_write_scanlines(). + * (This ordering ensures that the APP2 marker(s) will appear after the + * SOI and JFIF or Adobe markers, but before all else.) + */ + +extern void write_icc_profile JPP((j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len)); + + +/** + * Reading a JPEG file that may contain an ICC profile requires two steps: + * + * 1. After jpeg_create_decompress() but before jpeg_read_header(), + * call setup_read_icc_profile(). This routine tells the IJG library + * to save in memory any APP2 markers it may find in the file. + * + * 2. After jpeg_read_header(), call read_icc_profile() to find out + * whether there was a profile and obtain it if so. + */ + + +/** + * Prepare for reading an ICC profile + */ + +extern void setup_read_icc_profile JPP((j_decompress_ptr cinfo)); + + +/** + * See if there was an ICC profile in the JPEG file being read; + * if so, reassemble and return the profile data. + * + * TRUE is returned if an ICC profile was found, FALSE if not. + * If TRUE is returned, *icc_data_ptr is set to point to the + * returned data, and *icc_data_len is set to its length. + * + * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc() + * and must be freed by the caller with free() when the caller no longer + * needs it. (Alternatively, we could write this routine to use the + * IJG library's memory allocator, so that the data would be freed implicitly + * at jpeg_finish_decompress() time. But it seems likely that many apps + * will prefer to have the data stick around after decompression finishes.) + */ + +extern boolean read_icc_profile JPP((j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len)); + +#endif /* ICCJPEG_H */ diff --git a/src/libs/dimg/loaders/jp2kloader.cpp b/src/libs/dimg/loaders/jp2kloader.cpp new file mode 100644 index 00000000..66351c25 --- /dev/null +++ b/src/libs/dimg/loaders/jp2kloader.cpp @@ -0,0 +1,715 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-06-14 + * Description : A JPEG2000 IO file for DImg framework + * + * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * This implementation use Jasper API + * library : http://www.ece.uvic.ca/~mdadams/jasper + * Other JPEG2000 encoder-decoder : http://www.openjpeg.org + * + * Others Linux JPEG2000 Loader implementation using Jasper: + * http://cvs.graphicsmagick.org/cgi-bin/cvsweb.cgi/GraphicsMagick/coders/jp2.c + * https://subversion.imagemagick.org/subversion/ImageMagick/trunk/coders/jp2.c + * http://svn.ghostscript.com:8080/jasper/trunk/src/appl/jasper.c + * http://websvn.kde.org/trunk/KDE/tdelibs/kimgio/jp2.cpp + * + * 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. + * + * ============================================================ */ + +// This line must be commented to prevent any latency time +// when we use threaded image loader interface for each image +// files io. Uncomment this line only for debugging. +//#define ENABLE_DEBUG_MESSAGES + +// C ANSI includes. + +extern "C" +{ +#if !defined(__STDC_LIMIT_MACROS) +#define __STDC_LIMIT_MACROS +#endif +#include <stdint.h> +} + +// TQt includes. + +#include <tqfile.h> +#include <tqcstring.h> + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "jp2kloader.h" + +namespace Digikam +{ + +JP2KLoader::JP2KLoader(DImg* image) + : DImgLoader(image) +{ + m_hasAlpha = false; + m_sixteenBit = false; +} + +bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + readMetadata(filePath, DImg::JPEG); + + FILE *file = fopen(TQFile::encodeName(filePath), "rb"); + if (!file) + return false; + + unsigned char header[9]; + + if (fread(&header, 9, 1, file) != 1) + { + fclose(file); + return false; + } + + unsigned char jp2ID[5] = { 0x6A, 0x50, 0x20, 0x20, 0x0D, }; + unsigned char jpcID[2] = { 0xFF, 0x4F }; + + if (memcmp(&header[4], &jp2ID, 5) != 0 && + memcmp(&header, &jpcID, 2) != 0) + { + // not a jpeg2000 file + fclose(file); + return false; + } + + fclose(file); + + // ------------------------------------------------------------------- + // Initialize JPEG 2000 API. + + long i, x, y; + int components[4]; + unsigned int maximum_component_depth, scale[4], x_step[4], y_step[4]; + unsigned long number_components; + + jas_image_t *jp2_image = 0; + jas_stream_t *jp2_stream = 0; + jas_matrix_t *pixels[4]; + + int init = jas_init(); + if (init != 0) + { + DDebug() << "Unable to init JPEG2000 decoder" << endl; + return false; + } + + jp2_stream = jas_stream_fopen(TQFile::encodeName(filePath), "rb"); + if (jp2_stream == 0) + { + DDebug() << "Unable to open JPEG2000 stream" << endl; + return false; + } + + jp2_image = jas_image_decode(jp2_stream, -1, 0); + if (jp2_image == 0) + { + jas_stream_close(jp2_stream); + DDebug() << "Unable to decode JPEG2000 image" << endl; + return false; + } + + jas_stream_close(jp2_stream); + + // some pseudo-progress + if (observer) + observer->progressInfo(m_image, 0.1); + + // ------------------------------------------------------------------- + // Check color space. + + switch (jas_clrspc_fam(jas_image_clrspc(jp2_image))) + { + case JAS_CLRSPC_FAM_RGB: + { + components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_R); + components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_G); + components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_B); + if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0)) + { + jas_image_destroy(jp2_image); + DDebug() << "Error parsing JPEG2000 image : Missing Image Channel" << endl; + return false; + } + + number_components = 3; + components[3] = jas_image_getcmptbytype(jp2_image, 3); + if (components[3] > 0) + { + m_hasAlpha = true; + number_components++; + } + break; + } + case JAS_CLRSPC_FAM_GRAY: + { + components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_GRAY_Y); + if (components[0] < 0) + { + jas_image_destroy(jp2_image); + DDebug() << "Error parsing JP2000 image : Missing Image Channel" << endl; + return false; + } + number_components=1; + break; + } + case JAS_CLRSPC_FAM_YCBCR: + { + components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_Y); + components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CB); + components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CR); + if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0)) + { + jas_image_destroy(jp2_image); + DDebug() << "Error parsing JP2000 image : Missing Image Channel" << endl; + return false; + } + number_components = 3; + components[3] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_UNKNOWN); + if (components[3] > 0) + { + m_hasAlpha = true; + number_components++; + } + // FIXME : image->colorspace=YCbCrColorspace; + break; + } + default: + { + jas_image_destroy(jp2_image); + DDebug() << "Error parsing JP2000 image : Colorspace Model Is Not Supported" << endl; + return false; + } + } + + // ------------------------------------------------------------------- + // Check image geometry. + + imageWidth() = jas_image_width(jp2_image); + imageHeight() = jas_image_height(jp2_image); + + for (i = 0; i < (long)number_components; i++) + { + if ((((jas_image_cmptwidth(jp2_image, components[i])* + jas_image_cmpthstep(jp2_image, components[i])) != (long)imageWidth())) || + (((jas_image_cmptheight(jp2_image, components[i])* + jas_image_cmptvstep(jp2_image, components[i])) != (long)imageHeight())) || + (jas_image_cmpttlx(jp2_image, components[i]) != 0) || + (jas_image_cmpttly(jp2_image, components[i]) != 0) || + (jas_image_cmptsgnd(jp2_image, components[i]) != false)) + { + jas_image_destroy(jp2_image); + DDebug() << "Error parsing JPEG2000 image : Irregular Channel Geometry Not Supported" << endl; + return false; + } + x_step[i] = jas_image_cmpthstep(jp2_image, components[i]); + y_step[i] = jas_image_cmptvstep(jp2_image, components[i]); + } + + // ------------------------------------------------------------------- + // Convert image data. + + m_hasAlpha = number_components > 3; + maximum_component_depth = 0; + + for (i = 0; i < (long)number_components; i++) + { + maximum_component_depth = TQMAX(jas_image_cmptprec(jp2_image,components[i]), + (long)maximum_component_depth); + pixels[i] = jas_matrix_create(1, ((unsigned int)imageWidth())/x_step[i]); + if (!pixels[i]) + { + jas_image_destroy(jp2_image); + DDebug() << "Error decoding JPEG2000 image data : Memory Allocation Failed" << endl; + return false; + } + } + + if (maximum_component_depth > 8) + m_sixteenBit = true; + + for (i = 0 ; i < (long)number_components ; i++) + { + scale[i] = 1; + int prec = jas_image_cmptprec(jp2_image, components[i]); + if (m_sixteenBit && prec < 16) + scale[i] = (1 << (16 - jas_image_cmptprec(jp2_image, components[i]))); + } + + uchar* data = 0; + if (m_sixteenBit) // 16 bits image. + data = new uchar[imageWidth()*imageHeight()*8]; + else + data = new uchar[imageWidth()*imageHeight()*4]; + + if (!data) + { + DDebug() << "Error decoding JPEG2000 image data : Memory Allocation Failed" << endl; + jas_image_destroy(jp2_image); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + return false; + } + + uint checkPoint = 0; + uchar *dst = data; + unsigned short *dst16 = (unsigned short *)data; + + for (y = 0 ; y < (long)imageHeight() ; y++) + { + for (i = 0 ; i < (long)number_components; i++) + { + int ret = jas_image_readcmpt(jp2_image, (short)components[i], 0, + ((unsigned int) y) / y_step[i], + ((unsigned int) imageWidth()) / x_step[i], + 1, pixels[i]); + if (ret != 0) + { + DDebug() << "Error decoding JPEG2000 image data" << endl; + delete [] data; + jas_image_destroy(jp2_image); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + return false; + } + } + + switch (number_components) + { + case 1: // Grayscale. + { + for (x = 0 ; x < (long)imageWidth() ; x++) + { + dst[0] = (uchar)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0])); + dst[1] = dst[0]; + dst[2] = dst[0]; + dst[3] = 0xFF; + + dst += 4; + } + break; + } + case 3: // RGB. + { + if (!m_sixteenBit) // 8 bits image. + { + for (x = 0 ; x < (long)imageWidth() ; x++) + { + // Blue + dst[0] = (uchar)(scale[2]*jas_matrix_getv(pixels[2], x/x_step[2])); + // Green + dst[1] = (uchar)(scale[1]*jas_matrix_getv(pixels[1], x/x_step[1])); + // Red + dst[2] = (uchar)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0])); + // Alpha + dst[3] = 0xFF; + + dst += 4; + } + } + else // 16 bits image. + { + for (x = 0 ; x < (long)imageWidth() ; x++) + { + // Blue + dst16[0] = (unsigned short)(scale[2]*jas_matrix_getv(pixels[2], x/x_step[2])); + // Green + dst16[1] = (unsigned short)(scale[1]*jas_matrix_getv(pixels[1], x/x_step[1])); + // Red + dst16[2] = (unsigned short)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0])); + // Alpha + dst16[3] = 0xFFFF; + + dst16 += 4; + } + } + + break; + } + case 4: // RGBA. + { + if (!m_sixteenBit) // 8 bits image. + { + for (x = 0 ; x < (long)imageWidth() ; x++) + { + // Blue + dst[0] = (uchar)(scale[2] * jas_matrix_getv(pixels[2], x/x_step[2])); + // Green + dst[1] = (uchar)(scale[1] * jas_matrix_getv(pixels[1], x/x_step[1])); + // Red + dst[2] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x/x_step[0])); + // Alpha + dst[3] = (uchar)(scale[3] * jas_matrix_getv(pixels[3], x/x_step[3])); + + dst += 4; + } + } + else // 16 bits image. + { + for (x = 0 ; x < (long)imageWidth() ; x++) + { + // Blue + dst16[0] = (unsigned short)(scale[2]*jas_matrix_getv(pixels[2], x/x_step[2])); + // Green + dst16[1] = (unsigned short)(scale[1]*jas_matrix_getv(pixels[1], x/x_step[1])); + // Red + dst16[2] = (unsigned short)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0])); + // Alpha + dst16[3] = (unsigned short)(scale[3]*jas_matrix_getv(pixels[3], x/x_step[3])); + + dst16 += 4; + } + } + + break; + } + } + + // use 0-10% and 90-100% for pseudo-progress + if (observer && y >= (long)checkPoint) + { + checkPoint += granularity(observer, y, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + jas_image_destroy(jp2_image); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)y)/((float)imageHeight()) ))); + } + } + + // ------------------------------------------------------------------- + // Get ICC color profile. + + jas_iccprof_t *icc_profile = 0; + jas_stream_t *icc_stream = 0; + jas_cmprof_t *cm_profile = 0; + + cm_profile = jas_image_cmprof(jp2_image); + if (cm_profile != 0) + icc_profile = jas_iccprof_createfromcmprof(cm_profile); + + if (icc_profile != 0) + { + icc_stream = jas_stream_memopen(NULL, 0); + + if (icc_stream != 0) + { + if (jas_iccprof_save(icc_profile, icc_stream) == 0) + { + if (jas_stream_flush(icc_stream) == 0) + { + TQMap<int, TQByteArray>& metaData = imageMetaData(); + jas_stream_memobj_t *blob = (jas_stream_memobj_t *) icc_stream->obj_; + TQByteArray profile_rawdata(blob->len_); + memcpy(profile_rawdata.data(), blob->buf_, blob->len_); + metaData.insert(DImg::ICC, profile_rawdata); + jas_stream_close(icc_stream); + } + } + } + } + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageSetAttribute("format", "JP2K"); + imageData() = data; + + jas_image_destroy(jp2_image); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + + return true; +} + +bool JP2KLoader::save(const TQString& filePath, DImgLoaderObserver *observer) +{ + FILE *file = fopen(TQFile::encodeName(filePath), "wb"); + if (!file) + return false; + + fclose(file); + + // ------------------------------------------------------------------- + // Initialize JPEG 2000 API. + + long i, x, y; + unsigned long number_components; + + jas_image_t *jp2_image = 0; + jas_stream_t *jp2_stream = 0; + jas_matrix_t *pixels[4]; + jas_image_cmptparm_t component_info[4]; + + int init = jas_init(); + if (init != 0) + { + DDebug() << "Unable to init JPEG2000 decoder" << endl; + return false; + } + + jp2_stream = jas_stream_fopen(TQFile::encodeName(filePath), "wb"); + if (jp2_stream == 0) + { + DDebug() << "Unable to open JPEG2000 stream" << endl; + return false; + } + + number_components = imageHasAlpha() ? 4 : 3; + + for (i = 0 ; i < (long)number_components ; i++) + { + component_info[i].tlx = 0; + component_info[i].tly = 0; + component_info[i].hstep = 1; + component_info[i].vstep = 1; + component_info[i].width = imageWidth(); + component_info[i].height = imageHeight(); + component_info[i].prec = imageBitsDepth(); + component_info[i].sgnd = false; + } + + jp2_image = jas_image_create(number_components, component_info, JAS_CLRSPC_UNKNOWN); + if (jp2_image == 0) + { + jas_stream_close(jp2_stream); + DDebug() << "Unable to create JPEG2000 image" << endl; + return false; + } + + if (observer) + observer->progressInfo(m_image, 0.1); + + // ------------------------------------------------------------------- + // Check color space. + + if (number_components >= 3 ) // RGB & RGBA + { + // Alpha Channel + if (number_components == 4 ) + jas_image_setcmpttype(jp2_image, 3, JAS_IMAGE_CT_OPACITY); + + jas_image_setclrspc(jp2_image, JAS_CLRSPC_SRGB); + jas_image_setcmpttype(jp2_image, 0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R)); + jas_image_setcmpttype(jp2_image, 1, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G)); + jas_image_setcmpttype(jp2_image, 2, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B)); + } + + // ------------------------------------------------------------------- + // Set ICC color profile. + + // FIXME : doesn't work yet! + + jas_cmprof_t *cm_profile = 0; + jas_iccprof_t *icc_profile = 0; + + TQByteArray profile_rawdata = m_image->getICCProfil(); + + icc_profile = jas_iccprof_createfrombuf((uchar*)profile_rawdata.data(), profile_rawdata.size()); + if (icc_profile != 0) + { + cm_profile = jas_cmprof_createfromiccprof(icc_profile); + if (cm_profile != 0) + { + jas_image_setcmprof(jp2_image, cm_profile); + } + } + + // ------------------------------------------------------------------- + // Convert to JPEG 2000 pixels. + + for (i = 0 ; i < (long)number_components ; i++) + { + pixels[i] = jas_matrix_create(1, (unsigned int)imageWidth()); + if (pixels[i] == 0) + { + for (x = 0 ; x < i ; x++) + jas_matrix_destroy(pixels[x]); + + jas_image_destroy(jp2_image); + DDebug() << "Error encoding JPEG2000 image data : Memory Allocation Failed" << endl; + return false; + } + } + + unsigned char* data = imageData(); + unsigned char* pixel; + unsigned short r, g, b, a=0; + uint checkpoint = 0; + + for (y = 0 ; y < (long)imageHeight() ; y++) + { + if (observer && y == (long)checkpoint) + { + checkpoint += granularity(observer, imageHeight(), 0.8); + if (!observer->continueQuery(m_image)) + { + jas_image_destroy(jp2_image); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)y)/((float)imageHeight()) ))); + } + + for (x = 0 ; x < (long)imageWidth() ; x++) + { + pixel = &data[((y * imageWidth()) + x) * imageBytesDepth()]; + + if ( imageSixteenBit() ) // 16 bits image. + { + b = (unsigned short)(pixel[0]+256*pixel[1]); + g = (unsigned short)(pixel[2]+256*pixel[3]); + r = (unsigned short)(pixel[4]+256*pixel[5]); + + if (imageHasAlpha()) + a = (unsigned short)(pixel[6]+256*pixel[7]); + } + else // 8 bits image. + { + b = (unsigned short)pixel[0]; + g = (unsigned short)pixel[1]; + r = (unsigned short)pixel[2]; + + if (imageHasAlpha()) + a = (unsigned short)(pixel[3]); + } + + jas_matrix_setv(pixels[0], x, r); + jas_matrix_setv(pixels[1], x, g); + jas_matrix_setv(pixels[2], x, b); + + if (number_components > 3) + jas_matrix_setv(pixels[3], x, a); + } + + for (i = 0 ; i < (long)number_components ; i++) + { + int ret = jas_image_writecmpt(jp2_image, (short) i, 0, (unsigned int)y, + (unsigned int)imageWidth(), 1, pixels[i]); + if (ret != 0) + { + DDebug() << "Error encoding JPEG2000 image data" << endl; + + jas_image_destroy(jp2_image); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + return false; + } + } + } + + TQVariant qualityAttr = imageGetAttribute("quality"); + int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; + + if (quality < 0) + quality = 90; + if (quality > 100) + quality = 100; + + TQString rate; + TQTextStream ts( &rate, IO_WriteOnly ); + + // NOTE: to have a lossless compression use quality=100. + // jp2_encode()::optstr: + // - rate=#B => the resulting file size is about # bytes + // - rate=0.0 .. 1.0 => the resulting file size is about the factor times + // the uncompressed size + ts << "rate=" << ( quality / 100.0F ); + + DDebug() << "JPEG2000 quality: " << quality << endl; + DDebug() << "JPEG2000 " << rate << endl; + +# if defined(JAS_VERSION_MAJOR) && (JAS_VERSION_MAJOR >= 3) + const jas_image_fmtinfo_t *jp2_fmtinfo = jas_image_lookupfmtbyname("jp2"); + int ret = -1; + if (jp2_fmtinfo) + { + ret = jas_image_encode(jp2_image, jp2_stream, jp2_fmtinfo->id, rate.utf8().data()); + } +# else + int ret = jp2_encode(jp2_image, jp2_stream, rate.utf8().data()); +# endif + + if (ret != 0) + { + DDebug() << "Unable to encode JPEG2000 image" << endl; + + jas_image_destroy(jp2_image); + jas_stream_close(jp2_stream); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + + return false; + } + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageSetAttribute("savedformat", "JP2K"); + + saveMetadata(filePath); + + jas_image_destroy(jp2_image); + jas_stream_close(jp2_stream); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + + return true; +} + +bool JP2KLoader::hasAlpha() const +{ + return m_hasAlpha; +} + +bool JP2KLoader::sixteenBit() const +{ + return m_sixteenBit; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/jp2kloader.h b/src/libs/dimg/loaders/jp2kloader.h new file mode 100644 index 00000000..04ec214e --- /dev/null +++ b/src/libs/dimg/loaders/jp2kloader.h @@ -0,0 +1,69 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-06-14 + * Description : A JPEG2000 IO file for DImg framework + * + * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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 JP2KLOADER_H +#define JP2KLOADER_H + +// C ansi includes. + +extern "C" +{ +#include <jasper/jasper.h> +} + +// TQt includes. + +#include <tqstring.h> + +// Local includes. + +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ +class DImg; + +class DIGIKAM_EXPORT JP2KLoader : public DImgLoader +{ + +public: + + JP2KLoader(DImg* image); + + bool load(const TQString& filePath, DImgLoaderObserver *observer); + bool save(const TQString& filePath, DImgLoaderObserver *observer); + + virtual bool hasAlpha() const; + virtual bool sixteenBit() const; + virtual bool isReadOnly() const { return false; }; + +private: + + bool m_sixteenBit; + bool m_hasAlpha; +}; + +} // NameSpace Digikam + +#endif /* JP2KLOADER_H */ diff --git a/src/libs/dimg/loaders/jp2ksettings.cpp b/src/libs/dimg/loaders/jp2ksettings.cpp new file mode 100644 index 00000000..af0737c1 --- /dev/null +++ b/src/libs/dimg/loaders/jp2ksettings.cpp @@ -0,0 +1,139 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save JPEG 2000 image options. + * + * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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. + * + * ============================================================ */ + +// TQt includes. + +#include <tqstring.h> +#include <tqlabel.h> +#include <tqcheckbox.h> +#include <tqlayout.h> +#include <tqwhatsthis.h> + +// KDE includes. + +#include <tdelocale.h> +#include <kdialog.h> +#include <knuminput.h> + +// Local includes. + +#include "jp2ksettings.h" +#include "jp2ksettings.moc" + +namespace Digikam +{ + +class JP2KSettingsPriv +{ + +public: + + JP2KSettingsPriv() + { + JPEG2000Grid = 0; + labelJPEG2000compression = 0; + JPEG2000compression = 0; + JPEG2000LossLess = 0; + } + + TQGridLayout *JPEG2000Grid; + + TQLabel *labelJPEG2000compression; + + TQCheckBox *JPEG2000LossLess; + + KIntNumInput *JPEG2000compression; +}; + +JP2KSettings::JP2KSettings(TQWidget *parent) + : TQWidget(parent, 0, TQt::WDestructiveClose) +{ + d = new JP2KSettingsPriv; + + d->JPEG2000Grid = new TQGridLayout(this, 1, 1, KDialog::spacingHint()); + d->JPEG2000LossLess = new TQCheckBox(i18n("Lossless JPEG 2000 files"), this); + + TQWhatsThis::add(d->JPEG2000LossLess, i18n("<p>Toggle lossless compression for JPEG 2000 images.<p>" + "If you enable this option, you will use a lossless method " + "to compress JPEG 2000 pictures.<p>")); + + d->JPEG2000compression = new KIntNumInput(75, this); + d->JPEG2000compression->setRange(1, 100, 1, true ); + d->labelJPEG2000compression = new TQLabel(i18n("JPEG 2000 quality:"), this); + + TQWhatsThis::add( d->JPEG2000compression, i18n("<p>The quality value for JPEG 2000 images:<p>" + "<b>1</b>: low quality (high compression and small " + "file size)<p>" + "<b>50</b>: medium quality<p>" + "<b>75</b>: good quality (default)<p>" + "<b>100</b>: high quality (no compression and " + "large file size)<p>" + "<b>Note: JPEG 2000 is not a lossless image " + "compression format when you use this setting.</b>")); + + d->JPEG2000Grid->addMultiCellWidget(d->JPEG2000LossLess, 0, 0, 0, 1); + d->JPEG2000Grid->addMultiCellWidget(d->labelJPEG2000compression, 1, 1, 0, 0); + d->JPEG2000Grid->addMultiCellWidget(d->JPEG2000compression, 1, 1, 1, 1); + d->JPEG2000Grid->setColStretch(1, 10); + + connect(d->JPEG2000LossLess, TQ_SIGNAL(toggled(bool)), + this, TQ_SLOT(slotToggleJPEG2000LossLess(bool))); + + connect(d->JPEG2000LossLess, TQ_SIGNAL(toggled(bool)), + this, TQ_SLOT(slotToggleJPEG2000LossLess(bool))); +} + +JP2KSettings::~JP2KSettings() +{ + delete d; +} + +void JP2KSettings::setCompressionValue(int val) +{ + d->JPEG2000compression->setValue(val); +} + +int JP2KSettings::getCompressionValue() +{ + return d->JPEG2000compression->value(); +} + +void JP2KSettings::setLossLessCompression(bool b) +{ + d->JPEG2000LossLess->setChecked(b); + slotToggleJPEG2000LossLess(d->JPEG2000LossLess->isChecked()); +} + +bool JP2KSettings::getLossLessCompression() +{ + return d->JPEG2000LossLess->isChecked(); +} + +void JP2KSettings::slotToggleJPEG2000LossLess(bool b) +{ + d->JPEG2000compression->setEnabled(!b); + d->labelJPEG2000compression->setEnabled(!b); +} + +} // namespace Digikam + diff --git a/src/libs/dimg/loaders/jp2ksettings.h b/src/libs/dimg/loaders/jp2ksettings.h new file mode 100644 index 00000000..951cb2fc --- /dev/null +++ b/src/libs/dimg/loaders/jp2ksettings.h @@ -0,0 +1,67 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save JPEG 2000 image options. + * + * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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 JP2KSETTINGS_H +#define JP2KSETTINGS_H + +// KDE includes. + +#include <tqwidget.h> + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class JP2KSettingsPriv; + +class DIGIKAM_EXPORT JP2KSettings : public TQWidget +{ +TQ_OBJECT + + +public: + + JP2KSettings(TQWidget *parent=0); + ~JP2KSettings(); + + void setCompressionValue(int val); + int getCompressionValue(); + + void setLossLessCompression(bool b); + bool getLossLessCompression(); + +private slots: + + void slotToggleJPEG2000LossLess(bool); + +private: + + JP2KSettingsPriv* d; +}; + +} // namespace Digikam + +#endif /* JP2KSETTINGS_H */ diff --git a/src/libs/dimg/loaders/jpegloader.cpp b/src/libs/dimg/loaders/jpegloader.cpp new file mode 100644 index 00000000..58f6e20a --- /dev/null +++ b/src/libs/dimg/loaders/jpegloader.cpp @@ -0,0 +1,676 @@ +/* ============================================================ + * + * 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 <[email protected]> + * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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 + +// This line must be commented to prevent any latency time +// when we use threaded image loader interface for each image +// files io. Uncomment this line only for debugging. +//#define ENABLE_DEBUG_MESSAGES + +// C ansi includes. + +extern "C" +{ +#include "iccjpeg.h" +} + +// C+ includes. + +#include <cstdio> +#include <cstdlib> + +// TQt includes. + +#include <tqfile.h> +#include <tqcstring.h> + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "jpegloader.h" + +namespace Digikam +{ + +// To manage Errors/Warnings handling provide by libjpeg + +void JPEGLoader::dimg_jpeg_error_exit(j_common_ptr cinfo) +{ + dimg_jpeg_error_mgr* myerr = (dimg_jpeg_error_mgr*) cinfo->err; + + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << endl; +#endif + + 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); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << " (" << msg_level << ")" << endl; +#else + Q_UNUSED(msg_level); +#endif +} + +void JPEGLoader::dimg_jpeg_output_message(j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << endl; +#endif +} + +JPEGLoader::JPEGLoader(DImg* image) + : DImgLoader(image) +{ +} + +bool JPEGLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + readMetadata(filePath, DImg::JPEG); + + FILE *file = fopen(TQFile::encodeName(filePath), "rb"); + if (!file) + return false; + + unsigned char header[2]; + + if (fread(&header, 2, 1, file) != 1) + { + fclose(file); + return false; + } + + unsigned char jpegID[] = { 0xFF, 0xD8 }; + + if (memcmp(header, jpegID, 2) != 0) + { + // not a jpeg file + fclose(file); + return false; + } + + fseek(file, 0L, SEEK_SET); + + 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; + + // If an error occurs during reading, libjpeg will jump here + + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_decompress(&cinfo); + fclose(file); + return false; + } + + // ------------------------------------------------------------------- + // Find out if we do the fast-track loading with reduced size. Jpeg specific. + int scaledLoadingSize = 0; + TQVariant attribute = imageGetAttribute("jpegScaledLoadingSize"); + if (attribute.isValid()) + scaledLoadingSize = attribute.toInt(); + + // ------------------------------------------------------------------- + // Set JPEG decompressor instance + + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, file); + + // Recording ICC profile marker (from iccjpeg.c) + setup_read_icc_profile(&cinfo); + + // read image information + jpeg_read_header(&cinfo, true); + + // set decompression parameters + cinfo.do_fancy_upsampling = false; + cinfo.do_block_smoothing = false; + + if (scaledLoadingSize) + { + int imgSize = TQMAX(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; + } + + // 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 + switch (cinfo.jpeg_color_space) + { + case JCS_UNKNOWN: + // perhaps jpeg_read_header did some guessing, leave value unchanged + break; + case JCS_GRAYSCALE: + case JCS_RGB: + case JCS_YCbCr: + cinfo.out_color_space = JCS_RGB; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo.out_color_space = JCS_CMYK; + break; + } + + // initialize decompression + jpeg_start_decompress(&cinfo); + + // some pseudo-progress + if (observer) + observer->progressInfo(m_image, 0.1); + + // ------------------------------------------------------------------- + // Get image data. + + int w = cinfo.output_width; + int h = cinfo.output_height; + uchar *dest = 0; + + uchar *ptr, *line[16], *data=0; + uchar *ptr2=0; + int x, y, l, i, scans, count, prevy; + + if (cinfo.rec_outbuf_height > 16) + { + jpeg_destroy_decompress(&cinfo); + fclose(file); + DDebug() << k_funcinfo << "Height of JPEG scanline buffer out of range!" << endl; + 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); + fclose(file); + DDebug() << k_funcinfo + << "JPEG colorspace (" + << cinfo.out_color_space + << ") or Number of JPEG color components (" + << cinfo.output_components + << ") unsupported!" << endl; + return false; + } + + data = new uchar[w * 16 * cinfo.output_components]; + + if (!data) + { + jpeg_destroy_decompress(&cinfo); + fclose(file); + DDebug() << k_funcinfo << "Cannot allocate memory!" << endl; + return false; + } + + dest = new uchar[w * h * 4]; + + if (!dest) + { + delete [] data; + jpeg_destroy_decompress(&cinfo); + fclose(file); + DDebug() << k_funcinfo << "Cannot allocate memory!" << endl; + 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.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] dest; + jpeg_destroy_decompress(&cinfo); + fclose(file); + 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.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] dest; + jpeg_destroy_decompress(&cinfo); + fclose(file); + 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.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] dest; + jpeg_destroy_decompress(&cinfo); + fclose(file); + 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 TQt'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; + } + } + } + } + + delete [] data; + + // ------------------------------------------------------------------- + // Read image ICC profile + + TQMap<int, TQByteArray>& metaData = imageMetaData(); + + JOCTET *profile_data=NULL; + uint profile_size; + + read_icc_profile (&cinfo, &profile_data, &profile_size); + + if (profile_data != NULL) + { + TQByteArray profile_rawdata(profile_size); + memcpy(profile_rawdata.data(), profile_data, profile_size); + metaData.insert(DImg::ICC, profile_rawdata); + free (profile_data); + } + else + { + // If ICC profile is null, check Exif metadata. + checkExifWorkingColorSpace(); + } + + // ------------------------------------------------------------------- + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + // ------------------------------------------------------------------- + + fclose(file); + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageWidth() = w; + imageHeight() = h; + imageData() = dest; + imageSetAttribute("format", "JPEG"); + + return true; +} + +bool JPEGLoader::save(const TQString& filePath, DImgLoaderObserver *observer) +{ + FILE *file = fopen(TQFile::encodeName(filePath), "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; + + // If an error occurs during writing, libjpeg will jump here + + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_compress(&cinfo); + fclose(file); + 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; + + TQVariant qualityAttr = imageGetAttribute("quality"); + int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; + + if (quality < 0) + quality = 90; + if (quality > 100) + quality = 100; + + TQVariant subSamplingAttr = imageGetAttribute("subsampling"); + int subsampling = subSamplingAttr.isValid() ? subSamplingAttr.toInt() : 1; // Medium + + jpeg_set_defaults(&cinfo); + + // B.K.O #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) : Medium + { + DDebug() << "Using LibJPEG medium chroma-subsampling (4:2:2)" << endl; + 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:1:1) : High + { + DDebug() << "Using LibJPEG high chroma-subsampling (4:1:1)" << endl; + 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; + } + default: // 1x1 1x1 1x1 (4:4:4) : None + { + DDebug() << "Using LibJPEG none chroma-subsampling (4:4:4)" << endl; + 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, true); + jpeg_start_compress(&cinfo, true); + + DDebug() << "Using LibJPEG quality compression value: " << quality << endl; + + if (observer) + observer->progressInfo(m_image, 0.1); + + // ------------------------------------------------------------------- + // Write ICC profil. + + TQByteArray profile_rawdata = m_image->getICCProfil(); + + if (!profile_rawdata.isEmpty()) + { + write_icc_profile (&cinfo, (JOCTET *)profile_rawdata.data(), profile_rawdata.size()); + } + + if (observer) + observer->progressInfo(m_image, 0.2); + + // ------------------------------------------------------------------- + // Write Image data. + + uchar* line = new uchar[w*3]; + uchar* dstPtr = 0; + uint checkPoint = 0; + + if (!imageSixteenBit()) // 8 bits image. + { + + uchar* srcPtr = data; + + for (uint j=0; j<h; j++) + { + + if (observer && j == checkPoint) + { + checkPoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] line; + jpeg_destroy_compress(&cinfo); + fclose(file); + 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]; + dstPtr[1] = srcPtr[1]; + dstPtr[0] = srcPtr[2]; + + srcPtr += 4; + dstPtr += 3; + } + + jpeg_write_scanlines(&cinfo, &line, 1); + } + } + else + { + unsigned short* srcPtr = (unsigned short*)data; + + for (uint j=0; j<h; j++) + { + + if (observer && j == checkPoint) + { + checkPoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] line; + jpeg_destroy_compress(&cinfo); + fclose(file); + 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; + dstPtr[1] = (srcPtr[1] * 255UL)/65535UL; + dstPtr[0] = (srcPtr[2] * 255UL)/65535UL; + + srcPtr += 4; + dstPtr += 3; + } + + jpeg_write_scanlines(&cinfo, &line, 1); + } + } + + delete [] line; + + // ------------------------------------------------------------------- + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + fclose(file); + + imageSetAttribute("savedformat", "JPEG"); + + saveMetadata(filePath); + + return true; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/jpegloader.h b/src/libs/dimg/loaders/jpegloader.h new file mode 100644 index 00000000..b5d64f18 --- /dev/null +++ b/src/libs/dimg/loaders/jpegloader.h @@ -0,0 +1,80 @@ +/* ============================================================ + * + * 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 <[email protected]>, Gilles Caulier + * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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 JPEGLOADER_H +#define JPEGLOADER_H + +// C ansi includes. + +extern "C" +{ +#include <setjmp.h> +#include <jpeglib.h> +} + +// TQt includes. + +#include <tqstring.h> + +// Local includes. + +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ +class DImg; + +class DIGIKAM_EXPORT JPEGLoader : public DImgLoader +{ + +public: + + JPEGLoader(DImg* image); + + bool load(const TQString& filePath, DImgLoaderObserver *observer); + bool save(const TQString& filePath, DImgLoaderObserver *observer); + + virtual bool hasAlpha() const { return false; } + virtual bool sixteenBit() const { return false; } + virtual bool isReadOnly() const { return false; }; + +private: + + // To manage Errors/Warnings handling provide by libjpeg + + struct dimg_jpeg_error_mgr : public jpeg_error_mgr + { + jmp_buf setjmp_buffer; + }; + + static void dimg_jpeg_error_exit(j_common_ptr cinfo); + static void dimg_jpeg_emit_message(j_common_ptr cinfo, int msg_level); + static void dimg_jpeg_output_message(j_common_ptr cinfo); + +}; + +} // NameSpace Digikam + +#endif /* JPEGLOADER_H */ diff --git a/src/libs/dimg/loaders/jpegsettings.cpp b/src/libs/dimg/loaders/jpegsettings.cpp new file mode 100644 index 00000000..62bd6365 --- /dev/null +++ b/src/libs/dimg/loaders/jpegsettings.cpp @@ -0,0 +1,154 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save JPEG image options. + * + * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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. + * + * ============================================================ */ + +// TQt includes. + +#include <tqstring.h> +#include <tqlabel.h> +#include <tqlayout.h> +#include <tqwhatsthis.h> +#include <tqcombobox.h> + +// KDE includes. + +#include <tdelocale.h> +#include <kdialog.h> +#include <knuminput.h> +#include <kactivelabel.h> + +// Local includes. + +#include "jpegsettings.h" +#include "jpegsettings.moc" + +namespace Digikam +{ + +class JPEGSettingsPriv +{ + +public: + + JPEGSettingsPriv() + { + JPEGGrid = 0; + labelJPEGcompression = 0; + JPEGcompression = 0; + labelWarning = 0; + labelSubSampling = 0; + subSamplingCB = 0; + } + + TQGridLayout *JPEGGrid; + + TQLabel *labelJPEGcompression; + TQLabel *labelSubSampling; + + TQComboBox *subSamplingCB; + + KActiveLabel *labelWarning; + + KIntNumInput *JPEGcompression; +}; + +JPEGSettings::JPEGSettings(TQWidget *parent) + : TQWidget(parent, 0, TQt::WDestructiveClose) +{ + d = new JPEGSettingsPriv; + + d->JPEGGrid = new TQGridLayout(this, 1, 2, KDialog::spacingHint()); + d->JPEGcompression = new KIntNumInput(75, this); + d->JPEGcompression->setRange(1, 100, 1, true ); + d->labelJPEGcompression = new TQLabel(i18n("JPEG quality:"), this); + + TQWhatsThis::add(d->JPEGcompression, i18n("<p>The JPEG image quality:<p>" + "<b>1</b>: low quality (high compression and small " + "file size)<p>" + "<b>50</b>: medium quality<p>" + "<b>75</b>: good quality (default)<p>" + "<b>100</b>: high quality (no compression and " + "large file size)<p>" + "<b>Note: JPEG always uses lossy compression.</b>")); + + d->labelWarning = new KActiveLabel(i18n("<qt><font size=-1 color=\"red\"><i>" + "Warning: <a href='http://en.wikipedia.org/wiki/JPEG'>JPEG</a> is a<br>" + "lossy compression<br>" + "image format!</p>" + "</i></qt>"), this); + + d->labelWarning->setFrameStyle(TQFrame::Box | TQFrame::Plain); + d->labelWarning->setLineWidth(1); + d->labelWarning->setFrameShape(TQFrame::Box); + + d->labelSubSampling = new TQLabel(i18n("Chroma subsampling:"), this); + + d->subSamplingCB = new TQComboBox(false, this); + d->subSamplingCB->insertItem(i18n("None")); // 1x1, 1x1, 1x1 (4:4:4) + d->subSamplingCB->insertItem(i18n("Medium")); // 2x1, 1x1, 1x1 (4:2:2) + d->subSamplingCB->insertItem(i18n("High")); // 2x2, 1x1, 1x1 (4:1:1) + TQWhatsThis::add(d->subSamplingCB, i18n("<p>JPEG Chroma subsampling level \n(color is saved with less resolution " "than luminance):<p>" + "<b>None</b>=best: uses 4:4:4 ratio. Does not employ chroma " + "subsampling at all. This preserves edges and contrasting " + "colors, whilst adding no additional compression<p>" + "<b>Medium</b>: uses 4:2:2 ratio. Medium compression: reduces " + "the color resolution by one-third with little to " + "no visual difference<p>" + "<b>High</b>: use 4:1:1 ratio. High compression: suits " + "images with soft edges but tends to alter colors<p>" + "<b>Note: JPEG always uses lossy compression.</b>")); + + d->JPEGGrid->addMultiCellWidget(d->labelJPEGcompression, 0, 0, 0, 0); + d->JPEGGrid->addMultiCellWidget(d->JPEGcompression, 0, 0, 1, 1); + d->JPEGGrid->addMultiCellWidget(d->labelSubSampling, 1, 1, 0, 0); + d->JPEGGrid->addMultiCellWidget(d->subSamplingCB, 1, 1, 1, 1); + d->JPEGGrid->addMultiCellWidget(d->labelWarning, 0, 1, 2, 2); + d->JPEGGrid->setColStretch(1, 10); + d->JPEGGrid->setRowStretch(2, 10); +} + +JPEGSettings::~JPEGSettings() +{ + delete d; +} + +void JPEGSettings::setCompressionValue(int val) +{ + d->JPEGcompression->setValue(val); +} + +int JPEGSettings::getCompressionValue() +{ + return d->JPEGcompression->value(); +} + +void JPEGSettings::setSubSamplingValue(int val) +{ + d->subSamplingCB->setCurrentItem(val); +} + +int JPEGSettings::getSubSamplingValue() +{ + return d->subSamplingCB->currentItem(); +} + +} // namespace Digikam diff --git a/src/libs/dimg/loaders/jpegsettings.h b/src/libs/dimg/loaders/jpegsettings.h new file mode 100644 index 00000000..70e20d42 --- /dev/null +++ b/src/libs/dimg/loaders/jpegsettings.h @@ -0,0 +1,63 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save JPEG image options. + * + * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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 JPEGSETTINGS_H +#define JPEGSETTINGS_H + +// KDE includes. + +#include <tqwidget.h> + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class JPEGSettingsPriv; + +class DIGIKAM_EXPORT JPEGSettings : public TQWidget +{ +TQ_OBJECT + + +public: + + JPEGSettings(TQWidget *parent=0); + ~JPEGSettings(); + + void setCompressionValue(int val); + int getCompressionValue(); + + void setSubSamplingValue(int val); + int getSubSamplingValue(); + +private: + + JPEGSettingsPriv* d; +}; + +} // namespace Digikam + +#endif /* JPEGSETTINGS_H */ diff --git a/src/libs/dimg/loaders/pngloader.cpp b/src/libs/dimg/loaders/pngloader.cpp new file mode 100644 index 00000000..402cf944 --- /dev/null +++ b/src/libs/dimg/loaders/pngloader.cpp @@ -0,0 +1,993 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-11-01 + * Description : a PNG image loader for DImg framework. + * + * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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. + * + * ============================================================ */ + +// This line must be commented to prevent any latency time +// when we use threaded image loader interface for each image +// files io. Uncomment this line only for debugging. +//#define ENABLE_DEBUG_MESSAGES + +#define PNG_BYTES_TO_CHECK 4 + +// C Ansi includes. + +extern "C" +{ +#include <unistd.h> +#include <stdarg.h> +} + +// C++ includes. + +#include <cstdlib> +#include <cstdio> + +// TQt includes. + +#include <tqfile.h> +#include <tqcstring.h> + +// Local includes. + +#include "daboutdata.h" +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "pngloader.h" + +namespace Digikam +{ + +#if PNG_LIBPNG_VER_MAJOR > 1 || ( PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5 ) + typedef png_bytep iCCP_data; +#else + typedef png_charp iCCP_data; +#endif + +PNGLoader::PNGLoader(DImg* image) + : DImgLoader(image) +{ + m_hasAlpha = false; + m_sixteenBit = false; +} + +bool PNGLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + png_uint_32 w32, h32; + int width, height; + FILE *f; + int bit_depth, color_type, interlace_type; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + + readMetadata(filePath, DImg::PNG); + + // ------------------------------------------------------------------- + // Open the file + + f = fopen(TQFile::encodeName(filePath), "rb"); + if ( !f ) + { + DDebug() << k_funcinfo << "Cannot open image file." << endl; + return false; + } + + unsigned char buf[PNG_BYTES_TO_CHECK]; + + fread(buf, 1, PNG_BYTES_TO_CHECK, f); + if (png_sig_cmp(buf, 0, PNG_BYTES_TO_CHECK)) + { + DDebug() << k_funcinfo << "Not a PNG image file." << endl; + fclose(f); + return false; + } + rewind(f); + + // ------------------------------------------------------------------- + // Initialize the internal structures + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) + { + DDebug() << k_funcinfo << "Invalid PNG image file structure." << endl; + fclose(f); + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + DDebug() << k_funcinfo << "Cannot reading PNG image file structure." << endl; + png_destroy_read_struct(&png_ptr, NULL, NULL); + fclose(f); + return false; + } + + // ------------------------------------------------------------------- + // PNG error handling. If an error occurs during reading, libpng + // will jump here + + if (setjmp(png_jmpbuf(png_ptr))) + { + DDebug() << k_funcinfo << "Internal libPNG error during reading file. Process aborted!" << endl; + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(f); + return false; + } + + png_init_io(png_ptr, f); + + // ------------------------------------------------------------------- + // Read all PNG info up to image data + + png_read_info(png_ptr, info_ptr); + + png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *) (&w32), + (png_uint_32 *) (&h32), &bit_depth, &color_type, + &interlace_type, NULL, NULL); + + width = (int)w32; + height = (int)h32; + + // TODO: Endianness: + // You may notice that the code for little and big endian + // below is now identical. This was found to work by PPC users. + // If this proves right, all the conditional clauses can be removed. + + if (bit_depth == 16) + { +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in 16 bits/color/pixel." << endl; +#endif + m_sixteenBit = true; + + switch (color_type) + { + case PNG_COLOR_TYPE_RGB : // RGB +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_RGB" << endl; +#endif + m_hasAlpha = false; + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); + else // PPC + png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); + + break; + + case PNG_COLOR_TYPE_RGB_ALPHA : // RGBA +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_RGB_ALPHA" << endl; +#endif + m_hasAlpha = true; + break; + + case PNG_COLOR_TYPE_GRAY : // Grayscale +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_GRAY" << endl; +#endif + png_set_gray_to_rgb(png_ptr); + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); + else // PPC + png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); + + m_hasAlpha = false; + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA : // Grayscale + Alpha +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_GRAY_ALPHA" << endl; +#endif + png_set_gray_to_rgb(png_ptr); + m_hasAlpha = true; + break; + + case PNG_COLOR_TYPE_PALETTE : // Indexed +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_PALETTE" << endl; +#endif + png_set_palette_to_rgb(png_ptr); + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); + else // PPC + png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); + + m_hasAlpha = false; + break; + + default: +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << "PNG color type unknown." << endl; +#endif + return false; + } + } + else + { +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << "PNG in >=8 bits/color/pixel." << endl; +#endif + m_sixteenBit = false; + png_set_packing(png_ptr); + + switch (color_type) + { + case PNG_COLOR_TYPE_RGB : // RGB +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_RGB" << endl; +#endif + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + else // PPC + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + + m_hasAlpha = false; + break; + + case PNG_COLOR_TYPE_RGB_ALPHA : // RGBA +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_RGB_ALPHA" << endl; +#endif + m_hasAlpha = true; + break; + + case PNG_COLOR_TYPE_GRAY : // Grayscale +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_GRAY" << endl; +#endif + png_set_expand_gray_1_2_4_to_8(png_ptr); + png_set_gray_to_rgb(png_ptr); + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + else // PPC + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + + m_hasAlpha = false; + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA : // Grayscale + alpha +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_GRAY_ALPHA" << endl; +#endif + png_set_gray_to_rgb(png_ptr); + m_hasAlpha = true; + break; + + case PNG_COLOR_TYPE_PALETTE : // Indexed +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_PALETTE" << endl; +#endif + png_set_packing(png_ptr); + png_set_palette_to_rgb(png_ptr); + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + else // PPC + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + + m_hasAlpha = true; + break; + + default: +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << "PNG color type unknown." << endl; +#endif + return false; + } + } + + if(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png_ptr); + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_bgr(png_ptr); + else // PPC + png_set_bgr(png_ptr); + //png_set_swap_alpha(png_ptr); + + if (observer) + observer->progressInfo(m_image, 0.1); + + // ------------------------------------------------------------------- + // Get image data. + + // Call before png_read_update_info and png_start_read_image() + // For non-interlaced images number_passes will be 1 + int number_passes = png_set_interlace_handling(png_ptr); + + png_read_update_info(png_ptr, info_ptr); + + uchar *data = 0; + + if (m_sixteenBit) + data = new uchar[width*height*8]; // 16 bits/color/pixel + else + data = new uchar[width*height*4]; // 8 bits/color/pixel + + uchar **lines = 0; + lines = (uchar **)malloc(height * sizeof(uchar *)); + if (!lines) + { + DDebug() << k_funcinfo << "Cannot allocate memory to load PNG image data." << endl; + png_read_end(png_ptr, info_ptr); + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); + fclose(f); + delete [] data; + return false; + } + + for (int i = 0; i < height; i++) + { + if (m_sixteenBit) + lines[i] = data + (i * width * 8); + else + lines[i] = data + (i * width * 4); + } + + // The easy way to read the whole image + // png_read_image(png_ptr, lines); + // The other way to read images is row by row. Necessary for observer. + // Now we need to deal with interlacing. + + for (int pass = 0; pass < number_passes; pass++) + { + int y; + int checkPoint = 0; + for (y = 0; y < height; y++) + { + if (observer && y == checkPoint) + { + checkPoint += granularity(observer, height, 0.7); + if (!observer->continueQuery(m_image)) + { + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); + fclose(f); + delete [] data; + free(lines); + return false; + } + // use 10% - 80% for progress while reading rows + observer->progressInfo(m_image, 0.1 + (0.7 * ( ((float)y)/((float)height) )) ); + } + + png_read_rows(png_ptr, lines+y, NULL, 1); + } + } + + free(lines); + + // Swap bytes in 16 bits/color/pixel for DImg + + if (m_sixteenBit) + { + uchar ptr[8]; // One pixel to swap + + for (int p = 0; p < width*height*8; p+=8) + { + memcpy (&ptr[0], &data[p], 8); // Current pixel + + data[ p ] = ptr[1]; // Blue + data[p+1] = ptr[0]; + data[p+2] = ptr[3]; // Green + data[p+3] = ptr[2]; + data[p+4] = ptr[5]; // Red + data[p+5] = ptr[4]; + data[p+6] = ptr[7]; // Alpha + data[p+7] = ptr[6]; + } + } + + if (observer) + observer->progressInfo(m_image, 0.9); + + // ------------------------------------------------------------------- + // Read image ICC profile + + TQMap<int, TQByteArray>& metaData = imageMetaData(); + +#if PNG_LIBPNG_VER_MAJOR > 1 || ( PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5 ) + png_charp profile_name; + iCCP_data profile_data=NULL; +#else + png_charp profile_name, profile_data=NULL; +#endif + png_uint_32 profile_size; + int compression_type; + + png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &profile_size); + + if (profile_data != NULL) + { + TQByteArray profile_rawdata(profile_size); + memcpy(profile_rawdata.data(), profile_data, profile_size); + metaData.insert(DImg::ICC, profile_rawdata); + } + else + { + // If ICC profile is null, check Exif metadata. + checkExifWorkingColorSpace(); + } + + // ------------------------------------------------------------------- + // Get embbeded text data. + + png_text* text_ptr; + int num_comments = png_get_text(png_ptr, info_ptr, &text_ptr, NULL); + + /* + Standard Embedded text includes in PNG : + + Title Short (one line) title or caption for image + Author Name of image's creator + Description Description of image (possibly long) + Copyright Copyright notice + Creation Time Time of original image creation + Software Software used to create the image + Disclaimer Legal disclaimer + Warning Warning of nature of content + Source Device used to create the image + Comment Miscellaneous comment; conversion from GIF comment + + Extra Raw profiles tag are used by ImageMAgick and defines at this URL : + http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-5.87/html/TagNames/PNG.html#TextualData + */ + + for (int i = 0; i < num_comments; i++) + { + // Check if we have a Raw profile embedded using ImageMagick technic. + + if (memcmp(text_ptr[i].key, "Raw profile type exif", 21) != 0 || + memcmp(text_ptr[i].key, "Raw profile type APP1", 21) != 0 || + memcmp(text_ptr[i].key, "Raw profile type iptc", 21) != 0) + { + imageSetEmbbededText(text_ptr[i].key, text_ptr[i].text); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "Reading PNG Embedded text: key=" << text_ptr[i].key + << " text=" << text_ptr[i].text << endl; +#endif + } + } + + // ------------------------------------------------------------------- + + png_read_end(png_ptr, info_ptr); + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); + fclose(f); + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageWidth() = width; + imageHeight() = height; + imageData() = data; + imageSetAttribute("format", "PNG"); + + return true; +} + +bool PNGLoader::save(const TQString& filePath, DImgLoaderObserver *observer) +{ + FILE *f; + png_structp png_ptr; + png_infop info_ptr; + uchar *ptr, *data = 0; + uint x, y, j; + png_bytep row_ptr; + png_color_8 sig_bit; + int quality = 75; + int compression = 3; + + // ------------------------------------------------------------------- + // Open the file + + f = fopen(TQFile::encodeName(filePath), "wb"); + if ( !f ) + { + DDebug() << k_funcinfo << "Cannot open target image file." << endl; + return false; + } + + + // ------------------------------------------------------------------- + // Initialize the internal structures + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) + { + DDebug() << k_funcinfo << "Invalid target PNG image file structure." << endl; + fclose(f); + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) + { + DDebug() << k_funcinfo << "Cannot create PNG image file structure." << endl; + png_destroy_write_struct(&png_ptr, (png_infopp) NULL); + fclose(f); + return false; + } + + // ------------------------------------------------------------------- + // PNG error handling. If an error occurs during writing, libpng + // will jump here + + if (setjmp(png_jmpbuf(png_ptr))) + { + DDebug() << k_funcinfo << "Internal libPNG error during writing file. Process aborted!" << endl; + fclose(f); + png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); + png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); + return false; + } + + png_init_io(png_ptr, f); + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_bgr(png_ptr); + else // PPC + png_set_swap_alpha(png_ptr); + + if (imageHasAlpha()) + { + png_set_IHDR(png_ptr, info_ptr, imageWidth(), imageHeight(), imageBitsDepth(), + PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + if (imageSixteenBit()) + data = new uchar[imageWidth() * 8 * sizeof(uchar)]; + else + data = new uchar[imageWidth() * 4 * sizeof(uchar)]; + } + else + { + png_set_IHDR(png_ptr, info_ptr, imageWidth(), imageHeight(), imageBitsDepth(), + PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + if (imageSixteenBit()) + data = new uchar[imageWidth() * 6 * sizeof(uchar)]; + else + data = new uchar[imageWidth() * 3 * sizeof(uchar)]; + } + + sig_bit.red = imageBitsDepth(); + sig_bit.green = imageBitsDepth(); + sig_bit.blue = imageBitsDepth(); + sig_bit.alpha = imageBitsDepth(); + png_set_sBIT(png_ptr, info_ptr, &sig_bit); + + // ------------------------------------------------------------------- + // Quality to convert to compression + + TQVariant qualityAttr = imageGetAttribute("quality"); + quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; + + if (quality < 1) + quality = 1; + if (quality > 99) + quality = 99; + + quality = quality / 10; + compression = 9 - quality; + + if (compression < 0) + compression = 0; + if (compression > 9) + compression = 9; + + png_set_compression_level(png_ptr, compression); + + // ------------------------------------------------------------------- + // Write ICC profil. + + TQByteArray profile_rawdata = m_image->getICCProfil(); + + if (!profile_rawdata.isEmpty()) + { +#if PNG_LIBPNG_VER_MAJOR > 1 || ( PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5 ) + png_set_iCCP(png_ptr, info_ptr, (png_charp)("icc"), PNG_COMPRESSION_TYPE_BASE, reinterpret_cast<iCCP_data>(profile_rawdata.data()), profile_rawdata.size()); +#else + png_set_iCCP(png_ptr, info_ptr, (png_charp)"icc", PNG_COMPRESSION_TYPE_BASE, profile_rawdata.data(), profile_rawdata.size()); +#endif + } + + // ------------------------------------------------------------------- + // Write embbeded Text + + typedef TQMap<TQString, TQString> EmbeddedTextMap; + EmbeddedTextMap map = imageEmbeddedText(); + + for (EmbeddedTextMap::iterator it = map.begin(); it != map.end(); ++it) + { + if (it.key() != TQString("Software") && it.key() != TQString("Comment")) + { + png_text text; + text.key = (char*)it.key().ascii(); + text.text = (char*)it.data().ascii(); +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "Writing PNG Embedded text: key=" << text.key << " text=" << text.text << endl; +#endif + text.compression = PNG_TEXT_COMPRESSION_zTXt; + png_set_text(png_ptr, info_ptr, &(text), 1); + } + } + + // Update 'Software' text tag. + TQString software("digiKam "); + software.append(digikam_version); + TQString libpngver(PNG_HEADER_VERSION_STRING); + libpngver.replace('\n', ' '); + software.append(TQString(" (%1)").arg(libpngver)); + png_text text; + text.key = (png_charp)("Software"); + text.text = (char *)software.ascii(); +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "Writing PNG Embedded text: key=" << text.key << " text=" << text.text << endl; +#endif + text.compression = PNG_TEXT_COMPRESSION_zTXt; + png_set_text(png_ptr, info_ptr, &(text), 1); + + // Write embedded Raw profiles metadata (Exif/Iptc) in text tag using ImageMagick technic. + // Write digiKam comment like an iTXt chunk using UTF8 encoding. + // NOTE: iTXt will be enable by default with libpng >= 1.3.0. + + typedef TQMap<int, TQByteArray> MetaDataMap; + MetaDataMap metaDataMap = imageMetaData(); + + for (MetaDataMap::iterator it = metaDataMap.begin(); it != metaDataMap.end(); ++it) + { + TQByteArray ba = it.data(); + + switch (it.key()) + { + +#ifdef PNG_iTXt_SUPPORTED + + // TODO : this code is not yet tested. It require libpng 1.3.0. + + case(DImg::COM): + { + png_text comment; + comment.key = "Comment"; + comment.text = ba.data(); + comment.itxt_length = ba.size(); + comment.compression = PNG_ITXT_COMPRESSION_zTXt; + png_set_text(png_ptr, info_ptr, &(comment), 1); + + DDebug() << "Writing digiKam comment into iTXt PNG chunk : " << ba << endl; + break; + } +#endif + + case(DImg::EXIF): + { + const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; + TQByteArray profile; + + // If bytes array do not start with ImageMagick header, Exif metadata have been created from + // scratch using Exiv2. In this case, we need to add Exif header from start. + if (memcmp(ba.data(), "exif", 4) != 0 && + memcmp(ba.data(), "iptc", 4) != 0 && + memcmp(ba.data(), "profile", 7) != 0) + { + profile = TQByteArray(ba.size() + sizeof(ExifHeader)); + memcpy(profile.data(), ExifHeader, sizeof(ExifHeader)); + memcpy(profile.data()+sizeof(ExifHeader), ba.data(), ba.size()); + } + else + { + profile = ba; + } + + writeRawProfile(png_ptr, info_ptr, (png_charp)("exif"), profile.data(), (png_uint_32) profile.size()); + break; + } + case(DImg::IPTC): + { + writeRawProfile(png_ptr, info_ptr, (png_charp)("iptc"), ba.data(), (png_uint_32) ba.size()); + break; + } + default: + break; + } + } + + if (observer) + observer->progressInfo(m_image, 0.2); + + // ------------------------------------------------------------------- + // Write image data + + png_write_info(png_ptr, info_ptr); + png_set_shift(png_ptr, &sig_bit); + png_set_packing(png_ptr); + ptr = imageData(); + + uint checkPoint = 0; + for (y = 0; y < imageHeight(); y++) + { + + if (observer && y == checkPoint) + { + checkPoint += granularity(observer, imageHeight(), 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + fclose(f); + png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); + png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); + return false; + } + observer->progressInfo(m_image, 0.2 + (0.8 * ( ((float)y)/((float)imageHeight()) ))); + } + + j = 0; + + for (x = 0; x < imageWidth()*imageBytesDepth(); x+=imageBytesDepth()) + { + if (imageSixteenBit()) + { + if (imageHasAlpha()) + { + data[j++] = ptr[x+1]; // Blue + data[j++] = ptr[ x ]; + data[j++] = ptr[x+3]; // Green + data[j++] = ptr[x+2]; + data[j++] = ptr[x+5]; // Red + data[j++] = ptr[x+4]; + data[j++] = ptr[x+7]; // Alpha + data[j++] = ptr[x+6]; + } + else + { + data[j++] = ptr[x+1]; // Blue + data[j++] = ptr[ x ]; + data[j++] = ptr[x+3]; // Green + data[j++] = ptr[x+2]; + data[j++] = ptr[x+5]; // Red + data[j++] = ptr[x+4]; + } + } + else + { + if (imageHasAlpha()) + { + data[j++] = ptr[ x ]; // Blue + data[j++] = ptr[x+1]; // Green + data[j++] = ptr[x+2]; // Red + data[j++] = ptr[x+3]; // Alpha + } + else + { + data[j++] = ptr[ x ]; // Blue + data[j++] = ptr[x+1]; // Green + data[j++] = ptr[x+2]; // Red + } + } + } + + row_ptr = (png_bytep) data; + + png_write_rows(png_ptr, &row_ptr, 1); + ptr += (imageWidth() * imageBytesDepth()); + } + + delete [] data; + + // ------------------------------------------------------------------- + + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); + png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); + + fclose(f); + + imageSetAttribute("savedformat", "PNG"); + + saveMetadata(filePath); + + return true; +} + +bool PNGLoader::hasAlpha() const +{ + return m_hasAlpha; +} + +bool PNGLoader::sixteenBit() const +{ + return m_sixteenBit; +} + +void PNGLoader::writeRawProfile(png_struct *ping, png_info *ping_info, char *profile_type, + char *profile_data, png_uint_32 length) +{ + png_textp text; + + long i; + + uchar *sp; + + png_charp dp; + + png_uint_32 allocated_length, description_length; + + const uchar hex[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; + + DDebug() << "Writing Raw profile: type=" << profile_type << ", length=" << length << endl; + + text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text)); + description_length = strlen((const char *) profile_type); + allocated_length = (png_uint_32) (length*2 + (length >> 5) + 20 + description_length); + + text[0].text = (png_charp) png_malloc(ping, allocated_length); + text[0].key = (png_charp) png_malloc(ping, (png_uint_32) 80); + text[0].key[0] = '\0'; + + concatenateString(text[0].key, "Raw profile type ", 4096); + concatenateString(text[0].key, (const char *) profile_type, 62); + + sp = (uchar*)profile_data; + dp = text[0].text; + *dp++='\n'; + + copyString(dp, (const char *) profile_type, allocated_length); + + dp += description_length; + *dp++='\n'; + + formatString(dp, allocated_length-strlen(text[0].text), "%8lu ", length); + + dp += 8; + + for (i=0; i < (long) length; i++) + { + if (i%36 == 0) + *dp++='\n'; + + *(dp++)=(char) hex[((*sp >> 4) & 0x0f)]; + *(dp++)=(char) hex[((*sp++ ) & 0x0f)]; + } + + *dp++='\n'; + *dp='\0'; + text[0].text_length = (png_size_t) (dp-text[0].text); + text[0].compression = -1; + + if (text[0].text_length <= allocated_length) + png_set_text(ping, ping_info,text, 1); + + png_free(ping, text[0].text); + png_free(ping, text[0].key); + png_free(ping, text); +} + +size_t PNGLoader::concatenateString(char *destination, const char *source, const size_t length) +{ + char *q; + + const char *p; + + size_t i; + + size_t count; + + if ( !destination || !source || length == 0 ) + return 0; + + p = source; + q = destination; + i = length; + + while ((i-- != 0) && (*q != '\0')) + q++; + + count = (size_t) (q-destination); + i = length-count; + + if (i == 0) + return(count+strlen(p)); + + while (*p != '\0') + { + if (i != 1) + { + *q++=(*p); + i--; + } + p++; + } + + *q='\0'; + + return(count+(p-source)); +} + +size_t PNGLoader::copyString(char *destination, const char *source, const size_t length) +{ + char *q; + + const char *p; + + size_t i; + + if ( !destination || !source || length == 0 ) + return 0; + + p = source; + q = destination; + i = length; + + if ((i != 0) && (--i != 0)) + { + do + { + if ((*q++=(*p++)) == '\0') + break; + } + while (--i != 0); + } + + if (i == 0) + { + if (length != 0) + *q='\0'; + + do + { + } + while (*p++ != '\0'); + } + + return((size_t) (p-source-1)); +} + +long PNGLoader::formatString(char *string, const size_t length, const char *format,...) +{ + long n; + + va_list operands; + + va_start(operands,format); + n = (long) formatStringList(string, length, format, operands); + va_end(operands); + return(n); +} + +long PNGLoader::formatStringList(char *string, const size_t length, const char *format, va_list operands) +{ + int n = vsnprintf(string, length, format, operands); + + if (n < 0) + string[length-1] = '\0'; + + return((long) n); +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/pngloader.h b/src/libs/dimg/loaders/pngloader.h new file mode 100644 index 00000000..202a278c --- /dev/null +++ b/src/libs/dimg/loaders/pngloader.h @@ -0,0 +1,73 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-11-01 + * Description : a PNG image loader for DImg framework. + * + * Copyright (C) 2005-2009 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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 PNGLOADER_H +#define PNGLOADER_H + +extern "C" +{ +#include <stdarg.h> +#include <png.h> +} + +// Local includes. + +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ +class DImg; + +class DIGIKAM_EXPORT PNGLoader : public DImgLoader +{ +public: + + PNGLoader(DImg* image); + + bool load(const TQString& filePath, DImgLoaderObserver *observer); + bool save(const TQString& filePath, DImgLoaderObserver *observer); + + virtual bool hasAlpha() const; + virtual bool sixteenBit() const; + virtual bool isReadOnly() const { return false; }; + +private: + + void writeRawProfile(png_struct *ping, png_info *ping_info, char *profile_type, + char *profile_data, png_uint_32 length); + + size_t concatenateString(char *destination, const char *source, const size_t length); + size_t copyString(char *destination, const char *source, const size_t length); + long formatString(char *string, const size_t length, const char *format,...); + long formatStringList(char *string, const size_t length, const char *format, va_list operands); + +private: + + bool m_sixteenBit; + bool m_hasAlpha; +}; + +} // NameSpace Digikam + +#endif /* PNGLOADER_H */ diff --git a/src/libs/dimg/loaders/pngsettings.cpp b/src/libs/dimg/loaders/pngsettings.cpp new file mode 100644 index 00000000..ce39219b --- /dev/null +++ b/src/libs/dimg/loaders/pngsettings.cpp @@ -0,0 +1,102 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save PNG image options. + * + * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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. + * + * ============================================================ */ + +// TQt includes. + +#include <tqstring.h> +#include <tqlabel.h> +#include <tqlayout.h> +#include <tqwhatsthis.h> + +// KDE includes. + +#include <tdelocale.h> +#include <kdialog.h> +#include <knuminput.h> + +// Local includes. + +#include "pngsettings.h" +#include "pngsettings.moc" + +namespace Digikam +{ + +class PNGSettingsPriv +{ + +public: + + PNGSettingsPriv() + { + PNGGrid = 0; + labelPNGcompression = 0; + PNGcompression = 0; + } + + TQGridLayout *PNGGrid; + + TQLabel *labelPNGcompression; + + KIntNumInput *PNGcompression; +}; + +PNGSettings::PNGSettings(TQWidget *parent) + : TQWidget(parent, 0, TQt::WDestructiveClose) +{ + d = new PNGSettingsPriv; + + d->PNGGrid = new TQGridLayout(this, 1, 1, KDialog::spacingHint()); + d->PNGcompression = new KIntNumInput(9, this); + d->PNGcompression->setRange(1, 9, 1, true ); + d->labelPNGcompression = new TQLabel(i18n("PNG compression:"), this); + + TQWhatsThis::add(d->PNGcompression, i18n("<p>The compression value for PNG images:<p>" + "<b>1</b>: low compression (large file size but " + "short compression duration - default)<p>" + "<b>5</b>: medium compression<p>" + "<b>9</b>: high compression (small file size but " + "long compression duration)<p>" + "<b>Note: PNG is always a lossless image " + "compression format.</b>")); + d->PNGGrid->addMultiCellWidget(d->labelPNGcompression, 0, 0, 0, 0); + d->PNGGrid->addMultiCellWidget(d->PNGcompression, 0, 0, 1, 1); + d->PNGGrid->setColStretch(1, 10); +} + +PNGSettings::~PNGSettings() +{ + delete d; +} + +void PNGSettings::setCompressionValue(int val) +{ + d->PNGcompression->setValue(val); +} + +int PNGSettings::getCompressionValue() +{ + return d->PNGcompression->value(); +} + +} // namespace Digikam diff --git a/src/libs/dimg/loaders/pngsettings.h b/src/libs/dimg/loaders/pngsettings.h new file mode 100644 index 00000000..aecca935 --- /dev/null +++ b/src/libs/dimg/loaders/pngsettings.h @@ -0,0 +1,60 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save PNG image options. + * + * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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 PNGSETTINGS_H +#define PNGSETTINGS_H + +// KDE includes. + +#include <tqwidget.h> + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class PNGSettingsPriv; + +class DIGIKAM_EXPORT PNGSettings : public TQWidget +{ +TQ_OBJECT + + +public: + + PNGSettings(TQWidget *parent=0); + ~PNGSettings(); + + void setCompressionValue(int val); + int getCompressionValue(); + +private: + + PNGSettingsPriv* d; +}; + +} // namespace Digikam + +#endif /* PNGSETTINGS_H */ diff --git a/src/libs/dimg/loaders/ppmloader.cpp b/src/libs/dimg/loaders/ppmloader.cpp new file mode 100644 index 00000000..15c19423 --- /dev/null +++ b/src/libs/dimg/loaders/ppmloader.cpp @@ -0,0 +1,178 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-21-11 + * Description : A 16 bits/color/pixel PPM IO file for + * DImg framework + * + * Copyright (C) 2005 by Renchi Raju <[email protected]> + * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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. + * + * ============================================================ */ + +// This line must be commented to prevent any latency time +// when we use threaded image loader interface for each image +// files io. Uncomment this line only for debugging. +//#define ENABLE_DEBUG_MESSAGES + +// C ansi includes. + +extern "C" +{ +#include <unistd.h> +} + +// C++ includes. + +#include <cstdio> +#include <cmath> + +// TQt includes. + +#include <tqfile.h> +#include <tqimage.h> + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "ppmloader.h" + +namespace Digikam +{ + +PPMLoader::PPMLoader(DImg* image) + : DImgLoader(image) +{ +} + +bool PPMLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + //TODO: progress information + int width, height, rgbmax; + char nl; + + FILE *file = fopen(TQFile::encodeName(filePath), "rb"); + if (!file) + { + DDebug() << k_funcinfo << "Cannot open image file." << endl; + return false; + } + + ushort header; + + if (fread(&header, 2, 1, file) != 1) + { + DDebug() << k_funcinfo << "Cannot read header of file." << endl; + fclose(file); + return false; + } + + uchar* c = (uchar*) &header; + if (*c != 'P') + { + DDebug() << k_funcinfo << "Not a PPM file." << endl; + fclose(file); + return false; + } + + c++; + if (*c != '6') + { + DDebug() << k_funcinfo << "Not a PPM file." << endl; + fclose(file); + return false; + } + + rewind(file); + + if (fscanf (file, "P6 %d %d %d%c", &width, &height, &rgbmax, &nl) != 4) + { + DDebug() << "Corrupted PPM file." << endl; + pclose (file); + return false; + } + + if (rgbmax <= 255) + { + DDebug() << k_funcinfo << "Not a 16 bits per color per pixel PPM file." << endl; + pclose (file); + return false; + } + + if (observer) + observer->progressInfo(m_image, 0.1); + + unsigned short *data; + + data = new unsigned short[width*height*4]; + unsigned short *dst = data; + uchar src[6]; + float fac = 65535.0 / rgbmax; + int checkpoint = 0; + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "rgbmax=" << rgbmax << " fac=" << fac << endl; +#endif + + for (int h = 0; h < height; h++) + { + + if (observer && h == checkpoint) + { + checkpoint += granularity(observer, height, 0.9); + if (!observer->continueQuery(m_image)) + { + delete [] data; + pclose( file ); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.9 * ( ((float)h)/((float)height) ))); + } + + for (int w = 0; w < width; w++) + { + + fread (src, 6 *sizeof(unsigned char), 1, file); + + dst[0] = (unsigned short)((src[4]*256 + src[5]) * fac); // Blue + dst[1] = (unsigned short)((src[2]*256 + src[3]) * fac); // Green + dst[2] = (unsigned short)((src[0]*256 + src[1]) * fac); // Red + dst[3] = 0xFFFF; + + dst += 4; + } + } + + fclose( file ); + + //---------------------------------------------------------- + + imageWidth() = width; + imageHeight() = height; + imageData() = (uchar*)data; + imageSetAttribute("format", "PPM"); + + return true; +} + +bool PPMLoader::save(const TQString& /*filePath*/, DImgLoaderObserver */*observer*/) +{ + return false; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/ppmloader.h b/src/libs/dimg/loaders/ppmloader.h new file mode 100644 index 00000000..283fdd26 --- /dev/null +++ b/src/libs/dimg/loaders/ppmloader.h @@ -0,0 +1,59 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-21-11 + * Description : A 16 bits/color/pixel PPM IO file for + * DImg framework + * + * Copyright (C) 2005 by Renchi Raju <[email protected]> + * Copyright (C) 2005-2006 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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 PPMLOADER_H +#define PPMLOADER_H + +// Local includes. + +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ +class DImg; + +class DIGIKAM_EXPORT PPMLoader : public DImgLoader +{ +public: + + PPMLoader(DImg* image); + + bool load(const TQString& filePath, DImgLoaderObserver *observer); + bool save(const TQString& filePath, DImgLoaderObserver *observer); + + virtual bool hasAlpha() const { return false; }; + virtual bool sixteenBit() const { return true; }; + virtual bool isReadOnly() const { return true; }; + +private: + + bool m_alpha; + bool m_sixteenBit; +}; + +} // NameSpace Digikam + +#endif /* PPMLOADER_H */ diff --git a/src/libs/dimg/loaders/qimageloader.cpp b/src/libs/dimg/loaders/qimageloader.cpp new file mode 100644 index 00000000..f35335cf --- /dev/null +++ b/src/libs/dimg/loaders/qimageloader.cpp @@ -0,0 +1,126 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-14 + * Description : A TQImage loader for DImg framework. + * + * Copyright (C) 2005 by Renchi Raju <[email protected]> + * Copyright (C) 2006-2007 by Caulier Gilles <caulier dot gilles at gmail dot com> + * + * 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. + * + * ============================================================ */ + +// TQt includes. + +#include <tqimage.h> + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "qimageloader.h" + +namespace Digikam +{ + +TQImageLoader::TQImageLoader(DImg* image) + : DImgLoader(image) +{ +} + +bool TQImageLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + // Loading is opaque to us. No support for stopping from observer, + // progress info are only pseudo values + TQImage image(filePath); + + if (observer) + observer->progressInfo(m_image, 0.9); + + if (image.isNull()) + { + DDebug() << "Cannot loading \"" << filePath << "\" using DImg::TQImageLoader!" << endl; + return false; + } + + m_hasAlpha = image.hasAlphaBuffer(); + TQImage target = image.convertDepth(32); + + uint w = target.width(); + uint h = target.height(); + uchar* data = new uchar[w*h*4]; + uint* sptr = (uint*)target.bits(); + uchar* dptr = data; + + for (uint i = 0 ; i < w*h ; i++) + { + dptr[0] = tqBlue(*sptr); + dptr[1] = tqGreen(*sptr); + dptr[2] = tqRed(*sptr); + dptr[3] = tqAlpha(*sptr); + + dptr += 4; + sptr++; + } + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageWidth() = w; + imageHeight() = h; + imageData() = data; + + // We considering that PNG is the most representative format of an image loaded by TQt + imageSetAttribute("format", "PNG"); + + return true; +} + +bool TQImageLoader::save(const TQString& filePath, DImgLoaderObserver *observer) +{ + TQVariant qualityAttr = imageGetAttribute("quality"); + int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; + + if (quality < 0) + quality = 90; + if (quality > 100) + quality = 100; + + TQVariant formatAttr = imageGetAttribute("format"); + TQCString format = formatAttr.toCString(); + + TQImage image = m_image->copyTQImage(); + + if (observer) + observer->progressInfo(m_image, 0.1); + + // Saving is opaque to us. No support for stopping from observer, + // progress info are only pseudo values + bool success = image.save(filePath, format.upper(), quality); + if (observer && success) + observer->progressInfo(m_image, 1.0); + + imageSetAttribute("format", format.upper()); + + return success; +} + +bool TQImageLoader::hasAlpha() const +{ + return m_hasAlpha; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/qimageloader.h b/src/libs/dimg/loaders/qimageloader.h new file mode 100644 index 00000000..f81cf7ef --- /dev/null +++ b/src/libs/dimg/loaders/qimageloader.h @@ -0,0 +1,57 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-14 + * Description : A TQImage loader for DImg framework. + * + * Copyright (C) 2005 by Renchi Raju <[email protected]> + * Copyright (C) 2006-2007 by Caulier Gilles <caulier dot gilles at gmail dot com> + * + * 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 TQIMAGELOADER_H +#define TQIMAGELOADER_H + +// Local includes. + +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ +class DImg; + +class DIGIKAM_EXPORT TQImageLoader : public DImgLoader +{ +public: + + TQImageLoader(DImg* image); + + virtual bool load(const TQString& filePath, DImgLoaderObserver *observer); + virtual bool save(const TQString& filePath, DImgLoaderObserver *observer); + + virtual bool hasAlpha() const; + virtual bool sixteenBit() const { return false; }; + virtual bool isReadOnly() const { return false; }; + +private: + + bool m_hasAlpha; +}; + +} // NameSpace Digikam + +#endif /* TQIMAGELOADER_H */ diff --git a/src/libs/dimg/loaders/rawloader.cpp b/src/libs/dimg/loaders/rawloader.cpp new file mode 100644 index 00000000..8ecaa1f3 --- /dev/null +++ b/src/libs/dimg/loaders/rawloader.cpp @@ -0,0 +1,371 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-11-01 + * Description : A digital camera RAW files loader for DImg + * framework using an external dcraw instance. + * + * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com> + * Copyright (C) 2005-2008 by Marcel Wiesweg <[email protected]> + * + * 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. + * + * ============================================================ */ + +// C++ includes. + +#include <cmath> + +// TQt includes. + +#include <tqcstring.h> + +// KDE includes. + +#include <kstandarddirs.h> + +// Local includes. + +#include "ddebug.h" +#include "imagehistogram.h" +#include "imagecurves.h" +#include "imagelevels.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "bcgmodifier.h" +#include "whitebalance.h" +#include "rawloader.h" +#include "rawloader.moc" + +namespace Digikam +{ + +RAWLoader::RAWLoader(DImg* image, DRawDecoding rawDecodingSettings) + : DImgLoader(image) +{ + m_rawDecodingSettings = rawDecodingSettings; + m_customRawSettings = rawDecodingSettings; + m_observer = 0; +} + +bool RAWLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + m_observer = observer; + + // We are using TDEProcess here, and make two assumptions: + // - there is an event loop (not for ioslaves) + // - we are not called from the event loop thread + // These assumptions are currently true for all use cases in digikam, + // except the thumbnails iosalve, which will set this attribute. + // I hope when porting to TQt4, all the event loop stuff (and this problem) can be removed. + if (imageGetAttribute("noeventloop").isValid()) + return false; + + readMetadata(filePath, DImg::RAW); + + // NOTE: Here, we don't check a possible embedded work-space color profile using + // the method checkExifWorkingColorSpace() like with JPEG, PNG, and TIFF loaders, + // because RAW file are always in linear mode. + + int width, height, rgbmax; + TQByteArray data; + if (!KDcrawIface::KDcraw::decodeRAWImage(filePath, m_rawDecodingSettings, + data, width, height, rgbmax)) + return false; + + return (loadedFromDcraw(data, width, height, rgbmax, observer)); +} + +bool RAWLoader::checkToCancelWaitingData() +{ + return (m_observer ? !m_observer->continueQuery(m_image) : false); +} + +void RAWLoader::setWaitingDataProgress(double value) +{ + if (m_observer) + m_observer->progressInfo(m_image, value); +} + +#if KDCRAW_VERSION < 0x000106 +bool RAWLoader::checkToCancelRecievingData() +{ + return (m_observer ? m_observer->isShuttingDown() : false); +} + +void RAWLoader::setRecievingDataProgress(double value) +{ + if (m_observer) + m_observer->progressInfo(m_image, value); +} +#endif + +bool RAWLoader::loadedFromDcraw(TQByteArray data, int width, int height, int rgbmax, + DImgLoaderObserver *observer) +{ + int checkpoint = 0; + + if (m_rawDecodingSettings.sixteenBitsImage) // 16 bits image + { + uchar *image = new uchar[width*height*8]; + + unsigned short *dst = (unsigned short *)image; + uchar *src = (uchar*)data.data(); + float fac = 65535.0 / rgbmax; + checkpoint = 0; + + for (int h = 0; h < height; h++) + { + if (observer && h == checkpoint) + { + checkpoint += granularity(observer, height, 1.0); + if (!observer->continueQuery(m_image)) + { + return false; + } + observer->progressInfo(m_image, 0.7 + 0.2*(((float)h)/((float)height)) ); + } + + for (int w = 0; w < width; w++) + { +#if KDCRAW_VERSION < 0x000106 + dst[0] = (unsigned short)((src[4]*256 + src[5]) * fac); // Blue + dst[1] = (unsigned short)((src[2]*256 + src[3]) * fac); // Green + dst[2] = (unsigned short)((src[0]*256 + src[1]) * fac); // Red +#else + dst[0] = (unsigned short)((src[5]*256 + src[4]) * fac); // Blue + dst[1] = (unsigned short)((src[3]*256 + src[2]) * fac); // Green + dst[2] = (unsigned short)((src[1]*256 + src[0]) * fac); // Red +#endif + dst[3] = 0xFFFF; + + dst += 4; + src += 6; + } + } + + +#if KDCRAW_VERSION < 0x000106 + // ---------------------------------------------------------- + + // Special case : if Color Management is not used here, output color space is in sRGB* color space + // RAW decoded image is a linear-histogram image with 16 bits color depth. + // No auto white balance and no gamma adjustemnts are performed. Image is a black hole. + // We need to reproduce all dcraw 8 bits color depth adjustements here. + + if (m_rawDecodingSettings.outputColorSpace != DRawDecoding::RAWCOLOR) + { + ImageHistogram histogram(image, width, height, true); + + int perc, val, total; + float white=0.0, r, gamma=2.222222; + unsigned short lut[65536]; + + // Search 99th percentile white level. + + perc = (int)(width * height * 0.01); + DDebug() << "White Level: " << perc << endl; + for (int c = 1 ; c < 4 ; c++) + { + total = 0; + for (val = 65535 ; val > 256 ; --val) + if ((total += (int)histogram.getValue(c, val)) > perc) + break; + + if (white < val) white = (float)val; + } + + white *= 1.0 / m_rawDecodingSettings.brightness; + + DDebug() << "White Point: " << white << endl; + + // Compute the Gamma lut accordingly. + + for (int i=0; i < 65536; i++) + { + r = i / white; + val = (int)(65536.0 * (r <= 0.018 ? r*4.5 : pow(r, 1.0/gamma) * 1.099-0.099)); + if (val > 65535) val = 65535; + lut[i] = val; + } + + // Apply Gamma lut to the whole image. + + unsigned short *im = (unsigned short *)image; + for (int i = 0; i < width*height; i++) + { + im[0] = lut[im[0]]; // Blue + im[1] = lut[im[1]]; // Green + im[2] = lut[im[2]]; // Red + im += 4; + } + } +#endif + + // ---------------------------------------------------------- + + imageData() = (uchar *)image; + } + else // 8 bits image + { + uchar *image = new uchar[width*height*4]; + uchar *dst = image; + uchar *src = (uchar*)data.data(); + checkpoint = 0; + + for (int h = 0; h < height; h++) + { + + if (observer && h == checkpoint) + { + checkpoint += granularity(observer, height, 1.0); + if (!observer->continueQuery(m_image)) + { + return false; + } + observer->progressInfo(m_image, 0.7 + 0.2*(((float)h)/((float)height)) ); + } + + for (int w = 0; w < width; w++) + { + // No need to adapt RGB components accordinly with rgbmax value because dcraw + // always return rgbmax to 255 in 8 bits/color/pixels. + + dst[0] = src[2]; // Blue + dst[1] = src[1]; // Green + dst[2] = src[0]; // Red + dst[3] = 0xFF; // Alpha + + dst += 4; + src += 3; + } + } + + // NOTE: if Color Management is not used here, output color space is in sRGB* color space. + // Gamma and White balance are previously adjusted by dcraw in 8 bits color depth. + + imageData() = image; + } + + //---------------------------------------------------------- + // Assign the right color-space profile. + + TDEGlobal::dirs()->addResourceType("profiles", TDEGlobal::dirs()->kde_default("data") + "digikam/profiles"); + switch(m_rawDecodingSettings.outputColorSpace) + { + case DRawDecoding::SRGB: + { + TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "srgb.icm"); + m_image->getICCProfilFromFile(directory + "srgb.icm"); + break; + } + case DRawDecoding::ADOBERGB: + { + TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "adobergb.icm"); + m_image->getICCProfilFromFile(directory + "adobergb.icm"); + break; + } + case DRawDecoding::WIDEGAMMUT: + { + TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "widegamut.icm"); + m_image->getICCProfilFromFile(directory + "widegamut.icm"); + break; + } + case DRawDecoding::PROPHOTO: + { + TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "prophoto.icm"); + m_image->getICCProfilFromFile(directory + "prophoto.icm"); + break; + } + default: + // No icc color-space profile to assign in RAW color mode. + break; + } + + //---------------------------------------------------------- + + + imageWidth() = width; + imageHeight() = height; + imageSetAttribute("format", "RAW"); + + postProcessing(observer); + + return true; +} + +void RAWLoader::postProcessing(DImgLoaderObserver *observer) +{ + if (!m_customRawSettings.postProcessingSettingsIsDirty()) + return; + + if (m_customRawSettings.exposureComp != 0.0 || m_customRawSettings.saturation != 1.0) + { + WhiteBalance wb(m_rawDecodingSettings.sixteenBitsImage); + wb.whiteBalance(imageData(), imageWidth(), imageHeight(), m_rawDecodingSettings.sixteenBitsImage, + 0.0, // black + m_customRawSettings.exposureComp, // exposure + 6500.0, // temperature (neutral) + 1.0, // green + 0.5, // dark + 1.0, // gamma + m_customRawSettings.saturation); // saturation + } + if (observer) observer->progressInfo(m_image, 0.92); + + if (m_customRawSettings.lightness != 0.0 || + m_customRawSettings.contrast != 1.0 || + m_customRawSettings.gamma != 1.0) + { + BCGModifier bcg; + bcg.setBrightness(m_customRawSettings.lightness); + bcg.setContrast(m_customRawSettings.contrast); + bcg.setGamma(m_customRawSettings.gamma); + bcg.applyBCG(imageData(), imageWidth(), imageHeight(), m_rawDecodingSettings.sixteenBitsImage); + } + if (observer) observer->progressInfo(m_image, 0.94); + + if (!m_customRawSettings.curveAdjust.isEmpty()) + { + DImg tmp(imageWidth(), imageHeight(), m_rawDecodingSettings.sixteenBitsImage); + ImageCurves curves(m_rawDecodingSettings.sixteenBitsImage); + curves.setCurvePoints(ImageHistogram::ValueChannel, m_customRawSettings.curveAdjust); + curves.curvesCalculateCurve(ImageHistogram::ValueChannel); + curves.curvesLutSetup(ImageHistogram::AlphaChannel); + curves.curvesLutProcess(imageData(), tmp.bits(), imageWidth(), imageHeight()); + memcpy(imageData(), tmp.bits(), tmp.numBytes()); + } + if (observer) observer->progressInfo(m_image, 0.96); + + if (!m_customRawSettings.levelsAdjust.isEmpty()) + { + DImg tmp(imageWidth(), imageHeight(), m_rawDecodingSettings.sixteenBitsImage); + ImageLevels levels(m_rawDecodingSettings.sixteenBitsImage); + int j=0; + for (int i = 0 ; i < 4; i++) + { + levels.setLevelLowInputValue(i, m_customRawSettings.levelsAdjust[j++]); + levels.setLevelHighInputValue(i, m_customRawSettings.levelsAdjust[j++]); + levels.setLevelLowOutputValue(i, m_customRawSettings.levelsAdjust[j++]); + levels.setLevelHighOutputValue(i, m_customRawSettings.levelsAdjust[j++]); + } + + levels.levelsLutSetup(ImageHistogram::AlphaChannel); + levels.levelsLutProcess(imageData(), tmp.bits(), imageWidth(), imageHeight()); + memcpy(imageData(), tmp.bits(), tmp.numBytes()); + } + if (observer) observer->progressInfo(m_image, 0.98); +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/rawloader.h b/src/libs/dimg/loaders/rawloader.h new file mode 100644 index 00000000..22171a20 --- /dev/null +++ b/src/libs/dimg/loaders/rawloader.h @@ -0,0 +1,86 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-11-01 + * Description : A digital camera RAW files loader for DImg + * framework using an external dcraw instance. + * + * Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com> + * Copyright (C) 2005-2008 by Marcel Wiesweg <[email protected]> + * + * 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 RAWLOADER_H +#define RAWLOADER_H + +// LibKDcraw includes. + +#include <libkdcraw/version.h> +#include <libkdcraw/kdcraw.h> + +// Local includes. + +#include "drawdecoding.h" +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ +class DImg; + +class DIGIKAM_EXPORT RAWLoader : public KDcrawIface::KDcraw, public DImgLoader +{ + TQ_OBJECT + + +public: + + RAWLoader(DImg* image, DRawDecoding rawDecodingSettings=DRawDecoding()); + + bool load(const TQString& filePath, DImgLoaderObserver *observer=0); + + // NOTE: RAW files are always Read only. + bool save(const TQString& /*filePath*/, DImgLoaderObserver */*observer=0*/) { return false; }; + + bool hasAlpha() const { return false; }; + bool isReadOnly() const { return true; }; + bool sixteenBit() const { return m_rawDecodingSettings.sixteenBitsImage; }; + +private: + + // Methods to load RAW image using external dcraw instance. + + bool loadedFromDcraw(TQByteArray data, int width, int height, int rgbmax, + DImgLoaderObserver *observer); + + bool checkToCancelWaitingData(); + void setWaitingDataProgress(double value); + void postProcessing(DImgLoaderObserver *observer); + +#if KDCRAW_VERSION < 0x000106 + bool checkToCancelRecievingData(); + void setRecievingDataProgress(double value); +#endif + +private: + + DImgLoaderObserver *m_observer; + DRawDecoding m_customRawSettings; +}; + +} // NameSpace Digikam + +#endif /* RAWLOADER_H */ diff --git a/src/libs/dimg/loaders/tiffloader.cpp b/src/libs/dimg/loaders/tiffloader.cpp new file mode 100644 index 00000000..2e554143 --- /dev/null +++ b/src/libs/dimg/loaders/tiffloader.cpp @@ -0,0 +1,806 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-17 + * Description : A TIFF IO file for DImg framework + * + * Copyright (C) 2005 by Renchi Raju <[email protected]> + * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * Specifications & references: + * - TIFF 6.0 : http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf + * - TIFF/EP : http://www.map.tu.chiba-u.ac.jp/IEC/100/TA2/recdoc/N4378.pdf + * - TIFF/Tags : http://www.awaresystems.be/imaging/tiff/tifftags.html + * - DNG : http://www.adobe.com/products/dng/pdfs/dng_spec.pdf + * + * Others Linux Tiff Loader implementation using libtiff: + * - http://websvn.kde.org/trunk/koffice/filters/krita/tiff/kis_tiff_converter.cc + * - http://artis.inrialpes.fr/Software/TiffIO/ + * - http://cvs.graphicsmagick.org/cgi-bin/cvsweb.cgi/GraphicsMagick/coders/tiff.c + * - http://freeimage.cvs.sourceforge.net/freeimage/FreeImage/Source/FreeImage/PluginTIFF.cpp + * - http://freeimage.cvs.sourceforge.net/freeimage/FreeImage/Source/Metadata/XTIFF.cpp + * - https://subversion.imagemagick.org/subversion/ImageMagick/trunk/coders/tiff.c + * + * Test images repository: + * - http://www.remotesensing.org/libtiff/images.html + * + * 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. + * + * ============================================================ */ + +// This line must be commented to prevent any latency time +// when we use threaded image loader interface for each image +// files io. Uncomment this line only for debugging. +//#define ENABLE_DEBUG_MESSAGES + +// C ANSI includes. + +extern "C" +{ +#include <tiffvers.h> +} + +// C++ includes. + +#include <cstdio> + +// TQt includes. + +#include <tqfile.h> + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "dmetadata.h" +#include "tiffloader.h" + +namespace Digikam +{ + +// To manage Errors/Warnings handling provide by libtiff + +void TIFFLoader::dimg_tiff_warning(const char* module, const char* format, va_list warnings) +{ +#ifdef ENABLE_DEBUG_MESSAGES + char message[4096]; + vsnprintf(message, 4096, format, warnings); + DDebug() << module << "::" << message << endl; +#else + Q_UNUSED(module); + Q_UNUSED(format); + Q_UNUSED(warnings); +#endif +} + +void TIFFLoader::dimg_tiff_error(const char* module, const char* format, va_list errors) +{ +#ifdef ENABLE_DEBUG_MESSAGES + char message[4096]; + vsnprintf(message, 4096, format, errors); + DDebug() << module << "::" << message << endl; +#else + Q_UNUSED(module); + Q_UNUSED(format); + Q_UNUSED(errors); +#endif +} + +TIFFLoader::TIFFLoader(DImg* image) + : DImgLoader(image) +{ + m_hasAlpha = false; + m_sixteenBit = false; +} + +bool TIFFLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + readMetadata(filePath, DImg::TIFF); + + // ------------------------------------------------------------------- + // TIFF error handling. If an errors/warnings occurs during reading, + // libtiff will call these methods + + TIFFSetWarningHandler(dimg_tiff_warning); + TIFFSetErrorHandler(dimg_tiff_error); + + // ------------------------------------------------------------------- + // Open the file + + TIFF* tif = TIFFOpen(TQFile::encodeName(filePath), "r"); + if (!tif) + { + DDebug() << k_funcinfo << "Cannot open image file." << endl; + return false; + } + +#ifdef ENABLE_DEBUG_MESSAGES + TIFFPrintDirectory(tif, stdout, 0); +#endif + + // ------------------------------------------------------------------- + // Get image information. + + uint32 w, h; + uint16 bits_per_sample; + uint16 samples_per_pixel; + uint16 photometric; + uint32 rows_per_strip; + tsize_t strip_size; + tstrip_t num_of_strips; + + TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGEWIDTH, &w); + TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGELENGTH, &h); + + TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); + TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel); + + if (TIFFGetFieldDefaulted(tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip) == 0 || + rows_per_strip == 0 || rows_per_strip == (unsigned int)-1) + { + DWarning() << "TIFF loader: Cannot handle non-stripped images. Loading file " << filePath << endl; + TIFFClose(tif); + return false; + } + + if (bits_per_sample == 0 || samples_per_pixel == 0 || + rows_per_strip == 0 || rows_per_strip > h) + { + DWarning() << "TIFF loader: Encountered invalid value 0 in image." + << " bits_per_sample " << bits_per_sample + << " samples_per_pixel " << samples_per_pixel + << " rows_per_strip " << rows_per_strip + << " Loading file " << filePath << endl; + TIFFClose(tif); + return false; + } + + // TODO: check others TIFF color-spaces here. Actually, only RGB and MINISBLACK + // have been tested. + // Complete description of TIFFTAG_PHOTOMETRIC tag can be found at this url: + // http://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html + + TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric); + if (photometric != PHOTOMETRIC_RGB && + photometric != PHOTOMETRIC_MINISBLACK) + { + DWarning() << "Can't handle image without RGB color-space: " + << photometric << endl; + TIFFClose(tif); + return false; + } + + if (samples_per_pixel == 4) + m_hasAlpha = true; + else + m_hasAlpha = false; + + if (bits_per_sample == 16) + m_sixteenBit = true; + else + m_sixteenBit = false; + + // ------------------------------------------------------------------- + // Read image ICC profile + + TQMap<int, TQByteArray>& metaData = imageMetaData(); + + uchar *profile_data=0; + uint32 profile_size; + + if (TIFFGetField (tif, TIFFTAG_ICCPROFILE, &profile_size, &profile_data)) + { + TQByteArray profile_rawdata(profile_size); + memcpy(profile_rawdata.data(), profile_data, profile_size); + metaData.insert(DImg::ICC, profile_rawdata); + } + else + { + // If ICC profile is null, check Exif metadata. + checkExifWorkingColorSpace(); + } + + // ------------------------------------------------------------------- + // Get image data. + + if (observer) + observer->progressInfo(m_image, 0.1); + + uchar* data = 0; + + strip_size = TIFFStripSize(tif); + num_of_strips = TIFFNumberOfStrips(tif); + + if (bits_per_sample == 16) // 16 bits image. + { + data = new uchar[w*h*8]; + uchar* strip = new uchar[strip_size]; + long offset = 0; + long bytesRead = 0; + + uint checkpoint = 0; + + for (tstrip_t st=0; st < num_of_strips; st++) + { + if (observer && st == checkpoint) + { + checkpoint += granularity(observer, num_of_strips, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] strip; + TIFFClose(tif); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)st)/((float)num_of_strips) ))); + } + + bytesRead = TIFFReadEncodedStrip(tif, st, strip, strip_size); + + if (bytesRead == -1) + { + DDebug() << k_funcinfo << "Failed to read strip" << endl; + delete [] data; + TIFFClose(tif); + return false; + } + + ushort *stripPtr = (ushort*)(strip); + ushort *dataPtr = (ushort*)(data + offset); + ushort *p; + + // tiff data is read as BGR or ABGR or Greyscale + + if (samples_per_pixel == 3) + { + for (int i=0; i < bytesRead/6; i++) + { + p = dataPtr; + + // See B.K.O #148037 : take a care about byte order with Motorola computers. + if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC + { + p[3] = *stripPtr++; + p[0] = *stripPtr++; + p[1] = *stripPtr++; + p[2] = 0xFFFF; + } + else + { + p[2] = *stripPtr++; + p[1] = *stripPtr++; + p[0] = *stripPtr++; + p[3] = 0xFFFF; + } + + dataPtr += 4; + } + + offset += bytesRead/6 * 8; + } + else if (samples_per_pixel == 1) // See B.K.O #148400: Greyscale pictures only have _one_ sample per pixel + { + for (int i=0; i < bytesRead/2; i++) + { + // We have to read two bytes for one pixel + p = dataPtr; + + // See B.K.O #148037 : take a care about byte order with Motorola computers. + if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC + { + p[3] = 0xFFFF; + p[0] = *stripPtr; + p[1] = *stripPtr; + p[2] = *stripPtr++; + } + else + { + p[0] = *stripPtr; // RGB have to be set to the _same_ value + p[1] = *stripPtr; + p[2] = *stripPtr++; + p[3] = 0xFFFF; // set alpha to 100% + } + dataPtr += 4; + } + + offset += bytesRead*4; // The _byte_offset in the data array is, of course, four times bytesRead + } + else // ABGR + { + for (int i=0; i < bytesRead/8; i++) + { + p = dataPtr; + + // See B.K.O #148037 : take a care about byte order with Motorola computers. + if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC + { + p[3] = *stripPtr++; + p[0] = *stripPtr++; + p[1] = *stripPtr++; + p[2] = *stripPtr++; + } + else + { + p[2] = *stripPtr++; + p[1] = *stripPtr++; + p[0] = *stripPtr++; + p[3] = *stripPtr++; + } + + dataPtr += 4; + } + + offset += bytesRead; + } + } + + delete [] strip; + } + else // Non 16 bits images ==> get it on BGRA 8 bits. + { + data = new uchar[w*h*4]; + uchar* strip = new uchar[w*rows_per_strip*4]; + long offset = 0; + long pixelsRead = 0; + + // this is inspired by TIFFReadRGBAStrip, tif_getimage.c + char emsg[1024] = ""; + TIFFRGBAImage img; + uint32 rows_to_read; + + uint checkpoint = 0; + + // test whether libtiff can read format and initiate reading + + if (!TIFFRGBAImageOK(tif, emsg) || !TIFFRGBAImageBegin(&img, tif, 0, emsg)) + { + DDebug() << k_funcinfo << "Failed to set up RGBA reading of image, filename " + << TIFFFileName(tif) << " error message from Libtiff: " << emsg << endl; + delete [] data; + delete [] strip; + TIFFClose(tif); + return false; + } + + img.req_orientation = ORIENTATION_TOPLEFT; + + // read strips from image: read rows_per_strip, so always start at beginning of a strip + for (uint row = 0; row < h; row += rows_per_strip) + { + if (observer && row >= checkpoint) + { + checkpoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] strip; + TIFFClose(tif); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)row)/((float)h) ))); + } + + img.row_offset = row; + img.col_offset = 0; + + if( row + rows_per_strip > img.height ) + rows_to_read = img.height - row; + else + rows_to_read = rows_per_strip; + + // Read data + + if (TIFFRGBAImageGet(&img, (uint32*)strip, img.width, rows_to_read ) == -1) + { + DDebug() << k_funcinfo << "Failed to read image data" << endl; + delete [] data; + delete [] strip; + TIFFClose(tif); + return false; + } + + pixelsRead = rows_to_read * img.width; + + uchar *stripPtr = (uchar*)(strip); + uchar *dataPtr = (uchar*)(data + offset); + uchar *p; + + // Reverse red and blue + + for (int i=0; i < pixelsRead; i++) + { + p = dataPtr; + + // See B.K.O #148037 : take a care about byte order with Motorola computers. + if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC + { + p[3] = *stripPtr++; + p[0] = *stripPtr++; + p[1] = *stripPtr++; + p[2] = *stripPtr++; + } + else + { + p[2] = *stripPtr++; + p[1] = *stripPtr++; + p[0] = *stripPtr++; + p[3] = *stripPtr++; + } + + dataPtr += 4; + } + + offset += pixelsRead * 4; + } + + TIFFRGBAImageEnd(&img); + delete [] strip; + } + + // ------------------------------------------------------------------- + + TIFFClose(tif); + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageWidth() = w; + imageHeight() = h; + imageData() = data; + imageSetAttribute("format", "TIFF"); + + return true; +} + +bool TIFFLoader::save(const TQString& filePath, DImgLoaderObserver *observer) +{ + TIFF *tif; + uchar *data; + uint32 w, h; + + w = imageWidth(); + h = imageHeight(); + data = imageData(); + + // ------------------------------------------------------------------- + // TIFF error handling. If an errors/warnings occurs during reading, + // libtiff will call these methods + + TIFFSetWarningHandler(dimg_tiff_warning); + TIFFSetErrorHandler(dimg_tiff_error); + + // ------------------------------------------------------------------- + // Open the file + + tif = TIFFOpen(TQFile::encodeName(filePath), "w"); + + if (!tif) + { + DDebug() << k_funcinfo << "Cannot open target image file." << endl; + return false; + } + + // ------------------------------------------------------------------- + // Set image properties + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, w); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, h); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE); + + // Image must be compressed using deflate algorithm ? + TQVariant compressAttr = imageGetAttribute("compress"); + bool compress = compressAttr.isValid() ? compressAttr.toBool() : false; + + if (compress) + { + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_ADOBE_DEFLATE); + TIFFSetField(tif, TIFFTAG_ZIPQUALITY, 9); + // NOTE : this tag values aren't defined in libtiff 3.6.1. '2' is PREDICTOR_HORIZONTAL. + // Use horizontal differencing for images which are + // likely to be continuous tone. The TIFF spec says that this + // usually leads to better compression. + // See this url for more details: + // http://www.awaresystems.be/imaging/tiff/tifftags/predictor.html + TIFFSetField(tif, TIFFTAG_PREDICTOR, 2); + } + else + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + + // Image has an alpha channel ? + if (imageHasAlpha()) + { + uint16 sampleinfo[1] = { EXTRASAMPLE_UNASSALPHA }; + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 4); + TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, EXTRASAMPLE_ASSOCALPHA, sampleinfo); + } + else + { + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + } + + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, (uint16)imageBitsDepth()); + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0)); + + // ------------------------------------------------------------------- + // Write meta-data Tags contents. + + DMetadata metaData; + metaData.setExif(m_image->getExif()); + metaData.setIptc(m_image->getIptc()); + + // Standard IPTC tag (available with libtiff 3.6.1) + + TQByteArray ba = metaData.getIptc(true); + if (!ba.isEmpty()) + { +#if defined(TIFFTAG_PHOTOSHOP) + TIFFSetField (tif, TIFFTAG_PHOTOSHOP, (uint32)ba.size(), (uchar *)ba.data()); +#endif + } + + // Standard XMP tag (available with libtiff 3.6.1) + +#if defined(TIFFTAG_XMLPACKET) + tiffSetExifDataTag(tif, TIFFTAG_XMLPACKET, &metaData, "Exif.Image.XMLPacket"); +#endif + + // Standard Exif Ascii tags (available with libtiff 3.6.1) + + tiffSetExifAsciiTag(tif, TIFFTAG_DOCUMENTNAME, &metaData, "Exif.Image.DocumentName"); + tiffSetExifAsciiTag(tif, TIFFTAG_IMAGEDESCRIPTION, &metaData, "Exif.Image.ImageDescription"); + tiffSetExifAsciiTag(tif, TIFFTAG_MAKE, &metaData, "Exif.Image.Make"); + tiffSetExifAsciiTag(tif, TIFFTAG_MODEL, &metaData, "Exif.Image.Model"); + tiffSetExifAsciiTag(tif, TIFFTAG_DATETIME, &metaData, "Exif.Image.DateTime"); + tiffSetExifAsciiTag(tif, TIFFTAG_ARTIST, &metaData, "Exif.Image.Artist"); + tiffSetExifAsciiTag(tif, TIFFTAG_COPYRIGHT, &metaData, "Exif.Image.Copyright"); + + TQString soft = metaData.getExifTagString("Exif.Image.Software"); + TQString libtiffver(TIFFLIB_VERSION_STR); + libtiffver.replace('\n', ' '); + soft.append(TQString(" ( %1 )").arg(libtiffver)); + TIFFSetField(tif, TIFFTAG_SOFTWARE, (const char*)soft.ascii()); + + // NOTE: All others Exif tags will be written by Exiv2 (<= 0.18) + + // ------------------------------------------------------------------- + // Write ICC profil. + + TQByteArray profile_rawdata = m_image->getICCProfil(); + + if (!profile_rawdata.isEmpty()) + { +#if defined(TIFFTAG_ICCPROFILE) + TIFFSetField(tif, TIFFTAG_ICCPROFILE, (uint32)profile_rawdata.size(), (uchar *)profile_rawdata.data()); +#endif + } + + // ------------------------------------------------------------------- + // Write full image data in tiff directory IFD0 + + if (observer) + observer->progressInfo(m_image, 0.1); + + uint8 *buf=0; + uchar *pixel; + double alpha_factor; + uint32 x, y; + uint8 r8, g8, b8, a8=0; + uint16 r16, g16, b16, a16=0; + int i=0; + + buf = (uint8 *) _TIFFmalloc(TIFFScanlineSize(tif)); + + if (!buf) + { + DDebug() << k_funcinfo << "Cannot allocate memory buffer for main image." << endl; + TIFFClose(tif); + return false; + } + + uint checkpoint = 0; + + for (y = 0; y < h; y++) + { + + if (observer && y == checkpoint) + { + checkpoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + _TIFFfree(buf); + TIFFClose(tif); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)y)/((float)h) ))); + } + + i = 0; + + for (x = 0; x < w; x++) + { + pixel = &data[((y * w) + x) * imageBytesDepth()]; + + if ( imageSixteenBit() ) // 16 bits image. + { + b16 = (uint16)(pixel[0]+256*pixel[1]); + g16 = (uint16)(pixel[2]+256*pixel[3]); + r16 = (uint16)(pixel[4]+256*pixel[5]); + + if (imageHasAlpha()) + { + // TIFF makes you pre-mutiply the rgb components by alpha + + a16 = (uint16)(pixel[6]+256*pixel[7]); + alpha_factor = ((double)a16 / 65535.0); + r16 = (uint16)(r16*alpha_factor); + g16 = (uint16)(g16*alpha_factor); + b16 = (uint16)(b16*alpha_factor); + } + + // This might be endian dependent + + buf[i++] = (uint8)(r16); + buf[i++] = (uint8)(r16 >> 8); + buf[i++] = (uint8)(g16); + buf[i++] = (uint8)(g16 >> 8); + buf[i++] = (uint8)(b16); + buf[i++] = (uint8)(b16 >> 8); + + if (imageHasAlpha()) + { + buf[i++] = (uint8)(a16) ; + buf[i++] = (uint8)(a16 >> 8) ; + } + } + else // 8 bits image. + { + b8 = (uint8)pixel[0]; + g8 = (uint8)pixel[1]; + r8 = (uint8)pixel[2]; + + if (imageHasAlpha()) + { + // TIFF makes you pre-mutiply the rgb components by alpha + + a8 = (uint8)(pixel[3]); + alpha_factor = ((double)a8 / 255.0); + r8 = (uint8)(r8*alpha_factor); + g8 = (uint8)(g8*alpha_factor); + b8 = (uint8)(b8*alpha_factor); + } + + // This might be endian dependent + + buf[i++] = r8; + buf[i++] = g8; + buf[i++] = b8; + + if (imageHasAlpha()) + buf[i++] = a8; + } + } + + if (!TIFFWriteScanline(tif, buf, y, 0)) + { + DDebug() << k_funcinfo << "Cannot write main image to target file." << endl; + _TIFFfree(buf); + TIFFClose(tif); + return false; + } + } + + _TIFFfree(buf); + TIFFWriteDirectory(tif); + + // ------------------------------------------------------------------- + // Write thumbnail in tiff directory IFD1 + + TQImage thumb = m_image->smoothScale(160, 120, TQSize::ScaleMin).copyTQImage(); + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, (uint32)thumb.width()); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, (uint32)thumb.height()); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0)); + + uchar *pixelThumb; + uchar *dataThumb = thumb.bits(); + uint8 *bufThumb = (uint8 *) _TIFFmalloc(TIFFScanlineSize(tif)); + + if (!bufThumb) + { + DDebug() << k_funcinfo << "Cannot allocate memory buffer for thumbnail." << endl; + TIFFClose(tif); + return false; + } + + for (y = 0 ; y < uint32(thumb.height()) ; y++) + { + i = 0; + + for (x = 0 ; x < uint32(thumb.width()) ; x++) + { + pixelThumb = &dataThumb[((y * thumb.width()) + x) * 4]; + + // This might be endian dependent + bufThumb[i++] = (uint8)pixelThumb[2]; + bufThumb[i++] = (uint8)pixelThumb[1]; + bufThumb[i++] = (uint8)pixelThumb[0]; + } + + if (!TIFFWriteScanline(tif, bufThumb, y, 0)) + { + DDebug() << k_funcinfo << "Cannot write thumbnail to target file." << endl; + _TIFFfree(bufThumb); + TIFFClose(tif); + return false; + } + } + + _TIFFfree(bufThumb); + TIFFClose(tif); + + // ------------------------------------------------------------------- + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageSetAttribute("savedformat", "TIFF"); + + saveMetadata(filePath); + + return true; +} + +bool TIFFLoader::hasAlpha() const +{ + return m_hasAlpha; +} + +bool TIFFLoader::sixteenBit() const +{ + return m_sixteenBit; +} + +void TIFFLoader::tiffSetExifAsciiTag(TIFF* tif, ttag_t tiffTag, + const DMetadata *metaData, const char* exifTagName) +{ + TQByteArray tag = metaData->getExifTagData(exifTagName); + if (!tag.isEmpty()) + { + TQCString str(tag.data(), tag.size()); + TIFFSetField(tif, tiffTag, (const char*)str); + } +} + +void TIFFLoader::tiffSetExifDataTag(TIFF* tif, ttag_t tiffTag, + const DMetadata *metaData, const char* exifTagName) +{ + TQByteArray tag = metaData->getExifTagData(exifTagName); + if (!tag.isEmpty()) + { + TIFFSetField (tif, tiffTag, (uint32)tag.size(), (char *)tag.data()); + } +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/tiffloader.h b/src/libs/dimg/loaders/tiffloader.h new file mode 100644 index 00000000..484afd06 --- /dev/null +++ b/src/libs/dimg/loaders/tiffloader.h @@ -0,0 +1,76 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-17 + * Description : A TIFF IO file for DImg framework + * + * Copyright (C) 2005 by Renchi Raju <[email protected]> + * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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 TIFFLOADER_H +#define TIFFLOADER_H + +// C ansi includes. + +extern "C" +{ +#include <tiffio.h> +#include <tiff.h> +} + +// Local includes. + +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class DImg; +class DMetadata; + +class DIGIKAM_EXPORT TIFFLoader : public DImgLoader +{ +public: + + TIFFLoader(DImg* image); + + bool load(const TQString& filePath, DImgLoaderObserver *observer); + bool save(const TQString& filePath, DImgLoaderObserver *observer); + + virtual bool hasAlpha() const; + virtual bool sixteenBit() const; + virtual bool isReadOnly() const { return false; }; + +private: + + void tiffSetExifAsciiTag(TIFF* tif, ttag_t tiffTag, const DMetadata *metaData, const char* exifTagName); + void tiffSetExifDataTag(TIFF* tif, ttag_t tiffTag, const DMetadata *metaData, const char* exifTagName); + + static void dimg_tiff_warning(const char* module, const char* format, va_list warnings); + static void dimg_tiff_error(const char* module, const char* format, va_list errors); + +private: + + bool m_sixteenBit; + bool m_hasAlpha; +}; + +} // NameSpace Digikam + +#endif /* TIFFLOADER_H */ diff --git a/src/libs/dimg/loaders/tiffsettings.cpp b/src/libs/dimg/loaders/tiffsettings.cpp new file mode 100644 index 00000000..3ea7e20c --- /dev/null +++ b/src/libs/dimg/loaders/tiffsettings.cpp @@ -0,0 +1,94 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save TIFF image options. + * + * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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. + * + * ============================================================ */ + +// TQt includes. + +#include <tqstring.h> +#include <tqlabel.h> +#include <tqcheckbox.h> +#include <tqlayout.h> +#include <tqwhatsthis.h> + +// KDE includes. + +#include <tdelocale.h> +#include <kdialog.h> + +// Local includes. + +#include "tiffsettings.h" +#include "tiffsettings.moc" + +namespace Digikam +{ + +class TIFFSettingsPriv +{ + +public: + + TIFFSettingsPriv() + { + TIFFGrid = 0; + TIFFcompression = 0; + } + + TQGridLayout *TIFFGrid; + + TQCheckBox *TIFFcompression; +}; + +TIFFSettings::TIFFSettings(TQWidget *parent) + : TQWidget(parent, 0, TQt::WDestructiveClose) +{ + d = new TIFFSettingsPriv; + + d->TIFFGrid = new TQGridLayout(this, 1, 1, KDialog::spacingHint()); + d->TIFFcompression = new TQCheckBox(i18n("Compress TIFF files"), this); + + TQWhatsThis::add( d->TIFFcompression, i18n("<p>Toggle compression for TIFF images.<p>" + "If you enable this option, you can reduce " + "the final file size of the TIFF image.</p>" + "<p>A lossless compression format (Deflate) " + "is used to save the file.<p>")); + d->TIFFGrid->addMultiCellWidget(d->TIFFcompression, 0, 0, 0, 1); + d->TIFFGrid->setColStretch(1, 10); +} + +TIFFSettings::~TIFFSettings() +{ + delete d; +} + +void TIFFSettings::setCompression(bool b) +{ + d->TIFFcompression->setChecked(b); +} + +bool TIFFSettings::getCompression() +{ + return d->TIFFcompression->isChecked(); +} + +} // namespace Digikam + diff --git a/src/libs/dimg/loaders/tiffsettings.h b/src/libs/dimg/loaders/tiffsettings.h new file mode 100644 index 00000000..e546ce0f --- /dev/null +++ b/src/libs/dimg/loaders/tiffsettings.h @@ -0,0 +1,60 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save TIFF image options. + * + * Copyright (C) 2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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 TIFFSETTINGS_H +#define TIFFSETTINGS_H + +// KDE includes. + +#include <tqwidget.h> + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class TIFFSettingsPriv; + +class DIGIKAM_EXPORT TIFFSettings : public TQWidget +{ +TQ_OBJECT + + +public: + + TIFFSettings(TQWidget *parent=0); + ~TIFFSettings(); + + void setCompression(bool b); + bool getCompression(); + +private: + + TIFFSettingsPriv* d; +}; + +} // namespace Digikam + +#endif /* TIFFSETTINGS_H */ |