NineSlice: more robust ninepatch

Monkey Forums/Monkey Code/NineSlice: more robust ninepatch

Raph(Posted 2014) [#1]
I think this is in decent enough shape to release right now. Basically, it's a ninepatch system specifically meant for games, which often need nicer windows than plain old ninepatches give.



Demo: http://raphkoster.com/monkey/nineslice/

You can grab the top edge of a window to drag it and the bottom right corner to resize it to see the tiling behaviors. This code doesn't do any of that though. :)

What it does:

- You can load either one image and have it sliced into thirds for you, or nine separate ones, in which case you can have them all be different sizes.
- it will draw this as a ninepatch, as usual
- you can adjust border widths.
- more importantly, it supports edges and a center that tiles, best-fits, or stretches. You can do this per edge.

TILE_STRETCH is the usual ninepatch behavior. This is what you see in the gray window in the demo.

TILE_CLIP will tile perfectly, but that means that a repeating texture will get cut off at the right and bottom edges. This is the grid pattern in the wood window.

TILE_FIT will attempt to tile perfectly, but will adjust each tile very slightly so that there is always a whole number of tiles. This is the bubble window -- both edges and center. It's also the bottom edge (the braid) in the wood window.

The wood window has all three methods used in one window -- sides and top are stretch.

The red progress bar is stretch; the blue bubble one, however, is using clip; you can see how you can get more gamelike bars with it.

Note that it also has stuff like minimum widths in there, and therefore doesn't currently handle a border being set to smaller than the actual art.

Needless to say, tiling is a ton more draw calls. So you can always query NineSlice.DrawCalls to see how many are piling up. Be sure to clear it too :)




Nobuyuki(Posted 2014) [#2]
cool


ziggy(Posted 2014) [#3]
What's a nine patch? XD


Supertino(Posted 2014) [#4]
Not tried your code Raph as I developed my own ninepatch a while back and this is not a snipe at your implementation Raph but beware of issues around grabbing an image of a grabbed image, in other words if someone is uses Atlas's and uses grab image to get the images out of that they wont be able to use grabimage on those images due to issues with what surfaces mojo\monkey refers to see. http://www.monkey-x.com/Community/posts.php?topic=2301#22522

For that reason and because I use atlas's that use grabimage I used exclusively used DrawImageRect with my ninepatch.

Just some food for thought. Your ninepatch does more than mine does so I think I'll go update =D


Paul - Taiphoz(Posted 2014) [#5]
@ziggy its a method for splitting a texture up into 9 patchs for scaling and rendering. like this.




Raph(Posted 2014) [#6]
Supertino, thanks for the heads-up! I haven't integrated this into any atlases yet so I hadn't run into that.

One of the things about releasing code like this is that I keep holding off on integrating it with stuff like atlases or creating dependencies with other code because very quickly you end up with a framework rather than a useful bit of code. :P Like, I would like to integrate this with the AnimImage stuff I posted, but as soon as I do that, I'm basically saying you need both.

I pulled this out of a GUI system I am working on only because it was easily abstracted...

Makes me wish we had a preprocessor directive that lets you query for the existence of specific modules. Anybody else running into this sort of issue?


Raph(Posted 2014) [#7]
Just went to post up a demo and saw that HTML5 shows gaps all over the place with the tiling. :P Sorry everyone, works fine in GLFW ;)


Nobuyuki(Posted 2014) [#8]
Makes me wish we had a preprocessor directive that lets you query for the existence of specific modules. Anybody else running into this sort of issue?

Nah, not really. Anything that can't be abstracted, accessed with an interface or incorporated wholesale probably doesn't belong in the code if it's meant to be reusable.

When I designed inputPointers for simpleUI, I was worried originally that it would need a scale aware pointer from autoscale/autofit to have any wide adoption. However I eventually worked around it by packing in a pointer which checks the global matrix. If you have a feature that you want to play well with your other module but still distribute them separately, then create a compatible interface between them using existing type primitives, or built in containers/classes for i/o.


muddy_shoes(Posted 2014) [#9]
Makes me wish we had a preprocessor directive that lets you query for the existence of specific modules. Anybody else running into this sort of issue?


The usual response to this sort of thing is to define an interface that your code can use to access the functionality and then provide a separate implementation of that interface for a specific module if you want. That way people can use the module on its own, with the module you've provided an interface implementation for or with other code that provides the same functionality if they provide their own implementation of your interface.

I wrote some nine-slice code a while back but never used it much. I think it worked across targets at the time at least. I'll see if I can dig it up.

By the way, I believe that the problem Supertino mentioned was fixed, or at least the change I provided was added to mojo.


ziggy(Posted 2014) [#10]
Ok! been doing this for years and didn't know it was called nine patch! Nice bit of code!


Supertino(Posted 2014) [#11]
Just went to post up a demo and saw that HTML5 shows gaps all over the place with the tiling. :P Sorry everyone, works fine in GLFW ;)


I had that same same issue, I think it's par for the course when dealing with floats on the stretchable parts, an easy fix for me was to overlap.. erm.... underlap? the stretched sides by 1 pixel on any side that meets a corner.


Raph(Posted 2014) [#12]
Code is updated, gaps are gone I think. Also fixed a couple other bugs I spotted.


Raph(Posted 2014) [#13]
Demo and screenshot added at the top.


Michael Flad(Posted 2014) [#14]
I use various variations of 9/3 patches in my games too and I'd like to make a suggestion for a small addition.

Make the drawing of the middle filler tile optional using a flag and you can use the class to draw dynamic sized frames/outlines/cursors


Raph(Posted 2014) [#15]
Nice idea. Code updated in original post. You can set .drawCenter to True or False to have it appear. I suppose extending it to any of the nine pieces might make sense in some cases, to avoid overdrawing alpha.


Derron(Posted 2014) [#16]
A more complete ninePatch system also includes 4 borders (opaque pixel lines): Android uses such a system.

top border: defines col split positions for tiles
1 | 2 | 3
4 | 5 | 6
7 | 8 | 9

left border: defines row split position for tiles:
1 2 3
-------
4 5 6
-------
7 8 9

right border/bottom border: define content area within the ninepatch.

Together with code this makes ninePatches more selfcontained (no need for further configuration except special tiling/scaling behaviour). Using the content area definition allows artists to use decorations more freely and avoids another incode-configuration (or xml/...).


@Atlases
It does not matter if you read from an atlas or single sprites as soon as you can stretch/tile image parts.


bye
Ron


Raph(Posted 2014) [#17]
Derron, the border functionality is in there, just not done with reading the pixel line. The scissor area, though, I am not a fan of making part of the NineSlice class, because one of its applications is as a building block for GUI systems, which likely define their own gadgets with their own scissors. I'd rather have the window define clip regions than the window background art define it.

I gather that in Android a NinePatch is actually a UI widget?

It wouldn't be very hard to add the pixel line functionality in...


Derron(Posted 2014) [#18]
I use this in my GUI-System (BlitzMax) too ... without much hassle. For simple usage it just redirects some work from configfiles to the artfiles.

Gadgets (or Widgets) normally consists of smaller elements - so they are compositions (except basic widgets):
Label: Basic widget (text display)
Panel: Basic widget (a background sprite or color)
Button: consists of Panel + Label (maybe some Panels for Overlays like Icons)
Input: Button extension to handle "keystroke receiving"
CheckBox: some kind of Button with no background but eg. an Overlay
Window: Outside: Panel + Label + Buttons (if it is a Windows-like window), Content: Panel
ScrollBar: Panel + 3 Buttons (up, position, down)
ScrollablePanel: Panel + 2 Scrollbars
List: ScrollablePanel, ListItems: BasicWidget with custom draw (could be images or whatever)
DropDown: Input/Button + List (hidden when closed)
...
If you need more decorations on an element, you easily could add them as "Panels" (covering all or specific areas) of the widget.

Of course you always could code it as extension from the basewidget to avoid making things "complicated" but you then loose some OOP benefits (less code for same functionality).


@Android
No it is not a widget, but some kind of "Sprite" (in my framework a Sprite can be a single part of an image or a ninepatch collection - so the enduser does not need to know what underlying technique is used: user just says: Draw this sprite at a size of (Width,Height) and the framework either stretches the image or does ninepatch-scaling).

Do not ignore that each image with 4 * pixel borders adds (2*h+2w-4) bytes of used RAM (or VideoRAM), So if you are coding at the limit, a text configuration could be the better option. Your framework then just could offer both ways.


bye
Ron