Serenity Operating System
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}