3

I'm trying to change some pixel's color in a PNG file. As a test, the following script should read a (256x256) PNG file, draw a red diagonal on it, and write the resulting image to file. There's a diagonal alright, but it's white. What am I missing?

#!/usr/bin/env perl

use GD;

$in_file = "in_file.png";

open($PNG,"<$in_file");
$im = newFromPng GD::Image($PNG) or die "Cannot open $in_file for importing";
close($PNG);

$red = $im->colorAllocate(255,0,0);

for($i=0;$i<256;$i++) {
 $im->setPixel($i,$i,$red);
}

$out_file = "out_file.png";

open($IF,">$out_file") or die "Cannot open $out_file for writing?";
binmode $IF;
print $IF $im->png();
close($IF);
7
  • 1
    Works for me, see answer below Commented Apr 9, 2024 at 20:30
  • 1
    I ran your code as-is and it worked for me too. Perhaps your PNG uses some unusual combination of options that isn't handled well by GD? Did you create it yourself? If so, how?
    – Ted Lyngmo
    Commented Apr 9, 2024 at 20:40
  • Ted, the image I was using is a tile from Openstreetmap. a.tile.openstreetmap.org/16/22697/31700.png to be precise. Håkon, that worked for me, too.
    – dehulst
    Commented Apr 10, 2024 at 20:07
  • @dehulst Ok, with that png, I reproduced the issue with your code. It also reproduced the issue with Håkons code so I wonder what made it work for you.
    – Ted Lyngmo
    Commented Apr 10, 2024 at 20:25
  • Too late with the edit, so it became an addit ;-) So it looks like it's the image that causes the problem. Still, I cannot think of anything that would allow changing the color of a pixel, but only to white... Any one having any ideas? In te mean time, I'll try to read up on the PNG specs.
    – dehulst
    Commented Apr 10, 2024 at 20:26

2 Answers 2

4

The problem is that the color table in the image is full so colorAllocate fails (and returns -1).

You shouldn't really use colorAllocate on an existing palette based image anyway since the color may already exist in the color table.

If you don't wish to add a new color, you could instead use colorClosest(255,0,0) - but that may give you a color far from what you wanted.

A better option is often colorResolve(255,0,0) that will try to find an exact match for the color in the existing table or add it if it's missing.

In your case however, you can't add the missing color and there is no red in the 256 colors already existing.

One option is to sacrifise the color that is closest to red and simply make it red.

Example:

#!/usr/bin/env perl

use strict;
use warnings;

use GD;

sub forceAllocate {
    my ($im, $wr, $wg, $wb) = @_; # wanted color

    my $color = $im->colorResolve($wr, $wg, $wb);

    my ($gr,$gg,$gb) = $im->rgb($color); # gotten color

    if ($wr != $gr || $wg != $gg || $wb != $gb) {
        # the wanted color doesn't exist and the color table is full

        # deallocate the color we got (which is the closest to the wanted)
        $im->colorDeallocate($color);

        # and now allocate the one we want
        $color = $im->colorAllocate($wr, $wg, $wb);
        die "something went wrong" if($color == -1);
    }

    return $color;
}

my $in_file = "in_file.png";

my $im = GD::Image->newFromPng($in_file) or die "$in_file: $!";

my $red = forceAllocate($im, 255, 0, 0);

printf "red: $red\n"; # print the index of our color

for(my $i=0; $i<256; $i++) {
    $im->setPixel($i, $i, $red);
}

my $out_file = "out_file.png";

open(my $IF,'>', $out_file) or die "$out_file: $!";
binmode $IF;
print $IF $im->png();
close($IF);

Note though that if the replaced color is used a lot, this could change the appearance drastically. You may instead want to look though the image to find the color least used and replace that color instead. I'll leave that up to you.

Another option could be to convert the png to use truecolor instead of a color table of only 256 colors. This will let you use up to ~16 million colors in the same picture. Here colorAllocate doesn't fail, since there is no color table to fill. Note that this operation will increase the size of the resulting image considerably.

Example:

#!/usr/bin/env perl

use strict;
use warnings;

use GD;

my $in_file = "in_file.png";

my $old = GD::Image->newFromPng($in_file) or die "$in_file: $!";
my ($x, $y) = $old->getBounds();

# create a truecolor image
my $im = GD::Image->new($x, $y, 1); # the '1' means truecolor

# and copy the original image into the truecolor image:
$im->copy($old, 0,0, 0,0, $x, $y);

my $red = $im->colorAllocate(255, 0, 0);

printf "red: $red\n";

for(my $i=0; $i<256; $i++) {
    $im->setPixel($i, $i, $red);
}

my $out_file = "out_file.png";

open(my $IF,'>', $out_file) or die "Cannot open $out_file for writing?";
binmode $IF;
print $IF $im->png();
close($IF);

Apparently, newFromPng has a second $truecolor argument ($image = GD::Image->newFromPng($file, [$truecolor])) "... to specify if the return image should be palette-based or truecolor" but my GD version doesn't seem to support that. If that works for you, you don't have to create a new image and copy the old image into that. Instead you could just do:

my $im = GD::Image->newFromPng($in_file, 1) or die "$in_file: $!";
my $red = $im->colorAllocate(255, 0, 0);
1
  • 1
    Ted, the colorClosest indeed yielded a color not resembling red. The forceAllocate version works for me, the resulting color is red enough for me. Working with truecolor almost doubles the size of my test file. Thanks!
    – dehulst
    Commented Apr 11, 2024 at 9:22
1

Tested on Ubuntu 23.10: First I created a blank blue image:

convert -size 256x256 xc:blue in_file.png

Used the following script:

#! /usr/bin/env perl

use v5.38;
use GD;

my $in_file = "in_file.png";
my $out_file = "out_file.png";
my $im = GD::Image->newFromPng($in_file) or die "Cannot open $in_file for importing";
my $red = $im->colorAllocate(255,0,0);
for(my $i=0; $i<256; $i++) {
    $im->setPixel($i,$i,$red);
}
open(my $IF,">:raw", $out_file) or die "Cannot open $out_file for writing";
print $IF $im->png();
close($IF);

Result:

enter image description here

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.