Technote

by sizuhiko

CakePHP1.2.1.8004 で AuthコンポーネントとScaffoldを併用する際の注意事項

<!– more –>先日セキュリティアップデートとして、1.2.0から1.2.1に更新されたのは、皆さんご存知のことと思います。

で、昨日やっとこさ1.2.1を使い始めたら、なんとAuthでログインしてなくてもscaffoldのアクションが動いちゃうじゃないですか!!。これはまずい。。

なお肝心のセキュリティアップデートは、CakePHP 1.2.1.8004へアップデート推奨からリンクされている「Changeset 7979」の差分のようなので、今回の回避策を使っても、問題なさそうだと判断しています。まぁscaffold使ってなければ関係ないのですけどね。

で、問題の箇所はAuthComponent::startup()で

$isErrorOrTests = (
    strtolower($controller->name) == 'cakeerror' ||
    (strtolower($controller->name) == 'tests' && Configure::read() > 0) ||
    !in_array($controller->params['action'], $controller->methods)
);
if ($isErrorOrTests) {
    return true;
}

という箇所があるのですが、

!in_array($controller->params['action'], $controller->methods)

というコードの「$controller->methods」にはscaffoldのアクションが含まれていないんですよね。なんでコントローラにそんなメソッドないよエラーページに遷移しようとして、ログイン画面には遷移しないんですが、ほんとのところはscaffoldに遷移して、一覧・追加・変更・削除ができちゃうというトホホな感じです。

なのでscaffoldをAuthのログイン傘下で使っている人は、Authから$controller->methodsに含まれているかどうかチェックは外してしまいましょう。

これから本家のTracにチケットを書くとします。。。

その後、あっさり本家から「Bakeしてね」とやんわり修正を断られてしまったので、Auth使う人はscaffold使わないか、$controller->methodsにbeforeFilterでscaffoldAction追加するとか(たぶんこれは良くないけど)、Authを継承して変えちゃう(startupの前でscaffoldActionをarray_mergeして、戻ってきたら元の値に戻せば・・・まぁ、どうか?というのもありますけど)とか、まぁいろいろ策はあるかな?と思います。

PS: この、$controller->methodsですが、どこで使ってんの?と調べてみたら、dispatcherとauthだけでした。dispatcherは当然として、なんでauthが!??という不思議な感は否めないのですが、こればっかりは仕方ないですね。今後使うところ増えるかもしれないし。

Flex2でCoverFlowクローンを作る (9) ~Amazonと連携する

<!– more –>現在、オブジェクト倶楽部のメールマガジンで連載している「Flexで体験するリッチクライアント」を補足するTechnoteです。

第九回の今回は最終回となります。AmazonWebserviceと連携してタイトルや著者名を表示する手法を整理します。

■今回サンプルの完成イメージ

画像をクリックしながら、左右に動かしたり、勢い良く左右に動かしたりすると、CoverFlowのように動作します。

前回は画像を重ね合わせることで、写真部分の見栄えは「ほぼ」CoverFlowに近くなりました。今回はAmazonWebserviceと連携して、asinIdのタイトルや著者を表示します。

■ファイルの準備

プロジェクトファイルをダウンロードして、任意のディレクトリで解凍します。

  • reflector.as3proj
  • reflector.mxml
  • ImageReflector.as
  • TransformUtil.as
  • CoverFlowLayout.as
  • RepeaterHBox.as
  • AmazonWebservice.as

新しいファイルAmazonWebservice.asが追加になっています。追加になったファイルは後ほど詳しく解説します。

reflector.as3projをダブルクリックすると、FlashDevelopが実行されます。

■AmazonWebサービスに登録する

AmazonWebサービスを使うと、今回やろうとしているような、asinIdの商品情報を取得することができます。

精しくは、こちらのドキュメントが日本語であるので、一読すると良いでしょう。

http://www.amazon.co.jp/gp/feature.html?docId=451209

まだアカウントをもっていない方は、

http://aws.amazon.com/

から登録しましょう。

登録すると、登録ID(Subscription ID)が取得できます。

そのIDをAmazonWebservice.asのAWSAccessKeyId変数に入れてください。

public class AmazonWebservice {
    private const AWSAccessKeyId:String = "***** あなたの登録ID ******";
    private const AmazonRegionCode:String = "09"; // 日本は09

■Amazonから商品情報を取得する

Amazonとのデータのやりとりは、FlashからGETパラメータで必要な情報を送り込み、戻りとしてXMLを受け取るシンプルなRESTインターフェースです。

AmazonへリクエストするURLをgetAmazonRestUrl関数で作っています。

private function getAmazonRestUrl(asin:String):String 
{
    var url:String = "http://webservices.amazon.co.jp/onca/xml?"
               + "Service=AWSECommerceService&"
               + "AWSAccessKeyId="+AWSAccessKeyId+"&"
               + "Operation=ItemLookup&"
               + "IdType=ASIN&ItemId=" + asin + "&"
               + "ResponseGroup=Request,ItemIds,ItemAttributes,Tracks,EditorialReview";
    return url; 
}

パラメータごとの詳細な解説は、上記ドキュメントを参照して欲しいのですが、ここでは IdTypeをASINとして、ItemIdで取得したいasinIdを指定します。

レスポンスはすべて取得することもできますが、ここではResponseGroupに書いた内容に絞り込んで 取得します。

■Amazonからの戻り値を取得する

URLを呼び出して、結果を取得するのは、AmazonからCoverFlow用の写真を取得するインターフェースと同じです。

_amazonLoader = new URLLoader();
_amazonLoader.addEventListener(Event.COMPLETE, completeAmazonRequest);
_amazonLoader.load(new URLRequest(getAmazonRestUrl(asin)));

のようにして、URLから取得できたらcompleteAmazonRequest関数が呼ばれます。

受信部分のコードは

private function completeAmazonRequest(event:Event):void
{
    namespace ns = 'http://webservices.amazon.com/AWSECommerceService/2005-10-05';
    use namespace ns;
    _xml = new XML(_amazonLoader.data);
}

と、たったこれだけです。Amazon Webサービスで指定されたネーミングスペースを指定して、XMLオブジェクトを作ります。

戻ってきたデータはXML形式なので、これでActionScriptから参照しやすい形式となります。

■XML内のデータを解析する

取得したXMLを解析するのはとても簡単です。

たとえばWeb2.0ビギナーズバイブルのasinIdを指定して戻ってくるXMLは以下のとおりです。

<?xml version="1.0" ?>
<ItemLookupResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2005-10-05">
  <OperationRequest>
    <HTTPHeaders>
      <Header Name="UserAgent" Value="Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)">
      </Header>
    </HTTPHeaders>
    <RequestId>53c87848-02e3-4317-8136-b49b1d2a0108</RequestId>
    <Arguments>
      <Argument Name="Operation" Value="ItemLookup"></Argument>
      <Argument Name="Service" Value="AWSECommerceService"></Argument>
      <Argument Name="ItemId" Value="4839923221"></Argument>
      <Argument Name="IdType" Value="ASIN"></Argument>
      <Argument Name="AWSAccessKeyId" Value="********************"></Argument>
      <Argument Name="ResponseGroup" Value="Request,ItemIds,ItemAttributes,Tracks,EditorialReview"></Argument>
    </Arguments>
    <RequestProcessingTime>0.1317150000000000</RequestProcessingTime>
  </OperationRequest>
  <Items>
    <Request>
      <IsValid>True</IsValid>
      <ItemLookupRequest>
        <Condition>New</Condition>
        <DeliveryMethod>Ship</DeliveryMethod>
        <IdType>ASIN</IdType>
        <MerchantId>Amazon</MerchantId>
        <OfferPage>1</OfferPage>
        <ItemId>4839923221</ItemId>
        <ResponseGroup>Request</ResponseGroup>
        <ResponseGroup>ItemIds</ResponseGroup>
        <ResponseGroup>ItemAttributes</ResponseGroup>
        <ResponseGroup>Tracks</ResponseGroup>
        <ResponseGroup>EditorialReview</ResponseGroup>
        <ReviewPage>1</ReviewPage>
      </ItemLookupRequest>
    </Request>
    <Item>
      <ASIN>4839923221</ASIN>
      <DetailPageURL>http://www.amazon.co.jp/Web2-0%E3%83%93%E3%82%AE%E3%83%8A%E3%83%BC%E3%82%BA%E3%83%90%E3%82%A4%E3%83%96%E3%83%AB-%E4%BC%8A%E8%97%A4-%E6%B5%A9%E4%B8%80/dp/4839923221%3FSubscriptionId%3D*******************%26tag%3Dws%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D4839923221</DetailPageURL>
      <ItemAttributes>
        <Author>伊藤 浩一</Author>
        <Author>大津 真</Author>
        <Author>岸田 健一郎</Author>
        <Author>まえだ ひさこ</Author>
        <Author>安井 力</Author>
        <Binding>単行本</Binding>
        <EAN>9784839923228</EAN>
        <ISBN>4839923221</ISBN>
        <Label>毎日コミュニケーションズ</Label>
        <ListPrice>
          <Amount>3990</Amount>
          <CurrencyCode>JPY</CurrencyCode>
          <FormattedPrice>¥ 3,990</FormattedPrice>
        </ListPrice>
        <Manufacturer>毎日コミュニケーションズ</Manufacturer>
        <NumberOfPages>927</NumberOfPages>
        <PackageDimensions>
          <Height Units="hundredths-inches">205</Height>
          <Length Units="hundredths-inches">921</Length>
          <Weight Units="hundredths-pounds">340</Weight>
          <Width Units="hundredths-inches">724</Width>
        </PackageDimensions>
        <ProductGroup>Book</ProductGroup>
        <PublicationDate>2007-04</PublicationDate>
        <Publisher>毎日コミュニケーションズ</Publisher>
        <Studio>毎日コミュニケーションズ</Studio>
        <Title>Web2.0ビギナーズバイブル</Title>
      </ItemAttributes>
    </Item>
  </Items>
</ItemLookupResponse>

で、本のタイトルは、

ItemLookupResponse
 +-- Item
      +-- ItemAttributes
           +-- Title

という階層にあります。これを取得するにはXMLオブジェクトから

_xml.Items.Item.ItemAttributes.child('Title');

と書くだけで取得できます。_xmlオブジェクトからXMLの階層をたどるようにタグ名を.(ドット)で区切っていくだけです。簡単ですね。

■ImageReflectorから使う

AmazonWebserviceの実装ができたら、ImageReflectorから呼び出します。

initLoader()の末尾に、

_amazonInfo.callAmazon(asinId);

を追加し、asinIdの商品情報を取得します。情報が取得できたか判断して、描画終了という判断にするために、isCompleted関数を

public function isCompleted():Boolean {
    if (!_invalidatedReflection) return false
    if (_amazonInfo == null) return false
    if (!_amazonInfo.isLoaded()) return false;

    return true;
}

のように修正して、Amazon Webサービスからのロードが終了していることも判断材料とします。

■画面に描画する

最後に画面に描画領域を作成します。

mxmlにラベルとして、タイトルと著者名を表示する欄を作ります。

<Label id="title" text="タイトル" color="0xFFFFFF" paddingTop="-180" paddingLeft="20" fontWeight="bold" fontSize="11" />
<Label id="subtitle" text="著者" color="0xFFFFFF" paddingLeft="20" fontWeight="bold" fontSize="11" />

この表示は、特定の本を選択したときに変更したいので、選択が変更されたときのイベントselectedIndex関数で、値を代入します。

// 選択された項目のタイトルを表示
title.text = imageReflector[value].titleText;
subtitle.text = imageReflector[value].authorText;

お約束ですが、ImageReflector側では、titleTextとauthorTextはBindableにしておく必要があります。

■クロスドメインの注意事項

amazonの画像サーバのcrossdomain.xmlは画像サーバ内からだけしか参照できないので、ここで作成したSWFファイルをサーバにアップロードしてもうまく表示できません。

本記事上の完成イメージは、PHPサイトでプロキシしています。

まずPHPのコードは過去記事を参考にしてください。

■さいごに

これまで読んでくださった皆様、ありがとうございました。こんな機能もFlex版CoverFlowに欲しいぞ!という場合は、ぜひコメントください。 よろしくお願いします。

CakePHPカンファレンス東京 2008

<!– more –>CakePHPカンファレンス東京が10/25(土)に行われました。:)

私も発表者として、「Agileな開発現場での実践例」というお題で、テスト駆動開発の実例を紹介してきました。;)

駆動?!、まぁ駆動はしてなかったのですが。。。。。。(詳しくは資料を参照ください):ko:

とにかく、あっという間の申し込み締め切りという盛り上がりが、当日も続いており非常に楽しい&有意義な時間を過ごすことができました。

家に帰ったらBlogを書くまでがイベントです、というお約束も、このところこの会に向け資料の準備、ネタの仕込みなど気が張っていたので、帰宅後は死んだように寝てしまいました。ちょっと出遅れたので、当日の盛況ぶりは、イベント申込ページにトラックバックされている他の方のBlogで・・・・

CakePHP1.2本の話も少ししたかったのに、結局最後に軽くネタをふって終わってしまいました。

そうそう、Slideshareでフォントを思いどおりにしたい場合は、自分の端末でPDFにすればよかったのですね。今回はキレイにヒラギノなどが閲覧できました。

Flex2でCoverFlowクローンを作る (8) ~画像を重ね合わせる

<!– more –>現在、オブジェクト倶楽部のメールマガジンで連載している「Flexで体験するリッチクライアント」を補足するTechnoteです。

第八回は、画像を重ね合わせる手法を整理します。

■今回サンプルの完成イメージ

画像をクリックしながら、左右に動かしたり、勢い良く左右に動かしたりすると、CoverFlowのように加速効果付きでスクロールします。

前回は画像を掴んで勢い良く放したり、掴みながらスクロールさせるといった動作を、マウスイベントを取得して実装してみました。今回は、画像を重ね合わせていきます。

■ファイルの準備

プロジェクトファイルをダウンロードして、任意のディレクトリで解凍します。

  • reflector.as3proj
  • reflector.mxml
  • ImageReflector.as
  • TransformUtil.as
  • CoverFlowLayout.as
  • RepeaterHBox.as

新しいファイルCoverFlowLayoutとRepeaterBoxの2つが追加になっています。追加になったファイルは後ほど詳しく解説します。

reflector.as3projをダブルクリックすると、FlashDevelopが実行されます。

■選択している画像以外の表示幅を狭くする

画像を重ね合わせるときに、まずはじめに考える方法は、標記のとおりだと思います。そこで、今回はoverrlapWidthをいう変数を使って、重ね合わせの幅を定義してみました。実際に適用する箇所は、

  • measure() – 実際に描画する縦横サイズを返す処理
  • transformImage() – 画像を斜めに描画する処理
  • animateTransform() – アニメーション描画を開始する処理

です。画像は左右の両側から重なるイメージなので、重なり幅が20とすると、実際の画像幅は倍の40必要となりますので、画像の幅として*2をしています。

measuredWidth = overrlapWidth*2;

このように修正してプレビューしてみるとどうなるでしょうか?

残念ながら赤丸で囲ったように、選択画像より右側の並びが変になってしまいます。

■要素のZ順序を入れ替える

Window操作のプログラミングをした経験のある人なら、この状況を標記のとおり解決できる、と推測するでしょう。そしてもちろんFlexでも同様の機能を持っています。

そこで以下のようにmxmlのonCreationComplete()を変更してみました。

private function onCreationComplete(event:FlexEvent):void {
    if(event.currentTarget.repeaterIndex >= (dp.length - 1)){
        // 最初の重なりは一番右の項目が下になるよう(逆順)にする。
        for(var i:int = 0; i < (dp.length/2); i++) {
            h_frame.swapChildren(imageReflector[i], imageReflector[dp.length-i-1]);
        }
        h_frame.invalidateProperties();
        // 重なりスワップ終了
        initScroll();
    }
}

このように修正してプレビューしてみるとどうなるでしょうか?

うわぁーーーーーー、なぜか表示する順番が変になってしまいました。なぜこうなるのでしょうか?

■Flexはオープンソースだから、ソースを読めばいいじゃないか

Flexの各クラスはcoreでない限りソースが提供されています。ともかくh_frameのクラスであるHBOXの描画について調べてみましょう。

frameworks/source/mx/containers/HBox.asにはほとんど処理はないので、親クラスであるBoxのコードを見ます。

override protected function updateDisplayList(unscaledWidth:Number,
                                                  unscaledHeight:Number):void
{
    super.updateDisplayList(unscaledWidth, unscaledHeight);

    layoutObject.updateDisplayList(unscaledWidth, unscaledHeight);
}

描画のコードはこのようになっていて、実際の描画はどうもlayoutObjectがやっているようです。そこでmx.containers.utilityClasses.BoxLayoutのupdateDisplayListを見てみると、

for (i = 0; i < n; i++)
{
    obj = IUIComponent(target.getChildAt(i));
    top = (h - obj.height) * verticalAlign + paddingTop;
    obj.move(Math.floor(left), Math.floor(top));
    if (obj.includeInLayout)
        left += obj.width + gap;
}

のように、getChildAtを使って、子要素の順番(=Z順)に表示位置の計算をしているのがわかります。swapChildrenは子要素の順番を変更することで描画の重なりを変更しようとしているので、2番目のプレビューのようになるのはやもえません。ではどのようにHBoxに定義された順番(レイアウト計算順序)とZ順(表示順序)を別々に管理し、描画したら良いのでしょうか?

■Repeaterを使うHBox、RepeaterHBoxを作る

つまりこういうことです。HBox内に要素を並べるとき、通常は要素の並びを持っている訳ではありませんが、HBox内でRepeaterを使っているなら、要素の順番はRepeater自信が保持しています。

そこでRepeaterHBoxを作ることにします。今回は簡単に要素の並びを取得できるように、mxml上のパラメータrepeaterArray=“{imageReflector}"でクラス変数に配列を渡すことにしました。

<local:RepeaterHBox id="h_frame" height="400" width="500" backgroundColor="0x000000" paddingLeft="200" paddingRight="200" verticalScrollPolicy="off" scroll="onScroll(event)" mouseUp="onMouseUp(event)" mouseDown="onMouseDown(event)" mouseMove="onMouseMove(event)" mouseOut="onMouseOut(event)"  initialize="onInitialize()" repeaterArray="{imageReflector}" >
    <Repeater id="rp" dataProvider="{dp}">
        <local:ImageReflector id="imageReflector" asinId="{rp.currentItem}" creationComplete="onCreationComplete(event)" click="changeSelect(event)" />
    </Repeater>
</local:RepeaterHBox>

RepeaterHBoxの特徴は、HBoxを継承しHBoxである機能はもちろん、前節で問題となっていた、BoxLayoutを変更することです。RepeaterHBoxでは独自のレイアウト処理CoverFlowLayoutを使っています。CoverFlowLayoutはBoxLayoutの一部を変更したいだけなので、BoxLayoutのソースコードを丸ごとコピーしてきて、前記のコードを下記 のように修正します。

for (i = 0; i < n; i++)
{
    obj = IUIComponent(target.repeaterArray[i]);
    top = (h - obj.height) * verticalAlign + paddingTop;
    obj.move(Math.floor(left), Math.floor(top));
    if (obj.includeInLayout)
        left += obj.width + gap;
}

表示順を変更しても、リピーターの定義(target.repeaterArray)順に要素の表示位置(レイアウト)を計算すれば問題は解決します。

■mx_internal::で内部要素にアクセスする

RepeaterHBoxクラスでは見慣れない記述mx_internalがあると思います。

mx_internal::layoutObject = new CoverFlowLayout();

実はこのmx_internalは、Flexの内部クラスに直接アクセスするために、重要な記述です。今回のように標準コンポーネントをカスタマイズする場合、通常のメソッドオーバーライドだけでは、どうしてもうまくカスタマイズできない場合があります。例えば、coreの中の変数だったり、以下のように内部名前空間に変数が定義されている場合です。

/**
 *  @private
 */
mx_internal var layoutObject:BoxLayout = new BoxLayout();

これはBoxクラスで、レイアウトオブジェクトを定義している例ですが、要はprivate変数として扱っているものなのです。ただこれだと独自レイアウトによる描画ができないので、上記のようにmx_internal::layoutObjectと書くことで、内部スコープの変数値を上書きできるのです。

■クロスドメインの注意事項

amazonの画像サーバのcrossdomain.xmlは画像サーバ内からだけしか参照できないので、ここで作成したSWFファイルをサーバにアップロードしてもうまく表示できません。

本記事上の完成イメージは、PHPサイトでプロキシしています。

まずPHPのコードは過去記事を参考にしてください。

■次回予告

次回で最終回、最後にamazonからタイトル、著者を取り出して表示する機能を実装してみます。

Flex2でCoverFlowクローンを作る (7) ~マウスイベントを取得する

<!– more –>現在、オブジェクト倶楽部のメールマガジンで連載している「Flexで体験するリッチクライアント」を補足するTechnoteです。

第七回は、マウスイベントを取得する手順を整理します。

■今回サンプルの完成イメージ

画像をクリックしながら、左右に動かしたり、勢い良く左右に動かしたりすると、CoverFlowのように加速効果付きでスクロールします。

前回はスクロール処理とアニメーション効果を付けてみました。今回は画像を掴んで勢い良く放したり、掴みながらスクロールさせるといった動作を、マウスイベントを取得して変更していきます。今回は、mxmlファイルのみ修正していきます。

■ファイルの準備

プロジェクトファイルをダウンロードして、任意のディレクトリで解凍します。

  • reflector.as3proj
  • reflector.mxml
  • ImageReflector.as
  • TransformUtil.as

前回から修正したのは、reflector.mxmlだけです。

reflector.as3projをダブルクリックすると、FlashDevelopが実行されます。

■マウスの基本動作はクリックと移動

Flexではマウスを操作した結果は、マウスイベントをフックすると知ることができます。例えばmxmlで以下のように書いてみます。

<HBox id="h_frame" height="400" width="500" backgroundColor="0x000000" 
      paddingLeft="200" paddingRight="200" verticalScrollPolicy="off" scroll="onScroll(event)" 
      mouseUp="onMouseUp(event)" mouseDown="onMouseDown(event)" mouseMove="onMouseMove(event)"  
      mouseOut="onMouseOut(event)" initialize="onInitialize()" >

このコードが今回修正したCoverFlow表示領域のボックス記述です。それぞれ

  1. mouseUp : マウスのボタンが押された後、放されたときに発生する

  2. mouseDown : マウスとボタンが押されたときに発生する

  3. mouseMove : マウスポインターが動いたときに発生する

  4. mouseOut : マウスポインターがHBOXから出たときに発生する

のような意味を持っています。

■マウスがどこで操作されたのか調べる

マウスイベントは表示領域で取得しますが、対象のHBOXには様々な子コントロールが含まれています。本サンプルで使っているのは

  • カバー写真
  • 横スクロールバー

です。マウスイベント(MouseEvent)がどこで発生したのかを調べるには、MouseEvent#targetで判断できますが、mxmlで定義されているクラス名を判断材 料にする場合は、MouseEvent#target.parentを使います。これは、target自体は表示リストノードであるため、

if(event.target.parent is HScrollBar)

のように記述する必要があります。ちょっと面倒ですね。。

■マウスが領域外に出たときを考慮する

マウス操作を実装していて、忘れがちになるのが、この処理です。全画面表示で操作しているようなケースでは無縁ですが、実際はドラッグしながらウィンドウの外でマウスを放した、というケースは珍しくありません。このときイベントを拾っておかないと、再度マウスが領域に戻ったとき、マウスが押されたままと間違って判断してしまうことがあります。マウスが領域から出たら、一旦マウスを放した、という仮定が必要となります。マウスが領域外に出たかどうかはステージオブジェクトのマウスイベントをフックします。これは前述のMouseOutでHBOXから出たのか判断していますが、勢い良くマウスを動かして、一気にFlashPlayerから出てしまうと、MouseOutが呼ばれないことがあります。

そのため、FlashPlayerからマウスが出た場合も想定して、

h_frame.stage.addEventListener(Event.MOUSE_LEAVE, onMouseOut);

のように、記述します。

似たイベントで、MouseEvent.ROLL_OUTがありますが、これはHTMLの:hoverのような実装をする際に使うイベントです。つまり対象のコントロールの上に来た・離れたというのを制御します。

■クロスドメインの注意事項

amazonの画像サーバのcrossdomain.xmlは画像サーバ内からだけしか参照できないので、ここで作成したSWFファイルをサーバにアップロードしてもうまく表示できません。

本記事上の完成イメージは、PHPサイトでプロキシしています。

まずPHPのコードは過去記事を参考にしてください。

■次回予告

画像間の隙間をなくして、ホンモノのように画像が重なって表示されている効果を実装してみます。