최근 어플리케이션 보면 셀카/셀피를 찍어 이미지 필터를 적용할 수 있는 앱을 쉽게 찾아볼 수 있다. Facebook, Instagram, B612 등 사진을 올릴 때 필터를 쉽게 적용해서 올릴 수 있다. 사실 이런 필터 기능들은 예전부터 포토샵을 이용하면 쉽게 적용할 수 있었다. 사실 필터란, 레스터 그래픽으로 이루어진 사진들의 픽셀(화소)값들을 특정 규칙에 따라 색을 바꿔주는 것이다.
구글에 javascript Filter를 검색해보면 몇몇 Filter Library를 찾을 수 있다. Filter Library를 이용하면 쉽게 필터를 적용할 수 있지만 내 입맛에 맞게 바꾸는 것은 쉽지가 않다. 이번 포스트를 통해 이러한 이미지 필터를 직접 만들어보자. 플랫폼은 물론 웹 기반이 되겠다. 로컬에 저장된 이미지를 불러와 웹 브라우저에서 javascript를 이용해 만든 이미지 필터를 적용할 것이다.
프로젝트 세팅
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Image Filter</title>
</head>
<body>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="filter.js"></script>
</html>
filter.js
console.log('filter init');
웹브라우저를 열어보자. filter init log가 정상적으로 찍힌다면 준비가 필터를 만든 준비가 끝난 것이다.
Canvas 만들기
index.html - body에 추가
<body>
<canvas id="canvas" width="500" height="700"></canvas>
<input id="loadButton" type="file" accept="image/*">
</body>
index.js를 수정한 후 열어보면 캔버스가 투명해서 잘 안보인다. border를 넣어서 경계를 보이게 해주자.
index.js - style 추가
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Image Filter</title>
<style>
canvas {
border : black 1px solid;
border-radius: 5px;
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="600"></canvas>
<input id="loadButton" type="file" accept="image/*">
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="filter.js"></script>
</html>
완성된 Layout
브라우저를 살짝 줄이면 대충 이런식으로 보인다. 이제 파일 선택 버튼을 눌러서 캔버스에 이미지를 불러와 보도록 하자.
로컬 이미지 불러오기
filter.js
// canvas 객체 생성
var canvas = $('#canvas')[0];
var ctx = canvas.getContext('2d');
캔버스에서 이미지를 그리기 위해선 drawImage() method를 사용한다.
context.drawImage(img, x, y, width, height); |
만약 이미지를 그대로 넣으면 캔버스 보다 큰 이미지가 들어올 경우 짤리게 된다. 이미지 width나 height가 클 경우 캔버스에 맞춰서 그리도록 사전에 처리를 해주는 함수를 만들자.
function drawImageData(image) {
image.height *= canvas.offsetWidth / image.width;
image.width = canvas.offsetWidth;
if(image.height > canvas.offsetHeight){
image.width *= canvas.offsetHeight / image.height;
image.height = canvas.offsetHeight;
}
ctx.drawImage(image, 0, 0, image.width, image.height);
}
자 이제 input 버튼에 이벤트를 걸어, 로컬파일을 불러왔을때 캔버스에 그리도록 하자. FileReader 객체를 이용해서 불러온 파일을 dataURL 포멧으로 바꿀 수 있다. 데이터 URL객체를 사용하는 이유는 drawImage에 전달할 이미지 객체를 만들고, width와 height를 가져오기 위해서이다.
$('#loadButton').on('change', function (e) {
var file = e.target.files[0];
var fileReader = new FileReader();
fileReader.onload = function (e) {
var image = new Image();
image.src = e.target.result;
image.onload = function () {
drawImageData(image);
}
};
fileReader.readAsDataURL(file);
});
filter.js - 이미지 불러오기
// canvas 객체 생성
var canvas = $('#canvas')[0];
var ctx = canvas.getContext('2d');
function drawImageData(image) {
image.height *= canvas.offsetWidth / image.width;
image.width = canvas.offsetWidth;
if(image.height > canvas.offsetHeight){
image.width *= canvas.offsetHeight / image.height;
image.height = canvas.offsetHeight;
}
ctx.drawImage(image, 0, 0, image.width, image.height);
}
// click input button
$('#loadButton').on('change', function (e) {
var file = e.target.files[0];
var fileReader = new FileReader();
fileReader.onload = function (e) {
var image = new Image();
image.src = e.target.result;
image.onload = function () {
drawImageData(image);
}
};
fileReader.readAsDataURL(file);
});
Filter 적용하기
context.getImageData(x, y, width, height); |
function drawImageData(image) {
image.height *= canvas.offsetWidth / image.width;
image.width = canvas.offsetWidth;
if(image.height > canvas.offsetHeight){
image.width *= canvas.offsetHeight / image.height;
image.height = canvas.offsetHeight;
}
ctx.drawImage(image, 0, 0, image.width, image.height);
console.log(ctx.getImageData(0,0, canvas.width, canvas.height));
}
[그림] 로컬 파일을 불러온 후 console로 찍어본 결과
console로 출력된 결과물을 보면 data key안에 Uint8ClampedArray로 이미지 정보가 들어있는 것을 볼 수 있다. 쉽게 말하면 배열로 이미지 정보가 들어있는 것이다. 여기서 한 Pixel의 정보는 4개의 단위로 쪼개진다. 예를들면 Uint8ClampedArray[0] ~ Uint8ClampedArray[3] 까지가 좌상단의 첫번째 픽셀에 대한 정보이다.
순서대로 RGBA값, 즉 빛의 3원색인 적,녹,청의 대한 값과 alpha(밝기)에 대한 정보라 할 수 있다. 각각의 값들은 0부터 255까지의 int값을 가질 수 있다.
이제 픽셀값을 가져왔으니 실제로 값을 변경시켜서 필터 효과를 적용해보자. input element 아래다가 필터 적용 버튼을 만들자.
index.html
<body>
<canvas id="canvas" width="600" height="600"></canvas>
<input id="loadButton" type="file" accept="image/*">
<button id="filterButton">Filter</button>
</body>
이제 가장 간단한 invert 필터부터 만들어 보도록 하자. invert란 말 그대로 화소의 색깔을 반전시켜 주는 필터이다. 쉽게 생각하면, RGB 값들을 반대로 뒤집어 주면 된다. filter.js 밑에 다음 함수를 추가시켜 주자. invertFilter 함수는 getImageData로 가져온 오브젝트를 넘겨받아 pixel 데이터에 일련의 프로세싱 과정을 거쳐 새로운 pixel 데이터를 리턴한다. 여기서는 최대값(255) - 기존값을 해서 색상이 반전되는 효과를 주도록 하자.
filter.js - invert filter
function invertFilter(pixels) {
var d = pixels.data;
for(var i=0; i<pixels.data.length; i+=4 ){
d[i] = 255 - d[i]; // R
d[i+1] = 255 - d[i+1]; // G
d[i+2] = 255 - d[i+2]; // B
d[i+3] = 255; // Alpha
}
return pixels;
}
이제 Filter 버튼을 눌렀을 때 이벤트를 걸어서, invertFilter를 적용해서 캔버스에 다시 그려보자. 캔버스에 image데이터를 그려주기 위해서는 putImageData() method를 사용한다.
context.putImageData(imageData, x, y); |
$('#filterButton').on('click', function () {
// imageData를 가져온다.
var pixels = ctx.getImageData(0,0, canvas.width, canvas.height);
// image processing
var filteredData = invertFilter(pixels);
// Canvas에 다시 그린다.
ctx.putImageData(filteredData, 0 , 0);
});
filter.js - 완성된 버전
// canvas 객체 생성
var canvas = $('#canvas')[0];
var ctx = canvas.getContext('2d');
function drawImageData(image) {
image.height *= canvas.offsetWidth / image.width;
image.width = canvas.offsetWidth;
if(image.height > canvas.offsetHeight){
image.width *= canvas.offsetHeight / image.height;
image.height = canvas.offsetHeight;
}
ctx.drawImage(image, 0, 0, image.width, image.height);
console.log(ctx.getImageData(0,0, canvas.width, canvas.height));
}
// click input button
$('#loadButton').on('change', function (e) {
var file = e.target.files[0];
var fileReader = new FileReader();
fileReader.onload = function (e) {
var image = new Image();
image.src = e.target.result;
image.onload = function () {
drawImageData(image);
}
};
fileReader.readAsDataURL(file);
});
$('#filterButton').on('click', function () {
// imageData를 가져온다.
var pixels = ctx.getImageData(0,0, canvas.width, canvas.height);
// image processing
var filteredData = invertFilter(pixels);
// Canvas에 다시 그린다.
ctx.putImageData(filteredData, 0 , 0);
});
// Filters
function invertFilter(pixels) {
var d = pixels.data;
for(var i=0; i<pixels.data.length; i+=4 ){
d[i] = 255 - d[i]; // R
d[i+1] = 255 - d[i+1]; // G
d[i+2] = 255 - d[i+2]; // B
d[i+3] = 255; // Alpha
}
return pixels;
}
[그림] invertFilter를 적용한 이미지
Image Filters
function somethingFilter(pixels) {
var d = pixels.data;
// image processing logic
return pixels;
}
Brightness Filter
간단하게 밝기조절부터 시작하자. 밝기조절은 RGB값에다가 균등한 상수를 더해주면 전체적으로 밝게 보이도록 할 수 있다.
function brightnessFilter(pixels, value) {
var d = pixels.data;
for(var i =0; i< d.length; i+=4){
d[i] += value/3;
d[i+1] += value/3;
d[i+2] += value/3;
}
return pixels;
}
필터를 추가해준후 filterButton Event에서 invertFilter(pixels); 부분을 brightnessFilter(pixels, value);로 바꿔주자. value 부분이 높을수록 더욱 밝게 바꿔 줄 수 있다. 좀더 응용하면 silde bar등을 이용해서 밝기조절이 가능하게도 만들 수 있다.
$('#filterButton').on('click', function () {
// imageData를 가져온다.
var pixels = ctx.getImageData(0,0, canvas.width, canvas.height);
// image processing
var filteredData = brightnessFilter(pixels, 100);
// Canvas에 다시 그린다.
ctx.putImageData(filteredData, 0 , 0);
});
$('#filterButton').on('click', function () {
// imageData를 가져온다.
var pixels = ctx.getImageData(0,0, canvas.width, canvas.height);
// image processing
var filteredData = brightnessFilter(pixels, 100);
// Canvas에 다시 그린다.
ctx.putImageData(filteredData, 0 , 0);
});
[그림] Brightness Filter 적용
Grayscale Filter
function grayscaleFilter(pixels) {
var d = pixels.data;
for(var i =0; i< d.length; i+=4){
var r = d[i];
var g = d[i+1];
var b = d[i+2];
var v = 0.2126*r + 0.7152*g + 0.0722*b; // 보정값
d[i] = d[i+1] = d[i+2] = v // RBG 색을 같게 맞추자
}
return pixels;
}
필터를 추가해준후 filterButton Event에서 invertFilter(pixels); 부분을 grayscaleFilter(pixels);로 바꿔주자.
$('#filterButton').on('click', function () {
// imageData를 가져온다.
var pixels = ctx.getImageData(0,0, canvas.width, canvas.height);
// image processing
var filteredData = grayscaleFilter(pixels);
// Canvas에 다시 그린다.
ctx.putImageData(filteredData, 0 , 0);
});
[그림] Grayscale Filter 적용
Sepia Filter
이번엔 빛바랜 효과를 내는 필터를 만들어보자. 빛바랜 효과를 위해선 R값을 상대적으로 G값의 보정값을 높혀주고, G, B값은 G값의 0.5정도 보정값을 주도록 하자.
function sepiaFilter(pixels) {
var d = pixels.data;
for(var i =0; i< d.length; i+=4){
var r = d[i];
var g = d[i+1];
var b = d[i+2];
d[i] = r*0.3588 + g*0.7044 + b*0.1368;
d[i+1] = r*0.2990 + g*0.5870 + b*0.1140;
d[i+2] = r*0.2392 + g*0.4696 + b*0.0912;
}
return pixels;
}
[그림] Sepia Filter 적용
마치며
'JavaScript' 카테고리의 다른 글
Javascript Image Filter - convolution (0) | 2017.05.02 |
---|---|
Javascript ES6 Proxy (1) | 2017.04.16 |
Webpack2 시작하기 (3) | 2017.04.08 |
ECMA 2015를 공부해보자 (1) | 2016.08.07 |
자바스크립트 배열 (0) | 2015.11.01 |