.. _program_listing_file_include_util_function_image_WebpWriter.hpp: Program Listing for File WebpWriter.hpp ======================================= |exhale_lsh| :ref:`Return to documentation for file ` (``include\util\function\image\WebpWriter.hpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp #pragma once #include "util/common/Result.hpp" #include "util/function/FunctionContext.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace PDJE_UTIL::function::image { enum class RasterPixelFormat { gray8, gray_alpha8, rgb8, rgba8 }; struct RasterImageView { std::span pixels = {}; std::size_t width = 0; std::size_t height = 0; std::size_t stride = 0; RasterPixelFormat pixel_format = RasterPixelFormat::rgba8; }; struct EncodeWebpArgs { RasterImageView image; int compression_level = -1; }; struct WriteWebpArgs { RasterImageView image; std::filesystem::path output_path; int compression_level = -1; }; namespace detail { struct ImageLayout { std::size_t row_bytes = 0; std::size_t effective_stride = 0; }; inline bool checked_multiply(std::size_t lhs, std::size_t rhs, std::size_t &result) noexcept { if (lhs != 0 && rhs > (std::numeric_limits::max() / lhs)) { return false; } result = lhs * rhs; return true; } inline bool checked_add(std::size_t lhs, std::size_t rhs, std::size_t &result) noexcept { if (rhs > (std::numeric_limits::max() - lhs)) { return false; } result = lhs + rhs; return true; } inline constexpr std::size_t bytes_per_pixel(RasterPixelFormat pixel_format) noexcept { switch (pixel_format) { case RasterPixelFormat::gray8: return 1; case RasterPixelFormat::gray_alpha8: return 2; case RasterPixelFormat::rgb8: return 3; case RasterPixelFormat::rgba8: return 4; } return 0; } inline common::Result validate_image(const RasterImageView &image) { if (image.pixels.data() == nullptr) { return common::Result::failure( { common::StatusCode::invalid_argument, "RasterImageView.pixels must reference valid image data." }); } if (image.width == 0 || image.height == 0) { return common::Result::failure( { common::StatusCode::invalid_argument, "RasterImageView width and height must be greater than zero." }); } if (image.width > 16383 || image.height > 16383) { return common::Result::failure( { common::StatusCode::invalid_argument, "RasterImageView dimensions must fit within WebP limits." }); } ImageLayout layout; if (!checked_multiply(image.width, bytes_per_pixel(image.pixel_format), layout.row_bytes)) { return common::Result::failure( { common::StatusCode::invalid_argument, "RasterImageView row size overflows size_t." }); } layout.effective_stride = image.stride == 0 ? layout.row_bytes : image.stride; if (layout.effective_stride < layout.row_bytes) { return common::Result::failure( { common::StatusCode::invalid_argument, "RasterImageView.stride must be zero or greater than or equal to " "the packed row size." }); } std::size_t required_bytes = layout.row_bytes; if (image.height > 1) { std::size_t tail_bytes = 0; if (!checked_multiply( layout.effective_stride, image.height - 1, tail_bytes) || !checked_add(tail_bytes, layout.row_bytes, required_bytes)) { return common::Result::failure( { common::StatusCode::invalid_argument, "RasterImageView buffer size calculation overflows " "size_t." }); } } if (image.pixels.size() < required_bytes) { return common::Result::failure( { common::StatusCode::invalid_argument, "RasterImageView.pixels is smaller than the specified width, " "height, and stride require." }); } return common::Result::success(layout); } inline common::Result> pack_rgba(const RasterImageView &image, const ImageLayout &layout) { std::size_t packed_size = 0; if (!checked_multiply( image.width * image.height, std::size_t{ 4 }, packed_size)) { return common::Result>::failure( { common::StatusCode::invalid_argument, "RasterImageView packed RGBA buffer size overflows size_t." }); } std::vector rgba(packed_size, 0); for (std::size_t y = 0; y < image.height; ++y) { const auto *src_row = image.pixels.data() + (y * layout.effective_stride); auto *dst_row = rgba.data() + ((y * image.width) * 4); for (std::size_t x = 0; x < image.width; ++x) { const auto *src = src_row + (x * bytes_per_pixel(image.pixel_format)); auto *dst = dst_row + (x * 4); switch (image.pixel_format) { case RasterPixelFormat::gray8: dst[0] = src[0]; dst[1] = src[0]; dst[2] = src[0]; dst[3] = 255; break; case RasterPixelFormat::gray_alpha8: dst[0] = src[0]; dst[1] = src[0]; dst[2] = src[0]; dst[3] = src[1]; break; case RasterPixelFormat::rgb8: dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = 255; break; case RasterPixelFormat::rgba8: dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; break; } } } return common::Result>::success(std::move(rgba)); } } // namespace detail inline common::Result> encode_webp(const EncodeWebpArgs &args, function::EvalOptions options = {}) { (void)options; if (args.compression_level < -1 || args.compression_level > 9) { return common::Result>::failure( { common::StatusCode::invalid_argument, "EncodeWebpArgs.compression_level must be between -1 and 9." }); } auto layout = detail::validate_image(args.image); if (!layout.ok()) { return common::Result>::failure( layout.status()); } auto packed_rgba = detail::pack_rgba(args.image, layout.value()); if (!packed_rgba.ok()) { return common::Result>::failure( packed_rgba.status()); } std::uint8_t *encoded_bytes = nullptr; const auto encoded_size = WebPEncodeLosslessRGBA(packed_rgba.value().data(), static_cast(args.image.width), static_cast(args.image.height), static_cast(args.image.width * 4), &encoded_bytes); if (encoded_size == 0 || encoded_bytes == nullptr) { return common::Result>::failure( { common::StatusCode::internal_error, "WebPEncodeLosslessRGBA() failed while encoding the image." }); } std::vector bytes(encoded_size); std::memcpy(bytes.data(), encoded_bytes, encoded_size); WebPFree(encoded_bytes); return common::Result>::success(std::move(bytes)); } inline common::Result write_webp(const WriteWebpArgs &args, function::EvalOptions options = {}) { (void)options; if (args.output_path.empty()) { return common::Result::failure( { common::StatusCode::invalid_argument, "WriteWebpArgs.output_path must not be empty." }); } auto encoded = encode_webp( { .image = args.image, .compression_level = args.compression_level }); if (!encoded.ok()) { return common::Result::failure(encoded.status()); } std::ofstream output(args.output_path, std::ios::binary | std::ios::trunc); if (!output.is_open()) { return common::Result::failure( { common::StatusCode::io_error, "Failed to open the WebP output path for writing." }); } const auto &bytes = encoded.value(); output.write(reinterpret_cast(bytes.data()), static_cast(bytes.size())); if (!output.good()) { return common::Result::failure( { common::StatusCode::io_error, "Failed while writing WebP bytes to the output path." }); } return common::Result::success(); } } // namespace PDJE_UTIL::function::image