Monthly Archive for August, 2007

ConvolutionFilter effect

Convolution  는 회선기법으로써 입력픽셀과 그 주위의 이웃한 픽셀들을 가중평균(weighted average) 하여 처리하는 기법으로 플래시에서도 ConvolutionFilter 를 지원한다.
다른 필터에 비해 사용빈도에 있어서 떨어지지만 잘만 사용한다면 플래시에서도 포토샵에서 지원하는 여러 효과들의 필터 효과를 낼 수 있을것이다..

인접픽셀들을 이용하기 때문에 나타낼 수 있는 효과는 대부분 이미지의 경계부분(edge)  변형으로 생기는 embossing,blur,sharpen,accented-edge 같은 효과들이다.

convolution 필터는 9개의 파라미터가 존재하는데 일반적으로 아래와 같이 4개를 사용하여 제어를 한다.

ConvolutionFilter(matrixX:Number=0, matrixY:Number=0,matrix:Array=null,divisor:Number=1.0)

여기서 가장 중요한 부분이 matrix 라는 배열 파라미터인데 일반적으로 3×3 행렬을 사용한다.

0 0 0
0 1 0 =>  [0,0,0,0,1,0,0,0,0]    형렬표기방법은 좌측과 같이 열단위로 끊어서 작성한다.
0 0 0
<default>

matrix 는 중앙에 있는 값(1)을 기준으로  대칭형태로 사용한다. 이는 인접픽셀과의 가중치 계산을 위한 비율과도 무관하지 않다. 또한 모든 matirx 의 요소 값의 합이 1을 기준으로 크게되면 전체적인
이미지의 밝기(brightness)가 증가하게 되고, 반대로 1보다 작게 되면 밝기가 감소하게 된다.

위에서 볼수 있듯이 matrix 값의 총합이 1이 아닐경우 원본값에서 이미지 밝기에서 차이가 난다.
이런 뜻하지 않은 이미지 밝기의 변화로 인해 의도했던 이미지 효과가 묻혀버리게 되는 일이 발생하는데 이런걸 방지하기 위해 divisor 를 사용하여 값을 조정해준다.
divisor 은 convolution  필터의 4번째 파라미터로서 결과값에 대한 강도를 낮춰주게 된다.
matrix 전체의 총합을 divisor 로 나눈값이 1을 기준으로 설정될수 있는 밝기로 생각하면 된다.

ConvolutionFilter(3,3,[0, 0, 0, 0, 1, 0, 0, 0, 0])
ConvolutionFilter(3,3,[0, 0, 0, 0, 3, 0, 0, 0, 0],3)

따라서 위 두개의 필터값은 같은 효과를 나타낸다.

<embossing>
embossing 의 matrix 는 양수의 중앙값을 기준으로 좌우의 값의 부호가 반대인 좌우대칭형태
a   d  -c          -2  -1  0
b   e -b   ==>  -1   1   1   ==>    new ConvolutionFilter(3,3,[-2, -1, 0, -1, 1, 1, 0, 1, 2])
c -d  -a            0   1   2

<sharpening>
sharpening 의 matrix 는 양수의 중앙값을 기준으로 상하대칭형태
a   b   c          0    -1    0
d   e d   ==>  -1   5   -1   ==>    new ConvolutionFilter(3,3,[0, -1, 0, -1, 5, -1, 0, -1, 0])
c   b   a          0    -1    0

<accented-edge>
accented-edge 의 matrix 는 sharpening 과 비슷한 형태로 음수의 중앙값을 기준으로 상하대칭형태
a   b   c          0    1    0
d   e d   ==>  1   -3   1   ==>    new ConvolutionFilter(3,3,[0, 1, 0, 1, -3, 1, 0, 1, 0])
c   b   a          0    1    0

위 모든 효과는 중앙값(e) 을 조정하여 효과의 강도를 조정할 수 있다.

download sample

Tween Engine Class -AS3.0

flash 를 사용하면서 가장 많이 사용하는 부분은 아마도 트윈 부분일 것이다. 오브젝트의 속성값을 변경하면서 수많은 느낌의 모션을 만들수 있었다.
기존에 포함되어 있는 Tween 클래스는 많은 사용자들이 이런  다양한 트윈을 손쉽게 구현할 수 있게 해준다. 하지만, 생각보다 많은 기능들이 포함되어있어서 덩치도 클뿐더러 구현하는 코드 자체도 길어지게 되었다.
또한 AS3.0에서는 garbage collection 의 동작으로 인해 참조값이 없는 객체에 대해서는 collector 에 의해 메모리 수집의 대상이 되기 때문에 트윈클래스의 인스턴스를 지역변수로 참조하여 사용하게 되면 뜻하지 않게 트윈이 완료되기 이전에 객체가 사라지는 문제가 발생할 수도 있다..
따라서 이런 문제를 해결하기 위해 지역변수보다 좀더 높은 클래스 단계의 scope 의 참조를 사용하게 되는데 이럴경우 불필요하게 많은 참조 변수들을 클래스가 가지게되여 메모리를 소비하게 된다.

그리고 가장 빈번히 사용하게 되는 트윈의 경우 일일이 이벤트 핸들러 함수를 구현하기가 여간 번거로운게 아니다. 코드자체의 길이도 길어질 뿐만 아니라 가독성 측면에서도 떨어지는게 사실이다.

이런 문제를 조금이나마 해결하고자 이전 버전의 Tween 클래스를 기반으로 AS3.0 으로 컨버팅하였다.

기본적은 틀은 이전에 AS2.0 으로 구현한 로직과 큰 차이는 없다.
참조값을 유지하기 위해 전역속성으로 트윈을 적용하였고 객체를 생성하지 않아도 되기 때문에 한결 코드도 간결해졌다.

***** update(2007.10.30) ******

pause 와 resume 기능을 추가
원하는 오브젝트의 트윈에 적용가능함

pause(), pauseAll() 나 resume(), resumeAll() 를 사용하여 전체또는 부분적인 트윈을 제어할 수 있다.

<TweenEngine class>

package com.dstrict.ub.utils.transitions{
 
 import flash.display.DisplayObject;
 import flash.events.*;
 import flash.utils.Dictionary;
 /**
  DisplayObject tween class
  트윈을 적용할 속성의 갯수에 관계없이 일괄적으로 적용가능
  마지막 parameter 로 정해진 형식의 이벤트 오브젝트 적용시 이벤트 시작과 끝을 tracking 할 수 있음. 
 
  @example code
  <code>
 
   function onTweenStart(param):void{
    trace("onTweenStart----&gt;"+param);
   }
 
   function onTweenFinish(param):void{
    trace("onTweenFinish----&gt;"+param);
   }
 
TweenEngine.start(circle,{x:400},Regular.easeOut,30,{onStart:onTweenStart,
onStartParams:["circle"],onFinish:onTweenFinish,onFinishParams:["circle"]});
 </code>
 
 */
 
 public class TweenEngine {
   private static var _referContainer : Dictionary=new Dictionary(true);
  /**
   @param targetObj : DisplayObject , tween 적용할 오브젝트
   @param tweenProperty : Object , 속성오브젝트 ex. {x:100,y:100,alpha:1}
   @param easing : Function , 이징함수
   @param duration : int , 지속프레임
   @param rest : Obejct , [optional]  event object
   ex. {onStart:onTweenStart,onStartParams:[],onFinish:onTweenFinish,onFinishParams:[]}
  */
  public static function start(targetObj:*,tweenProperty:Object,easing:Function,duration:int,...rest):void{
   var time:int=1;
   var beginning:Array=new Array();
   var change:Array=new Array();
   var isCreated : Boolean=false;
   var displayObj : DisplayObject;
 
   TweenEngine.stop(targetObj);
 
   if(!(targetObj is DisplayObject)){
      displayObj = new Shape();
      isCreated=true;
    }else{
      displayObj = targetObj;
     }
 
   for(var i:* in tweenProperty){
       beginning.push(targetObj[i]);
       change.push(tweenProperty[i]-targetObj[i]);
   }
 
   if(rest.length &amp;&amp; rest[0].onStart){
    var eventStartObject:Object=new Object();
    eventStartObject.onStart=rest[0].onStart;
    eventStartObject.onStartParams=rest[0].onStartParams;
 
    //onStart event
    eventStartObject.onStart.apply(eventStartObject,eventStartObject.onStartParams);
   }
 
   //Nested function
   var update:Function=function(){
    var objIdx:int=0;
      for(var i:* in tweenProperty){
       targetObj[i]=easing(time,beginning[objIdx],change[objIdx],duration);
       objIdx++;
      }
      time++;
      if(time&gt;duration){
      delete _referContainer[displayObj];
       displayObj.removeEventListener(Event.ENTER_FRAME,update);
       if(isCreated) displayObj=null;
 
       if(rest.length &amp;&amp; rest[0].onFinish){
        var eventFinishObject:Object=new Object();
        eventFinishObject.onFinish=rest[0].onFinish;
        eventFinishObject.onFinishParams=rest[0].onFinishParams;
 
        //onFinish event
        eventFinishObject.onFinish.apply(eventFinishObject,eventFinishObject.onFinishParams);
       }
     }
   }
     _referContainer[displayObj] = update;
      displayObj.addEventListener(Event.ENTER_FRAME,update);
  }
 
  //Update 2007.10.30
  /**
   * Pause a tweening for a given object.
   */
  public static function  pause(targetObj : *) : void {
   if(targetObj.hasEventListener(Event.ENTER_FRAME)) {
    targetObj.removeEventListener(Event.ENTER_FRAME, _referContainer[targetObj]);
   }
  }
 
  /**
   * Pause all tweenings on the engine.
   */
 public static function pauseAll() : void {
   for(var item:* in _referContainer) {
    if(item.hasEventListener(Event.ENTER_FRAME)) {
     item.removeEventListener(Event.ENTER_FRAME, _referContainer[item]);
    }
   }
  }
 
  /**
   * Resume a tweening from a given object.
   */
  public static function resume(targetObj : *) : void {
   if(_referContainer[targetObj] != null) {
    targetObj.addEventListener(Event.ENTER_FRAME, _referContainer[targetObj]);
   }
  }
 
  /**
   * Resume all tweenings on the engine.
   */
  public static function resumeAll() : void {
   for(var item:* in _referContainer) {
    if(_referContainer[item] != null) {
     item.addEventListener(Event.ENTER_FRAME, _referContainer[item]);
    }
   }
  }
 
  /**
   *  Stop a tweening for a given object
   */
  public static function stop(targetObj : *) : void {
   if(targetObj.hasEventListener(Event.ENTER_FRAME)) {
    targetObj.removeEventListener(Event.ENTER_FRAME, _referContainer[targetObj]);
    delete _referContainer[targetObj];
   }
  }
 
  /**
   * Remove all tweenings from the engine.
   */
  public static function stopAll() : void {
   for(var item:* in _referContainer) {
    if(item.hasEventListener(Event.ENTER_FRAME)) {
     item.removeEventListener(Event.ENTER_FRAME, _referContainer[item]);
     delete _referContainer[item];
    }
   }
  }
 
 }
}
 
import com.dstrict.ub.utils.transitions.*;
import fl.motion.easing.*;
 
TweenEngine.start(circle,{x:400,y:300},Cubic.easeInOut,20,{onStart:onTweenStart,onStartParams:["circle start"],
onFinish:onTweenFinish,onFinishParams:["circle finish"]});
 
//----&gt; 마지막 오브젝트 값은 이벤트 핸들러를 참조하는 값으로 옵션사항이다., 사용시 이벤트 오브젝에서 함수이름을 onStart , onFinish 로
            이벤트 파라미터를 onStartParams,onFinishParams 로 키값을 사용해야만 한다.
 
 function onTweenStart(param):void{
   trace("onTweenStart----&gt;"+param);
}
 
function onTweenFinish(param):void{
  trace("onTweenFinish----&gt;"+param);
}
 
//stop tween
stage.addEventListener(MouseEvent.CLICK,onStop);
function onStop(evt:MouseEvent){
 TweenEngine.stop(circle);
}

download sample

Checking user bandwidth

우리나라의 웹환경에서는 초고속 인터넷과 같은 인프라의 발전으로 대역폭(bandwidth)을 고려해야할 만큼 네트워크 속도가 절대적으로 중요하지는 않다.
워낙 전송 속도가 빨라서 왠만한 웹사이트는 로딩시간없이 실시간으로  보여진다. 하지만 이는 특수한 우리나라의 경우에서만 해당하는 사항일뿐 아직까지 대부분의 다른나라에서의 전송속도는 생각하는 것 만큼 빠르지 않다. 좀더 범용적인 사이트의 개발을 위해서는 반드시 고려해야 할 사항이다.

특히 영상위주의 컨텐츠가 포함된 사이트의 경우 사용자의 대역폭을 고려하는 것이 좀더 사용자에게 좋은 컨텐츠를 제공하는 방법이 될 수 있다. 대부분 영상을 보여주는 방법으로 점진적 다운로드 방식(progressive download)을 사용한다. 실시간으로 다운로드 한만큼 플레이하는방식으로 이는 사용자 환경의 대역폭이 서비스의 질을 크게 좌우하게 된다.

영상을 보여주는 데 있어 점진적 다운로드 방식은 영상의 전체적인 용량보다는 영상의 압축률에 더 큰 영향을 받는다. 다시말해 데이타를 로드하면서 플레이하는 방식이기 때문에 비디오의 압축률을 높여 단위시간당 받을 수 있는 용량을 보다 크게 하여 좀더 원할하게 플레이 할 수 있다.

따라서 미리 사용자의 대역폭을 알아내어 거기에 알맞는 압축률의 비디오를 제공할 수 있는 것이다.
플래시에서 일반적으로 대역폭을 측정하는 방법으로는 일정크기의 샘플파일을 다운로드하여 걸린시간을 측정하여 구할 수 있다. 하지만 이 대역폭은 절대적인 속도를 의미하는 것은 아니다.
네트워크 사용량, 네트웍크 지연과 같은 여러가지 요인에 의해 언제든지 속도가 변경될 수 있다. 항상 측정된 속도로 데이타를 받을 수 있다는 의미가 아니다. 현재 시간에서 대략적인 사용자의 네트워크 속도를 가늠해 볼 수 있는데 의미가 있다. 불안정한 네트워크 일수록 측정 대역폭의 편차가 크게 발생한다.

대역폭 측정의 정확성을 높이기 위해서는 테스트하기 위해 사용하는 샘플의 용량을 크게 하거나,측정하는 테스트의 횟수를 늘려야한다. 하지만 이는 전송속도 측정을 위해 불필요하게 자원을 소모하게 되기 때문에 어느정도 적정선을 유지해야한다.
여기서 제공하는 방법은 샘플파일의 크기를 50~100 KB 로 제한하고 측정횟수를 2회로 한정하였다.
위 조건으로 측정한다고 해도 사용자의 네트워크 속도가 50KB 미만일 경우 100~200KB 를 테스트를위해 최소 2~4초정도의 시간이 걸리게 된다.

<bandwidthCheck class>

import com.dstrict.UB.events.Dispatcher;
import com.dstrict.UB.events.Event;
 
class com.dstrict.UB.util.system.BandwidthCheck extends Dispatcher {
 
private var _bandwidthSet:Array;
private var _bandwidth:Number;
 
private var _startTime:Number;
private var _checkCount:Number;
 
private var _loader:MovieClipLoader;
private var _container:MovieClip;
 
public function get bandwidth():Number{
return _bandwidth;
}
 
public function BandwidthCheck() {
_checkCount=0;
_bandwidth=0;
_bandwidthSet=new Array();
}
 
public function check():Void{
_container= _root.createEmptyMovieClip("container", _root.getNextHighestDepth());
var nocache:Number=Math.random()*1000000;
_loader=new MovieClipLoader();
_loader.addListener(this);
_loader.loadClip("bandwidthDummy.png?nocache="+nocache,_container);
}
 
private function calculateBandwidth():Void{
for(var i in _bandwidthSet){
_bandwidth+=_bandwidthSet[i];
}
_bandwidth=_bandwidth/_checkCount;
 
//dispatch a complete event
startEvent(new Event(Event.COMPLETE,this));
}
 
private function onLoadStart(targetMc:MovieClip):Void{
_startTime=getTimer();
}
 
private function onLoadComplete(targetMc:MovieClip):Void{
_loader.unloadClip(_container);
var elapsedTime:Number=(getTimer()-_startTime)/1000;
_checkCount++;
 
var progress:Object = _loader.getProgress(targetMc);
var kilobytes:Number=progress.bytesTotal/1024;
var kBps:Number=kilobytes/elapsedTime;
_bandwidthSet.push(kBps);
 
if(_checkCount==1){
check();
}else{
calculateBandwidth();
}
}
}

<sample code>

var bandwidthCheck:BandwidthCheck=new BandwidthCheck();
bandwidthCheck.addEventListener(Event.COMPLETE,onBandwidthCheck);
bandwidthCheck.check();
 
function onBandwidthCheck(evt:Event){
trace("bandwidth-----------------&gt;"+evt.target.bandwidth);
}

대역폭(bandwidth) 의 단위로는 KBps(kilobytes per second) 이다. 브라우저 캐쉬로 부터 다운받는것을 방지하기 위해 첨부한 문자열로 인해 반드시 웹상에서 다운받아야 로드할수 있다.

download sample