xachen has asked for the wisdom of the Perl Monks concerning the following question:

Hello, I'm trying to add text to an existing image. The code below works for generating a new GD image, putting text on it and exporting, however when I try to copy a source image it doesn't properly "copy" to the main GD image.
#!/usr/bin/perl use Getopt::Std; use GD; use GD::Simple; use GD::Text; use GD::Text::Wrap; use GD::Text::Align; use Image::Size; use Data::Dumper; my %opt; my $img_width = 300; my $img_height = 300; my $start_height = 1; getopt('tfscoipj', \%opt); if ($opt{'p'} =~ /^(top|bottom)$/ && $opt{'j'} =~ /^(left|center|right +)$/ && -f $opt{'i'} && $opt{'i'} =~ /\.(jpg|JPG|jpeg|JPEG|gif|GIF|png +|PNG)$/) { my ($embed_width, $embed_height) = imgsize($opt{'i'}); if ($embed_width > 300) { $img_width = $embed_width; } $img_height = $embed_height + 300; $start_height += (4 + $embed_height); } else { $opt{'j'} = "left"; } #GD::Image->trueColor(1); my $gd = GD::Image->new($img_width, $img_height); my %colors = ( 'white' => $gd->colorAllocate(255,255,255), 'black' => $gd->colorAllocate(0,0,0), 'red' => $gd->colorAllocate(255,0,0), 'blue' => $gd->colorAllocate(0,0,255), 'grey' => $gd->colorAllocate(127,127,127), ); if ($opt{'t'} !~ /^[a-zA-Z0-9\.\-\_\s\!\#\:]{1,20}$/) { print "ERROR: Must provide text string be between 1 and 20 charact +ers long.\n"; exit -1; } elsif ($opt{'f'} !~ /^[a-zA-Z0-9]{1,20}$/) { print "ERROR: Must provide font name\n"; exit -1; } elsif ($opt{'s'} !~ /^[\d]{1,2}$/) { print "ERROR: Must provide valid font size\n"; exit -1; } elsif ($opt{'s'} < 12 || $opt{'s'} > 30) { print "ERROR: Font size must be 12-30 points\n"; exit -1; } elsif (!defined($colors{$opt{'c'}})){ print "ERROR: Must provide a valid color\n"; exit -1; } elsif (!-f "/usr/ttfonts/".$opt{'f'}.".ttf") { print "ERROR: $opt{'f'} font not available!\n"; exit -1; } elsif (length($opt{'o'}) < 1) { print "ERROR: must provide file name to save image to\n"; exit -1; } #elsif (-f $opt{'o'}) { # print "ERROR: file $opt{'o'} already exists\n"; # exit -1; #} print "$opt{'o'}: $opt{'c'} $opt{'f'} $opt{'s'}: $opt{'t'}\n"; $gd->transparent($colors{'white'}); $gd->setAntiAliased($colors{$opt{'c'}}); my $wrapbox = GD::Text::Wrap->new($gd, line_space => 3, color => $colors{$opt{'c'}}, text => $opt{'t'}, ); $wrapbox->set_font("/usr/ttfonts/".$opt{'f'}.".ttf", $opt{'s'}); $wrapbox->set(align => $opt{'j'}); $wrapbox->draw(1, $start_height); #$gd->rectangle($wrapbox->get_bounds(1, 300), $colors{'white'}); if ($start_height > 1 && $embed_height > 1 && $embed_width > 1) { my $source_img; if ($opt{'i'} =~ /\.(jpg|jpeg|JPEG|JPG)$/) { $source_img = GD::Image->newFromJpeg($opt{'i'}); } print "$img_width $img_height\n"; $gd->copy($source_img, 1, 1, 0, 0, $img_width, $img_height); } open(FILE, ">".$opt{'o'}); binmode STDOUT; print FILE $gd->jpeg; close(FILE); sub rgb2n { unpack 'N', pack 'CCCC', 0, @_ }

Replies are listed 'Best First'.
Re: Add text to image
by BrowserUk (Patriarch) on Jan 06, 2010 at 08:17 UTC

    You create a new image:

    my $gd = GD::Image->new($img_width, $img_height);

    Write some text into it:

    my $wrapbox = GD::Text::Wrap->new($gd, line_space => 3, color => $colors{$opt{'c'}}, text => $opt{'t'}, );

    Then load an image from disk:

    $source_img = GD::Image->newFromJpeg($opt{'i'});

    And attempt to copy the text onto it:

    $gd->copy($source_img, 1, 1, 0, 0, $img_width, $img_height);

    Accept that you are copying from the image you loaded, into the image containing the text! Thereby overwriting the text you drew with a 300 x 300 section of the image--assuming it is at least that big. You then write that to disk.

    Does that accurately sum up the results you describe as "it doesn't properly "copy""?

    The copy operation should almost certainly be more like:

    $source_img->copy($gd, 1, 1, 0, 0, $img_width, $img_height);

    Though you should probably use the width & height of the bounding box returned from the text operation to limit the amount of the original image you overwrite.

    Perhaps better still, depending upon your goals, would be to simply write the text directly onto the loaded image. You could then set the background color of the text operation to transparent and get see-through text.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Add text to image
by CountZero (Bishop) on Jan 06, 2010 at 07:08 UTC
    Did you check whether
    $source_img = GD::Image->newFromJpeg($opt{'i'});
    succeeds? In case of error, it returns undef. You can check that with the usual
    or die "Failed to load $opt{'i'}: $!
    .

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

Re: Add text to image
by stefbv (Priest) on Jan 06, 2010 at 08:23 UTC

    After running your script and another test script based on the SYNOPSIS of the GD::Text::Wrap module, my conclusion is that unless I miss something critical about the usage, it may be something wrong with the module. Both scripts result image contains no text, but only some strange colored rectangles

    use strict; use warnings; use GD; use GD::Text::Wrap; my $gd = GD::Image->new(800,600); my $white = $gd->colorAllocate(255,255,255); my $blue = $gd->colorAllocate(0,0,255); my $text = <<EOSTR; Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. EOSTR my $wrapbox = GD::Text::Wrap->new( $gd, line_space => 4, color => $blue, text => $text, ); $wrapbox->set_font(gdMediumBoldFont); $wrapbox->set_font('arial', 12); $wrapbox->set(align => 'left', width => 120); $wrapbox->draw(10,140); $gd->rectangle($wrapbox->get_bounds(10,140), $blue); open my $file, '>', 'outfile.jpg'; binmode $file; print { $file } $gd->jpeg; close $file;

    An alternative solution is to use PerlMagick (ImageMagick).

    Update: revised as suggested by gmargo.

    Update2: revised as suggested by gmargo :-) Thank you.

      stefbv, your script works just fine. However, you write the text in WHITE while the background is also WHITE. Change the text color and it shows up.

Re: Add text to image
by gmargo (Hermit) on Jan 06, 2010 at 14:12 UTC

    You set binmode STDOUT; when I think you meant to set binmode FILE;.

Re: Add text to image
by zentara (Cardinal) on Jan 06, 2010 at 16:02 UTC
Re: Add text to image
by gmargo (Hermit) on Jan 06, 2010 at 17:11 UTC

    There's another problem, which you would see immediately if you use strict;.

    Line 90 of your script has this line:

    if ($start_height > 1 && $embed_height > 1 && $embed_width > 1) {

    However, $embed_height and $embed_width are actually undefined (and treated as zero), since they are not the same variables defined on line 19 of your script, which are in a different scope:

    my ($embed_width, $embed_height) = imgsize($opt{'i'});

    New Year's Resolution: always use strict and warnings.

    Update: If you fix the above by moving the variable scope, and if you follow BrowserUk's advice about overwriting your text with the image (just move the $wrapbox->draw(1, $start_height); line to after the image copy), then your program will work.