Colour Converter in C

In this post I will write code in C to convert colour values between decimal red, green and blue, hexadecimal RGB, and hue, saturation and lightness or HSL.

Most people will be familiar with representing colours in RGB format using either decimal or hexadecimal numbers, for example in CSS you might use

Using rgb values in CSS

.redonyellow
{
    color: #FF0000;
    background-color: rgb(255, 255, 0);
}

Many people will also be aware of HSL which is a cylindrical coordinate representation of a colour, and is described fully (along with another similar system called HSV) in this Wikipedia article. You can also use HSL in CSS, for example

Using HSL values in CSS

.redonyellow
{
    color: hsl(0, 100%, 50%);
    background-color: hsl(60, 100%, 50%);
}

Planning the Code

This project will consist of a source code file containing structs to hold RGB in decimal or hexadecimal, HSL values and the functions to convert between them. There are also utility functions to validate values held in structs and to split or combine hexadecimal values from/to single strings. We will also need the corresponding header files, and a file containing the main function to test and demonstrate the code, and code to populate test data. The structs will be:

  • RGBdec
  • RGBhex
  • HSL

and the functions will be:

  • validate_rgbhex
  • validate_hsl

  • split_hex
  • combine_hex

  • hex2dec
  • hex2hsl
  • dec2hex
  • dec2hsl
  • hsl2dec
  • hsl2hex

I apologise to anyone who objects to my use of "2" instead of the "to". Feel free to use proper English if you wish!

Coding

Create a new folder and within it create these empty files, download the zip, or use the Github repository.

  • colourconverter.c
  • colourconverter.h
  • test.c
  • test.h
  • main.h

Source Code Links

ZIP File
GitHub

As you can see I have used the British spelling of colour but again it's up to you. The largest number of visitors to this site are from the US, and I am afraid I can offer them no logical reason for our use of the superfluous letter 'u'!

Open colourconverter.h and type or paste the following. This file will then be finished but you might like to leave it open for reference.

colourconverter.h

#include<stdlib.h>
#include<stdbool.h>
#include<stdio.h>
#include<math.h>

//--------------------------------------------------------
// STRUCTS
//--------------------------------------------------------
typedef struct
{
    unsigned char R;
    unsigned char G;
    unsigned char B;
} RGBdec;

typedef struct
{
    char R[3];
    char G[3];
    char B[3];
} RGBhex;

typedef struct
{
    int H;
    double S;
    double L;
} HSL;

//--------------------------------------------------------
// FUNCTION PROTOTYPES
//--------------------------------------------------------
bool validateRGBhex(RGBhex rgbh);
bool validateHSL(HSL hsl);

bool splithex(char* hexcolour, RGBhex* rgbh);
void combinehex(RGBhex rgbh, char* combined);

void hex2dec(RGBhex rgbh, RGBdec* rgbd);
void hex2hsl(RGBhex rgbh, HSL* hsl);
void dec2hex(RGBdec rgbd, RGBhex* rgbh);
void dec2hsl(RGBdec rgbd, HSL* hsl);
void hsl2dec(HSL hsl, RGBdec* rgbd);
void hsl2hex(HSL hsl, RGBhex* rgbh);

void showRGBdec(RGBdec rgbd);
void showRGBhex(RGBhex rgbh);
void showHSL(HSL hsl);

This is all quite straightforward - we have three structs, each holding the three component parts of their respective colour representations. After that we have the prototypes of the various functions.

Now we can move on to implementing those functions in colourconverter.c. This is not so straightforward; in fact it's quite long and complex.

colourconverter.c

#include<stdbool.h>
#include<stdio.h>
#include<string.h>
#include<ctype.h>

#include"colourconverter.h"

//--------------------------------------------------------
// FUNCTION PROTOTYPES
//--------------------------------------------------------
static int indexofchar(char* str, char c);

static int calculate_hue(RGBdec rgbd);
static double calculate_saturation(RGBdec rgbd, double lightness);
static double calculate_lightness(RGBdec rgbd);

//--------------------------------------------------------
// FUNCTION showRGBdec
//--------------------------------------------------------
void showRGBdec(RGBdec rgbd)
{
    printf("rgb(%d, %d, %d)\n", rgbd.R, rgbd.G, rgbd.B);
}

//--------------------------------------------------------
// FUNCTION showRGBhex
//--------------------------------------------------------
void showRGBhex(RGBhex rgbh)
{
    printf("#%s%s%s\n", rgbh.R, rgbh.G, rgbh.B);
}

//--------------------------------------------------------
// FUNCTION showHSL
//--------------------------------------------------------
void showHSL(HSL hsl)
{
    printf("hsl(%d, %4.2lf%%, %4.2lf%%)\n", hsl.H, hsl.S, hsl.L);
}

//--------------------------------------------------------
// FUNCTION validhex - checks character is 0-9, A-F or a-f
//--------------------------------------------------------
static bool validhex(char c)
{
    if( (c < 48 || c > 57) && (c < 65 || c > 70) && (c < 97 || c > 102) )
        return false;
    else
        return true;
}

//--------------------------------------------------------
// FUNCTION validateRGBhex
//--------------------------------------------------------
bool validateRGBhex(RGBhex rgbh)
{
    //R, G and B must each have exactly 2 chars
    if(strlen(rgbh.R) != 2 || strlen(rgbh.G) != 2 || strlen(rgbh.B) != 2)
        return false;

    //combine into one string for easier iterating
    char combined[7] = "";
    combinehex(rgbh, combined);

    //iterate combined, checking each character is 0-9, A-F or A-F
    for(int c = 0; c <=5; c++)
    {
        if(!validhex(combined[c]))
            return false;
    }

    return true;
}

//--------------------------------------------------------
// FUNCTION validateHSL
//--------------------------------------------------------
bool validateHSL(HSL hsl)
{
    //hue - 0-360
    if(hsl.H < 0 || hsl.H > 360)
        return false;

    //saturation - 0-100
    if(hsl.S < 0 || hsl.S > 100)
        return false;

    //lightness - 0-100
    if(hsl.L < 0 || hsl.L > 100)
        return false;

    return true;
}

//--------------------------------------------------------
// FUNCTION combinehex
// write R, G & B to single string
//--------------------------------------------------------
void combinehex(RGBhex rgbh, char* combined)
{
    strcat(combined, rgbh.R);
    strcat(combined, rgbh.G);
    strcat(combined, rgbh.B);
}

//--------------------------------------------------------
// FUNCTION splithex
// populate RGBhex values from single string
//--------------------------------------------------------
void splithex(char* hexcolour, RGBhex* rgbh)
{
    //must have 6 chars
    if(strlen(hexcolour) != 6)
    {
        rgbh = NULL;

        return false;
    }

    //check each char valid hex
    for(int c = 0; c <=5; c++)
    {
        if(!validhex(hexcolour[c]))
        {
            rgbh = NULL;

            return false;
        }
    }

    memcpy(rgbh->R, hexcolour + 0, 2);
    rgbh->R[2] = '\0';
    memcpy(rgbh->G, hexcolour + 2, 2);
    rgbh->G[2] = '\0';
    memcpy(rgbh->B, hexcolour + 4, 2);
    rgbh->B[2] = '\0';

    return true;
}

//--------------------------------------------------------
// FUNCTION hex2dec
//--------------------------------------------------------
void hex2dec(RGBhex rgbh, RGBdec* rgbd)
{
    //array of valid hexadecimal digits, indexes correspond to values
    char hexvalues[] = "0123456789ABCDEF";

    rgbd->R = indexofchar(hexvalues, toupper(rgbh.R[1])) + (indexofchar(hexvalues, toupper(rgbh.R[0])) * 16);
    rgbd->G = indexofchar(hexvalues, toupper(rgbh.G[1])) + (indexofchar(hexvalues, toupper(rgbh.G[0])) * 16);
    rgbd->B = indexofchar(hexvalues, toupper(rgbh.B[1])) + (indexofchar(hexvalues, toupper(rgbh.B[0])) * 16);
}

//--------------------------------------------------------
// FUNCTION dec2hex
//--------------------------------------------------------
void dec2hex(RGBdec rgbd, RGBhex* rgbh)
{
    char hexvalues[] = "0123456789ABCDEF";
    int i0;
    int i1;

    i0 = rgbd.R / 16;
    i1 = rgbd.R - (16 * i0);
    rgbh->R[0] = hexvalues[i0];
    rgbh->R[1] = hexvalues[i1];
    rgbh->R[2] = '\0';

    i0 = rgbd.G / 16;
    i1 = rgbd.G - (16 * i0);
    rgbh->G[0] = hexvalues[i0];
    rgbh->G[1] = hexvalues[i1];
    rgbh->G[2] = '\0';

    i0 = rgbd.B / 16;
    i1 = rgbd.B - (16 * i0);
    rgbh->B[0] = hexvalues[i0];
    rgbh->B[1] = hexvalues[i1];
    rgbh->B[2] = '\0';
}

//--------------------------------------------------------
// FUNCTION dec2hsl
//--------------------------------------------------------
void dec2hsl(RGBdec rgbd, HSL* hsl)
{
    hsl->H = (int)calculate_hue(rgbd);
    hsl->L = calculate_lightness(rgbd);
    hsl->S = calculate_saturation(rgbd, hsl->L);
}

//--------------------------------------------------------
// FUNCTION hex2hsl
//--------------------------------------------------------
void hex2hsl(RGBhex rgbh, HSL* hsl)
{
    RGBdec rgbd;

    hex2dec(rgbh, &rgbd);

    dec2hsl(rgbd, hsl);
}

//--------------------------------------------------------
// FUNCTION hsl2dec
//--------------------------------------------------------
void hsl2dec(HSL hsl, RGBdec* rgbd)
{
    hsl.L /= 100.0;
    hsl.S /= 100.0;

    if(hsl.S == 0)
    {
        rgbd->R = hsl.L * 255.0;
        rgbd->G = hsl.L * 255.0;
        rgbd->B = hsl.L * 255.0;
    }
    else
    {
        //temporary_1
        double temporary_1;

        if(hsl.L < 0.5)
        {
            temporary_1 = hsl.L * (1.0 + hsl.S);
        }
        else
        {
            temporary_1 = hsl.L + hsl.S - hsl.L * hsl.S;
        }

        //temporary_2
        double temporary_2 = 2.0 * hsl.L - temporary_1;

        //hue
        double hue = hsl.H / 360.0;

        //temporary_R, temporary_G and temporary_B
        double temporary_R = hue + 0.333;
        double temporary_G = hue;
        double temporary_B = hue - 0.333;

        //ensure temporary_R, temporary_G and temporary_B are between 0 and 1
        if(temporary_R < 0.0)
            temporary_R += 1.0;
        if(temporary_R > 1.0)
            temporary_R -= 1.0;

        if(temporary_G < 0.0)
            temporary_G += 1.0;
        if(temporary_G > 1.0)
            temporary_G -= 1.0;

        if(temporary_B < 0.0)
            temporary_B += 1.0;
        if(temporary_B > 1.0)
            temporary_B -= 1.0;

        //RED
        if((6.0 * temporary_R) < 1.0)
        {
            rgbd->R = round((temporary_2 + (temporary_1 - temporary_2) * 6.0 * temporary_R) * 255.0);
        }
        else if((2.0 * temporary_R) < 1.0)
        {
            rgbd->R = round(temporary_1 * 255.0);
        }
        else if((3.0 * temporary_R) < 2.0)
        {
            rgbd->R = round((temporary_2 + (temporary_1 - temporary_2) * (0.666 - temporary_R) * 6.0) * 255.0);
        }
        else
        {
            rgbd->R = round(temporary_2 * 255.0);
        }

        //GREEN
        if((6.0 * temporary_G) < 1.0)
        {
            rgbd->G = round((temporary_2 + (temporary_1 - temporary_2) * 6.0 * temporary_G) * 255.0);
        }
        else if((2.0 * temporary_G) < 1.0)
        {
            rgbd->G = round(temporary_1 * 255.0);
        }
        else if((3.0 * temporary_G) < 2.0)
        {
            rgbd->G = round((temporary_2 + (temporary_1 - temporary_2) * (0.666 - temporary_G) * 6.0) * 255.0);
        }
        else
        {
            rgbd->G = round(temporary_2 * 255.0);
        }

        //BLUE
        if((6.0 * temporary_B) < 1.0)
        {
            rgbd->B = round((temporary_2 + (temporary_1 - temporary_2) * 6.0 * temporary_B) * 255.0);
        }
        else if((2.0 * temporary_B) < 1.0)
        {
            rgbd->B = round(temporary_1 * 255.0);
        }
        else if((3.0 * temporary_B) < 2.0)
        {
            rgbd->B = round((temporary_2 + (temporary_1 - temporary_2) * (0.666 - temporary_B) * 6.0) * 255.0);
        }
        else
        {
            rgbd->B = round(temporary_2 * 255.0);
        }
    }
}

//--------------------------------------------------------
// FUNCTION hsl2hex
//--------------------------------------------------------
void hsl2hex(HSL hsl, RGBhex* rgbh)
{
    RGBdec rgbd;

    hsl2dec(hsl, &rgbd);

    dec2hex(rgbd, rgbh);
}

//--------------------------------------------------------
// FUNCTION indexofchar
//--------------------------------------------------------
static int indexofchar(char* str, char c)
{
    char* i = strchr (str, c);
    return i != NULL ? i - str : -1;
}

//---------------------------------------------------------------------------------
// FUNCTION calculate_hue
// Hue is 0 if grey (Max == Min)
//---------------------------------------------------------------------------------
static int calculate_hue(RGBdec rgbd)
{
    double max = 0;
    double min = 0;

    double dR;
    double dG;
    double dB;

    dR = (double)rgbd.R / 255.0;
    dG = (double)rgbd.G / 255.0;
    dB = (double)rgbd.B / 255.0;

    if(dR >= dG && dR >= dB)
        max = dR;
    else if(dG >= dB && dG >= dR)
        max = dG;
    else if(dB >= dG && dB >= dR)
        max = dB;

    if(dR <= dG && dR <= dB)
        min = dR;
    else if(dG <= dB && dG <= dR)
        min = dG;
    else if(dB <= dG && dB <= dR)
        min = dB;

    double hue;

    if(max == min)
    {
        hue = 0;
    }
    else
    {
        if(max == dR)
        {
            hue = (dG - dB) / (max - min);
        }
        else if(max == dG)
        {
            hue = 2.0 + (dB - dR) / (max - min);
        }
        else if(max == dB)
        {
            hue = 4.0 + (dR - dG) / (max - min);
        }

        hue *= 60.0;

        if(hue < 0.0)
        {
            hue += 360.0;
        }
    }

    return hue;
}

//---------------------------------------------------------------------------------
// FUNCTION calculate_saturation
// 0 - 100
//---------------------------------------------------------------------------------
static double calculate_saturation(RGBdec rgbd, double lightness)
{
    double max = 0;
    double min = 0;

    double dR;
    double dG;
    double dB;

    dR = (double)rgbd.R / 255.0;
    dG = (double)rgbd.G / 255.0;
    dB = (double)rgbd.B / 255.0;

    if(dR >= dG && dR >= dB)
        max = dR;
    else if(dG >= dB && dG >= dR)
        max = dG;
    else if(dB >= dG && dB >= dR)
        max = dB;

    if(dR <= dG && dR <= dB)
        min = dR;
    else if(dG <= dB && dG <= dR)
        min = dG;
    else if(dB <= dG && dB <= dR)
        min = dB;

    double saturation;

    if(max == min)
    {
        saturation = 0.0;
    }
    else
    {
        if(lightness < 50.0)
        {
            saturation = ((max - min) / (max + min)) * 100.0;
        }
        else
        {
            saturation = ((max - min) / (2.0 - max - min)) * 100.0;
        }
    }

    return saturation;
}

//---------------------------------------------------------------------------------
// FUNCTION calculate_lightness
// 0 - 100
//---------------------------------------------------------------------------------
static double calculate_lightness(RGBdec rgbd)
{
    double max = 0;
    double min = 0;

    double dR;
    double dG;
    double dB;

    dR = (double)rgbd.R / 255.0;
    dG = (double)rgbd.G / 255.0;
    dB = (double)rgbd.B / 255.0;

    if(dR >= dG && dR >= dB)
        max = dR;
    else if(dG >= dB && dG >= dR)
        max = dG;
    else if(dB >= dG && dB >= dR)
        max = dB;

    if(dR <= dG && dR <= dB)
        min = dR;
    else if(dG <= dB && dG <= dR)
        min = dG;
    else if(dB <= dG && dB <= dR)
        min = dB;

    double lightness = ((min + max) / 2.0) * 100.0;

    return lightness;
}

Functions showRGBdec, showRGBhex and showHSL

These are very straightforward, they merely print out the properties of a strucy for debugging and demonstration.

Function validhex

A simple function which will be used later to check a character is a valid hexadecimal digit, ie. 0 to 9, a to f or A to F.

Functions validateRGBhex and validateHSL

In validateRGBhex we firstly we check each of the three values is exactly two characters long. We then call combinehex to get a single string which we iterate, checking each character is valid using the validhex function.

For validateHSL we simply check the values are within the allowable ranges, 0° to 360° for hue and 0% to 100% for saturation and lightness.

Functions combinehex and splithex

combinehex is another very simple function which uses strcat to combine three hexadecimal values into one.

splithex first validates the length and characters of the string, and then copies the relevant parts to the RGBhex struct.

Function hex2dec

This function firstly creates an array of hexadecimal digits, their indexes corresponding to their decimal values. For example hexvalues[15] = 'F', so to get the decimal value of F we just find its position in the array using the indexofchar function.

This is used by finding the decimal value of the second (units) component of each of the RGB values, and then the first (16s) value. The 16s value then of course needs to be multiplied by 16. These two values added together give us the decimal value of the hexadecimal number.

Function dec2hex

Going from decimal to hexadecimal is a bit more involved. Firstly we again use an array of hexadecimal values with indexes mapping to decimal values. Then for each RGB value we initialize a couple of variables, one for the 16s and one for the units. We then use these as indexes of the hexvalues array to set the hexadecimal characters, not forgetting to set the last character to null.

Function dec2hsl

This function itself is very simple, it just calls three other functions to set the H, S and L values. These functions aren't so simple though, as we will see later.

Function hex2hsl

We don't do the hexadecimal to HSL conversion directly, but just make use of two other functions, firstly converting the hexadecimal to decimal, and then converting the decimal to HSL.

Function hsl2dec

To make the calculations easier we first convert the lightness and saturation from percentages to decimal fractions, for example 50% becomes 0.5.

Next we check whether the saturation is 0, ie. whether the colour is a shade of grey. If so we just get the RGB values by multiplying up the lightness by 255 and we're done. If not things get a lot more complicated.

Firstly we need a few variables to use later. These are temporary_1, temporary_2, hue, temporary_R, temporary_G and temporary_B, their values being set as shown in the code. The last three then need to be corrected if they are outside the range 0-1.

There are four ways of calculating each of the three values R, G and B, the conditions and calculation for each being as per the code.

Function hsl2hex

Here we take the same approach as hex2hsl by using other functions, firstly converting HSL to decimal and then decimal to hexadecimal.

Function indexofchar

The function used earlier which returns the index of the specified character in the specified string.

Function calculate_hue

We need the RGB values as decimals, so create three variables to hold the RGB values divided by 255. We then find the maximum and minimum of these values. If the minimum and maximum are the same we have white, a shade of grey or black therefore hue is set to 0.

If the minimum and maximum are different we calculate the hue in one of three different ways depending on which colour is max. Hue is then multiplied by 60, and shunted forward by 360 if it happens to fall below 0.

Function calculate_saturation

This function is very similar to calculate_hue and there is considerable duplication of code. If I were trying to squeeze optimum performance from this code I could combine these along with calculate_lightness, but have kept them separate for clarity. As you can see, saturation is calculated in one of two ways depending on the value of lightness. This is passed as an argument and therefore calculate_lightness must be called before calculate_saturation.

Function calculate_lightness

Again considerable overlap with calculate_hue and calculate_saturation, and there is only one calculation of lightness irrespective of any other values.

Having got to the end of that rather monstrous code we'll write something simple for a bit of light relief. We'll need a few sample colours in each of the three representations to test our code. I have kept the creation of these in separate .h and .c files. Let's start off with the header file.

test.h

#include<stdbool.h>

#include"colourconverter.h"

#define SAMPLE_SIZE 8

//--------------------------------------------------------
// STRUCTS
//--------------------------------------------------------
typedef struct
{
    RGBdec rgbd;
    RGBhex rgbh;
    HSL hsl;
}testsample;

//--------------------------------------------------------
// FUNCTION PROTOTYPES
//--------------------------------------------------------
void initialize_samples(testsample testsamples[SAMPLE_SIZE]);

This starts off with a struct to hold the same colour in RGB decimal, RGB hexadecimal and HSL. Once we have populated a few of these using the function prototyped here we can run our conversions on them to make sure they produce the correct values.

Now let's move on to implementing initialize_samples.

test.c

#include"test.h"
#include"stdlib.h"

//--------------------------------------------------------
// FUNCTION initialize_samples
//--------------------------------------------------------
void initialize_samples(testsample testsamples[SAMPLE_SIZE])
{
    testsamples[0] = (testsample){.rgbd = (RGBdec){.R = 255, .G = 0, .B = 0},
        .rgbh = (RGBhex){.R = "FF", .G = "00", .B = "00"},
        .hsl = (HSL){.H = 0, .S = 100, .L = 50}};

    testsamples[1] = (testsample){.rgbd = (RGBdec){.R = 0, .G = 0, .B = 0},
        .rgbh = (RGBhex){.R = "00", .G = "00", .B = "00"},
        .hsl = (HSL){.H = 0, .S = 0, .L = 0}};

    testsamples[2] = (testsample){.rgbd = (RGBdec){.R = 128, .G = 128, .B = 128},
        .rgbh = (RGBhex){.R = "80", .G = "80", .B = "80"},
        .hsl = (HSL){.H = 0, .S = 0, .L = 50.2}};

    testsamples[3] = (testsample){.rgbd = (RGBdec){.R = 255, .G = 255, .B = 255},
        .rgbh = (RGBhex){.R = "FF", .G = "FF", .B = "FF"},
        .hsl = (HSL){.H = 0, .S = 0, .L = 100}};

    testsamples[4] = (testsample){.rgbd = (RGBdec){.R = 255, .G = 128, .B = 0},
        .rgbh = (RGBhex){.R = "FF", .G = "80", .B = "00"},
        .hsl = (HSL){.H = 30, .S = 100, .L = 50}};

    testsamples[5] = (testsample){.rgbd = (RGBdec){.R = 0, .G = 128, .B = 255},
        .rgbh = (RGBhex){.R = "00", .G = "80", .B = "FF"},
        .hsl = (HSL){.H = 210, .S = 100, .L = 50}};

    testsamples[6] = (testsample){.rgbd = (RGBdec){.R = 32, .G = 64, .B = 128},
        .rgbh = (RGBhex){.R = "20", .G = "40", .B = "80"},
        .hsl = (HSL){.H = 220, .S = 60, .L = 31.4}};

    testsamples[7] = (testsample){.rgbd = (RGBdec){.R = 128, .G = 192, .B = 128},
        .rgbh = (RGBhex){.R = "80", .G = "C0", .B = "80"},
        .hsl = (HSL){.H = 120, .S = 33.68, .L = 62.75}};
}

Again this is quite straightforward if a bit tedious - it just takes an array of testsamples as an argument and populates it. Now we are on the home straight and will write the main function and a few test functions for it to call.

main.c

#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<stdbool.h>

#include"test.h"

//--------------------------------------------------------
// FUNCTION PROTOTYPES
//--------------------------------------------------------
void test_validations(void);
void test_split_combine(void);
void test_conversions(void);

//--------------------------------------------------------
// FUNCTION main
//--------------------------------------------------------
void main(void)
{
    puts("--------------------");
    puts("| codedrome.com    |");
    puts("| Colour Converter |");
    puts("--------------------\n");

    //test_validations();

    //test_split_combine();

    //test_conversions();
}

//--------------------------------------------------------
// FUNCTION test_validations
//--------------------------------------------------------
void test_validations(void)
{
    // create and validate RGBhex
    RGBhex rgbh;
    bool rgbhvalid;

    rgbh = (RGBhex){.R = "FF", .G = "00", .B = "FF"};
    rgbhvalid = validateRGBhex(rgbh);
    showRGBhex(rgbh);
    printf("RGB Hexadecimal valid: %s\n", rgbhvalid == true ? "Yes" : "No");

    rgbh = (RGBhex){.R = "GG", .G = "00", .B = "HH"};
    rgbhvalid = validateRGBhex(rgbh);
    showRGBhex(rgbh);
    printf("RGB Hexadecimal valid: %s\n", rgbhvalid == true ? "Yes" : "No");
    puts("");

    // create and validate HSL
    HSL hsl;
    bool hslvalid;

    hsl = (HSL){.H = 0, .S = 50, .L = 75};
    hslvalid = validateHSL(hsl);
    showHSL(hsl);
    printf("HSL valid: %s\n", hslvalid == true ? "Yes" : "No");

    hsl = (HSL){.H = 361, .S = -50, .L = 101};
    hslvalid = validateHSL(hsl);
    showHSL(hsl);
    printf("HSL valid: %s\n", hslvalid == true ? "Yes" : "No");
}

//--------------------------------------------------------
// FUNCTION test_split_combine
//--------------------------------------------------------
void test_split_combine(void)
{
    // create an RGBhex by splitting a string
    char hex[] = "00FF00";
    printf("RGBhex created from string %s\n", hex);
    RGBhex rgbh2;
    if(splithex(hex, &rgbh2))
    {
        showRGBhex(rgbh2);
    }
    else
    {
        printf("%s is not a valid colour\n", hex);
    }

    puts("");

    // create a hexadecimal string by combining an RGBhex
    RGBhex rgbh3 = (RGBhex){.R = "00", .G = "00", .B = "FF"};
    printf("hexadecimal string created from RGBhex ");
    showRGBhex(rgbh3);
    char hexstr[7] = "";
    combinehex(rgbh3, hexstr);
    printf("%s\n", hexstr);
}

//--------------------------------------------------------
// FUNCTION test_conversions
//--------------------------------------------------------
void test_conversions(void)
{
    RGBdec rgbd;
    RGBhex rgbh;
    HSL hsl;

    testsample testsamples[SAMPLE_SIZE];

    initialize_samples(testsamples);

    for(int i = 0; i < SAMPLE_SIZE; i++)
    {
        puts("Test Sample\n-----------");

        printf("rgb: ");
        showRGBdec(testsamples[i].rgbd);
        printf("hex: ");
        showRGBhex(testsamples[i].rgbh);
        printf("hsl: ");
        showHSL(testsamples[i].hsl);

        puts("Conversions\n-----------");

        printf("hex2dec: ");
        hex2dec(testsamples[i].rgbh, &rgbd);
        showRGBdec(rgbd);

        printf("hsl2dec: ");
        hsl2dec(testsamples[i].hsl, &rgbd);
        showRGBdec(rgbd);

        printf("dec2hex: ");
        dec2hex(testsamples[i].rgbd, &rgbh);
        showRGBhex(rgbh);

        printf("hsl2hex: ");
        hsl2hex(testsamples[i].hsl, &rgbh);
        showRGBhex(rgbh);

        printf("dec2hsl: ");
        dec2hsl(testsamples[i].rgbd, &hsl);
        showHSL(hsl);

        printf("hex2hsl: ");
        hex2hsl(testsamples[i].rgbh, &hsl);
        showHSL(hsl);

        puts("");
    }
}

In test_validations we first create two RGBhex structs, validate them and output the results. You might have spotted that the first is valid and the second is invalid. We then do the same with HSL, again with one valid and one invalid values.

The next function is test_split_combine. Here we first create a string of hexadecimal, then pass it to splithex to populate an RGBhex which is then printed. We then do the opposite by creating an RGBhex and then calling combinehex to create a single string, again printing the results.

Finally we implement the test_conversions function. This firstly creates an array of testsample structs and calls initialize_samples to populate it. We then iterate this array, printing the test data in its three forms and then calling all six conversion functions.

That's the code finished so we can compile and run it. Uncomment test_validations() in main and then in the terminal run these two commands.

Compile and run

gcc main.c colourconverter.c test.c -std=c11 -lm -o main

./main

You should get the following output. The second hexadecimal contains Gs and Hs and is therefore invalid, and the second HSL contains values outside the valid ranges so both validation functions return false.

Program Output - test_validations

--------------------
| codedrome.com    |
| Colour Converter |
--------------------

#FF00FF
RGB Hexadecimal valid: Yes
#GG00HH
RGB Hexadecimal valid: No

hsl(0, 50.00%, 75.00%)
HSL valid: Yes
hsl(361, -50.00%, 101.00%)
HSL valid: No

Now comment out test_validations() and uncomment test_split_combine(). Compile and run again to get the following output.

Program Output - test_split_combine

--------------------
| codedrome.com    |
| Colour Converter |
--------------------

RGBhex created from string 00FF00
#00FF00

hexadecimal string created from RGBhex #0000FF
0000FF

Now comment out test_split_combine(), uncomment test_conversions(), compile and run. There are eight samples but I'll just show one here. You may get minor differences due to rounding errors.

Program Output - test_conversions

--------------------
| codedrome.com    |
| Colour Converter |
--------------------

Test Sample
-----------
rgb:     rgb(128, 192, 128)
hex:     #80C080
hsl:     hsl(120, 33.68%, 62.75%)
Conversions
-----------
hex2dec: rgb(128, 192, 128)
hsl2dec: rgb(128, 192, 128)
dec2hex: #80C080
hsl2hex: #80C080
dec2hsl: hsl(120, 33.68%, 62.75%)
hex2hsl: hsl(120, 33.68%, 62.75%)

Leave a Reply

Your email address will not be published. Required fields are marked *