Serenity Operating System
at master 255 lines 7.7 kB view raw
1/* 2 * Copyright (c) 2020, Till Mayer <till.mayer@web.de> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org> 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include "CardPainter.h" 10#include <LibConfig/Client.h> 11#include <LibGfx/Font/Font.h> 12#include <LibGfx/Font/FontDatabase.h> 13 14namespace Cards { 15 16CardPainter& CardPainter::the() 17{ 18 static CardPainter s_card_painter; 19 return s_card_painter; 20} 21 22CardPainter::CardPainter() 23{ 24 m_background_image_path = Config::read_string("Games"sv, "Cards"sv, "CardBackImage"sv, "/res/icons/cards/buggie-deck.png"sv); 25} 26 27static constexpr Gfx::CharacterBitmap s_diamond { 28 " # " 29 " ### " 30 " ##### " 31 " ####### " 32 "#########" 33 " ####### " 34 " ##### " 35 " ### " 36 " # "sv, 37 9, 9 38}; 39 40static constexpr Gfx::CharacterBitmap s_heart { 41 " # # " 42 " ### ### " 43 "#########" 44 "#########" 45 "#########" 46 " ####### " 47 " ##### " 48 " ### " 49 " # "sv, 50 9, 9 51}; 52 53static constexpr Gfx::CharacterBitmap s_spade { 54 " # " 55 " ### " 56 " ##### " 57 " ####### " 58 "#########" 59 "#########" 60 " ## # ## " 61 " ### " 62 " ### "sv, 63 9, 9 64}; 65 66static constexpr Gfx::CharacterBitmap s_club { 67 " ### " 68 " ##### " 69 " ##### " 70 " ## ### ## " 71 "###########" 72 "###########" 73 "#### # ####" 74 " ## ### ## " 75 " ### "sv, 76 11, 9 77}; 78 79NonnullRefPtr<Gfx::Bitmap> CardPainter::card_front(Suit suit, Rank rank) 80{ 81 auto suit_id = to_underlying(suit); 82 auto rank_id = to_underlying(rank); 83 84 auto& existing_bitmap = m_cards[suit_id][rank_id]; 85 if (!existing_bitmap.is_null()) 86 return *existing_bitmap; 87 88 m_cards[suit_id][rank_id] = create_card_bitmap(); 89 paint_card_front(*m_cards[suit_id][rank_id], suit, rank); 90 91 return *m_cards[suit_id][rank_id]; 92} 93 94NonnullRefPtr<Gfx::Bitmap> CardPainter::card_back() 95{ 96 if (!m_card_back.is_null()) 97 return *m_card_back; 98 99 m_card_back = create_card_bitmap(); 100 paint_card_back(*m_card_back); 101 102 return *m_card_back; 103} 104 105NonnullRefPtr<Gfx::Bitmap> CardPainter::card_front_highlighted(Suit suit, Rank rank) 106{ 107 auto suit_id = to_underlying(suit); 108 auto rank_id = to_underlying(rank); 109 110 auto& existing_bitmap = m_cards_highlighted[suit_id][rank_id]; 111 if (!existing_bitmap.is_null()) 112 return *existing_bitmap; 113 114 m_cards_highlighted[suit_id][rank_id] = create_card_bitmap(); 115 paint_highlighted_card(*m_cards_highlighted[suit_id][rank_id], card_front(suit, rank)); 116 117 return *m_cards_highlighted[suit_id][rank_id]; 118} 119 120NonnullRefPtr<Gfx::Bitmap> CardPainter::card_front_inverted(Suit suit, Rank rank) 121{ 122 auto suit_id = to_underlying(suit); 123 auto rank_id = to_underlying(rank); 124 125 auto& existing_bitmap = m_cards_inverted[suit_id][rank_id]; 126 if (!existing_bitmap.is_null()) 127 return *existing_bitmap; 128 129 m_cards_inverted[suit_id][rank_id] = create_card_bitmap(); 130 paint_inverted_card(*m_cards_inverted[suit_id][rank_id], card_front(suit, rank)); 131 132 return *m_cards_inverted[suit_id][rank_id]; 133} 134 135NonnullRefPtr<Gfx::Bitmap> CardPainter::card_back_inverted() 136{ 137 if (!m_card_back_inverted.is_null()) 138 return *m_card_back_inverted; 139 140 m_card_back_inverted = create_card_bitmap(); 141 paint_inverted_card(card_back(), *m_card_back_inverted); 142 143 return *m_card_back_inverted; 144} 145 146void CardPainter::set_background_image_path(DeprecatedString path) 147{ 148 if (m_background_image_path == path) 149 return; 150 151 m_background_image_path = path; 152 if (!m_card_back.is_null()) 153 paint_card_back(*m_card_back); 154 if (!m_card_back_inverted.is_null()) 155 paint_inverted_card(*m_card_back_inverted, *m_card_back); 156} 157 158void CardPainter::set_background_color(Color background_color) 159{ 160 m_background_color = background_color; 161 162 // Clear any cached card bitmaps that depend on the background color. 163 for (auto& suit_array : m_cards_highlighted) { 164 for (auto& rank_array : suit_array) 165 rank_array = nullptr; 166 } 167} 168 169NonnullRefPtr<Gfx::Bitmap> CardPainter::create_card_bitmap() 170{ 171 return Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { Card::width, Card::height }).release_value_but_fixme_should_propagate_errors(); 172} 173 174void CardPainter::paint_card_front(Gfx::Bitmap& bitmap, Cards::Suit suit, Cards::Rank rank) 175{ 176 auto const suit_color = (suit == Suit::Diamonds || suit == Suit::Hearts) ? Color::Red : Color::Black; 177 178 auto const& suit_symbol = [&]() -> Gfx::CharacterBitmap const& { 179 switch (suit) { 180 case Suit::Diamonds: 181 return s_diamond; 182 case Suit::Clubs: 183 return s_club; 184 case Suit::Spades: 185 return s_spade; 186 case Suit::Hearts: 187 return s_heart; 188 default: 189 VERIFY_NOT_REACHED(); 190 } 191 }(); 192 193 Gfx::Painter painter { bitmap }; 194 auto paint_rect = bitmap.rect(); 195 auto& font = Gfx::FontDatabase::default_font().bold_variant(); 196 197 painter.fill_rect_with_rounded_corners(paint_rect, Color::Black, Card::card_radius); 198 paint_rect.shrink(2, 2); 199 painter.fill_rect_with_rounded_corners(paint_rect, Color::White, Card::card_radius - 1); 200 201 paint_rect.set_height(paint_rect.height() / 2); 202 paint_rect.shrink(10, 6); 203 204 auto text_rect = Gfx::IntRect { 4, 6, static_cast<int>(ceilf(font.width("10"sv))), font.pixel_size_rounded_up() }; 205 painter.draw_text(text_rect, card_rank_label(rank), font, Gfx::TextAlignment::Center, suit_color); 206 207 painter.draw_bitmap( 208 { text_rect.x() + (text_rect.width() - suit_symbol.size().width()) / 2, text_rect.bottom() + 5 }, 209 suit_symbol, suit_color); 210 211 for (int y = Card::height / 2; y < Card::height; ++y) { 212 for (int x = 0; x < Card::width; ++x) { 213 bitmap.set_pixel(x, y, bitmap.get_pixel(Card::width - x - 1, Card::height - y - 1)); 214 } 215 } 216} 217 218void CardPainter::paint_card_back(Gfx::Bitmap& bitmap) 219{ 220 Gfx::Painter painter { bitmap }; 221 auto paint_rect = bitmap.rect(); 222 painter.clear_rect(paint_rect, Gfx::Color::Transparent); 223 224 painter.fill_rect_with_rounded_corners(paint_rect, Color::Black, Card::card_radius); 225 auto inner_paint_rect = paint_rect.shrunken(2, 2); 226 painter.fill_rect_with_rounded_corners(inner_paint_rect, Color::White, Card::card_radius - 1); 227 228 auto image = Gfx::Bitmap::load_from_file(m_background_image_path).release_value_but_fixme_should_propagate_errors(); 229 painter.blit({ (bitmap.width() - image->width()) / 2, (bitmap.height() - image->height()) / 2 }, image, image->rect()); 230} 231 232void CardPainter::paint_inverted_card(Gfx::Bitmap& bitmap, Gfx::Bitmap const& source_to_invert) 233{ 234 Gfx::Painter painter { bitmap }; 235 painter.clear_rect(bitmap.rect(), Gfx::Color::Transparent); 236 painter.blit_filtered(Gfx::IntPoint {}, source_to_invert, source_to_invert.rect(), [&](Color color) { 237 return color.inverted(); 238 }); 239} 240 241void CardPainter::paint_highlighted_card(Gfx::Bitmap& bitmap, Gfx::Bitmap const& source_to_highlight) 242{ 243 Gfx::Painter painter { bitmap }; 244 auto paint_rect = source_to_highlight.rect(); 245 auto background_complement = m_background_color.xored(Color::White); 246 247 painter.fill_rect_with_rounded_corners(paint_rect, Color::Black, Card::card_radius); 248 paint_rect.shrink(2, 2); 249 painter.fill_rect_with_rounded_corners(paint_rect, background_complement, Card::card_radius - 1); 250 paint_rect.shrink(4, 4); 251 painter.fill_rect_with_rounded_corners(paint_rect, Color::White, Card::card_radius - 1); 252 painter.blit({ 4, 4 }, source_to_highlight, source_to_highlight.rect().shrunken(8, 8)); 253} 254 255}