From e2db4ba5eefb9866341ca7cdf2fef08a3214b522 Mon Sep 17 00:00:00 2001 From: Soo Hwan Na Date: Mon, 1 Jul 2024 11:31:17 +0900 Subject: [PATCH] TgBot++: imagep: Add version getter, make dependencies optional --- .github/workflows/macos_test.yml | 2 +- cmake/tgbotpng.cmake | 41 ++++++++++++++---- src/command_modules/rotatepic.cpp | 69 +++++-------------------------- src/imagep/ImagePBase.hpp | 2 + src/imagep/ImageProcAll.cpp | 67 ++++++++++++++++++++++++++++++ src/imagep/ImageProcAll.hpp | 31 ++++++++++++++ src/imagep/ImageProcOpenCV.cpp | 5 +++ src/imagep/ImageProcOpenCV.hpp | 7 ++-- src/imagep/ImageProcessingAll.hpp | 5 --- src/imagep/ImageTypeJPEG.cpp | 8 ++++ src/imagep/ImageTypeJPEG.hpp | 3 +- src/imagep/ImageTypePNG.cpp | 5 +++ src/imagep/ImageTypePNG.hpp | 4 +- src/imagep/ImageTypeWEBP.cpp | 21 ++++++++-- src/imagep/ImageTypeWEBP.hpp | 4 +- 15 files changed, 191 insertions(+), 83 deletions(-) create mode 100644 src/imagep/ImageProcAll.cpp create mode 100644 src/imagep/ImageProcAll.hpp delete mode 100644 src/imagep/ImageProcessingAll.hpp diff --git a/.github/workflows/macos_test.yml b/.github/workflows/macos_test.yml index 9fe6d895..85ed407d 100644 --- a/.github/workflows/macos_test.yml +++ b/.github/workflows/macos_test.yml @@ -19,7 +19,7 @@ jobs: # https://github.com/Homebrew/homebrew-core/issues/169820#issuecomment-2080459578 - name: Set up dependencies run: | - brew install boost ninja llvm webp libpng libjpeg opencv + brew install boost ninja llvm webp libpng git submodule update --init sed -i '' '/^#define _LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS/d' "$(brew --prefix llvm)"/include/c++/v1/__config_site diff --git a/cmake/tgbotpng.cmake b/cmake/tgbotpng.cmake index db6657cc..17e93943 100644 --- a/cmake/tgbotpng.cmake +++ b/cmake/tgbotpng.cmake @@ -1,14 +1,39 @@ include(cmake/FindWebP.cmake) -find_package(PNG REQUIRED) -find_package(JPEG REQUIRED) -find_package(OpenCV REQUIRED core imgproc highgui) +find_package(PNG) +find_package(JPEG) +find_package(OpenCV COMPONENTS core imgproc highgui) + +set(TGBOTPNG_SOURCES) +set(TGBOTPNG_FLAGS) +set(TGBOTPNG_LIBS) + +if (${WebP_FOUND}) + extend_set(TGBOTPNG_FLAGS -DHAVE_LIBWEBP) + extend_set(TGBOTPNG_SOURCES src/imagep/ImageTypeWEBP.cpp) + message(STATUS "libWebP Present") +endif() +if (${JPEG_FOUND}) + extend_set(TGBOTPNG_FLAGS -DHAVE_LIBJPEG) + extend_set(TGBOTPNG_SOURCES src/imagep/ImageTypeJPEG.cpp) + message(STATUS "libJPEG Present") +endif() +if (${PNG_FOUND}) + extend_set(TGBOTPNG_FLAGS -DHAVE_LIBPNG) + extend_set(TGBOTPNG_SOURCES src/imagep/ImageTypePNG.cpp) + message(STATUS "libPNG Present") +endif() +if (${OpenCV_FOUND}) + extend_set(TGBOTPNG_FLAGS -DHAVE_OPENCV) + extend_set(TGBOTPNG_SOURCES src/imagep/ImageProcOpenCV.cpp) + message(STATUS "OpenCV Present") +endif() add_library_san(TgBotImgProc SHARED - src/imagep/ImageProcOpenCV.cpp - src/imagep/ImageTypeWEBP.cpp - src/imagep/ImageTypeJPEG.cpp - src/imagep/ImageTypePNG.cpp) -target_include_directories(TgBotImgProc PUBLIC + ${TGBOTPNG_SOURCES} + src/imagep/ImageProcAll.cpp) + +target_compile_definitions(TgBotImgProc PRIVATE ${TGBOTPNG_FLAGS}) +target_include_directories(TgBotImgProc PRIVATE ${PNG_INCLUDE_DIR} ${WebP_INCLUDE_DIRS} ${JPEG_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS}) target_link_libraries(TgBotImgProc ${PNG_LIBRARIES} ${WebP_LIBRARIES} ${JPEG_LIBRARIES} ${OpenCV_LIBS}) diff --git a/src/command_modules/rotatepic.cpp b/src/command_modules/rotatepic.cpp index 314ab277..8156f818 100644 --- a/src/command_modules/rotatepic.cpp +++ b/src/command_modules/rotatepic.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include @@ -24,15 +24,11 @@ struct ProcessImageParam { }; namespace { - -using ImageVariants = std::variant; -template -bool tryToProcess(ImageVariants& images, ProcessImageParam param) { - auto& inst = std::get(images); - if (inst.read(param.srcPath)) { +bool processPhotoFile(ProcessImageParam& param) { + ImageProcessingAll procAll(param.srcPath); + if (procAll.read()) { LOG(INFO) << "Successfully read image"; - LOG(INFO) << "Rotating image by " << param.rotation << " degrees"; - switch (inst.rotate_image(param.rotation)) { + switch (procAll.rotate(param.rotation)) { case PhotoBase::Result::kErrorInvalidArgument: LOG(ERROR) << "Invalid rotation angle"; return false; @@ -47,60 +43,15 @@ bool tryToProcess(ImageVariants& images, ProcessImageParam param) { break; } if (param.greyscale) { - LOG(INFO) << "Converting image to greyscale"; - inst.to_greyscale(); + procAll.to_greyscale(); } - LOG(INFO) << "Writing image to " << param.destPath; - return inst.write(param.destPath); - } else { - LOG(ERROR) << "Counld't read or parse image"; + return procAll.write(param.destPath); } return false; } -bool processPhotoFile(ProcessImageParam& param) { - ImageVariants images; - - // First try: PNG - LOG(INFO) << "First try: PNG"; - images.emplace(); - param.destPath = "output.png"; - if (tryToProcess<0>(images, param)) { - LOG(INFO) << "PNG liked it. Wrote image: " << param.destPath; - return true; - } - - // Second try: WebP - LOG(INFO) << "Second try: WebP"; - images.emplace(); - param.destPath = "output.webp"; - if (tryToProcess<1>(images, param)) { - LOG(INFO) << "WebP liked it. Wrote image: " << param.destPath; - return true; - } - - // Third try: JPEG - LOG(INFO) << "Third try: JPEG"; - images.emplace(); - param.destPath = "output.jpg"; - if (tryToProcess<2>(images, param)) { - LOG(INFO) << "JPEG liked it. Wrote image: " << param.destPath; - return true; - } - - // Fourth try: OpenCV - LOG(INFO) << "Fourth try: OpenCV"; - images.emplace(); - param.destPath = "output.png"; - if (tryToProcess<3>(images, param)) { - LOG(INFO) << "OpenCV liked it. Wrote image: " << param.destPath; - return true; - } - - // Failed to process - LOG(ERROR) << "No one liked it. Failed to process"; - return false; -} +constexpr std::string_view kDownloadFile = "inpic.bin"; +constexpr std::string_view kOutputFile = "outpic.png"; void rotateStickerCommand(const Bot& bot, const Message::Ptr message) { MessageWrapper wrapper(bot, message); @@ -152,7 +103,6 @@ void rotateStickerCommand(const Bot& bot, const Message::Ptr message) { } const auto file = bot.getApi().getFile(fileid.value()); - constexpr std::string_view kDownloadFile = "inpic.bin"; if (!file) { wrapper.sendMessageOnExit("Failed to download sticker file."); return; @@ -172,6 +122,7 @@ void rotateStickerCommand(const Bot& bot, const Message::Ptr message) { params.srcPath = kDownloadFile.data(); params.greyscale = greyscale; params.rotation = rotation; + params.destPath = kOutputFile.data(); if (processPhotoFile(params)) { const auto infile = diff --git a/src/imagep/ImagePBase.hpp b/src/imagep/ImagePBase.hpp index 1def9775..d13de78b 100644 --- a/src/imagep/ImagePBase.hpp +++ b/src/imagep/ImagePBase.hpp @@ -84,6 +84,8 @@ struct PhotoBase { file, [](FILE* f) { fclose(f); }); } + [[nodiscard]] virtual std::string version() const = 0; + protected: /** * @brief Rotates the image by the specified angle. diff --git a/src/imagep/ImageProcAll.cpp b/src/imagep/ImageProcAll.cpp new file mode 100644 index 00000000..307e6435 --- /dev/null +++ b/src/imagep/ImageProcAll.cpp @@ -0,0 +1,67 @@ +#include "ImageProcAll.hpp" + +#include + +#include "imagep/ImagePBase.hpp" + +ImageProcessingAll::ImageProcessingAll(std::filesystem::path filename) + : _filename(std::move(filename)) { +#ifdef HAVE_OPENCV + impls.emplace_back(std::make_unique()); +#endif +#ifdef HAVE_LIBJPEG + impls.emplace_back(std::make_unique()); +#endif +#ifdef HAVE_LIBPNG + impls.emplace_back(std::make_unique()); +#endif +#ifdef HAVE_LIBWEBP + impls.emplace_back(std::make_unique()); +#endif +} + +bool ImageProcessingAll::read() { + for (auto& impl : impls) { + if (impl->read(_filename)) { + // We found the backend suitable. Select one and dealloc others. + _impl = std::move(impl); + impls.clear(); + + LOG(INFO) << "Using implementation: " << _impl->version(); + return true; + } + } + LOG(INFO) << "No backend was suitable to read"; + return false; +} + +PhotoBase::Result ImageProcessingAll::rotate(int angle) { + if (!_impl) { + LOG(ERROR) << "No backend selected for rotation"; + return PhotoBase::Result::kErrorNoData; + } + DLOG(INFO) << "Calling impl->rotate with angle: " << angle; + return _impl->rotate_image(angle); +} + +void ImageProcessingAll::to_greyscale() { + if (!_impl) { + LOG(ERROR) << "No backend selected for greyscale conversion"; + return; + } + DLOG(INFO) << "Calling impl->to_greyscale"; + _impl->to_greyscale(); +} + +bool ImageProcessingAll::write(const std::filesystem::path& filename) { + if (!_impl) { + LOG(ERROR) << "No backend selected for writing"; + return false; + } + if (filename.empty()) { + LOG(ERROR) << "Filename is empty"; + return false; + } + DLOG(INFO) << "Calling impl->write with filename: " << filename; + return _impl->write(filename); +} \ No newline at end of file diff --git a/src/imagep/ImageProcAll.hpp b/src/imagep/ImageProcAll.hpp new file mode 100644 index 00000000..cfa75569 --- /dev/null +++ b/src/imagep/ImageProcAll.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#ifdef HAVE_OPENCV +#include "ImageProcOpenCV.hpp" +#endif +#ifdef HAVE_LIBJPEG +#include "ImageTypeJPEG.hpp" +#endif +#ifdef HAVE_LIBPNG +#include "ImageTypePNG.hpp" +#endif +#ifdef HAVE_LIBWEBP +#include "ImageTypeWEBP.hpp" +#endif +#include "imagep/ImagePBase.hpp" + +struct ImageProcessingAll { + bool read(); + PhotoBase::Result rotate(int angle); + void to_greyscale(); + bool write(const std::filesystem::path& filename); + + explicit ImageProcessingAll(std::filesystem::path filename); + + private: + std::filesystem::path _filename; + std::vector> impls; + std::unique_ptr _impl; +}; \ No newline at end of file diff --git a/src/imagep/ImageProcOpenCV.cpp b/src/imagep/ImageProcOpenCV.cpp index 8a3587e7..627813b3 100644 --- a/src/imagep/ImageProcOpenCV.cpp +++ b/src/imagep/ImageProcOpenCV.cpp @@ -1,4 +1,5 @@ #include "ImageProcOpenCV.hpp" +#include bool OpenCVImage::read(const std::filesystem::path& filename) { image = cv::imread(filename.string(), cv::IMREAD_UNCHANGED); @@ -49,3 +50,7 @@ bool OpenCVImage::write(const std::filesystem::path& filename) { } return true; } + +std::string OpenCVImage::version() const { + return "OpenCV version: " + cv::getVersionString(); +} \ No newline at end of file diff --git a/src/imagep/ImageProcOpenCV.hpp b/src/imagep/ImageProcOpenCV.hpp index cc15de17..da039ac7 100644 --- a/src/imagep/ImageProcOpenCV.hpp +++ b/src/imagep/ImageProcOpenCV.hpp @@ -1,9 +1,9 @@ #pragma once -#include "ImagePBase.hpp" - #include +#include "ImagePBase.hpp" + /** * @brief Derived class for photo manipulation using OpenCV. * @@ -12,13 +12,14 @@ */ class OpenCVImage : public PhotoBase { public: - OpenCVImage() = default; + OpenCVImage() noexcept = default; ~OpenCVImage() override = default; bool read(const std::filesystem::path& filename) override; Result _rotate_image(int angle) override; void to_greyscale() override; bool write(const std::filesystem::path& filename) override; + std::string version() const override; private: cv::Mat image; diff --git a/src/imagep/ImageProcessingAll.hpp b/src/imagep/ImageProcessingAll.hpp deleted file mode 100644 index d1f5afb2..00000000 --- a/src/imagep/ImageProcessingAll.hpp +++ /dev/null @@ -1,5 +0,0 @@ -// Meta-include -#include "ImageProcOpenCV.hpp" -#include "ImageTypeJPEG.hpp" -#include "ImageTypePNG.hpp" -#include "ImageTypeWEBP.hpp" \ No newline at end of file diff --git a/src/imagep/ImageTypeJPEG.cpp b/src/imagep/ImageTypeJPEG.cpp index 2493e8f5..db8e7909 100644 --- a/src/imagep/ImageTypeJPEG.cpp +++ b/src/imagep/ImageTypeJPEG.cpp @@ -197,4 +197,12 @@ bool JPEGImage::write(const std::filesystem::path& filename) { fclose(outfile); return true; +} + +#define _STR(x) #x +#define STR(x) _STR(x) +#define LIBJPEG_TURBO_VERSION_STR STR(LIBJPEG_TURBO_VERSION) + +std::string JPEGImage::version() const { + return "libjpeg version " LIBJPEG_TURBO_VERSION_STR; } \ No newline at end of file diff --git a/src/imagep/ImageTypeJPEG.hpp b/src/imagep/ImageTypeJPEG.hpp index c3e108ef..62405a09 100644 --- a/src/imagep/ImageTypeJPEG.hpp +++ b/src/imagep/ImageTypeJPEG.hpp @@ -10,13 +10,14 @@ */ class JPEGImage : public PhotoBase { public: - JPEGImage() = default; + JPEGImage() noexcept = default; ~JPEGImage() override = default; bool read(const std::filesystem::path& filename) override; Result _rotate_image(int angle) override; void to_greyscale() override; bool write(const std::filesystem::path& filename) override; + std::string version() const override; private: std::unique_ptr image_data; diff --git a/src/imagep/ImageTypePNG.cpp b/src/imagep/ImageTypePNG.cpp index 2dcffb68..315a0fc7 100644 --- a/src/imagep/ImageTypePNG.cpp +++ b/src/imagep/ImageTypePNG.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace { void absl_warn_fn(png_structp png_ptr, png_const_charp error_message) { @@ -258,3 +259,7 @@ bool PngImage::write(const std::filesystem::path& filename) { contains_data = false; return true; } + +std::string PngImage::version() const { + return PNG_LIBPNG_VER_STRING; +} \ No newline at end of file diff --git a/src/imagep/ImageTypePNG.hpp b/src/imagep/ImageTypePNG.hpp index 8776769c..ec07c668 100644 --- a/src/imagep/ImageTypePNG.hpp +++ b/src/imagep/ImageTypePNG.hpp @@ -11,7 +11,7 @@ #include "ImagePBase.hpp" struct PngImage : PhotoBase { - PngImage() = default; + PngImage() noexcept = default; ~PngImage() override = default; struct MiniSharedMalloc { @@ -77,6 +77,8 @@ struct PngImage : PhotoBase { */ bool write(const std::filesystem::path& filename) override; + std::string version() const override; + private: using transform_fn_t = std::function #include +#include #include #include #include @@ -77,18 +77,19 @@ WebPImage::Result WebPImage::_rotate_image(int angle) { rotated_data = std::make_unique(rotated_width * rotated_height * 4); - + for (int y = 0; y < height_; ++y) { for (int x = 0; x < width_; ++x) { const auto source_index = (y * width_ + x) * 4; long dest_index = 0; - + switch (angle) { case kAngle90: dest_index = (x * rotated_width + (height_ - 1 - y)) * 4; break; case kAngle180: - dest_index = ((height_ - 1 - y) * width_ + (width_ - 1 - x)) * 4; + dest_index = + ((height_ - 1 - y) * width_ + (width_ - 1 - x)) * 4; break; case kAngle270: dest_index = ((width_ - 1 - x) * rotated_width + y) * 4; @@ -155,3 +156,15 @@ bool WebPImage::write(const std::filesystem::path& filename) { return true; } + +std::string WebPImage::version() const { + constexpr int kVersionBits = 8; + constexpr int kVersionMask = 0xff; + int version = WebPGetDecoderVersion(); + int major = (version >> kVersionBits >> kVersionBits) & kVersionMask; + int minor = (version >> kVersionBits) & kVersionMask; + int revision = version & kVersionMask; + + return ("libwebp version: " + std::to_string(major) + "." + + std::to_string(minor) + "." + std::to_string(revision)); +} \ No newline at end of file diff --git a/src/imagep/ImageTypeWEBP.hpp b/src/imagep/ImageTypeWEBP.hpp index a7f59bd3..2060fd94 100644 --- a/src/imagep/ImageTypeWEBP.hpp +++ b/src/imagep/ImageTypeWEBP.hpp @@ -8,7 +8,7 @@ class WebPImage : public PhotoBase { public: - WebPImage() = default; + WebPImage() noexcept = default; ~WebPImage() override = default; /** @@ -56,6 +56,8 @@ class WebPImage : public PhotoBase { */ bool write(const std::filesystem::path& filename) override; + std::string version() const override; + private: long width_{}; long height_{};