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