| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Lego::From::PNG; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 438 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 36 |  | 
| 4 | 1 |  |  | 1 |  | 5 | use warnings; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 43 |  | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | BEGIN { | 
| 7 | 1 |  |  | 1 |  | 22 | $Lego::From::PNG::VERSION = '0.02'; | 
| 8 |  |  |  |  |  |  | } | 
| 9 |  |  |  |  |  |  |  | 
| 10 | 1 |  |  | 1 |  | 165 | use Image::PNG::Libpng qw(:all); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | use Image::PNG::Const qw(:all); | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | use Lego::From::PNG::Const qw(:all); | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | use Lego::From::PNG::Brick; | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | use Data::Debug; | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | sub new { | 
| 20 |  |  |  |  |  |  | my $class = shift; | 
| 21 |  |  |  |  |  |  | my %args = ref $_[0] eq 'HASH' ? %{$_[0]} : @_; | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | my $hash = {}; | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | $hash->{'filename'} = $args{'filename'}; | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | $hash->{'unit_size'} = $args{'unit_size'} || 1; | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | # Brick depth and height defaults | 
| 30 |  |  |  |  |  |  | $hash->{'brick_depth'} = 1; | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | $hash->{'brick_height'} = 1; | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | # White list default | 
| 35 |  |  |  |  |  |  | $hash->{'whitelist'} = ($args{'whitelist'} && ref($args{'whitelist'}) eq 'ARRAY' && scalar(@{$args{'whitelist'}}) > 0) ? $args{'whitelist'} : undef; | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | # Black list default | 
| 38 |  |  |  |  |  |  | $hash->{'blacklist'} = ($args{'blacklist'} && ref($args{'blacklist'}) eq 'ARRAY' && scalar(@{$args{'blacklist'}}) > 0) ? $args{'blacklist'} : undef; | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | my $self = bless ($hash, ref ($class) || $class); | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | return $self; | 
| 43 |  |  |  |  |  |  | } | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | sub lego_colors { | 
| 46 |  |  |  |  |  |  | my $self = shift; | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | return $self->{'lego_colors'} ||= do { | 
| 49 |  |  |  |  |  |  | my $hash = {}; | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | for my $color ( LEGO_COLORS ) { | 
| 52 |  |  |  |  |  |  | my ($on_key, $cn_key, $hex_key, $r_key, $g_key, $b_key) = ( | 
| 53 |  |  |  |  |  |  | $color . '_OFFICIAL_NAME', | 
| 54 |  |  |  |  |  |  | $color . '_COMMON_NAME', | 
| 55 |  |  |  |  |  |  | $color . '_HEX_COLOR', | 
| 56 |  |  |  |  |  |  | $color . '_RGB_COLOR_RED', | 
| 57 |  |  |  |  |  |  | $color . '_RGB_COLOR_GREEN', | 
| 58 |  |  |  |  |  |  | $color . '_RGB_COLOR_BLUE', | 
| 59 |  |  |  |  |  |  | ); | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | no strict 'refs'; | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | $hash->{ $color } = { | 
| 64 |  |  |  |  |  |  | 'cid'           => $color, | 
| 65 |  |  |  |  |  |  | 'official_name' => Lego::From::PNG::Const->$on_key, | 
| 66 |  |  |  |  |  |  | 'common_name'   => Lego::From::PNG::Const->$cn_key, | 
| 67 |  |  |  |  |  |  | 'hex_color'     => Lego::From::PNG::Const->$hex_key, | 
| 68 |  |  |  |  |  |  | 'rgb_color'     => [ | 
| 69 |  |  |  |  |  |  | Lego::From::PNG::Const->$r_key, | 
| 70 |  |  |  |  |  |  | Lego::From::PNG::Const->$g_key, | 
| 71 |  |  |  |  |  |  | Lego::From::PNG::Const->$b_key, | 
| 72 |  |  |  |  |  |  | ], | 
| 73 |  |  |  |  |  |  | }; | 
| 74 |  |  |  |  |  |  | } | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | $hash; | 
| 77 |  |  |  |  |  |  | }; | 
| 78 |  |  |  |  |  |  | } | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | sub lego_bricks { | 
| 81 |  |  |  |  |  |  | my $self = shift; | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | return $self->{'lego_bricks'} ||= do { | 
| 84 |  |  |  |  |  |  | my $hash = {}; | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | for my $color ( LEGO_COLORS ) { | 
| 87 |  |  |  |  |  |  | for my $length ( LEGO_BRICK_LENGTHS ) { | 
| 88 |  |  |  |  |  |  | my $brick = Lego::From::PNG::Brick->new( color => $color, length => $length ); | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | $hash->{ $brick->identifier } = $brick; | 
| 91 |  |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  | } | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  | $hash; | 
| 95 |  |  |  |  |  |  | }; | 
| 96 |  |  |  |  |  |  | } | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | sub png { | 
| 99 |  |  |  |  |  |  | my $self = shift; | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | return $self->{'png'} ||= do { | 
| 102 |  |  |  |  |  |  | my $png = read_png_file($self->{'filename'}, transforms => PNG_TRANSFORM_STRIP_ALPHA); | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | $png; | 
| 105 |  |  |  |  |  |  | }; | 
| 106 |  |  |  |  |  |  | }; | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | sub png_info { | 
| 109 |  |  |  |  |  |  | my $self = shift; | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | return $self->{'png_info'} ||= $self->png->get_IHDR; | 
| 112 |  |  |  |  |  |  | } | 
| 113 |  |  |  |  |  |  |  | 
| 114 |  |  |  |  |  |  | sub block_row_width { | 
| 115 |  |  |  |  |  |  | my $self = shift; | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | return $self->{'block_row_width'} ||= $self->png_info->{'width'} / $self->{'unit_size'}; | 
| 118 |  |  |  |  |  |  | } | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | sub process { | 
| 121 |  |  |  |  |  |  | my $self = shift; | 
| 122 |  |  |  |  |  |  | my %args = ref $_[0] eq 'HASH' ? %{$_[0]} : @_; | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | my $tally = { | 
| 125 |  |  |  |  |  |  | bricks => {}, | 
| 126 |  |  |  |  |  |  | plan   => [], | 
| 127 |  |  |  |  |  |  | }; | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | if($self->{'filename'}) { | 
| 130 |  |  |  |  |  |  | my @blocks = $self->_png_blocks_of_color; | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | my @units = $self->_approximate_lego_colors( blocks => \@blocks ); | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | my @bricks = $self->_generate_brick_list(units => \@units); | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | $tally->{'plan'} = [ map { $_->flatten } @bricks ]; | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | my %list; | 
| 139 |  |  |  |  |  |  | for my $brick(@bricks) { | 
| 140 |  |  |  |  |  |  | if(! exists $list{ $brick->identifier }) { | 
| 141 |  |  |  |  |  |  | $list{ $brick->identifier } = $brick->flatten; | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | delete $list{ $brick->identifier }{'meta'}; # No need for meta in brick list | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | $list{ $brick->identifier }{'quantity'} = 1; | 
| 146 |  |  |  |  |  |  | } | 
| 147 |  |  |  |  |  |  | else { | 
| 148 |  |  |  |  |  |  | $list{ $brick->identifier }{'quantity'}++; | 
| 149 |  |  |  |  |  |  | } | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | $tally->{'bricks'} = \%list; | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | if($args{'view'}) { | 
| 156 |  |  |  |  |  |  | my $view   = $args{'view'}; | 
| 157 |  |  |  |  |  |  | my $module = "Lego::From::PNG::View::$view"; | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | $tally = eval { | 
| 160 |  |  |  |  |  |  | (my $file = $module) =~ s|::|/|g; | 
| 161 |  |  |  |  |  |  | require $file . '.pm'; | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | $module->new($self)->print($tally); | 
| 164 |  |  |  |  |  |  | }; | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | die "Failed to format as a view ($view). $@" if $@; | 
| 167 |  |  |  |  |  |  | } | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | return $tally; | 
| 170 |  |  |  |  |  |  | } | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | sub _png_blocks_of_color { | 
| 173 |  |  |  |  |  |  | my $self = shift; | 
| 174 |  |  |  |  |  |  | my %args = ref $_[0] eq 'HASH' ? %{$_[0]} : @_; | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | my @blocks; | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | return @blocks unless $self->{'filename'}; # No file, no blocks | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | my $pixel_bytecount = 3; | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | my $y = -1; | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  | for my $pixel_row( @{$self->png->get_rows} ) { | 
| 185 |  |  |  |  |  |  | $y++; | 
| 186 |  |  |  |  |  |  |  | 
| 187 |  |  |  |  |  |  | next unless ($y % $self->{'unit_size'}) == 0; | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | my $row = $y / $self->{'unit_size'}; # get actual row of blocks we are current on | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | my @values = unpack 'C*', $pixel_row; | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | my $row_width = ( scalar(@values) / $pixel_bytecount ) / $self->{'unit_size'}; | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | for(my $col = 0; $col < $row_width; $col++) { | 
| 196 |  |  |  |  |  |  | my ($r, $g, $b) = ( | 
| 197 |  |  |  |  |  |  | $values[ ($self->{'unit_size'} * $pixel_bytecount * $col)     ], | 
| 198 |  |  |  |  |  |  | $values[ ($self->{'unit_size'} * $pixel_bytecount * $col) + 1 ], | 
| 199 |  |  |  |  |  |  | $values[ ($self->{'unit_size'} * $pixel_bytecount * $col) + 2 ] | 
| 200 |  |  |  |  |  |  | ); | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | $blocks[ ($row * $row_width) + $col ] = { | 
| 203 |  |  |  |  |  |  | r => $r, | 
| 204 |  |  |  |  |  |  | g => $g, | 
| 205 |  |  |  |  |  |  | b => $b, | 
| 206 |  |  |  |  |  |  | }; | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  | } | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | return @blocks; | 
| 211 |  |  |  |  |  |  | } | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | sub _find_lego_color { | 
| 214 |  |  |  |  |  |  | my $self  = shift; | 
| 215 |  |  |  |  |  |  | my $block = shift; | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | my @optimal_color = | 
| 218 |  |  |  |  |  |  | map  { $_->{'cid'} } | 
| 219 |  |  |  |  |  |  | sort { $a->{'score'} <=> $b->{'score'} } | 
| 220 |  |  |  |  |  |  | map  { | 
| 221 |  |  |  |  |  |  | +{ | 
| 222 |  |  |  |  |  |  | cid => $_->{'cid'}, | 
| 223 |  |  |  |  |  |  | score => abs( $block->{'r'} - $_->{'rgb_color'}[0] ) | 
| 224 |  |  |  |  |  |  | + abs( $block->{'g'} - $_->{'rgb_color'}[1] ) | 
| 225 |  |  |  |  |  |  | + abs( $block->{'b'} - $_->{'rgb_color'}[2] ), | 
| 226 |  |  |  |  |  |  | }; | 
| 227 |  |  |  |  |  |  | } | 
| 228 |  |  |  |  |  |  | values %{ $self->lego_colors }; | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | my ($optimal_color) = grep { | 
| 231 |  |  |  |  |  |  | my $choose_this_color = 1; | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | $choose_this_color = 0 if ! $self->is_whitelisted( $_, 'color' ); | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | $choose_this_color = 0 if $self->is_blacklisted( $_, 'color' ); | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | $choose_this_color; # return result | 
| 238 |  |  |  |  |  |  | } @optimal_color; # first color in list that passes whitelist and blacklist should be the optimal color for tested block | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | return $optimal_color; | 
| 241 |  |  |  |  |  |  | } | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | sub _approximate_lego_colors { | 
| 244 |  |  |  |  |  |  | my $self = shift; | 
| 245 |  |  |  |  |  |  | my %args = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_; | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | die 'blocks not valid' unless $args{'blocks'} && ref( $args{'blocks'} ) eq 'ARRAY'; | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | my @colors; | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | for my $block(@{ $args{'blocks'} }) { | 
| 252 |  |  |  |  |  |  | push @colors, $self->_find_lego_color( $block ); | 
| 253 |  |  |  |  |  |  | } | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | return @colors; | 
| 256 |  |  |  |  |  |  | } | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | sub _generate_brick_list { | 
| 259 |  |  |  |  |  |  | my $self = shift; | 
| 260 |  |  |  |  |  |  | my %args = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_; | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | die 'units not valid' unless $args{'units'} && ref( $args{'units'} ) eq 'ARRAY'; | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | my $unit_count   = scalar(@{ $args{'units'} }); | 
| 265 |  |  |  |  |  |  | my @units        = @{ $args{'units'} }; | 
| 266 |  |  |  |  |  |  | my $row_width    = $self->block_row_width; | 
| 267 |  |  |  |  |  |  | my $brick_height = 1; # bricks are only one unit high | 
| 268 |  |  |  |  |  |  | my @brick_list; | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | for(my $y = 0; $y < ($unit_count / $row_width); $y++) { | 
| 271 |  |  |  |  |  |  | my @row = splice @units, 0, $row_width; | 
| 272 |  |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  | my $push_color = sub { | 
| 274 |  |  |  |  |  |  | my ($color, $length) = @_; | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | if($color) { | 
| 277 |  |  |  |  |  |  | push @brick_list, Lego::From::PNG::Brick->new( | 
| 278 |  |  |  |  |  |  | color  => $color, | 
| 279 |  |  |  |  |  |  | depth  => $self->{'brick_depth'}, | 
| 280 |  |  |  |  |  |  | length => $length, | 
| 281 |  |  |  |  |  |  | height => $self->{'brick_height'}, | 
| 282 |  |  |  |  |  |  | meta   => { | 
| 283 |  |  |  |  |  |  | y => $y, | 
| 284 |  |  |  |  |  |  | }, | 
| 285 |  |  |  |  |  |  | ); | 
| 286 |  |  |  |  |  |  | } | 
| 287 |  |  |  |  |  |  | }; | 
| 288 |  |  |  |  |  |  |  | 
| 289 |  |  |  |  |  |  | my $process_color_sample = sub { | 
| 290 |  |  |  |  |  |  | my ($color, $length) = @_; | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | return if $length <= 0; | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | # Now make sure we find bricks we are allowed to use | 
| 295 |  |  |  |  |  |  | FIND_BRICKS: { | 
| 296 |  |  |  |  |  |  | for( 1 .. $length) { # Only need to loop at least the number of times equal to the length of color found | 
| 297 |  |  |  |  |  |  | my $valid_length = $length; | 
| 298 |  |  |  |  |  |  | FIND_VALID_LENGTH: { | 
| 299 |  |  |  |  |  |  | for(;$valid_length > 0;$valid_length--) { | 
| 300 |  |  |  |  |  |  | my $dim = join('x',$self->{'brick_depth'},$valid_length,$self->{'brick_height'}); | 
| 301 |  |  |  |  |  |  | my $brk = join('_', $color, $dim); | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | next FIND_VALID_LENGTH if $self->is_blacklisted( $dim, 'dimension' ) || $self->is_blacklisted( $brk, 'brick' ); | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | last FIND_VALID_LENGTH if $self->is_whitelisted( $dim, 'dimension' ) && $self->is_whitelisted( $brk, 'brick' ); | 
| 306 |  |  |  |  |  |  | } | 
| 307 |  |  |  |  |  |  | } | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | $push_color->($color, $valid_length); | 
| 310 |  |  |  |  |  |  | $length -= $valid_length; | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | last FIND_BRICKS if $length <= 0; # No need to push more bricks, we found them all | 
| 313 |  |  |  |  |  |  | } | 
| 314 |  |  |  |  |  |  | } | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  | die "No valid bricks found for remaining units of color" if $length > 0; # Catch if we have gremlins in our whitelist/blacklist | 
| 317 |  |  |  |  |  |  | }; | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | # Run through rows and process colors | 
| 320 |  |  |  |  |  |  | my $next_brick_color = ''; | 
| 321 |  |  |  |  |  |  | my $next_brick_length = 0; | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | for my $color(@row) { | 
| 324 |  |  |  |  |  |  | if( $color ne $next_brick_color ) { | 
| 325 |  |  |  |  |  |  | $process_color_sample->($next_brick_color, $next_brick_length); | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | $next_brick_color = $color; | 
| 328 |  |  |  |  |  |  | $next_brick_length = 0; | 
| 329 |  |  |  |  |  |  | } | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | $next_brick_length++; | 
| 332 |  |  |  |  |  |  | } | 
| 333 |  |  |  |  |  |  |  | 
| 334 |  |  |  |  |  |  | $process_color_sample->($next_brick_color, $next_brick_length); # Process last color found | 
| 335 |  |  |  |  |  |  | } | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | return @brick_list; | 
| 338 |  |  |  |  |  |  | } | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | sub _list_filters { | 
| 341 |  |  |  |  |  |  | my $self    = shift; | 
| 342 |  |  |  |  |  |  | my $allowed = $_[0] && ref($_[0]) eq 'ARRAY' ? $_[0] | 
| 343 |  |  |  |  |  |  | : ($_[0]) ? [ shift ] | 
| 344 |  |  |  |  |  |  | : []; # optional filter restriction | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | my $filters = { | 
| 347 |  |  |  |  |  |  | color     => qr{^([A-Z_]+)(?:_\d+x\d+x\d+)?$}i, | 
| 348 |  |  |  |  |  |  | dimension => qr{^(\d+x\d+x\d+)$}i, | 
| 349 |  |  |  |  |  |  | brick     => qr{^([A-Z_]+_\d+x\d+x\d+)$}i, | 
| 350 |  |  |  |  |  |  | }; | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | $filters = +{ map { $_ => $filters->{$_} } @$allowed } if scalar @$allowed; | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | return $filters; | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | sub whitelist { shift->{'whitelist'} } | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | sub has_whitelist { | 
| 360 |  |  |  |  |  |  | my $self    = shift; | 
| 361 |  |  |  |  |  |  | my $allowed = shift; # arrayref listing filters we can use | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | my $found = 0; | 
| 364 |  |  |  |  |  |  | for my $filter(values $self->_list_filters($allowed)) { | 
| 365 |  |  |  |  |  |  | $found += scalar( grep { /$filter/ } @{ $self->whitelist || [] } ); | 
| 366 |  |  |  |  |  |  | } | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | return $found; | 
| 369 |  |  |  |  |  |  | } | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  | sub is_whitelisted { | 
| 372 |  |  |  |  |  |  | my $self    = shift; | 
| 373 |  |  |  |  |  |  | my $val     = shift; | 
| 374 |  |  |  |  |  |  | my $allowed = shift; # arrayref listing filters we can use | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | return 1 if ! $self->has_whitelist($allowed); # return true if there is no whitelist | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | for my $entry( @{ $self->whitelist || [] } ) { | 
| 379 |  |  |  |  |  |  | for my $filter( values %{ $self->_list_filters($allowed) } ) { | 
| 380 |  |  |  |  |  |  | next unless $entry =~ /$filter/; # if there is at least a letter at the beginning then this entry has a color we can check | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | my $capture = $entry; | 
| 383 |  |  |  |  |  |  | $capture =~ s/$filter/$1/; | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | return 1 if $val eq $capture; | 
| 386 |  |  |  |  |  |  | } | 
| 387 |  |  |  |  |  |  | } | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | return 0; # value is not in whitelist | 
| 390 |  |  |  |  |  |  | } | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | sub blacklist { shift->{'blacklist'} } | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | sub has_blacklist { | 
| 395 |  |  |  |  |  |  | my $self    = shift; | 
| 396 |  |  |  |  |  |  | my $allowed = shift; # optional filter restriction | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | my $found = 0; | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | for my $filter(values $self->_list_filters($allowed)) { | 
| 401 |  |  |  |  |  |  | $found += scalar( grep { /$filter/ } @{ $self->blacklist || [] } ); | 
| 402 |  |  |  |  |  |  | } | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | return $found; | 
| 405 |  |  |  |  |  |  | } | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | sub is_blacklisted { | 
| 408 |  |  |  |  |  |  | my $self    = shift; | 
| 409 |  |  |  |  |  |  | my $val     = shift; | 
| 410 |  |  |  |  |  |  | my $allowed = shift; # optional filter restriction | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | return 0 if ! $self->has_blacklist($allowed); # return false if there is no blacklist | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | for my $entry( @{ $self->blacklist || [] } ) { | 
| 415 |  |  |  |  |  |  | for my $filter( values %{ $self->_list_filters($allowed) } ) { | 
| 416 |  |  |  |  |  |  | next unless $entry =~ /$filter/; # if there is at least a letter at the beginning then this entry has a color we can check | 
| 417 |  |  |  |  |  |  |  | 
| 418 |  |  |  |  |  |  | my $capture = $1 || $entry; | 
| 419 |  |  |  |  |  |  |  | 
| 420 |  |  |  |  |  |  | return 1 if $val eq $capture; | 
| 421 |  |  |  |  |  |  | } | 
| 422 |  |  |  |  |  |  | } | 
| 423 |  |  |  |  |  |  |  | 
| 424 |  |  |  |  |  |  | return 0; # value is not in blacklist | 
| 425 |  |  |  |  |  |  | } | 
| 426 |  |  |  |  |  |  |  | 
| 427 |  |  |  |  |  |  | =pod | 
| 428 |  |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | =head1 NAME | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  | Lego::From::PNG - Convert PNGs into plans to build a two dimensional lego replica. | 
| 432 |  |  |  |  |  |  |  | 
| 433 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 434 |  |  |  |  |  |  |  | 
| 435 |  |  |  |  |  |  | use Lego::From::PNG; | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | my $object = Lego::From::PNG; | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | $object->brick_tally(); | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | Convert a PNG into a block list and plans to build a two dimensional replica of the PNG. The plans are built with brick | 
| 444 |  |  |  |  |  |  | knobs pointed vertically so the picture will look like a flat surface to the viewer. Meaning the only dimension | 
| 445 |  |  |  |  |  |  | of the brick being determined is the length. Depth and height are all the same for all bricks. | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | $hash->{'filename'} = $args{'filename'}; | 
| 448 |  |  |  |  |  |  |  | 
| 449 |  |  |  |  |  |  | $hash->{'unit_size'} = $args{'unit_size'} || 1; | 
| 450 |  |  |  |  |  |  |  | 
| 451 |  |  |  |  |  |  | # Brick depth and height defaults | 
| 452 |  |  |  |  |  |  | $hash->{'brick_depth'} = 1; | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | $hash->{'brick_height'} = 1; | 
| 455 |  |  |  |  |  |  |  | 
| 456 |  |  |  |  |  |  | # White list default | 
| 457 |  |  |  |  |  |  | $hash->{'whitelist'} = ($args{'whitelist'} && ref($args{'whitelist'}) eq 'ARRAY' && scalar(@{$args{'whitelist'}}) > 0) ? $args{'whitelist'} : undef; | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | # Black list default | 
| 460 |  |  |  |  |  |  | $hash->{'blacklist'} = ($args{'blacklist'} && ref($args{'blacklist'}) eq 'ARRAY' && scalar(@{$args{'blacklist'}}) > 0) ? $args{'blacklist'} : undef; | 
| 461 |  |  |  |  |  |  |  | 
| 462 |  |  |  |  |  |  | =head1 USAGE | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | =head2 new | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | Usage     : ->new() | 
| 467 |  |  |  |  |  |  | Purpose   : Returns Lego::From::PNG object | 
| 468 |  |  |  |  |  |  |  | 
| 469 |  |  |  |  |  |  | Returns   : Lego::From::PNG object | 
| 470 |  |  |  |  |  |  | Argument  : | 
| 471 |  |  |  |  |  |  | filename - Optional. The file name of the PNG to process. Optional but if not provided, can't process the png. | 
| 472 |  |  |  |  |  |  | e.g. filename => '/location/of/the.png' | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | unit_size - Optional. The size of pixels squared to determine a single unit of a brick. Defaults to 1. | 
| 475 |  |  |  |  |  |  | e.g. unit_size => 2 # pixelated colors are 2x2 in size | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | brick_depth - Optional. The depth of all generated bricks. Defaults to 1. | 
| 478 |  |  |  |  |  |  | e.g. brick_depth => 2 # final depth of all bricks are 2. So 2 x length x height | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | brick_heigtht - Optional. The height of all generated bricks. Defaults to 1. | 
| 481 |  |  |  |  |  |  | e.g. brick_height => 2 # final height of all bricks are 2. So depth x length x 2 | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | whitelist - Optional. Array ref of colors, dimensions or color and dimensions that are allowed in the final plan output. | 
| 484 |  |  |  |  |  |  | e.g. whitelist => [ 'BLACK', 'WHITE', '1x1x1', '1x2x1', '1x4x1', 'BLACK_1x6x1' ] | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | blacklist - Optional. Array ref of colors, dimensions or color and dimensions that are not allowed in the final plan output. | 
| 487 |  |  |  |  |  |  | e.g. blacklist => [ 'RED', '1x10x1', '1x12x1', '1x16x1', 'BLUE_1x8x1' ] | 
| 488 |  |  |  |  |  |  |  | 
| 489 |  |  |  |  |  |  | Throws    : | 
| 490 |  |  |  |  |  |  |  | 
| 491 |  |  |  |  |  |  | Comment   : | 
| 492 |  |  |  |  |  |  | See Also  : | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | =head2 lego_colors | 
| 495 |  |  |  |  |  |  |  | 
| 496 |  |  |  |  |  |  | Usage     : ->lego_colors() | 
| 497 |  |  |  |  |  |  | Purpose   : Returns lego color constants consolidated as a hash. | 
| 498 |  |  |  |  |  |  |  | 
| 499 |  |  |  |  |  |  | Returns   : Hash ref with color constants keyed by the official color name in key form. | 
| 500 |  |  |  |  |  |  | Argument  : | 
| 501 |  |  |  |  |  |  | Throws    : | 
| 502 |  |  |  |  |  |  |  | 
| 503 |  |  |  |  |  |  | Comment   : | 
| 504 |  |  |  |  |  |  | See Also  : | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  | =head2 lego_bricks | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  | Usage     : ->lego_bricks() | 
| 509 |  |  |  |  |  |  | Purpose   : Returns a list of all possible lego bricks | 
| 510 |  |  |  |  |  |  |  | 
| 511 |  |  |  |  |  |  | Returns   : Hash ref with L objects keyed by their identifier | 
| 512 |  |  |  |  |  |  | Argument  : | 
| 513 |  |  |  |  |  |  | Throws    : | 
| 514 |  |  |  |  |  |  |  | 
| 515 |  |  |  |  |  |  | Comment   : | 
| 516 |  |  |  |  |  |  | See Also  : | 
| 517 |  |  |  |  |  |  |  | 
| 518 |  |  |  |  |  |  | =head2 png | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | Usage     : ->png() | 
| 521 |  |  |  |  |  |  | Purpose   : Returns Image::PNG::Libpng object. | 
| 522 |  |  |  |  |  |  |  | 
| 523 |  |  |  |  |  |  | Returns   : Returns Image::PNG::Libpng object. See L for more details. | 
| 524 |  |  |  |  |  |  | Argument  : | 
| 525 |  |  |  |  |  |  | Throws    : | 
| 526 |  |  |  |  |  |  |  | 
| 527 |  |  |  |  |  |  | Comment   : | 
| 528 |  |  |  |  |  |  | See Also  : | 
| 529 |  |  |  |  |  |  |  | 
| 530 |  |  |  |  |  |  | =head2 png_info | 
| 531 |  |  |  |  |  |  |  | 
| 532 |  |  |  |  |  |  | Usage     : ->png_info() | 
| 533 |  |  |  |  |  |  | Purpose   : Returns png IHDR info from the Image::PNG::Libpng object | 
| 534 |  |  |  |  |  |  |  | 
| 535 |  |  |  |  |  |  | Returns   : A hash of values containing information abou the png such as width and height. See get_IHDR in L for more details. | 
| 536 |  |  |  |  |  |  | Argument  : filename  => the PNG to load and part | 
| 537 |  |  |  |  |  |  | unit_size => the pixel width and height of one unit, blocks are generally identified as Nx1 blocks where N is the number of units of the same color | 
| 538 |  |  |  |  |  |  | Throws    : | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | Comment   : | 
| 541 |  |  |  |  |  |  | See Also  : | 
| 542 |  |  |  |  |  |  |  | 
| 543 |  |  |  |  |  |  | =head2 block_row_width | 
| 544 |  |  |  |  |  |  |  | 
| 545 |  |  |  |  |  |  | Usage     : ->block_row_width() | 
| 546 |  |  |  |  |  |  | Purpose   : Return the width of one row of blocks. Since a block list is a single dimension array this is useful to figure out whict row a block is on. | 
| 547 |  |  |  |  |  |  |  | 
| 548 |  |  |  |  |  |  | Returns   : The length of a row of blocks (image width / unit size) | 
| 549 |  |  |  |  |  |  | Argument  : | 
| 550 |  |  |  |  |  |  | Throws    : | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  | Comment   : | 
| 553 |  |  |  |  |  |  | See Also  : | 
| 554 |  |  |  |  |  |  |  | 
| 555 |  |  |  |  |  |  | =head2 process | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  | Usage     : ->process() | 
| 558 |  |  |  |  |  |  | Purpose   : Convert a provided PNG into a list of lego blocks that will allow building of a two dimensional lego replica. | 
| 559 |  |  |  |  |  |  |  | 
| 560 |  |  |  |  |  |  | Returns   : Hashref containing information about particular lego bricks found to be needed based on the provided PNG. | 
| 561 |  |  |  |  |  |  | Also included is the build order for those bricks. | 
| 562 |  |  |  |  |  |  | Argument  : view => 'a view' - optionally format the return data. options include: JSON and HTML | 
| 563 |  |  |  |  |  |  | Throws    : | 
| 564 |  |  |  |  |  |  |  | 
| 565 |  |  |  |  |  |  | Comment   : | 
| 566 |  |  |  |  |  |  | See Also  : | 
| 567 |  |  |  |  |  |  |  | 
| 568 |  |  |  |  |  |  | =head2 _png_blocks_of_color | 
| 569 |  |  |  |  |  |  |  | 
| 570 |  |  |  |  |  |  | Usage     : ->_png_blocks_of_color() | 
| 571 |  |  |  |  |  |  | Purpose   : Convert a provided PNG into a list of rgb values based on [row][color]. Size of blocks are determined by 'unit_size' | 
| 572 |  |  |  |  |  |  |  | 
| 573 |  |  |  |  |  |  | Returns   : A list of hashes contain r, g and b values. e.g. ( { r => #, g => #, b => # }, { ... }, ... ) | 
| 574 |  |  |  |  |  |  | Argument  : | 
| 575 |  |  |  |  |  |  | Throws    : | 
| 576 |  |  |  |  |  |  |  | 
| 577 |  |  |  |  |  |  | Comment   : | 
| 578 |  |  |  |  |  |  | See Also  : | 
| 579 |  |  |  |  |  |  |  | 
| 580 |  |  |  |  |  |  | =head2 _find_lego_color | 
| 581 |  |  |  |  |  |  |  | 
| 582 |  |  |  |  |  |  | Usage     : ->_find_lego_color | 
| 583 |  |  |  |  |  |  | Purpose   : given an rgb hash, finds the optimal lego color | 
| 584 |  |  |  |  |  |  |  | 
| 585 |  |  |  |  |  |  | Returns   : A lego color common name key that can then reference lego color information using L | 
| 586 |  |  |  |  |  |  | Argument  : | 
| 587 |  |  |  |  |  |  | Throws    : | 
| 588 |  |  |  |  |  |  |  | 
| 589 |  |  |  |  |  |  | Comment   : | 
| 590 |  |  |  |  |  |  | See Also  : | 
| 591 |  |  |  |  |  |  |  | 
| 592 |  |  |  |  |  |  | =head2 _approximate_lego_colors | 
| 593 |  |  |  |  |  |  |  | 
| 594 |  |  |  |  |  |  | Usage     : ->_approximate_lego_colors() | 
| 595 |  |  |  |  |  |  | Purpose   : Generate a list of lego colors based on a list of blocks ( array of hashes containing rgb values ) | 
| 596 |  |  |  |  |  |  |  | 
| 597 |  |  |  |  |  |  | Returns   : A list of lego color common name keys that can then reference lego color information using L | 
| 598 |  |  |  |  |  |  | Argument  : | 
| 599 |  |  |  |  |  |  | Throws    : | 
| 600 |  |  |  |  |  |  |  | 
| 601 |  |  |  |  |  |  | Comment   : | 
| 602 |  |  |  |  |  |  | See Also  : | 
| 603 |  |  |  |  |  |  |  | 
| 604 |  |  |  |  |  |  | =head2 _generate_brick_list | 
| 605 |  |  |  |  |  |  |  | 
| 606 |  |  |  |  |  |  | Usage     : ->_approximate_lego_colors() | 
| 607 |  |  |  |  |  |  | Purpose   : Generate a list of lego colors based on a list of blocks ( array of hashes containing rgb values ) | 
| 608 |  |  |  |  |  |  |  | 
| 609 |  |  |  |  |  |  | Returns   : A list of lego color common name keys that can then reference lego color information using L | 
| 610 |  |  |  |  |  |  | Argument  : | 
| 611 |  |  |  |  |  |  | Throws    : | 
| 612 |  |  |  |  |  |  |  | 
| 613 |  |  |  |  |  |  | Comment   : | 
| 614 |  |  |  |  |  |  | See Also  : | 
| 615 |  |  |  |  |  |  |  | 
| 616 |  |  |  |  |  |  | =head2 _list_filters | 
| 617 |  |  |  |  |  |  |  | 
| 618 |  |  |  |  |  |  | Usage     : ->_list_filters() | 
| 619 |  |  |  |  |  |  | Purpose   : return whitelist/blacklist filters | 
| 620 |  |  |  |  |  |  |  | 
| 621 |  |  |  |  |  |  | Returns   : an hashref of filters | 
| 622 |  |  |  |  |  |  | Argument  : an optional filter restriction to limit set of filters returned to just one | 
| 623 |  |  |  |  |  |  | Throws    : | 
| 624 |  |  |  |  |  |  |  | 
| 625 |  |  |  |  |  |  | Comment   : | 
| 626 |  |  |  |  |  |  | See Also  : | 
| 627 |  |  |  |  |  |  |  | 
| 628 |  |  |  |  |  |  | =head2 whitelist | 
| 629 |  |  |  |  |  |  |  | 
| 630 |  |  |  |  |  |  | Usage     : ->whitelist() | 
| 631 |  |  |  |  |  |  | Purpose   : return any whitelist settings stored in this object | 
| 632 |  |  |  |  |  |  |  | 
| 633 |  |  |  |  |  |  | Returns   : an arrayref of whitelisted colors and/or blocks, or undef | 
| 634 |  |  |  |  |  |  | Argument  : | 
| 635 |  |  |  |  |  |  | Throws    : | 
| 636 |  |  |  |  |  |  |  | 
| 637 |  |  |  |  |  |  | Comment   : | 
| 638 |  |  |  |  |  |  | See Also  : | 
| 639 |  |  |  |  |  |  |  | 
| 640 |  |  |  |  |  |  | =head2 has_whitelist | 
| 641 |  |  |  |  |  |  |  | 
| 642 |  |  |  |  |  |  | Usage     : ->has_whitelist(), ->has_whitelist($filter) | 
| 643 |  |  |  |  |  |  | Purpose   : return a true value if there is a whitelist with at least one entry in it based on the allowed filters, otherwise a false value is returned | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  | Returns   : 1 or 0 | 
| 646 |  |  |  |  |  |  | Argument  : $filter - optional scalar containing the filter to restrict test to | 
| 647 |  |  |  |  |  |  | Throws    : | 
| 648 |  |  |  |  |  |  |  | 
| 649 |  |  |  |  |  |  | Comment   : | 
| 650 |  |  |  |  |  |  | See Also  : | 
| 651 |  |  |  |  |  |  |  | 
| 652 |  |  |  |  |  |  | =head2 is_whitelisted | 
| 653 |  |  |  |  |  |  |  | 
| 654 |  |  |  |  |  |  | Usage     : ->is_whitelisted($value), ->is_whitelisted($value, $filter) | 
| 655 |  |  |  |  |  |  | Purpose   : return a true if the value is whitelisted, otherwise false is returned | 
| 656 |  |  |  |  |  |  |  | 
| 657 |  |  |  |  |  |  | Returns   : 1 or 0 | 
| 658 |  |  |  |  |  |  | Argument  : $value - the value to test, $filter - optional scalar containing the filter to restrict test to | 
| 659 |  |  |  |  |  |  | Throws    : | 
| 660 |  |  |  |  |  |  |  | 
| 661 |  |  |  |  |  |  | Comment   : | 
| 662 |  |  |  |  |  |  | See Also  : | 
| 663 |  |  |  |  |  |  |  | 
| 664 |  |  |  |  |  |  | =head2 blacklist | 
| 665 |  |  |  |  |  |  |  | 
| 666 |  |  |  |  |  |  | Usage     : ->blacklist | 
| 667 |  |  |  |  |  |  | Purpose   : return any blacklist settings stored in this object | 
| 668 |  |  |  |  |  |  |  | 
| 669 |  |  |  |  |  |  | Returns   : an arrayref of blacklisted colors and/or blocks, or undef | 
| 670 |  |  |  |  |  |  | Argument  : | 
| 671 |  |  |  |  |  |  | Throws    : | 
| 672 |  |  |  |  |  |  |  | 
| 673 |  |  |  |  |  |  | Comment   : | 
| 674 |  |  |  |  |  |  | See Also  : | 
| 675 |  |  |  |  |  |  |  | 
| 676 |  |  |  |  |  |  | =head2 has_blacklist | 
| 677 |  |  |  |  |  |  |  | 
| 678 |  |  |  |  |  |  | Usage     : ->has_blacklist(), ->has_whitelist($filter) | 
| 679 |  |  |  |  |  |  | Purpose   : return a true value if there is a blacklist with at least one entry in it based on the allowed filters, otherwise a false value is returned | 
| 680 |  |  |  |  |  |  |  | 
| 681 |  |  |  |  |  |  | Returns   : 1 or 0 | 
| 682 |  |  |  |  |  |  | Argument  : $filter - optional scalar containing the filter to restrict test to | 
| 683 |  |  |  |  |  |  | Throws    : | 
| 684 |  |  |  |  |  |  |  | 
| 685 |  |  |  |  |  |  | Comment   : | 
| 686 |  |  |  |  |  |  | See Also  : | 
| 687 |  |  |  |  |  |  |  | 
| 688 |  |  |  |  |  |  | =head2 is_blacklisted | 
| 689 |  |  |  |  |  |  |  | 
| 690 |  |  |  |  |  |  | Usage     : ->is_blacklisted($value), ->is_whitelisted($value, $filter) | 
| 691 |  |  |  |  |  |  | Purpose   : return a true if the value is blacklisted, otherwise false is returned | 
| 692 |  |  |  |  |  |  |  | 
| 693 |  |  |  |  |  |  | Returns   : 1 or 0 | 
| 694 |  |  |  |  |  |  | Argument  : $value - the value to test, $filter - optional scalar containing the filter to restrict test to | 
| 695 |  |  |  |  |  |  | Throws    : | 
| 696 |  |  |  |  |  |  |  | 
| 697 |  |  |  |  |  |  | Comment   : | 
| 698 |  |  |  |  |  |  | See Also  : | 
| 699 |  |  |  |  |  |  |  | 
| 700 |  |  |  |  |  |  | =head1 BUGS | 
| 701 |  |  |  |  |  |  |  | 
| 702 |  |  |  |  |  |  | =head1 SUPPORT | 
| 703 |  |  |  |  |  |  |  | 
| 704 |  |  |  |  |  |  | =head1 AUTHOR | 
| 705 |  |  |  |  |  |  |  | 
| 706 |  |  |  |  |  |  | Travis Chase | 
| 707 |  |  |  |  |  |  | CPAN ID: GAUDEON | 
| 708 |  |  |  |  |  |  | gaudeon@cpan.org | 
| 709 |  |  |  |  |  |  | https://github.com/gaudeon/Lego-From-Png | 
| 710 |  |  |  |  |  |  |  | 
| 711 |  |  |  |  |  |  | =head1 COPYRIGHT | 
| 712 |  |  |  |  |  |  |  | 
| 713 |  |  |  |  |  |  | This program is free software licensed under the... | 
| 714 |  |  |  |  |  |  |  | 
| 715 |  |  |  |  |  |  | The MIT License | 
| 716 |  |  |  |  |  |  |  | 
| 717 |  |  |  |  |  |  | The full text of the license can be found in the | 
| 718 |  |  |  |  |  |  | LICENSE file included with this module. | 
| 719 |  |  |  |  |  |  |  | 
| 720 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 721 |  |  |  |  |  |  |  | 
| 722 |  |  |  |  |  |  | perl(1). | 
| 723 |  |  |  |  |  |  |  | 
| 724 |  |  |  |  |  |  | =cut | 
| 725 |  |  |  |  |  |  |  | 
| 726 |  |  |  |  |  |  | 1; | 
| 727 |  |  |  |  |  |  |  |