Coverage Report

Created: 2026-07-25 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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