diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index bedc3ad..14db235 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -53,6 +53,7 @@ kimageformats_read_tests( rgb tga xcf + webp ) # Basic write tests diff --git a/autotests/read/webp/bw-cwebp-lossless.png b/autotests/read/webp/bw-cwebp-lossless.png new file mode 100644 index 0000000..e2d506b Binary files /dev/null and b/autotests/read/webp/bw-cwebp-lossless.png differ diff --git a/autotests/read/webp/bw-cwebp-lossless.webp b/autotests/read/webp/bw-cwebp-lossless.webp new file mode 100644 index 0000000..3567af1 Binary files /dev/null and b/autotests/read/webp/bw-cwebp-lossless.webp differ diff --git a/autotests/read/webp/bw-cwebp.png b/autotests/read/webp/bw-cwebp.png new file mode 100644 index 0000000..ea9dcea Binary files /dev/null and b/autotests/read/webp/bw-cwebp.png differ diff --git a/autotests/read/webp/bw-cwebp.webp b/autotests/read/webp/bw-cwebp.webp new file mode 100644 index 0000000..9c2e0c0 Binary files /dev/null and b/autotests/read/webp/bw-cwebp.webp differ diff --git a/autotests/read/webp/rgb-cwebp-lossless.png b/autotests/read/webp/rgb-cwebp-lossless.png new file mode 100644 index 0000000..267d678 Binary files /dev/null and b/autotests/read/webp/rgb-cwebp-lossless.png differ diff --git a/autotests/read/webp/rgb-cwebp-lossless.webp b/autotests/read/webp/rgb-cwebp-lossless.webp new file mode 100644 index 0000000..0151334 Binary files /dev/null and b/autotests/read/webp/rgb-cwebp-lossless.webp differ diff --git a/autotests/read/webp/rgb-cwebp.png b/autotests/read/webp/rgb-cwebp.png new file mode 100644 index 0000000..528df3f Binary files /dev/null and b/autotests/read/webp/rgb-cwebp.png differ diff --git a/autotests/read/webp/rgb-cwebp.webp b/autotests/read/webp/rgb-cwebp.webp new file mode 100644 index 0000000..d889a9b Binary files /dev/null and b/autotests/read/webp/rgb-cwebp.webp differ diff --git a/cmake/FindWebP.cmake b/cmake/FindWebP.cmake new file mode 100644 index 0000000..7aaa650 --- /dev/null +++ b/cmake/FindWebP.cmake @@ -0,0 +1,203 @@ +# Try to find the WebP library +# +# This will define: +# +# WebP_FOUND - True if WebP is available +# WebP_LIBRARIES - Link to these to use WebP +# WebP_INCLUDE_DIRS - Include directory for WebP +# WebP_DEFINITIONS - Compiler flags required to link against WebP +# +# In addition the following more fine grained variables will be defined: +# +# WebP_WebP_FOUND WebP_WebP_INCLUDE_DIR WebP_WebP_LIBRARY +# WebP_Decoder_FOUND WebP_Decoder_INCLUDE_DIR WebP_Decoder_LIBRARY +# WebP_DeMux_FOUND WebP_DeMux_INCLUDE_DIR WebP_DeMux_LIBRARY +# WebP_Mux_FOUND WebP_Mux_INCLUDE_DIR WebP_Mux_LIBRARY +# +# Additionally, the following imported targets will be defined: +# +# WebP::WebP +# WebP::Decoder +# WebP::DeMux +# WebP::Mux +# +# Note that the Decoder library provides a strict subset of the functionality +# of the WebP library; it is therefore not included in WebP_LIBRARIES (unless +# the WebP library is not found), and you should not link to both WebP::WebP +# and WebP::Decoder. +# +# Copyright (c) 2011 Fredrik Höglund +# Copyright (c) 2013 Martin Gräßlin +# Copyright (c) 2013-2014, Alex Merry, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +if(${CMAKE_VERSION} VERSION_LESS 2.8.12) + message(FATAL_ERROR "CMake 2.8.12 is required by FindWebP.cmake") +endif() + +set(knownComponents + WebP + Decoder + DeMux + Mux) + +unset(unknownComponents) + +set(pkgConfigModules) +set(requiredComponents) + +if (WebP_FIND_COMPONENTS) + set(comps ${WebP_FIND_COMPONENTS}) +else() + set(comps ${knownComponents}) +endif() + +# iterate through the list of requested components, and check that we know them all. +# If not, fail. +foreach(comp ${comps}) + list(FIND knownComponents ${comp} index ) + if("${index}" STREQUAL "-1") + list(APPEND unknownComponents "${comp}") + else() + if("${comp}" STREQUAL "WebP") + list(APPEND pkgConfigModules "libwebp") + elseif("${comp}" STREQUAL "Decoder") + list(APPEND pkgConfigModules "libwebpdecoder") + elseif("${comp}" STREQUAL "DeMux") + list(APPEND pkgConfigModules "libwebpdemux") + elseif("${comp}" STREQUAL "Mux") + list(APPEND pkgConfigModules "libwebpmux") + endif() + endif() +endforeach() + +if(DEFINED unknownComponents) + set(msgType STATUS) + if(WebP_FIND_REQUIRED) + set(msgType FATAL_ERROR) + endif() + if(NOT WebP_FIND_QUIETLY) + message(${msgType} "WebP: requested unknown components ${unknownComponents}") + endif() + return() +endif() + +macro(_WEBP_HANDLE_COMPONENT _comp) + set(_header) + set(_lib) + set(_pkgconfig_module) + if("${_comp}" STREQUAL "WebP") + set(_header "webp/encode.h") + set(_lib "webp") + set(_pkgconfig_module "libwebp") + elseif("${_comp}" STREQUAL "Decoder") + set(_header "webp/decode.h") + set(_lib "webpdecoder") + set(_pkgconfig_module "libwebpdecoder") + elseif("${_comp}" STREQUAL "DeMux") + set(_header "webp/demux.h") + set(_lib "webpdemux") + set(_pkgconfig_module "libwebpdemux") + elseif("${_comp}" STREQUAL "Mux") + set(_header "webp/mux.h") + set(_lib "webpmux") + set(_pkgconfig_module "libwebpmux") + endif() + + find_path(WebP_${_comp}_INCLUDE_DIR NAMES ${_header} HINTS ${PKG_WebP_INCLUDE_DIRS}) + find_library(WebP_${_comp}_LIBRARY NAMES ${_lib} HINTS ${PKG_WebP_LIBRARY_DIRS}) + + if(WebP_${_comp}_INCLUDE_DIR AND WebP_${_comp}_LIBRARY) + if(NOT "${_comp}" STREQUAL "Decoder") + list(APPEND WebP_INCLUDE_DIRS ${WebP_${_comp}_INCLUDE_DIR}) + list(APPEND WebP_LIBRARIES ${WebP_${_comp}_LIBRARY}) + endif() + endif() + + if(PKG_WebP_VERSION AND NOT PKG_WebP_${_pkgconfig_module}_VERSION) + # this is what gets set if we only search for one module + set(WebP_${_comp}_VERSION_STRING "${PKG_WebP_VERSION}") + else() + set(WebP_${_comp}_VERSION_STRING "${PKG_WebP_${_pkgconfig_module}_VERSION}") + endif() + if(NOT WebP_VERSION_STRING) + set(WebP_VERSION_STRING ${WebP_${_comp}_VERSION_STRING}) + endif() + + find_package_handle_standard_args(WebP_${_comp} + FOUND_VAR + WebP_${_comp}_FOUND + REQUIRED_VARS + WebP_${_comp}_LIBRARY + WebP_${_comp}_INCLUDE_DIR + VERSION_VAR + WebP_${_comp}_VERSION_STRING + ) + + mark_as_advanced(WebP_${_comp}_LIBRARY WebP_${_comp}_INCLUDE_DIR) + + # compatibility for old variable naming + set(WebP_${_comp}_INCLUDE_DIRS ${WebP_${_comp}_INCLUDE_DIR}) + set(WebP_${_comp}_LIBRARIES ${WebP_${_comp}_LIBRARY}) + + if(WebP_${_comp}_FOUND AND NOT TARGET WebP::${_comp}) + add_library(WebP::${_comp} UNKNOWN IMPORTED) + set_target_properties(WebP::${_comp} PROPERTIES + IMPORTED_LOCATION "${WebP_${_comp}_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${WebP_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${WebP_${_comp}_INCLUDE_DIR}" + ) + endif() +endmacro() + +include(FindPackageHandleStandardArgs) +# Use pkg-config to get the directories and then use these values +# in the FIND_PATH() and FIND_LIBRARY() calls +find_package(PkgConfig) +pkg_check_modules(PKG_WebP QUIET ${pkgConfigModules}) + +set(WebP_DEFINITIONS ${PKG_WebP_CFLAGS}) + +foreach(comp ${comps}) + _webp_handle_component(${comp}) +endforeach() + +if (WebP_Decoder_FOUND AND NOT WebP_WebP_FOUND) + list(APPEND WebP_INCLUDE_DIRS ${WebP_Decoder_INCLUDE_DIR}) + list(APPEND WebP_LIBRARIES ${WebP_Decoder_LIBRARY}) +endif() + +if (WebP_INCLUDE_DIRS) + list(REMOVE_DUPLICATES WebP_INCLUDE_DIRS) +endif() + +if(WebP_WebP_FOUND) + if(WebP_DeMux_FOUND) + add_dependencies(WebP::DeMux WebP::WebP) + endif() + if(WebP_Mux_FOUND) + add_dependencies(WebP::Mux WebP::WebP) + endif() +endif() + +find_package_handle_standard_args(WebP + FOUND_VAR + WebP_FOUND + REQUIRED_VARS + WebP_LIBRARIES + WebP_INCLUDE_DIRS + VERSION_VAR + WebP_VERSION_STRING + HANDLE_COMPONENTS + ) + +# compatibility for old variable naming +set(WebP_INCLUDE_DIR ${WebP_INCLUDE_DIRS}) + +include(FeatureSummary) +set_package_properties(WebP PROPERTIES + URL http://www.openexr.com/ + DESCRIPTION "A library for handling WebP/VP8 image files") + diff --git a/src/imageformats/CMakeLists.txt b/src/imageformats/CMakeLists.txt index 7cafd18..422515d 100644 --- a/src/imageformats/CMakeLists.txt +++ b/src/imageformats/CMakeLists.txt @@ -119,6 +119,32 @@ install(FILES tga.desktop DESTINATION ${SERVICES_INSTALL_DIR}/qimageioplugins/) ################################## +find_package(WebP COMPONENTS WebP) +set_package_properties(WebP PROPERTIES + TYPE OPTIONAL + PURPOSE "Required for the QImage plugin for WebP images" +) + +if(WebP_FOUND) + add_library(kimg_webp MODULE webp.cpp) + target_link_libraries(kimg_webp Qt5::Gui WebP::WebP) + + install(TARGETS kimg_webp DESTINATION ${QT_PLUGIN_INSTALL_DIR}/imageformats/) + install(FILES webp.desktop DESTINATION ${SERVICES_INSTALL_DIR}/qimageioplugins/) + + find_package(SharedMimeInfo) + set_package_properties(SharedMimeInfo PROPERTIES + TYPE RECOMMENDED + PURPOSE "Required to install the WebP MIME Type information" + ) + if (SharedMimeInfo_FOUND) + install(FILES webp.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) + update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) + endif() +endif() + +################################## + add_library(kimg_xcf MODULE xcf.cpp) target_link_libraries(kimg_xcf Qt5::Gui) diff --git a/src/imageformats/webp.cpp b/src/imageformats/webp.cpp new file mode 100644 index 0000000..f22dcd8 --- /dev/null +++ b/src/imageformats/webp.cpp @@ -0,0 +1,232 @@ +/* +QImageIO Routines to read/write WebP images. + +Copyright (c) 2012,2013 Martin Koller + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*/ + +#include + +#include "webp.h" +#include +#include + +#include +#include + +//--------------------------------------------------------------------- + +WebPHandler::WebPHandler() + : quality(75) +{ +} + +//--------------------------------------------------------------------- + +bool WebPHandler::canRead() const +{ + if (canRead(device())) { + setFormat("webp"); + return true; + } + return false; +} + +//--------------------------------------------------------------------- + +bool WebPHandler::read(QImage *retImage) +{ + QByteArray data = device()->readAll(); + + WebPBitstreamFeatures features; + VP8StatusCode ret = WebPGetFeatures(reinterpret_cast(data.constData()), data.size(), &features); + if ( ret != VP8_STATUS_OK ) { + return false; + } + + if ( features.has_alpha ) { + *retImage = QImage(features.width, features.height, QImage::Format_ARGB32); + } else { + *retImage = QImage(features.width, features.height, QImage::Format_RGB32); + } + + if ( retImage->isNull() ) { // out of memory + return false; + } + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + if ( WebPDecodeARGBInto(reinterpret_cast(data.constData()), + data.size(), reinterpret_cast(retImage->bits()), + retImage->byteCount(), retImage->bytesPerLine()) == 0 ) { + return false; + } +#else + if ( WebPDecodeBGRAInto(reinterpret_cast(data.constData()), + data.size(), reinterpret_cast(retImage->bits()), + retImage->byteCount(), retImage->bytesPerLine()) == 0 ) { + return false; + } +#endif + + return true; +} + +//--------------------------------------------------------------------- + +bool WebPHandler::write(const QImage &image) +{ + // limitation in WebP + if ( (image.height() > 16383) || (image.height() == 0) || + (image.width() > 16383) || (image.width() == 0) ) + return false; + + uint8_t *imageData = new uint8_t[image.width() * image.height() * (3 + image.hasAlphaChannel())]; + + size_t idx = 0; + for (int y = 0; y < image.height(); y++) { + const QRgb *scanline = reinterpret_cast(image.constScanLine(y)); + for (int x = 0; x < image.width(); x++) { + imageData[idx++] = qRed(scanline[x]); + imageData[idx++] = qGreen(scanline[x]); + imageData[idx++] = qBlue(scanline[x]); + + if ( image.hasAlphaChannel() ) { + imageData[idx++] = qAlpha(scanline[x]); + } + } + } + + uint8_t *output = 0; + size_t size; + if ( image.hasAlphaChannel() ) { + size = WebPEncodeRGBA(imageData, image.width(), image.height(), image.width() * 4, quality, &output); + } else { + size = WebPEncodeRGB(imageData, image.width(), image.height(), image.width() * 4, quality, &output); + } + delete [] imageData; + + if ( size == 0 ) { + free(output); + return false; + } + + device()->write(reinterpret_cast(output), size); + free(output); + + return true; +} + +//--------------------------------------------------------------------- + +QByteArray WebPHandler::format() const +{ + return "webp"; +} + +//--------------------------------------------------------------------- + +bool WebPHandler::supportsOption(ImageOption option) const +{ + return (option == Quality) || (option == Size); +} + +//--------------------------------------------------------------------- + +QVariant WebPHandler::option(ImageOption option) const +{ + switch ( option ) + { + case Quality: + return quality; + + case Size: { + QByteArray data = device()->peek(26); + + int width = 0, height = 0; + + if ( WebPGetInfo(reinterpret_cast(data.constData()), + data.size(), &width, &height) == 0 ) + return QSize(); // header error + + return QSize(width, height); + } + + default: return QVariant(); + } +} + +//--------------------------------------------------------------------- + +void WebPHandler::setOption(ImageOption option, const QVariant &value) +{ + if (option == Quality) + quality = qBound(0, value.toInt(), 100); +} + +//--------------------------------------------------------------------- + +bool WebPHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning("WebPHandler::canRead() called with no device"); + return false; + } + + // WebP file header: 4 bytes "RIFF", 4 bytes length, 4 bytes "WEBP" + QByteArray header = device->peek(12); + + return (header.size() == 12) && header.startsWith("RIFF") && header.endsWith("WEBP"); +} + +//--------------------------------------------------------------------- +//--------------------------------------------------------------------- + +QImageIOPlugin::Capabilities WebPPlugin::capabilities(QIODevice *device, const QByteArray &format) const +{ + // this plugin produces webp images it cannot read at the moment, + // so do not claim to be able to write + if (format == "webp") + return Capabilities(CanRead); + //return Capabilities(CanRead | CanWrite); + if (!format.isEmpty()) + return 0; + if (!device->isOpen()) + return 0; + + Capabilities cap; + if (device->isReadable() && WebPHandler::canRead(device)) + cap |= CanRead; + /* + if (device->isWritable()) + cap |= CanWrite; + */ + return cap; +} + +//--------------------------------------------------------------------- + +QImageIOHandler *WebPPlugin::create(QIODevice *device, const QByteArray &format) const +{ + QImageIOHandler *handler = new WebPHandler; + handler->setDevice(device); + handler->setFormat(format); + return handler; +} + +//--------------------------------------------------------------------- + diff --git a/src/imageformats/webp.desktop b/src/imageformats/webp.desktop new file mode 100644 index 0000000..3526be5 --- /dev/null +++ b/src/imageformats/webp.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=QImageIOPlugins +X-KDE-ImageFormat=webp +X-KDE-MimeType=image/x-webp +X-KDE-Read=true +X-KDE-Write=true diff --git a/src/imageformats/webp.h b/src/imageformats/webp.h new file mode 100644 index 0000000..02b90ee --- /dev/null +++ b/src/imageformats/webp.h @@ -0,0 +1,59 @@ +/* +QImageIO Routines to read/write WebP images. + +Copyright (c) 2012,2013 Martin Koller + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*/ + +#ifndef WEBP_H +#define WEBP_H + +#include + +class WebPHandler : public QImageIOHandler +{ +public: + WebPHandler(); + + virtual bool canRead() const; + virtual bool read(QImage *image); + virtual bool write(const QImage &image); + + virtual QByteArray format() const; + + virtual bool supportsOption(ImageOption option) const; + virtual QVariant option(ImageOption option) const; + virtual void setOption(ImageOption option, const QVariant &value); + + static bool canRead(QIODevice *device); + +private: + int quality; +}; + +class WebPPlugin : public QImageIOPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "webp.json") + +public: + virtual Capabilities capabilities(QIODevice *device, const QByteArray &format) const; + virtual QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const; +}; + +#endif diff --git a/src/imageformats/webp.json b/src/imageformats/webp.json new file mode 100644 index 0000000..9e81ff9 --- /dev/null +++ b/src/imageformats/webp.json @@ -0,0 +1,4 @@ +{ + "Keys": [ "webp" ], + "MimeTypes": [ "image/x-webp" ] +} diff --git a/src/imageformats/webp.xml b/src/imageformats/webp.xml new file mode 100644 index 0000000..4246f76 --- /dev/null +++ b/src/imageformats/webp.xml @@ -0,0 +1,7 @@ + + + + WebP image + + +