12#ifdef PACS_WITH_JPEG2000_CODEC
20#ifdef PACS_WITH_JPEG2000_CODEC
25void opj_error_callback(
const char* msg,
void* client_data) {
26 auto* error_msg =
static_cast<std::string*
>(client_data);
27 if (error_msg && msg) {
28 if (!error_msg->empty()) {
29 error_msg->append(
"; ");
31 error_msg->append(msg);
33 while (!error_msg->empty() && error_msg->back() ==
'\n') {
34 error_msg->pop_back();
43void opj_warning_callback([[maybe_unused]]
const char* msg,
44 [[maybe_unused]]
void* client_data) {
53void opj_info_callback([[maybe_unused]]
const char* msg,
54 [[maybe_unused]]
void* client_data) {
61struct opj_memory_stream {
70OPJ_SIZE_T opj_memory_stream_read(
void* buffer, OPJ_SIZE_T nb_bytes,
void* user_data) {
71 auto* stream =
static_cast<opj_memory_stream*
>(user_data);
72 if (!stream || stream->offset >= stream->size) {
73 return static_cast<OPJ_SIZE_T
>(-1);
76 OPJ_SIZE_T bytes_to_read = (std::min)(nb_bytes,
77 static_cast<OPJ_SIZE_T
>(stream->size - stream->offset));
78 std::memcpy(buffer, stream->data + stream->offset, bytes_to_read);
79 stream->offset += bytes_to_read;
86OPJ_OFF_T opj_memory_stream_skip(OPJ_OFF_T nb_bytes,
void* user_data) {
87 auto* stream =
static_cast<opj_memory_stream*
>(user_data);
94 if (stream->offset <
static_cast<size_t>(-nb_bytes)) {
95 nb_bytes = -
static_cast<OPJ_OFF_T
>(stream->offset);
99 if (stream->offset + nb_bytes > stream->size) {
100 nb_bytes =
static_cast<OPJ_OFF_T
>(stream->size - stream->offset);
104 stream->offset =
static_cast<size_t>(
static_cast<OPJ_OFF_T
>(stream->offset) + nb_bytes);
111OPJ_BOOL opj_memory_stream_seek(OPJ_OFF_T nb_bytes,
void* user_data) {
112 auto* stream =
static_cast<opj_memory_stream*
>(user_data);
113 if (!stream || nb_bytes < 0 ||
static_cast<size_t>(nb_bytes) > stream->size) {
116 stream->offset =
static_cast<size_t>(nb_bytes);
123struct opj_output_buffer {
124 std::vector<uint8_t> data;
131OPJ_SIZE_T opj_output_buffer_write(
void* buffer, OPJ_SIZE_T nb_bytes,
void* user_data) {
132 auto* output =
static_cast<opj_output_buffer*
>(user_data);
133 if (!output || !buffer) {
134 return static_cast<OPJ_SIZE_T
>(-1);
137 size_t new_size = output->offset + nb_bytes;
138 if (new_size > output->data.size()) {
139 output->data.resize(new_size);
142 std::memcpy(output->data.data() + output->offset, buffer, nb_bytes);
143 output->offset += nb_bytes;
150OPJ_OFF_T opj_output_buffer_skip(OPJ_OFF_T nb_bytes,
void* user_data) {
151 auto* output =
static_cast<opj_output_buffer*
>(user_data);
152 if (!output || nb_bytes < 0) {
156 size_t new_offset = output->offset +
static_cast<size_t>(nb_bytes);
157 if (new_offset > output->data.size()) {
158 output->data.resize(new_offset);
160 output->offset = new_offset;
167OPJ_BOOL opj_output_buffer_seek(OPJ_OFF_T nb_bytes,
void* user_data) {
168 auto* output =
static_cast<opj_output_buffer*
>(user_data);
169 if (!output || nb_bytes < 0) {
173 size_t new_offset =
static_cast<size_t>(nb_bytes);
174 if (new_offset > output->data.size()) {
175 output->data.resize(new_offset);
177 output->offset = new_offset;
185OPJ_CODEC_FORMAT detect_j2k_format(std::span<const uint8_t> data) {
186 if (data.size() < 12) {
187 return OPJ_CODEC_J2K;
191 static constexpr uint8_t jp2_signature[] = {
192 0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A
195 if (std::memcmp(data.data(), jp2_signature,
sizeof(jp2_signature)) == 0) {
196 return OPJ_CODEC_JP2;
200 if (data[0] == 0xFF && data[1] == 0x4F) {
201 return OPJ_CODEC_J2K;
204 return OPJ_CODEC_J2K;
236#ifndef PACS_WITH_JPEG2000_CODEC
241 "JPEG 2000 codec not available: OpenJPEG library not found at build time");
244 bool use_lossless =
lossless_ || options.lossless;
249 std::memset(&cmptparm[i], 0,
sizeof(opj_image_cmptparm_t));
252 cmptparm[i].sgnd = params.
is_signed() ? 1 : 0;
255 cmptparm[i].w = params.
width;
256 cmptparm[i].h = params.
height;
260 OPJ_COLOR_SPACE color_space = OPJ_CLRSPC_GRAY;
268 opj_image_t* image = opj_image_create(
280 image->x1 = params.
width;
281 image->y1 = params.
height;
285 size_t pixel_count =
static_cast<size_t>(params.
width) * params.
height;
288 OPJ_INT32* comp_data = image->comps[c].data;
290 for (
size_t i = 0; i < pixel_count; ++i) {
297 src_idx = (c * pixel_count + i) * bytes_per_sample;
300 if (src_idx + bytes_per_sample > pixel_data.size()) {
301 opj_image_destroy(image);
306 if (bytes_per_sample == 1) {
308 ?
static_cast<OPJ_INT32
>(
static_cast<int8_t
>(pixel_data[src_idx]))
309 :
static_cast<OPJ_INT32
>(pixel_data[src_idx]);
312 uint16_t raw =
static_cast<uint16_t
>(pixel_data[src_idx]) |
313 (
static_cast<uint16_t
>(pixel_data[src_idx + 1]) << 8);
315 ?
static_cast<OPJ_INT32
>(
static_cast<int16_t
>(raw))
316 :
static_cast<OPJ_INT32
>(raw);
318 comp_data[i] = value;
323 opj_codec_t* codec = opj_create_compress(OPJ_CODEC_J2K);
325 opj_image_destroy(image);
330 std::string error_msg;
331 opj_set_error_handler(codec, opj_error_callback, &error_msg);
332 opj_set_warning_handler(codec, opj_warning_callback,
nullptr);
333 opj_set_info_handler(codec, opj_info_callback,
nullptr);
336 opj_cparameters_t parameters;
337 opj_set_default_encoder_parameters(¶meters);
339 parameters.tcp_numlayers = 1;
340 parameters.cp_disto_alloc = 1;
345 parameters.irreversible = 0;
346 parameters.tcp_rates[0] = 0;
349 parameters.irreversible = 1;
353 if (options.quality > 0 && options.quality <= 100) {
355 ratio = 2.0f + (100.0f -
static_cast<float>(options.quality)) * 0.98f;
357 parameters.tcp_rates[0] = ratio;
361 if (!opj_setup_encoder(codec, ¶meters, image)) {
362 opj_destroy_codec(codec);
363 opj_image_destroy(image);
368 opj_output_buffer output_buf;
369 output_buf.offset = 0;
370 output_buf.data.reserve(pixel_data.size() / 2);
372 opj_stream_t* stream = opj_stream_default_create(OPJ_FALSE);
374 opj_destroy_codec(codec);
375 opj_image_destroy(image);
379 opj_stream_set_user_data(stream, &output_buf,
nullptr);
380 opj_stream_set_user_data_length(stream, 0);
381 opj_stream_set_write_function(stream, opj_output_buffer_write);
382 opj_stream_set_skip_function(stream, opj_output_buffer_skip);
383 opj_stream_set_seek_function(stream, opj_output_buffer_seek);
386 if (!opj_start_compress(codec, image, stream)) {
387 opj_stream_destroy(stream);
388 opj_destroy_codec(codec);
389 opj_image_destroy(image);
393 if (!opj_encode(codec, stream)) {
394 opj_stream_destroy(stream);
395 opj_destroy_codec(codec);
396 opj_image_destroy(image);
400 if (!opj_end_compress(codec, stream)) {
401 opj_stream_destroy(stream);
402 opj_destroy_codec(codec);
403 opj_image_destroy(image);
408 opj_stream_destroy(stream);
409 opj_destroy_codec(codec);
410 opj_image_destroy(image);
413 output_buf.data.resize(output_buf.offset);
415 return kcenon::pacs::ok<compression_result>(
compression_result{std::move(output_buf.data), params});
421#ifndef PACS_WITH_JPEG2000_CODEC
422 (void)compressed_data;
425 "JPEG 2000 codec not available: OpenJPEG library not found at build time");
427 if (compressed_data.empty()) {
432 OPJ_CODEC_FORMAT format = detect_j2k_format(compressed_data);
435 opj_codec_t* codec = opj_create_decompress(format);
441 std::string error_msg;
442 opj_set_error_handler(codec, opj_error_callback, &error_msg);
443 opj_set_warning_handler(codec, opj_warning_callback,
nullptr);
444 opj_set_info_handler(codec, opj_info_callback,
nullptr);
447 opj_dparameters_t parameters;
448 opj_set_default_decoder_parameters(¶meters);
450 if (!opj_setup_decoder(codec, ¶meters)) {
451 opj_destroy_codec(codec);
456 opj_memory_stream input_stream;
457 input_stream.data = compressed_data.data();
458 input_stream.size = compressed_data.size();
459 input_stream.offset = 0;
461 opj_stream_t* stream = opj_stream_default_create(OPJ_TRUE);
463 opj_destroy_codec(codec);
467 opj_stream_set_user_data(stream, &input_stream,
nullptr);
468 opj_stream_set_user_data_length(stream, compressed_data.size());
469 opj_stream_set_read_function(stream, opj_memory_stream_read);
470 opj_stream_set_skip_function(stream, opj_memory_stream_skip);
471 opj_stream_set_seek_function(stream, opj_memory_stream_seek);
474 opj_image_t* image =
nullptr;
475 if (!opj_read_header(stream, codec, &image)) {
476 opj_stream_destroy(stream);
477 opj_destroy_codec(codec);
482 if (!opj_decode(codec, stream, image)) {
483 opj_image_destroy(image);
484 opj_stream_destroy(stream);
485 opj_destroy_codec(codec);
489 if (!opj_end_decompress(codec, stream)) {
490 opj_image_destroy(image);
491 opj_stream_destroy(stream);
492 opj_destroy_codec(codec);
498 output_params.
width =
static_cast<uint16_t
>(image->x1 - image->x0);
499 output_params.
height =
static_cast<uint16_t
>(image->y1 - image->y0);
502 if (image->numcomps > 0) {
503 output_params.
bits_stored =
static_cast<uint16_t
>(image->comps[0].prec);
510 if (image->numcomps == 1) {
512 }
else if (image->numcomps == 3) {
513 output_params.
photometric = (image->color_space == OPJ_CLRSPC_SRGB)
520 opj_image_destroy(image);
521 opj_stream_destroy(stream);
522 opj_destroy_codec(codec);
524 std::to_string(params.
width) +
", got " +
525 std::to_string(output_params.
width));
528 opj_image_destroy(image);
529 opj_stream_destroy(stream);
530 opj_destroy_codec(codec);
532 std::to_string(params.
height) +
", got " +
533 std::to_string(output_params.
height));
537 size_t pixel_count =
static_cast<size_t>(output_params.
width) * output_params.
height;
539 size_t total_size = pixel_count * output_params.
samples_per_pixel * bytes_per_sample;
541 std::vector<uint8_t> output_data(total_size);
544 for (OPJ_UINT32 c = 0; c < image->numcomps; ++c) {
545 const OPJ_INT32* comp_data = image->comps[c].data;
547 for (
size_t i = 0; i < pixel_count; ++i) {
549 OPJ_INT32 value = comp_data[i];
551 if (bytes_per_sample == 1) {
552 output_data[dst_idx] =
static_cast<uint8_t
>(value & 0xFF);
555 output_data[dst_idx] =
static_cast<uint8_t
>(value & 0xFF);
556 output_data[dst_idx + 1] =
static_cast<uint8_t
>((value >> 8) & 0xFF);
562 opj_image_destroy(image);
563 opj_stream_destroy(stream);
564 opj_destroy_codec(codec);
567 return kcenon::pacs::ok<compression_result>(
compression_result{std::move(output_data), output_params});
580 float compression_ratio,
581 int resolution_levels)
582 : impl_(std::make_unique<
impl>(lossless, compression_ratio, resolution_levels)) {}
590 return impl_->
is_lossless_mode() ? kTransferSyntaxUIDLossless : kTransferSyntaxUIDLossy;
603 if (params.bits_stored < 1 || params.bits_stored > 16) {
608 if (params.bits_allocated != 8 && params.bits_allocated != 16) {
613 if (params.samples_per_pixel != 1 && params.samples_per_pixel != 3) {
618 if (params.width == 0 || params.height == 0) {
628 if (params.samples_per_pixel != 0 &&
629 params.samples_per_pixel != 1 &&
630 params.samples_per_pixel != 3) {
PIMPL implementation for jpeg2000_codec.
codec_result encode(std::span< const uint8_t > pixel_data, const image_params ¶ms, const compression_options &options) const
impl(bool lossless, float compression_ratio, int resolution_levels)
int resolution_levels() const noexcept
float compression_ratio() const noexcept
codec_result decode(std::span< const uint8_t > compressed_data, const image_params ¶ms) const
bool is_lossless_mode() const noexcept
JPEG 2000 codec implementation supporting both lossless and lossy modes.
bool is_lossy() const noexcept override
Checks if this codec produces lossy compression.
std::unique_ptr< impl > impl_
~jpeg2000_codec() override
bool can_encode(const image_params ¶ms) const noexcept override
Checks if this codec supports the given image parameters.
codec_result encode(std::span< const uint8_t > pixel_data, const image_params ¶ms, const compression_options &options={}) const override
Compresses pixel data to JPEG 2000 format.
bool can_decode(const image_params ¶ms) const noexcept override
Checks if this codec can decode data with given parameters.
float compression_ratio() const noexcept
Gets the current compression ratio setting.
jpeg2000_codec(bool lossless=true, float compression_ratio=kDefaultCompressionRatio, int resolution_levels=kDefaultResolutionLevels)
Constructs a JPEG 2000 codec instance.
codec_result decode(std::span< const uint8_t > compressed_data, const image_params ¶ms) const override
Decompresses JPEG 2000 data.
int resolution_levels() const noexcept
Gets the number of DWT resolution levels.
std::string_view name() const noexcept override
Returns a human-readable name for the codec.
bool is_lossless_mode() const noexcept
Checks if this codec is configured for lossless mode.
@ ycbcr_full
YCbCr full range (JPEG standard)
@ monochrome2
Minimum pixel value displayed as black.
@ rgb
Red, Green, Blue color model.
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.
Successful result of a compression/decompression operation.
Parameters describing image pixel data.
uint16_t samples_per_pixel
Number of samples per pixel (0028,0002) 1 for grayscale, 3 for color.
uint16_t bits_allocated
Bits allocated per pixel sample (0028,0100) Valid values: 8, 16.
uint16_t height
Image height in pixels (Rows - 0028,0010)
photometric_interpretation photometric
Photometric interpretation (0028,0004)
uint16_t planar_configuration
Planar configuration (0028,0006) 0 = interleaved (R1G1B1R2G2B2...), 1 = separate planes (RRR....
uint16_t pixel_representation
Pixel representation (0028,0103) 0 = unsigned, 1 = signed.
uint16_t width
Image width in pixels (Columns - 0028,0011)
uint16_t bits_stored
Bits stored per pixel sample (0028,0101) Must be <= bits_allocated.
bool is_signed() const noexcept
Checks if pixel values are signed integers.
uint16_t high_bit
High bit position (0028,0102) Typically bits_stored - 1.