diff --git a/src/SFML/Graphics/CMakeLists.txt b/src/SFML/Graphics/CMakeLists.txt index fee5ce5f1..112b8806f 100644 --- a/src/SFML/Graphics/CMakeLists.txt +++ b/src/SFML/Graphics/CMakeLists.txt @@ -21,8 +21,6 @@ set(SRC ${SRCROOT}/GLExtensions.cpp ${SRCROOT}/Image.cpp ${INCROOT}/Image.hpp - ${SRCROOT}/ImageLoader.cpp - ${SRCROOT}/ImageLoader.hpp ${INCROOT}/PrimitiveType.hpp ${INCROOT}/Rect.hpp ${INCROOT}/Rect.inl @@ -134,8 +132,8 @@ endif() # add preprocessor symbols target_compile_definitions(sfml-graphics PRIVATE "STBI_FAILURE_USERMSG") -# ImageLoader.cpp must be compiled with the -fno-strict-aliasing +# Image.cpp must be compiled with the -fno-strict-aliasing # when gcc is used; otherwise saving PNGs may crash in stb_image_write if(SFML_COMPILER_GCC) - set_source_files_properties(${SRCROOT}/ImageLoader.cpp PROPERTIES COMPILE_FLAGS -fno-strict-aliasing) + set_source_files_properties(${SRCROOT}/Image.cpp PROPERTIES COMPILE_FLAGS -fno-strict-aliasing) endif() diff --git a/src/SFML/Graphics/Image.cpp b/src/SFML/Graphics/Image.cpp index f8162acd6..1502d6926 100644 --- a/src/SFML/Graphics/Image.cpp +++ b/src/SFML/Graphics/Image.cpp @@ -26,12 +26,19 @@ // Headers //////////////////////////////////////////////////////////// #include -#include #include +#include +#include #ifdef SFML_SYSTEM_ANDROID #include #endif + +#define STB_IMAGE_IMPLEMENTATION +#include +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include + #include #include @@ -39,6 +46,38 @@ #include +namespace +{ +// stb_image callbacks that operate on a sf::InputStream +int read(void* user, char* data, int size) +{ + auto& stream = *static_cast(user); + return static_cast(stream.read(data, size)); +} + +void skip(void* user, int size) +{ + auto& stream = *static_cast(user); + if (stream.seek(stream.tell() + size) == -1) + sf::err() << "Failed to seek image loader input stream" << std::endl; +} + +int eof(void* user) +{ + auto& stream = *static_cast(user); + return stream.tell() >= stream.getSize(); +} + +// stb_image callback for constructing a buffer +void bufferFromCallback(void* context, void* data, int size) +{ + const auto* source = static_cast(data); + auto* dest = static_cast*>(context); + std::copy(source, source + size, std::back_inserter(*dest)); +} +} // namespace + + namespace sf { //////////////////////////////////////////////////////////// @@ -109,7 +148,36 @@ bool Image::loadFromFile(const std::filesystem::path& filename) { #ifndef SFML_SYSTEM_ANDROID - return priv::ImageLoader::getInstance().loadImageFromFile(filename, m_pixels, m_size); + // Clear the array (just in case) + m_pixels.clear(); + + // Load the image and get a pointer to the pixels in memory + int width = 0; + int height = 0; + int channels = 0; + auto* ptr = stbi_load(filename.string().c_str(), &width, &height, &channels, STBI_rgb_alpha); + + if (ptr) + { + // Assign the image properties + m_size = Vector2u(Vector2i(width, height)); + + // Copy the loaded pixels to the pixel buffer + m_pixels.assign(ptr, ptr + width * height * 4); + + // Free the loaded pixels (they are now in our own pixel buffer) + stbi_image_free(ptr); + + return true; + } + else + { + // Error, failed to load the image + err() << "Failed to load image\n" + << formatDebugPathInfo(filename) << "\nReason: " << stbi_failure_reason() << std::endl; + + return false; + } #else @@ -123,27 +191,182 @@ bool Image::loadFromFile(const std::filesystem::path& filename) //////////////////////////////////////////////////////////// bool Image::loadFromMemory(const void* data, std::size_t size) { - return priv::ImageLoader::getInstance().loadImageFromMemory(data, size, m_pixels, m_size); + // Check input parameters + if (data && size) + { + // Clear the array (just in case) + m_pixels.clear(); + + // Load the image and get a pointer to the pixels in memory + int width = 0; + int height = 0; + int channels = 0; + const auto* buffer = static_cast(data); + auto* ptr = stbi_load_from_memory(buffer, static_cast(size), &width, &height, &channels, STBI_rgb_alpha); + + if (ptr) + { + // Assign the image properties + m_size = Vector2u(Vector2i(width, height)); + + // Copy the loaded pixels to the pixel buffer + m_pixels.assign(ptr, ptr + width * height * 4); + + // Free the loaded pixels (they are now in our own pixel buffer) + stbi_image_free(ptr); + + return true; + } + else + { + // Error, failed to load the image + err() << "Failed to load image from memory. Reason: " << stbi_failure_reason() << std::endl; + + return false; + } + } + else + { + err() << "Failed to load image from memory, no data provided" << std::endl; + return false; + } } //////////////////////////////////////////////////////////// bool Image::loadFromStream(InputStream& stream) { - return priv::ImageLoader::getInstance().loadImageFromStream(stream, m_pixels, m_size); + // Clear the array (just in case) + m_pixels.clear(); + + // Make sure that the stream's reading position is at the beginning + if (stream.seek(0) == -1) + { + err() << "Failed to seek image stream" << std::endl; + return false; + } + + // Setup the stb_image callbacks + stbi_io_callbacks callbacks; + callbacks.read = read; + callbacks.skip = skip; + callbacks.eof = eof; + + // Load the image and get a pointer to the pixels in memory + int width = 0; + int height = 0; + int channels = 0; + auto* ptr = stbi_load_from_callbacks(&callbacks, &stream, &width, &height, &channels, STBI_rgb_alpha); + + if (ptr) + { + // Assign the image properties + m_size = Vector2u(Vector2i(width, height)); + + // Copy the loaded pixels to the pixel buffer + m_pixels.assign(ptr, ptr + width * height * 4); + + // Free the loaded pixels (they are now in our own pixel buffer) + stbi_image_free(ptr); + + return true; + } + else + { + // Error, failed to load the image + err() << "Failed to load image from stream. Reason: " << stbi_failure_reason() << std::endl; + return false; + } } //////////////////////////////////////////////////////////// bool Image::saveToFile(const std::filesystem::path& filename) const { - return priv::ImageLoader::getInstance().saveImageToFile(filename, m_pixels, m_size); + // Make sure the image is not empty + if (!m_pixels.empty() && m_size.x > 0 && m_size.y > 0) + { + // Deduce the image type from its extension + + // Extract the extension + const std::filesystem::path extension = filename.extension(); + const Vector2i convertedSize = Vector2i(m_size); + + if (extension == ".bmp") + { + // BMP format + if (stbi_write_bmp(filename.string().c_str(), convertedSize.x, convertedSize.y, 4, m_pixels.data())) + return true; + } + else if (extension == ".tga") + { + // TGA format + if (stbi_write_tga(filename.string().c_str(), convertedSize.x, convertedSize.y, 4, m_pixels.data())) + return true; + } + else if (extension == ".png") + { + // PNG format + if (stbi_write_png(filename.string().c_str(), convertedSize.x, convertedSize.y, 4, m_pixels.data(), 0)) + return true; + } + else if (extension == ".jpg" || extension == ".jpeg") + { + // JPG format + if (stbi_write_jpg(filename.string().c_str(), convertedSize.x, convertedSize.y, 4, m_pixels.data(), 90)) + return true; + } + else + { + err() << "Image file extension " << extension << " not supported\n"; + } + } + + err() << "Failed to save image\n" << formatDebugPathInfo(filename) << std::endl; + return false; } + //////////////////////////////////////////////////////////// std::optional> Image::saveToMemory(std::string_view format) const { - return priv::ImageLoader::getInstance().saveImageToMemory(std::string(format), m_pixels, m_size); + // Make sure the image is not empty + if (!m_pixels.empty() && m_size.x > 0 && m_size.y > 0) + { + // Choose function based on format + const std::string specified = toLower(std::string(format)); + const Vector2i convertedSize = Vector2i(m_size); + + std::vector buffer; + + if (specified == "bmp") + { + // BMP format + if (stbi_write_bmp_to_func(bufferFromCallback, &buffer, convertedSize.x, convertedSize.y, 4, m_pixels.data())) + return buffer; + } + else if (specified == "tga") + { + // TGA format + if (stbi_write_tga_to_func(bufferFromCallback, &buffer, convertedSize.x, convertedSize.y, 4, m_pixels.data())) + return buffer; + } + else if (specified == "png") + { + // PNG format + if (stbi_write_png_to_func(bufferFromCallback, &buffer, convertedSize.x, convertedSize.y, 4, m_pixels.data(), 0)) + return buffer; + } + else if (specified == "jpg" || specified == "jpeg") + { + // JPG format + if (stbi_write_jpg_to_func(bufferFromCallback, &buffer, convertedSize.x, convertedSize.y, 4, m_pixels.data(), 90)) + return buffer; + } + } + + err() << "Failed to save image with format " << std::quoted(format) << std::endl; + return std::nullopt; } diff --git a/src/SFML/Graphics/ImageLoader.cpp b/src/SFML/Graphics/ImageLoader.cpp deleted file mode 100644 index 70a4c854d..000000000 --- a/src/SFML/Graphics/ImageLoader.cpp +++ /dev/null @@ -1,328 +0,0 @@ -//////////////////////////////////////////////////////////// -// -// SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2023 Laurent Gomila (laurent@sfml-dev.org) -// -// This software is provided 'as-is', without any express or implied warranty. -// In no event will the authors be held liable for any damages arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it freely, -// subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; -// you must not claim that you wrote the original software. -// If you use this software in a product, an acknowledgment -// in the product documentation would be appreciated but is not required. -// -// 2. Altered source versions must be plainly marked as such, -// and must not be misrepresented as being the original software. -// -// 3. This notice may not be removed or altered from any source distribution. -// -//////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include - -#include -#include -#include -#define STB_IMAGE_IMPLEMENTATION -#include -#define STB_IMAGE_WRITE_IMPLEMENTATION -#include - -#include -#include -#include -#include - -#include - - -namespace -{ -// stb_image callbacks that operate on a sf::InputStream -int read(void* user, char* data, int size) -{ - auto* stream = static_cast(user); - return static_cast(stream->read(data, size)); -} -void skip(void* user, int size) -{ - auto* stream = static_cast(user); - - if (stream->seek(stream->tell() + size) == -1) - sf::err() << "Failed to seek image loader input stream" << std::endl; -} -int eof(void* user) -{ - auto* stream = static_cast(user); - return stream->tell() >= stream->getSize(); -} - -// stb_image callback for constructing a buffer -void bufferFromCallback(void* context, void* data, int size) -{ - auto* source = static_cast(data); - auto* dest = static_cast*>(context); - std::copy(source, source + size, std::back_inserter(*dest)); -} -} // namespace - - -namespace sf::priv -{ -//////////////////////////////////////////////////////////// -ImageLoader& ImageLoader::getInstance() -{ - static ImageLoader instance; - - return instance; -} - - -//////////////////////////////////////////////////////////// -ImageLoader::ImageLoader() = default; - - -//////////////////////////////////////////////////////////// -bool ImageLoader::loadImageFromFile(const std::filesystem::path& filename, std::vector& pixels, Vector2u& size) -{ - // Clear the array (just in case) - pixels.clear(); - - // Load the image and get a pointer to the pixels in memory - int width = 0; - int height = 0; - int channels = 0; - unsigned char* ptr = stbi_load(filename.string().c_str(), &width, &height, &channels, STBI_rgb_alpha); - - if (ptr) - { - // Assign the image properties - size.x = static_cast(width); - size.y = static_cast(height); - - if (width > 0 && height > 0) - { - // Copy the loaded pixels to the pixel buffer - pixels.resize(static_cast(width * height * 4)); - std::memcpy(pixels.data(), ptr, pixels.size()); - } - - // Free the loaded pixels (they are now in our own pixel buffer) - stbi_image_free(ptr); - - return true; - } - else - { - // Error, failed to load the image - err() << "Failed to load image\n" - << formatDebugPathInfo(filename) << "\nReason: " << stbi_failure_reason() << std::endl; - - return false; - } -} - - -//////////////////////////////////////////////////////////// -bool ImageLoader::loadImageFromMemory(const void* data, std::size_t dataSize, std::vector& pixels, Vector2u& size) -{ - // Check input parameters - if (data && dataSize) - { - // Clear the array (just in case) - pixels.clear(); - - // Load the image and get a pointer to the pixels in memory - int width = 0; - int height = 0; - int channels = 0; - const auto* buffer = static_cast(data); - unsigned char* ptr = stbi_load_from_memory(buffer, static_cast(dataSize), &width, &height, &channels, STBI_rgb_alpha); - - if (ptr) - { - // Assign the image properties - size.x = static_cast(width); - size.y = static_cast(height); - - if (width > 0 && height > 0) - { - // Copy the loaded pixels to the pixel buffer - pixels.resize(static_cast(width * height * 4)); - std::memcpy(pixels.data(), ptr, pixels.size()); - } - - // Free the loaded pixels (they are now in our own pixel buffer) - stbi_image_free(ptr); - - return true; - } - else - { - // Error, failed to load the image - err() << "Failed to load image from memory. Reason: " << stbi_failure_reason() << std::endl; - - return false; - } - } - else - { - err() << "Failed to load image from memory, no data provided" << std::endl; - return false; - } -} - - -//////////////////////////////////////////////////////////// -bool ImageLoader::loadImageFromStream(InputStream& stream, std::vector& pixels, Vector2u& size) -{ - // Clear the array (just in case) - pixels.clear(); - - // Make sure that the stream's reading position is at the beginning - if (stream.seek(0) == -1) - { - err() << "Failed to seek image stream" << std::endl; - return false; - } - - // Setup the stb_image callbacks - stbi_io_callbacks callbacks; - callbacks.read = &read; - callbacks.skip = &skip; - callbacks.eof = &eof; - - // Load the image and get a pointer to the pixels in memory - int width = 0; - int height = 0; - int channels = 0; - unsigned char* ptr = stbi_load_from_callbacks(&callbacks, &stream, &width, &height, &channels, STBI_rgb_alpha); - - if (ptr) - { - // Assign the image properties - size.x = static_cast(width); - size.y = static_cast(height); - - if (width && height) - { - // Copy the loaded pixels to the pixel buffer - pixels.resize(static_cast(width * height * 4)); - std::memcpy(pixels.data(), ptr, pixels.size()); - } - - // Free the loaded pixels (they are now in our own pixel buffer) - stbi_image_free(ptr); - - return true; - } - else - { - // Error, failed to load the image - err() << "Failed to load image from stream. Reason: " << stbi_failure_reason() << std::endl; - - return false; - } -} - - -//////////////////////////////////////////////////////////// -bool ImageLoader::saveImageToFile(const std::filesystem::path& filename, - const std::vector& pixels, - const Vector2u& size) -{ - // Make sure the image is not empty - if (!pixels.empty() && (size.x > 0) && (size.y > 0)) - { - // Deduce the image type from its extension - - // Extract the extension - const std::filesystem::path extension = filename.extension(); - const Vector2i convertedSize = Vector2i(size); - - if (extension == ".bmp") - { - // BMP format - if (stbi_write_bmp(filename.string().c_str(), convertedSize.x, convertedSize.y, 4, pixels.data())) - return true; - } - else if (extension == ".tga") - { - // TGA format - if (stbi_write_tga(filename.string().c_str(), convertedSize.x, convertedSize.y, 4, pixels.data())) - return true; - } - else if (extension == ".png") - { - // PNG format - if (stbi_write_png(filename.string().c_str(), convertedSize.x, convertedSize.y, 4, pixels.data(), 0)) - return true; - } - else if (extension == ".jpg" || extension == ".jpeg") - { - // JPG format - if (stbi_write_jpg(filename.string().c_str(), convertedSize.x, convertedSize.y, 4, pixels.data(), 90)) - return true; - } - else - { - err() << "Image file extension " << extension << " not supported\n"; - } - } - - err() << "Failed to save image\n" << formatDebugPathInfo(filename) << std::endl; - return false; -} - -//////////////////////////////////////////////////////////// -std::optional> ImageLoader::saveImageToMemory(const std::string& format, - const std::vector& pixels, - const Vector2u& size) -{ - // Make sure the image is not empty - if (!pixels.empty() && (size.x > 0) && (size.y > 0)) - { - // Choose function based on format - const std::string specified = toLower(format); - const Vector2i convertedSize = Vector2i(size); - - std::vector buffer; - - if (specified == "bmp") - { - // BMP format - if (stbi_write_bmp_to_func(bufferFromCallback, &buffer, convertedSize.x, convertedSize.y, 4, pixels.data())) - return buffer; - } - else if (specified == "tga") - { - // TGA format - if (stbi_write_tga_to_func(&bufferFromCallback, &buffer, convertedSize.x, convertedSize.y, 4, pixels.data())) - return buffer; - } - else if (specified == "png") - { - // PNG format - if (stbi_write_png_to_func(&bufferFromCallback, &buffer, convertedSize.x, convertedSize.y, 4, pixels.data(), 0)) - return buffer; - } - else if (specified == "jpg" || specified == "jpeg") - { - // JPG format - if (stbi_write_jpg_to_func(&bufferFromCallback, &buffer, convertedSize.x, convertedSize.y, 4, pixels.data(), 90)) - return buffer; - } - } - - err() << "Failed to save image with format " << std::quoted(format) << std::endl; - return std::nullopt; -} - -} // namespace sf::priv diff --git a/src/SFML/Graphics/ImageLoader.hpp b/src/SFML/Graphics/ImageLoader.hpp deleted file mode 100644 index 0f891fe0d..000000000 --- a/src/SFML/Graphics/ImageLoader.hpp +++ /dev/null @@ -1,146 +0,0 @@ -//////////////////////////////////////////////////////////// -// -// SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2023 Laurent Gomila (laurent@sfml-dev.org) -// -// This software is provided 'as-is', without any express or implied warranty. -// In no event will the authors be held liable for any damages arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it freely, -// subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; -// you must not claim that you wrote the original software. -// If you use this software in a product, an acknowledgment -// in the product documentation would be appreciated but is not required. -// -// 2. Altered source versions must be plainly marked as such, -// and must not be misrepresented as being the original software. -// -// 3. This notice may not be removed or altered from any source distribution. -// -//////////////////////////////////////////////////////////// - -#pragma once - -//////////////////////////////////////////////////////////// -// Headers -//////////////////////////////////////////////////////////// -#include - -#include - -#include -#include -#include -#include - - -namespace sf -{ -class InputStream; - -namespace priv -{ -//////////////////////////////////////////////////////////// -/// \brief Load/save image files -/// -//////////////////////////////////////////////////////////// -class ImageLoader -{ -public: - //////////////////////////////////////////////////////////// - /// \brief Deleted copy constructor - /// - //////////////////////////////////////////////////////////// - ImageLoader(const ImageLoader&) = delete; - - //////////////////////////////////////////////////////////// - /// \brief Deleted copy assignment - /// - //////////////////////////////////////////////////////////// - ImageLoader& operator=(const ImageLoader&) = delete; - - //////////////////////////////////////////////////////////// - /// \brief Get the unique instance of the class - /// - /// \return Reference to the ImageLoader instance - /// - //////////////////////////////////////////////////////////// - static ImageLoader& getInstance(); - - //////////////////////////////////////////////////////////// - /// \brief Load an image from a file on disk - /// - /// \param filename Path of image file to load - /// \param pixels Array of pixels to fill with loaded image - /// \param size Size of loaded image, in pixels - /// - /// \return True if loading was successful - /// - //////////////////////////////////////////////////////////// - bool loadImageFromFile(const std::filesystem::path& filename, std::vector& pixels, Vector2u& size); - - //////////////////////////////////////////////////////////// - /// \brief Load an image from a file in memory - /// - /// \param data Pointer to the file data in memory - /// \param dataSize Size of the data to load, in bytes - /// \param pixels Array of pixels to fill with loaded image - /// \param size Size of loaded image, in pixels - /// - /// \return True if loading was successful - /// - //////////////////////////////////////////////////////////// - bool loadImageFromMemory(const void* data, std::size_t dataSize, std::vector& pixels, Vector2u& size); - - //////////////////////////////////////////////////////////// - /// \brief Load an image from a custom stream - /// - /// \param stream Source stream to read from - /// \param pixels Array of pixels to fill with loaded image - /// \param size Size of loaded image, in pixels - /// - /// \return True if loading was successful - /// - //////////////////////////////////////////////////////////// - bool loadImageFromStream(InputStream& stream, std::vector& pixels, Vector2u& size); - - //////////////////////////////////////////////////////////// - /// \brief Save an array of pixels as an image file - /// - /// \param filename Path of image file to save - /// \param pixels Array of pixels to save to image - /// \param size Size of image to save, in pixels - /// - /// \return True if saving was successful - /// - //////////////////////////////////////////////////////////// - bool saveImageToFile(const std::filesystem::path& filename, const std::vector& pixels, const Vector2u& size); - - //////////////////////////////////////////////////////////// - /// \brief Save an array of pixels as an encoded image buffer - /// - /// \param format Must be "bmp", "png", "tga" or "jpg"/"jpeg". - /// \param pixels Array of pixels to save to image - /// \param size Size of image to save, in pixels - /// - /// \return Buffer with encoded data if saving was successful - /// - //////////////////////////////////////////////////////////// - std::optional> saveImageToMemory(const std::string& format, - const std::vector& pixels, - const Vector2u& size); - -private: - //////////////////////////////////////////////////////////// - /// \brief Default constructor - /// - //////////////////////////////////////////////////////////// - ImageLoader(); -}; - -} // namespace priv - -} // namespace sf