MTAssetThumbnailLinkタグとMTAssetThumbnailURLタグのsquareモディファイアの動作

MTAssetThumbnailLinkタグとMTAssetThumbnailURLタグのsquareモディファイアの動作

Posted at June 8,2010 2:55 AM
Tag:[MTAssetThumbnailLink, MTAssetThumbnailURL, square]

MTAssetThumbnailLinkタグとMTAssetThumbnailURLタグのsquareモディファイアの動作について調べてみました。調査の発端はriatwさんのツイートです。

riatwさんのツイート

1.squareモディファイアについて

squareモディファイアは、サムネイルの縦横比を1:1で出力するためのものです。辺のサイズは widthモディファイア, heightモディファイアまたはscale モディファイアで決定します。

冒頭に記したとおり、このモディファイアはMTAssetThumbnailLinkタグ、MTAssetThumbnailURLタグで利用できます。

2.サンプル

上がsquareモディファイアで整形した画像、下が元画像です。どの部分を切り取っているかは次項で解説します。

サンプル画像

3.squareモディファイアで切り取られる部分

squareモディファイアでは、まず次の2つの値を元に、1辺のサイズ(縮小したサイズではなく、縦横比を1:1にするためのサイズ)とx方向およびy方向の切り取り開始位置を決定します。

  • 元画像の幅(px):w
  • 元画像の高さ(px):h

wがhより大きい場合は次のように計算します。

  • 1辺のサイズ:h
  • x方向の切り取り開始位置:w-h/2(1辺が画像の中心になるようにxを設定)
  • y方向の切り取り開始位置:0

wがhより小さい場合(またはwとhが等しい場合)は次のように計算します。

  • 1辺のサイズ:w
  • x方向の切り取り開始位置:0
  • y方向の切り取り開始位置:h-w/2(1辺が画像の中心になるようにyを設定)

その後、正方形に切り取った画像を、widthモディファイア、heightモディファイア、scaleモディファイアで指定されたサイズに縮小します。拡大はできません。

4.処理の流れ

おまけで、トレース結果を残しておきます。

MTAssetThumbnailURLタグはMT::Template::Tags::Asset::_hdlr_asset_thumbnail_urlを実行します。

sub _hdlr_asset_thumbnail_url {
    my ($ctx, $args) = @_;
    my $a = $ctx->stash('asset')
        or return $ctx->_no_asset_error();
    return '' unless $a->has_thumbnail;
 
    my %arg;
    foreach (keys %$args) {
        $arg{$_} = $args->{$_};
    }
    $arg{Width} = $args->{width} if $args->{width};
    $arg{Height} = $args->{height} if $args->{height};
    $arg{Scale} = $args->{scale} if $args->{scale};
    $arg{Square} = $args->{square} if $args->{square}; 
    my ($url, $w, $h) = $a->thumbnail_url(%arg);
    return $url || '';
}

MTAssetThumbnailLinkタグはMT::Template::Tags::Asset::_hdlr_asset_thumbnail_linkを実行します。いずれも、後は青色で示す関数を順次実行します(多分)。

sub _hdlr_asset_thumbnail_link {
    my ($ctx, $args) = @_;
    my $a = $ctx->stash('asset')
        or return $ctx->_no_asset_error();
    my $class = ref($a);
    return '' unless UNIVERSAL::isa($a, 'MT::Asset::Image');
 
    # # Load MT::Image
    # require MT::Image;
    # my $img = new MT::Image(Filename => $a->file_path)
    #     or return $ctx->error(MT->translate(MT::Image->errstr));
 
    # Get dimensions
    my %arg;
    $arg{Width} = $args->{width} if $args->{width};
    $arg{Height} = $args->{height} if $args->{height};
    $arg{Scale} = $args->{scale} if $args->{scale};
    $arg{Square} = $args->{square} if $args->{square}; 
    my ($url, $w, $h) = $a->thumbnail_url(%arg);
    my $ret = sprintf qq(<a href="%s"), $a->url;
    if ($args->{new_window}) {
        $ret .= qq( target="_blank");
    }
    $ret .= sprintf qq(><img src="%s" width="%d" height="%d" alt="" /></a>), $url, $w, $h;
    $ret;
}

MT::Asset::thumbnail_urlを実行します。

sub thumbnail_url {
    my $asset = shift;
    my (%param) = @_;
 
    require File::Basename;
    if (my ($thumbnail_file, $w, $h) = $asset->thumbnail_file(@_)) {
        return $asset->stock_icon_url(@_) if !defined $thumbnail_file;
        my $file = File::Basename::basename($thumbnail_file);
        my $asset_file_path = $asset->SUPER::file_path();
        my $site_url;
        my $blog = $asset->blog;
        if (!$blog) {
            $site_url = $param{Pseudo} ? '%s' : MT->instance->support_directory_url;
        }
        elsif ( $asset_file_path =~ m/^%a/ ) {
            $site_url = $param{Pseudo} ? '%a' : $blog->archive_url;
        }
        else {
            $site_url = $param{Pseudo} ? '%r' : $blog->site_url;
        }
 
        if ($file && $site_url) {
            require MT::Util;
            my $path = $param{Path};
            if (!defined $path) {
                $path = MT::Util::caturl(MT->config('AssetCacheDir'), unpack('A4A2', $asset->created_on));
            } else {
                require File::Spec;
                my @path = File::Spec->splitdir($path);
                $path = '';
                for my $p (@path) {
                    $path = MT::Util::caturl($path, $p);
                }
            }
            $file = MT::Util::encode_url($file);
            $site_url = MT::Util::caturl($site_url, $path, $file);
            return ($site_url, $w, $h);
        }
    }
 
    # Use a stock icon
    return $asset->stock_icon_url(@_);
}

MT::Asset::Image::thumbnail_file(MT::Assetを継承)を実行します。

sub thumbnail_file {
    my $asset     = shift;
    my (%param)   = @_;
    my $fmgr;
    my $blog = $param{Blog} || $asset->blog;
 
    require MT::FileMgr;
    $fmgr ||= $blog ? $blog->file_mgr : MT::FileMgr->new('Local');
    return undef unless $fmgr;
 
    my $file_path = $asset->file_path;
    return undef unless $fmgr->exists( $file_path );
 
    require MT::Util;
    my $asset_cache_path = $asset->_make_cache_path($param{Path});
    my ( $i_h, $i_w ) = ( $asset->image_height, $asset->image_width );
    return undef unless $i_h && $i_w;
 
    # Pretend the image is already square, for calculation purposes.
    if ($param{Square}) {
        require MT::Image;
        my %square = MT::Image->inscribe_square(
            Width => $i_w, Height => $i_h );
        ($i_h, $i_w) = @square{qw( Size Size )};
        if ( $param{Width} && !$param{Height} ) {
            $param{Height} = $param{Width};
        }
        elsif ( !$param{Width} && $param{Height} ) {
            $param{Width} = $param{Height};
        }
    }
    if ( my $scale = $param{Scale} ) {
        $param{Width}  = int( ( $i_w * $scale ) / 100 );
        $param{Height} = int( ( $i_h * $scale ) / 100 );
    }
    if ( !exists $param{Width} && !exists $param{Height} ) {
        $param{Width}  = $i_w;
        $param{Height} = $i_h;
    }
 
    # find the longest dimension of the image:
    my ( $n_h, $n_w ) =
      _get_dimension( $i_h, $i_w, $param{Height}, $param{Width} );
 
    my $file = $asset->thumbnail_filename(%param) or return;
    my $thumbnail = File::Spec->catfile( $asset_cache_path, $file );
 
    # thumbnail file exists and is dated on or later than source image
    if ( $fmgr->exists($thumbnail)
      && ( $fmgr->file_mod_time($thumbnail) >= $fmgr->file_mod_time($file_path))) {
        return ( $thumbnail, $n_w, $n_h );
    }
    # stale or non-existent thumbnail. let's create one!
    return undef unless $fmgr->can_write($asset_cache_path);
 
    my $data;
    if ( ( $n_w == $i_w ) && ( $n_h == $i_h ) && !$param{Square}
      && !$param{Type} ) {
        $data = $fmgr->get_data( $file_path, 'upload' );
    }
    else {
        # create a thumbnail for this file
        require MT::Image;
        my $img = new MT::Image( Filename => $file_path )
          or return $asset->error( MT::Image->errstr );
 
        # Really make the image square, so our scale calculation works out.
        if ($param{Square}) {
            ($data) = $img->make_square()
              or return $asset->error(
                MT->translate( "Error cropping image: [_1]", $img->errstr ) );
        }
 
        ($data) = $img->scale( Height => $n_h, Width => $n_w )
          or return $asset->error(
            MT->translate( "Error scaling image: [_1]", $img->errstr ) );
 
        if (my $type = $param{Type}) {
            ($data) = $img->convert( Type => $type )
              or return $asset->error(
                MT->translate( "Error converting image: [_1]", $img->errstr ) );
        }
    }
    $fmgr->put_data( $data, $thumbnail, 'upload' )
      or return $asset->error(
        MT->translate( "Error creating thumbnail file: [_1]", $fmgr->errstr ) );
    return ( $thumbnail, $n_w, $n_h );
}

MT::Image::inscribe_squareを実行します。

sub inscribe_square {
    my $class = shift;
    my %params = @_;
    my ($w, $h) = @params{qw( Width Height )};
 
    my ($dim, $x, $y);
 
    if ($w > $h) {
        $dim = $h;
        $x = int(($w - $dim) / 2);
        $y = 0;
    }
    else {
        $dim = $w;
        $x = 0;
        $y = int(($h - $dim) / 2);
    }
 
    return ( Size => $dim, X => $x, Y => $y ); 
}

MT::Image::make_squareを実行します。

sub make_square {
    my $image = shift;
    my %square = $image->inscribe_square(
        Width  => $image->{width},
        Height => $image->{height},
    );
    $image->crop(%square);
}

このあとは利用している画像ライブラリによって、実行関数が異なります。

ImageMagickを利用している場合、MT::Image::ImageMagick::cropを実行します。

sub crop {
    my $image = shift;
    my %param = @_;
    my ($size, $x, $y) = @param{qw( Size X Y )};
    my $magick = $image->{magick};
 
    my $err = $magick->Crop(width => $size, height => $size, x => $x, y => $y);
    return $image->error(MT->translate(
        "Cropping a [_1]x[_1] square at [_2],[_3] failed: [_4]", $size, $x,
        $y, $err)) if $err;
 
    ## Remove page offsets from the original image, per this thread:
    ## http://studio.imagemagick.org/pipermail/magick-users/2003-September/010803.html
    $magick->Set( page => '+0+0' );
 
    ($image->{width}, $image->{height}) = ($size, $size);
    wantarray ? ($magick->ImageToBlob, $size, $size) : $magick->ImageToBlob;
}

NetPBMを利用している場合、MT::Image::NetPBM::cropを実行します。

sub crop {
    my $image = shift;
    my %param = @_;
    my ($size, $x, $y) = @param{qw( Size X Y )};
 
    my($w, $h) = $image->get_dimensions(@_);
    my $type = $image->{type};
    my($out, $err);
    my $pbm = $image->_find_pbm or return;
    my @in = ("$pbm${type}topnm", ($image->{data} ? () : $image->{file} ? $image->{file} : ()));
 
    my @crop = ("${pbm}pnmcut", $x, $y, $size, $size);
    my @out;
    for my $try (qw( ppm pnm )) {
        my $prog = "${pbm}${try}to$type";
        @out = ($prog), last if -x $prog;
    }
    my(@quant);
    if ($type eq 'gif') {
        push @quant, ([ "${pbm}ppmquant", 256 ], '|');
    }
    IPC::Run::run(\@in, '<', ($image->{data} ? \$image->{data} : \undef), '|',
        \@crop, '|',
        @quant,
        \@out, \$out, \$err)
        or return $image->error(MT->translate(
            "Cropping to [_1]x[_1] failed: [_2]", $size, $err));
    ($image->{width}, $image->{height}, $image->{data}) = ($w, $h, $out);
    wantarray ? ($out, $w, $h) : $out;
}

GDを利用している場合、MT::Image::GD::cropを実行します。

sub crop {
    my $image = shift;
    my %param = @_;
    my ($size, $x, $y) = @param{qw( Size X Y )};
    my $src = $image->{gd};
    my $gd = GD::Image->new($size, $size, 1);  # True color image (24 bit)
    $gd->copy($src, 0, 0, $x, $y, $size, $size);
    ($image->{gd}, $image->{width}, $image->{height}) = ($gd, $size, $size);
    wantarray ? ($image->blob, $size, $size) : $image->blob;
}

以上です。

関連記事
zenback
人気エントリー
トラックバックURL


コメントする
greeting

*必須

*必須(非表示)


ご質問のコメントの回答については、内容あるいは多忙の場合、1週間以上かかる場合があります。また、すべてのご質問にはお答えできない可能性があります。予めご了承ください。

太字イタリックアンダーラインハイパーリンク引用
[サインインしない場合はここにCAPTCHAを表示します]

コメント投稿後にScript Errorや500エラーが表示された場合は、すぐに再送信せず、ブラウザの「戻る」ボタンで一旦エントリーのページに戻り(プレビュー画面で投稿した場合は、投稿内容をマウスコピーしてからエントリーのページに戻り)、ブラウザをリロードして投稿コメントが反映されていることを確認してください。

コメント欄に(X)HTMLタグやMTタグを記述される場合、「<」は「&lt;」、「>」は「&gt;」と入力してください。例えば「<$MTBlogURL$>」は「&lt;$MTBlogURL$&gt;」となります(全て半角文字)