KImageFormats

heif.cpp
1 /*
2  High Efficiency Image File Format (HEIF) support for QImage.
3 
4  SPDX-FileCopyrightText: 2020 Sirius Bakke <[email protected]>
5  SPDX-FileCopyrightText: 2021 Daniel Novomesky <[email protected]>
6 
7  SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 
10 #include "heif_p.h"
11 #include "libheif/heif_cxx.h"
12 
13 #include <QColorSpace>
14 #include <QDebug>
15 #include <QPointF>
16 #include <QSysInfo>
17 #include <string.h>
18 
19 namespace // Private.
20 {
21 struct HeifQIODeviceWriter : public heif::Context::Writer {
22  HeifQIODeviceWriter(QIODevice *device)
23  : m_ioDevice(device)
24  {
25  }
26 
27  heif_error write(const void *data, size_t size) override
28  {
29  heif_error error;
30  error.code = heif_error_Ok;
31  error.subcode = heif_suberror_Unspecified;
32  error.message = errorOkMessage;
33 
34  qint64 bytesWritten = m_ioDevice->write(static_cast<const char *>(data), size);
35 
36  if (bytesWritten < static_cast<qint64>(size)) {
37  error.code = heif_error_Encoding_error;
38  error.message = QIODeviceWriteErrorMessage;
39  error.subcode = heif_suberror_Cannot_write_output_data;
40  }
41 
42  return error;
43  }
44 
45  static constexpr const char *errorOkMessage = "Success";
46  static constexpr const char *QIODeviceWriteErrorMessage = "Bytes written to QIODevice are smaller than input data size";
47 
48 private:
49  QIODevice *m_ioDevice;
50 };
51 
52 } // namespace
53 
54 HEIFHandler::HEIFHandler()
55  : m_parseState(ParseHeicNotParsed)
56  , m_quality(100)
57 {
58 }
59 
60 bool HEIFHandler::canRead() const
61 {
62  if (m_parseState == ParseHeicNotParsed && !canRead(device())) {
63  return false;
64  }
65 
66  if (m_parseState != ParseHeicError) {
67  setFormat("heif");
68  return true;
69  }
70  return false;
71 }
72 
73 bool HEIFHandler::read(QImage *outImage)
74 {
75  if (!ensureParsed()) {
76  return false;
77  }
78 
79  *outImage = m_current_image;
80  return true;
81 }
82 
83 bool HEIFHandler::write(const QImage &image)
84 {
85  if (image.format() == QImage::Format_Invalid || image.isNull()) {
86  qWarning("No image data to save");
87  return false;
88  }
89 
90  int save_depth; // 8 or 10bit per channel
91  QImage::Format tmpformat; // format for temporary image
92  const bool save_alpha = image.hasAlphaChannel();
93 
94  switch (image.format()) {
103  save_depth = 10;
104  break;
105  default:
106  if (image.depth() > 32) {
107  save_depth = 10;
108  } else {
109  save_depth = 8;
110  }
111  break;
112  }
113 
114  heif_chroma chroma;
115  if (save_depth > 8) {
116  if (save_alpha) {
117  tmpformat = QImage::Format_RGBA64;
118  chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE;
119  } else {
120  tmpformat = QImage::Format_RGBX64;
121  chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE;
122  }
123  } else {
124  if (save_alpha) {
125  tmpformat = QImage::Format_RGBA8888;
126  chroma = heif_chroma_interleaved_RGBA;
127  } else {
128  tmpformat = QImage::Format_RGB888;
129  chroma = heif_chroma_interleaved_RGB;
130  }
131  }
132 
133  const QImage tmpimage = image.convertToFormat(tmpformat);
134 
135  try {
136  heif::Context ctx;
137  heif::Image heifImage;
138  heifImage.create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma);
139 
140  QByteArray iccprofile = tmpimage.colorSpace().iccProfile();
141  if (iccprofile.size() > 0) {
142  std::vector<uint8_t> rawProfile(iccprofile.begin(), iccprofile.end());
143  heifImage.set_raw_color_profile(heif_color_profile_type_prof, rawProfile);
144  }
145 
146  heifImage.add_plane(heif_channel_interleaved, image.width(), image.height(), save_depth);
147  int stride = 0;
148  uint8_t *const dst = heifImage.get_plane(heif_channel_interleaved, &stride);
149  size_t rowbytes;
150 
151  switch (save_depth) {
152  case 10:
153  if (save_alpha) {
154  for (int y = 0; y < tmpimage.height(); y++) {
155  const uint16_t *src_word = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
156  uint16_t *dest_word = reinterpret_cast<uint16_t *>(dst + (y * stride));
157  for (int x = 0; x < tmpimage.width(); x++) {
158  int tmp_pixelval;
159  // R
160  tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
161  *dest_word = qBound(0, tmp_pixelval, 1023);
162  src_word++;
163  dest_word++;
164  // G
165  tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
166  *dest_word = qBound(0, tmp_pixelval, 1023);
167  src_word++;
168  dest_word++;
169  // B
170  tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
171  *dest_word = qBound(0, tmp_pixelval, 1023);
172  src_word++;
173  dest_word++;
174  // A
175  tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
176  *dest_word = qBound(0, tmp_pixelval, 1023);
177  src_word++;
178  dest_word++;
179  }
180  }
181  } else { // no alpha channel
182  for (int y = 0; y < tmpimage.height(); y++) {
183  const uint16_t *src_word = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
184  uint16_t *dest_word = reinterpret_cast<uint16_t *>(dst + (y * stride));
185  for (int x = 0; x < tmpimage.width(); x++) {
186  int tmp_pixelval;
187  // R
188  tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
189  *dest_word = qBound(0, tmp_pixelval, 1023);
190  src_word++;
191  dest_word++;
192  // G
193  tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
194  *dest_word = qBound(0, tmp_pixelval, 1023);
195  src_word++;
196  dest_word++;
197  // B
198  tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
199  *dest_word = qBound(0, tmp_pixelval, 1023);
200  src_word++;
201  dest_word++;
202  // X
203  src_word++;
204  }
205  }
206  }
207  break;
208  case 8:
209  rowbytes = save_alpha ? (tmpimage.width() * 4) : (tmpimage.width() * 3);
210  for (int y = 0; y < tmpimage.height(); y++) {
211  memcpy(dst + (y * stride), tmpimage.constScanLine(y), rowbytes);
212  }
213  break;
214  default:
215  qWarning() << "Unsupported depth:" << save_depth;
216  return false;
217  break;
218  }
219 
220  heif::Encoder encoder(heif_compression_HEVC);
221 
222  encoder.set_lossy_quality(m_quality);
223  if (m_quality > 90) {
224  if (m_quality == 100) {
225  encoder.set_lossless(true);
226  }
227  encoder.set_string_parameter("chroma", "444");
228  }
229 
230  heif::Context::EncodingOptions encodingOptions;
231  encodingOptions.save_alpha_channel = save_alpha;
232 
233  if ((tmpimage.width() % 2 == 1) || (tmpimage.height() % 2 == 1)) {
234  qWarning() << "Image has odd dimension!\nUse even-numbered dimension(s) for better compatibility with other HEIF implementations.";
235  if (save_alpha) {
236  // This helps to save alpha channel when image has odd dimension
237  encodingOptions.macOS_compatibility_workaround = 0;
238  }
239  }
240 
241  ctx.encode_image(heifImage, encoder, encodingOptions);
242 
243  HeifQIODeviceWriter writer(device());
244 
245  ctx.write(writer);
246 
247  } catch (const heif::Error &err) {
248  qWarning() << "libheif error:" << err.get_message().c_str();
249  return false;
250  }
251 
252  return true;
253 }
254 
255 bool HEIFHandler::canRead(QIODevice *device)
256 {
257  if (!device) {
258  qWarning("HEIFHandler::canRead() called with no device");
259  return false;
260  }
261 
262  const QByteArray header = device->peek(28);
263  return HEIFHandler::isSupportedBMFFType(header);
264 }
265 
266 bool HEIFHandler::isSupportedBMFFType(const QByteArray &header)
267 {
268  if (header.size() < 28) {
269  return false;
270  }
271 
272  const char *buffer = header.constData();
273  if (qstrncmp(buffer + 4, "ftyp", 4) == 0) {
274  if (qstrncmp(buffer + 8, "heic", 4) == 0) {
275  return true;
276  }
277  if (qstrncmp(buffer + 8, "heis", 4) == 0) {
278  return true;
279  }
280  if (qstrncmp(buffer + 8, "heix", 4) == 0) {
281  return true;
282  }
283 
284  /* we want to avoid loading AVIF files via this plugin */
285  if (qstrncmp(buffer + 8, "mif1", 4) == 0) {
286  for (int offset = 16; offset <= 24; offset += 4) {
287  if (qstrncmp(buffer + offset, "avif", 4) == 0) {
288  return false;
289  }
290  }
291  return true;
292  }
293 
294  if (qstrncmp(buffer + 8, "mif2", 4) == 0) {
295  return true;
296  }
297  if (qstrncmp(buffer + 8, "msf1", 4) == 0) {
298  return true;
299  }
300  }
301 
302  return false;
303 }
304 
305 QVariant HEIFHandler::option(ImageOption option) const
306 {
307  if (option == Quality) {
308  return m_quality;
309  }
310 
311  if (!supportsOption(option) || !ensureParsed()) {
312  return QVariant();
313  }
314 
315  switch (option) {
316  case Size:
317  return m_current_image.size();
318  break;
319  default:
320  return QVariant();
321  break;
322  }
323 }
324 
325 void HEIFHandler::setOption(ImageOption option, const QVariant &value)
326 {
327  switch (option) {
328  case Quality:
329  m_quality = value.toInt();
330  if (m_quality > 100) {
331  m_quality = 100;
332  } else if (m_quality < 0) {
333  m_quality = 100;
334  }
335  break;
336  default:
337  QImageIOHandler::setOption(option, value);
338  break;
339  }
340 }
341 
342 bool HEIFHandler::supportsOption(ImageOption option) const
343 {
344  return option == Quality || option == Size;
345 }
346 
347 bool HEIFHandler::ensureParsed() const
348 {
349  if (m_parseState == ParseHeicSuccess) {
350  return true;
351  }
352  if (m_parseState == ParseHeicError) {
353  return false;
354  }
355 
356  HEIFHandler *that = const_cast<HEIFHandler *>(this);
357 
358  return that->ensureDecoder();
359 }
360 bool HEIFHandler::ensureDecoder()
361 {
362  if (m_parseState != ParseHeicNotParsed) {
363  if (m_parseState == ParseHeicSuccess) {
364  return true;
365  }
366  return false;
367  }
368 
369  const QByteArray buffer = device()->readAll();
370  if (!HEIFHandler::isSupportedBMFFType(buffer)) {
371  m_parseState = ParseHeicError;
372  return false;
373  }
374 
375  try {
376  heif::Context ctx;
377  ctx.read_from_memory_without_copy((const void *)(buffer.constData()), buffer.size());
378 
379  heif::ImageHandle handle = ctx.get_primary_image_handle();
380 
381  const bool hasAlphaChannel = handle.has_alpha_channel();
382  const int bit_depth = handle.get_luma_bits_per_pixel();
383  heif_chroma chroma;
384 
385  QImage::Format target_image_format;
386 
387  if (bit_depth == 10 || bit_depth == 12) {
388  if (hasAlphaChannel) {
389  chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE;
390  target_image_format = QImage::Format_RGBA64;
391  } else {
392  chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE;
393  target_image_format = QImage::Format_RGBX64;
394  }
395  } else if (bit_depth == 8) {
396  if (hasAlphaChannel) {
397  chroma = heif_chroma_interleaved_RGBA;
398  target_image_format = QImage::Format_ARGB32;
399  } else {
400  chroma = heif_chroma_interleaved_RGB;
401  target_image_format = QImage::Format_RGB32;
402  }
403  } else {
404  m_parseState = ParseHeicError;
405  if (bit_depth > 0) {
406  qWarning() << "Unsupported bit depth:" << bit_depth;
407  } else {
408  qWarning() << "Undefined bit depth.";
409  }
410  return false;
411  }
412 
413  heif::Image img = handle.decode_image(heif_colorspace_RGB, chroma);
414 
415  const int imageWidth = img.get_width(heif_channel_interleaved);
416  const int imageHeight = img.get_height(heif_channel_interleaved);
417 
418  QSize imageSize(imageWidth, imageHeight);
419 
420  if (!imageSize.isValid()) {
421  m_parseState = ParseHeicError;
422  qWarning() << "HEIC image size invalid:" << imageSize;
423  return false;
424  }
425 
426  int stride = 0;
427  const uint8_t *const src = img.get_plane(heif_channel_interleaved, &stride);
428 
429  if (!src || stride <= 0) {
430  m_parseState = ParseHeicError;
431  qWarning() << "HEIC data pixels information not valid!";
432  return false;
433  }
434 
435  m_current_image = QImage(imageSize, target_image_format);
436  if (m_current_image.isNull()) {
437  m_parseState = ParseHeicError;
438  qWarning() << "Unable to allocate memory!";
439  return false;
440  }
441 
442  switch (bit_depth) {
443  case 12:
444  if (hasAlphaChannel) {
445  for (int y = 0; y < imageHeight; y++) {
446  const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
447  uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
448  for (int x = 0; x < imageWidth; x++) {
449  int tmpvalue;
450  // R
451  tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
452  tmpvalue = qBound(0, tmpvalue, 65535);
453  *dest_data = (uint16_t)tmpvalue;
454  src_word++;
455  dest_data++;
456  // G
457  tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
458  tmpvalue = qBound(0, tmpvalue, 65535);
459  *dest_data = (uint16_t)tmpvalue;
460  src_word++;
461  dest_data++;
462  // B
463  tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
464  tmpvalue = qBound(0, tmpvalue, 65535);
465  *dest_data = (uint16_t)tmpvalue;
466  src_word++;
467  dest_data++;
468  // A
469  tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
470  tmpvalue = qBound(0, tmpvalue, 65535);
471  *dest_data = (uint16_t)tmpvalue;
472  src_word++;
473  dest_data++;
474  }
475  }
476  } else { // no alpha channel
477  for (int y = 0; y < imageHeight; y++) {
478  const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
479  uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
480  for (int x = 0; x < imageWidth; x++) {
481  int tmpvalue;
482  // R
483  tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
484  tmpvalue = qBound(0, tmpvalue, 65535);
485  *dest_data = (uint16_t)tmpvalue;
486  src_word++;
487  dest_data++;
488  // G
489  tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
490  tmpvalue = qBound(0, tmpvalue, 65535);
491  *dest_data = (uint16_t)tmpvalue;
492  src_word++;
493  dest_data++;
494  // B
495  tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
496  tmpvalue = qBound(0, tmpvalue, 65535);
497  *dest_data = (uint16_t)tmpvalue;
498  src_word++;
499  dest_data++;
500  // X = 0xffff
501  *dest_data = 0xffff;
502  dest_data++;
503  }
504  }
505  }
506  break;
507  case 10:
508  if (hasAlphaChannel) {
509  for (int y = 0; y < imageHeight; y++) {
510  const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
511  uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
512  for (int x = 0; x < imageWidth; x++) {
513  int tmpvalue;
514  // R
515  tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
516  tmpvalue = qBound(0, tmpvalue, 65535);
517  *dest_data = (uint16_t)tmpvalue;
518  src_word++;
519  dest_data++;
520  // G
521  tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
522  tmpvalue = qBound(0, tmpvalue, 65535);
523  *dest_data = (uint16_t)tmpvalue;
524  src_word++;
525  dest_data++;
526  // B
527  tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
528  tmpvalue = qBound(0, tmpvalue, 65535);
529  *dest_data = (uint16_t)tmpvalue;
530  src_word++;
531  dest_data++;
532  // A
533  tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
534  tmpvalue = qBound(0, tmpvalue, 65535);
535  *dest_data = (uint16_t)tmpvalue;
536  src_word++;
537  dest_data++;
538  }
539  }
540  } else { // no alpha channel
541  for (int y = 0; y < imageHeight; y++) {
542  const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
543  uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
544  for (int x = 0; x < imageWidth; x++) {
545  int tmpvalue;
546  // R
547  tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
548  tmpvalue = qBound(0, tmpvalue, 65535);
549  *dest_data = (uint16_t)tmpvalue;
550  src_word++;
551  dest_data++;
552  // G
553  tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
554  tmpvalue = qBound(0, tmpvalue, 65535);
555  *dest_data = (uint16_t)tmpvalue;
556  src_word++;
557  dest_data++;
558  // B
559  tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
560  tmpvalue = qBound(0, tmpvalue, 65535);
561  *dest_data = (uint16_t)tmpvalue;
562  src_word++;
563  dest_data++;
564  // X = 0xffff
565  *dest_data = 0xffff;
566  dest_data++;
567  }
568  }
569  }
570  break;
571  case 8:
572  if (hasAlphaChannel) {
573  for (int y = 0; y < imageHeight; y++) {
574  const uint8_t *src_byte = src + (y * stride);
575  uint32_t *dest_pixel = reinterpret_cast<uint32_t *>(m_current_image.scanLine(y));
576  for (int x = 0; x < imageWidth; x++) {
577  int red = *src_byte++;
578  int green = *src_byte++;
579  int blue = *src_byte++;
580  int alpha = *src_byte++;
581  *dest_pixel = qRgba(red, green, blue, alpha);
582  dest_pixel++;
583  }
584  }
585  } else { // no alpha channel
586  for (int y = 0; y < imageHeight; y++) {
587  const uint8_t *src_byte = src + (y * stride);
588  uint32_t *dest_pixel = reinterpret_cast<uint32_t *>(m_current_image.scanLine(y));
589  for (int x = 0; x < imageWidth; x++) {
590  int red = *src_byte++;
591  int green = *src_byte++;
592  int blue = *src_byte++;
593  *dest_pixel = qRgb(red, green, blue);
594  dest_pixel++;
595  }
596  }
597  }
598  break;
599  default:
600  m_parseState = ParseHeicError;
601  qWarning() << "Unsupported bit depth:" << bit_depth;
602  return false;
603  break;
604  }
605 
606  heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle.get_raw_image_handle());
607  struct heif_error err;
608  if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) {
609  int rawProfileSize = (int)heif_image_handle_get_raw_color_profile_size(handle.get_raw_image_handle());
610  if (rawProfileSize > 0) {
611  QByteArray ba(rawProfileSize, 0);
612  err = heif_image_handle_get_raw_color_profile(handle.get_raw_image_handle(), ba.data());
613  if (err.code) {
614  qWarning() << "icc profile loading failed";
615  } else {
616  m_current_image.setColorSpace(QColorSpace::fromIccProfile(ba));
617  if (!m_current_image.colorSpace().isValid()) {
618  qWarning() << "HEIC image has Qt-unsupported or invalid ICC profile!";
619  }
620  }
621  } else {
622  qWarning() << "icc profile is empty";
623  }
624 
625  } else if (profileType == heif_color_profile_type_nclx) {
626  struct heif_color_profile_nclx *nclx = nullptr;
627  err = heif_image_handle_get_nclx_color_profile(handle.get_raw_image_handle(), &nclx);
628  if (err.code || !nclx) {
629  qWarning() << "nclx profile loading failed";
630  } else {
631  const QPointF redPoint(nclx->color_primary_red_x, nclx->color_primary_red_y);
632  const QPointF greenPoint(nclx->color_primary_green_x, nclx->color_primary_green_y);
633  const QPointF bluePoint(nclx->color_primary_blue_x, nclx->color_primary_blue_y);
634  const QPointF whitePoint(nclx->color_primary_white_x, nclx->color_primary_white_y);
635 
636  QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom;
637  float q_trc_gamma = 0.0f;
638 
639  switch (nclx->transfer_characteristics) {
640  case 4:
641  q_trc = QColorSpace::TransferFunction::Gamma;
642  q_trc_gamma = 2.2f;
643  break;
644  case 5:
645  q_trc = QColorSpace::TransferFunction::Gamma;
646  q_trc_gamma = 2.8f;
647  break;
648  case 8:
649  q_trc = QColorSpace::TransferFunction::Linear;
650  break;
651  case 2:
652  case 13:
653  q_trc = QColorSpace::TransferFunction::SRgb;
654  break;
655  default:
656  qWarning("CICP color_primaries: %d, transfer_characteristics: %d\nThe colorspace is unsupported by this plug-in yet.",
657  nclx->color_primaries,
658  nclx->transfer_characteristics);
659  q_trc = QColorSpace::TransferFunction::SRgb;
660  break;
661  }
662 
663  if (q_trc != QColorSpace::TransferFunction::Custom) { // we create new colorspace using Qt
664  switch (nclx->color_primaries) {
665  case 1:
666  case 2:
667  m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma));
668  break;
669  case 12:
670  m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma));
671  break;
672  default:
673  m_current_image.setColorSpace(QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma));
674  break;
675  }
676  }
677  heif_nclx_color_profile_free(nclx);
678 
679  if (!m_current_image.colorSpace().isValid()) {
680  qWarning() << "HEIC plugin created invalid QColorSpace from NCLX!";
681  }
682  }
683 
684  } else {
685  m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb));
686  }
687 
688  } catch (const heif::Error &err) {
689  m_parseState = ParseHeicError;
690  qWarning() << "libheif error:" << err.get_message().c_str();
691  return false;
692  }
693 
694  m_parseState = ParseHeicSuccess;
695  return true;
696 }
697 
698 QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
699 {
700  if (format == "heif" || format == "heic") {
701  Capabilities format_cap;
702  if (heif_have_decoder_for_format(heif_compression_HEVC)) {
703  format_cap |= CanRead;
704  }
705  if (heif_have_encoder_for_format(heif_compression_HEVC)) {
706  format_cap |= CanWrite;
707  }
708  return format_cap;
709  }
710  if (!format.isEmpty()) {
711  return {};
712  }
713  if (!device->isOpen()) {
714  return {};
715  }
716 
717  Capabilities cap;
718  if (device->isReadable() && HEIFHandler::canRead(device) && heif_have_decoder_for_format(heif_compression_HEVC)) {
719  cap |= CanRead;
720  }
721 
722  if (device->isWritable() && heif_have_encoder_for_format(heif_compression_HEVC)) {
723  cap |= CanWrite;
724  }
725  return cap;
726 }
727 
728 QImageIOHandler *HEIFPlugin::create(QIODevice *device, const QByteArray &format) const
729 {
730  QImageIOHandler *handler = new HEIFHandler;
731  handler->setDevice(device);
732  handler->setFormat(format);
733  return handler;
734 }
KGUIADDONS_EXPORT qreal chroma(const QColor &)
QImage convertToFormat(QImage::Format format, Qt::ImageConversionFlags flags) const &const
bool isWritable() const const
const uchar * constScanLine(int i) const const
int depth() const const
bool isEmpty() const const
bool hasAlphaChannel() const const
bool isNull() const const
bool isReadable() const const
qint64 peek(char *data, qint64 maxSize)
QColorSpace fromIccProfile(const QByteArray &iccProfile)
int toInt(bool *ok) const const
int width() const const
const char * constData() const const
QByteArray readAll()
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
void setDevice(QIODevice *device)
QByteArray::iterator begin()
bool isOpen() const const
QColorSpace colorSpace() const const
QByteArray iccProfile() const const
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
QByteArray::iterator end()
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.