Pages: [1]
  Print  
Author Topic: Sprite Patch Scripts  (Read 441 times)
Offline (Male) Goombert
Posted on: May 01, 2016, 07:09:11 PM

Contributor
Location: Cappuccino, CA
Joined: Jan 2013
Posts: 3063

View Profile WWW
This was requested of me to write a script that can do this. Drawing sprites this way makes it easier to scale parts of them without messing up and blurring the corners. This is especially useful for rendering UI controls.

NOTE: These scripts should work in older GM versions, but if you are using Studio or ENIGMA you may want to replace control variable increments (such as += 1 in a for loop) with ++i as an optimization because preincrement is faster. If you are using ENIGMA then it is even faster to just use an int with preincrement.
Code: (GML) [Select]
// this works in old GM versions but is slower
for (i = 0; i < 3; i += 1) {
// this is faster and only works in GMS, ENIGMA, or C++
for (i = 0; i < 3; ++i) {
// this is the fastest in both ENIGMA or C++
for (int i = 0; i < 3; ++i) {

This is what a 9-patch sprite looks like:


My first attempt makes use of a single sprite and its subimages.
Code: (GML) [Select]
/// draw_sprite_patch(sprite, x, y);
/// by Robert B. Colton
///
/// draws a 9 patch sprite with x, y as the top-left corner
/// subimages are in the order: top-left, top-center, top-right,
/// middle-left, middle-center, middle-right, bottom-left, bottom-center, bottom-right

var spr = argument0;
var xx = argument1, yy = argument2;
var ww = sprite_get_width(argument0), hh = sprite_get_height(argument0);

for (i = 0; i < 3; i += 1) {
    for (j = 0; j < 3; j += 1) {
        draw_sprite(
            spr,
            j + (i * 3),
            xx + j * ww,
            yy + i * hh);
    }
}

My second attempt actually adds the arguments for scaling. If you wanted to center it around the x, y parameter you can just subtract the sprite origin from xx and yy.
Code: (GML) [Select]
/// draw_sprite_patch(sprite, x, y, stretchedWidth, stretchedHeight);
/// by Robert B. Colton
///
/// draws a 9 patch sprite with x, y as the top-left corner where the middle and
/// center row and column is stretched to the given width and height
/// subimages in the order: top-left, top-center, top-right,
/// middle-left, middle-center, middle-right, bottom-left, bottom-center, bottom-right

var spr = argument0;
var xx = argument1, yy = argument2;
var sw = argument3, sh = argument4;
var ww = sprite_get_width(spr), hh = sprite_get_height(spr);

// top-left and top-right corners do not need scaled
draw_sprite(spr, 0, xx, yy);
draw_sprite(spr, 2, xx + ww + sw, yy);

// bottom-left and bottom-right corners do not need scaled
draw_sprite(spr, 6, xx, yy + hh + sh);
draw_sprite(spr, 8, xx + ww + sw, yy + hh + sh);

// top-center and bottom-center need stretched horizontally
draw_sprite_stretched(spr, 1, xx + ww, yy, sw, hh);
draw_sprite_stretched(spr, 7, xx + ww, yy + hh + sh, sw, hh);

// middle-left and middle-right needs stretched vertically
draw_sprite_stretched(spr, 3, xx, yy + hh, ww, sh);
draw_sprite_stretched(spr, 5, xx + ww + sw, yy + hh, ww, sh);

// middle-center needs stretched horizontally and vertically
draw_sprite_stretched(spr, 4, xx + ww, yy + hh, sw, sh);

And my last attempt uses a single subimage of a single sprite.
Code: (GML) [Select]
/// draw_sprite_patch(sprite, subimg, x, y, stretchedWidth, stretchedHeight, sourceWidth, sourceHeight);
/// by Robert B. Colton
///
/// draws a 9 patch sprite with x, y as the top-left corner where the middle and center row and column
/// is stretched to the given width and height
/// subimages in the order: top-left, top-center, top-right, middle-left, middle-center, middle-right,
/// bottom-left, bottom-center, bottom-right

var spr = argument0, subimg = argument1;
var xx = argument2, yy = argument3;
var sw = argument4, sh = argument5;
var ow = argument6, oh = argument7;
var cw = (sprite_get_width(spr) - ow) / 2,
    ch = (sprite_get_height(spr) - oh) / 2;
var color = c_white, alpha = draw_get_alpha();

/// do this if you want to center the sprite around its origin
/// var xx = (sprite_get_xoffset(spr) / sprite_get_width(spr)) * (sprite_get_width(spr) - ow + sw),
///    yy = (sprite_get_yoffset(spr) / sprite_get_height(spr)) * (sprite_get_height(spr) - oh + sh);

// top-left and top-right corners do not need scaled
draw_sprite_part(spr, subimg, 0, 0, cw, ch, xx, yy);
draw_sprite_part(spr, subimg, cw + ow, 0, cw, ch, xx + cw + sw, yy);

// bottom-left and bottom-right corners do not need scaled
draw_sprite_part(spr, subimg, 0, ch + oh, cw, ch, xx, yy + ch + sh);
draw_sprite_part(spr, subimg, cw + ow, ch + oh, cw, ch, xx + cw + sw, yy + ch + sh);

// top-center and bottom-center need stretched horizontally
draw_sprite_part_ext(spr, subimg, cw, 0, ow, ch, xx + cw, yy, sw/ow, 1, color, alpha);
draw_sprite_part_ext(spr, subimg, cw, ch + oh, ow, ch, xx + cw, yy + ch + sh, sw/ow, 1, color, alpha);

// middle-left and middle-right needs stretched vertically
draw_sprite_part_ext(spr, subimg, 0, ch, cw, oh, xx, yy + ch, 1, sh/oh, color, alpha);
draw_sprite_part_ext(spr, subimg, cw + ow, ch, cw, oh, xx + cw + sw, yy + ch, 1, sh/oh, color, alpha);

// middle-center needs stretched horizontally and vertically
draw_sprite_part_ext(spr, subimg, cw, ch, ow, oh, xx + cw, yy + ch, sw/ow, sh/oh, color, alpha);

I can think of about 50 more ways that this can be done, including using the tile drawing functions, which may be faster because they are batched and have little overhead. I haven't wrote those yet but if anybody needs it done a different way, feel free to ask me and I'll write the script.
« Last Edit: May 01, 2016, 09:51:50 PM by Robert B Colton » Logged
Offline (Unknown gender) TheExDeus
Reply #1 Posted on: May 02, 2016, 03:22:29 PM

Developer
Joined: Apr 2008
Posts: 1919

View Profile
In ENIGMA you can use a function for that directly - called draw_sprite_padded.

draw_sprite_padded(int spr, int subimg, gs_scalar left, gs_scalar top, gs_scalar right, gs_scalar bottom, gs_scalar x1, gs_scalar y1, gs_scalar x2, gs_scalar y2, int color, gs_scalar alpha)

It should be relatively efficient and batch well. And it is used in BGUI extension, so I agree it is often used in GUI's.
Logged
Offline (Male) Goombert
Reply #2 Posted on: May 02, 2016, 09:52:24 PM

Contributor
Location: Cappuccino, CA
Joined: Jan 2013
Posts: 3063

View Profile WWW
I am sorry Harri I should have thought you would have used something similar for the GUI stuff.  How does that function work though? It just looks like draw_sprite_part to me.
Logged
Offline (Unknown gender) TheExDeus
Reply #3 Posted on: May 03, 2016, 04:34:51 AM

Developer
Joined: Apr 2008
Posts: 1919

View Profile
The left/top/right/bottom is padding in pixels and x1,y1,x2,y2 is the rectangle on the screen it will draw to. So in your example sprite padding would be 1/3 of image width and height.
Code: (EDL) [Select]
double left = sprite_get_width(spr)/3.0;
double top = sprite_get_height(spr)/3.0;
double right = left; //Same padding for right and bottom as top ~50 pixels in the above image
double bottom = top;
draw_sprite_padded(spr, subimg, left, top, right, bottom, 100, 100, mouse_x, mouse_y, c_white, 1.0);
This would draw the sprite from 100,100 to mouse_x,mouse_y keeping the corners and sides correct and stretching the middle. The function also takes into account cases when the given rectangle (x1,y1,x2,y2) is negative or when it is smaller than the padding. Like in the above case padding 50pixels on each side. So the drawn image will never be smaller than 100pixels in width and height even if you specify smaller rectangle.
Logged
Pages: [1]
  Print