File Coverage

lib/Image/ColorDetector.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Image::ColorDetector;
2 2     2   1618 use 5.008005;
  2         8  
  2         76  
3 2     2   10 use strict;
  2         4  
  2         79  
4 2     2   31 use warnings;
  2         3  
  2         108  
5              
6             our $VERSION = "0.04";
7              
8 2     2   1651 use parent qw( Exporter );
  2         629  
  2         13  
9             our @EXPORT_OK = qw(
10             detect
11             );
12              
13 2     2   129 use Carp ();
  2         4  
  2         31  
14              
15 2     2   893 use Image::Magick;
  0            
  0            
16             use List::Util qw( max min );
17              
18             sub detect {
19             my ($file_path) = @_;
20              
21             Carp::croak(q{$file_path is required})
22             unless (defined $file_path);
23              
24             my $hsvs_ref = _extract_hsv($file_path);
25             my $color_names_ref = _allot_color_name($hsvs_ref);
26             my $color_palette = _count_up_for_each_color_name($color_names_ref);
27             my $color_name = _main_color_name($color_palette);
28              
29             if ($color_name) {
30             return $color_name;
31             }
32             else {
33             return;
34             }
35             }
36              
37             sub _extract_hsv {
38             my ($img) = @_;
39             $img or return;
40              
41             my $im = Image::Magick->new;
42             open(IMAGE, $img);
43              
44             my $ret = $im->Read(file => \*IMAGE);
45              
46             if ($ret) {
47             Carp::croak("$ret\ninvalid image source: $!");
48             }
49              
50             close(IMAGE);
51              
52             my ($w, $h) = $im->Get('width', 'height');
53              
54             my @pixels = $im->GetPixels(
55             width => $w,
56             height => $h,
57             x => 0,
58             y => 0,
59             map => 'RGB',
60             );
61              
62             my @rgbs;
63             my @hsvs;
64             while (@pixels) {
65             my %rgb_hash;
66             $rgb_hash{r} = (int((shift @pixels) / 256) / 255);
67             $rgb_hash{g} = (int((shift @pixels) / 256) / 255);
68             $rgb_hash{b} = (int((shift @pixels) / 256) / 255);
69             push @rgbs, \%rgb_hash;
70              
71             my $max = max $rgb_hash{r}, $rgb_hash{g}, $rgb_hash{b};
72             my $min = min $rgb_hash{r}, $rgb_hash{g}, $rgb_hash{b};
73              
74             next if ($max <= 0);
75              
76             my %hsv_hash;
77             $hsv_hash{v} = $max;
78             $hsv_hash{s} = 255 * ( ($max - $min) / $max );
79              
80             if ($hsv_hash{s} == 0) {
81             next;
82             }
83             elsif ($max == $rgb_hash{r}) {
84             $hsv_hash{h} = 60 * ( ($rgb_hash{g} - $rgb_hash{b}) / ($max - $min) );
85             }
86             elsif ($max == $rgb_hash{g}) {
87             $hsv_hash{h} = 60 * ( 2 + ($rgb_hash{b} - $rgb_hash{r}) / ($max - $min) );
88             }
89             elsif ($max == $rgb_hash{b}) {
90             $hsv_hash{h} = 60 * ( 4 + ($rgb_hash{r} - $rgb_hash{g}) / ($max - $min) );
91             }
92             else {
93             next;
94             }
95             push @hsvs, \%hsv_hash;
96             }
97             return \@hsvs;
98             }
99              
100             sub _allot_color_name {
101             my ($hsvs) = @_;
102              
103             return
104             if (!$hsvs || ref($hsvs) ne 'ARRAY');
105              
106             my @hsv_with_color;
107             for my $hsv (@$hsvs) {
108             if (!$hsv->{h}) {
109             next;
110             }
111             elsif (($hsv->{h} >= 0 && $hsv->{h} < 20) || ($hsv->{h} >= 330 && $hsv->{h} < 360)) {
112             $hsv->{color} = 'RED';
113             }
114             elsif ($hsv->{h} >= 20 && $hsv->{h} < 50) {
115             $hsv->{color} = 'ORANGE';
116             }
117             elsif ($hsv->{h} >= 50 && $hsv->{h} < 70) {
118             $hsv->{color} = 'YELLOW';
119             }
120             elsif ($hsv->{h} >= 70 && $hsv->{h} < 85) {
121             $hsv->{color} = 'LIME';
122             }
123             elsif ($hsv->{h} >= 85 && $hsv->{h} < 171) {
124             $hsv->{color} = 'GREEN';
125             }
126             elsif ($hsv->{h} >= 171 && $hsv->{h} < 192) {
127             $hsv->{color} = 'AQUA';
128             }
129             elsif ($hsv->{h} >= 192 && $hsv->{h} < 265) {
130             $hsv->{color} = 'BLUE';
131             }
132             elsif ($hsv->{h} >= 265 && $hsv->{h} < 290) {
133             $hsv->{color} = 'VIOLET';
134             }
135             elsif ($hsv->{h} >= 290 && $hsv->{h} < 330) {
136             $hsv->{color} = 'PURPLE';
137             }
138             else {
139             next;
140             }
141             push @hsv_with_color, $hsv;
142             }
143             return \@hsv_with_color;
144             }
145              
146             sub _count_up_for_each_color_name {
147             my ($hsv_with_color) = @_;
148              
149             return
150             if (!$hsv_with_color || ref($hsv_with_color) ne 'ARRAY');
151              
152             my %color_palette = (
153             RED => 0,
154             ORANGE => 0,
155             YELLOW => 0,
156             LIME => 0,
157             GREEN => 0,
158             AQUA => 0,
159             BLUE => 0,
160             VIOLET => 0,
161             PURPLE => 0,
162             );
163              
164             my @colors = map { $_->{color} } @$hsv_with_color;
165             for my $color (@colors) {
166             for my $palette_key (keys %color_palette) {
167             if ($color eq $palette_key) {
168             $color_palette{$palette_key}++;
169             }
170             }
171             }
172             return \%color_palette;
173             }
174              
175             sub _main_color_name {
176             my ($color_palette_href) = @_;
177              
178             return
179             if (!$color_palette_href || ref($color_palette_href) ne 'HASH');
180              
181             return 'BLACK-AND-WHITE'
182             unless (grep { $_ > 0 } values %$color_palette_href);
183              
184             my @sorted_colors =
185             map {
186             $_->[0]
187             }
188             sort {
189             $b->[1] <=> $a->[1]
190             }
191             map {
192             [$_, $color_palette_href->{$_}]
193             }
194             keys %$color_palette_href;
195              
196             return shift @sorted_colors;
197             }
198              
199              
200              
201              
202             1;
203             __END__