LCDS, BlazeDS, ZendAMF등을 이용해 AMF(ActionScript Message Format)으로 데이터를 주고받는 형태는 이미 많은 예제들과 문서들이 있다. AMF는 데이터 통신을 위한 일종의 약속된 규약이고 이 데이터를 주고 받고 하는 과정에서 서로의 언어에 맞게 직렬화(Serialization)하는 것은 각 언어에서 지원해주는 라이브러리를 사용하면 된다. ActionScript 3.0은 기본 API에서 지원해준다. 자바의 경우는 BlazeDS나 LCDS를 사용하면 된다. PHP의 경우에는 ZendAMF를 사용하면 된다. 이들을 이용하면 가령, 자바와 java.lang.Interger는 ActionScript의 int나 uint로... java.lang.Double은 ActionScript의 Number형과 직렬화된다. 이는 일종의 기본적으로 지원되는 직렬화이다.

다음은 BlazeDS에서 기본적으로 지원되는 직렬화이다.
Serializing between ActionScript and Java

하지만 이런 기본 직렬화과정을 사용하지 않고 직렬화 자체를 커스터마이징(customizing)할 수 있다. 가령 클라이언트(예,Flash 애플리케이션)에서는 아이디, 이름, 속성, 가격의 정보가 중요하지만 서버(예,자바)에서는 재고(inventory)가 중요한 경우가 있다. 이런 경우에는 Flex와 Java쪽의 직렬화하는 클래스를 다음과 같이 디자인 할 수 있겠다.

// Product.as
package samples.externalizable {

import flash.utils.IExternalizable;
import flash.utils.IDataInput;
import flash.utils.IDataOutput;

[RemoteClass(alias="samples.externalizable.Product")]
public class Product implements IExternalizable {
    public function Product(name:String=null) {
        this.name = name;
    }

    public var id:int;
    public var name:String;
    public var properties:Object;
    public var price:Number;

    public function readExternal(input:IDataInput):void {
        name = input.readObject() as String;
        properties = input.readObject();
        price = input.readFloat();
    }

    public function writeExternal(output:IDataOutput):void {
        output.writeObject(name);
        output.writeObject(properties);
        output.writeFloat(price);
    }
}
}


// Product.java
package samples.externalizable;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Map;

/**
* This Externalizable class requires that clients sending and 
* receiving instances of this type adhere to the data format
* required for serialization.
*/
public class Product implements Externalizable {
    private String inventoryId;
    public String name;
    public Map properties;
    public float price;

    public Product()
    {
    }

        /**
        * Local identity used to track third party inventory. This property is
        * not sent to the client because it is server-specific.
        * The identity must start with an 'X'.
        */
        public String getInventoryId() {
            return inventoryId;
        }

        public void setInventoryId(String inventoryId) {
            if (inventoryId != null && inventoryId.startsWith("X"))
            {
                this.inventoryId = inventoryId;
            }
            else
            {
                throw new IllegalArgumentException("3rd party product
                inventory identities must start with 'X'");
            }
        }

        /**
         * Deserializes the client state of an instance of ThirdPartyProxy
         * by reading in String for the name, a Map of properties
         * for the description, and 
         * a floating point integer (single precision) for the price. 
         */
        public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
            // Read in the server properties from the client representation.
            name = (String)in.readObject();
            properties = (Map)in.readObject();
            price = in.readFloat();
            setInventoryId(lookupInventoryId(name, price));
        }
        /**
         * Serializes the server state of an instance of ThirdPartyProxy
         * by sending a String for the name, a Map of properties
         * String for the description, and a floating point
         * integer (single precision) for the price. Notice that the inventory 
         * identifier is not sent to external clients.
         */
        public void writeExternal(ObjectOutput out) throws IOException {
            // Write out the client properties from the server representation
            out.writeObject(name);
            out.writeObject(properties);
            out.writeFloat(price);
        }
        
        private static String lookupInventoryId(String name, float price) {
            String inventoryId = "X" + name + Math.rint(price);
            return inventoryId;
        }
}

위 코드는 ActionScript의 flash.utils.IExternalizable 인터페이스와 Java의 java.io.Externalizable 인터페이스를 이용해 기본직렬화를 무시하고 이들 인터페이스에 정의된 readExternal와 writeExternal 메소드를 호출하여 직렬화 자체를 커스터마이징 할 수 있다는 것을 의미한다. Externalizable 인터페이스를 구현한 클래스에 정의된 메소드가 기본 직렬화보다 우선순위가 높다는 것을 기억하면 되겠다.

Flex의 ArrayCollection을 다시 한번 보기 바란다. 이 클래스는 flash.utils.IExternalizable를 구현했다.
mx.collections.ArrayCollection

꼭 이런 경우만은 아니다. 쓸데없는 데이터의 크기를 줄이기 위한 방법도 해당한다. 예를들어 ActionScript 코드를 보면 다음과 같다.


class Example implements IExternalizable {
  
      public var one:Boolean;
      public var two:Boolean;
      public var three:Boolean;
      public var four:Boolean;
      public var five:Boolean;
      public var six:Boolean;
      public var seven:Boolean;
      public var eight:Boolean;
       public function writeExternal(output:IDataOutput) {
           var flag:int = 0;
           if (one) flag |= 1;
          if (two) flag |= 2;
          if (three) flag |= 4;
          if (four) flag |= 8;
          if (five) flag |= 16;
          if (six) flag |= 32;
          if (seven) flag |= 64;
          if (eight) flag |= 128;
           output.writeByte(flag);
      }
       public function readExternal(input:IDataInput) {
           var flag:int = input.readByte();
           one = (flag & 1) != 0;
          two = (flag & 2) != 0;
          three = (flag & 4) != 0;
          four = (flag & 8) != 0;
          five = (flag & 16) != 0;
          six = (flag & 32) != 0;
          seven = (flag & 64) != 0;
          eight = (flag & 128) != 0;
      }
 }

데이터 통신을 위해 쓸데없이 Boolean객체를 주고 받을 필요없다. 위 코드처럼 직렬화를 커스터마이징한다면 송수신 데이터 자체의 크기도 줄일 수 있다. 이는 매우 유용하다.


참고글
flash.utils.IExternalizable
커스텀 직렬화의 사용
ActionScript 3.0 데이터 유형 및 직렬화(serialization)
Serializing between ActionScript and Java
AMF 3 스팩
AS3 BitmapData AMF solution using IExternalizable
Flex and PHP: remoting with Zend AMF

글쓴이 : 지돌스타(http://blog.jidolstar.com/644)


아래는 Papervision3D에 쓸 수 있는 Primitive인 GeodesicSphere 클래스를 만들어 테스트한 화면이다.


위 프로그램의 소스는 아래 링크를 참고한다.
http://wonderfl.net/code/e6f14ed092706ba9f539f64da252a92200724769

소스코드상에서 GeodesicSphere 클래스 부분만 제외하고 전부 테스트 코드이다.

Away3DLite환경에서 만든 GeodesicSphere도 만들었다.

http://wonderfl.net/code/5dcd89ac60896ae55604aa018747bc0d861b2bff

참고글 : http://blog.jidolstar.com/589

글쓴이 : 지돌스타(http://blog.jidolstar.com/643)



Flash 애플리케이션을 만들때  통합개발환경(IDE)인 Flash CS3, CS4 나 Flex Builder, Flash Builder, FDT와 같은 툴을 주로 사용할 것이다. 실무에서는 당연히 이런 툴을 선택할 수 밖에 없다. 그러나 이들은 사실 유료이고 무겁다.

Flash MiniBuilder라는 프로그램은 간단하게 ActionScript 3.0 기반으로 개발할 수 있도록 Adobe AIR로 만들어진 가벼운 개발툴이다. 이것은 Victor Drâmbă라는 사람이 만들었으며 그의 블로그(http://www.victordramba.com)를 가보면 MiniBuilder에 대한 소식을 볼 수 있다.

MiniBuilder는 Flex SDK 3와 Java JRE 설치를 먼저 해야 정상적으로 사용할 수 있으며 사용하는 방법은 다음 동영상에서 잘 나와있다. 약간 복잡해 보이지만 중간에 start.bat를 실행해야하는 번거로움은 AIR 2.0에서는 없어질 것이라 생각한다.
http://www.youtube.com/watch?v=bjEc2eT_rCE&feature=player_embedded


이 프로그램은 Flex SDK에서 제공하는 컴파일러를 이용해서 만들어진 ActionScript 3.0코드를 컴파일하게 된다. 워낙 가볍기 때문에 Flash Builder에서 느껴지는 묵직함은 찾아볼 수 없다. 일반적인 코딩도 별 어려움이 없고 또한 코드힌트기능 및 에러메시지 출력 기능도 있기 때문에 이 또한 유용하다. 아쉬운 점은 디버깅을 할 수 없고 한글 주석도 못단다는 점.

MiniBuilder는 공개소스이다. GPL 라이센스를 가지며 Google Code(http://code.google.com/p/minibuilder/)에서 전반적인 설명과 소스코드를 볼 수 있다. 원한다면 Flex Builder 3나 Flash Builder 4에서 SVN으로 다운로드 받아 직접 소스를 테스트해볼 수도 있다. (본인도 SVN으로 다운받아 해봤다. 자바소스까지 포함되어 있으므로 Eclipse+Flex Builder 3 Plug-in 환경에서 작업할 것을 권한다. MXML 사용 안하고 전부 ActionScript 3.0으로 만들었다.)



MiniBuilder는 AIR뿐 아니라 Flash버전으로도 제작되어 유명한 ActionScript 3.0 코드 공유사이트인 Wonderfl의 에디터로도 활용되었다. 다음 제작자의 글을 보면 그에 대한 소개가 나와있다.
http://www.victordramba.com/?p=71



벌써 이것이 만들어져 소개된지 몇개월 되었는데 이렇게 멋진 공개 프로그램이 한국에 소개된게 딱 하나밖에 못본 것 같아 간단하게 소개해본다.

참고 링크
Victor Drâmbă의 블로그 : http://www.victordramba.com
Flash MiniBuilder Google Code : http://code.google.com/p/minibuilder
설치 및 사용방법 : http://www.youtube.com/watch?v=bjEc2eT_rCE&feature=player_embedded


글쓴이 : 지돌스타(http://blog.jidolstar.com/642)

로컬자원을 서버에 전송하기 위해 우리는 FileReference Class를 사용하면 된다.
FileReference로 파일을 서버에 전송하는 방법은 많이 공개가 되어 있다.
알다시피 FileReference의 browse()함수를 호출한 뒤, select 이벤트가 발생시 upload() 함수를 이용하여 선택한 로컬자원을 서버로 보낸다.

서버에서는 아주 단순하게 서버 임시 저장소에 저장된 파일을 원하는 곳에 복사하기만 하면 된다.
php의 경우 move_uploaded_file() 메소드를 사용하면 되겠다.

그럼 Flex 시행도중 캡쳐한 화면을 저장하는 경우에도 위와 같은 방법으로 저장이 가능할까?

답은 "된다"


나는 예전에 ImageSnapshot 클래스를 이용해 base64로 변환해서 서버로 전송한 뒤에 base64를 decode하여 저장하는 방법에 대해서 언급한적이 있다. (http://blog.jidolstar.com/301 참고)
이 방법의 단점은 이미지가 큰 경우 base64로 encode, decode 하는 과정에서 서버성능 및 클라이언트 성능에 따라 속도 및 부하에 영향을 준다. 그러므로 이 방법으로는 PNGEncoder 및 JPGEncoder로 PNG, JPG 파일 형식에 맞는 데이타를 만들었다고 해도, FileReference와 같이 데이타를 전송을 할 수 없었다.

하지만 Google을 열심히 돌아다녔다니 이 문제점에 대한 해결의 실마리를 찾을 수 있었다.
(역시 Google!!!)

간단히 방법을 요약하자면
화면을 BitmapData로 만들어 PNGEncoder나 JPGEncoder를 이용하여 encode한 다음, 그 결과값인 byteArray값을 서버에 전송한다. 전송된 데이타는 FileReference에서 upload()을 이용해 보낸 파일을 저장할때와 동일하게 저장하면 되겠다.

1. BitmapData로 캡쳐해라.

아래 target은 캡쳐할 UIComponent와 같은 DisplayObject 객체이다.

BitmapData로 캡쳐
var bitmapData:BitmapData = new BitmapData(target.width, target.height, true, 0xFFFFFF);
var drawingRectangle:Rectanglenew Rectangle(0, 0, target.width, target.height);
bitmapData.draw(target, new Matrix(), null, null, drawingRectangle, false);

단, BitmapData를 이용해서 화면을 캡쳐할 대상이 외부 동영상이나 사진같은 거라면 crossdomain.xml 에 대한 check가 있어야 한다. 컨텐츠 로드시 checkPolicyFile 속성을  true로 설정할 필요가 있겠다.
그리고 2880px 이상의 크기는 BitmapData로 만들 수 없다.



2. JPG나 PNG로 Encode 하여 ByteArray를 얻는다.


Flex 3 SDK에는 mx.graphics.codec 패키지에 JPGEncoder와 PNGEncoder가 있다. 인터넷을 뒤져보면 GIFEncoder등도 있을것이다. Flex 2 환경에서 작업한다면 Google code에 Adobe AS3 Corelib에 이들이 제공된다. 만약 JPGEncoder를 사용한다면 다음과 같이 하면 되겠다.

JPGEncoder를 이용하여  JPG  ByteArray값 만들기
import mx.graphics.codec.JPGEncoder;

var byteArray:ByteArray = new JPGEncoder().encode(bitmapData);


Flex 3 SDK는 이러한 Encoder가 IImageEncoder로 구현되었다. 필요하다면 언제 어디서나 Encoder를 바꿔야 하는 경우 IImageEncoder를 사용하는 것이 좋을 수 있겠다.
가령 아래 예제처럼 말이다.

다양한 Image Encoder 사용하기
import mx.graphics.codec.*;

var imageType:String = "jpg";
var imageEncoder:IImageEncoder;
if( imageType.toUpperCase() == "JPG" ) imageEncoder= new JPEGEncoder();
else if( imageType.toUpperCase() == "PNG" ) imageEncoder= new PNGEncoder();
var byteArray:ByteArray = imageEncoder.encode(bitmapData);



 

3. 서버에 ByteArray를 전송한다.

데이타를 전송할때는 FileReference를 사용하지 않는다.
바로 URLLoader와 URLRequest만 이용해서 전송이 가능하다. 참고 데이타는 POST방식으로 URLVariable을 이용해서 보낼 수 있다.

Flex/AIR 데이터 전송 방법
//assumed variable declarations
//var byteArray:ByteArray = 2번째 단계에서 JPG 데이타를 얻었다.
//var fileName:String = "desiredfilename.jpg" //저장할 파일 이름이다. 아무거나 적자!
//var uploadPath:String = "저장할 때 사용되는 서버측 script 경로"
//var parameters:URLVariables = 이미지 이외에 다른 보낼 다른 데이타가 있다면 이것을 이용한다.
//function onComplete(eventObj:Event):void {  성공적으로 데이타를 전송했을때 핸들러 함수 정의
//function onError(eventObj:ErrorEvent):void {  이미지 전송을 실패했을때 핸들러 함수 정의

var urlRequest:URLRequest = new URLRequest();
urlRequest.url = uploadPath;
//urlRequest.contentType = 'multipart/form-data; boundary=' + UploadPostHelper.getBoundary();
urlRequest.method = URLRequestMethod.POST;
urlRequest.data = UploadPostHelper.getPostData(file, byteArray, parameters);
urlRequest.requestHeaders.push( new URLRequestHeader( 'Cache-Control', 'no-cache' ) );
urlRequest.requestHeaders.push( new URLRequestHeader( 'Content-Type', 'multipart/form-data; boundary=' +UploadPostHelper.getBoundary()) );

var urlLoader:URLLoader = new URLLoader();
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener(Event.COMPLETE, onComplete);
urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
urlLoader.load(urlRequest);


위에 진한 부분에 대한 클래스는 아래에 정의되어 있다. 당신은 이 클래스가 어떻게 구성되었는가 열심히 공부할 필요가 없다.(원한다면 해도 된다. 말리지 않음 ^^)

UploadPostHelper Class (Language : java)
package {

    import flash.events.*;
    import flash.net.*;
    import flash.utils.ByteArray;
    import flash.utils.Endian;

    /**
     * Take a fileName, byteArray, and parameters object as input and return ByteArray post data suitable for a UrlRequest as output
     *
     * @see http://marstonstudio.com/?p=36
     * @see http://www.w3.org/TR/html4/interact/forms.html
     * @see http://www.jooce.com/blog/?p=143
     * @see http://www.jooce.com/blog/wp%2Dcontent/uploads/2007/06/uploadFile.txt
     * @see http://blog.je2050.de/2006/05/01/save-bytearray-to-file-with-php/
     *
     * @author Jonathan Marston
     * @version 2007.08.19
     *
     * This work is licensed under a Creative Commons Attribution NonCommercial ShareAlike 3.0 License.
     * @see http://creativecommons.org/licenses/by-nc-sa/3.0/
     *
     */

    public class UploadPostHelper {

        /**
         * Boundary used to break up different parts of the http POST body
         */

        private static var _boundary:String = "";

        /**
         * Get the boundary for the post.
         * Must be passed as part of the contentType of the UrlRequest
         */

        public static function getBoundary():String {

            if(_boundary.length == 0) {
                for (var i:int = 0; i < 0x20; i++ ) {
                    _boundary += String.fromCharCode( int( 97 + Math.random() * 25 ) );
                }
            }

            return _boundary;
        }

        /**
         * Create post data to send in a UrlRequest
         */

        public static function getPostData(fileName:String, byteArray:ByteArray, parameters:Object = null):ByteArray {

            var i: int;
            var bytes:String;

            var postData:ByteArray = new ByteArray();
            postData.endian = Endian.BIG_ENDIAN;

            //add Filename to parameters
            if(parameters == null) {
                parameters = new Object();
            }
            parameters.Filename = fileName;

            //add parameters to postData
            for(var name:String in parameters) {
                postData = BOUNDARY(postData);
                postData = LINEBREAK(postData);
                bytes = 'Content-Disposition: form-data; name="' + name + '"';
                for ( i = 0; i < bytes.length; i++ ) {
                    postData.writeByte( bytes.charCodeAt(i) );
                }
                postData = LINEBREAK(postData);
                postData = LINEBREAK(postData);
                postData.writeUTFBytes(parameters[name]);
                postData = LINEBREAK(postData);
            }

            //add Filedata to postData
            postData = BOUNDARY(postData);
            postData = LINEBREAK(postData);
            bytes = 'Content-Disposition: form-data; name="Filedata"; filename="';
            for ( i = 0; i < bytes.length; i++ ) {
                postData.writeByte( bytes.charCodeAt(i) );
            }
            postData.writeUTFBytes(fileName);
            postData = QUOTATIONMARK(postData);
            postData = LINEBREAK(postData);
            bytes = 'Content-Type: application/octet-stream';
            for ( i = 0; i < bytes.length; i++ ) {
                postData.writeByte( bytes.charCodeAt(i) );
            }
            postData = LINEBREAK(postData);
            postData = LINEBREAK(postData);
            postData.writeBytes(byteArray, 0, byteArray.length);
            postData = LINEBREAK(postData);

            //add upload filed to postData
            postData = LINEBREAK(postData);
            postData = BOUNDARY(postData);
            postData = LINEBREAK(postData);
            bytes = 'Content-Disposition: form-data; name="Upload"';
            for ( i = 0; i < bytes.length; i++ ) {
                postData.writeByte( bytes.charCodeAt(i) );
            }
            postData = LINEBREAK(postData);
            postData = LINEBREAK(postData);
            bytes = 'Submit Query';
            for ( i = 0; i < bytes.length; i++ ) {
                postData.writeByte( bytes.charCodeAt(i) );
            }
            postData = LINEBREAK(postData);

            //closing boundary
            postData = BOUNDARY(postData);
            postData = DOUBLEDASH(postData);

            return postData;
        }

        /**
         * Add a boundary to the PostData with leading doubledash
         */

        private static function BOUNDARY(p:ByteArray):ByteArray {
            var l:int = UploadPostHelper.getBoundary().length;

            p = DOUBLEDASH(p);
            for (var i:int = 0; i < l; i++ ) {
                p.writeByte( _boundary.charCodeAt( i ) );
            }
            return p;
        }

        /**
         * Add one linebreak
         */

        private static function LINEBREAK(p:ByteArray):ByteArray {
            p.writeShort(0x0d0a);
            return p;
        }

        /**
         * Add quotation mark
         */

        private static function QUOTATIONMARK(p:ByteArray):ByteArray {
            p.writeByte(0x22);
            return p;
        }

        /**
         * Add Double Dash
         */

        private static function DOUBLEDASH(p:ByteArray):ByteArray {
            p.writeShort(0x2d2d);
            return p;
        }

    }
}



한가지 중요한 정보를 언급하겠다.
URLLoader를 이용해 서버에 전송할때, 프로그램이 같은 도메인상에 있는 경우에는 보안문제가 없다. 하지만 다른 도메인에 위치한 서버로 이미지를 전송할때는 반드시 crossdomain.xml을 check해야한다.

1. Security.loadPolicyFile(http://다른도메인/crossdomain.xml); 를 URLLoader의 load()함수를 호출하기 전에 호출한다.

2. Flash Player 9.0.124.0 버전부터는 HTTP Header 보안취약점을 해결하기 위해서 cross domain 정책이 변경되었는데.... 서버측에 있는 crossdomain.xml에 allow-http-request-headers-from가 추가되어져야 한다. 이것은 HTTP 헤더 전송을 허용할지 결정해준다.

crossdomain.xml (Language : xml)
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
      <allow-access-from domain="*.jidolstar.com" />
      <allow-http-request-headers-from domain="*.jidolstar.com" headers="*"/>
</cross-domain-policy>

위 처럼 서버측 crossdomain.xml에 allow-http-request-headers-from을 추가함으로 다른 도메인간에 HTTP 헤더 전송을 허용할 수 있다.

서로 다른 도메인에 SWF와 서버측 코드가 배치되어 있다면 반드시 이 사실을 숙지하길 바란다.


3. Flash Player 10에서는 사용자 인터렉션이 반드시 필요하다.

다음글을 참고하세요.

http://blog.jidolstar.com/411 


 

4. 서버측 코드 작성

만약 위 3번 코드에서 var parameters:URLVariables를 아래와 같이 작성했다고 하자.

URLVariables 설정 (Language : java)
var parameters:URLVariables = new URLVariables();
parameters.method = "id";
parameters.userId = "2000321";


그럼 PHP 코드로 아래와 같은 방법처럼 만들면 되겠다.(테스트는 안해봤음)

PHP 코드 예제 (Language : php)
<?php
$method = $_POST['method'];
$userId = $_POST['userId'];
$file_temp = $_FILES['Filedata']['tmp_name'];
$file_name = $_FILES['Filedata']['name'];
$file_size = $_FILES['Filedata']['size'];

if( $method == "id" )
{
  $movePath = "/home/myPath/images/$userId_$file_name";
}
else
{
  $movePath = "/home/myPath/images/time_".time()."_".$file_name;
}

move_uploaded_file($file_temp,$movePath);

echo "save Complete";
?>


마지막 save Comple 메시지를 받기 위해서는 Flex의 Complete 이벤트 발생시 아래와 같은 방법으로 받으면 되겠다. 이것을 알아내는데도 많이 힘들었다. 일종의 팁이다. ^^;

데이타 받기 (Language : java)
var loader:URLLoader = URLLoader(event.target);
var bytedata:ByteArray = loader.data;
var strData:String = bytedata.toString();
trace( strData)


실제 업무에도 잘 이용하고 있다.
이미지 에디터 등을 만들때 아주아주 유용한 자료가 될 것이다.

누가 예제 프로그램 만들어서 트랙백 걸어 주시면 고맙겠다.


자료 출처 
http://marstonstudio.com/2007/08/19/how-to-take-a-snapshot-of-a-flash-movie-and-automatically-upload-the-jpg-to-a-server-in-three-easy-steps/


지돌스타 블로그내 참고자료
 - ImageSnapshot 클래스에 대해 : http://blog.jidolstar.com/301
 - FileReference의 UploadCompleteData 이벤트 : http://blog.jidolstar.com/324
 - 동영상 캡쳐 방법 : http://blog.jidolstar.com/215


글쓴이 : 지돌스타(http://blog.jidolstar.com/352)

CDS는 프랑스의 스트라스부르 관측소 데이터 센터(Centre de Données astronomiques de Strasbourg)에서 제공하는 천체목록 데이타 서비스이다. 여기서는 VizieR(카탈로그), Simbad(천체대상), Aladin(Sky atlas)를 기본해서 다양한 천체목록 서비스를 하고 있다. 이들에 대한 가이드문서를 참고하면 이용하는데 도움이 된다.

CDS는 학술적 내용의 접근에 용이하도록 되어 있기 때문에 일반인들보다는 천문학자들에게 매우 유용하다. 본인은 이곳을 통해 다양한 천체정보를 종종 얻어오곤 한다.

CDS와 같은 서비스가 있는 이유는 천문학에서 다루는 천체들을 정리한 목록과 이름이 정말 많기 때문이다. 같은 대상이라 하더라도 그것들을 정리한 목록의 수가 많기 때문이다. 가령, 안드로메다은하만 하더라도 M31, NGC 224, Andromeda Galaxy, UGC 454, PGC 2557, LEDA 2557(참고:Wikipedia) 등 정말 많다. 이렇게 엄청 많은 목록이 존재할 수 밖에 없었던 이유는 천체목록이 생성된 시기에 따른 기술적 진보가 있었기 때문이다. 여전히 지금도 수많은 천체대상이 많이 발견되고 있기 때문에 이러한 목록은 없어지지 않고 항상 현존하게 된다.

CDS에서 제공하고 있는 목록만 하더라도 엄청 많다. (참고 : Catalogues and files available at CDS) 은하, 성운/성단, 별의 단순 분류에 따른 목록도 있지만 학술적 목적에 의해 만들어진 목록도 꽤 된다는 것을 알 수 있다.

CDS에서는 많은 개발자들의 참여를 유도할 수 있는 서비스가 준비되어 있다.

이 개발자 코너를 통해 CDS에 제공하는 데이터를 다양한 형태로 가공해 다른 서비스를 만들 수 있다.

본인도 천체대상 검색이 필요했다. 사용자가 찾기 원하는 천체의 이름을 입력하면 그에 대한 정보를 얻어오는 기능이다. 그러나 엄청많은 이름의 천체목록을 다 검색하도록 만드는 것은 왠지 불가능해보인다. (언제 데이터베이스를 만들고 그들 의 연관 알고리즘을 언제 만들어!) CDS 개발자 코너에서 제공하고 있는 CDS XML Web Services는 이것을 가능하게 했다.

CDS XML Web Services : http://cds.u-strasbg.fr/cdsws.gml

이 서비스는 XML 형태로 SOAP(Simpe Object Access Protocal)을 이용해 서비스가 진행되며 Simbad, NED, VizieR에서 제공하는 천체목록을 검색해서 결과를 반환해준다.

위 서비스에서 Name Resolver 서비스가 있다.

Name Resolver는 천체이름을 입력하면 다른 천체이름과 천체에 대한 몇가지 이름정보를 반환해주는 역할을 한다.

현재 접근 주소는 4개가 있다. 그러므로 어느 한 주소로 접근하지 못하더라도 다른 주소로 접근하면 되겠다.


    위 주소로 접근해서 사용하는 예제도 CDS Name Resolver 서비스 문서에 매우 잘 나와 있으며 JavaPerl 예제도 있다.
     
    아래는 PHP 예제이다.

    <?php
    $wsdl_url = "http://cdsws.u-strasbg.fr/axis/services/Sesame?wsdl";
    $client = new SoapClient($wsdl_url);
    $tmp = $client->Sesame('M 31', 'x', true, "N");
    var_dump($tmp);
    ?>
    


    위 소스가 제대로 돌아가려면 php.ini 에 Soap가 동작할 수 있을 수 있게 모듈을 열어주어야 한다.  위와 같이 할 경우 M31 대상을 XML형태로 NED에서 찾는 것을 의미한다. XML 형태로 $temp에 저장되므로 별도로 XML 파서를 이용해야할 것이다. 한가지 예이긴 하지만 문서를 잘보면 사용방법을 익힐 수 있다. 읽어오는 방식도 XML뿐 아니라 HTML, String형태로도 반환하며 각종 옵션도 있으므로 꼼꼼히 챙겨보자. 단 deprecated로 표시된 것은 다음에 없어질 것이니 사용하지 말자. 위 형태 말고 다음처럼 쓸수도 있다.

    sesame("m31","x",true,"N"), sesame("m31","xi",false,"A"), sesame("m31","Hpi",true,"NS"), ...


    위 결과는 다음과 같다.


    <?xml version="1.0" encoding="UTF-8"?>
    <Sesame xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="http://vizier.u-strasbg.fr/xml/sesame_4.xsd">
    <Target option="N">
      <name>M 31</name>
      <!-- Q5133035 #1 -->
      <Resolver name="N=NED">
        <INFO>from cache</INFO>
        <otype>G</otype>
        <jpos>00:42:44.32 +41:16:08.5</jpos>
        <jradeg>010.6846833</jradeg>
        <jdedeg>+41.2690361</jdedeg>
        <refPos>1992ApJ...390L...9C</refPos>
        <errRAmas>375</errRAmas><errDEmas>375</errDEmas>
        <Vel><v>-300.09229</v><e>3.89730</e><r>1991RC3.9.C...0000d</r></Vel>
        <MType>SA(s)b</MType>
        <oname>MESSIER 031 </oname>
        <alias>NGC 0224</alias>
        <alias>Andromeda Galaxy</alias>
        <alias>UGC 00454</alias>
        <alias>CGCG 535-017</alias>
        <alias>CGCG 0040.0+4100</alias>
        <alias>MCG +07-02-016</alias>
        <alias>GIN 801</alias>
        <alias>B3 0040+409</alias>
        <alias>2MASX J00424433+4116074</alias>
        <alias>IRAS  00400+4059</alias>
        <alias>IRAS F00400+4059</alias>
        <alias>KTG 01C</alias>
        <alias>HOLM 017A</alias>
        <alias>PGC 002557</alias>
        <alias>UZC J004244.3+411608</alias>
        <alias>87GB 004002.2+405940</alias>
        <alias>87GB[BWE91] 0040+4059</alias>
        <alias>6C B004001.6+410004</alias>
        <alias>MY 0040+409A</alias>
        <alias>CXOM31 J004244.3+411608</alias>
        <alias>RX J0042.6+4115</alias>
        <alias>1RXS J004241.8+411535</alias>
        <alias>EXSS 0039.9+4059</alias>
        <alias>1H 0039+408</alias>
        <alias>1ES 0039+409</alias>
        <alias>XSS J00425+4102</alias>
        <alias>LGG 011:[G93] 001</alias>
        <alias>[PFJ93] 44</alias>
        <alias>[MHH96] J004241+411531</alias>
        <alias>[VCV2001] J004244.3+411610</alias>
        <alias>MESSIER 031:[KGP2002] r1-010</alias>
        <alias>MESSIER 031:[PFH2005] 321</alias>
        <alias>MESSIER 031:[VG2007] 001</alias>
        <alias>0039+408</alias>
        <alias>0040+4059</alias>
        <alias>LEDA 002557</alias>
        <nrefs>3044</nrefs>
      </Resolver>
    </Target>
    </Sesame>
    <!--- ====Done (2009-Dec-29,08:30:21z)==== -->
    


    결과에서 alias(별칭) 부분이 바로 검색한 M31의 다른 이름들이다. 정말 많다. ^^;
    간단히 위치정보(jpos, jradeg, jdedeg)도 알 수 있다.

    이를 이용해 다양한 정보를 제공하는 또 다른 천문학 관련 애플리케이션을 만들 수 있게 된다.
    혹시 지나가다가 이것을 이용해서 만든 프로그램이 있다면 알려주었으면 한다. ^^

    글쓴이 : 지돌스타(http://blog.jidolstar.com/641)


    지난 2009년 12월 18일에 프로젝트 코드명 Squiggly(스퀴글리)가 Prerelease판으로 업데이트 되었다.
    http://labs.adobe.com/technologies/squiggly/

    Squiggly는 Flash Player 10, AIR 1.5이상에서 동작한다. 이 엔진은 ActionScript 3.0 기반이며 영어로 문장에서 사용된 단어를 영어사전의 그것과 비교하여 단어의 철자를 체크한다. 이번 업데이트는 TLF(Adobe Text Layout Framework)를 기반으로 하는 Flex 4 Spark 컴포넌트를 지원하도록 했다. Squiggly는 Flex Builder, Flash Builder, Flash CS4등 다양한 툴에서 사용할 수 있다. 단지 아쉬운 것은 영어단어만 체크한다는 점이다.


    다음 페이지는 Squiggly 데모이다.
    http://labs.adobe.com/technologies/squiggly/demo/

    개발자는 언제든지 예제소스와 ASDoc을 아래 링크에서 받아볼 수 있다.


    이것을 쓸지 모르겠지만 아무튼 알아두고 있어 나쁠 것도 없으니....

    참고
    http://adnaru.com/242

    글쓴이 : 지돌스타(http://blog.jidolstar.com/640)

     


    Flash, Flex, AIR, ActionScript 프로젝트를 진행하면서 모든 코드를 손수 만들어 사용하는 것은 그다지 추천할 만한 일은 아니다. 왜냐하면 아무리 실력있는 개발자라도 프로젝트에 필요한 코드모음을 전부 만드는 것은 너무 큰 노력이 들뿐만 아니라 코드의 유지보수에도 매우 많은 시간을 허비할 수 있기 때문이다.

    Flash 기반 코드 라이브러리는 이미 수년간에 걸쳐 공개되어 있다. 조금만 노력을 기울이면 3D, 트윈, 물리엔진, 수학, 보안, 데이터관리, MVC,   as3corelib, Papervision3d, Away3d, Flex 컴포넌트, 각종 유틸등 ActionScript/Flex/AIR/Flash에 관련된 수십,수백가지 우량(?) 라이브러리들을 쉽게 사용할 수 있다.


    위에 링크되어 있는 라이브러리만 해도 정말 현업에 바로 사용할 수 있는 것들이 정말 많다. 또한 단순히 공개차원을 넘어 지속적으로 업데이트가 되며 완성도가 높아지고 있다.

    그럼 다시 생각해보자. 내가 3D 게임을 만들기 위해 3D 엔진을 만들어야할 것인가? 내가 트윈(tween)을 구현하기 위해 트윈엔진을 만들어야 할것인가? 각종유틸이 필요해서 그걸 다 내가 만들것인가? 만약 검증되지 않고 아직 별도로 공개되어 있지 않은 것이라면 당연히 만들어야할 것이다. 하지만 기본적으로 큰 노력없이 쉽게 사용할 수 있고 자동으로 업데이트 및 검증까지 해주는 사람들이 만든 훌륭한 라이브러리가 있는데... 왜!!! 그걸 다 만들 필요가 있는가이다.

    재미있게도 위에서 소개한 라이브러리들은 대부분 Google 코드(http://code.google.com)에 공개되어 있다. 이는 무엇을 의미하는가? 업데이트할때마다 압축파일을 받아 라이브러리에 복사하는 수고가 필요없이 SVN으로 간단하게 업데이트 할 수 있다는 것을 말한다. 이것을 적극 활용하는 방법을 소개하는 것이 이 글을 쓰는 목적이다.

    필자는 Flash 개발자라면 누구라도(?) 잘 알고 있는 Flash 3D 엔진인 Papervision3D과 Away3D를 SVN을 이용해 가져와 프로젝트에서 활용하는 방법을 언급하고자 한다. 개발환경은 Flash Builder 4를 기준으로 하겠다. (거의 모든 기능을 Flex Builder 3에서도 할 수 있다.)


    1. Flex/Flash Builder에 SVN 플러그인 설치
    이제부터 설명할 공개 라이브러리를 사용하기 위해 Flash builder에 SVN 플러그인을 설치해야한다. 방법은 어렵지 않다. 다음 링크를 참고한다.

    Flex Builder 에서 Subversion(SVN) 사용하기


    2. Google Code에서 Papervision3D SVN 주소 확인하기
    Google 코드 사이트(http://code.google.com)로 들어가 papervision3d로 검색해서 Papervison3D 구글코드 페이지(http://code.google.com/p/papervision3d/)로 들어간다. Papervision3d의 라이센스와 각종 링크들을 보면 다른 정보들도 확인할 수 있다.

    물론 화면 우측 Featured downloads에 있는 zip 및 swc 파일을 받아 직접 프로젝트에 복사해 사용할 수 있으나(많은 블로그에 이 방법을 소개하고 있다.) 앞서 말한데로 새로 업데이트되는 최신소스를 바로바로 사용해보려면 이 방법은 너무 불편하다. 게다가 공개 라이브러리를 사용하는게 4~5개 이상 넘어가면 지속적인 업데이트가 힘들다.

    어떤 개발자는 너무 최신 버전이면 버그가 있을지도 모르지 않겠느냐? 라고 의심할 수 있을 것이다. 하지만 Papervision3D와 같이 이미 대중적(?)으로 인기를 모으고 있는 라이브러리라면 철저한 검증없이 함부로 최신화(commit) 하지 않는다.


    위 Source 탭으로 들어가 Checkout 메뉴를 보면 아래처럼 SVN checkout 주소가 있다. 이 주소는 저장소의 주소라 불리운다. 여러분은 이 주소로 Papervision3D 소스 코드를 받아올 수 있겠다.




    3. SVN을 이용해 Papervision3D 라이브러리 프로젝트 만들기
    이제 본격적으로 Papervision3d를 SVN으로 받아와 라이브러리 프로젝트를 만들어보자.

    Flash Builder를 실행한다. 실행되면 메뉴에서 File > Import 로 들어간다. 아래 처럼 창이 뜨면 SVN > Checkout Projects from SVN 을 선택한다음 Next 버튼을 누른다.



    아래 처럼 보이면 새로운 저장소 위치를 만들기 위해 Create a new repository location을 선택후 Next 버튼을 누른다. 이는 새로운 저장소라는 것은 SVN을 접속할 주소를 의미한다고 보면 되겠다. 여기서는 Google 코드에서 봤던 주소 http://papervision3d.googlecode.com/svn/trunk 가 되겠다.

    아래처럼 나오면 주소를 입력하고 Next 버튼을 누른다.



    아래 화면처럼 나오면 http://papervision3d.googlecode.com/svn/trunk 저장소 내에 있는 Papervision3D 소스 경로를 볼 수 있다. 우리는 ActionScript 3.0 기반에서 제작하기 때문에 as3/trunk를 선택한다. 이때 선택하는 기준은 그 안에 src 폴더의 상위 폴더이어야 한다. 이 방식은 다른 라이브러리를 참조해서 하더라도 저장소내에 구조가 조금씩 다르지만 같은 방법을 사용하므로 기억하자.


    위 창에서 Next 버튼을 누르면 다음 창으로 전환된다. 세부적으로 Check out 하는 방법을 설정할 수 있는데 우리는 최신 버전을 사용할 것이기 때문에 바로 Finish 버튼을 누른다.



    이제 Papervison3D 라이브러리를 가져올 SVN 주소 설정을 끝냈으므로 이 라이브러리가 담길 프로젝트를 생성해야한다. 위 절차를 모두 끝내면 자동적으로 다음과 같이 New Project 창이 뜬다. 이때 Flex Library Project를 선택하면 되겠다.


    다음 그림처럼 Project name을 Papervision3D(이름은 아무거나 써도 상관없다.)으로 설정하고 Flex SDK Version을 선택한 다음 Finish버튼을 누른다.


    참고로 Flex SDK Version을 선택시에 라이브러리가 AIR 코드가 포함되어 있는 경우라면 Include Adobe AIR libraries를 선택해야한다.(예: as3corelib) 또한 라이브러리가 구동되기 위한 SDK가 무엇인지도 알아야한다. 가령 Flash Player 10부터 3D API를 사용한 라이브러리라면 그것을 지원하는 SDK를 사용해야한다는 것을 의미한다. SDK는 Adobe Labs에서 최신버전을 받아 사용하면 된다.(방법을 모르시면 댓글을 ^^)

    아래처럼 Confirm Overwrite 메시지가 뜨면 그냥 OK 버튼을 누른다.



    이제 구글 코드 저장소로부터 아래처럼 Papervision 3D 코드를 받아온다.


    Package Explorer에 추가된 Papervion3D 라이브러리 프로젝트를 확인하자. 하지만 만약 에러메시지가 나온다면 다음과 같이 해보자. 해당 프로젝트를 선택하고 오른쪽 마우스 버튼을 눌러 컨텍스트 메뉴에서 Properties로 들어가보자. 아래와 같은 창이 나오면 왼쪽 메뉴에서 Flex Library Build Path를 누르고 우측 상단 탭에 Classes를 선택한다. 그리고 Select classes to include in the library를 선택한 다음 src 앞에 check박스를 체크한다. 그리고 OK 버튼을 누르면 다시 컴파일 되면서 컴파일 에러가 사라질 것이다.



    여러분은 이제 최신버전의 Papervision3D를 사용할 수 있게 되었다. 아래처럼 해당 프로젝트를 선택하고 오른쪽 마우스 버튼을 눌러 컨텍스트 메뉴에서 Team > Update to Head를 선택하는 것만으로 최신버전으로 업데이트 할 수 있게 된다. 이러한 방법은 SVN으로 공유한 다른 라이브러리들을 항상 최신으로 유지하는데 쉬운 방법을 제공하게 된다.


    한가지 언급하자면 위에서 소개하는 방법은 Papervision3D를 예로 들었기 때문에 다른 라이브러리의 공개 형태에 따라 수행하는 방법의 차이가 있을 수 있다는 점을 강조하고 싶다. 그러한 점은 개발자가 알아서 체크할 수 있어야 한다.


    4. 조금만 더 가다듬어 보기 (관심있는 사람만 읽으면 됨)
    3번까지 진행하는 것만으로도 개발하는데 전혀 문제없지만 아래 내용을 좀더 알고 가면 더욱 좋을 듯 싶다.

    지금까지 구글 코드로부터 SVN 저장소를 통해 받아온 Papervison3d 라이브러리는 설정파일을 제외한 ActionScript 3.0 코드가 대부분이다. 그래서 실질적으로 프로젝트에 필요한 설정구성요소인 .actionScriptProperties나 .flexLibProperties등의 파일들은 Papervision3D 개발자가 처음부터 저장소에 공유하지 않는다. 왜냐하면 .actionScriptProperties, .flexLibProperties들은 Flex Builder나 Flash Builder와 같이 특정 개발툴에서 필요한 파일이기 때문에 다른 IDE에서는 무의미 할 수 있기 때문이다.

    아래 처럼 Papervision3D의 프로젝트 이름에 SVN으로 공유되었으나 폴더그림에 * 표시가 되어 있다. 이 표시는 원래 SVN 저장소 내용과 프로젝트 내용이 다를때 나온다. 이 다른 내용이라는 것은 금방 언급했던 .actionScriptProperties, .flexLibProperties 등이다.


    Flash Builder에서는 프로젝트내의 .actionScriptProperties, .flexLibProperties 파일을 직접 볼 수 없도록 되어 있다. 이는 Filter 처리가 되어 있기 때문이다. 아래 그림처럼 선택해 Filter 처리된 파일들을 전부 check out하자.



    위 그림 처럼 Filter 처리된 파일을 check out하면 아래처럼 프로젝트 내에 .actionScriptProperties, .flexLibProperties 등을 볼 수 있게 된다. 이 파일의 아이콘에 ?로 표시된 것은 저장소에 이 파일이 존재하지 않음을 나타내는 것이다.

    * 표시나 ? 표시를 없애기 위해서 이러한 파일들이 SVN 저장소와 공유되지 않도록 무시할 수 있도록 하면 된다. Flash Builder에 설치된 SVN 플러그인은 이러한 기능을 지원해준다. 메뉴에서 window > Preference로 들어가 창이 뜨면 Team > Ignored Resources로 들어가면 아래 처럼 나온다. 여기에 다음 내용을 Add Pattern하자.

    .actionScriptProject
    .actionScriptProperties
    .flexProperties
    .flexProject
    .project
    bin
    .settings
    참고 : http://labs.adobe.com/wiki/index.php/Flex_Builder:resources:plugins:subversion:filtering




    이제 아래 그림처럼 * 표시나 ? 표시가 없어지고 저장소의 원본과 프로젝트의 소스가 일치함을 나타내는 표시로 바뀐다.




    5. Away3D를 SVN으로 받아오기

    Papervision3D 라이브러리를 이용해서 애플리케이션을 만드는 예제는 굳이 설명안해도 된다고 생각한다. 대신 Away3D를 활용하는 방법을 약간 언급하고자 한다.

    Away3D도 Papervision3D처럼 구글코드(http://code.google.com/p/away3d/)를 통해 SVN으로 소스를 공개하고 있다. 하지만 Away3D는 Papervision3D와 다르게 Flash Player 9, Flash Player 10 버전을 함께 제공하면서 Flash Player 10 3D 라이브러리를 이용한 3D 엔진인 Away3DLite도 제공한다. 또한 몇가지 유용한 예제도 함께 제공하고 있다.


    Away3D 저장소 주소(http://away3d.googlecode.com/svn/trunk)로 접속하면 아래처럼 나온다. fp9과 fp10은 각각 Flash Player 9, 10 버전을 의미한다. 아무래도 Flash Player 10 버전이 최신이면서 효율적으로 만들어졌으므로 fp10에 있는 공개 라이브러리와 예제소스를 참고할 것이다.

    fp10에 보면 Away3D, Away3DLite 소스가 있음을 확인할 수 있다. 또한 그에 대한 예제는 Examples에 들어 있다.


    아래는 해당 라이브러리와 예제의 저장소 주소이다.


    여러분은 Papervision3D를 SVN으로 프로젝트를 만든 것처럼 Away3D에서도 그래도 하면 된다. 단. Examples의 경우 중간에 프로젝트 생성시 ActionScript 프로젝트를 선택하기 바란다. 물론 Flash Player 10 기준이라는 점도 명심하자.

    예제 프로젝트에서는 해당 라이브러리 프로젝트의 경로를 포함시켜줘야한다. Away3D_Example은 Away3D 라이브러리를 Away3DLite_Examples는 Away3dLite 라이브러리를 포함해야한다. 방법은 간단하다. Away3D_Example와 Away3DLite_Examples내에 libs 폴더안에 각각의 라이브러리 프로젝트에서 만들어진 bin디렉토리에 있는 
    swc를 복사하면 된다. 하지만 이는 애플리케이션을 전부 만들고 배포하고 난 다음에 백업을 위해 선택할 방법이고 실제로 개발할때는 다음과 같이 한다.

    1. Away3D_Example 프로젝트를 선택한다.
    2. 메뉴에서 Project > Properties를 선택한다.
    3. 창이 뜨면 좌측 ActionScript Build Path(Flex의 경우 Flex Build Path)를 선택한다.
    4. 상단 Library path 탭을 선택한다.
    5. Add Project를 선택한 다음 Away3D 라이브러리를 선택한다.
    6. OK 버튼을 누른다.
    7. Away3D_Example의 .actionScriptProperties를 연다.
    아래 그림처럼  default package에 있는 소스 들은 전부 애플리케이션들이다. 이 코드들이 진정한 애플리케이션으로서 동작하려면 .actionScriptProperties내에 application으로 등록해야한다. 수작업이긴 해도 전부 등록하자. 또는 해당 as 파일을 선택후 마우스 오른쪽 버튼을 눌러 Set as Default Application 항목을 선택해도 된다. 이는 애플리케이션으로 등록하는 동시에 Default 애플리케이션으로 잡아준다. Default 애플리케이션을 지칭하는 것은 .actionScriptProperties 파일 내에 actionScriptProperties 태그의 mainApplicationPath 속성이 그 역할을 한다.



    위 처럼 설정하면 이제 예제 파일들을 아무 문제없이 동작시킬 수 있을 것이다.
    8. Away3DLite_Example도 위와 비슷한 방법으로 하면 되겠다.

    아래는 Away3DLite_Examples에 있는 Advanced_Mario1000.as 애플리케이션을 실행하는 화면이다.

    아마 어떤 애플리케이션은 as3dmod 라이브러리가 필요하다는 에러가 나올것이다. 이것도 비슷한 방법으로 Google Code에서 가져올 수 있다. 단, 몇가지 다른 설정을 해야하는데 여기서 설명하지 않고 각자의 몫으로 돌리겠다.


    정리하며

    개발자라면 Google Code와 친해져야하지 않을까? 또한 기존 라이브러리가 있는가 검색을 해보는 성의가 있어야하지 않을까? 그리고 그 라이브러리를 잘 활용하는 방법을 스스로 깨달으려고 노력해야하지 않을까?

    잘못된 사항이나 더 좋은 정보가 있다면 얼마든지 댓글이나 트랙백 부탁한다.


    참고
    bs-papervision : http://code.google.com/p/bs-papervision/ 
    as3dmod : http://code.google.com/p/as3dmod/
    sandy3d : http://www.flashsandy.org 
    alternativa3D : http://alternativaplatform.com/en/alternativa3d/

    글쓴이 : 지돌스타(http://blog.jidolstar.com/639)




    싸이월드의 미니홈피는 수년간 국내에서 가장 영향력 있는 SNS(소셜 네트워크 서비스)로 자리매김했다. 하지만 나와 일촌만의 놀이터로 미니홈피는 굳어져 가고 있다는 평가를 받고 있다.

    개방과 공유를 핵심가치로 여기는 web 2.0이 전 세계를 강타하는 현재, 개인의 콘텐츠는 사람과 사람을 연결하는 새로운 소통의 도구로 사용되고 있으며 기술적으로도 많은 발전이 일어났다.

    블로그는 RSS(새로운 글이 작성될 경우 직접 방문하지 않더라도 자동으로 배달)를 기본 기능으로 가지고 있으며 해외의 대형 서비스들은 자사의 핵심기능을 오픈하고 타 서비스와의 공생을 선택했다.

    국내의 대형 포털 서비스 역시 세계적 흐름에 발맞춰 자사의 서비스를 속속 개방하고 있으며 국내 블로그 서비스 중 가장 많은 사용자를 보유한 네이버 블로그 역시 점진적으로 서비스 오픈 정책을 펼쳐가고 있다.

    최근 싸이월드의 이용률이 주춤하는 동안 그 뒤를 이을 새로운 서비스들이 속속 생겨나고 있다. 그 중 ‘스타플’ 서비스(http://starpl.com)에서는 미니홈피에서 자신의 글을 스타플로 옮길 수 있는 기능을 최근 오픈했다. 스타플은 사용자에게 실제로 존재하는 ‘별’을 개인공간으로 주고 자신의 별에 있는 타임라인에 일대기를 담을 수 있는 감성적인 서비스를 제공한다.

    스타플의 한 관계자는 “스타플이 개인의 일대기를 담을 수 있는 타임라인을 제공하기 때문에 미니홈피의 글을 옮겨오고 싶다는 사용자 의견이 많았다”며 “그래서 미니홈피의 글을 나의 별에 쉽게 담을 수 있는 기능을 개발하게 됐다”고 기능 개발에 대한 이유를 설명했다.

    이번 스타플에서 제공하는 ‘외부글 가져오기’ 기능은 사용자 미니홈피의 사진첩, 다이어리, 게시판 등의 거의 모든 글을 옮겨 올 수 있는 기능을 제공하며 자신의 타임라인에 시간순으로 담기게 되어 외부 서비스의 글을 이용해 손쉽게 자신의 일대기를 강화할 수 있게 됐다.

    또한 스타플에서는 앞으로 다양한 블로그 서비스의 글도 담아올 수 있도록 기능을 확대하여 제공할 예정이다.

    출처 : http://news.cnbnews.com/category/read.html?bcode=97512

    스타플 관련 다른 글
    [현장에서] 다음 세대를 위한 웹개방화 정책
    `제2의 트위터` 꿈꾼다
    새로운 스타플 업데이트 소식입니다!


    위에서 보여지는 캡쳐화면은 일본 국립천문대 다운로드 페이지(http://www.nao.ac.jp/download/index.html)의 일부이다. 해당 화면은 달을 종이로 만들 수 있도록 PDF 파일로 제공하고 있다. 국가차원에서 이렇게 재미있는 자료를 공개하고 있어 매우 좋아보였다.

    이곳 말고도 다양한 망원경 종이 공예를 할 수 있는 자료도 받을 수 있다.
    http://www.nro.nao.ac.jp/alma/J/outreach/papermodel.html 

    참고할 만한 자료
    일본 문부과학성에서 제공하는 우주도(宇宙圖)
    Flash로 구동되는 천문프로그램, 천문노트 별자리판(ver20070123)

    글쓴이 : 지돌스타(http://blog.jidolstar.com/637)

    일본 문부과학성에서 제공하고 있는 우주도 포스터이다. 인간을 중심으로하는 우주의 탄생부터 시작해 현재우주까지 하나의 포스터에 매우 자세하게 보여주고 있다.

    아래 링크로부터 다운로드 받을 수 있다.

    http://www.mext.go.jp/a_menu/kagaku/week/uchuu/001.zip

    만약 다운로드 받지 못하면 아래 링크에서 다운로드 받길 바란다.



    한국어로된 이런 멋진 포스터 안나오나?

    글쓴이 : 지돌스타(http://blog.jidolstar.com/636)



    예전에 wonderfl.net에 대해서 소개한바 있다. 여기서 소개하는 Beautifl.netWonderfl.net에서 올라온 엄선된 아름다운(?) ActionScript/Flex 컨텐츠를 목록화 해서 쉽게 볼 수 있도록 해주는 서비스이다.

    Beautifl.net은 적절히 분류(3D, Effect, Physics, Game, Sound등)가 되어 있어 관심있는 영역의 Flash 컨텐츠도 감상할 수 있겠다. PV, Pick, New 순으로 정렬도 되기 때문에 이 또한 유용하다. 또한 좋은 컨텐츠가 있다면 누구든지 추천할 수 있으며 RSS feed로도 구독할 수 있다. 

    Flash로 이런 것도 만들 수 있구나를 넘어 실제 코드도 볼 수 있기 때문에 Flash 개발자들의 스킬업을 위해 이보다 좋은 것도 없을 것 같다.

    Wonderfl.netBeautifl.net을 통해 멋진 Flash 세계를 감상해보자.

    참고로 이 둘 사이트는 모두 일본에서 만들어졌다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com/634)


    Flash 엔터프라이즈 개발 업체를 제외하고는 플래시 개발자가 2~3명인 업체가 대부분입니다. 게다가 Flex/AIR등에 처음 접하는 경우도 다분하죠. 결국 Flash 프로젝트의 실무를 어찌 진행할 것인가 고민이 되는 것이 현실입니다. 그래서 도움이 될만한 글들을 모아봤습니다.

    http://opencast.naver.com/FL188/15

    이 캐스트는 베스트 No.로 12월 10일에 추천되었습니다. ^^
    http://opencast.naver.com/home/bestNo.nhn?exposureDate=20091210 

    글쓴이 : 지돌스타(http://blog.jidolstar.com/633)

    지난 2009년 11월 어도비(Adobe)에서는 AIR 2.0 Beta를 새롭게 발표하면서 퍼포먼스 향상과 더 많은 OS의 자원을 사용할 수 있는 API 기능을 추가해서 발표했다. Flash Player 10.1 Prerelease와 함께 발표된 Adobe AIR 2.0의 새로운 기능 및 추가/개선 사항에 대해서 정리해보았다.

    참고로 이 글은 Elad Elrom의 AIR 2 Enhancements Complete Overview  와 AIR 2 Rerelease Note를 참고해서 정리한 것이다.




    1. 새로운 기능 (이번 페이지에서 다룸)
      - File Promises
      - Screen reader
      - Native Processes
      - New networking support
      - Global Error Handling
      - Packaging an AIR application in a native installer
    2. 기존 API에 대한 추가된 기능 (다음 편에 다룸)
      - Max Size of NativeWindow
      - Idle time-out
      - Mac Vector Printing
      - Database Transaction Savepoints
      - Microphone Access API
      - Open file with Default Application
    3. 플랫폼 인식 관련 API (다음 편에 다룸)
      - Multi-touch functionality
      - Mass Storage Device Detection
    4. 퍼포먼스 향상 (다음 편에 다룸)


    출처 : http://tinyurl.com/ydxevnu




    Flash Builder 4 beta 2에서 Adobe AIR 2.0로 개발 환경 만들기 

    여기에서 보여지는 예제는 모두 Flash Builder 4 beta 2에 기본으로 설치된 Flex 4 SDK를 이용한다. 하지만 AIR 2.0로 테스트해보기 위해서는 AIR 2.0 SDK를 따로 받아 다음과 같은 과정이 필요하다.

    Windows 환경이라면 다음과 같이 하면 되겠다.

  • 만약 Flash Builder를 설치 안했다면 다음 링크를 통해 받는다.
    1. AIR 2.0 SDK와 Runtime을 다운로드 받는다.
    2. 다운받은 Runtime을 실행해 설치한다.
    3. Flash builder가 설치된 sdks/4.0.0과 sdks/3.4.1 폴더를 복사해서 sdks 폴더안에 붙인뒤 4.0.0_AIR2.0, 3.4.1_AIR2.0을 만든다. 이렇게 되면 sdks 폴더안에는 3.4.1, 4.0.0, 3.4.1_AIR2.0, 4.0.0_AIR2.0 이름을 가진 4개의 SDK가 만들어진다.
      본인의 경우는 C:/Program Files/Adobe/Adobe Flash Builder Plug-in Beta 2/sdks 내에 있다. 
    4. AIR 2.0 SDK는 압축을 풀고 그안에 있는 내용을 방금 만든 4.0.0_AIR2.0, 3.4.1_AIR2.0 폴더에 복사한다.
    5. Flash Builder를 실행한다.
    6. 메뉴에서 Window > Preference 로 들어간다.
    7. 창이 뜨면 왼쪽 메뉴에서 Flash builder > Installed Flex SDKs를 선택한다.
    8. 우측에 Add버튼을 눌러 위에서 새로 만든 AIR 2.0을 위한 SDKs들을 선택한다.  
      Flex SDK Name은 각각 Flex 3.4 AIR 2.0, Flex 4.0 AIR 2.0 등으로 이름을 바꿔도 된다.
      본인의 경우 아래 경로가 되겠다.
      C:/Program Files/Adobe/Adobe Flash Builder Plug-in Beta 2/sdks/3.4.1_AIR2.0/
      C:/Program Files/Adobe/Adobe Flash Builder Plug-in Beta 2/sdks/4.0.0_AIR2.0/
      아래처럼 Flex 4.0으로 체크되어 있다면 AIR 2.0기반의 Flex 4.0 SDK를 디폴트로 바꾸자.
    9. 이제 모든 개발환경이 완료되었다. 자신의 프로젝트 생성시에 원하는 SDK를 선택하면 되겠다.  물론 아래에서 소개하는 예시를 따르려면 Flex 3든 Flex 4든 AIR 2.0 SDK를 선택해야겠다.


    Mac 환경이라면 다음과 같이 하면 되겠다.

    Mac에서 AIR 2.0 Beta 개발환경 만들기 - Flash Builder 4 Beta 2 기반

    참고로 Flash CS4나 CS3, 드림위버나 기타 다른 IDE에서 개발하시는 분들에게는 본인의 경험이 없어 정보를 줄 수 없다.

  •  
    1. Adobe AIR 2.0 새로운 기능

    1.1 IPv6 지원
    AIR 2.0은 모든 Network API에 대해서 IPv6를 지원하게 되었다. 현재 인터넷에서 주로 사용하고 있는 IP 주소의 버전은 IPv4로서 무려 20년이 넘도록 사용하고 있다. IPv4는 32비트 주소체계를 가지며 4,294,967,296개의 제한된 갯수의 주소를 가진다. IPv6는 이 부족함을 해소하기 위해 만들어진 IP 주소 체계이다. IPv6는 128비트 주소체계를 가진다. IPv6가 일반적으로 사용되고 있는 것은 아니지만  가까운 미래에 IPv6가 범용적으로 도입되어 네트워크 IP 주소체계가 더욱 확장될 수 있으므로 미리 지원될 수 있도록 한 것으로 여겨진다. IPv6에 대한 자세한 내용은 아래 링크를 참고한다.




    1.2  UDP(User Datagram Protocol) 소켓, Server 소켓, TLS/SSL 소켓 지원
    AIR 2.0에서는 더욱 다양한 소켓 통신 관련 클래스들이 추가되었다. 이전버전까지 반쪽짜리 였다면 이제 어느정도 구색을 갖춘듯 하다. 

    AIR 2.0에서 지원하는 네트워크에 관련된 내용은 다음 링크를 참고한다.


    서버 소켓 : ServerSocket 클래스 
    기존 AIR 버전에서는 반쪽 소켓통신만 지원되었다. 즉, 클라이언트는 만들 수 있었지만 서버측을 만들지 못했다. 이번 AIR 2.0에서 서버 소켓이 지원됨에 따라 이를 이용한 다양한 네트워크 애플리케이션을 만들 수 있을 것으로 보인다.

    서버 소켓은 TCP(Transmissin Control Protocal)을 기반으로 한다. TCP는 연결지향 통신 규약으로 인터넷 상의 컴퓨터들 사이에 데이터를 통신하기 위해 IP와 함께 사용된다. TCP는 데이터의 통신 중간에 데이터가 유실되더라도 신뢰도를  체크해서 완벽한 데이터를 통신할 수 있는 신뢰형 기반의 데이터 통신시 사용된다. 일반적인 웹브라우져를 이용한 통신데이터는 모두 TCP를 근간으로 한다.



    AIR 2.0에서 지원하는 서버 Socket는 ServerSocket 클래스에서 관장한다.



    UDP 소켓 : DatagramSocket 클래스
    UDP는 User Datagram Protocal의 약어로 IP를 사용해 네트워크내에서 컴퓨터들간에 데이타 통신하기 위해 제한된 서비스만 제공하는 통신 규칙이다. TCP의 대안이기도 하며 IP와 함께 사용하면 UDP/IP라고 표현하기도 한다. UDP는 정확한 데이터를 전송할 의무가 주어지지 않은 비연결지향성을 가져 데이터의 흐름에 신뢰를 할 수 없는 것이 특징이다. TCP처럼 통신을 하기전 상대방을 확인하는 절차가 없기 때문에 데이터 정확도는 떨어질 수 있지만 빠른 데이타 전송에 유리하다. 그래서 음악 및 비디오 실시간 스트리밍 서비스에 잘 사용하는 프로토콜이다.



    AIR 2.0에서는 UDP 통신을 DatagramSocket 클래스와 DatagramSocketDataEvent 클래스를 지원하고 있다. 더욱 자세한 내용은 아래 링크를 참고한다.



    Secure 클라이언트 소켓 : SecureSocket 클래스 
    소켓서버에 접속하기 위해 SSLv4(Secure Socket Layer version 4) 또는 TLSv1(Transport Layer Security version 1) 기반으로 하는 프로토콜을 이용하여 SecureSocket 클래스로 접속할 수 있다. Secure 소켓을 이용하면 암호화된 데이터 전송으로 서버인증(server authentication), 데이터 무결성(data integrity), 메시지 기밀성(message confidentiality)등의 장점을 가진다. 




    1.3 네트워크 정보 
    AIR 2.0의 API로 네트워크 정보를 얻어낼 수 있는 클래스인 NetworkInfo가 추가되었다. 이 클래스를 이용하면 사용자의 기기의 Mac Address, IP정보 등의 네트워크 정보를 얻어올 수 있다.


    Adobe 문서에서 보여지는 예제에서는 var networkInfo:NetworkInfo = new NetworkInfo() 처럼 생성해서 사용하도록 되어 있는데 잘못된 것이다. var networkInfo:NetworkInfo = NetworkInfo.networkInfo; 처럼 static 속성으로 접근해야한다.
    그리고 NetworkInterface에 networkInterfaceDefault 속성 또한 존재하지 않는다.  
    package { 
    	import flash.display.Sprite; 
    	import flash.net.InterfaceAddress; 
    	import flash.net.NetworkInfo; 
    	import flash.net.NetworkInterface; 
    	
    	public class NetworkInformationExample extends Sprite 
    	{ 
    		public function NetworkInformationExample() 
    		{ 
    			var networkInfo:NetworkInfo.<NetworkInterface> = NetworkInfo.networkInfo;
    			var interfaces:Vector.<NetworkInterface> = networkInfo.findInterfaces(); 
    			
    			if( interfaces != null ) 
    			{ 
    				trace( "Interface count: " + interfaces.length ); 
    				for each ( var interfaceObj:NetworkInterface in interfaces ) 
    				{ 
    					trace( "\nname: "             + interfaceObj.name ); 
    					trace( "display name: "     + interfaceObj.displayName ); 
    					trace( "mtu: "                 + interfaceObj.mtu ); 
    					trace( "active?: "             + interfaceObj.active ); 
    					//trace( "default?: "         + interfaceObj.networkInterfaceDefault ); 
    					trace( "parent interface: " + interfaceObj.parent ); 
    					trace( "hardware address: " + interfaceObj.hardwareAddress ); 
    					if( interfaceObj.subInterfaces != null ) 
    					{ 
    						trace( "# subinterfaces: " + interfaceObj.subInterfaces.length ); 
    					} 
    					trace("# addresses: "     + interfaceObj.addresses.length ); 
    					for each ( var address:InterfaceAddress in interfaceObj.addresses ) 
    					{ 
    						trace( "  type: "           + address.ipVersion ); 
    						trace( "  address: "         + address.address ); 
    						trace( "  broadcast: "         + address.broadcast ); 
    						trace( "  prefix length: "     + address.prefixLength ); 
    					} 
    				}             
    			} 
    		}     
    	} 
    }
    

     
    위 프로그램은 NetworkInfo를 사용하는 아주 간단한 예시이다. Flash Builder 4 에서 이 예시를 테스트해볼때는 AIR 프로젝트 생성시 마지막에 mxml대신 as로 확장자를 두고 생성해서 테스트 하면 되겠다.


    1.4 DNS lookup
    AIR 2.0에서부터 DNSResolver 클래스를 제공하여 DNS(Domain Name System) 정보를 찾을 수 있게 되었다. 정보를 찾게되면 DNSResolverEvent가 발생한다. 

    DNS는 각 도메인 이름(가령 examples.com)을 IP주소로 가리킨다. 일반적으로 사람은 111.123.431.11와 같은 IP주소보다 examples.com처럼 도메인 이름과 같은 단어조합을 더욱 인지하기 쉽다. 이러한 이유로 실제로는 도메인 이름을 사용하면 DNS를 걸쳐 이에 해당하는 IP주소로 연결시켜 관련 서버로 접속하게 한다. AIR 2.0에서 제공하는 DNSResolver 클래스를 이용하면 이러한 관계를 조사해볼 수 있게 된다. 

    IP주소는 IPv4(32비트) 또는 IPv6(128비트)를 사용할 수 있다. 

    DNSResolver 클래스의 lookup() 함수를 통해 host이름과 레코드 형태(record type)을 넘겨주면 DNSResolverEvent.LOOK_UP 이벤트를 발생하여 관련 레코드 타입에 대한 정보를 반환해준다. 
     



     다음은 예제이다.
    package
    {
    	import flash.desktop.NativeApplication;
    	import flash.display.Sprite;
    	import flash.events.DNSResolverEvent;
    	import flash.events.ErrorEvent;
    	import flash.net.dns.AAAARecord;
    	import flash.net.dns.ARecord;
    	import flash.net.dns.DNSResolver;
    	import flash.net.dns.MXRecord;
    	import flash.net.dns.PTRRecord;
    	import flash.net.dns.SRVRecord;
    	import flash.utils.getQualifiedClassName;
    	
    	public class DNSResolverExample extends Sprite
    	{
    		private var lookupCount:int = 0;
    		public function DNSResolverExample()
    		{
    			//Create the resolver object
    			var resolver:DNSResolver = new DNSResolver();
    			resolver.addEventListener( DNSResolverEvent.LOOKUP, lookupComplete );
    			resolver.addEventListener( ErrorEvent.ERROR, lookupError );
    			
    			//Look up records
    			resolver.lookup( "yahoo.com", ARecord );
    			resolver.lookup( "yahoo.com", AAAARecord );
    			resolver.lookup( "yahoo.com", MXRecord );
    			resolver.lookup( "209.191.93.53", PTRRecord );
    			resolver.lookup( "_sip._tcp.yahoo.com.", SRVRecord );
    		}
    		
    		private function lookupComplete( event:DNSResolverEvent ):void
    		{
    			trace("-------------------------------------");
    			trace( "Query string: " + event.host );
    			trace( "Record type: " +  flash.utils.getQualifiedClassName( event.resourceRecords[0] ) + 
    				", count: " + event.resourceRecords.length );
    			for each( var record:* in event.resourceRecords )
    			{
    				if( record is ARecord ) trace( record.name + " : " + record.address );
    				if( record is AAAARecord ) trace( record.name + " : " + record.address );
    				if( record is MXRecord ) trace( record.name + " : " + record.exchange + ", " + record.preference );
    				if( record is PTRRecord ) trace( record.name + " : " + record.ptrdName );
    				if( record is SRVRecord ) trace( record.name + " : " + record.target + ", " + record.port +
    					", " + record.priority + ", " + record.weight );
    			}    
    			if( ++lookupCount == 5 )
    			{
    				NativeApplication.nativeApplication.exit();
    			}
    		}
    		
    		private function lookupError( error:ErrorEvent ):void
    		{
    			trace("-------------------------------------");
    			trace("Error: " + error.text );
    			if( ++lookupCount == 5 )
    			{
    				NativeApplication.nativeApplication.exit();
    			}
    		}
    	}
    }
    



    1.5 스크린 리더 지원(Screen Reader Support)

    AIR 2.0은 스크린 리더를 지원하기 위한 Accessibility 클래스가 추가되었다. 스크린 리더는 시각 장애 사용자들을 위한 보조 기술로서 화면 내용을 오디오 버전으로 제공하는 것이다. 이 클래스는 현재 Windows 환경에서만 제공된다. 이것이 지원되는지 확인하려면 Capabilities.hasAccessibility 정적속성이 true이어야 한다. 버튼, 무비클립, 텍스트필드와 같은 객체에 액세스 가능한 속성을 얻고 설정하기 위해 DisplayObject.accessibilityProperties 속성을 사용하며 이 값이 바뀌었다는 것을 알려주기 위해 Accessibility.updateProperties() 정적메소드를 이용한다.

    이 기능을 사용하기 위해 프로젝트의 컴파일 옵션을 추가해야한다. Flash builder 4에서 해당 프로젝트를 선택후 메뉴의 Project > Properties를 선택한다. 새창이 나오면 Flex Compiler를 선택후 컴파일 옵션에 Generate accessible SWF file을 선택후 OK 버튼을 누른다.


    package {
    	import flash.accessibility.Accessibility;
    	import flash.desktop.NativeApplication;
    	import flash.display.Sprite;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	import flash.utils.setTimeout;
    	
    	public class AccessibilityExample extends Sprite {
    		public static const BUTTON_WIDTH:uint = 90;
    		public static const BUTTON_HEIGHT:uint = 20;
    		
    		private var gutter:uint = 5;
    		private var menuLabels:Array = new Array("PROJECTS", "PORTFOLIO", "CONTACT");
    		private var menuDescriptions:Array = new Array("Learn more about our projects"
    			, "See our portfolio"
    			, "Get in touch with our team");
    		
    		public function AccessibilityExample() {
    			configureAssets();
    			NativeApplication.nativeApplication.autoExit = true;
    			stage.align = StageAlign.TOP_LEFT;
    			stage.scaleMode = StageScaleMode.NO_SCALE;
    			stage.nativeWindow.activate();
    			setTimeout(updateAccessibility, 2000); 
    		}
    		
    		private function updateAccessibility():void {
    			trace("Accessibility.active: " + Accessibility.active);
    			if(Accessibility.active) {
    				Accessibility.updateProperties();
    			}
    		}
    		
    		private function configureAssets():void {
    			var child:CustomAccessibleButton;
    			for(var i:uint; i < menuLabels.length; i++) {
    				child = new CustomAccessibleButton();
    				child.y = (numChildren * (BUTTON_HEIGHT + gutter));
    				child.setLabel(menuLabels[i]);
    				child.setDescription(menuDescriptions[i]);
    				addChild(child);
    			}
    		}
    	}
    }
    
    import flash.accessibility.AccessibilityProperties;
    import flash.display.Shape;
    import flash.display.SimpleButton;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.text.TextFormat;
    import flash.text.TextFormatAlign;
    import flash.text.TextField;
    
    class CustomAccessibleButton extends Sprite {
    	private var button:SimpleButton;
    	private var label:TextField;
    	private var description:String;
    	private var _name:String;
    	
    	public function CustomAccessibleButton(_width:uint = 0, _height:uint = 0) {
    		_width = (_width == 0) ? AccessibilityExample.BUTTON_WIDTH : _width;
    		_height = (_height == 0) ? AccessibilityExample.BUTTON_HEIGHT : _height;
    		
    		button = buildButton(_width, _height);
    		label = buildLabel(_width, _height);
    		
    		addEventListener(Event.ADDED, addedHandler);
    	}
    	
    	private function addedHandler(event:Event):void {
    		trace("addedHandler: " + this._name);
    		var accessProps:AccessibilityProperties = new AccessibilityProperties();
    		accessProps.name = this._name;
    		accessProps.description = description;
    		accessibilityProperties = accessProps;
    		removeEventListener(Event.ADDED, addedHandler);
    	}
    	
    	private function buildButton(_width:uint, _height:uint):SimpleButton {
    		var child:SimpleButton = new CustomSimpleButton(_width, _height);
    		addChild(child);
    		return child;
    	}
    	
    	private function buildLabel(_width:uint, _height:uint):TextField {
    		var format:TextFormat = new TextFormat();
    		format.font = "Verdana";
    		format.size = 11;
    		format.color = 0xFFFFFF;
    		format.align = TextFormatAlign.CENTER;
    		format.bold = true;
    		
    		var child:TextField = new TextField();
    		child.y = 1;
    		child.width = _width;
    		child.height = _height;
    		child.selectable = false;
    		child.defaultTextFormat = format;
    		child.mouseEnabled = false;
    		
    		addChild(child);
    		return child;
    	}
    	
    	public function setLabel(text:String):void {
    		label.text = text;
    		this._name = text;
    	}
    	
    	public function setDescription(text:String):void {
    		description = text;
    	}
    }
    
    class CustomSimpleButton extends SimpleButton {
    	private var upColor:uint = 0xFFCC00;
    	private var overColor:uint = 0xCCFF00;
    	private var downColor:uint = 0x00CCFF;
    	
    	public function CustomSimpleButton(_width:uint, _height:uint) {
    		downState = new ButtonDisplayState(downColor, _width, _height);
    		overState = new ButtonDisplayState(overColor, _width, _height);
    		upState = new ButtonDisplayState(upColor, _width, _height);
    		hitTestState = new ButtonDisplayState(upColor, _width, _height);
    		useHandCursor = true;
    	}        
    }
    
    class ButtonDisplayState extends Shape {
    	private var bgColor:uint;
    	private var _width:uint;
    	private var _height:uint;
    	
    	public function ButtonDisplayState(bgColor:uint, _width:uint, _height:uint) {
    		this.bgColor = bgColor;
    		this._width = _width;
    		this._height = _height;
    		draw();
    	}
    	
    	private function draw():void {
    		graphics.beginFill(bgColor);
    		graphics.drawRect(0, 0, _width, _height);
    		graphics.endFill();
    	}
    }
    


    1.6 네이티브 프로세스의 직접 실행과 상호 작용( Launching and Interacting with Native Processes )
    네이티브 프로세스에 대해서 본인도 처음 접하는 용어라 해석하기 어려웠는데, 이해한바 각 운영체제에 실행될 수 있는 코드라고 생각하면 될 것 같다. 가령, 윈도우에서는 exe, dll, ocx등이겠고 맥이라면 dmg, app 등이 아닐까 생각한다.

    AIR 2.0부터 기존의 애플리케이션을 실행해 AIR 애플리케이션과 표준입출력을 통한 상호작용할 수 있는 기능이 추가되었다. 가령, C언어로 만든 프로그램을 자신의 AIR 애플리케이션과 함께 배포했다고 하자. AIR 애플리케이션이 이 C언어로 만든 프로그램을 실행하고 표준입출력을 이용해 통신이 가능해진다. 또는 이미 설치된 Editplus에 File을 넘겨서 실행하고 종료시점도 알 수 있다.

    자세한 내용은 다음을 참고한다.

    이 기능을 수행하려면 반드시 AIR 디스크립터 파일에 다음 코드를 꼭 넣어야 한다.


    <supportedProfiles>extendedDesktop</supportedProfiles>
    


    아래는 Editplus를 이용해 Text파일을 열고 Editplus가 닫힐 때 감지하는 간단한 Flex 4를 이용한 AIR 2.0 애플리케이션 코드이다. NativeProcess와 NativeProcessStartupInfo 클래스의 용도를 잘 살펴보자.
    <?xml version="1.0" encoding="utf-8"?>
    <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
    					   xmlns:s="library://ns.adobe.com/flex/spark" 
    					   xmlns:mx="library://ns.adobe.com/flex/halo">
    	<fx:Script>
    		<![CDATA[
    			import flash.events.NativeProcessExitEvent;
    			
    			public function executeNativeProcess():void 
    			{
    				var executable:File = new File("C:/Program Files/EditPlus 3/editplus.exe"); //c:/Windows/System32/notepad.exe
    				var workingDirectory:File = new File("c:/");
    				
    				var nativeProcess:NativeProcess = new NativeProcess();
    				
    				if (NativeProcess.isSupported) 
    				{
    					trace("Native Process Supported");
    				}
    				
    				var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
    				nativeProcessStartupInfo.executable = executable;
    				nativeProcessStartupInfo.workingDirectory = workingDirectory;
    				
    				var args:Vector.<String> = new Vector.<String>();   
    				args.push("c:/LAN.log"); // open file that was given with the executable application 
    				nativeProcessStartupInfo.arguments = args;
    				
    				nativeProcess.addEventListener( NativeProcessExitEvent.EXIT, onExitError );
    				
    				try {
    					nativeProcess.start(nativeProcessStartupInfo);
    				} catch (error:IllegalOperationError) {
    					trace("Illegal Operation: "+error.toString());
    				} catch (error:ArgumentError) {
    					trace("Argument Error: "+error.toString());
    				} catch (error:Error) {
    					trace ("Error: "+error.toString());
    				}
    				
    				if (nativeProcess.running) 
    				{
    					trace ("Native Process Support");
    				}       
    			}
    			
    			public function onExitError(event:NativeProcessExitEvent):void 
    			{
    				trace( "Native Process Exit code: "+event.exitCode );
    			}            
    			
    		]]>
    	</fx:Script>
    	
    	<s:Button id="button"
    			  label="Open File foobar.txt with text editor"
    			  click="executeNativeProcess();"
    			  width="250" />
    	
    </s:WindowedApplication>
    

    Mac인 경우 Mac에 설치되어 기본으로 설치되어 있는 텍스트 에디터를 실행할 수도 있다. 이것도 별다른 설정없이 같은 방법으로 실행할 수 있다. new File()에는 "/Applications/TextEdit/TextEdit.app/Contents/MacOS/TextEdit"와 "/" 로 대체하고 args.push()부분은 foobar.txt가 데스크톱에 있을때 "/User/사용자계정/Desktop/foobar.txt"로 대체해서 실행하면 된다. 

    NativeProcess 클래스는 단순히 실행과 종료만 담당하지 않는다. AIR 애플리케이션과 Native 실행 소스간에 통신도 가능하다. 다음 링크는 HTML/Javascript, Flex, Flash 개발자들을 위한 예제이다. AIR코드 뿐 아니라 C코드도 있으니 꼭 다운받아 실행해보길 바란다.

    다음과 같이 Flex 4 코드로 변경해봤다.


    <?xml version="1.0" encoding="utf-8"?>
    <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
    					   xmlns:s="library://ns.adobe.com/flex/spark" 
    					   xmlns:mx="library://ns.adobe.com/flex/halo" 
    					   width="350" height="150"
    					   applicationComplete="windowedapplication1_applicationCompleteHandler(event)">
    	<fx:Script>
    		<![CDATA[
    			import flash.events.ProgressEvent;
    			import flash.system.Capabilities;
    			import flash.utils.IDataInput;
    			
    			import mx.controls.Alert;
    			import mx.events.FlexEvent;
    			
    			private var process:NativeProcess;
    			
    			private function windowedapplication1_applicationCompleteHandler(event:FlexEvent):void {
    				//Native Process가 지원되는지 확인. 
    				//지원되려면 반드시 디스크립터에  <supportedProfiles>extendedDesktop</supportedProfiles>을 추가해야한다.
    				if( NativeProcess.isSupported ) {
    					launchEchoTest();
    				} else {
    					Alert.show("NativeProcess not supported.","Error");
    				}
    			}			
    			private function launchEchoTest():void {
    				//실행할 Native 애플리케이션 선택
    				var file:File = File.applicationDirectory;
    				file = file.resolvePath("NativeApps");
    				if( Capabilities.os.toLocaleLowerCase().indexOf("win") > -1 ) {
    					file = file.resolvePath("Windows/bin/echoTestWin.exe");
    				} else {
    					file = file.resolvePath("Mac/bin/echoTestMac");
    				}
    				
    				//Native Process를 진행하기 위한 정보 설정 
    				var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
    				info.executable = file;
    				
    				//Native Process생성
    				process = new NativeProcess();
    				
    				//보내고 받을때 발생하는 이벤트에 대한 처리 
    				process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, function(event:ProgressEvent):void {
    					var date:Date = new Date();
    					var bytes:IDataInput = process.standardOutput;
    					textReceived.text = bytes.readUTFBytes( bytes.bytesAvailable );
    					launchEchoTest();
    					lbDateStamp.text = date.toString();
    				} );
    				process.addEventListener(ProgressEvent.STANDARD_INPUT_PROGRESS, function(event:ProgressEvent):void {
    					process.closeInput();
    				} );
    				//Native Process 시작 
    				process.start( info );
    			}
    			private function writeData():void {
    				process.standardInput.writeUTFBytes( textToSend.text + "\n" );
    			}
    		]]>
    	</fx:Script>
    	<fx:Declarations>
    		<!-- Place non-visual elements (e.g., services, value objects) here -->
    	</fx:Declarations>
    	<s:VGroup paddingLeft="10" paddingRight="10" paddingTop="10" paddingBottom="10">
    		<mx:Form>
    			<mx:FormItem label="Text to send" direction="horizontal">
    				<s:TextInput id="textToSend"/>	
    				<s:Button label="writeData()" click="writeData()"/>
    			</mx:FormItem>
    			<mx:FormItem label="Text received">
    				<s:TextInput id="textReceived" editable="false"/>
    			</mx:FormItem>
    		</mx:Form>
    		<s:Label id="lbDateStamp"/>
    	</s:VGroup>
    </s:WindowedApplication>
    
    


    아래 코드는 AIR 애플리케이션과 통신할 C코드(Mac용)이다.
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    
    #define BUFFER_SIZE 8192
    
    int main(int argc, char** argv)
    {
    	char buf[BUFFER_SIZE];
    	int  cnt;
    	
    	while ( !feof(stdin) )
    	{
    		cnt = read(STDIN_FILENO, buf, sizeof buf); 
    		if (-1 == cnt) 
    		{
    			perror("read");
    			exit(1);
    		}
    		if (0 == cnt)
    		{
    			// eof reached...
    			exit(0);
    		}
    
    		write(STDOUT_FILENO, buf, cnt ); 
    	}
    	
    	return 0;
    }
    
    




    NativeProcess의 standardInput과 standardOutput 속성은 각각 IDataOutput, IDataInput을 입력과 출력값으로 받고 있다. 그러므로 이 속성을 이용해 표준입출력(standard input/output)을 이용해 서로 통신하게 된다.

    매우 다양한 형태로 응용이 가능할 것이라 판단한다.


    1.7 File Promise 지원

    AIR 2.0부터 FilePromise 클래스가 새로 추가되어 드래그&드롭 형태로 리모트 파일을 AIR 애플리케이션 외부로 다운로드 받을 수 있게 되었다. URLFilePromise 클래스를 이용해 이 작업을 할 수 있다.

    이 기능은 Windows와 Mac에서만 지원되며(Clipboard.supportsFilePromise로 확인) 반드시 AIR 애플리케이션 외부로 드롭하는 경우만 해당한다. 일반적인 클립보드 복사&붙이기 기능으로는 할 수 없다.

    다음 코드는 예시이다.

     

    <?xml version="1.0" encoding="utf-8"?>
    <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
    					   xmlns:s="library://ns.adobe.com/flex/spark" 
    					   xmlns:mx="library://ns.adobe.com/flex/halo"
    					   applicationComplete="windowedapplication1_applicationCompleteHandler(event)">
    	<fx:Script>
    		<![CDATA[
    			import mx.controls.Alert;
    			import mx.events.DragEvent;
    			import mx.events.FlexEvent;
    			
    			private var clipboard:Clipboard;
    			private function windowedapplication1_applicationCompleteHandler(event:FlexEvent):void {
    				clipboard = new Clipboard();
    				if( !clipboard.supportsFilePromise ) {
    					Alert.show( "File Promise를 지원하지 않습니다." );
    				}
    			}
    			private function onDragOut(event:DragEvent):void {
    				trace( event.target, event.type );
    				var items:Array = fileData.selectedItems;
    				var promises:Array = new Array();
    				for each (var item:Object in items) {
    					var filePromise:URLFilePromise = new URLFilePromise();
    					var request:URLRequest = new URLRequest(item.url);
    					filePromise.request = request;
    					filePromise.relativePath = item.name;
    					promises.push(filePromise);
    				}
    				clipboard.setData(ClipboardFormats.FILE_PROMISE_LIST_FORMAT, promises);
    				NativeDragManager.doDrag(fileData, clipboard);     
    			}
    			private function onDragOutComplete(event:NativeDragEvent):void {
    				trace( "Drag out complete");
    				clipboard = new Clipboard();
    			}            
    		]]>
    	</fx:Script>
    	
    	<fx:Declarations>
    		<mx:ArrayCollection id="arrColl">
    			<mx:source>
    				<fx:Array>
    					<fx:Object name="rhall.jpg" url="http://a1.twimg.com/profile_images/57117466/robert_m_hall_bio_photo_big_normal.jpg" />
    					<fx:Object name="bobjim.jpg" url="http://a1.twimg.com/profile_images/51723308/ryancampbell3_normal.jpg"/>
    					<fx:Object name="jenschr.jpg" url="http://a1.twimg.com/profile_images/43222252/jenschr_mugshot3_normal.jpg"/>
    					<fx:Object name="adamflater.jpg" url="http://a1.twimg.com/profile_images/21503622/Photo_8_normal.jpg"/>
    					<fx:Object name="reboog711.jpg" url="http://a1.twimg.com/profile_images/16984682/DSCF0044_normal.jpg"/>
    				</fx:Array>
    			</mx:source>
    		</mx:ArrayCollection>
    	</fx:Declarations>
    	
    	<mx:DataGrid id="fileData" dragEnabled="true"
    				 dataProvider="{arrColl}" 
    				 dragStart="onDragOut(event)"
    				 nativeDragComplete="onDragOutComplete(event)" width="100%">
    		<mx:columns>
    			<mx:DataGridColumn dataField="name" width="100"/>
    			<mx:DataGridColumn dataField="url"/>
    		</mx:columns>
    	</mx:DataGrid>
    	
    </s:WindowedApplication>
    




    1.8 Global Error Handling

    AIR 애플리케이션이 런타임 도중에 에러가 발생할 때 국부적 Error 처리는 이미 가능했다. 하지만 개발자가 애플리케이션의 어느 부분에서 Error가 발생할지 또는 문제가 발생할지 찾기 힘든 것이 현실이다. 국부적으로 일어나는 에러를 처지하지 못하는 경우 애플리케이션이 동작중에 어떤 부분에서 에러가 발생하든지 대처할 수 있도록 Global Error를 처리할 수 있는 기능이 AIR 2.0과 Flash Player 10.1에 추가가 되었다. 다음 글을 참고하자.

    Global Error를 처리하는데 UncaughtErrorEvent 클래스를 이용한다. 이름에서도 볼 수 있듯이 기존의 try..catch문으로 처리하지 못한 Error가 발생할 때 발생함을 알 수 있다.

    발생하는 UncaughtErrorEvent를 처리하기 위해 LoaderInfo 객체의 uncaughtErrorEvents 속성에 이벤트 처리를 위한 함수를 등록하면 된다.

    loaderInfo.uncaughtErrorEvents.addEventListener( UncaughtErrorEvent.UNCAUGHT_ERROR, handler );

    단, 로드된 SWF 객체의 경우에는 Loader의 uncaughtErrorEvents 속성에 이벤트 처리 함수를 등록한다.

    loader.uncaughtErrorEvents.addEventListener( UncaughtErrorEvent.UNCAUGHT_ERROR, handler );


    하나의 SWF가 다른 SWF에 로드되는 경우 UncaughtErrorEvent 이벤트 전파에 대한 처리가 필요하다.  A.swf가 B.swf를 Loader를 이용해 로드했다면 다음과 같은 순서로 LoaderInfo와 Loader 객체에 UncaughtErrorEvent 가 전파된다.

    1. (Capture phase) A.swf의 LoaderInfo
    2. (Capture phase) A.swf내의 Loader 
    3. (Target phase) B.swf의 LoaderInfo
    4. (Bubble phase) A.swf의 Loader
    5. (Bubble phase) A.swf의 LoaderInfo

    Loader객체의 uncaughtErrorEvents 속성은 결코 이벤트 전파시 target phase가 발생하지 않음에 주목하자. 그리고 이렇게 이벤트가 전파되는데는 디스플레이 리스트에 등록되어 있어야만 한다. 즉 addChild()된 객체어야한 한다. 중간에 이벤트 전파를 중단하려면 stopPropagation()이나 stopImmediatePropagation() 메소드를 이용하면 되겠다. 또한 ADL 기반나 디버그용 Flash Player에서 구동 경우 에러표시 다이얼로그가 뜨는 것을 방지하기 위해 UncaughtErrorEvent 이벤트 발생시 preventDefault() 메소드를 사용해 안뜨도록 할 수 있다.

    아래는 AIR 2.0 예제이다.

    package {
    	import flash.desktop.*;
    	import flash.display.*;
    	import flash.events.*;
    	public class UncaughtErrorEventExample extends Sprite {
    		public function UncaughtErrorEventExample()
    		{
    			var options:NativeWindowInitOptions = new NativeWindowInitOptions();
    			options.type = NativeWindowType.UTILITY;
    			
    			var window:NativeWindow = new NativeWindow(options);
    			window.width = 200;
    			window.height = 200;
    			window.title = "UncaughtErrorEventExample";
    			
    			window.stage.scaleMode = StageScaleMode.NO_SCALE;
    			window.stage.align = StageAlign.TOP_LEFT;
    			
    			window.activate();
    			window.addEventListener(Event.CLOSING, function($event:Event):void {
    				NativeApplication.nativeApplication.exit();
    			});			
    
    			var btn:Sprite = new Sprite();
    			btn.useHandCursor = true;
    			btn.buttonMode = true;
    			btn.graphics.clear();
    			btn.graphics.beginFill(0xFFCC00);
    			btn.graphics.drawRect(0, 0, 100, 50);
    			btn.graphics.endFill();
    			btn.addEventListener(MouseEvent.CLICK, clickHandler);			
    			window.stage.addChild(btn);
    			
    			loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, uncaughtErrorHandler);
    		}
    		private function uncaughtErrorHandler(e:UncaughtErrorEvent):void {
    			if (e.error is Error) {
    				var error:Error = e.error as Error;
    				// do something with the error
    				trace(error.errorID, error.name, error.message);
    			} else if (e.error is ErrorEvent) {
    					var errorEvent:ErrorEvent = e.error as ErrorEvent;
    					// do something with the error
    					trace(errorEvent.errorID);
    			} else {
    				// a non-Error, non-ErrorEvent type was thrown and uncaught
    				trace( " a non-Error, non-ErrorEvent type was thrown and uncaught " );
    			}
    			e.preventDefault();
    		}
    		private function clickHandler(event:MouseEvent):void
    		{
    			throw new Error("Gak!");
    		}
    	}
    }
    


    위 예제에서 볼 수 있듯이 AIR 애플리케이션의 노란 부분을 클릭하면 강제로 에러를 발생시킨다. 이때 이 에러를 처리하기 위한 try...catch문이 없으므로 UncaughtErrorEvent가 발생하고 그것을 처리하고 있다. 마지막에 e.preventDefault()를 둬서 에러표시 다이얼로그가 뜨는 것을 방지한다.


    1.9 Packaging an AIR application in a native installer

    AIR 2.0가 생소하게 다가오는 이유중에 하나는 바로 AIR 2.0을 설치할 때 과정이다. 뱃지(Badge)기능을 이용해 웹페이지에서 AIR 런타임 및 해당 AIR 애플리케이션을 다운로드 받을 수 있지만 그 과정이 일반 사용자에게 생소하게 다가올 수 있다. AIR 2.0에서는 이러한 생소함을 덜어주기 위해 각 OS별로 설치패키지를 따로 구성할 수 있도록 지원하고 있다. 가령, Windows는 exe, Mac은 dmg으로 설치 패키지를 구성할 수 있다는 말이다.

    AIR 2.0의 ADT의 옵션설정으로 설치 패키지를 만들 수 있다. 다음 링크를 참고한다.


    연관글






    정리하며 

    이번 글은 Adobe AIR 2.0 Overview 첫번째 글로 AIR 2.0에 추가된 새로운 기능에 대해서만 언급했다.  "기존 API에서 추가된 기능", "플랫폼 인식 관련 API", "퍼포먼스 향상"에 관련된 내용도 조만간 언급하겠다. 이 글을 읽고 AIR 2.0 기반으로 하는 다양한 애플리케이션 예제들이 많이 나왔으면 하는 바램이다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com/629)
    어떤 분이 Flex 3.4환경에서 MenuBar에 색을 입히려하다가 잘 안되서 본인의 블로그에 질문을 해주셨다.


     <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    	<mx:Style>
    		MenuBar { 
    			font-weight:bold; 
    			color:#FFFFFF;
    			fill-colors:#0D0A92, #1B16F4, #37920A, #59EB12;
    			fill-alphas:1.0,1.0;
    		}
    		MenuBarItem {
    			theme-color:#0000FF;
    		}
    		Menu { 
    			alternatingItemColors:#ffffff,#eff4fa;
    			color:#000000;
    			top:4px;
    			bottom:2px;
    			horizontalGap:2px;	
    			roll-over-color:#00ff00;
    		}
    	</mx:Style>
    	<mx:MenuBar id="myMenuBar" labelField="@label" >
    		<mx:XMLList>
    			<menuitem label="MenuItem A">
    				<menuitem label="SubMenuItem A-1" enabled="false"/>
    				<menuitem label="SubMenuItem A-2"/>
    			</menuitem>
    			<menuitem label="MenuItem B"/>
    			<menuitem label="MenuItem C"/>
    			<menuitem label="MenuItem D">
    				<menuitem label="SubMenuItem D-1" 
    						  type="radio" groupName="one"/>
    				<menuitem label="SubMenuItem D-2" 
    						  type="radio" groupName="one"
    						  selected="true"/>
    				<menuitem label="SubMenuItem D-3" 
    						  type="radio" groupName="one"/>
    			</menuitem>
    		</mx:XMLList>
    	</mx:MenuBar>
    </mx:Application>
    



    질문은 MenuBarItem의 themeColor를 #0000FF로 지정했는데 저렇게 나오냐는 것이였다. 참고로 MenuBar는 저 파란색의 바를 말하는 것이고 MenuBarItem은 MenuItem A, MenuItem B...등으로 표시된 것이다. 이들은 모두 UIComponent를 상속했다. 마지막으로 Menu는 서버메뉴로써 List를 확장한 것이다.

    질문처럼 직관적으로 봐도 문제가 있어 보인다. 이렇게 나오는 이유를 알기 위해서는 스킨부분을 찾아봐야 한다.

    (아래내용이 약간 복잡할 수 있으나 천천히 따라오면 그리 복잡한 것도 아니다. 원래 이런걸 말로 설명해야 빠른 건데 글로 설명하려 하니 어렵다. ^^;)


    원인 찾기

    MenuBar의 아이템렌더러는 menuBarItemRenderer속성에 지정된다. 기본적으로 MenuBarItem 클래스가 된다. 쉽게 이야기해 위 그림에서 MenuItem A, MenuItem B... 등은 다 MenuBarItem으로 생성되어 그려진 것이다. 만약 MenuBarItem을 다른 것으로 대체하고 싶다면 IMenuBarItemRenderer 인터페이스를 구현해서 다른 MenuBarItem을 만들면 되겠다. 아래는 MenuBar의 menuBarItemRenderer 속성을 설명하고 있다.

    (문제의 원인을 파악하기 위해 가장 먼저 찾아야 할 것은 API 문서이다.)



    MenuBarItem의 스킨은 무엇일까? 아래 MenuBar에서 Style부분을 보자. MenubarItem의 스킨은 바로 ActivatorSkin 클래스라는 것을 알 수 있다. 결국 스킨도 내 마음대로 바꿀 수 있는 것이다.

    Flex 3.4에서 스킨은 크게 그래픽기반 스킨과 프로그램적 스킨(Programmatic Skin)이 있다. 그래픽기반 스킨은 이미지를 생각하면 된다. 하지만 프로그램적 스킨은 ActionScript 3.0 코드이다. MenuBar의 itemSkin 스타일 속성으로 mx.skins.halo.ActivatorSkin을 썼다는 것은 바로 프로그램적 스킨을 사용한다는 이야기이다.

    (문서를 봐도 해결의 실마리가 안보인다면 SDK를 뜯어보자!)

    그럼 이들이 어떻게 동작하는 것일까? MenuBar부터 시작해 MenuBarItem, ActivatorSkin으로 자연스럽게 찾아보면 된다. Flex 3.4 SDK 소스는 공개되어 있으므로 아래 그림처럼 Flash builder 4에서 쉽게 찾아볼 수 있다. Flex Builder 3를 사용한다면 이 기능은 사용할 수 없다.

    Flex SDK 클래스를 보려면 framework.swc를 펼쳐보면 된다.



    MenuBar 소스에서 mouseOverHandler, mouseUpHandler, mouseDownHandler 메소드들을 보자. 이들 메소드는 마우스 이벤트를 처리하는데 잘 보면 이런 코드들이 있다.

    item.menuBarItemState = "itemDownSkin";
    


    여기서 item은 IMenuBarItemRenderer 인터페이스의 참조이다. 그리고 MenuBarItem 클래스는 이 인터페이스를 구현한 것이다. 결국 MenuBarItem의 menuBarItemState 속성에 itemDownSkin이라고 지정한 것이다.

    그럼 MenuBarItem 소스의 menuBarItemState 부분을 보자.


        public function set menuBarItemState(value:String):void
        {
            _menuBarItemState = value;
            viewSkin(_menuBarItemState);
        }   
    
        private function viewSkin(state:String):void
        {  	
        	var newSkinClass:Class = Class(getStyle(state));
        	var newSkin:IFlexDisplayObject;
        	var stateName:String = "";
        	    	
        	if (!newSkinClass)
        	{
        		// Try the default skin
        		newSkinClass = Class(getStyle(skinName)); 		
        		    	
        		if (state == "itemDownSkin")
        			stateName = "down";
        		else if (state == "itemOverSkin")
        			stateName = "over";
        		else if (state == "itemUpSkin")
        			stateName = "up";
        		
        		// If we are using the default skin, then 
        		if (defaultSkinUsesStates)
        			state = skinName;
        		
         		if (!checkedDefaultSkin && newSkinClass)
        		{
    	    		newSkin = IFlexDisplayObject(new newSkinClass());
    	    		// Check if the skin class is a state client or a programmatic skin
    	    		if (!(newSkin is IProgrammaticSkin) && newSkin is IStateClient)
    	    		{
    	    			defaultSkinUsesStates = true;
    	    			state = skinName;
    	    		}
    	    		
    	    		if (newSkin)
    	    		{
    		    		checkedDefaultSkin = true;
    		    	}
        		}
        	}
        	
          	newSkin = IFlexDisplayObject(getChildByName(state));
    
            if (!newSkin)
            {
                if (newSkinClass)
                {
                    newSkin = new newSkinClass();
    
                    DisplayObject(newSkin).name = state;
    
                    if (newSkin is ISimpleStyleClient)
                        ISimpleStyleClient(newSkin).styleName = this;
    
                    addChildAt(DisplayObject(newSkin), 0);
                }
            }
    
            newSkin.setActualSize(unscaledWidth, unscaledHeight);
    
            if (currentSkin)
                currentSkin.visible = false;
    
            if (newSkin)
                newSkin.visible = true;
    
            currentSkin = newSkin;
            
            // Update the state of the skin if it accepts states and it implements the IStateClient interface.
    		if (defaultSkinUsesStates && currentSkin is IStateClient)
    		{
    			IStateClient(currentSkin).currentState = stateName;
    		}
        }
    

    MenuBarItem의 menuBarItemState에 상태변화 값을 넣어주면 MenuBarItem의 viewSkin() 메소드에서 각 상태값에 따라서 스킨을 만들어준다. viewSkin 메소드에서 newSkin = IFlexDisplayObject(new newSkinClass()); 은 새로운 스킨을 만드는 부분이 newSkinClass가 바로 ActivatorSkin 클래스가 된다. 그리고 newSkin = IFlexDisplayObject(getChildByName(state));  부분은 이전에 만든 스킨이 있는지 확인하는 부분이다. viewSkin() 메소드는 그래픽적 스킨, 프로그램적 스킨등에 모두 대응되며 게으른(lazy) 스킨 생성을 한다. 여기서 게으른 생성이라는 것은 가령, 애플리케이션이 실행 당시에 마우스 Over스킨을 생성하는 것이 아니라 마우스Over시에 스킨은 바로 그때 생성해준다는 것이다. viewSkin() 메소드가 결국 ActivatorSkin 클래스로 하여금 스킨을 만들어주는 것이다.

    ActivatorSkin 클래스의 일부분을 보자.

    	private static function calcDerivedStyles(themeColor:uint,  fillColor0:uint, fillColor1:uint):Object
    	{
    		var key:String = HaloColors.getCacheKey(themeColor,fillColor0, fillColor1);
    				
    		if (!cache[key])
    		{
    			var o:Object = cache[key] = {};
    			
    			// Cross-component styles.
    			HaloColors.addHaloColors(o, themeColor, fillColor0, fillColor1);
    		}
    		
    		return cache[key];
    	}
    
    	/**
    	 *  @private
    	 */
    	override protected function updateDisplayList(w:Number, h:Number):void
    	{
    		super.updateDisplayList(w, h);
    
    		if (!getStyle("translucent"))
    			drawHaloRect(w, h);
    		else
    			drawTranslucentHaloRect(w, h);
    	}
    
    	/**
    	 *  @private
    	 */
    	private function drawHaloRect(w:Number, h:Number):void
    	{
    		var fillAlphas:Array = getStyle("fillAlphas");
    		var fillColors:Array = getStyle("fillColors");
    		var highlightAlphas:Array = getStyle("highlightAlphas");				
    		var themeColor:uint = getStyle("themeColor");
    
    		var themeColorDrk1:Number =
    			ColorUtil.adjustBrightness2(themeColor, -25);
    
    		// Derivative styles.
    		var derStyles:Object = calcDerivedStyles(themeColor, fillColors[0], fillColors[1]);
    												 
    		graphics.clear();
    
    		switch (name)
    		{
    			case "itemUpSkin": // up/disabled
    			{
    				// invisible hit area
    				drawRoundRect(
    					x, y, w, h, 0,
    					0xFFFFFF, 0);
    				break;
    			}
    
    			case "itemDownSkin":
    			{
    				// face
    				drawRoundRect(
    					x + 1, y + 1, w - 2, h - 2, 0,
    					[ derStyles.fillColorPress1, derStyles.fillColorPress2], 1,
    					verticalGradientMatrix(0, 0, w, h )); 
    									
    				// highlight
    				drawRoundRect(
    					x + 1, y + 1, w - 2, h - 2 / 2, 0,
    					[ 0xFFFFFF, 0xFFFFFF ], highlightAlphas,
    					verticalGradientMatrix(0, 0, w - 2, h - 2));
    
    				break;
    			}
    
    			case "itemOverSkin":
    			{
    				var overFillColors:Array;
    				if (fillColors.length > 2)
    					overFillColors = [ fillColors[2], fillColors[3] ];
    				else
    					overFillColors = [ fillColors[0], fillColors[1] ];
    
    				var overFillAlphas:Array;
    				if (fillAlphas.length > 2)
    					overFillAlphas = [ fillAlphas[2], fillAlphas[3] ];
      				else
    					overFillAlphas = [ fillAlphas[0], fillAlphas[1] ];
    
    				// face
    				drawRoundRect(
    					x + 1, y + 1, w - 2, h - 2, 0,
    					overFillColors, overFillAlphas,
    					verticalGradientMatrix(0, 0, w, h )); 
    
    				// highlight
    				drawRoundRect(
    					x + 1, y + 1, w - 2, h - 2 / 2, 0,
    					[ 0xFFFFFF, 0xFFFFFF ], highlightAlphas,
    					verticalGradientMatrix(0, 0, w - 2, h - 2));
    				
    				break;
    			}
    		}
    
    		filters = [ new BlurFilter(2, 0) ];
    	}
    


    그림을 그릴때 drawHaloRect() 메소드를 호출해서 각 상태값에 따라서 그림을 그려준다. 우리가 처음 themeColor를 지정했음에도 불구하고 다른 색으로 보였던 부분을 알기 위해서는 drawHaloRect()에 switch분기점에서 "itemDownSkin"을 보면 된다. derStyles.fillColorPress1, derStyles.fillColorPress2 색을 사용하는 것을 볼 수 있으며 코드에서 derStyles는 calcDerivedStyles(themeColor, fillColors[0], fillColors[1]);에 의해 만들어진다. calcDerivedStyles() 메소드를 보면 결국 HaloColors.addHaloColors(o, themeColor, fillColor0, fillColor1); 으로 색이 결정됨을 볼 수있다. 여기서 HaloColors 클래스내에서 addHaloColors() 정적 메소드를 보면 다음과 같다.

    public static function addHaloColors(colors:Object,  themeColor:uint,  fillColor0:uint, fillColor1:uint):void
    {
    	var key:String = getCacheKey(themeColor, fillColor0, fillColor1); 
    	var o:Object = cache[key];
    	
    	if (!o)
    	{
    		o = cache[key] = {};
    		
    		// Cross-component styles
    		o.themeColLgt = ColorUtil.adjustBrightness(themeColor, 100);
    		o.themeColDrk1 = ColorUtil.adjustBrightness(themeColor, -75);
    		o.themeColDrk2 = ColorUtil.adjustBrightness(themeColor, -25);
    		o.fillColorBright1 = ColorUtil.adjustBrightness2(fillColor0, 15);
    		o.fillColorBright2 = ColorUtil.adjustBrightness2(fillColor1, 15);
    		o.fillColorPress1 = ColorUtil.adjustBrightness2(themeColor, 85);
    		o.fillColorPress2 = ColorUtil.adjustBrightness2(themeColor, 60);
    		o.bevelHighlight1 = ColorUtil.adjustBrightness2(fillColor0, 40);
    		o.bevelHighlight2 = ColorUtil.adjustBrightness2(fillColor1, 40);
    	}
    	
    	colors.themeColLgt = o.themeColLgt;
    	colors.themeColDrk1 = o.themeColDrk1;
    	colors.themeColDrk2 = o.themeColDrk2;
    	colors.fillColorBright1 = o.fillColorBright1;
    	colors.fillColorBright2 = o.fillColorBright2;
    	colors.fillColorPress1 = o.fillColorPress1;
    	colors.fillColorPress2 = o.fillColorPress2;
    	colors.bevelHighlight1 = o.bevelHighlight1;
    	colors.bevelHighlight2 = o.bevelHighlight2;
    }
    

    위에서 우리가 주목할 점은 o.fillColorPress1와 o.fillColorPress2를 설정할 때 ColorUtil.adjustBrightness2(themeColor, 85);와 같은 방법으로 새로 색을 설정하는 부분이다.

    결국 무엇인가? 지금까지 MenuBar, MenuBarItem, ActivatorSkin 클래스를 쭉 따라가며 themaColor 스타일값이 원하는대로 보여지지 않은 것은 바로 HaloColors 클래스의 addHaloColors() 부분에서 찾을 수 있는 것이다.


    해결하기

    위처럼 themeColor가 원하는대로 적용되지 않은 것 처럼 보인 이유를 분명히 알았다. Flex 3.4가 매우 잘 만들어졌다고 할지라도 이것도 결국 사람이 만든거라 완벽히 원하는대로 기능을 쓸 수 없는 경우가 생긴다. 그러면서 발생하는 문제를 해결하기 위해 단지 피상적으로 문서만 의지해서 해결하려고 하면 절대 답을 못찾을 수 있다. 표면상 문제를 해결할 수 없다면 문제의 부분을 찾기위해 SDK 소스를 위와 같이 찾아보는 스킬을 길러야 한다.
     
    어떤 문제인지 파악했으니 이제 해결해보자. 일단 기본적으로 CSS로 themeColor 와 같은 설정값으로 해결못하는 것을 알았으니 MenuBarItem에 새로운 ActivatorSkin 와 비슷한 MyActivatorSkin 을 만들고 더불어 HaloColors도 동일하게 MyHaloColors를 만들자. 이들은 기존 ActivatorSkin.as와 HaloColors.as 소스를 복사해서 만들면 된다. 물론 package부분 설정 및 include부분 설정으로 인한 에러는 제거해주자.

    MyHaloColors 클래스내에 o.fillColorPress1, o.fillColorPress2에서 ColorUtil.adjustBrightness2(themeColor, 85); 처럼 된 것을 ColorUtil.adjustBrightness2(themeColor, 0);으로 바꾸자.

    그런 다음 메인소스에 CSS에서 MenuBar 셀렉터에 itemSkin을 다음처럼 추가한다.

    MenuBar { 
    	font-weight:bold; 
    	color:#FFFFFF;
    	fill-colors:#0D0A92, #1B16F4, #37920A, #59EB12;
    	fill-alphas:1.0,1.0;
    	itemSkin: ClassReference("skins.MyActivatorSkin");
    }
    


    문제가 해결되었다.


    결론
    나는 개인적으로 Flex 프레임워크가 완벽하다고 보지 않는다. 그래도 전체적으로 잘 구조가 잘 잡혀있는 만큼 구조를 이해하고 잘 따라가보면 표면상으로만 해결하지 못한 부분을 해결할 수 있다. 너무 책과 문서에만 의존하지 말고 개발자라면 Flex 소스도 조금씩 분석해보길 바란다.

    참고로 몇가지 덧붙여 말하면...
    사실 본인은 Flex 3를 손놓은지 꽤 되었다. Flex 4도 지금 입문단계정도이다. 기본적으로 실무에서 Flex 프레임워크로 개발한다기 보다 애플리케이션의 퍼포먼스나 개발적 스타일의 문제로 ActionScript 3.0기반으로 개발한다. Flex 는 너무 유연하게 만들려고 노력해서 그런지 전체적으로 무겁다는 것을 지울 수 없어 간단히 또 빠르고 사용자에게 불편함이 없는 그런 개발이 필요할때는 Flex를 이용하지만 속도,메모리가 중요한 개발은 무조건 ActionScript 3.0으로 개발한다.

    Flex는 ActionScript 3.0으로 만들어진 프레임워크 일종이며 mxml이라는 것은 이 Flex 프레임워크를 손쉽게 쓰기 위해 도입된 xml이다. 이를 파싱해서 ActionScript 3.0으로 해석해 다시 swf로 만드는 컴파일러가 compc, mxmlc인 것이다. Flex에 대해서 이러한 점을 알고 공부하고 개발하신다면 여러가지로 도움이 될 것이다. 


    참고글

    [Flex] 자식 컴포넌트에 CSS를 적용시키는 통상적인 방법 소개
    [Flex] 스킨(Skin)을 이용하여 확장가능한 게이지 컴포넌트 제작


    글쓴이 : 지돌스타(http://blog.jidolstar.com/627)
    처음 Flash Builder 4 beta 2를 설치하면 설치된 디렉토리에 sdks 디렉토리가 있고 이 안에 3.4.1, 4.0.0 의 두개의 디렉토리가 있다. 이 디렉토리는 SDK 두개의 버전을 말한다. 이는 윈도우(MS Windows)던 맥(Mac)이든 동일하다. 윈도우는 C:/Program Files/Adobe/Flash Builder 4 beta 2/sdks가 되겠고 맥이라면 /Application/Adobe Flash Builder 4 beta 2/sdks가 될 것이다.

    하지만 AIR 1.5개발을 위한 sdk들이며 새로운 AIR 2.0 Beta를 테스트해보기 위해서는 sdks를 변경해야한다. 윈도우에서는 Adobe Labs에서 AIR 2.0 Beta SDK를 다운로드 받아 해당 sdks안에 3.4.1, 4.0.0 디렉토리에 각각 복사하고 AIR 2.0 Runtime을 설치하는 것만으로 개발환경이 구축된다. (이미 윈도우환경에서 AIR 2.0 Beta를 Flash Builder 4에 적용하는 방법을 소개했었다. : http://blog.jidolstar.com/619)

    하지만 맥에서의 복사는  윈도우 운영체제에서 복사와 다른 개념을 가진다. 윈도우에서는 같은 디렉토리 이름의 경우 이전의 내용과 덮어쓰려는 내용을 합쳐버린다. 하지만 맥은 같은 이름의 디렉토리는 제거하고 복사할 디렉토리로 바꾼다. 이를 대치한다고 한다. 그래서 맥 사용자는 AIR 2.0 개발환경을 구축하기 위해 조금 복잡한 과정을 거쳐야한다.

    약간 복잡하더라도 리눅스를 조금만 다뤄봤다면 쉽게 이 문제를 해결할 수 있다.

    • 파인더에서 Application(응용 프로그램) > 유틸리티 에서 터미널을 실행한다. 
    • 만약 이전에 root 계정을 만든 적이 있다면 바로 su를 입력해 root권한으로 접근한다.
      아니면 sudo passwd root 를 입력해서 현재 계정의 암호를 입력하고 root암호를 설정후 성공적으로 입력하면 su로 접속해 방금 입력한 root암호로 접속한다. 참고로 root로 접속하면 명령 프롬프트가 $에서 #로 바뀐다.
    • 다운로드 받은 AIR 2.0 SDKs를 기존 sdks에 복사하기 위해 cp -rf orig_dir/* dest_dir를 이용한다.
      만약 AIR 2.0 SDKs를 자신의 홈에 복사해 두었다면 다음과 같이 터미널 창에 입력하여 강제 복사한다.
      cp -rf /Users/jidolstar/AIR2.0SDK/* /Applications/Adobe\ Flash\ Builder\ Beta\ 2/sdks/3.4.1/
      cp -rf /Users/jidolstar/AIR2.0SDK/* /Applications/Adobe\ Flash\ Builder\ Beta\ 2/sdks/4.0.0/

    윈도우와 맥의 디렉토리 복사에 대한 개념이 달라 본인도 몇시간 고민한 결과 찾아낸 방법이다. 좋은 정보가 되었으면 한다.


    추가사항 
    기존에 AIR 1.5 기반의 SDKs를 그대로 두면서 AIR 2.0도 필요할 때 개발하고 싶은 사람도 있을 것이다. 
    그런 경우에는 위의 설명에서 약간만 다르게 하면 되겠다. 위에서 설명한 2번째 내용에서 root로 접속하는 것까지는 같다. 

    1. Flash builder 4의 sdks 디렉토리에 있는 3.4.1과 4.0.0의 복사본을 만든다. 
      mkdir 3.4.1_AIR2.0
      mkdir 4.0.0_AIR2.0
      cp -R 3.4.1/* 3.4.1_AIR2.0
      cp -R 4.0.0/* 4.0.0_AIR2.0
    2. AIR 2.0 SDKs를 3.4.1_AIR2.0과 4.0.0_AIR2.0 폴더에 복사한다.
      cp -rf /Users/jidolstar/AIR2.0SDK/* /Applications/Adobe\ Flash\ Builder\ Beta\ 2/sdks/3.4.1_AIR2.0/
      cp -rf /Users/jidolstar/AIR2.0SDK/* /Applications/Adobe\ Flash\ Builder\ Beta\ 2/sdks/4.0.0_AIR2.0/
    3. Flash Builder 를 실행한다.
    4. 메뉴에서 Eclipse > Preference에 들어간다. (참고로 Window였다면 Window > Preference이다.)
    5. 창이 뜨면 왼쪽 메뉴에서 Flash builder > Installed Flex SDKs를 선택한다.
    6. 우측에 Add버튼을 눌러 위에서 새로 만든 AIR 2.0을 위한 SDKs들을 선택한다.  
      Flex SDK Name은 각각 Flex 3.4 AIR 2.0, Flex 4.0 AIR 2.0 등으로 이름을 바꿔도 된다.
      본인의 경우 아래 경로가 되겠다.
      /Applications/Adobe\ Flash\ Builder\ Beta\ 2/sdks/3.4.1_AIR2.0/
      /Applications/Adobe\ Flash\ Builder\ Beta\ 2/sdks/4.0.0_AIR2.0/
      본인은 아래처럼 AIR 2.0기반의 Flex 4.0 SDK를 디폴트로 잡았다.

    7. 이제 모든 개발환경이 완료되었다. 자신의 프로젝트 생성시에 원하는 SDK를 선택하면 되겠다. 
    8. 만약 프로젝트 중간에 AIR 1.5기반으로 만들다가 AIR 2.0기반으로 바꾸려면 해당프로젝트를 선택후 마우스 우클릭으로 Properties를 선택한다. 창이 뜨면 좌측 메뉴에서 Flex Compiler를 선택후 Flex SDK Version을 해당 SDK로 바꾸면 되겠다.



    Adobe AIR로 만든 아주아주 간단한 웹브라우져이다. Adobe AIR는 Webkit 엔진을 내장한 HTMLLoader라는 클래스가 있다. Webkit엔진은 사파리, 구글 크롬등에 사용하는 엔진이다. Flex 4에서는 이것을 HTML로 한번 랩핑해서 사용하고 있다. Adobe AIR에서 이 엔진을 사용하면 로드되는 페이지의 DOM에 직접 접근이 가능하며 DOM 이벤트 발생시 ActionScript 3.0 함수를 호출하게끔 하는 형태로도 변경이 가능해진다. 생각보다 활용도가 무궁무진하다. 게다가 AIR 2.0부터는 한글입력 문제가 해결되었다 . 또한 HTML5/CSS3를 지원하기 시작했다.

    아래 코드는 AIR 2.0, Flash Builder 4 Beta 2 환경에서 만들었다. 이 환경을 구축하는 방법은 다음과 같이 한다.

  • 만약 Flash Builder를 설치 안했다면 다음 링크를 통해 받는다.
  • AIR 2.0 SDK와 Runtime을 다운로드 받는다.
  • 다운받은 Runtime을 실행해 설치한다.
  • (MS Windows의 경우)SDK는 압축을 풀고 그안에 있는 내용을 Flash builder가 설치된 sdks/4.0.0과 sdks/3.4.1 폴더에 각각 덮어씌운다. 본인의 경우는 C:\Program Files\Adobe\Adobe Flash Builder Plug-in Beta 2\sdks\4.0.0
  • Flash Builder를 실행한다.
  • 메뉴에서 File > New > Flex Project를 선택한다.
  • 프로젝트 이름을 적고 Application Type은 AIR를 선택한다. Finish를 한다.
  • 다음 아래 코드를 메인 코드에 복사해서 덮어씌운다.




  • <?xml version="1.0" encoding="utf-8"?>
    <!--
    	간단한 Adobe AIR 웹브라우져
    	제작 : 지용호 
    -->
    <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
    					   xmlns:s="library://ns.adobe.com/flex/spark" 
    					   xmlns:mx="library://ns.adobe.com/flex/halo"
    					   backgroundFrameRate="0.01"
    					   windowComplete="windowedapplication1_windowCompleteHandler(event)">
    	<fx:Script>
    		<![CDATA[
    			import flash.events.Event;
    			import flash.net.URLRequest;
    			import flash.net.navigateToURL;
    			
    			import mx.events.AIREvent;
    
    			private function windowedapplication1_windowCompleteHandler(event:AIREvent):void {
    				nativeWindow.x = 0;
    				nativeWindow.y = 0;
    				width = 1280;
    				height = 900;
    				//browser.htmlLoader.navigateInSystemBrowser = true;
    			}
    			private function linkClickHandler(o:Object):void {
    				navigateToURL( new URLRequest(o.currentTarget.href), "blank" );
    			}
    			private function browser_locationChangeHandler(event:Event):void {
    				trace( "browser_locationChangeHandler" );
    				tiAddress.text = browser.location;
    				btnBack.enabled = browser.historyPosition==0?false:true;
    				btnNext.enabled= (browser.historyPosition < browser.historyLength-1)?true:false;
    			}
    			private function onGoHandler(event:Event):void {
    				browser.location = tiAddress.text;
    			}
    			private function browser_completeHandler(event:Event):void
    			{
    				trace( "browser_completeHandler" );
    				tiAddress.text = browser.location;
    				var dom:Object = HTML(event.currentTarget).domWindow.document;
    				var links:Object = dom.getElementsByTagName("a");
    				for( var i:Number = 0; i < links.length; i++ ) {
    					if( links[i].target.toLowerCase() == "_blank" || links[i].target.toLowerCase() == "_new" ) {
    						links[i].onclick = linkClickHandler;
    					}
    				}
    			}
    		]]>
    	</fx:Script>
    	<s:VGroup width="100%" height="100%">
    		<s:HGroup verticalAlign="middle" paddingLeft="10" paddingRight="10" paddingTop="5" paddingBottom="5">
    			<s:Button id="btnBack" label="뒤로" click="browser.historyBack()"/>
    			<s:Button id="btnNext" label="앞으로" click="browser.historyForward()"/>
    			<s:Button label="새로고침" click="browser.reload()"/>
    			<s:Label text="주소 : "/>
    			<s:TextInput id="tiAddress" width="400" enter="onGoHandler(event)"/>
    			<s:Button label="가기" click="onGoHandler(event)"/>
    		</s:HGroup>
    		<s:HGroup verticalAlign="middle" paddingLeft="10" paddingRight="10" paddingTop="5" paddingBottom="5">
    			<s:Button label="지돌스타 블로그" click="browser.location='http://blog.jidolstar.com'"/>
    			<s:Button label="위콘 블로그" click="browser.location='http://weconize.com'"/>
    			<s:Button label="천문노트" click="browser.location='http://astronote.org'"/>
    			<s:Button label="Adobe RIA" click="browser.location='http://adoberia.co.kr'"/>
    			<s:Button label="Adobe Labs" click="browser.location='http://labs.adobe.com'"/>
    			<s:Button label="Adobe Development Center" click="browser.location='http://www.adobe.com/devnet/'"/>
    		</s:HGroup>
    		<!-- http://labs.adobe.com/wiki/index.php/Apollo:Articles:Using_HTML_in_Flex-based_Apollo_Applications -->
    		<mx:HTML id="browser" width="100%" height="100%" 
    				 location="http://blog.jidolstar.com"
    				 complete="browser_completeHandler(event)"
    				 locationChange="browser_locationChangeHandler(event)"/>
    	</s:VGroup>
    </s:WindowedApplication>
    
    

    Ajax, ActionScript 3.0, Flex, Flash등을 이용하면 AIR 애플리케이션을 만들 수 있다. Flash를 몰라도 Ajax를 알면 만들 수 있기 때문에 웹개발자들이 데스크탑 영역으로의 개발이 가능해졌다. 실제로 Ajax로 만들어진 애플리케이션도 꽤 된다. Adobe AIR 세계에 많은 개발자가 동참하길 바란다.


    글쓴이 : 지돌스타( http://blog.jidolstar.com/621 )
    어제 Adobe AIR에서 CPU 사용을 줄이는 방법에 대해서 소개했다. 이 글에 Hika님과 찬익님이 그에 대해 댓글을 달아주시며 다른 정보를 공유해주셨다. 정말 감사드린다. 이런 것이 블로그의 매력일 것이다. 블로그를 내 완벽한 지식을 전달하기 위해 쓴다는 것은 다소 어폐가 있다. 완벽한 것은 없다. 블로그는 지식 소통의 도구로 잘 활용하면 좋은 정보 공유를 통한 더욱 완전해지는 지식습득이 가능하게 한다. 난 지난 몇년간 블로그를 하면서 정말 몸소 체험했다. 지금 이글을 보고 있는 분들도 꼭 블로그를 하길 권장한다.

    각설하고. 찬익님이 Flex 4 기반에서 AIR 애플리케이션을 만들때 FrameRate를 조절하는 방법으로 backgroundFrameRate를 소개해주었다. 이것은 Event.ACTIVATE와 Event.DEACTIVATE를 이용해서 이미 Flex 4의
    WindowedApplication 클래스에 적용되어 있다. 아래 코드는 WindowedApplication 클래스의 일부이다.

    /**
    *  Constructor.
    *  
    *  @langversion 3.0
    *  @playerversion AIR 1.5
    *  @productversion Flex 4
    */
    public function WindowedApplication()
    {
    	super();
    
    	addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
    	addEventListener(FlexEvent.PREINITIALIZE, preinitializeHandler);
    	addEventListener(FlexEvent.UPDATE_COMPLETE, updateComplete_handler);
    	addEventListener(FlexEvent.CREATION_COMPLETE, creationCompleteHandler);
    
    	var nativeApplication:NativeApplication = NativeApplication.nativeApplication;
    	nativeApplication.addEventListener(Event.ACTIVATE, nativeApplication_activateHandler);
    	nativeApplication.addEventListener(Event.DEACTIVATE, nativeApplication_deactivateHandler);
    	nativeApplication.addEventListener(Event.NETWORK_CHANGE, dispatchEvent);
    
    	nativeApplication.addEventListener(InvokeEvent.INVOKE, nativeApplication_invokeHandler);
    	initialInvokes = new Array();
    
    	//Force DragManager to instantiate so that it can handle drags from
    	//outside the app.
    	DragManager.isDragging;
    }
    
    /**
    *  @private
    *  Storage for the backgroundFrameRate property.
    */
    private var _backgroundFrameRate:Number = 1;
    
    /**
    *  Specifies the frame rate to use when the application is inactive.
    *  When set to -1, no background frame rate throttling occurs.
    *
    *  @default 1
    *  
    *  @langversion 3.0
    *  @playerversion AIR 1.5
    *  @productversion Flex 4
    */
    public function get backgroundFrameRate():Number
    {
    	return _backgroundFrameRate;
    }
    
    /**
    *  @private
    */ 
    public function set backgroundFrameRate(frameRate:Number):void
    {
    	_backgroundFrameRate = frameRate;
    }
    
    /**
    *  @private
    */
    private function nativeApplication_activateHandler(event:Event):void
    {
    	dispatchEvent(new AIREvent(AIREvent.APPLICATION_ACTIVATE));
    
    	// Restore throttled framerate if appropriate when application is activated.
    	if (prevActiveFrameRate >= 0 && stage)
    	{
    	    stage.frameRate = prevActiveFrameRate;  
    	    prevActiveFrameRate = -1;
    	}
    }
    
    /**
    *  @private
    */
    private function nativeApplication_deactivateHandler(event:Event):void
    {
    	dispatchEvent(new AIREvent(AIREvent.APPLICATION_DEACTIVATE));
    
    	// Throttle framerate if appropriate when application is deactivated.
    	// Ensure we've received an updateComplete on the chance our layout
    	// manager is using phased instantiation (we don't wish to store a
    	// maxed out (1000fps) framerate).
    	if ((_backgroundFrameRate >= 0) && (ucCount > 0) && stage)
    	{
    	    prevActiveFrameRate = stage.frameRate;
    	    stage.frameRate = _backgroundFrameRate; 
    	}
    }
    


    이는 Flex 4를 이용해 AIR 애플리케이션을 만들때 별도의 조치없이 backgroundFrameRate 속성 설정만으로 frameRate를 조절할 수 있다. 이 값은 기본으로 1이다. 더 좋은 값은 0.01이라고 한다. 이는 거의 멈추게 하는 수준이다. 이 속성은 Flex 4에서 AIR를 위해 WindowedApplication 에만 적용되어 있다. Flash기반인 Application 클래스에는 이 속성이 없다.
    참고글
    Adobe AIR에서 CPU 사용을 줄이는 방법

    글쓴이 : 지돌스타(http://blog.jidolstar.com/624)

    오랜만에 네이버 오픈케스트에 케스팅했습니다. 이만저만 하는게 많다보니 여기에 신경을 전혀 못쓰고 있었네요. 요즘에는 AIR분야를 좀 해보려고 하고 있습니다. 항상 눈팅만하다가 본격적으로 결과물을 내어야할 것이 생겨서 이것저것 해보는 중입니다. 이런걸 정리하다 보면 저 자신도 너무 모르는게 많고 항상 부족함을 느낍니다. 아무튼 혼자 아는 것보다 함께 알아서 서로 공유해 서로 커나가는 것이 제 생각입니다. 좋은 정보가 되길 바랍니다.

    http://opencast.naver.com/FL188/14

    글쓴이 : 지돌스타(http://blog.jidolstar.com)
    Adobe AIR는 다양한 OS(Windows, Mac, Linux)에 구동될 수 있는 애플리케이션을 만드는데 사용하는 일종의 Flash Platfom기술이다. Flash ActionScript 3.0이나 Ajax만으로도 AIR 애플리케이션을 만들 수 있기 때문에 기존에 웹개발자들이 다른 언어를 배우지 않고 일반 데스크탑용 애플리케이션을 만드는데 있어서 접근성이 좋다. 앞으로 AIR는 데스크탑뿐 아니라 모바일등과 같은 다양한 기기에도 적용될 예정이다.

    하지만 AIR외에 다른 데스크탑용 애플리케이션과 비교해 그 적용범위가 아직까지 많은 부분 부족하고 복잡한 UI를 다루는데 있어서 성능문제가 걸릴 수 있다. 하지만 AIR 2.0 Beta 버전이 오픈된 것을 볼 수 있었듯이 계속 발전해나갈 것이다. 물론 성능면에서도 마찬가지 일 것이다. 그래도 약간은 부족하다. 기능은 그렇다 쳐도 지금의 성능을  개발자가 조금만 신경을 쓴다면  획기적으로 향상시킬 수 있다.

    일전에 Reducing CPU usage in Adobe AIR라는 글을 보았다. 여기서는 매우 단순하고 쉬운 방법으로 framerate를 줄임으로서 CPU 사용율을 현격히 줄이는 방법을 소개하고 있다.

    이 방법이 퍼포먼스를 올리기 위한 방법의 하나의 예시는 될 수 있지만 그 전부는 아니라는 점은 밝혀둔다.

    (이 글을 완벽히 이해하기 위해서는 ActionScript 3.0과 AIR에 대한 전반적 이해가 요구됩니다.)

    FrameRate를 줄임
    Framerate를 줄임(Framerate throttling)은 애플리케이션의 휴면(idle)이 있을때 자원 사용을 줄여 퍼포먼스를 증가시키는 기술을 의미한다. 이를 구현하기 위해 ActionScript 3.0에서 매우 유용한 속성으로 Stage.frameRate가 있다. 이것을 이용하면 런타임시에 애플리케이션의 framerate를 변경시킬 수 있다. 

    여기서 보여지는 예제는 Reducing CPU usage in Adobe AIR에서 소개한 글에 나온 예제를 조금더 실용적으로 만들었다.(실제로 실행해 볼 수 있도록) Flash Builder 4와 AIR 2.0 SDK를 설치한 사람들이라면 이 예제를 실제로 테스트 해볼 수 있다. 아래 순서대로 개발/테스트 환경을 구축하면 된다.

  • 만약 Flash Builder를 설치 안했다면 다음 링크를 통해 받으세요.
  • SDK와 Runtime을 다운로드 받습니다.
  • 다운받은 Runtime을 실행해 설치합니다.
  • (MS Windows의 경우)SDK는 압축을 풀고 그안에 있는 내용을 Flash builder가 설치된 sdks/4.0.0과 sdks/3.4.1 폴더에 각각 덮어씌웁니다. 제 경우는 C:\Program Files\Adobe\Adobe Flash Builder Plug-in Beta 2\sdks\4.0.0
  • Flash Builder를 실행합니다.
  • 메뉴에서 File > New > Flex Project를 선택합니다.
  • 프로젝트 이름을 적고 Application Type은 AIR를 선택합니다.
  • Next버튼을 두번 클릭후 Main Application file이름이 프로젝트명.mxml로 되어 있다면 Novice.as로 바꾸세요. 그리고 Finish 버튼을 누릅니다.
  • 아래 첫번째 초급 코드를 복사해서 Novice클래스를 열어 붙힙니다.
  • 디버그 모드로 테스트합니다.
  • 두번째, 세번째의 경우 만들어진 프로젝트의 소스폴더에 해당 클래스 이름으로 Class를 만듭니다. File > New > ActionScript Class를 선택한뒤 아래 소스를 복사해 붙여넣으면 되겠죠?
  • Package Explorer에 프로젝트명을 선택후 마우스 우클릭해서 컨텍스트 메뉴에서 Properties를 선택합니다.
  • 창이 뜨면 좌측 Flex Applications를 선택하고 오른쪽에 Add를 눌러 추가한 클래스(Intermediate.as, Expert.as)를 추가한뒤 OK를 합니다. Intermediate-app.xml과 Expert-app.xml이 자동 생성됩니다.
  • 이제 디버그 모드로 실행하면서 동작하는 형태를 학습하세요.
  • 아래 소스는 다음 링크에서 다운로드 받으셔도 됩니다.



  • 초급
    Framerate를 줄일때의 시점을 선택하는데 가장 쉽고 유용한 방법은 NativeApplication에서 Event.ACTIVATE 와 Event.DEACTIVATE 이벤트를 사용하는 것이다. AIR로 만들어진 빈윈도우를 선택해서 사용할때 CPU사용율이 1.8%라면 다른 윈도우를 선택해 그 윈도우의 CPU 사용율이 0.4%까지 떨여졌다고 한다. 또한 framerate를 0.01로 지정하면 0.2%까지 떨어진다고 한다. 이에 대한 예제는 다음과 같다. 예제가 이해하기 쉬우므로 따로 설명하지 않겠다.


    package {
    	import flash.desktop.NativeApplication;
    	import flash.display.NativeWindow;
    	import flash.display.NativeWindowInitOptions;
    	import flash.display.NativeWindowType;
    	import flash.display.Sprite;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	import flash.events.Event;
    	import flash.text.TextField;
    	import flash.text.TextFieldAutoSize;
    	import flash.utils.getTimer;
    	
    	/**
    	 * CPU 사용 줄이기 예제 1 
    	 * @author Yongho, Ji
    	 * @since 2009.12.1
    	 * @see http://blog.jidolstar.com/622
    	 */ 
    	public class Novice extends Sprite {
    		private var __isActive:Boolean = false;
    		private var __window:NativeWindow;
    		private var __textField:TextField;
    		public function Novice() {
    			__init();
    			var options:NativeWindowInitOptions = new NativeWindowInitOptions();
    			options.type = NativeWindowType.UTILITY;
    			
    			__window = new NativeWindow(options);
    			__window.width = 200;
    			__window.height = 200;
    			__window.title = "Novice";
    			
    			__textField = new TextField();
    			__textField.autoSize = TextFieldAutoSize.LEFT;
    			__textField.text = "";
    			
    			__window.stage.scaleMode = StageScaleMode.NO_SCALE;
    			__window.stage.align = StageAlign.TOP_LEFT;
    			
    			__window.stage.addChild(__textField);
    			__window.activate();
    			__window.addEventListener(Event.CLOSING,__onClosing);
    		}
    		private function __init():void {
    			NativeApplication.nativeApplication.addEventListener(Event.ACTIVATE, __onActive );
    			NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, __onDeactive );
    			stage.addEventListener(Event.ENTER_FRAME,__onEnterFrame);
    		}
    		private function __onActive($event:Event):void {
    			stage.frameRate = 50;
    			__isActive = true;
    		}
    		private function __onDeactive($event:Event):void {
    			stage.frameRate = 1;
    			__isActive = false;
    		}
    		private function __onEnterFrame($event:Event):void {
    			__textField.text =  "active:" + __isActive + " " + getTimer();
    		}
    		private function __onClosing($event:Event):void {
    			NativeApplication.nativeApplication.exit();
    		}
    	}
    }
    


    중급

    위의 예제보다 조금더 고급적으로 framerate를 조절할 필요가 있다. 가령, 마우스 휠 이벤트에 따라 스크롤이 되는 컨텐츠가 있는 경우가 그것인데 평소에는 작은 framerate를 유지하다가 스크롤시에 빠른 렌더링이 필요하므로 framerate를 올려주는 것이다. 구체적으로 MouseEvent.MOUSE_WHEEL이 발생시 framerate를 올려주고 Event.ENTER_FRAME 이벤트에서 스크롤링후 500ms이 지난 다음 다시 framerate를 줄이는 것이다. 이도 휴면(idle)상태에서 쓸데없이 렌더링되는 것을 방지하고 필요한 동작할 때만 빠른 렌더링을 요구하도록 함으로써 CPU 사용율을 줄여주는 것이다.

    package {
    	import flash.desktop.NativeApplication;
    	import flash.display.NativeWindow;
    	import flash.display.NativeWindowInitOptions;
    	import flash.display.NativeWindowType;
    	import flash.display.Sprite;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	import flash.events.Event;
    	import flash.events.MouseEvent;
    	import flash.text.TextField;
    	import flash.text.TextFieldAutoSize;
    	import flash.utils.getTimer;
    	/**
    	 * CPU 사용 줄이기 예제 2 
    	 * @author Yongho, Ji
    	 * @since 2009.12.1
    	 * @see http://blog.jidolstar.com/622
    	 */ 
    	public class Intermediate extends Sprite {
    		private static const ACTIVE:int = 50;
    		private static const INACTIVE:int = 1;
    		private var __isActive:Boolean = false;
    		private var __isScrolling:Boolean = false;
    		private var __buffer:int;
    
    		private var __window:NativeWindow;
    		public function Intermediate() {
    			__init();
    			var options:NativeWindowInitOptions = new NativeWindowInitOptions();
    			options.type = NativeWindowType.UTILITY;
    			
    			__window = new NativeWindow(options);
    			__window.width = 200;
    			__window.height = 200;
    			__window.title = "Intermediate";
    			
    			var textField:TextField = new TextField();
    			textField.y = 20;
    			textField.width = 195;
    			textField.height = 175;
    			textField.multiline = true;
    			textField.wordWrap = true;
    			textField.border = true;
    			textField.borderColor = 0xff0000;
    			textField.text = "Adobe AIR는 다양한 OS(Windows, Mac, Linux)에 구동될 수 있는 애플리케이션을 만드는데 사용하는 일종의 Flash Platfom기술이다. Flash ActionScript 3.0이나 Ajax만으로도 AIR 애플리케이션을 만들 수 있기 때문에 기존에 웹개발자들이 다른 언어를 배우지 않고 일반 데스크탑용 애플리케이션을 만드는데 있어서 접근성이 좋다. 앞으로 AIR는 데스크탑뿐 아니라 모바일등과 같은 다양한 기기에도 적용될 예정이다.하지만 원천적으로 데스크탑용 애플리케이션과 비교해 그 적용범위가 아직까지 많은 부분 부족하고 복잡한 UI를 다루는데 있어서 성능문제가 걸릴 수 있다. 적용되는 범위는 AIR 2.0 Beta 버전이 오픈된 것을 볼 수 있었듯이 계속 발전해나갈 것이다. 또한 성능면에서도 마찬가지 일 것이다. 그래도 성능면에 있어서 개발자가 조금만 신경을 쓴다면 어떤 부분에 있어서 획기적으로 AIR 애플리케이션의 성능을 향상시킬 수 있다. 일전에 Reducing CPU usage in Adobe AIR라는 글을 보았다. 여기서는 매우 단순하고 쉬운 방법으로 framerate를 줄임으로서 CPU 사용율을 현격히 줄이는 방법을 소개하고 있다. ";
    
    			__window.stage.scaleMode = StageScaleMode.NO_SCALE;
    			__window.stage.align = StageAlign.TOP_LEFT;
    			
    			__window.stage.addChild(textField);
    			__window.activate();
    			__window.addEventListener(Event.CLOSING,__onClosing);
    		}
    		private function __init():void {
    			NativeApplication.nativeApplication.addEventListener(Event.ACTIVATE, __onActive );
    			NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, __onDeactive );
    			stage.addEventListener(MouseEvent.MOUSE_WHEEL, __onMouseWheel, true);
    		}
    		private function __onActive($event:Event):void {
    			stage.frameRate = ACTIVE;
    			__isActive = true;
    			trace( "active" );
    		}
    		private function __onDeactive($event:Event):void {
    			stage.frameRate = INACTIVE;
    			__isActive = false;
    			trace( "deactive" );
    		}
    		private function __onMouseWheel($event:MouseEvent):void {
    			if( !__isActive ) {
    				if ( !__isScrolling ) {
    					stage.addEventListener(Event.ENTER_FRAME, __onEnterFrame, true);
    				}
    				stage.frameRate = ACTIVE;
    				__isScrolling = true;
    				__buffer = getTimer()+500;
    			}
    		}
    		private function __onEnterFrame($event:Event):void {
    			if( __buffer < getTimer() ) {
    				stage.frameRate = INACTIVE;
    				__isScrolling = false;
    			}
    		}
    		private function __onClosing($event:Event):void {
    			NativeApplication.nativeApplication.exit();
    		}
    	}
    }
    


    고급

    위에서 소개한 경우보다 약간더 어려운 주제로 넘어가보자. 먼저 창이 보이지 않을때와 보일때에 어떻게 처리할 것인가이다. 보이는 경우라면 일단 최소한의 framerate를 5로 주고 안보인다면 1로 준다. 이 경우는 MS Windows에서는 Tray Icon으로 바뀌며 창이 안보여질 때나 Mac에서 Dock으로만 표시될 필요가 있을때 사용될 수 있을 것이다. 굳이 보여지지 않는데 지나친 framerate를 줄 필요가 없기 때문이다. 평상시 Active한 상태에서는 24 framerate를 유지하지면 때에 따라서 부드럽게 운동하는 모습을 렌더링할 필요가 있을 때가 있다. 상태변화에 따라 Tweener 기능을 사용하는 경우가 그것인데 이때는 평소보다 framerate를 올려줄 필요가 있을 수 있다.

    아래 AIR 애플리케이션 소스코드는 두개의 버튼이 있다. 한개는 창의 visible을 false로 지정했다가 1초후 다시 true로 해주는 것이고 또 하나는 가상의 애니메이션이 있다고 가정하고 1초정도 여분을 둔다. 각 상태가 변할때마다 framerate를 Dubugging 시에 콘솔창에서 상태변화를 확인할 수 있도록 짜여있다.

    package {
    	import flash.desktop.NativeApplication;
    	import flash.display.NativeWindow;
    	import flash.display.NativeWindowInitOptions;
    	import flash.display.NativeWindowType;
    	import flash.display.Shape;
    	import flash.display.SimpleButton;
    	import flash.display.Sprite;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	import flash.events.Event;
    	import flash.events.MouseEvent;
    	import flash.filters.BevelFilter;
    	import flash.text.TextField;
    	import flash.text.TextFormat;
    	import flash.utils.getTimer;
    	import flash.utils.setTimeout;
    	/**
    	 * CPU 사용 줄이기 예제 3 
    	 * @author Yongho, Ji
    	 * @since 2009.12.1
    	 * @see http://blog.jidolstar.com/622
    	 */ 
    	public class Expert extends Sprite {
    		public static const ANIMATING:int = 50;
    		public static const ACTIVE:int = 24;
    		public static const INACTIVE_VISIBLE:int = 5;
    		public static const INACTIVE_INVISIBLE:int = 1;
    		
    		private var __isActive:Boolean = false;
    		private var __isAnimating:Boolean = false;
    		private var __window:NativeWindow;
    		private var __buffer:int;
    		
    		public function Expert() {
    			__init();
    			var options:NativeWindowInitOptions = new NativeWindowInitOptions();
    			options.type = NativeWindowType.UTILITY;
    			
    			__window = new NativeWindow(options);
    			__window.width = 200;
    			__window.height = 200;
    			__window.title = "Expert";
    			
    			__window.stage.scaleMode = StageScaleMode.NO_SCALE;
    			__window.stage.align = StageAlign.TOP_LEFT;
    			
    			//창을 감추기 버튼 
    			var buttonSkin:Sprite = new Sprite;
    			buttonSkin.graphics.beginFill( 0xff0000, 1.0 );
    			buttonSkin.graphics.drawRect(0,0,100,50);
    			buttonSkin.graphics.endFill();
    			buttonSkin.filters = [new BevelFilter()];
    			var textFormat:TextFormat = new TextFormat();
    			textFormat.color = 0xffffff;
    			textFormat.align = "center";
    			var textField:TextField = new TextField();
    			textField.defaultTextFormat = textFormat;
    			textField.text = "창을 감추기(1초후에 나타남)";
    			textField.width = 100;
    			textField.multiline = true;
    			textField.wordWrap = true;
    			textField.y = buttonSkin.height/2 - textField.textHeight/2; 
    			buttonSkin.addChild( textField );
    			var button:SimpleButton = new SimpleButton(buttonSkin,buttonSkin,buttonSkin,buttonSkin);
    			button.addEventListener(MouseEvent.CLICK, __onHide );
    			__window.stage.addChild( button );
    			
    			//Animation 동작 시키기 버튼 
    			buttonSkin = new Sprite;
    			buttonSkin.graphics.beginFill( 0x0000ff, 1.0 );
    			buttonSkin.graphics.drawRect(0,0,100,50);
    			buttonSkin.graphics.endFill();
    			buttonSkin.filters = [new BevelFilter()];
    			textFormat = new TextFormat();
    			textFormat.color = 0xffffff;
    			textFormat.align = "center";
    			textField = new TextField();
    			textField.defaultTextFormat = textFormat;
    			textField.text = "동작시키기(1 second)";
    			textField.width = 100;
    			textField.multiline = true;
    			textField.wordWrap = true;
    			textField.y = buttonSkin.height/2 - textField.textHeight/2; 
    			buttonSkin.addChild( textField );
    			button= new SimpleButton(buttonSkin,buttonSkin,buttonSkin,buttonSkin);
    			button.y = 52;
    			button.addEventListener(MouseEvent.CLICK, __onAnimate );
    			__window.stage.addChild( button );			
    						
    			__window.activate();
    			__window.addEventListener(Event.CLOSING,__onClosing);
    		}
    		private function __init():void {
    			NativeApplication.nativeApplication.addEventListener(Event.ACTIVATE, __onActive );
    			NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, __onDeactive );
    		}
    		private function setFrameRate( frameRate:Number ):void {
    			stage.frameRate = frameRate;
    			trace( frameRate );
    		}
    		private function animate($duration:int = 1000):void {
    			trace( "동작시작");
    			setFrameRate( 50 );
    			__buffer = getTimer() + $duration;
    			if(!__isAnimating) {
    				stage.addEventListener(Event.ENTER_FRAME,__onEnterFrame);
    			}
    		}
    		private function active():void {
    			if(!__isAnimating) {
    				setFrameRate( ACTIVE );	
    			} 
    			trace( "active " );
    		}
    		private function deactive():void {
    			if (!__isAnimating) {
    				setFrameRate( (__window.visible) ? INACTIVE_VISIBLE : INACTIVE_INVISIBLE );
    			}
    			trace( "deactive " );
    		}
    		private function show():void {
    			__window.visible = true;
    			active();
    		}
    		private function hide():void {
    			__window.visible = false;	
    			deactive();
    		}
    		private function __onActive($event:Event):void {
    			__isActive = true;
    			active();
    		}
    		private function __onDeactive($event:Event):void {
    			__isActive = false;
    			deactive();
    		}
    		private function __onEnterFrame($event:Event):void {
    			if( __buffer < getTimer() ) {
    				trace( "동작끝");
    				stage.removeEventListener(Event.ENTER_FRAME,__onEnterFrame);
    				__isAnimating = false;
    				if( __isActive ) {
    					active();
    				} else {
    					deactive();
    				}
    			}
    		}
    		private function __onHide($event:MouseEvent):void {
    			hide();
    			setTimeout(show,1000);
    		}
    		private function __onAnimate($event:MouseEvent):void {
    			animate( 1000 );
    		}
    		private function __onClosing($event:Event):void {
    			NativeApplication.nativeApplication.exit();
    		}
    	}
    }
    


    참고글
    Reducing CPU usage in Adobe AIR 
    [머드초보]시스템트레이아이콘(system tray icon) 예제


    글쓴이 : 지돌스타(http://blog.jidolstar.com/622)
    찬익님의 블로그(http://blog.chanik.com/25)로 부터 매우 재미있는 내용을 봤다. 바로 커스텀 메타데이터 태그(custom metadata tag)에 대한 내용인데 커스텀 컴포넌트, 커스텀 이벤트등의 용어는 들어봤어도 커스텀 메타데이터 태그는 생소하다. 말그대로 Flex에서 사용하는 메타데이터 태그인 [Event], [Bindable] 이런 것 들은 사실 예전부터 mxmlc로 컴파일될 때에 ActionScript 3.0으로 변환된다는 것은 알고 있었지만 메타데이터 태그가 이렇게 사용되는지에 대한 내막은 전혀 알지 못했던 것이 사실이다. 아무튼 너무 좋은 정보라 공유하고자 한다.

    사실 Flex 2시절부터 커스텀 메타데이타에 대한 이야기가 있었다고 한다. 하지만 Flex 3서부터 컴파일 옵션에 "-keep-as3-metadata MyTag"와 같은 형태로 사용할 수 있게 되었다. 이는 다음과 같이 ActionScript 3.0 코드에서 사용할 수 있게 된다.
    [Listen(obj="this.closeButton", event="click")]
    public function closeClickHandler(event:MouseEvent) {...}
    

    메타데이타 태그는 위처럼 메소드 뿐 아니라 변수 및 클래스에도 사용할 수 있다.


    커스텀 메타데이타를 사용하는 방법

    커스텀 메타데이타를 사용하는 방법은 3단계로 이뤄진다.

    1. 컴파일러 구성
    Flex 프로젝트 컴파일 옵션으로 "-keep-as3-metadata+=Meta1, Meta2" 를 삽입한다. 만약 라이브러리 프로젝트에서 한다면 거기에 이 옵션을 사용하면 이 라이브러리 프로젝트를 참조하는 다른 프로젝트에 따로 줄필요는 없다.

    2. metadata를 작성

    package
    {
        [Meta2(param1 = "param 1 value")]
        public class TestClass
        {
    
            [Meta1(param1 = "param 1 value", param2 = "param 2 value")]
            public var test1:String;
            
            
            [Meta2(paramA = "param 1 value", paramB = "param 2 value")]
            public function get test2():String
            {
                return null;
            }
            
            public function set test2(val:String):void
            {
            }
    
            [Meta1(param1 = "param 1 value")]
            public function someMethod():void
            {
            };
    
        }
    }
    

    3. 런타임(runtime)시에 작성한 메타데이타를 사용
    describeType(TestClass)를 이용한다. 위처럼 클래스와 메소드에 메타데이타를 작성했다면 다음과 같이 E4X형태의 XML로 접근할 수 있다.

    <type name="TestClass" base="Class" isDynamic="true" isFinal="true" isStatic="true">
      <extendsClass type="Class"/>
      <extendsClass type="Object"/>
      <accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
      <factory type="TestClass">
    
        <metadata name="Meta2">
          <arg key="param1" value="param 1 value"/>
        </metadata>
    
        <extendsClass type="Object"/>
    
        <method name="someMethod" declaredBy="TestClass" returnType="void">
          <metadata name="Meta1">
            <arg key="param1" value="param 1 value"/>
          </metadata>
        </method>
    
        <variable name="test1" type="String">
          <metadata name="Meta1">
            <arg key="param1" value="param 1 value"/>
            <arg key="param2" value="param 2 value"/>
          </metadata>
        </variable>
    
        <accessor name="test2" access="readwrite" type="String" declaredBy="TestClass">
          <metadata name="Meta2">
            <arg key="paramA" value="param 1 value"/>
            <arg key="paramB" value="param 2 value"/>
          </metadata>
        </accessor>
    
      </factory>
    </type>
    

    만약 Class의 [Meta2]의 param1에 접근한다면 다음과 같처럼 하면 된다.
    var testClass:TestClass = new TestClass();
    var xml:XML = describeType(testClass);
    trace(xml.metadata.(@name==Meta2).arg.(@key==param1).@value);
    

    위에 대한 결과는 'param 1 value'이 된다.


    어떻게 활용할 수 있을까?
    이것을 어떻게 활용할지에 대해서는 찬익님의 언급처럼 FlexUnit 4에서 지원하는 [Test] [BeforeClass]등과 같은 좋은 예시가 있다. 그 외에 것도 예제가 있는지 확인해봤다. 그 결과 재미있는 글을 발견했다.

    Annotating ActionScript Classes with Custom Metadata + Simple ORM Framework for AIR

    이 글에서 말하고 있는 요지는 커스텀 메타데이타를 이용해서 Value Object로 작성한 클래스에 이로 만들어진 객체가 데이타베이스의 어떤 테이블과 어떤 필드에 들어가는지 결정해줄 수 있다는 것이다.


    package
    {
    	[Bindable]
    	[Table(name="contact")]
    	public class Contact
    	{
    		[Id]
    		[Column(name="contact_id")]
    		public var contactId:int;
    
    		[Column(name="first_name")]
    		public var firstName:String;
    
    		[Column(name="last_name")]
    		public var lastName:String;
    		public var address:String;
    		public var city:String;
    		public var state:String;
    		public var zip:String;
    		public var phone:String;
    		public var email:String;
    	}
    }
    
    

    위처럼 작성된 클래스는 contactId 값은 contact 테이블의 contact_id 필드와 매치가 되도록 만든다는 것이다. [Id]메타데이타는 Primay Key와 같은 존재이다. 결국 이것을 해석해서 DB와 연동하는 AIR기반의 EntityManager를 만들어 위 클래스의 객체를 다음과 같이 사용하면 DB연동까지 된다.


    var contact:Contact = new Contact();
    contact.firstName = "Christophe";
    contact.lastName = "Coenraets";
    contact.email = "ccoenrae@adob.com";
    entityManager.save(contact);
    


    이를 수정하려면 다음처럼 쓴다.
    contact.firstName = "Chris";
    entityManager.save(contact);
    


    삭제하려면 다음처럼 쓴다.
    entityManager.remove(contact);
    

    너무 기발하다. 만약 Metadata를 사용하지 않았다면 Value Object와 DB 테이블의 필드명을 맞추기 위해 좀 노가다를 해야할지 모른다. 그런데 Metadata를 이용함으로써 프레임워크 단위로 말끔하게 이 문제를 해결해준다. 

    이에 대한 소스와 테스트 자료는 [여기]에서 다운로드 받는다.
    혹시 링크가 깨졌다면 다음 링크에서 받길 바란다. 
     
    이렇게 활용할 수 있다는 것이 감동이다. 
     

    정리하며
    찬익(http://blog.chanik.com)님 덕분에 아침부터 재미있는 정보를 얻을 수 있었다. 좋은 정보를 공유해주신 찬익님께 감사하며 커스텀 메타데이타에 대한 활용을 담은 글들이 많이 나왔으면 한다.


    참고글
    Custom metadata in three simple steps.
    Annotating ActionScript Classes with Custom Metadata + Simple ORM Framework for AIR
    찬익님의 커스텀 메타데이터 태그


    위 모든 예제는 Flash Builder 4 및 Flex Builder 3에서 테스트 해볼 수 있습니다.


    글쓴이 : 지돌스타(http://blog.jidolstar.com/620)

    안녕하세요. 1개월간 http://flexdocs.kr개인적인 이유로 운영하지 못하다가 좋은 무료웹호스팅을 통해 다시 정상적으로 열었습니다. 무료 웹호스팅 계정은 http://000webhost.com으로 외국 서비스이고 몇몇 한국분들이 잘 사용하신다고 들어서 부랴부랴 다시 복구하게 되었습니다. 약간 단점이 있다면 외국 서비스인 만큼 밤시간대에 느릴 수 있다는 겁니다.

    단, 기존에 SVN으로 Flex 2문서를 수정하는 작업이 진행되었는데 이제 그 시스템을 사용할 수 없게 되었습니다. 사실 이제 더이상 필요성을 못느끼고 있는터라 이제 안정적으로 서비스를 운영하는데만 초점을 맞출 예정입니다. 호스팅서비스 회사에서 강제로 계정을 지우거나 망하지 않는 이상 flexdocs.kr은 살아있을 겁니다.

    이제 flexdocs로 공개적인 모임이나 문서 업데이트는 하지 않을 것이며 Flex한글문서에 목마름을 조금이나마 달래주는 그런 사이트로서의 명맥을 유지할 것이라 생각합니다.

    Flex 4도 나온 마당에 버젓한 Flex 4 한글문서가 나오길 간절히 바라는 마음입니다.

    관심가져주신 분들께 깊은 감사드립니다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com)

    Flash Platform 개발자 분들에게 반가운 소식이 있습니다. AIR 2.0 Beta와 Flash Player 10.1 Prelease 버전이 공개되었습니다. 이제 Adobe Labs(http://labs.adobe.com/)에서 직접 다운로드 받을 수 있습니다.


     

    Adobe AIR 2.0 beta

    AIR 2.0은 다음과 같은 새로운 feature들이 추가되었습니다.(영어 압박? 그럼 : http://blog.chanik.com/entry/AIR-20-Beta-릴리즈 )

    • Support for the detection of mass storage devices.
    • Advanced networking capabilities like secure sockets, UDP support, and the ability to listen on sockets.
    • Support for native code integration.
    • The ability to open a file with its default application.
    • Multi-touch and gesture support.
    • New APIs for access to raw microphone data.
    • Webkit update with HTML5/CSS3 support.
    • Global error handling.
    • Improved cross-platform printing
    • Improved security and support for enterprise and government standards.

    AIR 2.0에 대해서 자세한 설명 다음 글들을 참고하세요. 



    AIR 2.0으로 개발테스트 하시려면 다음과 같이 합니다. 윈도우, Flash Builder 4 beta 2 환경을 가정하고 설명드립니다.

    더 쉽게 환경을 구축하고 테스트 해보시겠다면 다음 링크를 참고하세요.
    http://blog.jidolstar.com/619 

     

    1. 만약 Flash Builder를 설치 안했다면 다음 링크를 통해 받으세요.
    2. SDK와 Runtime을 다운로드 받습니다.
    3. 다운받은 Runtime을 실행해 설치합니다.
    4. SDK는 압축을 풀고 그안에 있는 내용을 Flash builder가 설치된 sdks/4.0.0과 sdks/3.4.1 폴더에 각각 덮어씌웁니다. 제 경우는 C:\Program Files\Adobe\Adobe Flash Builder Plug-in Beta 2\sdks\4.0.0
    5. Flash Builder를 실행합니다.
      Windows>Preferences에 들어가 Flash Builder > Installed Flex SDKs를 확인해보세요.
    6. Learn to Use Adobe AIR 2 Beta에 들어가 관련 내용을 학습합니다.
    7. Adobe AIR 2 Sample Applications에 들어가 Sample을 받아보고 실제 Flash Builder에서 작업해봅시다. (해당작업화면을 이미지 캡쳐하고 이런거 너무 힘들어서요. 그냥 말로 주절주절 써봅니다.)
      저는 일단 수많은 예제들중에 Microphone 예제의 소스를 다운로드 받았습니다.
      Flash Builder에서 Flex Project를 생성합니다. 물론 Application Type은 Desktop으로 설정해야겠죠? 예제를 다운받아 사용하므로 프로젝트 이름은 예제 소스 압축을 풀어보면 메인소스의 이름을 선택해주면 됩니다. 저의 경우 MicrophoneExamples군요. 프로젝트를 생성했으면 그 다음으로 Flash Builder에서 방금 생성한 프로젝트를 선택한 상태에서 마우스 오른쪽 버튼을 눌러 Import로 들어갑니다. 창이 뜨면 General > Archive File을 선택하고 다음 버튼을 누릅니다. Browse...버튼을 눌러 다운받은 소스압축파일을 선택합니다. 그리고 Finish하세요. Overwrite할거냐 물으면 Yes To All 을 선택하시면 됩니다. 초반에 sdk 에러가 뜰겁니다. 원본 소스의 sdk 설정이 달라서 그러는데요. 이것을 해결하기 위해 프로젝트명을 선택한후 마우스오른쪽 버튼을 눌러 Properties로 들어가 Flex Compiler를 선택한 다음 Use a specific SDK를 Flex 3.4로 선택해주시면 됩니다. 4.0이 아닌 이유는 예제 소스가 mx:WindowedApplication으로 만든 것으로보아 Flex 3.4 기반으로 만들어졌기 때문입니다. 다른 예제가 s:WindowedApplication으로 만들어져 있다면 Flex 4.0을 선택하세요. 다 끝났습니다. 이제 실행해보세요.


      앗... 제 데톱에 마이크가 없군요... ㅡㅡ; 아무튼 이렇게 하면 됩니다. Sample에는 Flex뿐 아니라 Ajax, Flash용도 있으니 잘 활용하시면 학습하는데 도움이 될겁니다.

     

    Flash Player 10.1 Prelease

    Flash Player 10.1의 가장 큰 특징은 바로 모바일 지원입니다. 제 블로그에서 앞서 설명했지만 Flash Player 10.1은 오픈소스프로젝트를 통해 탄생된 결과물입니다. 데스크탑 뿐아니라 다양한 기기에서도 Adobe Flash Platform 기술이 적용될 수 있도록 Flash Player 10.1을 만든겁니다. 이는 모바일과 같이 저급(?) 하드웨어 기반에서 항상 이슈가 되어 왔던 메모리, 전력소비, 속도, 하드웨어가속등의 문제를 해결한 Flash Player를 만들었다는 것을 의미합니다. 이외에도 차세대 모바일에 걸맞는 몇가지 기능(멀티터치등)을 구현할 수 있는 API도 추가되었죠. 또한 H.264 비디오도 지원해줍니다.

    Flash Player 10.1에 대해서 더욱 자세히 알고 싶다면 Flash Player 10.1 Beta Release Notes를 참고하시면 될 것 같습니다.

    Flash Player 10.1 기반의 프로그램을 개발하고 싶다면 Flash Player 10.1 플러그인을 자신의 컴퓨터에 먼저 설치한뒤 Player Global SWC를 다운받아 개발하면 됩니다. 일련의 절차를 간단하게 설명드립니다.

    참고로 Flash Player 10.1은 정식 배포버전이 아닙니다. 테스트 목적으로 하시고 실제 배포는 하지 마세요.

    Flash Player 10.1 플러그인을 http://labs.adobe.com/downloads/flashplayer10.html 에서 다운로드 받습니다. 기존에 설치된 Flash Player는 삭제를 하시고 설치하세요. 윈도우의 경우 파이어폭스와 모질라 계열이라면 Download plug-in for Windows (EXE, 2.2 MB)를 받고 IE라면 Download active-x for Windows (EXE, 2.2 MB)를 다운로드 받으세요. 아쉽게도 개발자를 위한 디버그 버전은 아직 없는 것 같습니다.

    Flash Player 10.1 기반 개발을 위해 해당 API로 개발할 수 있도록 지원해주는 SWC를 다운로드를 같은 페이지에서 받을 수 있습니다. 다운받아 압축을 풀면 playerglobal.swc가 있습니다. 이것을 자신의 프로젝트에 포함하여 개발하면 됩니다. 함께 포함된 readme.txt파일을 보면 사용하는 방법이 잘 나와 있는데 여기서도 간단히 설명해 드리죠. (단, Flash Builder 4 beta 2 기준입니다.)

    1. 만약 Flash Builder를 설치 안했다면 다음 링크를 통해 받으세요.
    2. 새로운 애플리케이션 프로젝트를 만듭니다.
    3. playerglobal.swc를 playerglobal10.1.swf로 이름을 바꾸고 만들어진 프로젝트의 libs폴더에 복사합니다. libs폴더에 복사하면 자동으로 Referenced Libraries로 설정됩니다. (여기서 이름을 바꾼 이유는 다음 내용을 설정할 때 playerglobal.swc의 Link Type을 수정못하도록 Flash Builder가 만들어져 있기 때문입니다.)
    4. 프로젝트명을 선택후 마우스 오른쪽 버튼을 눌러 properties를 선택한 뒤 Flex Builder Path를 선택합니다. 그런 다음 Library path 탭을 선택한뒤 libs 폴더를 열어 Link Type을 External로 바꿉니다. 그런 다음 Flex 4.0 를 열어 기존 playerglobal.swc는 삭제합니다. Link type이 External이라는 것은 컴파일시 사용한 클래스를 빼놓는다는 뜻입니다. 어짜피 playerglobal.swc에 정의된 클래스는 이미 Flash player 10.1에서 지원해주기 때문에 굳이 swf안에 포함할 필요가 없기 때문이지요.
    5. Flash Player 10.1을 요구하는 HTML이 되도록 만들어주기 위해 Properties 창에서 Flex Compiler를 선택합니다. Adobe Flash Player options에서 Use a specific version을 10.1.0으로 바꾸거나 Additional compiler arguments에 -target-player=10.1.0 을 추가합니다.

    이로써 Flash Player 10.1 에서 돌아가는 Flash 애플리케이션을 만들때 환경 구축을 모두 완료했습니다.

    아래처럼 Flash Player 10.1 API인 TouchEvent, TransformGestureEvent 등을 사용할 수 있게 되었습니다.



    아래 링크를 통해 Flash Player 10.1에 대한 다양한 정보를 얻을 수 있습니다.


    앞으로 정식버전이 배포되고 Flash CS5도 나오게 되면 아이폰 및 다양한 기기에서 동작하는 재미있는 애플리케이션들을 만들 수 있게 될 것입니다. 

    Flash Platform 개발자들이여.. 향후 10년 밥벌이 벌었습니다. ㅋㅋㅋ


    글쓴이 : 지돌스타(http://blog.jidolstar.com/617)
     

    적도좌표계(Equatorial Coordinate System)는 항성(별)의 위치를 측정하는 가장 기본좌표계이다. 이 좌표계에서 별의 위치는 적경(Right Ascension)과 적위(Declination)값이 되며 이 좌표값의 기준점은 춘분점이 된다. 세차 및 장동과 같은 지구의 미세한 변화로 인해 이 춘분점은 계속 바뀌므로 필요에 따라 기준 시간(원기, Epoch)를 지정한다. 

    현대의 별자리는 1930년 국체천문연맹(IAU)에서 88개의 별자리로 지정되어 있다. 이 별자리는 서양기준 별자리이며 각 나라 및 문화권 마다 다양한 별자리가 있긴 하지만 통상 별자리라고 하면 이 88개 별자리를 지칭한다. 

    별자리는 단순히 별과 별을 이어주는 선으로 연결된 것만 지칭하지 않는다. 명확한 별자리 영역(Constellation Boundaries)이 존재한다. 이 영역은 1875년을 원기로 하는 춘분점을 기준으로 만들어졌으며 오늘날에도 사용하고 있다. 1875년 당시의 별자리 경계선은 적경,적위에 따라 아래 그림처럼 네모 반듯한 모습을 가진다. (투영방식은 정적도법을 이용했다.)


    하지만 시간이 지날수록 지구의 운동으로 인해 춘분점이 변해감에 따라 1875년 당시의 별자리 영역은 조금씩 틀어지기 시작했다. 아래 그림은 2000년을 원기로 했을 경우 별자리 경계 영역을 보여주고 있다. 위 그림과 달리 왜곡되어 있는 모습을 쉽게 볼 수 있다.



    그러므로 어떤 특정 별 또는 천체가 어떤 별자리에 속하는지 확인하기 위해서는 그에 상응하는 계산을 해줄 필요가 있다. 즉, 1875년 기준으로 해당 천체의 별자리가 지정된 것이므로 주어진 원기에 대한 천체의 별자리를 찾기 위해서는 별자리 영역을 당시에 지정된 영역으로 왜곡한다.

    이러한 과정을 할 수 있는 소스 및 데이타가 이미 공개되어 있다. 다음 링크를 참고한다.

    Identification of a Constellation From Position (Roman 1987)

    C로 만들어진 코드도 있으므로 참고한다.
    http://astrocomplutense.es/dobles/program.c 

    나는 위에서 소개한 코드를 이용해서 Flash로 원기와 적경,적위를 입력하면 그에 대응하는 별자리 정보를 출력해주는 아주 간단한 애플리케이션을 만들어 보았다.


    이 프로그램은 세차운동만 적용되었으며 또한 보간데이터를 활용했기 때문에 아주 정확하게는 경계선 부근의 천체의 경우 실제 별자리가 안나올 수 있다.

    이 이론은 SNS서비스인 스타플(http://starpl.com)에도 적용되어 있다.


    글쓴이 : 지돌스타(http://blog.jidolstar.com/616)

    율리우스 적일(積日, Julian Date)을 계산하는 방법에 대해서 정리했다.  율리우스 적일은 천문계산의 기본이면서도 매우 중요한 개념이다.

     

    율리우스 적일(Julian Date)은 다음과 같이 정의된다.

     

    장소 : 영국 그리니치(Greenwich, 경도 0도) 기준
    시점 : B.C. 4713년 1월 1월 세계시 12시
    부터 세어진 날의 수

     

    율리우스 적일(이하, JD)는 ‘어느 시점부터 몇 일째 되는 날’과 같이 날짜를 세는 방법이라고 생각하면 된다. 적일에서 ‘적(積)’자는 쌓을 적자이다. 즉 날짜를 쌓아간다는 의미이다. 한가지 더 고려해야할 것은 단순히 날짜만 세는 것이 아니라 소수점까지 포함한 시간까지 확장한다. 그러므로 JD는 날짜와 시간을 하나의 실수값으로 정의가 가능해진다.

     

    JD가 천문학에서 많이 활용되는 이유는 천체의 운동을 기술하기 위해 시간을 편리하게 적용할 수 있기 때문이다. 일상 생활에서 사용하는 년월일,시분초는 인식하기 편하지만 계산을 위해서는 JD로 변환이 필요하게 된다.

     

    JD은 Julian Days, Julian Day Numbers와 동일한 의미를 가지지만 Astronomical Algorithms 저자 Jean Meeus는 Julian Date대신 Julian Day로 사용할 것을 권면하고 있다. 왜냐하면 Date라는 용어는 달력에서 사용하는 년,월,일을 표기해야할 것 같은 느낌이 강하기 때문이라고 한다. 그러나 필자는 그러한 구분은 상관없다고 판단한다.

     

    In many book we read “Julian Date” instead of “Julian Day”. A date consists of a year number, a month, adn a day of the month, in any calendar. For me, a Julian date is a date in the Julian calendar, just as a Gregorian date refers to the Gregorian calendar. The JD has nothing to do with the Julian calendar.

    Astronomical Algorithms 2nd edition Chapter 7 에서 발췌

     

    한가지 더 언급하자면 Jean Meeus도 지적했듯이 JD와 율리우스력(Julian Calendar), 그레고리력(Gregorian calendar)와 다르다는 점을 인식해야한다. JD는 날수(day number)이고 율리우스력이나 그레고리력은 달력이다. 이에 대해서는 다시 언급하겠다.

     

    필자는 JDN(Julian Day Number)는 JD의 정수부분이라고 정의하겠다. 그러므로 1999년 1월 1일 0h UT는 JD로 2451179.5이고 JDN는 2451179이 된다.

     

    시간(Time)

    JD는 세계시(Universal Time, 이하 UT)를 기준으로 한다. JD와 UT와의 관계를 말하기에 앞서 잠깐 시간(Time)에 대해서 언급해 보는 것이 좋을 것 같다. 왜냐하면 시간의 변천 역사와 체계에 대한 이해가 선수되어야 천문 관련 계산에 도움이 되기 때문이다. 하지만 정확히 이해하기에는 쉽지 않다. 그래도 개념정도는 알아야 한다고 생각한다.

     

    진태양시(True Solor Time)

    태양일을 기준으로한 시간. 태양이 자오선상에 도달한 후 다시 도달할 때까지 걸리는 시간을 24시간으로 결정하는 시간체계이다. 지구의 궤도가 타원이고 자전주기가 변하기 때문에 매일매일 다른 시간체계를 가지게 된다. 과거 태양을 기반으로 시계를 만들었을때 사용했으며 오늘날에는 사용하지 않는다.

     

     

    항성시(Sidereal Time)

    별을 기준으로 하는 시간이다. 별이 자오선상에 도달한 후 다시 도달할 때까지 걸리는 시간으로 대략적으로 23시간 56분 4초 정도로 4분 정도 실제하루와 차이가 발생한다. 이렇게 차이가 발생하는 이유는 지구가 자전하면서도 공전하기 때문이다. 일상생활에서는 별보다는 태양이 우선이므로 항성시는 사용하지 않지만 천문계산을 위해서는 꼭 필요한 시간체계이다.

     

     

    평균태양시(Mean Solar Time, 이하 MST)

    1초는 평균태양일(Mean Solar Day)의 1/86400 간격으로 정의되었다. 평균이라는 단어가 생긴것은 실제 태양이 자오선에서 다시 자오선에 오는데 걸리는 시간이 계절에 따라 빠르게는 16분 느리게는 14분까지 주기가 변하기 때문에 실제 태양의 운동으로 재는 시간은 오차를 없애고자 사용한 것이다. 이러한 오차는 지구의 자전축의 기울기와 지구의 공전궤도가 타원형이라는 것에서 기인한다. 이 오차를 균시차(equation of time)이라 불리고 “균시차 = 진태양시 – 평균태양시” 관계를 가진다.


    평균태양일은 지구의 자전속도가 느려지는 것을 보정해주지 못한다.

     

    MST는 1956년 이전까지 사용했던 시간이다.

     

     

    세계시(Universal Time, 이하 UT)

    UT는 전세계적으로 통용될 수 있는 시간의 필요성에 따라 만들어진 시간체계이다. UT는 그리니치(경오 0도)상에서 자오선에 대한 평균태양시(MST)이다.  그리니치 평균태양시(Greenwich Mean Time, 이하 GMT)와 UT는 정확하게 같지 않다. 원래 GMT는 자정이 아닌 정오에 시작하도록 만든 시간이다. 1925년 1월 1일에 GMT를 12시간 앞당겨 자정으로 시작하는 것을 제정했고 1928년 이전 GMT와 새로운 GMT의 용어의 혼란을 피하기 위해 만들어진 것이 바로 UT이다. 오늘날에는 GMT와 UT는 같은 의미로 통용되지만 천문학에서는 사용하지 않는다.

     

    UT는 10분의 수초에 정밀도를 요하는 경우 UT0, UT1, UT2로 세부적으로 구분하여 사용하게 된다.

     

    • UT0 :  가장 기본적인 UT로 별의 자오선 통과를 광학적으로 측정하여 구한 UT. 현대적인 방법으로는 GPS를 이용한다. 이것은 실제 UT로 통용되지는 않는다.
    • UT1 : UT0를 보정한 UT로 지구의 자전축의 운동(극운동) 때문에 생기는 자오선의 변화에 대한 보정을 가한 UT이다. 일반적으로 UT라고 하면 UT1을 의미한다. 오늘날 UT라고 하면 UT1을 의미한다.
    • UT2 : UT1에서 지구의 계절별 자전율 변화에 의해서 일어나는 작은 불규칙성 보정한 UT. UT2는 10-7의 상대오차를 가진다. UT1과 UT2의 관계는 다음과 같다.
      UT2 - UT1 = 0.022 * sin(2*pi*t) - 0.017 * cos(2*pi*t)
      - 0.007 * sin(4*pi*t) + 0.006 * cos(4*pi*t)
      t = 2000.0 + (MJD - 51544.03) / 365.2422
      pi = 3.14159265...여기서 UT1은 초단위이고 t는 Besselian Date값, 그리고 MJD는 Modified Julian Date(Julian Date-2400000.5)이다.

     

    UT와 GMT에 대한 더 자세한 내용은 아래 링크를 참고한다.

     

     

    UT를 다른지역의 시간대로 변경할 수 있다. 가령 한국표준시(Korean Standard Time, 이하 KST)는 UT보다 9h이 빠르다. 그러므로 다음과 같은 관계가 성립한다.

     

    • UT = KST – 9h

     

    표준시(Standard Time)은 일상적으로 사용할 수 있도록 시간대를 형성해주는 반면 지방시(Local Time)는 경도에 따라서 달라진다.

     

     

    동적시(Dynamical Time,이하 DT )

    DT의 역사는 역표시(Ephemeris Time, 이하 ET)로 부터 시작한다.

     

    ET는 1900년 1월 0일 12시 태양년의 1/31556925.9747을 1초로 삼는 시간체계이다. 그러므로 이 ET는 매우 정확한 비율로 계산되는 시간체계가 된다.

     

    MST기반인 UT의 경우 평균값을 이용하기 때문에 달과 태양에 의해 일어나는 조석마찰로 인해 느려지는 지구의 자전주기와 유동성을 갖고 있는 지구 내부 물질의 움직임에 의해 자전의 속도가 달라지는 현상에 의해 지구를 기준으로하는 실제 시간은 느려진다는 것을 발견하게 되었다. 아래는 1973년부터 2008년까지 지구 자전의 주기의 변화를 보여주고 있다.

     

    1956년에 국제도량형위원회(CIPM)은 지구자전대신 지구의 공전을 기초로 하는 ET를 시간의 표준으로 결정하게 된다. 이것은 달, 행성, 우주의 다른 태양계 천체들의 운동에 대해 기술할때 사용된다.

     

    지구 공전으로 기준삼은 ET는 지구 자전으로 기준삼는 UT와 시간차이가 멀어지기 시작했다. 1902년 이후 2000년에 UT와 ET는 63초 이상 차이가 벌어졌다.

     

    ET는 1960년에서 1983년까지 사용했다. 지구공전기반의 ET는 1984년에 원자시계 기반인 지구동적시(Terrestial Dynamic Time:TDT)로 대체되었다. 더불어 태양계의 질량중심과 관련된 Barycentric Dynamical Time(TDB)도 사용되었다. TDB와 TDT의 차이점은 태양계의 질량중심을 기준으로 한다는 것 외에는 같으며 0.0017초 차이 정도난다. TDT는 2001년에 지구시(Terrestial Time,TT)로 용어가 변경된다. 일반적으로 TT(TDT)와 TDB는 거의 차이가 없기 때문에 구분하지 않지만 20 microseconds보다 더 작은 정확도를 가지는 행성의 위치를 계산해야하는 경우 TDB를 사용한다.

     

    참고로 TDT는 TAI(국제원자시)와 아래와 같은 관계가 성립된다.

     

    TDT = TAI + 32.184 = UTC + (윤초수, 2009년까지 34초) + 32.184

     

    TDB는 아래와 같은 공식이 성립한다.

    TDB = TDT + 0.001658 sin( g ) + 0.000014 sin( 2g ) seconds
    g = 357.53 + 0.9856003 ( JD - 2451545.0 ) degrees
    JD는 Julian Date이고, g는 지구의 mean anomaly이다.

     

    만약 짧은 시간의 정확성이 중요하다면 UT와 ET의 차이값을 알아야한다. 이값은 ΔT로 표기되며 다음과 같은 공식을 가진다.

     

    ΔT = ET – UT (1984년 이전), TDT – UT(1984년~2000년), TT - UT(2001년부터)

     

    ΔT는 아래와 같이 변한다.

    • ΔT = +65초 (2000년)
    • ΔT = +69초 (2005년)
    • ΔT = +80초 (2015년)

     

    ΔT는 다음식으로도 유도가 가능하다.

    ΔT = 32.184 + (TAI-UTC) - (UT1-UTC)

     

    TAI-UTC은 해당년도까지 적용된 윤초이다. 이에 대한 자료는 [여기]를 참고한다.


    UT1-UTC는 세계시와 세계협정시 차이값이다. 이들 값에 대해서는 다음에 설명하는 세계협정시(UTC)를 보길 바란다.

     

    ΔT는 지구의 운동으로 인해 아주 정확하게 예측하는 것은 불가능하다. 결국 과거의 자료를 토대로 예측하는 방법을 쓸 수 밖에 없다. 아래 사이트에는 ΔT를 계산하는 c코드가 공개되어 있다. dltat.c 를 참고한다.

     

    ET에 대한 더욱 자세한 내용은 다음 글을 참고한다.

     

     

     

    국제원자시(International Atomic Time, 이하 TAI)

    지구의 운동 기반이 아닌 세슘원자시계를 기반으로한 국제표준이다. TAI에서 1초는 세슘원자가 2개의 초미세구조 사이에서 전이할때 복사 또는 흡수하는 전기에너지 주기의 9,192,631,770배와 동일한 시간으로 정의한다.  TAI의 원점은 1958년 1월 1일 0h UT2로 한다. TAI는 원자시계를 기반으로 하므로 매우 정밀하다. 실생활에는 쓰이는 시간체계는 아니지만 과학적 용도로 쓰기에 적합하다. 실생활에 사용하는 시간은 아래에서 설명하는 세계협정시(UTC)이다.

     

    1987년 12월 31일까지 국제시보국(BIH)에서 TAI, UTC, 지구자전요소 계산 업무를 했고, 1988년 1월 1일부터는 국제도량형국(BIPM)의 Time Section에서 TAI, UTC 결정업무를 맞고 지구자전요소 결정 및 윤초의 결정 통보는 국제지구자전연구부(IERS)에서 담당하게 되었다.

     

     

     

    세계협정시(Coordinated Universal Time, 이하 UTC)

    오늘날에는 일상생활에 UTC을 국제표준으로 삼고 있다. UTC는 TAI기반으로 UT1과 0.9초 내로 차이가 나도록 윤초(leap second) 개념을 도입한 것이다. 즉 TAI는 현재 가장 정확한 시간체계이고 UTC는 그 정확한 시간에 지구자전도 고려한 시간인 것이다.

     

    윤초는 1972년부터 국제시보국(BIH)에서 최초로 도입했고 1988년 1월 1일 부터는 국제지구자전연구부(IERS)에서 지구자전요소의 결정 및 윤초의 결정과 통보업무를 담당하고 있다.

     

    매월 말에 윤초를 넣을 수 있지만 6월 말, 12월 말에 더하거나 빼는 것만으로도 충분하다.

    아래 그림은 UT1과 UTC의 차이를 보여주고 있는 그래프이다. (출처:위키피디아)


    UT1과 UTC가 0.9초 내로 조정되는 것을 확인하자.



    참고로 UT1-UTC는 ΔUT 로 표현한다. 예언되는 ΔUT를 DUT로 쓰여진다.

     

    UT1 = UTC + DUT1

     

    위 식에서 DUT1은 IERS Bulletin A를 참고하여 예상이 가능하다. 2009년 DUT1은 다음과 같이 계산한다.

    DUT1 = UT1-UTC =   0.3597 -  0.00093 (MJD - 54924) - (UT2-UT1)

     

    여기에서 UT2-UT1은 UT설명시 나왔던 값이므로 계산후 그대로 대입하면 되겠다.

    [여기]에 DUT1 예상 결과가 잘 나와 있다.

     

    아래는 TAI와 UTC의 관계식이다.

     

    윤초(leap second) = TAI – UTC

     

    1972년에 윤초를 10초를 더한 것을 시작으로 2009년 까지 양의 윤초만 적용하여 위 공식에 의하면 윤초는 2009년 현재 34초가 된다.

     

     

    위 관계를 표로 나타내는 것은 다음 링크를 보자.

     

    아래 링크를 통해 UTC와 윤초에 대해서 더욱 공부할 수 있겠다.

     

    GPS 시간(GPST)

    GPS는 1980년 6월 1일 UTC가 TAI와 19초 차이나는 시점부터 적용했다. TAI와 같은 원자시계를 기반으로 하며 UTC처럼 윤초를 더하지 않는다.

     

    시간 관계 (Time Relationships)

     

    위 그림은 지금까지 설명한 시간관계를 하나의 그래프로 보여주고 있다.

     

     

    Julian Date(JD)와 Julian Ephemeris Date(JDE)

    JD는 UT를 기반으로 한다고 언급했었다. 하지만 JDE는 ET(TT,TDT)를 기반으로 한다. UT는 지구의 운동에 따라 계산되어 실제로는 명확하지 않지만 일상생활에서는 가장 적합하다. 하지만 ET의 경우 원자시계를 기반으로 한다. 앞서 ΔT = TDT(or ET) – UT 를 언급했다. 실제로 2015년에는 +80초가 된다. 그러므로 JD와 JDE는 구분되어 생각해야한다.

     

    • 1977년 4월 26.4일 UT = JD 2443259.9 = JD 2443259.9 UT
    • 1977년 4월 26.4일 ET = JDE 2443259.9 = JD 2443259.9 ET

     

    위는 다른 값이라는 것을 항상 생각하자.

     

    그레고리력(Gregorian Calendar)과 율리우스력(Julian Calendar)

    율리우스력은 해당 년도가 4로 나누어 떨어지는 경우 365일이 아닌 366일을 1년으로 삼는 달력이다. 이렇게 하루를 포함하는 해를 윤년(Leap Year)이라고 한다. 이런 윤년이 발생하는 이유는 일년이 딱 365일이 아니기 때문이다. 율리우스력에서 1년은 365.25일이다. 그러므로 4년이 흐르면 1일이 차이가 나게 되어 있다. 이것을 보정하기 위해 만든 것이 윤년인 것이다.

     

    가령, 900년 1236년의 경우 4로 나눠어지므로 윤년이고 750년 1429년은 보통해이다.

     

    율리우스력은 1582년 10월 4일까지 사용되어졌다.

     

    그레고리력은 기본적으로 율리우스력과 비슷하게 4년마다 윤년이 있다. 대신 4로 나눠떨어지지만 400으로 나눠떨어지지 않는 해는 윤년이 아닌 보통해로 생각한다. 이 달력은 오늘날 사용하고 있다.

     

    가령, 1700년, 1800년, 1900년은 4로 나눠지지만 400으로 나눠지지 않으므로 보통해이다. 반면 1600년, 2000년, 2400년은 4로 나눠지면서 400으로 나눠 떨어지므로 윤년이 된다.

     

    그레고리력은 율리우스력과 다르게 1년을 365.2425일로 본다.

     

    그레고리력은 1582년 10월 15일부터 사용하기 시작했다. 여기서 율리우스력의 마지막 날과 그레고리력의 시작일에 비어 있다. 1582년 10월 5일에 그레고리력을 사용하기 시작하면서 그 다음날을 1582년 10월 15일로 만들었기 때문이다. 이렇게 된데에는 율리우스력이 역법상 오차가 있었기 때문인데, 당시 춘분점이 3월 21일이어야하는데 율리우스력에 의하면 3월 11일로 무려 10일이나 차이가 났기 때문이다. 율리우스력이 1년을 365.25로 해서 그것이 누적되어 원래 시간보다 더 빨리 계수되는 달력이였기 때문에 이것을 극복하고자 1년을 더 정확한 365.2425일로 수정하고 이에 맞는 그레고리력을 사용하게 된 것이다.

     

    한국의 경우 조선시대에 1895년 을미개혁 때인 양력 1896년 1월 1일(건양 원년)부터 그레고리력을 처음 사용하게 되었다.

     

    날짜로 부터 Julian Date 계산

    JD는 앞서 설명한대로 4713년 B.C.부터 계수한 날 수이다. JD는 또한 경도 0도를 기준으로하는 UT를 기반으로 하기 때문에 천문계산에 시간의 척도로서 적합하다. “언제 목성이 어디에 있었나”를 질문할 때 “언제”가 명확하기 때문이다.

     

    UT에 해당하는 날짜가 주어졌을 때 JD를 계산하는 방법에 대해서 생각해보자.

     

    1. Y는 해당년도, M는 월(1월=1,2월=2), D는 해당 월의 날짜이다. D는 시간값도 포함한 소수값으로 생각하자. 가령 3일 12시 UT라면 D=3.5이다.
    2. M>2인 경우 Y,M은 변경하지 않는다.
      M = 1 또는 2인 경우 Y=Y-1, M=M+12로 계산한다.
    3. 그레고리력(Gregorian Calendar)의 경우 아래처럼 계산한다.
      A = INT(Y/100), B = 2 – A + INT(A/4)
      여기서 INT는 ()안에 들어간 값을 넘지않는 가장 큰 정수이다.
      율리우스력(Julian Calendar)의 경우 B=0이다.
    4. JD는 다음과 같이 계산된다.
      JD = INT(365.25(Y+4716)) + INT(30.6001(M+1)) + D + B – 1524.5
      여기서 30.6001은 정확히는 30.6을 써야한다. 하지만 컴퓨터 계산시 10.6이여 하는데 10.599999999 이런식으로 표현되는 경우가 발생하면 INT(10.6)과 INT(10.5999..)의 결과가 달라진다. 이 문제 대해 대처하기 위해 30.6001을 사용한 것이다. 이러한 에러를 Round-off Error라고 불린다.

     

    계산시 중요한 점은 B.C.(기원전)에 대한 처리이다. 1년 B.C. 다음 해는 1년 A.D.이다. 0년 A.D.가 없기 때문에 이것은 계산하는 사람으로 하여금 혼동을 주게 된다. 그래서 위 계산식에서 Y값을 정할때 B.C.값의 경우 –(B.C.값)+1 로 처리한다. 가령, 585 B.C.는 -584이다. 참고로 –582 B.C. 처럼 B.C.에 –를 사용하지 말자. 잘못된 표기이다.

     

    그러므로 기원전 4713년 1월 1일 12UT부터 JD가 시작하므로 Y=-4712, M=1, D=1.5 를 계산식에 넣으면 값은 0이 떨어지게 되어 있다.

     

    만약 1582년 10월 5일부터 1582년 10월 14일에 해당하는 값을 가지고 계산하려는 경우는 막아야한다. 앞서 설명했지만 율리우스력은 1582년 10월 4일까지이고 그레고리력은 1582년 10월 15일 부터이여서 그 사이 날짜는 실제로 없기 때문이다.

     

    만약 주어진 해의 1월 0.0일(=이전해 12월 31.0일)의 JD를 계산하려면 다음식을 이용한다. 단 그레고리력일때만 된다.

     

    Y = year – 1

    A = INT( Y / 100 )

    JD0 = INT(365.25Y) – A + INT(A/4) + 1721424.5

     

    위 조건에서 1901년부터 2099년까지만 고려한다면 다음식을 사용한다.

    JD0 = 1721409.5 + INT(365.25 x (year-1) )

     

    Julian Date로부터 날짜 계산

    여기서 언급하는 JD가 음수값일때는 적용할 수 없다.

    1. JD + 0.5의 정수부분은 Z, 소수점 이하 부분은 F로 지정한다.
    2. A값 계산
      Z < 2299161이면 A=Z 이다.
      Z >= 2299161 이면 다음식을 적용
      alpha = INT( (Z – 1867216.25)/36524.25)
      A = Z + 1 + alpha – INt( alpha/4 )
    3. B,C,D,E 결정
      B = A + 1524
      C = INT( (B –122.1) / 365.25 )
      D = INT( 365.25 C )
      E = INT( (B – D)/30.6001 )
    4. 일(day) 계산
      day = B – D – INT(30.6001 E ) + F
    5. 월(month) 계산
      E < 14일 경우 month = E – 1
      E = 14 또는 15일 경우 month = E - 13
    6. 년(year) 계산
      month>2일 경우 year = C-4716
      month=1,2일 경우 year = C-4715

     

    위에서 30.6001대신 30.6을 쓰면 컴퓨터 계산이 오류가 있을 수 있다. day의 소수점 이하값은 시간을 뜻한다. 소수점값만 취해서 24를 곱하면 시간이 되겠다.

     

    Julian Date와 날짜와의 관계표

    아래 관계값은 계산시 비교해볼 수 있도록 하기 위해 적는다.

    • 2000년 1월 1.5일 = JD 2451545.0
    • 1999년 1월 1.0일 = JD 2451179.5
    • 1988년 1월 27.0일 = JD 2447187.5
    • 1600년 12월 31.0일 = JD 2305812.5
    • 837년 4월 10.3일 = JD 2026871.8
    • -123년 12월 31.0일 = JD 1676496.5
    • -1001년 8월 17.9일 = JD 1355671.4
    • -4712년 1월 1.5일 = JD 0.0

     

    Modified Julian Date(MJD)

    JD은 너무 큰 숫자이다. 또한 JD는 하루의 시작이 정오이다. 그래서 1858년 11월 17일 0h UT를 기준으로 하는 JD를 따로 지정했는데 MJD이다.

    MJD는 다음과 같이 계산한다.

    MJD = JD – 2400000.5

     

    시간간격 계산

    헬리혜성은 근일점(perihelion)을 1910년 4월 20일에 도달한 후 1986년 2월 9일에 다시 도달했다. 이 날짜동안의 날수를 어떻게 계산할 것인가? JD를 이용하면 아주 쉽게 계산이 가능하다.

     

    1910년 4월 20.0일 =  JD 2418781.5
    1986년 2월 9.0일 = JD 2446470.5
    차이는 27689일이다.

    요일 계산하기

    1954년 6월 30일 무슨 요일일까? 계산이 어려울 것 같지만 JD를 이용하면 매우 쉽게 계산이 가능해진다. JD는 월요일부터 시작한다. 단순하게 아래 계산식대로 하면 요일을 쉽게 계산할 수 있다.

    (JD + 1.5) % 7 (%은 나머지를 계산하는 식이다.)

    결과값이 0이면 일요일, 1이면 월요일 6이면 토요일이된다.

    1954년 6월 30일에 대응하는 JD는 2434923.5이다. 1.5를 더하면 2434925이므로 7로 나누면 3이 나온다. 결과는 수요일이다.

     

    참고글

     

    참고서적

    • Astronomical Algorithms 2nd, Jean Meeus

    오늘날 우리가 일상생활에서 사용하는 시간체계는 UTC(Coordinated Universal Time; 세계협정시)이다. 이는 원자시계를 근간으로 하지만 더불어 지구의 운동까지 고려한 UT(Universal Time;세계시)와 0.9초 차이가 안나도록 윤초(leap year)를 매년 6월 말 및 12월 말에 넣어주는 방식을 택하고 있다. 즉, UTC는 원자시계 + 지구운동을 같이 고려하고 있는 것이다. 원자시계를 기반으로하는 시간체계는 TAI(International Atomic Time, 국제원자시)이다. 이 시간은 2009년 이래로 UTC와 +64초 차이가 나게 되었다. TAI는 매우 정교한 시간을 만들어주기 때문에 느려지거나 빨라지지 않는다. 하지만 UTC는 UT와의 차이를 좁혀주므로 지구의 운동에 민감하게 된다.  UTC와 UT는 거의 구별을 안하지만 엄밀히 말하면 다르다. UT는 지구자전에 의해 연속적으로 변하는 값이지만 UTC는 UT와 0.9초 벌어지지 않기 위해 불연속적으로 변한다.

     

    천문계산을 하는데 있어서 일상에서 사용하는 시간과 과학을 목적으로하는 시간체계는 차이가 있다. 일상생활에 사용하는 UT나 UTC는 과학계산에 적합하지 않다. 왜냐하면 균일한 시간이 아니기 때문이다. TAI를 사용하기 이전에 시간의 균일하지 않은 문제를 해결하기 위해 ET(Ephemeris Time,역표시), TDT(Terrestiral Dynamical Time), DT(Dynamical Time)등을 만들게 되었다.

     

    식(eclipse, 일식/월식등)을 예견하기 위해 태양, 달의 위치는 Terrestiral Dynamical Time(DT)기반으로 계산된다. 왜냐하면 DT는 균일한 시간체계이기 때문이다. 우리가 평소에 쓰는 것은 UTC이고 이것은 UT와 차이없도록 만들어진다. 이는 지구의 운동에 관련된 시간체계이기 때문에 균일한 시간이 아니다. 그래서 일상생활의 시간에 어떤 천문 현상을 예측하기 위해 항상 UT와 DT의 차이값을 알아야만 한다. 이 차이값을 delta-T(ΔT)라고 하며 다음과 같은 관계를 가진다.

     

    ΔT = DT – UT = 32.184 + (TAI-UTC) - (UT1-UTC)

     

    이 식을 보면 알겠지만 ΔT는 DT와 UT의 차이면서 TAI, UTC, UT와 관계가 있다는 것을 알 수 있다. 즉 DT를 몰라도 UT, TAI, UTC만 알면 충분히 계산해 낼 수 있는 시간이다. UT의 경우 지구의 운동을 측정하면 되고 TAI는 원자시계이므로 이미 알고 있는 값이다. 또한 UTC도 윤초를 더한 날이 언제인지 알고 있으므로 충분히 알아낼 수 있는 값이다. 하지만 이런 관계는 현대시간에서만 이런 계산이 가능하다. 일반적으로 ΔT를 계산하는데는 과거, 현재, 미래까지 통용할 수 있는 식을 원한다. 그럼 먼저 과거의 ΔT는 어떻게 구할까?

     

    과거의 ΔT는 역사적 기록으로부터 추론된다. 일찍이 우리나라를 비롯한 유럽, 중동, 중국등의 나라에서는 수십건의 식(eclipse)을 관측하고 기록해왔다. 역사적 기록은 낮은 정확성에도 불구하고 과거 ΔT를 계산하는데 중요한 자료로 사용된다. 1609년부터는 망원경을 이용한 천체관측을 시작하게 되어 달의 별을 가림현상(달의 엄폐)등을 관측하여 전보다 높은 정확도를 가진 관측자료를 만들게 되어 ΔT의 오차를  줄일 수 있게 되었다.

    현대에는 지구 운동과 독립적인 원자시계 및 퀘이사 전파측정으로 거의 완벽한  ΔT를 계산할 수 있게 되었다. 하지만 지구의 운동은  어떻게 변할지 예상하기 힘들다. 1965년부터 1980년까지 ΔT는 평균적으로 1년에 0.99초씩 증가했다. 또 1985년부터 2000년까지 ΔT는 평균적으로 1년에  0.63초씩 증가했다. 2000년부터 2005년까지는 단지 0.18초 증가했다. 이렇게 불규칙하게 변하는 ΔT이기 때문에 미래의 ΔT의 정확한 예측은 이론적으로는 힘들다. 대신 달에 의한 조수차로 지구의 자전 주기가 길어지는 것은 예상이 가능하다. 예측 계산에 따르면 2010년에는 ΔT는 +67초, 2050년에는 +93초, 2100년에는 203초, 그리고 2200년에는 442초가 될 것이라 예상할 수 있다.

     

    ΔT를 계산하는 방법은 매우 다양하다. 본인은 이 ΔT를 계산하기 위해 NASA(미국항공우주국)에서 제공하는 “다항식을 이용한 ΔT 계산”을 이용해서 B.C. 2000년 부터 A.D. 3000년까지 ΔT를 계산해보았다.

     

    다음 프로그램에서 버튼을 누르면 ΔT를 계산해서 출력해준다. 누르고 반응이 없더라도 기다리면 출력된다.

    여기서 사용한 식은 Morrison과 Setphenson[2004]가 연구결과로 만든 것으로 달의 평균운동으로 인해 결정된 조석항(the secular tidal term in the mean motion of the moon)을 -26 arcsec/cy^2로 가정하고 계산한 것이다. 하지만 ELP-2000/82 lunar ephemeris에서 사용하는 달의 식변화는 이 값을 -25.858 arcsec/cy^2((Chapront, Chapront-Touzé, and Francou,2002)으로 계산했다. 그러므로 필요한 경우 최종 계산된 결과값인 ΔT에 아래와 같은 작은 보정값 c를 구해 더해줄 필요가 있다.  참고로 cy는Julian century를 뜻한다.

     

    c = - 0.91072 * (ndot + 26.0 ) * t^2
    t = (year-1955)/100
    ndot=-25.858

     

    위 보정값 c를 아래처럼 ΔT에 더한다.

     

    보정된 ΔT = ΔT + c

     

    위 ndot는 다른 값을 넣을 수 있다. 필요에 따라서 보정값을 조정하면 되겠다.

     

    앞서 설명했지만 ΔT는 정확하게 구할 수 있는 값이 아니다. 연구하는 사람마다 다르게 나오며 시간이 지날 수록 지구의 운동을 측정을 통해 계속 보정하여 다른 식으로 바뀌어질 수 있다. 그때는 이 식을 사용하는 사람이 알아서 기존에 사용하던 식을 대체할 필요가 있을 것이다.

     

    해당 년도에 대해 역사적으로 추론 또는 근래 계산법에 의한 ΔT의 오차범위에 대한 정보는 “Historycal values of DELTA T(ΔT)”를 참고한다.

     

    참고글

     

     글쓴이 : 지돌스타(http://blog.jidolstar.com/486)

    세차운동(歲差運動, Precession)은 일반적으로 강체에 돌림힘(Torque)이 작용할 때, 회전하는 물체의 축이 어떤 부동축의 둘레를 회전하는 현상을 말한다. 천문학에서 말하는 세차운동은 지구의 자전축이 불변인 황도면의 축의 둘레를 2만 5800년 주기로 회전하는 운동을 말한다. 이 현상은 지구에 작용하는 달, 태양의 중력과 지구가 타원체라는 것에서부터 기인한다.

     

    천문 계산을 하는데 있어서 세차를 고려해야 과거,미래의 천체위치를 제대로 예측할 수 있다. 왜냐하면 세차운동으로 인해 지구 자전축이 변한다는 것은 천체 위치의 기준점인 춘분점(Vernal Equinox)의 위치도 변한다는 것을 의미하기 때문이다.

     

    여기서는 적도좌표계(Equatorial Coordinate System)황도좌표계(Ecliptic Coordinate System)에서 세차운동 계산 방법을 학습하는 것을 주 목표로 한다. 세차운동의 공식 유도 및 사용되는 각종 다항식이 어떻게 만들어졌는지 알아내는 과정은 생략한다.

     

    이 글의 참고 서적은 Jean Meeus의 Astronomical Algorithms 2nd임을 밝혀둔다. 하지만 이 책에서 소개한 대로만 하지 않으며 더 효과적으로 계산하는 방법도 소개하겠다.

     

    이 글은 세차운동 계산시 정밀한 방법(Rigorous Method)을 이용한다. 낮은 정밀도를 가지는 방법은 책을 참고하길 바란다.

     

    Julian Century

    1 Julian Century는 100 Julian Year와 같다.  Julian Year는 86000 SI 초를 기반으로하는 365.25일을 1로 측정하는 단위이다. Julian Century는 Julian Year의 100배 이므로 36525일을 1로 측정하는 단위이다.  보통 J2000.0 = JDE 2451545.0 = 2000년 1월 1일 12h DT를 0으로 시작하게 된다. 여기서 JDE는 Julian Ephemeris Date, DT는 Dynamic Time을 의미한다.

     

    그러므로 Julian Date(JD)로부터 Julian Century는 다음과 같이 계산한다.

     

    T = (JD0 – J2000) / 36525

     

    세차운동의 경우 어떤 시작원기(the initial epoch)부터 마지막 원기(the final epoch)까지의 세차운동값을 계산해야한다. 그러므로 시작원기와 마지막원기의 Julian Century차이는 다음과 같다.

     

    t = (JD – JD0) / 36525

    T, t에서 JD0는 시작원기, JD는 마지막 원기를 의미한다.

     

     

    적도좌표계에서 세차운동적용 (일반 공식 이용)

    Meeus의 책에 보면 다음과 같은 적도좌표계에서 세차운동을 계산하기 위한 수치적 표현공식이 적혀있다.

     

    ζ = (2306”.2181 + 1”.39656T – 0”.000139 T2 ) t
          + (0”.30188 – 0”.000344T)t2 + 0”.017998 t3

    z = ζ + (0”.79280+0”.000411*T)t2 + 0”.000205t3

    ϑ =(2004”.3109 – 0”.85330T + 0”.000217T2)t
           - ( 0”.42665+0”.000217T )t2 + 0.041833t3

     

    이 식은 IAU(International Astronomical Union, 1976)에서 지정한 값으로 관측을 통해 얻어지는 데이타로 만들어지는 식이다. 참고로 IAU에서는 IAU2006 세차모델을 사용하도록 권하고 있고 미해군천문대에서는 2009년부터 천문역서에 적용하고 있다.(참고 : IAU2006 세차모델)

     

    시작원기의 적경,적위를 (α0, δ0)라고 하고 세차운동이 적용된 마지막원기의 적경,적위를 (α, δ)로 한다면 위 수치적 표현공식을 이용해 다음과 같이 (α, δ)를 계산할 수 있다. 이 식을 유도하는 과정은 구면천문학에 관련된 책을 참고하기 바란다.

     

    A = cos(δ0)  sin( α0 + ζ )

    B = cos( ϑ ) cos( δ0 ) cos( α0 + ζ ) – sin( ϑ ) sin( δ0 )

    C = sin( ϑ ) cos ( δ0 ) cos( α0 + ζ ) + cos( ϑ ) sin( δ0 )

    tan(  α – z ) = A/B

    sin( δ ) = C

     

    이 식만으로도 정확한 계산을 할 수 있다. 그러나 컴퓨터 프로그램을 이용하여 계산을 한다면 이 식은 매우 비효율적인 식이 된다. 가령, 수천, 수만개의 별이 있다고 하자. 그 별들은 어떤 시작원기를 가지는 각각 다른 α0, δ0값을 가지고 있을 것이다. 이들 α0, δ0을 세차운동이 적용된 새로운 α, δ를 계산하기 위해 일일히 cos, sin을 계산한다. 컴퓨터 프로그램을 한 사람은 알겠지만 cos, sin은 컴퓨터의 많은 자원을 소비하는 급수형태의 계산식이다. cos, sin을 너무 많이 쓰면 계산의 효율이 급격하게 떨어져서 1분이면 도출할 수 있는 계산을 1시간~2시간 이상식 소비할 수도 있게 된다.

     

    컴퓨터 프로그램을 이용한 계산을 하려면 되도록이면 cos, sin과 같이 매우 비싼 자원을 소비하는 함수 사용을 지양하도록 해야한다. 그래야 빠른 계산을 할 수 있기 때문이다.

     

     

    적도좌표계에서 세차운동적용 ( 변환행렬이용 )

     

    앞서 세차운동이 적용된 새로운 α, δ를 계산하는 식은 한개의 천체 위치를 계산할 때마다 α0, δ0이 포함된 cos, sin을 계산해야한다. 그래서 새로운 α0, δ0가 주어져도 cos, sin을 최소한으로 이용하여 계산하는 방법을 여기서 소개한다. 그것은 변환행렬(Transformation Matrix)를 이용하는 방법이다.

     

    변환행렬을 이용하면 다음과 같은 계산이 가능해진다. 세차운동을 적용하는 3x3 변환행렬을 P라고 하자. 그리고 α0, δ0를 직교좌표계로 바꾼 백터(vector)를 r0=[x0,y0,z0]라고 하고 세차운동이 적용된 α, δ를 직교좌표계를 바꾼 백터를 r=[x,y,z]라고 한다. 그럼 다음과 같이 계산하면 되겠다.

     

     

    변환행렬 P는 세차운동이 적용되어 앞서 설명한 공식과 다르게 α0, δ0이 분리가 되었다. 행렬 P만 구하면 cos, sin과 같이 큰 자원을 소비하는 함수를 계산할 때마다 이용할 필요가 없어지며 대신, 단순하게 행렬과 백터 곱으로 수천,수만개의 별을 빠른 시간안에 세차운동을 적용할 수 있게 된다.

     

    그럼 P는 어떻게 구할까? 그것은 회전변환행렬을 이용하면 된다. 앞서 구한 ζ, z, ϑ 값은 모두 세차운동에 의해 계산된 각도이다. 이 각도를 이용해 시작원기때의 적도좌표를 3번 회전변환하여 마지막원기때의 적도좌표를 구하는 것이다.

     

    회전변환행렬에서 X축 회전 행렬을 Rx, Y축 회전 행렬을 Ry, Z축 회전행렬을 Rz라고 하자. 이들 행렬은 모두 3x3 행렬이다.

     

     



     

     

    다른 참고를 보면 행렬안에 sin의 부호가 바뀐것을 볼 수 있는데 회전의 대상을 어떻게 정하느냐에 따라서 다른 결과이다. 천문계산에서 회전행렬은 위 정의대로 하는 것이 좋다.

     

    적도좌표계에서 세차운동을 적용한 변환행렬 P는 다음과 같이 주어진다.

     

     

     

    위 변환행렬은 ζ, z, ϑ 이 적용된 행렬곱이다. 이 행렬곱의 결과는 다음과 같다.

     

     

    컴퓨터 프로그램으로 계산할 때는 이렇게 변환행렬을 만들어 계산하면 되겠다.

     

     

    황도좌표계에서 세차운동적용 ( 변환행렬이용 )

     

    황도좌표계에서도 적도좌표계와 동일한 방식이 적용된다.

     

    Π = 174˚.876383889 + 3289”.4789 + 0”.60622 T2 
          - (869”.8089 + 0”.50491T)t + 0”.03536 t2

    π = (5029”.0966 + 2”.22226T – 0”.000042T2)t
         + (1”.11113 – 0”.000042T)t2 – 0”.000006t3

    η = (47”.0029 – 0”.06603T – 0”.000598T2)t
         + (-0”.03302 + 0”.000598T)t2 + 0.000060t3

     

    시작원기 황경(ecliptic longitude),황위(ecliptic latitude)가 λ0,β0 이고 마지막원기 λ,β이라고 하자. 이때 각각의 직교좌표값을 r0, r이라고 한다면 변환행렬 P는 다음과 같다.

     


     

    결과적으로 P는 다음과 같이 결정된다.

     

     

     

    응용하기

    지금까지 세차운동이 적용된 2가지 변환행렬을 알아보았다. 이 행렬은 필요에 따라서 선택해서 사용한다. 한가지 예를 들어보겠다. 적도좌표계에서 세차운동을 적용한 변환행렬을 Pequ, 황도좌표계에서 세차운동을 적용은 변환행렬을 Pecl이라고 하자. 황도좌표계에서 적도좌표계로 변환하는 행렬을 EclToEqu라고 가정한다. 만약 어떤 행성의 황경과 황위를 계산했다고 하면 다음과 같은 2가지 방법으로 새로운 적경, 적위를 계산할 수 있을 것이다.

     

    r = EclToEqu Pecl  r0

    r = Pequ Ecl2Equ  r0

    여기서 r0는 황경,황위를 담은 직각좌표계에서의 벡터값, r은 적경,적위를 담은 직각좌표계에서의 벡터값

     

    세차운동 계산기(Precession Calculator)

    이 프로그램은 지금까지 설명한 내용을 토대로 만든 프로그램이다.  

     

     

    참고


    글쓴이 : 지돌스타(http://blog.jidolstar.com/487)



    Flash, Flex, AIR 개발자들이 한권정도는 꼭 가지고 있을 만한 책이다. 이 책은 콜린 무크의 유명한 책인 Essential ActionScript 3.0을 지금도 업계에서 활발히 일하고 계신 유윤선, 송호철님이 번역한 책이다. ActionScript 3.0에 관한한 거의 바이블 수준이다. 시간될 때마다 쭉 훑어보며 느낀 거지만 ActionScript 3.0에 대한 전반적인 개념을 습득하는데 이만한 책이 없다고 생각된다. 

    Flash, Flex, AIR를 개발할 때 가장 기초가 되는 개념은 ActionScript 3.0이다. ActionScript 3.0에 대한 전반적인 개념 및 지식없이 Flex에 접근하다 보면 결국 한계를 느끼게 될 것이다. 왜냐하면 Flex 프레임워크가 모든 기능을 수행해 줄 수 있도록 만들어진 것은 아니기 때문이다. Flex는 프레임워크로서 가치가 있는 것이지 모든 기능을 Flex에서 찾으면 안된다. 또한 Flash로 개발할 때도 마찬가지 인데 타임라인만 의지하다가 뭔가 고급기술을 다뤄야할 필요가 있을때 ActionScript 3.0을 알아야 가능해진다. Flash든 Flex든 AIR든... Flash Platform 개발자가 접근할 수 있는 가장 기초가 되는 것은 결국 ActionScript 3.0이다. (예외적으로 AIR의 경우 Javascript로 만들 수 있는데 이는 컴파일러를 지원하는 차원이지 근간은 ActionScript 이다.)  

    이 책의 한가지 단점이라면 Flash Player 10 API에 해당하는 내용까지 다루지는 않고 있다. 이와 관련된 내용은 여전히 온라인의 ActionScript 3.0 라이브 독을 참고해야한다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com/615

      

    일본에 Spark 프로젝트가 있다. 이 프로젝트는 ActionScript Class Library를 오픈소스로 만들고 공유하는 것을 하나의 모토로 삼고 있는데 그 유명한 증강현실(FLARToolKit)을 Flash로 구현한 것도 이 프로젝트에서 진행된 것이다. 이외에도 너무 멋진 라이브러리가 이 프로젝트를 통해 만들어졌고 여기서 만들어진 몇몇 라이브러리를 실무에 적용하고 있다.

     
    Flex 4에는 Spark로 구성된 새로운 컴포넌트들이 존재한다. 그런데 이 Spark가 바로 일본의 Spark 프로젝트의 이름에서 온것임이 확신된다. 즉, Flex 4의 Spark 컴포넌트는 많은 부분에서 일본이 관여했다는 것을 의미한다. 실제로 2009 Adobe MAX에서 Spark 패키지 설계에 나이 20정도 되는 젊은 일본 개발자와 관계가 있다는 것을 확인했다.

    Spark 프로젝트로 일본은 Adobe의 큰 관심을 받게 되었고, 일본 RIA시장에 있어서 없어선 안될 정도로 크게 성장해 있는 상태이다. 아마 일본 RIA 분야에 있어서 자부심을 생기게 하는 훌륭한 프로젝트 팀이라고 생각한다.

    이에 반해 한국은 이 정도로 활발히 진행되는 오픈 프로젝트가 전무하다. 이는 빨리빨리 주위의 한국의 개발 환경덕택에 개발자들이 다른 활동할 수 있는 여지가 없는 것이 가장 큰 문제겠고, 오픈 지향이 미덕으로 방향을 잡고 있는 마당에도 유독 소프트웨어는 폐쇄적인 것도 문제라고 생각한다. 또한 개발자들은 무슨 문서 작성하는 사람처럼 취급해 버리는 무지한 관리자들이 있는한 한국에서 일본과 같은 개방적이고 활발한 개발 커뮤니티나 오픈 소스, 오픈 프로젝트는 어려울 것이다. 당장 문제를 해결하고 이익만 추구하는 한국의 IT시장이 지속된다면 소프트웨어 개발자는 그저 노동만 하고 그에 대한 돈만 벌어가는 존재로 전락할 수 밖에 없다. 창의적인 개발자는 사라지고 그저 일만 받고 처리하는 수동적인 개발자만 늘어나게 될 것이고 결국 한 나라의 IT시장의 발전에 큰 저해요소로 작용할 것이 분명하다.

    한국에도 일본 Spark 프로젝트와 같은 활발하고 멋진 팀이 자발적으로 시행될 수 있는 국가적, 기업적 지원이 적극적으로 이뤄지길 희망한다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com/573)


    프로그램상에 폰트를 포함할 때 다음과 같은 문장을 사용할 것이다.

    [Embed(source='C:/WINDOWS/Fonts/ARIAL.TTF', fontName = "MyFont", mimeType = "application/x-font-truetype")]
    private var font:Class;
    

    그런데 Flash Builder 4에서 작업할 때부터 위처럼 Embed한 폰트가 적용이 되지 않았다. 사용한 True Type 폰트는 언제나 테스트할때 문제없이 동작한 것이기 때문에 왜 안되는지 의문이 들었다. Flash Builder 4의 컴파일러인 mxmlc가 버그가 있거나 다른 설정이 추가되었나 싶었지만 그렇지 않았다. 그래서 열심히 구글링해봤더니 폰트를 Embed 처리할때 embedAsCFF 속성이 추가되었다는 것을 발견하고 아래처럼 테스트 코드를 작성해봤다.

    package {
    	import flash.display.Sprite;
    	import flash.text.TextField;
    	import flash.text.TextFormat;
    	import flash.text.TextFieldAutoSize;
    	import flash.text.AntiAliasType;
    	[SWF(width = "500", height = "240", backgroundColor = "#FFFFFF", framerate = "24")]
    	public class FontTest extends Sprite {
    		[Embed(source='C:/WINDOWS/Fonts/ARIAL.TTF', embedAsCFF="false", fontName = "MyFont", mimeType = "application/x-font-truetype")]
    		private var font:Class;
    		public function FontTest() {
    			var format:TextFormat = new TextFormat();
    			format.font = "MyFont";
    			format.size = 30;
    			format.color = 0x000000;
    			var label:TextField = new TextField();
    			label.embedFonts = true;
    			label.autoSize = TextFieldAutoSize.LEFT;
    			label.antiAliasType = AntiAliasType.ADVANCED;
    			label.defaultTextFormat = format;
    			label.text = "blog.jidolstar.com";
    			addChild(label);
    			label.rotation = 5;
    		}
    	}
    }
    


    여기서 embedAsCFF 속성은 도데체 뭔가? 이 전에 CFF(Compact Font Format)가 뭔지 알아야만 했다.

    폰트형태는 여러가지 종류가 있다. OTF(Open Type Font)와 TTF(True Type Font)등이 있다. OpenType은 MS와 Adobe가 TrueType의 후속작으로 Cross Platform을 지원하도록 만들 파일 포멧이라고 한다. 이 OpenType에는 Glyph Outline형식에 대한 정의는 없고 TrueType, CFF, Adobe Type 1(PostScript)를 사용한다고 한다. 이 말은 OTF중에는 TrueType으로 글자모양을 정의한 폰트도 있고 CFF 또는 PostScript Font로 정의한 폰트도 있음을 의미한다.
     
    OpenType과 TrueType에 대한 더욱 자세한 내용은 아래 링크를 참고하자.
    http://en.wikipedia.org/wiki/OpenType
    http://en.wikipedia.org/wiki/TrueType

    CFF는 OpenType의 하나로서 다음 링크에 더 세세하게 설명하고 있다.
    http://en.wikipedia.org/wiki/Compact_Font_Format#Compact_Font_Format

    CFF에 대한 스펙은 다음 파일을 참고한다.


    Flash Player 10과 Adobe AIR 1.5이상부터 TTF뿐 아니라 CFF도 지원하게 되었다. 이는 PDF와 직접적인 관계가 있는 것 같다.  그래서 폰트를 포함할때 TTF인지 CFF인지 명시할 필요에 의해 생긴 것이 embededAsCFF인듯하다. 만약 TTF를 사용한다면 위 예제처럼 embededAsCFF를 false로 지정해야한다.

    필자도 Font Type에 대한 지식은 거의 없어서 난감했다. CFF를 포함하면 글꼴 렌더링시에 텍스트를 보다 쉽게 읽을 수 있으며 작은 크기에서 글꼴의 표시 품질이 향상된다고 Adobe ActionScript 3.0 가이드 문서에는 나와 있을뿐 정확히 TTF와 CFF에 대한 명확한 해석을 찾지는 못했다.

    Adobe ActionScript 3.0 가이드 : 글꼴을 이용한 작업


    갑자기 대두된 Font 포함문제 때문에 매우 당혹스러웠지만 그나마 해결방법이라도 찾아 다행이다. 이에 대해 정확히 기술된 한글문서를 아직 찾지 못했다. 더욱 다양한 글들이 많이 올라왔으면 한다.

    이 실험은 Flash Builder 4 Beta 2에서 테스트 했으며 아래 링크로부터 Flash Builder 4를 다운받을 수 있다.
    http://www.adoberia.co.kr/pds/down.html?src=text&kw=000026



    글쓴이 : 지돌스타(http://blog.jidolstar.com/614)
    Flex 또는 ActionScript 3.0 라이브러리를 만들면서 하나의 네임스페이스를 지정키 위해 manifest설정을 하게 된다. (manifest를 모른다면 일단 Flex Builder에서 나만의 manifest만들기를 먼저 읽어보길 바란다.)

    Flash Builder 4 beta 2를 이용하면서 같은 방법으로 manifest를 설정하는데 아래와 같은 에러가 계속 나는 것이다.


    경험상 소스상에는 문제가 없는 것이기 때문에 다른 부분을 찾다가 beta 2에 있는 새로운 기능을 포착했다. 아래 이미지는 해당 프로젝트에서 Properties > Flex Library Builder Path 에서 Classes 탭을 선택했을때 화면이다. 이전 버전의 Builder에서는 보이고 있는 라디오 버튼이 없었던 것으로 알고 있는데 이번에 새롭게 들어갔나보다.

    저 기능은 라이브러리 컴파일시 컴파일할 대상의 Class를 선택하는 기능인데 이번에 기능이 바뀌면서 자동으로 모든 클래스가 포함되는 것을 선택하도록 하는 기능이 추가된 것이다. 이게 기본값이다. manifest관련 에러가 났던 것인 이 부분이 원인이였다. 다음 처럼 Select classes to Include In the library를 선택해보고 다시 컴파일하면 에러가 사라진다.


    아놔~~ 이 때문에 1시간 허비했다. 아직 beta버전이니깐 넘어가자. 정식버전 나오면 나아지겠지.

    Flash Builder 4는 이전 Flex Builder 3에 비해 너무도 많은 부분이 개선이 되었다. 이제 개발툴로서 제대로 자리매김하는 것으로 보인다. Flash Builder 4 및 다른 제품군을 다운 받기 위해 아래 링크를 참고하길 바란다.

    http://www.adoberia.co.kr/pds/down.html?src=text&kw=000026

    글쓴이 : 지돌스타(http://blog.jidolstar.com/613)

    + Recent posts