Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qwebphandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qwebphandler_p.h"
5#include "webp/mux.h"
6#include "webp/encode.h"
7#include <qcolor.h>
8#include <qimage.h>
9#include <qdebug.h>
10#include <qpainter.h>
11#include <qvariant.h>
12#include <QtEndian>
13
14static const int riffHeaderSize = 12; // RIFF_HEADER_SIZE from webp/format_constants.h
15
17 m_quality(75),
18 m_scanState(ScanNotScanned),
19 m_features(),
20 m_formatFlags(0),
21 m_loop(0),
22 m_frameCount(0),
23 m_demuxer(NULL),
24 m_composited(NULL)
25{
26 memset(&m_iter, 0, sizeof(m_iter));
27}
28
30{
31 WebPDemuxReleaseIterator(&m_iter);
32 WebPDemuxDelete(m_demuxer);
33 delete m_composited;
34}
35
37{
38 if (m_scanState == ScanNotScanned && !canRead(device()))
39 return false;
40
41 if (m_scanState != ScanError) {
43
44 if (m_features.has_animation && m_iter.frame_num >= m_frameCount)
45 return false;
46
47 return true;
48 }
49 return false;
50}
51
53{
54 if (!device) {
55 qWarning("QWebpHandler::canRead() called with no device");
56 return false;
57 }
58
60 return header.startsWith("RIFF") && header.endsWith("WEBP");
61}
62
63bool QWebpHandler::ensureScanned() const
64{
65 if (m_scanState != ScanNotScanned)
66 return m_scanState == ScanSuccess;
67
68 m_scanState = ScanError;
69
70 QWebpHandler *that = const_cast<QWebpHandler *>(this);
71 const int headerBytesNeeded = sizeof(WebPBitstreamFeatures);
72 QByteArray header = device()->peek(headerBytesNeeded);
73 if (header.size() < headerBytesNeeded)
74 return false;
75
76 // We do no random access during decoding, just a readAll() of the whole image file. So if
77 // if it is all available already, we can accept a sequential device. The riff header contains
78 // the file size minus 8 bytes header
79 qint64 byteSize = qFromLittleEndian<quint32>(header.constData() + 4);
80 if (device()->isSequential() && device()->bytesAvailable() < byteSize + 8) {
81 qWarning() << "QWebpHandler: Insufficient data available in sequential device";
82 return false;
83 }
84 if (WebPGetFeatures((const uint8_t*)header.constData(), header.size(), &(that->m_features)) == VP8_STATUS_OK) {
85 if (m_features.has_animation) {
86 // For animation, we have to read and scan whole file to determine loop count and images count
87 if (that->ensureDemuxer()) {
88 that->m_loop = WebPDemuxGetI(m_demuxer, WEBP_FF_LOOP_COUNT);
89 that->m_frameCount = WebPDemuxGetI(m_demuxer, WEBP_FF_FRAME_COUNT);
90 that->m_bgColor = QColor::fromRgba(QRgb(WebPDemuxGetI(m_demuxer, WEBP_FF_BACKGROUND_COLOR)));
91
92 QSize sz(that->m_features.width, that->m_features.height);
93 that->m_composited = new QImage;
94 if (!QImageIOHandler::allocateImage(sz, QImage::Format_ARGB32, that->m_composited))
95 return false;
96 if (that->m_features.has_alpha)
97 that->m_composited->fill(Qt::transparent);
98
99 m_scanState = ScanSuccess;
100 }
101 } else {
102 m_scanState = ScanSuccess;
103 }
104 }
105
106 return m_scanState == ScanSuccess;
107}
108
109bool QWebpHandler::ensureDemuxer()
110{
111 if (m_demuxer)
112 return true;
113
114 m_rawData = device()->readAll();
115 m_webpData.bytes = reinterpret_cast<const uint8_t *>(m_rawData.constData());
116 m_webpData.size = m_rawData.size();
117
118 m_demuxer = WebPDemux(&m_webpData);
119 if (m_demuxer == NULL)
120 return false;
121
122 m_formatFlags = WebPDemuxGetI(m_demuxer, WEBP_FF_FORMAT_FLAGS);
123 return true;
124}
125
127{
128 if (!ensureScanned() || !ensureDemuxer())
129 return false;
130
131 QRect prevFrameRect;
132 if (m_iter.frame_num == 0) {
133 // Read global meta-data chunks first
134 WebPChunkIterator metaDataIter;
135 if ((m_formatFlags & ICCP_FLAG) && WebPDemuxGetChunk(m_demuxer, "ICCP", 1, &metaDataIter)) {
136 QByteArray iccProfile = QByteArray::fromRawData(reinterpret_cast<const char *>(metaDataIter.chunk.bytes),
137 metaDataIter.chunk.size);
138 // Ensure the profile is 4-byte aligned.
139 if (reinterpret_cast<qintptr>(iccProfile.constData()) & 0x3)
140 iccProfile.detach();
141 m_colorSpace = QColorSpace::fromIccProfile(iccProfile);
142 // ### consider parsing EXIF and/or XMP metadata too.
143 WebPDemuxReleaseChunkIterator(&metaDataIter);
144 }
145
146 // Go to first frame
147 if (!WebPDemuxGetFrame(m_demuxer, 1, &m_iter))
148 return false;
149 } else {
150 if (m_iter.has_alpha && m_iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND)
151 prevFrameRect = currentImageRect();
152
153 // Go to next frame
154 if (!WebPDemuxNextFrame(&m_iter))
155 return false;
156 }
157
158 WebPBitstreamFeatures features;
159 VP8StatusCode status = WebPGetFeatures(m_iter.fragment.bytes, m_iter.fragment.size, &features);
160 if (status != VP8_STATUS_OK)
161 return false;
162
165 if (!QImageIOHandler::allocateImage(QSize(m_iter.width, m_iter.height), format, &frame))
166 return false;
167 uint8_t *output = frame.bits();
168 size_t output_size = frame.sizeInBytes();
169#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
170 if (!WebPDecodeBGRAInto(
171 reinterpret_cast<const uint8_t*>(m_iter.fragment.bytes), m_iter.fragment.size,
172 output, output_size, frame.bytesPerLine()))
173#else
174 if (!WebPDecodeARGBInto(
175 reinterpret_cast<const uint8_t*>(m_iter.fragment.bytes), m_iter.fragment.size,
176 output, output_size, frame.bytesPerLine()))
177#endif
178 return false;
179
180 if (!m_features.has_animation) {
181 // Single image
182 *image = frame;
183 } else {
184 // Animation
185 QPainter painter(m_composited);
186 if (!prevFrameRect.isEmpty()) {
188 painter.fillRect(prevFrameRect, Qt::black);
189 }
190 if (m_features.has_alpha) {
191 if (m_iter.blend_method == WEBP_MUX_NO_BLEND)
193 else
195 }
197
198 *image = *m_composited;
199 }
200 image->setColorSpace(m_colorSpace);
201
202 return true;
203}
204
206{
207 if (image.isNull()) {
208 qWarning() << "source image is null.";
209 return false;
210 }
211 if (std::max(image.width(), image.height()) > WEBP_MAX_DIMENSION) {
212 qWarning() << "QWebpHandler::write() source image too large for WebP: " << image.size();
213 return false;
214 }
215
216 QImage srcImage = image;
217 bool alpha = srcImage.hasAlphaChannel();
219 if (srcImage.format() != newFormat)
220 srcImage = srcImage.convertToFormat(newFormat);
221
222 WebPPicture picture;
223 WebPConfig config;
224
225 if (!WebPPictureInit(&picture) || !WebPConfigInit(&config)) {
226 qWarning() << "failed to init webp picture and config";
227 return false;
228 }
229
230 picture.width = srcImage.width();
231 picture.height = srcImage.height();
232 picture.use_argb = 1;
233 bool failed = false;
234 if (alpha)
235 failed = !WebPPictureImportRGBA(&picture, srcImage.bits(), srcImage.bytesPerLine());
236 else
237 failed = !WebPPictureImportRGB(&picture, srcImage.bits(), srcImage.bytesPerLine());
238
239 if (failed) {
240 qWarning() << "failed to import image data to webp picture.";
241 WebPPictureFree(&picture);
242 return false;
243 }
244
245 int reqQuality = m_quality < 0 ? 75 : qMin(m_quality, 100);
246 if (reqQuality < 100) {
247 config.lossless = 0;
248 config.quality = reqQuality;
249 } else {
250 config.lossless = 1;
251 config.quality = 70; // For lossless, specifies compression effort; 70 is libwebp default
252 }
253 config.alpha_quality = config.quality;
254 WebPMemoryWriter writer;
255 WebPMemoryWriterInit(&writer);
256 picture.writer = WebPMemoryWrite;
257 picture.custom_ptr = &writer;
258
259 if (!WebPEncode(&config, &picture)) {
260 qWarning() << "failed to encode webp picture, error code: " << picture.error_code;
261 WebPPictureFree(&picture);
262 WebPMemoryWriterClear(&writer);
263 return false;
264 }
265
266 bool res = false;
267 if (image.colorSpace().isValid()) {
268 int copy_data = 0;
269 WebPMux *mux = WebPMuxNew();
270 WebPData image_data = { writer.mem, writer.size };
271 WebPMuxSetImage(mux, &image_data, copy_data);
272 uint8_t vp8xChunk[10];
273 uint8_t flags = 0x20; // Has ICCP chunk, no XMP, EXIF or animation.
274 if (image.hasAlphaChannel())
275 flags |= 0x10;
276 vp8xChunk[0] = flags;
277 vp8xChunk[1] = 0;
278 vp8xChunk[2] = 0;
279 vp8xChunk[3] = 0;
280 const unsigned width = image.width() - 1;
281 const unsigned height = image.height() - 1;
282 vp8xChunk[4] = width & 0xff;
283 vp8xChunk[5] = (width >> 8) & 0xff;
284 vp8xChunk[6] = (width >> 16) & 0xff;
285 vp8xChunk[7] = height & 0xff;
286 vp8xChunk[8] = (height >> 8) & 0xff;
287 vp8xChunk[9] = (height >> 16) & 0xff;
288 WebPData vp8x_data = { vp8xChunk, 10 };
289 if (WebPMuxSetChunk(mux, "VP8X", &vp8x_data, copy_data) == WEBP_MUX_OK) {
290 QByteArray iccProfile = image.colorSpace().iccProfile();
291 WebPData iccp_data = {
292 reinterpret_cast<const uint8_t *>(iccProfile.constData()),
293 static_cast<size_t>(iccProfile.size())
294 };
295 if (WebPMuxSetChunk(mux, "ICCP", &iccp_data, copy_data) == WEBP_MUX_OK) {
296 WebPData output_data;
297 if (WebPMuxAssemble(mux, &output_data) == WEBP_MUX_OK) {
298 res = (output_data.size ==
299 static_cast<size_t>(device()->write(reinterpret_cast<const char *>(output_data.bytes), output_data.size)));
300 }
301 WebPDataClear(&output_data);
302 }
303 }
304 WebPMuxDelete(mux);
305 }
306 if (!res) {
307 res = (writer.size ==
308 static_cast<size_t>(device()->write(reinterpret_cast<const char *>(writer.mem), writer.size)));
309 }
310 WebPPictureFree(&picture);
311 WebPMemoryWriterClear(&writer);
312
313 return res;
314}
315
317{
318 if (!supportsOption(option) || !ensureScanned())
319 return QVariant();
320
321 switch (option) {
322 case Quality:
323 return m_quality;
324 case Size:
325 return QSize(m_features.width, m_features.height);
326 case Animation:
327 return m_features.has_animation;
328 case BackgroundColor:
329 return m_bgColor;
330 default:
331 return QVariant();
332 }
333}
334
336{
337 switch (option) {
338 case Quality:
339 m_quality = value.toInt();
340 return;
341 default:
342 break;
343 }
345}
346
348{
349 return option == Quality
350 || option == Size
351 || option == Animation
353}
354
356{
357 if (!ensureScanned())
358 return 0;
359
360 if (!m_features.has_animation)
361 return 1;
362
363 return m_frameCount;
364}
365
367{
368 if (!ensureScanned() || !m_features.has_animation)
369 return 0;
370
371 // Frame number in WebP starts from 1
372 return m_iter.frame_num - 1;
373}
374
376{
377 if (!ensureScanned())
378 return QRect();
379
380 return QRect(m_iter.x_offset, m_iter.y_offset, m_iter.width, m_iter.height);
381}
382
384{
385 if (!ensureScanned() || !m_features.has_animation)
386 return 0;
387
388 // Loop count in WebP starts from 0
389 return m_loop - 1;
390}
391
393{
394 if (!ensureScanned() || !m_features.has_animation)
395 return 0;
396
397 return m_iter.duration;
398}
IOBluetoothDevice * device
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
void detach()
Definition qbytearray.h:625
static QByteArray fromRawData(const char *data, qsizetype size)
Constructs a QByteArray that uses the first size bytes of the data array.
Definition qbytearray.h:409
static QColorSpace fromIccProfile(const QByteArray &iccProfile)
Creates a QColorSpace from ICC profile iccProfile.
static QColor fromRgba(QRgb rgba) noexcept
Static convenience function that returns a QColor constructed from the given QRgb value rgba.
Definition qcolor.cpp:2385
\inmodule QtCore \reentrant
Definition qiodevice.h:34
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
qint64 peek(char *data, qint64 maxlen)
ImageOption
This enum describes the different options supported by QImageIOHandler.
static bool allocateImage(QSize size, QImage::Format format, QImage *image)
virtual void setOption(ImageOption option, const QVariant &value)
Sets the option option with the value value.
QIODevice * device() const
Returns the device currently assigned to the QImageIOHandler.
void setFormat(const QByteArray &format)
Sets the format of the QImageIOHandler to format.
\inmodule QtGui
Definition qimage.h:37
Format
The following image formats are available in Qt.
Definition qimage.h:41
@ Format_RGBA8888
Definition qimage.h:59
@ Format_RGB888
Definition qimage.h:55
@ Format_RGB32
Definition qimage.h:46
@ Format_ARGB32
Definition qimage.h:47
void fill(uint pixel)
Fills the entire image with the given pixelValue.
Definition qimage.cpp:1758
The QPainter class performs low-level painting on widgets and other paint devices.
Definition qpainter.h:46
void setCompositionMode(CompositionMode mode)
Sets the composition mode to the given mode.
void drawImage(const QRectF &targetRect, const QImage &image, const QRectF &sourceRect, Qt::ImageConversionFlags flags=Qt::AutoColor)
Draws the rectangular portion source of the given image into the target rectangle in the paint device...
@ CompositionMode_SourceOver
Definition qpainter.h:98
@ CompositionMode_Clear
Definition qpainter.h:100
@ CompositionMode_Source
Definition qpainter.h:101
void fillRect(const QRectF &, const QBrush &)
Fills the given rectangle with the brush specified.
\inmodule QtCore\reentrant
Definition qrect.h:30
\inmodule QtCore
Definition qsize.h:25
\inmodule QtCore
Definition qvariant.h:65
bool write(const QImage &image) override
Writes the image image to the assigned device.
bool supportsOption(ImageOption option) const override
Returns true if the QImageIOHandler supports the option option; otherwise returns false.
bool read(QImage *image) override
Read an image from the device, and stores it in image.
QRect currentImageRect() const override
Returns the rect of the current image.
int currentImageNumber() const override
For image formats that support animation, this function returns the sequence number of the current im...
int imageCount() const override
For image formats that support animation, this function returns the number of images in the animation...
bool canRead() const override
Returns true if an image can be read from the device (i.e., the image format is supported,...
QVariant option(ImageOption option) const override
Returns the value assigned to option as a QVariant.
int loopCount() const override
For image formats that support animation, this function returns the number of times the animation sho...
int nextImageDelay() const override
For image formats that support animation, this function returns the number of milliseconds to wait un...
void setOption(ImageOption option, const QVariant &value) override
Sets the option option with the value value.
@ transparent
Definition qnamespace.h:47
@ black
Definition qnamespace.h:30
Definition image.cpp:4
#define QByteArrayLiteral(str)
Definition qbytearray.h:52
static QString header(const QString &name)
EGLConfig config
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qWarning
Definition qlogging.h:166
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
GLint GLsizei GLsizei height
GLint GLsizei width
GLbitfield flags
GLint GLsizei GLsizei GLenum format
GLuint res
GLuint GLenum option
GLfloat GLfloat GLfloat alpha
Definition qopenglext.h:418
QT_BEGIN_NAMESPACE typedef unsigned int QRgb
Definition qrgb.h:13
long long qint64
Definition qtypes.h:60
ptrdiff_t qintptr
Definition qtypes.h:166
QT_BEGIN_NAMESPACE typedef uchar * output
static const int riffHeaderSize
QPainter painter(this)
[7]
QFrame frame
[0]