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.
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
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
_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.