13#ifdef PACS_WITH_JPEG_CODEC
24#ifdef PACS_WITH_JPEG_CODEC
32struct jpeg_error_handler {
34 jmp_buf setjmp_buffer;
35 std::string error_message;
41void jpeg_error_exit(j_common_ptr cinfo) {
42 auto* err =
reinterpret_cast<jpeg_error_handler*
>(cinfo->err);
45 char buffer[JMSG_LENGTH_MAX];
46 (*cinfo->err->format_message)(cinfo, buffer);
47 err->error_message = buffer;
50 std::longjmp(err->setjmp_buffer, 1);
56void jpeg_output_message([[maybe_unused]] j_common_ptr cinfo) {
63class jpeg_compressor {
66 cinfo_.err = jpeg_std_error(&jerr_.pub);
67 jerr_.pub.error_exit = jpeg_error_exit;
68 jerr_.pub.output_message = jpeg_output_message;
70 jpeg_create_compress(&cinfo_);
74 jpeg_destroy_compress(&cinfo_);
77 jpeg_compressor(
const jpeg_compressor&) =
delete;
78 jpeg_compressor& operator=(
const jpeg_compressor&) =
delete;
80 jpeg_compress_struct* operator->() {
return &cinfo_; }
81 jpeg_compress_struct&
get() {
return cinfo_; }
82 jpeg_error_handler&
error() {
return jerr_; }
85 jpeg_compress_struct cinfo_{};
86 jpeg_error_handler jerr_{};
92class jpeg_decompressor {
95 cinfo_.err = jpeg_std_error(&jerr_.pub);
96 jerr_.pub.error_exit = jpeg_error_exit;
97 jerr_.pub.output_message = jpeg_output_message;
99 jpeg_create_decompress(&cinfo_);
102 ~jpeg_decompressor() {
103 jpeg_destroy_decompress(&cinfo_);
106 jpeg_decompressor(
const jpeg_decompressor&) =
delete;
107 jpeg_decompressor& operator=(
const jpeg_decompressor&) =
delete;
109 jpeg_decompress_struct* operator->() {
return &cinfo_; }
110 jpeg_decompress_struct&
get() {
return cinfo_; }
111 jpeg_error_handler&
error() {
return jerr_; }
114 jpeg_decompress_struct cinfo_{};
115 jpeg_error_handler jerr_{};
119codec_result make_compression_error(
const std::string& message) {
124codec_result make_decompression_error(
const std::string& message) {
129codec_result make_compression_ok(std::vector<uint8_t> data,
const image_params& params) {
130 return kcenon::pacs::ok<compression_result>(compression_result{std::move(data), params});
148 std::span<const uint8_t> pixel_data,
151#ifndef PACS_WITH_JPEG_CODEC
157 "JPEG Baseline codec not available: libjpeg-turbo not found at build time");
159 if (pixel_data.empty()) {
160 return make_compression_error(
"Empty pixel data");
164 return make_compression_error(
165 "Invalid parameters for JPEG Baseline: requires 8-bit depth");
169 if (pixel_data.size() != expected_size) {
170 return make_compression_error(
171 "Pixel data size mismatch: expected " + std::to_string(expected_size) +
172 ", got " + std::to_string(pixel_data.size()));
175 jpeg_compressor compressor;
178 if (setjmp(compressor.error().setjmp_buffer)) {
179 return make_compression_error(
180 "JPEG compression failed: " + compressor.error().error_message);
184 uint8_t* out_buffer =
nullptr;
185 unsigned long out_size = 0;
186 jpeg_mem_dest(&compressor.get(), &out_buffer, &out_size);
189 compressor->image_width = params.
width;
190 compressor->image_height = params.
height;
194 compressor->in_color_space = JCS_GRAYSCALE;
197 compressor->in_color_space = JCS_RGB;
200 jpeg_set_defaults(&compressor.get());
203 int quality = std::clamp(options.quality, 1, 100);
204 jpeg_set_quality(&compressor.get(), quality, TRUE);
208 switch (options.chroma_subsampling) {
210 compressor->comp_info[0].h_samp_factor = 1;
211 compressor->comp_info[0].v_samp_factor = 1;
212 compressor->comp_info[1].h_samp_factor = 1;
213 compressor->comp_info[1].v_samp_factor = 1;
214 compressor->comp_info[2].h_samp_factor = 1;
215 compressor->comp_info[2].v_samp_factor = 1;
218 compressor->comp_info[0].h_samp_factor = 2;
219 compressor->comp_info[0].v_samp_factor = 1;
220 compressor->comp_info[1].h_samp_factor = 1;
221 compressor->comp_info[1].v_samp_factor = 1;
222 compressor->comp_info[2].h_samp_factor = 1;
223 compressor->comp_info[2].v_samp_factor = 1;
227 compressor->comp_info[0].h_samp_factor = 2;
228 compressor->comp_info[0].v_samp_factor = 2;
229 compressor->comp_info[1].h_samp_factor = 1;
230 compressor->comp_info[1].v_samp_factor = 1;
231 compressor->comp_info[2].h_samp_factor = 1;
232 compressor->comp_info[2].v_samp_factor = 1;
238 jpeg_start_compress(&compressor.get(), TRUE);
242 std::vector<JSAMPROW> row_pointers(params.
height);
244 for (JDIMENSION row = 0; row < params.
height; ++row) {
245 row_pointers[row] =
const_cast<JSAMPROW
>(
246 pixel_data.data() + row * row_stride);
250 jpeg_write_scanlines(&compressor.get(), row_pointers.data(),
251 static_cast<JDIMENSION
>(params.
height));
254 jpeg_finish_compress(&compressor.get());
257 std::vector<uint8_t> result(out_buffer, out_buffer + out_size);
260 std::free(out_buffer);
265 return make_compression_ok(std::move(result), output_params);
270 std::span<const uint8_t> compressed_data,
272#ifndef PACS_WITH_JPEG_CODEC
273 (void)compressed_data;
277 "JPEG Baseline codec not available: libjpeg-turbo not found at build time");
279 if (compressed_data.empty()) {
280 return make_decompression_error(
"Empty compressed data");
283 jpeg_decompressor decompressor;
286 if (setjmp(decompressor.error().setjmp_buffer)) {
287 return make_decompression_error(
288 "JPEG decompression failed: " + decompressor.error().error_message);
295 jpeg_mem_src(&decompressor.get(),
296 const_cast<unsigned char*
>(compressed_data.data()),
297 static_cast<unsigned long>(compressed_data.size()));
300 int header_result = jpeg_read_header(&decompressor.get(), TRUE);
301 if (header_result != JPEG_HEADER_OK) {
302 return make_decompression_error(
"Invalid JPEG header");
306 if (params.
width > 0 && decompressor->image_width != params.
width) {
307 return make_decompression_error(
308 "Image width mismatch: expected " + std::to_string(params.
width) +
309 ", got " + std::to_string(decompressor->image_width));
311 if (params.
height > 0 && decompressor->image_height != params.
height) {
312 return make_decompression_error(
313 "Image height mismatch: expected " + std::to_string(params.
height) +
314 ", got " + std::to_string(decompressor->image_height));
318 if (decompressor->num_components == 3) {
319 decompressor->out_color_space = JCS_RGB;
323 jpeg_start_decompress(&decompressor.get());
326 JDIMENSION row_stride = decompressor->output_width *
327 static_cast<JDIMENSION
>(decompressor->output_components);
328 size_t output_size =
static_cast<size_t>(row_stride) * decompressor->output_height;
329 std::vector<uint8_t> output(output_size);
332 while (decompressor->output_scanline < decompressor->output_height) {
333 JSAMPROW row = output.data() +
334 decompressor->output_scanline * row_stride;
335 jpeg_read_scanlines(&decompressor.get(), &row, 1);
339 jpeg_finish_decompress(&decompressor.get());
343 output_params.
width =
static_cast<uint16_t
>(decompressor->output_width);
344 output_params.
height =
static_cast<uint16_t
>(decompressor->output_height);
348 output_params.
samples_per_pixel =
static_cast<uint16_t
>(decompressor->output_components);
358 return make_compression_ok(std::move(output), output_params);
366 : impl_(std::make_unique<
impl>()) {}
375 return kTransferSyntaxUID;
379 return "JPEG Baseline (Process 1)";
387 return params.valid_for_jpeg_baseline();
393 if (params.bits_allocated != 0 && params.bits_allocated != 8)
return false;
394 if (params.samples_per_pixel != 0 &&
395 params.samples_per_pixel != 1 &&
396 params.samples_per_pixel != 3) {
403 std::span<const uint8_t> pixel_data,
410 std::span<const uint8_t> compressed_data,
PIMPL implementation for jpeg_baseline_codec.
codec_result decode(std::span< const uint8_t > compressed_data, const image_params ¶ms) const
codec_result encode(std::span< const uint8_t > pixel_data, const image_params ¶ms, const compression_options &options) const
JPEG Baseline (Process 1) codec implementation.
bool is_lossy() const noexcept override
Checks if this codec produces lossy compression.
bool can_decode(const image_params ¶ms) const noexcept override
Checks if this codec can decode data with given parameters.
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 JPEG Baseline format.
codec_result decode(std::span< const uint8_t > compressed_data, const image_params ¶ms) const override
Decompresses JPEG Baseline data.
jpeg_baseline_codec()
Constructs a JPEG Baseline codec instance.
bool can_encode(const image_params ¶ms) const noexcept override
Checks if this codec supports the given image parameters.
~jpeg_baseline_codec() override
std::unique_ptr< impl > impl_
@ error
Node returned an error.
@ monochrome2
Minimum pixel value displayed as black.
@ rgb
Red, Green, Blue color model.
constexpr int compression_error
constexpr int decompression_error
@ get
C-GET retrieve request/response.
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.
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)
size_t frame_size_bytes() const noexcept
Calculates the size of uncompressed pixel data in bytes.
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 valid_for_jpeg_baseline() const noexcept
Validates image parameters for JPEG Baseline compression.
bool is_grayscale() const noexcept
Checks if the image is grayscale (single sample per pixel).
uint16_t high_bit
High bit position (0028,0102) Typically bits_stored - 1.