File Coverage

blib/lib/Image/Base/Gtk2/Gdk/Pixbuf.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             # Copyright 2010, 2011, 2012 Kevin Ryde
2              
3             # This file is part of Image-Base-Gtk2.
4             #
5             # Image-Base-Gtk2 is free software; you can redistribute it and/or modify it
6             # under the terms of the GNU General Public License as published by the Free
7             # Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Image-Base-Gtk2 is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Image-Base-Gtk2. If not, see .
17              
18              
19             package Image::Base::Gtk2::Gdk::Pixbuf;
20 2     2   36278 use 5.008;
  2         14  
  2         258  
21 2     2   15 use strict;
  2         4  
  2         89  
22 2     2   12 use warnings;
  2         3  
  2         104  
23 2     2   22 use Carp;
  2         13  
  2         223  
24 2     2   3714 use Gtk2;
  0            
  0            
25             use List::Util 'min','max';
26             use Image::Base 1.12; # version 1.12 for ellipse() $fill
27              
28             our $VERSION = 11;
29             our @ISA = ('Image::Base');
30              
31             # uncomment this to run the ### lines
32             #use Smart::Comments;
33              
34              
35             sub new {
36             my ($class, %params) = @_;
37             ### Gdk-Pixbuf new: \%params
38              
39             my $self;
40             my $filename = delete $params{'-file'};
41              
42             # $obj->new(...) means make a copy, with some extra settings
43             if (ref $class) {
44             $self = bless { %$class }, ref $class;
45             if (defined $filename) {
46             $self->load ($filename);
47             } elsif (! defined $params{'-pixbuf'}) {
48             $self->{'-pixbuf'} = $self->{'-pixbuf'}->copy;
49             }
50              
51             } else {
52             if (! defined $filename) {
53             if (! $params{'-pixbuf'}) {
54             ### create new GdkPixbuf
55              
56             my $pixbuf = $params{'-pixbuf'} = Gtk2::Gdk::Pixbuf->new
57             (delete $params{'-colorspace'} || 'rgb',
58             delete $params{'-has_alpha'},
59             delete $params{'-bits_per_sample'} || 8,
60             delete $params{'-width'},
61             delete $params{'-height'});
62             $pixbuf->fill (0xFF000000);
63             }
64             }
65             $self = bless {}, $class;
66             if (defined $filename) {
67             $self->load ($filename);
68             }
69             $self->set (%params);
70             }
71              
72             return $self;
73             }
74              
75             my %attr_to_get_method = (-has_alpha => 'get_has_alpha',
76             -colorspace => 'get_colorspace',
77             -width => 'get_width',
78             -height => 'get_height',
79             );
80             my %attr_to_get_option = (-hotx => 'x_hot',
81             -hoty => 'y_hot',
82             );
83             sub _get {
84             my ($self, $key) = @_;
85             if (my $method = $attr_to_get_method{$key}) {
86             return $self->{'-pixbuf'}->$method;
87             }
88             if ((my $option = $attr_to_get_option{$key})
89             && ! exists $self->{$key}) {
90             ### get_option(): $option
91             return $self->{'-pixbuf'}->get_option($option);
92             }
93             return $self->SUPER::_get($key);
94             }
95              
96             sub set {
97             my ($self, %params) = @_;
98             ### Image-Base-Pixbuf set(): \%params
99              
100             if (my $pixbuf = $params{'-pixbuf'}) {
101             $pixbuf->get_bits_per_sample == 8
102             or croak "Only pixbufs of 8 bits per sample supported";
103             $pixbuf->get_colorspace eq 'rgb'
104             or croak "Only pixbufs of 'rgb' colorspace supported";
105              
106             delete @{$self}{keys %attr_to_get_option}; # hash slice
107             }
108              
109             foreach my $key (keys %params) {
110             if (my $method = $attr_to_get_method{$key}) {
111             croak "$key is read-only";
112             }
113             }
114              
115             %$self = (%$self, %params);
116             ### set leaves: $self
117             }
118              
119             sub load {
120             my ($self, $filename) = @_;
121             if (@_ == 1) {
122             $filename = $self->get('-file');
123             } else {
124             $self->set('-file', $filename);
125             }
126             ### load: $filename
127              
128             # Gtk2::Gdk::Pixbuf->new_from_file doesn't seem to give back the format
129             # used to load, so go to PixbufLoader in load_fh()
130             open my $fh, '<', $filename or croak "Cannot open $filename: $!";
131             binmode ($fh) or die "Oops, cannot set binmode: $!";
132             $self->load_fh ($fh);
133             close $fh or croak "Error closing $filename: $!";
134             }
135              
136             sub load_fh {
137             my ($self, $fh, $filename) = @_;
138             ### load_fh()
139             my $loader = Gtk2::Gdk::PixbufLoader->new;
140             for (;;) {
141             my $buf;
142             my $len = read ($fh, $buf, 8192);
143             if (! defined $len) {
144             croak "Error reading file",
145             (defined $filename ? (' ',$filename) : ()),
146             ": $!";
147             }
148             if ($len == 0) {
149             last;
150             }
151             $loader->write ($buf);
152             }
153             $loader->close;
154             $self->set (-pixbuf => $loader->get_pixbuf,
155             -file_format => $loader->get_format->{'name'});
156             ### loaded format: $self->{'-file_format'}
157             }
158              
159             sub load_string {
160             my ($self, $str) = @_;
161             ### load_string()
162             my $loader = Gtk2::Gdk::PixbufLoader->new;
163             $loader->write ($str);
164             $loader->close;
165             $self->set (-pixbuf => $loader->get_pixbuf,
166             -file_format => $loader->get_format->{'name'});
167             ### loaded format: $self->{'-file_format'}
168             }
169              
170             sub save {
171             my ($self, $filename) = @_;
172             ### Image-Base-Pixbuf save(): @_
173             if (@_ == 2) {
174             $self->set('-file', $filename);
175             } else {
176             $filename = $self->get('-file');
177             }
178             ### $filename
179              
180             my $file_format = $self->get('-file_format');
181             if (! defined $file_format) {
182             $file_format = _filename_to_format($filename);
183             if (! defined $file_format) {
184             croak 'No -file_format set';
185             }
186             }
187              
188             # cf Gtk2::Ex::PixbufBits save_adapt()
189             my @options;
190             $file_format = lc($file_format);
191             if ($file_format eq 'png') {
192             if (Gtk2->check_version(2,8,0)
193             && defined (my $zlib_compression = $self->get('-zlib_compression'))) {
194             @options = (compress => $zlib_compression)
195             }
196             } elsif ($file_format eq 'jpeg') {
197             if (defined (my $quality = $self->get('-quality_percent'))) {
198             @options = (quality => $quality)
199             }
200             } elsif ($file_format eq 'ico') {
201             if (defined (my $x_hot = $self->get('-hotx'))) {
202             @options = (x_hot => $x_hot);
203             }
204             if (defined (my $y_hot = $self->get('-hoty'))) {
205             push @options, y_hot => $y_hot;
206             }
207             }
208             ### @options
209             $self->{'-pixbuf'}->save ($filename, $file_format, @options);
210             }
211              
212             sub _filename_to_format {
213             my ($filename) = @_;
214             $filename =~ /\.([a-z]+)$/i or return undef;
215             my $ext = lc($1);
216             foreach my $format (Gtk2::Gdk::Pixbuf->get_formats) {
217             foreach my $fext (@{$format->{'extensions'}}) {
218             if ($ext eq $fext) {
219             return $format->{'name'};
220             }
221             }
222             }
223             }
224              
225             #------------------------------------------------------------------------------
226             # drawing
227              
228             sub xy {
229             my ($self, $x, $y, $colour) = @_;
230              
231             my $pixbuf = $self->{'-pixbuf'};
232              
233             unless ($x >= 0
234             && $y >= 0
235             && $x < $pixbuf->get_width
236             && $y < $pixbuf->get_height) {
237             ### outside 0,0,width,height ...
238             return undef; # fetch or store
239             }
240              
241             if (@_ >= 4) {
242             ### Image-GdkPixbuf xy: "$x, $y, $colour"
243             my $data;
244             my $has_alpha = $pixbuf->get_has_alpha;
245             if (lc($colour) eq 'none') {
246             if (! $has_alpha) {
247             croak "pixbuf has no alpha channel for colour None";
248             }
249             $data = "\0\0\0\0";
250             } else {
251             my $colorobj = $self->colour_to_colorobj($colour);
252             $data = pack ('CCC',
253             $colorobj->red >> 8,
254             $colorobj->green >> 8,
255             $colorobj->blue >> 8)
256             . "\xFF"; # alpha
257             }
258              
259             # $pixbuf->fill($pixel) would also be possible, but new_from_data()
260             # saves a separate create and fill
261             ### $data
262             my $src_pixbuf = Gtk2::Gdk::Pixbuf->new_from_data
263             ($data,
264             'rgb',
265             $has_alpha,
266             8, # bits per sample
267             1,1, # width,height
268             4); # rowstride
269             $src_pixbuf->copy_area (0,0, # src x,y
270             1,1, # src width,height
271             $pixbuf, # dest
272             $x,$y); # dest x,y
273             ### leaves: $pixbuf->get_pixels
274              
275             } else {
276             my $n_channels = $pixbuf->get_n_channels;
277             my $rgba = substr ($pixbuf->get_pixels,
278             $y*$pixbuf->get_rowstride() + $x*$n_channels,
279             $n_channels);
280             ### Image-GdkPixbuf xy fetch: "$x, $y"
281             ### $n_channels
282             ### has_alpha: $pixbuf->get_has_alpha
283             ### $rgba
284             if (substr($rgba,3,1) eq "\0") {
285             return 'None';
286             }
287             return sprintf '#%02X%02X%02X', unpack 'CCC', $rgba;
288             }
289             }
290              
291             sub line {
292             my ($self, $x1,$y1, $x2,$y2, $colour) = @_;
293             if ($x1 == $x2 || $y1 == $y2) {
294             # solid horizontal or vertical
295             shift->rectangle (@_, 1);
296             } else {
297             shift->SUPER::line (@_);
298             }
299             }
300              
301             sub rectangle {
302             my ($self, $x1,$y1, $x2,$y2, $colour, $fill) = @_;
303             ### Pixbuf rectangle(): "$x1,$y1, $x2,$y2, $colour, ".($fill||0)
304              
305             # sort coordinates as they could be the wrong way around from line()
306             ($x1,$x2) = (min($x1,$x2), max($x1,$x2));
307             ($y1,$y2) = (min($y1,$y2), max($y1,$y2));
308              
309             my $pixbuf = $self->{'-pixbuf'};
310             my $pixbuf_width = $pixbuf->get_width;
311             my $pixbuf_height = $pixbuf->get_height;
312              
313             unless ($x2 >= 0
314             && $y2 >= 0
315             && $x1 < $pixbuf_width
316             && $y1 < $pixbuf_height) {
317             ### entirely outside 0,0,width,height ...
318             return;
319             }
320              
321             if ($fill || $x2-$x1 <= 1 || $y2-$y1 <= 1) {
322             ### filled rectangle by copy_area() ...
323              
324             $x1 = max ($x1, 0);
325             $y1 = max ($y1, 0);
326             $x2 = min ($x2, $pixbuf_width-1);
327             $y2 = min ($y2, $pixbuf_height-1);
328              
329             my $w = $x2 - $x1 + 1;
330             my $h = $y2 - $y1 + 1;
331              
332             my $has_alpha = $pixbuf->get_has_alpha;
333             my $pixel;
334             if (lc($colour) eq 'none') {
335             if (! $has_alpha) {
336             croak "pixbuf has no alpha channel for colour None";
337             }
338             $pixel = 0;
339             } else {
340             my $colorobj = $self->colour_to_colorobj($colour);
341             $pixel = (( ($colorobj->red & 0xFF00) << 16)
342             + (($colorobj->green & 0xFF00) << 8)
343             + ($colorobj->blue & 0xFF00)
344             + 0xFF);
345             }
346             my $src_pixbuf = Gtk2::Gdk::Pixbuf->new
347             ('rgb',
348             $has_alpha,
349             8, # bits per sample
350             $w,$h); # width,height
351             $src_pixbuf->fill ($pixel);
352              
353             ### copy_area: "to $x1,$y1 size $w,$h"
354             $src_pixbuf->copy_area (0,0, # src x,y
355             $w,$h, # src width,height
356             $pixbuf, # dest
357             $x1,$y1); # dest x,y
358             } else {
359             shift->SUPER::rectangle(@_);
360             }
361             }
362              
363             my %colorobj = (set => Gtk2::Gdk::Color->new (0xFF,0xFF,0xFF, 1),
364             clear => Gtk2::Gdk::Color->new (0,0,0, 0));
365             # not documented ...
366             sub colour_to_colorobj {
367             my ($self, $colour) = @_;
368             if (my $colorobj = $colorobj{lc($colour)}) {
369             return $colorobj;
370             }
371             my $colorobj = Gtk2::Gdk::Color->parse ($colour)
372             || croak "Cannot parse colour: $colour";
373             return $colorobj;
374             }
375              
376             1;
377             __END__