Office Automation 혹은 Home Automation
※ 주 : 이번에는 HTML과 JavaScript를 주로 사용했지만 프로그래밍 언어 강좌는 아닙니다.
요구사항(해결이 필요한 '문제')이 있을 때 그것을 어떠한 방식으로 해결해나가는가에 대한 기록입니다.
제 경우에는 HTML4.0이 표준이던 시절부터 접하여 어느덧 18년차(...)이지만 깊이는 여전히 없습니다. 독자 여러분들께서도 'Hyper Text Markup Language'라는 글자 그대로, 이것이 하나의 언어이고 문법이겠거니 하면서 문제해결의 흐름을 읽어내려가신다면 좋겠습니다.
코드 부분에서 더 궁금하신 내용은 관련 책이나 온라인상에도 강좌가 굉장히 많으니 키워드 삼아 한번 찾아보시면 도움이 될 것 같네요.
색 고르는 거 좀 도와주세요에서 이어집니다.
https://brunch.co.kr/@dayang/1
우리집 디자이너는 스스로 탐구하고 공부하는 것을 좋아하는 사람이다.
누가 시켜서 하는 일도 아닌데 차근차근 끈기있게 하는 걸 보면 신기할 따름이지만 답답할 때도 종종-자주- 있었다.
어느날은 색상을 골라야 한다며 다양한 변화(variation)를 줘가면서 스포이드로 일일이 컬러코드를 찍어보는 것이 아닌가.
호기롭게 얘기했다. 그런 단순한 작업은 얼마든지 자동으로 할 수 있다고.
기본 색상과 변화량(변수)을 주면 바뀌는 색과 컬러코드를 보여주는 프로그램 정도는 쉽지 않겠는가?
처음엔 근사한(?) 모바일 애플리케이션을 생각했다.
하지만 PC로 작업하려면 결국 앱에 표시된 컬러코드를 다시 타이핑해야한단다.
쓰임에 맞게, 구현도 실행도 간단한 HTML+JavaScript를 사용하기로 했다.
<!DOCTYPE html>
<html>
<head>
<!-- 지금 head는 없어도 되지만 처음 시작이니까 한번 -->
<meta charset="utf-8">
<title>ColorCode Generator by SBR</title>
<meta name="keywords" content="ProblemSolving,HTML,JavaScript">
<meta name="author" content="SBR (http://LLUN.com)">
</head>
<body>
<div>'구현이 들어갈 자리'</div>
</body>
</html>
Analogous color를 이용하여 일정 간격만큼 떨어진 색상을 표시해주고, 그 컬러코드를 표시해주세요.
Analogous(유사한) 색상이 있다고 한다.
HSB 색상 체계에서 Hue(색상)값을 지정한 값만큼 +-해주면 된단다.
정리하면,
- Input : 두 개의 텍스트(①컬러코드, ②변수)
- Output : 두 개의 컬러코드(컬러코드-변수, 컬러코드+변수)
간단하다.
우선 네모칸을 세 개 그리고 칸마다 각각의 색상을 표시해준다.
가운데는 입력한 색상, 왼쪽에는 뺀 값, 오른쪽에는 더한 값을 배경 색상으로 칠하고 그 위에 텍스트로 컬러코드를 표시한다.
1회용으로 쓸 건 아니니까, 사용자의 입력을 받기 위해 최초 기준 색상을 입력할 텍스트상자가 하나 필요하고 +-값을 지정할 상자도 하나 더 필요하겠다.
<body>
<div>
HEX#:<input type="text" id="hex" size="7" maxlength="7" value="ffce00"/>
Parameter:<input type="text" id="param" size="2" style="width:20px;" value="40">
</div>
<div class="display" name="rect"></div>
<div class="display" name="rect"></div>
<div class="display" name="rect"></div>
</body>
파라미터 다음으로, 버튼을 추가하여 클릭하면 실행되도록 함수로 연결
<input type="button" value="Execution" onclick="find();"/>
웹표준이나 통일성은... 고려하면 좋지만 여기선 적당히 넘어가도록 하자.
목표에만 충실하도록, 로컬 html 페이지를 만드는 거니까.
내맘대로 '적정기술'이라고 부르겠다.
*앞으로도 동작은 하되 완벽한 코드를 완성하지는 않으려 한다.
구글 크롬(Chrome) 등의 HTML5를 지원하는 브라우저에서 열어보면,
그런데 네모 세 칸은 어디갔을까?
잘 숨어(?)있으니까 걱정말고 다음으로 넘어가자.
이제 버튼을 클릭하면 실행될 find()함수를 작성할 차례다.
여기서부터 프로그래밍이 시작된다. 산수 수준이니 차근차근 따라서... ctrl+C, V를 하면 된다.
그리고 HEX코드를 가져다가 가공하고 다루는 방법에는 여러가지가 있겠지만,
이것저것 테스트해본 끝에 TinyColor라는 자바스크립트 라이브러리를 가져와서 사용하기로 했다.
라이브러리를 통해 HEX코드로 이루어진 RGB 색상을 다른 여러 가지 색 체계로 간편하게 변환이 가능하다.
※ 개발을 함에 있어서 내 신조는, '바퀴를 다시 만들려고 하지 마라'(Don't reinvent the wheel)이다.
우선 <head></head> 사이에 아래와 같이 추가.
물론 js파일을 html과 같은 폴더에 저장했다는 전제 하에 진행한다.
<script type='text/javascript' src='tinycolor.js'></script>
메인 코드이다.
<script type="text/javascript">
function find() {
//텍스트박스의 값을 가져옴(읽어들임)
var hex = document.getElementById("hex").value;
var param = parseInt(document.getElementById("param").value);
//tinycolor로 간단히 변환
var tColor = tinycolor(hex);
//가운데 칸에 입력값을 그대로 표시
var rect = document.getElementsByName('rect')[1];
rect.style.backgroundColor = tColor.toHexString();
rect.innerHTML = tColor.toHexString();
//parameter만큼 Hue값을 -하여 왼쪽칸에 표시
var newColor = tinycolor({h:(tColor.toHsv().h-param+360)%360,
s:tColor.toHsv().s, v:tColor.toHsv().v});
rect = document.getElementsByName('rect')[0];
rect.style.backgroundColor = newColor.toHexString();
rect.innerHTML = newColor.toHexString();
//반대로 Hue값을 +하여 오른쪽칸에 표시
newColor = tinycolor({h:(tColor.toHsv().h+param)%360,
s:tColor.toHsv().s, v:tColor.toHsv().v});
rect = document.getElementsByName('rect')[2];
rect.style.backgroundColor = newColor.toHexString();
rect.innerHTML = newColor.toHexString();
//브라우저의 콘솔로 값을 확인할 수 있음 (생략해도 무방)
console.log((tColor.toHsv().h-param+360)%360);
console.log(tColor.toHsv().h);
console.log((tColor.toHsv().h+param)%360);
}
</script>
tColor.toHsv().h로 Hue값을 읽어서 parameter에 입력한 숫자만큼 더하거나 빼고 다시 h값으로 사용하여 새로운 색상(newColor)을 만든 것이다.
왜 360으로 더하거나 나눠서 그 나머지를 구하게 됐는지는 한번만 곰곰이 생각해보면 알 수 있을 것이다.
(정답: 더하거나 빼도 항상 0~360 범위의 숫자를 갖기 위해)
텍스트상자에 oninput속성(이벤트)을 추가하면 일일이 버튼을 클릭하지 않아도 입력값에 따른 결과를 바로바로 확인할 수 있다.
HEX#:<input type="text" id="hex" size="7" maxlength="7" value="ffce00" oninput="find();"/>
Parameter:<input type="text" id="param" size="2" style="width:20px;" value="40" oninput="find();"/>
앗, 기능은 완성됐지만 색 비교를 해야하는데 칸과 칸 사이에 간격이 있고,
컬러코드는 위에 덩그러니 붙어있어서 아름답지가(?) 않다.
표로 바꿔보자.
<!-- 주석처리
<div class="display" name="rect"></div>
<div class="display" name="rect"></div>
<div class="display" name="rect"></div>
//-->
<table border="0" cellspacing="0">
<tr>
<td class="display" name="rect" valign="middle"></td>
<td class="display" name="rect" valign="middle"></td>
<td class="display" name="rect" valign="middle"></td>
</tr>
</table>
head에 CSS 스타일도 추가해준다.
<style type="text/css">
<!--
body { font-size:10pt; font-family:verdana,tahoma; }
.display {
text-align: center;
width: 100px;
height: 100px;
}
//-->
</style>
여전히 아름답지는 않지만 컬러코드가 잘 나오는 걸 확인하고는 여기서 끝.
HSB 색체계 말고 LCH의 H로 바꿔서 적용해주세요.
tinycolor는 다양한 색체계로의 변환이 손쉽게 가능하지만 LCH는 지원하지 않았다.
사실 이 모듈을 사용한 이유는 analogous() 함수가 있어서 따로 구현하지 않아도 다양하게 활용이 가능하다는 점이었는데,
어쩔 수 없이 다른 방법을 찾아야했다.
analogous: function(, results = 6, slices = 30) -> array<TinyColor>
첫번째로 찾은 것은 인터넷에서 Color.js파일을 구했지만 주석에 표기된 사이트 접속이 안 되고 코드도 뭔가 함수의 중첩인데다가 복잡한 느낌이라...
/* Code for converting from RGB to CIE-LCh adapted from Stuart Denman, http://www.stuartdenman.com/improved-color-blending/ */
var convertRGBtoCIELCh = function(){
convertRBGtoXYZ();
convertXYZtoCIELab();
convertCIELabToCIELCh();
}
다음으로 찾은 것이 chroma 라이브러리였다. (이름부터가 LCh의 C라니!)
LCH로의 변환이 간편했고, GitHub를 통해 관리가 되고 있다.
항상 무조건 최적, 최선을 찾을 필요는 없다. 목표로 하는 데 적절한 기술을 사용하면 그만이다.
라이브러리 하나 더 추가.
<script type='text/javascript' src='chroma.min.js'></script>
이제 hex값을 L,C,H로 각각 분해한 후 parseInt()를 통해 숫자형식으로 바꿔준다.
표를 한줄<tr> 더 추가하고 이전과 같은 방식으로 왼쪽((H-param+360)%360)과 오른쪽((H+param)%360)의 색상을 구했다.
/* Phase 2. LCH */
//또 한번 가운데 칸에 입력값을 그대로 표시
rect = document.getElementsByName('rect')[4];
rect.style.backgroundColor = tColor.toHexString();
rect.innerHTML = tColor.toHexString();
//LCH로 각각 분해
var L = parseInt(chroma(hex).get('lch.l'));
var C = parseInt(chroma(hex).get('lch.c'));
var H = parseInt(chroma(hex).get('lch.h'));
//두번째줄 왼쪽
var lchColor = chroma.lch(L, C, (H-param+360)%360);
rect = document.getElementsByName('rect')[3];
rect.style.backgroundColor = lchColor.hex();
rect.innerHTML = lchColor.hex();
//오른쪽
lchColor = chroma.lch(L, C, (H+param)%360);
rect = document.getElementsByName('rect')[5];
rect.style.backgroundColor = lchColor.hex();
rect.innerHTML = lchColor.hex();
위아랫줄은 각기 다른 결과로 나온 값인데다가, 보기 답답한 부분이 있어 여백을 줬다. <tr height="10"></tr>
#2에서 나온 색상 중 하나를 골라 명도를 높이고, 나머지 색의 명도를 낮춰서 색의 단계를 만들어 주세요. 반대로도요.
요구사항이 한층 복잡해졌다.
명도는 Brightness도 있지만 Luminance도 비슷한 역할을 하는 듯싶다.
높이고 낮추는 데 '정확한' 값이 없다. 스포이드로 찍어준 이미지를 가지고 적당히 튜닝해보기로 한다.
우선 단계를 넓히기 위해 9칸짜리 표를 추가.
코드가 점점 중복되고 길어지고 있어서 반복되는 부분은 함수로 만들어준다.
<table border="0" cellspacing="0">
<tr>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
</tr>
<tr height="10"></tr>
<tr>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
<td class="depth" name="grade" valign="middle"></td>
</tr>
</table>
function changeBG(name,i,colorName){
var obj = document.getElementsByName(name)[i];
obj.style.backgroundColor = colorName;
obj.innerHTML = colorName;
}
이 함수를 사용해서
var rect = document.getElementsByName('rect')[1];
rect.style.backgroundColor = tColor.toHexString();
rect.innerHTML = tColor.toHexString();
위 세 줄을 아래 한 줄로 바꿀 수 있다.
changeBG('rect',1,tColor.toHexString());
한쪽은 25만큼 밝게, 다른 한쪽은 어둡게 그리고 그 반대로도 값을 구한다.
var light = tinycolor(lchColor.hex()).lighten(25).toString();
var dark = tinycolor(lchColor.hex()).darken(25).toString();
/* 중략 */
light = tinycolor(lchColor.hex()).lighten(25).toString();
dark = tinycolor(lchColor.hex()).darken(25).toString();
3개에서 5개 색으로 늘어났고, 이 사이의 중간색들을 구해서 총 9개로 만든다.
역시 반복 사용되는 코드라 함수로 만들었다.
function setMid(fromColor,toColor,i){
var name = "grade";
var mColor = chroma.mix(fromColor, toColor, 0.5, 'lch');
changeBG(name,i,mColor);
}
// 사이사이 중간색을 추가하여 5 to 9개로 늘림
setMid(getBG(0),getBG(2),1);
setMid(getBG(2),getBG(4),3);
setMid(getBG(4),getBG(6),5);
setMid(getBG(6),getBG(8),7);
0번째(배열의 시작은 0번부터이다.)와 2번째 칸의 색상을 가져와서 중간값을 구한 후 1번칸에 설정하는 내용이다.
좀 복잡해보이지만 재사용성을 고려하면 아래가 좀더 적합할 것 같다.
function getMid(fromColor,toColor){
return chroma.mix(fromColor, toColor, 0.5, 'lch');
}
changeBG('grade',1,getMid(getBG(0),getBG(2)));
changeBG('grade',3,getMid(getBG(2),getBG(4)));
changeBG('grade',5,getMid(getBG(4),getBG(6)));
changeBG('grade',7,getMid(getBG(6),getBG(8)));
어쨌든 동일한 동작이기 때문에 결과는 같다.
이로써 1개의 색상 입력을 통해 3개의 근처 색상(analogous color)을 구하고,
근처 색상을 각각 밝게/어둡게, 어둡게/밝게한 5개의 색상(*2)을 만들었으며
5개 색상에서 각각의 중간색들을 구해서 총 9개(*2)의 컬러코드를 클릭 한번으로 구하는 프로그램이 완성되었다.
끝으로 색상이 만들어진 연유를 행간에 친절하게 설명하면서 마무리.
텍스트박스에 다른 색상을 넣어보면 다음과 같이 동작한다.
여기서 끝난 줄 알았는데...
아니, analogous를 쓰겠다는 게 아니고 그 색에서 Brightness를 수정한 색상을 양 끝으로 해서 그라데이션을 넣어달라니까요.
잘못 전달된(잘못 이해한) 요구사항으로 인해 작업을 다시 고쳐야 한다.
한집안에서도 이렇게 커뮤니케이션이 어려워서야.
이왕 이렇게 된 거 또 표를 만들자.
<table border="0" cellspacing="0">
<tr>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
</tr>
<tr>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
<td class="depth" name="gradation" valign="middle"></td>
</tr>
</table>
처음 생각보다 HTML코드가 길어지고 있다.
목표로 한 양 끝 값은 맞았으니 그대로 두고, 그 값을 9단계 계조로 펼치기만 하면 된다.
중간색을 구할 때 썼던 chroma.mix() 메소드를 이용하면 될 것 같다.
8등분이니까 값은 0.125로 수정.
동일한 작업을 반복할 때에는 폼나게 반복문을 써보자.
function gradation(fromColor,toColor,num,count){
var name = "gradation";
var step = 1/num;
for(var i=0; i<=num; i++) {
var mColor = chroma.mix(fromColor, toColor, step*i, 'lch');
changeBG(name,i+count,mColor);
}
}
함수를 두번 호출하는 것으로 끝.
(마지막 count 파라미터는 좀 억지스럽지만)
gradation(getBG(0),getBG(8),8,0);
gradation(getBG(9),getBG(17),8,9);
딱 요구사항에만 부합하는 코드지만
비슷한 무언가를 찾던 분들께 도움이 되길 바라며.
※ 전체 코드 (.js 라이브러리는 별도 다운로드 필요)
절차가 정해져있고 그것이 반복되는 일이라면,
프로그래밍이 해결해줄 수 있습니다.
자신있는 언어가 있다면 무엇이든, 심지어는 엑셀 수식으로도 충분히 가능합니다.