Pygame의 Rect 객체에 대하여 어느정도 조작이 가능하다면 다음 단계는 이미지를 불러와서 조작해볼 차례다.

 

pygame모듀에는 이미지 조작 메소드들이 있다. 참조변수 transform 의 메소드들로 조작할 수 있다.

 

이번 포스트의 코드는 좀 복잡해 보인다. While 루프에서 여러가지 메소드의 기능들을 구현하도록 했고 심심하니까 배경에 스스로 움직이는 도형을 넣었다. 이 코드들이 다 조화를 이룬다. 정리를 좋아하는 사람들은 좀더 코드를 간결하게 하는 방법을 만들수 있을 것이다.

 

프로그래밍을 하다보면 때로는 자동차 수리점 같다. 나는 이런 옛날 표현을 좋아하는데 컴퓨터는 자주 뻑이난다. 소프트웨어 안정성이 나날이 좋아지고 있지만 뜯어보면 예나 지금이나 비슷하다. 컴퓨터는 하드웨어적이나 소프트웨어적이나 결국 뻑이 나도록 설계되어 있다. 그리 완벽해 보이는 컴퓨터의 세상도 완벽하지는 않다. 컴퓨터는 신이 아니다.

 

완벽한 것처럼 보이기 위해 수많은 IT회사의 직원들이 공장이나 물건같이 실체도 없는 컴퓨팅의 세계에서 고된 정신노동과 싸우고 있는 것이다. 네이버 같은 대기업의 빌딩은 아주 크니까 실체가 있는 것 같다. 그런데 포스코(포항제철)에 한번 가보면 알게 된다. 수만평의 엄청난 부지에 수천도가 넘는 용광로에 시뻘건 쇳물이 흐르고 있다. 그것에 비하면 컴퓨터 서버와 네트워크에 의존하는 IT 기업의 실체란 건 만져지지 않는다. 심지어 카카오같은 기업의 수익률은 현대자동차, 포스코 같은 전통 제조업에 비해 상상을 초월한다. 

 

어쩌면 우리사회도 아직 프로그래밍의 진정한 능력이 100프로 발휘되지 않은 사회라는 생각이 든다. 서양 선진국에 비해 경제발전의 역사가 짧아서 그런가 보다. 하지만 프로그래밍은 컴퓨터 한대로 시작할 수 있기 때문에 매력적이다. 취미로 해도 부담없어 좋고 부업으로 돈까지 벌면 더 좋을 것이다 ㅎㅎ. 현실과 다르더라도 그런 사심(?)으로 입문 하는거다 ㅋㅋ

 


 

 

이번 포스트의 내용은 이미지를 프로세싱하는 메소드들을 다룬다.

 

1. 이미지 로드

 

이미지를 로드한다. 배경이 투명한 png 파일을 사용할 것이다. png 파일은 구글에서 저작권이 없는 파일을 다운로드 받아 사용하는게 좋다. 요새는 무료 스톡사이트가 많아서 이미지를 받아서 변형해서 쓰면 된다.

 

무료 스톡 이미지 사이트 모음

 

무료 스톡 이미지 사이트 모음 | 상업적 사용가능 이미지,영상 | 저작권 걱정없는 자료 사이트

인터넷에서 콘텐츠를 생산하려면 항상 걸리는 것이 있다. 불펌이라는 단어는 여기서만 봐야할 콘텐츠를 누가 허락없이 저기로 가져가서 사용한다는 말이다. 불펌때문에 뭔가 경제적인 이득이 �

tactmarketing.tistory.com

PNG 파일

대략 캐릭터라고 생각하고 PNG 파일을 만들었다. 스톡 이미지를 가져와서 파이어알파카 같은 프리웨어를 쓰면 좋다. 배경을 투명하게 저장해야 된다. 안그러면 사각형 사진이 되버리니까 애써 조작한 느낌이 안난다.

 

파일을 가져와서 파이썬 소스코드가 있는 폴더에 저장한 후 이미지를 로드한다.

 

pygame.imgae.load 메소드로 로드한다.

 

*기타사항

 

PYGAME 같은 모듈을 가져다 쓸때는 작동 원리에 대해서 궁금할 수도 있다. 그런데 입문자가 처음에 거기까지 이해하는 것은 쉽지 않다. 왜냐하면 이 모듈을 만든 Pete Shinners 도 SDL (Simple Direct Media Library) 를 바탕으로 PYGAME 라이브러리를 만들었다. 빠른 퍼포먼스를 위해 C언어와 어셈블리어가 쓰여졌고 그가 2000년부터 몇년간이나 진행했던 프로젝트였다. 때문에 파이썬이라는 내용만 배운 사람은 당연히 이해할 수 없다.

 

PYGAME은 오픈소스이긴 하지만 그 오픈소스를 전부 다 이해하고 사용하는 사람은 드물다.(있긴 있다. 뭐든 순식간에 이해하는 천재들은) 사용법을 잘 알고 사용하면 된다. 자동차를 탈때 자동차를 만드는 지식은 필요하지 않다. 오래도록 안전하게 잘 타기 위해서 관리와 정비에 대한 지식이 필요하지만 그 이상이 되기 위해서는 프로의 교육과 장비가 필요하다. 소프트웨어 개발도 그와같다.

 

그런데 이게 최근에 프레임워크냐 컴퓨터구조냐 어느것이 더 중요하냐라는 논쟁이 되고 있다. 딱히 최근의 논쟁은 아니라고 생각한다. 원천기술은 항상 중요했고 변화의 속도는 항상 엄청났다. 특히 IT기술 중 소프트웨어 기술의 변화 속도는 엄청나게 빠르다. 뭐 하나 만들어놓고 10년간 버틸 수 있는 곳이 아니다.

 

대학에서 배운 지식은 2-3년이면 구식이 될 것이다. 다만 대학에서는 스스로 학습할 수 있는 능력을 길러 주는 곳이라 보기때문에 통계적으로 대학에서 배운 이들의 생존력이 좀더 좋다고 까지는 말할 수 있는 것이다. 그 말은 이점이 된다는 말이다. 대학을 나와도 대학을 안나온 사람보다 실력에서 밀리면 거기까지다. 근데 생각보다 밀리는 사람들은 많이있다. 너무 오래동안 한 분야를 하다보면 사람이 질릴 때도 있는 것이다. 특정한 학과를 나왔다고 그 분야에서 뼈를 묻으라는 법은 없다. 또 세월이 그렇게 놔두지도 않는다.

 

PYGAME같은 오픈소스 라이브러리 개발자들이 딱히 금전적으로 큰 보상을 위해 만든게 아니라고 보는 시각이다. 오픈소스 자체가 그렇다. 정말 자신이 원해서 진행한 프로젝트다라는 것도 중요하고 금전의 보상은 아니지만 명예는 얻는다. 그러니까 돈은 아니지만 실제로는 보상이 있는 것이다.

 

오픈소스에 대해 이야기하면 리누즈 토발즈 이야기를 해야하는데 그건 너무 삼천포고 관심있는 사람은 파이게임 인트로를 읽어보기 바란다. 파이게임을 어떻게 사용해야 하는지 알 수있다.

 

파이게임 인트로

 

Pygame Intro — pygame v2.0.0.dev7 documentation

This article is an introduction to the pygame library for Python programmers. The original version appeared in the Py Zine, volume 1 issue 3. This version contains minor revisions, to create an all-around better article. Pygame is a Python extension librar

www.pygame.org

 

다시 이미지를 로드해서 img라는 Surface 참조 인스턴스를 사용할 것이다. convert는 이미지를 파이게임에 최적화 시킨다. 이미지에 맞는 사각형 객체를 만들어서 중앙에 위치시킨다. 이 과정은 이미지와 사각형을 동기화 시키는 것이다.

 

그리고 또 하나의 Surface 객체 참조를 만든다. 이미지에 관련된 메소드를 사용하다 보면, 원본과 변형된 상태의 이미지를 구분하기 위해 이름이 다른 참조변수가 필요하다. img1 에  img 를 대입해서 사용한다. rect2 로 객체를 하나 더 만들어서 중앙에 세팅한다.

img = pygame.image.load('crt1.png')
img.convert()
rect = img.get_rect()
rect.center = width //2 , height //2

# rect2
img1 = img
img1.convert()
rect2 = img1.get_rect()
rect2.center = width //2 , height //2

 

이미지를 컨트롤 하기 위해 필요한 글로벌 변수들을 더 선언한다.

angle = 0
scale = 1
mouse = [0,0] #pygame.mouse.get_pos();
mode = False

angle과 scale은 로테이트 기능의 각도와 크기, mouse와 mode는 MOUSEMOTION 이벤트에 사용한다.

 

2. 이벤트 처리기

for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        elif event.type == MOUSEBUTTONDOWN:
            if rect.collidepoint(event.pos):
                moving = True

        elif event.type == MOUSEBUTTONUP:
            moving = False

        elif event.type == MOUSEMOTION and moving:
            rect.move_ip(event.rel)
            rect2.center = rect.center
            #rect2.move_ip(event.rel) #either way works

이벤트 처리기에서 대부분 처리한다. 다음은 마우스 드래그로 이미지를 움직이는 것이다. event.type 세개가 필요하다. 처음 클릭했을 때의 상황을 보자. rect.collidepoint 라는 메소드가 있다. collide <- 게임에서는 충돌감지에 쓰는 단어다. 즉 마우스 버튼이 클릭되었을때 rect 사각형안에 충돌감지(클릭한 좌표 event.pos)하여 boolean을 리턴한다.

 

>> 클릭하면 moving이 True가 된다. 드래그의 조건이다.

 

elif 조건문은 그 다음에 MOUSEMOTION으로 가서 moving이 True일때 동작한다. move_ip는 이전 포스트에서 많이 설명했다. 2D 벡터를 인자로 주면 움직인다. event.rel 변수는 relative position을 말한다. 마지막에 rect2.center를 rect.center와 맞추는 작업이 필요하다. 이미지를 조작 변형하는 과정에서 내부적으로 변형된 이미지와 원본 이미지의 좌표가 달라진다. 좌표를 다시 맞춘다.

 

전체의 흐름을 생각해보자. 사용자는 이미지와 연결된 사각형 영역에 마우스를 클릭한 채로 마우스를 이동시킨다. 이동할 때마다 변형된 이미지의 사각형 rect2의 좌표를 원본의 좌표에 일치시킨다.

 

이미지 드래그 이동

3. 로테이트(회전)과 스케일업(확대)

        elif event.type == KEYDOWN:
            if event.key == K_r:
                if event.mod & KMOD_SHIFT:
                    angle -= 15
                else:
                    angle += 15
                img1 = pygame.transform.rotozoom(img,angle,scale)
                #rect2 = img1.get_rect()
                #rect2.center = rect.center #get pos

            elif event.key == K_s:
                if event.mod & KMOD_SHIFT:
                    scale /= 1.2
                else:
                    scale *= 1.2
                img1 = pygame.transform.rotozoom(img, angle, scale)
                #rect2 = img1.get_rect()
                #rect2.center = rect.center  # get pos

이미지를 회전시키는 법과 확대 축소 기능이다. 로테이트는 r로 설정해두고 Shift 키를 같이 누르면 반대로 회전한다. 확대도 원리가 같다. transform 타입의 rotozoom 메소드로 변형이 가능하다. 원본이미지와 각도,크기를 넣으면 변형된 그래픽이 만들어진다.

 

로테이트 스케일

 

 

4. 초기화, 플립, 스케일업, 라플레시안 메소드

초기화 플립 등 메소드

elif 코드는 하나의 단위로 보면된다. 지역의 개념으로 하나의 이벤트가 발생하면 elif 에서 처리를 다 끝내는 것이다. 공통의 코드만 밖으로 뺀다.

 

첫번째 elif는 O를 누르면 초기화가 되도록 한다. 각도와 크기를 설정하고 중앙으로 모아준다. 주의할 점은 원본과 변형본을 같게 만들때 사각형 객체의 좌표도 같이 맞춰준다. 그러니까 2개씩 4개가 있는 것이다. 이미지 객체 2개, 사각형 객체 2개.

 

두번째와 세번째는 플립한다. h는 수평 v는 수직 hozizon and vertical

K_2 는 이미지를 두배 해준다. 그냥 편의상 만들어 놓은 메소드같다. 앞에서 배율을 지정한 메소드를 사용했었다.

 

K_l은 라플레시안 메소드로 외곽선을 따서 투명인간을 만들어 준다. 타블렛으로 그려서 필압과 앤티앨리싱이 들어갔는데 도트그래픽을 사용하면 외곽선이 깔끔하게 따진다.

 

마지막 공통 코드다. rect2.center = rect.center 에 맞춰줘야 좌표가 일치한다. Rect 크래스가 내부적으로 좌표가 자동으로 바뀌지는 않는다. img1 (이미지) 에 대한 처리는 이미지대로 처리되지만 img1.get_rect()로 이미지의 바뀌는 좌표까지 전달되지 않는다. 조금 혼란스러울 수가 있는데 이미지를 표시할 때 Rect 객체를 사용한다는 것을 생각하면 알수있다. 예를들어

 

pyg.draw.rect(screen,BLACK,rect,1)
screen.blit(myball,rect)

 

지난 포스트의 볼 예제이다. 첫번째 줄은 사각형을 그리는 메소드이고, 다음 줄은 myball 이라는 이미지를 rect 좌표에 그리는 메소드이다. bilt(블릿) 메소드는 Surface 간에 복사작업니다. (이경우는 윈도우창 screen 에 이미지를 복사함) 즉 이미지 좌표로 그리는 것이 아니라 rect에서 좌표를 받아와서 그린 것이다.

 


            elif event.key == K_o:
                img1 = img
                angle = 0
                scale = 1
                rect2 = img1.get_rect()
                rect.center = width //2, height //2  #default center
                rect2.center = width //2 , height //2

            elif event.key == K_h:
                img1 = pygame.transform.flip(img1, True, False)

            elif event.key == K_v:
                img1 = pygame.transform.flip(img1, False, True)

            elif event.key == K_2:
                img1 = pygame.transform.scale2x(img1)

            elif event.key == K_l:
                img1 = pygame.transform.laplacian(img1)

            rect2 = img1.get_rect()
            rect2.center = rect.center  # get pos

    screen.fill(C1_BLUE)
    screen.blit(img1, rect2)
    pygame.draw.rect(screen,pyc.BLUE,rect2,5)

    if moving:
        pygame.draw.rect(screen,pyc.RED, rect2,3)

    pygame.display.update()

마지막 부분은 screen Surface에 그리는 작업이다. img1 과 rect2는 둘다 변형된 이미지와 사각형을 의미한다.

 

5. 마우스로 이미지 변형하기

마우스로 이미지를 조작한다. 키보드 1을 토글하여 조작모드를 변경한다. 이를 위한 boolean 변수 mode를 만들고, mouse 좌표를 추적할 변수를 만든다. 마우스 모션을 보면 조금 수학식이 나와있다. 파이썬의 기본 제공되는 math 모듈을 사용해야 한다. 피타고라스 공식으로 이미지의 중심(rect.center)에서 부터 마우스가 위치한 곳까지의 거리를 구할 것이다.

 

앵글과 스케일은 마우스의 위치를 따르도록 계산이 된다. 스케일의 배수 abs(5*d/width) 의 5를 바꿔서 실행해보면 알 수 있다.

 

코드 마지막 부분에 마우스 위치와 이미지의 중심을 잇는 선과 점을 그려서 시각적 컨트롤이 가능하게 한다.

 

마우스 컨트롤

mouse = [0,0]
mode = False

	for event in pygame.event.get():
        if event.type == QUIT:
            running = False
                
        elif event.type == MOUSEMOTION and mode:

            # about mouse
            mouse = event.pos
            x = mouse[0] - rect.center[0]
            y = mouse[1] - rect.center[1]
            d = math.sqrt(x ** 2 + y ** 2)
            print(rect.center)
            print(rect2.center)
            print(x, y)

            angle = math.degrees(-math.atan2(y, x))
            scale = abs(5 * d / width)
            img1 = pygame.transform.rotozoom(img, angle, scale)

            rect2 = img1.get_rect()
            rect2.center = rect.center


        elif event.type == KEYDOWN:   
            elif event.key == K_1:
                if mode == True:
                    mode = False
                    print ("마우스 조작모드 종료")
                else:
                    mode = True
                    print ("마우스 조작모드 시작")

            rect2 = img1.get_rect()
            rect2.center = rect.center  # get pos

    screen.fill(C1_BLUE)
    pygame.draw.line(screen,pyc.GREEN,rect2.center,mouse,1)
    pygame.draw.circle(screen, pyc.RED, mouse, 10, 5)
    pygame.display.update()

pygame.quit()

 

이미지 조작법에 대해 어느정도 알게 되었다. 더 자세한 내용은 pygame 의 docs를 참고한다. 거의 모든 프로그래밍이 그렇지만 프레임웤와 라이브러리를 사용할 때 Documenation을 잘 읽어봐야 한다. 얼마나 documentation 이 잘 되어 있는가도 프레임웤의 선택에 영향을 미친다. PYGAME 정도면 십년이상 검증된 모듈이니까 웹에 많은 자료들이 있다. 스택오버플로우에서도 많은 내용이 올라와 있다. 유독 파이썬에 관한 질문이 참 많아서 좋은 것 같다.

 

번역기능도 어느정도 쓸만하니 충분할 것이라 생각한다.

 

다음 포스트에서는 파이게임의 텍스트 기능 등 아직 남아있는 주제들을 다룰 예정이다.

 

 

https://www.pygame.org/docs/

 

Pygame Front Page — pygame v2.0.0.dev7 documentation

 

www.pygame.org

 

 

 

 

* 전체 소스코드(아래)

import pygame
from pygame.locals import *
from pygame.rect import *
import pyc
import math

size = width, height = 800,600
screen = pygame.display.set_mode(size)

C1_BLUE = (204, 204, 255) # Custom blue color 1

r1 = Rect(0,0,50,70)
r2 = Rect(0,0,50,50)

speed1 = [2,7]
speed2 = [7,2]

# Load an image

img = pygame.image.load('crt1.png')
img.convert()
moving = False
rect = img.get_rect()
rect.center = width //2 , height //2

# rect2
img1 = img
img1.convert()
rect2 = img1.get_rect()
rect2.center = width //2 , height //2

angle = 0
scale = 1

print(rect.center)
print(rect2.center)

pygame.init()

clock = pygame.time.Clock()
running = True

mouse = [0,0] #pygame.mouse.get_pos();
mode = False

while running:
    clock.tick(60)

    # little entertainment

    r1.move_ip(speed1)
    r2.move_ip(speed2)

    #r1

    if r1.left < 0:
        speed1[0] *= -1
    if r1.right > width:
        speed1[0] *= -1
    if r1.top < 0:
        speed1[1] *= -1
    if r1.bottom > height:
        speed1[1] *= -1

    #r2
    if r2.top < 0:
        speed2[1] *= -1
    if r2.left < 0:
        speed2[0] *= -1
    if r2.right > width:
        speed2[0] *= -1
    if r2.bottom > height:
        speed2[1] *= -1

    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        elif event.type == MOUSEBUTTONDOWN:
            if rect.collidepoint(event.pos):
                moving = True

        elif event.type == MOUSEBUTTONUP:
            moving = False

        elif event.type == MOUSEMOTION and mode:

            # about mouse
            mouse = event.pos
            x = mouse[0] - rect.center[0]
            y = mouse[1] - rect.center[1]
            d = math.sqrt(x ** 2 + y ** 2)
            print(rect.center)
            print(rect2.center)
            print(x, y)

            angle = math.degrees(-math.atan2(y, x))
            scale = abs(5 * d / width)
            img1 = pygame.transform.rotozoom(img, angle, scale)

            rect2 = img1.get_rect()
            rect2.center = rect.center

        elif event.type == MOUSEMOTION and moving:
            rect.move_ip(event.rel)
            rect2.center = rect.center
            #rect2.move_ip(event.rel) #either way works

        elif event.type == KEYDOWN:
            if event.key == K_r:
                if event.mod & KMOD_SHIFT:
                    angle -= 15
                else:
                    angle += 15
                img1 = pygame.transform.rotozoom(img,angle,scale)
                #rect2 = img1.get_rect()
                #rect2.center = rect.center #get pos

            elif event.key == K_s:
                if event.mod & KMOD_SHIFT:
                    scale /= 1.2
                else:
                    scale *= 1.2
                img1 = pygame.transform.rotozoom(img, angle, scale)
                #rect2 = img1.get_rect()
                #rect2.center = rect.center  # get pos


            elif event.key == K_o:
                img1 = img
                angle = 0
                scale = 1
                rect2 = img1.get_rect()
                rect.center = width //2, height //2  #default center
                rect2.center = width //2 , height //2

            elif event.key == K_h:
                img1 = pygame.transform.flip(img1, True, False)

            elif event.key == K_v:
                img1 = pygame.transform.flip(img1, False, True)

            elif event.key == K_2:
                img1 = pygame.transform.scale2x(img1)

            elif event.key == K_l:
                img1 = pygame.transform.laplacian(img1)

            elif event.key == K_1:
                if mode == True:
                    mode = False
                    print ("마우스 조작모드 종료")
                else:
                    mode = True
                    print ("마우스 조작모드 시작")

            rect2 = img1.get_rect()
            rect2.center = rect.center  # get pos

    screen.fill(C1_BLUE)

    pygame.draw.rect(screen,pyc.GREEN, r1)
    pygame.draw.ellipse(screen,pyc.RED,r2)

    #screen.blit(img, rect)
    screen.blit(img1, rect2)
    #pygame.draw.rect(screen,pyc.RED,rect,10)
    pygame.draw.rect(screen,pyc.BLUE,rect2,5)

    pygame.draw.line(screen,pyc.GREEN,rect2.center,mouse,1)
    #pygame.draw.circle(screen, pyc.RED,rect2.center, 10, 5)
    pygame.draw.circle(screen, pyc.RED, mouse, 10, 5)

    if moving:
        pygame.draw.rect(screen,pyc.RED, rect2,3)

    pygame.display.update()

pygame.quit()

 

 

 

 

공유하기

facebook twitter kakaoTalk kakaostory naver band