1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-28 12:05:08 +00:00
serenity/Userland/Libraries/LibCards/CardPainter.cpp
Cubic Love 02913e2184 LibCards: Adjust card design
Alter the card dimensions to be 80x110px with a corner radius of 7px.
This is inspired by the dimensions of physical playing cards. It gives
12px of padding between the illustration and the card's border.

Move the card letter and symbol closer to the edge to make space.

Adjust the Club symbol to have the same dimensions as the other symbols.
2023-10-04 13:46:49 +01:00

254 lines
7.5 KiB
C++

/*
* Copyright (c) 2020, Till Mayer <till.mayer@web.de>
* Copyright (c) 2022, the SerenityOS developers.
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "CardPainter.h"
#include <LibConfig/Client.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/FontDatabase.h>
namespace Cards {
CardPainter& CardPainter::the()
{
static CardPainter s_card_painter;
return s_card_painter;
}
CardPainter::CardPainter()
{
m_background_image_path = MUST(String::from_deprecated_string(Config::read_string("Games"sv, "Cards"sv, "CardBackImage"sv, "/res/graphics/cards/backs/buggie-deck.png"sv)));
}
static constexpr Gfx::CharacterBitmap s_diamond {
" # "
" ### "
" ##### "
" ####### "
"#########"
" ####### "
" ##### "
" ### "
" # "sv,
9, 9
};
static constexpr Gfx::CharacterBitmap s_heart {
" # # "
" ### ### "
"#########"
"#########"
"#########"
" ####### "
" ##### "
" ### "
" # "sv,
9, 9
};
static constexpr Gfx::CharacterBitmap s_spade {
" # "
" ### "
" ##### "
" ####### "
"#########"
"#########"
" ## # ## "
" ### "
" ### "sv,
9, 9
};
static constexpr Gfx::CharacterBitmap s_club {
" ### "
" ##### "
" ##### "
"## ### ##"
"#########"
"#########"
" ## # ## "
" ### "
" ### "sv,
9, 9
};
NonnullRefPtr<Gfx::Bitmap> CardPainter::card_front(Suit suit, Rank rank)
{
auto suit_id = to_underlying(suit);
auto rank_id = to_underlying(rank);
auto& existing_bitmap = m_cards[suit_id][rank_id];
if (!existing_bitmap.is_null())
return *existing_bitmap;
m_cards[suit_id][rank_id] = create_card_bitmap();
paint_card_front(*m_cards[suit_id][rank_id], suit, rank);
return *m_cards[suit_id][rank_id];
}
NonnullRefPtr<Gfx::Bitmap> CardPainter::card_back()
{
if (!m_card_back.is_null())
return *m_card_back;
m_card_back = create_card_bitmap();
paint_card_back(*m_card_back);
return *m_card_back;
}
NonnullRefPtr<Gfx::Bitmap> CardPainter::card_front_highlighted(Suit suit, Rank rank)
{
auto suit_id = to_underlying(suit);
auto rank_id = to_underlying(rank);
auto& existing_bitmap = m_cards_highlighted[suit_id][rank_id];
if (!existing_bitmap.is_null())
return *existing_bitmap;
m_cards_highlighted[suit_id][rank_id] = create_card_bitmap();
paint_highlighted_card(*m_cards_highlighted[suit_id][rank_id], card_front(suit, rank));
return *m_cards_highlighted[suit_id][rank_id];
}
NonnullRefPtr<Gfx::Bitmap> CardPainter::card_front_inverted(Suit suit, Rank rank)
{
auto suit_id = to_underlying(suit);
auto rank_id = to_underlying(rank);
auto& existing_bitmap = m_cards_inverted[suit_id][rank_id];
if (!existing_bitmap.is_null())
return *existing_bitmap;
m_cards_inverted[suit_id][rank_id] = create_card_bitmap();
paint_inverted_card(*m_cards_inverted[suit_id][rank_id], card_front(suit, rank));
return *m_cards_inverted[suit_id][rank_id];
}
NonnullRefPtr<Gfx::Bitmap> CardPainter::card_back_inverted()
{
if (!m_card_back_inverted.is_null())
return *m_card_back_inverted;
m_card_back_inverted = create_card_bitmap();
paint_inverted_card(card_back(), *m_card_back_inverted);
return *m_card_back_inverted;
}
void CardPainter::set_background_image_path(StringView path)
{
if (m_background_image_path == path)
return;
m_background_image_path = MUST(String::from_utf8(path));
if (!m_card_back.is_null())
paint_card_back(*m_card_back);
if (!m_card_back_inverted.is_null())
paint_inverted_card(*m_card_back_inverted, *m_card_back);
}
void CardPainter::set_background_color(Color background_color)
{
m_background_color = background_color;
// Clear any cached card bitmaps that depend on the background color.
for (auto& suit_array : m_cards_highlighted) {
for (auto& rank_array : suit_array)
rank_array = nullptr;
}
}
NonnullRefPtr<Gfx::Bitmap> CardPainter::create_card_bitmap()
{
return Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { Card::width, Card::height }).release_value_but_fixme_should_propagate_errors();
}
void CardPainter::paint_card_front(Gfx::Bitmap& bitmap, Cards::Suit suit, Cards::Rank rank)
{
auto const suit_color = (suit == Suit::Diamonds || suit == Suit::Hearts) ? Color::Red : Color::Black;
auto const& suit_symbol = [&]() -> Gfx::CharacterBitmap const& {
switch (suit) {
case Suit::Diamonds:
return s_diamond;
case Suit::Clubs:
return s_club;
case Suit::Spades:
return s_spade;
case Suit::Hearts:
return s_heart;
default:
VERIFY_NOT_REACHED();
}
}();
Gfx::Painter painter { bitmap };
auto paint_rect = bitmap.rect();
auto& font = Gfx::FontDatabase::default_font().bold_variant();
painter.fill_rect_with_rounded_corners(paint_rect, Color::Black, Card::card_radius);
paint_rect.shrink(2, 2);
painter.fill_rect_with_rounded_corners(paint_rect, Color::White, Card::card_radius - 1);
paint_rect.set_height(paint_rect.height() / 2);
paint_rect.shrink(10, 6);
auto text_rect = Gfx::IntRect { 1, 6, font.width_rounded_up("10"sv), font.pixel_size_rounded_up() };
painter.draw_text(text_rect, card_rank_label(rank), font, Gfx::TextAlignment::Center, suit_color);
painter.draw_bitmap(
{ text_rect.x() + (text_rect.width() - suit_symbol.size().width()) / 2, text_rect.bottom() + 4 },
suit_symbol, suit_color);
for (int y = Card::height / 2; y < Card::height; ++y) {
for (int x = 0; x < Card::width; ++x)
bitmap.set_pixel(x, y, bitmap.get_pixel(Card::width - x - 1, Card::height - y - 1));
}
}
void CardPainter::paint_card_back(Gfx::Bitmap& bitmap)
{
Gfx::Painter painter { bitmap };
auto paint_rect = bitmap.rect();
painter.clear_rect(paint_rect, Gfx::Color::Transparent);
painter.fill_rect_with_rounded_corners(paint_rect, Color::Black, Card::card_radius);
auto inner_paint_rect = paint_rect.shrunken(2, 2);
painter.fill_rect_with_rounded_corners(inner_paint_rect, Color::White, Card::card_radius - 1);
auto image = Gfx::Bitmap::load_from_file(m_background_image_path).release_value_but_fixme_should_propagate_errors();
painter.blit({ (bitmap.width() - image->width()) / 2, (bitmap.height() - image->height()) / 2 }, image, image->rect());
}
void CardPainter::paint_inverted_card(Gfx::Bitmap& bitmap, Gfx::Bitmap const& source_to_invert)
{
Gfx::Painter painter { bitmap };
painter.clear_rect(bitmap.rect(), Gfx::Color::Transparent);
painter.blit_filtered(Gfx::IntPoint {}, source_to_invert, source_to_invert.rect(), [&](Color color) {
return color.inverted();
});
}
void CardPainter::paint_highlighted_card(Gfx::Bitmap& bitmap, Gfx::Bitmap const& source_to_highlight)
{
Gfx::Painter painter { bitmap };
auto paint_rect = source_to_highlight.rect();
auto background_complement = m_background_color.xored(Color::White);
painter.fill_rect_with_rounded_corners(paint_rect, Color::Black, Card::card_radius);
paint_rect.shrink(2, 2);
painter.fill_rect_with_rounded_corners(paint_rect, background_complement, Card::card_radius - 1);
paint_rect.shrink(4, 4);
painter.fill_rect_with_rounded_corners(paint_rect, Color::White, Card::card_radius - 1);
painter.blit({ 4, 4 }, source_to_highlight, source_to_highlight.rect().shrunken(8, 8));
}
}