File Coverage

blib/lib/Gtk2/Ex/PixbufBits.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 2007, 2008, 2009, 2010, 2011, 2012 Kevin Ryde
2              
3             # This file is part of Gtk2-Ex-WidgetBits.
4             #
5             # Gtk2-Ex-WidgetBits is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU General Public License as published
7             # by the Free Software Foundation; either version 3, or (at your option) any
8             # later version.
9             #
10             # Gtk2-Ex-WidgetBits is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13             # Public License for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Gtk2-Ex-WidgetBits. If not, see .
17              
18             package Gtk2::Ex::PixbufBits;
19 2     2   3085 use 5.008;
  2         8  
  2         118  
20 2     2   13 use strict;
  2         3  
  2         77  
21 2     2   18 use warnings;
  2         5  
  2         81  
22 2     2   15 use Carp;
  2         3  
  2         188  
23 2     2   11441 use Gtk2;
  0            
  0            
24             use List::Util;
25              
26             use Exporter;
27             our @ISA = ('Exporter');
28             our @EXPORT_OK = qw(type_to_format
29             save_adapt
30             save_adapt_options
31             sampled_majority_color);
32              
33             our $VERSION = 48;
34              
35             # uncomment this to run the ### lines
36             #use Smart::Comments;
37              
38             #------------------------------------------------------------------------------
39              
40             sub type_to_format {
41             my ($type) = @_;
42             return List::Util::first {$_->{'name'} eq $type}
43             Gtk2::Gdk::Pixbuf->get_formats;
44             }
45              
46             # Not absolutely sure of the BMP limit, but io-bmp.c reads and writes it as
47             # a signed 32-bit, hence 2^31-1.
48             #
49             # PNG spec requires width>=1 and height>=1.
50             #
51             my %type_max_size = (ico => 255,
52             jpeg => 65500,
53             png => 0x7FFF_FFFF,
54             bmp => 0x7FFF_FFFF,
55             tiff => 0xFFFF_FFFF,
56             );
57             sub type_max_size {
58             my ($type) = @_;
59             if (my $size = $type_max_size{$type}) {
60             return ($size, $size);
61             } else {
62             return;
63             }
64             }
65             sub type_supports_size {
66             my ($type, $width, $height) = @_;
67             if (my $size = $type_max_size{$type}) {
68             if ($width > $size || $height > $size) {
69             return 0;
70             }
71             }
72             return 1;
73             }
74              
75             #------------------------------------------------------------------------------
76              
77             # Could extract the tEXts from get_option() as defaults to save back. But
78             # can't list what's in there, so maybe only the png specified ones.
79              
80             sub save_adapt {
81             my $pixbuf = shift; # ($pixbuf, $filename, $type, key=>value, ...)
82             $pixbuf->save (save_adapt_options($pixbuf, @_));
83             }
84              
85             my %tiff_compression_types = (none => 1,
86             huffman => 2,
87             lzw => 5,
88             jpeg => 7,
89             deflate => 8);
90              
91             sub save_adapt_options {
92             my $pixbuf = shift;
93             my $filename = shift;
94             my $type = shift;
95             if (@_ & 1) {
96             croak 'PixbufBits save_adapt(): option key without value (odd number of arguments)';
97             }
98             my @first = ($filename, $type);
99             my @rest;
100             my %seen;
101              
102             while (@_) {
103             my $key = shift;
104             my $value = shift;
105             if ($key eq 'zlib_compression') {
106             next unless $type eq 'png';
107             # png saving always available, but compression option only in 2.8 up
108             next if Gtk2->check_version(2,8,0);
109             $key = 'compression';
110              
111             } elsif ($key eq 'tiff_compression_type') {
112             next unless $type eq 'tiff';
113             next if Gtk2->check_version(2,20,0); # new in 2.20
114             $key = 'compression';
115             $value = $tiff_compression_types{$value} || $value;
116              
117             } elsif ($key =~ /^tEXt:/) {
118             next unless $type eq 'png';
119             # Gtk2-Perl 1.221 doesn't upgrade byte values to utf8 the way it does
120             # in other wrappers, ensure utf8 for output
121             utf8::upgrade($value);
122             # text before "compression" or Gtk 2.20.1 botches the file output
123             push @first, $key, $value;
124             next;
125              
126             } elsif ($key eq 'quality_percent') {
127             next unless $type eq 'jpeg';
128             $key = 'quality';
129              
130             } elsif ($key eq 'x_hot' || $key eq 'y_hot') {
131             # no xpm saving as of 2.20, but maybe it would use x_hot/y_hot
132             # if/when available ... || $type eq 'xpm';
133             next unless $type eq 'ico';
134             $seen{$key} = 1;
135             next if ! defined $value; # undef means no hotspot
136              
137             # } elsif ($key eq 'depth') {
138             # next unless $type eq 'ico';
139             #
140             # } elsif ($key eq 'icc-profile') {
141             # # this mangling not yet documented ....
142             # next unless $type eq 'png' || $type eq 'tiff';
143             # next if Gtk2->check_version(2,20,0);
144             }
145             push @rest, $key, $value;
146             }
147              
148             if ($pixbuf && $type eq 'ico') {
149             foreach my $key ('x_hot', 'y_hot') {
150             unless ($seen{$key}) {
151             if (defined (my $default = $pixbuf->get_option($key))) {
152             push @rest, $key, $default;
153             }
154             }
155             }
156             }
157              
158             return @first, @rest;
159             }
160              
161             #------------------------------------------------------------------------------
162             # Currently all pixels if <= 1800, or 900 pixels at random otherwise, with
163             # transparents skipped but only up to an absolute limit of 3600 attempts.
164             #
165             # The worst case is every pixel different and a hash entry for each. If
166             # that was done for every pixel of a big image then it might use a lot of
167             # memory. An in-place sort could put same pixels adjacent to find the
168             # biggest count, but a sort of a big image might be a bit slow.
169             #
170             use constant _SAMPLES => 900;
171              
172             sub sampled_majority_color {
173             my ($pixbuf) = @_;
174              
175             my $bytes_per_sample = $pixbuf->get_bits_per_sample / 8;
176             my $n_channels = $pixbuf->get_n_channels;
177              
178             if ($pixbuf->get_colorspace ne 'rgb'
179             || $bytes_per_sample != 1) {
180             croak "sampled_majority_color() can only read 8-bit RGB or RGBA";
181             #
182             # || $bytes_per_sample != int($bytes_per_sample)
183             }
184              
185             my $width = $pixbuf->get_width;
186             my $height = $pixbuf->get_height;
187             my $row_stride = $pixbuf->get_rowstride;
188             my $pixel_bytes = $bytes_per_sample * 3;
189             my $pixel_stride = $bytes_per_sample * $n_channels;
190             my $zero = "\0" x $bytes_per_sample;
191             my $data = $pixbuf->get_pixels;
192              
193             my %hash;
194             # return true if accumulated, false if skip a transparent pixel
195             my $acc = sub {
196             my ($offset) = @_;
197             return (substr ($data, $offset+$pixel_bytes, $bytes_per_sample) ne $zero
198             && ++$hash{substr ($data, $offset, $pixel_bytes)});
199             };
200              
201             if ($width * $height < 2 * _SAMPLES) {
202             foreach my $y (0 .. $width-1) {
203             my $offset = $y * $row_stride;
204             foreach my $x (0 .. $width-1) {
205             $acc->($offset);
206             $offset += $pixel_stride;
207             }
208             }
209             } else {
210             for (my $i = 0; $i < _SAMPLES; $i++) {
211             unless ($acc->($pixel_stride * int(rand($width)) # x
212             + $row_stride * int(rand($height)))) { # y
213             $i -= .75;
214             }
215             }
216             }
217              
218             if (! %hash) {
219             ### oops, only saw transparent pixels, what to do?
220             return '#000000';
221             }
222              
223             return sprintf '#%02X%02X%02X', unpack ('C*', _hash_key_with_max_value (\%hash));
224             }
225              
226             # sub _pixel_bytes_to_color_string {
227             # my ($pixbuf, $bytes) = @_;
228             # if ($pixbuf->get_colorspace eq 'rgb') {
229             # if ($pixbuf->get_bits_per_sample == 8) {
230             # return sprintf '#%02X%02X%02X', unpack ('C*', $bytes);
231             # }
232             # # if ($pixbuf->get_bits_per_sample == 16) {
233             # # return sprintf '#%04X%04X%04X', unpack ('S*', $bytes);
234             # # }
235             # }
236             # croak "sampled_majority_color() can only read 8-bit RGB or RGBA";
237             # }
238              
239             # $hash is a hashref, return the key from it with the biggest value,
240             # comparing values as numbers with ">"
241             sub _hash_key_with_max_value {
242             my ($hashref) = @_;
243             my ($max_key, $max_value) = each %$hashref;
244             while (my ($key, $value) = each %$hashref) {
245             if ($value > $max_value) {
246             $max_key = $key;
247             $max_value = $value;
248             }
249             }
250             return $max_key;
251             }
252              
253             1;
254             __END__