File Coverage

blib/lib/Imager/Fountain.pm
Criterion Covered Total %
statement 100 108 92.5
branch 33 38 86.8
condition 7 12 58.3
subroutine 11 11 100.0
pod 5 5 100.0
total 156 174 89.6


line stmt bran cond sub pod time code
1             package Imager::Fountain;
2 3     3   2787 use 5.006;
  3         10  
3 3     3   31 use strict;
  3         6  
  3         80  
4 3     3   22 use Imager::Color::Float;
  3         7  
  3         3558  
5              
6             our $VERSION = "1.009";
7              
8             =head1 NAME
9              
10             Imager::Fountain - a class for building fountain fills suitable for use by
11             the fountain filter.
12              
13             =head1 SYNOPSIS
14              
15             use Imager::Fountain;
16             my $f1 = Imager::Fountain->read(gimp=>$filename);
17             $f->write(gimp=>$filename);
18             my $f1 = Imager::Fountain->new;
19             $f1->add(start=>0, middle=>0.5, end=>1.0,
20             c0=>Imager::Color->new(...),
21             c1=>Imager::Color->new(...),
22             type=>$trans_type, color=>$color_trans_type);
23              
24             =head1 DESCRIPTION
25              
26             Provide an interface to build arrays suitable for use by the Imager
27             fountain filter. These can be loaded from or saved to a GIMP gradient
28             file or you can build them from scratch.
29              
30             =over
31              
32             =item read(gimp=>$filename)
33              
34             =item read(gimp=>$filename, name=>\$name)
35              
36             Loads a gradient from the given GIMP gradient file, and returns a
37             new Imager::Fountain object.
38              
39             If the name parameter is supplied as a scalar reference then any name
40             field from newer GIMP gradient files will be returned in it.
41              
42             my $gradient = Imager::Fountain->read(gimp=>'foo.ggr');
43             my $name;
44             my $gradient2 = Imager::Fountain->read(gimp=>'bar.ggr', name=>\$name);
45              
46             =cut
47              
48             sub read {
49 11     11 1 5821 my ($class, %opts) = @_;
50              
51 11 100       26 if ($opts{gimp}) {
52 10         13 my $fh;
53 10 100       18 if (ref($opts{gimp})) {
54 1         3 $fh = $opts{gimp};
55             }
56             else {
57 9 100       296 unless (open $fh, "<", $opts{gimp}) {
58 1         9 $Imager::ERRSTR = "Cannot open $opts{gimp}: $!";
59 1         8 return;
60             }
61             }
62              
63 9         19 my $trash_name;
64 9 100 66     36 my $name_ref = $opts{name} && ref $opts{name} ? $opts{name} : \$trash_name;
65              
66 9         37 return $class->_load_gimp_gradient($fh, $opts{gimp}, $name_ref);
67             }
68             else {
69 1         18 warn "${class}::read: Nothing to do!";
70 1         9 return;
71             }
72             }
73              
74             =item write(gimp=>$filename)
75              
76             =item write(gimp=>$filename, name=>$name)
77              
78             Save the gradient to a GIMP gradient file.
79              
80             The second variant allows the gradient name to be set (for newer
81             versions of the GIMP).
82              
83             $gradient->write(gimp=>'foo.ggr')
84             or die Imager->errstr;
85             $gradient->write(gimp=>'bar.ggr', name=>'the bar gradient')
86             or die Imager->errstr;
87              
88             =cut
89              
90             sub write {
91 2     2 1 771 my ($self, %opts) = @_;
92              
93 2 50       7 if ($opts{gimp}) {
94 2         2 my $fh;
95 2 50       4 if (ref($opts{gimp})) {
96 0         0 $fh = $opts{gimp};
97             }
98             else {
99 2 50       123 unless (open $fh, ">", $opts{gimp}) {
100 0         0 $Imager::ERRSTR = "Cannot open $opts{gimp}: $!";
101 0         0 return;
102             }
103             }
104              
105 2         11 return $self->_save_gimp_gradient($fh, $opts{gimp}, $opts{name});
106             }
107             else {
108 0         0 warn "Nothing to do\n";
109 0         0 return;
110             }
111             }
112              
113             =item new
114              
115             Create an empty fountain fill description.
116              
117             =cut
118              
119             sub new {
120 5     5 1 30 my ($class) = @_;
121              
122 5         16 return bless [], $class;
123             }
124              
125             sub _first {
126 56     56   91 for (@_) {
127 99 100       261 return $_ if defined;
128             }
129 0         0 return undef;
130             }
131              
132             =item add(start=>$start, middle=>$middle, end=>1.0, c0=>$start_color, c1=>$end_color, type=>$trans_type, color=>$color_trans_type)
133              
134             Adds a new segment to the fountain fill, the possible options are:
135              
136             =over
137              
138             =item *
139              
140             C - the start position in the gradient where this segment takes
141             effect between 0 and 1. Default: 0.
142              
143             =item *
144              
145             C - the mid-point of the transition between the 2
146             colors, between 0 and 1. Default: average of C and C.
147              
148             =item *
149              
150             C - the end of the gradient, from 0 to 1. Default: 1.
151              
152             =item *
153              
154             C - the color of the fountain fill where the fill parameter is
155             equal to I. Default: opaque black.
156              
157             =item *
158              
159             C - the color of the fountain fill where the fill parameter is
160             equal to I. Default: opaque black.
161              
162             =item *
163              
164             C - the type of segment, controls the way in which the fill parameter
165             moves from 0 to 1. Default: linear.
166              
167             This can take any of the following values:
168              
169             =over
170              
171             =item *
172              
173             C
174              
175             =item *
176              
177             C - unimplemented so far.
178              
179             =item *
180              
181             C
182              
183             =item *
184              
185             C
186              
187             =item *
188              
189             C
190              
191             =back
192              
193             =item *
194              
195             C - the way in which the color transitions between C and C.
196             Default: direct.
197              
198             This can take any of the following values:
199              
200             =over
201              
202             =item *
203              
204             C - each channel is simple scaled between c0 and c1.
205              
206             =item *
207              
208             C - the color is converted to a HSV value and the scaling is
209             done such that the hue increases as the fill parameter increases.
210              
211             =item *
212              
213             C - the color is converted to a HSV value and the scaling is
214             done such that the hue decreases as the fill parameter increases.
215              
216             =back
217              
218             =back
219              
220             In most cases you can ignore some of the arguments, eg.
221              
222             # assuming $f is a new Imager::Fountain in each case here
223             use Imager ':handy';
224             # simple transition from red to blue
225             $f->add(c0=>NC('#FF0000'), c1=>NC('#0000FF'));
226             # simple 2 stages from red to green to blue
227             $f->add(end=>0.5, c0=>NC('#FF0000'), c1=>NC('#00FF00'))
228             $f->add(start=>0.5, c0=>NC('#00FF00'), c1=>NC('#0000FF'));
229              
230             =cut
231              
232             # used to translate segment types and color transition types to numbers
233             my %type_names =
234             (
235             linear => 0,
236             curved => 1,
237             sine => 2,
238             sphereup=> 3,
239             spheredown => 4,
240             );
241              
242             my %color_names =
243             (
244             direct => 0,
245             hueup => 1,
246             huedown => 2
247             );
248              
249             sub add {
250 8     8 1 35 my ($self, %opts) = @_;
251              
252 8         21 my $start = _first($opts{start}, 0);
253 8         20 my $end = _first($opts{end}, 1);
254 8         31 my $middle = _first($opts{middle}, ($start+$end)/2);
255             my @row =
256             (
257             $start, $middle, $end,
258             _first($opts{c0}, Imager::Color::Float->new(0,0,0,1)),
259             _first($opts{c1}, Imager::Color::Float->new(1,1,1,0)),
260             _first($opts{type} && $type_names{$opts{type}}, $opts{type}, 0),
261 8   33     48 _first($opts{color} && $color_names{$opts{color}}, $opts{color}, 0)
      66        
262             );
263 8         480 push(@$self, \@row);
264              
265 8         40 $self;
266             }
267              
268             =item simple(positions=>[ ... ], colors=>[...])
269              
270             Creates a simple fountain fill object consisting of linear segments.
271              
272             The array references passed as positions and colors must have the same
273             number of elements. They must have at least 2 elements each.
274              
275             colors must contain Imager::Color or Imager::Color::Float objects.
276              
277             eg.
278              
279             my $f = Imager::Fountain->simple(positions=>[0, 0.2, 1.0],
280             colors=>[ NC(255,0,0), NC(0,255,0),
281             NC(0,0,255) ]);
282              
283             =cut
284              
285             sub simple {
286 5     5 1 53 my ($class, %opts) = @_;
287              
288 5 100 66     25 if ($opts{positions} && $opts{colors}) {
289 4         9 my $positions = $opts{positions};
290 4         6 my $colors = $opts{colors};
291 4 100       12 unless (@$positions == @$colors) {
292 1         2 $Imager::ERRSTR = "positions and colors must be the same size";
293 1         3 return;
294             }
295 3 100       9 unless (@$positions >= 2) {
296 1         3 $Imager::ERRSTR = "not enough segments";
297 1         7 return;
298             }
299 2         5 my $f = $class->new;
300 2         10 for my $i (0.. $#$colors-1) {
301 3         14 $f->add(start=>$positions->[$i], end=>$positions->[$i+1],
302             c0 => $colors->[$i], c1=>$colors->[$i+1]);
303             }
304 2         12 return $f;
305             }
306             else {
307 1         11 warn "Nothing to do";
308 1         8 return;
309             }
310             }
311              
312             =back
313              
314             =head2 Implementation Functions
315              
316             Documented for internal use.
317              
318             =over
319              
320             =item _load_gimp_gradient($class, $fh, $name)
321              
322             Does the work of loading a GIMP gradient file.
323              
324             =cut
325              
326             sub _load_gimp_gradient {
327 9     9   19 my ($class, $fh, $filename, $name) = @_;
328              
329 9         144 my $head = <$fh>;
330 9         20 chomp $head;
331 9 100       22 unless ($head eq 'GIMP Gradient') {
332 1         4 $Imager::ERRSTR = "$filename is not a GIMP gradient file";
333 1         16 return;
334             }
335 8         18 my $count = <$fh>;
336 8         10 chomp $count;
337 8 100       24 if ($count =~ /^name:\s?(.*)/i) {
338 3 50       15 ref $name and $$name = $1;
339 3         6 $count = <$fh>; # try again
340 3         4 chomp $count;
341             }
342 8 100       32 unless ($count =~ /^\d+$/) {
343 1         5 $Imager::ERRSTR = "$filename is missing the segment count";
344 1         20 return;
345             }
346 7         10 my @result;
347 7         24 for my $i (1..$count) {
348 11         20 my $row = <$fh>;
349 11         14 chomp $row;
350 11         45 my @row = split ' ', $row;
351 11 100       24 unless (@row == 13) {
352 1         5 $Imager::ERRSTR = "Bad segment definition";
353 1         19 return;
354             }
355 10         23 my ($start, $middle, $end) = splice(@row, 0, 3);
356 10         44 my $c0 = Imager::Color::Float->new(splice(@row, 0, 4));
357 10         42 my $c1 = Imager::Color::Float->new(splice(@row, 0, 4));
358 10         33 my ($type, $color) = @row;
359 10         45 push(@result, [ $start, $middle, $end, $c0, $c1, $type, $color ]);
360             }
361 6         133 return bless \@result,
362             }
363              
364             =item _save_gimp_gradient($self, $fh, $name)
365              
366             Does the work of saving to a GIMP gradient file.
367              
368             =cut
369              
370             sub _save_gimp_gradient {
371 2     2   7 my ($self, $fh, $filename, $name) = @_;
372              
373 2         8 print $fh "GIMP Gradient\n";
374 2 100       5 defined $name or $name = '';
375 2         6 $name =~ tr/ -~/ /cds;
376 2 100       6 if ($name) {
377 1         4 print $fh "Name: $name\n";
378             }
379 2         7 print $fh scalar(@$self),"\n";
380 2         5 for my $row (@$self) {
381 4         5 printf $fh "%.6f %.6f %.6f ",@{$row}[0..2];
  4         36  
382 4         9 for my $i (0, 1) {
383 8         23 for ($row->[3+$i]->rgba) {
384 32         92 printf $fh "%.6f ", $_/255.0;
385             }
386             }
387 4         6 print $fh "@{$row}[5,6]";
  4         9  
388 4 50       11 unless (print $fh "\n") {
389 0         0 $Imager::ERRSTR = "write error: $!";
390 0         0 return;
391             }
392             }
393              
394 2         69 return 1;
395             }
396              
397             =back
398              
399             =head1 FILL PARAMETER
400              
401             The add() documentation mentions a fill parameter in a few places,
402             this is as good a place as any to discuss it.
403              
404             The process of deciding the color produced by the gradient works
405             through the following steps:
406              
407             =over
408              
409             =item 1.
410              
411             calculate the base value, which is typically a distance or an angle of
412             some sort. This can be positive or occasionally negative, depending on
413             the type of fill being performed (linear, radial, etc).
414              
415             =item 2.
416              
417             clamp or convert the base value to the range 0 through 1, how this is
418             done depends on the repeat parameter. I'm calling this result the
419             fill parameter.
420              
421             =item 3.
422              
423             the appropriate segment is found. This is currently done with a
424             linear search, and the first matching segment is used. If there is no
425             matching segment the pixel is not touched.
426              
427             =item 4.
428              
429             the fill parameter is scaled from 0 to 1 depending on the segment type.
430              
431             =item 5.
432              
433             the color produced, depending on the segment color type.
434              
435             =back
436              
437             =head1 AUTHOR
438              
439             Tony Cook
440              
441             =head1 SEE ALSO
442              
443             Imager(3)
444              
445             =cut