Krita Source Code Documentation
Loading...
Searching...
No Matches
HeifImport.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2018 Dirk Farin <farin@struktur.de>
3 * SPDX-FileCopyrightText: 2020-2021 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
4 * SPDX-FileCopyrightText: 2021 Daniel Novomesky <dnovomesky@gmail.com>
5 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10#include "HeifImport.h"
11#include "HeifError.h"
12
13#include <QBuffer>
14
15#include <kpluginfactory.h>
16#include <libheif/heif.h>
17#include <libheif/heif_cxx.h>
18
19#include <KisDocument.h>
21#include <KoColorProfile.h>
22#include <KoColorSpace.h>
23#include <KoColorSpaceEngine.h>
26#include <kis_group_layer.h>
27#include <kis_image.h>
28#include <kis_iterator_ng.h>
30#include <kis_meta_data_entry.h>
31#include <kis_meta_data_store.h>
32#include <kis_meta_data_value.h>
33#include <kis_node.h>
34#include <kis_paint_device.h>
35#include <kis_paint_layer.h>
36#include <kis_transaction.h>
37#include <qmutex.h>
38
40
41using heif::Error;
42
43K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_heif_import.json", registerPlugin<HeifImport>();)
44
45HeifImport::HeifImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
46{
47}
48
52
53
54class Reader_QIODevice : public heif::Context::Reader {
55public:
56 Reader_QIODevice(QIODevice *device)
57 : m_device(device)
58 , m_total_length(m_device->bytesAvailable())
59 {
60 }
61
62 int64_t get_position() const override
63 {
64 return m_device->pos();
65 }
66 int read(void *data, size_t size) override
67 {
68 qint64 readSize =
69 m_device->read(static_cast<char *>(data), static_cast<qint64>(size));
70 return (readSize > 0 && readSize != static_cast<qint64>(size));
71 }
72 int seek(int64_t position) override
73 {
74 return !m_device->seek(position);
75 }
76 heif_reader_grow_status wait_for_file_size(int64_t target_size) override
77 {
78 return (target_size > m_total_length)
79 ? heif_reader_grow_status_size_beyond_eof
80 : heif_reader_grow_status_size_reached;
81 }
82
83private:
84 QIODevice* m_device;
86};
87
88#if LIBHEIF_HAVE_VERSION(1, 13, 0)
89class Q_DECL_HIDDEN HeifLock
90{
91public:
92 HeifLock()
93 : p()
94 {
95 heif_init(&p);
96 }
97
98 ~HeifLock()
99 {
100 heif_deinit();
101 }
102
103private:
104 heif_init_params p;
105};
106#endif
107
108namespace Planar
109{
110inline auto readPlanarLayer(const int luma,
111 LinearizePolicy policy,
112 bool applyOOTF,
113 bool hasAlpha,
114 const int width,
115 const int height,
116 const uint8_t *imgR,
117 const int strideR,
118 const uint8_t *imgG,
119 const int strideG,
120 const uint8_t *imgB,
121 const int strideB,
122 const uint8_t *imgA,
123 const int strideA,
125 float displayGamma,
126 float displayNits,
127 const KoColorSpace *colorSpace)
128{
129 return createOptimizedClass<readLayerImpl>(luma,
130 policy,
131 applyOOTF,
132 hasAlpha,
133 width,
134 height,
135 imgR,
136 strideR,
137 imgG,
138 strideG,
139 imgB,
140 strideB,
141 imgA,
142 strideA,
143 it,
144 displayGamma,
145 displayNits,
146 colorSpace);
147}
148} // namespace Planar
149
150namespace HDR
151{
152template<typename... Args>
153inline auto readInterleavedLayer(const int luma,
154 LinearizePolicy linearizePolicy,
155 bool applyOOTF,
156 const int channels,
157 const int width,
158 const int height,
159 const uint8_t *img,
160 const int stride,
162 float displayGamma,
163 float displayNits,
164 const KoColorSpace *colorSpace)
165{
166 return createOptimizedClass<HDR::readLayerImpl>(luma,
167 linearizePolicy,
168 applyOOTF,
169 channels,
170 width,
171 height,
172 img,
173 stride,
174 it,
175 displayGamma,
176 displayNits,
177 colorSpace);
178}
179} // namespace HDR
180
181namespace SDR
182{
183template<typename... Args>
184inline auto readInterleavedLayer(LinearizePolicy linearizePolicy,
185 bool applyOOTF,
186 const int channels,
187 const int width,
188 const int height,
189 const uint8_t *img,
190 const int stride,
192 float displayGamma,
193 float displayNits,
194 const KoColorSpace *colorSpace)
195{
196 return createOptimizedClass<SDR::readLayerImpl>(linearizePolicy,
197 applyOOTF,
198 channels,
199 width,
200 height,
201 img,
202 stride,
203 it,
204 displayGamma,
205 displayNits,
206 colorSpace);
207}
208} // namespace SDR
209
211{
212#if LIBHEIF_HAVE_VERSION(1, 13, 0)
213 HeifLock lock;
214#endif
215
216#if LIBHEIF_HAVE_VERSION(1, 20, 2)
217 using HeifStrideType = size_t;
218 auto heifGetPlaneMethod = std::mem_fn(qNonConstOverload<heif_channel, HeifStrideType*>(&heif::Image::get_plane2));
219#elif LIBHEIF_HAVE_VERSION(1, 20, 0)
220 using HeifStrideType = size_t;
221 auto heifGetPlaneMethod = std::mem_fn(qNonConstOverload<heif_channel, HeifStrideType*>(&heif::Image::get_plane));
222#else
223 using HeifStrideType = int;
224 auto heifGetPlaneMethod = std::mem_fn(qNonConstOverload<heif_channel, HeifStrideType*>(&heif::Image::get_plane));
225#endif
226
227 // Wrap input stream into heif Reader object
228 Reader_QIODevice reader(io);
229
230 try {
231 heif::Context ctx;
232 ctx.read_from_reader(reader);
233
234 // decode primary image
235
236 heif::ImageHandle handle = ctx.get_primary_image_handle();
237
238 heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle.get_raw_image_handle());
239
240
241 heif::Image heifimage = handle.decode_image(heif_colorspace_undefined, heif_chroma_undefined);
242 heif_colorspace heifModel = heifimage.get_colorspace();
243 heif_chroma heifChroma = heifimage.get_chroma_format();
244 int luma = handle.get_luma_bits_per_pixel();
245 bool hasAlpha = handle.has_alpha_channel();
246
247 dbgFile << "loading heif" << heifModel << heifChroma << luma;
248
250 bool applyOOTF = true;
251 float displayGamma = 1.2f;
252 float displayNits = 1000.0;
253
254 struct heif_error err {
255 };
256 const KoColorProfile *profile = nullptr;
257 KoID colorDepth = Integer8BitsColorDepthID;
258 QString colorModel = RGBAColorModelID.id();
259
260 if (luma > 8) {
261 colorDepth = Integer16BitsColorDepthID;
262 }
263 // First, get the profile, because sometimes the image may be encoded in YCbCr and the embedded icc profile is graya.
264
265 if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) {
266 dbgFile << "profile type is icc profile";
267 // rICC are 'restricted' icc profiles, and are matrix shaper profiles
268 // that are either RGB or Grayscale, and are of the input or display types.
269 // They are from the JPEG2000 spec.
270
271 int rawProfileSize = (int) heif_image_handle_get_raw_color_profile_size(handle.get_raw_image_handle());
272 if (rawProfileSize > 0) {
273 QByteArray ba(rawProfileSize, 0);
274 err = heif_image_handle_get_raw_color_profile(handle.get_raw_image_handle(), ba.data());
275 if (err.code) {
276 dbgFile << "icc profile loading failed:" << err.message;
277 } else {
278 profile = KoColorSpaceRegistry::instance()->createColorProfile(colorModel, colorDepth.id(), ba);
280 colorModel = profile->colorModelID();
281 }
282 } else {
283 dbgFile << "icc profile is empty";
284 }
285 } else if (profileType == heif_color_profile_type_nclx) {
286 dbgFile << "profile type is nclx coding independent code points";
287 // NCLX parameters is a colorspace description used for videofiles.
288 // We generate a color profile for most entries, except that we cannot handle
289 // PQ, HLG or 428 in an icc profile, so we convert those on the fly to linear.
290
291 struct heif_color_profile_nclx *nclx = nullptr;
292 err = heif_image_handle_get_nclx_color_profile(handle.get_raw_image_handle(), &nclx);
293 if (err.code || !nclx) {
294 dbgFile << "nclx profile loading failed" << err.message;
295 } else {
296 TransferCharacteristics transferCharacteristic = TransferCharacteristics(nclx->transfer_characteristics);
297 ColorPrimaries primaries = ColorPrimaries(nclx->color_primaries);
298 if (nclx->transfer_characteristics == heif_transfer_characteristic_ITU_R_BT_2100_0_PQ) {
299 dbgFile << "linearizing from PQ";
300 linearizePolicy = LinearizePolicy::LinearFromPQ;
301 transferCharacteristic = TRC_LINEAR;
302 }
303 if (nclx->transfer_characteristics == heif_transfer_characteristic_ITU_R_BT_2100_0_HLG) {
304 dbgFile << "linearizing from HLG";
305 if (!document->fileBatchMode()) {
306 KisDlgHLGImport dlg(applyOOTF, displayGamma, displayNits);
307 dlg.exec();
308 applyOOTF = dlg.applyOOTF();
309 displayGamma = dlg.gamma();
310 displayNits = dlg.nominalPeakBrightness();
311 }
312 linearizePolicy = LinearizePolicy::LinearFromHLG;
313 transferCharacteristic = TRC_LINEAR;
314 }
315 if (nclx->transfer_characteristics == heif_transfer_characteristic_SMPTE_ST_428_1) {
316 dbgFile << "linearizing from SMPTE 428";
317 linearizePolicy = LinearizePolicy::LinearFromSMPTE428;
318 transferCharacteristic = TRC_LINEAR;
319 }
320
321 if (nclx->transfer_characteristics == heif_transfer_characteristic_IEC_61966_2_4 ||
322 nclx->transfer_characteristics == heif_transfer_characteristic_ITU_R_BT_1361) {
323 transferCharacteristic = TRC_ITU_R_BT_709_5;
324 }
325
326 const QVector<double> colorants = [&]() -> QVector<double> {
327 if (primaries == PRIMARIES_UNSPECIFIED) {
328 return {};
329 } else {
330 return {
331 static_cast<double>(nclx->color_primary_white_x),
332 static_cast<double>(nclx->color_primary_white_y),
333 static_cast<double>(nclx->color_primary_red_x),
334 static_cast<double>(nclx->color_primary_red_y),
335 static_cast<double>(nclx->color_primary_green_x),
336 static_cast<double>(nclx->color_primary_green_y),
337 static_cast<double>(nclx->color_primary_blue_x),
338 static_cast<double>(nclx->color_primary_blue_y)};
339 }
340 }();
341
342 profile = KoColorSpaceRegistry::instance()->profileFor(colorants,
343 primaries,
344 transferCharacteristic);
345
346 if (linearizePolicy != LinearizePolicy::KeepTheSame) {
347 colorDepth = Float32BitsColorDepthID;
348 }
349
350 heif_nclx_color_profile_free(nclx);
351 dbgFile << "nclx profile found" << profile->name();
352 }
353 } else {
354 dbgFile << "no profile found";
355 }
356
357 // Now, to figure out the correct chroma and color model.
358 if (heifModel == heif_colorspace_monochrome || colorModel == GrayAColorModelID.id()) {
359 // Grayscale image.
360 if (heifChroma != heif_chroma_monochrome && colorModel == GrayAColorModelID.id()) {
361 heifimage = handle.decode_image(heif_colorspace_YCbCr, heif_chroma_monochrome);
362 }
363 colorModel = GrayAColorModelID.id();
364 heifChroma = heif_chroma_monochrome;
365
366 }
367
368 // Get the default profile if we haven't found one up till now.
369 if (!profile) {
370 if (colorModel == RGBAColorModelID.id()) {
374 } else {
375 const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorModel, colorDepth.id());
376 QString profileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId);
377 profile = KoColorSpaceRegistry::instance()->profileByName(profileName);
378 }
379 }
380
381 const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth.id(), profile);
382
383 int width = handle.get_width();
384
385 int height = handle.get_height();
386
387 // convert HEIF image to Krita KisDocument
388
389 KisImageSP image = new KisImage(document->createUndoStore(), width, height, colorSpace,
390 "HEIF image");
391
392 KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8);
393
394 if (luma != 8 && luma != 10 && luma != 12) {
395 dbgFile << "unknown bitdepth" << luma;
396 }
397
398 if (heifChroma == heif_chroma_monochrome) {
399 dbgFile << "monochrome heif file, bits:" << luma;
400 HeifStrideType strideG = 0;
401 HeifStrideType strideA = 0;
402 const uint8_t *imgG = heifGetPlaneMethod(heifimage, heif_channel_Y, &strideG);
403 const uint8_t *imgA =
404 heifGetPlaneMethod(heifimage, heif_channel_Alpha, &strideA);
405 const int width = heifimage.get_width(heif_channel_Y);
406 const int height = heifimage.get_height(heif_channel_Y);
408 layer->paintDevice()->createHLineIteratorNG(0, 0, width);
409
411 hasAlpha,
412 width,
413 height,
414 it,
415 imgG,
416 imgA,
417 strideG,
418 strideA);
419 } else if (heifChroma == heif_chroma_444) {
420 dbgFile << "planar heif file, bits:" << luma;
421
422 HeifStrideType strideR = 0;
423 HeifStrideType strideG = 0;
424 HeifStrideType strideB = 0;
425 HeifStrideType strideA = 0;
426 const uint8_t* imgR = heifGetPlaneMethod(heifimage, heif_channel_R, &strideR);
427 const uint8_t* imgG = heifGetPlaneMethod(heifimage, heif_channel_G, &strideG);
428 const uint8_t* imgB = heifGetPlaneMethod(heifimage, heif_channel_B, &strideB);
429 const uint8_t *imgA =
430 heifGetPlaneMethod(heifimage, heif_channel_Alpha, &strideA);
431 KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, 0, width);
432
434 linearizePolicy,
435 applyOOTF,
436 hasAlpha,
437 width,
438 height,
439 imgR,
440 strideR,
441 imgG,
442 strideG,
443 imgB,
444 strideB,
445 imgA,
446 strideA,
447 it,
448 displayGamma,
449 displayNits,
450 colorSpace);
451 } else if (heifChroma == heif_chroma_interleaved_RGB || heifChroma == heif_chroma_interleaved_RGBA) {
452 HeifStrideType stride = 0;
453 dbgFile << "interleaved SDR heif file, bits:" << luma;
454
455 const uint8_t *img = heifGetPlaneMethod(heifimage, heif_channel_interleaved, &stride);
456 width = heifimage.get_width(heif_channel_interleaved);
457 height = heifimage.get_height(heif_channel_interleaved);
459 layer->paintDevice()->createHLineIteratorNG(0, 0, width);
460
461 SDR::readInterleavedLayer(linearizePolicy,
462 applyOOTF,
463 hasAlpha,
464 width,
465 height,
466 img,
467 stride,
468 it,
469 displayGamma,
470 displayNits,
471 colorSpace);
472
473 } else if (heifChroma == heif_chroma_interleaved_RRGGBB_LE || heifChroma == heif_chroma_interleaved_RRGGBBAA_LE || heifChroma == heif_chroma_interleaved_RRGGBB_BE || heifChroma == heif_chroma_interleaved_RRGGBB_BE) {
474 HeifStrideType stride = 0;
475 dbgFile << "interleaved HDR heif file, bits:" << luma;
476
477 const uint8_t *img =
478 heifGetPlaneMethod(heifimage, heif_channel_interleaved, &stride);
480 layer->paintDevice()->createHLineIteratorNG(0, 0, width);
481
483 linearizePolicy,
484 applyOOTF,
485 hasAlpha,
486 width,
487 height,
488 img,
489 stride,
490 it,
491 displayGamma,
492 displayNits,
493 colorSpace);
494 }
495
496 image->addNode(layer.data(), image->rootLayer().data());
497 image->cropImage(QRect(0, 0, width, height));
498
499 // --- Iterate through all metadata blocks and extract Exif and XMP metadata ---
500
501 std::vector<heif_item_id> metadata_IDs = handle.get_list_of_metadata_block_IDs();
502
503 for (heif_item_id id : metadata_IDs) {
504
505 if (handle.get_metadata_type(id) == "Exif") {
506 // Read exif information
507
508 std::vector<uint8_t> exif_data = handle.get_metadata(id);
509
510 if (exif_data.size()>4) {
511 size_t skip = ((quint32(exif_data[0]) << 24U)
512 | (quint32(exif_data[1])) << 16U
513 | (quint32(exif_data[2]) << 8U) | exif_data[3])
514 + 4u;
515
516 if (exif_data.size() > skip) {
517 KisMetaData::IOBackend *exifIO =
519
520 // Copy the exif data into the byte array
521 QByteArray ba(
522 reinterpret_cast<char *>(exif_data.data() + skip),
523 static_cast<int>(exif_data.size() - skip));
524 QBuffer buf(&ba);
525 exifIO->loadFrom(layer->metaData(), &buf);
526 }
527 }
528 }
529
530 if (handle.get_metadata_type(id) == "mime" &&
531 handle.get_metadata_content_type(id) == "application/rdf+xml") {
532 // Read XMP information
533
534 std::vector<uint8_t> xmp_data = handle.get_metadata(id);
536
537 // Copy the xmp data into the byte array
538 QByteArray ba(reinterpret_cast<char *>(xmp_data.data()), static_cast<int>(xmp_data.size()));
539 QBuffer buf(&ba);
540 xmpIO->loadFrom(layer->metaData(), &buf);
541 }
542 }
543
544 document->setCurrentImage(image);
546 } catch (Error &err) {
547 return setHeifError(document, err);
548 }
549}
550
551#include <HeifImport.moc>
KisImportExportErrorCode setHeifError(KisDocument *document, heif::Error error)
Definition HeifError.cpp:10
const Params2D p
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID GrayAColorModelID("GRAYA", ki18n("Grayscale/Alpha"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID Integer16BitsColorDepthID("U16", ki18n("16-bit integer/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
ColorPrimaries
The colorPrimaries enum Enum of colorants, follows ITU H.273 for values 0 to 255, and has extra known...
@ PRIMARIES_UNSPECIFIED
@ PRIMARIES_ITU_R_BT_709_5
TransferCharacteristics
The transferCharacteristics enum Enum of transfer characteristics, follows ITU H.273 for values 0 to ...
@ TRC_IEC_61966_2_1
@ TRC_ITU_R_BT_709_5
const quint8 OPACITY_OPAQUE_U8
LinearizePolicy
The KoColorTransferFunctions class.
HeifImport(QObject *parent, const QVariantList &)
~HeifImport() override
KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration=0) override
KisGroupLayerSP rootLayer() const
QString nextLayerName(const QString &baseName="") const
Definition kis_image.cc:715
void cropImage(const QRect &newRect)
start asynchronous operation on cropping the image
Definition kis_image.cc:870
The base class for import and export filters.
virtual bool loadFrom(Store *store, QIODevice *ioDevice) const =0
static KisMetadataBackendRegistry * instance()
KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w)
const T value(const QString &id) const
Definition KoID.h:30
QString id() const
Definition KoID.cpp:63
heif_reader_grow_status wait_for_file_size(int64_t target_size) override
QIODevice * m_device
int64_t get_position() const override
int read(void *data, size_t size) override
Reader_QIODevice(QIODevice *device)
int64_t m_total_length
int seek(int64_t position) override
K_PLUGIN_FACTORY_WITH_JSON(KritaASCCDLFactory, "kritaasccdl.json", registerPlugin< KritaASCCDL >();) KritaASCCDL
#define dbgFile
Definition kis_debug.h:53
auto readPlanarLayer(const int luma, Args &&...args)
auto readInterleavedLayer(const int luma, LinearizePolicy linearizePolicy, bool applyOOTF, const int channels, const int width, const int height, const uint8_t *img, const int stride, KisHLineIteratorSP it, float displayGamma, float displayNits, const KoColorSpace *colorSpace)
auto readPlanarLayer(const int luma, LinearizePolicy policy, bool applyOOTF, bool hasAlpha, const int width, const int height, const uint8_t *imgR, const int strideR, const uint8_t *imgG, const int strideG, const uint8_t *imgB, const int strideB, const uint8_t *imgA, const int strideA, KisHLineIteratorSP it, float displayGamma, float displayNits, const KoColorSpace *colorSpace)
auto readInterleavedLayer(LinearizePolicy linearizePolicy, bool applyOOTF, const int channels, const int width, const int height, const uint8_t *img, const int stride, KisHLineIteratorSP it, float displayGamma, float displayNits, const KoColorSpace *colorSpace)
KisMetaData::Store * metaData()
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
KisPaintDeviceSP paintDevice
virtual QString colorModelID() const
const KoColorProfile * profileByName(const QString &name) const
QString colorSpaceId(const QString &colorModelId, const QString &colorDepthId) const
const KoColorProfile * profileFor(const QVector< double > &colorants, ColorPrimaries colorPrimaries, TransferCharacteristics transferFunction) const
profileFor tries to find the profile that matches these characteristics, if no such profile is found,...
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
QString defaultProfileForColorSpace(const QString &colorSpaceId) const
const KoColorProfile * createColorProfile(const QString &colorModelId, const QString &colorDepthId, const QByteArray &rawData)
void addProfile(KoColorProfile *profile)