1
\$\begingroup\$

I would like to scale or resize my texture2d but when I do it, the texture border becomes stretched.

Here's an example :

This is my original texture

enter image description here

And this the texture after I change the Scale:

enter image description here

The texture's border becomes stretched compared to the original, and I would like to preserve exactly the same border than the original texture.

Is there a way to re-scale a texture without having it stretched?

If so, could you help me find the best way to do it?

\$\endgroup\$
  • \$\begingroup\$ Although I don't know about monogame but the scenario you are asking about called "9 slice scaling" in which image borders retain to specific sizes and scale the rest of the inner image. You can search it with this line of action. "9 slice scaling" \$\endgroup\$ – Hamza Hasan Jan 27 '16 at 12:15
  • \$\begingroup\$ for concept you can check this link \$\endgroup\$ – Hamza Hasan Jan 27 '16 at 12:17
  • \$\begingroup\$ I have upload new images, now the border can be seen easily @HamzaHasan Thank I will check this \$\endgroup\$ – MrMordem Jan 27 '16 at 13:07
  • \$\begingroup\$ I implemented 9 patch scaling very recently in MonoGame.Extended. I'll be happy to answer this question in more detail in the morning github.com/craftworkgames/MonoGame.Extended/blob/master/Source/… \$\endgroup\$ – craftworkgames Jan 27 '16 at 14:05
  • \$\begingroup\$ I'll be happy to read this, I check your link thank \$\endgroup\$ – MrMordem Jan 27 '16 at 14:47
1
\$\begingroup\$

So here's the deal with 9 patches (or as @hamza-hasan called them in the comments "9 slice scaling"). Either way the concept the same.

The concept is pretty simple. Slice your source image up into 9 sections as shown in the image below. For reference I've drawn 4 red lines to represent how the texture might be sliced up to create the 9 patches. I've also numbered the patches 0 to 8.

enter image description here

It's worth noting that I didn't align the red lines with the exact edges where the black meets the blue. You could do this if you want, but it's not strictly necessary. In many cases the lines can be pretty rough estimates and it'll still work just fine.

Obviously, the goal is the be able to make the source image bigger (wider and taller) without stretching the borders. The 9 patch method does a pretty good job, but just keep in mind that the middle (4) and the sides (1,3,5,7) will still stretch. Depending on your texture, the effect might be great, or it could still look stretched. You sometimes have to fiddle with the numbers to get it to look okay.

When you render the box, typically the corners (0,2,6,8) won't stretch, the top/bottom (1,7) will stretch horizontally, left/right (3,5) will stretch vertically and the middle (4) will stretch in both directions.

There's a couple of ways to think about these 9 patches. One way is to think about them as rectangles, which they are, but another way is to just think about the distance between the lines and the edge of the image. That's what I've done in the following algorithm from MonoGame.Exteneded.

private Rectangle[] CreatePatches(Rectangle rectangle)
{
    var x = rectangle.X;
    var y = rectangle.Y;
    var w = rectangle.Width;
    var h = rectangle.Height;
    var middleWidth = w - LeftPadding - RightPadding;
    var middleHeight = h - TopPadding - BottomPadding;
    var bottomY = y + h - BottomPadding;
    var rightX = x + w - RightPadding;
    var leftX = x + LeftPadding;
    var topY = y + TopPadding;
    var patches = new[]
    {
        new Rectangle(x,      y,        LeftPadding,  TopPadding),      // top left
        new Rectangle(leftX,  y,        middleWidth,  TopPadding),      // top middle
        new Rectangle(rightX, y,        RightPadding, TopPadding),      // top right
        new Rectangle(x,      topY,     LeftPadding,  middleHeight),    // left middle
        new Rectangle(leftX,  topY,     middleWidth,  middleHeight),    // middle
        new Rectangle(rightX, topY,     RightPadding, middleHeight),    // right middle
        new Rectangle(x,      bottomY,  LeftPadding,  BottomPadding),   // bottom left
        new Rectangle(leftX,  bottomY,  middleWidth,  BottomPadding),   // bottom middle
        new Rectangle(rightX, bottomY,  RightPadding, BottomPadding)    // bottom right
    };
    return patches;
}

What it does is take a source rectangle and split it up into 9 rectangles representing the patches. The cool thing is you can use the same method for both the source texture and the final render.

For example, to get the source rectangles you can run it over the rectangle representing the input texture. (I'm using a TextureRegion here from MonoGame.Extended, standard MonoGame doesn't have texture regions but the same principle applies for Texture2D.Bounds)

_sourcePatches = CreatePatches(textureRegion.Bounds);

Then when you want to render the final image you can generate a new set of patches for the sprite batch. Since the same algorithm is used for the input and output rectangles, the indices of the array of rectangles should match up and the render becomes trivial.

public void Draw(SpriteBatch spriteBatch, Rectangle rectangle)
{
    var destinationPatches = CreatePatches(rectangle);

    for (var i = 0; i < _sourcePatches.Length; i++)
    {
        spriteBatch.Draw(TextureRegion.Texture, sourceRectangle: _sourcePatches[i],
            destinationRectangle: destinationPatches[i], color: Color);
    }
}

Note 1: Running the CreatePatches method every frame could have an impact on performance. There's potentially some optimizations to be done by caching the patches if the destination rectangle hasn't changed.

Note 2: You can also create variations of this by changing the number of patches. For example, you can remove the top and bottom sections if you only need to scale left and right. In which case there would only be 3 patches. The algorithm could be modified to remove rectangles with a height or width of zero in this case.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.