1/*
2QImageIO Routines to read/write WebP images.
3
4Copyright (c) 2012,2013 Martin Koller <kollix@aon.at>
5
6This library is free software; you can redistribute it and/or
7modify it under the terms of the GNU Lesser General Public
8License as published by the Free Software Foundation; either
9version 2.1 of the License, or (at your option) version 3, or any
10later version accepted by the membership of KDE e.V. (or its
11successor approved by the membership of KDE e.V.), which shall
12act as a proxy defined in Section 6 of version 3 of the license.
13
14This library is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17Lesser General Public License for more details.
18
19You should have received a copy of the GNU Lesser General Public
20License along with this library. If not, see <http://www.gnu.org/licenses/>.
21*/
22
23#include <stdlib.h>
24
25#include "webp.h"
26#include <webp/decode.h>
27#include <webp/encode.h>
28
29#include <QImage>
30#include <QVariant>
31
32//---------------------------------------------------------------------
33
34WebPHandler::WebPHandler()
35 : quality(75)
36{
37}
38
39//---------------------------------------------------------------------
40
41bool WebPHandler::canRead() const
42{
43 if (canRead(device())) {
44 setFormat("webp");
45 return true;
46 }
47 return false;
48}
49
50//---------------------------------------------------------------------
51
52bool WebPHandler::read(QImage *retImage)
53{
54 QByteArray data = device()->readAll();
55
56 WebPBitstreamFeatures features;
57 VP8StatusCode ret = WebPGetFeatures(reinterpret_cast<const uint8_t*>(data.constData()), data.size(), &features);
58 if ( ret != VP8_STATUS_OK ) {
59 return false;
60 }
61
62 if ( features.has_alpha ) {
63 *retImage = QImage(features.width, features.height, QImage::Format_ARGB32);
64 } else {
65 *retImage = QImage(features.width, features.height, QImage::Format_RGB32);
66 }
67
68 if ( retImage->isNull() ) { // out of memory
69 return false;
70 }
71
72#if Q_BYTE_ORDER == Q_BIG_ENDIAN
73 if ( WebPDecodeARGBInto(reinterpret_cast<const uint8_t*>(data.constData()),
74 data.size(), reinterpret_cast<uint8_t*>(retImage->bits()),
75 retImage->byteCount(), retImage->bytesPerLine()) == 0 ) {
76 return false;
77 }
78#else
79 if ( WebPDecodeBGRAInto(reinterpret_cast<const uint8_t*>(data.constData()),
80 data.size(), reinterpret_cast<uint8_t*>(retImage->bits()),
81 retImage->byteCount(), retImage->bytesPerLine()) == 0 ) {
82 return false;
83 }
84#endif
85
86 return true;
87}
88
89//---------------------------------------------------------------------
90
91bool WebPHandler::write(const QImage &image)
92{
93 // limitation in WebP
94 if ( (image.height() > 16383) || (image.height() == 0) ||
95 (image.width() > 16383) || (image.width() == 0) )
96 return false;
97
98 uint8_t *imageData = new uint8_t[image.width() * image.height() * (3 + image.hasAlphaChannel())];
99
100 size_t idx = 0;
101 for (int y = 0; y < image.height(); y++) {
102 const QRgb *scanline = reinterpret_cast<const QRgb*>(image.constScanLine(y));
103 for (int x = 0; x < image.width(); x++) {
104 imageData[idx++] = qRed(scanline[x]);
105 imageData[idx++] = qGreen(scanline[x]);
106 imageData[idx++] = qBlue(scanline[x]);
107
108 if ( image.hasAlphaChannel() ) {
109 imageData[idx++] = qAlpha(scanline[x]);
110 }
111 }
112 }
113
114 uint8_t *output = 0;
115 size_t size;
116 if ( image.hasAlphaChannel() ) {
117 size = WebPEncodeRGBA(imageData, image.width(), image.height(), image.width() * 4, quality, &output);
118 } else {
119 size = WebPEncodeRGB(imageData, image.width(), image.height(), image.width() * 4, quality, &output);
120 }
121 delete [] imageData;
122
123 if ( size == 0 ) {
124 free(output);
125 return false;
126 }
127
128 device()->write(reinterpret_cast<const char*>(output), size);
129 free(output);
130
131 return true;
132}
133
134//---------------------------------------------------------------------
135
136QByteArray WebPHandler::format() const
137{
138 return "webp";
139}
140
141//---------------------------------------------------------------------
142
143bool WebPHandler::supportsOption(ImageOption option) const
144{
145 return (option == Quality) || (option == Size);
146}
147
148//---------------------------------------------------------------------
149
150QVariant WebPHandler::option(ImageOption option) const
151{
152 switch ( option )
153 {
154 case Quality:
155 return quality;
156
157 case Size: {
158 QByteArray data = device()->peek(26);
159
160 int width = 0, height = 0;
161
162 if ( WebPGetInfo(reinterpret_cast<const uint8_t*>(data.constData()),
163 data.size(), &width, &height) == 0 )
164 return QSize(); // header error
165
166 return QSize(width, height);
167 }
168
169 default: return QVariant();
170 }
171}
172
173//---------------------------------------------------------------------
174
175void WebPHandler::setOption(ImageOption option, const QVariant &value)
176{
177 if (option == Quality)
178 quality = qBound(0, value.toInt(), 100);
179}
180
181//---------------------------------------------------------------------
182
183bool WebPHandler::canRead(QIODevice *device)
184{
185 if (!device) {
186 qWarning("WebPHandler::canRead() called with no device");
187 return false;
188 }
189
190 // WebP file header: 4 bytes "RIFF", 4 bytes length, 4 bytes "WEBP"
191 QByteArray header = device->peek(12);
192
193 return (header.size() == 12) && header.startsWith("RIFF") && header.endsWith("WEBP");
194}
195
196//---------------------------------------------------------------------
197//---------------------------------------------------------------------
198
199class WebPPlugin : public QImageIOPlugin
200{
201public:
202 QStringList keys() const;
203 Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
204 QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
205};
206
207//---------------------------------------------------------------------
208
209QStringList WebPPlugin::keys() const
210{
211 return QStringList() << "webp";
212}
213
214//---------------------------------------------------------------------
215
216QImageIOPlugin::Capabilities WebPPlugin::capabilities(QIODevice *device, const QByteArray &format) const
217{
218 if (format == "webp")
219 return Capabilities(CanRead | CanWrite);
220 if (!format.isEmpty())
221 return 0;
222 if (!device->isOpen())
223 return 0;
224
225 Capabilities cap;
226 if (device->isReadable() && WebPHandler::canRead(device))
227 cap |= CanRead;
228 if (device->isWritable())
229 cap |= CanWrite;
230 return cap;
231}
232
233//---------------------------------------------------------------------
234
235QImageIOHandler *WebPPlugin::create(QIODevice *device, const QByteArray &format) const
236{
237 QImageIOHandler *handler = new WebPHandler;
238 handler->setDevice(device);
239 handler->setFormat(format);
240 return handler;
241}
242
243//---------------------------------------------------------------------
244
245Q_EXPORT_STATIC_PLUGIN(WebPPlugin)
246Q_EXPORT_PLUGIN2(webp, WebPPlugin)
247