20constexpr uint8_t kMarkerPrefix = 0xFF;
21constexpr uint8_t kSOI = 0xD8;
22constexpr uint8_t kEOI = 0xD9;
23constexpr uint8_t kSOF3 = 0xC3;
24constexpr uint8_t kDHT = 0xC4;
25constexpr uint8_t kSOS = 0xDA;
28constexpr int kMaxPrecision = 16;
29constexpr int kMinPrecision = 2;
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));
42inline uint16_t
read_be16(
const uint8_t* data) {
43 return static_cast<uint16_t
>((data[0] << 8) | data[1]);
51 explicit bit_writer(std::vector<uint8_t>& output) :
output_(output) {}
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);
60 num_bits -= bits_to_write;
76 output_.push_back(current_byte_);
78 if (current_byte_ == 0xFF) {
95 bit_reader(
const uint8_t* data,
size_t size)
102 int read_bits(
int num_bits) {
104 while (num_bits > 0) {
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);
114 num_bits -= bits_to_read;
119 bool has_more()
const {
124 void advance_byte() {
129 if (pos_ > 0 && data_[pos_ - 1] == 0xFF && current_byte_ == 0x00) {
152struct huffman_table {
163 generate_default_table();
166 void generate_default_table() {
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
178 for (
int i = 0; i <= 16; ++i) {
179 if (code_lengths[i] > last_length) {
192inline int category(
int diff) {
193 if (diff == 0)
return 0;
194 int abs_diff = diff < 0 ? -diff : diff;
196 while (abs_diff > 0) {
210inline int encode_diff(
int diff,
int cat) {
213 return diff + (1 << cat) - 1;
221inline int decode_diff(
int value,
int cat) {
222 if (cat == 0)
return 0;
223 int half = 1 << (cat - 1);
226 return value - (1 << cat) + 1;
240inline int predict(
int ra,
int rb,
int rc,
int predictor) {
245 case 4:
return ra + rb - rc;
246 case 5:
return ra + ((rb - rc) >> 1);
247 case 6:
return rb + ((ra - rc) >> 1);
248 case 7:
return (ra + rb) >> 1;
268 std::span<const uint8_t> pixel_data,
272 if (pixel_data.empty()) {
278 "Invalid parameters for JPEG Lossless: requires 8/12/16-bit grayscale");
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()));
290 }
catch (
const std::exception& e) {
296 std::span<const uint8_t> compressed_data,
299 if (compressed_data.empty()) {
305 }
catch (
const std::exception& e) {
313 if (params.bits_stored < kMinPrecision || params.bits_stored > kMaxPrecision) {
317 if (params.bits_allocated != 8 && params.bits_allocated != 16) {
321 if (params.samples_per_pixel != 1) {
328 std::span<const uint8_t> pixel_data,
331 std::vector<uint8_t> output;
332 output.reserve(pixel_data.size());
338 output.push_back(kMarkerPrefix);
339 output.push_back(kSOI);
352 bit_writer writer(output);
354 int width = params.
width;
355 int height = params.
height;
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;
363 return pixel_data[idx] | (pixel_data[idx + 1] << 8);
365 return pixel_data[idx];
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);
380 if (x == 0 && y == 0) {
398 int diff = shifted_pixel - pred;
402 if (diff < -mod_range / 2) {
404 }
else if (diff >= mod_range / 2) {
409 int cat = category(diff);
410 int encoded = encode_diff(diff, cat);
413 writer.write_bits(ht.codes[cat], ht.code_lengths[cat]);
417 writer.write_bits(
static_cast<uint32_t
>(encoded), cat);
425 output.push_back(kMarkerPrefix);
426 output.push_back(kEOI);
429 return kcenon::pacs::ok<compression_result>(
compression_result{std::move(output), output_params});
433 output.push_back(kMarkerPrefix);
434 output.push_back(kSOF3);
437 uint16_t length = 8 + 3 * 1;
441 output.push_back(
static_cast<uint8_t
>(params.
bits_stored));
454 output.push_back(0x11);
458 void write_dht(std::vector<uint8_t>& output, [[maybe_unused]]
int precision)
const {
461 output.push_back(kMarkerPrefix);
462 output.push_back(kDHT);
465 std::vector<uint8_t> table_data;
466 table_data.push_back(0x00);
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];
478 for (
int i = 0; i < 16; ++i) {
479 table_data.push_back(
static_cast<uint8_t
>(length_counts[i]));
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));
492 write_be16(output,
static_cast<uint16_t
>(2 + table_data.size()));
495 output.insert(output.end(), table_data.begin(), table_data.end());
499 output.push_back(kMarkerPrefix);
500 output.push_back(kSOS);
510 output.push_back(0x00);
513 output.push_back(
static_cast<uint8_t
>(
predictor_));
523 std::span<const uint8_t> compressed_data,
526 const uint8_t* data = compressed_data.data();
527 size_t size = compressed_data.size();
531 if (size < 2 || data[0] != kMarkerPrefix || data[1] != kSOI) {
542 bool found_sof =
false;
543 bool found_sos =
false;
544 size_t scan_start = 0;
547 while (pos < size - 1) {
548 if (data[pos] != kMarkerPrefix) {
552 uint8_t marker = data[pos + 1];
555 if (marker == kEOI) {
560 if (marker == kMarkerPrefix) {
566 if (marker >= 0xD0 && marker <= 0xD7) {
571 if (pos + 2 > size) {
577 if (pos + length - 2 > size) {
581 if (marker == kSOF3) {
586 precision = data[pos];
590 }
else if (marker == kDHT) {
594 }
else if (marker == kSOS) {
609 if (!found_sof || !found_sos) {
614 if (params.
width > 0 && params.
width != width) {
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) {
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);
635 bit_reader reader(&data[scan_start], scan_end - scan_start);
637 auto set_pixel = [&](
int x,
int y,
int value) {
638 size_t idx =
static_cast<size_t>(y) * width + x;
641 output[idx] =
static_cast<uint8_t
>(value & 0xFF);
642 output[idx + 1] =
static_cast<uint8_t
>((value >> 8) & 0xFF);
644 output[idx] =
static_cast<uint8_t
>(value);
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;
653 return output[idx] | (output[idx + 1] << 8);
659 int max_value = (1 << precision) - 1;
661 for (
int y = 0; y < height; ++y) {
662 for (
int x = 0; x < width; ++x) {
665 if (cat < 0 || cat > 16) {
672 encoded = reader.read_bits(cat);
676 int diff = decode_diff(encoded, cat);
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);
684 if (x == 0 && y == 0) {
698 int shifted_pixel = (pred + diff) & (mod_range - 1);
700 pixel = std::clamp(pixel, 0, max_value);
702 set_pixel(x, y, pixel);
708 output_params.
width =
static_cast<uint16_t
>(width);
709 output_params.
height =
static_cast<uint16_t
>(height);
711 output_params.
bits_stored =
static_cast<uint16_t
>(precision);
712 output_params.
high_bit =
static_cast<uint16_t
>(precision - 1);
718 return kcenon::pacs::ok<compression_result>(
compression_result{std::move(output), output_params});
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) {
742 : impl_(std::make_unique<
impl>(predictor, point_transform)) {}
751 return kTransferSyntaxUID;
755 return "JPEG Lossless (Process 14, SV1)";
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;
773 if (params.bits_allocated != 0 &&
774 params.bits_allocated != 8 &&
775 params.bits_allocated != 16) {
778 if (params.samples_per_pixel != 0 && params.samples_per_pixel != 1) {
793 std::span<const uint8_t> pixel_data,
800 std::span<const uint8_t> compressed_data,
PIMPL implementation for jpeg_lossless_codec.
codec_result decode_frame(std::span< const uint8_t > compressed_data, const image_params ¶ms) const
codec_result decode(std::span< const uint8_t > compressed_data, const image_params ¶ms) const
void write_sos(std::vector< uint8_t > &output) const
impl(int predictor, int point_transform)
bool valid_for_jpeg_lossless(const image_params ¶ms) const noexcept
int point_transform() 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 ¶ms) const
codec_result encode(std::span< const uint8_t > pixel_data, const image_params ¶ms, const compression_options &options) const
void write_dht(std::vector< uint8_t > &output, int precision) const
int predictor() const noexcept
codec_result encode_frame(std::span< const uint8_t > pixel_data, const image_params ¶ms) const
JPEG Lossless (Process 14, Selection Value 1) codec implementation.
bool can_encode(const image_params ¶ms) 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 ¶ms) 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.
std::unique_ptr< impl > impl_
~jpeg_lossless_codec() override
int point_transform() const noexcept
Gets the current point transform value.
bool can_decode(const image_params ¶ms) 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 ¶ms, const compression_options &options={}) const override
Compresses pixel data to JPEG Lossless format.
std::vector< uint8_t > & output_
std::array< uint32_t, 17 > codes
std::array< int, 17 > code_lengths
@ 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.
constexpr uint16_t read_be16(const uint8_t *data) noexcept
Reads a 16-bit value from big-endian bytes.
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)
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.