diff --git a/CMakeLists.txt b/CMakeLists.txt index 6463723..ea855d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,15 +14,16 @@ find_package(DPP REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(SQLITE3 REQUIRED sqlite3) pkg_check_modules(CAIRO REQUIRED cairo) +pkg_check_modules(WEBP REQUIRED libwebpdecoder) # Link the pre-installed DPP package. target_link_libraries(${PROJECT_NAME} ${DPP_LIBRARIES} ${SQLITE3_LIBRARIES} - ${CAIRO_LIBRARIES}) + ${CAIRO_LIBRARIES} ${WEBP_LIBRARIES}) # Include the DPP directories. target_include_directories( ${PROJECT_NAME} PRIVATE ${DPP_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} - ${CAIRO_INCLUDE_DIR}) + ${CAIRO_INCLUDE_DIR} ${WEBP_INCLUDE_DIR}) # Set C++ version set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20 diff --git a/src/Commands/GenerativeCommands.cpp b/src/Commands/GenerativeCommands.cpp index b59c703..12eb992 100644 --- a/src/Commands/GenerativeCommands.cpp +++ b/src/Commands/GenerativeCommands.cpp @@ -1,6 +1,8 @@ #include "../Common.hpp" #include "../Utility/CairoTools.hpp" +#include +#include #include #include #include @@ -25,11 +27,22 @@ void commandGenerateReport(const dpp::slashcommand_t &event, 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(event.get_parameter("headline"))); + auto imageAsSurface = fileType->second(result.body); + if (imageAsSurface == nullptr) { + event.edit_response("Failed to load the background image :("); + return; + } + // Don't forget that its only funny if it is all in upper case + std::string headline = + std::get(event.get_parameter("headline")); + + // Never seen this method before but looks awesome + std::transform(headline.begin(), headline.end(), headline.data(), + ::toupper); + + std::string responseData = + GenerateReportImage(imageAsSurface, headline); dpp::message response(event.command.channel_id, ""); response.add_file("report.png", responseData); event.edit_response(response); diff --git a/src/Utility/CairoGenerate.cpp b/src/Utility/CairoGenerate.cpp index 87c34af..65117eb 100644 --- a/src/Utility/CairoGenerate.cpp +++ b/src/Utility/CairoGenerate.cpp @@ -1,4 +1,5 @@ #include "CairoTools.hpp" +#include std::string GenerateReportImage(cairo_surface_t *background, std::string headline) { diff --git a/src/Utility/CairoTools.cpp b/src/Utility/CairoTools.cpp index 4091316..f0db874 100644 --- a/src/Utility/CairoTools.cpp +++ b/src/Utility/CairoTools.cpp @@ -1,5 +1,11 @@ #include "CairoTools.hpp" +#define STB_IMAGE_IMPLEMENTATION +#include + +#include +#include + std::unordered_map> supportedImageFileTypes{{"image/png", pngToCairoSurface}, @@ -31,9 +37,63 @@ cairo_surface_t *pngToCairoSurface(const std::string &data) { return cairo_image_surface_create_from_png_stream( cairoReadPNGdata, const_cast(&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(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(data.data()), + data.size(), &width, &height); + + if (imgData == nullptr) { + return nullptr; + } + + auto image = swizzleToBGRA(imgData, width, height, false); + WebPFree(imgData); + return image; } diff --git a/src/Utility/CairoTools.hpp b/src/Utility/CairoTools.hpp index 1ac1dcf..9825553 100644 --- a/src/Utility/CairoTools.hpp +++ b/src/Utility/CairoTools.hpp @@ -6,7 +6,7 @@ #define REPORT_HEIGHT 1200 #define REPORT_HEIGHT_OFFSET 200 #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_LENGTH 26 #define REPORT_RESOURCE_BUMPER_PATH "./assets/report_bumper.png" @@ -23,8 +23,8 @@ cairo_status_t cairoOutputAsPNGStream(void *closure, const unsigned char *data, unsigned int length); cairo_surface_t *pngToCairoSurface(const std::string &data); -cairo_surface_t *jpegToCairoSurface(std::string data); -cairo_surface_t *webpToCairoSurface(std::string data); +cairo_surface_t *jpegToCairoSurface(const std::string &data); +cairo_surface_t *webpToCairoSurface(const std::string &data); extern std::unordered_map>