음악, 삶, 개발

Juce 로 만드는 GUI 파트4 - Colour 클래스 본문

개발 공부/Juce 공부방

Juce 로 만드는 GUI 파트4 - Colour 클래스

Lee_____ 2020. 10. 27. 20:29

< 참고자료 > 

Colour Class Reference

Colours Namespace Reference


< Colour 클래스 >

Juce 에서 색깔을 나타내는 클래스이다.

추후, Graphics 클래스의 멤버 함수의 인자로 매우 자주 사용된다.

또한 Colour 객체를 받는 다른 클래스의 함수도 매우 많이 있다.

따라서 이 포스트에서는 어떻게 Colour 객체를 생성하는지에 매우 많은 예제들을 사용하여 배울것이다.

한가지 Colour 클래스를 사용할때 유의점은, 영어 스펠링이 Color 가 아니라, Colour 라는것이다..

(내가 코딩할때 이 부분에서 오타를 너무 많이 냈었다...)

둘다 틀린 말은 아니지만 미국에서는 Color 라고 쓰지만, 

영어를 사용하는 다른 국가에서는 Colour 라고 쓴다고한다.


< 공식 문서 설명글 >

Represents a colour, also including a transparency value.

The colour is stored internally as unsigned 8-bit red, green, blue and alpha values.

 

내부적으로 8 bit 의 R,G,B,A 로 저장된다고 이야기하고있다.

따라서 Colour 클래스의 크기는 8 x 4 인 32bit 로 유추해볼수있다.


 

< juce::Colours 네임스페이스 사용하기 >

Juce 에서는 우리가 Colour 객체를 따로 만들필요없이,

juce::Colours::색깔이름 을 사용하면 Colour 객체가 필요한 함수의 인자로 곧바로 넘길수있도록,

약 140가지 색의 Colour 객체를 미리 만들어놓았다.

아래는 공식문서의 일부이다.

juce::Colours 의 공식문서중 일부.. 스크롤하면 더 나옴.

공식문서에 따르면 이 색깔들은 HTML 에서 사용하는 색들이라고한다.

사용법은 매우 매우 간단하다.

위에 색들중 aqua 색상을 사용하고자한다면, juce::Colours::aqua 를 입력후,

Colour 객체를 요구하는 함수 자리에 넘기면 된다.

void paint(juce::Graphics& g) override {

    g.fillAll(juce::Colours::aqua);

}

juce::Colours::aqua 의 사용


< Colour 객체 만들기 1 : Hex Code 사용하기 + Adobe Illustrator >

위의 juce::Colours Colour 들의 값은 Hex Code 로 되어있다.

const Colour aqua { 0xff00ffff };

 

이것이 가능한 이유는,  Colour 생성자가 Hex Code 값을 지원하기때문이다.

Colour::Colour(uint32 argb)	

 

uin32unsigned int 의 Juce 대체어로, 32bits integer 를 의미한다.

Hex Code 는 각 R,G,B 값을 10진법인 0-255 의 범위를 16진수로 변환하여 사용한다.

Juce 에서는 ARGB 를 사용하기때문에, 총 4개의 16진수가 연속적으로 들어가야한다.

(Hex Code 에 대해서 자세히 알고자한다면 여기 나무위키 설명을 참고하자)

 

위의 Alpha 에서 opaquetransparent (투명한) 의 반대로, 전혀 투명하지않은 상태 (불투명) 을 의미한다.

10진수 0 은 16진수 0 (투명함) 이고, 10진수 255 는 16진수로 FF (불투명) 이다.

alpha 값은 추후 Colour 클래스의 다른 함수로 컨트롤이 가능하기때문에

우선 oxff 로 값을 시작하고, RGB 값을 16진수로 순차적으로 입력하는면 된다.

Hex Code 의 장점은 무엇보다 코드의 간결함이다. RGB 값을 따로 따로 생성자에 입력하지않아도 되기때문이다.

컴파일할때마다 색깔을 조금씩 조정하면서 디자인을  판단해나가는것은 매우 많은 시간 낭비이다.

대부분의 색깔에 대한 결정 (디자인의 결정)은 Adobe Illustrator 같은 툴에서 완성을 한뒤 코드로 옮기는것이 지혜로운 방법이다.

(이것이 내가 일러스트레이터를 무척 열심히 하고있는 하나의 이유이기도 하다)

사실, 적절한 색깔을 머리로 떠올린뒤, 곧바로 ARGB 16진수로 적을수있는 사람은 아무도 없을것이다.

결국 디자인 프로그램에서 여러가지 색깔을 만들어본뒤에, 이걸 Hex Code 로 변환해서 Juce 코드에 적용하는것이 최선이다.

 

일러스트레이터를 열어, 원하는 색을 만들어보았다.

현재 Color 패널에 6C67FF 가 적혀있다.

이값은 RGB 를 의미한다.

이 값앞에 불투명한 Alpha 값을 표현하는 0XFF (불투명) 를 붙여주면 0XFF6C67FF 가 된다.

한마디로 일러스트레이터에서 색을 만들어 Hex Code 를 복사한뒤, 이 값앞에 0XFF 를 붙여서 Colour 객체를 초기화하면된다.

참고로 0XFF 의 0 은 숫자 0 이다.

void paint(juce::Graphics& g) override {

    const juce::Colour color {0XFF6C67FF}; // cf4b2b 를 일러스트레이터에서 가져옴!

    g.fillAll(color);

}

컴파일 해보면.

내가 일러스트레이터에서 만든 색대로 나오는것을 볼수있다.

 

일러스트레이터에서 색을 만들때 한가지 주의사항이 있다.

반드시 프로젝트 파일을 만들때 Color Mode를 RGB 로 해야한다. 

Default 는 CMYK 인데, 같은 RGB 값을 입력해도 색이 많이 틀리게 나온다.

우리는 어짜피 출력물을 만드는게 아니라, 사람들이 컴퓨터에서 사용하는

플러그인을 만드는것이기때문에 RGB Color 를 사용해야하는것이다.


< Colour 객체 만들기 2 : 11가지의 생성자들 > 

Colour ()
Colour (const Colour &)
Colour (uint32 argb)
Colour (uint8 red, uint8 green, uint8 blue)
Colour (uint8 red, uint8 green, uint8 blue, uint8 alpha)
Colour (uint8 red, uint8 green, uint8 blue, float alpha)
Colour (float hue, float saturation, float brightness, uint8 alpha)
Colour (float hue, float saturation, float brightness, float alpha)
Colour (PixelARGB argb)
Colour (PixelRGB rgb)
Colour (PixelAlpha alpha)

Colour 객체를 생성하기위해서는 앞서 설명한 ARGB, Hex Code 를 사용하는 생성자뿐만 아니라 총 11가지의 생성자가있다.

하지만 가급적이면 Colour(uint32 argb) 만을 사용하자.

이미 말했지만, 디자인적인 결정을 코딩하면서 한다는건 매우 시간낭비이기때문이다.

아무 인자를 받지않는 Colour() 생성자가 정도는 기억해두면 좋겠다. 

이 생성자는 완전 투명한, transparent black 색이다.

개인적으로 추천하는 방식은 juce::Colours::색깔명 namespace 처럼, 

나만의 색깔 저장소를 만들어 한곳에 모아두고 필요한 클래스에 사용하는것이 현명할것이다.

// 나만의 색깔 저장소!

namespace lee {

    namespace Colours {

        const juce::Colour noteBorder {0XFF6C67FF};

    }

}

< Colour 복사하기 : 복사의 가능성에 대한 고민 > 

Colour 를 복사할 일이 있겠냐고 생각할수있지만, 

개발을 하다보면 내가 예상치못한 값을 복사해야하는 경우가 매우 빈번히 생긴다.

우리는 복사에 대해 매우 쉽게 생각하는데, C++ 의 개발자들은 복사에 매우 예민하다.

매우 거대한 객체를 복사하려하는것은 프로그램을 느리게 만들고, 많은 예상치못한 문제들을 발생시킬수있기때문이다.

객체를 복사하는것에 대해 생각할때 가장 먼저 해야할일은 일단 객체의 크기를 콘솔에 찍어보는것이다.

DBG(sizeof(juce::Colour)); // 4 bytes

위의 코드를 실행했더니, 44 Bytes = 32 bits 이다.

32 bits 크기가 나온 이유는 A, R, G, B 의 각 값이 8 bits, 즉 8 bits x 4 개 = 32 bits 이기때문이다.

이정도면 매우 작은 객체이기에 복사하는데에 쫄지않아도된다.

 

크기가 작은것은 확인을 했다.

하지만 크기가 작다고 복사가 늘 가능한것은 아니다. 

클래스의 창조자가 이를 허락해야한다.

우리는 Colour 클래스의 창조자가 아니라 사용자다.

Colour 클래스는 C++ 에서 제공하는 Type 이 아니므로,

개발자가 복사를 지원하지않게끔 설계해놓았다면 우리는 복사할수없는것이다. (가능할수도있지만, 복사하려 하지않는게 좋다)

객체간의 복사가 실제로 가능한지 확인하려면 

Copy ConstructorCopy Assignment Operator, 즉 operator= 가 해당 클래스에 정의되어있는지,

클래스 공식 문서를 확인해보자. 

"32 bits 밖에 안되는 클래스니까 정의되어있지않을까? 👀" 하는 마음으로...

Colour::Colour(const Colour &) // Creates a copy of another Colour object.
Colour& Colour::operator=(const Colour &) // Copies another Colour object.	

 

역시나 둘다 정의되어있다.👌

사실 클래스 내에 Copy Constructor operator= 가 정의되어있다면, 

이 클래스를 창조한 개발자가 "이 클래스는 크기가 작으니까 복사를 해도 돼!" 라고 말한것이나 다름없다.

이를 조금 더 해석하면 "괜히 Colour포인터reference 로 넘길려는 바보같은 짓을 하지마" 이다.

이처럼, 클래스 공식문서를 읽으면서 개발자의 개발 의도를 이해하려는 노력이 중요하다.

왜 Copy Constructor 가 없지? 하며 불평할게 아니라,

왜 없을까...이유가 있을거야...라고 생각해보라는것이다.

Juce 같은 거대한 프레임워크의 창조자들은 이 분야에서 신에 가까운 엄청난 C++ 고수들이다.

이들이 실수로 Copy Constructor 를 빼먹는 초보같은 짓을 할리가 절대 없기때문이다.

 

예를 들어, juce::Component 클래스 같은 경우, 184 bytes = 1472 bits 이다. 

이건 매우 매우 큰 클래스이다. 

따라서 juce::Component 문서를 보면, 눈을 씻고 찾아봐도 Copy Constructoroperator= 가 정의되어있지않다.

한마디로 절대 복사하려 하지말라는 소리다.

 

Colour 객체의 복사를 이야기할때, 굳이 복사에 관한 이야기를 길게 한것은 

우리가 클래스를 설계하는 창조자의 입장이 되었을때도 이 사실을 유념해야한다는것이다.

무조건적으로 Copy Constructoroperator= 를 만들지말고, 내 클래스의 크기를 항상 판단하고 결정해야한다.

Juce 의 클래스들을 공부하면서, 우리의 클래스를 만들기위한 선조들의 지혜 역시 얻어가야한다.

 

다시 원래의 이야기로 돌아가 이야기를 마무리 짓자.

Copy Constructor 와 operator= 가 Colour 클래스에 정의되어있다.

따라서 Colour 를 복사하는, 아래와 같은 코드들이 가능하다.

const juce::Colour color        {0XFF6C67FF};
const juce::Colour colorCopied  {color}; // by Copy Constructor

juce::Colour transparentColor;  // You shouldn't use const if you want to assign something other!
transparentColor = colorCopied; // by operator= a.k.a Copy Assignment

당연한 이야기지만, 노파심에서 다시 말하면

Copy Asssignment 를 당할 객체는 당연히 const 여서는 안된다.

따라서 이 Colour 객체가 추후에 값이 변하기를 원하거나, 그럴 가능성이 있다면

당연히 const 를 붙이면 안된다.

하지만, 복사를 해줄 객체는 const 이든 아니든 상관없다.

const   juce::Colour color        {0XFF6C67FF};
        juce::Colour colorCopied  {color}; // by Copy Constructor

        juce::Colour emptyColor;

        emptyColor = color;         // copied from const Colour object
        emptyColor = colorCopied;   // copied from non-const Colour object

< Colour 비교하기 >

두개의 Colour 객체를 서로 비교할수있는 연산자 =!= 또한 제공한다.

bool Colour::operator==	(const Colour& other) const
bool Colour::operator!=	(const Colour& other) const

사실 색깔을 서로 비교할 일이 있을지는 아직 전혀 모르겠다.

사용할 일이 아마도 없을거같지만, 인생은 알수없기에..

아래와 같이 사용하면 된다.

const   juce::Colour color        {0XFF6C67FF};
const   juce::Colour colorCopied  {color}; // by Copy Constructor

bool isSameColor { color == colorCopied };

if (isSameColor) {

    DBG("Yes it's same color!");

}

또는,

const   juce::Colour color        {0XFF6C67FF};
const   juce::Colour colorCopied  {color}; // by Copy Constructor

if (color == colorCopied) {

    DBG("Yes it's same color!");

}

< Colour 정보 가져오기 : get 함수들 (쓸일이 없을듯) > 

uint8 	        getRed                  () const noexcept
uint8 	        getGreen                () const noexcept
uint8 	        getBlue                 () const noexcept
float 	        getFloatRed             () const noexcept
float 	        getFloatGreen           () const noexcept
float 	        getFloatBlue            () const noexcept
const PixelARGB getPixelARGB            () const noexcept
uint32 	        getARGB                 () const noexcept
uint8 	        getAlpha                () const noexcept
float 	        getFloatAlpha           () const noexcept
bool 	        isOpaque                () const noexcept
bool 	        isTransparent           () const noexcept
float 	        getHue                  () const noexcept
float 	        getSaturation           () const noexcept
float 	        getSaturationHSL        () const noexcept
float 	        getBrightness           () const noexcept
float 	        getLightness            () const noexcept
float 	        getPerceivedBrightness  () const noexcept
void 	        getHSB (float &hue, float &saturation, float &brightness)   const noexcept
void 	        getHSL (float &hue, float &saturation, float &lightness)    const noexcept

위와 같이 많은 get 함수들이 존재한다.

get 함수답게 () 뒤에 const 가 떡하니 붙어있다. 객체의 상태를 일절 변경하지않고, 값을 가져오는 용도이기때문이다.

 

간단히 살펴보면, isOpaque 는 나의 Colour 객체의 alpha 값이 255 인지 아닌지를 알려줄것이고,

isTransparentalpha 값이 0 인지 아닌지를 알려줄것이다.

get 함수임에도 인자를 받는  

getHSB (float& hue, float& saturation, float& brightness)

getHSL (float&hue, float &saturation, float &lightness) 

는 인자가 non-const reference 인것을 보니 output 파라미터이다.

사실 get 함수들을 쓸일이 있을까싶다.

아까 말했듯이, 나의 색깔은 모두 디자인 프로그램에서 결정될것이므로..

추후 get 해야할 상황이 만약 온다면, 그때가서 공식문서를 읽고 필요한걸 찾아 사용해도

늦지 않을것이다.


< 기존 Colour 로부터 다른 Colour 만들기 : set 함수스러운 get 함수들 >

Colour 	withAlpha                   (uint8 newAlpha)                                const noexcept
Colour 	withAlpha                   (float newAlpha)                                const noexcept
Colour 	withMultipliedAlpha         (float alphaMultiplier)                         const noexcept
Colour 	overlaidWith                (Colour foregroundColour)                       const noexcept
Colour 	interpolatedWith            (Colour other, float proportionOfOther)         const noexcept
Colour 	withHue                     (float newHue)                                  const noexcept
Colour 	withSaturation              (float newSaturation)                           const noexcept
Colour 	withSaturationHSL           (float newSaturation)                           const noexcept
Colour 	withBrightness              (float newBrightness)                           const noexcept
Colour 	withLightness               (float newLightness)                            const noexcept
Colour 	withRotatedHue              (float amountToRotate)                          const noexcept
Colour 	withMultipliedSaturation    (float multiplier)                              const noexcept
Colour 	withMultipliedSaturationHSL (float multiplier)                              const noexcept
Colour 	withMultipliedBrightness    (float amount)                                  const noexcept
Colour 	withMultipliedLightness     (float amount)                                  const noexcept
Colour 	brighter                    (float amountBrighter=0.4f)                     const noexcept
Colour 	darker                      (float amountDarker=0.4f)                       const noexcept
Colour 	contrasting                 (float amount=1.0f)                             const noexcept
Colour 	contrasting                 (Colour targetColour, float minLuminosityDiff)  const noexcept
String 	toString                    ()                                              const
String 	toDisplayString             (bool includeAlphaValue)                        const

get 함수가 있으니 set 함수도 있겠지...라는 유추는 착각이었다.

얼핏보면 함수명들이 set 함수 스럽지만 뒤에 const 가 다들 떡하니 붙어있다.

애초에 Colour 객체는 크기가 매우 작기에,

창조자는 Colour 의 값을 변경하는 대신, 사용자가 넘긴 인자값을 반영한 복사본을 return 해주는 방법을 택했다고 볼수있다.

창조자가 Colour 객체의 크기를 매우 하찮게 여긴다는것은 함수의 인자 형태를 보아도 잘 알수있다.

Colour overlaidWith (Colour foregroundColour) 

get 함수라는것이, 사실 객체의 멤버 변수의 값중 하나를 알려주는것이라고 생각하기쉽지만,

객체의 크기가 작을 경우 변화된 객체를 return 해주는데에도 충분히 사용할수있다는 지혜를 여기서 얻을수있다.

 

이전의 get 함수들과는 달리, 이 함수들은 실전에서 굉장히 쓸만한 여지가 있다.

디자인툴에서 작업을 할때도, 여러 도형들이 같은 색을 사용하지만, Alpha 값을 달리해서 변화를 주는 경우가 매우 빈번하기때문이다.

 

예를 들어, 3개의 Border 를 연달아 만들고싶은데, 색이 미묘하게 다르길 원한다고 해보자.

이때 3개의 다른 Colour 객체를 일일히 Hex Code 로 만드는 대신,

하나의 객체만 일단 만들고 withAlpha 함수를 사용하여 나머지 2개의 객체를 만들어내면 매우 편리할것이다.

const juce::Colour borderMain   { 0XFF6C67FF };
const juce::Colour border2      { borderMain.withAlpha(0.8f) }; // from borderMain
const juce::Colour border3      { borderMain.withAlpha(0.5f) }; // from borderMain

물론 위의 코드에서 사용할 withAlpha 안에 float 값 또한, 디자인 툴에서 미리 결정되어야할 것이라는 사실은 여전히 동일하다.

Hex Code 보다 이 방식이 좋은 이유는, 코드상에서 나의 의도를 나타낼수있기때문이다.

withSaturation 함수또한, 미묘한 색 변화를 표현할때 유용할것이다.


< 추가적인 Colour 만드는 방법 : 생성자스러운  static 함수들 (가독성을 높여라!) >

static Colour fromRGB         (uint8 red, uint8 green, uint8 blue)                            noexcept
static Colour fromRGBA        (uint8 red, uint8 green, uint8 blue, uint8 alpha)               noexcept
static Colour fromFloatRGBA   (float red, float green, float blue, float alpha)               noexcept
static Colour fromHSV         (float hue, float saturation, float brightness, float alpha)    noexcept
static Colour fromHSL         (float hue, float saturation, float lightness, float alpha)     noexcept
static Colour contrasting     (Colour colour1, Colour colour2)                                noexcept
static Colour greyLevel       (float brightness)                                              noexcept
static Colour fromString      (StringRef encodedColourString)

생성자에서 지원하는 방식이 아닌 추가적인 방식이거나,

코드의 가독성을 높이기위한 방법으로 보통 static 함수를 제공한다.

Colour                (float hue, float saturation, float brightness, float alpha) // 생성자
static Colour fromHSV (float hue, float saturation, float brightness, float alpha) // static 함수

위의 둘은 Colour 를 동일한 방법으로 만들수있지만,

fromHSV 가 코드에서 의도를 더욱 명확히 한다.

또한 11가지의 Colour 생성자 목록에는 RGBA 값을 모두 float 으로 넘기는 생성자가 없는데,  

static 함수에는 존재한다.

static Colour fromFloatRGBA (float red, float green, float blue, float alpha) 

static 함수는 추가적인 기능이며, 결과적으로 Colour 클래스의 멤버 함수가 아니기때문에,

color.fromFloatRGBA 로 호출할수없고 아래와 같이 코드를 작성해야한다.

juce::Colour::fromFloatRGBA(0.4f, 0.6f, 0.2f, 1.0f);

확실히 생성자를 이용하는것보다는 더욱 가독성이 생겼다.

결과적으로 아래와 같이 사용한다.

void paint(juce::Graphics& g) override {

    const juce::Colour color = juce::Colour::fromFloatRGBA(0.4f, 0.6f, 0.2f, 1.0f);
    
    g.fillAll(color);
    
}

< Colour 클래스 공부를 마치며.. : 결론내기 .>

Colour 클래스는 매우 다양한 방식으로 Colour 객체를 만들수있게해준다.

공부삼아 대부분의 내용을 훑어보았지만

결론을 지어보면 아래와 같다.

 

1. 디자인적 결정 (색 결정 포함) 은 디자인 툴에서!

2. 결정된 Hex Code 를 juce::Colour(uint8 rgba) 의 인자로

3. 나만의 namespace 만들어 Colour 객체들을 보관하기.

4. withAlpha 같은 함수들을 사용하여 Colour 객체간에 미묘한 색 변화를 주기.

 

 

다음 장에서는 ColourGradient 클래스에 대해 다룰것이다.