You want to draw a dialog box. Any size & textured! Not a plain rectangle.
One solution is to just have an image with a fixed width as the box. This works until you want to draw a bigger box, stretching the whole image will give you poor results.

The way I did it in in Condorra (and I assume everyone else does it this way) is to only stretch the inner parts of the image, leaving the corners untouched.ninepatch

Basically you split the image in 9 parts, then draw each part with the following rules:

Corners: 0, 2, 6, 8
Draw them as is. No scaling is applied.
0 gets drawn in the upper left. 2 in the upper right, …

Edges: 1, 3, 5, 7
These will be either scaled horizontally (1 & 7) or vertically (3 & 5).

Middle: 4
The middle will be stretched horizontally and vertically.

 

The result:

dialog

The corners are still sharp, as expected. No apparent hint of any quality loss through stretching.

 

The Code

I created a simple helper class for setting up a ninepatch and drawing it.
You create an instance and initialize it by providing the image and the middle cell coordinates and size. From that one can do the math to get all the necessary numbers.
It’s all pseudo-code:

class NinePatch {
  Image patches[9]
  Rect middle
  float cellWidth
  float cellHeight
  float minWidth
  float minHeight

public:
  
    void init(Image source, Rect middle) {
        self.middle = middle

        cellWidth = floor((source.width() - middle.w) / 2.0)
        cellHeight = floor((source.height() - middle.h) / 2.0)
        minWidth = source.width() - middle.w
        minHeight = source.height() - middle.h

        //corners
        patches[0] = subimage(source, 0, 0, cellWidth, cellHeight)
        patches[2] = subimage(source, source.width()-cellWidth, 0, cellWidth, cellHeight)
        patches[6] = subimage(source, 0, source.height()-cellHeight, cellWidth, cellHeight)
        patches[8] = subimage(source, source.width()-cellWidth, source.height()-cellHeight, cellWidth, cellHeight)

        //horizontal
        patches[1] = subimage(source, cellWidth, 0, middle.w, cellHeight)
        patches[7] = subimage(source, cellWidth, source.height()-cellHeight, middle.w, cellHeight)

        //vertical
        patches[3] = subimage(source, 0, cellHeight, cellWidth, middle.h)
        patches[5] = subimage(source, source.Width-cellWidth, cellHeight, cellWidth, middle.h)

        //middle
        patches[4] = subimage(source, middle.x, middle.y, middle.w, middle.h)
    }

    void draw(float w, float h) {
        w = max(w, minWidth)
        h = max(h, minHeight)
        float sx = (w - minWidth) / middle.w
        float sy = (h - minHeight) / middle.h

        drawImage(patches[0], 0, 0)
        drawImage(patches[1], cellWidth, 0, sx, 1.0)
        drawImage(patches[2], w-cellWidth, 0)
        drawImage(patches[3], 0, cellHeight, 1.0, sy)
        drawImage(patches[4], cellWidth, cellHeight, sx, sy)
        drawImage(patches[5], w-cellWidth, cellHeight, 1.0, sy)
        drawImage(patches[6], 0, h-cellHeight)
        drawImage(patches[7], cellWidth, h-cellHeight, sx, 1.0)
        drawImage(patches[8], w-cellWidth, h-cellHeight)
    }

}

Please note that this code assumes the following:

  • a Rect class (x, y, w, h)
  • an Image class with width() and height() methods
  • the subimage() function returns a new image with a subpart of the passed one
  • drawImage signature: image, x, y, scaleX, scaleY
  • all images should have their anchors/handles set to the top-left (0,0)

Final Notes

Now with the instance ready, you can just call draw(width, height) and it will just work. The draw() method also makes sure that you never draw the box too small. Providing the middle rectangle lets you work out how big everything is. You could also decide that each of the 9 cells is the same width/height but I think this is far more flexible. Just move the rectangle values into a config file.