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#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
512 case 0: // top-to-bottom
513 result = result.mirrored(false, true);
514 break;
515 case 1: // left-to-right
516 result = result.mirrored(true, false);
517 break;
518#else
519 case 0: // top-to-bottom
520 result = result.flipped(Qt::Vertical);
521 break;
522 case 1: // left-to-right
523 result = result.flipped(Qt::Horizontal);
524 break;
525#endif
526 }
527 }
528
529 if (resultformat == result.format()) {
530 m_current_image = result;
531 } else {
532 m_current_image = result.convertToFormat(resultformat);
533 }
534
535 m_current_image.setColorSpace(colorspace);
536
537 m_estimated_dimensions = m_current_image.size();
538
539 m_must_jump_to_next_image = false;
540 return true;
541}
542
543bool QAVIFHandler::read(QImage *image)
544{
545 if (!ensureOpened()) {
546 return false;
547 }
548
549 if (m_must_jump_to_next_image) {
550 jumpToNextImage();
551 }
552
553 *image = m_current_image;
554 if (imageCount() >= 2) {
555 m_must_jump_to_next_image = true;
556 if (m_decoder->imageIndex >= m_decoder->imageCount - 1) {
557 // all frames in animation have been read
558 m_parseState = ParseAvifFinished;
559 }
560 } else {
561 // the static image has been read
562 m_parseState = ParseAvifFinished;
563 }
564 return true;
565}
566
567bool QAVIFHandler::write(const QImage &image)
568{
569 if (image.format() == QImage::Format_Invalid) {
570 qWarning("No image data to save!");
571 return false;
572 }
573
574 if ((image.width() > 0) && (image.height() > 0)) {
575 if ((image.width() > 65535) || (image.height() > 65535)) {
576 qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
577 return false;
578 }
579
580 if (image.width() > ((16384 * 16384) / image.height())) {
581 qWarning("Image (%dx%d) will not be saved because it has more than 256 megapixels!", image.width(), image.height());
582 return false;
583 }
584
585 if ((image.width() > 32768) || (image.height() > 32768)) {
586 qWarning("Image (%dx%d) has a dimension above 32768 pixels, saved AVIF may not work in other software!", image.width(), image.height());
587 }
588 } else {
589 qWarning("Image has zero dimension!");
590 return false;
591 }
592
593 const char *encoder_name = avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE);
594 if (!encoder_name) {
595 qWarning("Cannot save AVIF images because libavif was built without AV1 encoders!");
596 return false;
597 }
598
599 bool lossless = false;
600 if (m_quality >= 100) {
601 if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE)) {
602 lossless = true;
603 } else {
604 qWarning("You are using %s encoder. It is recommended to enable libAOM encoder in libavif to use lossless compression.", encoder_name);
605 }
606 }
607
608 if (m_quality > 100) {
609 m_quality = 100;
610 } else if (m_quality < 0) {
611 m_quality = KIMG_AVIF_DEFAULT_QUALITY;
612 }
613
614#if AVIF_VERSION < 1000000
615 int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
616 int minQuantizer = 0;
617 int maxQuantizerAlpha = 0;
618#endif
619 avifResult res;
620
621 bool save_grayscale; // true - monochrome, false - colors
622 int save_depth; // 8 or 10bit per channel
623 QImage::Format tmpformat; // format for temporary image
624
625 avifImage *avif = nullptr;
626
627 // grayscale detection
628 switch (image.format()) {
633 save_grayscale = true;
634 break;
636 save_grayscale = image.isGrayscale();
637 break;
638 default:
639 save_grayscale = false;
640 break;
641 }
642
643 // depth detection
644 switch (image.format()) {
653 save_depth = 10;
654 break;
655 default:
656 if (image.depth() > 32) {
657 save_depth = 10;
658 } else {
659 save_depth = 8;
660 }
661 break;
662 }
663
664#if AVIF_VERSION < 1000000
665 // deprecated quality settings
666 if (maxQuantizer > 20) {
667 minQuantizer = maxQuantizer - 20;
668 if (maxQuantizer > 40) { // we decrease quality of alpha channel here
669 maxQuantizerAlpha = maxQuantizer - 40;
670 }
671 }
672#endif
673
674 if (save_grayscale && !image.hasAlphaChannel()) { // we are going to save grayscale image without alpha channel
675 if (save_depth > 8) {
676 tmpformat = QImage::Format_Grayscale16;
677 } else {
678 tmpformat = QImage::Format_Grayscale8;
679 }
680 QImage tmpgrayimage = image.convertToFormat(tmpformat);
681
682 avif = avifImageCreate(tmpgrayimage.width(), tmpgrayimage.height(), save_depth, AVIF_PIXEL_FORMAT_YUV400);
683#if AVIF_VERSION >= 110000
684 res = avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
685 if (res != AVIF_RESULT_OK) {
686 qWarning("ERROR in avifImageAllocatePlanes: %s", avifResultToString(res));
687 return false;
688 }
689#else
690 avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
691#endif
692
693 if (tmpgrayimage.colorSpace().isValid()) {
694 avif->colorPrimaries = (avifColorPrimaries)1;
695 avif->matrixCoefficients = (avifMatrixCoefficients)1;
696
697 switch (tmpgrayimage.colorSpace().transferFunction()) {
698 case QColorSpace::TransferFunction::Linear:
699 /* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
700 avif->transferCharacteristics = (avifTransferCharacteristics)8;
701 break;
702 case QColorSpace::TransferFunction::SRgb:
703 /* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
704 avif->transferCharacteristics = (avifTransferCharacteristics)13;
705 break;
706#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
707 case QColorSpace::TransferFunction::St2084:
708 avif->transferCharacteristics = (avifTransferCharacteristics)16;
709 break;
710 case QColorSpace::TransferFunction::Hlg:
711 avif->transferCharacteristics = (avifTransferCharacteristics)18;
712 break;
713#endif
714 default:
715 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
716 break;
717 }
718 }
719
720 if (save_depth > 8) { // QImage::Format_Grayscale16
721 for (int y = 0; y < tmpgrayimage.height(); y++) {
722 const uint16_t *src16bit = reinterpret_cast<const uint16_t *>(tmpgrayimage.constScanLine(y));
723 uint16_t *dest16bit = reinterpret_cast<uint16_t *>(avif->yuvPlanes[0] + y * avif->yuvRowBytes[0]);
724 for (int x = 0; x < tmpgrayimage.width(); x++) {
725 int tmp_pixelval = (int)(((float)(*src16bit) / 65535.0f) * 1023.0f + 0.5f); // downgrade to 10 bits
726 *dest16bit = qBound(0, tmp_pixelval, 1023);
727 dest16bit++;
728 src16bit++;
729 }
730 }
731 } else { // QImage::Format_Grayscale8
732 for (int y = 0; y < tmpgrayimage.height(); y++) {
733 const uchar *src8bit = tmpgrayimage.constScanLine(y);
734 uint8_t *dest8bit = avif->yuvPlanes[0] + y * avif->yuvRowBytes[0];
735 for (int x = 0; x < tmpgrayimage.width(); x++) {
736 *dest8bit = *src8bit;
737 dest8bit++;
738 src8bit++;
739 }
740 }
741 }
742
743 } else { // we are going to save color image
744 if (save_depth > 8) {
745 if (image.hasAlphaChannel()) {
746 tmpformat = QImage::Format_RGBA64;
747 } else {
748 tmpformat = QImage::Format_RGBX64;
749 }
750 } else { // 8bit depth
751 if (image.hasAlphaChannel()) {
752 tmpformat = QImage::Format_RGBA8888;
753 } else {
754 tmpformat = QImage::Format_RGB888;
755 }
756 }
757
758#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
759 QImage tmpcolorimage;
760 auto cs = image.colorSpace();
761 if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && image.format() == QImage::Format_CMYK8888) {
762 tmpcolorimage = image.convertedToColorSpace(QColorSpace(QColorSpace::SRgb), tmpformat);
763 } else if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Gray) {
764 QColorSpace::TransferFunction trc_new = cs.transferFunction();
765 float gamma_new = cs.gamma();
766 if (trc_new == QColorSpace::TransferFunction::Custom) {
767 trc_new = QColorSpace::TransferFunction::SRgb;
768 }
769 tmpcolorimage = image.convertedToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, trc_new, gamma_new), tmpformat);
770 } else {
771 tmpcolorimage = image.convertToFormat(tmpformat);
772 }
773#else
774 QImage tmpcolorimage = image.convertToFormat(tmpformat);
775#endif
776
777 avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420;
778 if (m_quality >= KIMG_AVIF_QUALITY_HIGH) {
779 if (m_quality >= KIMG_AVIF_QUALITY_BEST) {
780 pixel_format = AVIF_PIXEL_FORMAT_YUV444; // best quality
781 } else {
782 pixel_format = AVIF_PIXEL_FORMAT_YUV422; // high quality
783 }
784 }
785
786 avifMatrixCoefficients matrix_to_save = (avifMatrixCoefficients)1; // default for Qt 5.12 and 5.13;
787
788 avifColorPrimaries primaries_to_save = (avifColorPrimaries)2;
789 avifTransferCharacteristics transfer_to_save = (avifTransferCharacteristics)2;
790 QByteArray iccprofile;
791
792 if (tmpcolorimage.colorSpace().isValid()) {
793 switch (tmpcolorimage.colorSpace().primaries()) {
794 case QColorSpace::Primaries::SRgb:
795 /* AVIF_COLOR_PRIMARIES_BT709 */
796 primaries_to_save = (avifColorPrimaries)1;
797 /* AVIF_MATRIX_COEFFICIENTS_BT709 */
798 matrix_to_save = (avifMatrixCoefficients)1;
799 break;
800 case QColorSpace::Primaries::DciP3D65:
801 /* AVIF_NCLX_COLOUR_PRIMARIES_P3, AVIF_NCLX_COLOUR_PRIMARIES_SMPTE432 */
802 primaries_to_save = (avifColorPrimaries)12;
803 /* AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL */
804 matrix_to_save = (avifMatrixCoefficients)12;
805 break;
806 default:
807 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
808 primaries_to_save = (avifColorPrimaries)2;
809 /* AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED */
810 matrix_to_save = (avifMatrixCoefficients)2;
811 break;
812 }
813
814 switch (tmpcolorimage.colorSpace().transferFunction()) {
815 case QColorSpace::TransferFunction::Linear:
816 /* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
817 transfer_to_save = (avifTransferCharacteristics)8;
818 break;
819 case QColorSpace::TransferFunction::Gamma:
820 if (qAbs(tmpcolorimage.colorSpace().gamma() - 2.2f) < 0.1f) {
821 /* AVIF_TRANSFER_CHARACTERISTICS_BT470M */
822 transfer_to_save = (avifTransferCharacteristics)4;
823 } else if (qAbs(tmpcolorimage.colorSpace().gamma() - 2.8f) < 0.1f) {
824 /* AVIF_TRANSFER_CHARACTERISTICS_BT470BG */
825 transfer_to_save = (avifTransferCharacteristics)5;
826 } else {
827 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
828 transfer_to_save = (avifTransferCharacteristics)2;
829 }
830 break;
831 case QColorSpace::TransferFunction::SRgb:
832 /* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
833 transfer_to_save = (avifTransferCharacteristics)13;
834 break;
835#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
836 case QColorSpace::TransferFunction::St2084:
837 transfer_to_save = (avifTransferCharacteristics)16;
838 break;
839 case QColorSpace::TransferFunction::Hlg:
840 transfer_to_save = (avifTransferCharacteristics)18;
841 break;
842#endif
843 default:
844 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
845 transfer_to_save = (avifTransferCharacteristics)2;
846 break;
847 }
848
849 // in case primaries or trc were not identified
850 if ((primaries_to_save == 2) || (transfer_to_save == 2)) {
851 if (lossless) {
852 iccprofile = tmpcolorimage.colorSpace().iccProfile();
853 } else {
854 // upgrade image to higher bit depth
855 if (save_depth == 8) {
856 save_depth = 10;
857 if (tmpcolorimage.hasAlphaChannel()) {
858 tmpcolorimage.convertTo(QImage::Format_RGBA64);
859 } else {
860 tmpcolorimage.convertTo(QImage::Format_RGBX64);
861 }
862 }
863
864 if ((primaries_to_save == 2) && (transfer_to_save != 2)) { // other primaries but known trc
865 primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
866 matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
867
868 switch (transfer_to_save) {
869 case 8: // AVIF_TRANSFER_CHARACTERISTICS_LINEAR
870 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::Linear));
871 break;
872 case 4: // AVIF_TRANSFER_CHARACTERISTICS_BT470M
873 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.2f));
874 break;
875 case 5: // AVIF_TRANSFER_CHARACTERISTICS_BT470BG
876 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.8f));
877 break;
878#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
879 case 16:
880 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::St2084));
881 break;
882 case 18:
883 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::Hlg));
884 break;
885#endif
886 default: // AVIF_TRANSFER_CHARACTERISTICS_SRGB + any other
887 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
888 transfer_to_save = (avifTransferCharacteristics)13;
889 break;
890 }
891 } else if ((primaries_to_save != 2) && (transfer_to_save == 2)) { // recognized primaries but other trc
892 transfer_to_save = (avifTransferCharacteristics)13;
893 tmpcolorimage.convertToColorSpace(tmpcolorimage.colorSpace().withTransferFunction(QColorSpace::TransferFunction::SRgb));
894 } else { // unrecognized profile
895 primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
896 transfer_to_save = (avifTransferCharacteristics)13;
897 matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
898 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
899 }
900 }
901 }
902 } else { // profile is unsupported by Qt
903 iccprofile = tmpcolorimage.colorSpace().iccProfile();
904 if (iccprofile.size() > 0) {
905 matrix_to_save = (avifMatrixCoefficients)6;
906 }
907 }
908
909 if (lossless && pixel_format == AVIF_PIXEL_FORMAT_YUV444) {
910 matrix_to_save = (avifMatrixCoefficients)0;
911 }
912 avif = avifImageCreate(tmpcolorimage.width(), tmpcolorimage.height(), save_depth, pixel_format);
913 avif->matrixCoefficients = matrix_to_save;
914
915 avif->colorPrimaries = primaries_to_save;
916 avif->transferCharacteristics = transfer_to_save;
917
918 if (iccprofile.size() > 0) {
919#if AVIF_VERSION >= 1000000
920 res = avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
921 if (res != AVIF_RESULT_OK) {
922 qWarning("ERROR in avifImageSetProfileICC: %s", avifResultToString(res));
923 return false;
924 }
925#else
926 avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
927#endif
928 }
929
930 avifRGBImage rgb;
931 avifRGBImageSetDefaults(&rgb, avif);
932 rgb.rowBytes = tmpcolorimage.bytesPerLine();
933 rgb.pixels = const_cast<uint8_t *>(tmpcolorimage.constBits());
934
935 if (save_depth > 8) { // 10bit depth
936 rgb.depth = 16;
937
938 if (!tmpcolorimage.hasAlphaChannel()) {
939 rgb.ignoreAlpha = AVIF_TRUE;
940 }
941
942 rgb.format = AVIF_RGB_FORMAT_RGBA;
943 } else { // 8bit depth
944 rgb.depth = 8;
945
946 if (tmpcolorimage.hasAlphaChannel()) {
947 rgb.format = AVIF_RGB_FORMAT_RGBA;
948 } else {
949 rgb.format = AVIF_RGB_FORMAT_RGB;
950 }
951 }
952
953 res = avifImageRGBToYUV(avif, &rgb);
954 if (res != AVIF_RESULT_OK) {
955 qWarning("ERROR in avifImageRGBToYUV: %s", avifResultToString(res));
956 return false;
957 }
958 }
959
960 avifRWData raw = AVIF_DATA_EMPTY;
961 avifEncoder *encoder = avifEncoderCreate();
962 encoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
963
964#if AVIF_VERSION < 1000000
965 encoder->minQuantizer = minQuantizer;
966 encoder->maxQuantizer = maxQuantizer;
967
968 if (image.hasAlphaChannel()) {
969 encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
970 encoder->maxQuantizerAlpha = maxQuantizerAlpha;
971 }
972#else
973 encoder->quality = m_quality;
974
975 if (image.hasAlphaChannel()) {
976 if (m_quality >= KIMG_AVIF_QUALITY_LOW) {
977 encoder->qualityAlpha = 100;
978 } else {
979 encoder->qualityAlpha = 100 - (KIMG_AVIF_QUALITY_LOW - m_quality) / 2;
980 }
981 }
982#endif
983
984 encoder->speed = 6;
985
986 res = avifEncoderWrite(encoder, avif, &raw);
987 avifEncoderDestroy(encoder);
988 avifImageDestroy(avif);
989
990 if (res == AVIF_RESULT_OK) {
991 qint64 status = device()->write(reinterpret_cast<const char *>(raw.data), raw.size);
992 avifRWDataFree(&raw);
993
994 if (status > 0) {
995 return true;
996 } else if (status == -1) {
997 qWarning("Write error: %s", qUtf8Printable(device()->errorString()));
998 return false;
999 }
1000 } else {
1001 qWarning("ERROR: Failed to encode: %s", avifResultToString(res));
1002 }
1003
1004 return false;
1005}
1006
1007QVariant QAVIFHandler::option(ImageOption option) const
1008{
1009 if (option == Quality) {
1010 return m_quality;
1011 }
1012
1013 if (!supportsOption(option) || !ensureParsed()) {
1014 return QVariant();
1015 }
1016
1017 switch (option) {
1018 case Size:
1019 return m_estimated_dimensions;
1020 case Animation:
1021 if (imageCount() >= 2) {
1022 return true;
1023 } else {
1024 return false;
1025 }
1026 default:
1027 return QVariant();
1028 }
1029}
1030
1031void QAVIFHandler::setOption(ImageOption option, const QVariant &value)
1032{
1033 switch (option) {
1034 case Quality:
1035 m_quality = value.toInt();
1036 if (m_quality > 100) {
1037 m_quality = 100;
1038 } else if (m_quality < 0) {
1039 m_quality = KIMG_AVIF_DEFAULT_QUALITY;
1040 }
1041 return;
1042 default:
1043 break;
1044 }
1045 QImageIOHandler::setOption(option, value);
1046}
1047
1048bool QAVIFHandler::supportsOption(ImageOption option) const
1049{
1050 return option == Quality || option == Size || option == Animation;
1051}
1052
1053int QAVIFHandler::imageCount() const
1054{
1055 if (!ensureParsed()) {
1056 return 0;
1057 }
1058
1059 if (m_decoder->imageCount >= 1) {
1060 return m_decoder->imageCount;
1061 }
1062 return 0;
1063}
1064
1065int QAVIFHandler::currentImageNumber() const
1066{
1067 if (m_parseState == ParseAvifNotParsed) {
1068 return -1;
1069 }
1070
1071 if (m_parseState == ParseAvifError || !m_decoder) {
1072 return 0;
1073 }
1074
1075 if (m_parseState == ParseAvifMetadata) {
1076 if (m_decoder->imageCount >= 2) {
1077 return -1;
1078 } else {
1079 return 0;
1080 }
1081 }
1082
1083 return m_decoder->imageIndex;
1084}
1085
1086bool QAVIFHandler::jumpToNextImage()
1087{
1088 if (!ensureParsed()) {
1089 return false;
1090 }
1091
1092 avifResult decodeResult;
1093
1094 if (m_decoder->imageIndex >= 0) {
1095 if (m_decoder->imageCount < 2) {
1096 m_parseState = ParseAvifSuccess;
1097 return true;
1098 }
1099
1100 if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
1101 decodeResult = avifDecoderReset(m_decoder);
1102 if (decodeResult != AVIF_RESULT_OK) {
1103 qWarning("ERROR in avifDecoderReset: %s", avifResultToString(decodeResult));
1104 m_parseState = ParseAvifError;
1105 return false;
1106 }
1107 }
1108 }
1109
1110 decodeResult = avifDecoderNextImage(m_decoder);
1111
1112 if (decodeResult != AVIF_RESULT_OK) {
1113 qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
1114 m_parseState = ParseAvifError;
1115 return false;
1116 }
1117
1118 if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
1119 qWarning("Decoded image sequence size (%dx%d) do not match first image size (%dx%d)!",
1120 m_decoder->image->width,
1121 m_decoder->image->height,
1122 m_container_width,
1123 m_container_height);
1124
1125 m_parseState = ParseAvifError;
1126 return false;
1127 }
1128
1129 if (decode_one_frame()) {
1130 m_parseState = ParseAvifSuccess;
1131 return true;
1132 } else {
1133 m_parseState = ParseAvifError;
1134 return false;
1135 }
1136}
1137
1138bool QAVIFHandler::jumpToImage(int imageNumber)
1139{
1140 if (!ensureParsed()) {
1141 return false;
1142 }
1143
1144 if (m_decoder->imageCount < 2) { // not an animation
1145 if (imageNumber == 0) {
1146 if (ensureOpened()) {
1147 m_parseState = ParseAvifSuccess;
1148 return true;
1149 }
1150 }
1151 return false;
1152 }
1153
1154 if (imageNumber < 0 || imageNumber >= m_decoder->imageCount) { // wrong index
1155 return false;
1156 }
1157
1158 if (imageNumber == m_decoder->imageIndex) { // we are here already
1159 m_must_jump_to_next_image = false;
1160 m_parseState = ParseAvifSuccess;
1161 return true;
1162 }
1163
1164 avifResult decodeResult = avifDecoderNthImage(m_decoder, imageNumber);
1165
1166 if (decodeResult != AVIF_RESULT_OK) {
1167 qWarning("ERROR: Failed to decode %d th Image in sequence: %s", imageNumber, avifResultToString(decodeResult));
1168 m_parseState = ParseAvifError;
1169 return false;
1170 }
1171
1172 if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
1173 qWarning("Decoded image sequence size (%dx%d) do not match declared container size (%dx%d)!",
1174 m_decoder->image->width,
1175 m_decoder->image->height,
1176 m_container_width,
1177 m_container_height);
1178
1179 m_parseState = ParseAvifError;
1180 return false;
1181 }
1182
1183 if (decode_one_frame()) {
1184 m_parseState = ParseAvifSuccess;
1185 return true;
1186 } else {
1187 m_parseState = ParseAvifError;
1188 return false;
1189 }
1190}
1191
1192int QAVIFHandler::nextImageDelay() const
1193{
1194 if (!ensureOpened()) {
1195 return 0;
1196 }
1197
1198 if (m_decoder->imageCount < 2) {
1199 return 0;
1200 }
1201
1202 int delay_ms = 1000.0 * m_decoder->imageTiming.duration;
1203 if (delay_ms < 1) {
1204 delay_ms = 1;
1205 }
1206 return delay_ms;
1207}
1208
1209int QAVIFHandler::loopCount() const
1210{
1211 if (!ensureParsed()) {
1212 return 0;
1213 }
1214
1215 if (m_decoder->imageCount < 2) {
1216 return 0;
1217 }
1218
1219#if AVIF_VERSION >= 1000000
1220 if (m_decoder->repetitionCount >= 0) {
1221 return m_decoder->repetitionCount;
1222 }
1223#endif
1224 // Endless loop to work around https://github.com/AOMediaCodec/libavif/issues/347
1225 return -1;
1226}
1227
1228QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY)
1229{
1230 chrX = qBound(qreal(0.0), chrX, qreal(1.0));
1231 chrY = qBound(qreal(DBL_MIN), chrY, qreal(1.0));
1232
1233 if ((chrX + chrY) > qreal(1.0)) {
1234 chrX = qreal(1.0) - chrY;
1235 }
1236
1237 return QPointF(chrX, chrY);
1238}
1239
1240QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
1241{
1242 static const bool isAvifDecoderAvailable(avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_DECODE) != nullptr);
1243 static const bool isAvifEncoderAvailable(avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE) != nullptr);
1244
1245 if (format == "avif") {
1246 Capabilities format_cap;
1247 if (isAvifDecoderAvailable) {
1248 format_cap |= CanRead;
1249 }
1250 if (isAvifEncoderAvailable) {
1251 format_cap |= CanWrite;
1252 }
1253 return format_cap;
1254 }
1255
1256 if (format == "avifs") {
1257 Capabilities format_cap;
1258 if (isAvifDecoderAvailable) {
1259 format_cap |= CanRead;
1260 }
1261 return format_cap;
1262 }
1263
1264 if (!format.isEmpty()) {
1265 return {};
1266 }
1267 if (!device->isOpen()) {
1268 return {};
1269 }
1270
1271 Capabilities cap;
1272 if (device->isReadable() && QAVIFHandler::canRead(device) && isAvifDecoderAvailable) {
1273 cap |= CanRead;
1274 }
1275 if (device->isWritable() && isAvifEncoderAvailable) {
1276 cap |= CanWrite;
1277 }
1278 return cap;
1279}
1280
1281QImageIOHandler *QAVIFPlugin::create(QIODevice *device, const QByteArray &format) const
1282{
1283 QImageIOHandler *handler = new QAVIFHandler;
1284 handler->setDevice(device);
1285 handler->setFormat(format);
1286 return handler;
1287}
1288
1289#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)
typedef Capabilities
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
Vertical
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 24 2025 11:53:42 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.