2013年3月18日月曜日

Flash Player 10レベルで1辺2880ピクセル以上の画面キャプチャーを撮るには?

Flexで画面のキャプチャーを撮るにはこうするだろう。大抵はこれで上手くいく。

var bmpSrc:BitmapData = ImageSnapshot.captureBitmapData(this, null, null, null, null, true);

しかし、1辺が2880ピクセルを超えるキャプチャーを取ろうとすると、なぜか2880ピクセルに縮小されてしまう。

原因は間違いなく、ImageSnapshotだ。ソースをみると、こんなコードがあるんだ・・・

・・・
public static const MAX_BITMAP_DIMENSION:int = 2880;
・・・
// Cap width to BitmapData max of 2880 pixels
if (scaledWidth > MAX_BITMAP_DIMENSION)
{
    reductionScale = scaledWidth / MAX_BITMAP_DIMENSION;
    scaledWidth = MAX_BITMAP_DIMENSION;
    scaledHeight = scaledHeight / reductionScale;

    matrix.a = scaledWidth / width;
    matrix.d = scaledHeight / height;
}
・・・

それなら、ImageSnapshotをextendsしてオーバーライドするか・・・と思ってこうしてみた。

public override static const MAX_BITMAP_DIMENSION:int = 8191;

しかし、コンパイルが通らない。そういえば、「AS3では、staticメソッドを継承することもオーバーライドすることも出来ません。」だった_| ̄|○

AS3では、staticメソッドを継承することもオーバーライドすることも出来ません。 - hirossy javaとFlex2と。

こりゃダメかな・・・と思ったが、extendsするのではなく、このように新しいクラスを作って必要なメソッドを移植して定数の部分に適当な値を与えてやると、とりあえず1辺が2880を超える画像をキャプチャーできるようにはなった。

////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2007 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package spl.components {


  import flash.display.BitmapData;
  import flash.display.DisplayObject;
  import flash.display.IBitmapDrawable;
  import flash.display.Stage;
  import flash.geom.ColorTransform;
  import flash.geom.Matrix;
  import flash.geom.Rectangle;
  
  import mx.core.IFlexDisplayObject;
  import mx.core.IUIComponent;
  import mx.core.UIComponent;
  
  
  public class ImageSnapshot2 {
    
    
    /**
     *  The maximum width and height of a Bitmap.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    //public static const MAX_BITMAP_DIMENSION:int = 2880;
    public static const MAX_BITMAP_DIMENSION:int = 8191; //FP10の最大表示サイズに合わせてみる
    
    
    /**
     *  A utility method to grab a raw snapshot of a UI component as BitmapData.
     * 
     *  @param source An object that implements the
     *    <code>flash.display.IBitmapDrawable</code> interface.
     *
     *  @param matrix A Matrix object used to scale, rotate, or translate
     *  the coordinates of the captured bitmap.
     *  If you do not want to apply a matrix transformation to the image,
     *  set this parameter to an identity matrix,
     *  created with the default new Matrix() constructor, or pass a null value.
     *
     *  @param colorTransform A ColorTransform 
     *  object that you use to adjust the color values of the bitmap. If no object 
     *  is supplied, the bitmap image's colors are not transformed. If you must pass 
     *  this parameter but you do not want to transform the image, set this parameter 
     *  to a ColorTransform object created with the default new ColorTransform() constructor.
     *
     *  @param blendMode A string value, from the flash.display.BlendMode 
     *  class, specifying the blend mode to be applied to the resulting bitmap.
     *
     *  @param clipRect A Rectangle object that defines the 
     *  area of the source object to draw. If you do not supply this value, no clipping 
     *  occurs and the entire source object is drawn.
     *
     *  @param smoothing A Boolean value that determines whether a 
     *  BitmapData object is smoothed when scaled.
     *
     *  @return A BitmapData object representing the captured snapshot.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static function captureBitmapData(
      source:IBitmapDrawable, matrix:Matrix = null,
      colorTransform:ColorTransform = null,
      blendMode:String = null,
      clipRect:Rectangle = null,
      smoothing:Boolean = false):BitmapData
    {
      var data:BitmapData;
      var width:int;
      var height:int;
      
      var normalState:Array;
      if (source is IUIComponent)
        normalState = prepareToPrintObject(IUIComponent(source));
      
      try
      {
        if (source != null)
        {
          if (source is DisplayObject)
          {
            width = DisplayObject(source).width;
            height = DisplayObject(source).height;
          }
          else if (source is BitmapData)
          {
            width = BitmapData(source).width;
            height = BitmapData(source).height;
          }
          else if (source is IFlexDisplayObject)
          {
            width = IFlexDisplayObject(source).width;
            height = IFlexDisplayObject(source).height;
          }
        }
        
        // We default to an identity matrix
        // which will match screen resolution
        if (!matrix)
          matrix = new Matrix(1, 0, 0, 1);
        
        var scaledWidth:Number = width * matrix.a;
        var scaledHeight:Number = height * matrix.d;
        var reductionScale:Number = 1;
        
        // Cap width to BitmapData max of 2880 pixels
        if (scaledWidth > MAX_BITMAP_DIMENSION)
        {
          reductionScale = scaledWidth / MAX_BITMAP_DIMENSION;
          scaledWidth = MAX_BITMAP_DIMENSION;
          scaledHeight = scaledHeight / reductionScale;
          
          matrix.a = scaledWidth / width;
          matrix.d = scaledHeight / height;
        }
        
        // Cap height to BitmapData max of 2880 pixels
        if (scaledHeight > MAX_BITMAP_DIMENSION)
        {
          reductionScale = scaledHeight / MAX_BITMAP_DIMENSION;
          scaledHeight = MAX_BITMAP_DIMENSION;
          scaledWidth = scaledWidth / reductionScale;
          
          matrix.a = scaledWidth / width;
          matrix.d = scaledHeight / height;
        }
        
        // the fill should be transparent: 0xARGB -> 0x00000000
        // only explicitly drawn pixels will show up
        data = new BitmapData(scaledWidth, scaledHeight, true, 0x00000000);
        data.draw(source, matrix, colorTransform,
          blendMode, clipRect, smoothing);
      }
      finally
      {
        if (source is IUIComponent)
          finishPrintObject(IUIComponent(source), normalState);
      }
      
      return data;
    }


    /**
     *  @private
     *  Reverts the target and its parents back to a pre-capture state.
     */
    private static function finishPrintObject(target:IUIComponent,
normalStates:Array):void
    {
      var obj:DisplayObject = target is DisplayObject ?
        DisplayObject(target) :
        null;
      var index:Number = 0;
      while (obj != null)
      {
        if (obj is UIComponent)
        {
          UIComponent(obj).finishPrint(normalStates[index++], UIComponent(target));
        }
        else if (obj is DisplayObject && !(obj is Stage))
        {
          DisplayObject(obj).mask = normalStates[index++];
        }
        
        obj = (obj.parent is DisplayObject) ? DisplayObject(obj.parent) : null;
      }
    }


    /**
     *  @private
     *  Prepare the target and its parents for image capture.
     */
    private static function prepareToPrintObject(target:IUIComponent):Array
    {
      var normalStates:Array = [];
      
      var obj:DisplayObject = target is DisplayObject ?
        DisplayObject(target) :
        null;
      var index:Number = 0;
      
      while (obj != null)
      {
        if (obj is UIComponent)
        {
          normalStates[index++] =
            UIComponent(obj).prepareToPrint(UIComponent(target));
        }
        else if (obj is DisplayObject && !(obj is Stage))
        {
          normalStates[index++] = DisplayObject(obj).mask;
          DisplayObject(obj).mask = null;
        }
        
        obj = obj.parent is DisplayObject ?
          DisplayObject(obj.parent) :
          null;
      }
      
      return normalStates;
    }


  } //END class

} //END package

ただ、この定数をいくらでも大きく出来るかと言ったらそうではない。BitmapDataの制限により、1辺が8191を超えることはできない。しかも、1辺が8191以下だったとしても、面積が16,777,215ピクセルを超える場合はエラーになる。

BitmapData - Adobe ActionScript® 3(AS3 )API リファレンス

・・・
Flash Player 10 では、BitmapData オブジェクトの最大サイズは幅または高さで 8,191 ピクセルです。総ピクセル数が 16,777,215 ピクセルを超えることはできません。(したがって、BitmapData オブジェクトの幅が 8,191 ピクセルであった場合、高さは 2,048 ピクセルまでしか指定できません)。
・・・

したがって、8191x2048はOKだけど8191x2049はダメ。6192x3508=17,403,188もダメだ。

FP11からはBitmapDataのサイズ制限は撤廃されたはずだ。

しかし、たとえFP11で走らせたとしても、Flash Builder 4.0.1 ビルド277662(framework_4.1.0.16076.swz)でコンパイルした場合、制限はそのままだった。

Flash Builder 4.6(framework_4.6.0.23201.swz)でコンパイルすれば制限が撤廃されるのでは?と思うけど、これは未確認。

しかし恐らく、無理にImageSnapshotを改造せずに、Flash Builder 4.6でコンパイルできるようにプロジェクトを書き換えるのが無難だろうなと思う。


追記13.03.18:BitmapFilter系にも制限が出るので、可能であれば早いとこFP11レベルに書き換えたほうがいいなと改めて思った。。。
琴線探査: Flash Player 10レベルだとBitmapFilterにもサイズ制限があることを思い出した・・・