#1. [H.264] 동영상 압축의 기본 개념
#2. [H.264] 동영상 압축의 기본 개념 – RGB, YCbCr, 서브샘플링
#3. [H.264] Block-based Motion Estimation(ME)
#4. [H.264] Discrete Cosine Transform(DCT) 이산 코사인 변환
#5. [H.264] Quantization(양자화)과 Zig-zag scanning

안녕하세요? 맨날맑음 입니다.

이전 포스팅에서는 동영상의 정의와, 동영상을 압축하는 기본적인 방법에 대해 간단히 살펴 보았습니다.
이번 시간에는 동영상을 압축하는 자세한 방법을 알아보기 전에 색공간 RGB, YUV(YCbCr) 그리고 YUV 서브샘플링에 대해 알아 보도록 하겠습니다.

본론으로 들어가기 전에 간단하게 색공간(Color Space)에 관해 알아 보도록 하겠습니다.
위키의 설명에 보면 색공간은 색표시계를 3차원으로 표현한 공간 개념이라고 나와있습니다. 즉 색을 표현하는 방법은 여러가지(RGB, CMYK, HSV, CIE, YCbCr 등)가 있는데 각각이 색을 표현하는(담는) 방법이라고 생각해도 될 것 같습니다.

#1. RGB 색공간

-그림1. RGB 채널의 원리 -

빛의 삼원색(빨강, 녹색, 파랑)을 이용하여 색을 표현하는 방법 입니다. 원색 신호라고도 불립니다. 일반적으로 TV나 모니터, HTML의 색상표현에 쓰이며,  컴퓨터의 모니터는 RGB방식을 사용하므로, 우리가 영상을 화면에 디스플레이 하기 위해서는 RGB로 변화 하여야만 합니다. 각 값들은 0~255까지의 값(8bit)을 가지고 있으며, 각 값들을 가산 혼합하여 색상을 표현합니다.

#2. YCbCr 색공간

-그림2.  YCbCr 이미지의 구성요소-

YCbCr에서 Y는 휘도(빛의 양)이고, Cb와 Cr은 색차(크로마)를 나타냅니다. 색차 신호라고도 불립니다.이 방식은 RGB보다 색상의 분리 및 전달 효과는 약하지만, 적은 데이터로 보다 많은 색상을 나타낼 수 있는 장점을 가집니다.

YUV를 사용하는 대표적인 기계는 텔레비전입니다. 지금은 전부 칼라 텔레비전이지만, 예전 흑백의 경우 휘도(밝기)만을 가지고, 방송을 하다가, 컬러TV는 CbCr성분을 더해서 방송을 하기 때문에 지금에 와서도, 흑백 TV를 볼 수 있는 이유입니다. Y 성분만 뽑아 쓰면 되니까요.

이 방식에서 흑백을 표현하기 위해서는 Y 성분만 있으면 됩니다. 사람이 물체를 인식하는데 휘도(밝기)에 민감하지 그 외 Color 성분은 별로 민감하지 않습니다. 이것은 눈의 특성을 보면 알 수 있는데요, 사람의 눈에서 휘도를 구분하는 ‘간상세포’는 눈 전체영역에 분포하게 되고, 색을 구분하는 ‘원추세포’는 눈의 중심부에만 분포하여, 사람의 눈은 색상보다는 밝기에 민감하게 됩니다.

이렇게 때문에 사람이 민감하지 않은 모든 색상정보를 전부 포함해야 하는 RGB방식에 비해서, 적응양의 데이터(약 2/1)로도 비슷한 화질을 낼 수 있게 됩니다. (아래 설명 할 서브샘플링을 하였을때..)

영상장치가 영상을 만들 때, 빛을 인식하여(RGB)를 YUV로 바꿔 주게 되며, YUV를 압축하기 위해 서브샘플링을 하여 데이터를 줄이고, 저장장치에 저장하게 됩니다. 계속 알아야 할 개념이 늘어나고 있습니다. 다음은 서브샘플링에 관해 알아보겠습니다.

#3. YbCbCr 서브샘플링

동영상(Image)의 크기를 줄이기 위해 YbCbCr을 그대로 사용하기 보다는 압축하는 것이 효율 적입니다. 앞서 설명처럼 인간은 색차 보다는 밝기에 더 민감하기 때문에 샘플링에서도 이러한 기법을 사용함으로써 데이터를 압축 할 수 있습니다. 쉽게 말해 Y성분을 CbCr보다 많이 할당 함으로써 데이터를 줄이는데 감소한 데이터에 비해서 시각적인 차이는 거의 없게 됩니다.

러한 샘플링 기법은 Y Cb Cr의 각각의 비율에 따라 조금씩 다르게 나누어 집니다. 인코딩에서 사용하는 샘플링 비율은 아래의 3개의 비율을 사용합니다.

  • YCbCr 4:4:4 format

                                                  - 그림3. YCbCr 4:4:4 format -
     각 픽셀당 Y Cb Cr이 각각 4개가 들어가게 됩니다.

 

  • YCbCr 4:2:2 format
                                                       - 그림4. YCbCr 4:2:2 format -
    각 픽셀당 2개의 블럭을 기준으로 Y 4개 CbCr 2개를 공유하여 사용합니다.

  • YCbCr 4:2:0 format (YCbCr 4:1:1)
                                                        - 그림5. YCbCr 4:2:0 format -
    이 방식이 동영상 인코딩시 표준으로 쓰입니다. 그림5를 보면 YCbCr 4:1:1로 표현하는 것이 맞지만 표준화 기구에서 정한 이름이 4:2:0이라고 합니다. 저도 처음에 이름 때문에 좀 개념이 해깔렸습니다. 각 픽셀당 Y 4개 CbCr 1개를 공유하여 사용합니다.

    내용 추가 '09.09.02
    YCbCr (4:2:0) 와 YCbCr (4:1:1) 의 차이는 같이 사용하는 자료들도 있고 다르게 구분하는 자료들도 있는 것 같군요. 그래서 제 생각에는 YCbCr (4:2:0) 와 YCbCr (4:1:1)이 유사하다고 생각하면 댈 것 같습니다.  실제로 많은곳에서  같은 서브샘플링 개념으로 사용되고 있는 것도 사실이니까요.


#4. YCbCr 4:2:0 video format일 경우 데이터 구조는 어떠할까요?

데이터의 Type은 Byte이고 값은 0~255의 값이 들어갑니다. 그리고 중요한 것은 YCbCr의 경우 파일의 정보를 표시하는 ‘헤더’는 존재 하지 않습니다. 그래서 다음과 값은 데이터 구조를 가지게 됩니다.


-그림6. YCbCr 4:2:0의 데이터 구조-

빨간 점선은 프레임의 구분을 의미 합니다. 즉 한 프레임에 해당하는 Y정보가 먼저 나오고, 뒤를 이어 Cb Cr의 정보가 파일에 쓰여 있습니다. 예를 들어  해상도가 10X10이고, 10개 Frame으로 구성된 동영상의 경우 Y 100개, cb 25개, Cr25개의 정보가 쭉 이어 나오고 또 Y 100개, cb 25개, Cr25개의 정보가 쭉 이어 나오는 식으로 총 10개(10 프레임이므로)가 나열 됩니다.

 

생각보다 간단한 구조입니다. 이렇게 생각해 보면 파일에서 100Byte를 읽어오고 50Byte 건너뛰고 또 100Byte를 읽고 50Byte를 건너뛰는 방법으로 파일을 읽어 새롭게 저장하면, 원래 영상에서 흑백영상을 뽑아 올 수도 있습니다.

이번엔 RGB, YCbCr의 색공간에 대해 살펴 보았고, 서브샘플링에 관해서도 살펴 보았습니다. 다음 번에 어떠한 내용부터 올려야 할지 아직 잘 모르겠네요. 일단은 Motion Estimation 기법에 관해 올리려고 생각 중에 있습니다.




-------------------------------------------------------------------------------------------------------------------------------------------

# 마치면서..(좀더 알아볼 점)

  • YCbCr에서 Y는 어떤(? 정확히 모르겠어요) 용어와 해깔리지 않기 위해 Y’라고 표현해야 맞다고 합니다. 즉 Y’CbCr입니다. 대강 설명하자면 아날로그 인코딩과 디지털 인코딩 방식의 차이라고 알고 있습니다.
  • YUV와 YCbCr이 같은 거처럼 글을 썻지만, 사실 비슷하지만 조금 다른 개념인 것 같습니다. 저는 인코딩에 필요한 개념으로 용어에 따른 차이가 있을 수 있겠습니다.

 

참고 사이트 : http://www.fourcc.org/

Posted by 맨날맑음
,

#1. [H.264] 동영상 압축의 기본 개념
#2. [H.264] 동영상 압축의 기본 개념 – RGB, YCbCr, 서브샘플링
#3. [H.264] Block-based Motion Estimation(ME)
#4. [H.264] Discrete Cosine Transform(DCT) 이산 코사인 변환
#5. [H.264] Quantization(양자화)과 Zig-zag scanning

안녕하세요? 맨날맑음 입니다.

#0. 들어가며..

방학을 이용하여, H.264 코덱을 공부하는 것을 목표로 해서 동영상의 인코딩, 디코딩에 관해 배워 나가고 있습니다.

이 내용들은 제가 세미나를 통해 배운 내용을 나름대로 정리하여 잊어 버리지 않게 하는 것이 용도이고, 저도 세미나 도중 이해 가지 않는 부분이 있으므로, 제가 올린 내용이 틀릴 수 있습니다. 내용에 틀린 점이 있을 시 지적해 주시면 즉시 내용을 수정 하도록 하겠습니다.

#1. 동영상이란?

동영상 압축의 기본을 알기 위해선 우선 동영상이 무엇인지 알아야 합니다.

 
-그림1. 동영상의 구조-

위의 그림에서 알 수 있듯이 동영상은 정지된 사진(프레임)이 시간을 축으로 여러 장 모여 있는 것입니다. 즉 주어진 시간 동안 연속으로 촬영된 사진이라 볼 수 있습니다.(사실 여기서 소리도 포함 되야 하지만, 소리는 별도의 인코딩 과정을 거친 후 먹싱 되므로 소리는 빼고 생각해 보겠습니다.)

렇다면 동영상(RGB 비디오)의 데이터 크기는 어떻게 될까요? 프레임은 그림파일(Image)와 같습니다. 예를 들어 512×512의 동영상이 있다고 하면, 이 동영상의 한 프레임을 기준으로 가로 : 512, 세로 : 512개의 점(픽셀)으로 구성되어있으며, 1픽셀은 8bit(256 levels)입니다.  그 중에서 회색이 차지하는 비율만 생각해 보면, Width×Height×Bit(512×512×8)가 됩니다. 그리고 3개의 Color들로 구성 되어있으므로, RGB의 경우 Width×Height×Bit×Color(512×512×8×3)이 되어 6291456bit가 됩니다. 6291456(bit) == 6144(kbit) == 6(mbit) 입니다.

512×512라는 비교적 작은 사이즈의 동영상도 무 압축일 경우, 한 프레임에 6메가라는 큰 사이즈가 되는 것입니다. 동영상서비스를 제공 할 경우 적어도 초당 24프레임을 보장해야 한다고 하니 1초의 동영상에 144메가라는 큰 용량이 필요한 것입니다. 이것이 우리가 동영상 압축을 공부해야 하는 이유이기도 합니다.

네트워크 기술이 많이 발달하였지만, 이런한 무 압축 비디오까지 실시간으로 전송 하는 데는 역부족 입니다. 더군다나 요즘 많이 보급된 FullHD의 경우 1920×1080의 해상도를 지원해야 하기 때문에, 어마어마한 데이터 양이 될 수 밖에 없습니다. 또한 네트워크 대역폭이 이 정보를 전부 전송해 줄 수 있다고 하더라도, 동영상의 크기를 줄여, 그 남는 대역폭을 이용하여 여러 다른 서비스를 제공 해 줄 수도 있을 것입니다.

 

#2. 동영상 압축 기법(동영상 압축을 위한 기본적인 방법)

이러한 동영상을 압축 하려면 어떻게 해야 할까요?

동영상 압축에는 크게 세 가지 기법이 사용 됩니다. 첫번째는 프레임 간의 상관관계 (temproal correlation)이고, 두번째는 공간적인 상관관계(spatial correlation)입니다. 세번째는 저주파 성분에 민감한 시각의 특성을 이용합니다 (DCT 변환).

                        -그림2. 프레임 9-                                                   -그림3. 프레임 10-

먼저 프레임간의 상관관계를 알아보도록 하겠습니다. 그림 2와 3은 각각 같은 동영상(football_sif.yuv)의 9번과 10번 프레임입니다. 마치 같은 그림처럼 닮아있습니다.  근접한 프레임은 거의 비슷한 이미지로 이루어져 있다는 아이디어를 이용하여, 영상의 크기를 줄일 수 있습니다.


-그림 4. Image의 공간적인 중복-

그림 4에서 보는 것과 같이 한 장의 이미지에도 인접한 픽셀간의 정보는 거의 중복 된다는 것을 알 수 있습니다. 이방법으로도 영상의 크기를 줄 일 수 있을 것입니다.

Temporal correlation(프레임간의 상관관계)은  그림 5,6처럼 현재 프레임(t)의 하나의 픽셀(x,y)은 이전 프레임(t-1)의 비슷한 위치(x,y)에 있을 가능성이 높고, 또한 두 필셀 사이에는 유사성이 높다는 것입니다.

                       -그림 5. Frame(t)-                                                -그림 6. Frame(t-1)-

이렇게 프레임(t)에서 프레임(t-1)과의 차를 계산해 보면 중복 데이터가 사라지고, 차이인 그림 7이 나오게 됩니다.


      -그림 7. Frame(t) 와 Frame(t-1)의 Difference - 


이번에 간단한 동영상의 정의와, 동영상을 압축하는 기본적인 방법을 알아 보았습니다. 다음에는 RGB와 YUV를 알아보고, 위에서 간단히 소개한 압축의 기법들을 자세히 소개하는(주파수 도메인(DCT)을 이용한 방법, Motion Estimation 등) 순으로 진행 할 예정이며, 기본적인 지식을 갖춘 후, H.264에 대해서도 알아 보도록 하겠습니다.

Posted by 맨날맑음
,

 안녕하세요? 맨날맑음 입니다.

다른 사용자의 블록를 돌아다닐때 마다 멋진 블로그가 많아 새롭게 블로그를 좀 꾸며 보려고 했는데; 역시 웹지식이 너무 없는 관계로 쉽지 않군요..(특히..CSS)

스킨 변경한다는 핑계로 포스팅을 멈추었는데;; 이제 다시 포스팅을 하고, 스킨은 HTML과 CSS, 스크립트를 좀 공부해가면서 천천히 변경해 보아야겠습니다.

Posted by 맨날맑음
,
i n v i t a t i o n

티스토리 초대장

+ 남은 초대장 수 : 00

안녕하세요!

티스토리에 보금자리를 마련하시려는 여러분께 초대장을 배포해 드리려고 합니다.

나만의, 내 생각을, 내 기억을 담는 소중한 블로그를 만들고 싶다면 티스토리로 시작해보세요!

티스토리 블로그는 초대에 의해서만 가입이 가능합니다. 원하시는 분은 댓글에 E-mail 주소를 남겨주시면 초대장을 보내드립니다. 남겨주실 때에는 꼭 비밀댓글로 남겨주세요!

 선착순으로 초대장 배포합니다^^
티스토리 이래서 좋아요!
1. 이미지, 동영상, 오디오, 파일까지! 무한 용량과 강력한 멀티미디어를 올릴 수 있어요!
2. 스킨위자드로 스킨을 내맘대로~ 거기에 기능 확장 플러그인까지!
3. 내가 원하는대로 myID.com으로 블로그 주소를 만들 수 있어요!
Posted by 맨날맑음
,

안녕하세요? 맨날맑음 입니다.

회원가입 기능을 구현하다 보면, 주민등록번호가 올바른지 체크해야 하는 경우가 생기게 됩니다.
우선 주민등록번호가 어떠한 형태(?)로 이루어 졌는지 알아 볼 필요가 있습니다.

주민등록번호는 총 13자리 숫자로 구성되어 있습니다.
모두 아시는 바와같이 앞의 6자리는 태어난 날의 년, 월, 일을 나타냅니다  
뒤의 7자리는 조금 복잡한데요. 
 # 1번 자리 : 성별 (ex. 남자 : 1또는3, 여자 : 2또는4)
    - 3과 4는 낮설겠지만 00년 이후 출생자부터는 남자는 3 여자는 4입니다.
 # 2~5번 자리 : 출생 신고 당시의 거주지 관할 동사무소의 지역코드
 # 6번 자리 : 출생 신고 날짜
 # 7번 자리 : 검정 코드
    - 7번 자리가 매우 중요합니다. 이것은 앞의 12자리의 유효성을 검증해 줄 매직넘버(?)로 쓰이게 됩니다. 

주민등록번호의 유효성을 알아 보기 위해서는 각 자리수의 미리 지정된 값을 곱하여, 곱한 결과를 전부 더하고,
그 값을 11로 나눈 후, 그 결과 값(나머지)11에서 빼어 13번째 자리수인 검정 코드와 일치하면 유효하게 됩니다.
참.. 말로 할려니 쉽지 않습니다.

주민등록번호가 123456 - 1234567 이라고 예를 들어 보겠습니다.
# 1. 아래와 같이 검정 코드인 7을 제외한 각자리에 미리 지정된 값(234567892345)을 각각 곱하여 줍니다.

# 2. 각 자리에서 곱한 결과를 모두 더해 줍니다.

# 3. 더한 결과(206)을 11로 나누어 줍니다(% 나머지 연산).

# 4. 11에서 나머지의 결과를 빼줍니다.

# 5. 나머지 3과 검증코드 7이 같은지 확인 합니다. 같으면 유효한 주민등록번호입니다. 이 경우 11에서 10을 뺀다던지, 1을 빼면 2자리의 결과가 나오게 됩니다. 그럴때는 뒤의 1자리만 취하여 비교하시면 됩니다. (이와같은 이유로 4번의 결과에 % 연산으로 10을 한번 더해주면 뒤의 1자리만 취할 수 있겠죠?)

# 결론 . 주민등록번호 123456 - 1234567는 틀린 주민번호 입니다!


C# 코드로 만들어 보겠습니다.
코드 작성시 고려 사항
  # 1. 주민등록번호가 13자리 인가?
  # 2. 13자리 모두 숫자인가?
  # 3. 위의 공식에 부합하는가?

bool IsAvailableRRN(string RRN)

{

    //공백 제거

    RRN = RRN.Replace(" ", "");

    //문자 '-' 제거

    RRN = RRN.Replace("-", "");

    //주민등록번호가 13자리인가?

    if (RRN.Length != 13)

    {

        return false;

    }

 

    int sum = 0;

    for (int i = 0; i < RRN.Length - 1; i++)

    {

        char c = RRN[i];

        //숫자로 이루어져 있는가?

        if (!char.IsNumber(c))

        {

            return false;

        }

        else

        {

            if (i < RRN.Length)

            {

                //지정된 숫자로 자리를 나눈 더한다.

                sum += int.Parse(c.ToString()) * ((i % 8) + 2);

            }

        }

    }

    // 검증코드와 결과 값이 같은가?

    if (!((((11 - (sum % 11)) % 10).ToString()) == ((RRN[RRN.Length - 1]).ToString())))

    {

        return false;

    }

    return true;

}

Posted by 맨날맑음
,
안녕하세요? 맨날맑음 입니다.

몇몇 프로그램을 보면, 윈도우끼리 도킹 기능으로 서로 일정거리 만큼 가까워지면 딱 달라붙게 만들어진 것을 볼 수 있습니다. 이런 기능의 명칭을 정확히 몰라서; 일단 제목은 '자석 윈도우'라고 정해 보았는데요;

밑의 예(이스트소프트의 알송)에서 볼 수 있듯이 각 윈도우끼리는 가까이 가면 달라 붙고, 제일 위의 윈도우를 이동하면 붙어있는 윈도우도 따라서 이동하게 됩니다.
이런 기능을 간단하게 Windows Form을 이용하여 만드는 방법을 알아보려 합니다. 이런 기능을 제공하는 Class Library가 있을까 찾아 보다가 결국 찾지는 못하고, 한 외국 사이트에 이와같은 소스를 발견하였습니다. 현재 출처를 정확하게 기억 나지 않습니다. 그래서 완성된 소스는 올리지 않습니다.(필요하신분은 요청하세요)
Form1.cs
 private void Form1_Move(object sender, EventArgs e)
        {
            if (isForm2Docked)
            {
                tempForm2.SetDesktopLocation(tempForm2.Location.X + (this.Location.X - prevLoc.X),
                    tempForm2.Location.Y + (this.Location.Y - prevLoc.Y));
            }
            prevLoc.X = this.Location.X;
            prevLoc.Y = this.Location.Y;
        }
 
메인이 되는 폼의 Move 이벤트에서는 현재 서브폼이 붙어있는지 확인하여, 붙어있으면 위치를 함께 옮겨줍니다.

Form2.cs
private void Form2_Move(object sender, EventArgs e)
        {
            bool amIDocked = false;
            Point newPoint = GetNewFormPosition(this, passedInForm, out amIDocked);
            this.SetDesktopLocation(newPoint.X, newPoint.Y);
            ((Form1)passedInForm).isForm2Docked = amIDocked;
        }

        public Point GetNewFormPosition(Form thisForm, Form parentForm, out bool isDocked)
        {
            int[] xRange = new int[2] { parentForm.Location.X, parentForm.Location.X + parentForm.Width };
            int[] yRange = new int[2] { parentForm.Location.Y, parentForm.Location.Y + parentForm.Height };
            int xGap = Math.Abs(thisForm.Location.X - parentForm.Location.X);
            int yGap = Math.Abs(thisForm.Location.Y - parentForm.Location.Y);
            int leftGap = Math.Abs((thisForm.Location.X + thisForm.Width) - parentForm.Location.X);
            int rightGap = Math.Abs(thisForm.Location.X - (parentForm.Location.X + parentForm.Width));
            int topGap = Math.Abs((thisForm.Location.Y + thisForm.Height) - parentForm.Location.Y);
            int bottomGap = Math.Abs(thisForm.Location.Y - (parentForm.Location.Y + parentForm.Height));
            int xNew = thisForm.Location.X;
            int yNew = thisForm.Location.Y;
            isDocked = false;

            if ((leftGap <= dockGap) && (((thisForm.Location.Y >= yRange[0]) && (thisForm.Location.Y <= yRange[1])) ||
                ((thisForm.Location.Y + parentForm.Height >= yRange[0]) && (thisForm.Location.Y + parentForm.Height <= yRange[1]))))
            {
                xNew = parentForm.Location.X - thisForm.Width;
                if (yGap <= dockGap) yNew = parentForm.Location.Y;
                isDocked = true;
            }
            if ((rightGap <= dockGap) && (((thisForm.Location.Y >= yRange[0]) && (thisForm.Location.Y <= yRange[1])) ||
                ((thisForm.Location.Y + parentForm.Height >= yRange[0]) && (thisForm.Location.Y + parentForm.Height <= yRange[1]))))
            {
                xNew = parentForm.Location.X + parentForm.Width;
                if (yGap <= dockGap) yNew = parentForm.Location.Y;
                isDocked = true;
            }
            if ((topGap <= dockGap) && (((thisForm.Location.X >= xRange[0]) && (thisForm.Location.X <= xRange[1])) ||
                ((thisForm.Location.X + parentForm.Width >= xRange[0]) && (thisForm.Location.X + parentForm.Width <= xRange[1]))))
            {
                yNew = parentForm.Location.Y - thisForm.Height;
                if (xGap <= dockGap) xNew = parentForm.Location.X;
                isDocked = true;
            }
            if ((bottomGap <= dockGap) && (((thisForm.Location.X >= xRange[0]) && (thisForm.Location.X <= xRange[1])) ||
                ((thisForm.Location.X + parentForm.Width >= xRange[0]) && (thisForm.Location.X + parentForm.Width <= xRange[1]))))
            {
                yNew = parentForm.Location.Y + parentForm.Height;
                if (xGap <= dockGap) xNew = parentForm.Location.X;
                isDocked = true;
            }
            return new Point(xNew, yNew);
        }

서브폼에서 좀 복잡해 보이긴 하지만, 메인폼의 영역을 알아와, 현재 서브폼의 위치가 달라 붙어야하는 위치라면 붙여 주고, 메인폼에 그 사실을 알리는 역할을 합니다.
이 소스를 응용하면, 상용프로그램처럼 윈도우간 도킹 기능을 만드는 것도 어렵지 않을 것 같습니다.

ps. 좌표를 일일이 비교해가며 구현하는 방법 외에 Class Library나 다른 방법을 아시는 분은 꼭 피드백을 부탁 드립니다!!
Posted by 맨날맑음
,

안녕하세요? 맨날맑음 입니다.

윈도우에서 [Ctrl + Alt + Del]키를 누르면 아래와 같이 Windows 작업관리자를 볼 수 있습니다. 응용 프로그램 탭을 보면 현재 실행 중인 프로그램의 목록을 확인 할 수 있는데요. 이와같이 프로그램을 만들다 보면 특정한 프로그램을 선택하는 창을 제공해야 할 때가 있습니다.
이런 기능을 만들기 위해서는 Win32 API의 도움을 받아야합니다.(.net class library에서 지원해 주는지는 잘 모르겠습니다. 아시는분은 꼭 알려주시길..) 잡설이지만 .NET의 Windows Form 개발시 자꾸만 Win32 API를 사용하게 되는군요'' 분명 그에 매칭하는 .NET Class가 있을꺼라는 생각이 드는데요(일부 크리티컬한 기능 제외);

Win32 API의 EnumWindows 함수는 현재 실행중인 프로세서들의 목록을 얻어올 수 있는 기능을 제공합니다.
http://msdn.microsoft.com/en-us/library/ms633497(VS.85).aspx 이 링크에서 보다 자세한 MSDN 도움말을 확인 할 수있습니다.

C#에서 사용시에는 다음과 같이 선언 해 주어야 하는데, 한가지 고민되는 사항은 첫번째 인자가 callback 함수의 '함수포인터'를 넣어야 되는 것입니다. 두번째 인자의 의미는 사용자 임의의 데이터를 넘길 때 사용합니다.
[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool EnumWindows(EnumWindowsCallback callback, int extraData);

그런데 C#에는 함수포인터가 없기 때문에 이를 대체하기 위한 수단으로 'Delegate'를 사용해야 합니다.

그래서 아래과 같이 EnumWindows의 Callback 함수와 형식이 일치하는 Delegate를 선언해 줍니다.
public delegate bool EnumWindowCallback(int hwnd, int lParam);
배웠으니 시험도 해볼겸 간단한 예제를 만들어 보겠습니다. Windows Form 프로젝트를 생성합니다.
Form위에 프로그램의 목록과 아이콘을 띄워줄 ListView를 가져다 놓습니다.

각자 입맛에 맛게 속성을 조정해 줍니다. 저는 Form크기에 맞게 딱 붙도록 Dock속성을 Fill로 변경해 보았습니다.
코드(form.cs)로 가서 아래의 코드를 추가합니다.
using System;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace Sample
{
    public partial class Form1 : Form
    {
        public delegate bool EnumWindowCallback(int hwnd, int lParam);
        
        [DllImport("user32.dll")]
        public static extern int EnumWindows(EnumWindowCallback callback, int y);

        [DllImport("user32.dll")]
        public static extern int GetParent(int hWnd);

        [DllImport("user32.dll")]
        public static extern int GetWindowText(int hWnd, StringBuilder text, int count);

        [DllImport("user32.dll")]
        public static extern long GetWindowLong(int hWnd, int nIndex);

        [DllImport("user32.dll")]
        public static extern IntPtr GetClassLong(IntPtr hwnd, int nIndex);

        const int GCL_HICON = -14; //GetWindowLong을 호출할 때 쓸 인자
        const int GCL_HMODULE = -16;
        ImageList imgList;//ListView의 Image로 쓸 리스트
               

        public Form1()
        {
            InitializeComponent();
            imgList = new ImageList();
            imgList.ImageSize = new Size(16, 16);
            listView1.SmallImageList = imgList;
            listView1.View = View.List;
            //CallBack 델리게이트 생성
            EnumWindowCallback callback = new EnumWindowCallback(EnumWindowsProc);
            EnumWindows(callback, 0);

        }
       
        public bool EnumWindowsProc(int hWnd, int lParam)
        {            
            //윈도우 핸들로 그 윈도우의 스타일을 얻어옴
            UInt32 style = (UInt32)GetWindowLong(hWnd, GCL_HMODULE);
            //해당 윈도우의 캡션이 존재하는지 확인
            if ((style & 0x10000000L) == 0x10000000L && (style & 0x00C00000L) == 0x00C00000L)
            {
                //부모가 바탕화면인지 확인
                if (GetParent(hWnd) == 0)
                {
                    StringBuilder Buf = new StringBuilder(256);
                    //응용프로그램의 이름을 얻어온다
                    if (GetWindowText(hWnd, Buf, 256) > 0)
                    {
                        try
                        {
                            //HICON 아이콘 핸들을 얻어온다
                            IntPtr hIcon = GetClassLong((IntPtr)hWnd, GCL_HICON);
                            //아이콘 핸들로 Icon 객체를 만든다
                            Icon icon = Icon.FromHandle(hIcon);
                            imgList.Images.Add(icon);                            
                        }
                        catch (Exception)
                        {
                            //예외의 경우는 자기 자신의 윈도우인 경우이다.
                            imgList.Images.Add(this.Icon);
                        }
                        listView1.Items.Add(new ListViewItem(Buf.ToString(), listView1.Items.Count));
                    }
                }
            }
            return true;
        }    
    }
}

소스가 좀 많아 보이긴 하는데, 복잡하진 않습니다. Win32API들을 사용하기 위해 EnumWindows, GetParent, GetWindowText, GetWindowLong, GetClassLong을 선언해 줍니다.
ListView의 Image를 넣기 위한 ImageList도 추가합니다.

생성자에서는 ImageListListView의 초기화를 하고, Delegete를 생성하여, EnumWindows를 호출해 줍니다.
그럼 OS는 현재 실행중인 프로세서를 EnumWindowsProc으로 전달해 주게 됩니다. 단 EnumWindowsProc으로 전달되는 것은 프로세스 목록(전체)이 아니라 한번에 하나씩 들어오게 됩니다.

EnumWindowsProc에서는 첫번째 매개변수로 들어온 윈도우 핸들을 이용하여 GetWindowLong을 호출해 윈도우의 스타일을 얻어오고, 해당윈도우의 캡션이 존재하는지 확인합니다.

그 다음으로 부모가 바탕화면인지 확인하여, 이것이 최상위의 윈도우인지 확인하게 됩니다. GetWindowText함수를 이용하여, 윈도우(응용프로그램)의 이름을 얻어온후, GetClassLong을 사용하여 해당 윈도우의 HICON 아이콘핸들을 얻어오게 됩니다(.net에서 사용하기 위해 IntPtr 사용), 이렇게 얻어온 아이콘핸들을 이용하여 .net의 Icon 클래스의 static메소드인 FromHandle을 이용하여 Icon 객체를 생성합니다.

예외처리 구문은 자기 자신의 윈도우일 경우 Icon을 못얻어오는 경우가 생겨서, try를 사용하였습니다.
EnumWindowsProc의 return은 true로 해주셔야, 계속하여 호출하게 됩니다. 상황에따라 원하는 윈도우를 찾은다음에 false를 return하면 되겠습니다. 실행시켜 보니 잘되는군요!
사실, API의 EnumWindows를 사용하지 않고 .net의 Process 클래스를 사용해도 현재 사용중인 프로세스의 목록을 얻어, 바탕화면이 부모인지 확인하고 MainWindowTitle을 확인하여 이와같은 기능을 만들 수 도 있습니다.(하지만 탐색기 같은 일부창의 경우 윈도우의 이름을 얻어올 수 없는 문제가 있습니다.) 아래는 Process를 이용하는 간단한 예제입니다.
Process[] pro = Process.GetProcesses();            
for (int i = 0; i < pro.Length; i++)
{
     if (pro[i].MainWindowHandle != IntPtr.Zero)
     {
          if (pro[i].MainWindowTitle == "")
              continue;
       //To do..
     }
}
Posted by 맨날맑음
,

안녕하세요? 맨날맑음 입니다.

네이트온을 사용하다 보면 친구가 로그인 했더나 대화 요청이 들어올때 트레이 아이콘 주위에 알림창이 뜨는것을 볼 수  있습니다.

이와 같이 트레이창의 위치를 윈도우 주위로 위치하는 것은 .NET에서도 Win32API의 SystemParamete
rsInfo
를 사용하면 간단하게 구현 할 수 있습니다.

.NET에서 Win32API를 사용하기위해 우선 using System.Runtime.InteropServices; 이 필요합니다.
또한 SystemParametersInfo에 대한 아래와 같은 선언도 필요합니다.
 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int SystemParametersInfo(int uAction, int uParam, out RECT lpvParam, int fuWinIni);


SystemParametersInfo 함수를 호출하여 작업표시줄의 영역을 뺀 화면의 크기를 얻어 오기 위하여 RECT 구조체가 필요 하기 때문에 아래와 같이 임의로 RECT 구조체를 정의해 줍니다.
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
        public int left;
        public int top;
        public int right;
        public int bottom;
}
사실 SystemParametersInfo는 매우 다양한 기능을 수행하는 API입니다. 여러가지 시스템 정보를 설정하거나 가져 올 수 있습니다. 예를들어 바탕화면 이미지를 변경한다던지, 작업영역의 크기를 알아낼 수 도 있고, 화면보호기의 동작 유무를 제어 할 수도 있습니다. 인자에 따른 기능이 너무 많아서 좀더 자세한 기능을 알고 싶다면 아래의 링크에서 MSDN 도움말을 확인 하시면 됩니다.
http://msdn.microsoft.com/en-us/library/ms724947(VS.85).aspx

위에 그림에서 보듯이 작업표시줄의 트레이 아이콘 위에 윈도우를 띄우려면(무언가 보여주고 싶다면...) 작업표시줄의 영역을 제외한 윈도우의 실질적인 작업영역의 크기를 얻어오면 됩니다.
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int SystemParametersInfo(int uAction, int uParam, out RECT lpvParam, int fuWinIni);

        private void SetFormLocationToTray(Form form)
        {
            int SPI_GETWORKAREA = 0x0030; //작업영역을 알아오는 Flag
            RECT r = new RECT();
            SystemParametersInfo(SPI_GETWORKAREA, 0, out r, 0);
            Size s = form.Size;
            Point p = new Point(r.right - s.Width, r.bottom - s.Height); 
            form.Location = p;
        }
 
SetFormLocationToTray 함수는 인자로 들어온 Form 객체의 위치를 Tray 주위로 옮겨 주는 역할을 합니다.
SystemParametersInfo의 첫번째 인자중 0x0030(SPI_GETWORKAREA)을 넣으면 작업표시중을 뺀 작업영역의 크기를 3번째 인자의 RECT 구조체에 얻어오게 됩니다.
알아온 작업영역의 크기로 위의 소스에서 보는것 처럼 현재 폼의 크기에 따라 계산을 하여 폼의 location을 변경하여 네이트온처럼 트레이 주위에 윈도우가 뜨게 할 수 있습니다.
Posted by 맨날맑음
,
안녕하세요? 맨날맑음 입니다.

어플리케이션을 개발 하다 보면 같은 프로그램이 두번 실행 되지 않아야 하는 경우가 종종 생깁니다.
이를 해결 하기 위해 여러 방법이 있지만.. 이번 포스팅에 소개 할 내용은 커널 동기화 객체 중 하나인 뮤텍스(Mutex)를 이용하여 해결하는 방법을 소개하려 합니다.

Mutex는 .NET의 System.Threding 네이스페이스에 포함된 클래스 입니다. 사실 둘이상의 스레드가 동시에 공유될 수 있는 리소스에 접근 할때 데드락의 위험이 있으므로 동기화 매커니즘이 필요하게 되는데요. Mutex는 리소스에 대한 단독 엑세스 권한을 하나의 스레드에만 부여하여 동기화하는 기본형식 입니다.
같은 리소스에 접근 할때 하나의 스레드가 Mutex를 걸고 어떠한 일을 수행 할 경우 다른 스레드는 먼저 수행하고 있는 스레드가 Mutex를 해제 할때까지 기다리게(일시중지) 되게 됩니다.
Mutex에 관한 자세한 설명은 아래의 링크에서 MSDN 도움말을 확인 할 수 있습니다.
http://msdn.microsoft.com/ko-kr/library/system.threading.mutex.mutex.aspx

테스트 하는 어플리케이션으로는 Windows Form 프로젝트를 예로 들겠습니다.
WinForm 프로젝트를 생성하면 Program.cs 가 생성 됩니다.
Main 함수에 다음과 같은 코드를 추가 시켜 주면 메세지 막스가 뜨면서 해당하는 프로그램이 중복 실행 되었다는걸 알릴 수 있습니다. 매우 간단하죠?
static class Program
    {
        /// 
        /// 해당 응용 프로그램의 주 진입점입니다.
        /// 
        [STAThread]
        static void Main()
        {
            bool createdNew;
            Mutex dup = new Mutex(true, "Focus Explorer Mutex", out createdNew); //Mutex생성
            if (createdNew)
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new MainForm());
                dup.ReleaseMutex(); //Mutex 해제
            }
            else // 프로그램이 중복 실행된 경우
            {
                MessageBox.Show("이미 프로그램이 실행중입니다.","Focus Explorer");
            }
        }
    }
 
우선 Mutex를 생성하고, 생성자에 두번째 인자에 뮤텍스 이름을 지정합니다.
세번째 인자로준 bool 값이 false일 경우 이미 뮤텍스가 걸리있는 상태이기 때문에.. 해당 프로젝트가 이미 생성되었다고 생각하고 프로그램이 실행중이라는 메세지 박스를 띄우고, 프로그램을 종료합니다.

이상 간단한 Mutex를 사용한 프로그램 중복 방지 방법이었습니다.
Posted by 맨날맑음
,
안녕하세요? 맨날맑음 입니다.

.NET 으로 프로젝트를 만들고, 배포를 하는 방법은 Click Once같은 방법도 있지만 이번에는 Windows Installer 배포에 대해 알아보겠습니다. 기존에 .Net으로 프로젝트 개발만 해보았지, 배포는 신경을 쓰지 않아 잘 모르다가, 이번에 VS2008에서 기본으로 제공되는 배포 프로젝트의 사용법을 알아 보았습니다.
아래 링크를 따라가면 배포프로젝트에 관한 MSDN 도움말을 보실 수 있습니다.
http://msdn.microsoft.com/ko-kr/library/206sadcd(VS.80).aspx

우선 배포할 프로그램을 준비합니다. 저는 간단한 윈폼 프로젝트로 하겠습니다.

VS2008에서 새 프로젝트를 추가 하고, 기타 프로젝트 형식 -> 설치 및 배포 -> 설치프로젝트를 선택합니다.

다음과 같이 배포 프로젝트가 생성됩니다.

솔루션 탐색기와 속성 창을 보면 아래와 같이 프로젝트와, 여러 속성이 보이게 됩니다.
이제 이 속성들의 의미를 하나하나 알아보겠습니다.
▷AddRemoveProgramIcon : 제어판 -> 프로그램 추가/제거에 표시될 아이콘을 등록합니다.
▷Author : 프로젝트 작성자의 이름을 등록합니다.
▷Desciption : 설치 관리자에 관한 설명을 등록합니다.
▷DetectNewerInstalledVersion : 프로그램 설치시 버전 비교를 통해 새 버전인지 확인 해 줍니다. 이미 설치 되있을 경우 설치 되어있다고 알려주기도 합니다.
▷Keyword : 설치 관리자를 검색하는데 사용 할 키워드를 지정합니다.
▷Localization : 로케일을 적용한다고 하는데, 글로벌 프로그램이 아니라면 신경 안써도 될 듯 합니다.
▷Manufacturer : 제조업체의 이름을 지정합니다
▷MunufacturerUrl : 제조업체의 홈페이지 링크를 지정.
▷PostBuildEvent : 배포프로젝트를 빌드한 후에 실행 할 명령줄을 지정합니다.
▷PreBuildEvent : 배포프로젝트를 빌드하기 전에 실행 할 명령줄을 지정합니다.
▷ProductCode : 응용프로그램의 고유 식별자(GUID)를 지정합니다.
▷ProductName : 프로그램의 공개 이름을 지정합니다.
▷RemovePrevionsVersions : 설치시 이전버전을 삭제 할지를 지정합니다.
▷RunPostBuildEvent : PostBuildEvent 속성에서 지정된 명령줄을 실행할 시기를 결정합니다.
▷SearchPath : 개발 컴퓨터의 어셈블리, 파일 또는 병합 모듈을 검색하는 데 사용되는 경로를 지정합니다.
Subject : 프로그램을 설명하는 추가정보를 지정합니다.
▷SupportPhone : 전화번호를 지정합니다.
▷SupportUrl : 마찬가지로 추가 설명을 하는 웹사이트 주소
TargetPlatform : 프로그램이 실행될 플랫폼을 지정.
▷Title : 설치 관리자의 제목을 지정합니다.
▷UpgradeCode : 프로그램 버전이 여러가지 일때 고유식별자를 지정합니다.
▷Virsion : 버전을 지정합니다.




속성 참 많네요.. 정작 중요한건 ProductName, Title, Author, MAnufacturer 정도 입니다.

이제 파일시스템 탭으로 이동하여 대상 컴퓨터의 파일 시스템 -> 응용 프로그램 폴더를 선택한 후 속성창을 확인합니다.

DefalutLocation이라는 속성에 [ProgramFilesFolder][Manufacturer]\[ProductName] 라고 되어 있습니다. 인스톨시 프로그램이 설치될 폴더 인데요. 이 설정으로 하게 되면 프로그램파일 폴더 밑에 제조회사명 밑에 프로그램 이름 폴더 안에 깔리게 되겠습니다. 맘에 드는 폴더로 변경 하시면 됩니다.

이제 파일을 추가 해 보겠습니다. 응용 프로그램 폴더에서 마우스 오른쪽 버튼을 누르면 파일이나 폴더등을 추가 할 수 있습니다. 미리 만들어 놓은 샘플 어플리케이션을 추가 하겠습니다.


이렇게 하면 앞서 지정된 설치 폴더에 MainApp.exe가 설치 됩니다. 명색이 인스톨 프로그램인데 이것만 지정하면 안되겠죠? 위에 사용자 바탕화면사용자 프로그램 메뉴가 보입니다.
말 그래로 사용자 바탕화면은 바탕화면에 설치 될 파일을 지정 할 수 있고, 사용자 프로그램 메뉴는 [시작]->[프로그램]의 폴더나 파일을 지정 할 수 있습니다.
바탕화면과 프로그램 메뉴에 MainApp.exe의 바로가기를 넣어주면 클라이언트가 아주 편리 할 것 같습니다.
MainApp.exe를 마우스 오른쪽 버튼으로 클릭하여 바로가기를 만듭니다. 저는 두개를 만들어 이름을 원하는데로 변경한 후 하나는 사용자 바탕화면으로 끌어다 놓았고, 사용자 프로그램 메뉴에는 [새폴더]를 하나 추가 하여, 그 안에 넣었습니다. 그리고 아이콘 파일(.ico)도 하나 추가하여 바로가기의 속성중 Icon에 연결 시켜 줍니다. 그럼 바로가기가 우리가 지정한 아이콘으로 생성 됩니다.

이제 언인스톨 기능을 하는 바로가기도 지원해 주어야 좀 더 있어보일 것입니다.
바탕화면에서 텍스트문서(txt)를 하나 추가해서 확장자를 bat로 바꾸어 줍니다. 편집기로 파일을 열어 Msiexec /x {ProductCode} 를 추가 해 줍니다. 여기서 ProductCode는 위에 프로젝트 속성중에 있던 코드 입니다. 지금 예제 대로 하면 Msiexec /x {A1715BBB-A953-4F01-B788-168542ED2BC3} 이 되겠네요.

현재 배포하는 샘플 프로그램이 매우 간단하여 파일이 하나이지만, 대부분의 응용은 여러 DLL을 포함하고 있을것입니다. 그리고 각 파일마다 설치하고 싶은 경로가 다를 수 있는데, 파일시스템 탭의 대상 컴퓨터의 파일 시스템을 오른쪽 버튼으로 누르면 특수폴더 추가에서 원하는 폴더를 추가 할 수 있습니다.

이제 이 파일을 응용프로그램 폴더에 포함 시켜주고, 마찬가지 방법으로 바로 가기를 만들어서 원하는 곳에 추가 시켜 줍니다. 프로젝트를 다시 빌드 하고 테스트 해보겠습니다. 프로젝트 폴더의 Relese 폴더에 들어가니 파일이 두개 보입니다.(Relese모드로 빌드 했을 경우, Debug 모드 일경우 Debug 폴더에 생성)
Setup.exe를 더블 클릭하니 설치가 잘 됩니다.
설치시 생긴 바로가기 아이콘으로 Uninstall도 잘되는지 확인해 보겠습니다.
정상적으로 잘 되는걸 볼 수 있습니다. 쓰다 보니 스크롤의 압박이군요;
만약 설치 대상컴퓨터(클라이언트)에 .Net Framwork가 없을때 자동으로 설치되게 하는 것 까지 쓰려고 했는데,
다음편으로 넘겨야 할 것 같습니다. 다음편에는 이같은 기능을 해주는 Boot Strapper에 관해 포스팅 하겠습니다.
Posted by 맨날맑음
,