Translucency Limitations
Andy S (2979) 504 posts |
At first I thought this was a bug, but after a lot of testing I’m going to call it a limitation. Simply put, when painting in a grey colour at reduced opacity onto a 256-colour greyscale sprite, the resulting colours often aren’t what you’d expect. For example, if I make a 256-colour sprite with the Greyscale palette in Paint, and paint with the brush using colour 70 at around 86.4% opacity, when the brush stroke is merged into the sprite it looks, to my eye, a lot lighter than it should be. Colour 68, by comparison, comes out much darker at the same 86.4% opacity. As another example, if I choose colour 255, pure white, to paint onto a white sprite but with the opacity slider turned down to, say, 99% opacity, the brush stroke comes out in a light grey! It turns out that it’s because somewhere along the line with the translation table and blend table or inverse table look-ups, the range of colours seems to be reduced to a much smaller number of greys than you might expect. I’ll show what I mean with some test cases. |
Andy S (2979) 504 posts |
The following BASIC program draws a 256-colour greyscale palette, first at full opacity and then at increasing levels of translucency. If you run it in a 16M-colour screen mode, the results are very good and more or less what I would expect (Note: if you instead choose “256 greys” from the Display Manager, the results are very odd, which could be another bug):
|
Andy S (2979) 504 posts |
Now, to illustrate the main issue, I modified the above program to draw its output into a second, intermediate sprite which has the same 256-colour greyscale palette. When you run it, again in a 16M-colour screen mode, the number of greys rendered is greatly reduced this time, with lots of banding visible. Note that the banding even seems to affect the palette drawn on the left, which is at full opacity:
|
Andy S (2979) 504 posts |
I concocted some similar tests using the brush tool in Paint. I made screenshots of the greyscale Colours window and turned them into a brush. 1. The screenshot used as a brush has 16M colours and was plotted onto a 256 greyscale sprite: 2. The screenshot used as a brush has 16M colours and was plotted onto a 16M-colour sprite: 3. The screenshot used as a brush has a palette of 256 greys and was plotted onto a 256 greyscale sprite: |
Andy S (2979) 504 posts |
You can see on the first screenshot, the results were perfect at 100% opacity, but at 50% opacity the nasty banding seems to reduce it to only using eight different greys (intuitively I’d expect up to 128 greys in the ideal case). The second screenshot might as well be our control, as the results look perfect at both opacity levels, although if we’re being really picky, colour 255 seems to have become rgb(254, 254, 254) instead of pure white. On the third screenshot, the banding effect is visible even at full opacity. Could that particular issue be due to the desktop’s “256 greys” palette (used to make the screenshot) perhaps being different to Paint’s 256 “Greyscale” palette? Either way, when we drop to 50% opacity, this one looks the worst of all. At the moment I’m not completely certain what exactly is reducing the number of greys used on these images, but given the output of my two BASIC programs, I’m thinking I can stop looking for a bug in Paint for this. In my BASIC programs I did request wide translation tables, which I thought would help, but didn’t seem to. In theory the second program shouldn’t need a translation table at all to plot one sprite into the other, so it’s odd that it looks so bad at full opacity. |
Andy S (2979) 504 posts |
Regarding the terrible banding on the third screenshot, I’ve just discovered that if you make a sprite in Paint with its 256 “Greyscale” palette, draw anything onto it (I used the successful rendering of the palette that’s on the left of screenshot 1) and then use the sprite itself as a brush to paint, in Stamp mode, back onto itself (at full Opacity) then that alone is enough to introduce the banding artifact. I suspect ColourTrans must be doing it. |
Rick Murray (539) 13747 posts |
It certainly seems more to me like ColourTrans acting up. If it is doing its job correctly, it shouldn’t really matter if the palettes differ – there are 256 colours and 8 bits per colour which gives 256 levels of grey. As long as both source and destination have comparable shades, there shouldn’t really be a problem…? [aside: I wonder if ColourTrans is able to recognise greyscale palettes, or if it is treating these as colours that happen to be grey?] Anyway, if your tests in BASIC show the same behaviour as Paint, raise it as a bug and let it go. It’s not Paint, it’s elsewhere. |
David J. Ruck (33) 1585 posts |
I wonder if there is a bogus conversion to the standard 256 colour palette in there at some point, which only has 16 levels of grey, that could explain the banding. |
Andy S (2979) 504 posts |
I wonder if there is a bogus conversion to the standard 256 colour palette in there at some point, which only has 16 levels of grey, that could explain the banding. That could partly explain it, although it doesn’t explain why on the right hand side of the first screenshot, the lightest colour it selected was rgb(246, 246, 246), used from colour 168 all the way up to colour 255. I think all standard RISC OS palettes probably have white available, and it’s blending with white, so it’s odd that it should select a darker colour. [aside: I wonder if ColourTrans is able to recognise greyscale palettes, or if it is treating these as colours that happen to be grey?] It’s possible, but certainly on Paint’s Greyscale palette, the red, green and blue channels always have the same values, so in theory finding the closest match on any one of them ought to find the right level of grey. In theory. |
Rick Murray (539) 13747 posts |
Something I noticed in the ColourTrans docs: It should be noted however that when dealing with true 256 colour screen modes that bit 7 of the mode flags should be set to indicate that the mode Not sure how this applies to sprites, but it’s perhaps something to look into? https://gitlab.riscosopen.org/RiscOS/Sources/Video/Render/Colours/-/blob/master/Doc/NewSWIs084 |
Rick Murray (539) 13747 posts |
Also, it looks like 256 colour lookup table need to be built by hand, not by ColourTrans? |
Andy S (2979) 504 posts |
Also, it looks like 256 colour lookup table need to be built by hand, not by ColourTrans? If true, that sounds like a very strange limitation in itself. I wonder if that’s a technical limitation or just a case of code not having been implemented. It does say that document is 24 years old, though. One other oddity in the docs which might be relevant is on the PRM page linked from the palette pointer on the entry for ColourTrans_GenerateTable, it says: If a palette pointer is used, the palette is expected to be 2, 4, 16 or 256 words long for 1bpp, 2bpp, 4bpp or 8bpp respectively. This means that a full 256 entries are expected for old-style 64 colour modes. Palette entries should be in standard &BBGGRR00 format. I assumed that was a mistake because a 256-entry palette is normally 512 words long, not 256, because of the support for flashing colours. If it’s indeed expecting only 256 words, then my BASIC examples are wrong. Anyway, the mention of the “full 256 entries” on that page seems to imply it can generate a translation table for 256-entry palettes, in contradiction to your linked document. |
Michael Drake (88) 336 posts |
Maybe related to the algorithm Jeffrey Lee described in this post :
|
Jeffrey Lee (213) 6048 posts |
The wiki is correct; the translation tables that ColourTrans generates don’t support flashing colours. So if you’re supplying a custom 256 colour palette, it needs to contain only 256 entries. When ColourTrans uses the palette from a sprite, I think it just uses the first colour of each pair. |
Andy S (2979) 504 posts |
The wiki is correct; the translation tables that ColourTrans generates don’t support flashing colours. So if you’re supplying a custom 256 colour palette, it needs to contain only 256 entries. When ColourTrans uses the palette from a sprite, I think it just uses the first colour of each pair. Ah thanks Jeffrey. This information doesn’t seem to alter any of my test results. Paint uses ColourTrans_ReadPalette as well as its own hard-coded palettes (that don’t have flashing colours) to pass to ColourTrans_GenerateTable. I altered the following calls in my second BASIC program to pass only 256 entries instead of 512:
The output looked exactly the same to me, which makes me wonder if ColourTrans could tell it didn’t need a table lookup to plot between the two sprites with the same mode and identical palettes. |
edwardx (1628) 37 posts |
From your first program: SYS "ColourTrans_GenerateTable", sarea%, "test", -1, 0, 0, ttabflags% TO ,,,,ttabsize% DIM ttab% ttabsize% SYS "ColourTrans_GenerateTable", sarea%, "test", -1, 0, ttab%, ttabflags% You passed 0 in R3, which tells ColourTrans to use the default palette. You want the current palette, which is -1. From your second program: REM Make ttab for plotting first sprite onto the second one SYS "ColourTrans_GenerateTable", sarea%, sprite_names$(0), 28, palette%, 0, ttabflags% TO ,,,,ttabsize% DIM ttab% ttabsize% SYS "ColourTrans_GenerateTable", sarea%, sprite_names$(0), 28, palette%, ttab%, ttabflags% Mode 28 is what the ColourTrans docs called “brain-damaged 16 entries with tints”. You need to give ColourTrans a mode selector block that specifies a full 256-colour palette: DIM modesel% 40 modesel%!0=1 modesel%!4=640 modesel%!8=480 modesel%!12=3 modesel%!16=-1 modesel%!20=0 modesel%!24=1<<7 modesel%!28=3 modesel%!32=255 modesel%!36=-1 |
Andy S (2979) 504 posts |
Thanks Edward. Mode 28 is what the ColourTrans docs called “brain-damaged 16 entries with tints”. You need to give ColourTrans a mode selector block that specifies a full 256-colour palette But Paint has always selected mode 28 when you choose the Greyscale palette (and now the Colour 256-entry palette as well). Could that be part of the problem? |
Rick Murray (539) 13747 posts |
Isn’t it amusing how time and modernity allows hindsight to be performed with perfect clarity… Suffice to say, it was designed like that for a reason. A reason that may well have been a trade off between colour capabilities and hardware cost. The display system was more flexible than a simple “RGB in a byte” style displays, permitting a choice of 4096 colours. It’s just… due to complications, not many bothered to try redefining the 256 colour palette. Besides, if you want brain damaged, allow me to suggest MODE 2 on the Beeb! ;-) |
Andy S (2979) 504 posts |
Isn’t it amusing how time and modernity allows hindsight to be performed with perfect clarity… It gets called “brain-damaged” in Paint’s code comments dating back to at least the RiscPC, I think (maybe older), so I put it down to being a witticism born out of programmer frustration. I’m amused the term made it into the documentation as well, though. It’s not the most politically correct choice of terminology for modernity, though. |
Rick Murray (539) 13747 posts |
Oh, I can well understand. I am writing something at the moment that uses a 256 colour mode, and the API is a disaster. This bit here wants the colours as a byte, that bit there wants colour+tint, and that bit over there (hello ColourTrans!) wants BBGGRRxx and it’ll do it by itself. Additionally, if you’re doing this stuff in C, you don’t get access to the TINT keyword in BASIC. You’ll pull your hair out until you realise that setting colours and tints manually requires a lot of buggering around with the arcane rune tossing known as VDU commands. The tint, particularly, is stuffed into the “everything else” VDU23, with a bunch of null bytes following to take up the space. I wrote my own routines to translate between all of this junk because ColourTrans appears to lack a call that is “I want something close to this colour, tell me what colour and tint I need”. It can return a GCOL or colour, but these have the two parts combined that need to be separated out for those things (like SpriteOp set pixel) that need them apart… I noticed in another topic a mention of three different ways to scroll a window. |
Stuart Swales (8827) 1323 posts |
I did think at the time that VIDC1 8bpp modes were pretty unusable. |
David J. Ruck (33) 1585 posts |
It was an inevitable consequence of trying to do 256 colours with only 16 palette registers and 4 bits per component (4096 colours). This does not allow a fully customisable 256 colours from 16m as we have now in 256 colour palettes, or even the 3×3×2 bit RGB as used on many other systems. The tinting system was pretty awful to use by hand, but accurate colour matching and error diffused dithering could do a fair job. !ChangeFSI did that for displaying images, and various programs employed the technique for other types of graphics. I wrote !CTEnhance (ColourTrans Enhance) to do error diffused dithering of OS graphics operations to give the impression of more colours, as long as you used ColourTrans_SetGCOL or GCOL R,G,B from BASIC. |
Chris Mahoney (1684) 2160 posts |
I had ‘fun’ with VDU23 to get inverted text for my port of the SQLite shell.
|
edwardx (1628) 37 posts |
Sprites are allowed to have a 256-entry palette even if they use a legacy 8bpp mode. ColourTrans can handle this when it’s given a sprite. The problem comes when you need to make a translation table for output to a sprite. I assume Paint is extracting the mode and palette from the destination sprite and passing those to ColourTrans_GenerateTable. ColourTrans sees that the destination mode is 28, which doesn’t have bit 7 of the mode flags set, so it assumes the destination palette it was given only has 16 entries. The easiest way to get a correct translation table for outputting to a sprite is to first switch output to the sprite and then pass -1 in both R2 and R3 when you call ColourTrans_GenerateTable. If you’re not switching output to the sprite I think you’ll need to do something like this: DIM modesel% 40 modesel%!0=1 modesel%!4=640 modesel%!8=480 modesel%!12=3 modesel%!16=-1 modesel%!20=0 modesel%!24=1<<7 modesel%!28=3 modesel%!32=255 modesel%!36=-1 SYS "ColourTrans_ReadPalette", sarea%, sprite_names$(1), 0, 0, 0 TO ,,,destpalsize% DIM destpal% destpalsize% SYS "ColourTrans_ReadPalette", sarea%, sprite_names$(1), destpal%, destpalsize%, 0 SYS "OS_SpriteOp", 256 + 37, sarea%, sprite_names$(1), -1 TO ,,,palsize%,,destmode% IF palsize% = 256 THEN destmode% = modesel% ENDIF SYS "ColourTrans_GenerateTable", sarea%, sprite_names$(0), destmode%, destpal%, 0, ttabflags% TO ,,,,ttabsize% DIM ttab% ttabsize% SYS "ColourTrans_GenerateTable", sarea%, sprite_names$(0), destmode%, destpal%, ttab%, ttabflags% I haven’t tested that properly though. |