sub printAddGreyscaleImage { my ($self, $filename, $isbindata, $imagesoftness) = @_; my $reph = $self->{reph}; if(!defined($imagesoftness)) { $imagesoftness = 1; } my $rawpic; if($isbindata) { $rawpic = GD::Image->newFromPngData($filename, 0); } else { $rawpic = GD::Image->newFromPng($filename, 0); } my ($w, $h) = $rawpic->getBounds(); my $destw = $self->{width}; my $scale = $w / $destw; my $desth = int($h / $scale); # Check if we got that image already cached my $cachekey = $imagesoftness . '_' . sha256_hex($rawpic->png); $reph->debuglog(" KEY $cachekey"); if(defined($self->{imagecache}->{$cachekey})) { $reph->debuglog(" using cached greyscale image conversion"); $self->{img}->copyResized($self->{imagecache}->{$cachekey}, 0, $self->{imgoffs}, # DEST X Y 0, 0, # SRC X Y $destw, $desth, # DEST W H $destw, $desth, # SRC W H ); $self->{imgoffs} += $desth; return; } $reph->debuglog(" need to do image conversion"); my $pic = GD::Image->new($destw, $desth); # Copy palette my $colorcount = $rawpic->colorsTotal; for(my $c = 0; $c < $colorcount; $c++) { my ($r,$g,$b) = $rawpic->rgb($c); $pic->colorAllocate($r, $g, $b); } $pic->copyResized($rawpic, 0, 0, # DEST X Y 0, 0, # SRC X Y $destw, $desth, # DEST W H $w, $h, # SRC W H ); # For caching converted images my $cachepic = GD::Image->new($destw, $desth); my $cachewhite = $cachepic->colorAllocate(255, 255, 255); my $cacheblack = $cachepic->colorAllocate(0, 0, 0); my @pixels; # Prepare for dithering for(my $y = 0; $y < $desth; $y++) { for(my $x = 0; $x < $destw; $x++) { my $index = $pic->getPixel($x, $y); my ($r,$g,$b) = $pic->rgb($index); my $greypixel = int(($r+$g+$b)/3); my $oldpixel = $greypixel * 1.0; $pixels[$x]->[$y] = $oldpixel; } } if($imagesoftness == 0) { for(my $y = 0; $y < $desth; $y++) { for(my $x = 0; $x < $destw; $x++) { my $oldpixel = $pixels[$x]->[$y]; # Simple monochrome conversion if($oldpixel < 128) { $self->{img}->setPixel($x, $y + $self->{imgoffs}, $self->{imgblack}); $cachepic->setPixel($x, $y, $cacheblack); } } } $self->{imagecache}->{$cachekey} = $cachepic; } elsif($imagesoftness == 2) { # Floyd-Steinberg dithering my @dither = ( [0, 0, 7], [3, 5, 1], ); for(my $y = 0; $y < $desth; $y++) { for(my $x = 0; $x < $destw; $x++) { my $oldpixel = $pixels[$x]->[$y]; # "Find closed palette/index value my $newpixel = 255.0; if($oldpixel < 128) { $newpixel = 0.0; } $pixels[$x]->[$y] = $newpixel; my $quanterror = $oldpixel - $newpixel; for(my $ditherx = 0; $ditherx < 3; $ditherx++) { for(my $dithery = 0; $dithery < 2; $dithery++) { my $deltax = $x + $ditherx - 1; my $deltay = $y + $dithery; my $factor = $dither[$dithery]->[$ditherx]; next unless($factor); next if($deltax < 0 || $deltax >= $destw); next if($deltay < 0 || $deltay >= $desth); my $change = $factor * $quanterror / 16.0; #print "## $oldpixel $newpixel $factor $quanterror $change\n"; $pixels[$deltax]->[$deltay] += $change; } } } } for(my $y = 0; $y < $desth; $y++) { for(my $x = 0; $x < $destw; $x++) { if($pixels[$x]->[$y] < 128) { #print "$x $y ", $pixels[$x]->[$y], "\n"; $self->{img}->setPixel($x, $y + $self->{imgoffs}, $self->{imgblack}); $cachepic->setPixel($x, $y, $cacheblack); } } } $self->{imagecache}->{$cachekey} = $cachepic; } elsif($imagesoftness == 1) { # Dithering https://en.wikipedia.org/wiki/Error_diffusion#minimized_average_error my @dither = ( [0, 0, 0, 7, 5], [3, 5, 7, 5, 3], [1, 3, 5, 3, 1], ); for(my $y = 0; $y < $desth; $y++) { for(my $x = 0; $x < $destw; $x++) { my $oldpixel = $pixels[$x]->[$y]; # "Find closed palette/index value my $newpixel = 255.0; if($oldpixel < 128) { $newpixel = 0.0; } $pixels[$x]->[$y] = $newpixel; my $quanterror = $oldpixel - $newpixel; for(my $ditherx = 0; $ditherx < 5; $ditherx++) { for(my $dithery = 0; $dithery < 3; $dithery++) { my $deltax = $x + $ditherx - 2; my $deltay = $y + $dithery - 1; my $factor = $dither[$dithery]->[$ditherx]; next unless($factor); next if($deltax < 0 || $deltax >= $destw); next if($deltay < 0 || $deltay >= $desth); my $change = $factor * $quanterror / 48.0; #print "## $oldpixel $newpixel $factor $quanterror $change\n"; $pixels[$deltax]->[$deltay] += $change; } } } } for(my $y = 0; $y < $desth; $y++) { for(my $x = 0; $x < $destw; $x++) { if($pixels[$x]->[$y] < 128) { #print "$x $y ", $pixels[$x]->[$y], "\n"; $self->{img}->setPixel($x, $y + $self->{imgoffs}, $self->{imgblack}); $cachepic->setPixel($x, $y, $cacheblack); } } } $self->{imagecache}->{$cachekey} = $cachepic; } elsif($imagesoftness == 3) { my @rawgreys = ( '0000000000000000', '0000000001000000', '0000100000100000', '0010000001000001', '1000001000101000', '1010000000010110', '0001010001101010', '1010110010100100', '1010010101101010', '1001011001011011', '1001011110101101', '1101101001111110', '1011111001111011', '1011011111101111', '1111011111101111', '1111111110111111', '1111111111111111', ); my $levels = scalar @rawgreys; my $bitlen = length($rawgreys[0]); my @greys; foreach my $rawgrey (@rawgreys) { my @parts = split//, $rawgrey; push @greys, \@parts; } for(my $y = 0; $y < $desth; $y++) { for(my $x = 0; $x < $destw; $x++) { my $index = $pic->getPixel($x, $y); my ($r,$g,$b) = $pic->rgb($index); my $greypixel = int(($r+$g+$b)/3); my $level = int($greypixel / (255 / $levels)); my $offs = int(rand($bitlen)); my $bit = $greys[$level]->[($x + $offs) % $bitlen]; if(!$bit) { $self->{img}->setPixel($x, $y + $self->{imgoffs}, $self->{imgblack}); $cachepic->setPixel($x, $y, $cacheblack); } else { } } } $self->{imagecache}->{$cachekey} = $cachepic; } $self->{imgoffs} += $desth; return; }