2007-12-11

Gimmie Some Skin

I think this screenshot speaks for itself:

WoopsiSkinned

Screens and windows can now be skinned, using a large number of bitmaps and the new SkinnedScreen and SkinnedWindow classes.

There are two new structs in Woopsi - ScreenSkin and WindowSkin. They contain all of the information necessary to create a skinned window or screen, such as the font to use, the bitmaps to use, the dimensions of the bitmaps, etc. Putting a skin together involves a lot of property setting but, once a skin struct has been created, it can be passed into new windows/screens via a pointer and that window/screen automatically implements all of the features of the skin.

Bitmaps necessary for screens are the title bar, and the screen depth/flip buttons in both normal and clicked states. The window requires a top-left image, top-centre image, top-right, left, right, bottom-left, bottom-centre and bottom-right (for both focused and blurred states). It also requires close and depth button bitmaps for normal and clicked states, both focused and blurred (ie. 4 bitmaps for each).

There is still some work to do - it would be nice to be able to specify on which side the various buttons appeared. The SkinnedWindow resize code doesn’t work (it’s still based on the standard gadget sizes). There are probably a few other problems that I haven’t considered.

Not too bad, though!

The skin I’ve put together illustrates one of the pitfalls with DS GUI design - the buttons are too small to click accurately. It doesn’t have separate border bitmaps for the blurred state (just re-used the pointers to the focused state), so it doesn’t appear to change when clicked.

I haven’t bothered supporting tiling. If I was putting that skin together in HTML, I’d use 1px wide background images and tile them to fill the width of an element. On the DS it’s more efficient (in terms of speed, not RAM) to just have 256px wide or 192px high images, and DMA_Copy chunks of them as needed.

I should mention that the skinning system doesn’t magically provide non-rectangular gadgets. Everything in Woopsi is a rectangle (supporting non-rectangular regions becomes exponentially more complicated), and if you put together a skin with round edges you’ll see that Woopsi just draws rectangles anyway.

Other changes are mainly fiddly. There are a few clipping fixes in the GraphicsPort. I’ve removed the SuperBitmap::newBitmapGraphicsPort() function because the GraphicsPort is inextricably linked to the DS’ physical screens (due tp top-screen y-co-ordinate calculations), so can’t really be used with the SuperBitmap. Woopsi increments its VBL counter before calling its children, as per Jeff’s request. The GraphicsPort’s text routines now handle their “colour” parameters properly. Lastly, window drag initialisation is handled by the WindowBorderTop class instead of some freaky code in the AmigaWindow class.

Comments

Jeff on 2007-12-11 at 23:44 said:

Good news: looks pretty Bad news: skin. are missing from sourceforge, but are referenced from woopsi.h

ant on 2007-12-11 at 23:54 said:

Fixed!

sin_sin on 2007-12-12 at 01:43 said:

Nice Skin! (v^-^v)? looks pretty good if more fonts are built-in & Unicode is supported~

ant on 2007-12-12 at 10:17 said:

I don’t think unicode is practical for the DS, from what I’m reading in Wikipedia. Assuming we only try to support up to the two-byte range, ignoring any other overhead, we need 15K per font if we only support 8x8 monochrome fonts.

There are 1920 codes in the first two bytes of UTF-8. If each font measures 8x8, that’s 15360 bytes minimum for a font if we limit the font to 1 bitplane, so that a value of 1 means “draw a pixel” and a value of 0 means “transparent”. If we try to support 16-bit fonts, which Woopsi currently does, the memory requirement goes up to 120K for a single font file.

If we tried to support the three-byte “Basic Multilingual Plane”, we need to accommodate 61438 character codes, which gives us 480K for monochrome fonts or 3.8MB for a 16-bit font.

Three-byte unicode is obviously never going to work, at least not with bitmap fonts, as even the monochrome font uses a huge portion of the available ROM space (16-bit fonts leave you with less than 200K for your entire program). Two-byte unicode would work, but as we’d only be providing a very limited (read: broken) subset of the standard it would probably be much more practical to:

  • Produce a standard ASCII-based font bitmap that contains the glyphs for the character set you want to use;
  • Write a Windows/lLinux/OSX/whatever program to remap your unicode text to the ASCII characters used in your font;
  • Add the remapped strings into your system.

If you really wanted to, you could make the remapping function part of the DS ROM, but that means you’ll need to port/write unicode routines for the DS.

I haven’t really got any interest in adding unicode support to Woopsi, but if anyone else wants to write it, I’ll consider adding it into the codebase.

Jeff on 2007-12-12 at 11:50 said:

I was just looking through PALib, specifically where it deals with retrieving the configured user information.

The users ‘nickname’ and ‘message’ are actually stored as UTF-16, or so it claims, but PALib converts to UTF-8 by just discarding the high-order byte, operating on the assumption that for most Roman alphabets, thats going to be close enough for government work.

Unicode is a waste of effort, for a machine with such a small memory.

Steven on 2007-12-12 at 20:21 said:

Unicode is as you say a waste of time on the DS, but needed for the far eastern markets…

A standard 8x8 mono BMF font uses about 18k (quick calc done there), each additional color adds 3 bytes to the file, or if you use a customised BMF-like file you can get that down to 2 bytes per color…

Assuming that the header for a BMF file was 20 bytes + colors (2 bytes / color) + font information for every character (0 - 255) including the non-printing ones (i.e. the dos smilies, etc) then the amout of memory used for the different Bits Per Plane (bpp) are:

1bpp - Mono - 3350 bytes 2bpp - 4 Col - 5404 bytes 4bpp - 16 Col - 9524 bytes 8bpp - 256 Col - 18196 bytes 16bpp - Full Col - 34068 Bytes

The last 2 options are not part of the standard BMF code (well none of them are normal BMF file layouts, but this is a modified version), so for the default topaz font you could, in theory at least, be able to have about 5 code pages available using just the mono file format in the space that your using for the current PAGfx implementation. This could be something as simple as a mono Glyph, EN-UK, FR, and 2 spare.

Sorry for hi-jacking this blog again - I’m just posting this information while I am also thinking about my own project (looking at implementing something simular to the above when I get around to looking at the memory usage of my project).

Jeff on 2007-12-12 at 21:59 said:

Part of the reason for using 8/16 bpp fonts is that it makes the blitting to the screen faster. 1/2/4 bpp fonts require an unpacking phase - I suspect that there are magic multiply instructions that might do it for you, but you really need a vector processor tp get decent speeds.

As to the far eastern markets, they

a) don’t buy my software b) have different hardware c) are learning English anyway…

;-)

Steven on 2007-12-12 at 22:26 said:

Actually the unpacking phase for a 1/2/4 bpp font is just as easy as the unpacking phase for a 8bpp font… and with some carful coding you can draw the font at the same speed as an 8bpp one.. 16bpp’s are the fastest as it’s a straight copy of the data…

They don’t have different hardware (unless they have the Chineese DS with it’s double sized BIOS) when it comes to a DS. I agree they are learning english, but at the same time some people may want to add the ability of a different codepage for the specialist characters for different locales..

Jeff on 2007-12-13 at 00:07 said:

The best way to hamstring a project is to dump in “people might want this” requirements.

We pissed away a bunch of time because one of our salesman said “we need to have Russian support” which involved supporting alternate codepages (more work than you might think due to other architectural issues I won’t go into). Once it was all built, we found out that “the Russians are happy using the English version” and then the kicker was that we didn’t make a sale anyway because foreign markets are a world unto themselves; you don’t win by being the best product, you win by knowing the right people.

Dealing with full Unicode support, to deal with Eastern languages, is something we’ve looked at and decided that there is no profit margin in it to support the effort that we would have to put in. Because thats how it works - whatever effort you spend has to be supported by something. It may well be that “good feeling you get when it works” - but thats insanely difficult to hold onto when you have to support a language that you don’t speak/read so you have no chance whatsoever of knowing whether you’ve done it right or not.

I’d like to see 1bit font support in principle. I’d like to see a thinner font (more like Geneva than Topaz) first though. I can achieve the latter one with a graphic editor; the former will require yet more meta-software-development, which is the dangerous path that leads you away from application development towards development tool development. Which is where your wheels start spinning and you achieve little that makes your wife say “wow”.

So, download a copy of Woopsi - it builds really really easily. And do the 1 bit font code. Ant has proven to be quite open to others contributing changes back.

Jeff on 2007-12-13 at 03:55 said:

I just realised that all references to “we” in my previous post might be misconstrued as referring to ant.

They don’t - they refer to my day job, the one that pays the bills. When we make sales.

Steven on 2007-12-13 at 05:34 said:

I was actually going to do 2 things - the font code and remove the need for PALib. I provided ant with the BMF code (which adds a little overhead to the font) and since I’ve already got a semi-useful tool for the conversion of bitmap to BMF file format I could get the font converted pretty fast…

I think I’ll have a go at that tonight after I get home from work (damm these early starts) as my own project has hit the end of it’s current design document, and I’m getting the next phase sorted now, it will just take some time (I code in some strange ways for sometimes)

And as for the “we” bit I read that as your day job from the “our salesman”. After the “Lets add russian” fiasco I hope that salesperson was nicknamed timewaster by the devs :D - or knowing what devs are like it could be something worse :D

Jeff on 2007-12-13 at 09:36 said:

Removing PA9.h is going to affect every header file - I listed all this in another thread here somewhere.

The big issue, as I see it, is keeping the POSSIBILITY of PALib, so that people have a choice.

ant on 2007-12-13 at 10:07 said:

All contributions gratefully received, as I mention in the readme file. The only requirements are that the code is generally useful, is of a high standard (or at least as good as the rest of Woopsi code), can be distributed under the BSD licence, and follows the coding style employed in the rest of the codebase. It might not be the best style, but at this point I’m sticking with it unless there’s a good reason to change. I find that imperfect but consistent style is far easier to work with than a mixture of different styles.

Oh, and don’t be upset if I tinker with the code if I think I can make improvements. I switched around the draw() function in Jeff’s Gradient gadget for example, not because there was anything wrong with it, but because I know how the clipping system works and could see an easy optimisation.

The 1bpp font would be fairly easy to achieve. All you’d need to do is create a new FontBase class that implements a few pure virtual functions (so it can be used as an interface), change the existing Font class so that it inherits from the FontBase, and create a new MonoFont (or some such) that also inherits from FontBase. All of the setColour(), etc, routines are written and can be included in the base. The only real difference between Font and MonoFont is that MonoFont needs a bool* whilst Font uses a u16*.

Oh, and then all other classes need to be modified to accept a FontBase class instead of a Font. I might have a look at this myself.

If anyone wants to remove PALib, I’d definitely appreciate their changes.

Steven on 2007-12-13 at 10:58 said:

I’ll give it a go tonight when I get in from work (I finish in a few hours time). But I really would like to know why does PALib need a 130K block of 0’s??? memset is better than that, and using the DMA functions is even better again….

ant on 2007-12-13 at 11:01 said:

FontBase and MonoFont classes are in there now. Just need to replace Font parameter in gadget constructors with FontBase.

The only differences between the Font and MonoFont classes are the type of bitmap they store (u16* and bool* respectively), the scanGlyph() function, the constructor, the getPixel() function and the getBitmap() function.

scanGlyph() needs to be overridden in all fonts as this function determines which characters within the font bitmap contain data and which don’t. getPixel() interacts with the font’s bitmap, so needs to know how to interpret the data. getBitmap() returns the bitmap pointer.

Once the gadgets work with the base class instead of the Font class, it would be trivial to add support for 2, 4 or 8bpp fonts if you decide you need them. It should also be possible to add the BMFont code fairly easily, too.

ant on 2007-12-13 at 11:24 said:

All done. Assuming everything’s gone to plan, you can pass a MonoFont pointer into all of your gadget constructors and have 1bpp fonts instead of 16bpp.

There is no overhead for this at runtime. Woopsi has to interrogate every pixel of a 16-bit font to check for the transparent colour before drawing it, and the same is true of the 1bpp font.

Fonts should be stored as arrays of bools. It would be easy to replace this with arrays of chars/shorts/whatevers and use bitmasks to extract the data, but I don’t think there’s any tangible benefit in doing so. Bools were the most obvious way for me to implement the font.

Now all I need is a tool that can convert 1bpp font data into C code…

EDIT:

If you do decide to use a 1bpp font, make sure you’ve still got the 16bpp system font in there - you need it for the glyphs.

Jeff on 2007-12-13 at 11:36 said:

Hmmm, storing them as bools is going to take the same amount of memory as bytes, I suspect. Compilers don’t usually pack things into individual bits unless you ask them to.

As such, your 1-bit font is not providing any memory advantage, just less pretty graphics. This is why I said the unpack is slightly more complicated - you really do need to do the bitmask thing if you are to benefit from the smaller bpp.

Jeff on 2007-12-13 at 11:39 said:

bool dummy[10]; dprint(“sizeof(dummy)=%d”,sizeof(dummy));

printed

sizeof(dummy)=10

as expected.

ant on 2007-12-13 at 12:30 said:

Gahh. It’s an easy fix. I’m looking at protecting the implementation of the fontbase, too - making private variables private, providing accessors, and implementing const-correctness. Looking through my old C++ code (the first thing I did was write a dynamic huffman compressor - nothing like jumping in at the deep end), it looks like I had the const business sorted out. Trying to remember how it works now.

I got everything else wrong, though - there’s a brilliant routine that does this:

if (_left != NULL) { return _left; } else { return NULL; }

Genius.

Steven on 2007-12-13 at 16:31 said:

That’s not as bad as PA Lib’s key code:

type.Anykey = (!(!((pad&2047))));

Err Anykey = not not pad? I’m not even going to guess at why that’s like that, but the logic is wrong if my logic training is correct….

Jeff on 2007-12-13 at 20:34 said:

Well, no. pad&2047 has up to 2^10 possible non-zero values, most of them greater than 1 and thus overflowing a 1-bit integer value (and a u8).

!(pad&2047) has two possible values, 0 or 1 !(!(pad&2047) has two possible values, 1 or 0

So, this is a construct that will translate “any non-zero value” into “something that fits safely into a very small integer.

(I hope this is clear. ie, u8 flag=(0xFF00 & 2047) results in flag=0, not 1)

Its subtle, and since I didn’t look at the definition of Anykey, it may be overly clever. But its definitely not a no-op.

Steven on 2007-12-13 at 21:08 said:

But the kicker is BUTTON_Y is defined as 2048…..

Jeff on 2007-12-13 at 23:47 said:

Well, hmmm. Thats what you get when you use literals rather than #defines, I guess.

Probably explains some bugs to PALib developers out there who’ve wondered why the Y button didn’t seem to work…

ant on 2007-12-13 at 23:59 said:

I wondered that in… (Checks blog archive) April. This was my fix, which I submitted to the developers (and was apparently ignored):

type.Anykey = (!(!((pad&4095))));