### ----------------------------------------------------
### -- Imlib2
### -- Licenses: Artistic-2.0
### -- Authors: Naoum Hankache, Henrique Dias
### -- File: lib/Imlib2.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 3
_direction) {
		imlib_context_set_direction($text_direction.value);
	}

	method context_get_direction() returns TextDirection {
		return TextDirection(imlib_context_get_direction());
	}

	method context_set_angle(Rat $angle = 0.0) {
		if -360.0 <= $angle <= 360.0 {
			imlib_context_set_angle($angle.Num);
		} else {
			die "Specify an angle between -360.0 and 360.0";
		}
	}

	method context_get_angle() returns Rat {
		return imlib_context_get_angle().Rat;
	}

	# RGBA
	multi method context_set_color(
			Int :$red! where 0..255,
			Int :$green! where 0..255,
			Int :$blue! where 0..255,
			Int :$alpha where 0..255 = 255) {

		imlib_context_set_color($red, $green, $blue, $alpha);
	}

	# HSVA
	multi method context_set_color(
			Int :$hue! where 0..360,
			Int :$saturation! where 0 .. 100,
			Int :$value! where 0 .. 100,
			Int :$alpha where 0..255 = 255) {

		imlib_context_set_color_hsva($hue.Num, ($saturation/100).Num, ($value/100).Num, $alpha);
	}

	# HLSA
	multi method context_set_color(
			Int :$hue! where 0 .. 360,
			Int :$lightness! where 0 .. 100,
			Int :$saturation! where 0 .. 100,
			Int :$alpha where 0..255 = 255) {

		imlib_context_set_color_hlsa($hue.Num, ($lightness/100).Num, ($saturation/100).Num, $alpha);
	}

	# CMYA
	multi method context_set_color(
			Int :$cyan! where 0..255,
			Int :$magenta! where 0..255,
			Int :$yellow! where 0..255,
			Int :$alpha where 0..255 = 255) {

		imlib_context_set_color_cmya($cyan, $magenta, $yellow, $alpha);
	}

	multi method context_set_color(Str $hexstr where /^'#'<[A..Fa..f\d]>**6..8$/) {
		my ($red, $green, $blue, $alpha) = (0, 0, 0, 255);

		if $hexstr.chars == 7 | 9 {
			$red = ("0x" ~ $hexstr.substr(1,2)).Int;
			$green = ("0x" ~ $hexstr.substr(3,2)).Int;
			$blue = ("0x" ~ $hexstr.substr(5,2)).Int;
		}
		$alpha = ("0x" ~ $hexstr.substr(7,2)).Int if $hexstr.chars == 9;

		imlib_context_set_color($red, $green, $blue, $alpha);
	}

	multi method context_set_color(Int $hex_value where { $hex_value >= 0 }) {

		my $red = (($hex_value +> 24) +& 0xFF).Int;
		my $green = (($hex_value +> 16) +& 0xFF).Int;
		my $blue = (($hex_value +> 8) +& 0xFF).Int;
		my $alpha = (($hex_value) +& 0xFF).Int;

		imlib_context_set_color($red, $green, $blue, $alpha);
	}

	# RGBA
	multi method context_get_color(
			Int :$red! is rw,
			Int :$green! is rw,
			Int :$blue! is rw,
			Int :$alpha! is rw) {

		my @red_color := CArray[int32].new();
		my @green_color := CArray[int32].new();
		my @blue_color := CArray[int32].new();
		my @alpha_color := CArray[int32].new();
		@red_color[0] = @green_color[0] = @blue_color[0] = @alpha_color[0] = 0;

		imlib_context_get_color(@red_color, @green_color, @blue_color, @alpha_color);

		$red = @red_color[0];
		$green = @green_color[0];
		$blue = @blue_color[0];
		$alpha = @alpha_color[0];
	}

	# HSVA
	# Needs to be fixed. Precision issue.
	multi method context_get_color(
			Int :$hue! is rw,
			Int :$saturation! is rw,
			Int :$value! is rw,
			Int :$alpha! is rw) {

		my @chue := CArray[num32].new();
		my @csaturation := CArray[num32].new();
		my @cvalue := CArray[num32].new();
		my @calpha := CArray[int32].new();
		@chue[0] = @csaturation[0] = @cvalue[0] = 0e0;
		@calpha[0] = 0;

		imlib_context_get_color_hsva(@chue, @csaturation, @cvalue, @calpha);

		$hue = @chue[0].Int;
		$saturation = (@csaturation[0] * 100).Int;
		$value = (@cvalue[0] * 100).Int;
		$alpha = @calpha[0];
	}

	# HLSA
	# Needs to be fixed. Precision issue.
	multi method context_get_color(
			Int :$hue! is rw,
			Int :$lightness! is rw,
			Int :$saturation! is rw,
			Int :$alpha! is rw) {

		my @chue := CArray[num32].new();
		my @clightness := CArray[num32].new();
		my @csaturation := CArray[num32].new();
		my @calpha := CArray[int32].new();
		@chue[0] = @clightness[0] = @csaturation[0] = 0e0;
		@calpha[0] = 0;

		imlib_context_get_color_hlsa(@chue, @clightness, @csaturation, @calpha);

		$hue = @chue[0].Int;
		$lightness = (@clightness[0] * 100).Int;
		$saturation = (@csaturation[0] * 100).Int;
		$alpha = @calpha[0];
	}

	# CMYA
	multi method context_get_color(
			Int :$cyan! is rw,
			Int :$magenta! is rw,
			Int :$yellow! is rw,
			Int :$alpha! is rw) {

		my @ccyan := CArray[int32].new();
		my @cmagenta := CArray[int32].new();
		my @cyellow := CArray[int32].new();
		my @calpha := CArray[int32].new();
		@ccyan[0] = @cmagenta[0] = @cyellow[0] = @calpha[0] = 0;

		imlib_context_get_color_cmya(@ccyan, @cmagenta, @cyellow, @calpha);

		$cyan = @ccyan[0];
		$magenta = @cmagenta[0];
		$yellow = @cyellow[0];
		$alpha = @calpha[0];
	}

	method context_get_color_range() returns Imlib2::ColorRange {
		return imlib_context_get_color_range();
	}

	method context_get_image() returns Imlib2::Image {
		return imlib_context_get_image();
	}

	method context_set_cliprect(
			Int :$x where {$x >= 0} = 0,
			Int :$y where {$y >= 0} = 0,
			Int :$width where {$width >= 0},
			Int :$height where {$height >= 0}) {
		imlib_context_set_cliprect($x, $y, $width, $height);
	}

	method context_get_cliprect(
			Int :$x! is rw,
			Int :$y! is rw,
			Int :$width! is rw,
			Int :$height! is rw) {

		my @cx := CArray[int32].new();
		my @cy := CArray[int32].new();
		my @cw := CArray[int32].new();
		my @ch := CArray[int32].new();
		@cx[0] = @cy[0] = @cw[0] = @ch[0] = 0;

		imlib_context_get_cliprect(@cx, @cy, @cw, @ch);

		$x = @cx[0];
		$y = @cy[0];
		$width = @cw[0];
		$height = @ch[0];
	}

	method set_cache_size(Int $bytes where {$bytes >= 0} = 0) {
		imlib_set_cache_size($bytes);
	}

	method get_cache_size() returns Int {
		return imlib_get_cache_size();
	}

	method set_color_usage(Int $max where {$max >= 0} = 256) {
		imlib_set_color_usage($max);
	}

	method get_color_usage() returns Int {
		return imlib_get_color_usage();
	}

	### loading functions ###

	multi method load_image(Str $filename) returns Imlib2::Image {
		return imlib_load_image($filename);
	}

	multi method load_image (
		Str :$filename,
		Bool :$immediately = False,
		Bool :$cache = True) returns Imlib2::Image {

		if $immediately {
			return $cache ??
				imlib_load_image_immediately($filename) !!
				imlib_load_image_immediately_without_cache($filename);
		}
		return $cache ??
			imlib_load_image($filename) !!
			imlib_load_image_without_cache($filename);
	}

	multi method load_image(
		Str $filename,
		LoadError $error_return is rw) returns Imlib2::Image {

		my @error := CArray[int32].new();
		@error[0] = 0;
		my $image = imlib_load_image_with_error_return($filename, @error);
		$error_return = LoadError(@error[0]);
		return $image;
	}

	method free_image(Bool $decache = False) {
		$decache ?? imlib_free_image_and_decache() !! imlib_free_image();
	}

	method flush_loaders() {
		imlib_flush_loaders();
	}

	### query/modify image parameters ###

	method image_get_width() returns Int {
		return imlib_image_get_width();
	}

	method image_get_height() returns Int {
		return imlib_image_get_height();
	}

	method image_get_size() {
		return (imlib_image_get_width(), imlib_image_get_height());
	}

	method image_get_filename() returns Str {
		return imlib_image_get_filename();
	}

	method image_set_has_alpha(Bool $has_alpha) {
		imlib_image_set_has_alpha($has_alpha ?? 1 !! 0);
	}

	method image_has_alpha() returns Bool {
		return imlib_image_has_alpha() ?? True !! False;
	}

	method image_set_changes_on_disk() {
		imlib_image_set_changes_on_disk();
	}

	# It needs to be fixed.
	method image_set_border(OpaquePointer $border) {
		imlib_image_set_border($border);
	}

	# It needs to be fixed.
	method image_get_border(OpaquePointer $border) {
		imlib_image_get_border($border);
	}

	method image_set_format(Str $format) {
		imlib_image_set_format($format);
	}

	method image_get_format() returns Str {
		return imlib_image_format();
	}

	method image_set_irrelevant(
		Bool :$format,
		Bool :$border,
		Bool :$alpha) {

		imlib_image_set_irrelevant_format($format ?? 1 !! 0) if defined($format);
		imlib_image_set_irrelevant_border($border ?? 1 !! 0) if defined($border);
		imlib_image_set_irrelevant_alpha($alpha ?? 1 !! 0) if defined($alpha);
	}

	# RGBA Pixel
	multi method image_query_pixel(
			List :$location (Int $x where { $x >= 0 }, Int $y where { $y >= 0 }) = (0, 0),
			Int :$red! is rw,
			Int :$green! is rw,
			Int :$blue! is rw,
			Int :$alpha! is rw) {

		my @red_color := CArray[int32].new();
		my @green_color := CArray[int32].new();
		my @blue_color := CArray[int32].new();
		my @alpha_color := CArray[int32].new();
		@red_color[0] = @green_color[0] = @blue_color[0] = @alpha_color[0] = 0;

		p6_imlib_image_query_pixel($x, $y, @red_color, @green_color, @blue_color, @alpha_color);

		$red = @red_color[0];
		$green = @green_color[0];
		$blue = @blue_color[0];
		$alpha = @alpha_color[0];
	}

	# HSVA Pixel
	multi method image_query_pixel(
			List :$location (Int $x where { $x >= 0 }, Int $y where { $y >= 0 }) = (0, 0),
			Int :$hue! is rw,
			Int :$saturation! is rw,
			Int :$value! is rw,
			Int :$alpha! is rw) {

		my @chue := CArray[num32].new();
		my @csaturation := CArray[num32].new();
		my @cvalue := CArray[num32].new();
		my @calpha := CArray[int32].new();
		@chue[0] = @csaturation[0] = @cvalue[0] = 0e0;
		@calpha[0] = 0;

		imlib_image_query_pixel_hsva($x, $y, @chue, @csaturation, @cvalue, @calpha);

		$hue = @chue[0].Int;
		$saturation = (@csaturation[0] * 100).Int;
		$value = (@cvalue[0] * 100).Int;
		$alpha = @calpha[0];
	}

	# HLSA Pixel
	multi method image_query_pixel(
			List :$location (Int $x where { $x >= 0 }, Int $y where { $y >= 0 }) = (0, 0),
			Int :$hue! is rw,
			Int :$lightness! is rw,
			Int :$saturation! is rw,
			Int :$alpha! is rw) {

		my @chue := CArray[num32].new();
		my @clightness := CArray[num32].new();
		my @csaturation := CArray[num32].new();
		my @calpha := CArray[int32].new();
		@chue[0] = @clightness[0] = @csaturation[0] = 0e0;
		@calpha[0] = 0;

		imlib_image_query_pixel_hlsa($x, $y, @chue, @clightness, @csaturation, @calpha);

		$hue = @chue[0].Int;
		$lightness = (@clightness[0] * 100).Int;
		$saturation = (@csaturation[0] * 100).Int;
		$alpha = @calpha[0];
	}

	# CMYA Pixel
	multi method image_query_pixel(
			List :$location (Int $x where { $x >= 0 }, Int $y where { $y >= 0 }) = (0, 0),
			Int :$cyan! is rw,
			Int :$magenta! is rw,
			Int :$yellow! is rw,
			Int :$alpha! is rw) {

		my @cyan_color := CArray[int32].new();
		my @magenta_color := CArray[int32].new();
		my @yellow_color := CArray[int32].new();
		my @alpha_color := CArray[int32].new();
		@cyan_color[0] = @magenta_color[0] = @yellow_color[0] = @alpha_color[0] = 0;

		imlib_image_query_pixel_cmya($x, $y, @cyan_color, @magenta_color, @yellow_color, @alpha_color);

		$cyan = @cyan_color[0];
		$magenta = @magenta_color[0];
		$yellow = @yellow_color[0];
		$alpha = @alpha_color[0];
	}

	### rendering functions ###

	method blend_image_onto_image(
		List :$source!(
			Imlib2::Image :$image!,
			List :location($sl)(Int $sx where { $sx >= 0 }, Int $sy where { $sy >= 0 }) = (0, 0),
			List :size($ss)!(Int $sw where { $sw > 0 }, Int $sh where { $sh > 0 })
		),
		List :$destination!(
			List :location($dl)!(Int $dx, Int $dy),
			List :size($ds)!(Int $dw where { $dw > 0 }, Int $dh where { $dh > 0 })
		),
		Bool :$merge_alpha = False) {

		imlib_blend_image_onto_image($image, $merge_alpha ?? 1 !! 0,
				$sx, $sy, $sw, $sh,
				$dx, $dy, $dw, $dh);
	}

	### creation functions ###

	method create_image(Int $width, Int $height) returns Imlib2::Image {
		return imlib_create_image($width, $height);
	}

	method clone_image() returns Imlib2::Image {
		return imlib_clone_image();
	}

	# Crop and Scale
	multi method create_resized_image(
		List :$location (Int $x where { $x >= 0 }, Int $y where { $y >= 0 }) = (0, 0),
		List :$scale!(Int $sw where { $sw >= 0 }, Int $sh where { $sh >= 0 }),
		List :$crop!(Int $cw where { $cw >= 0 }, Int $ch where { $ch >= 0 })
		) returns Imlib2::Image {

		my $w = imlib_image_get_width();
		my $h = imlib_image_get_height();
		$x = 0 unless $x < $w;
		$y = 0 unless $y < $h;
		$cw = $w if $cw > $w;
		$ch = $h if $ch > $h;

		return imlib_create_cropped_scaled_image($x, $y, $cw, $ch, $sw, $sh);
	}

	# Scale
	multi method create_resized_image(
		List :$location (Int $x where { $x >= 0 }, Int $y where { $y >= 0 }) = (0, 0),
		List :$scale!(Int $sw where { $sw >= 0 }, Int $sh where { $sh >= 0 })
		) returns Imlib2::Image {

		my $w = imlib_image_get_width();
		my $h = imlib_image_get_height();
		$x = 0 unless $x < $w;
		$y = 0 unless $y < $h;

		return imlib_create_cropped_scaled_image($x, $y, $w - $x, $h - $y, $sw, $sh);
	}
	# Crop
	multi method create_resized_image(
		List :$location (Int $x where { $x >= 0 }, Int $y where { $y >= 0 }) = (0, 0),
		List :$crop!(Int $cw where { $cw >= 0 }, Int $ch where { $ch >= 0 })
		) returns Imlib2::Image {

		my $w = imlib_image_get_width();
		my $h = imlib_image_get_height();
		$x = 0 unless $x < $w;
		$y = 0 unless $y < $h;
		$cw = $w if $cw > $w;
		$ch = $h if $ch > $h;

		return imlib_create_cropped_image($x, $y, $cw, $ch);
	}

	### imlib updates ###

	### image modification ###

	multi method image_flip(FlipMode $flip where $flip == IMLIB_FLIP_HORIZONTAL) {
		imlib_image_flip_horizontal();
	}

	multi method image_flip(FlipMode $flip where $flip == IMLIB_FLIP_VERTICAL) {
		imlib_image_flip_vertical();
	}

	multi method image_flip(FlipMode $flip where $flip == IMLIB_FLIP_DIAGONAL) {
		imlib_image_flip_diagonal();
	}

	method image_orientate(RotationMode $rotation) {
		imlib_image_orientate($rotation.value);
	}

	method image_blur(Int $radius where 0..128) {
		imlib_image_blur($radius);
	}

	method image_sharpen(Int $radius where 0..128) {
		imlib_image_sharpen($radius);
	}

	multi method image_tile(TileMode $tile where $tile == IMLIB_TILE_HORIZONTAL) {
		imlib_image_tile_horizontal();
	}

	multi method image_tile(TileMode $tile where $tile == IMLIB_TILE_VERTICAL) {
		imlib_image_tile_vertical();
	}

	multi method image_tile(TileMode $tile where { $tile == IMLIB_TILE_BOTH } = IMLIB_TILE_BOTH) {
		imlib_image_tile();
	}

	### fonts and text ###

	method load_font(Str $name, Int $size) returns Imlib2::Font {
		return imlib_load_font(($name, $size.Str).join("/"));
	}

	method free_font() {
		imlib_free_font();
	}

	multi method text_draw(Int $x, Int $y, Str $text) {
		imlib_text_draw($x, $y, $text);
	}

	multi method text_draw(Int $x, Int $y, Str $text, %metrics) {
		my @w := CArray[int32].new();
		my @h := CArray[int32].new();
		my @ha := CArray[int32].new();
		my @va := CArray[int32].new();
		@w[0] = @h[0] = @ha[0] = @va[0] = 0;

		imlib_text_draw_with_return_metrics($x, $y, $text, @w, @h, @ha, @va);
		%metrics{'width'} = @w[0];
		%metrics{'height'} = @h[0];
		%metrics{'horizontal_advance'} = @ha[0];
		%metrics{'vertical_advance'} = @va[0];
	}

	method get_text_size(Str $text) {
		my @w := CArray[int32].new();
		my @h := CArray[int32].new();
		@w[0] = @h[0] = 0;
		imlib_get_text_size($text, @w, @h);

		return (@w[0], @h[0]);
	}

	method get_text_advance(Str $text) {
		my @ha := CArray[int32].new();
		my @va := CArray[int32].new();
		@ha[0] = @va[0] = 0;
		imlib_get_text_advance($text, @ha, @va);

		return (@ha[0], @va[0]);
	}

	method get_text_inset(Str $text) returns Int {
		return imlib_get_text_inset($text);
	}

	#
	# NOTE:
	# imlib_text_get_location_at_index and imlib_text_get_index_and_location
	# needs to be fixed.
	#
	method text_get_index_and_location(
			Str $text,
			Int $x where {$x >= 0},
			Int $y where {$y >= 0},
			%character) returns Int {

		my @cx := CArray[int32].new();
		my @cy := CArray[int32].new();
		my @cw := CArray[int32].new();
		my @ch := CArray[int32].new();
		@cx[0] = @cy[0] = @cw[0] = @ch[0] = 0;

		my Int $char_number = imlib_text_get_index_and_location($text, $x, $y, @cx, @cy, @cw, @ch);
		if $char_number != -1 {
			%character{'x'} = @cx[0];
			%character{'y'} = @cy[0];
			%character{'width'} = @cw[0];
			%character{'height'} = @ch[0];
		}
		return $char_number;
	}

	# this only work if text_get_index_and_location return > -1
	method text_get_location_at_index(
			Str $text,
			Int $x where {$x >= 0},
			Int $y where {$y >= 0},
			%geometry) {

		my @cx := CArray[int32].new();
		my @cy := CArray[int32].new();
		my @cw := CArray[int32].new();
		my @ch := CArray[int32].new();
		@cx[0] = @cy[0] = @cw[0] = @ch[0] = 0;

		imlib_text_get_location_at_index($text, $x, $y, @cx, @cy, @cw, @ch);
		%geometry{'x'} = @cx[0];
		%geometry{'y'} = @cy[0];
		%geometry{'width'} = @cw[0];
		%geometry{'height'} = @ch[0];
	}

	method get_font_ascent() returns Int {
		return imlib_get_font_ascent();
	}

	method get_font_descent() returns Int {
		return imlib_get_font_descent();
	}

	method get_maximum_font_ascent() returns Int {
		return imlib_get_maximum_font_ascent();
	}

	method get_maximum_font_descent() {
		return imlib_get_maximum_font_descent();
	}

	method add_path_to_font_path(Str $path) {
		imlib_add_path_to_font_path($path);
	}

	method remove_path_from_font_path(Str $path) {
		imlib_remove_path_from_font_path($path);
	}

	method list_font_path() returns Array {
		my @n := CArray[int32].new();
		@n[0] = 0;
		my @list := imlib_list_font_path(@n);
		return copy_CArray2p6Array(@list, @n[0]);
	}

	method list_fonts() returns Array {
		my @n := CArray[int32].new();
		@n[0] = 0;
		my @list := imlib_list_fonts(@n);
		my @copy = copy_CArray2p6Array(@list, @n[0]);
		imlib_free_font_list(@list, @n[0]);
		return @copy;
	}

	method set_font_cache_size(Int $bytes) {
		imlib_set_font_cache_size($bytes);
	}

	method get_font_cache_size() returns Int {
		return imlib_get_font_cache_size();
	}

	method flush_font_cache() {
		imlib_flush_font_cache();
	}

	### color modifiers ###

	method create_color_modifier() {
		return imlib_create_color_modifier();
	}

	method free_color_modifier() {
		imlib_free_color_modifier();
	}

	method modify_color_modifier(*%arguments) {
		for %arguments.kv -> Str $key, Int $value where -127 .. 127 {
			next unless $value;
			given $key {
				imlib_modify_color_modifier_brightness(($value/127).Num) when "brightness"; # range -1 .. 1
				imlib_modify_color_modifier_contrast((1 + $value/127).Num) when "contrast"; # range 0 .. 2
				imlib_modify_color_modifier_gamma((1 + $value/127).Num) when "gamma";       # range 0 .. 2
			}
		}
	}

	method modify_color_modifier_brightness(Rat $brightness_value where -1.0 .. 1.0 = 0.0) {
		imlib_modify_color_modifier_brightness($brightness_value.Num);
	}

	method modify_color_modifier_contrast(Rat $contrast_value where 0.0 .. 2.0 = 1.0) {
		imlib_modify_color_modifier_contrast($contrast_value.Num);
	}

	method modify_color_modifier_gamma(Rat $gamma_value where 0.0 .. 2.0 = 1.0) {
		imlib_modify_color_modifier_gamma($gamma_value.Num);
	}

	method set_color_modifier_tables(@red_table, @green_table, @blue_table, @alpha_table) {
		my $cred_table = init_table(@red_table);
		my $cgreen_table = init_table(@green_table);
		my $cblue_table = init_table(@blue_table);
		my $calpha_table = init_table(@alpha_table);

		imlib_set_color_modifier_tables($cred_table, $cgreen_table, $cblue_table, $calpha_table);
	}

	method get_color_modifier_tables(@red_table, @green_table, @blue_table, @alpha_table) {
		my $cred_table = init_table();
		my $cgreen_table = init_table();
		my $cblue_table = init_table();
		my $calpha_table = init_table();

		imlib_get_color_modifier_tables($cred_table, $cgreen_table, $cblue_table, $calpha_table);

		for 0..255 {
			@red_table[$_] = $cred_table[$_];
			@green_table[$_] = $cgreen_table[$_];
			@blue_table[$_] = $cblue_table[$_];
			@alpha_table[$_] = $calpha_table[$_];
		}
	}

	method reset_color_modifier() {
		imlib_reset_color_modifier();
	}

	multi method apply_color_modifier() {
		imlib_apply_color_modifier();
	}

	multi method apply_color_modifier(
		List :$location (Int $x where { $x >= 0 }, Int $y where { $y >= 0 }) = (0, 0),
		List :$size!(Int $w where { $w > 0 }, Int $h where { $h > 0 })) {

		imlib_apply_color_modifier_to_rectangle($x, $y, $w, $h);
	}

	### drawing on images ###

	method image_draw_pixel(
			Int $x where { $x >= 0 },
			Int $y where { $y >= 0 },
			Bool $update = False) returns Imlib2::Updates {

		return imlib_image_draw_pixel($x, $y, $update ?? 1 !! 0);
	}

	method image_draw_line(
		List :$start (Int $x1 where { $x1 >= 0 }, Int $y1 where { $y1 >= 0 }) = (0, 0),
		List :$end!(Int $x2 where { $x2 > 0 }, Int $y2 where { $y2 > 0 }),
		Bool :$update = False) returns Imlib2::Updates {

		return imlib_image_draw_line($x1, $y1, $x2, $y2, $update ?? 1 !! 0);
	}

	method image_draw_rectangle(
		List :$location (Int $x where { $x >= 0 }, Int $y where { $y >= 0 }) = (0, 0),
		List :$size!(Int $w where { $w > 0 }, Int $h where { $h > 0 }),
		Bool :$fill = False,
		Bool :$gradient = False,
		Rat :$angle where -360.0 .. 360.0 = 0.0,
		Bool :$hsva = False) {

		if $fill {
			if $gradient {
				$hsva ?? imlib_image_fill_hsva_color_range_rectangle($x, $y, $w, $h, $angle.Num) !!
				imlib_image_fill_color_range_rectangle($x, $y, $w, $h, $angle.Num);
			} else {
				imlib_image_fill_rectangle($x, $y, $w, $h);
			}
		} else {
			imlib_image_draw_rectangle($x, $y, $w, $h);
		}
	}

	### polygons ###

	method polygon_new() returns Imlib2::Polygon {
		return imlib_polygon_new();
	}

	method image_draw_polygon(
		Imlib2::Polygon $polygon,
		Bool :$fill = False,
		Bool :$closed = True) {

		$fill ??
			imlib_image_fill_polygon($polygon) !!
			imlib_image_draw_polygon($polygon, $closed ?? 1 !! 0);
	}

	### ellipses/circumferences ###

	method image_draw_ellipse(
		List :$center!(Int $xc, Int $yc),
		List :$amplitude!(Int $a where { $a > 0 }, Int $b where { $b > 0 }),
		  Bool :$fill = False) {

		$fill ??
			imlib_image_fill_ellipse($xc, $yc, $a, $b) !!
			imlib_image_draw_ellipse($xc, $yc, $a, $b);
	}

	method image_draw_circumference(
			List :$center!(Int $xc, Int $yc),
			   Int :$radius! where { $radius > 0 },
			  Bool :$fill = False) {

		$fill ??
			imlib_image_fill_ellipse($xc, $yc, $radius, $radius) !!
			imlib_image_draw_ellipse($xc, $yc, $radius, $radius);
	}

	### color ranges ###

	method create_color_range() returns Imlib2::ColorRange {
		return imlib_create_color_range();
	}

	method free_color_range() {
		imlib_free_color_range();
	}

	method add_color_to_color_range(Int $distance_away) {
		imlib_add_color_to_color_range($distance_away);
	}

	### saving ###

	multi method save_image($filename) {
		imlib_save_image($filename);
	}

	multi method save_image(Str $filename, LoadError $error_return is rw) {
		my @error := CArray[int32].new();
		@error[0] = 0;
		imlib_save_image_with_error_return($filename, @error);
		$error_return = LoadError(@error[0]);
	}

	### rotation/skewing ###

	method create_rotated_image(Rat $angle where -360.0 .. 360.0) returns Imlib2::Image {
		return imlib_create_rotated_image(($angle * pi/180).Num);
	}

	### image filters ###

	method image_clear() {
		imlib_image_clear();
	}

	### auxiliary functions ###

	multi method get_hex_color_code(Int $red, Int $green, Int $blue, Int $alpha) {
		return ("#",
			sprintf('%02x', $red),
			sprintf('%02x', $green),
			sprintf('%02x', $blue),
			sprintf('%02x', $alpha)).join("");
	}

	multi method get_hex_color_code(Int $red, Int $green, Int $blue) {
		return ("#",
			sprintf('%02x', $red),
			sprintf('%02