心魅 - cocoromi -

半角スペース時々全角

任意のテキストに擬似縁取りエフェクトをかけるテスト

FlashのIDEで作業をすればテキストを塗りに変換し線をつけることで、縁取りは簡単に作ることができる。
ところが、それではダイナミックなテキストに対応するのは難しいし、ましてや動的にフォントが変わるような場合は絶望的である。

そこで、サイズや、テキスト、フォントが動的に変化するものに対して縁をつけるサンプルを作ってみた。

バグがあるがとりあえず、貼る。

http://umezo.tsuyabu.in/samples/flex/aroundtextfilter/
#一部フォームの変更が即時反映されないBugがあります。
#回避策調査中

しくみ


基本的な仕組みは以下の図の用に対象の文字列をフィルター用レイヤで加工してそれをマスクに縁取りに使うBitmapをマスクします。
f:id:umezo:20090128005520p:image

ただ、単純にマスクするだけだと、それっぽくないのでGlowFilterやColorMatrixFilterで調整します。

//フィルタ用レイヤのフィルタを更新
this.uiMaskLabel.filters = [ this.filterGlow ];

//フィルタ用レイヤからBitmapデータを精製
var rect : Rectangle = new Rectangle( 0 , 0 , uiMaskLabel.width , uiMaskLabel.height );
var zero : Point = new Point( 0 , 0 );
var bitmap :BitmapData = ImageSnapshot.captureBitmapData( uiMaskLabel );

//ColorMatrixFilterでアルファ値を調整
var matrixFilter : ColorMatrixFilter = new ColorMatrixFilter([
	1 , 0 , 0 , 0 , 0 ,
	0 , 1 , 0 , 0 , 0 ,
	0 , 0 , 1 , 0 , 0 ,
	0 , 0 , 0 , slideEdge.value , 0 ,	
]);
bitmap.applyFilter( bitmap , rect , zero , matrixFilter );

//テクスチャを生成
tone.bitmapData = new ToneImage().bitmapData;
//マスク用データからアルファ値をコピー
tone.bitmapData.copyChannel( bitmap , new Rectangle( 0 , 0 , bitmap.width , bitmap.height ) , new Point( 0 , 0 ) ,  BitmapDataChannel.ALPHA , BitmapDataChannel.ALPHA );
//マスクを更新
this.maskUI.bitmapData = bitmap;

GlowFilterでテキストのBitmapデータを拡張します。この拡張した部分が縁になります。ただこのままだと微妙なので2段階で調整します。1つ目は、アルファチャンネルのコピーこれで縁が外に行くにつれて透明になるようになります。さらにColorMatrixFilterを使ってアルファ値をさらに調節します。
これにより、透明じゃない部分を増やし全体を整えます。 slideEdge.value が大きくなるほど、縁のアルファ部分が薄くなり、上手く調節すると擬似アンチエイリアスの効果が得られます。

まとめ


フィルタをくしすれば、動的なテキストに縁をつけることが出来る。
一方で、テキストサイズ毎にフィルタパラメータを調節する必要があったりと、完全に動的に使うにはまだ難しい部分が残る。

ソースコードは続きでどうぞ。

appendix


プログラム全文

<?xml version="1.0"?>
<mx:Application

	xmlns:mx="http://www.adobe.com/2006/mxml"
	width="100%"
	height="100%"
	
	styleName="plain"
	
	creationComplete="init();"
	
>
	<mx:Script >
		<![CDATA[
			import flash.display.Bitmap;
			import flash.display.MovieClip;
			import flash.filters.ColorMatrixFilter;
			import flash.geom.Point;
			import mx.controls.ComboBox;
			import flash.text.TextField;
			import mx.controls.Label;
			import mx.controls.Text;
			import mx.collections.ArrayCollection;
			import flash.text.Font;
			import mx.collections.Sort;
			import mx.collections.SortField;
			
			import flash.display.BitmapData;
			
			import mx.graphics.ImageSnapshot;
			
			import flash.display.BitmapDataChannel;

			

			[Bindable]
			private var textData:String = "umezo.tsuyabu.in";
			
			[Bindable]
			private var fontsData:ArrayCollection ;
			
			[Embed(source = '../res/tone.png')]
			[Bindble]
			private var ToneImage:Class;
			
			
			private var tone:Bitmap;
			private var maskUI:Bitmap;
			private function init():void {
				fontsData = new ArrayCollection(Font.enumerateFonts(true));
				var fontSort:Sort = new Sort();
				fontSort.fields = [new SortField("fontName")];
				fontsData.sort = fontSort;
				
				tone = new ToneImage();
				maskUI = new Bitmap(); 
				
				tone.mask = maskUI;
				sprite.addChild( tone );
				sprite.addChild( maskUI );
				

				this.updateFilter();
				
				//this.uiMaskLabel.mask = this.maskImage ;
			}
			
			private function setFont():void {
				this.uiMainLabel.setStyle( "fontFamily" , this.uiListFont.selectedLabel );
				this.uiMaskLabel.setStyle( "fontFamily" , this.uiListFont.selectedLabel );
				
				this.updateFilter();
			}
			
			
			private function updateFilter():void {
				this.uiMaskLabel.filters = [ this.filterGlow ];
				
				var rect : Rectangle = new Rectangle( 0 , 0 , uiMaskLabel.width , uiMaskLabel.height );
				var zero : Point = new Point( 0 , 0 );
				var bitmap :BitmapData = ImageSnapshot.captureBitmapData( uiMaskLabel );
				
				var matrixFilter : ColorMatrixFilter = new ColorMatrixFilter([
					1 , 0 , 0 , 0 , 0 ,
					0 , 1 , 0 , 0 , 0 ,
					0 , 0 , 1 , 0 , 0 ,
					0 , 0 , 0 , slideEdge.value , 0 ,	
				]);
				bitmap.applyFilter( bitmap , rect , zero , matrixFilter );
				tone.bitmapData = new ToneImage().bitmapData;
				tone.bitmapData.copyChannel( bitmap , new Rectangle( 0 , 0 , bitmap.width , bitmap.height ) , new Point( 0 , 0 ) ,  BitmapDataChannel.ALPHA , BitmapDataChannel.ALPHA );
				this.maskUI.bitmapData = bitmap;
				//tone.mask = this.uiMaskLabel;

			}
			
		]]>
	</mx:Script>
	<!--
	<mx:BlurFilter id="filterBlur" blurX="{slideBlurSize.value}" blurY="{slideBlurSize.value}" quality="{slideBlurQ.value}"></mx:BlurFilter>
	-->
	<mx:BlurFilter id="filterBlur" blurX="{slideBlurSize.value}" blurY="{slideBlurSize.value}" quality="{slideBlurQ.value}"></mx:BlurFilter>
	<mx:GlowFilter id="filterGlow" color="#FFFFFF" blurX="{slideBlurSize.value}" blurY="{slideBlurSize.value}" quality="{slideBlurQ.value}" strength="{slideStrength.value}"></mx:GlowFilter>
	<mx:HBox width="100%" height="100%">
		<mx:VBox id="container" width="100%" height="100%" backgroundColor="{uiBGColor.selectedColor}">
			<mx:Spacer height="100%"></mx:Spacer>
			<mx:HBox width="100%">
				<mx:Spacer width="100%"></mx:Spacer>
				<mx:Canvas>
					<mx:Label id="uiMaskLabel" visible="false" text="{uiTextData.text}" filters="{ [filterGlow] }" cacheAsBitmap="true" fontSize="{slideFontSize.value}" color="#FFFFFF"></mx:Label>
					<mx:UIComponent id="sprite"></mx:UIComponent>
					<mx:Label id="uiMainLabel" text="{uiTextData.text}" fontSize="{slideFontSize.value}" color="{uiFontColor.selectedColor}"></mx:Label>
				</mx:Canvas>
				<mx:Spacer width="100%"></mx:Spacer>
			</mx:HBox>
			<mx:Spacer height="100%"></mx:Spacer>
		</mx:VBox>
		<mx:VBox>
			<mx:Panel title="Text" width="100%">
				<mx:HBox visible="false">
					<mx:Label text="ForeGround"></mx:Label>
					<mx:ColorPicker id="uiBGColor" selectedColor="#000000"></mx:ColorPicker>
				</mx:HBox>
				<mx:HBox>
					<mx:Label text="BackGround"></mx:Label>
					<mx:ColorPicker id="uiFontColor" selectedColor="#FFFFFF"></mx:ColorPicker>
				</mx:HBox>
				<mx:TextInput id="uiTextData" width="100%" text="{textData}" change="updateFilter();"></mx:TextInput>
				<mx:ComboBox id="uiListFont" width="100%" dataProvider="{fontsData}" labelField="fontName" change="setFont();"></mx:ComboBox>
			</mx:Panel>
			<mx:Panel title="Parameters" width="100%" height="100%">
				<mx:HSlider width="100%" id="slideFontSize" value="106" snapInterval="1" minimum="20" maximum="120" change="updateFilter();"></mx:HSlider>
				<mx:HSlider width="100%" id="slideBlurSize" value="0" snapInterval="1" minimum="10" maximum="40" change="updateFilter();"></mx:HSlider>
				<mx:HSlider width="100%" id="slideBlurQ" value="2"  snapInterval="1"  change="updateFilter();"></mx:HSlider>
				<mx:HSlider width="100%" id="slideStrength" value="4"  snapInterval="1"  change="updateFilter();"></mx:HSlider>
				<mx:HSlider width="100%" id="slideEdge" value="2.67" change="updateFilter();"></mx:HSlider>
			</mx:Panel>
		</mx:VBox>
	</mx:HBox>
</mx:Application>