Compare commits

..

11 Commits

Author SHA1 Message Date
cat
14d98194b3 Added proper credits for used libraries 2025-07-23 20:31:39 +03:00
cat
6ef969edb6 removed junk 2025-07-23 20:15:39 +03:00
cat
8dda053dcf webpdecoder->webp 2025-07-23 20:15:07 +03:00
cat
3c4aa768ec Updated cmake 2025-07-23 20:04:44 +03:00
cat
28253e423c Finished generate_report 2025-07-23 20:00:39 +03:00
cat
09abd6c116 Added component response for generate_report 2025-07-23 18:46:39 +03:00
cat
ba2ad34d6e Added components, and moved command names to common.hpp 2025-07-23 17:56:54 +03:00
cat
4e33e07188 Reorganising cairo 2025-07-23 04:10:54 +03:00
cat
7f147bb58c Added the generate_report's confirmation. Just need to do buttons. 2025-07-23 04:05:34 +03:00
cat
f662317bdb Moved static string to locale 2025-07-23 03:38:10 +03:00
cat
efa80c4c87 Added support for jpeg(stb) and webp(libwebp) 2025-07-23 03:33:25 +03:00
13 changed files with 263 additions and 66 deletions

View File

@@ -4,6 +4,7 @@ cmake_minimum_required(VERSION 3.22)
project(TheBartender VERSION 0.3) project(TheBartender VERSION 0.3)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
file(COPY assets DESTINATION ${CMAKE_BINARY_DIR})
# Create an executable # Create an executable
add_executable(${PROJECT_NAME} src/Base/Entry.cpp src/Commands.cpp add_executable(${PROJECT_NAME} src/Base/Entry.cpp src/Commands.cpp
@@ -14,15 +15,17 @@ find_package(DPP REQUIRED)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(SQLITE3 REQUIRED sqlite3) pkg_check_modules(SQLITE3 REQUIRED sqlite3)
pkg_check_modules(CAIRO REQUIRED cairo) pkg_check_modules(CAIRO REQUIRED cairo)
pkg_check_modules(STB REQUIRED stb)
pkg_check_modules(WEBP REQUIRED libwebp)
# Link the pre-installed DPP package. # Link the pre-installed DPP package.
target_link_libraries(${PROJECT_NAME} ${DPP_LIBRARIES} ${SQLITE3_LIBRARIES} target_link_libraries(${PROJECT_NAME} ${DPP_LIBRARIES} ${SQLITE3_LIBRARIES}
${CAIRO_LIBRARIES}) ${CAIRO_LIBRARIES} ${WEBP_LIBRARIES})
# Include the DPP directories. # Include the DPP directories.
target_include_directories( target_include_directories(
${PROJECT_NAME} PRIVATE ${DPP_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} ${PROJECT_NAME} PRIVATE ${DPP_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR}
${CAIRO_INCLUDE_DIR}) ${CAIRO_INCLUDE_DIR} ${WEBP_INCLUDE_DIR})
# Set C++ version # Set C++ version
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20 set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20

View File

@@ -1,22 +1,47 @@
// Bot info // Bot info
#define BOT_NAME "The Bartender Bot" #define BOT_NAME "The Bartender Bot"
#define BOT_VERSION "v0.3-1" #define BOT_VERSION "v0.4-1"
#define CURRENCY_NAME "Night Coin" #define CURRENCY_NAME "Night Coin"
// Buttons
#define BUTTON_ACCEPT "Confirm"
#define BUTTON_CANCEL "Cancel"
// Misc
#define REQUEST_CANCELLED "Your request has been cancelled. " RESPONSE_NO_CHARGE
#define RESPONSE_NO_CHARGE "You were not charged for this request."
///
/// COMMANDS
///
// Command ping // Command ping
#define COMMAND_PING "ping"
#define COMMAND_PING_DESCRIPTION "Ping-pong test" #define COMMAND_PING_DESCRIPTION "Ping-pong test"
#define COMMAND_GENERATE_REPORT "generate_report" // Command generate_report
#define COMMAND_GENERATE_REPORT_DESCRIPTION "Generate a fake news report" #define COMMAND_GENERATE_REPORT_DESCRIPTION "Generate a fake news report"
#define COMMAND_GENERATE_REPORT_ARGS_IMAGE_DESCRIPTION \ #define COMMAND_GENERATE_REPORT_ARGS_IMAGE_DESCRIPTION \
"Upload a background image to use (Only supports PNG, JPEG, and WEBP files)" "Upload a background image to use (Only supports PNG, JPEG, and WEBP files)"
#define COMMAND_GENERATE_REPORT_ARGS_HEADLINE_DESCRIPTION \ #define COMMAND_GENERATE_REPORT_ARGS_HEADLINE_DESCRIPTION \
"What is the headline?" "What is the headline?"
#define COMMAND_GENERATE_REPORT_FAIL_NOT_SUPPORTED(filetype) \
"File type: " + filetype + \
" is not supported. Only PNG, JPEG, and WEBP are allowed!"
#define COMMAND_GENERATE_REPORT_FAIL_IMAGE_DOWNLOAD \
"Failed to download the background image! Aborting." RESPONSE_NO_CHARGE
#define COMMAND_GENERATE_REPORT_FAIL_IMAGE_LOAD \
"Failed to load the background image! Aborting." RESPONSE_NO_CHARGE
#define COMMAND_GENERATE_REPORT_FAIL_INSUFFICIENT_BALANCE(amount) \
"You do not have " + amount + " " CURRENCY_NAME "(s) to afford this."
#define COMMAND_GENERATE_REPORT_CONFIRMATION_QUESTION(price, imageURL, \
imageMIME, headline) \
"This image generation will cost you " + price + \
" " CURRENCY_NAME \
"(s). Are you sure you want to continue?\nHeadline: " + \
headline + "\n[" + imageMIME + "](" + imageURL + ")"
// Command get_pfp // Command get_pfp
#define COMMAND_GET_PFP "get_pfp"
#define COMMAND_GET_PFP_DESCRIPTION \ #define COMMAND_GET_PFP_DESCRIPTION \
"Get someone elses profile picture as an image" "Get someone elses profile picture as an image"
#define COMMAND_GET_PFP_ARGS_USER \ #define COMMAND_GET_PFP_ARGS_USER \
@@ -34,17 +59,16 @@
") and their [server profile picture](" + guildpfp + ")" ") and their [server profile picture](" + guildpfp + ")"
// Command about // Command about
#define COMMAND_ABOUT "about"
#define COMMAND_ABOUT_DESCRIPTION "Info about the bot" #define COMMAND_ABOUT_DESCRIPTION "Info about the bot"
#define COMMAND_ABOUT_RESPONSE \ #define COMMAND_ABOUT_RESPONSE \
"## " BOT_NAME " " BOT_VERSION \ "## " BOT_NAME " " BOT_VERSION \
"\n-> Written by <@!607952795794145281>.\n-> Source code " \ "\n-> Written by <@!607952795794145281>.\n-> Source code " \
"https://git.thenight.club/cat/BartenderBot.\n-> Made with " \ "https://git.thenight.club/cat/BartenderBot.\n-> Made with " \
"[D++](<https://dpp.dev/>) and tears." "[D++](<https://dpp.dev/>), [cairo](<https://www.cairographics.org/>), " \
"[stb](<https://github.com/nothings/stb>), " \
"[libwebp](<https://chromium.googlesource.com/webm/libwebp>), and tears."
// Command balance/bal // Command balance/bal
#define COMMAND_BALANCE "balance"
#define COMMAND_BALANCE_SHORT "bal"
#define COMMAND_BALANCE_DESCRIPTION "See someone's balance of " CURRENCY_NAME #define COMMAND_BALANCE_DESCRIPTION "See someone's balance of " CURRENCY_NAME
#define COMMAND_BALANCE_USER_DESCRIPTION \ #define COMMAND_BALANCE_USER_DESCRIPTION \
"Leave this blank if you want to see your own balance" "Leave this blank if you want to see your own balance"
@@ -54,7 +78,6 @@
"<@!" + person + "> currently has " + balance + " " CURRENCY_NAME "(s)" "<@!" + person + "> currently has " + balance + " " CURRENCY_NAME "(s)"
// Command pay // Command pay
#define COMMAND_PAY "pay"
#define COMMAND_PAY_DESCRIPTION "Send someone some amount of " CURRENCY_NAME "s" #define COMMAND_PAY_DESCRIPTION "Send someone some amount of " CURRENCY_NAME "s"
#define COMMAND_PAY_ARGS_USER_DESCRIPTION "Who do you want to pay" #define COMMAND_PAY_ARGS_USER_DESCRIPTION "Who do you want to pay"
#define COMMAND_PAY_ARGS_AMOUNT_DESCRIPTION "How much do you want to pay" #define COMMAND_PAY_ARGS_AMOUNT_DESCRIPTION "How much do you want to pay"
@@ -65,7 +88,6 @@
"Successfully sent <@!" + recipient + "> " + amount + " " CURRENCY_NAME "(s)!" "Successfully sent <@!" + recipient + "> " + amount + " " CURRENCY_NAME "(s)!"
// Command print_money // Command print_money
#define COMMAND_PRINT_MONEY "print_money"
#define COMMAND_PRINT_DESCRIPTION "Allows the admin to print money on-demand" #define COMMAND_PRINT_DESCRIPTION "Allows the admin to print money on-demand"
#define COMMAND_PRINT_ARGS_AMOUNT_DESCRIPTION "How much are we printing boss?" #define COMMAND_PRINT_ARGS_AMOUNT_DESCRIPTION "How much are we printing boss?"
#define COMMAND_PRINT_FAIL_NO_PRIVILIEGE(recipient) \ #define COMMAND_PRINT_FAIL_NO_PRIVILIEGE(recipient) \
@@ -75,7 +97,6 @@
recipient + "> !" recipient + "> !"
// Command burn_money // Command burn_money
#define COMMAND_BURN_MONEY "burn_money"
#define COMMAND_BURN_DESCRIPTION \ #define COMMAND_BURN_DESCRIPTION \
"Allows the admin to burn money, burn baby burn!" "Allows the admin to burn money, burn baby burn!"
#define COMMAND_BURN_ARGS_AMOUNT_DESCRIPTION "How much are we burning?" #define COMMAND_BURN_ARGS_AMOUNT_DESCRIPTION "How much are we burning?"
@@ -88,7 +109,6 @@
recipient + "> !" recipient + "> !"
// Command money_leaderboard // Command money_leaderboard
#define COMMAND_MONEY_LEADERBOARD "money_leaderboard"
#define COMMAND_MONEY_LEADERBOARD_DESCRIPTION \ #define COMMAND_MONEY_LEADERBOARD_DESCRIPTION \
"See who are the wealthiest members of the server, and who are... less " \ "See who are the wealthiest members of the server, and who are... less " \
"fortunate." "fortunate."

View File

@@ -3,6 +3,7 @@
#include "../Databases.hpp" #include "../Databases.hpp"
#include <cstdlib> #include <cstdlib>
#include <dpp/appcommand.h>
#include <dpp/cluster.h> #include <dpp/cluster.h>
#include <dpp/dispatcher.h> #include <dpp/dispatcher.h>
#include <dpp/dpp.h> #include <dpp/dpp.h>
@@ -32,6 +33,16 @@ int main(int argc, char **argv) {
event.reply("Could not find that command :("); event.reply("Could not find that command :(");
}); });
bot.on_button_click([&bot](const dpp::button_click_t &event) {
event.reply(dpp::ir_deferred_update_message, "processing");
auto component = Components.find(event.custom_id);
if (component != Components.end()) {
component->second(event, bot);
return;
}
});
bot.on_ready([&bot](const dpp::ready_t &event) { bot.on_ready([&bot](const dpp::ready_t &event) {
if (dpp::run_once<struct register_bot_commands>()) { if (dpp::run_once<struct register_bot_commands>()) {
createCommands(event, bot); createCommands(event, bot);

View File

@@ -6,6 +6,8 @@
extern std::unordered_map<std::string, std::function<void(COMMAND_ARGS)>> extern std::unordered_map<std::string, std::function<void(COMMAND_ARGS)>>
Commands; Commands;
extern std::unordered_map<std::string, std::function<void(COMPONENT_ARGS)>>
Components;
void deleteCommands(const dpp::ready_t &event, dpp::cluster &bot) { void deleteCommands(const dpp::ready_t &event, dpp::cluster &bot) {
// bot.guild_bulk_command_delete(GUILD); // bot.guild_bulk_command_delete(GUILD);

View File

@@ -3,6 +3,9 @@
#include "Commands/MoneyCommands.cpp" #include "Commands/MoneyCommands.cpp"
#include "Commands/OtherCommands.cpp" #include "Commands/OtherCommands.cpp"
// Component Responses
#include "Commands/Components/GenerativeComponents.cpp"
// Registry // Registry
std::unordered_map<std::string, std::function<void(COMMAND_ARGS)>> Commands{ std::unordered_map<std::string, std::function<void(COMMAND_ARGS)>> Commands{
{COMMAND_PING, commandPing}, {COMMAND_PING, commandPing},
@@ -15,3 +18,7 @@ std::unordered_map<std::string, std::function<void(COMMAND_ARGS)>> Commands{
{COMMAND_MONEY_LEADERBOARD, commandMoneyLeaderboard}, {COMMAND_MONEY_LEADERBOARD, commandMoneyLeaderboard},
{COMMAND_GET_PFP, commandGetPFP}, {COMMAND_GET_PFP, commandGetPFP},
{COMMAND_GENERATE_REPORT, commandGenerateReport}}; {COMMAND_GENERATE_REPORT, commandGenerateReport}};
std::unordered_map<std::string, std::function<void(COMPONENT_ARGS)>> Components{
{COMPONENT_COMMAND_CANCEL, componentCancel},
{COMPONENT_GENERATE_REPORT_CONFIRM, componentGenerateReport}};

View File

@@ -0,0 +1,53 @@
#include "../../Common.hpp"
#include "../../Utility/CairoTools.hpp"
#include <dpp/cluster.h>
#include <dpp/dispatcher.h>
#include <dpp/misc-enum.h>
#include <string>
void componentGenerateReport(COMPONENT_ARGS) {
std::string content = event.command.get_context_message().content;
// Find headline
size_t start = content.find(": ") + 2;
std::string headline =
content.substr(start, content.find("\n[", start) - start);
// Find image MIME
start = content.find("[") + 1;
std::string imageType =
content.substr(start, content.find("]", start) - start);
// Find image link
start = content.find("](") + 2;
std::string url = content.substr(start, content.find(")", start) - start);
// Handler
auto fileType = supportedImageFileTypes.find(imageType);
bot.request(url, dpp::http_method::m_get,
[event, &bot, headline,
fileType](const dpp::http_request_completion_t &result) {
// We might not be able to download it
if (result.status != 200) {
event.edit_response(
COMMAND_GENERATE_REPORT_FAIL_IMAGE_DOWNLOAD);
return;
}
// Its possible that file is corrupted
auto imageAsSurface = fileType->second(result.body);
if (imageAsSurface == nullptr) {
event.edit_response(COMMAND_GENERATE_REPORT_FAIL_IMAGE_LOAD);
return;
}
std::string responseData =
GenerateReportImage(imageAsSurface, headline);
dpp::message response(event.command.channel_id, "");
response.add_file("report.png", responseData);
event.edit_response(response);
});
}
void componentCancel(COMPONENT_ARGS) { event.edit_response(REQUEST_CANCELLED); }

View File

@@ -1,46 +1,61 @@
#include "../Common.hpp" #include "../Common.hpp"
#include "../Utility/CairoTools.hpp" #include "../Utility/CairoTools.hpp"
#include "TransactionMethods.hpp"
#include <dpp/dispatcher.h> #include <dpp/dispatcher.h>
#include <dpp/dpp.h> #include <dpp/dpp.h>
#include <dpp/guild.h> #include <dpp/guild.h>
#include <dpp/message.h>
#include <dpp/misc-enum.h>
#include <dpp/queues.h>
#include <dpp/snowflake.h> #include <dpp/snowflake.h>
#include <dpp/user.h>
#include <functional>
#include <string> #include <string>
#include <unordered_map>
void commandGenerateReport(const dpp::slashcommand_t &event, void commandGenerateReport(const dpp::slashcommand_t &event,
dpp::cluster &bot) { dpp::cluster &bot) {
event.thinking(); event.thinking();
auto issuer = event.command.get_issuing_user().id;
// Insufficient balance
if (!checkFromUsersBalance(issuer, COMMAND_GENERATE_REPORT_COST)) {
event.edit_response(COMMAND_GENERATE_REPORT_FAIL_INSUFFICIENT_BALANCE(
std::to_string(COMMAND_GENERATE_REPORT_COST)));
return;
}
deductFromUsersBalance(issuer, COMMAND_GENERATE_REPORT_COST);
// Start processing
dpp::snowflake fileId = dpp::snowflake fileId =
std::get<dpp::snowflake>(event.get_parameter("image")); std::get<dpp::snowflake>(event.get_parameter("image"));
dpp::attachment file = event.command.get_resolved_attachment(fileId); dpp::attachment file = event.command.get_resolved_attachment(fileId);
auto fileType = supportedImageFileTypes.find(file.content_type); auto fileType = supportedImageFileTypes.find(file.content_type);
if (fileType != supportedImageFileTypes.end()) {
bot.request(
file.url, dpp::http_method::m_get,
[event, &bot, fileType](const dpp::http_request_completion_t &result) {
std::string responseData = GenerateReportImage(
fileType->second(
result.body), // Image itself processed for cario to handle
std::get<std::string>(event.get_parameter("headline")));
dpp::message response(event.command.channel_id, ""); if (fileType != supportedImageFileTypes.end()) {
response.add_file("report.png", responseData); // Don't forget that its only funny if it is all in upper case
event.edit_response(response); std::string headline =
}); std::get<std::string>(event.get_parameter("headline"));
// Never seen this method before but looks awesome
std::transform(headline.begin(), headline.end(), headline.data(),
::toupper);
dpp::message confirmRequest(
event.command.channel_id,
COMMAND_GENERATE_REPORT_CONFIRMATION_QUESTION(
std::to_string(COMMAND_GENERATE_REPORT_COST), file.url,
fileType->first, headline));
confirmRequest.add_component(
dpp::component()
.add_component(dpp::component()
.set_label(BUTTON_ACCEPT)
.set_type(dpp::cot_button)
.set_style(dpp::component_style::cos_success)
.set_id(COMPONENT_GENERATE_REPORT_CONFIRM))
.add_component(dpp::component()
.set_label(BUTTON_CANCEL)
.set_type(dpp::cot_button)
.set_style(dpp::component_style::cos_danger)
.set_id(COMPONENT_COMMAND_CANCEL)));
event.edit_response(confirmRequest);
} else { } else {
event.edit_response( event.edit_response(
"File type: " + file.content_type + COMMAND_GENERATE_REPORT_FAIL_NOT_SUPPORTED(file.content_type));
" is not allowed. Only PNG, JPEG, and WEBP are allowed!");
} }
} }
//
// ACTUAL GENERATION STARTS HERE
//

View File

@@ -34,12 +34,13 @@ void commandPay(const dpp::slashcommand_t &event, dpp::cluster &bot) {
std::get<dpp::snowflake>(event.get_parameter("recipient")).str(); std::get<dpp::snowflake>(event.get_parameter("recipient")).str();
std::uint64_t amount = std::get<std::int64_t>(event.get_parameter("amount")); std::uint64_t amount = std::get<std::int64_t>(event.get_parameter("amount"));
// See if we can deduct the payment first // Insufficient balance
if (!deductFromUsersBalance(event.command.get_issuing_user().id, amount)) { if (!checkFromUsersBalance(ADMIN_ID, amount)) {
event.reply(COMMAND_PAY_FAIL_INSUFFICIENT_AMOUNT(recipient, event.reply(COMMAND_PAY_FAIL_INSUFFICIENT_AMOUNT(recipient,
std::to_string(amount))); std::to_string(amount)));
return; return;
} }
deductFromUsersBalance(ADMIN_ID, amount);
// Lets pay them // Lets pay them
increaseFromUsersBalance(recipient, amount); increaseFromUsersBalance(recipient, amount);
@@ -68,12 +69,13 @@ void commandBurnMoney(const dpp::slashcommand_t &event, dpp::cluster &bot) {
} }
std::uint64_t amount = std::get<std::int64_t>(event.get_parameter("amount")); std::uint64_t amount = std::get<std::int64_t>(event.get_parameter("amount"));
// Insufficient in balance // Insufficient balance
if (!deductFromUsersBalance(ADMIN_ID, amount)) { if (!checkFromUsersBalance(ADMIN_ID, amount)) {
event.reply(COMMAND_BURN_FAIL_INSUFFICIENT_AMOUNT(std::to_string(amount))); event.reply(COMMAND_BURN_FAIL_INSUFFICIENT_AMOUNT(std::to_string(amount)));
return; return;
} }
deductFromUsersBalance(ADMIN_ID, amount);
event.reply( event.reply(
COMMAND_BURN_SUCCESS(std::to_string(ADMIN_ID), std::to_string(amount))); COMMAND_BURN_SUCCESS(std::to_string(ADMIN_ID), std::to_string(amount)));
} }
@@ -122,16 +124,15 @@ void increaseFromUsersBalance(const dpp::snowflake userid,
" WHERE UID=" + userid.str()); " WHERE UID=" + userid.str());
} }
bool deductFromUsersBalance(const dpp::snowflake userid, std::uint64_t amount) { void deductFromUsersBalance(const dpp::snowflake userid, std::uint64_t amount) {
std::uint64_t balance = std::stoll(getUserBalance(userid)); std::uint64_t balance = std::stoll(getUserBalance(userid));
if (balance < amount) {
return false;
}
execSQL("UPDATE MONEY SET CASH=" + std::to_string(balance - amount) + execSQL("UPDATE MONEY SET CASH=" + std::to_string(balance - amount) +
" WHERE UID=" + userid.str()); " WHERE UID=" + userid.str());
}
return true; bool checkFromUsersBalance(const dpp::snowflake userid, std::uint64_t amount) {
std::uint64_t balance = std::stoll(getUserBalance(userid));
return balance >= amount;
} }
std::string getUserBalance(const dpp::snowflake userid) { std::string getUserBalance(const dpp::snowflake userid) {

View File

@@ -3,6 +3,8 @@
// Helper methods // Helper methods
void increaseFromUsersBalance(const dpp::snowflake userid, void increaseFromUsersBalance(const dpp::snowflake userid,
std::uint64_t amount); std::uint64_t amount);
bool deductFromUsersBalance(const dpp::snowflake userid, std::uint64_t amount); void deductFromUsersBalance(const dpp::snowflake userid, std::uint64_t amount);
bool checkFromUsersBalance(const dpp::snowflake userid, std::uint64_t amount);
std::string getUserBalance(const dpp::snowflake userid); std::string getUserBalance(const dpp::snowflake userid);
void addUserToDatabase(const dpp::snowflake userid); void addUserToDatabase(const dpp::snowflake userid);

View File

@@ -1,6 +1,23 @@
#include "../settings.hpp" #include "../settings.hpp"
#define COMMAND_ARGS const dpp::slashcommand_t &event, dpp::cluster &bot #define COMMAND_ARGS const dpp::slashcommand_t &event, dpp::cluster &bot
#define COMPONENT_ARGS const dpp::button_click_t &event, dpp::cluster &bot
// Costs of commands // Costs of commands
#define COMMAND_GENERATE_REPORT_COST 2 #define COMMAND_GENERATE_REPORT_COST 2
// Command names
#define COMMAND_PING "ping"
#define COMMAND_GENERATE_REPORT "generate_report"
#define COMMAND_GET_PFP "get_pfp"
#define COMMAND_ABOUT "about"
#define COMMAND_BALANCE "balance"
#define COMMAND_BALANCE_SHORT "bal"
#define COMMAND_PAY "pay"
#define COMMAND_PRINT_MONEY "print_money"
#define COMMAND_BURN_MONEY "burn_money"
#define COMMAND_MONEY_LEADERBOARD "money_leaderboard"
// Component names
#define COMPONENT_GENERATE_REPORT_CONFIRM "generate_report_confirm"
#define COMPONENT_COMMAND_CANCEL "cancel"

View File

@@ -1,4 +1,6 @@
#include "CairoTools.hpp" #include "CairoTools.hpp"
#include <cairo/cairo.h>
#include <cctype>
std::string GenerateReportImage(cairo_surface_t *background, std::string GenerateReportImage(cairo_surface_t *background,
std::string headline) { std::string headline) {
@@ -6,14 +8,13 @@ std::string GenerateReportImage(cairo_surface_t *background,
CAIRO_FORMAT_ARGB32, REPORT_WIDTH, REPORT_HEIGHT); CAIRO_FORMAT_ARGB32, REPORT_WIDTH, REPORT_HEIGHT);
cairo_t *ctx = cairo_create(surface); cairo_t *ctx = cairo_create(surface);
cairo_set_source_rgb(ctx, 0, 0, 0); cairo_set_source_rgb(ctx, CAIRO_BLACK);
cairo_paint(ctx); cairo_paint(ctx);
int imgW = 0, imgH = 0;
// Background Image // Background Image
cairo_save(ctx); cairo_save(ctx);
imgW = cairo_image_surface_get_width(background); int imgW = cairo_image_surface_get_width(background),
imgH = cairo_image_surface_get_height(background); imgH = cairo_image_surface_get_height(background);
cairo_scale(ctx, (double)REPORT_WIDTH / imgW, cairo_scale(ctx, (double)REPORT_WIDTH / imgW,
(double)(REPORT_HEIGHT - REPORT_HEIGHT_OFFSET) / imgH); (double)(REPORT_HEIGHT - REPORT_HEIGHT_OFFSET) / imgH);
@@ -24,15 +25,15 @@ std::string GenerateReportImage(cairo_surface_t *background,
// Gradient // Gradient
cairo_pattern_t *gradient = cairo_pattern_t *gradient =
cairo_pattern_create_linear(0, 0, 0, REPORT_HEIGHT); cairo_pattern_create_linear(0, 0, 0, REPORT_HEIGHT);
cairo_pattern_add_color_stop_rgba(gradient, 0.85, 0, 0, 0, 1); cairo_pattern_add_color_stop_rgba(gradient, 0.85, CAIRO_BLACK, 1.0);
cairo_pattern_add_color_stop_rgba(gradient, 0, 0, 0, 0, 0); cairo_pattern_add_color_stop_rgba(gradient, 0.0, CAIRO_BLACK, 0.0);
cairo_rectangle(ctx, 0, 0, REPORT_WIDTH, REPORT_HEIGHT); cairo_rectangle(ctx, 0, 0, REPORT_WIDTH, REPORT_HEIGHT);
cairo_set_source(ctx, gradient); cairo_set_source(ctx, gradient);
cairo_fill(ctx); cairo_fill(ctx);
// Bumper // Bumper
cairo_save(ctx); cairo_save(ctx);
cairo_surface_t *bumper = static cairo_surface_t *bumper =
cairo_image_surface_create_from_png(REPORT_RESOURCE_BUMPER_PATH); cairo_image_surface_create_from_png(REPORT_RESOURCE_BUMPER_PATH);
imgW = cairo_image_surface_get_width(bumper); imgW = cairo_image_surface_get_width(bumper);
@@ -70,11 +71,15 @@ std::string GenerateReportImage(cairo_surface_t *background,
cairo_move_to(ctx, REPORT_TEXT_START_X, cairo_move_to(ctx, REPORT_TEXT_START_X,
REPORT_HEIGHT - REPORT_HEIGHT -
(REPORT_TEXT_JUMP_Y * (outputList.size() - lineCount++))); (REPORT_TEXT_JUMP_Y * (outputList.size() - lineCount++)));
cairo_set_source_rgb(ctx, CAIRO_TEXT_WHITE); cairo_set_source_rgb(ctx, CAIRO_QUARTZ);
cairo_show_text(ctx, line.c_str()); cairo_show_text(ctx, line.c_str());
} }
std::string data; std::string data;
cairo_surface_write_to_png_stream(surface, cairoOutputAsPNGStream, &data); cairo_surface_write_to_png_stream(surface, cairoOutputAsPNGStream, &data);
// Ugh cleanup
cairo_surface_destroy(surface);
cairo_destroy(ctx);
return data; return data;
} }

View File

@@ -1,5 +1,11 @@
#include "CairoTools.hpp" #include "CairoTools.hpp"
#define STB_IMAGE_IMPLEMENTATION
#include <stb/stb_image.h>
#include <webp/decode.h>
#include <webp/types.h>
std::unordered_map<std::string, std::unordered_map<std::string,
std::function<cairo_surface_t *(const std::string &)>> std::function<cairo_surface_t *(const std::string &)>>
supportedImageFileTypes{{"image/png", pngToCairoSurface}, supportedImageFileTypes{{"image/png", pngToCairoSurface},
@@ -31,9 +37,63 @@ cairo_surface_t *pngToCairoSurface(const std::string &data) {
return cairo_image_surface_create_from_png_stream( return cairo_image_surface_create_from_png_stream(
cairoReadPNGdata, const_cast<std::string *>(&data)); cairoReadPNGdata, const_cast<std::string *>(&data));
} }
cairo_surface_t *jpegToCairoSurface(std::string data) {
return cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 300, 200); ///
/// JPEG and WEBP
///
inline cairo_surface_t *swizzleToBGRA(unsigned char *data, int width,
int height, bool isjpeg = true) {
// AI code start - Reviewed, looks solid
cairo_surface_t *surface =
cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
unsigned char *surface_data = cairo_image_surface_get_data(surface);
int stride = cairo_image_surface_get_stride(surface);
// Convert RGB to BGRA
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
unsigned char *src =
data + (y * width + x) * (isjpeg ? 3 : 4); // Swizzling
unsigned char *dst = surface_data + y * stride + x * 4; // Still swizzling
dst[0] = src[2]; // B
dst[1] = src[1]; // G
dst[2] = src[0]; // R
dst[3] = src[3]; // A
}
}
cairo_surface_mark_dirty(surface);
// AI code end
return surface;
} }
cairo_surface_t *webpToCairoSurface(std::string data) {
return cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 300, 200); cairo_surface_t *jpegToCairoSurface(const std::string &data) {
int width, height, channels;
unsigned char *imgData = stbi_load_from_memory(
reinterpret_cast<const unsigned char *>(data.data()), data.size(), &width,
&height, &channels, 3 // Channel count
);
if (imgData == nullptr) {
return nullptr;
}
auto image = swizzleToBGRA(imgData, width, height);
stbi_image_free(imgData);
return image;
}
cairo_surface_t *webpToCairoSurface(const std::string &data) {
int width, height;
unsigned char *imgData =
WebPDecodeRGBA(reinterpret_cast<const uint8_t *>(data.data()),
data.size(), &width, &height);
if (imgData == nullptr) {
return nullptr;
}
auto image = swizzleToBGRA(imgData, width, height, false);
WebPFree(imgData);
return image;
} }

View File

@@ -6,11 +6,12 @@
#define REPORT_HEIGHT 1200 #define REPORT_HEIGHT 1200
#define REPORT_HEIGHT_OFFSET 200 #define REPORT_HEIGHT_OFFSET 200
#define REPORT_TEXT_START_X 40 #define REPORT_TEXT_START_X 40
#define REPORT_TEXT_JUMP_Y 120 #define REPORT_TEXT_JUMP_Y 125
#define REPORT_TEXT_FONT_SIZE 120.0 #define REPORT_TEXT_FONT_SIZE 120.0
#define REPORT_TEXT_LENGTH 26 #define REPORT_TEXT_LENGTH 26
#define REPORT_RESOURCE_BUMPER_PATH "./assets/report_bumper.png" #define REPORT_RESOURCE_BUMPER_PATH "./assets/report_bumper.png"
#define CAIRO_TEXT_WHITE 0.87, 0.87, 0.87 #define CAIRO_QUARTZ 0.87, 0.87, 0.87
#define CAIRO_BLACK 0.0, 0.0, 0.0
std::string GenerateReportImage(cairo_surface_t *background, std::string GenerateReportImage(cairo_surface_t *background,
std::string headline); std::string headline);
@@ -23,8 +24,8 @@ cairo_status_t cairoOutputAsPNGStream(void *closure, const unsigned char *data,
unsigned int length); unsigned int length);
cairo_surface_t *pngToCairoSurface(const std::string &data); cairo_surface_t *pngToCairoSurface(const std::string &data);
cairo_surface_t *jpegToCairoSurface(std::string data); cairo_surface_t *jpegToCairoSurface(const std::string &data);
cairo_surface_t *webpToCairoSurface(std::string data); cairo_surface_t *webpToCairoSurface(const std::string &data);
extern std::unordered_map<std::string, extern std::unordered_map<std::string,
std::function<cairo_surface_t *(const std::string &)>> std::function<cairo_surface_t *(const std::string &)>>