in reply to Image modules not returning or accepting GD::Image

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

Replies are listed 'Best First'.
Re^2: Image modules not returning or accepting GD::Image
by hippo (Archbishop) on Dec 02, 2022 at 11:02 UTC
    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...

        This part is to put that signature and dog pictures into a PDF that then gets emailed to [them]. So no, it doesn't have to be PDF but I cannot think of a format that would work quite as well.

        TBF, it sounds like a good use of PDF and I might go that way too. The obvious alternative is HTML email or perhaps even better email them a link with a unique code to a dynamic page on your site which produces the HTML. They can easily then print that off (or to PDF!) if they want.

        I have just started a new business partnership based around publishing so perhaps in future I will be doing more with dynamically generated PDFs.

        If you are moving into publishing then I would be even more strong in my recommendation to look seriously at LaTeX. Its styles and classes allow for templating of documents or entire books as you see fit and with human-readable source there is the option of generating the entire thing programmatically.

        More than half a lifetime ago* my classmates and I were given a course on LaTeX by our professor and while we could see the advantages he was espousing, the learning curve was indeed clearly steep. We continued to use various WYSIWYG alternatives (mostly DECWrite and/or Aster*x - I did say it was a long time ago) to produce our periodic assignments but eventually I and one or two others started to get to grips with LaTeX and produced some decent reports with it.

        When the time came to write our dissertations the class was split pretty evenly between LaTeX and the other options. I was very glad that I chose LaTeX in the end. The fact that the source was plain text meant that I could work on it anywhere (Windows PCs, Linux PCs, Unix Workstations, the Vaxen - did I mention this was last millennium?) and it took up much less space so could be saved on even the smallest floppy.

        One of my friends on the course persisted with using MS Word of all things. One day it crashed on her, corrupting the file and she lost not only that day's work but almost a week's worth to the last safe backup. I can still remember that event clearly to this day and it has always reinforced my decisions to go with open, text-based systems whenever possible and eschew proprietary binary alternatives. I have never regretted it.

        OK, back to the topic. Go with LaTeX for all the reasons above. See some of the beautiful output it produces. Know that it builds on decades of work by Donald Knuth so you just know it's going to be stellar.

        * Damn, I'm old.


        🦛

Re^2: Image modules not returning or accepting GD::Image
by Bod (Parson) on Dec 03, 2022 at 00:40 UTC

    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

        Thanks Ken - seeing a working example is extremely helpful.

        My first thought is that I would need to use a pipe but then realised that there doesn't seem to be anything to pipe to...

        You are right, I am using Perl v5.16 (still not moved toa VPS). I will try it without writing out and deleting a file some time but it is not a very high priority as the thing now works and will only get used a couple of times each month. Speed is not at all important - it could take half an hour, and it would still be acceptable.

      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;

        Ah!
        That's very helpful - thanks