Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Image modules not returning or accepting GD::Image

by Bod (Parson)
on Dec 01, 2022 at 19:24 UTC ( [id://11148484]=perlquestion: print w/replies, xml ) Need Help??

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

At first I thought I had simply found an error in the documentation. But as I seem to have found two related errors in two different modules, I am questioning either my understanding or if I am overlooking something silly.

I'm trying to put three images side by side into a PDF. As I don't know the dimensions of the images, I am using Image::Resize to set them all to the same height. This bit works fine. Then I am using GD::Tiler to stitch them together into a single image before using PDF::API2 to add the images to the PDF.

The documentation for GD::Tiler says that it returns a GD::Image object. But it doesn't - it returns an image. PNG by default. I've confirmed this with $test = 1; in the code sample below. I've also looked at the source code which confirms that the documentation is wrong.

So I've made a new GD::Image object from the PNG as PDF::API2 says that the image method takes a GD::Image object. I have confirmed that we have the right object type using $test = 2; in the code sample below.

However, when I pass the GD::Image object to $pdf->image I get this error:
Not a HASH reference at /home/shoples1/perl5/lib/perl5/PDF/API2.pm line 2359

Here is some test code to demonstrate the problem.

#!/usr/bin/perl use CGI::Carp qw(fatalsToBrowser); use strict; use warnings; use lib "$ENV{'DOCUMENT_ROOT'}/../lib"; use cPanelUserConfig; use PDF::API2; use GD::Tiler qw(tile); my $test = 0; my $pdf = PDF::API2->open("$ENV{'DOCUMENT_ROOT'}/../data/Consent.pdf" +); my $page = $pdf->open_page(1); my @image = ( "$ENV{'DOCUMENT_ROOT'}/images/admin/dogs/boomer.jpg", "$ENV{'DOCUMENT_ROOT'}/images/admin/dogs/1.jpg", "$ENV{'DOCUMENT_ROOT'}/images/admin/dogs/2.jpg", ); my $gd = tile( Images => \@image, Center => 1, ImagesPerRow => 3, ); # GD::Tiler is returning a PNG not a GD::Image object my $image = GD::Image->new($gd); if ($test == 1) { print "Content-type: image/png\n\n"; print $gd; exit; } if ($test == 2) { use Scalar::Util qw(blessed reftype); print "Content-type: text/plain\n\n"; print blessed($image); # GD::Imaage exit; } my $dogs = $pdf->image($image); # FAILS HERE $page->object($dogs, 1000 - $gd->width / 2, 100, 100); $pdf->save("$ENV{'DOCUMENT_ROOT'}/test.pdf"); print "Location: /test.pdf\n\n"; exit;

I've tried looking at the source for PDF::API2 but that part is beyond me. The image method works with a filepath but not with a GD::Image. I've not tried using a filehandle.

Have I found two separate modules with documentation errors around GD::Image or have I missed something obvious here?

Replies are listed 'Best First'.
Re: Image modules not returning or accepting GD::Image
by kcott (Archbishop) on Dec 02, 2022 at 10:43 UTC

    G'day Bod,

    GD::Tiler

    I can confirm that the tile() method returns PNG. I checked that the first eight characters were "\x{89}PNG\r\n\x{1a}\n"; as per "PNG File Format - Header".

    I suspect the original intent was to output raw image data, as you're seeing, not a GD::Image object. If you look down to the Format description, you'll see "Output image format; default is 'PNG'; valid values are 'GIF', 'PNG', 'JPEG'; case insensitive" [my emphasis]. This suggests that this is not a coding problem; just a documentation issue: s/Returns a GD::Image object/Returns raw image data/.

    Another thing about the tile() method is that, if you use "Images => \@images", all of the elements in @images will be converted to GD::Image objects. Avoid this with "Images => [@images]". They got it right in the SYNOPSIS but not, in my opinion, under METHODS.

    You can raise a bug report but don't hold your breath for any changes to be implemented. This module was last updated 16 years ago (2006): I'd guess it's abandonware (could be wrong).

    PDF::API2

    I can also confirm what you're seeing. I used Carp::Always and got stack traces like this:

    Not a HASH reference at /home/ken/perl5/perlbrew/perls/perl-5.36.0/lib +/site_perl/5.36.0/PDF/API2.pm line 2359. PDF::API2::image_gd(GD::Image=SCALAR(0x800ec1020)) called at / +home/ken/perl5/perlbrew/perls/perl-5.36.0/lib/site_perl/5.36.0/PDF/AP +I2.pm line 2240 PDF::API2::image(PDF::API2=HASH(0x8008589c0), GD::Image=SCALAR +(0x800ec1020)) called at ./test.pl line 32

    I concur with ++AM's assessment that this is a bug in the code. At line 2241:

    return image_gd($file, %options);

    should be

    return $self->image_gd($file, %options);

    Note that $self is a blessed hashref (PDF::API2=HASH(0x8008589c0)) while $file is a blessed scalarref (GD::Image=SCALAR(0x800ec1020)).

    In image_gd(), because the real $self isn't being passed, it's reading $file (a scalarref) as $self (should be a hashref): the "Not a HASH reference" error is due to "GD::Image=SCALAR(0x800ec1020)->{'pdf'}" at line 2360.

    I recommend that you do raise a bug report for this. PDF::API2 is updated fairly frequently (last was just a fortnight ago) and I'd imagine there's a good chance this bug will be fixed.

    By the way, I've been referring to the "PDF::API2 source code" for version 2.044. I have version 2.043 installed which probably explains why the line numbers in my messages are out by one; it looks like you have the same issue too.

    Solution

    Given that GD::Tiler::tile() is probably correctly returning image data, and that PDF::API2::image() has (at least currently) a problem with GD::Image objects, just avoid the GD::Image objects altogether. This code works for me:

    #!/usr/bin/env perl use strict; use warnings; use autodie; use PDF::API2; use GD::Tiler 'tile'; my $pdf_file = 'tiled_dogs.pdf'; my $png_file = 'tiled_dogs.png'; my @dog_jpegs = qw{husky.jpg labrador.jpg retriever.jpg}; my $tiled_dogs = tile( Images => [@dog_jpegs], Center => 1, ImagesPerRow => 3, ); my $pdf = PDF::API2::->new($pdf_file); my $page = $pdf->page(); open my $png_fh, '>:raw', $png_file; $png_fh->print($tiled_dogs); close $png_fh; my $canine_triptych = $pdf->image($png_file); unlink $png_file; $page->object($canine_triptych, 100, 650, 400); $pdf->save($pdf_file);

    — Ken

      Given that GD::Tiler::tile() is probably correctly returning image data, and that PDF::API2::image() has (at least currently) a problem with GD::Image objects, just avoid the GD::Image objects altogether.

      I entirely agree that this is the obvious low-effort fix. (++)

      The high-effort solution is this one and that's how I would approach it, assuming that having a PDF as output is non-negotiable. There's a fairly steep learning curve with LaTeX but it is well worth it and will be a skill that you (Bod) can drawn on for a lifetime - at least it has been with me.


      🦛

        assuming that having a PDF as output is non-negotiable

        It is not essential that it uses a PDF - I hadn't considered any alternatives.

        This a little side project. My partner has started looking after dogs here at home. UK legislation requires that Boomer, our office hound, and the visiting dogs have a "meet-and-greet" session to ensure they all get on. If they do, the owner has to sign to say they are happy for their dog(s) to be walked, fed, etc with Boomer. As the meet and greet is outside in a park, I've created a web system to guide the necessary questions then, with the help of Javascript canvas, the owner can sign the webpage and the signature gets uploaded along with pictures of their dogs. This part is to put that signature and dog pictures into a PDF that then gets emailed to then. So no, it doesn't have to be PDF but I cannot think of a format that would work quite as well.

        There's a fairly steep learning curve with LaTeX but it is well worth it and will be a skill that you (Bod) can drawn on for a lifetime - at least it has been with me

        Yes - LaTeX is on my radar. It has come up a couple of times as something that I need to investigate. The steep learning curve puts me off a bit, especially as I rarely work with PDFs programmatically. However, I have just started a new business partnership based around publishing so perhaps in future I will be doing more with dynamically generated PDFs.

        On your recommendation hippo, I will push LaTeX up the list a bit...

      G'day Ken

      Thank you for your usual thoroughness and helpfulness

      just avoid the GD::Image objects altogether

      I had actually come up with the same approach to a solution although I hadn't created it. I was thinking that writing a file out to disc and then reading it back in was a messy workaround and that I should be able to avoid using GD::Image using Perl's data structures. But writing the image file definitely works. Good to have my solution validated with your working example :)

      In the actual code, I still create a GD::Image so that I can read the width needed to centre the image on the PDF. But that is not used for placing the image, just for working out where it needs to be placed.

        "I was thinking that writing a file out to disc and then reading it back in was a messy workaround ..."

        I agree and had the same thought myself. There is a way to do this with in-memory filehandles; however, there were two reasons why I didn't pursue this.

        1. I didn't know if you'd want to reuse the generated image. I see from your "new, additional information" this isn't the case; or, at least, I assume it's not.

          By the way, the "unlink $png_file;" was intended as optional for you and as housekeeping for me; I omitted to specifically state that.

        2. I recalled that you were stuck with a fairly old version of Perl from your ISP. See the "Technical note" in "Opening a filehandle into an in-memory scalar" which may possibly still be a "hurdle" for you.

        As well as a filename, and a GD::Image object when that bug gets fixed, PDF::API2::image() can also take a filehandle. The following code produces exactly the same PDF as my previous code.

        #!/usr/bin/env perl use strict; use warnings; use autodie; use PDF::API2; use GD::Tiler 'tile'; my $pdf_file = 'tiled_dogs.pdf'; my @dog_jpegs = qw{husky.jpg labrador.jpg retriever.jpg}; my $tiled_dogs = tile( Images => [@dog_jpegs], Center => 1, ImagesPerRow => 3, ); my $pdf = PDF::API2::->new(); my $page = $pdf->page(); open my $png_fh, '<:raw', \$tiled_dogs; my $canine_triptych = $pdf->image($png_fh); close $png_fh; $page->object($canine_triptych, 100, 650, 400); $pdf->save($pdf_file);

        Assuming you don't want to keep the generated image, and you're not confronted with technical difficulties, this would be a better solution. I would expect this would also be much faster; but do Benchmark if speed is important to you.

        Update: Two minor tweaks to the code: removed parts left over from the previous script that were no longer needed here.

        — Ken

        In the actual code, I still create a GD::Image so that I can read the width

        No need. PDF::API2 has methods to get image size. + Using PDF::API2::image_whatever directly is safe, BTW.

        use strict; use warnings; use feature 'say'; use open IO => ':raw'; use GD; use PDF::API2; my $gd_obj = GD::Image-> new( scalar qx/convert rose: png:-/ ); my $pdf = PDF::API2-> new; my $pdf_image = $pdf-> image_gd( $gd_obj ); say $pdf_image-> width; say $pdf_image-> height;
OT LaTeX::Driver can be useful, Re: Image modules not returning or accepting GD::Image
by bliako (Monsignor) on Dec 02, 2022 at 09:01 UTC

    Hi Bod, I have nothing on the subject, this is OT: perhaps using a templated broschure in LaTeX could be useful here. Check LaTeX::Driver (with dependencies on the LaTeX binaries). LaTeX code is SGML-based and you will have little problem creating documents with it in no time. Plus there's enormous support around. In the context of what you are trying to do: create and save image(s) to disk via Perl, then create the PDF from a templated LaTeX document which reads documents and receives other content from you, via the driver. To sum it up, IMO, writing the broschure content in LaTeX and adjusting it programmatically/dynamically via Perl would be much more high-level than creating the PDF yourself.

Re: Image modules not returning or accepting GD::Image
by Discipulus (Canon) on Dec 02, 2022 at 07:59 UTC
    Hello Bod,

    probably his Anonymousness here above is correct, I was trying to decrypt the same part of code.. incidentally PDF-API2-2.044/source/t/gd.t runs fine on your box?

    About the tiler, I think your best option is to use GD directly for this: allocate a new image Hx1 Wx3 then copy img1 to 0,0 then img2 to 0,0+W and img3 to 0,0+Wx2 (if I remember parameter positions correctly).

    If you feel brave you can also use CopyResized from GD: so you can avoid also the Image::Resize dependency. Allocate the new empty image as needed and use CopyResized to fill in the three parts.

    I'd try this part of code separately before implementing it in your code: you'll have fun on this :)

    If it is possible avoid CGI stuff while presenting example code to run, not for the CGI per se but it is unrelated to the problem and I'd have to heavily modify your code if I'd want to run it on my own.

    L*

    PS gd_tiler using GD to create tiled images

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
      incidentally PDF-API2-2.044/source/t/gd.t runs fine on your box?

      I didn't recall any errors or warnings during installation but it was a while ago. So I have just upgraded from 2.043 to 2.044 and there were no errors or warnings

      If you feel brave you can also use CopyResized from GD

      I remember using CopyResized. The resizing worked IIRC but it messed up the colour palettes. A trip down memory lane in this thread - GD colorAllocate not changing colour

        > The resizing worked IIRC but it messed up the colour palettes

        Did you tryed: GD::Image->trueColor(1); before creating new images?

        L*

        There are no rules, there are no thumbs..
        Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: Image modules not returning or accepting GD::Image
by Anonymous Monk on Dec 02, 2022 at 07:34 UTC
    Have I found two separate modules with ...errors around GD::Image

    You have.

    For GD::Tiler::tile to return an object as doco claims, it should return $tiled. I guess (untested) you could pass Format => 'gd' to avoid png, but return value would be GD image data in scalar, not an object.

    With PDF::API2, it looks like plain bug in code; it should be method call (on $self) line #2241, compare to similar calls a few lines further (for jpeg, etc.).

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11148484]
Approved by hippo
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others musing on the Monastery: (4)
As of 2024-03-28 23:26 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found