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>
25#include <KoColorProfileQuery.h>
27#include <kis_group_layer.h>
28#include <kis_image.h>
29#include <kis_iterator_ng.h>
31#include <kis_meta_data_entry.h>
32#include <kis_meta_data_store.h>
33#include <kis_meta_data_value.h>
34#include <kis_node.h>
35#include <kis_paint_device.h>
36#include <kis_paint_layer.h>
37#include <kis_transaction.h>
38#include <qmutex.h>
39
41
42using heif::Error;
43
44K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_heif_import.json", registerPlugin<HeifImport>();)
45
46HeifImport::HeifImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
47{
48}
49
53
54
55class Reader_QIODevice : public heif::Context::Reader {
56public:
57 Reader_QIODevice(QIODevice *device)
58 : m_device(device)
59 , m_total_length(m_device->bytesAvailable())
60 {
61 }
62
63 int64_t get_position() const override
64 {
65 return m_device->pos();
66 }
67 int read(void *data, size_t size) override
68 {
69 qint64 readSize =
70 m_device->read(static_cast<char *>(data), static_cast<qint64>(size));
71 return (readSize > 0 && readSize != static_cast<qint64>(size));
72 }
73 int seek(int64_t position) override
74 {
75 return !m_device->seek(position);
76 }
77 heif_reader_grow_status wait_for_file_size(int64_t target_size) override
78 {
79 return (target_size > m_total_length)
80 ? heif_reader_grow_status_size_beyond_eof
81 : heif_reader_grow_status_size_reached;
82 }
83
84private:
85 QIODevice* m_device;
87};
88
89#if LIBHEIF_HAVE_VERSION(1, 13, 0)
90class Q_DECL_HIDDEN HeifLock
91{
92public:
93 HeifLock()
94 : p()
95 {
96 heif_init(&p);
97 }
98
99 ~HeifLock()
100 {
101 heif_deinit();
102 }
103
104private:
105 heif_init_params p;
106};
107#endif
108
109namespace Planar
110{
111inline auto readPlanarLayer(const int luma,
112 LinearizePolicy policy,
113 bool applyOOTF,
114 bool hasAlpha,
115 const int width,
116 const int height,
117 const uint8_t *imgR,
118 const int strideR,
119 const uint8_t *imgG,
120 const int strideG,
121 const uint8_t *imgB,
122 const int strideB,
123 const uint8_t *imgA,
124 const int strideA,
126 float displayGamma,
127 float displayNits,
128 const KoColorSpace *colorSpace)
129{
130 return createOptimizedClass<readLayerImpl>(luma,
131 policy,
132 applyOOTF,
133 hasAlpha,
134 width,
135 height,
136 imgR,
137 strideR,
138 imgG,
139 strideG,
140 imgB,
141 strideB,
142 imgA,
143 strideA,
144 it,
145 displayGamma,
146 displayNits,
147 colorSpace);
148}
149} // namespace Planar
150
151namespace HDR
152{
153template<typename... Args>
154inline auto readInterleavedLayer(const int luma,
155 LinearizePolicy linearizePolicy,
156 bool applyOOTF,
157 const int channels,
158 const int width,
159 const int height,
160 const uint8_t *img,
161 const int stride,
163 float displayGamma,
164 float displayNits,
165 const KoColorSpace *colorSpace)
166{
167 return createOptimizedClass<HDR::readLayerImpl>(luma,
168 linearizePolicy,
169 applyOOTF,
170 channels,
171 width,
172 height,
173 img,
174 stride,
175 it,
176 displayGamma,
177 displayNits,
178 colorSpace);
179}
180} // namespace HDR
181
182namespace SDR
183{
184template<typename... Args>
185inline auto readInterleavedLayer(LinearizePolicy linearizePolicy,
186 bool applyOOTF,
187 const int channels,
188 const int width,
189 const int height,
190 const uint8_t *img,
191 const int stride,
193 float displayGamma,
194 float displayNits,
195 const KoColorSpace *colorSpace)
196{
197 return createOptimizedClass<SDR::readLayerImpl>(linearizePolicy,
198 applyOOTF,
199 channels,
200 width,
201 height,
202 img,
203 stride,
204 it,
205 displayGamma,
206 displayNits,
207 colorSpace);
208}
209} // namespace SDR
210
212{
213#if LIBHEIF_HAVE_VERSION(1, 13, 0)
214 HeifLock lock;
215#endif
216
217#if LIBHEIF_HAVE_VERSION(1, 20, 2)
218 using HeifStrideType = size_t;
219 auto heifGetPlaneMethod = std::mem_fn(qNonConstOverload<heif_channel, HeifStrideType*>(&heif::Image::get_plane2));
220#elif LIBHEIF_HAVE_VERSION(1, 20, 0)
221 using HeifStrideType = size_t;
222 auto heifGetPlaneMethod = std::mem_fn(qNonConstOverload<heif_channel, HeifStrideType*>(&heif::Image::get_plane));
223#else
224 using HeifStrideType = int;
225 auto heifGetPlaneMethod = std::mem_fn(qNonConstOverload<heif_channel, HeifStrideType*>(&heif::Image::get_plane));
226#endif
227
228 // Wrap input stream into heif Reader object
229 Reader_QIODevice reader(io);
230
231 try {
232 heif::Context ctx;
233 ctx.read_from_reader(reader);
234
235 // decode primary image
236
237 heif::ImageHandle handle = ctx.get_primary_image_handle();
238
239 heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle.get_raw_image_handle());
240
241
242 heif::Image heifimage = handle.decode_image(heif_colorspace_undefined, heif_chroma_undefined);
243 heif_colorspace heifModel = heifimage.get_colorspace();
244 heif_chroma heifChroma = heifimage.get_chroma_format();
245 int luma = handle.get_luma_bits_per_pixel();
246 bool hasAlpha = handle.has_alpha_channel();
247
248 dbgFile << "loading heif" << heifModel << heifChroma << luma;
249
251 bool applyOOTF = true;
252 float displayGamma = 1.2f;
253 float displayNits = 1000.0;
254
255 struct heif_error err {
256 };
257 const KoColorProfile *profile = nullptr;
258 KoID colorDepth = Integer8BitsColorDepthID;
259 QString colorModel = RGBAColorModelID.id();
260
261 if (luma > 8) {
262 colorDepth = Integer16BitsColorDepthID;
263 }
264 // First, get the profile, because sometimes the image may be encoded in YCbCr and the embedded icc profile is graya.
265
266 if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) {
267 dbgFile << "profile type is icc profile";
268 // rICC are 'restricted' icc profiles, and are matrix shaper profiles
269 // that are either RGB or Grayscale, and are of the input or display types.
270 // They are from the JPEG2000 spec.
271
272 int rawProfileSize = (int) heif_image_handle_get_raw_color_profile_size(handle.get_raw_image_handle());
273 if (rawProfileSize > 0) {
274 QByteArray ba(rawProfileSize, 0);
275 err = heif_image_handle_get_raw_color_profile(handle.get_raw_image_handle(), ba.data());
276 if (err.code) {
277 dbgFile << "icc profile loading failed:" << err.message;
278 } else {
279 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 KoColorProfileQuery query(primaries, transferCharacteristic);
327 query.whitePoint = {static_cast<double>(nclx->color_primary_white_x),
328 static_cast<double>(nclx->color_primary_white_y)};
330 if (primaries == PRIMARIES_UNSPECIFIED) {
331 return {};
332 } else {
333 return {{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(query);
343
344 if (linearizePolicy != LinearizePolicy::KeepTheSame) {
345 colorDepth = Float32BitsColorDepthID;
346 }
347
348 heif_nclx_color_profile_free(nclx);
349 dbgFile << "nclx profile found" << profile->name();
350 }
351 } else {
352 dbgFile << "no profile found";
353 }
354
355 // Now, to figure out the correct chroma and color model.
356 if (heifModel == heif_colorspace_monochrome || colorModel == GrayAColorModelID.id()) {
357 // Grayscale image.
358 if (heifChroma != heif_chroma_monochrome && colorModel == GrayAColorModelID.id()) {
359 heifimage = handle.decode_image(heif_colorspace_YCbCr, heif_chroma_monochrome);
360 }
361 colorModel = GrayAColorModelID.id();
362 heifChroma = heif_chroma_monochrome;
363 } else if (heifModel == heif_colorspace_YCbCr) {
364 // we do not support importing YCrBr spaces directly
365 heifimage = handle.decode_image(heif_colorspace_RGB, heif_chroma_444);
366 colorModel = RGBAColorModelID.id();
367 heifModel = heifimage.get_colorspace();
368 heifChroma = heifimage.get_chroma_format();
369 }
370
371 // Get the default profile if we haven't found one up till now.
372 if (!profile) {
373 if (colorModel == RGBAColorModelID.id()) {
376 } else {
377 const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorModel, colorDepth.id());
378 QString profileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId);
379 profile = KoColorSpaceRegistry::instance()->profileByName(profileName);
380 }
381 }
382
383 const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth.id(), profile);
384
385 int width = handle.get_width();
386
387 int height = handle.get_height();
388
389 // convert HEIF image to Krita KisDocument
390
391 KisImageSP image = new KisImage(document->createUndoStore(), width, height, colorSpace,
392 "HEIF image");
393
394 KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8);
395
396 if (luma != 8 && luma != 10 && luma != 12) {
397 dbgFile << "unknown bitdepth" << luma;
398 }
399
400 if (heifChroma == heif_chroma_monochrome) {
401 dbgFile << "monochrome heif file, bits:" << luma;
402 HeifStrideType strideG = 0;
403 HeifStrideType strideA = 0;
404 const uint8_t *imgG = heifGetPlaneMethod(heifimage, heif_channel_Y, &strideG);
405 const uint8_t *imgA =
406 heifGetPlaneMethod(heifimage, heif_channel_Alpha, &strideA);
407 const int width = heifimage.get_width(heif_channel_Y);
408 const int height = heifimage.get_height(heif_channel_Y);
410 layer->paintDevice()->createHLineIteratorNG(0, 0, width);
411
413 hasAlpha,
414 width,
415 height,
416 it,
417 imgG,
418 imgA,
419 strideG,
420 strideA);
421 } else if (heifChroma == heif_chroma_444) {
422 dbgFile << "planar heif file, bits:" << luma;
423
424 HeifStrideType strideR = 0;
425 HeifStrideType strideG = 0;
426 HeifStrideType strideB = 0;
427 HeifStrideType strideA = 0;
428 const uint8_t* imgR = heifGetPlaneMethod(heifimage, heif_channel_R, &strideR);
429 const uint8_t* imgG = heifGetPlaneMethod(heifimage, heif_channel_G, &strideG);
430 const uint8_t* imgB = heifGetPlaneMethod(heifimage, heif_channel_B, &strideB);
431 const uint8_t *imgA =
432 heifGetPlaneMethod(heifimage, heif_channel_Alpha, &strideA);
433 KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, 0, width);
434
436 linearizePolicy,
437 applyOOTF,
438 hasAlpha,
439 width,
440 height,
441 imgR,
442 strideR,
443 imgG,
444 strideG,
445 imgB,
446 strideB,
447 imgA,
448 strideA,
449 it,
450 displayGamma,
451 displayNits,
452 colorSpace);
453 } else if (heifChroma == heif_chroma_interleaved_RGB || heifChroma == heif_chroma_interleaved_RGBA) {
454 HeifStrideType stride = 0;
455 dbgFile << "interleaved SDR heif file, bits:" << luma;
456
457 const uint8_t *img = heifGetPlaneMethod(heifimage, heif_channel_interleaved, &stride);
458 width = heifimage.get_width(heif_channel_interleaved);
459 height = heifimage.get_height(heif_channel_interleaved);
461 layer->paintDevice()->createHLineIteratorNG(0, 0, width);
462
463 SDR::readInterleavedLayer(linearizePolicy,
464 applyOOTF,
465 hasAlpha,
466 width,
467 height,
468 img,
469 stride,
470 it,
471 displayGamma,
472 displayNits,
473 colorSpace);
474
475 } 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) {
476 HeifStrideType stride = 0;
477 dbgFile << "interleaved HDR heif file, bits:" << luma;
478
479 const uint8_t *img =
480 heifGetPlaneMethod(heifimage, heif_channel_interleaved, &stride);
482 layer->paintDevice()->createHLineIteratorNG(0, 0, width);
483
485 linearizePolicy,
486 applyOOTF,
487 hasAlpha,
488 width,
489 height,
490 img,
491 stride,
492 it,
493 displayGamma,
494 displayNits,
495 colorSpace);
496 }
497
498 image->addNode(layer.data(), image->rootLayer().data());
499 image->cropImage(QRect(0, 0, width, height));
500
501 // --- Iterate through all metadata blocks and extract Exif and XMP metadata ---
502
503 std::vector<heif_item_id> metadata_IDs = handle.get_list_of_metadata_block_IDs();
504
505 for (heif_item_id id : metadata_IDs) {
506
507 if (handle.get_metadata_type(id) == "Exif") {
508 // Read exif information
509
510 std::vector<uint8_t> exif_data = handle.get_metadata(id);
511
512 if (exif_data.size()>4) {
513 size_t skip = ((quint32(exif_data[0]) << 24U)
514 | (quint32(exif_data[1])) << 16U
515 | (quint32(exif_data[2]) << 8U) | exif_data[3])
516 + 4u;
517
518 if (exif_data.size() > skip) {
519 KisMetaData::IOBackend *exifIO =
521
522 // Copy the exif data into the byte array
523 QByteArray ba(
524 reinterpret_cast<char *>(exif_data.data() + skip),
525 static_cast<int>(exif_data.size() - skip));
526 QBuffer buf(&ba);
527 exifIO->loadFrom(layer->metaData(), &buf);
528 }
529 }
530 }
531
532 if (handle.get_metadata_type(id) == "mime" &&
533 handle.get_metadata_content_type(id) == "application/rdf+xml") {
534 // Read XMP information
535
536 std::vector<uint8_t> xmp_data = handle.get_metadata(id);
538
539 // Copy the xmp data into the byte array
540 QByteArray ba(reinterpret_cast<char *>(xmp_data.data()), static_cast<int>(xmp_data.size()));
541 QBuffer buf(&ba);
542 xmpIO->loadFrom(layer->metaData(), &buf);
543 }
544 }
545
546 document->setCurrentImage(image);
548 } catch (Error &err) {
549 return setHeifError(document, err);
550 }
551}
552
553#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:716
void cropImage(const QRect &newRect)
start asynchronous operation on cropping the image
Definition kis_image.cc:871
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
The KoColorProfileQuery struct.
QList< KoColorimetryUtils::xy > rgbColorants
The desired white point of the profile.
KoColorimetryUtils::xy whitePoint
virtual QString colorModelID() const
const KoColorProfile * profileByName(const QString &name) const
QString colorSpaceId(const QString &colorModelId, const QString &colorDepthId) const
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorProfile * profileFor(const KoColorProfileQuery &query, const bool generate=true) const
profileFor tries to find the profile that matches these characteristics, if no such profile is found,...
QString defaultProfileForColorSpace(const QString &colorSpaceId) const
const KoColorProfile * createColorProfile(const QString &colorModelId, const QString &colorDepthId, const QByteArray &rawData)