KImageFormats

heif.cpp
1/*
2 High Efficiency Image File Format (HEIF) support for QImage.
3
4 SPDX-FileCopyrightText: 2020 Sirius Bakke <sirius@bakke.co>
5 SPDX-FileCopyrightText: 2021 Daniel Novomesky <dnovomesky@gmail.com>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "heif_p.h"
11#include "util_p.h"
12#include <libheif/heif.h>
13
14#include <QColorSpace>
15#include <QDebug>
16#include <QPointF>
17#include <QSysInfo>
18#include <limits>
19#include <string.h>
20
21size_t HEIFHandler::m_initialized_count = 0;
22bool HEIFHandler::m_plugins_queried = false;
23bool HEIFHandler::m_heif_decoder_available = false;
24bool HEIFHandler::m_heif_encoder_available = false;
25bool HEIFHandler::m_hej2_decoder_available = false;
26bool HEIFHandler::m_avci_decoder_available = false;
27
28extern "C" {
29static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx */, const void *data, size_t size, void *userdata)
30{
31 heif_error error;
32 error.code = heif_error_Ok;
33 error.subcode = heif_suberror_Unspecified;
34 error.message = "Success";
35
36 if (!userdata || !data || size == 0) {
37 error.code = heif_error_Usage_error;
38 error.subcode = heif_suberror_Null_pointer_argument;
39 error.message = "Wrong parameters!";
40 return error;
41 }
42
43 QIODevice *ioDevice = static_cast<QIODevice *>(userdata);
44 qint64 bytesWritten = ioDevice->write(static_cast<const char *>(data), size);
45
46 if (bytesWritten < static_cast<qint64>(size)) {
47 error.code = heif_error_Encoding_error;
48 error.message = "Bytes written to QIODevice are smaller than input data size";
49 error.subcode = heif_suberror_Cannot_write_output_data;
50 }
51
52 return error;
53}
54}
55
56HEIFHandler::HEIFHandler()
57 : m_parseState(ParseHeicNotParsed)
58 , m_quality(100)
59{
60}
61
62bool HEIFHandler::canRead() const
63{
64 if (m_parseState == ParseHeicNotParsed) {
65 QIODevice *dev = device();
66 if (dev) {
67 const QByteArray header = dev->peek(28);
68
69 if (HEIFHandler::isSupportedBMFFType(header)) {
70 setFormat("heif");
71 return true;
72 }
73
74 if (HEIFHandler::isSupportedHEJ2(header)) {
75 setFormat("hej2");
76 return true;
77 }
78
79 if (HEIFHandler::isSupportedAVCI(header)) {
80 setFormat("avci");
81 return true;
82 }
83 }
84 return false;
85 }
86
87 if (m_parseState != ParseHeicError) {
88 return true;
89 }
90 return false;
91}
92
93bool HEIFHandler::read(QImage *outImage)
94{
95 if (!ensureParsed()) {
96 return false;
97 }
98
99 *outImage = m_current_image;
100 return true;
101}
102
103bool HEIFHandler::write(const QImage &image)
104{
105 if (image.format() == QImage::Format_Invalid || image.isNull()) {
106 qWarning("No image data to save");
107 return false;
108 }
109
110#if LIBHEIF_HAVE_VERSION(1, 13, 0)
111 startHeifLib();
112#endif
113
114 bool success = write_helper(image);
115
116#if LIBHEIF_HAVE_VERSION(1, 13, 0)
117 finishHeifLib();
118#endif
119
120 return success;
121}
122
123bool HEIFHandler::write_helper(const QImage &image)
124{
125 int save_depth; // 8 or 10bit per channel
126 QImage::Format tmpformat; // format for temporary image
127 const bool save_alpha = image.hasAlphaChannel();
128
129 switch (image.format()) {
138 save_depth = 10;
139 break;
140 default:
141 if (image.depth() > 32) {
142 save_depth = 10;
143 } else {
144 save_depth = 8;
145 }
146 break;
147 }
148
149 heif_chroma chroma;
150 if (save_depth > 8) {
151 if (save_alpha) {
152 tmpformat = QImage::Format_RGBA64;
153 chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE;
154 } else {
155 tmpformat = QImage::Format_RGBX64;
156 chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE;
157 }
158 } else {
159 if (save_alpha) {
160 tmpformat = QImage::Format_RGBA8888;
161 chroma = heif_chroma_interleaved_RGBA;
162 } else {
163 tmpformat = QImage::Format_RGB888;
164 chroma = heif_chroma_interleaved_RGB;
165 }
166 }
167
168#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
169 QImage tmpimage;
170 auto cs = image.colorSpace();
171 if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && image.format() == QImage::Format_CMYK8888) {
172 tmpimage = image.convertedToColorSpace(QColorSpace(QColorSpace::SRgb), tmpformat);
173 } else if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Gray
175 QColorSpace::TransferFunction trc_new = cs.transferFunction();
176 float gamma_new = cs.gamma();
177 if (trc_new == QColorSpace::TransferFunction::Custom) {
178 trc_new = QColorSpace::TransferFunction::SRgb;
179 }
180 tmpimage = image.convertedToColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, trc_new, gamma_new), tmpformat);
181 } else {
182 tmpimage = image.convertToFormat(tmpformat);
183 }
184#else
185 QImage tmpimage = image.convertToFormat(tmpformat);
186#endif
187
188 struct heif_context *context = heif_context_alloc();
189 struct heif_error err;
190 struct heif_image *h_image = nullptr;
191
192 err = heif_image_create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma, &h_image);
193 if (err.code) {
194 qWarning() << "heif_image_create error:" << err.message;
195 heif_context_free(context);
196 return false;
197 }
198
199 QByteArray iccprofile = tmpimage.colorSpace().iccProfile();
200 if (iccprofile.size() > 0) {
201 heif_image_set_raw_color_profile(h_image, "prof", iccprofile.constData(), iccprofile.size());
202 }
203
204 heif_image_add_plane(h_image, heif_channel_interleaved, image.width(), image.height(), save_depth);
205 int stride = 0;
206 uint8_t *const dst = heif_image_get_plane(h_image, heif_channel_interleaved, &stride);
207 size_t rowbytes;
208
209 switch (save_depth) {
210 case 10:
211 if (save_alpha) {
212 for (int y = 0; y < tmpimage.height(); y++) {
213 const uint16_t *src_word = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
214 uint16_t *dest_word = reinterpret_cast<uint16_t *>(dst + (y * stride));
215 for (int x = 0; x < tmpimage.width(); x++) {
216 int tmp_pixelval;
217 // R
218 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
219 *dest_word = qBound(0, tmp_pixelval, 1023);
220 src_word++;
221 dest_word++;
222 // G
223 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
224 *dest_word = qBound(0, tmp_pixelval, 1023);
225 src_word++;
226 dest_word++;
227 // B
228 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
229 *dest_word = qBound(0, tmp_pixelval, 1023);
230 src_word++;
231 dest_word++;
232 // A
233 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
234 *dest_word = qBound(0, tmp_pixelval, 1023);
235 src_word++;
236 dest_word++;
237 }
238 }
239 } else { // no alpha channel
240 for (int y = 0; y < tmpimage.height(); y++) {
241 const uint16_t *src_word = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
242 uint16_t *dest_word = reinterpret_cast<uint16_t *>(dst + (y * stride));
243 for (int x = 0; x < tmpimage.width(); x++) {
244 int tmp_pixelval;
245 // R
246 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
247 *dest_word = qBound(0, tmp_pixelval, 1023);
248 src_word++;
249 dest_word++;
250 // G
251 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
252 *dest_word = qBound(0, tmp_pixelval, 1023);
253 src_word++;
254 dest_word++;
255 // B
256 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f);
257 *dest_word = qBound(0, tmp_pixelval, 1023);
258 src_word++;
259 dest_word++;
260 // X
261 src_word++;
262 }
263 }
264 }
265 break;
266 case 8:
267 rowbytes = save_alpha ? (tmpimage.width() * 4) : (tmpimage.width() * 3);
268 for (int y = 0; y < tmpimage.height(); y++) {
269 memcpy(dst + (y * stride), tmpimage.constScanLine(y), rowbytes);
270 }
271 break;
272 default:
273 qWarning() << "Unsupported depth:" << save_depth;
274 heif_image_release(h_image);
275 heif_context_free(context);
276 return false;
277 break;
278 }
279
280 struct heif_encoder *encoder = nullptr;
281 err = heif_context_get_encoder_for_format(context, heif_compression_HEVC, &encoder);
282 if (err.code) {
283 qWarning() << "Unable to get an encoder instance:" << err.message;
284 heif_image_release(h_image);
285 heif_context_free(context);
286 return false;
287 }
288
289 heif_encoder_set_lossy_quality(encoder, m_quality);
290 if (m_quality > 90) {
291 if (m_quality == 100) {
292 heif_encoder_set_lossless(encoder, true);
293 }
294 heif_encoder_set_parameter_string(encoder, "chroma", "444");
295 }
296
297 struct heif_encoding_options *encoder_options = heif_encoding_options_alloc();
298 encoder_options->save_alpha_channel = save_alpha;
299
300 if ((tmpimage.width() % 2 == 1) || (tmpimage.height() % 2 == 1)) {
301 qWarning() << "Image has odd dimension!\nUse even-numbered dimension(s) for better compatibility with other HEIF implementations.";
302 if (save_alpha) {
303 // This helps to save alpha channel when image has odd dimension
304 encoder_options->macOS_compatibility_workaround = 0;
305 }
306 }
307
308 err = heif_context_encode_image(context, h_image, encoder, encoder_options, nullptr);
309
310 if (encoder_options) {
311 heif_encoding_options_free(encoder_options);
312 }
313
314 if (err.code) {
315 qWarning() << "heif_context_encode_image failed:" << err.message;
316 heif_encoder_release(encoder);
317 heif_image_release(h_image);
318 heif_context_free(context);
319 return false;
320 }
321
322 struct heif_writer writer;
323 writer.writer_api_version = 1;
324 writer.write = heifhandler_write_callback;
325
326 err = heif_context_write(context, &writer, device());
327
328 heif_encoder_release(encoder);
329 heif_image_release(h_image);
330
331 if (err.code) {
332 qWarning() << "Writing HEIF image failed:" << err.message;
333 heif_context_free(context);
334 return false;
335 }
336
337 heif_context_free(context);
338 return true;
339}
340
341bool HEIFHandler::isSupportedBMFFType(const QByteArray &header)
342{
343 if (header.size() < 28) {
344 return false;
345 }
346
347 const char *buffer = header.constData();
348 if (memcmp(buffer + 4, "ftyp", 4) == 0) {
349 if (memcmp(buffer + 8, "heic", 4) == 0) {
350 return true;
351 }
352 if (memcmp(buffer + 8, "heis", 4) == 0) {
353 return true;
354 }
355 if (memcmp(buffer + 8, "heix", 4) == 0) {
356 return true;
357 }
358
359 /* we want to avoid loading AVIF files via this plugin */
360 if (memcmp(buffer + 8, "mif1", 4) == 0) {
361 for (int offset = 16; offset <= 24; offset += 4) {
362 if (memcmp(buffer + offset, "avif", 4) == 0) {
363 return false;
364 }
365 }
366 return true;
367 }
368
369 if (memcmp(buffer + 8, "mif2", 4) == 0) {
370 return true;
371 }
372 if (memcmp(buffer + 8, "msf1", 4) == 0) {
373 return true;
374 }
375 }
376
377 return false;
378}
379
380bool HEIFHandler::isSupportedHEJ2(const QByteArray &header)
381{
382 if (header.size() < 28) {
383 return false;
384 }
385
386 const char *buffer = header.constData();
387 if (memcmp(buffer + 4, "ftypj2ki", 8) == 0) {
388 return true;
389 }
390
391 return false;
392}
393
394bool HEIFHandler::isSupportedAVCI(const QByteArray &header)
395{
396 if (header.size() < 28) {
397 return false;
398 }
399
400 const char *buffer = header.constData();
401 if (memcmp(buffer + 4, "ftypavci", 8) == 0) {
402 return true;
403 }
404
405 return false;
406}
407
408QVariant HEIFHandler::option(ImageOption option) const
409{
410 if (option == Quality) {
411 return m_quality;
412 }
413
414 if (!supportsOption(option) || !ensureParsed()) {
415 return QVariant();
416 }
417
418 switch (option) {
419 case Size:
420 return m_current_image.size();
421 break;
422 default:
423 return QVariant();
424 break;
425 }
426}
427
428void HEIFHandler::setOption(ImageOption option, const QVariant &value)
429{
430 switch (option) {
431 case Quality:
432 m_quality = value.toInt();
433 if (m_quality > 100) {
434 m_quality = 100;
435 } else if (m_quality < 0) {
436 m_quality = 100;
437 }
438 break;
439 default:
440 QImageIOHandler::setOption(option, value);
441 break;
442 }
443}
444
445bool HEIFHandler::supportsOption(ImageOption option) const
446{
447 return option == Quality || option == Size;
448}
449
450bool HEIFHandler::ensureParsed() const
451{
452 if (m_parseState == ParseHeicSuccess) {
453 return true;
454 }
455 if (m_parseState == ParseHeicError) {
456 return false;
457 }
458
459 HEIFHandler *that = const_cast<HEIFHandler *>(this);
460
461#if LIBHEIF_HAVE_VERSION(1, 13, 0)
462 startHeifLib();
463#endif
464
465 bool success = that->ensureDecoder();
466
467#if LIBHEIF_HAVE_VERSION(1, 13, 0)
468 finishHeifLib();
469#endif
470 return success;
471}
472
473bool HEIFHandler::ensureDecoder()
474{
475 if (m_parseState != ParseHeicNotParsed) {
476 if (m_parseState == ParseHeicSuccess) {
477 return true;
478 }
479 return false;
480 }
481
482 const QByteArray buffer = device()->readAll();
483 if (!HEIFHandler::isSupportedBMFFType(buffer) && !HEIFHandler::isSupportedHEJ2(buffer) && !HEIFHandler::isSupportedAVCI(buffer)) {
484 m_parseState = ParseHeicError;
485 return false;
486 }
487
488 struct heif_context *ctx = heif_context_alloc();
489 struct heif_error err = heif_context_read_from_memory(ctx, static_cast<const void *>(buffer.constData()), buffer.size(), nullptr);
490
491 if (err.code) {
492 qWarning() << "heif_context_read_from_memory error:" << err.message;
493 heif_context_free(ctx);
494 m_parseState = ParseHeicError;
495 return false;
496 }
497
498 struct heif_image_handle *handle = nullptr;
499 err = heif_context_get_primary_image_handle(ctx, &handle);
500 if (err.code) {
501 qWarning() << "heif_context_get_primary_image_handle error:" << err.message;
502 heif_context_free(ctx);
503 m_parseState = ParseHeicError;
504 return false;
505 }
506
507 if ((heif_image_handle_get_width(handle) == 0) || (heif_image_handle_get_height(handle) == 0)) {
508 m_parseState = ParseHeicError;
509 heif_image_handle_release(handle);
510 heif_context_free(ctx);
511 qWarning() << "HEIC image has zero dimension";
512 return false;
513 }
514
515 const int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle);
516
517 if (bit_depth < 8) {
518 m_parseState = ParseHeicError;
519 heif_image_handle_release(handle);
520 heif_context_free(ctx);
521 qWarning() << "HEIF image with undefined or unsupported bit depth.";
522 return false;
523 }
524
525 const bool hasAlphaChannel = heif_image_handle_has_alpha_channel(handle);
526 heif_chroma chroma;
527
528 QImage::Format target_image_format;
529
530 if (bit_depth == 10 || bit_depth == 12) {
531 if (hasAlphaChannel) {
532 chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE;
533 target_image_format = QImage::Format_RGBA64;
534 } else {
535 chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE;
536 target_image_format = QImage::Format_RGBX64;
537 }
538 } else if (bit_depth == 8) {
539 if (hasAlphaChannel) {
540 chroma = heif_chroma_interleaved_RGBA;
541 target_image_format = QImage::Format_ARGB32;
542 } else {
543 chroma = heif_chroma_interleaved_RGB;
544 target_image_format = QImage::Format_RGB32;
545 }
546 } else {
547 m_parseState = ParseHeicError;
548 heif_image_handle_release(handle);
549 heif_context_free(ctx);
550 qWarning() << "Unsupported bit depth:" << bit_depth;
551 return false;
552 }
553
554 struct heif_decoding_options *decoder_option = heif_decoding_options_alloc();
555
556#if LIBHEIF_HAVE_VERSION(1, 13, 0)
557 decoder_option->strict_decoding = 1;
558#endif
559
560 struct heif_image *img = nullptr;
561 err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option);
562
563#if LIBHEIF_HAVE_VERSION(1, 13, 0)
564 if (err.code == heif_error_Invalid_input && err.subcode == heif_suberror_Unknown_NCLX_matrix_coefficients && img == nullptr && buffer.contains("Xiaomi")) {
565 qWarning() << "Non-standard HEIF image with invalid matrix_coefficients, probably made by a Xiaomi device!";
566
567 // second try to decode with strict decoding disabled
568 decoder_option->strict_decoding = 0;
569 err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option);
570 }
571#endif
572
573 if (decoder_option) {
574 heif_decoding_options_free(decoder_option);
575 }
576
577 if (err.code) {
578 qWarning() << "heif_decode_image error:" << err.message;
579 heif_image_handle_release(handle);
580 heif_context_free(ctx);
581 m_parseState = ParseHeicError;
582 return false;
583 }
584
585 const int imageWidth = heif_image_get_width(img, heif_channel_interleaved);
586 const int imageHeight = heif_image_get_height(img, heif_channel_interleaved);
587
588 QSize imageSize(imageWidth, imageHeight);
589
590 if (!imageSize.isValid()) {
591 heif_image_release(img);
592 heif_image_handle_release(handle);
593 heif_context_free(ctx);
594 m_parseState = ParseHeicError;
595 qWarning() << "HEIC image size invalid:" << imageSize;
596 return false;
597 }
598
599 int stride = 0;
600 const uint8_t *const src = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride);
601
602 if (!src || stride <= 0) {
603 heif_image_release(img);
604 heif_image_handle_release(handle);
605 heif_context_free(ctx);
606 m_parseState = ParseHeicError;
607 qWarning() << "HEIC data pixels information not valid!";
608 return false;
609 }
610
611 m_current_image = imageAlloc(imageSize, target_image_format);
612 if (m_current_image.isNull()) {
613 heif_image_release(img);
614 heif_image_handle_release(handle);
615 heif_context_free(ctx);
616 m_parseState = ParseHeicError;
617 qWarning() << "Unable to allocate memory!";
618 return false;
619 }
620
621 switch (bit_depth) {
622 case 12:
623 if (hasAlphaChannel) {
624 for (int y = 0; y < imageHeight; y++) {
625 const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
626 uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
627 for (int x = 0; x < imageWidth; x++) {
628 int tmpvalue;
629 // R
630 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
631 tmpvalue = qBound(0, tmpvalue, 65535);
632 *dest_data = (uint16_t)tmpvalue;
633 src_word++;
634 dest_data++;
635 // G
636 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
637 tmpvalue = qBound(0, tmpvalue, 65535);
638 *dest_data = (uint16_t)tmpvalue;
639 src_word++;
640 dest_data++;
641 // B
642 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
643 tmpvalue = qBound(0, tmpvalue, 65535);
644 *dest_data = (uint16_t)tmpvalue;
645 src_word++;
646 dest_data++;
647 // A
648 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
649 tmpvalue = qBound(0, tmpvalue, 65535);
650 *dest_data = (uint16_t)tmpvalue;
651 src_word++;
652 dest_data++;
653 }
654 }
655 } else { // no alpha channel
656 for (int y = 0; y < imageHeight; y++) {
657 const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
658 uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
659 for (int x = 0; x < imageWidth; x++) {
660 int tmpvalue;
661 // R
662 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
663 tmpvalue = qBound(0, tmpvalue, 65535);
664 *dest_data = (uint16_t)tmpvalue;
665 src_word++;
666 dest_data++;
667 // G
668 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
669 tmpvalue = qBound(0, tmpvalue, 65535);
670 *dest_data = (uint16_t)tmpvalue;
671 src_word++;
672 dest_data++;
673 // B
674 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f);
675 tmpvalue = qBound(0, tmpvalue, 65535);
676 *dest_data = (uint16_t)tmpvalue;
677 src_word++;
678 dest_data++;
679 // X = 0xffff
680 *dest_data = 0xffff;
681 dest_data++;
682 }
683 }
684 }
685 break;
686 case 10:
687 if (hasAlphaChannel) {
688 for (int y = 0; y < imageHeight; y++) {
689 const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
690 uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
691 for (int x = 0; x < imageWidth; x++) {
692 int tmpvalue;
693 // R
694 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
695 tmpvalue = qBound(0, tmpvalue, 65535);
696 *dest_data = (uint16_t)tmpvalue;
697 src_word++;
698 dest_data++;
699 // G
700 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
701 tmpvalue = qBound(0, tmpvalue, 65535);
702 *dest_data = (uint16_t)tmpvalue;
703 src_word++;
704 dest_data++;
705 // B
706 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
707 tmpvalue = qBound(0, tmpvalue, 65535);
708 *dest_data = (uint16_t)tmpvalue;
709 src_word++;
710 dest_data++;
711 // A
712 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
713 tmpvalue = qBound(0, tmpvalue, 65535);
714 *dest_data = (uint16_t)tmpvalue;
715 src_word++;
716 dest_data++;
717 }
718 }
719 } else { // no alpha channel
720 for (int y = 0; y < imageHeight; y++) {
721 const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride));
722 uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y));
723 for (int x = 0; x < imageWidth; x++) {
724 int tmpvalue;
725 // R
726 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
727 tmpvalue = qBound(0, tmpvalue, 65535);
728 *dest_data = (uint16_t)tmpvalue;
729 src_word++;
730 dest_data++;
731 // G
732 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
733 tmpvalue = qBound(0, tmpvalue, 65535);
734 *dest_data = (uint16_t)tmpvalue;
735 src_word++;
736 dest_data++;
737 // B
738 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f);
739 tmpvalue = qBound(0, tmpvalue, 65535);
740 *dest_data = (uint16_t)tmpvalue;
741 src_word++;
742 dest_data++;
743 // X = 0xffff
744 *dest_data = 0xffff;
745 dest_data++;
746 }
747 }
748 }
749 break;
750 case 8:
751 if (hasAlphaChannel) {
752 for (int y = 0; y < imageHeight; y++) {
753 const uint8_t *src_byte = src + (y * stride);
754 uint32_t *dest_pixel = reinterpret_cast<uint32_t *>(m_current_image.scanLine(y));
755 for (int x = 0; x < imageWidth; x++) {
756 int red = *src_byte++;
757 int green = *src_byte++;
758 int blue = *src_byte++;
759 int alpha = *src_byte++;
760 *dest_pixel = qRgba(red, green, blue, alpha);
761 dest_pixel++;
762 }
763 }
764 } else { // no alpha channel
765 for (int y = 0; y < imageHeight; y++) {
766 const uint8_t *src_byte = src + (y * stride);
767 uint32_t *dest_pixel = reinterpret_cast<uint32_t *>(m_current_image.scanLine(y));
768 for (int x = 0; x < imageWidth; x++) {
769 int red = *src_byte++;
770 int green = *src_byte++;
771 int blue = *src_byte++;
772 *dest_pixel = qRgb(red, green, blue);
773 dest_pixel++;
774 }
775 }
776 }
777 break;
778 default:
779 heif_image_release(img);
780 heif_image_handle_release(handle);
781 heif_context_free(ctx);
782 m_parseState = ParseHeicError;
783 qWarning() << "Unsupported bit depth:" << bit_depth;
784 return false;
785 break;
786 }
787
788 heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle);
789 if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) {
790 size_t rawProfileSize = heif_image_handle_get_raw_color_profile_size(handle);
791 if (rawProfileSize > 0 && rawProfileSize < std::numeric_limits<int>::max()) {
792 QByteArray ba(rawProfileSize, 0);
793 err = heif_image_handle_get_raw_color_profile(handle, ba.data());
794 if (err.code) {
795 qWarning() << "icc profile loading failed";
796 } else {
797 m_current_image.setColorSpace(QColorSpace::fromIccProfile(ba));
798 if (!m_current_image.colorSpace().isValid()) {
799 qWarning() << "HEIC image has Qt-unsupported or invalid ICC profile!";
800 }
801 }
802 } else {
803 qWarning() << "icc profile is empty or above limits";
804 }
805
806 } else if (profileType == heif_color_profile_type_nclx) {
807 struct heif_color_profile_nclx *nclx = nullptr;
808 err = heif_image_handle_get_nclx_color_profile(handle, &nclx);
809 if (err.code || !nclx) {
810 qWarning() << "nclx profile loading failed";
811 } else {
812 const QPointF redPoint(nclx->color_primary_red_x, nclx->color_primary_red_y);
813 const QPointF greenPoint(nclx->color_primary_green_x, nclx->color_primary_green_y);
814 const QPointF bluePoint(nclx->color_primary_blue_x, nclx->color_primary_blue_y);
815 const QPointF whitePoint(nclx->color_primary_white_x, nclx->color_primary_white_y);
816
817 QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom;
818 float q_trc_gamma = 0.0f;
819
820 switch (nclx->transfer_characteristics) {
821 case 4:
822 q_trc = QColorSpace::TransferFunction::Gamma;
823 q_trc_gamma = 2.2f;
824 break;
825 case 5:
826 q_trc = QColorSpace::TransferFunction::Gamma;
827 q_trc_gamma = 2.8f;
828 break;
829 case 8:
830 q_trc = QColorSpace::TransferFunction::Linear;
831 break;
832 case 2:
833 case 13:
834 q_trc = QColorSpace::TransferFunction::SRgb;
835 break;
836#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
837 case 16:
838 q_trc = QColorSpace::TransferFunction::St2084;
839 break;
840 case 18:
841 q_trc = QColorSpace::TransferFunction::Hlg;
842 break;
843#endif
844 default:
845 qWarning("CICP color_primaries: %d, transfer_characteristics: %d\nThe colorspace is unsupported by this plug-in yet.",
846 nclx->color_primaries,
847 nclx->transfer_characteristics);
848 q_trc = QColorSpace::TransferFunction::SRgb;
849 break;
850 }
851
852 if (q_trc != QColorSpace::TransferFunction::Custom) { // we create new colorspace using Qt
853 switch (nclx->color_primaries) {
854 case 1:
855 case 2:
856 m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma));
857 break;
858 case 12:
859 m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma));
860 break;
861 default:
862 m_current_image.setColorSpace(QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma));
863 break;
864 }
865 }
866 heif_nclx_color_profile_free(nclx);
867
868 if (!m_current_image.colorSpace().isValid()) {
869 qWarning() << "HEIC plugin created invalid QColorSpace from NCLX!";
870 }
871 }
872
873 } else {
874 m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb));
875 }
876
877 heif_image_release(img);
878 heif_image_handle_release(handle);
879 heif_context_free(ctx);
880 m_parseState = ParseHeicSuccess;
881 return true;
882}
883
884bool HEIFHandler::isHeifDecoderAvailable()
885{
886 HEIFHandler::queryHeifLib();
887
888 return m_heif_decoder_available;
889}
890
891bool HEIFHandler::isHeifEncoderAvailable()
892{
893 HEIFHandler::queryHeifLib();
894
895 return m_heif_encoder_available;
896}
897
898bool HEIFHandler::isHej2DecoderAvailable()
899{
900 HEIFHandler::queryHeifLib();
901
902 return m_hej2_decoder_available;
903}
904
905bool HEIFHandler::isAVCIDecoderAvailable()
906{
907 HEIFHandler::queryHeifLib();
908
909 return m_avci_decoder_available;
910}
911
912void HEIFHandler::queryHeifLib()
913{
914 QMutexLocker locker(&getHEIFHandlerMutex());
915
916 if (!m_plugins_queried) {
917#if LIBHEIF_HAVE_VERSION(1, 13, 0)
918 if (m_initialized_count == 0) {
919 heif_init(nullptr);
920 }
921#endif
922
923 m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC);
924 m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC);
925#if LIBHEIF_HAVE_VERSION(1, 13, 0)
926 m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000);
927#endif
928#if LIBHEIF_HAVE_VERSION(1, 19, 0)
929 m_avci_decoder_available = heif_have_decoder_for_format(heif_compression_AVC);
930#endif
931 m_plugins_queried = true;
932
933#if LIBHEIF_HAVE_VERSION(1, 13, 0)
934 if (m_initialized_count == 0) {
935 heif_deinit();
936 }
937#endif
938 }
939}
940
941void HEIFHandler::startHeifLib()
942{
943#if LIBHEIF_HAVE_VERSION(1, 13, 0)
944 QMutexLocker locker(&getHEIFHandlerMutex());
945
946 if (m_initialized_count == 0) {
947 heif_init(nullptr);
948 }
949
950 m_initialized_count++;
951#endif
952}
953
954void HEIFHandler::finishHeifLib()
955{
956#if LIBHEIF_HAVE_VERSION(1, 13, 0)
957 QMutexLocker locker(&getHEIFHandlerMutex());
958
959 if (m_initialized_count == 0) {
960 return;
961 }
962
963 m_initialized_count--;
964 if (m_initialized_count == 0) {
965 heif_deinit();
966 }
967
968#endif
969}
970
971QMutex &HEIFHandler::getHEIFHandlerMutex()
972{
973 static QMutex heif_handler_mutex;
974 return heif_handler_mutex;
975}
976
977QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
978{
979 if (format == "heif" || format == "heic") {
980 Capabilities format_cap;
981 if (HEIFHandler::isHeifDecoderAvailable()) {
982 format_cap |= CanRead;
983 }
984 if (HEIFHandler::isHeifEncoderAvailable()) {
985 format_cap |= CanWrite;
986 }
987 return format_cap;
988 }
989
990 if (format == "hej2") {
991 Capabilities format_cap;
992 if (HEIFHandler::isHej2DecoderAvailable()) {
993 format_cap |= CanRead;
994 }
995 return format_cap;
996 }
997
998 if (format == "avci") {
999 Capabilities format_cap;
1000 if (HEIFHandler::isAVCIDecoderAvailable()) {
1001 format_cap |= CanRead;
1002 }
1003 return format_cap;
1004 }
1005
1006 if (!format.isEmpty()) {
1007 return {};
1008 }
1009 if (!device->isOpen()) {
1010 return {};
1011 }
1012
1013 Capabilities cap;
1014 if (device->isReadable()) {
1015 const QByteArray header = device->peek(28);
1016
1017 if ((HEIFHandler::isSupportedBMFFType(header) && HEIFHandler::isHeifDecoderAvailable())
1018 || (HEIFHandler::isSupportedHEJ2(header) && HEIFHandler::isHej2DecoderAvailable())
1019 || (HEIFHandler::isSupportedAVCI(header) && HEIFHandler::isAVCIDecoderAvailable())) {
1020 cap |= CanRead;
1021 }
1022 }
1023
1024 if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) {
1025 cap |= CanWrite;
1026 }
1027 return cap;
1028}
1029
1030QImageIOHandler *HEIFPlugin::create(QIODevice *device, const QByteArray &format) const
1031{
1032 QImageIOHandler *handler = new HEIFHandler;
1033 handler->setDevice(device);
1034 handler->setFormat(format);
1035 return handler;
1036}
1037
1038#include "moc_heif_p.cpp"
KGUIADDONS_EXPORT qreal chroma(const QColor &)
QFlags< Capability > Capabilities
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
const char * constData() const const
bool contains(QByteArrayView bv) const const
bool isEmpty() const const
qsizetype size() const const
QColorSpace fromIccProfile(const QByteArray &iccProfile)
QByteArray iccProfile() const const
QColorSpace colorSpace() const const
const uchar * constScanLine(int i) const const
QImage convertToFormat(Format format, Qt::ImageConversionFlags flags) &&
QImage convertedToColorSpace(const QColorSpace &colorSpace) const const
int depth() const const
Format format() const const
bool hasAlphaChannel() const const
int height() const const
bool isNull() 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)
qint64 write(const QByteArray &data)
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.