1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2019-09-26
7 * Description : A HEIF IO file for DImg framework - save operations
8 *
9 * Copyright (C) 2019-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10 *
11 * This program is free software; you can redistribute it
12 * and/or modify it under the terms of the GNU General
13 * Public License as published by the Free Software Foundation;
14 * either version 2, or (at your option)
15 * any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * ============================================================ */
23
24 #include "dimgheifloader.h"
25
26 // Qt includes
27
28 #include <QFile>
29 #include <QVariant>
30 #include <QByteArray>
31 #include <QTextStream>
32 #include <QElapsedTimer>
33 #include <QDataStream>
34 #include <qplatformdefs.h>
35 #include <QScopedPointer>
36
37 // Local includes
38
39 #include "digikam_config.h"
40 #include "digikam_debug.h"
41 #include "dimg.h"
42 #include "dimgloaderobserver.h"
43 #include "metaengine.h"
44
45 // libx265 includes
46
47 #if defined(__clang__)
48 # pragma clang diagnostic push
49 # pragma clang diagnostic ignored "-Wundef"
50 # pragma clang diagnostic ignored "-Wgnu-anonymous-struct"
51 # pragma clang diagnostic ignored "-Wnested-anon-types"
52 #endif
53
54 #ifdef HAVE_X265
55 # include <x265.h>
56 #endif
57
58 #if defined(__clang__)
59 # pragma clang diagnostic pop
60 #endif
61
62 namespace Digikam
63 {
64
HeifQIODeviceWriter(struct heif_context *,const void * data,size_t size,void * userdata)65 static struct heif_error HeifQIODeviceWriter(struct heif_context* /* ctx */,
66 const void* data, size_t size, void* userdata)
67 {
68 QFile saveFile(QString::fromUtf8(static_cast<const char*>(userdata)));
69 heif_error error;
70
71 if (!saveFile.open(QIODevice::WriteOnly))
72 {
73 error.code = heif_error_Encoding_error;
74 error.subcode = heif_suberror_Cannot_write_output_data;
75 error.message = QByteArray("File open error").constData();
76
77 return error;
78 }
79
80 error.code = heif_error_Ok;
81 error.subcode = heif_suberror_Unspecified;
82 error.message = QByteArray("Success").constData();
83
84 qint64 bytesWritten = saveFile.write(static_cast<const char*>(data), size);
85
86 if (bytesWritten < static_cast<qint64>(size))
87 {
88 error.code = heif_error_Encoding_error;
89 error.subcode = heif_suberror_Cannot_write_output_data;
90 error.message = QByteArray("File write error").constData();
91 }
92
93 saveFile.close();
94
95 return error;
96 }
97
x265MaxBitsDepth()98 int DImgHEIFLoader::x265MaxBitsDepth()
99 {
100 int maxOutputBitsDepth = -1;
101
102 #ifdef HAVE_X265
103
104 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEVC encoder max bit depth:" << x265_max_bit_depth;
105 const x265_api* api = x265_api_get(x265_max_bit_depth);
106
107 if (api)
108 {
109 maxOutputBitsDepth = x265_max_bit_depth;
110 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEVC encoder max bits depth:" << maxOutputBitsDepth;
111 }
112 else
113 {
114 api = x265_api_get(8); // Try to failback to default 8 bits
115
116 if (api)
117 {
118 maxOutputBitsDepth = 8;
119 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEVC encoder max bits depth: 8 (default failback value)";
120 }
121 }
122
123 #endif
124
125 // cppcheck-suppress knownConditionTrueFalse
126 if (maxOutputBitsDepth == -1)
127 {
128 qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot get max supported HEVC encoder bits depth!";
129 }
130
131 return maxOutputBitsDepth;
132 }
133
save(const QString & filePath,DImgLoaderObserver * const observer)134 bool DImgHEIFLoader::save(const QString& filePath, DImgLoaderObserver* const observer)
135 {
136 m_observer = observer;
137
138 // -------------------------------------------------------------------
139 // Open the file
140
141 #ifdef Q_OS_WIN
142
143 FILE* const file = _wfopen((const wchar_t*)filePath.utf16(), L"wb");
144
145 #else
146
147 FILE* const file = fopen(filePath.toUtf8().constData(), "wb");
148
149 #endif
150
151 if (!file)
152 {
153 qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot open target image file.";
154 return false;
155 }
156
157 fclose(file);
158
159 QVariant qualityAttr = imageGetAttribute(QLatin1String("quality"));
160 int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 75;
161 bool lossless = (quality == 0);
162
163 // --- Determine libx265 encoder bits depth capability: 8=standard, 10, 12, or later 16.
164
165 int maxOutputBitsDepth = x265MaxBitsDepth();
166
167 if (maxOutputBitsDepth == -1)
168 {
169 return false;
170 }
171
172 heif_chroma chroma;
173
174 if (maxOutputBitsDepth > 8) // 16 bits image.
175 {
176 chroma = imageHasAlpha() ? heif_chroma_interleaved_RRGGBBAA_BE
177 : heif_chroma_interleaved_RRGGBB_BE;
178 }
179 else
180 {
181 chroma = imageHasAlpha() ? heif_chroma_interleaved_RGBA
182 : heif_chroma_interleaved_RGB;
183 }
184
185 // --- use standard HEVC encoder
186
187 QElapsedTimer timer;
188 timer.start();
189 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEVC encoder setup...";
190
191 struct heif_context* const ctx = heif_context_alloc();
192
193 if (!ctx)
194 {
195 qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot create HEIF context!";
196
197 return false;
198 }
199
200 struct heif_encoder* encoder = nullptr;
201 struct heif_error error = heif_context_get_encoder_for_format(ctx,
202 heif_compression_HEVC,
203 &encoder);
204 if (!isHeifSuccess(&error))
205 {
206 heif_context_free(ctx);
207
208 return false;
209 }
210
211 heif_encoder_set_lossy_quality(encoder, quality);
212 heif_encoder_set_lossless(encoder, lossless);
213
214 struct heif_image* image = nullptr;
215 error = heif_image_create(imageWidth(),
216 imageHeight(),
217 heif_colorspace_RGB,
218 chroma,
219 &image);
220 if (!isHeifSuccess(&error))
221 {
222 heif_encoder_release(encoder);
223 heif_context_free(ctx);
224
225 return false;
226 }
227
228 // --- Save color profile before to create image data, as converting to color space can be processed at this stage.
229
230 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF set color profile...";
231
232 saveHEICColorProfile(image);
233
234 // --- Add image data
235
236 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF setup data plane...";
237
238 error = heif_image_add_plane(image,
239 heif_channel_interleaved,
240 imageWidth(),
241 imageHeight(),
242 maxOutputBitsDepth);
243
244 if (!isHeifSuccess(&error))
245 {
246 heif_encoder_release(encoder);
247 heif_context_free(ctx);
248
249 return false;
250 }
251
252 int stride = 0;
253 uint8_t* const data = heif_image_get_plane(image,
254 heif_channel_interleaved,
255 &stride);
256
257 if (!data || (stride <= 0))
258 {
259 qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "HEIF data pixels information not valid!";
260 heif_encoder_release(encoder);
261 heif_context_free(ctx);
262
263 return false;
264 }
265
266 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF data container:" << data;
267 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF bytes per line:" << stride;
268
269 uint checkpoint = 0;
270 unsigned char r = 0;
271 unsigned char g = 0;
272 unsigned char b = 0;
273 unsigned char a = 0;
274 unsigned char* src = nullptr;
275 unsigned char* dst = nullptr;
276 unsigned short r16 = 0;
277 unsigned short g16 = 0;
278 unsigned short b16 = 0;
279 unsigned short a16 = 0;
280 unsigned short* src16 = nullptr;
281 unsigned short* dst16 = nullptr;
282 int div16 = 16 - maxOutputBitsDepth;
283 int mul8 = maxOutputBitsDepth - 8;
284 int nbOutputBytesPerColor = (maxOutputBitsDepth > 8) ? (imageHasAlpha() ? 4 * 2 : 3 * 2) // output data stored on 16 bits
285 : (imageHasAlpha() ? 4 : 3 ); // output data stored on 8 bits
286
287 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF output bytes per color:" << nbOutputBytesPerColor;
288 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF 16 to 8 bits coeff. :" << div16;
289 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF 8 to 16 bits coeff. :" << mul8;
290
291 for (unsigned int y = 0 ; y < imageHeight() ; ++y)
292 {
293 src = &imageData()[(y * imageWidth()) * imageBytesDepth()];
294 src16 = reinterpret_cast<unsigned short*>(src);
295 dst = reinterpret_cast<unsigned char*>(data + (y * stride));
296 dst16 = reinterpret_cast<unsigned short*>(dst);
297
298 for (unsigned int x = 0 ; x < imageWidth() ; ++x)
299 {
300 if (imageSixteenBit()) // 16 bits source image.
301 {
302 b16 = src16[0];
303 g16 = src16[1];
304 r16 = src16[2];
305
306 if (imageHasAlpha())
307 {
308 a16 = src16[3];
309 }
310
311 if (maxOutputBitsDepth > 8) // From 16 bits to 10 bits or more.
312 {
313 dst16[0] = (unsigned short)(r16 >> div16);
314 dst16[1] = (unsigned short)(g16 >> div16);
315 dst16[2] = (unsigned short)(b16 >> div16);
316
317 if (imageHasAlpha())
318 {
319 dst16[3] = (unsigned short)(a16 >> div16);
320 dst16 += 4;
321 }
322 else
323 {
324 dst16 += 3;
325 }
326 }
327 else // From 16 bits to 8 bits.
328 {
329 dst[0] = (unsigned char)(r16 >> div16);
330 dst[1] = (unsigned char)(g16 >> div16);
331 dst[2] = (unsigned char)(b16 >> div16);
332
333 if (imageHasAlpha())
334 {
335 dst[3] = (unsigned char)(a16 >> div16);
336 dst += 4;
337 }
338 else
339 {
340 dst += 3;
341 }
342 }
343
344 src16 += 4;
345 }
346 else // 8 bits source image.
347 {
348 b = src[0];
349 g = src[1];
350 r = src[2];
351
352 if (imageHasAlpha())
353 {
354 a = src[3];
355 }
356
357 if (maxOutputBitsDepth > 8) // From 8 bits to 10 bits or more.
358 {
359 dst16[0] = (unsigned short)(r << mul8);
360 dst16[1] = (unsigned short)(g << mul8);
361 dst16[2] = (unsigned short)(b << mul8);
362
363 if (imageHasAlpha())
364 {
365 dst16[3] = (unsigned short)(a << mul8);
366 dst16 += 4;
367 }
368 else
369 {
370 dst16 += 3;
371 }
372 }
373 else // From 8 bits to 8 bits.
374 {
375 dst[0] = r;
376 dst[1] = g;
377 dst[2] = b;
378
379 if (imageHasAlpha())
380 {
381 dst[3] = a;
382 dst += 4;
383 }
384 else
385 {
386 dst += 3;
387 }
388 }
389
390 src += 4;
391 }
392 }
393
394 if (m_observer && (y == (unsigned int)checkpoint))
395 {
396 checkpoint += granularity(m_observer, imageHeight(), 0.8F);
397
398 if (!m_observer->continueQuery())
399 {
400 heif_encoder_release(encoder);
401 heif_context_free(ctx);
402
403 return false;
404 }
405
406 m_observer->progressInfo(0.1F + (0.8F * (((float)y) / ((float)imageHeight()))));
407 }
408 }
409
410 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF master image encoding...";
411
412 // --- encode and write master image
413
414 struct heif_encoding_options* const options = heif_encoding_options_alloc();
415 options->save_alpha_channel = imageHasAlpha() ? 1 : 0;
416 struct heif_image_handle* image_handle = nullptr;
417 error = heif_context_encode_image(ctx,
418 image,
419 encoder,
420 options,
421 &image_handle);
422
423 if (!isHeifSuccess(&error))
424 {
425 heif_encoding_options_free(options);
426 heif_image_handle_release(image_handle);
427 heif_encoder_release(encoder);
428 heif_context_free(ctx);
429
430 return false;
431 }
432
433 // --- encode thumbnail
434
435 // Note: Only encode preview for large image.
436 // We will use the same preview size than DImg::prepareMetadataToSave()
437
438 const int previewSize = 1280;
439
440 if (qMin(imageWidth(), imageHeight()) > previewSize)
441 {
442 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF preview storage in thumbnail chunk...";
443
444 struct heif_image_handle* thumbnail_handle = nullptr;
445
446 error = heif_context_encode_thumbnail(ctx,
447 image,
448 image_handle,
449 encoder,
450 options,
451 previewSize,
452 &thumbnail_handle);
453 if (!isHeifSuccess(&error))
454 {
455 heif_encoding_options_free(options);
456 heif_image_handle_release(image_handle);
457 heif_encoder_release(encoder);
458 heif_context_free(ctx);
459
460 return false;
461 }
462
463 heif_image_handle_release(thumbnail_handle);
464 }
465
466 heif_encoding_options_free(options);
467 heif_encoder_release(encoder);
468
469 // --- Add Exif and XMP metadata
470
471 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF metadata storage...";
472
473 saveHEICMetadata(ctx, image_handle);
474
475 heif_image_handle_release(image_handle);
476
477 // --- write HEIF file
478
479 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF flush to file...";
480 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF encoding took:" << timer.elapsed() << "ms";
481
482 heif_writer writer;
483 writer.writer_api_version = 1;
484 writer.write = HeifQIODeviceWriter;
485
486 error = heif_context_write(ctx, &writer, (void*)filePath.toUtf8().constData());
487
488 if (!isHeifSuccess(&error))
489 {
490 heif_context_free(ctx);
491
492 return false;
493 }
494
495 heif_context_free(ctx);
496
497 imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("HEIF"));
498 saveMetadata(filePath);
499
500 return true;
501 }
502
saveHEICColorProfile(struct heif_image * const image)503 bool DImgHEIFLoader::saveHEICColorProfile(struct heif_image* const image)
504 {
505
506 #if LIBHEIF_NUMERIC_VERSION >= 0x01040000
507
508 QByteArray profile = m_image->getIccProfile().data();
509
510 if (!profile.isEmpty())
511 {
512 // Save color profile.
513
514 struct heif_error error = heif_image_set_raw_color_profile(image,
515 "prof", // FIXME: detect string in profile data
516 profile.data(),
517 profile.size());
518
519 if (error.code != 0)
520 {
521 qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot set HEIF color profile!";
522 return false;
523 }
524
525 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "Stored HEIF color profile size:" << profile.size();
526 }
527
528 #else
529
530 Q_UNUSED(image_handle);
531
532 #endif
533
534 return true;
535 }
536
saveHEICMetadata(struct heif_context * const heif_context,struct heif_image_handle * const image_handle)537 bool DImgHEIFLoader::saveHEICMetadata(struct heif_context* const heif_context,
538 struct heif_image_handle* const image_handle)
539 {
540 QScopedPointer<MetaEngine> meta(new MetaEngine(m_image->getMetadata()));
541
542 if (!meta->hasExif() && !meta->hasIptc() && !meta->hasXmp())
543 {
544 return false;
545 }
546
547 QByteArray exif = meta->getExifEncoded();
548 QByteArray iptc = meta->getIptc();
549 QByteArray xmp = meta->getXmp();
550 struct heif_error error;
551
552 if (!exif.isEmpty())
553 {
554 error = heif_context_add_exif_metadata(heif_context,
555 image_handle,
556 exif.data(),
557 exif.size());
558
559 if (error.code != 0)
560 {
561 qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot store HEIF Exif metadata!";
562 return false;
563 }
564
565 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "Stored HEIF Exif data size:" << exif.size();
566 }
567
568 if (!iptc.isEmpty())
569 {
570 error = heif_context_add_generic_metadata(heif_context,
571 image_handle,
572 iptc.data(),
573 iptc.size(),
574 "iptc",
575 nullptr);
576
577 if (error.code != 0)
578 {
579 qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot store HEIF Iptc metadata!";
580
581 return false;
582 }
583
584 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "Stored HEIF Iptc data size:" << iptc.size();
585 }
586
587 if (!xmp.isEmpty())
588 {
589 error = heif_context_add_XMP_metadata(heif_context,
590 image_handle,
591 xmp.data(),
592 xmp.size());
593
594 if (error.code != 0)
595 {
596 qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot store HEIF Xmp metadata!";
597
598 return false;
599 }
600
601 qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "Stored HEIF Xmp data size:" << xmp.size();
602 }
603
604 return true;
605 }
606
607 } // namespace Digikam
608