// Copyright (c) Team CharLS. // SPDX-License-Identifier: BSD-3-Clause #include "pch.h" #include "bmp_image.h" #include #include #include #include #include namespace { constexpr size_t bytes_per_rgb_pixel{3}; void convert_bgr_to_rgb(std::vector& triplet_buffer, const size_t width, const size_t height, const size_t stride) noexcept { for (size_t line{}; line != height; ++line) { const auto line_start{line * stride}; for (size_t pixel{}; pixel != width; ++pixel) { const auto column{pixel * bytes_per_rgb_pixel}; std::swap(triplet_buffer[line_start + column], triplet_buffer[line_start + column + 2]); } } } std::vector triplet_to_planar(const std::vector& buffer, const size_t width, const size_t height, const size_t stride) { std::vector result(bytes_per_rgb_pixel * width * height); const size_t byte_count_plane{width * height}; size_t plane_column{}; for (size_t line{}; line != height; ++line) { const auto line_start{line * stride}; for (size_t pixel{}; pixel != width; ++pixel) { const auto column{line_start + pixel * bytes_per_rgb_pixel}; result[plane_column] = buffer[column]; result[plane_column + 1 * byte_count_plane] = buffer[column + 1]; result[plane_column + 2 * byte_count_plane] = buffer[column + 2]; ++plane_column; } } return result; } void convert_bottom_up_to_top_down(uint8_t* triplet_buffer, const size_t width, const size_t height, const size_t stride) { const size_t row_length{width * bytes_per_rgb_pixel}; std::vector temp_row(row_length); for (size_t i{}; i != height / 2; ++i) { memcpy(temp_row.data(), &triplet_buffer[i * stride], row_length); const size_t bottom_row{height - i - 1}; memcpy(&triplet_buffer[i * stride], &triplet_buffer[bottom_row * stride], row_length); memcpy(&triplet_buffer[bottom_row * stride], temp_row.data(), row_length); } } std::vector encode_bmp_image_to_jpegls(const bmp_image& image, const charls::interleave_mode interleave_mode, const int near_lossless) { assert(image.dib_header.depth == 24); // This function only supports 24-bit BMP pixel data. assert(image.dib_header.compress_type == 0); // Data needs to be stored by pixel as RGB. charls::jpegls_encoder encoder; encoder.frame_info({image.dib_header.width, static_cast(image.dib_header.height), 8, bytes_per_rgb_pixel}) .interleave_mode(interleave_mode) .near_lossless(near_lossless); std::vector buffer(encoder.estimated_destination_size()); encoder.destination(buffer); // The resolution in BMP files is often 0 to indicate that no resolution has been defined. // The SPIFF header specification requires however that VRES and HRES are never 0. // The ISO 10918-3 recommendation for these cases is to define that the pixels should be interpreted as a square. if (image.dib_header.vertical_resolution < 100 || image.dib_header.horizontal_resolution < 100) { encoder.write_standard_spiff_header(charls::spiff_color_space::rgb, charls::spiff_resolution_units::aspect_ratio, 1, 1); } else { encoder.write_standard_spiff_header( charls::spiff_color_space::rgb, charls::spiff_resolution_units::dots_per_centimeter, image.dib_header.vertical_resolution / 100, image.dib_header.horizontal_resolution / 100); } size_t encoded_size; if (interleave_mode == charls::interleave_mode::none) { const auto planar_pixel_data{triplet_to_planar(image.pixel_data, image.dib_header.width, static_cast(image.dib_header.height), image.stride)}; encoded_size = encoder.encode(planar_pixel_data); } else { encoded_size = encoder.encode(image.pixel_data, image.stride); } buffer.resize(encoded_size); return buffer; } void save_buffer_to_file(const void* buffer, const size_t buffer_size, const char* filename) { assert(filename); assert(buffer); assert(buffer_size); std::ofstream output(filename, std::ios::binary); output.exceptions(std::ios::failbit | std::ios::badbit); output.write(static_cast(buffer), static_cast(buffer_size)); output.close(); // close explicitly to get feedback on failures. } void log_failure(const char* message) noexcept { try { std::cerr << message << "\n"; } catch (...) { // Catch and ignore all exceptions,to ensure a noexcept log function (but warn in debug builds) assert(false); } } struct options final { const char* input_filename; const char* output_filename; charls::interleave_mode interleave_mode; int near_lossless; options(const int argc, char** argv) { if (argc < 3) { throw std::runtime_error("Usage: [interleave-mode (none, line, or sample), " "default = none] [near-lossless, default = 0 (lossless)]\n"); } input_filename = argv[1]; output_filename = argv[2]; if (argc > 3) { interleave_mode = string_to_interleave_mode(argv[3]); } else { interleave_mode = charls::interleave_mode::none; } if (argc > 4) { near_lossless = static_cast(strtol(argv[4], nullptr, 10)); if (near_lossless < 0 || near_lossless > 255) throw std::runtime_error("Argument near_lossless needs to be in the range [0,255]\n"); } else { near_lossless = 0; } } private: static charls::interleave_mode string_to_interleave_mode(const char* argument) { if (strcmp(argument, "none") == 0) return charls::interleave_mode::none; if (strcmp(argument, "line") == 0) return charls::interleave_mode::line; if (strcmp(argument, "sample") == 0) return charls::interleave_mode::sample; throw std::runtime_error("Argument interleave-mode needs to be: none, line or sample\n"); } }; } // namespace int main(const int argc, char** argv) { try { std::ios::sync_with_stdio(false); const options options{argc, argv}; bmp_image bmp_image{options.input_filename}; // Pixels in the BMP file format are stored bottom up (when the height parameter is positive), JPEG-LS requires top // down. if (bmp_image.dib_header.height > 0) { convert_bottom_up_to_top_down(bmp_image.pixel_data.data(), bmp_image.dib_header.width, static_cast(bmp_image.dib_header.height), bmp_image.stride); } else { bmp_image.dib_header.height = std::abs(bmp_image.dib_header.height); } // Pixels in the BMP file format are stored as BGR. JPEG-LS (SPIFF header) only supports the RGB color model. // Note: without the optional SPIFF header no color information is stored in the JPEG-LS file and the common // assumption is RGB. convert_bgr_to_rgb(bmp_image.pixel_data, bmp_image.dib_header.width, static_cast(bmp_image.dib_header.height), bmp_image.stride); const auto encoded_buffer{encode_bmp_image_to_jpegls(bmp_image, options.interleave_mode, options.near_lossless)}; save_buffer_to_file(encoded_buffer.data(), encoded_buffer.size(), options.output_filename); return EXIT_SUCCESS; } catch (const std::exception& error) { log_failure(error.what()); } catch (...) { log_failure("Unknown error occurred"); assert(false); } return EXIT_FAILURE; }