File Coverage

blib/lib/Flame/Palette.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Flame::Palette;
2              
3 1     1   23267 use 5.008;
  1         3  
  1         39  
4 1     1   5 use strict;
  1         2  
  1         55  
5 1     1   20 use warnings;
  1         12  
  1         33  
6              
7 1     1   5 use Carp 'croak';
  1         2  
  1         90  
8 1     1   453 use Math::Interpolator::Linear;
  0            
  0            
9             use Math::Interpolator::Knot;
10             use XML::Parser;
11             use XML::Writer;
12              
13             require Exporter;
14              
15             our @ISA = qw(Exporter);
16              
17             # Items to export into callers namespace by default. Note: do not export
18             # names by default without a very good reason. Use EXPORT_OK instead.
19             # Do not simply export all your public functions/methods/constants.
20              
21             # This allows declaration use Flame::Palette ':all';
22             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
23             # will save memory.
24             our %EXPORT_TAGS = ( 'all' => [ qw(
25            
26             ) ] );
27              
28             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
29              
30             our @EXPORT = qw(
31            
32             );
33              
34             our $VERSION = '0.02';
35              
36              
37             # Preloaded methods go here.
38              
39             sub new {
40             my $class = shift;
41              
42             return bless {}, $class;
43             }
44              
45             sub check_integer {
46             my $self = shift;
47             my $subject = shift;
48             my $name = shift;
49              
50             croak "undefined subject" unless defined $subject;
51              
52             unless($subject =~ /^\d+$/ && $subject >= 0 && $subject <= 255) {
53             croak "invalid $name '$subject'";
54             }
55             }
56              
57             sub set {
58             my($self, %stuff) = @_;
59              
60             croak "missing index argument" unless exists $stuff{index};
61              
62             my $index = $stuff{index};
63              
64             $self->check_integer($index, 'index');
65              
66             if(exists $stuff{red} && exists $stuff{green} && exists $stuff{blue}) {
67             foreach(qw(red green blue)) {
68             $self->check_integer($stuff{$_}, $_);
69             }
70              
71             my($r, $g, $b) = map { $stuff{$_} } qw(red green blue);
72              
73             $self->{data}->[$index] = [$r, $g, $b];
74              
75             return 1;
76             }
77              
78             croak "expect index, red, green, blue arguments";
79             }
80              
81             sub get {
82             my($self, %stuff) = @_;
83              
84             croak "missing 'index' argument" unless exists $stuff{index};
85              
86             my $index = $stuff{index};
87              
88             $self->check_integer($index, 'index');
89              
90             my @data = (0, 0, 0);
91              
92             if(exists $self->{data} && $index < @{$self->{data}}) {
93             @data = @{$self->{data}->[$index] || []};
94             }
95              
96             if(wantarray) {
97             return @data;
98             }
99              
100             return \@data;
101             }
102              
103             sub unparse_xml {
104             my($self, $stream) = @_;
105              
106             my $w = XML::Writer->new(OUTPUT => $stream);
107              
108             $w->startTag('palette');
109              
110             foreach my $i (0...255) {
111             my($r, $g, $b) = $self->get(index => $i);
112              
113             $w->emptyTag('color', index => $i, rgb => join ' ', $r, $g, $b);
114             }
115              
116             $w->endTag;
117             }
118              
119             sub clear {
120             my $self = shift;
121              
122             delete $self->{data};
123             }
124              
125             sub parse_flame {
126             my($self, $stream) = @_;
127              
128             $self->clear;
129              
130             my $ok = 0;
131             my $done = 0;
132              
133             my $s_start = sub {
134             my(undef, $tag, %attr) = @_;
135              
136             unless($done) {
137             if($tag =~ /^flame$/) {
138             $ok = 1;
139             } elsif($ok) {
140             if($tag =~ /^color$/) {
141             croak "missing 'index' attribute in 'color' tag" unless exists $attr{index};
142             croak "missing 'rgb' attribute in 'color' tag" unless exists $attr{rgb};
143              
144             my($red, $green, $blue) = split '\D+', $attr{rgb};
145              
146             $self->set(index => $attr{index},
147             red => $red,
148             green => $green,
149             blue => $blue);
150             }
151             }
152             }
153             };
154              
155             my $s_end = sub {
156             my(undef, $tag) = @_;
157              
158             unless($done) {
159             if($tag =~ /^flame$/) {
160             $done = 1;
161             }
162             }
163             };
164              
165             XML::Parser->new(Handlers => { Start => $s_start, End => $s_end })->parse($stream);
166             }
167              
168             sub parse_xml {
169             my($self, $stream) = @_;
170              
171             $self->clear;
172              
173             my $level = 0;
174              
175             my $s_start = sub {
176             my(undef, $tag, %attr) = @_;
177              
178             if($tag =~ /^palette$/ && $level == 0) {
179             if(keys %attr != 0) {
180             croak "the 'palette' tag doesn't accept attributes";
181             }
182             } elsif($tag =~ /^color$/ && $level == 1) {
183             croak "missing 'index' attribute" unless exists $attr{index};
184             croak "missing 'rgb' attribute" unless exists $attr{rgb};
185              
186             my $index = $attr{index};
187             my $rgb = $attr{rgb};
188              
189             delete $attr{index};
190             delete $attr{rgb};
191              
192             if(keys %attr != 0) {
193             croak "the 'color' tag doesn't accept attributes but index,rgb";
194             }
195              
196             my($red, $green, $blue) = split '\D+', $rgb;
197              
198             $self->set(index => $index, red => $red, green => $green, blue => $blue);
199             } else {
200             croak "unknown tag '$tag'";
201             }
202              
203             $level++;
204             };
205              
206             my $s_end = sub { $level-- };
207              
208             XML::Parser->new(Handlers => { Start => $s_start, End => $s_end })->parse($stream);
209             }
210              
211             sub interpolate {
212             my $self = shift;
213              
214             my $i = 0;
215              
216             while($i < 255) {
217             if(defined $self->{data}->[$i] && !defined $self->{data}->[$i + 1]) {
218             my $j = $i + 1;
219              
220             while($j < 255 && !defined $self->{data}->[$j]) {
221             $j++;
222             }
223              
224             my($fr, $fg, $fb) = $self->get(index => $i);
225             my($tr, $tg, $tb) = $self->get(index => $j);
226              
227             my $ci = sub {
228             push my @p, Math::Interpolator::Knot->new($i, $_[0]);
229             push @p, Math::Interpolator::Knot->new($j, $_[1]);
230              
231             my $ipl = Math::Interpolator::Linear->new(@p);
232              
233             sub { int($ipl->y($_[0])) }
234             };
235              
236             my $r_ip = $ci->($fr, $tr);
237             my $g_ip = $ci->($fg, $tg);
238             my $b_ip = $ci->($fb, $tb);
239              
240             foreach($i...$j) {
241             $self->set(index => $_,
242             red => $r_ip->($_),
243             green => $g_ip->($_),
244             blue => $b_ip->($_));
245             }
246             }
247              
248             $i++;
249             }
250             }
251              
252             # TODO: CHECK DOCUMENTATION
253              
254             1;
255             __END__