KImageFormats

avif.cpp
1 /*
2  AV1 Image File Format (AVIF) support for QImage.
3 
4  SPDX-FileCopyrightText: 2020 Daniel Novomesky <[email protected]>
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 <cfloat>
16 
17 QAVIFHandler::QAVIFHandler()
18  : m_parseState(ParseAvifNotParsed)
19  , m_quality(52)
20  , m_container_width(0)
21  , m_container_height(0)
22  , m_rawAvifData(AVIF_DATA_EMPTY)
23  , m_decoder(nullptr)
24  , m_must_jump_to_next_image(false)
25 {
26 }
27 
28 QAVIFHandler::~QAVIFHandler()
29 {
30  if (m_decoder) {
31  avifDecoderDestroy(m_decoder);
32  }
33 }
34 
35 bool QAVIFHandler::canRead() const
36 {
37  if (m_parseState == ParseAvifNotParsed && !canRead(device())) {
38  return false;
39  }
40 
41  if (m_parseState != ParseAvifError) {
42  setFormat("avif");
43  return true;
44  }
45  return false;
46 }
47 
48 bool QAVIFHandler::canRead(QIODevice *device)
49 {
50  if (!device) {
51  return false;
52  }
53  QByteArray header = device->peek(144);
54  if (header.size() < 12) {
55  return false;
56  }
57 
58  avifROData input;
59  input.data = (const uint8_t *)header.constData();
60  input.size = header.size();
61 
62  if (avifPeekCompatibleFileType(&input)) {
63  return true;
64  }
65  return false;
66 }
67 
68 bool QAVIFHandler::ensureParsed() const
69 {
70  if (m_parseState == ParseAvifSuccess) {
71  return true;
72  }
73  if (m_parseState == ParseAvifError) {
74  return false;
75  }
76 
77  QAVIFHandler *that = const_cast<QAVIFHandler *>(this);
78 
79  return that->ensureDecoder();
80 }
81 
82 bool QAVIFHandler::ensureDecoder()
83 {
84  if (m_decoder) {
85  return true;
86  }
87 
88  m_rawData = device()->readAll();
89 
90  m_rawAvifData.data = (const uint8_t *)m_rawData.constData();
91  m_rawAvifData.size = m_rawData.size();
92 
93  if (avifPeekCompatibleFileType(&m_rawAvifData) == AVIF_FALSE) {
94  m_parseState = ParseAvifError;
95  return false;
96  }
97 
98  m_decoder = avifDecoderCreate();
99 
100 #if AVIF_VERSION >= 80400
101  m_decoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
102 #endif
103 
104 #if AVIF_VERSION >= 90100
105  m_decoder->strictFlags = AVIF_STRICT_DISABLED;
106 #endif
107 
108  avifResult decodeResult;
109 
110  decodeResult = avifDecoderSetIOMemory(m_decoder, m_rawAvifData.data, m_rawAvifData.size);
111  if (decodeResult != AVIF_RESULT_OK) {
112  qWarning("ERROR: avifDecoderSetIOMemory failed: %s", avifResultToString(decodeResult));
113 
114  avifDecoderDestroy(m_decoder);
115  m_decoder = nullptr;
116  m_parseState = ParseAvifError;
117  return false;
118  }
119 
120  decodeResult = avifDecoderParse(m_decoder);
121  if (decodeResult != AVIF_RESULT_OK) {
122  qWarning("ERROR: Failed to parse input: %s", avifResultToString(decodeResult));
123 
124  avifDecoderDestroy(m_decoder);
125  m_decoder = nullptr;
126  m_parseState = ParseAvifError;
127  return false;
128  }
129 
130  decodeResult = avifDecoderNextImage(m_decoder);
131 
132  if (decodeResult == AVIF_RESULT_OK) {
133  m_container_width = m_decoder->image->width;
134  m_container_height = m_decoder->image->height;
135 
136  if ((m_container_width > 32768) || (m_container_height > 32768)) {
137  qWarning("AVIF image (%dx%d) is too large!", m_container_width, m_container_height);
138  m_parseState = ParseAvifError;
139  return false;
140  }
141 
142  if ((m_container_width == 0) || (m_container_height == 0)) {
143  qWarning("Empty image, nothing to decode");
144  m_parseState = ParseAvifError;
145  return false;
146  }
147 
148  m_parseState = ParseAvifSuccess;
149  if (decode_one_frame()) {
150  return true;
151  } else {
152  m_parseState = ParseAvifError;
153  return false;
154  }
155  } else {
156  qWarning("ERROR: Failed to decode image: %s", avifResultToString(decodeResult));
157  }
158 
159  avifDecoderDestroy(m_decoder);
160  m_decoder = nullptr;
161  m_parseState = ParseAvifError;
162  return false;
163 }
164 
165 bool QAVIFHandler::decode_one_frame()
166 {
167  if (!ensureParsed()) {
168  return false;
169  }
170 
171  bool loadalpha;
172 
173  if (m_decoder->image->alphaPlane) {
174  loadalpha = true;
175  } else {
176  loadalpha = false;
177  }
178 
179  QImage::Format resultformat;
180 
181  if (m_decoder->image->depth > 8) {
182  if (loadalpha) {
183  resultformat = QImage::Format_RGBA64;
184  } else {
185  resultformat = QImage::Format_RGBX64;
186  }
187  } else {
188  if (loadalpha) {
189  resultformat = QImage::Format_RGBA8888;
190  } else {
191  resultformat = QImage::Format_RGBX8888;
192  }
193  }
194  QImage result(m_decoder->image->width, m_decoder->image->height, resultformat);
195 
196  if (result.isNull()) {
197  qWarning("Memory cannot be allocated");
198  return false;
199  }
200 
201  QColorSpace colorspace;
202  if (m_decoder->image->icc.data && (m_decoder->image->icc.size > 0)) {
203  const QByteArray icc_data((const char *)m_decoder->image->icc.data, (int)m_decoder->image->icc.size);
204  colorspace = QColorSpace::fromIccProfile(icc_data);
205  if (!colorspace.isValid()) {
206  qWarning("AVIF image has Qt-unsupported or invalid ICC profile!");
207  }
208  } else {
209  float prim[8] = {0.64f, 0.33f, 0.3f, 0.6f, 0.15f, 0.06f, 0.3127f, 0.329f};
210  // outPrimaries: rX, rY, gX, gY, bX, bY, wX, wY
211  avifColorPrimariesGetValues(m_decoder->image->colorPrimaries, prim);
212 
213  const QPointF redPoint(QAVIFHandler::CompatibleChromacity(prim[0], prim[1]));
214  const QPointF greenPoint(QAVIFHandler::CompatibleChromacity(prim[2], prim[3]));
215  const QPointF bluePoint(QAVIFHandler::CompatibleChromacity(prim[4], prim[5]));
216  const QPointF whitePoint(QAVIFHandler::CompatibleChromacity(prim[6], prim[7]));
217 
218  QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom;
219  float q_trc_gamma = 0.0f;
220 
221  switch (m_decoder->image->transferCharacteristics) {
222  /* AVIF_TRANSFER_CHARACTERISTICS_BT470M */
223  case 4:
224  q_trc = QColorSpace::TransferFunction::Gamma;
225  q_trc_gamma = 2.2f;
226  break;
227  /* AVIF_TRANSFER_CHARACTERISTICS_BT470BG */
228  case 5:
229  q_trc = QColorSpace::TransferFunction::Gamma;
230  q_trc_gamma = 2.8f;
231  break;
232  /* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
233  case 8:
234  q_trc = QColorSpace::TransferFunction::Linear;
235  break;
236  /* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
237  case 0:
238  case 2: /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
239  case 13:
240  q_trc = QColorSpace::TransferFunction::SRgb;
241  break;
242  default:
243  qWarning("CICP colorPrimaries: %d, transferCharacteristics: %d\nThe colorspace is unsupported by this plug-in yet.",
244  m_decoder->image->colorPrimaries,
245  m_decoder->image->transferCharacteristics);
246  q_trc = QColorSpace::TransferFunction::SRgb;
247  break;
248  }
249 
250  if (q_trc != QColorSpace::TransferFunction::Custom) { // we create new colorspace using Qt
251  switch (m_decoder->image->colorPrimaries) {
252  /* AVIF_COLOR_PRIMARIES_BT709 */
253  case 0:
254  case 1:
255  case 2: /* AVIF_COLOR_PRIMARIES_UNSPECIFIED */
256  colorspace = QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma);
257  break;
258  /* AVIF_COLOR_PRIMARIES_SMPTE432 */
259  case 12:
260  colorspace = QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma);
261  break;
262  default:
263  colorspace = QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma);
264  break;
265  }
266  }
267 
268  if (!colorspace.isValid()) {
269  qWarning("AVIF plugin created invalid QColorSpace from NCLX/CICP!");
270  }
271  }
272 
273  result.setColorSpace(colorspace);
274 
275  avifRGBImage rgb;
276  avifRGBImageSetDefaults(&rgb, m_decoder->image);
277 
278  if (m_decoder->image->depth > 8) {
279  rgb.depth = 16;
280  rgb.format = AVIF_RGB_FORMAT_RGBA;
281 
282  if (!loadalpha) {
283  if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
284  resultformat = QImage::Format_Grayscale16;
285  }
286  }
287  } else {
288  rgb.depth = 8;
289  rgb.format = AVIF_RGB_FORMAT_RGBA;
290 
291 #if AVIF_VERSION >= 80400
292  if (m_decoder->imageCount > 1) {
293  /* accelerate animated AVIF */
294  rgb.chromaUpsampling = AVIF_CHROMA_UPSAMPLING_FASTEST;
295  }
296 #endif
297 
298  if (loadalpha) {
299  resultformat = QImage::Format_ARGB32;
300  } else {
301  if (m_decoder->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
302  resultformat = QImage::Format_Grayscale8;
303  } else {
304  resultformat = QImage::Format_RGB32;
305  }
306  }
307  }
308 
309  rgb.rowBytes = result.bytesPerLine();
310  rgb.pixels = result.bits();
311 
312  avifResult res = avifImageYUVToRGB(m_decoder->image, &rgb);
313  if (res != AVIF_RESULT_OK) {
314  qWarning("ERROR in avifImageYUVToRGB: %s", avifResultToString(res));
315  return false;
316  }
317 
318  if (m_decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) {
319  if ((m_decoder->image->clap.widthD > 0) && (m_decoder->image->clap.heightD > 0) && (m_decoder->image->clap.horizOffD > 0)
320  && (m_decoder->image->clap.vertOffD > 0)) {
321  int new_width = (int)((double)(m_decoder->image->clap.widthN) / (m_decoder->image->clap.widthD) + 0.5);
322  if (new_width > result.width()) {
323  new_width = result.width();
324  }
325 
326  int new_height = (int)((double)(m_decoder->image->clap.heightN) / (m_decoder->image->clap.heightD) + 0.5);
327  if (new_height > result.height()) {
328  new_height = result.height();
329  }
330 
331  if (new_width > 0 && new_height > 0) {
332  int offx =
333  ((double)((int32_t)m_decoder->image->clap.horizOffN)) / (m_decoder->image->clap.horizOffD) + (result.width() - new_width) / 2.0 + 0.5;
334  if (offx < 0) {
335  offx = 0;
336  } else if (offx > (result.width() - new_width)) {
337  offx = result.width() - new_width;
338  }
339 
340  int offy =
341  ((double)((int32_t)m_decoder->image->clap.vertOffN)) / (m_decoder->image->clap.vertOffD) + (result.height() - new_height) / 2.0 + 0.5;
342  if (offy < 0) {
343  offy = 0;
344  } else if (offy > (result.height() - new_height)) {
345  offy = result.height() - new_height;
346  }
347 
348  result = result.copy(offx, offy, new_width, new_height);
349  }
350  }
351 
352  else { // Zero values, we need to avoid 0 divide.
353  qWarning("ERROR: Wrong values in avifCleanApertureBox");
354  }
355  }
356 
357  if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IROT) {
359  switch (m_decoder->image->irot.angle) {
360  case 1:
361  transform.rotate(-90);
362  result = result.transformed(transform);
363  break;
364  case 2:
365  transform.rotate(180);
366  result = result.transformed(transform);
367  break;
368  case 3:
369  transform.rotate(90);
370  result = result.transformed(transform);
371  break;
372  }
373  }
374 
375  if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IMIR) {
376 #if AVIF_VERSION > 90100
377  switch (m_decoder->image->imir.mode) {
378 #else
379  switch (m_decoder->image->imir.axis) {
380 #endif
381  case 0: // top-to-bottom
382  result = result.mirrored(false, true);
383  break;
384  case 1: // left-to-right
385  result = result.mirrored(true, false);
386  break;
387  }
388  }
389 
390  if (resultformat == result.format()) {
391  m_current_image = result;
392  } else {
393  m_current_image = result.convertToFormat(resultformat);
394  }
395 
396  m_must_jump_to_next_image = false;
397  return true;
398 }
399 
400 bool QAVIFHandler::read(QImage *image)
401 {
402  if (!ensureParsed()) {
403  return false;
404  }
405 
406  if (m_must_jump_to_next_image) {
407  jumpToNextImage();
408  }
409 
410  *image = m_current_image;
411  if (imageCount() >= 2) {
412  m_must_jump_to_next_image = true;
413  }
414  return true;
415 }
416 
417 bool QAVIFHandler::write(const QImage &image)
418 {
419  if (image.format() == QImage::Format_Invalid) {
420  qWarning("No image data to save");
421  return false;
422  }
423 
424  if ((image.width() > 32768) || (image.height() > 32768)) {
425  qWarning("Image is too large");
426  return false;
427  }
428 
429  int maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY * (100 - qBound(0, m_quality, 100)) / 100;
430  int minQuantizer = 0;
431  int maxQuantizerAlpha = 0;
432  avifResult res;
433 
434  bool save_grayscale; // true - monochrome, false - colors
435  int save_depth; // 8 or 10bit per channel
436  QImage::Format tmpformat; // format for temporary image
437 
438  avifImage *avif = nullptr;
439 
440  // grayscale detection
441  switch (image.format()) {
442  case QImage::Format_Mono:
446  save_grayscale = true;
447  break;
449  save_grayscale = image.isGrayscale();
450  break;
451  default:
452  save_grayscale = false;
453  break;
454  }
455 
456  // depth detection
457  switch (image.format()) {
466  save_depth = 10;
467  break;
468  default:
469  if (image.depth() > 32) {
470  save_depth = 10;
471  } else {
472  save_depth = 8;
473  }
474  break;
475  }
476 
477  // quality settings
478  if (maxQuantizer > 20) {
479  minQuantizer = maxQuantizer - 20;
480  if (maxQuantizer > 40) { // we decrease quality of alpha channel here
481  maxQuantizerAlpha = maxQuantizer - 40;
482  }
483  }
484 
485  if (save_grayscale && !image.hasAlphaChannel()) { // we are going to save grayscale image without alpha channel
486  if (save_depth > 8) {
487  tmpformat = QImage::Format_Grayscale16;
488  } else {
489  tmpformat = QImage::Format_Grayscale8;
490  }
491  QImage tmpgrayimage = image.convertToFormat(tmpformat);
492 
493  avif = avifImageCreate(tmpgrayimage.width(), tmpgrayimage.height(), save_depth, AVIF_PIXEL_FORMAT_YUV400);
494  avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
495 
496  if (tmpgrayimage.colorSpace().isValid()) {
497  avif->colorPrimaries = (avifColorPrimaries)1;
498  avif->matrixCoefficients = (avifMatrixCoefficients)1;
499 
500  switch (tmpgrayimage.colorSpace().transferFunction()) {
501  case QColorSpace::TransferFunction::Linear:
502  /* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
503  avif->transferCharacteristics = (avifTransferCharacteristics)8;
504  break;
505  case QColorSpace::TransferFunction::SRgb:
506  /* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
507  avif->transferCharacteristics = (avifTransferCharacteristics)13;
508  break;
509  default:
510  /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
511  break;
512  }
513  }
514 
515  if (save_depth > 8) { // QImage::Format_Grayscale16
516  for (int y = 0; y < tmpgrayimage.height(); y++) {
517  const uint16_t *src16bit = reinterpret_cast<const uint16_t *>(tmpgrayimage.constScanLine(y));
518  uint16_t *dest16bit = reinterpret_cast<uint16_t *>(avif->yuvPlanes[0] + y * avif->yuvRowBytes[0]);
519  for (int x = 0; x < tmpgrayimage.width(); x++) {
520  int tmp_pixelval = (int)(((float)(*src16bit) / 65535.0f) * 1023.0f + 0.5f); // downgrade to 10 bits
521  *dest16bit = qBound(0, tmp_pixelval, 1023);
522  dest16bit++;
523  src16bit++;
524  }
525  }
526  } else { // QImage::Format_Grayscale8
527  for (int y = 0; y < tmpgrayimage.height(); y++) {
528  const uchar *src8bit = tmpgrayimage.constScanLine(y);
529  uint8_t *dest8bit = avif->yuvPlanes[0] + y * avif->yuvRowBytes[0];
530  for (int x = 0; x < tmpgrayimage.width(); x++) {
531  *dest8bit = *src8bit;
532  dest8bit++;
533  src8bit++;
534  }
535  }
536  }
537 
538  } else { // we are going to save color image
539  if (save_depth > 8) {
540  if (image.hasAlphaChannel()) {
541  tmpformat = QImage::Format_RGBA64;
542  } else {
543  tmpformat = QImage::Format_RGBX64;
544  }
545  } else { // 8bit depth
546  if (image.hasAlphaChannel()) {
547  tmpformat = QImage::Format_RGBA8888;
548  } else {
549  tmpformat = QImage::Format_RGB888;
550  }
551  }
552 
553  QImage tmpcolorimage = image.convertToFormat(tmpformat);
554 
555  avifPixelFormat pixel_format = AVIF_PIXEL_FORMAT_YUV420;
556  if (maxQuantizer < 20) {
557  if (maxQuantizer < 10) {
558  pixel_format = AVIF_PIXEL_FORMAT_YUV444; // best quality
559  } else {
560  pixel_format = AVIF_PIXEL_FORMAT_YUV422; // high quality
561  }
562  }
563 
564  avifMatrixCoefficients matrix_to_save = (avifMatrixCoefficients)1; // default for Qt 5.12 and 5.13;
565 
566  avifColorPrimaries primaries_to_save = (avifColorPrimaries)2;
567  avifTransferCharacteristics transfer_to_save = (avifTransferCharacteristics)2;
568  QByteArray iccprofile;
569 
570  if (tmpcolorimage.colorSpace().isValid()) {
571  switch (tmpcolorimage.colorSpace().primaries()) {
572  case QColorSpace::Primaries::SRgb:
573  /* AVIF_COLOR_PRIMARIES_BT709 */
574  primaries_to_save = (avifColorPrimaries)1;
575  /* AVIF_MATRIX_COEFFICIENTS_BT709 */
576  matrix_to_save = (avifMatrixCoefficients)1;
577  break;
578  case QColorSpace::Primaries::DciP3D65:
579  /* AVIF_NCLX_COLOUR_PRIMARIES_P3, AVIF_NCLX_COLOUR_PRIMARIES_SMPTE432 */
580  primaries_to_save = (avifColorPrimaries)12;
581  /* AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL */
582  matrix_to_save = (avifMatrixCoefficients)12;
583  break;
584  default:
585  /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
586  primaries_to_save = (avifColorPrimaries)2;
587  /* AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED */
588  matrix_to_save = (avifMatrixCoefficients)2;
589  break;
590  }
591 
592  switch (tmpcolorimage.colorSpace().transferFunction()) {
593  case QColorSpace::TransferFunction::Linear:
594  /* AVIF_TRANSFER_CHARACTERISTICS_LINEAR */
595  transfer_to_save = (avifTransferCharacteristics)8;
596  break;
597  case QColorSpace::TransferFunction::Gamma:
598  if (qAbs(tmpcolorimage.colorSpace().gamma() - 2.2f) < 0.1f) {
599  /* AVIF_TRANSFER_CHARACTERISTICS_BT470M */
600  transfer_to_save = (avifTransferCharacteristics)4;
601  } else if (qAbs(tmpcolorimage.colorSpace().gamma() - 2.8f) < 0.1f) {
602  /* AVIF_TRANSFER_CHARACTERISTICS_BT470BG */
603  transfer_to_save = (avifTransferCharacteristics)5;
604  } else {
605  /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
606  transfer_to_save = (avifTransferCharacteristics)2;
607  }
608  break;
609  case QColorSpace::TransferFunction::SRgb:
610  /* AVIF_TRANSFER_CHARACTERISTICS_SRGB */
611  transfer_to_save = (avifTransferCharacteristics)13;
612  break;
613  default:
614  /* AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED */
615  transfer_to_save = (avifTransferCharacteristics)2;
616  break;
617  }
618 
619  // in case primaries or trc were not identified
620  if ((primaries_to_save == 2) || (transfer_to_save == 2)) {
621  // upgrade image to higher bit depth
622  if (save_depth == 8) {
623  save_depth = 10;
624  if (tmpcolorimage.hasAlphaChannel()) {
625  tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBA64);
626  } else {
627  tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBX64);
628  }
629  }
630 
631  if ((primaries_to_save == 2) && (transfer_to_save != 2)) { // other primaries but known trc
632  primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
633  matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
634 
635  switch (transfer_to_save) {
636  case 8: // AVIF_TRANSFER_CHARACTERISTICS_LINEAR
637  tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::Linear));
638  break;
639  case 4: // AVIF_TRANSFER_CHARACTERISTICS_BT470M
640  tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.2f));
641  break;
642  case 5: // AVIF_TRANSFER_CHARACTERISTICS_BT470BG
643  tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, 2.8f));
644  break;
645  default: // AVIF_TRANSFER_CHARACTERISTICS_SRGB + any other
646  tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
647  transfer_to_save = (avifTransferCharacteristics)13;
648  break;
649  }
650  } else if ((primaries_to_save != 2) && (transfer_to_save == 2)) { // recognized primaries but other trc
651  transfer_to_save = (avifTransferCharacteristics)13;
652  tmpcolorimage.convertToColorSpace(tmpcolorimage.colorSpace().withTransferFunction(QColorSpace::TransferFunction::SRgb));
653  } else { // unrecognized profile
654  primaries_to_save = (avifColorPrimaries)1; // AVIF_COLOR_PRIMARIES_BT709
655  transfer_to_save = (avifTransferCharacteristics)13;
656  matrix_to_save = (avifMatrixCoefficients)1; // AVIF_MATRIX_COEFFICIENTS_BT709
657  tmpcolorimage.convertToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, QColorSpace::TransferFunction::SRgb));
658  }
659  }
660  } else { // profile is unsupported by Qt
661  iccprofile = tmpcolorimage.colorSpace().iccProfile();
662  if (iccprofile.size() > 0) {
663  matrix_to_save = (avifMatrixCoefficients)6;
664  }
665  }
666 
667  avif = avifImageCreate(tmpcolorimage.width(), tmpcolorimage.height(), save_depth, pixel_format);
668  avif->matrixCoefficients = matrix_to_save;
669 
670  avif->colorPrimaries = primaries_to_save;
671  avif->transferCharacteristics = transfer_to_save;
672 
673  if (iccprofile.size() > 0) {
674  avifImageSetProfileICC(avif, (const uint8_t *)iccprofile.constData(), iccprofile.size());
675  }
676 
677  avifRGBImage rgb;
678  avifRGBImageSetDefaults(&rgb, avif);
679  rgb.rowBytes = tmpcolorimage.bytesPerLine();
680  rgb.pixels = const_cast<uint8_t *>(tmpcolorimage.constBits());
681 
682  if (save_depth > 8) { // 10bit depth
683  rgb.depth = 16;
684 
685  if (tmpcolorimage.hasAlphaChannel()) {
686  avif->alphaRange = AVIF_RANGE_FULL;
687  } else {
688  rgb.ignoreAlpha = AVIF_TRUE;
689  }
690 
691  rgb.format = AVIF_RGB_FORMAT_RGBA;
692  } else { // 8bit depth
693  rgb.depth = 8;
694 
695  if (tmpcolorimage.hasAlphaChannel()) {
696  rgb.format = AVIF_RGB_FORMAT_RGBA;
697  avif->alphaRange = AVIF_RANGE_FULL;
698  } else {
699  rgb.format = AVIF_RGB_FORMAT_RGB;
700  }
701  }
702 
703  res = avifImageRGBToYUV(avif, &rgb);
704  if (res != AVIF_RESULT_OK) {
705  qWarning("ERROR in avifImageRGBToYUV: %s", avifResultToString(res));
706  return false;
707  }
708  }
709 
710  avifRWData raw = AVIF_DATA_EMPTY;
711  avifEncoder *encoder = avifEncoderCreate();
712  encoder->maxThreads = qBound(1, QThread::idealThreadCount(), 64);
713  encoder->minQuantizer = minQuantizer;
714  encoder->maxQuantizer = maxQuantizer;
715 
716  if (image.hasAlphaChannel()) {
717  encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
718  encoder->maxQuantizerAlpha = maxQuantizerAlpha;
719  }
720 
721  encoder->speed = 7;
722 
723  res = avifEncoderWrite(encoder, avif, &raw);
724  avifEncoderDestroy(encoder);
725  avifImageDestroy(avif);
726 
727  if (res == AVIF_RESULT_OK) {
728  qint64 status = device()->write((const char *)raw.data, raw.size);
729  avifRWDataFree(&raw);
730 
731  if (status > 0) {
732  return true;
733  } else if (status == -1) {
734  qWarning("Write error: %s", qUtf8Printable(device()->errorString()));
735  return false;
736  }
737  } else {
738  qWarning("ERROR: Failed to encode: %s", avifResultToString(res));
739  }
740 
741  return false;
742 }
743 
744 QVariant QAVIFHandler::option(ImageOption option) const
745 {
746  if (option == Quality) {
747  return m_quality;
748  }
749 
750  if (!supportsOption(option) || !ensureParsed()) {
751  return QVariant();
752  }
753 
754  switch (option) {
755  case Size:
756  return m_current_image.size();
757  case Animation:
758  if (imageCount() >= 2) {
759  return true;
760  } else {
761  return false;
762  }
763  default:
764  return QVariant();
765  }
766 }
767 
768 void QAVIFHandler::setOption(ImageOption option, const QVariant &value)
769 {
770  switch (option) {
771  case Quality:
772  m_quality = value.toInt();
773  if (m_quality > 100) {
774  m_quality = 100;
775  } else if (m_quality < 0) {
776  m_quality = 52;
777  }
778  return;
779  default:
780  break;
781  }
782  QImageIOHandler::setOption(option, value);
783 }
784 
785 bool QAVIFHandler::supportsOption(ImageOption option) const
786 {
787  return option == Quality || option == Size || option == Animation;
788 }
789 
790 int QAVIFHandler::imageCount() const
791 {
792  if (!ensureParsed()) {
793  return 0;
794  }
795 
796  if (m_decoder->imageCount >= 1) {
797  return m_decoder->imageCount;
798  }
799  return 0;
800 }
801 
802 int QAVIFHandler::currentImageNumber() const
803 {
804  if (m_parseState == ParseAvifNotParsed) {
805  return -1;
806  }
807 
808  if (m_parseState == ParseAvifError || !m_decoder) {
809  return 0;
810  }
811 
812  return m_decoder->imageIndex;
813 }
814 
815 bool QAVIFHandler::jumpToNextImage()
816 {
817  if (!ensureParsed()) {
818  return false;
819  }
820 
821  if (m_decoder->imageCount < 2) {
822  return true;
823  }
824 
825  if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
826  avifDecoderReset(m_decoder);
827  }
828 
829  avifResult decodeResult = avifDecoderNextImage(m_decoder);
830 
831  if (decodeResult != AVIF_RESULT_OK) {
832  qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
833  m_parseState = ParseAvifError;
834  return false;
835  }
836 
837  if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
838  qWarning("Decoded image sequence size (%dx%d) do not match first image size (%dx%d)!",
839  m_decoder->image->width,
840  m_decoder->image->height,
841  m_container_width,
842  m_container_height);
843 
844  m_parseState = ParseAvifError;
845  return false;
846  }
847 
848  if (decode_one_frame()) {
849  return true;
850  } else {
851  m_parseState = ParseAvifError;
852  return false;
853  }
854 }
855 
856 bool QAVIFHandler::jumpToImage(int imageNumber)
857 {
858  if (!ensureParsed()) {
859  return false;
860  }
861 
862  if (m_decoder->imageCount < 2) { // not an animation
863  if (imageNumber == 0) {
864  return true;
865  } else {
866  return false;
867  }
868  }
869 
870  if (imageNumber < 0 || imageNumber >= m_decoder->imageCount) { // wrong index
871  return false;
872  }
873 
874  if (imageNumber == m_decoder->imageCount) { // we are here already
875  return true;
876  }
877 
878  avifResult decodeResult = avifDecoderNthImage(m_decoder, imageNumber);
879 
880  if (decodeResult != AVIF_RESULT_OK) {
881  qWarning("ERROR: Failed to decode %d th Image in sequence: %s", imageNumber, avifResultToString(decodeResult));
882  m_parseState = ParseAvifError;
883  return false;
884  }
885 
886  if ((m_container_width != m_decoder->image->width) || (m_container_height != m_decoder->image->height)) {
887  qWarning("Decoded image sequence size (%dx%d) do not match declared container size (%dx%d)!",
888  m_decoder->image->width,
889  m_decoder->image->height,
890  m_container_width,
891  m_container_height);
892 
893  m_parseState = ParseAvifError;
894  return false;
895  }
896 
897  if (decode_one_frame()) {
898  return true;
899  } else {
900  m_parseState = ParseAvifError;
901  return false;
902  }
903 }
904 
905 int QAVIFHandler::nextImageDelay() const
906 {
907  if (!ensureParsed()) {
908  return 0;
909  }
910 
911  if (m_decoder->imageCount < 2) {
912  return 0;
913  }
914 
915  int delay_ms = 1000.0 * m_decoder->imageTiming.duration;
916  if (delay_ms < 1) {
917  delay_ms = 1;
918  }
919  return delay_ms;
920 }
921 
922 int QAVIFHandler::loopCount() const
923 {
924  if (!ensureParsed()) {
925  return 0;
926  }
927 
928  if (m_decoder->imageCount < 2) {
929  return 0;
930  }
931 
932  return 1;
933 }
934 
935 QPointF QAVIFHandler::CompatibleChromacity(qreal chrX, qreal chrY)
936 {
937  chrX = qBound(qreal(0.0), chrX, qreal(1.0));
938  chrY = qBound(qreal(DBL_MIN), chrY, qreal(1.0));
939 
940  if ((chrX + chrY) > qreal(1.0)) {
941  chrX = qreal(1.0) - chrY;
942  }
943 
944  return QPointF(chrX, chrY);
945 }
946 
947 QImageIOPlugin::Capabilities QAVIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
948 {
949  if (format == "avif") {
950  return Capabilities(CanRead | CanWrite);
951  }
952 
953  if (format == "avifs") {
954  return Capabilities(CanRead);
955  }
956 
957  if (!format.isEmpty()) {
958  return {};
959  }
960  if (!device->isOpen()) {
961  return {};
962  }
963 
964  Capabilities cap;
965  if (device->isReadable() && QAVIFHandler::canRead(device)) {
966  cap |= CanRead;
967  }
968  if (device->isWritable()) {
969  cap |= CanWrite;
970  }
971  return cap;
972 }
973 
974 QImageIOHandler *QAVIFPlugin::create(QIODevice *device, const QByteArray &format) const
975 {
976  QImageIOHandler *handler = new QAVIFHandler;
977  handler->setDevice(device);
978  handler->setFormat(format);
979  return handler;
980 }
QImage convertToFormat(QImage::Format format, Qt::ImageConversionFlags flags) const &const
bool isWritable() const const
const uchar * constScanLine(int i) const const
QColorSpace::TransferFunction transferFunction() const const
int depth() const const
bool isEmpty() const const
bool hasAlphaChannel() const const
QColorSpace withTransferFunction(QColorSpace::TransferFunction transferFunction, float gamma) const const
KDOCTOOLS_EXPORT QString transform(const QString &file, const QString &stylesheet, const QVector< const char * > &params=QVector< const char * >())
bool isReadable() const const
bool isValid() const const
qint64 peek(char *data, qint64 maxSize)
QColorSpace fromIccProfile(const QByteArray &iccProfile)
int toInt(bool *ok) const const
bool isGrayscale() const const
QColorSpace::Primaries primaries() const const
int width() const const
const char * constData() const const
QByteArray readAll()
const uchar * constBits() const const
void setDevice(QIODevice *device)
bool isOpen() const const
QColorSpace colorSpace() const const
void convertToColorSpace(const QColorSpace &colorSpace)
QTransform & rotate(qreal angle, Qt::Axis axis)
int bytesPerLine() const const
QByteArray iccProfile() const const
int idealThreadCount()
float gamma() const const
char * data()
qint64 write(const char *data, qint64 maxSize)
typedef Capabilities
void setFormat(const QByteArray &format)
int height() const const
int size() const const
virtual void setOption(QImageIOHandler::ImageOption option, const QVariant &value)
QImage::Format format() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Dec 2 2021 22:45:39 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.