1 /*
2   libheif example application "heif".
3 
4   MIT License
5 
6   Copyright (c) 2017 struktur AG, Dirk Farin <farin@struktur.de>
7 
8   Permission is hereby granted, free of charge, to any person obtaining a copy
9   of this software and associated documentation files (the "Software"), to deal
10   in the Software without restriction, including without limitation the rights
11   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12   copies of the Software, and to permit persons to whom the Software is
13   furnished to do so, subject to the following conditions:
14 
15   The above copyright notice and this permission notice shall be included in all
16   copies or substantial portions of the Software.
17 
18   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24   SOFTWARE.
25 */
26 #if defined(HAVE_CONFIG_H)
27 #include "config.h"
28 #endif
29 
30 #include <errno.h>
31 #include <string.h>
32 #include <getopt.h>
33 
34 #include <fstream>
35 #include <iostream>
36 #include <memory>
37 #include <algorithm>
38 #include <vector>
39 #include <string>
40 
41 #include <libheif/heif.h>
42 
43 #if HAVE_LIBJPEG
44 extern "C" {
45 // Prevent duplicate definition for libjpeg-turbo v2.0
46 // Note: these 'undef's are only a workaround for a libjpeg-turbo-v2.0 bug and
47 // should be removed again later. Bug has been fixed in libjpeg-turbo-v2.0.1.
48 #include <jconfig.h>
49 #if defined(LIBJPEG_TURBO_VERSION_NUMBER) && LIBJPEG_TURBO_VERSION_NUMBER == 2000000
50 #undef HAVE_STDDEF_H
51 #undef HAVE_STDLIB_H
52 #endif
53 #include <jpeglib.h>
54 }
55 #endif
56 
57 #if HAVE_LIBPNG
58 extern "C" {
59 #include <png.h>
60 }
61 #endif
62 
63 #include <assert.h>
64 
65 #define JPEG_ICC_MARKER  (JPEG_APP0+2)  /* JPEG marker code for ICC */
66 #define JPEG_ICC_OVERHEAD_LEN  14        /* size of non-profile data in APP2 */
67 
68 int master_alpha = 1;
69 int thumb_alpha = 1;
70 int list_encoders = 0;
71 int two_colr_boxes = 0;
72 const char* encoderId = nullptr;
73 
74 int nclx_matrix_coefficients = 6;
75 int nclx_colour_primaries = 2;
76 int nclx_transfer_characteristic = 2;
77 int nclx_full_range = true;
78 
79 const int OPTION_NCLX_MATRIX_COEFFICIENTS = 1000;
80 const int OPTION_NCLX_COLOUR_PRIMARIES = 1001;
81 const int OPTION_NCLX_TRANSFER_CHARACTERISTIC = 1002;
82 const int OPTION_NCLX_FULL_RANGE_FLAG = 1003;
83 
84 static struct option long_options[] = {
85     {(char* const) "help",                    no_argument,       0,              'h'},
86     {(char* const) "quality",                 required_argument, 0,              'q'},
87     {(char* const) "output",                  required_argument, 0,              'o'},
88     {(char* const) "lossless",                no_argument,       0,              'L'},
89     {(char* const) "thumb",                   required_argument, 0,              't'},
90     {(char* const) "verbose",                 no_argument,       0,              'v'},
91     {(char* const) "params",                  no_argument,       0,              'P'},
92     {(char* const) "no-alpha",                no_argument,       &master_alpha,  0},
93     {(char* const) "no-thumb-alpha",          no_argument,       &thumb_alpha,   0},
94     {(char* const) "list-encoders",           no_argument,       &list_encoders, 1},
95     {(char* const) "encoders",                no_argument,       0,              'e'},
96     {(char* const) "bit-depth",               required_argument, 0,              'b'},
97     {(char* const) "even-size",               no_argument,       0,              'E'},
98     {(char* const) "avif",                    no_argument,       0,              'A'},
99     {(char* const) "matrix_coefficients",     required_argument, 0,              OPTION_NCLX_MATRIX_COEFFICIENTS},
100     {(char* const) "colour_primaries",        required_argument, 0,              OPTION_NCLX_COLOUR_PRIMARIES},
101     {(char* const) "transfer_characteristic", required_argument, 0,              OPTION_NCLX_TRANSFER_CHARACTERISTIC},
102     {(char* const) "full_range_flag",         required_argument, 0,              OPTION_NCLX_FULL_RANGE_FLAG},
103     {(char* const) "enable-two-colr-boxes",   no_argument,       &two_colr_boxes, 1},
104     {0, 0,                                                       0,              0}
105 };
106 
show_help(const char * argv0)107 void show_help(const char* argv0)
108 {
109   std::cerr << " heif-enc  libheif version: " << heif_get_version() << "\n"
110             << "----------------------------------------\n"
111             << "Usage: heif-enc [options] image.jpeg ...\n"
112             << "\n"
113             << "When specifying multiple source images, they will all be saved into the same HEIF/AVIF file.\n"
114             << "\n"
115             << "When using the x265 encoder, you may pass it any of its parameters by\n"
116             << "prefixing the parameter name with 'x265:'. Hence, to set the 'ctu' parameter,\n"
117             << "you will have to set 'x265:ctu' in libheif (e.g.: -p x265:ctu=64).\n"
118             << "Note that there is no checking for valid parameters when using the prefix.\n"
119             << "\n"
120             << "Options:\n"
121             << "  -h, --help       show help\n"
122             << "  -q, --quality    set output quality (0-100) for lossy compression\n"
123             << "  -L, --lossless   generate lossless output (-q has no effect)\n"
124             << "  -t, --thumb #    generate thumbnail with maximum size # (default: off)\n"
125             << "      --no-alpha   do not save alpha channel\n"
126             << "      --no-thumb-alpha  do not save alpha channel in thumbnail image\n"
127             << "  -o, --output     output filename (optional)\n"
128             << "  -v, --verbose    enable logging output (more -v will increase logging level)\n"
129             << "  -P, --params     show all encoder parameters\n"
130             << "  -b #             bit-depth of generated HEIF/AVIF file when using 16-bit PNG input (default: 10 bit)\n"
131             << "  -p               set encoder parameter (NAME=VALUE)\n"
132             << "  -A, --avif       encode as AVIF\n"
133             << "  --list-encoders  list all available encoders for the selected output format\n"
134             << "  -e, --encoder ID select encoder to use (the IDs can be listed with --list-encoders)\n"
135             << "  -E, --even-size  [deprecated] crop images to even width and height (odd sizes are not decoded correctly by some software)\n"
136             << "  --matrix_coefficients     nclx profile: color conversion matrix coefficients, default=6 (see h.273)\n"
137             << "  --colour_primaries        nclx profile: color primaries (see h.273)\n"
138             << "  --transfer_characteristic nclx profile: transfer characteristics (see h.273)\n"
139             << "  --full_range_flag         nclx profile: full range flag, default: 1\n"
140             << "  --enable-two-colr-boxes   will write both an ICC and an nclx color profile if both a present\n"
141             << "\n"
142             << "Note: to get lossless encoding, you need this set of options:\n"
143             << "  -L                       switch encoder to lossless mode\n"
144             << "  -p chroma=444            switch off color subsampling\n"
145             << "  --matrix_coefficients=0  encode in RGB color-space\n";
146 
147 }
148 
149 
150 #if HAVE_LIBJPEG
151 
JPEGMarkerIsIcc(jpeg_saved_marker_ptr marker)152 static bool JPEGMarkerIsIcc(jpeg_saved_marker_ptr marker)
153 {
154   return
155       marker->marker == JPEG_ICC_MARKER &&
156       marker->data_length >= JPEG_ICC_OVERHEAD_LEN &&
157       /* verify the identifying string */
158       GETJOCTET(marker->data[0]) == 0x49 &&
159       GETJOCTET(marker->data[1]) == 0x43 &&
160       GETJOCTET(marker->data[2]) == 0x43 &&
161       GETJOCTET(marker->data[3]) == 0x5F &&
162       GETJOCTET(marker->data[4]) == 0x50 &&
163       GETJOCTET(marker->data[5]) == 0x52 &&
164       GETJOCTET(marker->data[6]) == 0x4F &&
165       GETJOCTET(marker->data[7]) == 0x46 &&
166       GETJOCTET(marker->data[8]) == 0x49 &&
167       GETJOCTET(marker->data[9]) == 0x4C &&
168       GETJOCTET(marker->data[10]) == 0x45 &&
169       GETJOCTET(marker->data[11]) == 0x0;
170 }
171 
ReadICCProfileFromJPEG(j_decompress_ptr cinfo,JOCTET ** icc_data_ptr,unsigned int * icc_data_len)172 boolean ReadICCProfileFromJPEG(j_decompress_ptr cinfo,
173                                JOCTET** icc_data_ptr,
174                                unsigned int* icc_data_len)
175 {
176   jpeg_saved_marker_ptr marker;
177   int num_markers = 0;
178   int seq_no;
179   JOCTET* icc_data;
180   unsigned int total_length;
181 #define MAX_SEQ_NO  255        /* sufficient since marker numbers are bytes */
182   char marker_present[MAX_SEQ_NO + 1];      /* 1 if marker found */
183   unsigned int data_length[MAX_SEQ_NO + 1]; /* size of profile data in marker */
184   unsigned int data_offset[MAX_SEQ_NO + 1]; /* offset for data in marker */
185 
186   *icc_data_ptr = NULL;        /* avoid confusion if FALSE return */
187   *icc_data_len = 0;
188 
189   /* This first pass over the saved markers discovers whether there are
190    * any ICC markers and verifies the consistency of the marker numbering.
191    */
192 
193   for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++)
194     marker_present[seq_no] = 0;
195 
196   for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
197     if (JPEGMarkerIsIcc(marker)) {
198       if (num_markers == 0)
199         num_markers = GETJOCTET(marker->data[13]);
200       else if (num_markers != GETJOCTET(marker->data[13]))
201         return FALSE;        /* inconsistent num_markers fields */
202       seq_no = GETJOCTET(marker->data[12]);
203       if (seq_no <= 0 || seq_no > num_markers)
204         return FALSE;        /* bogus sequence number */
205       if (marker_present[seq_no])
206         return FALSE;        /* duplicate sequence numbers */
207       marker_present[seq_no] = 1;
208       data_length[seq_no] = marker->data_length - JPEG_ICC_OVERHEAD_LEN;
209     }
210   }
211 
212   if (num_markers == 0)
213     return FALSE;
214 
215   /* Check for missing markers, count total space needed,
216    * compute offset of each marker's part of the data.
217    */
218 
219   total_length = 0;
220   for (seq_no = 1; seq_no <= num_markers; seq_no++) {
221     if (marker_present[seq_no] == 0)
222       return FALSE;        /* missing sequence number */
223     data_offset[seq_no] = total_length;
224     total_length += data_length[seq_no];
225   }
226 
227   if (total_length <= 0)
228     return FALSE;        /* found only empty markers? */
229 
230   /* Allocate space for assembled data */
231   icc_data = (JOCTET*) malloc(total_length * sizeof(JOCTET));
232   if (icc_data == NULL)
233     return FALSE;        /* oops, out of memory */
234 
235   /* and fill it in */
236   for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
237     if (JPEGMarkerIsIcc(marker)) {
238       JOCTET FAR* src_ptr;
239       JOCTET* dst_ptr;
240       unsigned int length;
241       seq_no = GETJOCTET(marker->data[12]);
242       dst_ptr = icc_data + data_offset[seq_no];
243       src_ptr = marker->data + JPEG_ICC_OVERHEAD_LEN;
244       length = data_length[seq_no];
245       while (length--) {
246         *dst_ptr++ = *src_ptr++;
247       }
248     }
249   }
250 
251   *icc_data_ptr = icc_data;
252   *icc_data_len = total_length;
253 
254   return TRUE;
255 }
256 
257 
loadJPEG(const char * filename)258 std::shared_ptr<heif_image> loadJPEG(const char* filename)
259 {
260   struct heif_image* image = nullptr;
261 
262 
263   // ### Code copied from LibVideoGfx and slightly modified to use HeifPixelImage
264 
265   struct jpeg_decompress_struct cinfo;
266   struct jpeg_error_mgr jerr;
267 
268   // to store embedded icc profile
269   uint32_t iccLen;
270   uint8_t* iccBuffer = NULL;
271 
272   // open input file
273 
274   FILE* infile;
275   if ((infile = fopen(filename, "rb")) == NULL) {
276     std::cerr << "Can't open " << filename << "\n";
277     exit(1);
278   }
279 
280 
281   // initialize decompressor
282 
283   jpeg_create_decompress(&cinfo);
284 
285   cinfo.err = jpeg_std_error(&jerr);
286   jpeg_stdio_src(&cinfo, infile);
287 
288   /* Adding this part to prepare for icc profile reading. */
289   jpeg_save_markers(&cinfo, JPEG_APP0 + 2, 0xFFFF);
290 
291   jpeg_read_header(&cinfo, TRUE);
292 
293   boolean embeddedIccFlag = ReadICCProfileFromJPEG(&cinfo, &iccBuffer, &iccLen);
294 
295   if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
296     cinfo.out_color_space = JCS_GRAYSCALE;
297 
298     jpeg_start_decompress(&cinfo);
299 
300     JSAMPARRAY buffer;
301     buffer = (*cinfo.mem->alloc_sarray)
302         ((j_common_ptr) &cinfo, JPOOL_IMAGE, cinfo.output_width * cinfo.output_components, 1);
303 
304 
305     // create destination image
306 
307     struct heif_error err = heif_image_create(cinfo.output_width, cinfo.output_height,
308                                               heif_colorspace_monochrome,
309                                               heif_chroma_monochrome,
310                                               &image);
311     (void) err;
312     // TODO: handle error
313 
314     heif_image_add_plane(image, heif_channel_Y, cinfo.output_width, cinfo.output_height, 8);
315 
316     int y_stride;
317     uint8_t* py = heif_image_get_plane(image, heif_channel_Y, &y_stride);
318 
319 
320     // read the image
321 
322     while (cinfo.output_scanline < cinfo.output_height) {
323       (void) jpeg_read_scanlines(&cinfo, buffer, 1);
324 
325       memcpy(py + (cinfo.output_scanline - 1) * y_stride, *buffer, cinfo.output_width);
326     }
327   }
328   else {
329     cinfo.out_color_space = JCS_YCbCr;
330 
331     jpeg_start_decompress(&cinfo);
332 
333     JSAMPARRAY buffer;
334     buffer = (*cinfo.mem->alloc_sarray)
335         ((j_common_ptr) &cinfo, JPOOL_IMAGE, cinfo.output_width * cinfo.output_components, 1);
336 
337 
338     // create destination image
339 
340     struct heif_error err = heif_image_create(cinfo.output_width, cinfo.output_height,
341                                               heif_colorspace_YCbCr,
342                                               heif_chroma_420,
343                                               &image);
344     (void) err;
345 
346     heif_image_add_plane(image, heif_channel_Y, cinfo.output_width, cinfo.output_height, 8);
347     heif_image_add_plane(image, heif_channel_Cb, (cinfo.output_width + 1) / 2, (cinfo.output_height + 1) / 2, 8);
348     heif_image_add_plane(image, heif_channel_Cr, (cinfo.output_width + 1) / 2, (cinfo.output_height + 1) / 2, 8);
349 
350     int y_stride;
351     int cb_stride;
352     int cr_stride;
353     uint8_t* py = heif_image_get_plane(image, heif_channel_Y, &y_stride);
354     uint8_t* pcb = heif_image_get_plane(image, heif_channel_Cb, &cb_stride);
355     uint8_t* pcr = heif_image_get_plane(image, heif_channel_Cr, &cr_stride);
356 
357     // read the image
358 
359     //printf("jpeg size: %d %d\n",cinfo.output_width, cinfo.output_height);
360 
361     while (cinfo.output_scanline < cinfo.output_height) {
362       JOCTET* bufp;
363 
364       (void) jpeg_read_scanlines(&cinfo, buffer, 1);
365 
366       bufp = buffer[0];
367 
368       int y = cinfo.output_scanline - 1;
369 
370       for (unsigned int x = 0; x < cinfo.output_width; x += 2) {
371         py[y * y_stride + x] = *bufp++;
372         pcb[y / 2 * cb_stride + x / 2] = *bufp++;
373         pcr[y / 2 * cr_stride + x / 2] = *bufp++;
374 
375         if (x + 1 < cinfo.output_width) {
376           py[y * y_stride + x + 1] = *bufp++;
377         }
378 
379         bufp += 2;
380       }
381 
382 
383       if (cinfo.output_scanline < cinfo.output_height) {
384         (void) jpeg_read_scanlines(&cinfo, buffer, 1);
385 
386         bufp = buffer[0];
387 
388         y = cinfo.output_scanline - 1;
389 
390         for (unsigned int x = 0; x < cinfo.output_width; x++) {
391           py[y * y_stride + x] = *bufp++;
392           bufp += 2;
393         }
394       }
395     }
396   }
397 
398   if (embeddedIccFlag && iccLen > 0) {
399     heif_image_set_raw_color_profile(image, "prof", iccBuffer, (size_t) iccLen);
400   }
401 
402   // cleanup
403   free(iccBuffer);
404   jpeg_finish_decompress(&cinfo);
405   jpeg_destroy_decompress(&cinfo);
406 
407   fclose(infile);
408 
409   return std::shared_ptr<heif_image>(image,
410                                      [](heif_image* img) { heif_image_release(img); });
411 }
412 
413 #else
loadJPEG(const char * filename)414 std::shared_ptr<heif_image> loadJPEG(const char* filename)
415 {
416   std::cerr << "Cannot load JPEG because libjpeg support was not compiled.\n";
417   exit(1);
418 
419   return nullptr;
420 }
421 #endif
422 
423 
424 #if HAVE_LIBPNG
425 
426 static void
user_read_fn(png_structp png_ptr,png_bytep data,png_size_t length)427 user_read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
428 {
429   FILE* fh = (FILE*) png_get_io_ptr(png_ptr);
430   size_t n = fread((char*) data, length, 1, fh);
431   (void) n;
432 } // user_read_data
433 
434 
loadPNG(const char * filename,int output_bit_depth)435 std::shared_ptr<heif_image> loadPNG(const char* filename, int output_bit_depth)
436 {
437   FILE* fh = fopen(filename, "rb");
438   if (!fh) {
439     std::cerr << "Can't open " << filename << "\n";
440     exit(1);
441   }
442 
443 
444   // ### Code copied from LibVideoGfx and slightly modified to use HeifPixelImage
445 
446   struct heif_image* image = nullptr;
447 
448   png_structp png_ptr;
449   png_infop info_ptr;
450   png_uint_32 width, height;
451   int bit_depth, color_type, interlace_type;
452   int compression_type;
453   png_charp name;
454 #if (PNG_LIBPNG_VER < 10500)
455   png_charp png_profile_data;
456 #else
457   png_bytep png_profile_data;
458 #endif
459   uint8_t* profile_data = nullptr;
460   png_uint_32 profile_length = 5;
461 
462   /* Create and initialize the png_struct with the desired error handler
463    * functions.  If you want to use the default stderr and longjump method,
464    * you can supply NULL for the last three parameters.  We also supply the
465    * the compiler header file version, so that we know if the application
466    * was compiled with a compatible version of the library.  REQUIRED
467    */
468   png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
469   assert(png_ptr != NULL);
470 
471   /* Allocate/initialize the memory for image information.  REQUIRED. */
472   info_ptr = png_create_info_struct(png_ptr);
473   if (info_ptr == NULL) {
474     png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL);
475     assert(false); // , "could not create info_ptr");
476   } // if
477 
478   /* Set error handling if you are using the setjmp/longjmp method (this is
479    * the normal method of doing things with libpng).  REQUIRED unless you
480    * set up your own error handlers in the png_create_read_struct() earlier.
481    */
482   if (setjmp(png_jmpbuf(png_ptr))) {
483     /* Free all of the memory associated with the png_ptr and info_ptr */
484     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
485     /* If we get here, we had a problem reading the file */
486     assert(false); // , "fatal error in png library");
487   } // if
488 
489   /* If you are using replacement read functions, instead of calling
490    * png_init_io() here you would call: */
491   png_set_read_fn(png_ptr, (void*) fh, user_read_fn);
492   /* where user_io_ptr is a structure you want available to the callbacks */
493 
494   /* The call to png_read_info() gives us all of the information from the
495    * PNG file before the first IDAT (image data chunk).  REQUIRED
496    */
497   png_read_info(png_ptr, info_ptr);
498 
499   png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
500                &interlace_type, NULL, NULL);
501 
502   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) {
503     if (PNG_INFO_iCCP ==
504         png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, &png_profile_data, &profile_length) &&
505         profile_length > 0) {
506       profile_data = (uint8_t*) malloc(profile_length);
507       if (profile_data) {
508         memcpy(profile_data, png_profile_data, profile_length);
509       }
510     }
511   }
512   /**** Set up the data transformations you want.  Note that these are all
513    **** optional.  Only call them if you want/need them.  Many of the
514    **** transformations only work on specific types of images, and many
515    **** are mutually exclusive.
516    ****/
517 
518   // \TODO
519   //      /* Strip alpha bytes from the input data without combining with the
520   //       * background (not recommended).
521   //       */
522   //      png_set_strip_alpha(png_ptr);
523 
524   /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
525    * byte into separate bytes (useful for paletted and grayscale images).
526    */
527   png_set_packing(png_ptr);
528 
529 
530   /* Expand paletted colors into true RGB triplets */
531   if (color_type == PNG_COLOR_TYPE_PALETTE)
532     png_set_expand(png_ptr);
533 
534   /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
535   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
536     png_set_expand(png_ptr);
537 
538   /* Set the background color to draw transparent and alpha images over.
539    * It is possible to set the red, green, and blue components directly
540    * for paletted images instead of supplying a palette index.  Note that
541    * even if the PNG file supplies a background, you are not required to
542    * use it - you should use the (solid) application background if it has one.
543    */
544 
545 #if 0
546   // \TODO 0 is index in color lookup table - correct? used already?
547   png_color_16 my_background = {0, 255, 255, 255, 255};
548   png_color_16 *image_background;
549 
550   if (png_get_bKGD(png_ptr, info_ptr, &image_background))
551     png_set_background(png_ptr, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0);
552   else
553     png_set_background(png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
554 #endif
555 
556 
557   /* Optional call to gamma correct and add the background to the palette
558    * and update info structure.  REQUIRED if you are expecting libpng to
559    * update the palette for you (ie you selected such a transform above).
560    */
561   png_read_update_info(png_ptr, info_ptr);
562 
563   /* Allocate the memory to hold the image using the fields of info_ptr. */
564 
565   /* The easiest way to read the image: */
566   uint8_t** row_pointers = new png_bytep[height];
567   assert(row_pointers != NULL);
568 
569   for (uint32_t y = 0; y < height; y++) {
570     row_pointers[y] = (png_bytep) malloc(png_get_rowbytes(png_ptr, info_ptr));
571     assert(row_pointers[y] != NULL);
572   } // for
573 
574   /* Now it's time to read the image.  One of these methods is REQUIRED */
575   png_read_image(png_ptr, row_pointers);
576 
577   /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
578   png_read_end(png_ptr, info_ptr);
579 
580   /* clean up after the read, and free any memory allocated - REQUIRED */
581   png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
582 
583 
584   // OK, now we should have the png image in some way in
585   // row_pointers, have fun with it
586 
587   int band = 0;
588   switch (color_type) {
589     case PNG_COLOR_TYPE_GRAY:
590     case PNG_COLOR_TYPE_GRAY_ALPHA:
591       band = 1;
592       break;
593     case PNG_COLOR_TYPE_PALETTE:
594     case PNG_COLOR_TYPE_RGB:
595     case PNG_COLOR_TYPE_RGB_ALPHA:
596       band = 3;
597       break;
598     default:
599       assert(false); // , "unknown color type in png image.");
600   } // switch
601 
602 
603 
604 
605   struct heif_error err;
606 
607   bool has_alpha = (color_type & PNG_COLOR_MASK_ALPHA);
608 
609   if (band == 1 && bit_depth==8) {
610     err = heif_image_create((int) width, (int) height,
611                             heif_colorspace_monochrome,
612                             heif_chroma_monochrome,
613                             &image);
614     (void) err;
615 
616     heif_image_add_plane(image, heif_channel_Y, (int) width, (int) height, 8);
617 
618     int y_stride;
619     int a_stride;
620     uint8_t* py = heif_image_get_plane(image, heif_channel_Y, &y_stride);
621     uint8_t* pa = nullptr;
622 
623     if (has_alpha) {
624       heif_image_add_plane(image, heif_channel_Alpha, (int) width, (int) height, 8);
625 
626       pa = heif_image_get_plane(image, heif_channel_Alpha, &a_stride);
627     }
628 
629 
630     for (uint32_t y = 0; y < height; y++) {
631       uint8_t* p = row_pointers[y];
632 
633       if (has_alpha) {
634         for (uint32_t x = 0; x < width; x++) {
635           py[y * y_stride + x] = *p++;
636           pa[y * a_stride + x] = *p++;
637         }
638       }
639       else {
640         memcpy(&py[y * y_stride], p, width);
641       }
642     }
643   }
644   else if (band == 1) {
645     assert(bit_depth>8);
646 
647     err = heif_image_create((int) width, (int) height,
648                             heif_colorspace_monochrome,
649                             heif_chroma_monochrome,
650                             &image);
651     (void) err;
652 
653     int bdShift = 16 - output_bit_depth;
654 
655     heif_image_add_plane(image, heif_channel_Y, (int) width, (int) height, output_bit_depth);
656 
657     int y_stride;
658     int a_stride = 0;
659     uint16_t* py = (uint16_t*)heif_image_get_plane(image, heif_channel_Y, &y_stride);
660     uint16_t* pa = nullptr;
661 
662     if (has_alpha) {
663       heif_image_add_plane(image, heif_channel_Alpha, (int) width, (int) height, output_bit_depth);
664 
665       pa = (uint16_t*)heif_image_get_plane(image, heif_channel_Alpha, &a_stride);
666     }
667 
668     y_stride /= 2;
669     a_stride /= 2;
670 
671     for (uint32_t y = 0; y < height; y++) {
672       uint8_t* p = row_pointers[y];
673 
674       if (has_alpha) {
675         for (uint32_t x = 0; x < width; x++) {
676           uint16_t vp = (uint16_t) (((p[0] << 8) | p[1]) >> bdShift);
677           uint16_t va = (uint16_t) (((p[2] << 8) | p[3]) >> bdShift);
678 
679           py[x + y * y_stride] = vp;
680           pa[x + y * y_stride] = va;
681 
682           p += 4;
683         }
684       }
685       else {
686         for (uint32_t x = 0; x < width; x++) {
687           uint16_t vp = (uint16_t) (((p[0] << 8) | p[1]) >> bdShift);
688 
689           py[x + y * y_stride] = vp;
690 
691           p += 2;
692         }
693       }
694     }
695   }
696   else if (bit_depth == 8) {
697     err = heif_image_create((int) width, (int) height,
698                             heif_colorspace_RGB,
699                             has_alpha ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB,
700                             &image);
701     (void) err;
702 
703     heif_image_add_plane(image, heif_channel_interleaved, (int) width, (int) height,
704                          has_alpha ? 32 : 24);
705 
706     int stride;
707     uint8_t* p = heif_image_get_plane(image, heif_channel_interleaved, &stride);
708 
709     for (uint32_t y = 0; y < height; y++) {
710       if (has_alpha) {
711         memcpy(p + y * stride, row_pointers[y], width * 4);
712       }
713       else {
714         memcpy(p + y * stride, row_pointers[y], width * 3);
715       }
716     }
717   }
718   else {
719     err = heif_image_create((int) width, (int) height,
720                             heif_colorspace_RGB,
721                             has_alpha ?
722                             heif_chroma_interleaved_RRGGBBAA_LE :
723                             heif_chroma_interleaved_RRGGBB_LE,
724                             &image);
725     (void) err;
726 
727     int bdShift = 16 - output_bit_depth;
728 
729     heif_image_add_plane(image, heif_channel_interleaved, (int) width, (int) height, output_bit_depth);
730 
731     int stride;
732     uint8_t* p_out = (uint8_t*) heif_image_get_plane(image, heif_channel_interleaved, &stride);
733 
734     for (uint32_t y = 0; y < height; y++) {
735       uint8_t* p = row_pointers[y];
736 
737       uint32_t nVal = (has_alpha ? 4 : 3) * width;
738 
739       for (uint32_t x = 0; x < nVal; x++) {
740         uint16_t v = (uint16_t) (((p[0] << 8) | p[1]) >> bdShift);
741         p_out[2 * x + y * stride + 1] = (uint8_t) (v >> 8);
742         p_out[2 * x + y * stride + 0] = (uint8_t) (v & 0xFF);
743         p += 2;
744       }
745     }
746   }
747 
748   if (profile_data && profile_length > 0) {
749     heif_image_set_raw_color_profile(image, "prof", profile_data, (size_t) profile_length);
750   }
751 
752   free(profile_data);
753   for (uint32_t y = 0; y < height; y++) {
754     free(row_pointers[y]);
755   } // for
756 
757   delete[] row_pointers;
758 
759   return std::shared_ptr<heif_image>(image,
760                                      [](heif_image* img) { heif_image_release(img); });
761 }
762 
763 #else
loadPNG(const char * filename,int output_bit_depth)764 std::shared_ptr<heif_image> loadPNG(const char* filename, int output_bit_depth)
765 {
766   std::cerr << "Cannot load PNG because libpng support was not compiled.\n";
767   exit(1);
768 
769   return nullptr;
770 }
771 #endif
772 
773 
loadY4M(const char * filename)774 std::shared_ptr<heif_image> loadY4M(const char* filename)
775 {
776   struct heif_image* image = nullptr;
777 
778 
779   // open input file
780 
781   std::ifstream istr(filename, std::ios_base::binary);
782   if (istr.fail()) {
783     std::cerr << "Can't open " << filename << "\n";
784     exit(1);
785   }
786 
787 
788   std::string header;
789   getline(istr, header);
790 
791   if (header.find("YUV4MPEG2 ") != 0) {
792     std::cerr << "Input is not a Y4M file.\n";
793     exit(1);
794   }
795 
796   int w = -1;
797   int h = -1;
798 
799   size_t pos = 0;
800   for (;;) {
801     pos = header.find(' ', pos + 1) + 1;
802     if (pos == std::string::npos) {
803       break;
804     }
805 
806     size_t end = header.find_first_of(" \n", pos + 1);
807     if (end == std::string::npos) {
808       break;
809     }
810 
811     if (end - pos <= 1) {
812       std::cerr << "Header format error in Y4M file.\n";
813       exit(1);
814     }
815 
816     char tag = header[pos];
817     std::string value = header.substr(pos + 1, end - pos - 1);
818     if (tag == 'W') {
819       w = atoi(value.c_str());
820     }
821     else if (tag == 'H') {
822       h = atoi(value.c_str());
823     }
824   }
825 
826   std::string frameheader;
827   getline(istr, frameheader);
828 
829   if (frameheader != "FRAME") {
830     std::cerr << "Y4M misses the frame header.\n";
831     exit(1);
832   }
833 
834   if (w < 0 || h < 0) {
835     std::cerr << "Y4M has invalid frame size.\n";
836     exit(1);
837   }
838 
839   struct heif_error err = heif_image_create(w, h,
840                                             heif_colorspace_YCbCr,
841                                             heif_chroma_420,
842                                             &image);
843   (void) err;
844   // TODO: handle error
845 
846   heif_image_add_plane(image, heif_channel_Y, w, h, 8);
847   heif_image_add_plane(image, heif_channel_Cb, (w + 1) / 2, (h + 1) / 2, 8);
848   heif_image_add_plane(image, heif_channel_Cr, (w + 1) / 2, (h + 1) / 2, 8);
849 
850   int y_stride, cb_stride, cr_stride;
851   uint8_t* py = heif_image_get_plane(image, heif_channel_Y, &y_stride);
852   uint8_t* pcb = heif_image_get_plane(image, heif_channel_Cb, &cb_stride);
853   uint8_t* pcr = heif_image_get_plane(image, heif_channel_Cr, &cr_stride);
854 
855   for (int y = 0; y < h; y++) {
856     istr.read((char*) (py + y * y_stride), w);
857   }
858 
859   for (int y = 0; y < (h + 1) / 2; y++) {
860     istr.read((char*) (pcb + y * cb_stride), (w + 1) / 2);
861   }
862 
863   for (int y = 0; y < (h + 1) / 2; y++) {
864     istr.read((char*) (pcr + y * cr_stride), (w + 1) / 2);
865   }
866 
867   return std::shared_ptr<heif_image>(image,
868                                      [](heif_image* img) { heif_image_release(img); });
869 }
870 
871 
list_encoder_parameters(heif_encoder * encoder)872 void list_encoder_parameters(heif_encoder* encoder)
873 {
874   std::cerr << "Parameters for encoder `" << heif_encoder_get_name(encoder) << "`:\n";
875 
876   const struct heif_encoder_parameter* const* params = heif_encoder_list_parameters(encoder);
877   for (int i = 0; params[i]; i++) {
878     const char* name = heif_encoder_parameter_get_name(params[i]);
879 
880     switch (heif_encoder_parameter_get_type(params[i])) {
881       case heif_encoder_parameter_type_integer: {
882         heif_error error;
883 
884         std::cerr << "  " << name;
885 
886         if (heif_encoder_has_default(encoder, name)) {
887           int value;
888           error = heif_encoder_get_parameter_integer(encoder, name, &value);
889           (void) error;
890 
891           std::cerr << ", default=" << value;
892         }
893 
894         int have_minimum, have_maximum, minimum, maximum, num_valid_values;
895         const int* valid_values = nullptr;
896         error = heif_encoder_parameter_integer_valid_values(encoder, name,
897                                                             &have_minimum, &have_maximum,
898                                                             &minimum, &maximum,
899                                                             &num_valid_values,
900                                                             &valid_values);
901 
902         if (have_minimum || have_maximum) {  // TODO: only one is set
903           std::cerr << ", [" << minimum << ";" << maximum << "]";
904         }
905 
906         if (num_valid_values > 0) {
907           std::cerr << ", {";
908 
909           for (int p=0;p<num_valid_values;p++) {
910             if (p>0) {
911               std::cerr << ", ";
912             }
913 
914             std::cerr << valid_values[p];
915           }
916 
917           std::cerr << "}";
918         }
919 
920         std::cerr << "\n";
921       }
922         break;
923 
924       case heif_encoder_parameter_type_boolean: {
925         heif_error error;
926         std::cerr << "  " << name;
927 
928         if (heif_encoder_has_default(encoder, name)) {
929           int value;
930           error = heif_encoder_get_parameter_boolean(encoder, name, &value);
931           (void) error;
932 
933           std::cerr << ", default=" << (value ? "true" : "false");
934         }
935 
936         std::cerr << "\n";
937       }
938         break;
939 
940       case heif_encoder_parameter_type_string: {
941         heif_error error;
942         std::cerr << "  " << name;
943 
944         if (heif_encoder_has_default(encoder, name)) {
945           const int value_size = 50;
946           char value[value_size];
947           error = heif_encoder_get_parameter_string(encoder, name, value, value_size);
948           (void) error;
949 
950           std::cerr << ", default=" << value;
951         }
952 
953         const char* const* valid_options;
954         error = heif_encoder_parameter_string_valid_values(encoder, name, &valid_options);
955 
956         if (valid_options) {
957           std::cerr << ", { ";
958           for (int i = 0; valid_options[i]; i++) {
959             if (i > 0) { std::cerr << ","; }
960             std::cerr << valid_options[i];
961           }
962           std::cerr << " }";
963         }
964 
965         std::cerr << "\n";
966       }
967         break;
968     }
969   }
970 }
971 
972 
set_params(struct heif_encoder * encoder,std::vector<std::string> params)973 void set_params(struct heif_encoder* encoder, std::vector<std::string> params)
974 {
975   for (std::string p : params) {
976     auto pos = p.find_first_of('=');
977     if (pos == std::string::npos || pos == 0 || pos == p.size() - 1) {
978       std::cerr << "Encoder parameter must be in the format 'name=value'\n";
979       exit(5);
980     }
981 
982     std::string name = p.substr(0, pos);
983     std::string value = p.substr(pos + 1);
984 
985     struct heif_error error = heif_encoder_set_parameter(encoder, name.c_str(), value.c_str());
986     if (error.code) {
987       std::cerr << "Error: " << error.message << "\n";
988       exit(5);
989     }
990   }
991 }
992 
993 
show_list_of_encoders(const heif_encoder_descriptor * const * encoder_descriptors,int count)994 static void show_list_of_encoders(const heif_encoder_descriptor*const* encoder_descriptors,
995                                   int count)
996 {
997   std::cout << "Encoders (first is default):\n";
998   for (int i = 0; i < count; i++) {
999     std::cout << "- " << heif_encoder_descriptor_get_id_name(encoder_descriptors[i])
1000               << " = "
1001               << heif_encoder_descriptor_get_name(encoder_descriptors[i])
1002               << "\n";
1003   }
1004 }
1005 
1006 
main(int argc,char ** argv)1007 int main(int argc, char** argv)
1008 {
1009   int quality = 50;
1010   bool lossless = false;
1011   std::string output_filename;
1012   int logging_level = 0;
1013   bool option_show_parameters = false;
1014   int thumbnail_bbox_size = 0;
1015   int output_bit_depth = 10;
1016   bool enc_av1f = false;
1017   bool crop_to_even_size = false;
1018 
1019   std::vector<std::string> raw_params;
1020 
1021 
1022   while (true) {
1023     int option_index = 0;
1024     int c = getopt_long(argc, argv, "hq:Lo:vPp:t:b:AEe:", long_options, &option_index);
1025     if (c == -1)
1026       break;
1027 
1028     switch (c) {
1029       case 'h':
1030         show_help(argv[0]);
1031         return 0;
1032       case 'q':
1033         quality = atoi(optarg);
1034         break;
1035       case 'L':
1036         lossless = true;
1037         break;
1038       case 'o':
1039         output_filename = optarg;
1040         break;
1041       case 'v':
1042         logging_level++;
1043         break;
1044       case 'P':
1045         option_show_parameters = true;
1046         break;
1047       case 'p':
1048         raw_params.push_back(optarg);
1049         break;
1050       case 't':
1051         thumbnail_bbox_size = atoi(optarg);
1052         break;
1053       case 'b':
1054         output_bit_depth = atoi(optarg);
1055         break;
1056       case 'A':
1057         enc_av1f = true;
1058         break;
1059       case 'E':
1060         crop_to_even_size = true;
1061         break;
1062       case 'e':
1063         encoderId = optarg;
1064         break;
1065       case OPTION_NCLX_MATRIX_COEFFICIENTS:
1066         nclx_matrix_coefficients = atoi(optarg);
1067         break;
1068       case OPTION_NCLX_COLOUR_PRIMARIES:
1069         nclx_colour_primaries = atoi(optarg);
1070         break;
1071       case OPTION_NCLX_TRANSFER_CHARACTERISTIC:
1072         nclx_transfer_characteristic = atoi(optarg);
1073         break;
1074       case OPTION_NCLX_FULL_RANGE_FLAG:
1075         nclx_full_range = atoi(optarg);
1076         break;
1077     }
1078   }
1079 
1080   if (quality < 0 || quality > 100) {
1081     std::cerr << "Invalid quality factor. Must be between 0 and 100.\n";
1082     return 5;
1083   }
1084 
1085   if (logging_level > 0) {
1086     logging_level += 2;
1087 
1088     if (logging_level > 4) {
1089       logging_level = 4;
1090     }
1091   }
1092 
1093 
1094 
1095   // ==============================================================================
1096 
1097   std::shared_ptr<heif_context> context(heif_context_alloc(),
1098                                         [](heif_context* c) { heif_context_free(c); });
1099   if (!context) {
1100     std::cerr << "Could not create context object\n";
1101     return 1;
1102   }
1103 
1104 
1105   struct heif_encoder* encoder = nullptr;
1106 
1107 #define MAX_ENCODERS 5
1108   const heif_encoder_descriptor* encoder_descriptors[MAX_ENCODERS];
1109   int count = heif_context_get_encoder_descriptors(context.get(),
1110                                                    enc_av1f ? heif_compression_AV1 : heif_compression_HEVC,
1111                                                    nullptr,
1112                                                    encoder_descriptors, MAX_ENCODERS);
1113 
1114   if (list_encoders) {
1115     show_list_of_encoders(encoder_descriptors, count);
1116     return 0;
1117   }
1118 
1119   if (count > 0) {
1120     int idx = 0;
1121     if (encoderId != nullptr) {
1122       for (int i = 0; i <= count; i++) {
1123         if (i==count) {
1124           std::cerr << "Unknown encoder ID. Choose one from the list below.\n";
1125           show_list_of_encoders(encoder_descriptors, count);
1126           return 5;
1127         }
1128 
1129         if (strcmp(encoderId, heif_encoder_descriptor_get_id_name(encoder_descriptors[i])) == 0) {
1130           idx = i;
1131           break;
1132         }
1133       }
1134     }
1135 
1136     heif_error error = heif_context_get_encoder(context.get(), encoder_descriptors[idx], &encoder);
1137     if (error.code) {
1138       std::cerr << error.message << "\n";
1139       return 5;
1140     }
1141   }
1142   else {
1143     std::cerr << "No " << (enc_av1f ? "AV1" : "HEVC") << " encoder available.\n";
1144     return 5;
1145   }
1146 
1147 
1148   if (option_show_parameters) {
1149     list_encoder_parameters(encoder);
1150     return 0;
1151   }
1152 
1153 
1154   if (optind > argc - 1) {
1155     show_help(argv[0]);
1156     return 0;
1157   }
1158 
1159 
1160   struct heif_error error;
1161 
1162   for (; optind < argc; optind++) {
1163     std::string input_filename = argv[optind];
1164 
1165     if (output_filename.empty()) {
1166       std::string filename_without_suffix;
1167       std::string::size_type dot_position = input_filename.find_last_of('.');
1168       if (dot_position != std::string::npos) {
1169         filename_without_suffix = input_filename.substr(0, dot_position);
1170       }
1171       else {
1172         filename_without_suffix = input_filename;
1173       }
1174 
1175       output_filename = filename_without_suffix + (enc_av1f ? ".avif" : ".heic");
1176     }
1177 
1178 
1179     // ==============================================================================
1180 
1181     // get file type from file name
1182 
1183     std::string suffix;
1184     auto suffix_pos = input_filename.find_last_of('.');
1185     if (suffix_pos != std::string::npos) {
1186       suffix = input_filename.substr(suffix_pos + 1);
1187       std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower);
1188     }
1189 
1190     enum
1191     {
1192       PNG, JPEG, Y4M
1193     } filetype = JPEG;
1194     if (suffix == "png") {
1195       filetype = PNG;
1196     }
1197     else if (suffix == "y4m") {
1198       filetype = Y4M;
1199     }
1200 
1201     std::shared_ptr<heif_image> image;
1202     if (filetype == PNG) {
1203       image = loadPNG(input_filename.c_str(), output_bit_depth);
1204     }
1205     else if (filetype == Y4M) {
1206       image = loadY4M(input_filename.c_str());
1207     }
1208     else {
1209       image = loadJPEG(input_filename.c_str());
1210     }
1211 
1212     heif_color_profile_nclx nclx;
1213     nclx.matrix_coefficients = (heif_matrix_coefficients) nclx_matrix_coefficients;
1214     nclx.transfer_characteristics = (heif_transfer_characteristics) nclx_transfer_characteristic;
1215     nclx.color_primaries = (heif_color_primaries) nclx_colour_primaries;
1216     nclx.full_range_flag = (uint8_t) nclx_full_range;
1217 
1218     heif_image_set_nclx_color_profile(image.get(), &nclx);
1219 
1220     heif_encoder_set_lossy_quality(encoder, quality);
1221     heif_encoder_set_lossless(encoder, lossless);
1222     heif_encoder_set_logging_level(encoder, logging_level);
1223 
1224     set_params(encoder, raw_params);
1225 
1226     struct heif_encoding_options* options = heif_encoding_options_alloc();
1227     options->save_alpha_channel = (uint8_t) master_alpha;
1228     options->save_two_colr_boxes_when_ICC_and_nclx_available = (uint8_t)two_colr_boxes;
1229 
1230     if (crop_to_even_size) {
1231       if (heif_image_get_primary_width(image.get()) == 1 ||
1232           heif_image_get_primary_height(image.get()) == 1) {
1233         std::cerr << "Image only has a size of 1 pixel width or height. Cannot crop to even size.\n";
1234         return 1;
1235       }
1236 
1237       std::cerr << "Warning: option --even-size/-E is deprecated as it is not needed anymore.\n";
1238 
1239       int right = heif_image_get_primary_width(image.get()) % 2;
1240       int bottom = heif_image_get_primary_height(image.get()) % 2;
1241 
1242       error = heif_image_crop(image.get(), 0, right, 0, bottom);
1243       if (error.code != 0) {
1244         heif_encoding_options_free(options);
1245         std::cerr << "Could not crop image: " << error.message << "\n";
1246         return 1;
1247       }
1248     }
1249 
1250 
1251     struct heif_image_handle* handle;
1252     error = heif_context_encode_image(context.get(),
1253                                       image.get(),
1254                                       encoder,
1255                                       options,
1256                                       &handle);
1257     if (error.code != 0) {
1258       heif_encoding_options_free(options);
1259       std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n";
1260       return 1;
1261     }
1262 
1263     if (thumbnail_bbox_size > 0) {
1264       // encode thumbnail
1265 
1266       struct heif_image_handle* thumbnail_handle;
1267 
1268       options->save_alpha_channel = master_alpha && thumb_alpha;
1269 
1270       error = heif_context_encode_thumbnail(context.get(),
1271                                             image.get(),
1272                                             handle,
1273                                             encoder,
1274                                             options,
1275                                             thumbnail_bbox_size,
1276                                             &thumbnail_handle);
1277       if (error.code) {
1278         heif_encoding_options_free(options);
1279         std::cerr << "Could not generate thumbnail: " << error.message << "\n";
1280         return 5;
1281       }
1282 
1283       if (thumbnail_handle) {
1284         heif_image_handle_release(thumbnail_handle);
1285       }
1286     }
1287 
1288     heif_image_handle_release(handle);
1289     heif_encoding_options_free(options);
1290   }
1291 
1292   heif_encoder_release(encoder);
1293 
1294   error = heif_context_write_to_file(context.get(), output_filename.c_str());
1295   if (error.code) {
1296     std::cerr << error.message << "\n";
1297     return 5;
1298   }
1299 
1300   return 0;
1301 }
1302