TopMovable Typeカスタマイズアイテム > MTAssetThumbnailLinkタグとMTAssetThumbnailURLタグのsquareモディファイアの動作
2010年6月 8日

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

Posted at June 8,2010 2:55 AM
Category:[アイテム]
Tag:[, , ]

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;
}

以上です。

Posted by yujiro   このページの先頭に戻る
関連記事
この記事を読んだ人はこんな記事も読んでいます
人気エントリー
Hatena Hot Entries
Hatena Entries
トラックバックURL


コメントする
greeting

*必須

*必須(非表示)


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

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

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

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

Now loading...
Introduction
Entries of this Category
Recent Entries
Recent Comments
Recent Trackbacks
QRcode

現在停止中です
携帯電話からこのQRcodeを撮影することで携帯用URLを取得することができます

URI for cellular phones
ギターに入った猫
Styles
Font Size
Default
For defective color vision
Gray Scale
RGB Color
Search this site

このブログをメールで購読する by:FeedBurner

loading ...
Categories
Monthly Archives
BlogPeople
Syndicate this site
FeedBurner(RSS1.0/RSS2.0/Atom)
Counter
これまでのアクセス
クリエイティブ・コモンズ・ライセンス
Powered by
Movable Type 5.12