본문으로 바로가기

CHAR 4. 캔버스에서의 이미지

category HTML5 Canvas 2012. 3. 1. 23:39
▼ 캔버스에서 이미지로 할 수 있는 일
① 이미지 출력
② 픽셀단위 변경 후 출력

4.2 기본적인 이미지 처리
▼ 이미지 선언
1) HTML
<img src="ship1.png" id="spaceship">
2) JavaScript
var spaceShip = new Image();
spaceShip.src = "ship1.png";
4.2.1 이미지 미리 불러오기
- 코드 실행 전에 필요한 이미지가 완전히 로드되었는지 확인필요
→이미지의 load 이벤트를 기다리는 이벤트 리스너 추가

4.2.2 drawImage()로 이미지 출력
▼ context.drawImage(Image, dx, dy);
▼ context.drawImage(Image, dx, dy, dw, dh); -- 이미지 스케일링
     - dw, dh: 출력할 크기 / 원본이미지와 크기가 다르면 자동 크기 변환 / 기준점: left-top
     - 기준점 바꾸려면 행렬을 이용한 변환할 것
▼ drawImage(Image, sx, sy, sw, sh,dx, dy, dw, dh); - 이미지 일부분 복사(활용: 스프라이트 이미지 출력)
     - (sx, sy): 복사할 이미지의 시작 위치
     - (sw, sh): 복사할 너비, 높이
     - (dx, dy): 출력할 위치
     - (dw, dh): 출력할 크기

4.3 간단한 셀 스프라이트 애니메이션(100ms 불을 뿜는 우주선)
- 셀 애니메이션? 이미지 셀을 빠르게 재생해서 움직이는 애니메이션을 만드는 기법(책장을 빠르게 넘겨서 애니메이션처럼 보이는 효과)

 


▼[예제4-4]
var counter = 0; // 애니메이션 프레임 카운터(현재 출력하는 프레임 수)
var tileSheet = new Image();
tileSheet.addEventListner("load", eventShipLoaded, false);
tileSheet.src = "ships.png";

//이미지가 로드되면 타이머 시작
function eventShipLoaded() {
   startUp();
}

function drawScreen() {
   // 캔버스 배경
   context.fillStyle = "#aaaaaa";
   context.fillRect(0, 0, 500, 500);
   context.drawImage(tileSheet, 32*counter, 0, 32, 32, 50, 50, 64, 64); // 4.3.3 출력할 타일 바꾸기
   conter++;
   if( counter > 1 ) {
       counter = 0;
   }
}

function startUp() {
   setInterval(drawScreen, 100); // 프레임 틱(=timer tick) 100ms 마다 drawScreen() 실행
}


4.4 복잡한 셀 애니메이션(움직이는 듯한 탱크 만들기)

4.4.1 타일 시트 알아보기
- 32x32크기 4rows x 8cols = 32개 반복 → arrays[0~31] 로 넣을 수 있겠다.
- 움직이는 탱크에는 arrays[1~8]까지 8장 사용


▼[예제4-5]

var tileSheet = new Image();
tileSheet.addEventListner(‘load’, eventShipLoaded, false);
tileSheet.src = “tanks_sheet.png”;

// 탱크 애니메이션
var aniFrames = [1,2,3,4,5,6,7,8]; // 움직이는 탱크용 타일 인덱스
var frameIndex = 0; // 현재의 타일 인덱스( 0 ~ 7 )
// 움직이는 탱크
var x = 50;
var y = 50;
var dx = 0; // x축 이동량
var dy = -1; // y축 이동량

//이미지가 로드되면 타이머 시작
function eventShipLoaded() {
   startUp();
}

function drawScreen() {
   // 움직이기
   x += dx;    y += dy;
   if( y > 0 ) {
       y = 50;
   }

   // 캔버스 배경
   context.fillStyle = “#aaaaaa”;
   context.fillRect(0, 0, 500, 500);

   // 4.4.3 출력할 타일 위치
   var sourceX = Math.floor(aniFrames[frameIndex] % 8) * 32; // ( 0 ~ 7 )*32
   var sourceY = Math.floor(aniFrames[frameIndex] / 8) * 32;

   context.drawImage(tileSheet, sourceX, sourceY, 32, 32, 50, 50, 32, 32);

   frameIndex++;
   if( frameIndex == aniFrames.length ) {
       frameIndex = 0;
   }
}

function startUp() {
   setInterval(drawScreen, 100);
}

4.5 이미지에 회전 적용하기
- 캔버스에 그리기 전에 변환 행렬 적용

▼[예제4-7] 회전 변환
var tileSheet = new Image();
tileSheet.addEventListner(‘load’, eventShipLoaded, false);
tileSheet.src = “tanks_sheet.png”;

// 탱크 애니메이션
var aniFrames = [1,2,3,4,5,6,7,8]; // 움직이는 탱크용 타일 인덱스
var frameIndex = 0; // 현재의 타일 인덱스( 0 ~ 7 )
// 움직이는 탱크
var x = 50;
var y = 50;
var dx = 1; // x축 이동량
var dy = 0; // y축 이동량
var rotation = 90;

//이미지가 로드되면 타이머 시작
function eventShipLoaded() {
   startUp();
}

function drawScreen() {
  // 이동
  x += dx;    y += dy;
  if( x > theCanvas.Width - 32 ) {  x = 50;   }

   // 캔버스 배경
   context.fillStyle = “#aaaaaa”;
   context.fillRect(0, 0, 500, 500);

   // STEP 1. 스택에 현재 컨텍스트를 저장한다.
   context.save();
   // STEP 2. 변환 행렬을 단위행렬로 초기화
   context.setTransform(1, 0, 0, 1, 0, 0);

   // STEP 3. 변환 알고리즘 코드 적용
   var angleInRadians = rotation * Math.PI/180;
   context.translate(x+16, y+16);
   context.rotate(angleInRadians);

   // STEP 4. 이미지를 출력한다.
   var sourceX = Math.floor(aniFrames[frameIndex] % 8) * 32; // ( 0 ~ 7 )*32
   var sourceY = Math.floor(aniFrames[frameIndex] / 8) * 32;

   context.drawImage(tileSheet, sourceX, sourceY, 32, 32, -16, -16, 32, 32);
   context.restore();

   frameIndex++;
   if( frameIndex == aniFrames.length) {
       frameIndex = 0;
   }
}

// 이미지가 로드되면 그린다.
function startUp() {
   setInterval(drawScreen, 100);
}

4.6 타일로 배경 만들기
▼ 타일 맵 살펴보기
- walkable 타일: 게임 스프라이트가 밟고 지나가는 타일
- 가로x세로=10x10, 즉 크기 320x320인 배경을 만들어보자.

▼ Tiled로 타일 맵 만들기
- Tiled Map Editor(http://www.mapeditor.org/), 다양한 OS 지원
- 사용법: http://hiddenviewer.tistory.com/92
- 그 외의 맵 에디터: Mappy(http://tilemap.co.uk/mappy.php), TileStudio(http://tilestudio.sourceforge.net/)
- 만드는 목적: 타일을 격자 모양으로 배치해서 게임 배경 만들고 타일id를 통해서 타일 맵을 제어
- 결과물 (tiled_tile_map1.tmx): XML, 타일 번호가 0~31이 아닌 1~32로 저장됨


 
  
 
 
  
32,31,31,31,1,31,31,31,31,32,
1,1,1,1,1,1,1,1,1,1,
32,1,26,1,26,1,26,1,1,32,
32,26,1,1,26,1,1,26,1,32,
32,1,1,1,26,26,1,26,1,32,
32,1,1,26,1,1,1,26,1,32,
32,1,1,1,1,1,1,26,1,32,
1,1,26,1,26,1,26,1,1,1,
32,1,1,1,1,1,1,1,1,32,
32,31,31,31,1,31,31,31,31,32

 




[그림 4-9] Tiled로 만든 타일 맵의 예

4.6.3 캔버스에 맵 출력
▼[예제 4-10] 회전, 애니메이션, 이동
var tileSheet = new Image();
tileSheet.addEventListener(“load”, eventSheetLoaded, flase);
tileSheet.src = “tanks_sheet.png”;

// 인덱스 오프셋: tmx index[1~32] → array[0~31]
var mapIndexOffset = -1; 
// 맵의 폭과 높이
var mapRows = 10; // 타일 맵 행
var mapCols = 10;  // 타일 맵 열

// 맵 데이터 저장
var tileMap = [
	[32,31,31,31,1,31,31,31,31,32]
	, [1,1,1,1,1,1,1,1,1,1]
	, [32,1,26,1,26,1,26,1,1,32]
	, [32,26,1,1,26,1,1,26,1,32]
	, [32,1,1,1,26,26,1,26,1,32]
	, [32,1,1,26,1,1,1,26,1,32]
	, [32,1,1,1,1,1,1,26,1,32]
	, [1,1,26,1,26,1,26,1,1,1]
	, [32,1,1,1,1,1,1,1,1,32]
	, [32,31,31,31,1,31,31,31,31,32]
];

function eventSheetLoaded() {
   drawScreen();
}

function drawScreen() {
   for( var row = 0 ; row < mapRows ; row++ ) {
       for( var col = 0 ; col < mapCols ; col++ ) {
           var tileId = tileMap[row][col]+mapIndexOffset;
           // 캔버스에 맵 출력하기
           var sourceX = Math.floor(tileId % 8) * 32;
           var sourceY = Math.floor(tileId / 8) * 32;

           context.drawImage(tileSheet, sourceX, sourceY, 32, 32, col*32, row*32, 32, 32);
       }
   }
}

4.7 이미지 축소, 확대, 패닝
- 이미지 패닝(panningc)): 큰 이미지를 창문(canvas)으로 들여다 보기
- drawImage의 속성값 조정으로 축소,확대,패닝


▼ [예제4-13] 이미지를 축소(50%)하고 패닝하기 (full source)

<!doctype html>

<html lang="ko">

<head>

<meta charset="UTF-8">

<title>Chap4. 이미지 확대/축소/패닝</title>

<script src="http://www.modernizr.com/downloads/modernizr-latest.js"></script>

<script type="text/javascript">

window.addEventListener('load', eventWindowLoaded, false);

function eventWindowLoaded() {

   canvasApp();

}


function canvasSupport () {

   return Modernizr.canvas;

}



function canvasApp(){


   if (!canvasSupport()) {

       return;

   }


   var theCanvas = document.getElementById('canvas');

   var context = theCanvas.getContext('2d');


   var photo=new Image();

   photo.addEventListener('load', eventPhotoLoaded , false);

   //3548 x 2736

   photo.src="http://byhou.sshel.com/html5canvas/exam/ch4/butterfly.jpg";


   // 원본이미지에서 복사하려는 영역

   var srcWidth=500;

   var srcHeight=500;

   var srcX=0;

   var srcY=0;


   function eventPhotoLoaded() {

       startUp();

   }


   function drawScreen(){

       context.drawImage(photo, srcX, srcY, srcWidth, srcHeight, /* 원본 이미지에서 복사해 오려는 영역 */

                                   0,0, srcWidth*.5, srcHeight*.5 /*캔버스에 이미지를 놓을 영역(50%로 축소)*/);


       // 패닝 영역 이동(left,top좌표)

       windowX+=10;

       if (windowX>photo.width - windowWidth){

           windowX=photo.width - windowWidth;

       }

   }


   function startUp(){

       setInterval(drawScreen, 100 );

   }

} // End of canvasApp()

</script>

</head>

<body>

<div style="position: absolute; top: 20px; left: 20px; style=”border: 1px solid #d3d3d3;">

<canvas id="canvas" width="500" height="500">

Your browser does not support the HTML 5 Canvas.

</canvas>

</div>

</body>

</html>


4.7.5 애플리케이션: 축소 확대, 패닝 키보드로 제어하기
▼ 제어 변수
var currentScale = .5; // 50%
var minScale = .2; // 20%
var maxScale = 3; // 300%
var scaleIncrement = .1; // 10%
var panningIncrement = 10;

context.drawImage(photo, srcX, srcY, srcWidth, srcHeight,
                          0, 0, srcWidth*currentScale, srcHeight*currentScale);
▼ 키보드 입력
document.onkeydown = function(e) {
   e = e ? e : window.event;
}

▼ 키보드 상하/좌우 키로 패닝이동(끝에 닿으면 더 이상 이동안함)
var
case 37: // left
       srcX -= panningIncrement;
       if( srcX < 0 ) {
           srcX = 0;
       }
       break;
case 38: // top
       srcY -= panningIncrement;
       if( srcY < 0 ) {
               srcY = 0;
       }
       break;
case 39: // right
       srcX += panningIncrement;
       if( srcX + srcWidth > photo.width ) {
               srcX = photo.width - srcWidth;
       }
       break;
case 40: // bottoom
       srcY += panningIncrement;
       if( srcY + srcHeight > photo.height ) {
               srcY = photo.height - srcHeight;
       }
       break;

▼ +/-키로 확대/축소
case 109: // (-)
       currentScale -= scaleIncrement;
       if( currentScale < minScale ) {
           currentScale = minScale;
       }
       break;
case 107: // (+)
       currentScale += scaleIncrement;
       if( currentScale > maxScale ) {
           currentScale = maxScale;
       }
       break;
▼[예제4-15] 이지미 축소, 확대, 패닝 애플리케이션 -- ♠ 해보기: 코드 구성해보자


4.8 픽셀 처리
- 실시간으로 픽셀을 처리하는 방법을 보여주는 애플리케이션
▼ ImageData(픽셀 데이터 저장 메모리 영역)
① 생성자
imagedata = context.createImageData(sw, sh);
sw, sh: 픽셀 크기
imagedata = context.createImageData(imagedata);
imagedata와 같은 넓이를 갖는 새로운 ImageData 객체 생성
imagedata = context.createImageData();
비어 있는 ImageData 객체 생성

② 속성
imagedata.height, imagedata.width
imagedata.data: 픽셀의 이미지 정보 일차원 배열, 각 픽셀은 32bit 색상 정보(RGBA)

③ 캔버스에서 가져오기
imagedata = context.getImageData(sx, sy, sw, sh);
: 복사할 원본 이미지 영역(source)
④ 캔버스에 넣기
context.putImageData(imagedata, dx, dy);
이미지 데이터 전체를 캔버스의 dx, dy에 그린다.
context.putImageData(imagedata, dx, dy [, dirtyX, dirtyY, dirtyWidth, dirtyHeight ]);
이미지 데이터 중 dirty rectangle 부분만 캔버스에 출력한다.

4.8.2 Tile Stamper 애플리케이션
 

- 사용자가 이미지 안에서 하나의 박스를 선택하면 그 부분을 캔버스의 다른 부분에 도장처럼 찍을 수 있는 어플.
※ 로컬 웹 서버나 원격 웹 서버에서 실행하지 않으면 보안 에러가 발생할 수 있다.
▼ 실행 결과
- 위쪽에서 선택한 타일을 아래쪽에 클릭한 위치에 그리기

각 타일 크기: 32x32   (1024 pixels * 4 bytes/pixel =  TOTAL: 4096 KB)
- 32bit 이미지 데이터는 1pixel당 배열의 4칸(rgba)
▼ 따라서 모든 픽셀의 알파값을 128로 초기화 하는 코드는
for( j = 3 ; j imageData.data.length ; j+= 4 ) {
   imageData.data[j] = 128; // pixel 의  alpha값 = 0~255
}
▼ Tile Stamper 애플리케이션 만들기
① 마우스 이벤트 추가
onMouseMove(): 캔버스에서의 마우스 위치값 계산(캔버스 위치의 오프셋 적용)
onMouseClick() : 클릭위치가 소스 영역이면 타일 찾아 hightlight / 아니면 선택된 타일 그리기
② highlightTile(tileId/*0~31*/, sx, sy) - 소스 타일 하일라이트 효과주기
▼[예제4-16] Tile Stamper 애플리케이션 (full source)
4.9 한 캔버스에서 다른 캔버스로 복사하기
- HTML에 2개의 canvas 엘리먼트를 두고 한쪽에서 다른쪽으로 일부를 Copy해보기
- 웹 페이지에서 여러개의 <div>인스턴스 간/ 캔버스 객체 간에 이미지 데이터를 공유하고 복사할 수 있다.


♣ 참고
SpriteLib(픽셀기반 게임 스프라이트 라이브러리): http://www.flyingyogi.com/fun/spritelib.html

Images and Pixel Manipulation [http://www.w3schools.com/html5/html5_ref_canvas.asp]
Attribute Value Description
width number Specifies the width of the imagedata object
height number Specifies the height of the imagedata object
data array An array containing the rgba values of all pixels in an imagedata object
Method Description
drawImage() Use a picture when drawing onto the canvas
createImageData() Creates a blank imagedata object
getImageData(x,y,w,h) Creates a new imagedata object, containing data from the canvas
putImageData(imgdat,dx,dy,x,y,w,h) Draws imagedata onto the canvas






a) orientation: (어느 방위•위치에) 놓인 상태
b) orthogonal: <수학> 직각의, 직교(直交)하는
c) panning: 패닝(Panning)기법이란 '피사체의 움직임에 따라 카메라를 함께 움직이면서 촬영하는 것
반응형