11#ifdef PACS_WITH_HTJ2K_CODEC
12#include <openjph/ojph_codestream.h>
13#include <openjph/ojph_file.h>
14#include <openjph/ojph_mem.h>
15#include <openjph/ojph_params.h>
22 float compression_ratio,
23 int resolution_levels)
24 : lossless_(lossless),
26 compression_ratio_(compression_ratio),
27 resolution_levels_(resolution_levels) {}
34std::string_view
htj2k_codec::transfer_syntax_uid() const noexcept {
36 return use_rpcl_ ? kTransferSyntaxUIDRPCL : kTransferSyntaxUIDLossless;
38 return kTransferSyntaxUIDLossy;
43 return use_rpcl_ ?
"HTJ2K with RPCL (Lossless)" :
"HTJ2K (Lossless)";
45 return "HTJ2K (Lossy)";
53 if (params.width == 0 || params.height == 0) {
57 if (params.samples_per_pixel != 1 && params.samples_per_pixel != 3) {
61 if (params.bits_stored < 1 || params.bits_stored > 16) {
69 return can_encode(params);
88#ifdef PACS_WITH_HTJ2K_CODEC
93int compute_resolution_levels(
int requested, uint16_t width, uint16_t height) {
95 auto min_dim = std::min(width, height);
96 while (min_dim > 1 && max_levels < requested) {
100 return std::clamp(max_levels, 1, 32);
106 std::span<const uint8_t> pixel_data,
107 const image_params& params,
108 [[maybe_unused]]
const compression_options& options)
const {
110 if (pixel_data.empty()) {
115 if (params.width == 0 || params.height == 0) {
120 const int bytes_per_sample = (params.bits_stored <= 8) ? 1 : 2;
121 const size_t expected_size =
static_cast<size_t>(params.width)
122 * params.height * params.samples_per_pixel * bytes_per_sample;
126 "Pixel data too small: expected " + std::to_string(expected_size)
127 +
" bytes, got " + std::to_string(
pixel_data.size()));
131 ojph::codestream codestream;
134 ojph::param_siz siz = codestream.access_siz();
135 siz.set_image_extent(ojph::point{
136 static_cast<ojph::ui32
>(params.width),
137 static_cast<ojph::ui32
>(params.height)});
138 siz.set_image_offset(ojph::point{0, 0});
139 siz.set_tile_size(ojph::size{0, 0});
140 siz.set_tile_offset(ojph::point{0, 0});
141 siz.set_num_components(params.samples_per_pixel);
143 bool is_signed = (params.pixel_representation != 0);
144 for (ojph::ui32 c = 0; c < params.samples_per_pixel; ++c) {
145 siz.set_component(c, ojph::point{1, 1}, params.bits_stored, is_signed);
149 ojph::param_cod cod = codestream.access_cod();
150 int num_levels = compute_resolution_levels(
152 cod.set_num_decomposition(
static_cast<ojph::ui32
>(num_levels));
155 cod.set_block_dims(64, 64);
161 cod.set_color_transform(params.samples_per_pixel == 3);
165 cod.set_progression_order(
"RPCL");
167 cod.set_progression_order(
"CPRL");
172 ojph::param_qcd qcd = codestream.access_qcd();
177 qcd.set_irrev_quant(delta);
183 codestream.set_planar(params.samples_per_pixel == 1);
186 ojph::mem_outfile output;
189 codestream.write_headers(&output);
192 const auto width =
static_cast<ojph::ui32
>(params.width);
193 const auto height =
static_cast<ojph::ui32
>(params.height);
194 const auto num_comps =
static_cast<ojph::ui32
>(params.samples_per_pixel);
196 ojph::ui32 next_comp = 0;
197 ojph::line_buf*
line = codestream.exchange(
nullptr, next_comp);
199 for (ojph::ui32 y = 0; y < height; ++y) {
200 for (ojph::ui32 c = 0; c < num_comps; ++c) {
205 auto* dst =
line->i32;
206 if (bytes_per_sample == 1) {
207 if (num_comps == 1) {
209 +
static_cast<size_t>(y) * width;
210 for (ojph::ui32 x = 0; x < width; ++x) {
212 ?
static_cast<ojph::si32
>(
static_cast<int8_t
>(src[x]))
213 : static_cast<ojph::si32>(src[x]);
217 +
static_cast<size_t>(y) * width * num_comps;
218 for (ojph::ui32 x = 0; x < width; ++x) {
220 ?
static_cast<ojph::si32
>(
221 static_cast<int8_t
>(src[x * num_comps + next_comp]))
222 : static_cast<ojph::si32>(src[x * num_comps + next_comp]);
226 if (num_comps == 1) {
227 const auto* src =
reinterpret_cast<const uint16_t*
>(
228 pixel_data.data() +
static_cast<size_t>(y) * width * 2);
229 for (ojph::ui32 x = 0; x < width; ++x) {
231 ?
static_cast<ojph::si32
>(
static_cast<int16_t
>(src[x]))
232 : static_cast<ojph::si32>(src[x]);
235 const auto* src =
reinterpret_cast<const uint16_t*
>(
237 +
static_cast<size_t>(y) * width * num_comps * 2);
238 for (ojph::ui32 x = 0; x < width; ++x) {
240 ?
static_cast<ojph::si32
>(
241 static_cast<int16_t
>(src[x * num_comps + next_comp]))
242 : static_cast<ojph::si32>(src[x * num_comps + next_comp]);
247 line = codestream.exchange(line, next_comp);
254 auto compressed_size =
static_cast<size_t>(output.tell());
255 std::vector<uint8_t> result_data(compressed_size);
256 if (compressed_size > 0) {
257 std::memcpy(result_data.data(), output.get_data(), compressed_size);
262 return kcenon::pacs::ok<compression_result>(
263 compression_result{std::move(result_data), params});
265 }
catch (
const std::exception& e) {
268 std::string(
"HTJ2K encoding failed: ") + e.what());
273 std::span<const uint8_t> compressed_data,
274 const image_params& params)
const {
276 if (compressed_data.empty()) {
282 ojph::codestream codestream;
285 ojph::mem_infile input;
286 input.open(compressed_data.data(), compressed_data.size());
288 codestream.read_headers(&input);
291 ojph::param_siz siz = codestream.access_siz();
292 ojph::point extent = siz.get_image_extent();
293 auto decoded_width =
static_cast<uint16_t
>(extent.x);
294 auto decoded_height =
static_cast<uint16_t
>(extent.y);
295 auto num_comps =
static_cast<uint16_t
>(siz.get_num_components());
296 auto bit_depth =
static_cast<uint16_t
>(siz.get_bit_depth(0));
297 bool is_signed = siz.is_signed(0);
300 image_params output_params = params;
301 output_params.width = decoded_width;
302 output_params.height = decoded_height;
303 output_params.samples_per_pixel = num_comps;
304 output_params.bits_stored = bit_depth;
305 output_params.bits_allocated = (bit_depth <= 8) ? 8 : 16;
306 output_params.high_bit = bit_depth - 1;
307 output_params.pixel_representation = is_signed ? 1 : 0;
308 output_params.planar_configuration = 0;
310 if (num_comps == 1) {
312 }
else if (num_comps == 3) {
317 if (params.width > 0 && params.width != decoded_width) {
320 "Image width mismatch: expected " + std::to_string(params.width)
321 +
", got " + std::to_string(decoded_width));
323 if (params.height > 0 && params.height != decoded_height) {
326 "Image height mismatch: expected " + std::to_string(params.height)
327 +
", got " + std::to_string(decoded_height));
331 codestream.set_planar(num_comps == 1);
336 const int bytes_per_sample = (bit_depth <= 8) ? 1 : 2;
337 const size_t output_size =
static_cast<size_t>(decoded_width)
338 * decoded_height * num_comps * bytes_per_sample;
339 std::vector<uint8_t> output_data(output_size);
344 ojph::ui32 comp_num = 0;
345 for (ojph::ui32 y = 0; y < decoded_height; ++y) {
346 for (ojph::ui32 c = 0; c < num_comps; ++c) {
347 ojph::line_buf*
line = codestream.pull(comp_num);
349 if (bytes_per_sample == 1) {
350 if (num_comps == 1) {
351 auto* dst = output_data.data()
352 +
static_cast<size_t>(y) * decoded_width;
353 for (ojph::ui32 x = 0; x < decoded_width; ++x) {
354 auto val =
line->i32[x];
355 dst[x] =
static_cast<uint8_t
>(std::clamp(val, 0, 255));
358 auto* dst = output_data.data()
359 +
static_cast<size_t>(y) * decoded_width * num_comps;
360 for (ojph::ui32 x = 0; x < decoded_width; ++x) {
361 auto val =
line->i32[x];
362 dst[x * num_comps + comp_num] =
363 static_cast<uint8_t
>(std::clamp(val, 0, 255));
367 if (num_comps == 1) {
368 auto* dst =
reinterpret_cast<uint16_t*
>(
370 +
static_cast<size_t>(y) * decoded_width * 2);
371 for (ojph::ui32 x = 0; x < decoded_width; ++x) {
372 auto val =
line->i32[x];
374 dst[x] =
static_cast<uint16_t
>(
375 static_cast<int16_t
>(
376 std::clamp(val, -32768, 32767)));
378 dst[x] =
static_cast<uint16_t
>(
379 std::clamp(val, 0, 65535));
383 auto* dst =
reinterpret_cast<uint16_t*
>(
385 +
static_cast<size_t>(y) * decoded_width * num_comps * 2);
386 for (ojph::ui32 x = 0; x < decoded_width; ++x) {
387 auto val =
line->i32[x];
389 dst[x * num_comps + comp_num] =
390 static_cast<uint16_t
>(
391 static_cast<int16_t
>(
392 std::clamp(val, -32768, 32767)));
394 dst[x * num_comps + comp_num] =
395 static_cast<uint16_t
>(
396 std::clamp(val, 0, 65535));
406 return kcenon::pacs::ok<compression_result>(
407 compression_result{std::move(output_data), output_params});
409 }
catch (
const std::exception& e) {
412 std::string(
"HTJ2K decoding failed: ") + e.what());
419 [[maybe_unused]] std::span<const uint8_t> pixel_data,
424 "HTJ2K codec not available: OpenJPH library not found at build time");
428 [[maybe_unused]] std::span<const uint8_t> compressed_data,
432 "HTJ2K codec not available: OpenJPH library not found at build time");
High-Throughput JPEG 2000 (HTJ2K) codec implementation.
float compression_ratio() const noexcept
Gets the current compression ratio setting.
bool can_decode(const image_params ¶ms) const noexcept override
Checks if this codec can decode data with given parameters.
bool is_rpcl_mode() const noexcept
Checks if RPCL progression order is enabled.
bool can_encode(const image_params ¶ms) const noexcept override
Checks if this codec supports the given image parameters.
bool is_lossless_mode() const noexcept
Checks if this codec is configured for lossless mode.
std::string_view name() const noexcept override
Returns a human-readable name for the codec.
codec_result encode(std::span< const uint8_t > pixel_data, const image_params ¶ms, const compression_options &options={}) const override
Compresses pixel data to HTJ2K format.
htj2k_codec(bool lossless=true, bool use_rpcl=false, float compression_ratio=kDefaultCompressionRatio, int resolution_levels=kDefaultResolutionLevels)
Constructs an HTJ2K codec instance.
int resolution_levels() const noexcept
Gets the number of DWT resolution levels.
bool is_lossy() const noexcept override
Checks if this codec produces lossy compression.
codec_result decode(std::span< const uint8_t > compressed_data, const image_params ¶ms) const override
Decompresses HTJ2K data.
@ monochrome2
Minimum pixel value displayed as black.
@ rgb
Red, Green, Blue color model.
constexpr int compression_error
constexpr int decompression_error
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Result<T> type aliases and helpers for PACS system.
Compression quality settings for lossy codecs.
Parameters describing image pixel data.