]> git.donarmstrong.com Git - deb_pkgs/libapache-gallery-perl.git/blob - lib/Apache/Gallery.pm
[svn-upgrade] new version libapache-gallery-perl (1.0.1)
[deb_pkgs/libapache-gallery-perl.git] / lib / Apache / Gallery.pm
1 package Apache::Gallery;
2
3 # $Author: mil $ $Rev: 324 $
4 # $Date: 2011-02-22 21:56:06 +0100 (Tue, 22 Feb 2011) $
5
6 use strict;
7
8 use vars qw($VERSION);
9
10 $VERSION = "1.0.1";
11
12 BEGIN {
13
14         if (exists($ENV{MOD_PERL_API_VERSION})
15                 and ($ENV{MOD_PERL_API_VERSION}==2)) {
16                 require mod_perl2;
17                 if ($mod_perl::VERSION >= 1.99 && $mod_perl::VERSION < 2.0) {
18                         die "mod_perl 2.0.0 or later is now required";
19                 }
20                 require Apache2::ServerRec;
21                 require Apache2::RequestRec;
22                 require Apache2::Log;
23                 require APR::Table;
24                 require Apache2::RequestIO;
25                 require Apache2::SubRequest;
26                 require Apache2::Const;
27         
28                 Apache2::Const->import(-compile => 'OK','DECLINED','FORBIDDEN','NOT_FOUND','HTTP_NOT_MODIFIED');
29
30                 $::MP2 = 1;
31         } else {
32                 require mod_perl;
33
34                 require Apache;
35                 require Apache::Constants;
36                 require Apache::Request;
37         
38                 Apache::Constants->import('OK','DECLINED','FORBIDDEN','NOT_FOUND');
39                 $::MP2 = 0;
40         }
41 }
42
43 use Image::Info qw(image_info);
44 use Image::Size qw(imgsize);
45 use Image::Imlib2;
46 use Text::Template;
47 use File::stat;
48 use File::Spec;
49 use POSIX qw(floor);
50 use URI::Escape;
51 use CGI;
52 use CGI::Cookie;
53 use Encode;
54 use HTTP::Date;
55 use Digest::MD5 qw(md5_base64);
56
57 use Data::Dumper;
58
59 # Regexp for escaping URI's
60 my $escape_rule = "^A-Za-z0-9\-_.!~*'()\/";
61 my $memoized;
62
63 sub handler {
64
65         my $r = shift or Apache2::RequestUtil->request();
66
67         unless (($r->method eq 'HEAD') or ($r->method eq 'GET')) {
68                 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
69         }
70
71         if ((not $memoized) and ($r->dir_config('GalleryMemoize'))) {
72                 require Memoize;
73                 Memoize::memoize('get_imageinfo');
74                 $memoized=1;
75         }
76
77         $r->headers_out->{"X-Powered-By"} = "apachegallery.dk $VERSION - Hest design!";
78         $r->headers_out->{"X-Gallery-Version"} = '$Rev: 324 $ $Date: 2011-02-22 21:56:06 +0100 (Tue, 22 Feb 2011) $';
79
80         my $filename = $r->filename;
81         $filename =~ s/\/$//;
82         my $topdir = $filename;
83
84         my $media_rss_enabled = $r->dir_config('GalleryEnableMediaRss');
85
86         # Just return the http headers if the client requested that
87         if ($r->header_only) {
88
89                 if (!$::MP2) {
90                         $r->send_http_header;
91                 }
92
93                 if (-f $filename or -d $filename) {
94                         return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
95                 }
96                 else {
97                         return $::MP2 ? Apache2::Const::NOT_FOUND() : Apache::Constants::NOT_FOUND();
98                 }
99         }
100
101         my $cgi = new CGI;
102
103         # Handle selected images
104         if ($cgi->param('selection')) {
105                 my @selected = $cgi->param('selection');
106                 my $content = join "<br />\n",@selected;
107                 $r->content_type('text/html');
108                 $r->headers_out->{'Content-Length'} = length($content);
109
110                 if (!$::MP2) {
111                         $r->send_http_header;
112                 }
113
114                 $r->print($content);
115                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
116         }
117         
118         # Selectmode providing checkboxes beside all thumbnails
119         my $select_mode = $cgi->param('select');
120         
121         # Let Apache serve icons without us modifying the request
122         if ($r->uri =~ m/^\/icons/i) {
123                 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
124         }
125         # Lookup the file in the cache and scale the image if the cached
126         # image does not exist
127         if ($r->uri =~ m/\.cache\//i) {
128
129                 my $filename = $r->filename().$r->path_info();
130                 $filename =~ s/\.cache//;
131
132                 $filename =~ m/\/(\d+)x(\d+)\-/;
133                 my $image_width = $1;
134                 my $image_height = $2;
135
136                 $filename =~ s/\/(\d+)x(\d+)\-//;
137
138                 my ($width, $height, $type) = imgsize($filename);
139
140                 my $imageinfo = get_imageinfo($r, $filename, $type, $width, $height);
141         
142                 my $cached = scale_picture($r, $filename, $image_width, $image_height, $imageinfo);
143
144                 my $file = cache_dir($r, 0);
145                 $file =~ s/\.cache//;
146
147                 my $subr = $r->lookup_file($file);
148                 $r->content_type($subr->content_type());
149
150                 if ($::MP2) {
151                         my $fileinfo = stat($file);
152
153                         my $nonce = md5_base64($fileinfo->ino.$fileinfo->mtime);
154                         if ($r->headers_in->{"If-None-Match"} eq $nonce) {
155                                 return Apache2::Const::HTTP_NOT_MODIFIED();
156                         }
157
158                         if ($r->headers_in->{"If-Modified-Since"} && str2time($r->headers_in->{"If-Modified-Since"}) < $fileinfo->mtime) {
159                                 return Apache2::Const::HTTP_NOT_MODIFIED();
160                         }
161
162                         $r->headers_out->{"Content-Length"} = $fileinfo->size; 
163                         $r->headers_out->{"Last-Modified-Date"} = time2str($fileinfo->mtime); 
164                         $r->headers_out->{"ETag"} = $nonce;
165                         $r->sendfile($file);
166                         return Apache2::Const::OK();
167                 }
168                 else {
169                         $r->path_info('');
170                         $r->filename($file);
171                         return Apache::Constants::DECLINED();
172                 }
173                 
174         }
175
176         my $uri = $r->uri;
177         $uri =~ s/\/$//;
178
179         unless (-f $filename or -d $filename) {
180                 show_error($r, 404, "404!", "No such file or directory: ".uri_escape($r->uri, $escape_rule));
181                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
182         }
183
184         my $doc_pattern = $r->dir_config('GalleryDocFile');
185         unless ($doc_pattern) {
186                 $doc_pattern = '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|ogg|pdf|rtf|wav|dlt|html?|csv|eps)$'
187         }
188         my $img_pattern = $r->dir_config('GalleryImgFile');
189         unless ($img_pattern) {
190                 $img_pattern = '\.(jpe?g|png|tiff?|ppm)$'
191         }
192
193         # Let Apache serve files we don't know how to handle anyway
194         if (-f $filename && $filename !~ m/$img_pattern/i) {
195                 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
196         }
197
198         if (-d $filename) {
199
200                 unless (-d cache_dir($r, 0)) {
201                         unless (create_cache($r, cache_dir($r, 0))) {
202                                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
203                         }
204                 }
205
206                 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
207
208                 # Instead of reading the templates every single time
209                 # we need them, create a hash of template names and
210                 # the associated Text::Template objects.
211                 my %templates = create_templates({layout       => "$tpl_dir/layout.tpl",
212                                                   index        => "$tpl_dir/index.tpl",
213                                                   directory    => "$tpl_dir/directory.tpl",
214                                                   picture      => "$tpl_dir/picture.tpl",
215                                                   file         => "$tpl_dir/file.tpl",
216                                                   comment      => "$tpl_dir/dircomment.tpl",
217                                                   nocomment    => "$tpl_dir/nodircomment.tpl",
218                                                   rss          => "$tpl_dir/rss.tpl",
219                                                   rss_item     => "$tpl_dir/rss_item.tpl",
220                                                   navdirectory => "$tpl_dir/navdirectory.tpl",
221                                                  });
222
223
224
225
226                 my %tpl_vars;
227
228                 $tpl_vars{TITLE} = "Index of: $uri";
229
230                 if ($media_rss_enabled) {
231                         # Put the RSS feed on all directory listings
232                         $tpl_vars{META} = '<link rel="alternate" href="?rss=1" type="application/rss+xml" title="" id="gallery" />';
233                 }
234
235                 unless (opendir (DIR, $filename)) {
236                         show_error ($r, 500, $!, "Unable to access directory $filename: $!");
237                         return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
238                 }
239
240                 $tpl_vars{MENU} = generate_menu($r);
241
242                 $tpl_vars{FORM_BEGIN} = $select_mode?'<form method="post">':'';
243                 $tpl_vars{FORM_END}   = $select_mode?'<input type="submit" name="Get list" value="Get list"></form>':'';
244
245                 # Read, sort, and filter files
246                 my @files = grep { !/^\./ && -f "$filename/$_" } readdir (DIR);
247
248                 @files=gallerysort($r, @files);
249
250                 my @downloadable_files;
251
252                 if (@files) {
253                         # Remove unwanted files from list
254                         my @new_files = ();
255                         foreach my $picture (@files) {
256
257                                 my $file = $topdir."/".$picture;
258
259                                 if ($file =~ /$img_pattern/i) {
260                                         push (@new_files, $picture);
261                                 }
262
263                                 if ($file =~ /$doc_pattern/i) {
264                                         push (@downloadable_files, $picture);
265                                 }
266
267                         }
268                         @files = @new_files;
269                 }
270
271                 # Read and sort directories
272                 rewinddir (DIR);
273                 my @directories = grep { !/^\./ && -d "$filename/$_" } readdir (DIR);
274                 my $dirsortby;
275                 if (defined($r->dir_config('GalleryDirSortBy'))) {
276                         $dirsortby=$r->dir_config('GalleryDirSortBy');
277                 } else {
278                         $dirsortby=$r->dir_config('GallerySortBy');
279                 }
280                 if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
281                         @directories = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$dirsortby()." $_", @directories));
282                 } else {
283                         @directories = sort @directories;
284                 }
285
286                 closedir(DIR);
287
288
289                 # Combine directories and files to one listing
290                 my @listing;
291                 push (@listing, @directories);
292                 push (@listing, @files);
293                 push (@listing, @downloadable_files);
294                 
295                 if (@listing) {
296
297                         my $filelist;
298
299                         my $file_counter = 0;
300                         my $start_at = 1;
301                         my $max_files = $r->dir_config('GalleryMaxThumbnailsPerPage');
302
303                         if (defined($cgi->param('start'))) {
304                                 $start_at = $cgi->param('start');
305                                 if ($start_at < 1) {
306                                         $start_at = 1;
307                                 }
308                         }
309
310                         my $browse_links = "";
311                         if (defined($max_files)) {
312                         
313                                 for (my $i=1; $i<=scalar(@listing); $i++) {
314
315                                         my $from = $i;
316
317                                         my $to = $i+$max_files-1;
318                                         if ($to > scalar(@listing)) {
319                                                 $to = scalar(@listing);
320                                         }
321
322                                         if ($start_at < $from || $start_at > $to) {
323                                                 $browse_links .= "<a href=\"?start=$from\">$from - ".$to."</a> ";
324                                         }
325                                         else {
326                                                 $browse_links .= "$from - $to ";
327                                         }
328
329                                         $i+=$max_files-1;
330
331                                 }
332
333                         }
334
335                         $tpl_vars{BROWSELINKS} = $browse_links;
336
337                         DIRLOOP:
338                         foreach my $file (@listing) {
339
340                                 $file_counter++;
341
342                                 if ($file_counter < $start_at) {
343                                         next;
344                                 }
345
346                                 if (defined($max_files) && $file_counter > $max_files+$start_at-1) {
347                                         last DIRLOOP;
348                                 }
349
350                                 my $thumbfilename = $topdir."/".$file;
351
352                                 my $fileurl = $uri."/".$file;
353
354                                 if (-d $thumbfilename) {
355                                         my $dirtitle = '';
356                                         if (-e $thumbfilename . ".folder") {
357                                                 $dirtitle = get_filecontent($thumbfilename . ".folder");
358                                         }
359
360                                         $dirtitle = $dirtitle ? $dirtitle : $file;
361                                         $dirtitle =~ s/_/ /g if $r->dir_config('GalleryUnderscoresToSpaces');
362
363                                         $tpl_vars{FILES} .=
364                                              $templates{directory}->fill_in(HASH=> {FILEURL => uri_escape($fileurl, $escape_rule),
365                                                                                     FILE    => $dirtitle,
366                                                                                    }
367                                                                            );
368
369                                 }
370                                 elsif (-f $thumbfilename && $thumbfilename =~ /$doc_pattern/i && $thumbfilename !~ /$img_pattern/i) {
371                                         my $type = lc($1);
372                                         my $stat = stat($thumbfilename);
373                                         my $size = $stat->size;
374                                         my $filetype;
375
376                                         if ($thumbfilename =~ m/\.(mpe?g|avi|mov|asf|wmv)$/i) {
377                                                 $filetype = "video-$type";
378                                         } elsif ($thumbfilename =~ m/\.(txt|html?)$/i) {
379                                                 $filetype = "text-$type";
380                                         } elsif ($thumbfilename =~ m/\.(mp3|ogg|wav)$/i) {
381                                                 $filetype = "sound-$type";
382                                         } elsif ($thumbfilename =~ m/$doc_pattern/i) {
383                                                 $filetype = "application-$type";
384                                         } else {
385                                                 $filetype = "unknown";
386                                         }
387
388                                         $tpl_vars{FILES} .=
389                                              $templates{file}->fill_in(HASH => {%tpl_vars,
390                                                                                 FILEURL => uri_escape($fileurl, $escape_rule),
391                                                                                 ALT => "Size: $size Bytes",
392                                                                                 FILE => $file,
393                                                                                 TYPE => $type,
394                                                                                 FILETYPE => $filetype,
395                                                                                }
396                                                                       );
397                                 }
398                                 elsif (-f $thumbfilename) {
399
400                                         my ($width, $height, $type) = imgsize($thumbfilename);
401                                         next if $type eq 'Data stream is not a known image file format';
402
403                                         my @filetypes = qw(JPG TIF PNG PPM GIF);
404
405                                         next unless (grep $type eq $_, @filetypes);
406                                         my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $width, $height);        
407                                         my $imageinfo = get_imageinfo($r, $thumbfilename, $type, $width, $height);
408                                         my $cached = get_scaled_picture_name($thumbfilename, $thumbnailwidth, $thumbnailheight);
409
410                                         my $rotate = readfile_getnum($r, $imageinfo, $thumbfilename.".rotate");
411                                         my %file_vars = (FILEURL => uri_escape($fileurl, $escape_rule),
412                                                          FILE    => $file,
413                                                          DATE    => $imageinfo->{DateTimeOriginal} ? $imageinfo->{DateTimeOriginal} : '', # should this really be a stat of the file instead of ''?
414                                                          SRC     => uri_escape($uri."/.cache/$cached", $escape_rule),
415                                                          HEIGHT => (grep($rotate==$_, (1, 3)) ? $thumbnailwidth : $thumbnailheight),
416                                                          WIDTH => (grep($rotate==$_, (1, 3)) ? $thumbnailheight : $thumbnailwidth),
417                                                          SELECT  => $select_mode?'<input type="checkbox" name="selection" value="'.$file.'">&nbsp;&nbsp;':'',);
418                                         $tpl_vars{FILES} .= $templates{picture}->fill_in(HASH => {%tpl_vars,
419                                                                                                  %file_vars,
420                                                                                                 },
421                                                                                        );
422
423                                         if ($media_rss_enabled) {
424                                                 my ($content_image_width, undef, $content_image_height) = get_image_display_size($cgi, $r, $width, $height);
425                                                 my %item_vars = ( 
426                                                         THUMBNAIL => uri_escape($uri."/.cache/$cached", $escape_rule),
427                                                         LINK      => uri_escape($fileurl, $escape_rule),
428                                                         TITLE     => $file,
429                                                         CONTENT   => uri_escape($uri."/.cache/".$content_image_width."x".$content_image_height."-".$file, $escape_rule)
430                                                 );
431                                                 $tpl_vars{ITEMS} .= $templates{rss_item}->fill_in(HASH => { 
432                                                         %item_vars
433                                                 });
434                                         }
435                                 }
436                         }
437                 }
438                 else {
439                         $tpl_vars{FILES} = "No files found";
440                         $tpl_vars{BROWSELINKS} = "";
441                 }
442
443                 # Generate prev and next directory menu items
444                 $filename =~ m/(.*)\/.*?$/;
445                 my $parent_filename = $1;
446
447                 $r->document_root =~ m/(.*)\/$/;
448                 my $root_path = $1;
449                 print STDERR "$filename vs $root_path\n";
450                 if ($filename ne $root_path) {
451                         unless (opendir (PARENT_DIR, $parent_filename)) {
452                                 show_error ($r, 500, $!, "Unable to access parent directory $parent_filename: $!");
453                                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
454                         }
455         
456                         my @neighbour_directories = grep { !/^\./ && -d "$parent_filename/$_" } readdir (PARENT_DIR);
457                         my $dirsortby;
458                         if (defined($r->dir_config('GalleryDirSortBy'))) {
459                                 $dirsortby=$r->dir_config('GalleryDirSortBy');
460                         } else {
461                                 $dirsortby=$r->dir_config('GallerySortBy');
462                         }
463                         if ($dirsortby && $dirsortby =~ m/^(size|atime|mtime|ctime)$/) {
464                                 @neighbour_directories = map(/^\d+ (.*)/, sort map(stat("$parent_filename/$_")->$dirsortby()." $_", @neighbour_directories));
465                         } else {
466                                 @neighbour_directories = sort @neighbour_directories;
467                         }
468
469                         closedir(PARENT_DIR);
470
471                         my $neightbour_counter = 0;
472                         foreach my $neighbour_directory (@neighbour_directories) {
473                                 if ($parent_filename.'/'.$neighbour_directory eq $filename) {
474                                         if ($neightbour_counter > 0) {
475                                                 print STDERR "prev directory is " .$neighbour_directories[$neightbour_counter-1] ."\n";
476                                                 my $linktext = $neighbour_directories[$neightbour_counter-1];
477                                                 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
478                                                         $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder");
479                                                 }
480                                                 my %info = (
481                                                 URL => "../".$neighbour_directories[$neightbour_counter-1],
482                                                 LINK_NAME => "<<< $linktext",
483                                                 DIR_FILES => "",
484                                                 );
485                                                 $tpl_vars{PREV_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
486                                                 print STDERR $tpl_vars{PREV_DIR_FILES} ."\n";
487
488                                         }
489                                         if ($neightbour_counter < scalar @neighbour_directories - 1) {
490                                                 my $linktext = $neighbour_directories[$neightbour_counter+1];
491                                                 if (-e $parent_filename.'/'.$neighbour_directories[$neightbour_counter-1] . ".folder") {
492                                                         $linktext = get_filecontent($parent_filename.'/'.$neighbour_directories[$neightbour_counter+1] . ".folder");
493                                                 }
494                                                 my %info = (
495                                                 URL => "../".$neighbour_directories[$neightbour_counter+1],
496                                                 LINK_NAME => "$linktext >>>",
497                                                 DIR_FILES => "",
498                                                 );
499                                                 $tpl_vars{NEXT_DIR_FILES} = $templates{navdirectory}->fill_in(HASH=> {%info});
500                                                 print STDERR "next directory is " .$neighbour_directories[$neightbour_counter+1] ."\n";
501                                         }
502                                 }
503                                 $neightbour_counter++;
504                         }
505                 }
506
507                 if (-f $topdir . '.comment') {
508                         my $comment_ref = get_comment($topdir . '.comment');
509                         my %comment_vars;
510                         $comment_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
511                         $comment_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
512                         $tpl_vars{DIRCOMMENT} = $templates{comment}->fill_in(HASH => \%comment_vars);
513                         $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
514                 } else {
515                         $tpl_vars{DIRCOMMENT} = $templates{nocomment}->fill_in(HASH=>\%tpl_vars);
516                 }
517
518                 if ($cgi->param('rss')) {
519                         $tpl_vars{MAIN} = $templates{rss}->fill_in(HASH => \%tpl_vars);
520                         $r->content_type('application/rss+xml');
521                 } else {
522                         $tpl_vars{MAIN} = $templates{index}->fill_in(HASH => \%tpl_vars);
523                         $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
524                         $r->content_type('text/html');
525                 }
526
527                 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
528
529                 if (!$::MP2) {
530                         $r->send_http_header;
531                 }
532
533                 $r->print($tpl_vars{MAIN});
534                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
535
536         }
537         else {
538
539                 # original size
540                 if (defined($ENV{QUERY_STRING}) && $ENV{QUERY_STRING} eq 'orig') {
541                         if ($r->dir_config('GalleryAllowOriginal') ? 1 : 0) {
542                                 $r->filename($filename);
543                                 return $::MP2 ? Apache2::Const::DECLINED() : Apache::Constants::DECLINED();
544                         } else {
545                                 return $::MP2 ? Apache2::Const::FORBIDDEN() : Apache::Constants::FORBIDDEN();
546                         }
547                 }
548         
549                 # Create cache dir if not existing
550                 my @tmp = split (/\//, $filename);
551                 my $picfilename = pop @tmp;
552                 my $path = (join "/", @tmp)."/";
553                 my $cache_path = cache_dir($r, 1);
554
555                 unless (-d $cache_path) {
556                         unless (create_cache($r, $cache_path)) {
557                                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
558                         }
559                 }
560
561                 my ($orig_width, $orig_height, $type) = imgsize($filename);
562
563                 my $imageinfo = get_imageinfo($r, $filename, $type, $orig_width, $orig_height);
564
565                 my ($image_width, $width, $height, $original_size) = get_image_display_size($cgi, $r, $orig_width, $orig_height);
566
567                 my $cached = get_scaled_picture_name($filename, $image_width, $height);
568                 
569                 my $tpl_dir = $r->dir_config('GalleryTemplateDir');
570
571                 my %templates = create_templates({layout         => "$tpl_dir/layout.tpl",
572                                                   picture        => "$tpl_dir/showpicture.tpl",
573                                                   navpicture     => "$tpl_dir/navpicture.tpl",
574                                                   info           => "$tpl_dir/info.tpl",
575                                                   scale          => "$tpl_dir/scale.tpl",
576                                                   scaleactive    => "$tpl_dir/scaleactive.tpl",
577                                                   orig           => "$tpl_dir/orig.tpl",
578                                                   refresh        => "$tpl_dir/refresh.tpl",
579                                                   interval       => "$tpl_dir/interval.tpl",
580                                                   intervalactive => "$tpl_dir/intervalactive.tpl",
581                                                   slideshowisoff => "$tpl_dir/slideshowisoff.tpl",
582                                                   slideshowoff   => "$tpl_dir/slideshowoff.tpl",
583                                                   pictureinfo    => "$tpl_dir/pictureinfo.tpl",
584                                                   nopictureinfo  => "$tpl_dir/nopictureinfo.tpl",
585                                                  });
586
587                 my %tpl_vars;
588
589                 my $resolution = (($image_width > $orig_width) && ($height > $orig_height)) ? 
590                         "$orig_width x $orig_height" : "$image_width x $height";
591
592                 $tpl_vars{TITLE} = "Viewing ".$r->uri()." at $image_width x $height";
593                 $tpl_vars{META} = " ";
594                 $tpl_vars{RESOLUTION} = $resolution;
595                 $tpl_vars{MENU} = generate_menu($r);
596                 $tpl_vars{SRC} = uri_escape(".cache/$cached", $escape_rule);
597                 $tpl_vars{URI} = $r->uri();
598         
599                 my $exif_mode = $r->dir_config('GalleryEXIFMode');
600                 unless ($exif_mode) {
601                         $exif_mode = 'namevalue';
602                 }
603
604                 unless (opendir(DATADIR, $path)) {
605                         show_error($r, 500, "Unable to access directory", "Unable to access directory $path");
606                         return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
607                 }
608                 my @pictures = grep { /$img_pattern/i } readdir (DATADIR);
609                 closedir(DATADIR);
610                 @pictures = gallerysort($r, @pictures);
611
612                 $tpl_vars{TOTAL} = scalar @pictures;
613
614                 my $prevpicture;
615                 my $nextpicture;
616         
617                 for (my $i=0; $i <= $#pictures; $i++) {
618                         if ($pictures[$i] eq $picfilename) {
619
620                                 $tpl_vars{NUMBER} = $i+1;
621
622                                 $prevpicture = $pictures[$i-1];
623                                 my $displayprev = ($i>0 ? 1 : 0);
624
625                                 if ($r->dir_config("GalleryWrapNavigation")) {
626                                         $prevpicture = $pictures[$i>0 ? $i-1 : $#pictures];
627                                         $displayprev = 1;
628                                 }
629                                 if ($prevpicture and $displayprev) {
630                                         my ($orig_width, $orig_height, $type) = imgsize($path.$prevpicture);
631                                         my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);      
632                                         my $imageinfo = get_imageinfo($r, $path.$prevpicture, $type, $orig_width, $orig_height);
633                                         my $cached = get_scaled_picture_name($path.$prevpicture, $thumbnailwidth, $thumbnailheight);
634                                         my %nav_vars;
635                                         $nav_vars{URL}       = uri_escape($prevpicture, $escape_rule);
636                                         $nav_vars{FILENAME}  = $prevpicture;
637                                         $nav_vars{WIDTH}     = $width;
638                                         $nav_vars{PICTURE}   = uri_escape(".cache/$cached", $escape_rule);
639                                         $nav_vars{DIRECTION} = "&laquo; <u>p</u>rev";
640                                         $nav_vars{ACCESSKEY} = "P";
641                                         $tpl_vars{BACK} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
642                                 }
643                                 else {
644                                         $tpl_vars{BACK} = "&nbsp;";
645                                 }
646
647                                 $nextpicture = $pictures[$i+1];
648                                 if ($r->dir_config("GalleryWrapNavigation")) {
649                                         $nextpicture = $pictures[$i == $#pictures ? 0 : $i+1];
650                                 }       
651
652                                 if ($nextpicture) {
653                                         my ($orig_width, $orig_height, $type) = imgsize($path.$nextpicture);
654                                         my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);      
655                                         my $imageinfo = get_imageinfo($r, $path.$nextpicture, $type, $thumbnailwidth, $thumbnailheight);
656                                         my $cached = get_scaled_picture_name($path.$nextpicture, $thumbnailwidth, $thumbnailheight);
657                                         my %nav_vars;
658                                         $nav_vars{URL}       = uri_escape($nextpicture, $escape_rule);
659                                         $nav_vars{FILENAME}  = $nextpicture;
660                                         $nav_vars{WIDTH}     = $width;
661                                         $nav_vars{PICTURE}   = uri_escape(".cache/$cached", $escape_rule);
662                                         $nav_vars{DIRECTION} = "<u>n</u>ext &raquo;";
663                                         $nav_vars{ACCESSKEY} = "N";
664
665                                         $tpl_vars{NEXT} = $templates{navpicture}->fill_in(HASH => \%nav_vars);
666                                         $tpl_vars{NEXTURL}   = uri_escape($nextpicture, $escape_rule);
667                                 }
668                                 else {
669                                         $tpl_vars{NEXT} = "&nbsp;";
670                                         $tpl_vars{NEXTURL}   = '#';
671                                 }
672                         }
673                 }
674
675                 my $foundcomment = 0;
676                 if (-f $path . '/' . $picfilename . '.comment') {
677                         my $comment_ref = get_comment($path . '/' . $picfilename . '.comment');
678                         $foundcomment = 1;
679                         $tpl_vars{COMMENT} = $comment_ref->{COMMENT} . '<br />' if $comment_ref->{COMMENT};
680                         $tpl_vars{TITLE} = $comment_ref->{TITLE} if $comment_ref->{TITLE};
681                 } elsif ($r->dir_config('GalleryCommentExifKey')) {
682                         my $comment = decode("utf8", $imageinfo->{$r->dir_config('GalleryCommentExifKey')});
683                         $tpl_vars{COMMENT} = encode("iso-8859-1", $comment);
684                 } else {
685                         $tpl_vars{COMMENT} = '';
686                 }
687
688                 my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
689                 my $foundinfo = 0;
690                 my $exifvalues;
691                 foreach (@infos) {
692         
693                         my ($human_key, $exif_key) = (split " => ")[0,1];
694                         my $value = $imageinfo->{$human_key};
695                         if (defined($value)) {
696
697                                 $foundinfo = 1;
698
699                                 if ($exif_mode eq 'namevalue') {
700                                         my %info_vars;
701                                         $info_vars{KEY} = $human_key;
702                                         $info_vars{VALUE} = $value;
703                                         $tpl_vars{INFO} .=  $templates{info}->fill_in(HASH => \%info_vars);
704                                 }
705
706                                 if ($exif_mode eq 'variables') {
707                                         $tpl_vars{"EXIF_".uc($exif_key)} = $value;
708                                 }
709
710                                 if ($exif_mode eq 'values') {
711                                         $exifvalues .= "| ".$value." ";
712                                 }
713
714                         } 
715
716                 }
717
718                 if ($exif_mode eq 'values') {
719                         if (defined($exifvalues)) {
720                                 $tpl_vars{EXIFVALUES} = $exifvalues;
721                         }
722                         else {
723                                 $tpl_vars{EXIFVALUES} = "";
724                         }
725                 }
726
727                 if ($foundcomment and !$foundinfo) {
728                         $tpl_vars{INFO} = "";
729                 }
730
731                 if ($exif_mode ne 'namevalue') {
732                         $tpl_vars{INFO} = "";
733                 }
734
735                 if ($exif_mode eq 'namevalue' && $foundinfo or $foundcomment) {
736
737                         $tpl_vars{PICTUREINFO} = $templates{pictureinfo}->fill_in(HASH => \%tpl_vars);
738
739                         unless (defined($exifvalues)) {
740                                 $tpl_vars{EXIFVALUES} = "";
741                         }
742
743                 }
744                 else {
745                         $tpl_vars{PICTUREINFO} = $templates{nopictureinfo}->fill_in(HASH => \%tpl_vars);
746                 }
747
748                 # Fill in sizes and determine if any are smaller than the
749                 # actual image. If they are, $scaleable=1
750                 my $scaleable = 0;
751                 my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
752                 foreach my $size (@sizes) {
753                         if ($size<=$original_size) {
754                                 my %sizes_vars;
755                                 $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
756                                 $sizes_vars{SIZE}     = $size;
757                                 $sizes_vars{WIDTH}    = $size;
758                                 if ($width == $size) {
759                                         $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
760                                 }
761                                 else {
762                                         $tpl_vars{SIZES} .= $templates{scale}->fill_in(HASH => \%sizes_vars);
763                                 }
764                                 $scaleable = 1;
765                         }
766                 }
767
768                 unless ($scaleable) {
769                         my %sizes_vars;
770                         $sizes_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
771                         $sizes_vars{SIZE}     = $original_size;
772                         $sizes_vars{WIDTH}    = $original_size;
773                         $tpl_vars{SIZES} .= $templates{scaleactive}->fill_in(HASH => \%sizes_vars);
774                 }
775
776                 $tpl_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
777
778                 if ($r->dir_config('GalleryAllowOriginal')) {
779                         $tpl_vars{SIZES} .= $templates{orig}->fill_in(HASH => \%tpl_vars);
780                 }
781
782                 my @slideshow_intervals = split (/ /, $r->dir_config('GallerySlideshowIntervals') ? $r->dir_config('GallerySlideshowIntervals') : '3 5 10 15 30');
783                 foreach my $interval (@slideshow_intervals) {
784
785                         my %slideshow_vars;
786                         $slideshow_vars{IMAGEURI} = uri_escape($r->uri(), $escape_rule);
787                         $slideshow_vars{SECONDS} = $interval;
788                         $slideshow_vars{WIDTH} = ($width > $height ? $width : $height);
789
790                         if ($cgi->param('slideshow') && $cgi->param('slideshow') == $interval and $nextpicture) {
791                                 $tpl_vars{SLIDESHOW} .= $templates{intervalactive}->fill_in(HASH => \%slideshow_vars);
792                         }
793                         else {
794
795                                 $tpl_vars{SLIDESHOW} .= $templates{interval}->fill_in(HASH => \%slideshow_vars);
796
797                         }
798                 }
799
800                 if ($cgi->param('slideshow') and $nextpicture) {
801
802                         $tpl_vars{SLIDESHOW} .= $templates{slideshowoff}->fill_in(HASH => \%tpl_vars);
803
804                         unless ((grep $cgi->param('slideshow') == $_, @slideshow_intervals)) {
805                                 show_error($r, 200, "Invalid interval", "Invalid slideshow interval choosen");
806                                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
807                         }
808
809                         $tpl_vars{URL} = uri_escape($nextpicture, $escape_rule);
810                         $tpl_vars{WIDTH} = ($width > $height ? $width : $height);
811                         $tpl_vars{INTERVAL} = $cgi->param('slideshow');
812                         $tpl_vars{META} .=  $templates{refresh}->fill_in(HASH => \%tpl_vars);
813
814                 }
815                 else {
816                         $tpl_vars{SLIDESHOW} .=  $templates{slideshowisoff}->fill_in(HASH => \%tpl_vars);
817                 }
818
819                 $tpl_vars{MAIN} = $templates{picture}->fill_in(HASH => \%tpl_vars);
820                 $tpl_vars{MAIN} = $templates{layout}->fill_in(HASH => \%tpl_vars);
821
822                 $r->content_type('text/html');
823                 $r->headers_out->{'Content-Length'} = length($tpl_vars{MAIN});
824
825                 if (!$::MP2) {
826                         $r->send_http_header;
827                 }
828
829                 $r->print($tpl_vars{MAIN});
830                 return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
831
832         }
833
834 }
835
836 sub cache_dir {
837
838         my ($r, $strip_filename) = @_;
839
840         my $cache_root;
841
842         unless ($r->dir_config('GalleryCacheDir')) {
843
844                 $cache_root = '/var/tmp/Apache-Gallery/';
845                 if ($r->server->is_virtual) {
846                         $cache_root = File::Spec->catdir($cache_root, $r->server->server_hostname);
847                 } else {
848                         $cache_root = File::Spec->catdir($cache_root, $r->location);
849                 }
850
851         } else {
852
853                 $cache_root = $r->dir_config('GalleryCacheDir');
854
855         }
856
857         # If the uri contains .cache we need to remove it
858         my $uri = $r->uri;
859         $uri =~ s/\.cache//;
860
861         my (undef, $dirs, $filename) = File::Spec->splitpath($uri);
862         # We don't need a volume as this is a relative path
863
864         if ($strip_filename) {
865                 return(File::Spec->canonpath(File::Spec->catdir($cache_root, $dirs)));
866         } else {
867                 return(File::Spec->canonpath(File::Spec->catfile($cache_root, $dirs, $filename)));
868         }
869 }
870
871 sub create_cache {
872
873         my ($r, $path) = @_;
874
875                 unless (mkdirhier ($path)) {
876                         show_error($r, 500, $!, "Unable to create cache directory in $path: $!");
877                         return 0;
878                 }
879
880         return 1;
881 }
882
883 sub mkdirhier {
884
885         my $dir = shift;
886
887         unless (-d $dir) {
888
889                 unless (mkdir($dir, 0755)) {
890                         my $parent = $dir;
891                         $parent =~ s/\/[^\/]*$//;
892
893                         mkdirhier($parent);
894
895                         mkdir($dir, 0755);
896                 }
897         }
898 }
899
900 sub get_scaled_picture_name {
901
902         my ($fullpath, $width, $height) = @_;
903
904         my (undef, undef, $type) = imgsize($fullpath);
905
906         my @dirs = split(/\//, $fullpath);
907         my $filename = pop(@dirs);
908         my $newfilename;
909
910         if (grep $type eq $_, qw(PPM TIF GIF)) {
911                 $newfilename = $width."x".$height."-".$filename;
912                 # needs to be configurable
913                 $newfilename =~ s/\.(\w+)$/-$1\.jpg/;
914         } else {
915                 $newfilename = $width."x".$height."-".$filename;
916         }
917
918         return $newfilename;
919         
920 }
921
922 sub scale_picture {
923
924         my ($r, $fullpath, $width, $height, $imageinfo) = @_;
925
926         my @dirs = split(/\//, $fullpath);
927         my $filename = pop(@dirs);
928
929         my ($orig_width, $orig_height, $type) = imgsize($fullpath);
930
931         my $cache = cache_dir($r, 1);
932
933         my $newfilename = get_scaled_picture_name($fullpath, $width, $height);
934
935         if (($width > $orig_width) && ($height > $orig_height)) {
936                 # Run it through the resize code anyway to get watermarks
937                 $width = $orig_width;
938                 $height = $orig_height;
939         }
940
941         my ($thumbnailwidth, $thumbnailheight) = get_thumbnailsize($r, $orig_width, $orig_height);
942
943         # Do we want to generate a new file in the cache?
944         my $scale = 1;
945
946         if (-f $cache."/".$newfilename) {       
947                 $scale = 0;
948
949                 # Check to see if the image has changed
950                 my $filestat = stat($fullpath);
951                 my $cachestat = stat($cache."/".$newfilename);
952                 if ($filestat->mtime >= $cachestat->mtime) {
953                         $scale = 1;
954                 }       
955
956                 # Check to see if the .rotate file has been added or changed
957                 if (-f $fullpath . ".rotate") {
958                         my $rotatestat = stat($fullpath . ".rotate");
959                         if ($rotatestat->mtime > $cachestat->mtime) {
960                                 $scale = 1;
961                         }       
962                 }               
963                 # Check to see if the copyrightimage has been added or changed
964                 if ($r->dir_config('GalleryCopyrightImage') && -f $r->dir_config('GalleryCopyrightImage')) {
965                         unless ($width == $thumbnailwidth or $width == $thumbnailheight) {
966                                 my $copyrightstat = stat($r->dir_config('GalleryCopyrightImage'));
967                                 if ($copyrightstat->mtime > $cachestat->mtime) {
968                                         $scale = 1;
969                                 }       
970                         }
971                 }       
972
973         }       
974
975         if ($scale) {
976
977                 my $newpath = $cache."/".$newfilename;
978                 my $rotate = readfile_getnum($r, $imageinfo, $fullpath . ".rotate");
979                 my $quality = $r->dir_config('GalleryQuality');
980
981                 if ($width == $thumbnailwidth or $width == $thumbnailheight) {
982
983                         resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, '', '', '', '', '', '');
984
985                 } else {
986
987                         resizepicture($r, $fullpath, $newpath, $width, $height, $rotate, 
988                                 ($r->dir_config('GalleryCopyrightImage') ? $r->dir_config('GalleryCopyrightImage') : ''), 
989                                 ($r->dir_config('GalleryTTFDir') ? $r->dir_config('GalleryTTFDir') : ''), 
990                                 ($r->dir_config('GalleryCopyrightText') ? $r->dir_config('GalleryCopyrightText') : ''), 
991                                 ($r->dir_config('GalleryCopyrightColor') ? $r->dir_config('GalleryCopyrightColor') : ''), 
992                                 ($r->dir_config('GalleryTTFFile') ? $r->dir_config('GalleryTTFFile') : ''), 
993                                 ($r->dir_config('GalleryTTFSize') ?  $r->dir_config('GalleryTTFSize') : ''),
994                                 ($r->dir_config('GalleryCopyrightBackgroundColor') ?  $r->dir_config('GalleryCopyrightBackgroundColor') : ''),
995                                 $quality);
996
997                 }
998         }
999
1000         return $newfilename;
1001
1002 }
1003
1004 sub get_thumbnailsize {
1005         my ($r, $orig_width, $orig_height) = @_;
1006
1007         my $gallerythumbnailsize=$r->dir_config('GalleryThumbnailSize');
1008
1009         if (defined($gallerythumbnailsize)) {
1010                 warn("Invalid setting for GalleryThumbnailSize") unless
1011                         $gallerythumbnailsize =~ /^\s*\d+\s*x\s*\d+\s*$/i;
1012         }
1013
1014         my ($thumbnailwidth, $thumbnailheight) = split(/x/i, ($gallerythumbnailsize) ?  $gallerythumbnailsize : "100x75");
1015
1016         my $width = $thumbnailwidth;
1017         my $height = $thumbnailheight;
1018
1019         # If the image is rotated, flip everything around.
1020         if (defined $r->dir_config('GalleryThumbnailSizeLS')
1021         and $r->dir_config('GalleryThumbnailSizeLS') eq '1'
1022         and $orig_width < $orig_height) {
1023                 
1024                 $width = $thumbnailheight;
1025                 $height = $thumbnailwidth;
1026         }
1027
1028         my $scale = ($orig_width ? $width/$orig_width : 1);
1029
1030         if ($orig_height) {
1031                 if ($orig_height * $scale > $thumbnailheight) {
1032                         $scale = $height/$orig_height;
1033                         $width = $orig_width * $scale;
1034                 }
1035         }
1036
1037         $height = $orig_height * $scale;
1038
1039         $height = floor($height);
1040         $width  = floor($width);
1041
1042         return ($width, $height);
1043 }
1044
1045 sub get_image_display_size {
1046         my ($cgi, $r, $orig_width, $orig_height) = @_;
1047
1048         my $width = $orig_width;
1049
1050         my $original_size=$orig_height;
1051         if ($orig_width>$orig_height) {
1052                 $original_size=$orig_width;
1053         }
1054
1055         # Check if the selected width is allowed
1056         my @sizes = split (/ /, $r->dir_config('GallerySizes') ? $r->dir_config('GallerySizes') : '640 800 1024 1600');
1057
1058         my %cookies = fetch CGI::Cookie;
1059
1060         if ($cgi->param('width')) {
1061                 unless ((grep $cgi->param('width') == $_, @sizes) or ($cgi->param('width') == $original_size)) {
1062                         show_error($r, 200, "Invalid width", "The specified width is invalid");
1063                         return $::MP2 ? Apache2::Const::OK() : Apache::Constants::OK();
1064                 }
1065
1066                 $width = $cgi->param('width');
1067                 my $cookie = new CGI::Cookie(-name => 'GallerySize', -value => $width, -expires => '+6M');
1068                 $r->headers_out->{'Set-Cookie'} = $cookie;
1069
1070         } elsif ($cookies{'GallerySize'} && (grep $cookies{'GallerySize'}->value == $_, @sizes)) {
1071
1072                 $width = $cookies{'GallerySize'}->value;
1073
1074         } else {
1075                 $width = $sizes[0];
1076         }       
1077
1078         my $scale;
1079         my $image_width;
1080         if ($orig_width<$orig_height) {
1081                 $scale = ($orig_height ? $width/$orig_height: 1);
1082                 $image_width=$width*$orig_width/$orig_height;
1083         }
1084         else {
1085                 $scale = ($orig_width ? $width/$orig_width : 1);
1086                 $image_width = $width;
1087         }
1088
1089         my $height = $orig_height * $scale;
1090
1091         $image_width = floor($image_width);
1092         $width       = floor($width);
1093         $height      = floor($height);
1094
1095         return ($image_width, $width, $height, $original_size);
1096 }
1097
1098 sub get_imageinfo {
1099         my ($r, $file, $type, $width, $height) = @_;
1100         my $imageinfo = {};
1101         if ($type eq 'Data stream is not a known image file format') {
1102                 # should never be reached, this is supposed to be handled outside of here
1103                 log_error("Something was fishy with the type of the file $file\n");
1104         } else { 
1105
1106                 # Some files, like TIFF, PNG, GIF do not have EXIF info 
1107                 # embedded but use .thm files instead.
1108                 $imageinfo = get_imageinfo_from_thm_file($file, $width, $height);
1109
1110                 # If there is no .thm file and our file is a JPEG file we try to extract the EXIf
1111                 # info using Image::Info
1112                 unless (defined($imageinfo) && (grep $type eq $_, qw(JPG))) {
1113                         # Only for files that natively keep the EXIF info in the same file
1114                         $imageinfo = image_info($file);
1115                 }
1116         }
1117
1118         unless (defined($imageinfo->{width}) and defined($imageinfo->{height})) {
1119                 $imageinfo->{width} = $width;
1120                 $imageinfo->{height} = $height;
1121         }
1122
1123         my @infos = split /, /, $r->dir_config('GalleryInfo') ? $r->dir_config('GalleryInfo') : 'Picture Taken => DateTimeOriginal, Flash => Flash';
1124         foreach (@infos) {
1125                 
1126                 my ($human_key, $exif_key) = (split " => ")[0,1];
1127                 if (defined($exif_key) && defined($imageinfo->{$exif_key})) {
1128                         my $value = "";
1129                         if (ref($imageinfo->{$exif_key}) eq 'Image::TIFF::Rational') { 
1130                                 $value = $imageinfo->{$exif_key}->as_string;
1131                         } 
1132                         elsif (ref($imageinfo->{$exif_key}) eq 'ARRAY') {
1133                                 foreach my $element (@{$imageinfo->{$exif_key}}) {
1134                                         if (ref($element) eq 'ARRAY') {
1135                                                 foreach (@{$element}) {
1136                                                         $value .= $_ . ' ';
1137                                                 }
1138                                         } 
1139                                         elsif (ref($element) eq 'HASH') {
1140                                                 $value .= "<br />{ ";
1141                                         foreach (sort keys %{$element}) {
1142                                                         $value .= "$_ = " . $element->{$_} . ' ';
1143                                                 }
1144                                         $value .= "} ";
1145                                         } 
1146                                         else {
1147                                                 $value .= $element;
1148                                         }
1149                                         $value .= ' ';
1150                                 }
1151                         } 
1152                         else {
1153                                 my $exif_value = $imageinfo->{$exif_key};
1154                                 if ($human_key eq 'Flash' && $exif_value =~ m/\d/) {
1155                                         my %flashmodes = (
1156                                                 "0"  => "No",
1157                                                 "1"  => "Yes",
1158                                                 "9"  => "Yes",
1159                                                 "16" => "No (Compulsory) Should be External Flash",
1160                                                 "17" => "Yes (External)",
1161                                                 "24" => "No",
1162                                                 "25" => "Yes (Auto)",
1163                                                 "73" => "Yes (Compulsory, Red Eye Reducing)",
1164                                                 "89" => "Yes (Auto, Red Eye Reducing)"
1165                                         );
1166                                         $exif_value = defined $flashmodes{$exif_value} ? $flashmodes{$exif_value} : 'unknown flash mode';
1167                                 }
1168                                 $value = $exif_value;
1169                         }
1170                         if ($exif_key eq 'MeteringMode') {
1171                                 my $exif_value = $imageinfo->{$exif_key};
1172                                 if ($exif_value =~ /^\d+$/) {
1173                                         my %meteringmodes = (
1174                                                 '0' => 'unknown',
1175                                                 '1' => 'Average',
1176                                                 '2' => 'CenterWeightedAverage',
1177                                                 '3' => 'Spot',
1178                                                 '4' => 'MultiSpot',
1179                                                 '5' => 'Pattern',
1180                                                 '6' => 'Partial',
1181                                                 '255' => 'Other'
1182                                         );
1183                                         $exif_value = defined $meteringmodes{$exif_value} ? $meteringmodes{$exif_value} : 'unknown metering mode';
1184                                 }
1185                                 $value = $exif_value;
1186                                 
1187                         }
1188                         if ($exif_key eq 'LightSource') {
1189                                 my $exif_value = $imageinfo->{$exif_key};
1190                                 if ($exif_value =~ /^\d+$/) {
1191                                         my %lightsources = (
1192                                                 '0' => 'unknown',
1193                                                 '1' => 'Daylight',
1194                                                 '2' => 'Fluorescent',
1195                                                 '3' => 'Tungsten (incandescent light)',
1196                                                 '4' => 'Flash',
1197                                                 '9' => 'Fine weather',
1198                                                 '10' => 'Cloudy weather',
1199                                                 '11' => 'Shade',
1200                                                 '12' => 'Daylight fluorescent',
1201                                                 '13' => 'Day white fluorescent',
1202                                                 '14' => 'Cool white fluorescent',
1203                                                 '15' => 'White fluorescent',
1204                                                 '17' => 'Standard light A',
1205                                                 '18' => 'Standard light B',
1206                                                 '19' => 'Standard light C',
1207                                                 '20' => 'D55',
1208                                                 '21' => 'D65',
1209                                                 '22' => 'D75',
1210                                                 '23' => 'D50',
1211                                                 '24' => 'ISO studio tungsten',
1212                                                 '255' => 'other light source'
1213                                         );
1214                                         $exif_value = defined $lightsources{$exif_value} ? $lightsources{$exif_value} : 'unknown light source';
1215                                 }
1216                                 $value = $exif_value;
1217                         }
1218                         if ($exif_key eq 'FocalLength') {
1219                                 if ($value =~ /^(\d+)\/(\d+)$/) {
1220                                         $value = eval { $1 / $2 };
1221                                         if ($@) {
1222                                                 $value = $@;
1223                                         } else {
1224                                                 $value = int($value + 0.5) . "mm";
1225
1226                                         }
1227                                 }
1228                         }
1229                         if ($exif_key eq 'ShutterSpeedValue') {
1230                                 if ($value =~ /^((?:\-)?\d+)\/(\d+)$/) {
1231                                         $value = eval { $1 / $2 };
1232                                         if ($@) {
1233                                                 $value = $@;
1234                                         } else {
1235                                                 eval {
1236                                                         $value = 1/(exp($value*log(2)));
1237                                                         if ($value < 1) {
1238                                                                 $value = "1/" . (int((1/$value)));
1239                                                         } else {
1240                                                                 $value = int($value*10)/10; 
1241                                                         }
1242                                                 };
1243                                                 if ($@) {
1244                                                         $value = $@;
1245                                                 } else {
1246                                                         $value = $value . " sec";
1247                                                 }
1248                                         }
1249                                 }
1250                         }
1251                         if ($exif_key eq 'ApertureValue') {
1252                                 if ($value =~ /^(\d+)\/(\d+)$/) {
1253                                         $value = eval { $1 / $2 };
1254                                         if ($@) {
1255                                                 $value = $@;
1256                                         } else {
1257                                                 # poor man's rounding
1258                                                 $value = int(exp($value*log(2)*0.5)*10)/10;
1259                                                 $value = "f" . $value;
1260                                         }
1261                                 }
1262                         }
1263                         if ($exif_key eq 'FNumber') {
1264                                 if ($value =~ /^(\d+)\/(\d+)$/) {
1265                                         $value = eval { $1 / $2 };
1266                                         if ($@) {
1267                                                 $value = $@;
1268                                         } else {
1269                                                 $value = int($value*10+0.5)/10;
1270                                                 $value = "f" . $value;
1271                                         }
1272                                 }
1273                         }
1274                         $imageinfo->{$human_key} = $value;
1275                 } 
1276         }
1277
1278         if ($r->dir_config('GalleryUseFileDate') &&
1279                 ($r->dir_config('GalleryUseFileDate') eq '1'
1280                 || !$imageinfo->{"Picture Taken"} )) {
1281
1282                 my $st = stat($file);
1283                 $imageinfo->{"DateTimeOriginal"} = $imageinfo->{"Picture Taken"} = scalar localtime($st->mtime) if $st;
1284         }
1285
1286         return $imageinfo;
1287 }
1288
1289 sub get_imageinfo_from_thm_file {
1290
1291         my ($file, $width, $height) = @_;
1292
1293         my $imageinfo = undef;
1294         # Windows based file extensions are often .THM, so check 
1295         # for both .thm and .THM
1296         my $unix_file = $file;
1297         my $windows_file = $file;
1298         $unix_file =~ s/\.(\w+)$/.thm/;
1299         $windows_file =~ s/\.(\w+)$/.THM/;
1300
1301         if (-e $unix_file && -f $unix_file && -r $unix_file) {
1302                 $imageinfo = image_info($unix_file);
1303                 $imageinfo->{width} = $width;
1304                 $imageinfo->{height} = $height;
1305         }
1306         elsif (-e $windows_file && -f $windows_file && -r $windows_file) {
1307                 $imageinfo = image_info($windows_file);
1308                 $imageinfo->{width} = $width;
1309                 $imageinfo->{height} = $height;
1310         }
1311
1312         return $imageinfo;
1313 }
1314
1315
1316 sub readfile_getnum {
1317         my ($r, $imageinfo, $filename) = @_;
1318
1319         my $rotate = 0;
1320
1321         print STDERR "orientation: ".$imageinfo->{Orientation}."\n";
1322         # Check to see if the image contains the Orientation EXIF key,
1323         # but allow user to override using rotate
1324         if (!defined($r->dir_config("GalleryAutoRotate")) 
1325                 || $r->dir_config("GalleryAutoRotate") eq "1") {
1326                 if (defined($imageinfo->{Orientation})) {
1327                         print STDERR $imageinfo->{Orientation}."\n";
1328                         if ($imageinfo->{Orientation} eq 'right_top') {
1329                                 $rotate=1;
1330                         }       
1331                         elsif ($imageinfo->{Orientation} eq 'left_bot') {
1332                                 $rotate=3;
1333                         }
1334                 }
1335         }
1336
1337         if (open(FH, "<$filename")) {
1338                 my $temp = <FH>;
1339                 chomp($temp);
1340                 close(FH);
1341                 unless ($temp =~ /^\d$/) {
1342                         $rotate = 0;
1343                 }
1344                 unless ($temp == 1 || $temp == 2 || $temp == 3) {
1345                         $rotate = 0;
1346                 }
1347                 $rotate = $temp;
1348         }
1349
1350         return $rotate;
1351 }
1352
1353 sub get_filecontent {
1354         my $file = shift;
1355         open(FH, $file) or return undef;
1356         my $content = '';
1357         {
1358                 local $/;
1359                 $content = <FH>;
1360         }
1361         close(FH);
1362         return $content;
1363 }
1364
1365 sub get_comment {
1366         my $filename = shift;
1367         my $comment_ref = {};
1368         $comment_ref->{TITLE} = undef;
1369         $comment_ref->{COMMENT} = '';
1370
1371         open(FH, $filename) or return $comment_ref;
1372         my $title = <FH>;
1373         if ($title =~ m/^TITLE: (.*)$/) {
1374                 chomp($comment_ref->{TITLE} = $1);
1375         } 
1376         else {
1377                 $comment_ref->{COMMENT} = $title;
1378         }
1379
1380         while (<FH>) {
1381                 chomp;
1382                 $comment_ref->{COMMENT} .= $_;
1383         }
1384         close(FH);
1385
1386         return $comment_ref;
1387 }
1388
1389 sub show_error {
1390
1391         my ($r, $statuscode, $errortitle, $error) = @_;
1392
1393         my $tpl = $r->dir_config('GalleryTemplateDir');
1394
1395         my %templates = create_templates({layout => "$tpl/layout.tpl",
1396                                           error  => "$tpl/error.tpl",
1397                                          });
1398
1399         my %tpl_vars;
1400         $tpl_vars{TITLE}      = "Error! $errortitle";
1401         $tpl_vars{META}       = "";
1402         $tpl_vars{ERRORTITLE} = "Error! $errortitle";
1403         $tpl_vars{ERROR}      = $error;
1404
1405         $tpl_vars{MAIN} = $templates{error}->fill_in(HASH => \%tpl_vars);
1406
1407         $tpl_vars{PAGE} = $templates{layout}->fill_in(HASH => \%tpl_vars);
1408
1409         $r->status($statuscode);
1410         $r->content_type('text/html');
1411
1412         $r->print($tpl_vars{PAGE});
1413
1414 }
1415
1416 sub generate_menu {
1417
1418         my $r = shift;
1419
1420         my $root_text = (defined($r->dir_config('GalleryRootText')) ? $r->dir_config('GalleryRootText') : "root:" );
1421         my $root_path = (defined($r->dir_config('GalleryRootPath')) ? $r->dir_config('GalleryRootPath') : "" );
1422
1423         my $subr = $r->lookup_uri($r->uri);
1424         my $filename = $subr->filename;
1425
1426         my @links = split (/\//, $r->uri);
1427         my $uri = $r->uri;
1428         $uri =~ s/^$root_path//g;
1429
1430         @links = split (/\//, $uri);
1431
1432         # Get the full path of the base directory
1433         my $dirname;
1434         {
1435                 my @direlem = split (/\//, $filename);
1436                 for my $i ( 0 .. ( scalar(@direlem) - scalar(@links) ) ) {
1437                         $dirname .= shift(@direlem) . '/';
1438                 }
1439                 chop $dirname;
1440         }
1441
1442         my $picturename;
1443         if (-f $filename) {
1444                 $picturename = pop(@links);     
1445         }
1446
1447         if ($r->uri eq $root_path) {
1448                 return qq{ <a href="$root_path">$root_text</a> };
1449         }
1450
1451         my $menu;
1452         my $menuurl = $root_path;
1453         foreach my $link (@links) {
1454
1455                 $menuurl .= $link."/";
1456                 my $linktext = $link;
1457                 unless (length($link)) {
1458                         $linktext = "$root_text ";
1459                 }
1460                 else {
1461                         
1462                         $dirname = File::Spec->catdir($dirname, $link);
1463
1464                         if (-e $dirname . ".folder") {
1465                                 $linktext = get_filecontent($dirname . ".folder");
1466                         }
1467                 }
1468
1469                 if ("$root_path$uri" eq $menuurl) {
1470                         $menu .= "$linktext  / ";
1471                 }
1472                 else {
1473                         $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule)."\">$linktext</a> / ";
1474                 }
1475
1476         }
1477
1478         if (-f $filename) {
1479                 $menu .= $picturename;
1480         }
1481         else {
1482
1483                 if ($r->dir_config('GallerySelectionMode') && $r->dir_config('GallerySelectionMode') eq '1') {
1484                         $menu .= "<a href=\"".uri_escape($menuurl, $escape_rule);
1485                         $menu .= "?select=1\">[select]</a> ";
1486                 }
1487         }
1488
1489         return $menu;
1490 }
1491
1492 sub resizepicture {
1493         my ($r, $infile, $outfile, $x, $y, $rotate, $copyrightfile, $GalleryTTFDir, $GalleryCopyrightText, $text_color, $GalleryTTFFile, $GalleryTTFSize, $GalleryCopyrightBackgroundColor, $quality) = @_;
1494
1495         # Load image
1496         my $image = Image::Imlib2->load($infile) or warn("Unable to open file $infile, $!");
1497
1498         # Scale image
1499         $image=$image->create_scaled_image($x, $y) or warn("Unable to scale image $infile. Are you running out of memory?");
1500
1501         # Rotate image
1502         if ($rotate != 0) {
1503                 $image->image_orientate($rotate);
1504         }
1505
1506         # blend copyright image onto image
1507         if ($copyrightfile ne '') {
1508                 if (-f $copyrightfile and (my $logo=Image::Imlib2->load($copyrightfile))) {
1509                         my $x = $image->get_width();
1510                         my $y = $image->get_height();
1511                         my $logox = $logo->get_width();
1512                         my $logoy = $logo->get_height();
1513                         $image->blend($logo, 0, 0, 0, $logox, $logoy, $x-$logox, $y-$logoy, $logox, $logoy);
1514                 }
1515                 else {
1516                         log_error("GalleryCopyrightImage $copyrightfile was not found");
1517                 }
1518         }
1519
1520         if ($GalleryTTFDir && $GalleryCopyrightText && $GalleryTTFFile && $text_color) {
1521                 if (!-d $GalleryTTFDir) {
1522
1523                         log_error("GalleryTTFDir $GalleryTTFDir is not a dir\n");
1524
1525                 } elsif ($GalleryCopyrightText eq '') {
1526
1527                         log_error("GalleryCopyrightText is empty. No text inserted to picture\n");
1528
1529                 } elsif (!-e "$GalleryTTFDir/$GalleryTTFFile") {
1530
1531                         log_error("GalleryTTFFile $GalleryTTFFile was not found\n");
1532
1533                 } else {
1534  
1535                         $GalleryTTFFile =~ s/\.TTF$//i;
1536                         $image->add_font_path("$GalleryTTFDir");
1537
1538                         $image->load_font("$GalleryTTFFile/$GalleryTTFSize");
1539                         my($text_x, $text_y) = $image->get_text_size("$GalleryCopyrightText");
1540                         my $x = $image->get_width();
1541                         my $y = $image->get_height();
1542
1543                         my $offset = 3;
1544
1545                         if (($text_x < $x - $offset) && ($text_y < $y - $offset)) {
1546                                 if ($GalleryCopyrightBackgroundColor =~ /^\d+,\d+,\d+,\d+$/) {
1547                                         my ($br_val, $bg_val, $bb_val, $ba_val) = split (/,/, $GalleryCopyrightBackgroundColor);
1548                                         $image->set_colour($br_val, $bg_val, $bb_val, $ba_val);
1549                                         $image->fill_rectangle ($x-$text_x-$offset, $y-$text_y-$offset, $text_x, $text_y);
1550                                 }
1551                                 my ($r_val, $g_val, $b_val, $a_val) = split (/,/, $text_color);
1552                                 $image->set_colour($r_val, $g_val, $b_val, $a_val);
1553                                 $image->draw_text($x-$text_x-$offset, $y-$text_y-$offset, "$GalleryCopyrightText");
1554                         } else {
1555                                 log_error("Text is to big for the picture.\n");
1556                         }
1557                 }
1558         }
1559
1560         if ($quality && $quality =~ m/^\d+$/) {
1561                 $image->set_quality($quality);
1562         }
1563
1564         $image->save($outfile);
1565
1566 }
1567
1568 sub gallerysort {
1569         my $r=shift;
1570         my @files=@_;
1571         my $sortby = $r->dir_config('GallerySortBy');
1572         my $filename=$r->lookup_uri($r->uri)->filename;
1573         $filename=(File::Spec->splitpath($filename))[1] if (-f $filename);
1574         if ($sortby && $sortby =~ m/^(size|atime|mtime|ctime)$/) {
1575                 @files = map(/^\d+ (.*)/, sort map(stat("$filename/$_")->$sortby()." $_", @files));
1576         } else {
1577                 @files = sort @files;
1578         }
1579         return @files;
1580 }
1581
1582 # Create Text::Template objects used by Apache::Gallery. Takes a
1583 # hashref of template_name, template_filename pairs, and returns a
1584 # list of template_name, texttemplate_object pairs.
1585 sub create_templates {
1586      my $templates = shift;
1587
1588      # This routine is called whenever a template has an error. Prints
1589      # the error to STDERR and sticks the error in the output
1590      sub tt_broken {
1591           my %args = @_;
1592           # Pull out the name and filename from the arg option [see
1593           # Text::Template for details]
1594           @args{qw(name file)} = @{$args{arg}};
1595           print STDERR qq(Template $args{name} ("$args{file}") is broken: $args{error});
1596           # Don't include the file name in the output, as the user can see this.
1597           return qq(<!-- Template $args{name} is broken: $args{error} -->);
1598      }
1599
1600
1601
1602      my %texttemplate_objects;
1603
1604      for my $template_name (keys %$templates) {
1605           my $tt_obj = Text::Template->new(TYPE   => 'FILE',
1606                                            SOURCE => $$templates{$template_name},
1607                                            BROKEN => \&tt_broken,
1608                                            BROKEN_ARG => [$template_name, $$templates{$template_name}],
1609                                           )
1610                or die "Unable to create new Text::Template object for $template_name: $Text::Template::ERROR";
1611           $texttemplate_objects{$template_name} = $tt_obj;
1612      }
1613      return %texttemplate_objects;
1614 }
1615
1616 sub log_error {
1617         if ($::MP2) {
1618                 Apache2::RequestUtil->request->log_error(shift());
1619         } else {
1620                 Apache->request->log_error(shift());
1621         }
1622 }
1623
1624 1;
1625
1626 =head1 NAME
1627
1628 Apache::Gallery - mod_perl handler to create an image gallery
1629
1630 =head1 SYNOPSIS
1631
1632 See the INSTALL file in the distribution for installation instructions.
1633
1634 =head1 DESCRIPTION
1635
1636 Apache::Gallery creates an thumbnail index of each directory and allows 
1637 viewing pictures in different resolutions. Pictures are resized on the 
1638 fly and cached. The gallery can be configured and customized in many ways
1639 and a custom copyright image can be added to all the images without
1640 modifying the original.
1641
1642 =head1 CONFIGURATION
1643
1644 In your httpd.conf you set the global options for the gallery. You can
1645 also override each of the options in .htaccess files in your gallery
1646 directories.
1647
1648 The options are set in the httpd.conf/.htaccess file using the syntax:
1649 B<PerlSetVar OptionName 'value'>
1650
1651 Example: B<PerlSetVar GalleryCacheDir '/var/tmp/Apache-Gallery/'>
1652
1653 =over 4
1654
1655 =item B<GalleryAutoRotate>
1656
1657 Some cameras, like the Canon G3, can detect the orientation of a 
1658 the pictures you take and will save this information in the 
1659 'Orientation' EXIF field. Apache::Gallery will then automatically
1660 rotate your images. 
1661
1662 This behavior is default but can be disabled by setting GalleryAutoRotate
1663 to 0.
1664
1665 =item B<GalleryCacheDir>
1666
1667 Directory where Apache::Gallery should create its cache with scaled
1668 pictures. The default is /var/tmp/Apache-Gallery/ . Here, a directory
1669 for each virtualhost or location will be created automaticly. Make
1670 sure your webserver has write access to the CacheDir.
1671
1672 =item B<GalleryTemplateDir>
1673
1674 Full path to the directory where you placed the templates. This option
1675 can be used both in your global configuration and in .htaccess files,
1676 this way you can have different layouts in different parts of your 
1677 gallery.
1678
1679 No default value, this option is required.
1680
1681 =item B<GalleryInfo>
1682
1683 With this option you can define which EXIF information you would like
1684 to present from the image. The format is: '<MyName => KeyInEXIF, 
1685 MyOtherName => OtherKeyInEXIF'
1686
1687 Examples of keys: B<ShutterSpeedValue>, B<ApertureValue>, B<SubjectDistance>,
1688 and B<Camera>
1689
1690 You can view all the keys from the EXIF header using this perl-oneliner:
1691
1692 perl C<-e> 'use Data::Dumper; use Image::Info qw(image_info); print Dumper(image_info(shift));' filename.jpg
1693
1694 Default is: 'Picture Taken => DateTimeOriginal, Flash => Flash'
1695
1696 =item B<GallerySizes>
1697
1698 Defines which widths images can be scaled to. Images cannot be
1699 scaled to other widths than the ones you define with this option.
1700
1701 The default is '640 800 1024 1600'
1702
1703 =item B<GalleryThumbnailSize>
1704
1705 Defines the width and height of the thumbnail images. 
1706
1707 Defaults to '100x75'
1708
1709 =item B<GalleryThumbnailSizeLS>
1710
1711 If set to '1', B<GalleryThumbnailSize> is the long and the short side of
1712 the thumbnail image instead of the width and height.
1713
1714 Defaults to '0'.
1715
1716 =item B<GalleryCopyrightImage>
1717
1718 Image you want to blend into your images in the lower right
1719 corner. This could be a transparent png saying "copyright
1720 my name 2001".
1721
1722 Optional.
1723
1724 =item B<GalleryWrapNavigation>
1725
1726 Make the navigation in the picture view wrap around (So Next
1727 at the end displays the first picture, etc.)
1728
1729 Set to 1 or 0, default is 0
1730
1731 =item B<GalleryAllowOriginal>
1732
1733 Allow the user to download the Original picture without
1734 resizing or putting the CopyrightImage on it.
1735
1736 Set to 1 or 0, default is 0
1737
1738 =item B<GallerySlideshowIntervals>
1739
1740 With this option you can configure which intervals can be selected for
1741 a slideshow. The default is '3 5 10 15 30'
1742
1743 =item B<GallerySortBy>
1744
1745 Instead of the default filename ordering you can sort by any
1746 stat attribute. For example size, atime, mtime, ctime.
1747
1748 =item B<GalleryDirSortBy>
1749
1750 Set this variable to sort directories differently than other items,
1751 can be set to size, atime, mtime and ctime; setting any other value
1752 will revert to sorting by name.
1753
1754 =item B<GalleryMemoize>
1755
1756 Cache EXIF data using Memoize - this will make Apache::Gallery faster
1757 when many people access the same images, but it will also cache EXIF
1758 data until the current Apache child dies.
1759
1760 =item B<GalleryUseFileDate>
1761
1762 Set this option to 1 to make A::G show the files timestamp
1763 instead of the EXIF value for "Picture taken".
1764
1765 =item B<GallerySelectionMode>
1766
1767 Enable the selection mode. Select images with checkboxes and
1768 get a list of filenames. 
1769
1770 =item B<GalleryEXIFMode>
1771
1772 You can choose how Apache::Gallery should display EXIF info
1773 from your images. 
1774
1775 The default setting is 'namevalue'. This setting will make 
1776 Apache::Gallery print out the names and values of the EXIF values 
1777 you configure with GalleryInfo. The information will be parsed into 
1778 $INFO in pictureinfo.tpl.  
1779
1780 You can also set it to 'values' which will make A::G parse
1781 the configured values into the var $EXIFVALUES as 'value | value | value'
1782
1783 If you set this option to 'variables' the items you configure in GalleryInfo 
1784 will be available to your templates as $EXIF_<KEYNAME> (in all uppercase). 
1785 That means that with the default setting "Picture Taken => DateTimeOriginal, 
1786 Flash => Flash" you will have the variables $EXIF_DATETIMEORIGINAL and 
1787 $EXIF_FLASH avilable to your templates. You can place them
1788 anywhere you want.
1789
1790 =item B<GalleryRootPath>
1791
1792 Change the location of gallery root. The default is ""
1793
1794 =item B<GalleryRootText>
1795
1796 Change the name that appears as the root element in the menu. The
1797 default is "root:"
1798
1799 =item B<GalleryMaxThumbnailsPerPage>
1800
1801 This options controls how many thumbnails should be displayed in a 
1802 page. It requires $BROWSELINKS to be in the index.tpl template file.
1803
1804 =item B<GalleryImgFile>
1805
1806 Pattern matching the files you want Apache::Gallery to view in the
1807 index as thumbnails. 
1808
1809 The default is '\.(jpe?g|png|tiff?|ppm)$'
1810
1811 =item B<GalleryDocFile>
1812
1813 Pattern matching the files you want Apache::Gallery to view in the index
1814 as normal files. All other filetypes will still be served by Apache::Gallery
1815 but are not visible in the index.
1816
1817 The default is '\.(mpe?g|avi|mov|asf|wmv|doc|mp3|ogg|pdf|rtf|wav|dlt|html?|csv|eps)$'
1818
1819 =item B<GalleryTTFDir>
1820
1821 To use the GalleryCopyrightText feature you must set this option to the
1822 directory where your True Type fonts are stored. No default is set.
1823
1824 Example:
1825
1826         PerlSetVar      GalleryTTFDir '/usr/share/fonts/'
1827
1828 =item B<GalleryTTFFile>
1829
1830 To use the GalleryCopyrightText feature this option must be set to the
1831 name of the True Type font you wish to use. Example:
1832
1833         PerlSetVar      GalleryTTFFile 'verdanab.ttf'
1834
1835 =item B<GalleryTTFSize>
1836
1837 Configure the size of the CopyrightText that will be inserted as 
1838 copyright notice in the corner of your pictures.
1839
1840 Example:
1841
1842         PerlSetVar      GalleryTTFSize '10'
1843
1844 =item B<GalleryCopyrightText>
1845
1846 The text that will be inserted as copyright notice.
1847
1848 Example:
1849
1850         PerlSetVar      GalleryCopyrightText '(c) Michael Legart'
1851
1852 =item B<GalleryCopyrightColor>
1853
1854 The text color of your copyright notice.
1855
1856 Examples:
1857
1858 White:
1859         PerlSetVar      GalleryCopyrightColor '255,255,255,255'
1860
1861 Black:
1862         PerlSetVar      GalleryCopyrightColor '0,0,0,255'
1863
1864 Red:
1865         PerlSetVar      GalleryCopyrightColor '255,0,0,255'
1866
1867 Green:
1868         PerlSetVar      GalleryCopyrightColor '0,255,0,255'
1869
1870 Blue:
1871         PerlSetVar      GalleryCopyrightColor '0,0,255,255'
1872
1873 Transparent orange:
1874         PerlSetVar      GalleryCopyrightColor '255,127,0,127'
1875
1876 =item B<GalleryCopyrightBackgroundColor>
1877
1878 The background-color of a GalleryCopyrightText
1879
1880 r,g,b,a - for examples, see GalleryCopyrightColor
1881
1882 =item B<GalleryQuality>
1883
1884 The quality (1-100) of scaled images
1885
1886 This setting affects the quality of the scaled images.
1887 Set this to a low number to reduce the size of the scaled images.
1888 Remember to clear out your cache if you change this setting.
1889 Quality seems to default to 75, at least in the jpeg and png loader code in
1890 Imlib2 1.1.0.
1891
1892 Examples:
1893
1894 Quality at 50:
1895         PerlSetVar      GalleryQuality '50'
1896
1897 =item B<GalleryUnderscoresToSpaces>
1898
1899 Set this option to 1 to convert underscores to spaces in the listing
1900 of directory names.
1901
1902 =back
1903
1904 =item B<GalleryCommentExifKey>
1905
1906 Set this option to e.g. ImageDescription to use this field as comments
1907 for images.
1908
1909 =item B<GalleryEnableMediaRss>
1910
1911 Set this option to 1 to enable generation of a media RSS feed. This
1912 can be used e.g. together with the PicLens plugin from http://piclens.com
1913
1914 =head1 FEATURES
1915
1916 =over 4
1917
1918 =item B<Rotate images>
1919
1920 Some cameras, like the Canon G3, detects the orientation of a picture
1921 and adds this info to the EXIF header. Apache::Gallery detects this
1922 and automaticly rotates images with this info.
1923
1924 If your camera does not support this, you can rotate the images 
1925 manually, This can also be used to override the rotate information
1926 from a camera that supports that. You can also disable this behavior
1927 with the GalleryAutoRotate option.
1928
1929 To use this functionality you have to create file with the name of the 
1930 picture you want rotated appened with ".rotate". The file should include 
1931 a number where these numbers are supported:
1932
1933         "1", rotates clockwise by 90 degree
1934         "2", rotates clockwise by 180 degrees
1935         "3", rotates clockwise by 270 degrees
1936
1937 So if we want to rotate "Picture1234.jpg" 90 degrees clockwise we would
1938 create a file in the same directory called "Picture1234.jpg.rotate" with
1939 the number 1 inside of it.
1940
1941 =item B<Comments>
1942
1943 To include comments for a directory you create a <directory>.comment
1944 file where the first line can contain "TITLE: New title" which
1945 will be the title of the page, and a comment on the following 
1946 lines.
1947 To include comments for each picture you create files called 
1948 picture.jpg.comment where the first line can contain "TITLE: New
1949 title" which will be the title of the page, and a comment on the
1950 following lines.
1951
1952 Example:
1953
1954         TITLE: This is the new title of the page
1955         And this is the comment.<br />
1956         And this is line two of the comment.
1957
1958 The visible name of the folder is by default identical to the name of
1959 the folder, but can be changed by creating a file <directory>.folder
1960 with the visible name of the folder.
1961
1962 It is also possible to set GalleryCommentExifKey to the name of an EXIF
1963 field containing the comment, e.g. ImageDescription. The EXIF comment is
1964 overridden by the .comment file if it exists.
1965
1966 =back
1967
1968 =head1 DEPENDENCIES
1969
1970 =over 4
1971
1972 =item B<Perl 5>
1973
1974 =item B<Apache with mod_perl>
1975
1976 =item B<URI::Escape>
1977
1978 =item B<Image::Info>
1979
1980 =item B<Image::Size>
1981
1982 =item B<Text::Template>
1983
1984 =item B<Image::Imlib2>
1985
1986 =item B<X11 libraries>
1987 (ie, XFree86)
1988
1989 =item B<Imlib2>
1990 Remember the -dev package when using rpm, deb or other package formats!
1991
1992 =back
1993
1994 =head1 AUTHOR
1995
1996 Michael Legart <michael@legart.dk>
1997
1998 =head1 COPYRIGHT AND LICENSE
1999
2000 Copyright (C) 2001-2005 Michael Legart <michael@legart.dk>
2001
2002 Templates designed by Thomas Kjaer <tk@lnx.dk>
2003
2004 Apache::Gallery is free software and is released under the Artistic License.
2005 See B<http://www.perl.com/language/misc/Artistic.html> for details.
2006
2007 The video icons are from the GNOME project. B<http://www.gnome.org/>
2008
2009 =head1 THANKS
2010
2011 Thanks to Thomas Kjaer for templates and design of B<http://apachegallery.dk>
2012 Thanks to Thomas Eibner and other for patches. (See the Changes file)
2013
2014 =head1 SEE ALSO
2015
2016 L<perl>, L<mod_perl>, L<Image::Imlib2>, L<CGI::FastTemplate>,
2017 L<Image::Info>, and L<Image::Size>.
2018
2019 =cut