diff --git a/Userland/Libraries/LibCards/CMakeLists.txt b/Userland/Libraries/LibCards/CMakeLists.txt index 0b1ed02a2d..06e7bae7a8 100644 --- a/Userland/Libraries/LibCards/CMakeLists.txt +++ b/Userland/Libraries/LibCards/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES Card.cpp CardGame.cpp + CardPainter.cpp CardStack.cpp ) diff --git a/Userland/Libraries/LibCards/Card.cpp b/Userland/Libraries/LibCards/Card.cpp index 3a0dd9e4cb..a42d0279e6 100644 --- a/Userland/Libraries/LibCards/Card.cpp +++ b/Userland/Libraries/LibCards/Card.cpp @@ -6,145 +6,28 @@ */ #include "Card.h" -#include -#include -#include +#include namespace Cards { -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, - 11, 9 -}; - -static RefPtr s_background; -static RefPtr s_background_inverted; - Card::Card(Suit suit, Rank rank) : m_rect(Gfx::IntRect({}, { width, height })) - , m_front(Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, { width, height }).release_value_but_fixme_should_propagate_errors()) , m_suit(suit) , m_rank(rank) { VERIFY(to_underlying(rank) < card_count); - Gfx::IntRect paint_rect({ 0, 0 }, { width, height }); - - if (s_background.is_null()) { - s_background = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, { width, height }).release_value_but_fixme_should_propagate_errors(); - Gfx::Painter bg_painter(*s_background); - - auto image = Gfx::Bitmap::try_load_from_file("/res/icons/cards/buggie-deck.png"sv).release_value_but_fixme_should_propagate_errors(); - - float aspect_ratio = image->width() / static_cast(image->height()); - auto target_size = Gfx::IntSize(static_cast(aspect_ratio * (height - 5)), height - 5); - - bg_painter.fill_rect_with_rounded_corners(paint_rect, Color::Black, card_radius); - auto inner_paint_rect = paint_rect.shrunken(2, 2); - bg_painter.fill_rect_with_rounded_corners(inner_paint_rect, Color::White, card_radius - 1); - - bg_painter.draw_scaled_bitmap( - { { (width - target_size.width()) / 2, (height - target_size.height()) / 2 }, target_size }, - *image, image->rect()); - - s_background_inverted = invert_bitmap(*s_background); - } - - Gfx::Painter painter(m_front); - auto& font = Gfx::FontDatabase::default_font().bold_variant(); - - painter.fill_rect_with_rounded_corners(paint_rect, Color::Black, card_radius); - paint_rect.shrink(2, 2); - painter.fill_rect_with_rounded_corners(paint_rect, Color::White, card_radius - 1); - - paint_rect.set_height(paint_rect.height() / 2); - paint_rect.shrink(10, 6); - - auto text_rect = Gfx::IntRect { 4, 6, font.width("10"sv), font.glyph_height() }; - painter.draw_text(text_rect, card_rank_label(m_rank), font, Gfx::TextAlignment::Center, color()); - - auto const& symbol = [&]() -> Gfx::CharacterBitmap const& { - switch (m_suit) { - case Suit::Diamonds: - return s_diamond; - case Suit::Clubs: - return s_club; - break; - case Suit::Spades: - return s_spade; - case Suit::Hearts: - return s_heart; - default: - VERIFY_NOT_REACHED(); - } - }(); - - painter.draw_bitmap( - { text_rect.x() + (text_rect.width() - symbol.size().width()) / 2, text_rect.bottom() + 5 }, - symbol, color()); - - for (int y = height / 2; y < height; ++y) { - for (int x = 0; x < width; ++x) { - m_front->set_pixel(x, y, m_front->get_pixel(width - x - 1, height - y - 1)); - } - } - - m_front_inverted = invert_bitmap(*m_front); } void Card::draw(GUI::Painter& painter) const { - VERIFY(!s_background.is_null()); - if (m_inverted) - painter.blit(position(), m_upside_down ? *s_background_inverted : *m_front_inverted, m_front_inverted->rect()); - else - painter.blit(position(), m_upside_down ? *s_background : *m_front, m_front->rect()); + auto& card_painter = CardPainter::the(); + auto bitmap = [&]() { + if (m_inverted) + return m_upside_down ? card_painter.card_back_inverted() : card_painter.card_front_inverted(m_suit, m_rank); + + return m_upside_down ? card_painter.card_back() : card_painter.card_front(m_suit, m_rank); + }(); + painter.blit(position(), bitmap, bitmap->rect()); } void Card::clear(GUI::Painter& painter, Color const& background_color) const @@ -167,15 +50,4 @@ void Card::clear_and_draw(GUI::Painter& painter, Color const& background_color) save_old_position(); } -NonnullRefPtr Card::invert_bitmap(Gfx::Bitmap& bitmap) -{ - auto inverted_bitmap = bitmap.clone().release_value_but_fixme_should_propagate_errors(); - for (int y = 0; y < inverted_bitmap->height(); y++) { - for (int x = 0; x < inverted_bitmap->width(); x++) { - inverted_bitmap->set_pixel(x, y, inverted_bitmap->get_pixel(x, y).inverted()); - } - } - return *inverted_bitmap; -} - } diff --git a/Userland/Libraries/LibCards/Card.h b/Userland/Libraries/LibCards/Card.h index 6a4c4eaaf3..b3644013a4 100644 --- a/Userland/Libraries/LibCards/Card.h +++ b/Userland/Libraries/LibCards/Card.h @@ -113,11 +113,7 @@ public: private: Card(Suit, Rank); - static NonnullRefPtr invert_bitmap(Gfx::Bitmap&); - Gfx::IntRect m_rect; - NonnullRefPtr m_front; - RefPtr m_front_inverted; Gfx::IntPoint m_old_position; Suit m_suit; Rank m_rank; diff --git a/Userland/Libraries/LibCards/CardPainter.cpp b/Userland/Libraries/LibCards/CardPainter.cpp new file mode 100644 index 0000000000..c0f63e227e --- /dev/null +++ b/Userland/Libraries/LibCards/CardPainter.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2020, Till Mayer + * Copyright (c) 2022, the SerenityOS developers. + * Copyright (c) 2022, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "CardPainter.h" +#include +#include + +namespace Cards { + +CardPainter& CardPainter::the() +{ + static CardPainter s_card_painter; + return s_card_painter; +} + +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, + 11, 9 +}; + +NonnullRefPtr 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 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 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 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; +} + +NonnullRefPtr CardPainter::create_card_bitmap() +{ + return Gfx::Bitmap::try_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 { 4, 6, font.width("10"sv), font.glyph_height() }; + 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() + 5 }, + 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); + + auto image = Gfx::Bitmap::try_load_from_file("/res/icons/cards/buggie-deck.png"sv).release_value_but_fixme_should_propagate_errors(); + + float aspect_ratio = image->width() / static_cast(image->height()); + Gfx::IntSize target_size { static_cast(aspect_ratio * (Card::height - 5)), Card::height - 5 }; + + 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); + + painter.draw_scaled_bitmap( + { { (Card::width - target_size.width()) / 2, (Card::height - target_size.height()) / 2 }, target_size }, + *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({ 0, 0 }, source_to_invert, source_to_invert.rect(), [&](Color color) { + return color.inverted(); + }); +} + +} diff --git a/Userland/Libraries/LibCards/CardPainter.h b/Userland/Libraries/LibCards/CardPainter.h new file mode 100644 index 0000000000..247101e295 --- /dev/null +++ b/Userland/Libraries/LibCards/CardPainter.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Cards { + +class CardPainter { +public: + static CardPainter& the(); + + NonnullRefPtr card_front(Suit, Rank); + NonnullRefPtr card_back(); + NonnullRefPtr card_front_inverted(Suit, Rank); + NonnullRefPtr card_back_inverted(); + +private: + NonnullRefPtr create_card_bitmap(); + void paint_card_front(Gfx::Bitmap&, Suit, Rank); + void paint_card_back(Gfx::Bitmap&); + void paint_inverted_card(Gfx::Bitmap& bitmap, Gfx::Bitmap const& source_to_invert); + + Array, to_underlying(Rank::__Count)>, to_underlying(Suit::__Count)> m_cards; + Array, to_underlying(Rank::__Count)>, to_underlying(Suit::__Count)> m_cards_inverted; + RefPtr m_card_back; + RefPtr m_card_back_inverted; +}; + +}