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