diff --git a/Userland/Utilities/image.cpp b/Userland/Utilities/image.cpp index 66f1c93cb02..2aa77f740b0 100644 --- a/Userland/Utilities/image.cpp +++ b/Userland/Utilities/image.cpp @@ -57,6 +57,15 @@ static ErrorOr invert_cmyk(LoadedImage& image) return {}; } +static ErrorOr crop_image(LoadedImage& image, Gfx::IntRect const& rect) +{ + if (!image.bitmap.has>()) + return Error::from_string_view("Can't --crop CMYK bitmaps yet"sv); + auto& frame = image.bitmap.get>(); + frame = TRY(frame->cropped(rect)); + return {}; +} + static ErrorOr move_alpha_to_rgb(LoadedImage& image) { if (!image.bitmap.has>()) @@ -189,6 +198,7 @@ struct Options { bool no_output = false; int frame_index = 0; bool invert_cmyk = false; + Optional crop_rect; bool move_alpha_to_rgb = false; bool strip_alpha = false; StringView assign_color_profile_path; @@ -198,6 +208,28 @@ struct Options { u8 quality = 75; }; +template +static ErrorOr> parse_comma_separated_numbers(StringView rect_string) +{ + auto parts = rect_string.split_view(','); + Vector part_numbers; + for (size_t i = 0; i < parts.size(); ++i) { + auto part = parts[i].to_number(); + if (!part.has_value()) + return Error::from_string_view("comma-separated parts must be numbers"sv); + TRY(part_numbers.try_append(part.value())); + } + return part_numbers; +} + +static ErrorOr parse_rect_string(StringView rect_string) +{ + auto numbers = TRY(parse_comma_separated_numbers(rect_string)); + if (numbers.size() != 4) + return Error::from_string_view("rect must have 4 comma-separated parts"sv); + return Gfx::IntRect { numbers[0], numbers[1], numbers[2], numbers[3] }; +} + static ErrorOr parse_options(Main::Arguments arguments) { Options options; @@ -207,6 +239,8 @@ static ErrorOr parse_options(Main::Arguments arguments) args_parser.add_option(options.no_output, "Do not write output (only useful for benchmarking image decoding)", "no-output", {}); args_parser.add_option(options.frame_index, "Which frame of a multi-frame input image (0-based)", "frame-index", {}, "INDEX"); args_parser.add_option(options.invert_cmyk, "Invert CMYK channels", "invert-cmyk", {}); + StringView crop_rect_string; + args_parser.add_option(crop_rect_string, "Crop to a rectangle", "crop", {}, "x,y,w,h"); args_parser.add_option(options.move_alpha_to_rgb, "Copy alpha channel to rgb, clear alpha", "move-alpha-to-rgb", {}); args_parser.add_option(options.strip_alpha, "Remove alpha channel", "strip-alpha", {}); args_parser.add_option(options.assign_color_profile_path, "Load color profile from file and assign it to output image", "assign-color-profile", {}, "FILE"); @@ -219,6 +253,9 @@ static ErrorOr parse_options(Main::Arguments arguments) if (options.out_path.is_empty() ^ options.no_output) return Error::from_string_view("exactly one of -o or --no-output is required"sv); + if (!crop_rect_string.is_empty()) + options.crop_rect = TRY(parse_rect_string(crop_rect_string)); + return options; } @@ -236,6 +273,9 @@ ErrorOr serenity_main(Main::Arguments arguments) if (options.invert_cmyk) TRY(invert_cmyk(image)); + if (options.crop_rect.has_value()) + TRY(crop_image(image, options.crop_rect.value())); + if (options.move_alpha_to_rgb) TRY(move_alpha_to_rgb(image));