#include "image_processor.hh" #include "colour.hh" #include "config.h" #include "image.hh" #include "image_loader.hh" #include "io.hh" #include "size.hh" #include "spawner.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_JPEG || HAVE_PNG # include # include # include # include #endif #if HAVE_JPEG # include #endif #if HAVE_PNG # include #endif #if HAVE_RSVG # include # include # include # include # include #endif #if HAVE_XPM # include extern "C" { # include "xpm/include/dix.h" } #endif namespace { struct Request { bool head; Image::Format format; uint32_t max_width; uint32_t max_height; uint32_t background; size_t path_len; }; struct Response { uint32_t width; uint32_t height; uint8_t error; size_t scanline; }; struct Result { Response response; Image::Format format; std::unique_ptr pixels; }; std::expected write_request( io::Writer& writer, std::filesystem::path const& path, bool head, Image::Format format, uint32_t max_width, uint32_t max_height, Colour background) { auto path_str = path.native(); Request request{.head = head, .format = format, .max_width = max_width, .max_height = max_height, .background = background.argb, .path_len = path_str.size()}; auto ret = writer.repeat_write(&request, sizeof(request)); if (!ret.has_value() || ret.value() != sizeof(request)) { return std::unexpected(ImageLoadError::kProcessError); } ret = writer.repeat_write(path_str.data(), path_str.size()); if (!ret.has_value() || ret.value() != path_str.size()) { return std::unexpected(ImageLoadError::kProcessError); } return {}; } std::expected read_response(io::Reader& reader) { Response response; auto ret = reader.repeat_read(&response, sizeof(response)); if (!ret.has_value() || ret.value() != sizeof(response)) { return std::unexpected(ImageLoadError::kProcessError); } if (response.width == 0) { return std::unexpected(static_cast(response.error)); } return response; } Size rescale(Size const& size, uint32_t max_width, uint32_t max_height) { if (max_width > 0 && size.width > max_height) { if (max_height > 0 && size.height > max_height) { auto sx = static_cast(size.width) / static_cast(max_width); auto sy = static_cast(size.height) / static_cast(max_height); if (sx >= sy) { return Size{max_width, (size.height * max_width) / size.width}; } return Size{(size.width * max_height) / size.height, max_height}; } return Size{max_width, (size.height * max_width) / size.width}; } if (max_height > 0 && size.height > max_height) { return Size{(size.width * max_height) / size.height, max_height}; } return size; } void swap_four_bytes(std::span pixels, uint32_t width, uint32_t height, size_t scanline) { assert(height * scanline <= pixels.size()); auto* row = pixels.data(); while (height--) { auto* const pixel = reinterpret_cast(row); for (uint32_t x = 0; x < width; ++x) { pixel[x] = std::byteswap(pixel[x]); } row += scanline; } } void swap_two_bytes(std::span pixels, uint32_t width, uint32_t height, size_t scanline, size_t offset) { assert(offset < 4); assert(height * scanline <= pixels.size()); auto* row = pixels.data(); while (height--) { auto* pixel = row + offset; for (uint32_t x = 0; x < width; ++x, pixel += 4) { std::swap(pixel[0], pixel[2]); } row += scanline; } } void shift_left_bytes(std::span pixels, uint32_t width, uint32_t height, size_t scanline) { assert(height * scanline <= pixels.size()); auto* row = pixels.data(); while (height--) { auto* const pixel = reinterpret_cast(row); for (uint32_t x = 0; x < width; ++x) { pixel[x] = std::rotl(pixel[x], 8); } row += scanline; } } void shift_right_bytes(std::span pixels, uint32_t width, uint32_t height, size_t scanline) { assert(height * scanline <= pixels.size()); auto* row = pixels.data(); while (height--) { auto* const pixel = reinterpret_cast(row); for (uint32_t x = 0; x < width; ++x) { pixel[x] = std::rotr(pixel[x], 8); } row += scanline; } } void set_alpha_bytes(std::span pixels, uint32_t width, uint32_t height, size_t scanline, size_t offset) { assert(height * scanline <= pixels.size()); assert(offset < 4); auto* row = pixels.data(); while (height--) { auto* pixel = row + offset; for (uint32_t x = 0; x < width; ++x, pixel += 4) { *pixel = 0xff; } row += scanline; } } void insert_alpha_bytes(std::span pixels, uint32_t width, uint32_t height, size_t scanline, size_t offset) { assert(static_cast(width) * 4 <= scanline); assert(height * scanline <= pixels.size()); if (offset == 0) { auto* row = pixels.data(); while (height--) { auto* read_pixel = row + static_cast((width - 1) * 3); auto* write_pixel = row + static_cast((width - 1) * 4); for (uint32_t x = 0; x < width; ++x, read_pixel -= 3, write_pixel -= 4) { write_pixel[0] = 0xff; std::copy_n(read_pixel, 3, write_pixel + 1); } row += scanline; } } else { assert(offset == 3); auto* row = pixels.data(); while (height--) { auto* read_pixel = row + static_cast((width - 1) * 3); auto* write_pixel = row + static_cast((width - 1) * 4); for (uint32_t x = 0; x < width; ++x, read_pixel -= 3, write_pixel -= 4) { std::copy_n(read_pixel, 3, write_pixel); write_pixel[3] = 0xff; } row += scanline; } } } void rearrange_bytes(std::span pixels, uint32_t width, uint32_t height, size_t scanline, Image::Format source, Image::Format target) { switch (source) { case Image::Format::RGBA_8888: switch (target) { case Image::Format::RGBA_8888: return; case Image::Format::ARGB_8888: shift_right_bytes(pixels, width, height, scanline); return; case Image::Format::BGRA_8888: swap_two_bytes(pixels, width, height, scanline, 0); return; case Image::Format::ABGR_8888: swap_four_bytes(pixels, width, height, scanline); return; } break; case Image::Format::ARGB_8888: switch (target) { case Image::Format::RGBA_8888: shift_left_bytes(pixels, width, height, scanline); return; case Image::Format::ARGB_8888: return; case Image::Format::BGRA_8888: swap_four_bytes(pixels, width, height, scanline); return; case Image::Format::ABGR_8888: swap_two_bytes(pixels, width, height, scanline, 1); return; } break; case Image::Format::BGRA_8888: switch (target) { case Image::Format::RGBA_8888: swap_two_bytes(pixels, width, height, scanline, 0); return; case Image::Format::ARGB_8888: swap_four_bytes(pixels, width, height, scanline); return; case Image::Format::BGRA_8888: return; case Image::Format::ABGR_8888: shift_left_bytes(pixels, width, height, scanline); return; } break; case Image::Format::ABGR_8888: switch (target) { case Image::Format::RGBA_8888: swap_four_bytes(pixels, width, height, scanline); return; case Image::Format::ARGB_8888: swap_two_bytes(pixels, width, height, scanline, 1); return; case Image::Format::BGRA_8888: shift_right_bytes(pixels, width, height, scanline); return; case Image::Format::ABGR_8888: return; } break; } } #if HAVE_JPEG struct jpeg_extended_error_mgr { struct jpeg_error_mgr err; jmp_buf jmpbuf; }; void jpeg_error_exit(j_common_ptr info) { auto* err = reinterpret_cast(info->err); jpeg_destroy(info); longjmp(err->jmpbuf, 1); } void jpeg_error_output_nothing(j_common_ptr /* info */) { // be silent, do nothing } # if BITS_IN_JSAMPLE != 8 # error Unsupported libjpeg setup # endif std::expected load_jpeg( std::filesystem::path const& path, Request const& request) { jpeg_extended_error_mgr err; FILE* fh = nullptr; if (setjmp(err.jmpbuf)) { // This is better than exit() which is the default behavior, // but almost guaranteed to leak some memory here. if (fh) fclose(fh); return std::unexpected(ImageLoadError::kError); } fh = fopen(path.c_str(), "rb"); if (fh == nullptr) { if (errno == ENOENT) { return std::unexpected(ImageLoadError::kNoSuchFile); } return std::unexpected(ImageLoadError::kError); } jpeg_decompress_struct info; info.err = jpeg_std_error(&err.err); info.err->error_exit = jpeg_error_exit; info.err->output_message = jpeg_error_output_nothing; jpeg_create_decompress(&info); jpeg_stdio_src(&info, fh); if (jpeg_read_header(&info, TRUE) != JPEG_HEADER_OK) { jpeg_destroy_decompress(&info); fclose(fh); return std::unexpected(ImageLoadError::kError); } if (request.head) { Response resp{.width = info.image_width, .height = info.image_height, .error = 0, .scanline = 0}; jpeg_destroy_decompress(&info); fclose(fh); return Result{.response=resp, .format=Image::Format::RGBA_8888, .pixels=nullptr}; } enum class Conversion : uint8_t { kNone, kSetAlphaFirst, kSetAlphaLast, kAddAlphaFirst, kAddAlphaLast, } conversion = Conversion::kNone; Image::Format output_format = request.format; # if JCS_EXTENSIONS # if JCS_ALPHA_EXTENSIONS switch (request.format) { case Image::Format::RGBA_8888: info.out_color_space = JCS_EXT_RGBA; break; case Image::Format::BGRA_8888: info.out_color_space = JCS_EXT_BGRA; break; case Image::Format::ABGR_8888: info.out_color_space = JCS_EXT_ABGR; break; case Image::Format::ARGB_8888: info.out_color_space = JCS_EXT_ARGB; break; } # else switch (request.format) { case Image::Format::RGBA_8888: info.out_color_space = JCS_EXT_RGBX; conversion = Conversion::kSetAlphaLast; break; case Image::Format::BGRA_8888: info.out_color_space = JCS_EXT_BGRX; conversion = Conversion::kSetAlphaLast; break; case Image::Format::ABGR_8888: info.out_color_space = JCS_EXT_XBGR; conversion = Conversion::kSetAlphaFirst; break; case Image::Format::ARGB_8888: info.out_color_space = JCS_EXT_XRGB; conversion = Conversion::kSetAlphaFirst; break; } # endif # else # if RGB_RED != 0 || RGB_GREEN != 1 || RGB_BLUE != 2 || RGB_PIXELSIZE != 3 # error Unsupported libjpeg setup # endif info.out_color_space = JCS_RGB; switch (request.format) { case Image::Format::RGBA_8888: case Image::Format::BGRA_8888: format = Image::Format::RGBA_8888; conversion = Conversion::kAddAlphaLast; break; case Image::Format::ABGR_8888: case Image::Format::ARGB_8888: format = Image::Format::ARGB_8888; conversion = Conversion::kAddAlphaFirst; break; } # endif if (request.max_width || request.max_height) { auto new_size = rescale(Size{info.image_width, info.image_height}, request.max_width, request.max_height); unsigned int denom = 2; while (info.image_width / denom >= new_size.width && info.image_height / denom >= new_size.height) { info.scale_denom = denom; denom *= 2; } } jpeg_start_decompress(&info); size_t scanline = static_cast(info.output_width) * 4; auto pixels = std::make_unique(scanline * info.output_height); auto buffer = std::make_unique(info.rec_outbuf_height); while (info.output_scanline < info.output_height) { int buf_height = 1; buffer[0] = reinterpret_cast(pixels.get() + (info.output_scanline * scanline)); while (buf_height < info.rec_outbuf_height && info.output_scanline + buf_height < info.output_height) { buffer[buf_height] = reinterpret_cast( pixels.get() + ((info.output_scanline + buf_height) * scanline)); ++buf_height; } jpeg_read_scanlines(&info, buffer.get(), buf_height); } switch (conversion) { case Conversion::kNone: break; case Conversion::kAddAlphaFirst: insert_alpha_bytes(std::span{pixels.get(), scanline * info.output_height}, info.output_width, info.output_height, scanline, 0); break; case Conversion::kAddAlphaLast: insert_alpha_bytes(std::span{pixels.get(), scanline * info.output_height}, info.output_width, info.output_height, scanline, 3); break; case Conversion::kSetAlphaFirst: set_alpha_bytes(std::span{pixels.get(), scanline * info.output_height}, info.output_width, info.output_height, scanline, 0); break; case Conversion::kSetAlphaLast: set_alpha_bytes(std::span{pixels.get(), scanline * info.output_height}, info.output_width, info.output_height, scanline, 3); break; } Response resp{.width = info.output_width, .height = info.output_height, .error = 0, .scanline = scanline}; jpeg_destroy_decompress(&info); fclose(fh); return Result{.response = resp, .format = output_format, .pixels = std::move(pixels)}; } #endif #if HAVE_PNG // NOLINTBEGIN(misc-include-cleaner) void png_error(png_structp png_ptr, png_const_charp /* message */) { // Don't write anything longjmp(png_jmpbuf(png_ptr), 1); } void png_warning(png_structp /* png_ptr */, png_const_charp /* message */) { // Do nothing } std::expected load_png( std::filesystem::path const& path, Request const& request) { FILE* fh = fopen(path.c_str(), "rb"); if (fh == nullptr) { if (errno == ENOENT) { return std::unexpected(ImageLoadError::kNoSuchFile); } return std::unexpected(ImageLoadError::kError); } uint8_t header[8]; if (fread(header, 1, sizeof(header), fh) != sizeof(header)) { fclose(fh); return std::unexpected(ImageLoadError::kError); } if (png_sig_cmp(header, 0, sizeof(header))) { fclose(fh); return std::unexpected(ImageLoadError::kError); } png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, png_error, png_warning); if (!png_ptr) { fclose(fh); return std::unexpected(ImageLoadError::kError); } png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, nullptr, nullptr); fclose(fh); return std::unexpected(ImageLoadError::kError); } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); fclose(fh); return std::unexpected(ImageLoadError::kError); } png_init_io(png_ptr, fh); png_set_sig_bytes(png_ptr, sizeof(header)); png_set_alpha_mode(png_ptr, PNG_ALPHA_PNG, PNG_DEFAULT_sRGB); png_read_info(png_ptr, info_ptr); png_uint_32 width; png_uint_32 height; int bit_depth; int color_type; png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr, nullptr); if (request.head) { Response resp{.width =width, .height = height, .error = 0, .scanline = 0}; png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); fclose(fh); return Result{.response = resp, .format = Image::Format::RGBA_8888, .pixels = nullptr}; } if (request.background != 0) { png_color_16 background_color; Colour colour{request.background}; background_color.red = colour.red() << 8 | colour.red(); background_color.green = colour.green() << 8 | colour.green(); background_color.blue = colour.blue() << 8 | colour.blue(); png_set_background(png_ptr, &background_color, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1); } else { png_color_16p background_color; if (png_get_bKGD(png_ptr, info_ptr, &background_color)) png_set_background(png_ptr, background_color, PNG_BACKGROUND_GAMMA_FILE, 1, 1); } if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png_ptr); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png_ptr); if (bit_depth == 16) png_set_scale_16(png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png_ptr); switch (request.format) { case Image::Format::RGBA_8888: png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); break; case Image::Format::ARGB_8888: png_set_swap_alpha(png_ptr); png_set_filler(png_ptr, 0xff, PNG_FILLER_BEFORE); break; case Image::Format::BGRA_8888: png_set_bgr(png_ptr); png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); break; case Image::Format::ABGR_8888: png_set_bgr(png_ptr); png_set_swap_alpha(png_ptr); png_set_filler(png_ptr, 0xff, PNG_FILLER_BEFORE); break; } png_read_update_info(png_ptr, info_ptr); width = png_get_image_width(png_ptr, info_ptr); height = png_get_image_height(png_ptr, info_ptr); bit_depth = png_get_bit_depth(png_ptr, info_ptr); color_type = png_get_color_type(png_ptr, info_ptr); size_t scanline = png_get_rowbytes(png_ptr, info_ptr); /* Guard against integer overflow */ if (height > PNG_SIZE_MAX / scanline) png_error(png_ptr, "image_data buffer would be too large"); auto pixels = std::make_unique(scanline * height); auto rows = std::make_unique(height); for (png_uint_32 y = 0; y < height; ++y) rows[y] = pixels.get() + (y * scanline); png_read_image(png_ptr, rows.get()); png_read_end(png_ptr, nullptr); png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); fclose(fh); Response resp{.width = width, .height = height, .error = 0, .scanline = scanline}; return Result{.response = resp, .format = request.format, .pixels = std::move(pixels)}; } // NOLINTEND(misc-include-cleaner) #endif #if HAVE_RSVG std::expected load_svg( std::filesystem::path const& path, Request const& request) { GFile* file = g_file_new_for_path(path.c_str()); if (!file) { return std::unexpected(ImageLoadError::kNoSuchFile); } RsvgHandle* handle = rsvg_handle_new_from_gfile_sync(file, RSVG_HANDLE_FLAGS_NONE, nullptr, nullptr); if (!handle) { g_object_unref(file); return std::unexpected(ImageLoadError::kError); } g_object_unref(file); // TODO: Get this as part of the request? rsvg_handle_set_dpi(handle, 96.0); Size size; { gdouble width_double; // NOLINT(misc-include-cleaner) gdouble height_double; // NOLINT(misc-include-cleaner) if (!rsvg_handle_get_intrinsic_size_in_pixels(handle, &width_double, &height_double)) { g_object_unref(handle); return std::unexpected(ImageLoadError::kError); } size.width = static_cast(ceil(width_double)); size.height = static_cast(ceil(height_double)); } if (request.head) { Response resp{.width = size.width, .height = size.height, .error = 0, .scanline = 0}; g_object_unref(handle); return Result{.response = resp, .format = Image::Format::RGBA_8888, .pixels = nullptr}; } if (request.max_width || request.max_height) { size = rescale(size, request.max_width, request.max_height); } size_t scanline = static_cast(size.width) * 4; auto pixels = std::make_unique(scanline * size.height); cairo_surface_t* surface = cairo_image_surface_create_for_data( reinterpret_cast(pixels.get()), CAIRO_FORMAT_ARGB32, static_cast(size.width), static_cast(size.height), static_cast(scanline)); if (!surface) { g_object_unref(handle); return std::unexpected(ImageLoadError::kError); } cairo_t* cr = cairo_create(surface); if (request.background) { cairo_rectangle(cr, 0, 0, size.width, size.height); Colour colour{request.background}; cairo_set_source_rgba(cr, colour.red() / 255.0, colour.green() / 255.0, colour.blue() / 255.0, colour.alpha() / 255.0); cairo_fill(cr); } RsvgRectangle viewport = { .x = 0.0, .y = 0.0, .width = static_cast(size.width), .height = static_cast(size.height), }; if (!rsvg_handle_render_document(handle, cr, &viewport, nullptr)) { cairo_destroy(cr); cairo_surface_destroy(surface); g_object_unref(handle); return std::unexpected(ImageLoadError::kError); } cairo_destroy(cr); cairo_surface_destroy(surface); g_object_unref(handle); // Cairo uses premultiplied pixels, we don't { uint8_t* row = pixels.get(); for (uint32_t y = 0; y < size.height; ++y) { uint8_t* pixel = row; for (uint32_t x = 0; x < size.width; ++x, pixel += 4) { if constexpr (std::endian::native == std::endian::big) { if (pixel[0] != 0xff && pixel[0] > 0) { pixel[1] = (static_cast(pixel[1]) * 255) / pixel[0]; pixel[2] = (static_cast(pixel[2]) * 255) / pixel[0]; pixel[3] = (static_cast(pixel[3]) * 255) / pixel[0]; } } else { if (pixel[3] != 0xff && pixel[3] > 0) { pixel[0] = (static_cast(pixel[0]) * 255) / pixel[3]; pixel[1] = (static_cast(pixel[1]) * 255) / pixel[3]; pixel[2] = (static_cast(pixel[2]) * 255) / pixel[3]; } } } row += scanline; } } return Result{ .response = Response{.width = size.width, .height = size.height, .error = 0, .scanline = scanline}, .format = std::endian::native == std::endian::big ? Image::Format::ARGB_8888 : Image::Format::BGRA_8888, .pixels = std::move(pixels) }; } #endif #if HAVE_XPM std::optional xpm_parse_color(XpmColor const& color) { if (color.c_color) { auto const len = strlen(color.c_color); if (color.c_color[0] == '#' && len == 7) { uint32_t rgb; if (std::from_chars(color.c_color + 1, color.c_color + 7, rgb, 16).ec == std::errc()) { return 0xff000000 | rgb; } } if (len == 4 && (strcmp(color.c_color, "None") == 0 || strcmp(color.c_color, "none") == 0)) return 0; unsigned short red; unsigned short green; unsigned short blue; if (dixLookupBuiltinColor(0, color.c_color, len, &red, &green, &blue)) { return 0xff000000 | (static_cast(red & 0xff) << 16) | (green & 0xff00) | (blue & 0xff); } } if (color.m_color) { if (strcmp(color.m_color, "white") == 0) { return 0xffffffff; } if (strcmp(color.m_color, "black") == 0) { return 0xff000000; } } return std::nullopt; } std::expected load_xpm( std::filesystem::path const& path, Request const& request) { XpmImage image; XpmInfo info; auto ret = XpmReadFileToXpmImage(path.c_str(), &image, &info); if (ret != XpmSuccess) { if (ret == XpmOpenFailed) return std::unexpected(ImageLoadError::kNoSuchFile); return std::unexpected(ImageLoadError::kError); } if (request.head) { Response resp{.width = image.width, .height = image.height, .error = 0, .scanline = 0}; XpmFreeXpmImage(&image); XpmFreeXpmInfo(&info); return Result{.response = resp, .format = Image::Format::RGBA_8888, .pixels = nullptr}; } size_t scanline = static_cast(image.width) * 4; auto pixels = std::make_unique(scanline * image.height); auto colors = std::make_unique(image.ncolors); for (unsigned int i = 0; i < image.ncolors; ++i) { auto ret = xpm_parse_color(image.colorTable[i]); if (ret.has_value()) { colors[i] = ret.value(); } else { return std::unexpected(ImageLoadError::kUnsupportedFormat); } } auto* out_row = pixels.get(); auto* in = image.data; for (unsigned int y = 0; y < image.height; ++y) { auto* out_pixel = reinterpret_cast(out_row); for (unsigned int x = 0; x < image.width; ++x) { out_pixel[x] = colors[*in++]; } out_row += scanline; } XpmFreeXpmImage(&image); XpmFreeXpmInfo(&info); return Result{ .response = Response{.width = image.width, .height = image.height, .error = 0, .scanline = scanline}, .format = std::endian::native == std::endian::big ? Image::Format::ARGB_8888 : Image::Format::BGRA_8888, .pixels = std::move(pixels), }; } #endif } // namespace namespace image_processor { std::expected peek(Process& process, std::filesystem::path const& path) { auto ret = write_request(process.writer(), path, true, Image::Format::ARGB_8888, 0, 0, {}); if (!ret) { return std::unexpected(ret.error()); } auto ret2 = read_response(process.reader()); if (!ret2) return std::unexpected(ret2.error()); return Size{ret2->width, ret2->height}; } std::expected, ImageLoadError> load( Process& process, std::filesystem::path const& path, Image::Format format, uint32_t max_width, uint32_t max_height, std::optional background) { auto ret = write_request(process.writer(), path, false, format, max_width, max_height, background.value_or({})); if (!ret) { return std::unexpected(ret.error()); } auto ret2 = read_response(process.reader()); if (!ret2) return std::unexpected(ret2.error()); size_t size = static_cast(ret2->height) * ret2->scanline; auto pixels = std::make_unique(size); auto ret3 = process.reader().repeat_read(pixels.get(), size); if (!ret3.has_value() || ret3.value() != size) { return std::unexpected(ret.error()); } return std::make_unique(format, Size{ret2->width, ret2->height}, ret2->scanline, std::move(pixels)); } int run(std::unique_ptr reader, std::unique_ptr writer) { while (true) { Request request; auto ret = reader->repeat_read(&request, sizeof(request)); if (!ret.has_value() || ret.value() != sizeof(request)) break; std::string path_str(request.path_len, ' '); ret = reader->repeat_read(path_str.data(), path_str.size()); if (!ret.has_value() || ret.value() != path_str.size()) break; std::filesystem::path path(std::move(path_str)); auto extension = path.extension().native(); std::ranges::transform(extension, extension.begin(), [](unsigned char c){ return std::tolower(c); }); std::expected result{ std::unexpected(ImageLoadError::kUnsupportedFormat)}; #if HAVE_JPEG if (extension == ".jpeg" || extension == ".jpg" || extension == ".jpe" || extension == ".jfif" || extension == ".jfi" || extension == ".jif") { result = load_jpeg(path, request); } #endif #if HAVE_PNG if (path.extension() == ".png") { result = load_png(path, request); } #endif #if HAVE_RSVG if (path.extension() == ".svg" || path.extension() == ".svgz") { result = load_svg(path, request); } #endif #if HAVE_XPM if (path.extension() == ".xpm") { result = load_xpm(path, request); } #endif if (!request.head && result.has_value() && result->format != request.format) { auto size = static_cast(result->response.height) * result->response.scanline; rearrange_bytes(std::span(result->pixels.get(), size), result->response.width, result->response.height, result->response.scanline, result->format, request.format); } if (result.has_value()) { auto ret2 = writer->repeat_write(&result->response, sizeof(result->response)); if (!ret2.has_value() || ret2.value() != sizeof(result->response)) break; if (!request.head) { auto size = static_cast(result->response.height) * result->response.scanline; ret2 = writer->repeat_write(result->pixels.get(), size); if (!ret2.has_value() || ret2.value() != size) break; } } else { Response response{.width = 0, .height = 0, .error = static_cast(result.error()), .scanline = 0}; auto ret2 = writer->repeat_write(&response, sizeof(response)); if (!ret2.has_value() || ret2.value() != sizeof(response)) break; } } return -1; } } // namespace image_processor