diff --git a/CMakeLists.txt b/CMakeLists.txt index f7c72ec4..e37b1e86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,6 +192,15 @@ set_package_properties(OpenJPEG PROPERTIES PURPOSE "Support for JPEG 2000 image format" TYPE OPTIONAL ) + +kde4_optional_find_package(TIFF) +set_package_properties(TIFF PROPERTIES + DESCRIPTION "TIFF Library and Utilities" + URL "http://www.libtiff.org/" + PURPOSE "Support for TIFF image format" + TYPE OPTIONAL +) + # v143+ required for udev_monitor_filter_add_match_subsystem_devtype() kde4_optional_find_package(UDev 143) set_package_properties(UDev PROPERTIES diff --git a/kdeui/util/kimageio.h b/kdeui/util/kimageio.h index d65c5ee5..9c86a733 100644 --- a/kdeui/util/kimageio.h +++ b/kdeui/util/kimageio.h @@ -29,6 +29,7 @@ * @li ICO \ \ * @li JP2 \ * @li RAW \ + * @li TIFF \ */ namespace KImageIO { diff --git a/kimgio/CMakeLists.txt b/kimgio/CMakeLists.txt index 3331f460..1d76725c 100644 --- a/kimgio/CMakeLists.txt +++ b/kimgio/CMakeLists.txt @@ -98,3 +98,23 @@ if(OPENJPEG_FOUND) DESTINATION ${KDE4_PLUGIN_INSTALL_DIR}/plugins/imageformats ) endif(OPENJPEG_FOUND) + +################################## + +if(TIFF_FOUND) + include_directories(${TIFF_INCLUDE_DIRS}) + kde4_add_plugin(kimg_tiff tiff.cpp) + target_link_libraries(kimg_tiff + ${KDE4_KDECORE_LIBS} + ${QT_QTGUI_LIBRARY} + ${TIFF_LIBRARIES} + ) + set_target_properties(kimg_tiff PROPERTIES + OUTPUT_NAME tiff + ) + + install( + TARGETS kimg_tiff + DESTINATION ${KDE4_PLUGIN_INSTALL_DIR}/plugins/imageformats + ) +endif(TIFF_FOUND) diff --git a/kimgio/README b/kimgio/README index 1d754db3..88dbdb4b 100644 --- a/kimgio/README +++ b/kimgio/README @@ -8,6 +8,7 @@ JPEG ICO JP2 RAW +TIFF If you want to contribute plugin for image format and there is no solid C/C++ library for it (like there is for WebP) then it is unlikely it will be diff --git a/kimgio/tiff.cpp b/kimgio/tiff.cpp new file mode 100644 index 00000000..fdab2b7a --- /dev/null +++ b/kimgio/tiff.cpp @@ -0,0 +1,298 @@ +/* This file is part of the KDE libraries + Copyright (C) 2022 Ivailo Monev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "tiff.h" + +#include +#include + +#include +extern "C" { +#include +}; + +static const char* const s_tiffpluginformat = "tiff"; + +static const ushort s_peekbuffsize = 32; +// for reference: +// https://en.wikipedia.org/wiki/List_of_file_signatures +static const uchar s_tiffleheader[] = { 0x49, 0x49, 0x2A, 0x00 }; +static const uchar s_tiffbeheader[] = { 0x49, 0x49, 0x00, 0x2A }; + +static const struct HeadersTblData { + const uchar *header; + const int headersize; +} HeadersTbl[] = { + { s_tiffleheader, 4 }, + { s_tiffbeheader, 4 } +}; +static const qint16 HeadersTblSize = sizeof(HeadersTbl) / sizeof(HeadersTblData); + +static TIFFErrorHandler s_tifferrorhandler = NULL; +static TIFFErrorHandler s_tiffwarninghandler = NULL; + +void tiff_error_handler(const char* tiffmodule, const char* tiffformat, va_list tiffva) +{ + char vsnprintfbuff[1024]; + ::memset(vsnprintfbuff, '\0', sizeof(vsnprintfbuff)); + ::vsnprintf(vsnprintfbuff, sizeof(vsnprintfbuff), tiffformat, tiffva); + kError() << tiffmodule << vsnprintfbuff; +} + +void tiff_warning_handler(const char* tiffmodule, const char* tiffformat, va_list tiffva) +{ + char vsnprintfbuff[1024]; + ::memset(vsnprintfbuff, '\0', sizeof(vsnprintfbuff)); + ::vsnprintf(vsnprintfbuff, sizeof(vsnprintfbuff), tiffformat, tiffva); + kWarning() << tiffmodule << vsnprintfbuff; +} + +static tmsize_t tiff_read_proc(thandle_t tiffhandler, void* tiffptr, tmsize_t tiffsize) +{ + QIODevice* device = static_cast(tiffhandler); + return device->read(static_cast(tiffptr), tiffsize); +} + +static tmsize_t tiff_write_proc(thandle_t tiffhandler, void* tiffptr, tmsize_t tiffsize) +{ + // dummy + return 0; +} + +static toff_t tiff_seek_proc(thandle_t tiffhandler, toff_t tiffoffset, int tiffwhence) +{ + bool result = false; + QIODevice* device = static_cast(tiffhandler); + switch (tiffwhence) { + case SEEK_SET: { + result = device->seek(tiffoffset); + break; + } + case SEEK_CUR: { + result = device->seek(device->pos() + tiffoffset); + break; + } + case SEEK_END: { + result = device->seek(device->size() + tiffoffset); + break; + } + default: { + kWarning() << "Invalid whence value" << tiffwhence; + result = false; + break; + } + } + if (Q_UNLIKELY(!result)) { + kWarning() << "Could not seek" << tiffoffset << tiffwhence; + return -1; + } + return device->pos(); +} + +static int tiff_close_proc(thandle_t tiffhandler) +{ + // nothing to do + return 0; +} + +static toff_t tiff_size_proc(thandle_t tiffhandler) +{ + QIODevice* device = static_cast(tiffhandler); + return device->size(); +} + +static int tiff_mapfile_proc(thandle_t tiffhandler, void** tiffptr, toff_t* tiffsize) +{ + // dummy + return 0; +} + +static void tiff_unmapfile_proc(thandle_t tiffhandler, void* tiffptr, toff_t tiffsize) +{ + // dummy +} + +TIFFHandler::TIFFHandler() +{ +} + +TIFFHandler::~TIFFHandler() +{ +} + +bool TIFFHandler::canRead() const +{ + if (TIFFHandler::canRead(device())) { + setFormat(s_tiffpluginformat); + return true; + } + return false; +} + +bool TIFFHandler::read(QImage *image) +{ + s_tifferrorhandler = TIFFSetErrorHandler(tiff_error_handler); + s_tiffwarninghandler = TIFFSetWarningHandler(tiff_warning_handler); + + TIFF* tiffclient = TIFFClientOpen( + "TIFFHandler", "r", + device(), + tiff_read_proc, + tiff_write_proc, + tiff_seek_proc, + tiff_close_proc, + tiff_size_proc, + tiff_mapfile_proc, + tiff_unmapfile_proc + ); + if (!Q_UNLIKELY(tiffclient)) { + kWarning() << "Could not open client"; + TIFFSetErrorHandler(s_tifferrorhandler); + TIFFSetWarningHandler(s_tiffwarninghandler); + return false; + } + + // NOTE: TIFFReadRGBA* functions do internal conversion (i.e. YCbCr to RGBA) which does not + // work for all images + char tifferror[1024]; + ::memset(tifferror, '\0', sizeof(tifferror)); + int tiffresult = TIFFRGBAImageOK(tiffclient, tifferror); + if (tiffresult != 1) { + kWarning() << "Image is not OK" << tifferror; + TIFFClose(tiffclient); + TIFFSetErrorHandler(s_tifferrorhandler); + TIFFSetWarningHandler(s_tiffwarninghandler); + return false; + } + + uint32 tiffwidth = 0; + tiffresult = TIFFGetField(tiffclient, TIFFTAG_IMAGEWIDTH, &tiffwidth); + if (Q_UNLIKELY(tiffresult != 1)) { + kWarning() << "Could not get image width"; + TIFFClose(tiffclient); + TIFFSetErrorHandler(s_tifferrorhandler); + TIFFSetWarningHandler(s_tiffwarninghandler); + return false; + } + + uint32 tiffheight = 0; + tiffresult = TIFFGetField(tiffclient, TIFFTAG_IMAGELENGTH, &tiffheight); + if (Q_UNLIKELY(tiffresult != 1)) { + kWarning() << "Could not get image length"; + TIFFClose(tiffclient); + TIFFSetErrorHandler(s_tifferrorhandler); + TIFFSetWarningHandler(s_tiffwarninghandler); + return false; + } + + *image = QImage(tiffwidth, tiffheight, QImage::Format_ARGB32); + if (Q_UNLIKELY(image->isNull())) { + kWarning() << "Could not create image"; + TIFFClose(tiffclient); + TIFFSetErrorHandler(s_tifferrorhandler); + TIFFSetWarningHandler(s_tiffwarninghandler); + return false; + } + + tiffresult = TIFFReadRGBAImageOriented( + tiffclient, + tiffwidth, tiffheight, + reinterpret_cast(image->bits()), ORIENTATION_TOPLEFT, + 1 + ); + if (Q_UNLIKELY(tiffresult != 1)) { + kWarning() << "Could not read image"; + *image = QImage(); + TIFFClose(tiffclient); + TIFFSetErrorHandler(s_tifferrorhandler); + TIFFSetWarningHandler(s_tiffwarninghandler); + return false; + } + + TIFFClose(tiffclient); + TIFFSetErrorHandler(s_tifferrorhandler); + TIFFSetWarningHandler(s_tiffwarninghandler); + return true; +} + +bool TIFFHandler::write(const QImage &image) +{ + // this plugin is a read-only kind of plugin + return false; +} + +QByteArray TIFFHandler::name() const +{ + return s_tiffpluginformat; +} + +bool TIFFHandler::canRead(QIODevice *device) +{ + if (Q_UNLIKELY(!device)) { + kWarning() << "Called with no device"; + return false; + } + + const QByteArray data = device->peek(s_peekbuffsize); + + for (int i = 0; i < HeadersTblSize; i++) { + if (data.size() >= HeadersTbl[i].headersize && + ::memcmp(data.constData(), HeadersTbl[i].header, HeadersTbl[i].headersize) == 0) { + kDebug() << "Header detected"; + return true; + } + } + + return false; +} + +QStringList TIFFPlugin::keys() const +{ + return QStringList() << s_tiffpluginformat; +} + +QList TIFFPlugin::mimeTypes() const +{ + static const QList list = QList() + << "image/tiff"; + return list; +} + +QImageIOPlugin::Capabilities TIFFPlugin::capabilities(QIODevice *device, const QByteArray &format) const +{ + if (format == s_tiffpluginformat) { + return QImageIOPlugin::Capabilities(QImageIOPlugin::CanRead); + } + if (!device || !device->isOpen()) { + return 0; + } + if (device->isReadable() && TIFFHandler::canRead(device)) { + return QImageIOPlugin::Capabilities(QImageIOPlugin::CanRead); + } + return 0; +} + +QImageIOHandler *TIFFPlugin::create(QIODevice *device, const QByteArray &format) const +{ + QImageIOHandler *handler = new TIFFHandler(); + handler->setDevice(device); + handler->setFormat(format); + return handler; +} + +Q_EXPORT_PLUGIN2(tiff, TIFFPlugin) diff --git a/kimgio/tiff.h b/kimgio/tiff.h new file mode 100644 index 00000000..29e28a28 --- /dev/null +++ b/kimgio/tiff.h @@ -0,0 +1,49 @@ +/* This file is part of the KDE libraries + Copyright (C) 2022 Ivailo Monev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KIMG_TIFF_H +#define KIMG_TIFF_H + +#include +#include + +class TIFFHandler : public QImageIOHandler +{ +public: + TIFFHandler(); + ~TIFFHandler(); + + bool canRead() const final; + bool read(QImage *image) final; + bool write(const QImage &image) final; + + QByteArray name() const final; + + static bool canRead(QIODevice *device); +}; + +class TIFFPlugin : public QImageIOPlugin +{ +public: + QStringList keys() const; + QList mimeTypes() const; + Capabilities capabilities(QIODevice *device, const QByteArray &format) const final; + QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const final; +}; + +#endif // KIMG_TIFF_H