23inline uint32_t read_le32(
const uint8_t* data) {
24 return static_cast<uint32_t
>(data[0]) |
25 (
static_cast<uint32_t
>(data[1]) << 8) |
26 (
static_cast<uint32_t
>(data[2]) << 16) |
27 (
static_cast<uint32_t
>(data[3]) << 24);
33inline void write_le32(uint8_t* data, uint32_t value) {
34 data[0] =
static_cast<uint8_t
>(value & 0xFF);
35 data[1] =
static_cast<uint8_t
>((value >> 8) & 0xFF);
36 data[2] =
static_cast<uint8_t
>((value >> 16) & 0xFF);
37 data[3] =
static_cast<uint8_t
>((value >> 24) & 0xFF);
52void encode_rle_segment(std::span<const uint8_t> input, std::vector<uint8_t>& output) {
58 const size_t size = input.size();
62 uint8_t current = input[pos];
63 size_t run_length = 1;
65 while (pos + run_length < size &&
66 input[pos + run_length] == current &&
71 if (run_length >= 3) {
74 output.push_back(
static_cast<uint8_t
>(1 -
static_cast<int>(run_length)));
75 output.push_back(current);
79 std::vector<uint8_t> literal;
82 while (pos < size && literal.size() < 128) {
85 while (pos + ahead_run < size &&
86 input[pos + ahead_run] == input[pos] &&
96 literal.push_back(input[pos]);
100 if (!literal.empty()) {
102 output.push_back(
static_cast<uint8_t
>(literal.size() - 1));
103 output.insert(output.end(), literal.begin(), literal.end());
121 std::span<const uint8_t> input,
size_t expected_size) {
122 std::vector<uint8_t> output;
123 output.reserve(expected_size);
126 const size_t size = input.size();
128 while (pos < size && output.size() < expected_size) {
129 auto control =
static_cast<int8_t
>(input[pos]);
134 size_t count =
static_cast<size_t>(
control) + 1;
135 if (pos + count > size) {
136 return kcenon::pacs::pacs_error<std::vector<uint8_t>>(
138 "RLE decode: insufficient literal data");
140 output.insert(output.end(), input.begin() + pos, input.begin() + pos + count);
142 }
else if (control != -128) {
145 return kcenon::pacs::pacs_error<std::vector<uint8_t>>(
147 "RLE decode: missing replicate byte");
149 size_t count =
static_cast<size_t>(1 -
control);
150 uint8_t value = input[pos];
152 for (
size_t i = 0; i < count && output.size() < expected_size; ++i) {
153 output.push_back(value);
159 return kcenon::pacs::ok<std::vector<uint8_t>>(std::move(output));
171int calculate_segment_count(
const image_params& params) {
172 int bytes_per_sample = (params.bits_allocated + 7) / 8;
173 return params.samples_per_pixel * bytes_per_sample;
186 std::span<const uint8_t> pixel_data,
190 if (pixel_data.empty()) {
196 "Invalid parameters for RLE: requires 8/16-bit, 1-3 samples per pixel");
200 if (pixel_data.size() != expected_size) {
202 "Pixel data size mismatch: expected " + std::to_string(expected_size) +
203 ", got " + std::to_string(pixel_data.size()));
208 }
catch (
const std::exception& e) {
214 std::span<const uint8_t> compressed_data,
217 if (compressed_data.empty()) {
227 }
catch (
const std::exception& e) {
235 if (params.bits_allocated != 8 && params.bits_allocated != 16) {
239 if (params.samples_per_pixel < 1 || params.samples_per_pixel > 3) {
243 int num_segments = calculate_segment_count(params);
248 if (params.width == 0 || params.height == 0) {
255 std::span<const uint8_t> pixel_data,
258 int num_segments = calculate_segment_count(params);
259 size_t pixels_per_frame =
static_cast<size_t>(params.
width) * params.
height;
263 std::vector<std::vector<uint8_t>> segments(num_segments);
264 for (
auto& seg : segments) {
265 seg.reserve(pixels_per_frame);
273 if (bytes_per_sample == 1) {
277 segments[0].assign(pixel_data.begin(), pixel_data.end());
280 segments[0].resize(pixels_per_frame);
281 segments[1].resize(pixels_per_frame);
282 segments[2].resize(pixels_per_frame);
291 for (
size_t i = 0; i < pixels_per_frame; ++i) {
302 segments[0].resize(pixels_per_frame);
303 segments[1].resize(pixels_per_frame);
312 std::vector<uint8_t> r_plane(pixels_per_frame * 2);
313 std::vector<uint8_t> g_plane(pixels_per_frame * 2);
314 std::vector<uint8_t> b_plane(pixels_per_frame * 2);
317 for (
size_t i = 0; i < pixels_per_frame; ++i) {
318 size_t src_idx = i * 6;
319 size_t dst_idx = i * 2;
320 r_plane[dst_idx] = pixel_data[src_idx];
321 r_plane[dst_idx + 1] = pixel_data[src_idx + 1];
322 g_plane[dst_idx] = pixel_data[src_idx + 2];
323 g_plane[dst_idx + 1] = pixel_data[src_idx + 3];
324 b_plane[dst_idx] = pixel_data[src_idx + 4];
325 b_plane[dst_idx + 1] = pixel_data[src_idx + 5];
329 segments[0].resize(pixels_per_frame);
330 segments[1].resize(pixels_per_frame);
331 segments[2].resize(pixels_per_frame);
332 segments[3].resize(pixels_per_frame);
333 segments[4].resize(pixels_per_frame);
334 segments[5].resize(pixels_per_frame);
337 segments[1].data(), pixels_per_frame);
339 segments[3].data(), pixels_per_frame);
341 segments[5].data(), pixels_per_frame);
344 for (
size_t i = 0; i < pixels_per_frame; ++i) {
347 segments[s * 2].push_back(pixel_data[idx + 1]);
348 segments[s * 2 + 1].push_back(pixel_data[idx]);
355 std::vector<std::vector<uint8_t>> encoded_segments(num_segments);
356 for (
int i = 0; i < num_segments; ++i) {
357 encode_rle_segment(segments[i], encoded_segments[i]);
361 std::vector<uint8_t> output;
365 for (
const auto& seg : encoded_segments) {
366 total_size += seg.size();
367 if (seg.size() % 2 != 0) {
371 output.reserve(total_size);
377 write_le32(output.data(),
static_cast<uint32_t
>(num_segments));
381 for (
int i = 0; i < num_segments; ++i) {
382 write_le32(output.data() + 4 + i * 4, current_offset);
383 current_offset +=
static_cast<uint32_t
>(encoded_segments[i].size());
384 if (encoded_segments[i].size() % 2 != 0) {
390 for (
const auto& seg : encoded_segments) {
391 output.insert(output.end(), seg.begin(), seg.end());
392 if (seg.size() % 2 != 0) {
398 return kcenon::pacs::ok<compression_result>(
compression_result{std::move(output), output_params});
402 std::span<const uint8_t> compressed_data,
405 const uint8_t* header = compressed_data.data();
408 uint32_t num_segments = read_le32(header);
411 "Invalid RLE segment count: " + std::to_string(num_segments));
414 int expected_segments = calculate_segment_count(params);
415 if (
static_cast<int>(num_segments) != expected_segments) {
417 "Segment count mismatch: expected " + std::to_string(expected_segments) +
418 ", got " + std::to_string(num_segments));
422 std::vector<uint32_t> offsets(num_segments);
423 for (uint32_t i = 0; i < num_segments; ++i) {
424 offsets[i] = read_le32(header + 4 + i * 4);
425 if (offsets[i] >= compressed_data.size()) {
427 "Invalid segment offset: " + std::to_string(offsets[i]));
432 std::vector<size_t> sizes(num_segments);
433 for (uint32_t i = 0; i < num_segments; ++i) {
434 if (i + 1 < num_segments) {
435 sizes[i] = offsets[i + 1] - offsets[i];
437 sizes[i] = compressed_data.size() - offsets[i];
441 size_t pixels_per_frame =
static_cast<size_t>(params.
width) * params.
height;
445 std::vector<std::vector<uint8_t>> decoded_segments(num_segments);
446 for (uint32_t i = 0; i < num_segments; ++i) {
447 std::span<const uint8_t> segment_data(
448 compressed_data.data() + offsets[i], sizes[i]);
449 auto segment_result = decode_rle_segment(segment_data, pixels_per_frame);
450 if (segment_result.is_err()) {
453 "Segment " + std::to_string(i) +
": " + segment_result.error().message);
455 decoded_segments[i] = std::move(segment_result.value());
457 if (decoded_segments[i].size() != pixels_per_frame) {
459 "Segment " + std::to_string(i) +
" decoded size mismatch: expected " +
460 std::to_string(pixels_per_frame) +
", got " +
461 std::to_string(decoded_segments[i].size()));
466 size_t output_size = pixels_per_frame * params.
samples_per_pixel * bytes_per_sample;
467 std::vector<uint8_t> output(output_size);
469 if (bytes_per_sample == 1) {
473 output = std::move(decoded_segments[0]);
477 decoded_segments[0].data(),
478 decoded_segments[1].data(),
479 decoded_segments[2].data(),
484 for (
size_t i = 0; i < pixels_per_frame; ++i) {
495 decoded_segments[0].data(),
496 decoded_segments[1].data(),
501 std::vector<uint8_t> r_plane(pixels_per_frame * 2);
502 std::vector<uint8_t> g_plane(pixels_per_frame * 2);
503 std::vector<uint8_t> b_plane(pixels_per_frame * 2);
507 decoded_segments[1].data(),
508 r_plane.data(), pixels_per_frame);
510 decoded_segments[3].data(),
511 g_plane.data(), pixels_per_frame);
513 decoded_segments[5].data(),
514 b_plane.data(), pixels_per_frame);
517 for (
size_t i = 0; i < pixels_per_frame; ++i) {
518 size_t src_idx = i * 2;
519 size_t dst_idx = i * 6;
520 output[dst_idx] = r_plane[src_idx];
521 output[dst_idx + 1] = r_plane[src_idx + 1];
522 output[dst_idx + 2] = g_plane[src_idx];
523 output[dst_idx + 3] = g_plane[src_idx + 1];
524 output[dst_idx + 4] = b_plane[src_idx];
525 output[dst_idx + 5] = b_plane[src_idx + 1];
529 for (
size_t i = 0; i < pixels_per_frame; ++i) {
532 output[idx] = decoded_segments[s * 2 + 1][i];
533 output[idx + 1] = decoded_segments[s * 2][i];
551 return kcenon::pacs::ok<compression_result>(
compression_result{std::move(output), output_params});
565std::string_view
rle_codec::transfer_syntax_uid() const noexcept {
566 return kTransferSyntaxUID;
570 return "RLE Lossless";
579 if (params.bits_allocated != 8 && params.bits_allocated != 16) {
583 if (params.samples_per_pixel < 1 || params.samples_per_pixel > 3) {
587 int bytes_per_sample = (params.bits_allocated + 7) / 8;
588 int num_segments = params.samples_per_pixel * bytes_per_sample;
589 if (num_segments > kMaxSegments) {
593 if (params.width == 0 || params.height == 0) {
602 if (params.bits_allocated != 0 &&
603 params.bits_allocated != 8 &&
604 params.bits_allocated != 16) {
607 if (params.samples_per_pixel != 0 &&
608 (params.samples_per_pixel < 1 || params.samples_per_pixel > 3)) {
615 std::span<const uint8_t> pixel_data,
622 std::span<const uint8_t> compressed_data,
PIMPL implementation for rle_codec.
codec_result decode_frame(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
codec_result decode(std::span< const uint8_t > compressed_data, const image_params ¶ms) const
bool valid_for_rle(const image_params ¶ms) const noexcept
codec_result encode_frame(std::span< const uint8_t > pixel_data, const image_params ¶ms) const
DICOM RLE Lossless codec implementation.
std::string_view name() const noexcept override
Returns a human-readable name for the codec.
bool can_encode(const image_params ¶ms) const noexcept override
Checks if this codec supports the given image parameters.
rle_codec()
Constructs an RLE codec instance.
codec_result decode(std::span< const uint8_t > compressed_data, const image_params ¶ms) const override
Decompresses RLE data.
static constexpr size_t kRLEHeaderSize
RLE header size (64 bytes: 16 x 4-byte offsets)
codec_result encode(std::span< const uint8_t > pixel_data, const image_params ¶ms, const compression_options &options={}) const override
Compresses pixel data to RLE format.
std::unique_ptr< impl > impl_
bool can_decode(const image_params ¶ms) const noexcept override
Checks if this codec can decode data with given parameters.
static constexpr int kMaxSegments
Maximum number of RLE segments allowed by DICOM specification.
bool is_lossy() const noexcept override
Checks if this codec produces lossy compression.
void merge_planes_to_16bit(const uint8_t *high, const uint8_t *low, uint8_t *dst, size_t pixel_count) noexcept
Merge high and low byte planes into 16-bit data.
void interleaved_to_planar_rgb8(const uint8_t *src, uint8_t *r, uint8_t *g, uint8_t *b, size_t pixel_count) noexcept
Convert interleaved RGB to planar format using best available SIMD.
void planar_to_interleaved_rgb8(const uint8_t *r, const uint8_t *g, const uint8_t *b, uint8_t *dst, size_t pixel_count) noexcept
Convert planar RGB to interleaved format using best available SIMD.
void split_16bit_to_planes(const uint8_t *src, uint8_t *high, uint8_t *low, size_t pixel_count) noexcept
Split 16-bit data into high and low byte planes.
constexpr int decompression_error
@ control
Internal pipeline control messages.
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.
SIMD optimizations for RLE codec operations.
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.