/src/libheif/libheif/plugins/encoder_openjph.cc
Line | Count | Source |
1 | | /* |
2 | | * OpenJPH codec. |
3 | | * Copyright (c) 2023 Devon Sookhoo |
4 | | * Copyright (c) 2023 Dirk Farin <dirk.farin@gmail.com> |
5 | | * Copyright (C) 2024 Brad Hards |
6 | | * |
7 | | * This file is part of libheif. |
8 | | * |
9 | | * libheif is free software: you can redistribute it and/or modify |
10 | | * it under the terms of the GNU Lesser General Public License as |
11 | | * published by the Free Software Foundation, either version 3 of |
12 | | * the License, or (at your option) any later version. |
13 | | * |
14 | | * libheif is distributed in the hope that it will be useful, |
15 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | | * GNU Lesser General Public License for more details. |
18 | | * |
19 | | * You should have received a copy of the GNU Lesser General Public License |
20 | | * along with libheif. If not, see <http://www.gnu.org/licenses/>. |
21 | | */ |
22 | | |
23 | | // Portions of this code adapted from OpenJPH's ojph_compress.cpp |
24 | | // which is under the following license: |
25 | | //***************************************************************************/ |
26 | | // This software is released under the 2-Clause BSD license, included |
27 | | // below. |
28 | | // |
29 | | // Copyright (c) 2019, Aous Naman |
30 | | // Copyright (c) 2019, Kakadu Software Pty Ltd, Australia |
31 | | // Copyright (c) 2019, The University of New South Wales, Australia |
32 | | // |
33 | | // Redistribution and use in source and binary forms, with or without |
34 | | // modification, are permitted provided that the following conditions are |
35 | | // met: |
36 | | // |
37 | | // 1. Redistributions of source code must retain the above copyright |
38 | | // notice, this list of conditions and the following disclaimer. |
39 | | // |
40 | | // 2. Redistributions in binary form must reproduce the above copyright |
41 | | // notice, this list of conditions and the following disclaimer in the |
42 | | // documentation and/or other materials provided with the distribution. |
43 | | // |
44 | | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
45 | | // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
46 | | // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
47 | | // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
48 | | // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
49 | | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
50 | | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
51 | | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
52 | | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
53 | | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
54 | | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
55 | | //***************************************************************************/ |
56 | | |
57 | | |
58 | | #include "libheif/heif.h" |
59 | | #include "libheif/heif_plugin.h" |
60 | | #include "encoder_openjph.h" |
61 | | |
62 | | #include "openjph/ojph_mem.h" |
63 | | #include "openjph/ojph_defs.h" |
64 | | #include "openjph/ojph_file.h" |
65 | | #include "openjph/ojph_codestream.h" |
66 | | #include "openjph/ojph_params.h" |
67 | | #include "openjph/ojph_version.h" |
68 | | |
69 | | #include <string.h> |
70 | | |
71 | | #include <cassert> |
72 | | #include <limits> |
73 | | #include <sstream> |
74 | | #include <string> |
75 | | #include <vector> |
76 | | |
77 | | #include <iostream> |
78 | | |
79 | | static const int OJPH_PLUGIN_PRIORITY = 80; |
80 | | |
81 | | |
82 | | struct encoder_struct_ojph |
83 | | { |
84 | | // We do this for API reasons. Has no effect at this stage. |
85 | | int quality = 70; |
86 | | heif_chroma chroma = heif_chroma_undefined; |
87 | | |
88 | | // Context |
89 | | ojph::codestream codestream; |
90 | | std::string comment; |
91 | | |
92 | | // --- output |
93 | | bool data_read = false; |
94 | | ojph::mem_outfile outfile; |
95 | | }; |
96 | | |
97 | | |
98 | | #define MAX_NPARAMETERS 10 |
99 | | static heif_encoder_parameter ojph_encoder_params[MAX_NPARAMETERS]; |
100 | | const static heif_encoder_parameter* ojph_encoder_parameter_ptrs[MAX_NPARAMETERS + 1]; |
101 | | |
102 | | static const char* kParam_chroma = "chroma"; |
103 | | static const char* const kParam_chroma_valid_values[] = { |
104 | | "420", "422", "444", nullptr |
105 | | }; |
106 | | |
107 | | static const char* kParam_num_decompositions = "num_decompositions"; |
108 | | static const int NUM_DECOMPOSITIONS_MIN = 0; |
109 | | static const int NUM_DECOMPOSITIONS_MAX = 32; |
110 | | |
111 | | static const char* kParam_progression_order = "progression_order"; |
112 | | static const char* const kParam_progression_order_valid_values[] = { |
113 | | "LRCP", "RLCP", "RPCL", "PCRL", "CPRL", nullptr |
114 | | }; |
115 | | |
116 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 10 |
117 | | static const char* kParam_tlm_marker = "tlm_marker"; |
118 | | #endif |
119 | | |
120 | | static const char* kParam_codestream_comment = "codestream_comment"; |
121 | | |
122 | | static const char* kParam_tile_size = "tile_size"; |
123 | | |
124 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 11 |
125 | | static const char* kParam_tilepart_division = "tilepart_division"; |
126 | | static const char* const kParam_tilepart_division_valid_values[] = { |
127 | | "none", "resolution", "component", "both", nullptr |
128 | | }; |
129 | | #endif |
130 | | |
131 | | static const char* kParam_block_dimensions = "block_dimensions"; |
132 | | |
133 | | static void ojph_init_encoder_parameters() |
134 | 253 | { |
135 | 253 | heif_encoder_parameter* p = ojph_encoder_params; |
136 | 253 | const heif_encoder_parameter** d = ojph_encoder_parameter_ptrs; |
137 | 253 | int i = 0; |
138 | | |
139 | 253 | assert(i < MAX_NPARAMETERS); |
140 | 253 | p->version = 2; |
141 | 253 | p->name = heif_encoder_parameter_name_lossless; |
142 | 253 | p->type = heif_encoder_parameter_type_boolean; |
143 | 253 | p->boolean.default_value = false; |
144 | 253 | p->has_default = true; |
145 | 253 | d[i++] = p++; |
146 | | |
147 | 253 | assert(i < MAX_NPARAMETERS); |
148 | 253 | p->version = 2; |
149 | 253 | p->name = kParam_chroma; |
150 | 253 | p->type = heif_encoder_parameter_type_string; |
151 | 253 | p->string.default_value = "444"; |
152 | 253 | p->has_default = true; |
153 | 253 | p->string.valid_values = kParam_chroma_valid_values; |
154 | 253 | d[i++] = p++; |
155 | | |
156 | 253 | assert(i < MAX_NPARAMETERS); |
157 | 253 | p->version = 2; |
158 | 253 | p->name = kParam_num_decompositions; |
159 | 253 | p->type = heif_encoder_parameter_type_integer; |
160 | 253 | p->integer.default_value = 5; |
161 | 253 | p->integer.have_minimum_maximum = true; |
162 | 253 | p->integer.minimum = NUM_DECOMPOSITIONS_MIN; |
163 | 253 | p->integer.maximum = NUM_DECOMPOSITIONS_MAX; |
164 | 253 | p->integer.valid_values = NULL; |
165 | 253 | p->integer.num_valid_values = 0; |
166 | 253 | p->has_default = true; |
167 | 253 | d[i++] = p++; |
168 | | |
169 | 253 | assert(i < MAX_NPARAMETERS); |
170 | 253 | p->version = 2; |
171 | 253 | p->name = kParam_progression_order; |
172 | 253 | p->type = heif_encoder_parameter_type_string; |
173 | 253 | p->string.default_value = "RPCL"; |
174 | 253 | p->has_default = true; |
175 | 253 | p->string.valid_values = kParam_progression_order_valid_values; |
176 | 253 | d[i++] = p++; |
177 | | |
178 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 10 |
179 | | assert(i < MAX_NPARAMETERS); |
180 | | p->version = 2; |
181 | | p->name = kParam_tlm_marker; |
182 | | p->type = heif_encoder_parameter_type_boolean; |
183 | | p->boolean.default_value = false; |
184 | | p->has_default = true; |
185 | | d[i++] = p++; |
186 | | #endif |
187 | | |
188 | 253 | assert(i < MAX_NPARAMETERS); |
189 | 253 | p->version = 2; |
190 | 253 | p->name = kParam_codestream_comment; |
191 | 253 | p->type = heif_encoder_parameter_type_string; |
192 | 253 | p->string.default_value = nullptr; |
193 | 253 | p->has_default = false; |
194 | 253 | p->string.valid_values = nullptr; |
195 | 253 | d[i++] = p++; |
196 | | |
197 | 253 | assert(i < MAX_NPARAMETERS); |
198 | 253 | p->version = 2; |
199 | 253 | p->name = kParam_tile_size; |
200 | 253 | p->type = heif_encoder_parameter_type_string; |
201 | 253 | p->string.default_value = "0,0"; |
202 | 253 | p->has_default = true; |
203 | 253 | p->string.valid_values = nullptr; |
204 | 253 | d[i++] = p++; |
205 | | |
206 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 11 |
207 | | assert(i < MAX_NPARAMETERS); |
208 | | p->version = 2; |
209 | | p->name = kParam_tilepart_division; |
210 | | p->type = heif_encoder_parameter_type_string; |
211 | | p->string.default_value = "none"; |
212 | | p->has_default = true; |
213 | | p->string.valid_values = kParam_tilepart_division_valid_values; |
214 | | d[i++] = p++; |
215 | | #endif |
216 | | |
217 | 253 | assert(i < MAX_NPARAMETERS); |
218 | 253 | p->version = 2; |
219 | 253 | p->name = kParam_block_dimensions; |
220 | 253 | p->type = heif_encoder_parameter_type_string; |
221 | 253 | p->string.default_value = "64,64"; |
222 | 253 | p->has_default = true; |
223 | 253 | p->string.valid_values = nullptr; |
224 | 253 | d[i++] = p++; |
225 | | |
226 | 253 | d[i++] = nullptr; |
227 | 253 | } |
228 | | |
229 | | |
230 | | void ojph_init_plugin() |
231 | 253 | { |
232 | 253 | ojph_init_encoder_parameters(); |
233 | 253 | } |
234 | | |
235 | | void ojph_cleanup_plugin() |
236 | 0 | { |
237 | 0 | } |
238 | | |
239 | | |
240 | | //////////// Integer parameter setters |
241 | | |
242 | | // Note quality is part of the plugin API. |
243 | | |
244 | | heif_error ojph_set_parameter_quality(void* encoder_raw, int quality) |
245 | 0 | { |
246 | 0 | auto* encoder = (encoder_struct_ojph*) encoder_raw; |
247 | |
|
248 | 0 | encoder->quality = quality; |
249 | |
|
250 | 0 | return heif_error_ok; |
251 | 0 | } |
252 | | |
253 | | static const heif_error &ojph_set_num_decompositions(int value, encoder_struct_ojph *encoder) |
254 | 0 | { |
255 | 0 | if ((value < NUM_DECOMPOSITIONS_MIN) || (value > NUM_DECOMPOSITIONS_MAX)) { |
256 | 0 | return heif_error_invalid_parameter_value; |
257 | 0 | } |
258 | 0 | encoder->codestream.access_cod().set_num_decomposition(value); |
259 | 0 | return heif_error_ok; |
260 | 0 | } |
261 | | |
262 | | heif_error ojph_set_parameter_integer(void *encoder_raw, const char *name, int value) |
263 | 0 | { |
264 | 0 | encoder_struct_ojph* encoder = (encoder_struct_ojph*) encoder_raw; |
265 | |
|
266 | 0 | if (strcmp(name, heif_encoder_parameter_name_quality) == 0) { |
267 | 0 | return ojph_set_parameter_quality(encoder, value); |
268 | 0 | } else if (strcmp(name, kParam_num_decompositions) == 0) { |
269 | 0 | return ojph_set_num_decompositions(value, encoder); |
270 | 0 | } else { |
271 | 0 | return heif_error_unsupported_parameter; |
272 | 0 | } |
273 | 0 | } |
274 | | |
275 | | |
276 | | //////////// Integer parameter getters |
277 | | |
278 | | // Note quality is part of the plugin API |
279 | | |
280 | | heif_error ojph_get_parameter_quality(void* encoder_raw, int* quality) |
281 | 0 | { |
282 | 0 | auto* encoder = (encoder_struct_ojph*) encoder_raw; |
283 | |
|
284 | 0 | *quality = encoder->quality; |
285 | |
|
286 | 0 | return heif_error_ok; |
287 | 0 | } |
288 | | |
289 | | const heif_error &ojph_get_parameter_num_decompositions(encoder_struct_ojph *encoder, int *value) |
290 | 0 | { |
291 | 0 | *value = encoder->codestream.access_cod().get_num_decompositions(); |
292 | 0 | return heif_error_ok; |
293 | 0 | } |
294 | | |
295 | | heif_error ojph_get_parameter_integer(void *encoder_raw, const char *name, int *value) |
296 | 0 | { |
297 | 0 | encoder_struct_ojph* encoder = (encoder_struct_ojph*) encoder_raw; |
298 | |
|
299 | 0 | if (strcmp(name, heif_encoder_parameter_name_quality) == 0) { |
300 | 0 | return ojph_get_parameter_quality(encoder, value); |
301 | 0 | } else if (strcmp(name, kParam_num_decompositions) == 0) { |
302 | 0 | return ojph_get_parameter_num_decompositions(encoder, value); |
303 | 0 | } else { |
304 | 0 | return heif_error_unsupported_parameter; |
305 | 0 | } |
306 | 0 | } |
307 | | |
308 | | |
309 | | //////////// Boolean parameter setters |
310 | | |
311 | | // Note lossless is part of the plugin API |
312 | | |
313 | | heif_error ojph_set_parameter_lossless(void* encoder_raw, int lossless) |
314 | 0 | { |
315 | 0 | auto* encoder = (encoder_struct_ojph*) encoder_raw; |
316 | 0 | encoder->codestream.access_cod().set_reversible(lossless); |
317 | 0 | return heif_error_ok; |
318 | 0 | } |
319 | | |
320 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 10 |
321 | | const heif_error &ojph_set_tlm_marker_requested(encoder_struct_ojph *encoder, int value) |
322 | | { |
323 | | encoder->codestream.request_tlm_marker(value); |
324 | | return heif_error_ok; |
325 | | } |
326 | | #endif |
327 | | |
328 | | heif_error ojph_set_parameter_boolean(void *encoder_raw, const char *name, int value) |
329 | 0 | { |
330 | 0 | auto* encoder = (encoder_struct_ojph*) encoder_raw; |
331 | 0 | if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { |
332 | 0 | return ojph_set_parameter_lossless(encoder, value); |
333 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 10 |
334 | | } else if (strcmp(name, kParam_tlm_marker) == 0) { |
335 | | return ojph_set_tlm_marker_requested(encoder, value); |
336 | | #endif |
337 | 0 | } |
338 | 0 | return heif_error_unsupported_parameter; |
339 | 0 | } |
340 | | |
341 | | //////////// Boolean parameter getters |
342 | | |
343 | | // Note lossless is part of the plugin API |
344 | | |
345 | | heif_error ojph_get_parameter_lossless(void* encoder_raw, int* lossless) |
346 | 0 | { |
347 | 0 | auto* encoder = (encoder_struct_ojph*) encoder_raw; |
348 | 0 | *lossless = encoder->codestream.access_cod().is_reversible(); |
349 | 0 | return heif_error_ok; |
350 | 0 | } |
351 | | |
352 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 10 |
353 | | const heif_error &ojph_get_parameter_tlm_marker(encoder_struct_ojph *encoder, int *value) |
354 | | { |
355 | | *value = encoder->codestream.is_tlm_requested(); |
356 | | return heif_error_ok; |
357 | | } |
358 | | #endif |
359 | | |
360 | | heif_error ojph_get_parameter_boolean(void *encoder_raw, const char *name, int *value) |
361 | 0 | { |
362 | 0 | auto* encoder = (encoder_struct_ojph*) encoder_raw; |
363 | 0 | if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { |
364 | 0 | return ojph_get_parameter_lossless(encoder, value); |
365 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 10 |
366 | | } else if (strcmp(name, kParam_tlm_marker) == 0) { |
367 | | return ojph_get_parameter_tlm_marker(encoder, value); |
368 | | #endif |
369 | 0 | } else { |
370 | 0 | return heif_error_unsupported_parameter; |
371 | 0 | } |
372 | 0 | } |
373 | | |
374 | | |
375 | | //////////// String parameter getters |
376 | | |
377 | | static void safe_strcpy(char* dst, int dst_size, const char* src) |
378 | 0 | { |
379 | 0 | strncpy(dst, src, dst_size - 1); |
380 | 0 | dst[dst_size - 1] = 0; |
381 | 0 | } |
382 | | |
383 | | const heif_error &ojph_get_parameter_chroma(encoder_struct_ojph *encoder, char *value, int value_size) |
384 | 0 | { |
385 | 0 | switch (encoder->chroma) { |
386 | 0 | case heif_chroma_420: |
387 | 0 | safe_strcpy(value, value_size, "420"); |
388 | 0 | break; |
389 | 0 | case heif_chroma_422: |
390 | 0 | safe_strcpy(value, value_size, "422"); |
391 | 0 | break; |
392 | 0 | case heif_chroma_444: |
393 | 0 | safe_strcpy(value, value_size, "444"); |
394 | 0 | break; |
395 | 0 | case heif_chroma_undefined: |
396 | 0 | safe_strcpy(value, value_size, "undefined"); |
397 | 0 | break; |
398 | 0 | default: |
399 | 0 | assert(false); |
400 | 0 | return heif_error_invalid_parameter_value; |
401 | 0 | } |
402 | 0 | return heif_error_ok; |
403 | 0 | } |
404 | | |
405 | | const heif_error &ojph_get_parameter_progression_order(encoder_struct_ojph *encoder, char *value, int value_size) |
406 | 0 | { |
407 | 0 | safe_strcpy(value, value_size, encoder->codestream.access_cod().get_progression_order_as_string()); |
408 | 0 | return heif_error_ok; |
409 | 0 | } |
410 | | |
411 | | const heif_error &ojph_get_parameter_codestream_comment(encoder_struct_ojph *encoder, char *value, int value_size) |
412 | 0 | { |
413 | 0 | safe_strcpy(value, value_size, encoder->comment.c_str()); |
414 | 0 | return heif_error_ok; |
415 | 0 | } |
416 | | |
417 | | const heif_error &ojph_get_parameter_tile_size(encoder_struct_ojph *encoder, char *value, int value_size) |
418 | 0 | { |
419 | 0 | ojph::size tile_size = encoder->codestream.access_siz().get_tile_size(); |
420 | 0 | std::stringstream stringStream; |
421 | 0 | stringStream << tile_size.w << "," << tile_size.h; |
422 | 0 | safe_strcpy(value, value_size, stringStream.str().c_str()); |
423 | 0 | return heif_error_ok; |
424 | 0 | } |
425 | | |
426 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 11 |
427 | | const heif_error &ojph_get_parameter_tilepart_division(encoder_struct_ojph *encoder, char *value, int value_size) |
428 | | { |
429 | | bool res = encoder->codestream.is_tilepart_division_at_resolutions(); |
430 | | bool comp = encoder->codestream.is_tilepart_division_at_components(); |
431 | | if (res && comp) { |
432 | | safe_strcpy(value, value_size, "both"); |
433 | | } else if (res) { |
434 | | safe_strcpy(value, value_size, "resolution"); |
435 | | } else if (comp) { |
436 | | safe_strcpy(value, value_size, "component"); |
437 | | } else { |
438 | | safe_strcpy(value, value_size, "none"); |
439 | | } |
440 | | return heif_error_ok; |
441 | | } |
442 | | #endif |
443 | | |
444 | | const heif_error &ojph_get_parameter_block_dimensions(encoder_struct_ojph *encoder, char *value, int value_size) |
445 | 0 | { |
446 | 0 | ojph::size block_dims = encoder->codestream.access_cod().get_block_dims(); |
447 | 0 | std::stringstream stringStream; |
448 | 0 | stringStream << block_dims.w << "," << block_dims.h; |
449 | 0 | safe_strcpy(value, value_size, stringStream.str().c_str()); |
450 | 0 | return heif_error_ok; |
451 | 0 | } |
452 | | |
453 | | heif_error ojph_get_parameter_string(void *encoder_raw, const char *name, char *value, int value_size) |
454 | 0 | { |
455 | 0 | encoder_struct_ojph* encoder = (encoder_struct_ojph*) encoder_raw; |
456 | |
|
457 | 0 | if (strcmp(name, kParam_chroma) == 0) { |
458 | 0 | return ojph_get_parameter_chroma(encoder, value, value_size); |
459 | 0 | } else if (strcmp(name, kParam_progression_order) == 0) { |
460 | 0 | return ojph_get_parameter_progression_order(encoder, value, value_size); |
461 | 0 | } else if (strcmp(name, kParam_codestream_comment) == 0) { |
462 | 0 | return ojph_get_parameter_codestream_comment(encoder, value, value_size); |
463 | 0 | } else if (strcmp(name, kParam_tile_size) == 0) { |
464 | 0 | return ojph_get_parameter_tile_size(encoder, value, value_size); |
465 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 11 |
466 | | } else if (strcmp(name, kParam_tilepart_division) == 0) { |
467 | | return ojph_get_parameter_tilepart_division(encoder, value, value_size); |
468 | | #endif |
469 | 0 | } else if (strcmp(name, kParam_block_dimensions) == 0) { |
470 | 0 | return ojph_get_parameter_block_dimensions(encoder, value, value_size); |
471 | 0 | } else { |
472 | 0 | return heif_error_unsupported_parameter; |
473 | 0 | } |
474 | 0 | } |
475 | | |
476 | | //////////// String parameter setters |
477 | | |
478 | | static const heif_error &ojph_set_chroma(encoder_struct_ojph *encoder, const char *value) |
479 | 0 | { |
480 | 0 | if (strcmp(value, "420") == 0) { |
481 | 0 | encoder->chroma = heif_chroma_420; |
482 | 0 | return heif_error_ok; |
483 | 0 | } else if (strcmp(value, "422") == 0) { |
484 | 0 | encoder->chroma = heif_chroma_422; |
485 | 0 | return heif_error_ok; |
486 | 0 | } else if (strcmp(value, "444") == 0) { |
487 | 0 | encoder->chroma = heif_chroma_444; |
488 | 0 | return heif_error_ok; |
489 | 0 | } else { |
490 | 0 | return heif_error_invalid_parameter_value; |
491 | 0 | } |
492 | 0 | } |
493 | | |
494 | | static bool string_list_contains(const char* const* values_list, const char* value) |
495 | 0 | { |
496 | 0 | for (int i = 0; values_list[i]; i++) { |
497 | 0 | if (strcmp(values_list[i], value) == 0) { |
498 | 0 | return true; |
499 | 0 | } |
500 | 0 | } |
501 | | |
502 | 0 | return false; |
503 | 0 | } |
504 | | |
505 | | static const heif_error &ojph_set_progression_order(encoder_struct_ojph *encoder, const char *value) |
506 | 0 | { |
507 | 0 | if (string_list_contains(kParam_progression_order_valid_values, value)) { |
508 | 0 | encoder->codestream.access_cod().set_progression_order(value); |
509 | 0 | return heif_error_ok; |
510 | 0 | } else { |
511 | 0 | return heif_error_invalid_parameter_value; |
512 | 0 | } |
513 | 0 | } |
514 | | |
515 | | static const heif_error &ojph_set_codestream_comment(encoder_struct_ojph *encoder, const char *value) |
516 | 0 | { |
517 | 0 | if (value != nullptr) { |
518 | 0 | encoder->comment = std::string(value); |
519 | 0 | } |
520 | 0 | return heif_error_ok; |
521 | 0 | } |
522 | | |
523 | | static const heif_error &ojph_set_tile_size(encoder_struct_ojph *encoder, const char *value) |
524 | 0 | { |
525 | 0 | std::string valueStr(value); |
526 | 0 | size_t commaOffset = valueStr.find(","); |
527 | 0 | if (commaOffset == std::string::npos) { |
528 | 0 | return heif_error_invalid_parameter_value; |
529 | 0 | } |
530 | 0 | std::string xTSizText = valueStr.substr(0, commaOffset); |
531 | 0 | unsigned long xTSiz = std::stoul(xTSizText); |
532 | 0 | std::string yTSizText = valueStr.substr(commaOffset + 1); |
533 | 0 | unsigned long yTSiz = std::stoul(yTSizText); |
534 | 0 | if ((xTSiz < 1) |
535 | 0 | || (xTSiz > std::numeric_limits<unsigned int>::max()) |
536 | 0 | || (yTSiz < 1) |
537 | 0 | || (yTSiz > std::numeric_limits<unsigned int>::max())) { |
538 | 0 | return heif_error_invalid_parameter_value; |
539 | 0 | } |
540 | 0 | encoder->codestream.access_siz().set_tile_size(ojph::size((ojph::ui32)xTSiz, (ojph::ui32)yTSiz)); |
541 | 0 | return heif_error_ok; |
542 | 0 | } |
543 | | |
544 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 11 |
545 | | static const heif_error &ojph_set_tilepart_division(encoder_struct_ojph *encoder, const char *value) |
546 | | { |
547 | | if (strcmp(value, "none") == 0) { |
548 | | encoder->codestream.set_tilepart_divisions(false, false); |
549 | | return heif_error_ok; |
550 | | } else if (strcmp(value, "resolution") == 0) { |
551 | | encoder->codestream.set_tilepart_divisions(true, false); |
552 | | return heif_error_ok; |
553 | | } else if (strcmp(value, "component") == 0) { |
554 | | encoder->codestream.set_tilepart_divisions(false, true); |
555 | | return heif_error_ok; |
556 | | } else if (strcmp(value, "both") == 0) { |
557 | | encoder->codestream.set_tilepart_divisions(true, true); |
558 | | return heif_error_ok; |
559 | | } else { |
560 | | return heif_error_invalid_parameter_value; |
561 | | } |
562 | | } |
563 | | #endif |
564 | | |
565 | | // Get the base 2 logarithm for code block sizes. See ITU-T T.800 (11/2015) Table A.18 |
566 | | // Values are encoded 0 to 8 (i.e. its -2) |
567 | | static const int log_base_2(unsigned long v) |
568 | 0 | { |
569 | 0 | switch (v) { |
570 | 0 | case 4: |
571 | 0 | return 2 - 2; |
572 | 0 | case 8: |
573 | 0 | return 3 - 2; |
574 | 0 | case 16: |
575 | 0 | return 4 - 2; |
576 | 0 | case 32: |
577 | 0 | return 5 - 2; |
578 | 0 | case 64: |
579 | 0 | return 6 - 2; |
580 | 0 | case 128: |
581 | 0 | return 7 - 2; |
582 | 0 | case 256: |
583 | 0 | return 8 - 2; |
584 | 0 | case 512: |
585 | 0 | return 9 - 2; |
586 | 0 | case 1024: |
587 | 0 | return 10 - 2; |
588 | 0 | default: |
589 | | // any other value is invalid |
590 | 0 | return -1; |
591 | 0 | } |
592 | 0 | } |
593 | | |
594 | | static const heif_error &ojph_set_block_dimensions(encoder_struct_ojph *encoder, const char *value) |
595 | 0 | { |
596 | 0 | std::string valueStr(value); |
597 | 0 | size_t commaOffset = valueStr.find(","); |
598 | 0 | if (commaOffset == std::string::npos) { |
599 | 0 | return heif_error_invalid_parameter_value; |
600 | 0 | } |
601 | 0 | std::string widthText = valueStr.substr(0, commaOffset); |
602 | 0 | unsigned long width = std::stoul(widthText); |
603 | 0 | std::string heightText = valueStr.substr(commaOffset + 1); |
604 | 0 | unsigned long height = std::stoul(heightText); |
605 | 0 | int xcb = log_base_2(width); |
606 | 0 | int ycb = log_base_2(height); |
607 | 0 | if ((xcb == -1) || (ycb == -1) || (xcb + ycb > 12)) { |
608 | 0 | return heif_error_invalid_parameter_value; |
609 | 0 | } |
610 | 0 | encoder->codestream.access_cod().set_block_dims((ojph::ui32)width, (ojph::ui32)height); |
611 | 0 | return heif_error_ok; |
612 | 0 | } |
613 | | |
614 | | heif_error ojph_set_parameter_string(void *encoder_raw, const char *name, const char *value) |
615 | 0 | { |
616 | 0 | auto* encoder = (encoder_struct_ojph*) encoder_raw; |
617 | |
|
618 | 0 | if (strcmp(name, kParam_chroma) == 0) { |
619 | 0 | return ojph_set_chroma(encoder, value); |
620 | 0 | } else if (strcmp(name, kParam_progression_order) == 0) { |
621 | 0 | return ojph_set_progression_order(encoder, value); |
622 | 0 | } else if (strcmp(name, kParam_codestream_comment) == 0) { |
623 | 0 | return ojph_set_codestream_comment(encoder, value); |
624 | 0 | } else if (strcmp(name, kParam_tile_size) == 0) { |
625 | 0 | return ojph_set_tile_size(encoder, value); |
626 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 11 |
627 | | } else if (strcmp(name, kParam_tilepart_division) == 0) { |
628 | | return ojph_set_tilepart_division(encoder, value); |
629 | | #endif |
630 | 0 | } else if (strcmp(name, kParam_block_dimensions) == 0) { |
631 | 0 | return ojph_set_block_dimensions(encoder, value); |
632 | 0 | } else { |
633 | 0 | return heif_error_unsupported_parameter; |
634 | 0 | } |
635 | 0 | } |
636 | | |
637 | | static void ojph_set_default_parameters(void* encoder_raw) |
638 | 0 | { |
639 | 0 | encoder_struct_ojph* encoder = (encoder_struct_ojph*) encoder_raw; |
640 | 0 | for (const heif_encoder_parameter** p = ojph_encoder_parameter_ptrs; *p; p++) { |
641 | 0 | const heif_encoder_parameter* param = *p; |
642 | |
|
643 | 0 | if (param->has_default) { |
644 | 0 | switch (param->type) { |
645 | 0 | case heif_encoder_parameter_type_integer: |
646 | 0 | ojph_set_parameter_integer(encoder, param->name, param->integer.default_value); |
647 | 0 | break; |
648 | 0 | case heif_encoder_parameter_type_boolean: |
649 | 0 | ojph_set_parameter_boolean(encoder, param->name, param->boolean.default_value); |
650 | 0 | break; |
651 | 0 | case heif_encoder_parameter_type_string: |
652 | 0 | ojph_set_parameter_string(encoder, param->name, param->string.default_value); |
653 | 0 | break; |
654 | 0 | } |
655 | 0 | } |
656 | 0 | } |
657 | 0 | } |
658 | | |
659 | | ///// Actual encoding functionality |
660 | | |
661 | | heif_error ojph_new_encoder(void** encoder_out) |
662 | 0 | { |
663 | 0 | encoder_struct_ojph* encoder = new encoder_struct_ojph(); |
664 | 0 | encoder->outfile.open(); |
665 | 0 | *encoder_out = encoder; |
666 | |
|
667 | 0 | ojph_set_default_parameters(encoder); |
668 | |
|
669 | 0 | return heif_error_ok; |
670 | 0 | } |
671 | | |
672 | | void ojph_free_encoder(void* encoder_raw) |
673 | 0 | { |
674 | 0 | encoder_struct_ojph* encoder = (encoder_struct_ojph*) encoder_raw; |
675 | 0 | encoder->codestream.close(); |
676 | 0 | delete encoder; |
677 | 0 | } |
678 | | |
679 | | heif_error ojph_set_parameter_logging_level(void* encoder, int logging) |
680 | 0 | { |
681 | | // No logging level options in OpenJPH |
682 | 0 | return heif_error_ok; |
683 | 0 | } |
684 | | |
685 | | heif_error ojph_get_parameter_logging_level(void* encoder, int* logging) |
686 | 0 | { |
687 | | // No logging level options in OpenJPH |
688 | 0 | return heif_error_ok; |
689 | 0 | } |
690 | | |
691 | | const heif_encoder_parameter** ojph_list_parameters(void* encoder_raw) |
692 | 0 | { |
693 | 0 | return ojph_encoder_parameter_ptrs; |
694 | 0 | } |
695 | | |
696 | | |
697 | | void ojph_query_input_colorspace(heif_colorspace* inout_colorspace, heif_chroma* inout_chroma) |
698 | 0 | { |
699 | | // Replace the input colorspace/chroma with the one that is supported by the encoder and that |
700 | | // comes as close to the input colorspace/chroma as possible. |
701 | |
|
702 | 0 | if (*inout_colorspace == heif_colorspace_monochrome) { |
703 | 0 | *inout_colorspace = heif_colorspace_monochrome; |
704 | 0 | *inout_chroma = heif_chroma_monochrome; |
705 | 0 | } |
706 | 0 | else { |
707 | 0 | *inout_colorspace = heif_colorspace_YCbCr; |
708 | 0 | *inout_chroma = heif_chroma_444; |
709 | 0 | } |
710 | 0 | } |
711 | | |
712 | | void ojph_query_input_colorspace2(void* encoder_raw, heif_colorspace* inout_colorspace, heif_chroma* inout_chroma) |
713 | 0 | { |
714 | 0 | auto* encoder = (encoder_struct_ojph*) encoder_raw; |
715 | |
|
716 | 0 | if (*inout_colorspace == heif_colorspace_monochrome) { |
717 | 0 | *inout_colorspace = heif_colorspace_monochrome; |
718 | 0 | *inout_chroma = heif_chroma_monochrome; |
719 | 0 | } |
720 | 0 | else { |
721 | 0 | *inout_colorspace = heif_colorspace_YCbCr; |
722 | |
|
723 | 0 | if (encoder->chroma != heif_chroma_undefined) { |
724 | 0 | *inout_chroma = encoder->chroma; |
725 | 0 | } |
726 | 0 | else { |
727 | 0 | *inout_chroma = heif_chroma_444; |
728 | 0 | } |
729 | 0 | } |
730 | 0 | } |
731 | | |
732 | | std::vector<heif_channel> build_SIZ(encoder_struct_ojph *encoder, const heif_image *image) |
733 | 0 | { |
734 | 0 | std::vector<heif_channel> sourceChannels; |
735 | 0 | ojph::param_siz siz = encoder->codestream.access_siz(); |
736 | 0 | int width = heif_image_get_primary_width(image); |
737 | 0 | int height = heif_image_get_primary_height(image); |
738 | 0 | siz.set_image_extent(ojph::point(width, height)); |
739 | |
|
740 | 0 | heif_chroma chroma = heif_image_get_chroma_format(image); |
741 | |
|
742 | 0 | encoder->codestream.set_planar(true); |
743 | |
|
744 | 0 | if (chroma == heif_chroma_monochrome) { |
745 | 0 | sourceChannels = {heif_channel_Y}; |
746 | 0 | } else { |
747 | 0 | sourceChannels = {heif_channel_Y, heif_channel_Cb, heif_channel_Cr}; |
748 | 0 | } |
749 | |
|
750 | 0 | siz.set_num_components((ojph::ui32)sourceChannels.size()); |
751 | 0 | for (ojph::ui32 i = 0; i < siz.get_num_components(); i++) { |
752 | 0 | int bit_depth = heif_image_get_bits_per_pixel_range(image, sourceChannels[i]); |
753 | 0 | if (sourceChannels[i] == heif_channel_Y) { |
754 | 0 | siz.set_component(i, ojph::point(1, 1), bit_depth, false); |
755 | 0 | } else { // Cb or Cr |
756 | 0 | if (chroma == heif_chroma_444) { |
757 | 0 | siz.set_component(i, ojph::point(1, 1), bit_depth, false); |
758 | 0 | } else if (chroma == heif_chroma_422) { |
759 | 0 | siz.set_component(i, ojph::point(2, 1), bit_depth, false); |
760 | 0 | } else { |
761 | 0 | siz.set_component(i, ojph::point(2, 2), bit_depth, false); |
762 | 0 | } |
763 | 0 | } |
764 | 0 | } |
765 | 0 | siz.set_image_offset(ojph::point(0, 0)); |
766 | 0 | siz.set_tile_offset(ojph::point(0, 0)); |
767 | 0 | return sourceChannels; |
768 | 0 | } |
769 | | |
770 | | void build_COD(encoder_struct_ojph *encoder) |
771 | 0 | { |
772 | 0 | ojph::param_cod cod = encoder->codestream.access_cod(); |
773 | 0 | cod.set_color_transform(false); |
774 | 0 | } |
775 | | |
776 | | heif_error ojph_encode_image(void *encoder_raw, const heif_image *image, heif_image_input_class image_class) |
777 | 0 | { |
778 | 0 | encoder_struct_ojph* encoder = (encoder_struct_ojph*) encoder_raw; |
779 | |
|
780 | 0 | if (heif_image_get_colorspace(image) != heif_colorspace_YCbCr && |
781 | 0 | heif_image_get_colorspace(image) != heif_colorspace_monochrome) { |
782 | 0 | return { |
783 | 0 | heif_error_Encoding_error, |
784 | 0 | heif_suberror_Unspecified, |
785 | 0 | "OpenJPH encoder plugin received image with invalid colorspace." |
786 | 0 | }; |
787 | 0 | } |
788 | | |
789 | | // reset output position to start |
790 | 0 | encoder->outfile.seek(0, ojph::outfile_base::seek::OJPH_SEEK_SET); |
791 | 0 | encoder->data_read = false; |
792 | |
|
793 | 0 | std::vector<heif_channel> sourceChannels = build_SIZ(encoder, image); |
794 | 0 | build_COD(encoder); |
795 | | #if OPENJPH_MAJOR_VERSION > 1 || OPENJPH_MINOR_VERSION > 10 |
796 | | bool hasComment = (encoder->comment.length() > 0); |
797 | | ojph::comment_exchange com_ex; |
798 | | if (hasComment) { |
799 | | com_ex.set_string(encoder->comment.c_str()); |
800 | | } |
801 | | encoder->codestream.write_headers(&(encoder->outfile), &com_ex, hasComment ? 1 : 0); |
802 | | #else |
803 | 0 | encoder->codestream.write_headers(&(encoder->outfile)); |
804 | 0 | #endif |
805 | 0 | ojph::ui32 next_comp; |
806 | 0 | ojph::line_buf* cur_line = encoder->codestream.exchange(NULL, next_comp); |
807 | |
|
808 | 0 | for (const auto& sourceChannel : sourceChannels) { |
809 | 0 | size_t stride; |
810 | 0 | const uint8_t *data = heif_image_get_plane_readonly2(image, sourceChannel, &stride); |
811 | 0 | uint32_t component_height = heif_image_get_height(image, sourceChannel); |
812 | 0 | for (uint32_t y = 0; y < component_height; y++) { |
813 | 0 | const uint8_t *sourceLine = data + y * stride; |
814 | 0 | size_t outputWidth = cur_line->size; |
815 | 0 | ojph::si32 *targetLine = cur_line->i32; |
816 | 0 | for (uint32_t x = 0; x < outputWidth; x++) { |
817 | 0 | targetLine[x] = sourceLine[x]; |
818 | 0 | } |
819 | 0 | cur_line = encoder->codestream.exchange(cur_line, next_comp); |
820 | 0 | } |
821 | 0 | } |
822 | 0 | encoder->codestream.flush(); |
823 | |
|
824 | 0 | return heif_error_ok; |
825 | 0 | } |
826 | | |
827 | | heif_error ojph_get_compressed_data(void* encoder_raw, uint8_t** data, int* size, heif_encoded_data_type* type) |
828 | 0 | { |
829 | 0 | encoder_struct_ojph* encoder = (encoder_struct_ojph*) encoder_raw; |
830 | |
|
831 | 0 | if (encoder->data_read) { |
832 | 0 | *size = 0; |
833 | 0 | *data = nullptr; |
834 | 0 | } |
835 | 0 | else { |
836 | 0 | *size = (int) encoder->outfile.tell(); |
837 | 0 | *data = (uint8_t*) encoder->outfile.get_data(); |
838 | 0 | encoder->data_read = true; |
839 | 0 | } |
840 | 0 | return heif_error_ok; |
841 | 0 | } |
842 | | |
843 | | |
844 | | static const int MAX_PLUGIN_NAME_LENGTH = 80; |
845 | | static char plugin_name[MAX_PLUGIN_NAME_LENGTH]; |
846 | | |
847 | | const char* ojph_plugin_name() |
848 | 0 | { |
849 | 0 | snprintf(plugin_name, MAX_PLUGIN_NAME_LENGTH, |
850 | 0 | "OpenJPH %s.%s.%s", |
851 | 0 | OJPH_INT_TO_STRING(OPENJPH_VERSION_MAJOR), |
852 | 0 | OJPH_INT_TO_STRING(OPENJPH_VERSION_MINOR), |
853 | 0 | OJPH_INT_TO_STRING(OPENJPH_VERSION_PATCH) |
854 | 0 | ); |
855 | 0 | plugin_name[MAX_PLUGIN_NAME_LENGTH - 1] = 0; |
856 | |
|
857 | 0 | return plugin_name; |
858 | 0 | } |
859 | | |
860 | | |
861 | | heif_error ojph_start_sequence_encoding(void* encoder, const heif_image* image, |
862 | | enum heif_image_input_class image_class, |
863 | | uint32_t framerate_num, uint32_t framerate_denom, |
864 | | const heif_sequence_encoding_options* options) |
865 | 0 | { |
866 | 0 | return heif_error_ok; |
867 | 0 | } |
868 | | |
869 | | heif_error ojph_encode_sequence_frame(void* encoder, const heif_image* image, uintptr_t frame_nr) |
870 | 0 | { |
871 | 0 | return ojph_encode_image(encoder, image, heif_image_input_class_normal); |
872 | 0 | } |
873 | | |
874 | | heif_error ojph_end_sequence_encoding(void* encoder) |
875 | 0 | { |
876 | 0 | return heif_error_ok; |
877 | 0 | } |
878 | | |
879 | | heif_error ojph_get_compressed_data2(void* encoder, uint8_t** data, int* size, |
880 | | uintptr_t* frame_nr, |
881 | | int* is_keyframe, int* more_frame_packets) |
882 | 0 | { |
883 | 0 | heif_error err = ojph_get_compressed_data(encoder, data, size, nullptr); |
884 | |
|
885 | 0 | if (is_keyframe) { |
886 | 0 | *is_keyframe = true; |
887 | 0 | } |
888 | |
|
889 | 0 | if (more_frame_packets) { |
890 | 0 | *more_frame_packets = true; |
891 | 0 | } |
892 | |
|
893 | 0 | return err; |
894 | 0 | } |
895 | | |
896 | | static const heif_encoder_plugin encoder_plugin_openjph { |
897 | | /* plugin_api_version */ 4, |
898 | | /* compression_format */ heif_compression_HTJ2K, |
899 | | /* id_name */ "openjph", |
900 | | /* priority */ OJPH_PLUGIN_PRIORITY, |
901 | | /* supports_lossy_compression */ true, |
902 | | /* supports_lossless_compression */ true, |
903 | | /* get_plugin_name */ ojph_plugin_name, |
904 | | /* init_plugin */ ojph_init_plugin, |
905 | | /* cleanup_plugin */ ojph_cleanup_plugin, |
906 | | /* new_encoder */ ojph_new_encoder, |
907 | | /* free_encoder */ ojph_free_encoder, |
908 | | /* set_parameter_quality */ ojph_set_parameter_quality, |
909 | | /* get_parameter_quality */ ojph_get_parameter_quality, |
910 | | /* set_parameter_lossless */ ojph_set_parameter_lossless, |
911 | | /* get_parameter_lossless */ ojph_get_parameter_lossless, |
912 | | /* set_parameter_logging_level */ ojph_set_parameter_logging_level, |
913 | | /* get_parameter_logging_level */ ojph_get_parameter_logging_level, |
914 | | /* list_parameters */ ojph_list_parameters, |
915 | | /* set_parameter_integer */ ojph_set_parameter_integer, |
916 | | /* get_parameter_integer */ ojph_get_parameter_integer, |
917 | | /* set_parameter_boolean */ ojph_set_parameter_boolean, |
918 | | /* get_parameter_boolean */ ojph_get_parameter_boolean, |
919 | | /* set_parameter_string */ ojph_set_parameter_string, |
920 | | /* get_parameter_string */ ojph_get_parameter_string, |
921 | | /* query_input_colorspace */ ojph_query_input_colorspace, |
922 | | /* encode_image */ ojph_encode_image, |
923 | | /* get_compressed_data */ ojph_get_compressed_data, |
924 | | /* query_input_colorspace (v2) */ ojph_query_input_colorspace2, |
925 | | /* query_encoded_size (v3) */ nullptr, |
926 | | /* minimum_required_libheif_version */ LIBHEIF_MAKE_VERSION(1,21,0), |
927 | | /* start_sequence_encoding (v4) */ ojph_start_sequence_encoding, |
928 | | /* encode_sequence_frame (v4) */ ojph_encode_sequence_frame, |
929 | | /* end_sequence_encoding (v4) */ ojph_end_sequence_encoding, |
930 | | /* get_compressed_data2 (v4) */ ojph_get_compressed_data2, |
931 | | /* does_indicate_keyframes (v4) */ 1 |
932 | | }; |
933 | | |
934 | | const heif_encoder_plugin* get_encoder_plugin_openjph() |
935 | 253 | { |
936 | 253 | return &encoder_plugin_openjph; |
937 | 253 | } |
938 | | |
939 | | |
940 | | #if PLUGIN_OPENJPH_ENCODER |
941 | | heif_plugin_info plugin_info { |
942 | | 1, |
943 | | heif_plugin_type_encoder, |
944 | | &encoder_plugin_openjph |
945 | | }; |
946 | | #endif |