root/branches/release-34/lib/MT/Asset/Image.pm @ 1829

Revision 1829, 22.8 kB (checked in by fumiakiy, 20 months ago)

Square thumbnails now has appropriate file name. BugId:79235

Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT::Asset::Image;
8
9use strict;
10use base qw( MT::Asset );
11
12__PACKAGE__->install_properties( { class_type => 'image', } );
13__PACKAGE__->install_meta( { columns => [ 'image_width', 'image_height', ], } );
14
15# List of supported file extensions (to aid the stock 'can_handle' method.)
16sub extensions { [ qr/gif/i, qr/jpe?g/i, qr/png/i, ] }
17
18sub class_label {
19    MT->translate('Image');
20}
21
22sub class_label_plural {
23    MT->translate('Images');
24}
25
26sub metadata {
27    my $obj  = shift;
28    my $meta = $obj->SUPER::metadata(@_);
29
30    my $width  = $obj->image_width;
31    my $height = $obj->image_height;
32    $meta->{image_width}  = $width  if defined $width;
33    $meta->{image_height} = $height if defined $height;
34    $meta->{image_dimensions} = $meta->{ MT->translate("Actual Dimensions") } =
35      MT->translate( "[_1] x [_2] pixels", $width, $height )
36      if defined $width && defined $height;
37
38    $meta;
39}
40
41sub image_height {
42    my $asset = shift;
43    my $height = $asset->meta('image_height', @_);
44    return $height if $height || @_;
45
46    eval { require Image::Size; };
47    return undef if $@;
48    my ( $w, $h, $id ) = Image::Size::imgsize($asset->file_path);
49    $asset->meta('image_height', $h);
50    if ($asset->id) {
51        $asset->save;
52    }
53    return $h;
54}
55
56sub image_width {
57    my $asset = shift;
58    my $width = $asset->meta('image_width', @_);
59    return $width if $width || @_;
60
61    eval { require Image::Size; };
62    return undef if $@;
63    my ( $w, $h, $id ) = Image::Size::imgsize($asset->file_path);
64    $asset->meta('image_width', $w);
65    if ($asset->id) {
66        $asset->save;
67    }
68    return $w;
69}
70
71sub has_thumbnail {
72    1;
73}
74
75sub thumbnail_path {
76    my $asset   = shift;
77    my (%param) = @_;
78
79    $asset->_make_cache_path($param{Path});
80}
81
82sub thumbnail_file {
83    my $asset     = shift;
84    my (%param)   = @_;
85    my $file_path = $asset->file_path;
86    my @imginfo   = stat($file_path);
87    return undef unless @imginfo;
88
89    my $blog = $param{Blog} || $asset->blog;
90    my $fmgr;
91
92    require MT::Util;
93    my $asset_cache_path = $asset->_make_cache_path($param{Path});
94    my ( $i_h, $i_w ) = ( $asset->image_height, $asset->image_width );
95    return undef unless $i_h && $i_w;
96
97    # Pretend the image is already square, for calculation purposes.
98    if ($param{Square}) {
99        require MT::Image;
100        my %square = MT::Image->inscribe_square(
101            Width => $i_w, Height => $i_h );
102        ($i_h, $i_w) = @square{qw( Size Size )};
103        if ( $param{Width} && !$param{Height} ) {
104            $param{Height} = $param{Width};
105        }
106        elsif ( !$param{Width} && $param{Height} ) {
107            $param{Width} = $param{Height};
108        }
109    }
110
111    if ( my $scale = $param{Scale} ) {
112        $param{Width}  = int( ( $i_w * $scale ) / 100 );
113        $param{Height} = int( ( $i_h * $scale ) / 100 );
114    }
115    if ( !exists $param{Width} && !exists $param{Height} ) {
116        $param{Width}  = $i_w;
117        $param{Height} = $i_h;
118    }
119
120    # find the longest dimension of the image:
121    my ( $n_h, $n_w ) =
122      _get_dimension( $i_h, $i_w, $param{Height}, $param{Width} );
123
124    my $file = $asset->thumbnail_filename(%param) or return;
125    my $thumbnail = File::Spec->catfile( $asset_cache_path, $file );
126    my @thumbinfo = stat($thumbnail);
127
128    # thumbnail file exists and is dated on or later than source image
129    if ( @thumbinfo && ( $thumbinfo[9] >= $imginfo[9] ) ) {
130        return ( $thumbnail, $n_w, $n_h );
131    }
132
133    # stale or non-existent thumbnail. let's create one!
134    require MT::FileMgr;
135    $fmgr ||= $blog ? $blog->file_mgr : MT::FileMgr->new('Local');
136    return undef unless $fmgr;
137    return undef unless $fmgr->can_write($asset_cache_path);
138
139    my $data;
140    if ( ( $n_w == $i_w ) && ( $n_h == $i_h ) && !$param{Square}
141      && !$param{Type} ) {
142        $data = $fmgr->get_data( $file_path, 'upload' );
143    }
144    else {
145
146        # create a thumbnail for this file
147        require MT::Image;
148        my $img = new MT::Image( Filename => $file_path )
149          or return $asset->error( MT::Image->errstr );
150
151        # Really make the image square, so our scale calculation works out.
152        if ($param{Square}) {
153            ($data) = $img->make_square()
154              or return $asset->error(
155                MT->translate( "Error cropping image: [_1]", $img->errstr ) );
156        }
157
158        ($data) = $img->scale( Height => $n_h, Width => $n_w )
159          or return $asset->error(
160            MT->translate( "Error scaling image: [_1]", $img->errstr ) );
161
162        if (my $type = $param{Type}) {
163            ($data) = $img->convert( Type => $type )
164              or return $asset->error(
165                MT->translate( "Error converting image: [_1]", $img->errstr ) );
166        }
167    }
168    $fmgr->put_data( $data, $thumbnail, 'upload' )
169      or return $asset->error(
170        MT->translate( "Error creating thumbnail file: [_1]", $fmgr->errstr ) );
171    return ( $thumbnail, $n_w, $n_h );
172}
173
174sub _get_dimension {
175    my ( $i_h, $i_w, $h, $w ) = @_;
176
177    my ( $n_h, $n_w ) = ( $i_h, $i_w );
178    my $scale = '';
179    if ( $h && !$w ) {
180        $scale = 'h';
181    }
182    elsif ( $w && !$h ) {
183        $scale = 'w';
184    }
185    else {
186        if ( $i_h > $i_w ) {
187
188            # scale, if necessary, by height
189            if ( $i_h > $h ) {
190                $scale = 'h';
191            }
192            elsif ( $i_w > $w ) {
193                $scale = 'w';
194            }
195        }
196        else {
197
198            # scale, if necessary, by width
199            if ( $i_w > $w ) {
200                $scale = 'w';
201            }
202            elsif ( $i_h > $h ) {
203                $scale = 'h';
204            }
205        }
206    }
207    if ( $scale eq 'h' ) {
208
209        # scale by height
210        $n_h = $h;
211        $n_w = int( $i_w * $h / $i_h );
212    }
213    elsif ( $scale eq 'w' ) {
214
215        # scale by width
216        $n_w = $w;
217        $n_h = int( $i_h * $w / $i_w );
218    }
219    return ( $n_h, $n_w );
220}
221
222sub thumbnail_filename {
223    my $asset   = shift;
224    my (%param) = @_;
225    my $file    = $asset->file_name or return;
226
227    require MT::Util;
228    my $format = $param{Format} || MT->translate('%f-thumb-%wx%h%x');
229    my $width  = $param{Width}  || 'auto';
230    my $height = $param{Height} || 'auto';
231    $file =~ s/\.\w+$//;
232    my $base = File::Basename::basename($file);
233    my $id   = $asset->id;
234    my $ext  = lc($param{Type}) || $asset->file_ext || '';
235    $ext = '.' . $ext;
236    $format =~ s/%w/$width/g;
237    $format =~ s/%h/$height/g;
238    $format =~ s/%f/$base/g;
239    $format =~ s/%i/$id/g;
240    $format =~ s/%x/$ext/g;
241    return $format;
242}
243
244sub as_html {
245    my $asset   = shift;
246    my ($param) = @_;
247    my $text    = '';
248
249    $param->{enclose} = 1 unless exists $param->{enclose};
250
251    if ( $param->{include} ) {
252
253        my $fname = $asset->file_name;
254        require MT::Util;
255
256        my $thumb = undef;
257        if ( $param->{thumb} ) {
258            $thumb = MT::Asset->load( $param->{thumb_asset_id} )
259              || return $asset->error(
260                MT->translate(
261                    "Can't load image #[_1]",
262                    $param->{thumb_asset_id}
263                )
264              );
265        }
266
267        my $dimensions = sprintf(
268            'width="%s" height="%s"',
269            (
270                $thumb
271                ? ( $thumb->image_width, $thumb->image_height )
272                : ( $asset->image_width, $asset->image_height )
273            )
274        );
275        my $wrap_style = '';
276        if ( $param->{wrap_text} && $param->{align} ) {
277            $wrap_style = 'class="mt-image-' . $param->{align} . '" ';
278            if ( $param->{align} eq 'none' ) {
279                $wrap_style .= q{style=""};
280            }
281            elsif ( $param->{align} eq 'left' ) {
282                $wrap_style .= q{style="float: left; margin: 0 20px 20px 0;"};
283            }
284            elsif ( $param->{align} eq 'right' ) {
285                $wrap_style .= q{style="float: right; margin: 0 0 20px 20px;"};
286            }
287            elsif ( $param->{align} eq 'center' ) {
288                $wrap_style .= q{style="text-align: center; display: block; margin: 0 auto 20px;"};
289            }
290        }
291
292        if ( $param->{popup} ) {
293            my $popup = MT::Asset->load( $param->{popup_asset_id} )
294              || return $asset->error(
295                MT->translate(
296                    "Can't load image #[_1]",
297                    $param->{popup_asset_id}
298                )
299              );
300            my $link =
301              $thumb
302              ? sprintf(
303                '<img src="%s" %s alt="%s" %s />',
304                MT::Util::encode_html( $thumb->url ),   $dimensions,
305                MT::Util::encode_html( $asset->label ), $wrap_style
306              )
307              : MT->translate('View image');
308            $text = sprintf(
309q|<a href="%s" onclick="window.open('%s','popup','width=%d,height=%d,scrollbars=no,resizable=no,toolbar=no,directories=no,location=no,menubar=no,status=no,left=0,top=0'); return false">%s</a>|,
310                MT::Util::encode_html( $popup->url ),
311                MT::Util::encode_html( $popup->url ),
312                $asset->image_width,
313                $asset->image_height,
314                $link,
315            );
316        }
317        else {
318            if ( $param->{thumb} ) {
319                $text = sprintf(
320                    '<a href="%s"><img alt="%s" src="%s" %s %s /></a>',
321                    MT::Util::encode_html( $asset->url ),
322                    MT::Util::encode_html( $asset->label ),
323                    MT::Util::encode_html( $thumb->url ),
324                    $dimensions,
325                    $wrap_style,
326                );
327            }
328            else {
329                $text = sprintf(
330                    '<img alt="%s" src="%s" %s %s />',
331                    MT::Util::encode_html( $asset->label ),
332                    MT::Util::encode_html( $asset->url ),
333                    $dimensions, $wrap_style,
334                );
335            }
336        }
337    }
338    else {
339        $text = sprintf(
340            '<a href="%s">%s</a>',
341            MT::Util::encode_html( $asset->url ),
342            MT->translate('View image'),
343        );
344    }
345
346    return $param->{enclose} ? $asset->enclose($text) : $text;
347}
348
349# Return a HTML snippet of form options for inserting this asset
350# into a web page. Default behavior is no options.
351sub insert_options {
352    my $asset = shift;
353    my ($param) = @_;
354
355    my $app   = MT->instance;
356    my $perms = $app->{perms};
357    my $blog  = $asset->blog or return;
358
359    eval { require MT::Image; MT::Image->new or die; };
360    $param->{do_thumb} = $@ ? 0 : 1;
361
362    $param->{can_save_image_defaults} = $perms->can_save_image_defaults ? 1 : 0;
363
364    #$param->{constrain} = $blog->image_default_constrain ? 1 : 0;
365    $param->{popup}      = $blog->image_default_popup     ? 1 : 0;
366    $param->{wrap_text}  = $blog->image_default_wrap_text ? 1 : 0;
367    $param->{make_thumb} = $blog->image_default_thumb     ? 1 : 0;
368    $param->{ 'align_' . $_ } =
369      ( $blog->image_default_align || 'none' ) eq $_ ? 1 : 0
370      for qw(none left center right);
371    $param->{ 'unit_w' . $_ } =
372      ( $blog->image_default_wunits || 'pixels' ) eq $_ ? 1 : 0
373      for qw(percent pixels);
374    $param->{thumb_width} = $blog->image_default_width
375      || $asset->image_width
376      || 0;
377
378    return $app->build_page( 'dialog/asset_options_image.tmpl', $param );
379}
380
381sub on_upload {
382    my $asset = shift;
383    my ($param) = @_;
384
385    $asset->SUPER::on_upload(@_);
386
387    return unless $param->{new_entry};
388
389    my $app = MT->instance;
390    require MT::Util;
391
392    my $url    = $asset->url;
393    my $width  = $asset->image_width;
394    my $height = $asset->image_height;
395
396    my ( $base_url, $fname ) = $url =~ m|(.*)/([^/]*)|;
397    $url =
398        $base_url . '/'
399      . $fname;    # no need to re-encode filename; url is already encoded
400    my $blog = $asset->blog or return;
401    my $blog_id = $blog->id;
402
403    my ( $thumb, $thumb_width, $thumb_height );
404    $thumb_width = $param->{thumb_width};
405    $thumb       = $param->{thumb};
406    if ($thumb) {
407        if ( $thumb_width && ( $thumb_width !~ m/^\d+$/ ) ) {
408            undef $thumb_width;
409        }
410
411        # width > 1000 not really a thumbnail, so consider invalid
412        if ( $thumb_width > 1000 ) {
413            undef $thumb_width;
414        }
415    }
416    if ( $thumb && !$thumb_width ) {
417        undef $thumb;
418    }
419    if ( $param->{image_defaults} ) {
420        return $app->error(
421            $app->translate(
422                'Permission denied setting image defaults for blog #[_1]',
423                $blog_id
424            )
425        ) unless $app->{perms}->can_save_image_defaults;
426
427        # Save new defaults if requested.
428        $blog->image_default_wrap_text( $param->{wrap_text} ? 1 : 0 );
429        $blog->image_default_align( $param->{align} || MT::Blog::ALIGN() );
430        if ($thumb) {
431            $blog->image_default_thumb(1);
432            $blog->image_default_width($thumb_width);
433            $blog->image_default_wunits( $param->{thumb_width_type}
434                  || MT::Blog::UNITS() );
435        }
436        else {
437            $blog->image_default_thumb(0);
438            $blog->image_default_width(0);
439            $blog->image_default_wunits( MT::Blog::UNITS() );
440        }
441
442        #$blog->image_default_constrain($param->{constrain} ? 1 : 0);
443        $blog->image_default_popup( $param->{popup} ? 1 : 0 );
444        $blog->save or die $blog->errstr;
445    }
446
447    require MT::Util;
448    my $extra_path = undef;
449    my $extra_url = '';
450    if (defined $param->{middle_path} || defined $param->{extra_path}) {
451        my $middle_path = $param->{middle_path} || '';
452        my @split_path = split( '/', $middle_path );
453        $extra_path = '';
454
455        for my $middle (@split_path) {
456            $extra_path = File::Spec->catfile( $extra_path, $middle );
457        }
458        $extra_path = File::Spec->catfile( $extra_path, $param->{extra_path} ) if ($param->{extra_path});
459        $extra_url = MT::Util::caturl($middle_path, ($param->{extra_path} || ''));
460    }
461
462    # Thumbnail creation
463    if ( $thumb = $param->{thumb} ) {
464        require MT::Image;
465        my $image_type = scalar $param->{image_type};
466        my ( $w, $h ) = map $param->{$_}, qw( thumb_width thumb_height );
467        my ($pseudo_thumbnail_url) =
468          $asset->thumbnail_url( Height => $h, Width => $w, Path => $extra_path, Pseudo => 1 );
469        my $thumbnail = $asset->thumbnail_filename( Height => $h, Width => $w );
470        my $pseudo_thumbnail_path = File::Spec->catfile($asset->_make_cache_path($extra_path, 1), $thumbnail);
471        my ( $base, $path, $ext ) =
472          File::Basename::fileparse( $thumbnail, qr/[A-Za-z0-9]+$/ );
473        my $img_pkg         = MT::Asset->handler_for_file($thumbnail);
474        my $original;
475        my $asset_thumb = $img_pkg->load({
476            file_name => "$base$ext",
477            parent   => $asset->id,});
478        if (!$asset_thumb) {
479            $asset_thumb     = new $img_pkg;
480            $original        = $asset_thumb->clone;
481            $asset_thumb->blog_id($blog_id);
482            $asset_thumb->url($pseudo_thumbnail_url);
483            $asset_thumb->file_path($pseudo_thumbnail_path);
484            $asset_thumb->file_name("$base$ext");
485            $asset_thumb->file_ext($ext);
486            $asset_thumb->image_width($w);
487            $asset_thumb->image_height($h);
488            $asset_thumb->created_by( $app->user->id );
489            $asset_thumb->label($app->translate("Thumbnail image for [_1]", $asset->label || $asset->file_name));
490            $asset_thumb->parent( $asset->id );
491            $asset_thumb->save;
492        } else {
493            $original = $asset_thumb->clone;
494        }
495
496        # force these to calculate now, giving a full URL / file path
497        # for callbacks
498        $thumbnail = $asset_thumb->file_path;
499        my $thumbnail_url = $asset_thumb->url;
500        my $thumb_file_size = ( stat($thumbnail) )[7];
501
502        $app->run_callbacks( 'cms_post_save.asset', $app, $asset_thumb,
503            $original );
504
505        $param->{thumb_asset_id} = $asset_thumb->id;
506
507        $app->run_callbacks(
508            'cms_upload_file.' . $asset_thumb->class,
509            File  => $thumbnail,
510            file  => $thumbnail,
511            Url   => $thumbnail_url,
512            url   => $thumbnail_url,
513            Size  => $thumb_file_size,
514            size  => $thumb_file_size,
515            Asset => $asset_thumb,
516            asset => $asset_thumb,
517            Type  => 'thumbnail',
518            type  => 'thumbnail',
519            Blog  => $blog,
520            blog  => $blog
521        );
522
523        $app->run_callbacks(
524            'cms_upload_image',
525            File       => $thumbnail,
526            file       => $thumbnail,
527            Url        => $thumbnail_url,
528            url        => $thumbnail_url,
529            Asset      => $asset_thumb,
530            asset      => $asset_thumb,
531            Width      => $w,
532            width      => $w,
533            Height     => $h,
534            height     => $h,
535            ImageType  => $image_type,
536            image_type => $image_type,
537            Size       => $thumb_file_size,
538            size       => $thumb_file_size,
539            Type       => 'thumbnail',
540            type       => 'thumbnail',
541            Blog       => $blog,
542            blog       => $blog
543        );
544    }
545    if ( $param->{popup} ) {
546        require MT::Template;
547        if (
548            my $tmpl = MT::Template->load(
549                {
550                    blog_id => $blog_id,
551                    type    => 'popup_image'
552                }
553            )
554          )
555        {
556            ( my $rel_path = $param->{fname} ) =~ s!\.[^.]*$!!;
557            if ( $rel_path =~ m!\.\.|\0|\|! ) {
558                return $app->error(
559                    $app->translate( "Invalid basename '[_1]'", $rel_path ) );
560            }
561            my $ext = $blog->file_extension || '';
562            $ext = '.' . $ext if $ext ne '';
563            require MT::Template::Context;
564            my $ctx = MT::Template::Context->new;
565            $ctx->stash( 'blog',         $blog );
566            $ctx->stash( 'blog_id',      $blog->id );
567            $ctx->stash( 'asset',        $asset );
568            $ctx->stash( 'image_url',    $url );
569            $ctx->stash( 'image_width',  $width );
570            $ctx->stash( 'image_height', $height );
571            my $popup = $tmpl->build($ctx) or die $tmpl->errstr;
572            my $fmgr = $blog->file_mgr;
573            my $root_path =
574              $param->{site_path} ? $blog->site_path : $blog->archive_path;
575            my $pseudo_path = $param->{site_path} ? '%r' : '%a';
576            $root_path =
577              File::Spec->catfile( $root_path, ($extra_path || '') );
578            $pseudo_path = File::Spec->catfile( $pseudo_path, ($extra_path || '') );
579            my $abs_file_path =
580              File::Spec->catfile( $root_path, $rel_path . $ext );
581
582            ## If the popup filename already exists, we don't want to overwrite
583            ## it, because it could contain valuable data; so we'll just make
584            ## sure to generate the name uniquely.
585            my ( $i, $rel_path_ext ) = ( 0, $rel_path . $ext );
586            while ( $fmgr->exists($abs_file_path) ) {
587                $rel_path_ext = $rel_path . ++$i . $ext;
588                $abs_file_path =
589                  File::Spec->catfile( $root_path, $rel_path_ext );
590            }
591            $pseudo_path = File::Spec->catfile( $pseudo_path, $rel_path_ext );
592            my ( $vol, $dirs, $basename ) =
593              File::Spec->splitpath($rel_path_ext);
594            my $rel_url_ext =
595              File::Spec->catpath( $vol, $dirs,
596                MT::Util::encode_url($basename) );
597
598            ## Untaint. We have checked for security holes above, so we
599            ## should be safe.
600            ($abs_file_path) = $abs_file_path =~ /(.+)/s;
601            $fmgr->put_data( $popup, $abs_file_path, 'upload' )
602              or return $app->error(
603                $app->translate(
604                    "Error writing to '[_1]': [_2]", $abs_file_path,
605                    $fmgr->errstr
606                )
607              );
608            $url = $param->{site_path} ? '%r' : '%a';
609            $rel_url_ext =~ s!^/!!;
610            $url = MT::Util::caturl($url, $extra_url, $rel_url_ext);
611
612            my $html_pkg   = MT::Asset->handler_for_file($abs_file_path);
613            my $original;
614            my $asset_html = $html_pkg->load({
615                file_name => "$basename",
616                parent => $asset->id});
617            if (!$asset_html) {
618                $asset_html = new $html_pkg;
619                $original   = $asset_html->clone;
620                $asset_html->blog_id($blog_id);
621                $asset_html->url($url);
622                $asset_html->label($app->translate("Popup Page for [_1]", $asset->label || $asset->file_name));
623                $asset_html->file_path($pseudo_path);
624                $asset_html->file_name($basename);
625                $asset_html->file_ext( $blog->file_extension );
626                $asset_html->created_by( $app->user->id );
627                $asset_html->parent( $asset->id );
628                $asset_html->save;
629            } else {
630                $original   = $asset_html->clone;
631            }               
632
633            # Select back the real URL for callbacks
634            $url = $asset_html->url;
635
636            $param->{popup_asset_id} = $asset_html->id;
637
638            $app->run_callbacks( 'cms_post_save.asset', $app, $asset_html,
639                $original );
640
641            $app->run_callbacks(
642                'cms_upload_file.' . $asset_html->class,
643                File  => $abs_file_path,
644                file  => $abs_file_path,
645                Url   => $url,
646                url   => $url,
647                Asset => $asset_html,
648                asset => $asset_html,
649                Size  => length($popup),
650                size  => length($popup),
651                Type  => 'popup',
652                type  => 'popup',
653                Blog  => $blog,
654                blog  => $blog
655            );
656        }
657    }
658    1;
659}
660
661sub edit_template_param {
662    my $asset = shift;
663    my ($cb, $app, $param, $tmpl) = @_;
664
665    $param->{image_height} = $asset->image_height;
666    $param->{image_width}  = $asset->image_width;
667}
668
6691;
670
671__END__
672
673=head1 NAME
674
675MT::Asset::Image
676
677=head1 SYNOPSIS
678
679    use MT::Asset::Image;
680
681    # Example
682
683=head1 DESCRIPTION
684
685=head1 METHODS
686
687=head2 MT::Asset::Image->class
688
689Returns 'image', the identifier for this particular class of asset.
690
691=head2 MT::Asset::Image->class_label
692
693Returns the localized descriptive name for this type of asset.
694
695=head2 MT::Asset::Image->extensions
696
697Returns an arrayref of file extensions that are supported by this
698package.
699
700=head2 $asset->metadata
701
702Returns a hashref of metadata values for this asset.
703
704=head2 $asset->thumbnail_file(%param)
705
706Creates or retrieves the file path to a thumbnail image appropriate for
707the asset. If a thumbnail cannot be created, this routine will return
708undef.
709
710=head2 $asset->as_html
711
712Return the HTML I<IMG> element with the image asset attributes.
713
714=head1 AUTHOR & COPYRIGHT
715
716Please see the L<MT/"AUTHOR & COPYRIGHT"> for author, copyright, and
717license information.
718
719=cut
Note: See TracBrowser for help on using the browser.