3

I am writing a UI for a legacy computer called an Amiga (some of you may remember these). They had display modes that had fixed numbers of colours - 2,4,8,16 etc. and you could set any of these using the RGB of 0 to 255.

So on a 4 colour display you could have:

  • Colour 0 R=0, G=0, B=0 (black)
  • Colour 1 R=255, G=255, B=255 (white)
  • Colour 2 R=128, G=128, B=128 (mid Grey)
  • Colour 3 R=255, G=0, B=0 (Red)

The maximum number of colours you could have on 'normal' display was 255 (there were some special display modes that pushed this higher but I don't need to worry about these).

I am trying to write some C code that will read through the display's colour list and find the reddest, greenest, and bluest colour, but I just cannot get my head around the comparisons involved.

If I have 3 vars -red, green & blue - all ints which hold the current colours rgb vals and 3 more - stored_red, stored_stored_green & stored_blue.

How can I write a function that will remember the colour number which has the most red in relation to the other colours?

I've tried:

If ((red>stored_red) && ((blue <stored_blue) || (green<stored_green)))

But this doesn't work. I think I need to work on ratios but just cannot figure out the maths.

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
Cogvos
  • 33
  • 3
  • Before you compare colors, separate them to RGB nodes. – unalignedmemoryaccess Aug 29 '17 at 12:17
  • What I remember, the Amiga didn't have RGB modes. Instead it used *bit-planes* where the combination of bits bits for a single pixel was an index into the color table. And on the original Amiga the color tables consisted of 32 entries of *twelve* bits, four for each R, G and B. 256 colors came with AGA and the Amiga 1200 and 4000 late in the Amiga life-time. I still think it was bit-planed though, but with up to 8 planes. – Some programmer dude Aug 29 '17 at 12:20
  • For technical information and references for programming for the Amiga, I suggest [this site](http://amigadev.elowar.com/) which seems to have all RKM (Rom Kernel Manual) on it. – Some programmer dude Aug 29 '17 at 12:23
  • could do with having a more [mcve] to see if there's something you're doing wrong, like what values you're setting the `stored_*` variables to – Chris Turner Aug 29 '17 at 12:30
  • Thanks for the very quick replies - the technology behind the way the Amiga displays stuff is not the issue (perhaps I should not have mentioned the Hardware?) It's how given the stored values I have I can compare them so that I end up with the reddest (or bluest etc) colour. So its more of a math problem I guess? The 3 stored vars are set as stored_red =0, stored_green=255, stored_blue=255 before I start the comparisons. – Cogvos Aug 29 '17 at 12:32
  • The if fails as it cant take account of a colour that has a much higher red val than the stored one , and a fractionally higher blue or green. So if stored_red is 100, stored_green 20 and stored_blue 21 the if will not execute its code if red is 255, green is 21 and blue is 22. Even though the colour has much more red in it. – Cogvos Aug 29 '17 at 12:39
  • Take a look at [Is there an easy way to compare how close two colors are to each other](https://stackoverflow.com/questions/492211/is-there-an-easy-way-to-compare-how-close-two-colors-are-to-each-other). – Ian Abbott Aug 29 '17 at 15:16
  • Also look at the code under "A low-cost approximation" on [CompuPhase - Colour metric](https://www.compuphase.com/cmetric.htm). – Ian Abbott Aug 29 '17 at 15:27

3 Answers3

2

There are different ways to check for maximum "redness"/"greenness" etc, this first simple way here is what my answer originally used:

/* for all palette (color table) entries */
if(red > stored_red) {
    max_red_index = current_index;
    stored_red = red;
}
if(blue > stored_blue) {
    max_blue_index = current_index;
    stored_blue = blue;
}

and the same for green of course.
This will just give the colors with the maximum components, but after the comment of @Chris Turner I think this is probably not what you want.

Another way could be to check the ratio of red to the other colors (I'll just use only red from now on):

redness = red*2/(blue + green + 1)

This gives a number between 1 and 510 for "redness", it does not consider brightness. For example R-G-B 50-10-10 is more red (4) than 255-100-100 (2).

Many other formulas are possible, something like

redness = red*red/(blue + green + 1)

would also take the brightness into account and would consider 255-100-100 more red than 50-10-10.

To use these formulas, just set the red variable from above to the result of the redness formula.

alain
  • 11,939
  • 2
  • 31
  • 51
  • This won't get the most red colour as it doesn't take into account how much blue and green a colour has - 255,0,0 is more red than 255,255,255 (aka white) for example. – Chris Turner Aug 29 '17 at 13:28
  • Thanks for the comment - I understand what you mean, I'll edit my answer. – alain Aug 29 '17 at 13:36
  • Many thanks for the reply. This is exactly what I was looking for. Not sure which of the two (with or without brightness) I'll use I suspect it will be both depending on the users background colour (the brightness one for a dark colour the one without for a light colour) Just as an aside is it possible to amend these formulae to find the most yellow colour? I think this is far more difficult as you cannot use a maximum but some kind of range? If the Amiga had a Hue system it would be a lot easier, but it does not. – Cogvos Aug 29 '17 at 15:14
  • Thanks! For yellow you could add red+green, like `yellowness = (red+green)/(blue + 1)` or similar. This would treat reddish yellow as equally yellow than greenish yellow. I would try to play a bit with the formulae until you're satisfied with the results. – alain Aug 29 '17 at 15:21
  • @Cogvos On a second thought, `min(red, green)` might be better than `red + green` for yellowness. – alain Aug 29 '17 at 15:54
1

You should calculate the percentage of red to find the reddest color.

Here is a program that uses an RGB structure, with a function to compare two RGB structures for "reddness". If two colors have the same percentage of red, the color with the largest value in the .r field is considered "redder". Another function takes an array of pointers to RGB structures and returns a pointer to the RGB triple considered "reddest".

Note that care must be taken to check for RGB tuples which are all zeros (0, 0, 0) when calculating the percentage; this could lead to an attempted division by zero. Thanks to @alain for catching this mistake in my original code.

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

struct RGB {
    int r;
    int g;
    int b;
};

bool is_redder_pct(struct RGB *triple1, struct RGB *triple2);
struct RGB * max_red(struct RGB *triples, size_t num_triples);

int main(void)
{
    struct RGB colors[] = { { .r = 125, .g = 0, .b = 0 },
                            { .r = 150, .g = 255, .b = 0 },
                            { .r = 100, .g = 20, .b = 21 },
                            { .r = 255, .g = 21, .b = 22 },
                            { .r = 0, .g = 0, .b = 0 },
                            { .r = 255, .g = 255, .b = 255 },
                            { .r = 128, .g = 128, .b = 128 },
                            { .r = 255, .g = 0, .b = 0 } };
    size_t num_colors = sizeof colors / sizeof *colors;

    struct RGB *reddest = max_red(colors, num_colors);

    printf("The reddest color is: (%d, %d, %d)\n",
           reddest->r, reddest->g, reddest->b);

    return 0;
}

/* Returns true if triple1 is at least as red as triple2 */
bool is_redder_pct(struct RGB *triple1, struct RGB *triple2)
{
    bool ret_val;
    int triple1_sum = triple1->r + triple1->g + triple1->b;
    int triple2_sum = triple2->r + triple2->g + triple2->b;

    /* if triple1 is black, triple1 is not redder than triple2 */
    if (triple1_sum == 0) {
        ret_val = false;

    /* otherwise, if triple2 is black, triple1 is redder than triple2 */
    } else if (triple2_sum == 0) {
        ret_val = true;

    /* otherwise the percentages are calculated in a comparison */    
    } else {
        ret_val = triple1->r / (triple1_sum * 1.0)
            >= triple2->r / (triple2_sum * 1.0);
    }

    return ret_val;
}

/* Returns a pointer to the RGB struct in the array TRIPLES
 * that compares "reddest" */
struct RGB * max_red(struct RGB *triples, size_t num_triples)
{
    struct RGB *max = &triples[0];

    for (size_t i = 1; i < num_triples; i++) {
        struct RGB *curr = &triples[i];
        if (is_redder_pct(curr, max) && curr->r > max->r) {
            max = curr;
        }
    }

    return max;
}

Program output:

The reddest color is: (255, 0, 0)
ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • If black (0,0,0) is in the color table, there is a (float) division by zero. I don't remember if it is OK or causes a "guru meditation" on the Amiga. Nice answer. – alain Aug 29 '17 at 14:11
  • @alain-- Thanks for catching that mistake; and the array of test colors did contain a (0, 0, 0) triple, too! I think that all is well now.... – ad absurdum Aug 29 '17 at 14:28
  • Hi Many thanks for the solution Div by zero is a no-no, and will Guru (or on later systems cause a software failure). Now all I have to do is pick the solution that fits the situation. Many thanks for your hep. – Cogvos Aug 29 '17 at 15:21
  • @Cogvos-- note that the current code in this answer has no division by zero error; that was the result of a missed input case in the first version of the answer. – ad absurdum Sep 03 '17 at 04:59
0

The following is based on a "a low-cost approximation" of color difference from CompuPhase - Colour metric, but avoids the square root function (thereby measuring the square of the color difference, which is fine for comparison purposes), and scales the result to avoid loss of precision caused by integer division. It sorts an array of color tuples into order from "most red" to "least red".

The comparison makes mid-grey more red than black, and black more red than white, which seems fairly arbitrary!

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

/*
 * Sorts an array of RGB color values (each component from 0 to 255)
 * in descending order of redness.
 *
 * Uses a low-cost approximation of color distance from
 * <https://www.compuphase.com/cmetric.htm>.  It uses a weighted Euclidean
 * distance function to measure the distance between colors, where the weight
 * factors depend on the mean level of "red":
 *
 * rmean = (c1.r + c2.r) / 2
 * dr = c1.r - c2.r
 * dg = c1.g - c2.g
 * db = c1.b - c2.b
 *
 * dc = sqrt(((2 + (rmean / 256)) * dr * dr) + (4 * dg * dg) +
 *           ((2 + ((255 - rmean) / 256)) * db * db))
 *
 * Uses a modified version of the above which returns the square of the
 * distance between two colors scaled by a silly amount to avoid loss of
 * precision caused by integer division.
 */

struct rgb {
    unsigned char r;    /* red range 0..255 */
    unsigned char g;    /* green range 0..255 */
    unsigned char b;    /* blue range 0..255 */
};

/* distance squared between two colors, scaled by some silly amount. */
long color_dist_squared(const struct rgb *c1, const struct rgb *c2)
{
    long rsum = c1->r + c2->r;
    long dr = (long)c1->r - (long)c2->r;
    long dg = (long)c1->g - (long)c2->g;
    long db = (long)c1->b - (long)c2->b;

    return (((1024 + rsum) * dr * dr) + (2048 * dg * dg) +
            ((1534 - rsum) * db * db));
}

/* distance squared from pure red, scaled by some silly amount. */
long antiredness_squared(const struct rgb *c)
{
    const struct rgb pure_red = { .r = 255, .g = 0, .b = 0 };

    return color_dist_squared(&pure_red, c);
}

/*
 * qsort() comparison function.
 * a and b point to struct rgb values.
 * Returns 1 if *a is more anti-red (less red) than *b.
 * Returns 0 if *a and *b are equally (anti-)red.
 * Returns -1 if *a is less anti-red (more red) than *b.
 */
int compar_antiredness(const void *a, const void *b)
{
    const struct rgb *ca = (const struct rgb *)a;
    const struct rgb *cb = (const struct rgb *)b;
    long ara = antiredness_squared(ca);
    long arb = antiredness_squared(cb);
    long diff = ara - arb;

    return (diff > 0) - (diff < 0);
}

int main(void)
{
    struct rgb colors[] = {
        { .r = 125, .g = 0, .b = 0 },
        { .r = 150, .g = 255, .b = 0 },
        { .r = 100, .g = 20, .b = 21 },
        { .r = 255, .g = 21, .b = 22 },
        { .r = 0, .g = 0, .b = 0 },
        { .r = 255, .g = 255, .b = 255 },
        { .r = 128, .g = 128, .b = 128 },
        { .r = 255, .g = 0, .b = 0 },
    };
    size_t num_colors = sizeof(colors) / sizeof(colors[0]);
    size_t i;

    printf("Unsorted colors:\n");
    for (i = 0; i < num_colors; i++) {
        printf("[%zu] R=%u, G=%u, B=%u\n", i, (unsigned)colors[i].r,
               (unsigned)colors[i].g, (unsigned)colors[i].b);
    }
    printf("\n");
    qsort(colors, num_colors, sizeof(colors[0]), compar_antiredness);
    printf("Colors sorted from most red to least red:\n");
    for (i = 0; i < num_colors; i++) {
        printf("[%zu] R=%u, G=%u, B=%u\n", i, (unsigned)colors[i].r,
               (unsigned)colors[i].g, (unsigned)colors[i].b);
    }
    return 0;
}

Output from the above:

Unsorted colors:
[0] R=125, G=0, B=0
[1] R=150, G=255, B=0
[2] R=100, G=20, B=21
[3] R=255, G=21, B=22
[4] R=0, G=0, B=0
[5] R=255, G=255, B=255
[6] R=128, G=128, B=128
[7] R=255, G=0, B=0

Colors sorted from most red to least red:
[0] R=255, G=0, B=0
[1] R=255, G=21, B=22
[2] R=125, G=0, B=0
[3] R=100, G=20, B=21
[4] R=128, G=128, B=128
[5] R=0, G=0, B=0
[6] R=150, G=255, B=0
[7] R=255, G=255, B=255

EDIT: Of course, it is just as easy to sort from most green to least green, or from most blue to least blue using the following functions:

/* distance squared from pure green, scaled by some silly amount. */
long antigreenness_squared(const struct rgb *c)
{
    const struct rgb pure_green = { .r = 0, .g = 255, .b = 0 };

    return color_dist_squared(&pure_green, c);
}

/*
 * qsort() comparison function.
 * a and b point to struct rgb values.
 * Returns 1 if *a is more anti-green (less green) than *b.
 * Returns 0 if *a and *b are equally (anti-)green.
 * Returns -1 if *a is less anti-green (more green) than *b.
 */
int compar_antigreenness(const void *a, const void *b)
{
    const struct rgb *ca = (const struct rgb *)a;
    const struct rgb *cb = (const struct rgb *)b;
    long aga = antigreenness_squared(ca);
    long agb = antigreenness_squared(cb);
    long diff = aga - agb;

    return (diff > 0) - (diff < 0);
}

/* distance squared from pure blue, scaled by some silly amount. */
long antiblueness_squared(const struct rgb *c)
{
    const struct rgb pure_blue = { .r = 0, .g = 0, .b = 255 };

    return color_dist_squared(&pure_blue, c);
}

/*
 * qsort() comparison function.
 * a and b point to struct rgb values.
 * Returns 1 if *a is more anti-blue (less blue) than *b.
 * Returns 0 if *a and *b are equally (anti-)blue.
 * Returns -1 if *a is less anti-blue (more blue) than *b.
 */
int compar_antiblueness(const void *a, const void *b)
{
    const struct rgb *ca = (const struct rgb *)a;
    const struct rgb *cb = (const struct rgb *)b;
    long aba = antiblueness_squared(ca);
    long abb = antiblueness_squared(cb);
    long diff = aba - abb;

    return (diff > 0) - (diff < 0);
}
Ian Abbott
  • 15,083
  • 19
  • 33