Skip Menu |

This queue is for tickets about the CGI-Application-PhotoGallery CPAN distribution.

Report information
The Basics
Id: 37816
Status: open
Priority: 0/
Queue: CGI-Application-PhotoGallery

People
Owner: Nobody in particular
Requestors: tlhackque [...] yahoo.com
Cc:
AdminCc:

Bug Information
Severity: Normal
Broken in:
  • 0.01
  • 0.02
  • 0.03
  • 0.04
  • 0.05
  • 0.07
  • 0.08
  • 0.09
  • 0.10
  • 0.11
  • 0.13
  • 0.14
Fixed in: (no value)



Subject: Autorotation support
With the advent of orientation sensors in newer cameras, other browsers automagically rotate images to correct for camera orientation. When the same photos are displayed in PhotoGallery, people are confused because they aren't automagically rotated. The attached patch implements auto-rotation for the GD library. (I leave it as an exercise for someone else wants to port the patch to ImageMagick.) The patch is in two parts, although three files are attached: rotate_gd_patch is the enabling patch in the graphics library interface. pg_autorotate_patch is incremental to the patch provided in #37810. pg_autorotate_combined_patch combines both patches. This patch also changes some of the cache keys to ensure uniqueness in some imaginary corner cases - it's a good idea to delete any existing cache when upgrading. Note that rotated images are cached by this patch (but unrotated images are not, as that's just a waste of space.) The patch adds a dependency on Image::MetaData::JPEG. If it doesn't support an image file format, autorotation won't happen, but the image will be displayed as before. Hope this is useful to others.
Subject: rotate_gd_patch.diff
--- /usr/lib/perl5/site_perl/5.8.8/CGI/Application/PhotoGallery/GD.pm.10 2008-03-09 19:38:12.000000000 -0400 +++ /usr/lib/perl5/site_perl/5.8.8/CGI/Application/PhotoGallery/GD.pm 2008-07-20 21:03:22.000000000 -0400 @@ -19,6 +19,7 @@ use warnings; use GD; +use Image::MetaData::JPEG; our $VERSION = '0.10'; @@ -46,7 +47,7 @@ my $file = shift; my $size = shift; - my $image = $self->load( $file ); + my ($image, $hint) = $self->load( $file ); my ( $width, $height ) = $image->getBounds(); @@ -76,6 +77,7 @@ Loads C<$file> and returns a L<GD::Image>. +Also returns a cache hint. =cut sub load { @@ -97,7 +99,50 @@ $image = GD::Image->new( $file ); } - return $image; + $Image::MetaData::JPEG::show_warnings = undef; + + my $jpg = new Image::MetaData::JPEG($file, qr/APP(0|1)/, 'FASTREADONLY'); +# die 'Error: ' . Image::MetaData::JPEG::Error() unless $jpg; + return ($image, 0) unless $jpg; + + my $snum = $jpg->retrieve_app1_Exif_segment(-1); + for( my $i = 0; $i < $snum; $i++) { + + my $seg = $jpg->retrieve_app1_Exif_segment($i); + my $imgdat = $seg->get_Exif_data('IMAGE_DATA', 'TEXTUAL'); + + my $o = $imgdat->{'Orientation'}; + next unless $o; + + my $orient = @$o[0]; + if( $orient == 1 ) { # Top, Left-Hand + # Normal orientation + return ($image, 0); + } elsif( $orient == 2 ) { # Top, Right-Hand + $image->flipHorizontal(); + return ($image, 1); + } elsif( $orient == 3 ) { # Bottom, Right-Hand + $image->rotate180(); + return ($image, 1); + } elsif( $orient == 4 ) { # Bottom, Left-Hand + $image->flipVertical(); + return ($image, 1); + } elsif( $orient == 5 ) { # Left-Hand, Top + $image->flipVertical(); + return ($image->copyRotate90(), 1); + } elsif( $orient == 6 ) { # Right-Hand, Top + return ($image->copyRotate90(), 1); + } elsif( $orient == 7 ) { # Right-Hand, Bottom + $image->flipHorizontal(); + return ($image->copyRotate90(), 1); + } elsif( $orient == 8 ) { # Left-Hand, Bottom + return ($image->copyRotate270(), 1); + } + } + + # Orientation unknown or not specified + + return ($image, 0); } =head2 size( $file ) @@ -110,7 +155,7 @@ my $self = shift; my $file = shift; - my $image = $self->load( $file ); + my ($image, $hint) = $self->load( $file ); return $image->getBounds(); }
Subject: pg_autorotate_patch.diff
--- /usr/lib/perl5/site_perl/5.8.8/CGI/Application/PhotoGallery.pm.13+ 2008-07-20 11:28:09.000000000 -0400 +++ /usr/lib/perl5/site_perl/5.8.8/CGI/Application/PhotoGallery.pm 2008-07-20 21:32:21.000000000 -0400 @@ -359,9 +359,9 @@ my $output; my $cache = $self->cache; - my $key = $directory; + my $key = "$directory.#dir"; my $lastmod = ( stat( $directory ) )[ 9 ]; - my $cstamp = "$directory/.cachetime"; + my $cstamp = "$directory/#dir.cachetime"; if ( $output = $cache->get( $key ) ) { my $cachetime = $cache->get( $cstamp ); @@ -458,7 +458,7 @@ my $path = "$dir$photo"; my $cache = $self->cache; - my $key = "$path$size"; + my $key = "$path.#thumb$size"; my $lastmod = ( stat( $path ) )[ 9 ]; my $data; @@ -489,7 +489,7 @@ eval { $data = $gfx->resize( $errimg, $size ); }; if ($@) { - return $self->handle_error( "Unable to resize $path; file may be corrupt; error image also corrupt\nError string:$errstr" ); + return $self->handle_error( "Unable to resize $path; file may be corrupt; error image also corrupt\n<p>Error string:$errstr" ); } $self->header_props( @@ -525,6 +525,10 @@ my $dir = $self->param( 'photos_dir' ); my $photo = $query->param( 'photo' ); my $path = "$dir$photo"; + my $cache = $self->cache; + my $key = "$path.#image"; + my $cstamp="$key.#cachetime"; + my $cmime = "$key.#mime"; die 'ERROR: Missing $photo query argument.' unless $photo; @@ -535,21 +539,71 @@ $reqmod = HTTP::Date::str2time( ( split( /;/, $header, 2 ) )[ 0 ] ); } + my ($data, $mime); + if ( ($data = $cache->get( $key )) && ($mime = $cache->get( $cmime )) && ($mime eq $self->mime_types->mimeTypeOf( $path )) ) { + my $cachetime = $cache->get( $cstamp ); + if( $cachetime && $cachetime == $lastmod ) { + if ( $reqmod && $reqmod == $lastmod ) { + $self->header_props( { -status => '304 Not Modified' } ); + return; + } + + $self->header_add( + { +#Debug -status => "200 Cached", + -type => $mime, + -last_modified => HTTP::Date::time2str( $lastmod ) + } + ); + return $data; + } + } + + # Not cached. Still may be unchanged, as we only cache if rotated (to save space). + if ( $reqmod && $reqmod == $lastmod ) { $self->header_props( { -status => '304 Not Modified' } ); return; } - open( PHOTO, $path ) or die "ERROR: Cannot open $path: $!"; - binmode PHOTO; - my $data = do { local $/; <PHOTO> }; - close( PHOTO ); + $mime = $self->mime_types->mimeTypeOf( $path ); + + # The library handles auto-rotation + + my $gfx = $self->gfx_lib; + my $cacheThis; + eval { + ($data, $cacheThis) = $gfx->load( $path ); + }; if ($@) { + die "Unable to load $path: $@\n"; + } + + if( $cacheThis ) { + # The library did work, such as auto-rotation + # + # We could figure out how to convert to the original format, but it doesn't seem worth the effort. + # Note that we are accepting a default quality that will often compress the image. + + $data = $data->jpeg(); + $mime = 'image/jpeg'; + + $cache->set( $key => $data ); + $cache->set( $cstamp => $lastmod ); + $cache->set( $cmime => $mime ); + } else { + # No work, read the actual file data + + open( PHOTO, $path ) or die "ERROR: Cannot open $path: $!"; + binmode PHOTO; + $data = do { local $/; <PHOTO> }; + close( PHOTO ); + } $self->header_props( - { -type => $self->mime_types->mimeTypeOf( $path ), - -last_modified => HTTP::Date::time2str( $lastmod ) - } - ); + { -type => $mime, + -last_modified => HTTP::Date::time2str( $lastmod ) + } + ); return $data; } @@ -615,7 +669,7 @@ eval { ($width, $height) = $gfx->size( $path ); }; if ($@) { - return $self->handle_error("Unable to determine size of $path; file may be corrupt.\nError string:$@"); + return $self->handle_error("Unable to determine size of $path; file may be corrupt.\n<p>Error string:$@"); } # get data for prev/next/parent links
Subject: pg_autorotate_combined_patch.diff
--- /usr/lib/perl5/site_perl/5.8.8/CGI/Application/PhotoGallery.pm.13 2008-03-11 11:23:32.000000000 -0400 +++ /usr/lib/perl5/site_perl/5.8.8/CGI/Application/PhotoGallery.pm 2008-07-20 21:32:21.000000000 -0400 @@ -138,6 +138,13 @@ and height attributes on the image tag, thus saving the image will retain the full resolution. +=head2 max_height + +Setting this value will force the browser to scale images down to this +particular height and proportioned width. This is done by setting the width +and height attributes on the image tag, thus saving the image will retain the +full resolution. + =head2 cache_root Specifies where the file cache data will be stored. Defaults to FileCache @@ -352,9 +359,9 @@ my $output; my $cache = $self->cache; - my $key = $directory; + my $key = "$directory.#dir"; my $lastmod = ( stat( $directory ) )[ 9 ]; - my $cstamp = "$directory/.cachetime"; + my $cstamp = "$directory/#dir.cachetime"; if ( $output = $cache->get( $key ) ) { my $cachetime = $cache->get( $cstamp ); @@ -451,7 +458,7 @@ my $path = "$dir$photo"; my $cache = $self->cache; - my $key = "$path$size"; + my $key = "$path.#thumb$size"; my $lastmod = ( stat( $path ) )[ 9 ]; my $data; @@ -473,7 +480,26 @@ unless ( $data ) { my $gfx = $self->gfx_lib; - $data = $gfx->resize( $path, $size ); + eval { + $data = $gfx->resize( $path, $size ); + }; if ($@) { + my $errstr = $@; + my $errimg = $self->_dist_file( 'nothumb.jpg' ); + + eval { + $data = $gfx->resize( $errimg, $size ); + }; if ($@) { + return $self->handle_error( "Unable to resize $path; file may be corrupt; error image also corrupt\n<p>Error string:$errstr" ); + } + + $self->header_props( + { -type => $self->mime_types->mimeTypeOf( $errimg ), + -last_modified => HTTP::Date::time2str( ( stat( $errimg ) )[ 9 ] ) + } + ); + binmode STDOUT; + return $data; + } $cache->set( $key => $data ); } @@ -499,6 +525,10 @@ my $dir = $self->param( 'photos_dir' ); my $photo = $query->param( 'photo' ); my $path = "$dir$photo"; + my $cache = $self->cache; + my $key = "$path.#image"; + my $cstamp="$key.#cachetime"; + my $cmime = "$key.#mime"; die 'ERROR: Missing $photo query argument.' unless $photo; @@ -509,21 +539,71 @@ $reqmod = HTTP::Date::str2time( ( split( /;/, $header, 2 ) )[ 0 ] ); } + my ($data, $mime); + if ( ($data = $cache->get( $key )) && ($mime = $cache->get( $cmime )) && ($mime eq $self->mime_types->mimeTypeOf( $path )) ) { + my $cachetime = $cache->get( $cstamp ); + if( $cachetime && $cachetime == $lastmod ) { + if ( $reqmod && $reqmod == $lastmod ) { + $self->header_props( { -status => '304 Not Modified' } ); + return; + } + + $self->header_add( + { +#Debug -status => "200 Cached", + -type => $mime, + -last_modified => HTTP::Date::time2str( $lastmod ) + } + ); + return $data; + } + } + + # Not cached. Still may be unchanged, as we only cache if rotated (to save space). + if ( $reqmod && $reqmod == $lastmod ) { $self->header_props( { -status => '304 Not Modified' } ); return; } - open( PHOTO, $path ) or die "ERROR: Cannot open $path: $!"; - binmode PHOTO; - my $data = do { local $/; <PHOTO> }; - close( PHOTO ); + $mime = $self->mime_types->mimeTypeOf( $path ); + + # The library handles auto-rotation + + my $gfx = $self->gfx_lib; + my $cacheThis; + eval { + ($data, $cacheThis) = $gfx->load( $path ); + }; if ($@) { + die "Unable to load $path: $@\n"; + } + + if( $cacheThis ) { + # The library did work, such as auto-rotation + # + # We could figure out how to convert to the original format, but it doesn't seem worth the effort. + # Note that we are accepting a default quality that will often compress the image. + + $data = $data->jpeg(); + $mime = 'image/jpeg'; + + $cache->set( $key => $data ); + $cache->set( $cstamp => $lastmod ); + $cache->set( $cmime => $mime ); + } else { + # No work, read the actual file data + + open( PHOTO, $path ) or die "ERROR: Cannot open $path: $!"; + binmode PHOTO; + $data = do { local $/; <PHOTO> }; + close( PHOTO ); + } $self->header_props( - { -type => $self->mime_types->mimeTypeOf( $path ), - -last_modified => HTTP::Date::time2str( $lastmod ) - } - ); + { -type => $mime, + -last_modified => HTTP::Date::time2str( $lastmod ) + } + ); return $data; } @@ -585,7 +665,12 @@ my $gfx = $self->gfx_lib; - my ( $width, $height ) = $gfx->size( $path ); + my ( $width, $height ); + eval { + ($width, $height) = $gfx->size( $path ); + }; if ($@) { + return $self->handle_error("Unable to determine size of $path; file may be corrupt.\n<p>Error string:$@"); + } # get data for prev/next/parent links my ( undef, $search_dir ) = fileparse( $path ); @@ -622,6 +707,14 @@ } } + if ( defined( my $max_height = $self->param( 'max_height' ) ) ) { + if ( $height > $max_height ) { + my $scale = $max_height / $height; + $width = int( $width * $scale ); + $height = int( $height * $scale ); + } + } + $html->param( photo => $photo, width => $width, @@ -675,7 +768,7 @@ $error = 'ERROR: File not found.'; } else { - $self->header_props( { -status => '500 Error' } ); + $self->header_props( { -status => '200 Error' } ); } my $html = $self->load_tmpl(