diff --git a/src/libs/av/impl/AudioFile.cpp b/src/libs/av/impl/AudioFile.cpp index 1067fb7c..bd0b6504 100644 --- a/src/libs/av/impl/AudioFile.cpp +++ b/src/libs/av/impl/AudioFile.cpp @@ -242,11 +242,10 @@ namespace lms::av void AudioFile::visitAttachedPictures(std::function func) const { static const std::unordered_map codecMimeMap{ - { AV_CODEC_ID_BMP, "image/x-bmp" }, + { AV_CODEC_ID_BMP, "image/bmp" }, { AV_CODEC_ID_GIF, "image/gif" }, { AV_CODEC_ID_MJPEG, "image/jpeg" }, { AV_CODEC_ID_PNG, "image/png" }, - { AV_CODEC_ID_PNG, "image/x-png" }, { AV_CODEC_ID_PPM, "image/x-portable-pixmap" }, }; diff --git a/src/libs/image/CMakeLists.txt b/src/libs/image/CMakeLists.txt index 5c041943..15555911 100644 --- a/src/libs/image/CMakeLists.txt +++ b/src/libs/image/CMakeLists.txt @@ -1,6 +1,6 @@ add_library(lmsimage SHARED - impl/SvgImage.cpp + impl/EncodedImage.cpp ) target_include_directories(lmsimage INTERFACE @@ -26,7 +26,6 @@ if (${LMS_IMAGE_BACKEND} STREQUAL "stb") target_sources(lmsimage PRIVATE impl/stb/Image.cpp - impl/stb/JPEGImage.cpp impl/stb/RawImage.cpp ) target_compile_options(lmsimage PRIVATE "-DSTB_IMAGE_RESIZE_VERSION=${STB_IMAGE_RESIZE_VERSION}") @@ -38,7 +37,6 @@ elseif (${LMS_IMAGE_BACKEND} STREQUAL "graphicsmagick") target_sources(lmsimage PRIVATE impl/graphicsmagick/Image.cpp - impl/graphicsmagick/JPEGImage.cpp impl/graphicsmagick/RawImage.cpp ) target_link_libraries(lmsimage PRIVATE PkgConfig::GraphicsMagick++) diff --git a/src/libs/image/impl/EncodedImage.cpp b/src/libs/image/impl/EncodedImage.cpp new file mode 100644 index 00000000..bef1cbca --- /dev/null +++ b/src/libs/image/impl/EncodedImage.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "EncodedImage.hpp" + +#include +#include +#include + +#include "core/ITraceLogger.hpp" +#include "core/String.hpp" +#include "image/Exception.hpp" + +namespace lms::image +{ + namespace + { + std::string_view extensionToMimeType(const std::filesystem::path& extension) + { + static const std::unordered_map mimeTypesByExtension{ + { ".bmp", "image/bmp" }, + { ".gif", "image/gif" }, + { ".jpeg", "image/jpeg" }, + { ".jpg", "image/jpeg" }, + { ".png", "image/png" }, + { ".ppm", "image/x-portable-pixmap" }, + { ".svg", "image/svg+xml" }, + }; + + const auto it{ mimeTypesByExtension.find(core::stringUtils::stringToLower(extension.c_str())) }; + if (it == std::cend(mimeTypesByExtension)) + throw Exception{ "Unhandled image extension '" + extension.string() + "'" }; + return it->second; + } + + std::vector fileToBuffer(const std::filesystem::path& p) + { + LMS_SCOPED_TRACE_DETAILED("Image", "ReadFile"); + + std::ifstream ifs{ p.string(), std::ios::binary }; + if (!ifs.is_open()) + throw Exception{ "Cannot open file '" + p.string() + "' for reading purpose" }; + + std::vector data; + // read file content + ifs.seekg(0, std::ios::end); + std::streamsize size = ifs.tellg(); + if (size < 0) + throw Exception{ "Cannot determine file size for '" + p.string() + "'" }; + + ifs.seekg(0, std::ios::beg); + data.resize(size); + if (!ifs.read(reinterpret_cast(data.data()), size)) + throw Exception{ "Cannot read file content for '" + p.string() + "'" }; + + return data; + } + + } // namespace + + std::unique_ptr readImage(const std::filesystem::path& path) + { + return std::make_unique(path); + } + + std::unique_ptr readImage(std::span encodedData, std::string_view mimeType) + { + return std::make_unique(encodedData, mimeType); + } + + EncodedImage::EncodedImage(std::vector&& data, std::string_view mimeType) + : _data{ std::move(data) } + , _mimeType(mimeType) + { + } + + EncodedImage::EncodedImage(std::span data, std::string_view mimeType) + : _data(std::cbegin(data), std::cend(data)) + , _mimeType(mimeType) + { + } + + EncodedImage::EncodedImage(const std::filesystem::path& p) + : EncodedImage::EncodedImage{ fileToBuffer(p), extensionToMimeType(p.extension()) } + { + } +} // namespace lms::image \ No newline at end of file diff --git a/src/libs/image/impl/SvgImage.hpp b/src/libs/image/impl/EncodedImage.hpp similarity index 58% rename from src/libs/image/impl/SvgImage.hpp rename to src/libs/image/impl/EncodedImage.hpp index 4befe5cd..fcf87691 100644 --- a/src/libs/image/impl/SvgImage.hpp +++ b/src/libs/image/impl/EncodedImage.hpp @@ -19,23 +19,28 @@ #pragma once +#include #include #include "image/IEncodedImage.hpp" namespace lms::image { - class SvgImage : public IEncodedImage + class EncodedImage : public IEncodedImage { public: - SvgImage(std::vector&& data) - : _data{ std::move(data) } {} + EncodedImage(const std::filesystem::path& path); + EncodedImage(std::vector&& data, std::string_view mimeType); + EncodedImage(std::span data, std::string_view mimeType); + ~EncodedImage() override = default; + EncodedImage(const EncodedImage&) = delete; + EncodedImage& operator=(const EncodedImage&) = delete; - const std::byte* getData() const override { return &_data.front(); } - std::size_t getDataSize() const override { return _data.size(); } - std::string_view getMimeType() const override { return "image/svg+xml"; } + std::span getData() const override { return _data; } + std::string_view getMimeType() const override { return _mimeType; } private: const std::vector _data; + const std::string _mimeType; }; } // namespace lms::image \ No newline at end of file diff --git a/src/libs/image/impl/SvgImage.cpp b/src/libs/image/impl/SvgImage.cpp deleted file mode 100644 index 4534e4a0..00000000 --- a/src/libs/image/impl/SvgImage.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2015 Emeric Poupon - * - * This file is part of LMS. - * - * LMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LMS. If not, see . - */ - -#include "SvgImage.hpp" - -#include -#include - -#include "core/ITraceLogger.hpp" -#include "image/Exception.hpp" - -namespace lms::image -{ - std::unique_ptr readSvgFile(const std::filesystem::path& p) - { - LMS_SCOPED_TRACE_DETAILED("Image", "ReadSVG"); - - if (p.extension() != ".svg") - throw Exception{ "Unexpected file extension: '" + p.extension().string() + "', expected .svg" }; - - std::ifstream ifs{ p.string(), std::ios::binary }; - if (!ifs.is_open()) - throw Exception{ "Cannot open file '" + p.string() + "' for reading purpose" }; - - std::vector data; - // read file content - ifs.seekg(0, std::ios::end); - std::streamsize size = ifs.tellg(); - if (size < 0) - throw Exception{ "Cannot determine file size for '" + p.string() + "'" }; - - ifs.seekg(0, std::ios::beg); - data.resize(size); - if (!ifs.read(reinterpret_cast(data.data()), size)) - throw Exception{ "Cannot read file content for '" + p.string() + "'" }; - - return std::make_unique(std::move(data)); - } -} // namespace lms::image \ No newline at end of file diff --git a/src/libs/image/impl/graphicsmagick/Image.cpp b/src/libs/image/impl/graphicsmagick/Image.cpp index 44a59786..16f54fbe 100644 --- a/src/libs/image/impl/graphicsmagick/Image.cpp +++ b/src/libs/image/impl/graphicsmagick/Image.cpp @@ -19,26 +19,17 @@ #include "image/Image.hpp" -#include +#include -#include "RawImage.hpp" #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" +#include "image/Exception.hpp" + +#include "EncodedImage.hpp" +#include "RawImage.hpp" namespace lms::image { - std::unique_ptr decodeImage(const std::byte* encodedData, std::size_t encodedDataSize) - { - LMS_SCOPED_TRACE_DETAILED("Image", "DecodeBuffer"); - return std::make_unique(encodedData, encodedDataSize); - } - - std::unique_ptr decodeImage(const std::filesystem::path& path) - { - LMS_SCOPED_TRACE_DETAILED("Image", "DecodeFile"); - return std::make_unique(path); - } - void init(const std::filesystem::path& path) { Magick::InitializeMagick(path.string().c_str()); @@ -61,4 +52,39 @@ namespace lms::image static const std::array fileExtensions{ ".jpg", ".jpeg", ".png", ".bmp" }; return fileExtensions; } + + std::unique_ptr decodeImage(std::span encodedData) + { + LMS_SCOPED_TRACE_DETAILED("Image", "DecodeBuffer"); + return std::make_unique(encodedData); + } + + std::unique_ptr decodeImage(const std::filesystem::path& path) + { + LMS_SCOPED_TRACE_DETAILED("Image", "DecodeFile"); + return std::make_unique(path); + } + + std::unique_ptr encodeToJPEG(const IRawImage& rawImage, unsigned quality) + { + LMS_SCOPED_TRACE_DETAILED("Image", "WriteJPEG"); + + try + { + Magick::Image image{ static_cast(rawImage).getMagickImage() }; + image.magick("JPEG"); + image.quality(quality); + + Magick::Blob blob; + image.write(&blob); + + return std::make_unique(std::span{ static_cast(blob.data()), blob.length() }, "image/jpeg"); + } + catch (Magick::Exception& e) + { + LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what()); + throw Exception{ std::string{ "Magick read error: " } + e.what() }; + } + } + } // namespace lms::image diff --git a/src/libs/image/impl/graphicsmagick/JPEGImage.cpp b/src/libs/image/impl/graphicsmagick/JPEGImage.cpp deleted file mode 100644 index 94738b20..00000000 --- a/src/libs/image/impl/graphicsmagick/JPEGImage.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2020 Emeric Poupon - * - * This file is part of LMS. - * - * LMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LMS. If not, see . - */ - -#include "JPEGImage.hpp" - -#include "core/ILogger.hpp" -#include "core/ITraceLogger.hpp" -#include "image/Exception.hpp" - -#include "RawImage.hpp" - -namespace lms::image::GraphicsMagick -{ - JPEGImage::JPEGImage(const RawImage& rawImage, unsigned quality) - { - LMS_SCOPED_TRACE_DETAILED("Image", "WriteJPEG"); - - try - { - Magick::Image image{ rawImage.getMagickImage() }; - image.magick("JPEG"); - image.quality(quality); - image.write(&_blob); - } - catch (Magick::Exception& e) - { - LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what()); - throw Exception{ std::string{ "Magick read error: " } + e.what() }; - } - } - - const std::byte* JPEGImage::getData() const - { - return reinterpret_cast(_blob.data()); - } - - std::size_t JPEGImage::getDataSize() const - { - return _blob.length(); - } -} // namespace lms::image::GraphicsMagick diff --git a/src/libs/image/impl/graphicsmagick/JPEGImage.hpp b/src/libs/image/impl/graphicsmagick/JPEGImage.hpp deleted file mode 100644 index 4d9386c0..00000000 --- a/src/libs/image/impl/graphicsmagick/JPEGImage.hpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2020 Emeric Poupon - * - * This file is part of LMS. - * - * LMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LMS. If not, see . - */ - -#pragma once - -#include - -#include "image/IEncodedImage.hpp" - -namespace lms::image::GraphicsMagick -{ - class RawImage; - class JPEGImage : public IEncodedImage - { - public: - JPEGImage(const RawImage& rawImage, unsigned quality); - - private: - const std::byte* getData() const override; - std::size_t getDataSize() const override; - std::string_view getMimeType() const override { return "image/jpeg"; } - - Magick::Blob _blob; - }; -} // namespace lms::image::GraphicsMagick diff --git a/src/libs/image/impl/graphicsmagick/RawImage.cpp b/src/libs/image/impl/graphicsmagick/RawImage.cpp index f3550478..3e7d017a 100644 --- a/src/libs/image/impl/graphicsmagick/RawImage.cpp +++ b/src/libs/image/impl/graphicsmagick/RawImage.cpp @@ -19,24 +19,19 @@ #include "RawImage.hpp" -#include -#include - #include #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" #include "image/Exception.hpp" -#include "JPEGImage.hpp" - namespace lms::image::GraphicsMagick { - RawImage::RawImage(const std::byte* encodedData, std::size_t encodedDataSize) + RawImage::RawImage(std::span encodedData) { try { - Magick::Blob blob{ encodedData, encodedDataSize }; + Magick::Blob blob{ encodedData.data(), encodedData.size() }; _image.read(blob); } catch (Magick::WarningCoder& e) @@ -102,14 +97,8 @@ namespace lms::image::GraphicsMagick } } - std::unique_ptr RawImage::encodeToJPEG(unsigned quality) const - { - return std::make_unique(*this, quality); - } - Magick::Image RawImage::getMagickImage() const { return _image; } - } // namespace lms::image::GraphicsMagick diff --git a/src/libs/image/impl/graphicsmagick/RawImage.hpp b/src/libs/image/impl/graphicsmagick/RawImage.hpp index 13fc7b59..525f70ac 100644 --- a/src/libs/image/impl/graphicsmagick/RawImage.hpp +++ b/src/libs/image/impl/graphicsmagick/RawImage.hpp @@ -21,10 +21,10 @@ #include #include +#include #include -#include "image/IEncodedImage.hpp" #include "image/IRawImage.hpp" namespace lms::image::GraphicsMagick @@ -32,19 +32,17 @@ namespace lms::image::GraphicsMagick class RawImage : public IRawImage { public: - RawImage(const std::byte* encodedData, std::size_t encodedDataSize); + RawImage(std::span encodedData); RawImage(const std::filesystem::path& path); ImageSize getWidth() const override; ImageSize getHeight() const override; void resize(ImageSize width) override; - std::unique_ptr encodeToJPEG(unsigned quality) const override; - private: - friend class JPEGImage; Magick::Image getMagickImage() const; + private: Magick::Image _image; }; } // namespace lms::image::GraphicsMagick diff --git a/src/libs/image/impl/stb/Image.cpp b/src/libs/image/impl/stb/Image.cpp index 06b953aa..28e443d2 100644 --- a/src/libs/image/impl/stb/Image.cpp +++ b/src/libs/image/impl/stb/Image.cpp @@ -21,15 +21,31 @@ #include -#include "RawImage.hpp" +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include + #include "core/ITraceLogger.hpp" +#include "EncodedImage.hpp" +#include "RawImage.hpp" +#include "image/Exception.hpp" + namespace lms::image { - std::unique_ptr decodeImage(const std::byte* encodedData, std::size_t encodedDataSize) + void init(const std::filesystem::path& /*unused*/) + { + } + + std::span getSupportedFileExtensions() + { + static const std::array fileExtensions{ ".jpg", ".jpeg", ".png", ".bmp" }; + return fileExtensions; + } + + std::unique_ptr decodeImage(std::span encodedData) { LMS_SCOPED_TRACE_DETAILED("Image", "DecodeBuffer"); - return std::make_unique(encodedData, encodedDataSize); + return std::make_unique(encodedData); } std::unique_ptr decodeImage(const std::filesystem::path& path) @@ -38,13 +54,22 @@ namespace lms::image return std::make_unique(path); } - void init(const std::filesystem::path&) + std::unique_ptr encodeToJPEG(const IRawImage& rawImage, unsigned quality) { - } + LMS_SCOPED_TRACE_DETAILED("Image", "WriteJPEG"); - std::span getSupportedFileExtensions() - { - static const std::array fileExtensions{ ".jpg", ".jpeg", ".png", ".bmp" }; - return fileExtensions; + std::vector encodedData; + + auto writeCb{ [](void* ctx, void* writeData, int writeSize) { + auto& output{ *reinterpret_cast*>(ctx) }; + const std::size_t currentOutputSize{ output.size() }; + output.resize(currentOutputSize + writeSize); + std::copy(reinterpret_cast(writeData), reinterpret_cast(writeData) + writeSize, output.data() + currentOutputSize); + } }; + + if (::stbi_write_jpg_to_func(writeCb, &encodedData, rawImage.getWidth(), rawImage.getHeight(), 3, static_cast(rawImage).getData(), quality) == 0) + throw Exception{ "Failed to export in jpeg format!" }; + + return std::make_unique(std::move(encodedData), "image/jpeg"); } } // namespace lms::image \ No newline at end of file diff --git a/src/libs/image/impl/stb/JPEGImage.cpp b/src/libs/image/impl/stb/JPEGImage.cpp deleted file mode 100644 index bf3cf93a..00000000 --- a/src/libs/image/impl/stb/JPEGImage.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2020 Emeric Poupon - * - * This file is part of LMS. - * - * LMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LMS. If not, see . - */ - -#include "JPEGImage.hpp" - -#define STB_IMAGE_WRITE_IMPLEMENTATION -#include - -#include "core/ITraceLogger.hpp" -#include "image/Exception.hpp" - -#include "RawImage.hpp" - -namespace lms::image::STB -{ - JPEGImage::JPEGImage(const RawImage& rawImage, unsigned quality) - { - LMS_SCOPED_TRACE_DETAILED("Image", "WriteJPEG"); - - auto writeCb{ [](void* ctx, void* writeData, int writeSize) { - auto& output{ *reinterpret_cast*>(ctx) }; - const std::size_t currentOutputSize{ output.size() }; - output.resize(currentOutputSize + writeSize); - std::copy(reinterpret_cast(writeData), reinterpret_cast(writeData) + writeSize, output.data() + currentOutputSize); - } }; - - if (stbi_write_jpg_to_func(writeCb, &_data, rawImage.getWidth(), rawImage.getHeight(), 3, rawImage.getData(), quality) == 0) - { - _data.clear(); - throw Exception{ "Failed to export in jpeg format!" }; - } - } - - const std::byte* JPEGImage::getData() const - { - if (_data.empty()) - return nullptr; - - return &_data.front(); - } - - std::size_t JPEGImage::getDataSize() const - { - return _data.size(); - } -} // namespace lms::image::STB diff --git a/src/libs/image/impl/stb/RawImage.cpp b/src/libs/image/impl/stb/RawImage.cpp index 3187fd08..99021bf1 100644 --- a/src/libs/image/impl/stb/RawImage.cpp +++ b/src/libs/image/impl/stb/RawImage.cpp @@ -39,21 +39,19 @@ #include "core/ITraceLogger.hpp" #include "image/Exception.hpp" -#include "JPEGImage.hpp" - namespace lms::image::STB { - RawImage::RawImage(const std::byte* encodedData, std::size_t encodedDataSize) + RawImage::RawImage(std::span encodedData) { int n{}; - _data = UniquePtrFree{ ::stbi_load_from_memory(reinterpret_cast(encodedData), encodedDataSize, &_width, &_height, &n, 3), std::free }; + _data = UniquePtrFree{ ::stbi_load_from_memory(reinterpret_cast(encodedData.data()), encodedData.size(), &_width, &_height, &n, 3), std::free }; if (!_data) throw Exception{ "Cannot load image from memory: " + std::string{ ::stbi_failure_reason() } }; } RawImage::RawImage(const std::filesystem::path& p) { - int n; + int n{}; _data = UniquePtrFree{ stbi_load(p.string().c_str(), &_width, &_height, &n, 3), std::free }; if (!_data) throw Exception{ "Cannot load image from file: " + std::string{ ::stbi_failure_reason() } }; @@ -63,7 +61,7 @@ namespace lms::image::STB { LMS_SCOPED_TRACE_DETAILED("Image", "Resize"); - size_t height; + size_t height{}; if (_width == _height) { height = width; @@ -104,11 +102,6 @@ namespace lms::image::STB _width = width; } - std::unique_ptr RawImage::encodeToJPEG(unsigned quality) const - { - return std::make_unique(*this, quality); - } - ImageSize RawImage::getWidth() const { return _width; @@ -121,9 +114,7 @@ namespace lms::image::STB const std::byte* RawImage::getData() const { - if (!_data) - return nullptr; - + assert(_data); return reinterpret_cast(_data.get()); } } // namespace lms::image::STB \ No newline at end of file diff --git a/src/libs/image/impl/stb/RawImage.hpp b/src/libs/image/impl/stb/RawImage.hpp index 044217fd..b002decb 100644 --- a/src/libs/image/impl/stb/RawImage.hpp +++ b/src/libs/image/impl/stb/RawImage.hpp @@ -21,8 +21,8 @@ #include #include +#include -#include "image/IEncodedImage.hpp" #include "image/IRawImage.hpp" namespace lms::image::STB @@ -30,7 +30,7 @@ namespace lms::image::STB class RawImage : public IRawImage { public: - RawImage(const std::byte* encodedData, std::size_t encodedDataSize); + RawImage(std::span encodedData); RawImage(const std::filesystem::path& path); ~RawImage() override = default; @@ -41,7 +41,6 @@ namespace lms::image::STB ImageSize getHeight() const override; void resize(ImageSize width) override; - std::unique_ptr encodeToJPEG(unsigned quality) const override; const std::byte* getData() const; diff --git a/src/libs/image/include/image/IEncodedImage.hpp b/src/libs/image/include/image/IEncodedImage.hpp index 9bed88c0..f14120c9 100644 --- a/src/libs/image/include/image/IEncodedImage.hpp +++ b/src/libs/image/include/image/IEncodedImage.hpp @@ -20,6 +20,7 @@ #pragma once #include +#include #include namespace lms::image @@ -31,8 +32,7 @@ namespace lms::image public: virtual ~IEncodedImage() = default; - virtual const std::byte* getData() const = 0; - virtual std::size_t getDataSize() const = 0; + virtual std::span getData() const = 0; virtual std::string_view getMimeType() const = 0; }; } // namespace lms::image \ No newline at end of file diff --git a/src/libs/image/include/image/IRawImage.hpp b/src/libs/image/include/image/IRawImage.hpp index 3347228f..ad512905 100644 --- a/src/libs/image/include/image/IRawImage.hpp +++ b/src/libs/image/include/image/IRawImage.hpp @@ -19,9 +19,7 @@ #pragma once -#include - -#include "image/IEncodedImage.hpp" +#include "image/Types.hpp" namespace lms::image { @@ -34,6 +32,5 @@ namespace lms::image virtual ImageSize getHeight() const = 0; virtual void resize(ImageSize width) = 0; - virtual std::unique_ptr encodeToJPEG(unsigned quality) const = 0; }; } // namespace lms::image diff --git a/src/libs/image/include/image/Image.hpp b/src/libs/image/include/image/Image.hpp index 7bf6592f..39fc67f8 100644 --- a/src/libs/image/include/image/Image.hpp +++ b/src/libs/image/include/image/Image.hpp @@ -30,7 +30,12 @@ namespace lms::image { void init(const std::filesystem::path& path); std::span getSupportedFileExtensions(); - std::unique_ptr decodeImage(const std::byte* encodedData, std::size_t encodedDataSize); + + std::unique_ptr decodeImage(std::span encodedData); std::unique_ptr decodeImage(const std::filesystem::path& path); - std::unique_ptr readSvgFile(const std::filesystem::path& path); + + std::unique_ptr readImage(std::span encodedData, std::string_view mimeType); + std::unique_ptr readImage(const std::filesystem::path& path); + + std::unique_ptr encodeToJPEG(const IRawImage& rawImage, unsigned quality); } // namespace lms::image \ No newline at end of file diff --git a/src/libs/image/impl/stb/JPEGImage.hpp b/src/libs/image/include/image/Types.hpp similarity index 54% rename from src/libs/image/impl/stb/JPEGImage.hpp rename to src/libs/image/include/image/Types.hpp index 3b4ad706..ccd56849 100644 --- a/src/libs/image/impl/stb/JPEGImage.hpp +++ b/src/libs/image/include/image/Types.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Emeric Poupon + * Copyright (C) 2024 Emeric Poupon * * This file is part of LMS. * @@ -17,25 +17,9 @@ * along with LMS. If not, see . */ -#pragma once +#include -#include - -#include "image/IEncodedImage.hpp" - -namespace lms::image::STB +namespace lms::image { - class RawImage; - class JPEGImage : public IEncodedImage - { - public: - JPEGImage(const RawImage& rawImage, unsigned quality); - - private: - const std::byte* getData() const override; - std::size_t getDataSize() const override; - std::string_view getMimeType() const override { return "image/jpeg"; } - - std::vector _data; - }; -} // namespace lms::image::STB + using ImageSize = std::size_t; +} \ No newline at end of file diff --git a/src/libs/services/artwork/impl/ArtworkService.cpp b/src/libs/services/artwork/impl/ArtworkService.cpp index 839e4556..e9f7f01e 100644 --- a/src/libs/services/artwork/impl/ArtworkService.cpp +++ b/src/libs/services/artwork/impl/ArtworkService.cpp @@ -19,6 +19,8 @@ #include "ArtworkService.hpp" +#include + #include "av/IAudioFile.hpp" #include "av/Types.hpp" #include "core/IConfig.hpp" @@ -31,6 +33,7 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "image/Exception.hpp" +#include "image/IEncodedImage.hpp" #include "image/Image.hpp" namespace lms::cover @@ -48,8 +51,6 @@ namespace lms::cover return std::make_unique(db, defaultReleaseCoverSvgPath, defaultArtistImageSvgPath); } - using namespace image; - ArtworkService::ArtworkService(db::Db& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath) @@ -61,13 +62,13 @@ namespace lms::cover LMS_LOG(COVER, INFO, "Default release cover path = '" << defaultReleaseCoverSvgPath.string() << "'"); LMS_LOG(COVER, INFO, "Max cache size = " << _cache.getMaxCacheSize()); - _defaultReleaseCover = image::readSvgFile(defaultReleaseCoverSvgPath); // may throw - _defaultArtistImage = image::readSvgFile(defaultArtistImageSvgPath); // may throw + _defaultReleaseCover = image::readImage(defaultReleaseCoverSvgPath); // may throw + _defaultArtistImage = image::readImage(defaultArtistImageSvgPath); // may throw } - std::unique_ptr ArtworkService::getFromAvMediaFile(const av::IAudioFile& input, ImageSize width) const + std::unique_ptr ArtworkService::getFromAvMediaFile(const av::IAudioFile& input, std::optional width) const { - std::unique_ptr image; + std::unique_ptr image; input.visitAttachedPictures([&](const av::Picture& picture, const av::IAudioFile::MetadataMap& /* metadata */) { if (image) @@ -75,9 +76,16 @@ namespace lms::cover try { - std::unique_ptr rawImage{ decodeImage(picture.data, picture.dataSize) }; - rawImage->resize(width); - image = rawImage->encodeToJPEG(_jpegQuality); + if (!width) + { + image = image::readImage(std::span{ picture.data, picture.dataSize }, picture.mimeType); + } + else + { + auto rawImage{ image::decodeImage(std::span{ picture.data, picture.dataSize }) }; + rawImage->resize(*width); + image = image::encodeToJPEG(*rawImage, _jpegQuality); + } } catch (const image::Exception& e) { @@ -88,15 +96,22 @@ namespace lms::cover return image; } - std::unique_ptr ArtworkService::getFromImageFile(const std::filesystem::path& p, ImageSize width) const + std::unique_ptr ArtworkService::getFromImageFile(const std::filesystem::path& p, std::optional width) const { - std::unique_ptr image; + std::unique_ptr image; try { - std::unique_ptr rawImage{ decodeImage(p) }; - rawImage->resize(width); - image = rawImage->encodeToJPEG(_jpegQuality); + if (!width) + { + image = image::readImage(p); + } + else + { + auto rawImage{ image::decodeImage(p) }; + rawImage->resize(*width); + image = image::encodeToJPEG(*rawImage, _jpegQuality); + } } catch (const image::Exception& e) { @@ -106,17 +121,17 @@ namespace lms::cover return image; } - std::shared_ptr ArtworkService::getDefaultReleaseCover() + std::shared_ptr ArtworkService::getDefaultReleaseCover() { return _defaultReleaseCover; } - std::shared_ptr ArtworkService::getDefaultArtistImage() + std::shared_ptr ArtworkService::getDefaultArtistImage() { return _defaultArtistImage; } - bool ArtworkService::checkImageFile(const std::filesystem::path& filePath) const + bool ArtworkService::checkImageFile(const std::filesystem::path& filePath) { std::error_code ec; @@ -132,9 +147,9 @@ namespace lms::cover return true; } - std::unique_ptr ArtworkService::getTrackImage(const std::filesystem::path& p, ImageSize width) const + std::unique_ptr ArtworkService::getTrackImage(const std::filesystem::path& p, std::optional width) const { - std::unique_ptr image; + std::unique_ptr image; try { @@ -148,77 +163,81 @@ namespace lms::cover return image; } - std::shared_ptr ArtworkService::getTrackImage(db::TrackId trackId, ImageSize width) + std::shared_ptr ArtworkService::getTrackImage(db::TrackId trackId, std::optional width) { const ImageCache::EntryDesc cacheEntryDesc{ trackId, width }; - std::shared_ptr cover{ _cache.getImage(cacheEntryDesc) }; + std::shared_ptr cover{ _cache.getImage(cacheEntryDesc) }; if (cover) return cover; + std::filesystem::path trackFile; { db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; const db::Track::pointer track{ db::Track::find(session, trackId) }; if (track && track->hasCover()) - cover = getTrackImage(track->getAbsoluteFilePath(), width); + trackFile = track->getAbsoluteFilePath(); } + cover = getTrackImage(trackFile, width); if (cover) _cache.addImage(cacheEntryDesc, cover); return cover; } - std::shared_ptr ArtworkService::getReleaseCover(db::ReleaseId releaseId, ImageSize width) + std::shared_ptr ArtworkService::getReleaseCover(db::ReleaseId releaseId, std::optional width) { - using namespace db; const ImageCache::EntryDesc cacheEntryDesc{ releaseId, width }; - std::shared_ptr image{ _cache.getImage(cacheEntryDesc) }; + std::shared_ptr image{ _cache.getImage(cacheEntryDesc) }; if (image) return image; + std::filesystem::path imagePath; { - Session& session{ _db.getTLSSession() }; + db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; const db::Release::pointer release{ db::Release::find(session, releaseId) }; if (release) { if (const db::Image::pointer dbImage{ release->getImage() }) - image = getFromImageFile(dbImage->getAbsoluteFilePath(), width); + imagePath = dbImage->getAbsoluteFilePath(); } } + image = getFromImageFile(imagePath, width); if (image) _cache.addImage(cacheEntryDesc, image); return image; } - std::shared_ptr ArtworkService::getArtistImage(db::ArtistId artistId, ImageSize width) + std::shared_ptr ArtworkService::getArtistImage(db::ArtistId artistId, std::optional width) { - using namespace db; const ImageCache::EntryDesc cacheEntryDesc{ artistId, width }; - std::shared_ptr artistImage{ _cache.getImage(cacheEntryDesc) }; + std::shared_ptr artistImage{ _cache.getImage(cacheEntryDesc) }; if (artistImage) return artistImage; + std::filesystem::path imagePath; { - Session& session{ _db.getTLSSession() }; + db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - if (const Artist::pointer artist{ Artist::find(session, artistId) }) + if (const db::Artist::pointer artist{ db::Artist::find(session, artistId) }) { if (const db::Image::pointer image{ artist->getImage() }) - artistImage = getFromImageFile(image->getAbsoluteFilePath(), width); + imagePath = image->getAbsoluteFilePath(); } } + artistImage = getFromImageFile(imagePath, width); if (artistImage) _cache.addImage(cacheEntryDesc, artistImage); diff --git a/src/libs/services/artwork/impl/ArtworkService.hpp b/src/libs/services/artwork/impl/ArtworkService.hpp index ff8fa2f1..f08452a3 100644 --- a/src/libs/services/artwork/impl/ArtworkService.hpp +++ b/src/libs/services/artwork/impl/ArtworkService.hpp @@ -48,22 +48,20 @@ namespace lms::cover ArtworkService& operator=(const ArtworkService&) = delete; private: - std::shared_ptr getTrackImage(db::TrackId trackId, image::ImageSize width) override; - std::shared_ptr getReleaseCover(db::ReleaseId releaseId, image::ImageSize width) override; - std::shared_ptr getArtistImage(db::ArtistId artistId, image::ImageSize width) override; + std::shared_ptr getTrackImage(db::TrackId trackId, std::optional width) override; + std::shared_ptr getReleaseCover(db::ReleaseId releaseId, std::optional width) override; + std::shared_ptr getArtistImage(db::ArtistId artistId, std::optional width) override; std::shared_ptr getDefaultReleaseCover() override; std::shared_ptr getDefaultArtistImage() override; void flushCache() override; void setJpegQuality(unsigned quality) override; - std::shared_ptr getTrackImage(db::Session& dbSession, db::TrackId trackId, image::ImageSize width, bool allowReleaseFallback); - std::unique_ptr getFromAvMediaFile(const av::IAudioFile& input, image::ImageSize width) const; - std::unique_ptr getFromImageFile(const std::filesystem::path& p, image::ImageSize width) const; + std::unique_ptr getFromAvMediaFile(const av::IAudioFile& input, std::optional width) const; + std::unique_ptr getFromImageFile(const std::filesystem::path& p, std::optional width) const; + std::unique_ptr getTrackImage(const std::filesystem::path& path, std::optional width) const; - std::unique_ptr getTrackImage(const std::filesystem::path& path, image::ImageSize width) const; - - bool checkImageFile(const std::filesystem::path& filePath) const; + static bool checkImageFile(const std::filesystem::path& filePath); db::Db& _db; diff --git a/src/libs/services/artwork/impl/ImageCache.cpp b/src/libs/services/artwork/impl/ImageCache.cpp index 13ffc31e..586cf4b4 100644 --- a/src/libs/services/artwork/impl/ImageCache.cpp +++ b/src/libs/services/artwork/impl/ImageCache.cpp @@ -33,21 +33,29 @@ namespace lms::cover void ImageCache::addImage(const EntryDesc& entryDesc, std::shared_ptr image) { + // cache only resized files + if (!entryDesc.size) + return; + const std::unique_lock lock{ _mutex }; - while (_cacheSize + image->getDataSize() > _maxCacheSize && !_cache.empty()) + while (_cacheSize + image->getData().size() > _maxCacheSize && !_cache.empty()) { auto itRandom{ core::random::pickRandom(_cache) }; - _cacheSize -= itRandom->second->getDataSize(); + _cacheSize -= itRandom->second->getData().size(); _cache.erase(itRandom); } - _cacheSize += image->getDataSize(); + _cacheSize += image->getData().size(); _cache[entryDesc] = image; } std::shared_ptr ImageCache::getImage(const EntryDesc& entryDesc) const { + // cache only resized files + if (!entryDesc.size) + return {}; + const std::shared_lock lock{ _mutex }; const auto it{ _cache.find(entryDesc) }; diff --git a/src/libs/services/artwork/impl/ImageCache.hpp b/src/libs/services/artwork/impl/ImageCache.hpp index 41445296..d5866930 100644 --- a/src/libs/services/artwork/impl/ImageCache.hpp +++ b/src/libs/services/artwork/impl/ImageCache.hpp @@ -20,6 +20,7 @@ #pragma once #include +#include #include #include #include @@ -40,7 +41,7 @@ namespace lms::cover { using VariantType = std::variant; VariantType id; - std::size_t size; + std::optional size; bool operator==(const EntryDesc& other) const = default; }; @@ -60,7 +61,8 @@ namespace lms::cover { std::size_t operator()(const EntryDesc& entry) const { - return std::hash{}(entry.id) ^ std::hash{}(entry.size); + assert(entry.size); // should not cache unresized images + return std::hash{}(entry.id) ^ std::hash{}(*entry.size); } }; diff --git a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp index 6bb119f0..b0592015 100644 --- a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp +++ b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp @@ -21,6 +21,7 @@ #include #include +#include #include "database/ArtistId.hpp" #include "database/ReleaseId.hpp" @@ -39,13 +40,13 @@ namespace lms::cover public: virtual ~IArtworkService() = default; - virtual std::shared_ptr getArtistImage(db::ArtistId artistId, image::ImageSize width) = 0; + virtual std::shared_ptr getArtistImage(db::ArtistId artistId, std::optional width) = 0; // no logic to fallback to release here - virtual std::shared_ptr getTrackImage(db::TrackId trackId, image::ImageSize width) = 0; + virtual std::shared_ptr getTrackImage(db::TrackId trackId, std::optional width) = 0; // no logic to fallback to track here - virtual std::shared_ptr getReleaseCover(db::ReleaseId releaseId, image::ImageSize width) = 0; + virtual std::shared_ptr getReleaseCover(db::ReleaseId releaseId, std::optional width) = 0; // Svg images dont have image "size" virtual std::shared_ptr getDefaultReleaseCover() = 0; diff --git a/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp b/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp index a6b41938..78e09410 100644 --- a/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp +++ b/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp @@ -333,8 +333,9 @@ namespace lms::api::subsonic if (!trackId && !releaseId && !artistId) throw BadParameterGenericError{ "id" }; - std::size_t size{ getParameterAs(context.parameters, "size").value_or(1024) }; - size = core::utils::clamp(size, std::size_t{ 32 }, std::size_t{ 2048 }); + std::optional size{ getParameterAs(context.parameters, "size") }; + if (size) + *size = core::utils::clamp(*size, std::size_t{ 32 }, std::size_t{ 2048 }); std::shared_ptr cover; if (trackId) @@ -353,7 +354,7 @@ namespace lms::api::subsonic return; } - response.out().write(reinterpret_cast(cover->getData()), cover->getDataSize()); + response.out().write(reinterpret_cast(cover->getData().data()), cover->getData().size()); response.setMimeType(std::string{ cover->getMimeType() }); } diff --git a/src/lms/ui/resource/ArtworkResource.cpp b/src/lms/ui/resource/ArtworkResource.cpp index 131f085c..0bf8147b 100644 --- a/src/lms/ui/resource/ArtworkResource.cpp +++ b/src/lms/ui/resource/ArtworkResource.cpp @@ -232,7 +232,7 @@ namespace lms::ui if (image) { response.setMimeType(std::string{ image->getMimeType() }); - response.out().write(reinterpret_cast(image->getData()), image->getDataSize()); + response.out().write(reinterpret_cast(image->getData().data()), image->getData().size()); } else response.setStatus(404);