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

KDE's Doxygen guidelines are available online.