brunch

You can make anything
by writing

C.S.Lewis

by 지은 x NULL Jun 20. 2016

[HTML+JS] 컬러코드(색상표) 자동 생성기

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>



[요구사항 및 구현]


요구사항 #1.

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>


"참 쉽죠?" - 2

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();"/>



#1차 기능구현 완료

앗, 기능은 완성됐지만 색 비교를 해야하는데 칸과 칸 사이에 간격이 있고,

컬러코드는 위에 덩그러니 붙어있어서 아름답지가(?) 않다.


로 바꿔보자.


        <!-- 주석처리

        <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>


완성?

여전히 아름답지는 않지만 컬러코드가 잘 나오는 걸 확인하고는 여기서 끝.



요구사항 #2.

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();


색공간(HSB to LCH)만 바꿨을 뿐인데... 많이 다르다.


위아랫줄은 각기 다른 결과로 나온 값인데다가, 보기 답답한 부분이 있어 여백을 줬다. <tr height="10"></tr>

좀 숨통이 트인 것 같다.


요구사항 #3.

#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가지 색이 Analogous(LCH)로 만든 색과 동일함을 알 수 있다.

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번칸에 설정하는 내용이다.

9가지 색상이 도출되었다.

좀 복잡해보이지만 재사용성을 고려하면 아래가 좀더 적합할 것 같다.


            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)의 컬러코드를 클릭 한번으로 구하는 프로그램이 완성되었다.


끝으로 색상이 만들어진 연유를 행간에 친절하게 설명하면서 마무리.

개인작업이지만 다른 사람도 알아볼 수 있도록 범례를 추가


텍스트박스에 다른 색상을 넣어보면 다음과 같이 동작한다.

하드코딩이 아니다.


여기서 끝난 줄 알았는데...

#또다른 문제의 시작


요구사항 #3'.

아니, 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 라이브러리는 별도 다운로드 필요)



절차가 정해져있고 그것이 반복되는 일이라면,

프로그래밍이 해결해줄 수 있습니다.

자신있는 언어가 있다면 무엇이든, 심지어는 엑셀 수식으로도 충분히 가능합니다.


브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari