[SOLVED] ALLEGRO5 change pixel color of text based on background

Issue

First attempt:

void Tower::_DrawHealthBarText(std::string text, int x, int y, ALLEGRO_FONT* font)
{
    al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP);
    ALLEGRO_BITMAP* bmp = al_create_bitmap(196, 17);
    int bmpIndex[196][17] = { };

    al_set_target_bitmap(bmp);
    al_draw_rectangle(5, 20, 200, 20 + 15, al_map_rgb(255, 255, 255), 1.0f);
    al_draw_filled_rectangle(5, 20, 5 - 195.0f + ((195.0f / _player->playerMaxHealth) * _player->playerHealth) + 195.0f, 20 + 15, al_map_rgb(255, 255, 255));
    al_draw_filled_rectangle(4, 19, 201, 20 + 15 + 1, al_map_rgb(0, 0, 0));

    for (int x = 0; x < 196; x++)
    {
        for (int y = 0; y < 17; y++)
        {
            unsigned char r, g, b;
            al_unmap_rgb(al_get_pixel(bmp, x, y), &r, &g, &b);
            if ((r + g + b) / 3 == 255)
            {
                bmpIndex[x][y] = 0;
            }
            else if ((r + g + b) / 3 == 0)
            {
                bmpIndex[x][y] = 1;
            }
        }
    }
    // Clear to transparent background
    al_clear_to_color(rgba(0, 0, 0, 0));

    al_draw_text(getFont(12), al_map_rgb(255, 255, 255), 0, 0, 0, text.c_str());

    al_set_new_bitmap_flags(ALLEGRO_VIDEO_BITMAP);
    al_set_target_backbuffer(g_Window);
    al_draw_bitmap(bmp, x, y, 0);

    al_destroy_bitmap(bmp);
}

Second attempt:

int bmpIndex[196][17] = { };
void Tower::_DrawHealthBarText(std::string text, int _x, int _y, ALLEGRO_FONT* font)
{
    al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP);
    al_hold_bitmap_drawing(true);
    for (int x = 0; x < 196; x++)
    {
        for (int y = 0; y < 17; y++)
        {
            unsigned char r, g, b;
            al_unmap_rgb(al_get_pixel(al_get_backbuffer(g_Window), _x + x, _y + y), &r, &g, &b);
            if ((r + g + b) / 3 == 255)
            {
                bmpIndex[x][y] = 0;
            }
            else if ((r + g + b) / 3 == 0)
            {
                bmpIndex[x][y] = 1;
            }
        }
    }
    al_hold_bitmap_drawing(false);
    al_set_new_bitmap_flags(ALLEGRO_VIDEO_BITMAP);
}

I’ve googled and tried my own code as you can see above… However using this multi-leveled loop is very slow. My thought was to use a shader but I haven’t the slightest clue how to use them yet.

I want the text to change color based off the background of where the text is located…

Basically it’s a health bar and I want to show the text ontop of the health bar and change colors based on the background… If the background is black, the corresponding pixels are changed to white. Subsequently if the background pixels are white, the pixels of the text change to black.

like this:
photoshop render concept

MY FIX:

        // Hacky bullshit
        _DrawText(std::to_string((int)_player->playerHealth), 5 + ((195.0f - al_get_text_width(getFont(12), std::to_string((int)_player->playerHealth).c_str())) / 2), py + 18, getFont(12), al_map_rgb(255, 0, 0), 0);
        al_draw_rectangle(px + 5, py + 20, px + 200, py + 20 + 15, al_map_rgb(255, 255, 255), 1.0f);
        ALLEGRO_BITMAP* bmp = al_create_bitmap(200, 20);
        {
            al_set_target_bitmap(bmp);
            al_draw_filled_rectangle(5, 0, 5 + ((195.0f / _player->playerMaxHealth) * _player->playerHealth), 15, al_map_rgb(255, 255, 255));
            _DrawText(std::to_string((int)_player->playerHealth), 5 + ((195.0f - al_get_text_width(getFont(12), std::to_string((int)_player->playerHealth).c_str())) / 2), -2, getFont(12), al_map_rgb(155, 0, 0), 0);
            al_set_target_backbuffer(g_Window);
            al_draw_bitmap_region(bmp, 0, 0, 5 + ((195.0f / _player->playerMaxHealth) * _player->playerHealth), 20, px, py + 20, 0);
        }
        al_destroy_bitmap(bmp);

Solution

The simplest way is to use the clipping rectangle, a common feature of most graphics APIs. When you set the clipping rectangle, nothing can be drawn outside of it.

  • First, set the clipping rectangle to cover the white part of the progress bar. Draw your text in black.
  • Then, set the clipping rectangle to cover the black part of the progress bar. Draw your text in white, in the same position as it was drawn in black.
int x = 0, y = 0;
int bar_width = 196, bar_height = 17;
int bar_position = static_cast<int>(
    bar_width * _player->playerHealth / _player->playerMaxHealth);

// Set the clipping rectangle so text only appears on the left
// side of the bar (which is white),
al_set_clipping_rectangle(x, y, bar_position, bar_height);

// Draw black text.
al_draw_text(getFont(12), al_map_rgb(0, 0, 0), x, y, 0, text.c_str());

// Set the clipping rectangle so text only appears on the right
// side of the bar (which is black).
al_set_clipping_rectangle(x + bar_position, y, bar_width - bar_position, bar_height);

// Draw white text in the same position as the black text.
al_draw_text(getFont(12), al_map_rgb(255, 255, 255), x, y, 0, text.c_str());

// Reset clipping rectangle so you can draw graphics as usual again.
al_reset_clipping_rectangle();

Answered By – Jeff Booth

Answer Checked By – Willingham (BugsFixing Volunteer)

Leave a Reply

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