① 이미지 출력
② 픽셀단위 변경 후 출력
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로 저장됨
[그림 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)기법이란 '피사체의 움직임에 따라 카메라를 함께 움직이면서 촬영하는 것
'HTML5 Canvas' 카테고리의 다른 글
CHAR 6. HTML5 캔버스와 비디오 (0) | 2012.03.13 |
---|---|
CHAP 5. 수학과 물리학을 적용한 애니메이션 -- 미완성 (0) | 2012.03.05 |
CHAR 3. HTML5 캔버스 문자 API (6) | 2012.02.29 |
HTML5 Canvas 관련 사이트 (0) | 2012.02.27 |
CHAR 2. 캔버스에 그리기 (0) | 2012.02.22 |