| 1 | #!/usr/bin/perl |
|---|
| 2 | # -*- coding:utf-8 -*- |
|---|
| 3 | # |
|---|
| 4 | # Author: FUJIWARA Shunichiro <fujiwara.shunichiro at gmail.com> |
|---|
| 5 | # License: Same as Perl |
|---|
| 6 | # |
|---|
| 7 | |
|---|
| 8 | use strict; |
|---|
| 9 | use Imager; |
|---|
| 10 | use Imager::Fill; |
|---|
| 11 | use Imager::Font; |
|---|
| 12 | use URI::Fetch; |
|---|
| 13 | use Cache::Memcached; |
|---|
| 14 | use CGI::Carp qw / fatalsToBrowser /; |
|---|
| 15 | use CGI; |
|---|
| 16 | use Digest::SHA qw/ sha1_hex /; |
|---|
| 17 | use List::Util qw/ min max /; |
|---|
| 18 | use Data::Dumper; |
|---|
| 19 | |
|---|
| 20 | my $q = CGI->new; |
|---|
| 21 | |
|---|
| 22 | my $cache = Cache::Memcached->new({ |
|---|
| 23 | servers => ['127.0.0.1:11211'], |
|---|
| 24 | }); |
|---|
| 25 | |
|---|
| 26 | my $uri; |
|---|
| 27 | if ( $q->param('uri') ) { |
|---|
| 28 | $uri = $q->param('uri'); |
|---|
| 29 | } |
|---|
| 30 | else { |
|---|
| 31 | $uri = $q->path_info; |
|---|
| 32 | $uri =~ s{^/}{}; |
|---|
| 33 | $uri =~ s{(^https?:/)([^/])}{$1/$2}; |
|---|
| 34 | } |
|---|
| 35 | |
|---|
| 36 | my $data; |
|---|
| 37 | if ( $uri ) { |
|---|
| 38 | $data = data_from_uri($uri, $q); |
|---|
| 39 | $data = convert( $data, $cache, $q ); |
|---|
| 40 | if ($cache) { |
|---|
| 41 | $cache->set( cache_key( $uri, $q ) => $data => 3600 ); |
|---|
| 42 | } |
|---|
| 43 | output($data); |
|---|
| 44 | } |
|---|
| 45 | elsif ( $q->param('file') ) { |
|---|
| 46 | $data = data_from_upload( $q->param('file') ); |
|---|
| 47 | my $file = store_file( convert( $data, undef, $q ) ); |
|---|
| 48 | redirect($file); |
|---|
| 49 | } |
|---|
| 50 | elsif ( $q->param('files') ) { |
|---|
| 51 | $data = data_from_upload_multi( $q->param('files') ); |
|---|
| 52 | my $file = store_file( convert( $data, undef, $q ) ); |
|---|
| 53 | output_text( URI->new_abs( $file, $q->url )->as_string ); |
|---|
| 54 | } |
|---|
| 55 | else { |
|---|
| 56 | error("no data"); |
|---|
| 57 | } |
|---|
| 58 | exit; |
|---|
| 59 | |
|---|
| 60 | sub store_file { |
|---|
| 61 | my $data = shift; |
|---|
| 62 | my $file = sprintf "files/%s.jpg", sha1_hex( rand() . $ENV{REMOTE_ADDR} ); |
|---|
| 63 | open my $out, '>', $file or die $!; |
|---|
| 64 | print $out $data; |
|---|
| 65 | close $out; |
|---|
| 66 | return $file; |
|---|
| 67 | } |
|---|
| 68 | |
|---|
| 69 | sub data_from_upload { |
|---|
| 70 | my $fh = shift; |
|---|
| 71 | local $/ = undef; |
|---|
| 72 | $data = <$fh>; |
|---|
| 73 | } |
|---|
| 74 | |
|---|
| 75 | sub data_from_upload_multi { |
|---|
| 76 | my $n = shift; |
|---|
| 77 | local $/ = undef; |
|---|
| 78 | |
|---|
| 79 | for my $i ( 1 .. $n ) { |
|---|
| 80 | my $info = $q->uploadInfo( $q->param("file_$i") ); |
|---|
| 81 | if ( $info->{'Content-Type'} =~ /^image\// ) { |
|---|
| 82 | my $fh = $q->upload("file_$i"); |
|---|
| 83 | return <$fh>; |
|---|
| 84 | } |
|---|
| 85 | } |
|---|
| 86 | return; |
|---|
| 87 | } |
|---|
| 88 | |
|---|
| 89 | sub cache_key { |
|---|
| 90 | my ( $uri, $q ) = @_; |
|---|
| 91 | |
|---|
| 92 | return sprintf( |
|---|
| 93 | "result:%s:%s:%s:%s", |
|---|
| 94 | $uri, |
|---|
| 95 | $q->param('date'), |
|---|
| 96 | $q->param('noborder'), |
|---|
| 97 | $q->param('glossy'), |
|---|
| 98 | ); |
|---|
| 99 | } |
|---|
| 100 | |
|---|
| 101 | sub data_from_uri { |
|---|
| 102 | my ( $uri, $q ) = @_; |
|---|
| 103 | if ( my $data = $cache->get( cache_key( $uri, $q ) ) ) { |
|---|
| 104 | output($data); |
|---|
| 105 | exit; |
|---|
| 106 | } |
|---|
| 107 | else { |
|---|
| 108 | } |
|---|
| 109 | my $res = URI::Fetch->fetch( $uri, Cache => $cache ) |
|---|
| 110 | or error( URI::Fetch->errstr ); |
|---|
| 111 | return $res->content; |
|---|
| 112 | } |
|---|
| 113 | |
|---|
| 114 | sub convert { |
|---|
| 115 | my ( $data, $cache, $q ) = @_; |
|---|
| 116 | |
|---|
| 117 | my $img = Imager->new; |
|---|
| 118 | $img->read( data => $data ) |
|---|
| 119 | or error( $img->errstr ); |
|---|
| 120 | |
|---|
| 121 | $img->filter( type => "contrast", intensity => 0.7 ); |
|---|
| 122 | $img->filter( type => "conv", coef => [ 0, 1, 0.2 ] ); |
|---|
| 123 | $img = solid_border( $img, "#bbbbbb" ) unless $q->param('noborder'); |
|---|
| 124 | |
|---|
| 125 | my $fill = Imager::Fill->new( solid => random_color('302515'), combine => 'add' ); |
|---|
| 126 | $img->box( fill => $fill ); |
|---|
| 127 | $fill = Imager::Fill->new( solid => '#111111', combine => 'add' ); |
|---|
| 128 | $img->box( fill => $fill ); |
|---|
| 129 | $img = add_date( $img, $q->param('date') ) |
|---|
| 130 | if $q->param('date'); |
|---|
| 131 | |
|---|
| 132 | my $amount = $q->param('glossy') ? 3 : 10; |
|---|
| 133 | $img->filter( |
|---|
| 134 | type => 'noise', |
|---|
| 135 | amount => $amount, |
|---|
| 136 | subtype => 1, |
|---|
| 137 | ); |
|---|
| 138 | |
|---|
| 139 | $img->write( data => \$data, type => 'jpeg' ); |
|---|
| 140 | |
|---|
| 141 | return $data; |
|---|
| 142 | } |
|---|
| 143 | |
|---|
| 144 | sub redirect { |
|---|
| 145 | my $path = shift; |
|---|
| 146 | print "Location: $path\n\n"; |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | sub error { |
|---|
| 150 | my $msg = shift; |
|---|
| 151 | print "status: 500\n"; |
|---|
| 152 | print "Content-Type: text/html\n\n"; |
|---|
| 153 | die $msg; |
|---|
| 154 | } |
|---|
| 155 | |
|---|
| 156 | sub output { |
|---|
| 157 | print "Content-Type: image/jpeg\n"; |
|---|
| 158 | print "Content-Length: ", length($_[0]), "\n"; |
|---|
| 159 | print "\n"; |
|---|
| 160 | print $_[0]; |
|---|
| 161 | } |
|---|
| 162 | |
|---|
| 163 | sub output_text { |
|---|
| 164 | my $file = shift; |
|---|
| 165 | print "Content-Type: text/plain; charset=utf-8\n"; |
|---|
| 166 | print "\n"; |
|---|
| 167 | print "昭和の写真ジェネレータ\n"; |
|---|
| 168 | print "変換されました\n$file\n"; |
|---|
| 169 | } |
|---|
| 170 | |
|---|
| 171 | # solid_border is based on Imager/samples/border.pl |
|---|
| 172 | # http://search.cpan.org/~tonyc/Imager/samples/border.pl |
|---|
| 173 | sub solid_border { |
|---|
| 174 | my ( $source, $color ) = @_; |
|---|
| 175 | |
|---|
| 176 | my $w = $source->getwidth(); |
|---|
| 177 | my $h = $source->getheight(); |
|---|
| 178 | my $border = int ( max( $w * 0.015, $h * 0.015, 5 ) ); |
|---|
| 179 | my $out = Imager->new( |
|---|
| 180 | xsize => $source->getwidth() + 2 * $border, |
|---|
| 181 | ysize => $source->getheight() + 2 * $border, |
|---|
| 182 | bits => $source->bits, |
|---|
| 183 | channels => $source->getchannels, |
|---|
| 184 | ); |
|---|
| 185 | |
|---|
| 186 | $out->box( filled => 1, color => $color ) |
|---|
| 187 | or die "Invalid color '$color':", $out->errstr, "\n"; |
|---|
| 188 | |
|---|
| 189 | $out->paste( |
|---|
| 190 | left => $border, |
|---|
| 191 | top => $border, |
|---|
| 192 | img => $source, |
|---|
| 193 | ); |
|---|
| 194 | |
|---|
| 195 | return $out; |
|---|
| 196 | } |
|---|
| 197 | |
|---|
| 198 | sub random_color { |
|---|
| 199 | my $code = shift; |
|---|
| 200 | my @color = map { hex $_ } ( $code =~ m{(..)}g ); |
|---|
| 201 | |
|---|
| 202 | for my $c ( @color ) { |
|---|
| 203 | $c *= ( 1 + ( rand() > 0.5 ? +1 : -1 ) * rand(0.15) ); |
|---|
| 204 | $c = min( $c, 255 ); |
|---|
| 205 | $c = max( $c, 0 ); |
|---|
| 206 | } |
|---|
| 207 | $code = sprintf('#%02x%02x%02x', @color); |
|---|
| 208 | return $code; |
|---|
| 209 | } |
|---|
| 210 | |
|---|
| 211 | sub add_date { |
|---|
| 212 | my $img = shift; |
|---|
| 213 | my $date = shift; |
|---|
| 214 | $date =~ s/[^ \d\-\.]//g; |
|---|
| 215 | |
|---|
| 216 | my $color = Imager::Color->new('#ffaa33'); |
|---|
| 217 | my $ttfont = Imager::Font->new( |
|---|
| 218 | file => '7barPBd.TTF', # http://www.trojanbear.net/omake.htm#sevenbarb |
|---|
| 219 | ); |
|---|
| 220 | my $size = int( ($img->getwidth + $img->getheight) * 0.02 ); |
|---|
| 221 | $img->align_string( |
|---|
| 222 | font => $ttfont, |
|---|
| 223 | text => $date, |
|---|
| 224 | x => int( $img->getwidth - $size * 1.5 ), |
|---|
| 225 | y => int( $img->getheight - $size * 1.5 ), |
|---|
| 226 | halign => 'right', |
|---|
| 227 | valign => 'bottom', |
|---|
| 228 | size => $size, |
|---|
| 229 | color => $color, |
|---|
| 230 | aa => 1, |
|---|
| 231 | ); |
|---|
| 232 | return $img; |
|---|
| 233 | } |
|---|