File Coverage

blib/lib/GD/SIRDS.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package GD::SIRDS;
2              
3             =head1 NAME
4              
5             GD::SIRDS - Create a GD image of a Single Image Random Dot Stereogram
6              
7             =head1 SYNOPSIS
8              
9             use GD;
10             use GD::SIRDS;
11              
12             my ($src, $dst, @colors);
13              
14             $src = GD->new("some.png");
15             @colors = (
16             [ 0, 0, 0], # basic black
17             [204,204,204], # a nice grey
18             [ 0, 51,102], # a good dark blue-green
19             [ 0,102,153], # another good blue-green
20             );
21              
22             $dst = gd_sirds($src, \@colors);
23            
24             binmode STDOUT;
25             print $dst->png;
26              
27             =head1 DESCRIPTION
28              
29             C exports a single subroutine, C, that produces a
30             Single Image Random Dot Stereogram (SIRDS).
31              
32             =cut
33              
34 1     1   575538 use 5.006;
  1         4  
  1         51  
35 1     1   7 use strict;
  1         1  
  1         39  
36 1     1   6 use warnings;
  1         7  
  1         115  
37              
38             our $VERSION = '0.02';
39              
40             require Exporter;
41             our @ISA = qw(Exporter);
42             our @EXPORT = qw(
43             gd_sirds
44             );
45              
46 1     1   6 use Carp;
  1         2  
  1         326  
47 1     1   2679 use POSIX;
  1         26032  
  1         9  
48 1     1   15133 use GD;
  0            
  0            
49              
50             use constant DEPTH_OF_FIELD => 1/3;
51             use constant EYE_SEPARATION => 200;
52              
53             =over 4
54              
55             =item gd_sirds MAP,COLORS
56              
57             =item gd_sirds MAP,COLORS,CIRCLES
58              
59             =item gd_sirds MAP,COLORS,CIRCLES,EYESEP
60              
61             =item gd_sirds MAP,COLORS,CIRCLES,EYESEP,FIELDDEPTH
62              
63             Create a Single Image Random Dot Stereogram based on the given depth
64             MAP, with random dot colors selected from COLORS.
65              
66             The depth map can be either an instance of GD::Image or a reference to a
67             two-dimensional array of numbers between 0 and 1, inclusive. Lighter
68             colors (for Cs) and higher numbers (for arrays) stick out
69             more from the background.
70              
71             COLORS is a reference to an array of RGB triples, each triple represented
72             as an array of three integers between 0 and 255, as in L.
73              
74             Set CIRCLES to true to put two circles at the bottom of the image
75             representing the amount ones eyes need to diverge. (Aligning the
76             circles so that the two become three should produce the proper
77             divergence to see the stereogram.)
78              
79             EYESEP is the separation, in pixels, of the viewer's eyes. For a computer
80             monitor, the default of 200 seems to work well.
81              
82             FIELDDEPTH is a bit trickier. Assume that the three-dimensional object
83             displayed has an apparent distance from the viewer equal to twice the
84             distance from the viewer to the screen. That is, the bottom of the object
85             is as far behind the screen as the viewer is in front of the screen. In
86             that case, the top of the three-dimensional object is FIELDDEPTH
87             (default 1/3) of the way up back to the screen.
88              
89             =cut
90              
91             sub gd_sirds
92             {
93             my $map = shift; # depth map
94             my $colors = shift; # dot colors
95              
96             my $helper_circles = shift || 0; # draw helper circles?
97              
98             my $eye = shift || EYE_SEPARATION; # eye separation
99             my $dof = shift || DEPTH_OF_FIELD; # depth of field
100              
101             warn "GD::SIRDS::gd_sirds params loaded" if $main::GD_DEBUGGING;
102              
103             # check map for correctness and convert to a two-dimensional
104             # array if it isn't one already
105             if (ref $map eq "ARRAY") {
106             my $firstlen = @{$map->[0]};
107             for (@$map) {
108             croak "need a GD::Image or a two-dimensional array"
109             unless ref eq "ARRAY" and $firstlen == @$_;
110             }
111             } elsif (ref $map eq "GD::Image") {
112             $map = &_image2map($map);
113             } else {
114             croak "need a GD::Image or a two-dimensional array";
115             }
116              
117             warn "GD::SIRDS::gd_sirds map made" if $main::GD_DEBUGGING;
118              
119             my $width = @$map;
120             my $height = @{$map->[0]};
121              
122             # make the destination image
123             my $image = &_make_image($width, $height, $colors);
124              
125             warn "GD::SIRDS::gd_sirds destination image made" if $main::GD_DEBUGGING;
126              
127             for (my $y = 0; $y < $height; $y++) { # convert scan lines independantly
128             warn "GD::SIRDS::gd_sirds drawing line $y" if $main::GD_DEBUGGING;
129              
130             my @color; # color of this pixel
131             my @same; # a pixel to the right constrained to the same color
132              
133             for (my $x = 0; $x < $width; $x++) {
134             $same[$x] = $x; # each pixel intially linked with itself
135             }
136              
137             for (my $x = 0; $x < $width; $x++) {
138             my $depth = $map->[$x][$y];
139             # stereo separation at this ($x,$y) point
140             my $sep = floor(((1-$dof*$depth)*$eye / (2-$dof*$depth))+0.5);
141              
142             # pixels corresponding to left & right eyes must
143             # be the same...
144             my $left = floor($x - $sep/2);
145             my $right = floor($left + $sep);
146              
147             # ...except for hidden-surface removal
148             if (0 <= $left && $right < $width) {
149             my $visible;
150             my $t = 1;
151             my $zt;
152              
153             do {
154             $zt = $depth + 2*(2-$dof*$depth)*$t/($dof*$eye);
155             $visible = $map->[$x-$t][$y] < $zt
156             && $map->[$x+$t][$y] < $zt;
157             ++$t;
158             } while ($visible && $zt < 1);
159             if ($visible) {
160             my $l = $same[$left];
161             while ($l != $left && $l != $right) {
162             if ($l < $right) {
163             $left = $l;
164             $l = $same[$left];
165             } else {
166             $same[$left] = $right;
167             $left = $right;
168             $l = $same[$left];
169             $right = $l;
170             }
171             }
172             $same[$left] = $right;
173             }
174             }
175             }
176              
177             # assign colors to this row
178             my $num_colors = @$colors;
179             for (my $x = $width-1; $x >= 0; --$x) {
180             if ($same[$x] == $x) {
181             $color[$x] = int rand $num_colors;
182             } else {
183             $color[$x] = $color[$same[$x]];
184             }
185             $image->setPixel($x,$y,$color[$x]);
186             }
187             }
188              
189             warn "GD::SIRDS::gd_sirds stereogram generated" if $main::GD_DEBUGGING;
190              
191             if ($helper_circles) {
192             for (1..10) {
193             $image->arc($width/2-50, $height-10, $_, $_, 0, 360, 0);
194             $image->arc($width/2+50, $height-10, $_, $_, 0, 360, 0);
195             }
196             warn "GD::SIRDS::gd_sirds helper dots placed" if $main::GD_DEBUGGING;
197             }
198              
199              
200             return $image;
201             }
202              
203             #=============================================================================
204             # Helper subs (not exported)
205             #-----------------------------------------------------------------------------
206             # Convert an image into a depth map.
207             # input: a GD::Image
208             # output: a reference to a 2-dimensional array representing a depth map.
209             # depths are between 0 and 1
210             sub _image2map
211             {
212             my $image = shift;
213              
214             croak "not a GD::Image" unless ref $image eq "GD::Image";
215              
216             # find the luminance of each color in the image
217             my @grey_table;
218             for my $index (0 .. $image->colorsTotal-1) {
219             $grey_table[$index] = &_luminance($image->rgb($index));
220             }
221              
222             my ($width, $height) = $image->getBounds;
223              
224             my @map;
225             for my $x (0 .. $width-1) {
226             for my $y (0 .. $height-1) {
227             $map[$x][$y] = $grey_table[$image->getPixel($x,$y)];
228             }
229             }
230              
231             return \@map;
232             }
233              
234             # make an image, given sizes and colors
235             # input: width, height, color arrayref
236             # output: a GD::Image
237             sub _make_image
238             {
239             my $width = shift;
240             my $height = shift;
241             my $colors = shift;
242              
243             my $image = GD::Image->new($width, $height);
244              
245             foreach my $rgb (@$colors) {
246             $image->colorAllocate(@$rgb);
247             }
248              
249             return $image;
250             }
251              
252             # return the luminance (0 <= luminance <= 1) of an RGB triple
253             # (0 <= r|g|b <= 255).
254             # input: r, g, b
255             # output: luminance
256             sub _luminance
257             {
258             my ($r, $g, $b) = map {$_ / 255} @_; # (0 <= x <= 255) -> (0 <= x <= 1)
259              
260             # from Carey Bunks, _Grokking the GIMP_ (New Riders, 200),
261             # Section 5.5, p. 152. Also at http://gimp-savvy.com/BOOK/
262             return $r * 0.3 + $g * 0.59 + $b * 0.11;
263             }
264              
265             'Woopa woopa woo chuck chuck!';
266              
267             =back
268              
269             =head1 BUGS
270              
271             In some cases, GD seems to posterize (reduce the color depth) of images
272             when it reads them. I don't yet know when this happens. When it does
273             happen, a marked stair-step effect will occur in the generated stereogram.
274              
275             =head1 AUTHOR
276              
277             David "Cogent" Hand, Ecogent@cpan.orgE.
278              
279             Copyright (c) 2002. All rights reserved. This module is free software;
280             you may restribute and/or modify it under the same terms as Perl itself.
281              
282             =head1 SEE ALSO
283              
284             L.
285              
286             Thimbleby, H.W., Inglis, S., and Witten, I.H.
287             "Displaying 3D images: algorithms for single-image random-dot stereograms"
288             IEEE Computer, 27 (10) 38-48, October 1994.
289              
290             N.E. Thing Enterprises, I. Andrews McMeel Publishing, 1995.
291              
292             =cut
293              
294             __END__