KImageFormats

avif.cpp
1/*
2 AV1 Image File Format (AVIF) support for QImage.
3
4 SPDX-FileCopyrightText: 2020 Daniel Novomesky <dnovomesky@gmail.com>
5
6 SPDX-License-Identifier: BSD-2-Clause
7*/
8
9#include <QThread>
10#include <QtGlobal>
11
12#include <QColorSpace>
13
14#include "avif_p.h"
15#include "util_p.h"
16
17#include <cfloat>
18
19/*
20Quality range - compression/subsampling
21100 - lossless RGB compression
22< KIMG_AVIF_QUALITY_BEST, 100 ) - YUV444 color subsampling
23< KIMG_AVIF_QUALITY_HIGH, KIMG_AVIF_QUALITY_BEST ) - YUV422 color subsampling
24< 0, KIMG_AVIF_QUALITY_HIGH ) - YUV420 color subsampling
25< 0, KIMG_AVIF_QUALITY_LOW ) - lossy compression of alpha channel
26*/
27
28#ifndef KIMG_AVIF_DEFAULT_QUALITY
29#define KIMG_AVIF_DEFAULT_QUALITY 68
30#endif
31
32#ifndef KIMG_AVIF_QUALITY_BEST
33#define KIMG_AVIF_QUALITY_BEST 90
34#endif
35
36#ifndef KIMG_AVIF_QUALITY_HIGH
37#define KIMG_AVIF_QUALITY_HIGH 80
38#endif
39
40#ifndef KIMG_AVIF_QUALITY_LOW
41#define KIMG_AVIF_QUALITY_LOW 51
42#endif
43
44QAVIFHandler::QAVIFHandler()
45 : m_parseState(ParseAvifNotParsed)
46 , m_quality(KIMG_AVIF_DEFAULT_QUALITY)
47 , m_container_width(0)
48 , m_container_height(0)
49 , m_rawAvifData(AVIF_DATA_EMPTY)
50 , m_decoder(nullptr)
51 , m_must_jump_to_next_image(false)
52{
53}
54
55QAVIFHandler::~QAVIFHandler()
56{
57 if (m_decoder) {
58 avifDecoderDestroy(m_decoder);
59 }
60}
61
62bool QAVIFHandler::canRead() const
63{
64 if (m_parseState == ParseAvifNotParsed && !canRead(device())) {
65 return false;
66 }
67
68 if (m_parseState != ParseAvifError) {
69 setFormat("avif");
70
71 if (m_parseState == ParseAvifFinished) {
72 return false;
73 }
74
75 return true;
76 }
77 return false;
78}
79
80bool QAVIFHandler::canRead(QIODevice *device)
81{
82 if (!device) {
83 return false;
84 }
85 QByteArray header = device->peek(144);
86 if (header.size() < 12) {
87 return false;
88 }
89
90 avifROData input;
91 input.data = reinterpret_cast<const uint8_t *>(header.constData());
92 input.size = header.size();
93
94 if (avifPeekCompatibleFileType(&input)) {
95 return true;
96 }
97 return false;
98}
99
100bool QAVIFHandler::ensureParsed() const
101{
102 if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifMetadata || m_parseState == ParseAvifFinished) {
103 return true;
104 }
105 if (m_parseState == ParseAvifError) {
106 return false;
107 }
108
109 QAVIFHandler *that = const_cast<QAVIFHandler *>(this);
110
111 return that->ensureDecoder();
112}
113
114bool QAVIFHandler::ensureOpened() const
115{
116 if (m_parseState == ParseAvifSuccess || m_parseState == ParseAvifFinished) {
117 return true;
118 }
119 if (m_parseState == ParseAvifError) {
120 return false;
121 }
122
123 QAVIFHandler *that = const_cast<QAVIFHandler *>(this);
124 if (ensureParsed()) {
125 if (m_parseState == ParseAvifMetadata) {
126 bool success = that->jumpToNextImage();
127 that->m_parseState = success ? ParseAvifSuccess : ParseAvifError;
128 return success;
129 }
130 }
131
132 that->m_parseState = ParseAvifError;
133 return false;
134}
135
136bool QAVIFHandler::ensureDecoder()
137{
138 if (m_decoder) {
139 return true;
140 }
141
142 m_rawData = device()->readAll();
143
144 m_rawAvifData.data = reinterpret_cast<const uint8_t *>(m_rawData.constData());
145 m_rawAvifData.size = m_rawData.size();
146
147 if (avifPeekCompatibleFileType(&m_rawAvifData) == AVIF_FALSE) {
148 m_parseState = ParseAvifError;
149 return false;
150 }
151
152 m_decoder = avifDecoderCreate();
153
154 m_decoder->ignoreExif = AVIF_TRUE;
155 m_decoder->ignoreXMP = AVIF_TRUE;
156
157#if AVIF_VERSION >= 80400
158 m_decoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
159#endif
160
161#if AVIF_VERSION >= 90100
162 m_decoder->strictFlags = AVIF_STRICT_DISABLED;
163#endif
164
165#if AVIF_VERSION >= 110000
166 m_decoder->imageDimensionLimit = 65535;
167#endif
168
169 avifResult decodeResult;
170
171 decodeResult = avifDecoderSetIOMemory(m_decoder, m_rawAvifData.data, m_rawAvifData.size);
172 if (decodeResult != AVIF_RESULT_OK) {
173 qWarning("ERROR: avifDecoderSetIOMemory failed: %s", avifResultToString(decodeResult));
174
175 avifDecoderDestroy(m_decoder);
176 m_decoder = nullptr;
177 m_parseState = ParseAvifError;
178 return false;
179 }
180
181 decodeResult = avifDecoderParse(m_decoder);
182 if (decodeResult != AVIF_RESULT_OK) {
183 qWarning("ERROR: Failed to parse input: %s", avifResultToString(decodeResult));
184
185 avifDecoderDestroy(m_decoder);
186 m_decoder = nullptr;
187 m_parseState = ParseAvifError;
188 return false;
189 }
190
191 m_container_width = m_decoder->image->width;
192 m_container_height = m_decoder->image->height;
193
194 if ((m_container_width > 65535) || (m_container_height > 65535)) {
195 qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
196 m_parseState = ParseAvifError;
197 return false;
198 }
199
200 if ((m_container_width == 0) || (m_container_height == 0)) {
201 qWarning("Empty image, nothing to decode");
202 m_parseState = ParseAvifError;
203 return false;
204 }
205
206 if (m_container_width > ((16384 * 16384) / m_container_height)) {
207 qWarning("AVIF image (%dx%d) has more than 256 megapixels!", m_container_width, m_container_height);
208 m_parseState = ParseAvifError;
209 return false;
210 }
211
212 // calculate final dimensions with crop and rotate operations applied
213 int new_width = m_container_width;
214 int new_height = m_container_height;
215
216 if (m_decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) {
217 if ((m_decoder->image->clap.widthD > 0) && (m_decoder->image->clap.heightD > 0) && (m_decoder->image->clap.horizOffD > 0)
218 && (m_decoder->image->clap.vertOffD > 0)) {
219 int crop_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
220 if (crop_width < new_width && crop_width > 0) {
221 new_width = crop_width;
222 }
223 int crop_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5);
224 if (crop_height < new_height && crop_height > 0) {
225 new_height = crop_height;
226 }
227 }
228 }
229
230 if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IROT) {
231 if (m_decoder->image->irot.angle == 1 || m_decoder->image->irot.angle == 3) {
232 int tmp = new_width;
233 new_width = new_height;
234 new_height = tmp;
235 }
236 }
237
238 m_estimated_dimensions.setWidth(new_width);
239 m_estimated_dimensions.setHeight(new_height);
240
241 m_parseState = ParseAvifMetadata;
242 return true;
243}
244
245bool QAVIFHandler::decode_one_frame()
246{
247 if (!ensureParsed()) {
248 return false;
249 }
250
251 bool loadalpha;
252 bool loadgray = false;
253
254 if (m_decoder->image->alphaPlane) {
255 loadalpha = true;
256 } else {
257 loadalpha = false;
258 if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
259 loadgray = true;
260 }
261 }
262
263 QImage::Format resultformat;
264
265 if (m_decoder->image->depth > 8) {
266 if (loadalpha) {
267 resultformat = QImage::Format_RGBA64;
268 } else {
269 resultformat = QImage::Format_RGBX64;
270 }
271 } else {
272 if (loadalpha) {
273 resultformat = QImage::Format_ARGB32;
274 } else {
275 resultformat = QImage::Format_RGB32;
276 }
277 }
278
279 QImage result = imageAlloc(m_decoder->image->width, m_decoder->image->height, resultformat);
280 if (result.isNull()) {
281 qWarning("Memory cannot be allocated");
282 return false;
283 }
284
285 QColorSpace colorspace;
286 if (m_decoder->image->icc.data && (m_decoder->image->icc.size > 0)) {
287 const QByteArray icc_data(reinterpret_cast<const char *>(m_decoder->image->icc.data), m_decoder->image->icc.size);
288 colorspace = QColorSpace::fromIccProfile(icc_data);
289 if (!colorspace.isValid()) {
290 qWarning("AVIF image has Qt-unsupported or invalid ICC profile!");
291 }
292#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
293 else {
294 if (colorspace.colorModel() == QColorSpace::ColorModel::Cmyk) {
295 qWarning("CMYK ICC profile is not extected for AVIF, discarding the ICCprofile!");
296 colorspace = QColorSpace();
297 } else if (colorspace.colorModel() == QColorSpace::ColorModel::Rgb && loadgray) {
298 // Input is GRAY but ICC is RGB, we will return RGB image
299 loadgray = false;
300 } else if (colorspace.colorModel() == QColorSpace::ColorModel::Gray && !loadgray) {
301 // ICC is GRAY but we must return RGB (image has alpha channel for example)
302 // we create similar RGB profile (same whitepoint and TRC)
303 QPointF gray_whitePoint = colorspace.whitePoint();
304 if (gray_whitePoint.isNull()) {
305 gray_whitePoint = QPointF(0.3127f, 0.329f);
306 }
307
308 const QPointF redP(0.64f, 0.33f);
309 const QPointF greenP(0.3f, 0.6f);
310 const QPointF blueP(0.15f, 0.06f);
311
312 QColorSpace::TransferFunction trc_new = colorspace.transferFunction();
313 float gamma_new = colorspace.gamma();
314 if (trc_new == QColorSpace::TransferFunction::Custom) {
315 trc_new = QColorSpace::TransferFunction::SRgb;
316 }
317 colorspace = QColorSpace(gray_whitePoint, redP, greenP, blueP, trc_new, gamma_new);
318 if (!colorspace.isValid()) {
319 qWarning("AVIF plugin created invalid QColorSpace!");
320 }
321 }
322 }
323#endif
324 } else {
325 float prim[8] = {0.64f, 0.33f, 0.3f, 0.6f, 0.15f, 0.06f, 0.3127f, 0.329f};
326 // outPrimaries: rX, rY, gX, gY, bX, bY, wX, wY
327 avifColorPrimariesGetValues(m_decoder->image->colorPrimaries, prim);
328
329 const QPointF redPoint(QAVIFHandler::CompatibleChromacity(prim[0], prim[1]));
330 const QPointF greenPoint(QAVIFHandler::CompatibleChromacity(prim[2], prim[3]));
331 const QPointF bluePoint(QAVIFHandler::CompatibleChromacity(prim[4], prim[5]));
332 const QPointF whitePoint(QAVIFHandler::CompatibleChromacity(prim[6], prim[7]));
333
334 QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom;
335 float q_trc_gamma = 0.0f;
336
337 switch (m_decoder->image->transferCharacteristics) {
338 /* AVIF_TRANSFER_CHARACTERISTICS_BT470M */
339 case 4:
340 q_trc = QColorSpace::TransferFunction::Gamma;
341 q_trc_gamma = 2.2f;
342 break;
343 /* AVIF_TRANSFER_CHARACTERISTICS_BT470BG */
344 case 5:
345 q_trc = QColorSpace::TransferFunction::Gamma;
346 q_trc_gamma = 2.8f;
347 break;
348 /* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
349 case 8:
350 q_trc = QColorSpace::TransferFunction::Linear;
351 break;
352 /* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
353 case 0:
354 case 2: /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
355 case 13:
356 q_trc = QColorSpace::TransferFunction::SRgb;
357 break;
358#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
359 case 16: /* AVIF_TRANSFER_CHARACTERISTICS_PQ */
360 q_trc = QColorSpace::TransferFunction::St2084;
361 break;
362 case 18: /* AVIF_TRANSFER_CHARACTERISTICS_HLG */
363 q_trc = QColorSpace::TransferFunction::Hlg;
364 break;
365#endif
366 default:
367 qWarning("CICP colorPrimaries: %d, transferCharacteristics: %d\nThe colorspace is unsupported by this plug-in yet.",
368 m_decoder->image->colorPrimaries,
369 m_decoder->image->transferCharacteristics);
370 q_trc = QColorSpace::TransferFunction::SRgb;
371 break;
372 }
373
374 if (q_trc != QColorSpace::TransferFunction::Custom) { // we create new colorspace using Qt
375#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
376 if (loadgray) {
377 colorspace = QColorSpace(whitePoint, q_trc, q_trc_gamma);
378 } else {
379#endif
380 switch (m_decoder->image->colorPrimaries) {
381 /* AVIF_COLOR_PRIMARIES_BT709 */
382 case 0:
383 case 1:
384 case 2: /* AVIF_COLOR_PRIMARIES_UNSPECIFIED */
385 colorspace = QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma);
386 break;
387 /* AVIF_COLOR_PRIMARIES_SMPTE432 */
388 case 12:
389 colorspace = QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma);
390 break;
391 default:
392 colorspace = QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma);
393 break;
394 }
395#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
396 }
397#endif
398 }
399
400 if (!colorspace.isValid()) {
401 qWarning("AVIF plugin created invalid QColorSpace from NCLX/CICP!");
402 }
403 }
404
405 avifRGBImage rgb;
406 avifRGBImageSetDefaults(&rgb, m_decoder->image);
407
408#if AVIF_VERSION >= 1000000
409 rgb.maxThreads = m_decoder->maxThreads;
410#endif
411
412 if (m_decoder->image->depth > 8) {
413 rgb.depth = 16;
414 rgb.format = AVIF_RGB_FORMAT_RGBA;
415
416 if (loadgray) {
417 resultformat = QImage::Format_Grayscale16;
418 }
419 } else {
420 rgb.depth = 8;
421#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
422 rgb.format = AVIF_RGB_FORMAT_BGRA;
423#else
424 rgb.format = AVIF_RGB_FORMAT_ARGB;
425#endif
426
427#if AVIF_VERSION >= 80400
428 if (m_decoder->imageCount > 1) {
429 /* accelerate animated AVIF */
430 rgb.chromaUpsampling = AVIF_CHROMA_UPSAMPLING_FASTEST;
431 }
432#endif
433
434 if (loadgray) {
435 resultformat = QImage::Format_Grayscale8;
436 }
437 }
438
439 rgb.rowBytes = result.bytesPerLine();
440 rgb.pixels = result.bits();
441
442 avifResult res = avifImageYUVToRGB(m_decoder->image, &rgb);
443 if (res != AVIF_RESULT_OK) {
444 qWarning("ERROR in avifImageYUVToRGB: %s", avifResultToString(res));
445 return false;
446 }
447
448 if (m_decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) {
449 if ((m_decoder->image->clap.widthD > 0) && (m_decoder->image->clap.heightD > 0) && (m_decoder->image->clap.horizOffD > 0)
450 && (m_decoder->image->clap.vertOffD > 0)) {
451 int new_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
452 if (new_width > result.width()) {
453 new_width = result.width();
454 }
455
456 int new_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5);
457 if (new_height > result.height()) {
458 new_height = result.height();
459 }
460
461 if (new_width > 0 && new_height > 0) {
462 int offx =
463 ((double)((int32_t)m_decoder->image->clap.horizOffN)) / (m_decoder->image->clap.horizOffD) + (result.width() - new_width) / 2.0 + 0.5;
464 if (offx < 0) {
465 offx = 0;
466 } else if (offx > (result.width() - new_width)) {
467 offx = result.width() - new_width;
468 }
469
470 int offy =
471 ((double)((int32_t)m_decoder->image->clap.vertOffN)) / (m_decoder->image->clap.vertOffD) + (result.height() - new_height) / 2.0 + 0.5;
472 if (offy < 0) {
473 offy = 0;
474 } else if (offy > (result.height() - new_height)) {
475 offy = result.height() - new_height;
476 }
477
478 result = result.copy(offx, offy, new_width, new_height);
479 }
480 }
481
482 else { // Zero values, we need to avoid 0 divide.
483 qWarning("ERROR: Wrong values in avifCleanApertureBox");
484 }
485 }
486
487 if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IROT) {
489 switch (m_decoder->image->irot.angle) {
490 case 1:
491 transform.rotate(-90);
492 result = result.transformed(transform);
493 break;
494 case 2:
495 transform.rotate(180);
496 result = result.transformed(transform);
497 break;
498 case 3:
499 transform.rotate(90);
500 result = result.transformed(transform);
501 break;
502 }
503 }
504
505 if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IMIR) {
506#if AVIF_VERSION > 90100 && AVIF_VERSION < 1000000
507 switch (m_decoder->image->imir.mode) {
508#else
509 switch (m_decoder->image->imir.axis) {
510#endif
511 case 0: // top-to-bottom
512 result = result.mirrored(false, true);
513 break;
514 case 1: // left-to-right
515 result = result.mirrored(true, false);
516 break;
517 }
518 }
519
520 if (resultformat == result.format()) {
521 m_current_image = result;
522 } else {
523 m_current_image = result.convertToFormat(resultformat);
524 }
525
526 m_current_image.setColorSpace(colorspace);
527
528 m_estimated_dimensions = m_current_image.size();
529
530 m_must_jump_to_next_image = false;
531 return true;
532}
533
534bool QAVIFHandler::read(QImage *image)
535{
536 if (!ensureOpened()) {
537 return false;
538 }
539
540 if (m_must_jump_to_next_image) {
541 jumpToNextImage();
542 }
543
544 *image = m_current_image;
545 if (imageCount() >= 2) {
546 m_must_jump_to_next_image = true;
547 if (m_decoder->imageIndex >= m_decoder->imageCount - 1) {
548 // all frames in animation have been read
549 m_parseState = ParseAvifFinished;
550 }
551 } else {
552 // the static image has been read
553 m_parseState = ParseAvifFinished;
554 }
555 return true;
556}
557
558bool QAVIFHandler::write(const QImage &image)
559{
560 if (image.format() == QImage::Format_Invalid) {
561 qWarning("No image data to save!");
562 return false;
563 }
564
565 if ((image.width() > 0) && (image.height() > 0)) {
566 if ((image.width() > 65535) || (image.height() > 65535)) {
567 qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
568 return false;
569 }
570
571 if (image.width() > ((16384 * 16384) / image.height())) {
572 qWarning("Image (%dx%d) will not be saved because it has more than 256 megapixels!", image.width(), image.height());
573 return false;
574 }
575
576 if ((image.width() > 32768) || (image.height() > 32768)) {
577 qWarning("Image (%dx%d) has a dimension above 32768 pixels, saved AVIF may not work in other software!", image.width(), image.height());
578 }
579 } else {
580 qWarning("Image has zero dimension!");
581 return false;
582 }
583
584 const char *encoder_name = avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE);
585 if (!encoder_name) {
586 qWarning("Cannot save AVIF images because libavif was built without AV1 encoders!");
587 return false;
588 }
589
590 bool lossless = false;
591 if (m_quality >= 100) {
592 if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE)) {
593 lossless = true;
594 } else {
595 qWarning("You are using %s encoder. It is recommended to enable libAOM encoder in libavif to use lossless compression.", encoder_name);
596 }
597 }
598
599 if (m_quality > 100) {
600 m_quality = 100;
601 } else if (m_quality < 0) {
602 m_quality = KIMG_AVIF_DEFAULT_QUALITY;
603 }
604
605#if AVIF_VERSION < 1000000
606 int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
607 int minQuantizer = 0;
608 int maxQuantizerAlpha = 0;
609#endif
610 avifResult res;
611
612 bool save_grayscale; // true - monochrome, false - colors
613 int save_depth; // 8 or 10bit per channel
614 QImage::Format tmpformat; // format for temporary image
615
616 avifImage *avif = nullptr;
617
618 // grayscale detection
619 switch (image.format()) {
624 save_grayscale = true;
625 break;
627 save_grayscale = image.isGrayscale();
628 break;
629 default:
630 save_grayscale = false;
631 break;
632 }
633
634 // depth detection
635 switch (image.format()) {
644 save_depth = 10;
645 break;
646 default:
647 if (image.depth() > 32) {
648 save_depth = 10;
649 } else {
650 save_depth = 8;
651 }
652 break;
653 }
654
655#if AVIF_VERSION < 1000000
656 // deprecated quality settings
657 if (maxQuantizer > 20) {
658 minQuantizer = maxQuantizer - 20;
659 if (maxQuantizer > 40) { // we decrease quality of alpha channel here
660 maxQuantizerAlpha = maxQuantizer - 40;
661 }
662 }
663#endif
664
665 if (save_grayscale && !image.hasAlphaChannel()) { // we are going to save grayscale image without alpha channel
666 if (save_depth > 8) {
667 tmpformat = QImage::Format_Grayscale16;
668 } else {
669 tmpformat = QImage::Format_Grayscale8;
670 }
671 QImage tmpgrayimage = image.convertToFormat(tmpformat);
672
673 avif = avifImageCreate(tmpgrayimage.width(), tmpgrayimage.height(), save_depth, AVIF_PIXEL_FORMAT_YUV400);
674#if AVIF_VERSION >= 110000
675 res = avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
676 if (res != AVIF_RESULT_OK) {
677 qWarning("ERROR in avifImageAllocatePlanes: %s", avifResultToString(res));
678 return false;
679 }
680#else
681 avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
682#endif
683
684 if (tmpgrayimage.colorSpace().isValid()) {
685 avif->colorPrimaries = (avifColorPrimaries)1;
686 avif->matrixCoefficients = (avifMatrixCoefficients)1;
687
688 switch (tmpgrayimage.colorSpace().transferFunction()) {
689 case QColorSpace::TransferFunction::Linear:
690 /* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
691 avif->transferCharacteristics = (avifTransferCharacteristics)8;
692 break;
693 case QColorSpace::TransferFunction::SRgb:
694 /* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
695 avif->transferCharacteristics = (avifTransferCharacteristics)13;
696 break;
697#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
698 case QColorSpace::TransferFunction::St2084:
699 avif->transferCharacteristics = (avifTransferCharacteristics)16;
700 break;
701 case QColorSpace::TransferFunction::Hlg:
702 avif->transferCharacteristics = (avifTransferCharacteristics)18;
703 break;
704#endif
705 default:
706 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
707 break;
708 }
709 }
710
711 if (save_depth > 8) { // QImage::Format_Grayscale16
712 for (int y = 0; y < tmpgrayimage.height(); y++) {
713 const uint16_t *src16bit = reinterpret_cast<const uint16_t *>(tmpgrayimage.constScanLine(y));
714 uint16_t *dest16bit = reinterpret_cast<uint16_t *>(avif->yuvPlanes[0] + y * avif->yuvRowBytes[0]);
715 for (int x = 0; x < tmpgrayimage.width(); x++) {
716 int tmp_pixelval = (int)(((float)(*src16bit) / 65535.0f) * 1023.0f + 0.5f); // downgrade to 10 bits
717 *dest16bit = qBound(0, tmp_pixelval, 1023);
718 dest16bit++;
719 src16bit++;
720 }
721 }
722 } else { // QImage::Format_Grayscale8
723 for (int y = 0; y < tmpgrayimage.height(); y++) {
724 const uchar *src8bit = tmpgrayimage.constScanLine(y);
725 uint8_t *dest8bit = avif->yuvPlanes[0] + y * avif->yuvRowBytes[0];
726 for (int x = 0; x < tmpgrayimage.width(); x++) {
727 *dest8bit = *src8bit;
728 dest8bit++;
729 src8bit++;
730 }
731 }
732 }
733
734 } else { // we are going to save color image
735 if (save_depth > 8) {
736 if (image.hasAlphaChannel()) {
737 tmpformat = QImage::Format_RGBA64;
738 } else {
739 tmpformat = QImage::Format_RGBX64;
740 }
741 } else { // 8bit depth
742 if (image.hasAlphaChannel()) {
743 tmpformat = QImage::Format_RGBA8888;
744 } else {
745 tmpformat = QImage::Format_RGB888;
746 }
747 }
748
749#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
750 QImage tmpcolorimage;
751 auto cs = image.colorSpace();
752 if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && image.format() == QImage::Format_CMYK8888) {
753 tmpcolorimage = image.convertedToColorSpace(QColorSpace(QColorSpace::SRgb), tmpformat);
754 } else if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Gray) {
755 QColorSpace::TransferFunction trc_new = cs.transferFunction();
756 float gamma_new = cs.gamma();
757 if (trc_new == QColorSpace::TransferFunction::Custom) {
758 trc_new = QColorSpace::TransferFunction::SRgb;
759 }
760 tmpcolorimage = image.convertedToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, trc_new, gamma_new), tmpformat);
761 } else {
762 tmpcolorimage = image.convertToFormat(tmpformat);
763 }
764#else
765 QImage tmpcolorimage = image.convertToFormat(tmpformat);
766#endif
767
768 avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420;
769 if (m_quality >= KIMG_AVIF_QUALITY_HIGH) {
770 if (m_quality >= KIMG_AVIF_QUALITY_BEST) {
771 pixel_format = AVIF_PIXEL_FORMAT_YUV444; // best quality
772 } else {
773 pixel_format = AVIF_PIXEL_FORMAT_YUV422; // high quality
774 }
775 }
776
777 avifMatrixCoefficients matrix_to_save = (avifMatrixCoefficients)1; // default for Qt 5.12 and 5.13;
778
779 avifColorPrimaries primaries_to_save = (avifColorPrimaries)2;
780 avifTransferCharacteristics transfer_to_save = (avifTransferCharacteristics)2;
781 QByteArray iccprofile;
782
783 if (tmpcolorimage.colorSpace().isValid()) {
784 switch (tmpcolorimage.colorSpace().primaries()) {
785 case QColorSpace::Primaries::SRgb:
786 /* AVIF_COLOR_PRIMARIES_BT709 */
787 primaries_to_save = (avifColorPrimaries)1;
788 /* AVIF_MATRIX_COEFFICIENTS_BT709 */
789 matrix_to_save = (avifMatrixCoefficients)1;
790 break;
791 case QColorSpace::Primaries::DciP3D65:
792 /* AVIF_NCLX_COLOUR_PRIMARIES_P3, AVIF_NCLX_COLOUR_PRIMARIES_SMPTE432 */
793 primaries_to_save = (avifColorPrimaries)12;
794 /* AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL */
795 matrix_to_save = (avifMatrixCoefficients)12;
796 break;
797 default:
798 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
799 primaries_to_save = (avifColorPrimaries)2;
800 /* AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED */
801 matrix_to_save = (avifMatrixCoefficients)2;
802 break;
803 }
804
805 switch (tmpcolorimage.colorSpace().transferFunction()) {
806 case QColorSpace::TransferFunction::Linear:
807 /* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
808 transfer_to_save = (avifTransferCharacteristics)8;
809 break;
810 case QColorSpace::TransferFunction::Gamma:
811 if (qAbs(tmpcolorimage.colorSpace().gamma() - 2.2f) < 0.1f) {
812 /* AVIF_TRANSFER_CHARACTERISTICS_BT470M */
813 transfer_to_save = (avifTransferCharacteristics)4;
814 } else if (qAbs(tmpcolorimage.colorSpace().gamma() - 2.8f) < 0.1f) {
815 /* AVIF_TRANSFER_CHARACTERISTICS_BT470BG */
816 transfer_to_save = (avifTransferCharacteristics)5;
817 } else {
818 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
819 transfer_to_save = (avifTransferCharacteristics)2;
820 }
821 break;
822 case QColorSpace::TransferFunction::SRgb:
823 /* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
824 transfer_to_save = (avifTransferCharacteristics)13;
825 break;
826#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
827 case QColorSpace::TransferFunction::St2084:
828 transfer_to_save = (avifTransferCharacteristics)16;
829 break;
830 case QColorSpace::TransferFunction::Hlg:
831 transfer_to_save = (avifTransferCharacteristics)18;
832 break;
833#endif
834 default:
835 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
836 transfer_to_save = (avifTransferCharacteristics)2;
837 break;
838 }
839
840 // in case primaries or trc were not identified
841 if ((primaries_to_save == 2) || (transfer_to_save == 2)) {
842 if (lossless) {
843 iccprofile = tmpcolorimage.colorSpace().iccProfile();
844 } else {
845 // upgrade image to higher bit depth
846 if (save_depth == 8) {
847 save_depth = 10;
848 if (tmpcolorimage.hasAlphaChannel()) {
849 tmpcolorimage.convertTo(QImage::Format_RGBA64);
850 } else {
851 tmpcolorimage.convertTo(QImage::Format_RGBX64);
852 }
853 }
854
855 if ((primaries_to_save == 2) && (transfer_to_save != 2)) { // other primaries but known trc
856 primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
857 matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
858
859 switch (transfer_to_save) {
860 case 8: // AVIF_TRANSFER_CHARACTERISTICS_LINEAR
861 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::Linear));
862 break;
863 case 4: // AVIF_TRANSFER_CHARACTERISTICS_BT470M
864 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.2f));
865 break;
866 case 5: // AVIF_TRANSFER_CHARACTERISTICS_BT470BG
867 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.8f));
868 break;
869#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
870 case 16:
871 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::St2084));
872 break;
873 case 18:
874 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::Hlg));
875 break;
876#endif
877 default: // AVIF_TRANSFER_CHARACTERISTICS_SRGB + any other
878 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
879 transfer_to_save = (avifTransferCharacteristics)13;
880 break;
881 }
882 } else if ((primaries_to_save != 2) && (transfer_to_save == 2)) { // recognized primaries but other trc
883 transfer_to_save = (avifTransferCharacteristics)13;
884 tmpcolorimage.convertToColorSpace(tmpcolorimage.colorSpace().withTransferFunction(QColorSpace::TransferFunction::SRgb));
885 } else { // unrecognized profile
886 primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
887 transfer_to_save = (avifTransferCharacteristics)13;
888 matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
889 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
890 }
891 }
892 }
893 } else { // profile is unsupported by Qt
894 iccprofile = tmpcolorimage.colorSpace().iccProfile();
895 if (iccprofile.size() > 0) {
896 matrix_to_save = (avifMatrixCoefficients)6;
897 }
898 }
899
900 if (lossless && pixel_format == AVIF_PIXEL_FORMAT_YUV444) {
901 matrix_to_save = (avifMatrixCoefficients)0;
902 }
903 avif = avifImageCreate(tmpcolorimage.width(), tmpcolorimage.height(), save_depth, pixel_format);
904 avif->matrixCoefficients = matrix_to_save;
905
906 avif->colorPrimaries = primaries_to_save;
907 avif->transferCharacteristics = transfer_to_save;
908
909 if (iccprofile.size() > 0) {
910#if AVIF_VERSION >= 1000000
911 res = avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
912 if (res != AVIF_RESULT_OK) {
913 qWarning("ERROR in avifImageSetProfileICC: %s", avifResultToString(res));
914 return false;
915 }
916#else
917 avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
918#endif
919 }
920
921 avifRGBImage rgb;
922 avifRGBImageSetDefaults(&rgb, avif);
923 rgb.rowBytes = tmpcolorimage.bytesPerLine();
924 rgb.pixels = const_cast<uint8_t *>(tmpcolorimage.constBits());
925
926 if (save_depth > 8) { // 10bit depth
927 rgb.depth = 16;
928
929 if (!tmpcolorimage.hasAlphaChannel()) {
930 rgb.ignoreAlpha = AVIF_TRUE;
931 }
932
933 rgb.format = AVIF_RGB_FORMAT_RGBA;
934 } else { // 8bit depth
935 rgb.depth = 8;
936
937 if (tmpcolorimage.hasAlphaChannel()) {
938 rgb.format = AVIF_RGB_FORMAT_RGBA;
939 } else {
940 rgb.format = AVIF_RGB_FORMAT_RGB;
941 }
942 }
943
944 res = avifImageRGBToYUV(avif, &rgb);
945 if (res != AVIF_RESULT_OK) {
946 qWarning("ERROR in avifImageRGBToYUV: %s", avifResultToString(res));
947 return false;
948 }
949 }
950
951 avifRWData raw = AVIF_DATA_EMPTY;
952 avifEncoder *encoder = avifEncoderCreate();
953 encoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
954
955#if AVIF_VERSION < 1000000
956 encoder->minQuantizer = minQuantizer;
957 encoder->maxQuantizer = maxQuantizer;
958
959 if (image.hasAlphaChannel()) {
960 encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
961 encoder->maxQuantizerAlpha = maxQuantizerAlpha;
962 }
963#else
964 encoder->quality = m_quality;
965
966 if (image.hasAlphaChannel()) {
967 if (m_quality >= KIMG_AVIF_QUALITY_LOW) {
968 encoder->qualityAlpha = 100;
969 } else {
970 encoder->qualityAlpha = 100 - (KIMG_AVIF_QUALITY_LOW - m_quality) / 2;
971 }
972 }
973#endif
974
975 encoder->speed = 6;
976
977 res = avifEncoderWrite(encoder, avif, &raw);
978 avifEncoderDestroy(encoder);
979 avifImageDestroy(avif);
980
981 if (res == AVIF_RESULT_OK) {
982 qint64 status = device()->write(reinterpret_cast<const char *>(raw.data), raw.size);
983 avifRWDataFree(&raw);
984
985 if (status > 0) {
986 return true;
987 } else if (status == -1) {
988 qWarning("Write error: %s", qUtf8Printable(device()->errorString()));
989 return false;
990 }
991 } else {
992 qWarning("ERROR: Failed to encode: %s", avifResultToString(res));
993 }
994
995 return false;
996}
997
998QVariant QAVIFHandler::option(ImageOption option) const
999{
1000 if (option == Quality) {
1001 return m_quality;
1002 }
1003
1004 if (!supportsOption(option) || !ensureParsed()) {
1005 return QVariant();
1006 }
1007
1008 switch (option) {
1009 case Size:
1010 return m_estimated_dimensions;
1011 case Animation:
1012 if (imageCount() >= 2) {
1013 return true;
1014 } else {
1015 return false;
1016 }
1017 default:
1018 return QVariant();
1019 }
1020}
1021
1022void QAVIFHandler::setOption(ImageOption option, const QVariant &value)
1023{
1024 switch (option) {
1025 case Quality:
1026 m_quality = value.toInt();
1027 if (m_quality > 100) {
1028 m_quality = 100;
1029 } else if (m_quality < 0) {
1030 m_quality = KIMG_AVIF_DEFAULT_QUALITY;
1031 }
1032 return;
1033 default:
1034 break;
1035 }
1036 QImageIOHandler::setOption(option, value);
1037}
1038
1039bool QAVIFHandler::supportsOption(ImageOption option) const
1040{
1041 return option == Quality || option == Size || option == Animation;
1042}
1043
1044int QAVIFHandler::imageCount() const
1045{
1046 if (!ensureParsed()) {
1047 return 0;
1048 }
1049
1050 if (m_decoder->imageCount >= 1) {
1051 return m_decoder->imageCount;
1052 }
1053 return 0;
1054}
1055
1056int QAVIFHandler::currentImageNumber() const
1057{
1058 if (m_parseState == ParseAvifNotParsed) {
1059 return -1;
1060 }
1061
1062 if (m_parseState == ParseAvifError || !m_decoder) {
1063 return 0;
1064 }
1065
1066 if (m_parseState == ParseAvifMetadata) {
1067 if (m_decoder->imageCount >= 2) {
1068 return -1;
1069 } else {
1070 return 0;
1071 }
1072 }
1073
1074 return m_decoder->imageIndex;
1075}
1076
1077bool QAVIFHandler::jumpToNextImage()
1078{
1079 if (!ensureParsed()) {
1080 return false;
1081 }
1082
1083 avifResult decodeResult;
1084
1085 if (m_decoder->imageIndex >= 0) {
1086 if (m_decoder->imageCount < 2) {
1087 m_parseState = ParseAvifSuccess;
1088 return true;
1089 }
1090
1091 if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
1092 decodeResult = avifDecoderReset(m_decoder);
1093 if (decodeResult != AVIF_RESULT_OK) {
1094 qWarning("ERROR in avifDecoderReset: %s", avifResultToString(decodeResult));
1095 m_parseState = ParseAvifError;
1096 return false;
1097 }
1098 }
1099 }
1100
1101 decodeResult = avifDecoderNextImage(m_decoder);
1102
1103 if (decodeResult != AVIF_RESULT_OK) {
1104 qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
1105 m_parseState = ParseAvifError;
1106 return false;
1107 }
1108
1109 if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
1110 qWarning("Decoded image sequence size (%dx%d) do not match first image size (%dx%d)!",
1111 m_decoder->image->width,
1112 m_decoder->image->height,
1113 m_container_width,
1114 m_container_height);
1115
1116 m_parseState = ParseAvifError;
1117 return false;
1118 }
1119
1120 if (decode_one_frame()) {
1121 m_parseState = ParseAvifSuccess;
1122 return true;
1123 } else {
1124 m_parseState = ParseAvifError;
1125 return false;
1126 }
1127}
1128
1129bool QAVIFHandler::jumpToImage(int imageNumber)
1130{
1131 if (!ensureParsed()) {
1132 return false;
1133 }
1134
1135 if (m_decoder->imageCount < 2) { // not an animation
1136 if (imageNumber == 0) {
1137 if (ensureOpened()) {
1138 m_parseState = ParseAvifSuccess;
1139 return true;
1140 }
1141 }
1142 return false;
1143 }
1144
1145 if (imageNumber < 0 || imageNumber >= m_decoder->imageCount) { // wrong index
1146 return false;
1147 }
1148
1149 if (imageNumber == m_decoder->imageIndex) { // we are here already
1150 m_must_jump_to_next_image = false;
1151 m_parseState = ParseAvifSuccess;
1152 return true;
1153 }
1154
1155 avifResult decodeResult = avifDecoderNthImage(m_decoder, imageNumber);
1156
1157 if (decodeResult != AVIF_RESULT_OK) {
1158 qWarning("ERROR: Failed to decode %d th Image in sequence: %s", imageNumber, avifResultToString(decodeResult));
1159 m_parseState = ParseAvifError;
1160 return false;
1161 }
1162
1163 if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
1164 qWarning("Decoded image sequence size (%dx%d) do not match declared container size (%dx%d)!",
1165 m_decoder->image->width,
1166 m_decoder->image->height,
1167 m_container_width,
1168 m_container_height);
1169
1170 m_parseState = ParseAvifError;
1171 return false;
1172 }
1173
1174 if (decode_one_frame()) {
1175 m_parseState = ParseAvifSuccess;
1176 return true;
1177 } else {
1178 m_parseState = ParseAvifError;
1179 return false;
1180 }
1181}
1182
1183int QAVIFHandler::nextImageDelay() const
1184{
1185 if (!ensureOpened()) {
1186 return 0;
1187 }
1188
1189 if (m_decoder->imageCount < 2) {
1190 return 0;
1191 }
1192
1193 int delay_ms = 1000.0 * m_decoder->imageTiming.duration;
1194 if (delay_ms < 1) {
1195 delay_ms = 1;
1196 }
1197 return delay_ms;
1198}
1199
1200int QAVIFHandler::loopCount() const
1201{
1202 if (!ensureParsed()) {
1203 return 0;
1204 }
1205
1206 if (m_decoder->imageCount < 2) {
1207 return 0;
1208 }
1209
1210#if AVIF_VERSION >= 1000000
1211 if (m_decoder->repetitionCount >= 0) {
1212 return m_decoder->repetitionCount;
1213 }
1214#endif
1215 // Endless loop to work around https://github.com/AOMediaCodec/libavif/issues/347
1216 return -1;
1217}
1218
1219QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY)
1220{
1221 chrX = qBound(qreal(0.0), chrX, qreal(1.0));
1222 chrY = qBound(qreal(DBL_MIN), chrY, qreal(1.0));
1223
1224 if ((chrX + chrY) > qreal(1.0)) {
1225 chrX = qreal(1.0) - chrY;
1226 }
1227
1228 return QPointF(chrX, chrY);
1229}
1230
1231QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
1232{
1233 static const bool isAvifDecoderAvailable(avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_DECODE) != nullptr);
1234 static const bool isAvifEncoderAvailable(avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE) != nullptr);
1235
1236 if (format == "avif") {
1237 Capabilities format_cap;
1238 if (isAvifDecoderAvailable) {
1239 format_cap |= CanRead;
1240 }
1241 if (isAvifEncoderAvailable) {
1242 format_cap |= CanWrite;
1243 }
1244 return format_cap;
1245 }
1246
1247 if (format == "avifs") {
1248 Capabilities format_cap;
1249 if (isAvifDecoderAvailable) {
1250 format_cap |= CanRead;
1251 }
1252 return format_cap;
1253 }
1254
1255 if (!format.isEmpty()) {
1256 return {};
1257 }
1258 if (!device->isOpen()) {
1259 return {};
1260 }
1261
1262 Capabilities cap;
1263 if (device->isReadable() && QAVIFHandler::canRead(device) && isAvifDecoderAvailable) {
1264 cap |= CanRead;
1265 }
1266 if (device->isWritable() && isAvifEncoderAvailable) {
1267 cap |= CanWrite;
1268 }
1269 return cap;
1270}
1271
1272QImageIOHandler *QAVIFPlugin::create(QIODevice *device, const QByteArray &format) const
1273{
1274 QImageIOHandler *handler = new QAVIFHandler;
1275 handler->setDevice(device);
1276 handler->setFormat(format);
1277 return handler;
1278}
1279
1280#include "moc_avif_p.cpp"
Q_SCRIPTABLE CaptureState status()
KDOCTOOLS_EXPORT QString transform(const QString &file, const QString &stylesheet, const QList< const char * > &params=QList< const char * >())
QFlags< Capability > Capabilities
const char * constData() const const
char * data()
bool isEmpty() const const
qsizetype size() const const
QColorSpace fromIccProfile(const QByteArray &iccProfile)
float gamma() const const
QByteArray iccProfile() const const
bool isValid() const const
Primaries primaries() const const
TransferFunction transferFunction() const const
QColorSpace withTransferFunction(TransferFunction transferFunction, float gamma) const const
uchar * bits()
qsizetype bytesPerLine() const const
QColorSpace colorSpace() const const
const uchar * constBits() const const
const uchar * constScanLine(int i) const const
void convertTo(Format format, Qt::ImageConversionFlags flags)
void convertToColorSpace(const QColorSpace &colorSpace)
QImage convertToFormat(Format format, Qt::ImageConversionFlags flags) &&
QImage convertedToColorSpace(const QColorSpace &colorSpace) const const
QImage copy(const QRect &rectangle) const const
int depth() const const
Format format() const const
bool hasAlphaChannel() const const
int height() const const
bool isGrayscale() const const
bool isNull() const const
QImage mirrored(bool horizontal, bool vertical) &&
QImage transformed(const QTransform &matrix, Qt::TransformationMode mode) const const
int width() const const
void setDevice(QIODevice *device)
void setFormat(const QByteArray &format)
virtual void setOption(ImageOption option, const QVariant &value)
bool isOpen() const const
bool isReadable() const const
bool isWritable() const const
QByteArray peek(qint64 maxSize)
QByteArray readAll()
qint64 write(const QByteArray &data)
bool isNull() const const
int idealThreadCount()
int toInt(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 3 2025 12:01:07 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.