diff --git a/include/SFML/Graphics/Image.hpp b/include/SFML/Graphics/Image.hpp index 46d3053d4..94dacbfdf 100644 --- a/include/SFML/Graphics/Image.hpp +++ b/include/SFML/Graphics/Image.hpp @@ -36,7 +36,6 @@ #include #include -#include #include #include @@ -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> saveToMemory(std::string_view format) const; + [[nodiscard]] std::optional> saveToMemory(SaveFormat format) const; //////////////////////////////////////////////////////////// /// \brief Return the size (width and height) of the image diff --git a/src/SFML/Graphics/Image.cpp b/src/SFML/Graphics/Image.cpp index 9d9e9228c..118b3d099 100644 --- a/src/SFML/Graphics/Image.cpp +++ b/src/SFML/Graphics/Image.cpp @@ -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> Image::saveToMemory(std::string_view format) const +std::optional> 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 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; } diff --git a/test/Graphics/Image.test.cpp b/test/Graphics/Image.test.cpp index f4857716d..e851c8413 100644 --- a/test/Graphics/Image.test.cpp +++ b/test/Graphics/Image.test.cpp @@ -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> 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")