Skip to content

Source: Image Expander

Hapaxia edited this page Oct 20, 2025 · 1 revision

Introduction

<SFML 3>

This simple-to-use free-function expands the outside pixels of rectangle within an image.

One common usage for wanting to expand a part of an image is to make sure that, when used as a texture, the graphics library does not 'accidentally' choose the pixel outside of the required part/rectangle. This can cause gaps (or incorrect parts of a texture) and is a commonly noticed artifact when using tilemaps. This is one solution to this issue: separate all of the tiles by 2 pixels and then use this expander on each tile.

Any sf::Image can be used.

it must be non-const as it is modified.

Any rectangular portion of the image may be specified, including up to its edges.

it is okay for the expansion to surpass the edges of the images; out-of-bounds parts are ignored.

The amount of expansion can also be specified; this is in pixels and the default value is simply 1.

each corner pixel is used multiple times to expand in both directions.

imageExpander(image, rect); // single pixel expansion (default)
imageExpander(image, rect, 4u); // 4-pixel expansion

where image is an sf::Image (the image to process) and rect is an sf::IntRect (the rectangle portion to expand).

Code

#ifndef HAPAXIA_SFMLSNIPPETS_IMAGEEXPANDER
#define HAPAXIA_SFMLSNIPPETS_IMAGEEXPANDER

#include <SFML/Graphics.hpp>

void imageExpander(sf::Image& image, sf::IntRect rect, const std::size_t expansionAmount = 1u)
{
	if (expansionAmount == 0u)
		return;
	const sf::Vector2i imageSize(image.getSize());
	if (rect.size.x == 0)
		rect.size.x = imageSize.x;
	if (rect.size.y == 0)
		rect.size.y = imageSize.y;
	if (rect.position.x < 0)
	{
		rect.size.x += rect.position.x;
		rect.position.x = 0;
	}
	if (rect.position.y < 0)
	{
		rect.size.y += rect.position.y;
		rect.position.y = 0;
	}
	rect.size.x = std::min(rect.size.x, imageSize.x - rect.position.x);
	rect.size.y = std::min(rect.size.y, imageSize.y - rect.position.y);
	auto setPixel = [&](const sf::Vector2i position, const sf::Vector2i offset)
	{
		if ((position.x < 0) || (position.y < 0) || (position.x >= imageSize.x) || (position.y >= imageSize.y))
			return;
		image.setPixel(sf::Vector2u(position), image.getPixel(sf::Vector2u(position + offset)));
	};
	for (int i{ 0 }; i < std::max(rect.size.x, rect.size.y); ++i)
	{
		if (i < rect.size.x)
		{
			setPixel({ rect.position.x + i, rect.position.y - 1 }, { 0, 1 });
			setPixel({ rect.position.x + i, rect.position.y + rect.size.y }, { 0, -1 });
		}
		if (i < rect.size.y)
		{
			setPixel({ rect.position.x - 1, rect.position.y + i }, { 1, 0 });
			setPixel({ rect.position.x + rect.size.x, rect.position.y + i }, { -1, 0 });
		}
	}
	const sf::Vector2i topLeftCorner{ rect.position - sf::Vector2i{ 1, 1 } };
	const sf::Vector2i bottomRightCorner{ rect.position + rect.size };
	setPixel(topLeftCorner, { 1, 1 });
	setPixel(bottomRightCorner, { -1, -1 });
	setPixel({ topLeftCorner.x, bottomRightCorner.y }, { 1, -1 });
	setPixel({ bottomRightCorner.x, topLeftCorner.y }, { -1, 1 });
	if (expansionAmount > 1u)
	{
		rect.position -= { 1, 1 };
		rect.size += { 2, 2 };
		imageExpander(image, rect, expansionAmount - 1u);
	}
}

#endif // HAPAXIA_SFMLSNIPPETS_IMAGEEXPANDER

Usage

Header

To use the code above, you can simply save it as a header file and include it as necessary; it has include guards.

If you would prefer to have the code directly included in another file (header or source), you can remove the include guards (lines starting with #ifndef, #define and #endif)

Processing

Call the function and pass to it:

  • image (sf::Image, the image to process)
  • rect (sf::IntRect, the rectangular portion to expand)
  • expansionAmount (std::size_t, the amount of expansion in pixels) imageExpander(image, rect, expansionAmount);

License

This code is from https://github.com/Hapaxia/SfmlSnippets/tree/master/ImageExpander and provided under the zlib license.

It is from the GitHub repository SfmlSnippets by Hapaxia.

Example

Here is an example, showing a simple 5x5 tileset - that has tiles that are 2 pixels apart - have its individual tiles expand; this fills this 2-pixel gap. It shows the original image (left) as well as the processed image (right) but you can toggle the right image to show the original image instead by pressing the SPACE key (to see in-place comparison).

This image shows this exact example:
Example's Result
This next image shows the processed image at double size:
Example's Result

Here's the example's code:

////////////////////////////////////////////////////////////////
//
// The MIT License (MIT)
//
// Copyright (c) 2025 M.J.Silk
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////
//
//
//       ------------
//       INTRODUCTION
//       ------------
//
//   Loads a small (5x5 tiles) tileset into an sf::Image
//   Duplicates the image for comparison
//   Expands each tile in the set individually
//   Shows both original tileset and the processed side-by-side
//
//
//       --------
//       CONTROLS
//       --------
//
//   SPACE                  toggle texture for processed image (right); toggles between processed texture (default) and the original texture
//   ESC                    quit
// 
// 
//        ----
//        NOTE
//        ----
//
//    The texture is available in the resources folder, which is in the root folder. You may need to adjust the path.
//    You may also need to adjust the path of the included header ("ImageExpander.hpp") depending on your approach.
// 
//    This example is for use with SFML 3.
//
//
////////////////////////////////////////////////////////////////



#include <SFML/Graphics.hpp>

#include "../ImageExpander/ImageExpander.hpp"



int main()
{
	constexpr unsigned int gapBetweenImages{ 10u };


	// images
	sf::Image origImage{};
	if (!origImage.loadFromFile("resources/images/spaced_tiles.png"))
		return EXIT_FAILURE;
	sf::Image processedImage{ origImage };



	const sf::Vector2u windowSize{ (origImage.getSize().x * 2u) + (gapBetweenImages * 3u), origImage.getSize().y + (gapBetweenImages * 2u) };



	// expand tiles
	sf::IntRect rect{};
	rect.size = { 32, 32 };
	for (int y{ 0 }; y < 5; ++y)
	{
		rect.position.y = 34 * y;
		for (int x{ 0 }; x < 5; ++x)
		{
			rect.position.x = 34u * x;

			imageExpander(processedImage, rect);
		}
	}



	// textures
	sf::Texture origTexture{};
	sf::Texture processedTexture{};
	if ((!origTexture.loadFromImage(origImage)) || (!processedTexture.loadFromImage(processedImage)))
		return EXIT_FAILURE;



	// sprites
	sf::Sprite origSprite(origTexture);
	origSprite.setPosition({ static_cast<float>(gapBetweenImages), static_cast<float>(gapBetweenImages) });
	sf::Sprite processedSprite(processedTexture);
	processedSprite.setPosition({ origImage.getSize().x + static_cast<float>(gapBetweenImages * 2u), static_cast<float>(gapBetweenImages) });



	// flag to determine whether to show original image instead of the processed image (for in-place comparison). this can be toggled by pressing SPACE
	bool showOrig{ false };



	sf::RenderWindow window(sf::VideoMode(windowSize), "");
	while (window.isOpen())
	{
		// render
		window.clear();
		window.draw(origSprite);
		window.draw(processedSprite);
		window.display();

		// events
		while (const auto event{ window.pollEvent() })
		{
			if (event->is<sf::Event::Closed>())
				window.close();
			else if (const auto keyPressed{ event->getIf<sf::Event::KeyPressed>() })
			{
				switch (keyPressed->code)
				{
				case sf::Keyboard::Key::Escape:
					window.close();
					break;
				case sf::Keyboard::Key::Space:
					processedSprite.setTexture((showOrig = !showOrig) ? origTexture : processedTexture);
					break;
				}
			}
		}
	}
}

This example is also available on the GitHub repository, along with the texture used.

The instructions for this example are contained in comments near the top of the example code.

As visible in the code, the example is licensed using the MIT license.


Written by Hapaxia (Github | Mastodon | Bluesky | X (formerly Twitter) | SFML forum)

Clone this wiki locally