PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
jpeg_lossless_codec.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
7
8#include <algorithm>
9#include <array>
10#include <cstdint>
11#include <cstring>
12#include <stdexcept>
13#include <vector>
14
16
17namespace {
18
19// JPEG marker definitions
20constexpr uint8_t kMarkerPrefix = 0xFF;
21constexpr uint8_t kSOI = 0xD8; // Start of Image
22constexpr uint8_t kEOI = 0xD9; // End of Image
23constexpr uint8_t kSOF3 = 0xC3; // Start of Frame (Lossless)
24constexpr uint8_t kDHT = 0xC4; // Define Huffman Table
25constexpr uint8_t kSOS = 0xDA; // Start of Scan
26
27// DICOM JPEG Lossless default precision values
28constexpr int kMaxPrecision = 16;
29constexpr int kMinPrecision = 2;
30
34inline void write_be16(std::vector<uint8_t>& buf, uint16_t value) {
35 buf.push_back(static_cast<uint8_t>(value >> 8));
36 buf.push_back(static_cast<uint8_t>(value & 0xFF));
37}
38
42inline uint16_t read_be16(const uint8_t* data) {
43 return static_cast<uint16_t>((data[0] << 8) | data[1]);
44}
45
49class bit_writer {
50public:
51 explicit bit_writer(std::vector<uint8_t>& output) : output_(output) {}
52
53 void write_bits(uint32_t value, int num_bits) {
54 while (num_bits > 0) {
55 int bits_to_write = (std::min)(num_bits, 8 - bit_pos_);
56 int shift = num_bits - bits_to_write;
57 uint8_t mask = static_cast<uint8_t>((1 << bits_to_write) - 1);
58 current_byte_ |= static_cast<uint8_t>(((value >> shift) & mask) << (8 - bit_pos_ - bits_to_write));
59 bit_pos_ += bits_to_write;
60 num_bits -= bits_to_write;
61
62 if (bit_pos_ == 8) {
63 flush_byte();
64 }
65 }
66 }
67
68 void flush() {
69 if (bit_pos_ > 0) {
70 flush_byte();
71 }
72 }
73
74private:
75 void flush_byte() {
76 output_.push_back(current_byte_);
77 // Byte stuffing: insert 0x00 after 0xFF
78 if (current_byte_ == 0xFF) {
79 output_.push_back(0x00);
80 }
81 current_byte_ = 0;
82 bit_pos_ = 0;
83 }
84
85 std::vector<uint8_t>& output_;
86 uint8_t current_byte_ = 0;
87 int bit_pos_ = 0;
88};
89
93class bit_reader {
94public:
95 bit_reader(const uint8_t* data, size_t size)
96 : data_(data), size_(size), pos_(0), bit_pos_(0), current_byte_(0) {
97 if (size_ > 0) {
99 }
100 }
101
102 int read_bits(int num_bits) {
103 int value = 0;
104 while (num_bits > 0) {
105 if (bit_pos_ == 8) {
106 advance_byte();
107 }
108 int bits_available = 8 - bit_pos_;
109 int bits_to_read = (std::min)(num_bits, bits_available);
110 int shift = bits_available - bits_to_read;
111 uint8_t mask = static_cast<uint8_t>((1 << bits_to_read) - 1);
112 value = (value << bits_to_read) | ((current_byte_ >> shift) & mask);
113 bit_pos_ += bits_to_read;
114 num_bits -= bits_to_read;
115 }
116 return value;
117 }
118
119 bool has_more() const {
120 return pos_ < size_ || bit_pos_ < 8;
121 }
122
123private:
124 void advance_byte() {
125 ++pos_;
126 if (pos_ < size_) {
128 // Handle byte stuffing: skip 0x00 after 0xFF
129 if (pos_ > 0 && data_[pos_ - 1] == 0xFF && current_byte_ == 0x00) {
130 ++pos_;
131 if (pos_ < size_) {
133 }
134 }
135 }
136 bit_pos_ = 0;
137 }
138
139 const uint8_t* data_;
140 size_t size_;
141 size_t pos_;
142 int bit_pos_;
143 uint8_t current_byte_;
144};
145
152struct huffman_table {
153 // Code lengths for each category (0-16)
154 std::array<int, 17> code_lengths{};
155 // Huffman codes for each category
156 std::array<uint32_t, 17> codes{};
157
158 huffman_table() {
159 // Initialize with default JPEG lossless Huffman table
160 // Category 0-16 with appropriate code lengths
161 // This is a simplified version - actual implementation would
162 // optimize based on data statistics
163 generate_default_table();
164 }
165
166 void generate_default_table() {
167 // Default code lengths for lossless JPEG
168 // Shorter codes for smaller differences (more common)
169 static constexpr std::array<int, 17> default_lengths = {
170 2, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16
171 };
172
173 code_lengths = default_lengths;
174
175 // Generate codes from lengths
176 uint32_t code = 0;
177 int last_length = 0;
178 for (int i = 0; i <= 16; ++i) {
179 if (code_lengths[i] > last_length) {
180 code <<= (code_lengths[i] - last_length);
181 }
182 codes[i] = code;
183 ++code;
184 last_length = code_lengths[i];
185 }
186 }
187};
188
192inline int category(int diff) {
193 if (diff == 0) return 0;
194 int abs_diff = diff < 0 ? -diff : diff;
195 int cat = 0;
196 while (abs_diff > 0) {
197 abs_diff >>= 1;
198 ++cat;
199 }
200 return cat;
201}
202
210inline int encode_diff(int diff, int cat) {
211 if (diff < 0) {
212 // For negative numbers, use one's complement
213 return diff + (1 << cat) - 1;
214 }
215 return diff;
216}
217
221inline int decode_diff(int value, int cat) {
222 if (cat == 0) return 0;
223 int half = 1 << (cat - 1);
224 if (value < half) {
225 // Negative value (one's complement)
226 return value - (1 << cat) + 1;
227 }
228 return value;
229}
230
240inline int predict(int ra, int rb, int rc, int predictor) {
241 switch (predictor) {
242 case 1: return ra; // Px = Ra
243 case 2: return rb; // Px = Rb
244 case 3: return rc; // Px = Rc
245 case 4: return ra + rb - rc; // Px = Ra + Rb - Rc
246 case 5: return ra + ((rb - rc) >> 1); // Px = Ra + (Rb - Rc) / 2
247 case 6: return rb + ((ra - rc) >> 1); // Px = Rb + (Ra - Rc) / 2
248 case 7: return (ra + rb) >> 1; // Px = (Ra + Rb) / 2
249 default: return ra; // Default to predictor 1
250 }
251}
252
253} // namespace
254
259public:
261 : predictor_(std::clamp(predictor, 1, 7)),
262 point_transform_(std::clamp(point_transform, 0, 15)) {}
263
264 [[nodiscard]] int predictor() const noexcept { return predictor_; }
265 [[nodiscard]] int point_transform() const noexcept { return point_transform_; }
266
267 [[nodiscard]] codec_result encode(
268 std::span<const uint8_t> pixel_data,
269 const image_params& params,
270 [[maybe_unused]] const compression_options& options) const {
271
272 if (pixel_data.empty()) {
274 }
275
276 if (!valid_for_jpeg_lossless(params)) {
278 "Invalid parameters for JPEG Lossless: requires 8/12/16-bit grayscale");
279 }
280
281 size_t expected_size = params.frame_size_bytes();
282 if (pixel_data.size() != expected_size) {
284 "Pixel data size mismatch: expected " + std::to_string(expected_size) +
285 ", got " + std::to_string(pixel_data.size()));
286 }
287
288 try {
289 return encode_frame(pixel_data, params);
290 } catch (const std::exception& e) {
291 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, std::string("JPEG Lossless encoding failed: ") + e.what());
292 }
293 }
294
295 [[nodiscard]] codec_result decode(
296 std::span<const uint8_t> compressed_data,
297 const image_params& params) const {
298
299 if (compressed_data.empty()) {
301 }
302
303 try {
304 return decode_frame(compressed_data, params);
305 } catch (const std::exception& e) {
306 return kcenon::pacs::pacs_error<compression_result>(kcenon::pacs::error_codes::decompression_error, std::string("JPEG Lossless decoding failed: ") + e.what());
307 }
308 }
309
310private:
311 [[nodiscard]] bool valid_for_jpeg_lossless(const image_params& params) const noexcept {
312 // JPEG Lossless supports 2-16 bit precision
313 if (params.bits_stored < kMinPrecision || params.bits_stored > kMaxPrecision) {
314 return false;
315 }
316 // bits_allocated must be 8 or 16
317 if (params.bits_allocated != 8 && params.bits_allocated != 16) {
318 return false;
319 }
320 // Grayscale only for DICOM JPEG Lossless
321 if (params.samples_per_pixel != 1) {
322 return false;
323 }
324 return true;
325 }
326
328 std::span<const uint8_t> pixel_data,
329 const image_params& params) const {
330
331 std::vector<uint8_t> output;
332 output.reserve(pixel_data.size()); // Reserve worst-case
333
334 int precision = params.bits_stored;
335 bool is_16bit = params.bits_allocated == 16;
336
337 // Write SOI marker
338 output.push_back(kMarkerPrefix);
339 output.push_back(kSOI);
340
341 // Write SOF3 (Start of Frame - Lossless)
342 write_sof3(output, params);
343
344 // Write DHT (Huffman Table)
345 write_dht(output, precision);
346
347 // Write SOS (Start of Scan)
348 write_sos(output);
349
350 // Encode image data
351 huffman_table ht;
352 bit_writer writer(output);
353
354 int width = params.width;
355 int height = params.height;
356
357 auto get_pixel = [&](int x, int y) -> int {
358 if (x < 0 || y < 0) return 0;
359 size_t idx = static_cast<size_t>(y) * width + x;
360 if (is_16bit) {
361 idx *= 2;
362 // Little-endian pixel data
363 return pixel_data[idx] | (pixel_data[idx + 1] << 8);
364 }
365 return pixel_data[idx];
366 };
367
368 for (int y = 0; y < height; ++y) {
369 for (int x = 0; x < width; ++x) {
370 int pixel = get_pixel(x, y);
371 int ra = get_pixel(x - 1, y);
372 int rb = get_pixel(x, y - 1);
373 int rc = get_pixel(x - 1, y - 1);
374
375 // Apply point transform
376 int shifted_pixel = pixel >> point_transform_;
377
378 // Compute prediction
379 int pred;
380 if (x == 0 && y == 0) {
381 // First pixel: use 2^(P-Pt-1) as prediction
382 pred = 1 << (precision - point_transform_ - 1);
383 } else if (y == 0) {
384 // First row: use Ra
385 pred = ra >> point_transform_;
386 } else if (x == 0) {
387 // First column: use Rb
388 pred = rb >> point_transform_;
389 } else {
390 // Use selected predictor
391 pred = predict(ra >> point_transform_,
392 rb >> point_transform_,
393 rc >> point_transform_,
394 predictor_);
395 }
396
397 // Compute difference
398 int diff = shifted_pixel - pred;
399
400 // Modulo operation for precision
401 int mod_range = 1 << (precision - point_transform_);
402 if (diff < -mod_range / 2) {
403 diff += mod_range;
404 } else if (diff >= mod_range / 2) {
405 diff -= mod_range;
406 }
407
408 // Encode difference
409 int cat = category(diff);
410 int encoded = encode_diff(diff, cat);
411
412 // Write Huffman code for category
413 writer.write_bits(ht.codes[cat], ht.code_lengths[cat]);
414
415 // Write additional bits for actual value
416 if (cat > 0) {
417 writer.write_bits(static_cast<uint32_t>(encoded), cat);
418 }
419 }
420 }
421
422 writer.flush();
423
424 // Write EOI marker
425 output.push_back(kMarkerPrefix);
426 output.push_back(kEOI);
427
428 image_params output_params = params;
429 return kcenon::pacs::ok<compression_result>(compression_result{std::move(output), output_params});
430 }
431
432 void write_sof3(std::vector<uint8_t>& output, const image_params& params) const {
433 output.push_back(kMarkerPrefix);
434 output.push_back(kSOF3);
435
436 // Length = 8 + 3 * number_of_components
437 uint16_t length = 8 + 3 * 1; // 1 component (grayscale)
438 write_be16(output, length);
439
440 // Precision
441 output.push_back(static_cast<uint8_t>(params.bits_stored));
442
443 // Height
444 write_be16(output, params.height);
445
446 // Width
447 write_be16(output, params.width);
448
449 // Number of components
450 output.push_back(1);
451
452 // Component specification
453 output.push_back(1); // Component ID
454 output.push_back(0x11); // Sampling factors (1x1)
455 output.push_back(0); // Quantization table (not used for lossless)
456 }
457
458 void write_dht(std::vector<uint8_t>& output, [[maybe_unused]] int precision) const {
459 huffman_table ht;
460
461 output.push_back(kMarkerPrefix);
462 output.push_back(kDHT);
463
464 // Calculate table data
465 std::vector<uint8_t> table_data;
466 table_data.push_back(0x00); // DC table, ID 0
467
468 // Count codes of each length
469 std::array<int, 16> length_counts{};
470 for (int i = 0; i <= 16; ++i) {
471 int len = ht.code_lengths[i];
472 if (len >= 1 && len <= 16) {
473 ++length_counts[len - 1];
474 }
475 }
476
477 // Write length counts
478 for (int i = 0; i < 16; ++i) {
479 table_data.push_back(static_cast<uint8_t>(length_counts[i]));
480 }
481
482 // Write symbols in order of code length
483 for (int len = 1; len <= 16; ++len) {
484 for (int i = 0; i <= 16; ++i) {
485 if (ht.code_lengths[i] == len) {
486 table_data.push_back(static_cast<uint8_t>(i));
487 }
488 }
489 }
490
491 // Write length
492 write_be16(output, static_cast<uint16_t>(2 + table_data.size()));
493
494 // Write table data
495 output.insert(output.end(), table_data.begin(), table_data.end());
496 }
497
498 void write_sos(std::vector<uint8_t>& output) const {
499 output.push_back(kMarkerPrefix);
500 output.push_back(kSOS);
501
502 // Length
503 write_be16(output, 8); // Fixed length for 1 component
504
505 // Number of components
506 output.push_back(1);
507
508 // Component selector and Huffman table
509 output.push_back(1); // Component ID
510 output.push_back(0x00); // DC=0, AC=0
511
512 // Spectral selection (predictor for lossless)
513 output.push_back(static_cast<uint8_t>(predictor_));
514
515 // Spectral selection end (not used)
516 output.push_back(0);
517
518 // Successive approximation (point transform)
519 output.push_back(static_cast<uint8_t>(point_transform_));
520 }
521
523 std::span<const uint8_t> compressed_data,
524 const image_params& params) const {
525
526 const uint8_t* data = compressed_data.data();
527 size_t size = compressed_data.size();
528 size_t pos = 0;
529
530 // Parse SOI
531 if (size < 2 || data[0] != kMarkerPrefix || data[1] != kSOI) {
533 }
534 pos = 2;
535
536 int precision = 0;
537 int width = 0;
538 int height = 0;
539 int predictor = predictor_;
541 huffman_table ht;
542 bool found_sof = false;
543 bool found_sos = false;
544 size_t scan_start = 0;
545
546 // Parse markers
547 while (pos < size - 1) {
548 if (data[pos] != kMarkerPrefix) {
550 }
551
552 uint8_t marker = data[pos + 1];
553 pos += 2;
554
555 if (marker == kEOI) {
556 break;
557 }
558
559 // Skip fill bytes (0xFF)
560 if (marker == kMarkerPrefix) {
561 --pos;
562 continue;
563 }
564
565 // Markers with no length
566 if (marker >= 0xD0 && marker <= 0xD7) { // RST markers
567 continue;
568 }
569
570 // Read marker length
571 if (pos + 2 > size) {
573 }
574 uint16_t length = read_be16(&data[pos]);
575 pos += 2;
576
577 if (pos + length - 2 > size) {
579 }
580
581 if (marker == kSOF3) {
582 // Parse SOF3
583 if (length < 8) {
585 }
586 precision = data[pos];
587 height = read_be16(&data[pos + 1]);
588 width = read_be16(&data[pos + 3]);
589 found_sof = true;
590 } else if (marker == kDHT) {
591 // Parse DHT - use the table from the file
592 // For simplicity, we use our default table
593 // A full implementation would parse and use the embedded table
594 } else if (marker == kSOS) {
595 // Parse SOS
596 if (length >= 6) {
597 predictor = data[pos + 3];
598 point_transform = data[pos + 5] & 0x0F;
599 }
600 found_sos = true;
601 pos += length - 2;
602 scan_start = pos;
603 break;
604 }
605
606 pos += length - 2;
607 }
608
609 if (!found_sof || !found_sos) {
611 }
612
613 // Validate against params if provided
614 if (params.width > 0 && params.width != width) {
616 }
617 if (params.height > 0 && params.height != height) {
619 }
620
621 // Find scan data end (EOI marker)
622 size_t scan_end = size;
623 for (size_t i = scan_start; i < size - 1; ++i) {
624 if (data[i] == kMarkerPrefix && data[i + 1] == kEOI) {
625 scan_end = i;
626 break;
627 }
628 }
629
630 // Decode image data
631 bool is_16bit = precision > 8;
632 size_t output_size = static_cast<size_t>(width) * height * (is_16bit ? 2 : 1);
633 std::vector<uint8_t> output(output_size);
634
635 bit_reader reader(&data[scan_start], scan_end - scan_start);
636
637 auto set_pixel = [&](int x, int y, int value) {
638 size_t idx = static_cast<size_t>(y) * width + x;
639 if (is_16bit) {
640 idx *= 2;
641 output[idx] = static_cast<uint8_t>(value & 0xFF);
642 output[idx + 1] = static_cast<uint8_t>((value >> 8) & 0xFF);
643 } else {
644 output[idx] = static_cast<uint8_t>(value);
645 }
646 };
647
648 auto get_decoded_pixel = [&](int x, int y) -> int {
649 if (x < 0 || y < 0) return 0;
650 size_t idx = static_cast<size_t>(y) * width + x;
651 if (is_16bit) {
652 idx *= 2;
653 return output[idx] | (output[idx + 1] << 8);
654 }
655 return output[idx];
656 };
657
658 int mod_range = 1 << (precision - point_transform);
659 int max_value = (1 << precision) - 1;
660
661 for (int y = 0; y < height; ++y) {
662 for (int x = 0; x < width; ++x) {
663 // Decode Huffman category
664 int cat = decode_huffman_category(reader, ht);
665 if (cat < 0 || cat > 16) {
667 }
668
669 // Read additional bits
670 int encoded = 0;
671 if (cat > 0) {
672 encoded = reader.read_bits(cat);
673 }
674
675 // Decode difference
676 int diff = decode_diff(encoded, cat);
677
678 // Compute prediction
679 int ra = get_decoded_pixel(x - 1, y);
680 int rb = get_decoded_pixel(x, y - 1);
681 int rc = get_decoded_pixel(x - 1, y - 1);
682
683 int pred;
684 if (x == 0 && y == 0) {
685 pred = 1 << (precision - point_transform - 1);
686 } else if (y == 0) {
687 pred = ra >> point_transform;
688 } else if (x == 0) {
689 pred = rb >> point_transform;
690 } else {
691 pred = predict(ra >> point_transform,
692 rb >> point_transform,
693 rc >> point_transform,
694 predictor);
695 }
696
697 // Reconstruct pixel
698 int shifted_pixel = (pred + diff) & (mod_range - 1);
699 int pixel = shifted_pixel << point_transform;
700 pixel = std::clamp(pixel, 0, max_value);
701
702 set_pixel(x, y, pixel);
703 }
704 }
705
706 // Build output parameters
707 image_params output_params;
708 output_params.width = static_cast<uint16_t>(width);
709 output_params.height = static_cast<uint16_t>(height);
710 output_params.bits_allocated = is_16bit ? 16 : 8;
711 output_params.bits_stored = static_cast<uint16_t>(precision);
712 output_params.high_bit = static_cast<uint16_t>(precision - 1);
713 output_params.samples_per_pixel = 1;
714 output_params.planar_configuration = 0;
715 output_params.pixel_representation = 0;
717
718 return kcenon::pacs::ok<compression_result>(compression_result{std::move(output), output_params});
719 }
720
721 int decode_huffman_category(bit_reader& reader, const huffman_table& ht) const {
722 // Simple Huffman decoding - match against known codes
723 uint32_t code = 0;
724 for (int bits = 1; bits <= 16; ++bits) {
725 code = (code << 1) | reader.read_bits(1);
726 for (int cat = 0; cat <= 16; ++cat) {
727 if (ht.code_lengths[cat] == bits && ht.codes[cat] == code) {
728 return cat;
729 }
730 }
731 }
732 return -1; // Error
733 }
734
737};
738
739// jpeg_lossless_codec implementation
740
741jpeg_lossless_codec::jpeg_lossless_codec(int predictor, int point_transform)
742 : impl_(std::make_unique<impl>(predictor, point_transform)) {}
743
745
747
748jpeg_lossless_codec& jpeg_lossless_codec::operator=(jpeg_lossless_codec&&) noexcept = default;
749
750std::string_view jpeg_lossless_codec::transfer_syntax_uid() const noexcept {
751 return kTransferSyntaxUID;
752}
753
754std::string_view jpeg_lossless_codec::name() const noexcept {
755 return "JPEG Lossless (Process 14, SV1)";
756}
757
758bool jpeg_lossless_codec::is_lossy() const noexcept {
759 return false;
760}
761
762bool jpeg_lossless_codec::can_encode(const image_params& params) const noexcept {
763 // JPEG Lossless supports 2-16 bit precision, grayscale only
764 if (params.bits_stored < 2 || params.bits_stored > 16) return false;
765 if (params.bits_allocated != 8 && params.bits_allocated != 16) return false;
766 if (params.samples_per_pixel != 1) return false;
767 return true;
768}
769
770bool jpeg_lossless_codec::can_decode(const image_params& params) const noexcept {
771 // Can decode any grayscale JPEG Lossless
772 // Width/height can be 0 (unknown) - will read from JPEG header
773 if (params.bits_allocated != 0 &&
774 params.bits_allocated != 8 &&
775 params.bits_allocated != 16) {
776 return false;
777 }
778 if (params.samples_per_pixel != 0 && params.samples_per_pixel != 1) {
779 return false;
780 }
781 return true;
782}
783
784int jpeg_lossless_codec::predictor() const noexcept {
785 return impl_->predictor();
786}
787
789 return impl_->point_transform();
790}
791
793 std::span<const uint8_t> pixel_data,
794 const image_params& params,
795 const compression_options& options) const {
796 return impl_->encode(pixel_data, params, options);
797}
798
800 std::span<const uint8_t> compressed_data,
801 const image_params& params) const {
802 return impl_->decode(compressed_data, params);
803}
804
805} // namespace kcenon::pacs::encoding::compression
codec_result decode_frame(std::span< const uint8_t > compressed_data, const image_params &params) const
codec_result decode(std::span< const uint8_t > compressed_data, const image_params &params) const
bool valid_for_jpeg_lossless(const image_params &params) const noexcept
int decode_huffman_category(bit_reader &reader, const huffman_table &ht) const
void write_sof3(std::vector< uint8_t > &output, const image_params &params) const
codec_result encode(std::span< const uint8_t > pixel_data, const image_params &params, const compression_options &options) const
void write_dht(std::vector< uint8_t > &output, int precision) const
codec_result encode_frame(std::span< const uint8_t > pixel_data, const image_params &params) const
JPEG Lossless (Process 14, Selection Value 1) codec implementation.
bool can_encode(const image_params &params) const noexcept override
Checks if this codec supports the given image parameters.
codec_result decode(std::span< const uint8_t > compressed_data, const image_params &params) const override
Decompresses JPEG Lossless data.
bool is_lossy() const noexcept override
Checks if this codec produces lossy compression.
std::string_view name() const noexcept override
Returns a human-readable name for the codec.
jpeg_lossless_codec(int predictor=kDefaultPredictor, int point_transform=kDefaultPointTransform)
Constructs a JPEG Lossless codec instance.
int predictor() const noexcept
Gets the current predictor selection value.
int point_transform() const noexcept
Gets the current point transform value.
bool can_decode(const image_params &params) const noexcept override
Checks if this codec can decode data with given parameters.
codec_result encode(std::span< const uint8_t > pixel_data, const image_params &params, const compression_options &options={}) const override
Compresses pixel data to JPEG Lossless format.
uint8_t current_byte_
std::vector< uint8_t > & output_
size_t size_
size_t pos_
std::array< uint32_t, 17 > codes
std::array< int, 17 > code_lengths
int bit_pos_
const uint8_t * data_
@ monochrome2
Minimum pixel value displayed as black.
void write_be16(std::vector< uint8_t > &buffer, uint16_t value)
Writes a 16-bit value in big-endian byte order.
Definition byte_swap.h:124
constexpr uint16_t read_be16(const uint8_t *data) noexcept
Reads a 16-bit value from big-endian bytes.
Definition byte_swap.h:86
constexpr int decompression_error
Definition result.h:79
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Definition result.h:234
Result<T> type aliases and helpers for PACS system.
std::string_view code
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)
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.
uint16_t high_bit
High bit position (0028,0102) Typically bits_stored - 1.