File Coverage

blib/lib/Device/PaPiRus.pm
Criterion Covered Total %
statement 27 29 93.1
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 37 39 94.8


line stmt bran cond sub pod time code
1             package Device::PaPiRus;
2             #---AUTOPRAGMASTART---
3 1     1   13905 use 5.012;
  1         3  
4 1     1   4 use strict;
  1         1  
  1         20  
5 1     1   2 use warnings;
  1         4  
  1         28  
6 1     1   610 use diagnostics;
  1         137634  
  1         7  
7 1     1   805 use mro 'c3';
  1         652  
  1         3  
8 1     1   522 use English qw( -no_match_vars );
  1         2776  
  1         3  
9 1     1   320 use Carp;
  1         1  
  1         62  
10             our $VERSION = 1.3;
11 1     1   552 no if $] >= 5.017011, warnings => 'experimental::smartmatch';
  1         8  
  1         5  
12 1     1   713 use Fatal qw( close );
  1         10445  
  1         4  
13             #---AUTOPRAGMAEND---
14              
15 1     1   619 use GD;
  0            
  0            
16              
17             sub new {
18             my ($proto, %config) = @_;
19             my $class = ref($proto) || $proto;
20              
21             my $self = \%config;
22              
23             bless $self, $class; # bless with our class
24              
25             # Let's load the display size
26             if(defined($self->{debug}) && $self->{debug}) {
27             # Debugging only, no actual device
28             $self->{width} = 264;
29             $self->{height} = 176;
30             } else {
31             open(my $ifh, '<', '/dev/epd/panel') or croak($OS_ERROR);
32             my $line = <$ifh>;
33             close $ifh;
34             if($line =~ /\ (\d+)x(\d+)\ /) {
35             ($self->{width}, $self->{height}) = ($1, $2);
36             } else {
37             croak("Can't read panel dimensions!");
38             }
39             }
40              
41             # Default
42             $self->{threshold} = 150;
43             $self->{randomize_white} = 0;
44             $self->{randomize_black} = 0;
45             $self->{dithering} = 0;
46             $self->{greyscale} = 0;
47              
48             return $self;
49             }
50              
51             sub getWidth {
52             my ($self) = @_;
53              
54             return $self->{width};
55             }
56              
57             sub getHeight {
58             my ($self) = @_;
59              
60             return $self->{height};
61             }
62              
63             sub randomizeWhite {
64             my ($self, $val) = @_;
65              
66             $self->{randomize_white} = $val;
67             return;
68             }
69              
70             sub randomizeBlack {
71             my ($self, $val) = @_;
72              
73             $self->{randomize_black} = $val;
74             return;
75             }
76              
77             sub useDithering {
78             my ($self, $val) = @_;
79              
80             $self->{dithering} = $val;
81             return;
82             }
83              
84             sub useGreyscale {
85             my ($self, $val) = @_;
86              
87             $self->{greyscale} = $val;
88             return;
89             }
90              
91             sub setThreshold {
92             my ($self, $threshold) = @_;
93              
94             $threshold = 0 + $threshold;
95             if($threshold < 0) {
96             croak("Threshold can not be less than zero");
97             } elsif($threshold > 255) {
98             croak("Threshold can not be larger than 255");
99             }
100             $self->{threshold} = $threshold;
101             return;
102             }
103              
104             sub fullUpdate {
105             my ($self, $img) = @_;
106              
107             my $panelImage;
108             if($self->{dithering}) {
109             $panelImage = $self->calculateDitheringImage($img);
110             } elsif($self->{greyscale}) {
111             $panelImage = $self->calculateGreyscaleImage($img);
112             } else {
113             $panelImage = $self->calculateImage($img);
114             }
115             return $self->writeImage($panelImage, 'U');
116             }
117              
118             sub partialUpdate {
119             my ($self, $img) = @_;
120              
121             my $panelImage;
122             if($self->{dithering}) {
123             $panelImage = $self->calculateDitheringImage($img);
124             } elsif($self->{greyscale}) {
125             $panelImage = $self->calculateGreyscaleImage($img);
126             } else {
127             $panelImage = $self->calculateImage($img);
128             }
129             return $self->writeImage($panelImage, 'P');
130             }
131              
132             sub writeImage {
133             my ($self, $img, $mode) = @_;
134              
135             if(defined($self->{debug}) && $self->{debug}) {
136             # Debugging only, no actual device
137             return;
138             }
139            
140             open(my $ofh, '>', '/dev/epd/display') or croak($!);
141             binmode $ofh;
142             print $ofh $img;
143             close $ofh;
144              
145             open(my $cfh, '>', '/dev/epd/command') or croak($!);
146             print $cfh $mode;
147             close $cfh;
148              
149             return;
150             }
151              
152             sub calculateImage {
153             my ($self, $img) = @_;
154             my $outimg = '';
155              
156             my ($sourcewidth, $sourceheight) = $img->getBounds();
157             if($sourcewidth != $self->{width} || $sourceheight != $self->{height}) {
158             croak('Image dimensions (' . $sourcewidth . 'x' . $sourceheight . ') do not match panel size (' . $self->{width} . 'x' . $self->{height} . ')!');
159             }
160              
161             # We need to read 8 pixels of the image in one go, turn them into pure black&white bits and stuff the 8 of them into a single byte,
162             # correcting for endianess and all that...
163             for(my $y = 0; $y < $self->{height}; $y++) {
164             for(my $x = 0; $x < ($self->{width} / 8); $x++) {
165             my $buf = '';
166             for(my $offs = 0; $offs < 8; $offs++) {
167             my $index = $img->getPixel(($x*8) + $offs,$y);
168             my ($r,$g,$b) = $img->rgb($index);
169             my $grey = int(($r+$g+$b)/3);
170             if($grey > $self->{threshold}) {
171             if($self->{randomize_white} && int(rand(10000)) % 4 == 0) {
172             $buf .= "1";
173             } else {
174             $buf .= "0";
175             }
176             } else {
177             if($self->{randomize_black} && int(rand(10000)) % 4 == 0) {
178             $buf .= "0";
179             } else {
180             $buf .= "1";
181             }
182             }
183             }
184             my $byte = pack('b8', $buf);
185             $outimg .= $byte;
186             }
187             }
188              
189             return $outimg;
190             }
191              
192             sub calculateGreyscaleImage {
193             my ($self, $img) = @_;
194             my $outimg = '';
195              
196             my ($stepsize, @greys);
197             if($self->{greyscale} == 1) {
198             $stepsize = 2;
199             @greys = ('0000', '1000', '1010', '1110', '1111');
200             } elsif($self->{greyscale} == 2) {
201             $stepsize = 3;
202             @greys = (
203             '000000000',
204             '000010000',
205             '100000001',
206             '010101000',
207             '001010110',
208             '101010101',
209             '010101111',
210             '011111110',
211             '111101111',
212             '111111111',
213             );
214             } elsif($self->{greyscale} == 3) {
215             $stepsize = 4;
216             @greys = (
217             '0000000000000000',
218             '0000000001000000',
219             '0000100000100000',
220             '0010000001000001',
221             '1000001000101000',
222             '1010000000010110',
223             '0001010001101010',
224             '1010110010100100',
225             '1010010101101010',
226             '1001011001011011',
227             '1001011110101101',
228             '1101101001111110',
229             '1011111001111011',
230             '1011011111101111',
231             '1111011111101111',
232             '1111111110111111',
233             '1111111111111111',
234             );
235             } else {
236             croak("Greyscale mode " . $self->{greyscale} . " not implemented!");
237             }
238              
239             my ($sourcewidth, $sourceheight) = $img->getBounds();
240             if($sourcewidth != $self->{width} || $sourceheight != $self->{height}) {
241             croak('Image dimensions (' . $sourcewidth . 'x' . $sourceheight . ') do not match panel size (' . $self->{width} . 'x' . $self->{height} . ')!');
242             }
243              
244              
245              
246             # Init array with the greyscale pixel value of the image
247             my @opixel;
248             my @npixel;
249             for(my $x = 0; $x < $self->{width}; $x++) {
250             my @oline;
251             my @nline;
252             for(my $y = 0; $y < $self->{height}; $y++) {
253             my $index = $img->getPixel($x, $y);
254             my ($r,$g,$b) = $img->rgb($index);
255             my $oldpixel = int(($r+$g+$b)/3);
256             push @oline, $oldpixel;
257             push @nline, 0;
258             }
259             $opixel[$x] = \@oline;
260             $npixel[$x] = \@nline;
261             }
262              
263              
264             # Run greyscale
265             for(my $y = 0; $y < $self->{height} - ($stepsize - 1); $y+= $stepsize) {
266             for(my $x = 0; $x < $self->{width} - ($stepsize - 1); $x+= $stepsize) {
267             my $oldpixel = 0;
268             for(my $ox = 0; $ox < $stepsize; $ox++) {
269             for(my $oy = 0; $oy < $stepsize; $oy++) {
270             $oldpixel += $opixel[$x + $ox]->[$y + $oy];
271             }
272             }
273             $oldpixel = $oldpixel / ($stepsize * $stepsize); # Average
274              
275             my $offs = int($oldpixel / (256 / (($stepsize * $stepsize) + 1)));
276             my @greypixel = split//, $greys[$offs];
277              
278             for(my $ox = 0; $ox < $stepsize; $ox++) {
279             for(my $oy = 0; $oy < $stepsize; $oy++) {
280             $npixel[$x + $ox]->[$y + $oy] = $greypixel[($ox * $stepsize) + $oy];
281             }
282             }
283             }
284             }
285              
286             # We need to read 8 pixels of the image in one go, turn them into pure black&white bits and stuff the 8 of them into a single byte,
287             # correcting for endianess and all that...
288             for(my $y = 0; $y < $self->{height}; $y++) {
289             for(my $x = 0; $x < ($self->{width} / 8); $x++) {
290             my $buf = '';
291             for(my $offs = 0; $offs < 8; $offs++) {
292             my $raw = $npixel[($x * 8) + $offs]->[$y];
293             if($raw >= 0.5) {
294             $buf .= '0';
295             } else {
296             $buf .= '1';
297             }
298             }
299             my $byte = pack('b8', $buf);
300             $outimg .= $byte;
301             }
302             }
303              
304             return $outimg;
305             }
306              
307             sub calculateDitheringImage {
308             my ($self, $img) = @_;
309             my $outimg = '';
310              
311             my ($stepsize, @greys);
312             if($self->{dithering} == 1) {
313             $stepsize = 2;
314             @greys = ('0000', '1000', '1010', '1110', '1111');
315             } elsif($self->{dithering} == 2) {
316             $stepsize = 3;
317             @greys = (
318             '000000000',
319             '000010000',
320             '100000001',
321             '010101000',
322             '001010110',
323             '101010101',
324             '010101111',
325             '011111110',
326             '111101111',
327             '111111111',
328             );
329             } elsif($self->{dithering} == 3) {
330             $stepsize = 4;
331             @greys = (
332             '0000000000000000',
333             '0000000001000000',
334             '0000100000100000',
335             '0010000001000001',
336             '1000001000101000',
337             '1010000000010110',
338             '0001010001101010',
339             '1010110010100100',
340             '1010010101101010',
341             '1001011001011011',
342             '1001011110101101',
343             '1101101001111110',
344             '1011111001111011',
345             '1011011111101111',
346             '1111011111101111',
347             '1111111110111111',
348             '1111111111111111',
349             );
350             } else {
351             croak("Greyscale mode " . $self->{greyscale} . " not implemented!");
352             }
353              
354             my ($sourcewidth, $sourceheight) = $img->getBounds();
355             if($sourcewidth != $self->{width} || $sourceheight != $self->{height}) {
356             croak('Image dimensions (' . $sourcewidth . 'x' . $sourceheight . ') do not match panel size (' . $self->{width} . 'x' . $self->{height} . ')!');
357             }
358              
359              
360              
361             # Init array with the greyscale pixel value of the image
362             my @opixel;
363             my @npixel;
364             for(my $x = 0; $x < $self->{width}; $x++) {
365             my @oline;
366             my @nline;
367             for(my $y = 0; $y < $self->{height}; $y++) {
368             my $index = $img->getPixel($x, $y);
369             my ($r,$g,$b) = $img->rgb($index);
370             my $oldpixel = int(($r+$g+$b)/3);
371             push @oline, $oldpixel;
372             my @tmp;
373             push @nline, \@tmp;
374             }
375             $opixel[$x] = \@oline;
376             $npixel[$x] = \@nline;
377             }
378              
379              
380             # Run greyscale FOR EACH PIXEL
381             for(my $y = 0; $y < $self->{height}; $y++) {
382             for(my $x = 0; $x < $self->{width}; $x++) {
383             my $oldpixel = 0;
384             my $count = 0;
385             for(my $ox = 0; $ox < $stepsize; $ox++) {
386             for(my $oy = 0; $oy < $stepsize; $oy++) {
387             if(($x + $ox) < $self->{width} && ($y + $oy) < $self->{height}) {
388             $oldpixel += $opixel[$x + $ox]->[$y + $oy];
389             $count++
390             }
391             }
392             }
393             next unless $count;
394             $oldpixel = $oldpixel / $count; # Average
395              
396             my $offs = int($oldpixel / (256 / (($stepsize * $stepsize) + 1)));
397             my @greypixel = split//, $greys[$offs];
398              
399             for(my $ox = 0; $ox < $stepsize; $ox++) {
400             for(my $oy = 0; $oy < $stepsize; $oy++) {
401             push @{$npixel[$x + $ox]->[$y + $oy]}, $greypixel[($ox * $stepsize) + $oy];
402             }
403             }
404             }
405             }
406              
407             #print Dumper (\@npixel);
408             #die;
409              
410             # We need to read 8 pixels of the image in one go, turn them into pure black&white bits and stuff the 8 of them into a single byte,
411             # correcting for endianess and all that...
412             # We have multiple b&w values for each pixel. We need to add them up and decide if it really is B or W
413             for(my $y = 0; $y < $self->{height}; $y++) {
414             for(my $x = 0; $x < ($self->{width} / 8); $x++) {
415             my $buf = '';
416             for(my $offs = 0; $offs < 8; $offs++) {
417             #my $raw = $npixel[($x * 8) + $offs]->[$y];
418              
419             my @vals = @{$npixel[($x * 8) + $offs]->[$y]};
420             my $raw = 0;
421             foreach my $val (@vals) {
422             $raw += $val;
423             }
424             if(scalar @vals) {
425             $raw = $raw / (scalar @vals);
426             }
427              
428             if($raw >= 0.5) {
429             $buf .= '0';
430             } else {
431             $buf .= '1';
432             }
433             }
434             my $byte = pack('b8', $buf);
435             $outimg .= $byte;
436             }
437             }
438              
439             return $outimg;
440             }
441              
442             1;
443             __END__