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
253 if (m_decoder->image->alphaPlane) {
254 loadalpha = true;
255 } else {
256 loadalpha = false;
257 }
258
259 QImage::Format resultformat;
260
261 if (m_decoder->image->depth > 8) {
262 if (loadalpha) {
263 resultformat = QImage::Format_RGBA64;
264 } else {
265 resultformat = QImage::Format_RGBX64;
266 }
267 } else {
268 if (loadalpha) {
269 resultformat = QImage::Format_ARGB32;
270 } else {
271 resultformat = QImage::Format_RGB32;
272 }
273 }
274
275 QImage result = imageAlloc(m_decoder->image->width, m_decoder->image->height, resultformat);
276 if (result.isNull()) {
277 qWarning("Memory cannot be allocated");
278 return false;
279 }
280
281 QColorSpace colorspace;
282 if (m_decoder->image->icc.data && (m_decoder->image->icc.size > 0)) {
283 const QByteArray icc_data(reinterpret_cast<const char *>(m_decoder->image->icc.data), m_decoder->image->icc.size);
284 colorspace = QColorSpace::fromIccProfile(icc_data);
285 if (!colorspace.isValid()) {
286 qWarning("AVIF image has Qt-unsupported or invalid ICC profile!");
287 }
288 } else {
289 float prim[8] = {0.64f, 0.33f, 0.3f, 0.6f, 0.15f, 0.06f, 0.3127f, 0.329f};
290 // outPrimaries: rX, rY, gX, gY, bX, bY, wX, wY
291 avifColorPrimariesGetValues(m_decoder->image->colorPrimaries, prim);
292
293 const QPointF redPoint(QAVIFHandler::CompatibleChromacity(prim[0], prim[1]));
294 const QPointF greenPoint(QAVIFHandler::CompatibleChromacity(prim[2], prim[3]));
295 const QPointF bluePoint(QAVIFHandler::CompatibleChromacity(prim[4], prim[5]));
296 const QPointF whitePoint(QAVIFHandler::CompatibleChromacity(prim[6], prim[7]));
297
298 QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom;
299 float q_trc_gamma = 0.0f;
300
301 switch (m_decoder->image->transferCharacteristics) {
302 /* AVIF_TRANSFER_CHARACTERISTICS_BT470M */
303 case 4:
304 q_trc = QColorSpace::TransferFunction::Gamma;
305 q_trc_gamma = 2.2f;
306 break;
307 /* AVIF_TRANSFER_CHARACTERISTICS_BT470BG */
308 case 5:
309 q_trc = QColorSpace::TransferFunction::Gamma;
310 q_trc_gamma = 2.8f;
311 break;
312 /* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
313 case 8:
314 q_trc = QColorSpace::TransferFunction::Linear;
315 break;
316 /* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
317 case 0:
318 case 2: /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
319 case 13:
320 q_trc = QColorSpace::TransferFunction::SRgb;
321 break;
322 default:
323 qWarning("CICP colorPrimaries: %d, transferCharacteristics: %d\nThe colorspace is unsupported by this plug-in yet.",
324 m_decoder->image->colorPrimaries,
325 m_decoder->image->transferCharacteristics);
326 q_trc = QColorSpace::TransferFunction::SRgb;
327 break;
328 }
329
330 if (q_trc != QColorSpace::TransferFunction::Custom) { // we create new colorspace using Qt
331 switch (m_decoder->image->colorPrimaries) {
332 /* AVIF_COLOR_PRIMARIES_BT709 */
333 case 0:
334 case 1:
335 case 2: /* AVIF_COLOR_PRIMARIES_UNSPECIFIED */
336 colorspace = QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma);
337 break;
338 /* AVIF_COLOR_PRIMARIES_SMPTE432 */
339 case 12:
340 colorspace = QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma);
341 break;
342 default:
343 colorspace = QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma);
344 break;
345 }
346 }
347
348 if (!colorspace.isValid()) {
349 qWarning("AVIF plugin created invalid QColorSpace from NCLX/CICP!");
350 }
351 }
352
353 result.setColorSpace(colorspace);
354
355 avifRGBImage rgb;
356 avifRGBImageSetDefaults(&rgb, m_decoder->image);
357
358#if AVIF_VERSION >= 1000000
359 rgb.maxThreads = m_decoder->maxThreads;
360#endif
361
362 if (m_decoder->image->depth > 8) {
363 rgb.depth = 16;
364 rgb.format = AVIF_RGB_FORMAT_RGBA;
365
366 if (!loadalpha && (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
367 resultformat = QImage::Format_Grayscale16;
368 }
369 } else {
370 rgb.depth = 8;
371#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
372 rgb.format = AVIF_RGB_FORMAT_BGRA;
373#else
374 rgb.format = AVIF_RGB_FORMAT_ARGB;
375#endif
376
377#if AVIF_VERSION >= 80400
378 if (m_decoder->imageCount > 1) {
379 /* accelerate animated AVIF */
380 rgb.chromaUpsampling = AVIF_CHROMA_UPSAMPLING_FASTEST;
381 }
382#endif
383
384 if (!loadalpha && (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
385 resultformat = QImage::Format_Grayscale8;
386 }
387 }
388
389 rgb.rowBytes = result.bytesPerLine();
390 rgb.pixels = result.bits();
391
392 avifResult res = avifImageYUVToRGB(m_decoder->image, &rgb);
393 if (res != AVIF_RESULT_OK) {
394 qWarning("ERROR in avifImageYUVToRGB: %s", avifResultToString(res));
395 return false;
396 }
397
398 if (m_decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) {
399 if ((m_decoder->image->clap.widthD > 0) && (m_decoder->image->clap.heightD > 0) && (m_decoder->image->clap.horizOffD > 0)
400 && (m_decoder->image->clap.vertOffD > 0)) {
401 int new_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
402 if (new_width > result.width()) {
403 new_width = result.width();
404 }
405
406 int new_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5);
407 if (new_height > result.height()) {
408 new_height = result.height();
409 }
410
411 if (new_width > 0 && new_height > 0) {
412 int offx =
413 ((double)((int32_t)m_decoder->image->clap.horizOffN)) / (m_decoder->image->clap.horizOffD) + (result.width() - new_width) / 2.0 + 0.5;
414 if (offx < 0) {
415 offx = 0;
416 } else if (offx > (result.width() - new_width)) {
417 offx = result.width() - new_width;
418 }
419
420 int offy =
421 ((double)((int32_t)m_decoder->image->clap.vertOffN)) / (m_decoder->image->clap.vertOffD) + (result.height() - new_height) / 2.0 + 0.5;
422 if (offy < 0) {
423 offy = 0;
424 } else if (offy > (result.height() - new_height)) {
425 offy = result.height() - new_height;
426 }
427
428 result = result.copy(offx, offy, new_width, new_height);
429 }
430 }
431
432 else { // Zero values, we need to avoid 0 divide.
433 qWarning("ERROR: Wrong values in avifCleanApertureBox");
434 }
435 }
436
437 if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IROT) {
439 switch (m_decoder->image->irot.angle) {
440 case 1:
441 transform.rotate(-90);
442 result = result.transformed(transform);
443 break;
444 case 2:
445 transform.rotate(180);
446 result = result.transformed(transform);
447 break;
448 case 3:
449 transform.rotate(90);
450 result = result.transformed(transform);
451 break;
452 }
453 }
454
455 if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IMIR) {
456#if AVIF_VERSION > 90100 && AVIF_VERSION < 1000000
457 switch (m_decoder->image->imir.mode) {
458#else
459 switch (m_decoder->image->imir.axis) {
460#endif
461 case 0: // top-to-bottom
462 result = result.mirrored(false, true);
463 break;
464 case 1: // left-to-right
465 result = result.mirrored(true, false);
466 break;
467 }
468 }
469
470 if (resultformat == result.format()) {
471 m_current_image = result;
472 } else {
473 m_current_image = result.convertToFormat(resultformat);
474 }
475
476 m_estimated_dimensions = m_current_image.size();
477
478 m_must_jump_to_next_image = false;
479 return true;
480}
481
482bool QAVIFHandler::read(QImage *image)
483{
484 if (!ensureOpened()) {
485 return false;
486 }
487
488 if (m_must_jump_to_next_image) {
489 jumpToNextImage();
490 }
491
492 *image = m_current_image;
493 if (imageCount() >= 2) {
494 m_must_jump_to_next_image = true;
495 if (m_decoder->imageIndex >= m_decoder->imageCount - 1) {
496 // all frames in animation have been read
497 m_parseState = ParseAvifFinished;
498 }
499 } else {
500 // the static image has been read
501 m_parseState = ParseAvifFinished;
502 }
503 return true;
504}
505
506bool QAVIFHandler::write(const QImage &image)
507{
508 if (image.format() == QImage::Format_Invalid) {
509 qWarning("No image data to save!");
510 return false;
511 }
512
513 if ((image.width() > 0) && (image.height() > 0)) {
514 if ((image.width() > 65535) || (image.height() > 65535)) {
515 qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
516 return false;
517 }
518
519 if (image.width() > ((16384 * 16384) / image.height())) {
520 qWarning("Image (%dx%d) will not be saved because it has more than 256 megapixels!", image.width(), image.height());
521 return false;
522 }
523
524 if ((image.width() > 32768) || (image.height() > 32768)) {
525 qWarning("Image (%dx%d) has a dimension above 32768 pixels, saved AVIF may not work in other software!", image.width(), image.height());
526 }
527 } else {
528 qWarning("Image has zero dimension!");
529 return false;
530 }
531
532 const char *encoder_name = avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE);
533 if (!encoder_name) {
534 qWarning("Cannot save AVIF images because libavif was built without AV1 encoders!");
535 return false;
536 }
537
538 bool lossless = false;
539 if (m_quality >= 100) {
540 if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE)) {
541 lossless = true;
542 } else {
543 qWarning("You are using %s encoder. It is recommended to enable libAOM encoder in libavif to use lossless compression.", encoder_name);
544 }
545 }
546
547 if (m_quality > 100) {
548 m_quality = 100;
549 } else if (m_quality < 0) {
550 m_quality = KIMG_AVIF_DEFAULT_QUALITY;
551 }
552
553#if AVIF_VERSION < 1000000
554 int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
555 int minQuantizer = 0;
556 int maxQuantizerAlpha = 0;
557#endif
558 avifResult res;
559
560 bool save_grayscale; // true - monochrome, false - colors
561 int save_depth; // 8 or 10bit per channel
562 QImage::Format tmpformat; // format for temporary image
563
564 avifImage *avif = nullptr;
565
566 // grayscale detection
567 switch (image.format()) {
572 save_grayscale = true;
573 break;
575 save_grayscale = image.isGrayscale();
576 break;
577 default:
578 save_grayscale = false;
579 break;
580 }
581
582 // depth detection
583 switch (image.format()) {
592 save_depth = 10;
593 break;
594 default:
595 if (image.depth() > 32) {
596 save_depth = 10;
597 } else {
598 save_depth = 8;
599 }
600 break;
601 }
602
603#if AVIF_VERSION < 1000000
604 // deprecated quality settings
605 if (maxQuantizer > 20) {
606 minQuantizer = maxQuantizer - 20;
607 if (maxQuantizer > 40) { // we decrease quality of alpha channel here
608 maxQuantizerAlpha = maxQuantizer - 40;
609 }
610 }
611#endif
612
613 if (save_grayscale && !image.hasAlphaChannel()) { // we are going to save grayscale image without alpha channel
614 if (save_depth > 8) {
615 tmpformat = QImage::Format_Grayscale16;
616 } else {
617 tmpformat = QImage::Format_Grayscale8;
618 }
619 QImage tmpgrayimage = image.convertToFormat(tmpformat);
620
621 avif = avifImageCreate(tmpgrayimage.width(), tmpgrayimage.height(), save_depth, AVIF_PIXEL_FORMAT_YUV400);
622#if AVIF_VERSION >= 110000
623 res = avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
624 if (res != AVIF_RESULT_OK) {
625 qWarning("ERROR in avifImageAllocatePlanes: %s", avifResultToString(res));
626 return false;
627 }
628#else
629 avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
630#endif
631
632 if (tmpgrayimage.colorSpace().isValid()) {
633 avif->colorPrimaries = (avifColorPrimaries)1;
634 avif->matrixCoefficients = (avifMatrixCoefficients)1;
635
636 switch (tmpgrayimage.colorSpace().transferFunction()) {
637 case QColorSpace::TransferFunction::Linear:
638 /* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
639 avif->transferCharacteristics = (avifTransferCharacteristics)8;
640 break;
641 case QColorSpace::TransferFunction::SRgb:
642 /* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
643 avif->transferCharacteristics = (avifTransferCharacteristics)13;
644 break;
645 default:
646 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
647 break;
648 }
649 }
650
651 if (save_depth > 8) { // QImage::Format_Grayscale16
652 for (int y = 0; y < tmpgrayimage.height(); y++) {
653 const uint16_t *src16bit = reinterpret_cast<const uint16_t *>(tmpgrayimage.constScanLine(y));
654 uint16_t *dest16bit = reinterpret_cast<uint16_t *>(avif->yuvPlanes[0] + y * avif->yuvRowBytes[0]);
655 for (int x = 0; x < tmpgrayimage.width(); x++) {
656 int tmp_pixelval = (int)(((float)(*src16bit) / 65535.0f) * 1023.0f + 0.5f); // downgrade to 10 bits
657 *dest16bit = qBound(0, tmp_pixelval, 1023);
658 dest16bit++;
659 src16bit++;
660 }
661 }
662 } else { // QImage::Format_Grayscale8
663 for (int y = 0; y < tmpgrayimage.height(); y++) {
664 const uchar *src8bit = tmpgrayimage.constScanLine(y);
665 uint8_t *dest8bit = avif->yuvPlanes[0] + y * avif->yuvRowBytes[0];
666 for (int x = 0; x < tmpgrayimage.width(); x++) {
667 *dest8bit = *src8bit;
668 dest8bit++;
669 src8bit++;
670 }
671 }
672 }
673
674 } else { // we are going to save color image
675 if (save_depth > 8) {
676 if (image.hasAlphaChannel()) {
677 tmpformat = QImage::Format_RGBA64;
678 } else {
679 tmpformat = QImage::Format_RGBX64;
680 }
681 } else { // 8bit depth
682 if (image.hasAlphaChannel()) {
683 tmpformat = QImage::Format_RGBA8888;
684 } else {
685 tmpformat = QImage::Format_RGB888;
686 }
687 }
688
689#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
690 QImage tmpcolorimage;
691 auto cs = image.colorSpace();
692 if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && image.format() == QImage::Format_CMYK8888) {
693 tmpcolorimage = image.convertedToColorSpace(QColorSpace(QColorSpace::SRgb), tmpformat);
694 }
695 else {
696 tmpcolorimage = image.convertToFormat(tmpformat);
697 }
698#else
699 QImage tmpcolorimage = image.convertToFormat(tmpformat);
700#endif
701
702 avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420;
703 if (m_quality >= KIMG_AVIF_QUALITY_HIGH) {
704 if (m_quality >= KIMG_AVIF_QUALITY_BEST) {
705 pixel_format = AVIF_PIXEL_FORMAT_YUV444; // best quality
706 } else {
707 pixel_format = AVIF_PIXEL_FORMAT_YUV422; // high quality
708 }
709 }
710
711 avifMatrixCoefficients matrix_to_save = (avifMatrixCoefficients)1; // default for Qt 5.12 and 5.13;
712
713 avifColorPrimaries primaries_to_save = (avifColorPrimaries)2;
714 avifTransferCharacteristics transfer_to_save = (avifTransferCharacteristics)2;
715 QByteArray iccprofile;
716
717 if (tmpcolorimage.colorSpace().isValid()) {
718 switch (tmpcolorimage.colorSpace().primaries()) {
719 case QColorSpace::Primaries::SRgb:
720 /* AVIF_COLOR_PRIMARIES_BT709 */
721 primaries_to_save = (avifColorPrimaries)1;
722 /* AVIF_MATRIX_COEFFICIENTS_BT709 */
723 matrix_to_save = (avifMatrixCoefficients)1;
724 break;
725 case QColorSpace::Primaries::DciP3D65:
726 /* AVIF_NCLX_COLOUR_PRIMARIES_P3, AVIF_NCLX_COLOUR_PRIMARIES_SMPTE432 */
727 primaries_to_save = (avifColorPrimaries)12;
728 /* AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL */
729 matrix_to_save = (avifMatrixCoefficients)12;
730 break;
731 default:
732 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
733 primaries_to_save = (avifColorPrimaries)2;
734 /* AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED */
735 matrix_to_save = (avifMatrixCoefficients)2;
736 break;
737 }
738
739 switch (tmpcolorimage.colorSpace().transferFunction()) {
740 case QColorSpace::TransferFunction::Linear:
741 /* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
742 transfer_to_save = (avifTransferCharacteristics)8;
743 break;
744 case QColorSpace::TransferFunction::Gamma:
745 if (qAbs(tmpcolorimage.colorSpace().gamma() - 2.2f) < 0.1f) {
746 /* AVIF_TRANSFER_CHARACTERISTICS_BT470M */
747 transfer_to_save = (avifTransferCharacteristics)4;
748 } else if (qAbs(tmpcolorimage.colorSpace().gamma() - 2.8f) < 0.1f) {
749 /* AVIF_TRANSFER_CHARACTERISTICS_BT470BG */
750 transfer_to_save = (avifTransferCharacteristics)5;
751 } else {
752 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
753 transfer_to_save = (avifTransferCharacteristics)2;
754 }
755 break;
756 case QColorSpace::TransferFunction::SRgb:
757 /* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
758 transfer_to_save = (avifTransferCharacteristics)13;
759 break;
760 default:
761 /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
762 transfer_to_save = (avifTransferCharacteristics)2;
763 break;
764 }
765
766 // in case primaries or trc were not identified
767 if ((primaries_to_save == 2) || (transfer_to_save == 2)) {
768 if (lossless) {
769 iccprofile = tmpcolorimage.colorSpace().iccProfile();
770 } else {
771 // upgrade image to higher bit depth
772 if (save_depth == 8) {
773 save_depth = 10;
774 if (tmpcolorimage.hasAlphaChannel()) {
775 tmpcolorimage.convertTo(QImage::Format_RGBA64);
776 } else {
777 tmpcolorimage.convertTo(QImage::Format_RGBX64);
778 }
779 }
780
781 if ((primaries_to_save == 2) && (transfer_to_save != 2)) { // other primaries but known trc
782 primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
783 matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
784
785 switch (transfer_to_save) {
786 case 8: // AVIF_TRANSFER_CHARACTERISTICS_LINEAR
787 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::Linear));
788 break;
789 case 4: // AVIF_TRANSFER_CHARACTERISTICS_BT470M
790 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.2f));
791 break;
792 case 5: // AVIF_TRANSFER_CHARACTERISTICS_BT470BG
793 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.8f));
794 break;
795 default: // AVIF_TRANSFER_CHARACTERISTICS_SRGB + any other
796 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
797 transfer_to_save = (avifTransferCharacteristics)13;
798 break;
799 }
800 } else if ((primaries_to_save != 2) && (transfer_to_save == 2)) { // recognized primaries but other trc
801 transfer_to_save = (avifTransferCharacteristics)13;
802 tmpcolorimage.convertToColorSpace(tmpcolorimage.colorSpace().withTransferFunction(QColorSpace::TransferFunction::SRgb));
803 } else { // unrecognized profile
804 primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
805 transfer_to_save = (avifTransferCharacteristics)13;
806 matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
807 tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
808 }
809 }
810 }
811 } else { // profile is unsupported by Qt
812 iccprofile = tmpcolorimage.colorSpace().iccProfile();
813 if (iccprofile.size() > 0) {
814 matrix_to_save = (avifMatrixCoefficients)6;
815 }
816 }
817
818 if (lossless && pixel_format == AVIF_PIXEL_FORMAT_YUV444) {
819 matrix_to_save = (avifMatrixCoefficients)0;
820 }
821 avif = avifImageCreate(tmpcolorimage.width(), tmpcolorimage.height(), save_depth, pixel_format);
822 avif->matrixCoefficients = matrix_to_save;
823
824 avif->colorPrimaries = primaries_to_save;
825 avif->transferCharacteristics = transfer_to_save;
826
827 if (iccprofile.size() > 0) {
828#if AVIF_VERSION >= 1000000
829 res = avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
830 if (res != AVIF_RESULT_OK) {
831 qWarning("ERROR in avifImageSetProfileICC: %s", avifResultToString(res));
832 return false;
833 }
834#else
835 avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
836#endif
837 }
838
839 avifRGBImage rgb;
840 avifRGBImageSetDefaults(&rgb, avif);
841 rgb.rowBytes = tmpcolorimage.bytesPerLine();
842 rgb.pixels = const_cast<uint8_t *>(tmpcolorimage.constBits());
843
844 if (save_depth > 8) { // 10bit depth
845 rgb.depth = 16;
846
847 if (!tmpcolorimage.hasAlphaChannel()) {
848 rgb.ignoreAlpha = AVIF_TRUE;
849 }
850
851 rgb.format = AVIF_RGB_FORMAT_RGBA;
852 } else { // 8bit depth
853 rgb.depth = 8;
854
855 if (tmpcolorimage.hasAlphaChannel()) {
856 rgb.format = AVIF_RGB_FORMAT_RGBA;
857 } else {
858 rgb.format = AVIF_RGB_FORMAT_RGB;
859 }
860 }
861
862 res = avifImageRGBToYUV(avif, &rgb);
863 if (res != AVIF_RESULT_OK) {
864 qWarning("ERROR in avifImageRGBToYUV: %s", avifResultToString(res));
865 return false;
866 }
867 }
868
869 avifRWData raw = AVIF_DATA_EMPTY;
870 avifEncoder *encoder = avifEncoderCreate();
871 encoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
872
873#if AVIF_VERSION < 1000000
874 encoder->minQuantizer = minQuantizer;
875 encoder->maxQuantizer = maxQuantizer;
876
877 if (image.hasAlphaChannel()) {
878 encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
879 encoder->maxQuantizerAlpha = maxQuantizerAlpha;
880 }
881#else
882 encoder->quality = m_quality;
883
884 if (image.hasAlphaChannel()) {
885 if (m_quality >= KIMG_AVIF_QUALITY_LOW) {
886 encoder->qualityAlpha = 100;
887 } else {
888 encoder->qualityAlpha = 100 - (KIMG_AVIF_QUALITY_LOW - m_quality) / 2;
889 }
890 }
891#endif
892
893 encoder->speed = 6;
894
895 res = avifEncoderWrite(encoder, avif, &raw);
896 avifEncoderDestroy(encoder);
897 avifImageDestroy(avif);
898
899 if (res == AVIF_RESULT_OK) {
900 qint64 status = device()->write(reinterpret_cast<const char *>(raw.data), raw.size);
901 avifRWDataFree(&raw);
902
903 if (status > 0) {
904 return true;
905 } else if (status == -1) {
906 qWarning("Write error: %s", qUtf8Printable(device()->errorString()));
907 return false;
908 }
909 } else {
910 qWarning("ERROR: Failed to encode: %s", avifResultToString(res));
911 }
912
913 return false;
914}
915
916QVariant QAVIFHandler::option(ImageOption option) const
917{
918 if (option == Quality) {
919 return m_quality;
920 }
921
922 if (!supportsOption(option) || !ensureParsed()) {
923 return QVariant();
924 }
925
926 switch (option) {
927 case Size:
928 return m_estimated_dimensions;
929 case Animation:
930 if (imageCount() >= 2) {
931 return true;
932 } else {
933 return false;
934 }
935 default:
936 return QVariant();
937 }
938}
939
940void QAVIFHandler::setOption(ImageOption option, const QVariant &value)
941{
942 switch (option) {
943 case Quality:
944 m_quality = value.toInt();
945 if (m_quality > 100) {
946 m_quality = 100;
947 } else if (m_quality < 0) {
948 m_quality = KIMG_AVIF_DEFAULT_QUALITY;
949 }
950 return;
951 default:
952 break;
953 }
954 QImageIOHandler::setOption(option, value);
955}
956
957bool QAVIFHandler::supportsOption(ImageOption option) const
958{
959 return option == Quality || option == Size || option == Animation;
960}
961
962int QAVIFHandler::imageCount() const
963{
964 if (!ensureParsed()) {
965 return 0;
966 }
967
968 if (m_decoder->imageCount >= 1) {
969 return m_decoder->imageCount;
970 }
971 return 0;
972}
973
974int QAVIFHandler::currentImageNumber() const
975{
976 if (m_parseState == ParseAvifNotParsed) {
977 return -1;
978 }
979
980 if (m_parseState == ParseAvifError || !m_decoder) {
981 return 0;
982 }
983
984 if (m_parseState == ParseAvifMetadata) {
985 if (m_decoder->imageCount >= 2) {
986 return -1;
987 } else {
988 return 0;
989 }
990 }
991
992 return m_decoder->imageIndex;
993}
994
995bool QAVIFHandler::jumpToNextImage()
996{
997 if (!ensureParsed()) {
998 return false;
999 }
1000
1001 avifResult decodeResult;
1002
1003 if (m_decoder->imageIndex >= 0) {
1004 if (m_decoder->imageCount < 2) {
1005 m_parseState = ParseAvifSuccess;
1006 return true;
1007 }
1008
1009 if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
1010 decodeResult = avifDecoderReset(m_decoder);
1011 if (decodeResult != AVIF_RESULT_OK) {
1012 qWarning("ERROR in avifDecoderReset: %s", avifResultToString(decodeResult));
1013 m_parseState = ParseAvifError;
1014 return false;
1015 }
1016 }
1017 }
1018
1019 decodeResult = avifDecoderNextImage(m_decoder);
1020
1021 if (decodeResult != AVIF_RESULT_OK) {
1022 qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
1023 m_parseState = ParseAvifError;
1024 return false;
1025 }
1026
1027 if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
1028 qWarning("Decoded image sequence size (%dx%d) do not match first image size (%dx%d)!",
1029 m_decoder->image->width,
1030 m_decoder->image->height,
1031 m_container_width,
1032 m_container_height);
1033
1034 m_parseState = ParseAvifError;
1035 return false;
1036 }
1037
1038 if (decode_one_frame()) {
1039 m_parseState = ParseAvifSuccess;
1040 return true;
1041 } else {
1042 m_parseState = ParseAvifError;
1043 return false;
1044 }
1045}
1046
1047bool QAVIFHandler::jumpToImage(int imageNumber)
1048{
1049 if (!ensureParsed()) {
1050 return false;
1051 }
1052
1053 if (m_decoder->imageCount < 2) { // not an animation
1054 if (imageNumber == 0) {
1055 if (ensureOpened()) {
1056 m_parseState = ParseAvifSuccess;
1057 return true;
1058 }
1059 }
1060 return false;
1061 }
1062
1063 if (imageNumber < 0 || imageNumber >= m_decoder->imageCount) { // wrong index
1064 return false;
1065 }
1066
1067 if (imageNumber == m_decoder->imageIndex) { // we are here already
1068 m_must_jump_to_next_image = false;
1069 m_parseState = ParseAvifSuccess;
1070 return true;
1071 }
1072
1073 avifResult decodeResult = avifDecoderNthImage(m_decoder, imageNumber);
1074
1075 if (decodeResult != AVIF_RESULT_OK) {
1076 qWarning("ERROR: Failed to decode %d th Image in sequence: %s", imageNumber, avifResultToString(decodeResult));
1077 m_parseState = ParseAvifError;
1078 return false;
1079 }
1080
1081 if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
1082 qWarning("Decoded image sequence size (%dx%d) do not match declared container size (%dx%d)!",
1083 m_decoder->image->width,
1084 m_decoder->image->height,
1085 m_container_width,
1086 m_container_height);
1087
1088 m_parseState = ParseAvifError;
1089 return false;
1090 }
1091
1092 if (decode_one_frame()) {
1093 m_parseState = ParseAvifSuccess;
1094 return true;
1095 } else {
1096 m_parseState = ParseAvifError;
1097 return false;
1098 }
1099}
1100
1101int QAVIFHandler::nextImageDelay() const
1102{
1103 if (!ensureOpened()) {
1104 return 0;
1105 }
1106
1107 if (m_decoder->imageCount < 2) {
1108 return 0;
1109 }
1110
1111 int delay_ms = 1000.0 * m_decoder->imageTiming.duration;
1112 if (delay_ms < 1) {
1113 delay_ms = 1;
1114 }
1115 return delay_ms;
1116}
1117
1118int QAVIFHandler::loopCount() const
1119{
1120 if (!ensureParsed()) {
1121 return 0;
1122 }
1123
1124 if (m_decoder->imageCount < 2) {
1125 return 0;
1126 }
1127
1128#if AVIF_VERSION >= 1000000
1129 if (m_decoder->repetitionCount >= 0) {
1130 return m_decoder->repetitionCount;
1131 }
1132#endif
1133 // Endless loop to work around https://github.com/AOMediaCodec/libavif/issues/347
1134 return -1;
1135}
1136
1137QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY)
1138{
1139 chrX = qBound(qreal(0.0), chrX, qreal(1.0));
1140 chrY = qBound(qreal(DBL_MIN), chrY, qreal(1.0));
1141
1142 if ((chrX + chrY) > qreal(1.0)) {
1143 chrX = qreal(1.0) - chrY;
1144 }
1145
1146 return QPointF(chrX, chrY);
1147}
1148
1149QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
1150{
1151 static const bool isAvifDecoderAvailable(avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_DECODE) != nullptr);
1152 static const bool isAvifEncoderAvailable(avifCodecName(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_ENCODE) != nullptr);
1153
1154 if (format == "avif") {
1155 Capabilities format_cap;
1156 if (isAvifDecoderAvailable) {
1157 format_cap |= CanRead;
1158 }
1159 if (isAvifEncoderAvailable) {
1160 format_cap |= CanWrite;
1161 }
1162 return format_cap;
1163 }
1164
1165 if (format == "avifs") {
1166 Capabilities format_cap;
1167 if (isAvifDecoderAvailable) {
1168 format_cap |= CanRead;
1169 }
1170 return format_cap;
1171 }
1172
1173 if (!format.isEmpty()) {
1174 return {};
1175 }
1176 if (!device->isOpen()) {
1177 return {};
1178 }
1179
1180 Capabilities cap;
1181 if (device->isReadable() && QAVIFHandler::canRead(device) && isAvifDecoderAvailable) {
1182 cap |= CanRead;
1183 }
1184 if (device->isWritable() && isAvifEncoderAvailable) {
1185 cap |= CanWrite;
1186 }
1187 return cap;
1188}
1189
1190QImageIOHandler *QAVIFPlugin::create(QIODevice *device, const QByteArray &format) const
1191{
1192 QImageIOHandler *handler = new QAVIFHandler;
1193 handler->setDevice(device);
1194 handler->setFormat(format);
1195 return handler;
1196}
1197
1198#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) &&
void setColorSpace(const QColorSpace &colorSpace)
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)
int idealThreadCount()
int toInt(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:00:39 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.