use strict; use GD; # GetReferenceColors returns a coarse gradient of colors typical # at several Luma levels sub GetReferenceColors { my ($img, $count) = @_; # build %indexes hash where the keys are the indexes of each unique color # and the values are the number of pixels counted for the color index my ($width, $height) = $img->getBounds; my %indexes; for my $y (0..$height-1) { for my $x (0..$width-1) { my $idx = $img->getPixel($x, $y); ++$indexes{$idx}; } } # Build @colors array with one entry for each color # contains the color's RGB triple, its Luma (Y value) and pixel count my @colors; for my $idx (keys %indexes) { my @rgb = $img->rgb($idx); my $y = $rgb[0]*0.299 + $rgb[1]*0.587 + $rgb[2]*0.114; my $pixel_count = $indexes{$idx}; push(@colors, { rgb => [@rgb], y => $y, count => $pixel_count }); } # Sort @colors by ascending Luma value @colors = sort { $a->{y} <=> $b->{y} } @colors; # split @colors into $count groups, which overlap by one entry # calculate each group's average RGB value, weighted by pixel count # add each group's average [r,g,b] triple to @ref_colors. my @ref_colors; my $step = @colors / $count; for my $i (0..$count - 1) { my $start = int($i * $step); my $end = int(($i + 1) * $step); my $wsum = 0; my @csum = (0, 0, 0); for my $j ($start .. $end) { my $color = $colors[$j]; my $weight = $color->{count}; $wsum += $weight; for my $ci (0..2) { $csum[$ci] += $color->{rgb}->[$ci] * $weight; } } for my $ci (0..2) { $csum[$ci] = int($csum[$ci] / $wsum + 0.5); } push(@ref_colors, \@csum ); } return \@ref_colors; } # InterpolateColors interpolates between two [r,g,b] triples # by a weight factor between 0 and 1 sub InterpolateColors { my ($ca, $cb, $pb) = @_; my @rgb; for my $i (0..2) { push(@rgb, int($ca->[$i] * (1 - $pb) + $cb->[$i] * $pb + 0.5)); } return \@rgb; } # Builds an interpolated set of colors based on an image's # most commonly occurring colors at a series of brightness levels sub InterpolatePalette { my ($img, $count) = @_; # Build a 256-entry @gradient by linearly interpolating between # a set of 17 colors returned by GetReferenceColors my @gradient; my $ref_colors = GetReferenceColors($img, 17); for my $i (1..@$ref_colors-1) { my $c0 = $ref_colors->[$i-1]; my $c1 = $ref_colors->[$i]; for my $j (0..15) { my $p = $j/16; push(@gradient, InterpolateColors($c0, $c1, $p)); } } return \@gradient; } # Read the input image from a file my $file = $ARGV[0] // '07_AH_Esfahan Gold 65-ab.jpg'; my $img = GD::Image->newFromJpeg($file); # Calculate an interpolated gradient from the image's dominant colors my $r = InterpolatePalette($img); # Create a new image for displaying the color gradient on a grid my $len = 20; my $width = 16*$len+1; my $grid = new GD::Image($width, $width, 1); # Draw the grid my $background = $grid->colorResolve(0, 0, 0); $grid->filledRectangle(0, 0, $width, $width, $background); my $loc = 0; for my $color (@$r) { my $x = ($loc & 15) * $len; my $y = ($loc >> 4) * $len; ++$loc; my $color = $grid->colorResolve(@$color); $grid->filledRectangle($x+1, $y+1, $x+$len-1, $y+$len-1, $color); } # Save the result open(my $fh, '>:raw', 'grid.png') or die $!; print $fh $grid->png;