Manually inline all of sf::priv::ImageLoader

This layer of indirection was unnecessary. All of its functions
were only used once somewhere inside of Image.cpp. The code is shorter
and simpler and easier to reason about if we put the implementations
of these functions directly in Image.cpp.
This commit is contained in:
Chris Thrasher 2023-10-27 21:54:59 -06:00
parent 13dd9f59cf
commit 5d0996906b
4 changed files with 231 additions and 484 deletions

View File

@ -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()

View File

@ -26,12 +26,19 @@
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Graphics/Image.hpp>
#include <SFML/Graphics/ImageLoader.hpp>
#include <SFML/System/Err.hpp>
#include <SFML/System/InputStream.hpp>
#include <SFML/System/Utils.hpp>
#ifdef SFML_SYSTEM_ANDROID
#include <SFML/System/Android/ResourceStream.hpp>
#endif
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
#include <algorithm>
#include <ostream>
@ -39,6 +46,38 @@
#include <cstring>
namespace
{
// stb_image callbacks that operate on a sf::InputStream
int read(void* user, char* data, int size)
{
auto& stream = *static_cast<sf::InputStream*>(user);
return static_cast<int>(stream.read(data, size));
}
void skip(void* user, int size)
{
auto& stream = *static_cast<sf::InputStream*>(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<sf::InputStream*>(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<std::uint8_t*>(data);
auto* dest = static_cast<std::vector<std::uint8_t>*>(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<const unsigned char*>(data);
auto* ptr = stbi_load_from_memory(buffer, static_cast<int>(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<std::vector<std::uint8_t>> 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<std::uint8_t> 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;
}

View File

@ -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 <SFML/Graphics/ImageLoader.hpp>
#include <SFML/System/Err.hpp>
#include <SFML/System/InputStream.hpp>
#include <SFML/System/Utils.hpp>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
#include <filesystem>
#include <iomanip>
#include <iterator>
#include <ostream>
#include <cstring>
namespace
{
// stb_image callbacks that operate on a sf::InputStream
int read(void* user, char* data, int size)
{
auto* stream = static_cast<sf::InputStream*>(user);
return static_cast<int>(stream->read(data, size));
}
void skip(void* user, int size)
{
auto* stream = static_cast<sf::InputStream*>(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<sf::InputStream*>(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<std::uint8_t*>(data);
auto* dest = static_cast<std::vector<std::uint8_t>*>(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<std::uint8_t>& 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<unsigned int>(width);
size.y = static_cast<unsigned int>(height);
if (width > 0 && height > 0)
{
// Copy the loaded pixels to the pixel buffer
pixels.resize(static_cast<std::size_t>(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<std::uint8_t>& 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<const unsigned char*>(data);
unsigned char* ptr = stbi_load_from_memory(buffer, static_cast<int>(dataSize), &width, &height, &channels, STBI_rgb_alpha);
if (ptr)
{
// Assign the image properties
size.x = static_cast<unsigned int>(width);
size.y = static_cast<unsigned int>(height);
if (width > 0 && height > 0)
{
// Copy the loaded pixels to the pixel buffer
pixels.resize(static_cast<std::size_t>(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<std::uint8_t>& 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<unsigned int>(width);
size.y = static_cast<unsigned int>(height);
if (width && height)
{
// Copy the loaded pixels to the pixel buffer
pixels.resize(static_cast<std::size_t>(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<std::uint8_t>& 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<std::vector<std::uint8_t>> ImageLoader::saveImageToMemory(const std::string& format,
const std::vector<std::uint8_t>& 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<std::uint8_t> 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

View File

@ -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 <SFML/Config.hpp>
#include <SFML/System/Vector2.hpp>
#include <filesystem>
#include <optional>
#include <string>
#include <vector>
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<std::uint8_t>& 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<std::uint8_t>& 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<std::uint8_t>& 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<std::uint8_t>& 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<std::vector<std::uint8_t>> saveImageToMemory(const std::string& format,
const std::vector<std::uint8_t>& pixels,
const Vector2u& size);
private:
////////////////////////////////////////////////////////////
/// \brief Default constructor
///
////////////////////////////////////////////////////////////
ImageLoader();
};
} // namespace priv
} // namespace sf