CardPainter.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. /*
  2. * Copyright (c) 2020, Till Mayer <till.mayer@web.de>
  3. * Copyright (c) 2022, the SerenityOS developers.
  4. * Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
  5. * Copyright (c) 2023, David Ganz <david.g.ganz@gmail.com>
  6. *
  7. * SPDX-License-Identifier: BSD-2-Clause
  8. */
  9. #include "CardPainter.h"
  10. #include <AK/Array.h>
  11. #include <AK/GenericShorthands.h>
  12. #include <LibConfig/Client.h>
  13. #include <LibGfx/Font/Font.h>
  14. #include <LibGfx/Font/FontDatabase.h>
  15. namespace Cards {
  16. CardPainter& CardPainter::the()
  17. {
  18. static CardPainter s_card_painter;
  19. return s_card_painter;
  20. }
  21. CardPainter::CardPainter()
  22. {
  23. m_back_image_path = MUST(String::from_deprecated_string(Config::read_string("Games"sv, "Cards"sv, "CardBackImage"sv, "/res/graphics/cards/backs/Red.png"sv)));
  24. set_front_images_set_name(MUST(String::from_deprecated_string(Config::read_string("Games"sv, "Cards"sv, "CardFrontImages"sv, "Classic"sv))));
  25. }
  26. static constexpr Gfx::CharacterBitmap s_diamond {
  27. " # "
  28. " ### "
  29. " ##### "
  30. " ####### "
  31. "#########"
  32. " ####### "
  33. " ##### "
  34. " ### "
  35. " # "sv,
  36. 9, 9
  37. };
  38. static constexpr Gfx::CharacterBitmap s_heart {
  39. " # # "
  40. " ### ### "
  41. "#########"
  42. "#########"
  43. "#########"
  44. " ####### "
  45. " ##### "
  46. " ### "
  47. " # "sv,
  48. 9, 9
  49. };
  50. static constexpr Gfx::CharacterBitmap s_spade {
  51. " # "
  52. " ### "
  53. " ##### "
  54. " ####### "
  55. "#########"
  56. "#########"
  57. " ## # ## "
  58. " ### "
  59. " ### "sv,
  60. 9, 9
  61. };
  62. static constexpr Gfx::CharacterBitmap s_club {
  63. " ### "
  64. " ##### "
  65. " ##### "
  66. "## ### ##"
  67. "#########"
  68. "#########"
  69. " ## # ## "
  70. " ### "
  71. " ### "sv,
  72. 9, 9
  73. };
  74. static constexpr u8 s_disabled_alpha = 90;
  75. NonnullRefPtr<Gfx::Bitmap> CardPainter::card_front(Suit suit, Rank rank)
  76. {
  77. auto suit_id = to_underlying(suit);
  78. auto rank_id = to_underlying(rank);
  79. auto& existing_bitmap = m_cards[suit_id][rank_id];
  80. if (!existing_bitmap.is_null())
  81. return *existing_bitmap;
  82. m_cards[suit_id][rank_id] = create_card_bitmap();
  83. paint_card_front(*m_cards[suit_id][rank_id], suit, rank);
  84. return *m_cards[suit_id][rank_id];
  85. }
  86. NonnullRefPtr<Gfx::Bitmap> CardPainter::card_back()
  87. {
  88. if (!m_card_back.is_null())
  89. return *m_card_back;
  90. m_card_back = create_card_bitmap();
  91. paint_card_back(*m_card_back);
  92. return *m_card_back;
  93. }
  94. NonnullRefPtr<Gfx::Bitmap> CardPainter::card_front_highlighted(Suit suit, Rank rank)
  95. {
  96. auto suit_id = to_underlying(suit);
  97. auto rank_id = to_underlying(rank);
  98. auto& existing_bitmap = m_cards_highlighted[suit_id][rank_id];
  99. if (!existing_bitmap.is_null())
  100. return *existing_bitmap;
  101. m_cards_highlighted[suit_id][rank_id] = create_card_bitmap();
  102. paint_highlighted_card(*m_cards_highlighted[suit_id][rank_id], card_front(suit, rank));
  103. return *m_cards_highlighted[suit_id][rank_id];
  104. }
  105. NonnullRefPtr<Gfx::Bitmap> CardPainter::card_front_disabled(Suit suit, Rank rank)
  106. {
  107. auto suit_id = to_underlying(suit);
  108. auto rank_id = to_underlying(rank);
  109. auto& existing_bitmap = m_cards_disabled[suit_id][rank_id];
  110. if (!existing_bitmap.is_null())
  111. return *existing_bitmap;
  112. m_cards_disabled[suit_id][rank_id] = create_card_bitmap();
  113. paint_disabled_card(*m_cards_disabled[suit_id][rank_id], card_front(suit, rank));
  114. return *m_cards_disabled[suit_id][rank_id];
  115. }
  116. NonnullRefPtr<Gfx::Bitmap> CardPainter::card_front_inverted(Suit suit, Rank rank)
  117. {
  118. auto suit_id = to_underlying(suit);
  119. auto rank_id = to_underlying(rank);
  120. auto& existing_bitmap = m_cards_inverted[suit_id][rank_id];
  121. if (!existing_bitmap.is_null())
  122. return *existing_bitmap;
  123. m_cards_inverted[suit_id][rank_id] = create_card_bitmap();
  124. paint_inverted_card(*m_cards_inverted[suit_id][rank_id], card_front(suit, rank));
  125. return *m_cards_inverted[suit_id][rank_id];
  126. }
  127. NonnullRefPtr<Gfx::Bitmap> CardPainter::card_back_inverted()
  128. {
  129. if (!m_card_back_inverted.is_null())
  130. return *m_card_back_inverted;
  131. m_card_back_inverted = create_card_bitmap();
  132. paint_inverted_card(card_back(), *m_card_back_inverted);
  133. return *m_card_back_inverted;
  134. }
  135. NonnullRefPtr<Gfx::Bitmap> CardPainter::card_back_disabled()
  136. {
  137. if (!m_card_back_disabled.is_null())
  138. return *m_card_back_disabled;
  139. m_card_back_disabled = create_card_bitmap();
  140. paint_disabled_card(*m_card_back_disabled, card_back());
  141. return *m_card_back_disabled;
  142. }
  143. void CardPainter::set_back_image_path(StringView path)
  144. {
  145. if (m_back_image_path == path)
  146. return;
  147. m_back_image_path = MUST(String::from_utf8(path));
  148. if (!m_card_back.is_null())
  149. paint_card_back(*m_card_back);
  150. if (!m_card_back_inverted.is_null())
  151. paint_inverted_card(*m_card_back_inverted, *m_card_back);
  152. }
  153. void CardPainter::set_front_images_set_name(AK::StringView path)
  154. {
  155. if (m_front_images_set_name == path)
  156. return;
  157. m_front_images_set_name = MUST(String::from_utf8(path));
  158. if (m_front_images_set_name.is_empty()) {
  159. for (auto& pip_bitmap : m_suit_pips)
  160. pip_bitmap = nullptr;
  161. for (auto& pip_bitmap : m_suit_pips_flipped_vertically)
  162. pip_bitmap = nullptr;
  163. } else {
  164. auto diamond = Gfx::Bitmap::load_from_file(MUST(String::formatted("/res/graphics/cards/fronts/{}/diamond.png", m_front_images_set_name))).release_value_but_fixme_should_propagate_errors();
  165. m_suit_pips[to_underlying(Suit::Diamonds)] = diamond;
  166. m_suit_pips_flipped_vertically[to_underlying(Suit::Diamonds)] = diamond->flipped(Gfx::Orientation::Vertical).release_value_but_fixme_should_propagate_errors();
  167. auto club = Gfx::Bitmap::load_from_file(MUST(String::formatted("/res/graphics/cards/fronts/{}/club.png", m_front_images_set_name))).release_value_but_fixme_should_propagate_errors();
  168. m_suit_pips[to_underlying(Suit::Clubs)] = club;
  169. m_suit_pips_flipped_vertically[to_underlying(Suit::Clubs)] = club->flipped(Gfx::Orientation::Vertical).release_value_but_fixme_should_propagate_errors();
  170. auto heart = Gfx::Bitmap::load_from_file(MUST(String::formatted("/res/graphics/cards/fronts/{}/heart.png", m_front_images_set_name))).release_value_but_fixme_should_propagate_errors();
  171. m_suit_pips[to_underlying(Suit::Hearts)] = heart;
  172. m_suit_pips_flipped_vertically[to_underlying(Suit::Hearts)] = heart->flipped(Gfx::Orientation::Vertical).release_value_but_fixme_should_propagate_errors();
  173. auto spade = Gfx::Bitmap::load_from_file(MUST(String::formatted("/res/graphics/cards/fronts/{}/spade.png", m_front_images_set_name))).release_value_but_fixme_should_propagate_errors();
  174. m_suit_pips[to_underlying(Suit::Spades)] = spade;
  175. m_suit_pips_flipped_vertically[to_underlying(Suit::Spades)] = spade->flipped(Gfx::Orientation::Vertical).release_value_but_fixme_should_propagate_errors();
  176. }
  177. // Clear all bitmaps using front images
  178. for (auto& suit_array : m_cards) {
  179. for (auto& card_bitmap : suit_array)
  180. card_bitmap = nullptr;
  181. }
  182. for (auto& suit_array : m_cards_highlighted) {
  183. for (auto& card_bitmap : suit_array)
  184. card_bitmap = nullptr;
  185. }
  186. }
  187. void CardPainter::set_background_color(Color background_color)
  188. {
  189. m_background_color = background_color;
  190. // Clear any cached card bitmaps that depend on the background color.
  191. for (auto& suit_array : m_cards_highlighted) {
  192. for (auto& rank_array : suit_array)
  193. rank_array = nullptr;
  194. }
  195. }
  196. NonnullRefPtr<Gfx::Bitmap> CardPainter::create_card_bitmap()
  197. {
  198. return Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { Card::width, Card::height }).release_value_but_fixme_should_propagate_errors();
  199. }
  200. void CardPainter::paint_card_front_pips(Gfx::Bitmap& bitmap, Suit suit, Rank rank)
  201. {
  202. Gfx::Painter painter { bitmap };
  203. auto& pip_bitmap = m_suit_pips[to_underlying(suit)];
  204. auto& pip_bitmap_flipped_vertically = m_suit_pips_flipped_vertically[to_underlying(suit)];
  205. struct Pip {
  206. int x;
  207. int y;
  208. bool flip_vertically;
  209. };
  210. auto paint_pips = [&](Span<Pip> pips) {
  211. for (auto& pip : pips) {
  212. auto& bitmap = pip.flip_vertically ? pip_bitmap_flipped_vertically : pip_bitmap;
  213. painter.blit({ pip.x - bitmap->width() / 2, pip.y - bitmap->height() / 2 }, *bitmap, bitmap->rect());
  214. }
  215. };
  216. constexpr int column_left = Card::width * 1 / 3;
  217. constexpr int column_middle = Card::width * 1 / 2;
  218. constexpr int column_right = Card::width - column_left;
  219. constexpr int row_top = Card::height / 6;
  220. constexpr int row_middle = Card::height / 2;
  221. constexpr int row_bottom = Card::height - row_top - 1;
  222. constexpr int row_2_of_4 = row_top + (row_bottom - row_top) * 1 / 3;
  223. constexpr int row_3_of_4 = Card::height - row_2_of_4 - 1;
  224. constexpr int row_2_of_5 = row_top + (row_bottom - row_top) * 1 / 4;
  225. constexpr int row_4_of_5 = Card::height - row_2_of_5 - 1;
  226. constexpr int row_2_of_7 = row_top + (row_bottom - row_top) * 1 / 6;
  227. constexpr int row_6_of_7 = Card::height - row_2_of_7 - 1;
  228. switch (rank) {
  229. case Rank::Ace:
  230. paint_pips(Array<Pip, 1>({ Pip { column_middle, row_middle, false } }));
  231. break;
  232. case Rank::Two:
  233. paint_pips(Array<Pip, 2>({ { column_middle, row_top, false },
  234. { column_middle, row_bottom, true } }));
  235. break;
  236. case Rank::Three:
  237. paint_pips(Array<Pip, 3>({ { column_middle, row_top, false },
  238. { column_middle, row_middle, false },
  239. { column_middle, row_bottom, true } }));
  240. break;
  241. case Rank::Four:
  242. paint_pips(Array<Pip, 4>({ { column_left, row_top, false },
  243. { column_right, row_top, false },
  244. { column_left, row_bottom, true },
  245. { column_right, row_bottom, true } }));
  246. break;
  247. case Rank::Five:
  248. paint_pips(Array<Pip, 5>({ { column_left, row_top, false },
  249. { column_right, row_top, false },
  250. { column_middle, row_middle, false },
  251. { column_left, row_bottom, true },
  252. { column_right, row_bottom, true } }));
  253. break;
  254. case Rank::Six:
  255. paint_pips(Array<Pip, 6>({ { column_left, row_top, false },
  256. { column_right, row_top, false },
  257. { column_left, row_middle, false },
  258. { column_right, row_middle, false },
  259. { column_left, row_bottom, true },
  260. { column_right, row_bottom, true } }));
  261. break;
  262. case Rank::Seven:
  263. paint_pips(Array<Pip, 7>({ { column_left, row_top, false },
  264. { column_right, row_top, false },
  265. { column_middle, row_2_of_5, false },
  266. { column_left, row_middle, false },
  267. { column_right, row_middle, false },
  268. { column_left, row_bottom, true },
  269. { column_right, row_bottom, true } }));
  270. break;
  271. case Rank::Eight:
  272. paint_pips(Array<Pip, 8>({ { column_left, row_top, false },
  273. { column_right, row_top, false },
  274. { column_middle, row_2_of_5, false },
  275. { column_left, row_middle, false },
  276. { column_right, row_middle, false },
  277. { column_middle, row_4_of_5, true },
  278. { column_left, row_bottom, true },
  279. { column_right, row_bottom, true } }));
  280. break;
  281. case Rank::Nine:
  282. paint_pips(Array<Pip, 9>({ { column_left, row_top, false },
  283. { column_right, row_top, false },
  284. { column_left, row_2_of_4, false },
  285. { column_right, row_2_of_4, false },
  286. { column_middle, row_middle, false },
  287. { column_left, row_3_of_4, true },
  288. { column_right, row_3_of_4, true },
  289. { column_left, row_bottom, true },
  290. { column_right, row_bottom, true } }));
  291. break;
  292. case Rank::Ten:
  293. paint_pips(Array<Pip, 10>({ { column_left, row_top, false },
  294. { column_right, row_top, false },
  295. { column_middle, row_2_of_7, false },
  296. { column_left, row_2_of_4, false },
  297. { column_right, row_2_of_4, false },
  298. { column_left, row_3_of_4, true },
  299. { column_right, row_3_of_4, true },
  300. { column_middle, row_6_of_7, true },
  301. { column_left, row_bottom, true },
  302. { column_right, row_bottom, true } }));
  303. break;
  304. case Rank::Jack:
  305. case Rank::Queen:
  306. case Rank::King:
  307. case Rank::__Count:
  308. break;
  309. }
  310. }
  311. void CardPainter::paint_card_front(Gfx::Bitmap& bitmap, Cards::Suit suit, Cards::Rank rank)
  312. {
  313. auto const suit_color = (suit == Suit::Diamonds || suit == Suit::Hearts) ? Color::Red : Color::Black;
  314. auto const& suit_symbol = [&]() -> Gfx::CharacterBitmap const& {
  315. switch (suit) {
  316. case Suit::Diamonds:
  317. return s_diamond;
  318. case Suit::Clubs:
  319. return s_club;
  320. case Suit::Spades:
  321. return s_spade;
  322. case Suit::Hearts:
  323. return s_heart;
  324. default:
  325. VERIFY_NOT_REACHED();
  326. }
  327. }();
  328. Gfx::Painter painter { bitmap };
  329. auto paint_rect = bitmap.rect();
  330. auto& font = Gfx::FontDatabase::default_font().bold_variant();
  331. painter.fill_rect_with_rounded_corners(paint_rect, Color::Black, Card::card_radius);
  332. paint_rect.shrink(2, 2);
  333. painter.fill_rect_with_rounded_corners(paint_rect, Color::White, Card::card_radius - 1);
  334. paint_rect.set_height(paint_rect.height() / 2);
  335. paint_rect.shrink(10, 6);
  336. auto text_rect = Gfx::IntRect { 1, 6, font.width_rounded_up("10"sv), font.pixel_size_rounded_up() };
  337. painter.draw_text(text_rect, card_rank_label(rank), font, Gfx::TextAlignment::Center, suit_color);
  338. painter.draw_bitmap(
  339. { text_rect.x() + (text_rect.width() - suit_symbol.size().width()) / 2, text_rect.bottom() + 4 },
  340. suit_symbol, suit_color);
  341. for (int y = Card::height / 2; y < Card::height; ++y) {
  342. for (int x = 0; x < Card::width; ++x)
  343. bitmap.set_pixel(x, y, bitmap.get_pixel(Card::width - x - 1, Card::height - y - 1));
  344. }
  345. if (!m_front_images_set_name.is_empty()) {
  346. // Paint pips for number cards except the ace of spades
  347. if (!first_is_one_of(rank, Rank::Ace, Rank::Jack, Rank::Queen, Rank::King)
  348. || (rank == Rank::Ace && suit != Suit::Spades)) {
  349. paint_card_front_pips(bitmap, suit, rank);
  350. } else {
  351. // Paint pictures for royal cards and ace of spades
  352. StringView rank_name;
  353. switch (rank) {
  354. case Rank::Ace:
  355. rank_name = "ace"sv;
  356. break;
  357. case Rank::Jack:
  358. rank_name = "jack"sv;
  359. break;
  360. case Rank::Queen:
  361. rank_name = "queen"sv;
  362. break;
  363. case Rank::King:
  364. rank_name = "king"sv;
  365. break;
  366. default:
  367. break;
  368. }
  369. StringView suit_name;
  370. switch (suit) {
  371. case Suit::Diamonds:
  372. suit_name = "diamonds"sv;
  373. break;
  374. case Suit::Clubs:
  375. suit_name = "clubs"sv;
  376. break;
  377. case Suit::Hearts:
  378. suit_name = "hearts"sv;
  379. break;
  380. case Suit::Spades:
  381. suit_name = "spades"sv;
  382. break;
  383. case Suit::__Count:
  384. return;
  385. }
  386. auto front_image_path = MUST(String::formatted("/res/graphics/cards/fronts/{}/{}-{}.png", m_front_images_set_name, suit_name, rank_name));
  387. auto maybe_front_image = Gfx::Bitmap::load_from_file(front_image_path);
  388. if (maybe_front_image.is_error()) {
  389. dbgln("Failed to load `{}`: {}", front_image_path, maybe_front_image.error());
  390. return;
  391. }
  392. auto front_image = maybe_front_image.release_value();
  393. painter.blit({ (bitmap.width() - front_image->width()) / 2, (bitmap.height() - front_image->height()) / 2 }, front_image, front_image->rect());
  394. }
  395. }
  396. }
  397. void CardPainter::paint_card_back(Gfx::Bitmap& bitmap)
  398. {
  399. Gfx::Painter painter { bitmap };
  400. auto paint_rect = bitmap.rect();
  401. painter.clear_rect(paint_rect, Gfx::Color::Transparent);
  402. painter.fill_rect_with_rounded_corners(paint_rect, Color::Black, Card::card_radius);
  403. auto inner_paint_rect = paint_rect.shrunken(2, 2);
  404. painter.fill_rect_with_rounded_corners(inner_paint_rect, Color::White, Card::card_radius - 1);
  405. auto image = Gfx::Bitmap::load_from_file(m_back_image_path).release_value_but_fixme_should_propagate_errors();
  406. painter.blit({ (bitmap.width() - image->width()) / 2, (bitmap.height() - image->height()) / 2 }, image, image->rect());
  407. }
  408. void CardPainter::paint_inverted_card(Gfx::Bitmap& bitmap, Gfx::Bitmap const& source_to_invert)
  409. {
  410. Gfx::Painter painter { bitmap };
  411. painter.clear_rect(bitmap.rect(), Gfx::Color::Transparent);
  412. painter.blit_filtered(Gfx::IntPoint {}, source_to_invert, source_to_invert.rect(), [&](Color color) {
  413. return color.inverted();
  414. });
  415. }
  416. void CardPainter::paint_highlighted_card(Gfx::Bitmap& bitmap, Gfx::Bitmap const& source_to_highlight)
  417. {
  418. Gfx::Painter painter { bitmap };
  419. auto paint_rect = source_to_highlight.rect();
  420. auto background_complement = m_background_color.xored(Color::White);
  421. painter.fill_rect_with_rounded_corners(paint_rect, Color::Black, Card::card_radius);
  422. paint_rect.shrink(2, 2);
  423. painter.fill_rect_with_rounded_corners(paint_rect, background_complement, Card::card_radius - 1);
  424. paint_rect.shrink(4, 4);
  425. painter.fill_rect_with_rounded_corners(paint_rect, Color::White, Card::card_radius - 1);
  426. painter.blit({ 4, 4 }, source_to_highlight, source_to_highlight.rect().shrunken(8, 8));
  427. }
  428. void CardPainter::paint_disabled_card(Gfx::Bitmap& bitmap, Gfx::Bitmap const& source_to_disable)
  429. {
  430. Gfx::Painter painter { bitmap };
  431. auto disabled_color = Color(Color::Black);
  432. disabled_color.set_alpha(s_disabled_alpha);
  433. painter.blit_filtered(Gfx::IntPoint {}, source_to_disable, source_to_disable.rect(), [&](Color color) {
  434. return color.blend(disabled_color);
  435. });
  436. }
  437. }