Specify sf::Image's save format via a scoped enumeration

This commit is contained in:
Chris Thrasher 2024-07-13 13:49:56 -06:00
parent e185f6d53e
commit 40339ff06a
No known key found for this signature in database
GPG Key ID: 56FB686C9DFC8E2C
3 changed files with 106 additions and 144 deletions

View File

@ -36,7 +36,6 @@
#include <filesystem>
#include <optional>
#include <string_view>
#include <vector>
#include <cstddef>
@ -54,6 +53,18 @@ class InputStream;
class SFML_GRAPHICS_API Image
{
public:
////////////////////////////////////////////////////////////
/// \brief Supported formats for saving
///
////////////////////////////////////////////////////////////
enum class SaveFormat
{
BMP,
TGA,
PNG,
JPG
};
////////////////////////////////////////////////////////////
/// \brief Default constructor
///
@ -220,13 +231,14 @@ public:
/// if it already exists. This function fails if the image is empty.
///
/// \param filename Path of the file to save
/// \param format Encoding format to use
///
/// \return True if saving was successful
///
/// \see saveToMemory, loadFromFile
///
////////////////////////////////////////////////////////////
[[nodiscard]] bool saveToFile(const std::filesystem::path& filename) const;
[[nodiscard]] bool saveToFile(const std::filesystem::path& filename, SaveFormat format) const;
////////////////////////////////////////////////////////////
/// \brief Save the image to a buffer in memory
@ -244,7 +256,7 @@ public:
/// \see saveToFile, loadFromMemory
///
////////////////////////////////////////////////////////////
[[nodiscard]] std::optional<std::vector<std::uint8_t>> saveToMemory(std::string_view format) const;
[[nodiscard]] std::optional<std::vector<std::uint8_t>> saveToMemory(SaveFormat format) const;
////////////////////////////////////////////////////////////
/// \brief Return the size (width and height) of the image

View File

@ -317,44 +317,34 @@ bool Image::loadFromStream(InputStream& stream)
////////////////////////////////////////////////////////////
bool Image::saveToFile(const std::filesystem::path& filename) const
bool Image::saveToFile(const std::filesystem::path& filename, SaveFormat format) const
{
// 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
const Vector2i convertedSize = Vector2i(m_size);
// Extract the extension
const std::filesystem::path extension = filename.extension();
const Vector2i convertedSize = Vector2i(m_size);
if (extension == ".bmp")
switch (format)
{
// 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";
case SaveFormat::BMP:
if (stbi_write_bmp(filename.string().c_str(), convertedSize.x, convertedSize.y, 4, m_pixels.data()))
return true;
break;
case SaveFormat::TGA:
if (stbi_write_tga(filename.string().c_str(), convertedSize.x, convertedSize.y, 4, m_pixels.data()))
return true;
break;
case SaveFormat::PNG:
if (stbi_write_png(filename.string().c_str(), convertedSize.x, convertedSize.y, 4, m_pixels.data(), 0))
return true;
break;
case SaveFormat::JPG:
if (stbi_write_jpg(filename.string().c_str(), convertedSize.x, convertedSize.y, 4, m_pixels.data(), 90))
return true;
break;
default:
assert(false && "Invalid format enumeration provided");
return false;
}
}
@ -364,44 +354,39 @@ bool Image::saveToFile(const std::filesystem::path& filename) const
////////////////////////////////////////////////////////////
std::optional<std::vector<std::uint8_t>> Image::saveToMemory(std::string_view format) const
std::optional<std::vector<std::uint8_t>> Image::saveToMemory(SaveFormat format) const
{
// 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);
const Vector2i convertedSize = Vector2i(m_size);
std::vector<std::uint8_t> buffer;
if (specified == "bmp")
switch (format)
{
// 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;
case SaveFormat::BMP:
if (stbi_write_bmp_to_func(bufferFromCallback, &buffer, convertedSize.x, convertedSize.y, 4, m_pixels.data()))
return buffer;
break;
case SaveFormat::TGA:
if (stbi_write_tga_to_func(bufferFromCallback, &buffer, convertedSize.x, convertedSize.y, 4, m_pixels.data()))
return buffer;
break;
case SaveFormat::PNG:
if (stbi_write_png_to_func(bufferFromCallback, &buffer, convertedSize.x, convertedSize.y, 4, m_pixels.data(), 0))
return buffer;
break;
case SaveFormat::JPG:
if (stbi_write_jpg_to_func(bufferFromCallback, &buffer, convertedSize.x, convertedSize.y, 4, m_pixels.data(), 90))
return buffer;
break;
default:
assert(false && "Invalid format enumeration provided");
return std::nullopt;
}
}
err() << "Failed to save image with format " << std::quoted(format) << std::endl;
err() << "Failed to save image" << std::endl;
return std::nullopt;
}

View File

@ -117,7 +117,7 @@ TEST_CASE("[Graphics] sf::Image")
SECTION("Successful load")
{
const auto memory = sf::Image({24, 24}, sf::Color::Green).saveToMemory("png").value();
const auto memory = sf::Image({24, 24}, sf::Color::Green).saveToMemory(sf::Image::SaveFormat::PNG).value();
const sf::Image image(memory.data(), memory.size());
CHECK(image.getSize() == sf::Vector2u(24, 24));
CHECK(image.getPixelsPtr() != nullptr);
@ -344,13 +344,7 @@ TEST_CASE("[Graphics] sf::Image")
SECTION("Successful load")
{
const auto memory = []()
{
sf::Image savedImage;
savedImage.resize({24, 24}, sf::Color::Green);
return savedImage.saveToMemory("png").value();
}();
const auto memory = sf::Image({24, 24}, sf::Color::Green).saveToMemory(sf::Image::SaveFormat::PNG).value();
CHECK(image.loadFromMemory(memory.data(), memory.size()));
CHECK(image.getSize() == sf::Vector2u(24, 24));
CHECK(image.getPixelsPtr() != nullptr);
@ -384,24 +378,12 @@ TEST_CASE("[Graphics] sf::Image")
{
SECTION("Invalid size")
{
CHECK(!sf::Image({10, 0}, sf::Color::Magenta).saveToFile("test.jpg"));
CHECK(!sf::Image({0, 10}, sf::Color::Magenta).saveToFile("test.jpg"));
CHECK(!sf::Image({10, 0}, sf::Color::Magenta).saveToFile("test.jpg", sf::Image::SaveFormat::JPG));
CHECK(!sf::Image({0, 10}, sf::Color::Magenta).saveToFile("test.jpg", sf::Image::SaveFormat::JPG));
}
const sf::Image image({256, 256}, sf::Color::Magenta);
SECTION("No extension")
{
CHECK(!image.saveToFile("wheresmyextension"));
CHECK(!image.saveToFile("pls/add/extension"));
}
SECTION("Invalid extension")
{
CHECK(!image.saveToFile("test.ps"));
CHECK(!image.saveToFile("test.foo"));
}
SECTION("Successful save")
{
auto filename = std::filesystem::temp_directory_path();
@ -409,27 +391,32 @@ TEST_CASE("[Graphics] sf::Image")
SECTION("To .bmp")
{
filename /= "test.bmp";
CHECK(image.saveToFile(filename));
CHECK(image.saveToFile(filename, sf::Image::SaveFormat::BMP));
}
SECTION("To .tga")
{
filename /= "test.tga";
CHECK(image.saveToFile(filename));
CHECK(image.saveToFile(filename, sf::Image::SaveFormat::TGA));
}
SECTION("To .png")
{
filename /= "test.png";
CHECK(image.saveToFile(filename));
CHECK(image.saveToFile(filename, sf::Image::SaveFormat::PNG));
}
// Cannot test JPEG encoding due to it triggering UB in stbiw__jpg_writeBits
const sf::Image loadedImage(filename);
SECTION("Filename without extension")
{
filename /= "test";
CHECK(image.saveToFile(filename, sf::Image::SaveFormat::PNG));
}
const auto loadedImage = sf::Image(filename);
CHECK(loadedImage.getSize() == sf::Vector2u(256, 256));
CHECK(loadedImage.getPixelsPtr() != nullptr);
CHECK(std::filesystem::remove(filename));
}
}
@ -437,71 +424,49 @@ TEST_CASE("[Graphics] sf::Image")
SECTION("saveToMemory()")
{
SECTION("Invalid size")
{
CHECK(!sf::Image({10, 0}, sf::Color::Magenta).saveToMemory("test.jpg"));
CHECK(!sf::Image({0, 10}, sf::Color::Magenta).saveToMemory("test.jpg"));
}
CHECK(!sf::Image({10, 0}, sf::Color::Magenta).saveToMemory(sf::Image::SaveFormat::JPG));
CHECK(!sf::Image({0, 10}, sf::Color::Magenta).saveToMemory(sf::Image::SaveFormat::JPG));
}
SECTION("Successful save")
{
const sf::Image image({16, 16}, sf::Color::Magenta);
SECTION("No extension")
SECTION("To bmp")
{
CHECK(!image.saveToMemory(""));
const auto memory = image.saveToMemory(sf::Image::SaveFormat::BMP).value();
REQUIRE(memory.size() == 1146);
CHECK(memory[0] == 66);
CHECK(memory[1] == 77);
CHECK(memory[2] == 122);
CHECK(memory[3] == 4);
CHECK(memory[1000] == 255);
CHECK(memory[1001] == 255);
CHECK(memory[1002] == 255);
CHECK(memory[1003] == 0);
}
SECTION("Invalid extension")
SECTION("To tga")
{
CHECK(!image.saveToMemory("."));
CHECK(!image.saveToMemory("gif"));
CHECK(!image.saveToMemory(".jpg")); // Supposed to be "jpg"
const auto memory = image.saveToMemory(sf::Image::SaveFormat::TGA).value();
REQUIRE(memory.size() == 98);
CHECK(memory[0] == 0);
CHECK(memory[1] == 0);
CHECK(memory[2] == 10);
CHECK(memory[3] == 0);
}
SECTION("Successful save")
SECTION("To png")
{
std::optional<std::vector<std::uint8_t>> maybeOutput;
SECTION("To bmp")
{
maybeOutput = image.saveToMemory("bmp");
REQUIRE(maybeOutput.has_value());
const auto& output = *maybeOutput;
REQUIRE(output.size() == 1146);
CHECK(output[0] == 66);
CHECK(output[1] == 77);
CHECK(output[2] == 122);
CHECK(output[3] == 4);
CHECK(output[1000] == 255);
CHECK(output[1001] == 255);
CHECK(output[1002] == 255);
CHECK(output[1003] == 0);
}
SECTION("To tga")
{
maybeOutput = image.saveToMemory("tga");
REQUIRE(maybeOutput.has_value());
const auto& output = *maybeOutput;
REQUIRE(output.size() == 98);
CHECK(output[0] == 0);
CHECK(output[1] == 0);
CHECK(output[2] == 10);
CHECK(output[3] == 0);
}
SECTION("To png")
{
maybeOutput = image.saveToMemory("png");
REQUIRE(maybeOutput.has_value());
const auto& output = *maybeOutput;
REQUIRE(output.size() == 92);
CHECK(output[0] == 137);
CHECK(output[1] == 80);
CHECK(output[2] == 78);
CHECK(output[3] == 71);
}
// Cannot test JPEG encoding due to it triggering UB in stbiw__jpg_writeBits
const auto memory = image.saveToMemory(sf::Image::SaveFormat::PNG).value();
REQUIRE(memory.size() == 92);
CHECK(memory[0] == 137);
CHECK(memory[1] == 80);
CHECK(memory[2] == 78);
CHECK(memory[3] == 71);
}
// Cannot test JPEG encoding due to it triggering UB in stbiw__jpg_writeBits
}
SECTION("Set/get pixel")