File Coverage

blib/lib/Sew/Color.pm
Criterion Covered Total %
statement 81 175 46.2
branch 18 74 24.3
condition 2 6 33.3
subroutine 11 16 68.7
pod 0 12 0.0
total 112 283 39.5


line stmt bran cond sub pod time code
1             package Sew::Color;
2              
3 1     1   27974 use 5.010001;
  1         4  
  1         52  
4 1     1   6 use strict;
  1         3  
  1         44  
5 1     1   7 use warnings;
  1         7  
  1         48  
6 1     1   8 use Carp;
  1         2  
  1         2880  
7              
8             require Exporter;
9              
10             our @ISA = qw(Exporter);
11              
12             our $VERSION='1.05';
13             #
14             #use base 'Exporter';
15             our @EXPORT=(
16             'rgb', # rgb('Brother','405') returns the red green and blue colors of this thread.
17             'name', # returns english name of color, eg 'Bright Red'. Caution, not unique.
18             'code', # code('Brother',$r,$g,$b) gives the closest thread code to the given rgb
19             # in array context, returns (code, error distance) using a simple 3d color
20             # space model.
21             # 1st parameter may be a manufacturers name, empty (for all)
22             # a comma seperate list, or an array reference containing single manufacturers
23             'manlist',
24             'custom',
25             'custom_sub',
26             'custom_list',
27             'evecrgb',
28             'mag',
29             'sat'
30             ) ;
31             my $colorlist='';
32              
33             sub get_color_list
34             {
35             # Brother,Black,100,28,26,28
36 1     1 0 2 $colorlist={};
37 1         2 local $_;
38 1         6 while ()
39             {
40 1555 50       2867 m/^ *#/ and next;
41 1555         1469 chomp;
42 1555         4826 my @x=split(/,/);
43 1555         1746 my @rgb;
44 1555         3012 @rgb=@x[3..5];
45 1555 100       2863 exists($colorlist->{$x[0]}) or $colorlist->{$x[0]}={};
46 1555         3932 $colorlist->{$x[0]}->{$x[2]}={};
47 1555         2911 $colorlist->{$x[0]}->{$x[2]}->{name}=$x[1];
48 1555         5811 $colorlist->{$x[0]}->{$x[2]}->{rgb}=\@rgb;
49             }
50 1         32 close DATA;
51             }
52             sub rgb
53             {
54 1     1 0 9 my ($man,$code)=@_;
55              
56 1 50       6 $colorlist or get_color_list();
57              
58 1         4 my $r=$colorlist->{$man}->{$code}->{rgb};
59 1         10 return @$r;
60             }
61             sub name
62             {
63 1     1 0 1013 my ($man,$code)=@_;
64              
65 1 50       4 $colorlist or get_color_list();
66 1 50       4 if (!exists($colorlist->{$man}))
67             {
68 0         0 croak("Invalid manufacturer code '$man' supplied to function name()");
69             }
70              
71 1         3 my $r=$colorlist->{$man}->{$code}->{name};
72 1         6 return $r;
73             }
74              
75             sub manlist
76             {
77 0 0   0 0 0 $colorlist or get_color_list();
78 0         0 return keys %$colorlist;
79             }
80              
81             # give a list of threads that you have for custom searches.
82             # can be Brother 405 406 407 Maderia 1005 102
83             sub custom
84             {
85 0 0   0 0 0 $colorlist or get_color_list();
86 0         0 my @mankeys=keys %$colorlist;
87 0         0 my $man='';
88 0 0       0 if (@_==0)
89             {
90 0         0 for $man (@mankeys)
91             {
92 0         0 for my $code (keys %{$colorlist->{$man}})
  0         0  
93             {
94 0         0 delete $colorlist->{$man}->{$code}->{custom};
95             }
96             }
97 0         0 return;
98             }
99 0         0 for my $t (@_)
100             {
101 0         0 my $nmk;
102 0         0 $nmk='';
103 0         0 ($nmk)=grep { $t eq $_ } @mankeys;
  0         0  
104 0 0       0 defined $nmk or $nmk='';
105             #if (0
106 0 0       0 if ($nmk ne '')
107             {
108 0         0 $man=$nmk;
109 0         0 next;
110             }
111             # else its a code.
112 0 0       0 if ($t eq 'all') # add all for current manufacturer or all manufacturer.
113             {
114 0 0       0 if ($man ne '')
115             {
116 0         0 for my $key (keys %{$colorlist->{$man}})
  0         0  
117             {
118 0         0 $colorlist->{$man}->{$key}->{'custom'}=1;
119             }
120             }
121             else
122             {
123 0         0 for my $man (keys %{$colorlist})
  0         0  
124             {
125 0         0 for my $key (keys %{$colorlist->{$man}})
  0         0  
126             {
127 0         0 $colorlist->{$man}->{$key}->{'custom'}=1;
128             }
129             }
130             }
131 0         0 next;
132             }
133 0 0       0 die "Error no manufacturer given in call to custom for code $t or mispelt manufacturer!" if ($man eq '');
134 0 0       0 die "Invalid code '$t' for manufacturer $man in call to custom" if (!exists($colorlist->{$man}->{$t}));
135 0         0 $colorlist->{$man}->{$t}->{'custom'}=1;
136             }
137             }
138             # list entries for custom searches.
139             sub custom_list
140             {
141 0     0 0 0 my ($man,$format)=@_;
142             # man can be empty, a single manufacturer, or a ref to a list of manufacturers.
143             # format can be '%m replace with manufacturer code. %c replace with code, %% replace with %.
144             # Default is '%c';
145 0 0       0 $colorlist or get_color_list();
146              
147 0         0 my @r;
148              
149             my @mana;
150              
151 0 0       0 defined($format) or $format='%c';
152            
153 0 0       0 if ($man eq '')
    0          
154             {
155 0         0 @mana=();
156             }
157             elsif (!ref($man))
158             {
159 0         0 @mana=($man);
160             }
161             else
162             {
163 0         0 @mana=@$man;
164             }
165              
166 0 0       0 @mana=keys %$colorlist if (@mana==0);
167            
168 0         0 for my $man (@mana)
169             {
170 0         0 for my $key (keys %{$colorlist->{$man}})
  0         0  
171             {
172 0 0       0 if (exists($colorlist->{$man}->{$key}->{'custom'}))
173             {
174 0         0 my $f;
175 0         0 $f=$format;
176 0         0 $f=~s/%m/$man/g;
177 0         0 $f=~s/%c/$key/g;
178 0         0 $f=~s/%%/%/g;
179 0         0 push(@r,$f);
180             }
181             }
182             }
183 0         0 return @r;
184             }
185              
186             # remove keys from
187             sub custom_sub
188             {
189 0 0   0 0 0 $colorlist or get_color_list();
190 0         0 my @mankeys=keys %$colorlist;
191 0         0 my $man='';
192 0         0 my $nmk;
193 0         0 for my $t (@_)
194             {
195 0         0 my $nmk;
196 0         0 $nmk='';
197 0         0 ($nmk)=grep { $t eq $_ } @mankeys;
  0         0  
198 0 0       0 defined $nmk or $nmk='';
199             #if (0
200 0 0       0 if ($nmk ne '')
201             {
202 0         0 $man=$nmk;
203 0         0 next;
204             }
205             # else its a code.
206 0 0       0 if ($t eq 'all') # add all for current manufacturer or all manufacturer.
207             {
208 0 0       0 if ($man ne '')
209             {
210 0         0 for my $key (keys %{$colorlist->{$man}})
  0         0  
211             {
212 0         0 delete($colorlist->{$man}->{$key}->{'custom'});
213             }
214             }
215             else
216             {
217 0         0 for my $man (keys %{$colorlist})
  0         0  
218             {
219 0         0 for my $key (keys %{$colorlist->{$man}})
  0         0  
220             {
221 0         0 delete($colorlist->{$man}->{$key}->{'custom'})
222             }
223             }
224             }
225 0         0 next;
226             }
227 0 0       0 die "Error no manufacturer given in call to custom for code $t or mispelt manufacturer!" if ($man eq '');
228 0 0       0 die "Invalid code '$t' for manufacturer $man in call to custom" if (!exists($colorlist->{$man}->{$t}));
229 0         0 delete($colorlist->{$man}->{$t}->{'custom'});
230             }
231             }
232             sub code
233             {
234 1     1 0 3 my ($man,$r,$g,$b)=@_;
235 1         2 my $custom=0;
236 1         2 my @mans;
237              
238 1 50       3 $colorlist or get_color_list();
239              
240 1         5 my @mankeys=keys %$colorlist;
241 1         2 my $err=10000;
242 1         2 my $c='' ; # return value;
243 1         1 my $mk='';
244              
245 1 50       4 if (ref($man))
246             {
247 1         2 @mans=@$man;
248             }
249             else
250             {
251 0         0 @mans=($man);
252             }
253 1         3 @mans=map { split(/,/,$_) } @mans;
  1         3  
254 1         3 @mans=grep {$_ ne '' } @mans;
  0         0  
255 1 50       5 if (grep { $_ eq 'custom' } @mans )
  0         0  
256             {
257 0         0 $custom=1;
258 0         0 @mans=grep { $_ ne 'custom' } @mans;
  0         0  
259             }
260              
261 1         2 for my $mankey (@mankeys)
262             {
263 5 50 33     17 next if (@mans>0 and 0==grep {$mankey eq $_ } @mans); # only use the wanted keys;
  0         0  
264 5         6 for my $code (keys %{$colorlist->{$mankey}})
  5         417  
265             {
266             #print "#3 $mankey $code\n";
267 1554 50 33     2566 next if ($custom and !exists $colorlist->{$mankey}->{$code}->{'custom'} ) ;
268 1554         2529 my $rgb=$colorlist->{$mankey}->{$code}->{rgb};
269 1554         2747 my @rgb=@$rgb;
270 1554         2774 my $d3=($r-$rgb[0])**2+($g-$rgb[1])**2+($b-$rgb[2])**2;
271 1554         1409 $d3=sqrt($d3);
272             #print "$code ($r,$g,$b) - (@rgb) $d3\n";
273 1554 100       3379 if ($d3<$err)
274             {
275 11         12 $c=$code;
276 11         10 $err=$d3;
277 11         21 $mk=$mankey;
278             }
279             }
280             }
281 1 50       6 $err='' if ($c eq '');
282 1 50       4 if (wantarray) { return ($c,$mk,$err); }
  0         0  
283 1         48 return $c;
284             }
285             # return an error veector between 2 colours as rgb.
286             sub evecrgb
287             {
288 0     0 0 0 my ($r1,$g1,$b1,$r2,$g2,$b2)=@_;
289              
290 0         0 my ($r,$g,$b);
291              
292 0         0 ($r,$g,$b)=($r1-$r2,$g1-$g2,$b1-$b2);
293            
294 0         0 return ($r,$g,$b);
295             }
296             # return magnetude of rgb value.
297             sub mag
298             {
299 2     2 0 3 my ($r,$g,$b)=@_;
300              
301 2         5 return sqrt($r*$r+$g*$g+$b*$b);
302             }
303             # return saturation of rgb value.
304             # value returned is between 0 an 255 inclusive.
305             sub sat
306             {
307 1     1 0 1 my ($r,$g,$b)=@_;
308 1         3 my $s=0; # saturation is zero for black.
309              
310 1         2 my $w=min($r,$g,$b); # white component;
311 1         3 my $m=mag($r,$g,$b); # magnetude of given colour
312              
313 1         3 map { $_-=$w } ($r,$g,$b);
  3         5  
314              
315 1         2 my $nw=mag($r,$g,$b); # non white component;
316              
317 1 50       4 if ($m>=1)
318             {
319 1         2 $s=255*$nw/$m;
320             }
321 1         4 return $s;
322             }
323             sub min
324             {
325 1     1 0 3 my (@x)=@_;
326              
327 1         2 my $m=$x[0];
328              
329 1         1 for my $x (@x)
330             {
331 3 100       8 $m=$x if ($x<$m);
332             }
333 1         2 return $m;
334             }
335             return 1;
336             =head1 NAME
337              
338             Sew:Color - rgb colours for various manufactures of coloured embroidery thread.
339              
340             =head1 ABSTRACT
341              
342             Extensible Module for determining rgb colours of various manufacturers of embroidering thread
343             and the codes that go with them.
344              
345             =head1 SYNOPSIS
346              
347             use Sew::Color
348             my @rgb=rgb('Brother', '502');
349             my $name=name('Brother','502');
350              
351             print "$name (@rgb)\n";
352             my @m=manlist();
353              
354             =head1 DESCRIPTION
355              
356             These calls return respectively the red green and blue components of the colour of the thread
357             and the 'English' name of the thread colour. The colour components will be in the range 0 to 255.
358             In this case, Brother thread number 502.
359             Be aware that the name of the thread colour is not unique, there are some codes that have
360             the same name, although they are mostly different.
361              
362             The above code prints out
363            
364             Mint Green (148 190 140)
365              
366             code(Manufacturer,red,green.blue)
367              
368             This function does a simple search in the colour space to find the colour that is closest to the rgb values you provide.
369              
370             The parameters are
371              
372             Manufacturer: Can be a single manufacturer, a comma seperated list or an array reference of manufacturers.
373             It can be empty to search all known about.
374             red, green, blue are the colour co-ordinates to search for. Distnce is done through a very simple sequential search
375             using a simple 3-d colour model without any weightings. (so rgb all treated the same.)
376              
377             The return values are:
378              
379             In a scalar context, just the code, for example '502'.
380             In an array context it returns a 3 element array, with the following entries
381              
382             Thread code, eg '502'
383             Manufacturer, eg 'Brother'
384             Error distance, eg 42. This is the distance in linear units scaled to 255
385             between the thread found and the desired colour. Note that it can be more than 255
386             (Consider that the diagonal of a cube with side 255 is more than 255. ) but will normally
387             not be.
388              
389             Note that only one result is returned, and this ought tobe changed, all nearest results should be found.
390              
391             The function manlist() returns an array of the names of the manufacturers supported.
392              
393             =head2 Custom Searches
394              
395             If you only have certain threads that you want to search (you dont happen to have the full Madeira
396             in your store cupboard!) you can say which ones you do have by using the custom function. This is called as follows
397              
398             custom('Manufacturer',list of codes, 'Manufacturer', list of codes )
399              
400             A call to the code function with the special string 'custom' as manufacturer will search only these threads.
401              
402             custom()
403              
404             will reset all the custom threads.
405              
406             Multiple calls to custom where the argument list is not empty will add each new set to the custom search list.
407              
408             The special keyword all may be used with the custom function to either add all the threads for a manufacturer, or to add all threads of all manufacturers. so custom('Brother','all') would add all Brother threads, while custom('all') would add all known threads. Once added individual threads or sets can be removed with the custom_sub function.
409              
410             custom_sub() takes parameters similar to custom and will remove specific threads from the custom search list.
411              
412            
413              
414             =head2 Methods
415              
416             rgb(Manufacturer, code) returns a 255-max scaled rgb tripplet.
417             name(Manufacturer,code) returns the "English" name of the colour.
418             code(Manufacturer-list,r,g,b) returns either the code or an array
419             with the following: (Manufacturer,code,error distance)
420              
421             =head1 CAVEAT
422              
423             All should be aware that giving an rgb value for a thread colour will never be anything more than an approximation at best, even assuming
424             the values are right. Be aware that many thread manufacturers give or sell colour cards that have actual samples of the thread on, because even
425             using paint on paper has proved so unsatisfactory. Really I cannot say it loud enough, trying to represent real-world colours that are not
426             a photograph, using rgb values is massively approximate at best. For example, it depends on the angle of the light, the amount of
427             light, the type of light and other factors. Or it may not. I have seen materials that change colour quite noticibly depending on weather they
428             are viewed by sunlight, incandescent light or flourscent light. Its a manufacturers nightmare, but it happens.
429              
430             =head1 PROCESS
431              
432             In the main these values were derived by me by taking a web page which has a photograph of the thread, cropping it to remove anything like a shadow,
433             changing the size to 1 by 1 pixcel (so that all other pixcels are averaged) and then listing the colour of that pixcel.
434              
435             This results in rather real-world values - the extreme ends of the scale near 0 and 255 do not appear and the colours are a bit less saturated than...
436             well then you might think.
437              
438             Sulky helpfully provide a spreadsheet with rgb values. It would be a bit silly not to use it, wouldnt it? But the truth is that the values
439             you get are very different since they have clearly been normalised in some way so that blacks are fully black and whites are fully white.
440              
441             For example, Sulky "Black" 942-1005 has rgb values (0,0,0) in the spreadsheet. But using the other method, has rgb values (44,42,44).
442              
443             Which is right? The answer is of course that both are, and you need to use the values obtained carefully and sensibly, processing them if needed.
444              
445             Sulky do this (perhaps) because in part you are throwing away some of the precision in your 8 bit representation if you say the lowest value
446             I am going to have is 42. They are (probably) not happy using 8 bits any way, because from there perspective this is not much precision to
447             represent a world of colour, why throw some of it away?
448              
449             Which Sulky values did I include? In the end I included the real-world values since thats more compatible with the other manufacturers in the
450             package. Let me know if you think I should do other wise. It also allows me to easily include varigated threads (that have a delibneratly
451             variable colour along its length) since this will be correctly averaged.
452              
453             =head2 EXTENSION
454              
455             The module may be extended to a new manufacturer by adding lines of the following format to the module:
456              
457             manufacturer,english name,code,red,green,blue
458              
459             for example the line
460             Brother,Moss Green,515,48,125,38
461              
462             is responsible for the Moss Green number 515 entry.
463              
464             =head1 BUGS and the like
465              
466             There are many manufacturers not covered.
467              
468             If you use this please drop me an email to say it has been useful (or not) to you.
469              
470             The sat() function generally returned 255 in version 1.04. This is fixed in 1.05
471              
472             =head1 AUTHOR
473              
474             Mark Winder June 2012.
475             markwin (at) cpan.org
476              
477             =cut
478              
479             __DATA__