File Coverage

blib/lib/Lego/From/PNG.pm
Criterion Covered Total %
statement 8 10 80.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 12 14 85.7


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