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

KDE's Doxygen guidelines are available online.