in reply to Double the speed of Imager->setpixel

to generate an image involving about a million calls to Imager->setpixel

... almost certainly indicates a design flaw. Unless, of course, your pixels arrive at random unpredictable coordinates, to be scattered over a megapixel (or larger) canvas. Which I suspect they don't. (And even then it should be setscanline for each pixel with packed color as arg). Doco (emphasis mine):

The getscanline() and setscanline() methods can work with pixels packed into scalars. This is useful to remove the cost of creating color objects, but should only be used when performance is an issue
use strict; use warnings; use feature 'say'; use Time::HiRes 'time'; use Imager; sub get_RGB_triplet { map { int rand 256 } 0 .. 2 } { my $i = Imager-> new( xsize => 1000, ysize => 1000 ); my $t = time; for my $y ( 0 .. 999 ) { for my $x ( 0 .. 999 ) { my $c = Imager::Color-> new( get_RGB_triplet ); $i-> setpixel( x => $x, y => $y, color => $c ) } } say time - $t; } { my $i = Imager-> new( xsize => 1000, ysize => 1000 ); my $t = time; for my $y ( 0 .. 999 ) { my $packed_line = ''; for my $x ( 0 .. 999 ) { $packed_line .= pack 'C3x', get_RGB_triplet; } $i-> setscanline( y => $y, pixels => $packed_line ) } say time - $t; } __END__ 5.78085088729858 0.599802017211914

Replies are listed 'Best First'.
Re^2: Double the speed of Imager->setpixel
by Anonymous Monk on Dec 12, 2022 at 20:17 UTC
    > almost certainly indicates a design flaw. Unless, of course

    Wow that's a big difference! Thanks for the suggestion. The thing that's slowing down my program is sub _moveterm from this cool module Ham::WorldMap which draws the day/night regions on a world map:

    # _moveterm(wtab, noon, width, height) - update illuminated portion of + the globe. sub _moveterm { my ($wtab, $noon, $width, $height) = @_; my $illumMap = Imager->new(ysize => $height, xsize => $width); $illumMap = $illumMap->convert(preset => 'addalpha'); my $day = Imager::Color->new(255, 255, 255, 10); my ($i, $j, $oh, $nl, $nh); for ($i = 0; $i < $height; $i++) { if ($wtab->[$i] >= 0) { $nl = (($noon - ($wtab->[$i] / 2)) + $width) % $width; $nh = ($nl + $wtab->[$i]) - 1; $oh = ($nh - $nl) + 1; if (($nl + $oh) > $width) { for ($j = $nl; $j < $width; $j++) { $illumMap->setpixel(x => $j, y => $i, color => $da +y); } for ($j = 0; $j < ((($nl + $oh) - $width) + 1); $j++) +{ $illumMap->setpixel(x => $j, y => $i, color => $da +y); } } else { for ($j = $nl; $j < (($nl + $oh) + 1); $j++) { $illumMap->setpixel(x => $j, y => $i, color => $da +y); } } } } return $illumMap; }

      Patch to speed synopsis example execution time from 2.6 to 0.8s. Can't deny you the pleasure to replace third loop yourself, similarly to 1st and 2nd loops. Accidentally, that fragment (3d loop) isn't run with DateTime picked for synopsis, btw.

      --- WorldMap.pm.old Fri Jun 17 10:33:43 2016 +++ WorldMap.pm Tue Dec 13 10:27:14 2022 @@ -501,6 +501,7 @@ my $illumMap = Imager->new(ysize => $height, xsize => $width); $illumMap = $illumMap->convert(preset => 'addalpha'); my $day = Imager::Color->new(255, 255, 255, 10); + my $day_p = pack 'C4', 255, 255, 255, 10; my ($i, $j, $oh, $nl, $nh); for ($i = 0; $i < $height; $i++) { @@ -510,12 +511,10 @@ $oh = ($nh - $nl) + 1; if (($nl + $oh) > $width) { - for ($j = $nl; $j < $width; $j++) { - $illumMap->setpixel(x => $j, y => $i, color => $d +ay); - } - for ($j = 0; $j < ((($nl + $oh) - $width) + 1); $j++) + { - $illumMap->setpixel(x => $j, y => $i, color => $d +ay); - } + $illumMap->setscanline(x => $nl, y => $i, + pixels => $day_p x ($width - $nl - 1)); + $illumMap->setscanline(x => 0, y => $i, + pixels => $day_p x ($nl + $oh - $width)); } else { for ($j = $nl; $j < (($nl + $oh) + 1); $j++) { $illumMap->setpixel(x => $j, y => $i, color => $d +ay);
        > Patch to speed synopsis example execution time from 2.6 to 0.8s.

        Design flaw indeed. My app does a few other things and was taking nearly 5 seconds, without the Imager hacks. Your technique brings it down to the 0.8s range. This is astonishing! Feels almost instant, except in that third loop, thank you.

        > Can't deny you the pleasure to replace third loop yourself, similarly to 1st and 2nd loops.

        I barely grasp the original code, scanline is over my head for the time being, can you help an anonymonk out? This seemed logicalish from the third loop variables but didn't do enough, or did too much, not that I even know why:

        $illumMap->setscanline(x => $nl, y => $i, pixels => $day_p x ($nl + $o +h));
        > Accidentally, that fragment (3d loop) isn't run with DateTime picked for synopsis, btw.

        What does that mean? Thanks for your help. It's so much faster 5-6x unbelievable!

        Finally, to maximize enjoyment, Ham::Locator transforms latitude and longitude to pixel cooordinates or dots on the Ham::WorldMap:

        use Ham::Locator; use Ham::WorldMap; my $lat = 40.712778; my $lon = -74.006111; my $png = 'worldmap.png'; my $map = Ham::WorldMap->new; my $loc = Ham::Locator->new; $loc->set_latlng($lat,$lon); $map->dotAtLocator($loc->latlng2loc, 12, Imager::Color->new(0,255,0)); $map->drawNightRegions(DateTime->now); my ($x, $y) = $map->locatorToXY($loc->latlng2loc); $map->write($png) or die $map->errstr; print qq[Dot drawn at $x,$y on $png\n];