Fork me on GitHub

On Fading To Black

2002-03-28 (last edition of the initial revision)

I wish for this night-time
to last for a lifetime
The darkness around me
Shores of a solar sea
Oh how I wish to go down with the sun
Sleeping
Weeping
With you

— Nightwish
Sleeping Sun

It has occurred to me that by striving ever forward, we’ve forgotten to speak about some basic things, so for this tutorial and the next one, we’ll be taking a step back and reviewing some things. You may have guessed these techniques yourself, but it never hurts to have it spelled out. Also, I thought I’d share some new thoughts on development, we’ll take that first.

Most of the source for the tutorials in the past I’ve actually written in Devpac on a real Atari, but it has now become clear to me that developing in Windows on an IBM compatible is easier and more efficient. I got the tip over at http://www.atari-forum.com/, a discussion forum for all topics Atari (where I’m one of the moderators for the coding section, yay). Have one "launcher file" with only one line

                include whateveryoursourcename.s

By doing this, you’ll assemble any source files you want, and you can edit those source files outside of Devpac, and then assemble them in Devpac. When I wrote this tutorial, I had a file named _WRAP.S that had the line include tut15.s in it. Then I used Ultraedit (my editor of choice) to edit tut15.s, I also had Devpac running under STEem. Whenever I felt like assembling my source, I just saved in Ultraedit, ALT+Tab-bed into STEem and hit ALT+a to assemble my source; smooth and easy.

Speaking of Ultraedit, there is a topic going on over at http://www.atari-forum.com/viewtopic.php?t=946 to try and work out good syntax highlighting for Atari assembly in Ultraedit (http://www.ultraedit.com/). Wow, that’s a lot of various things you wouldn’t have seen pop up in a tutorial from say 1994. Now onto the serious stuff.

The palette is an extremely powerful thing when you want to change colours quick and easy. Unfortunately it has the obvious limitation of not changing the pixels. Using the palette you can black out the screen without erasing the contents (by setting all colours to black), make things pulse (by incrementing and decrementing colour intensity) or wait with displaying a picture. Say you want to calculate a big fractal, just set the palette to all 0, calculate your fractal, then whip in the palette to display the result. The effect will be that no one will see you draw the fractal, only the final result will be shown. As we’ve been through before, there are 16 colours in the palette, the first one being the background colour, located at $ff8240. Each colour is a word long, making the palette end at $ff825e. Each word is built up like this

00000RRR0GGG0BBB

The first three bits control blue intensity, then there’s a zero bit, the next three bits control green intensity, a zero and the final (non-zero) three bits control red intensity. The maximum value you can get out of three bits is 8, and since the colour intensities are at 4 bit boundaries, they are very easy to access in hex (since each character in hex mode is a 4 bit quantity). Thus $700 means max intensity of red and zero intensity for green and blue, $444 means medium intensity for all three colours.

When they built the STe, they thought that it would be nice to have more colours in the palette, and indeed, it’s easy to just add an additional bit since that would still have the palette on a 4 bit boundary, making each colour range from 0-15. However, there was a problem, they could not add a bit in the beginning and just shift the other bits to the left, since that would mean all old palette values would in effect be shifted left one bit creating an entirely different value than was originally intended.

The solution to this problem is cunning, but unfortunate. They added the least significant bit where the zero bit used to be. This maintains backwards compatibility, and adds 8 new possible colour intensities. So the STe palette looks like this

0000rRRRgGGGbBBB

This means that $700 is still (almost) maximum intensity of red. What in the memory is perceived as the most significant bit, is in palette terms the least significant bit. This sounds very confusing perhaps, but just picture moving the uppermost bit of each colour intensity first. Let’s say then that we want the intensity between $100 and $200, this would be colour $900, since that would be

0000rRRRgGGGbBBB
0000100100000000

Which we can interpret as

0000RRRrGGGgBBBb
0000001100000000

Thus, when using the STe palette, we must think about the fact that the most significant bit for each colour, is in actuality the least significant bit. The number order for intensities, from lowest to highest is 0, 8, 1, 9, 2, A, 3, B, 4, C, 5, D, 6, E, 7, F. So if you use colour $fff, the STe will interpret this as intensity 15 for all colours, and the ST will interpret it as colour intensity 7, since the ST doesn’t care about whether the fourth bit is set or not.

That should be all there is to the palette, making full utilization of it will be up to each one. In order to do something I thought we’d just do a simple fade in effect. Fading in a picture is so much nicer than just whipping it onto screen. Fading out is also much nicer than just zapping it away, you can also fade to white and make the screen sort of flash away.

What we want is to begin with a black palette and pixel data on the screen, then increment the colour values of the palette until they reach the values intended for the picture. In order to keep things simple, I opted to skip the STe palette since there’s lots of shifting involved whenever you want to use it. So the fade will only have a maximum of 7 intensities to work with, making it a pretty bad looking fade effect.

We’ll need a copy of the original palette, and a current palette which we increment until it reaches the original. It would be tempting to compare the real palette to the current one and add $111 (one intensity of each colour) if they don’t match, but that won’t work. Say one colour is supposed to be $100, if we compare our current $000 with that, they don’t match, so we add $111 making the current colour $111, which is more than $100. Instead, we must compare each red, green and blue value individually. This can easily be done by just masking off all bits except the three controlling the intensity for either red, green or blue.

                move.l  (a7)+, a1                 ; restore a1

                and.w   #%011100000000, d0        ; mask off all but red values
                and.w   #%011100000000, d1        ; mask off all but red values

                cmp.w   d1, d0                    ; see if red is correct intensity
                beq     red_fin                   ; if not ...
                add.w   #%000100000000, d1        ; ... add one intensity of red
red_fin

Let’s assume d0 holds the real colour, and d1 holds the temporary. All bits except the ones controlling red are masked off, then values compared. If they do not match, add one to the value. The value to add will be different depending on which intensity we check for, since different intensities begin at different bit positions. That’s pretty much it, here’s the entire source

                section text

                jsr     initialise

                movem.l picture+2, d0-d7          ; put picture palette in d0-d7
                movem.l d0-d7, pal                ; copy palette to pal

                movem.l temp_pal, d0-d7           ; put current palette in d0-d7
                movem.l d0-d7, $ff8240            ; apply current palette (all 0)

                move.w  #2, -(a7)                 ; get physbase
                trap    #14
                addq.l  #2, a7

                move.l  d0, a0                    ; a0 points to screen memory
                move.l  #picture+34, a1           ; a1 points to picture

                move.l  #7999, d0                 ; 8000 longwords to a screen
loop
                move.l  (a1)+, (a0)+              ; move one longword to screen
                dbf     d0, loop

                move.l  $70, old_70               ; backup $70
                move.l  #main, $70                ; start main routine

                move.w  #7, -(a7)                 ; wait keypress
                trap    #1
                addq.l  #2, a7

                move.l  old_70, $70               ; restore $70

                jsr restore

                clr.l   -(a7)
                trap    #1

main
                move.w  sr, -(a7)                 ; backup status register
                or.w    #$0700, sr                ; disable interrupts
                movem.l d0-d7/a0-a6, -(a7)        ; backup registers

                add.l   #1, counter               ; increment counter variable
                cmp.l   #15, counter              ; only execute main sometimes
                bne     do_nothing                ; skip instructions
                clr.l   counter                   ; reset counter

                move.l  #pal, a0                  ; a0 points to values to reach
                move.l  #temp_pal, a1             ; a1 points to current values

                rept    16                        ; do for each color
                jsr     check_red                 ; see if red intensity should increase
                jsr     check_green               ; see if green intensity should increase
                jsr     check_blue                ; see if blue intensity should increase
                add.l   #2, a0                    ; point to next color
                add.l   #2, a1                    ; point to next color
                endr

                movem.l temp_pal, d0-d7           ; put current palette in d0-d7
                movem.l d0-d7, $ff8240            ; apply current palette

do_nothing
                movem.l (a7)+, d0-d7/a0-a6        ; restore registers
                move.w  (a7)+, sr                 ; restore status register
                rte                               ; finished interrupt


check_red
                move.w  (a0), d0                  ; move one final color into d0
                move.w  (a1), d1                  ; move one temp color into d1

                and.w   #%011100000000, d0        ; mask off all but red values
                and.w   #%011100000000, d1        ; mask off all but red values

                cmp.w   d1, d0                    ; see if red is correct intensity
                beq     red_fin                   ; if not ...
                add.w   #%000100000000, (a1)      ; ... add one intensity of red
red_fin
                rts

check_green
                move.w  (a0), d0                  ; move one final color into d0
                move.w  (a1), d1                  ; move one temp color into d1

                and.w   #%000001110000, d0        ; mask off all but green values
                and.w   #%000001110000, d1        ; mask off all but green values

                cmp.w   d1, d0                    ; see if green at correct intensity
                beq     green_fin                 ; if not ...
                add.w   #%000000010000, (a1)      ; ... add one intensity of green
green_fin
                rts

check_blue
                move.w  (a0), d0                  ; move one final color into d0
                move.w  (a1), d1                  ; move one temp color into d1

                and.w   #%000000000111, d0        ; mask off all but blue values
                and.w   #%000000000111, d1        ; mask off all but blue values

                cmp.w   d1, d0                    ; see if blue at correct intensity
                beq     blue_fin                  ; if not ...
                add.w   #%000000000001, (a1)      ; ... add one intensity of blue
blue_fin
                rts

                include initlib.s

                section data

old_70          dc.l    0
picture         incbin  sleepsun.pi1
counter         dc.l    0

                section bss
pal             ds.w    16
temp_pal        ds.w    16
tutorial 15 screenshot
Figure 23. Fading

First I save the palette of the picture in a storage space, then I put the temporary palette in, since the temporary palette is initialised to all 0’s, this has the effect of blacking out the screen. Next I load up the picture as described in tutorial 6 and set up the main routine.

The counter code is for delay purposes; otherwise the fade effect would hardly be visible. I make a0 point to the palette to reach, and point a1 to the temporary one. Then I check the individual intensities, and add 2 to each pointer in order to point to the next colour, repeating this for the number of colours in the palette, namely 16.

You will notice that the check sub-routines are a bit different than the one described above, I add to the value pointed to by a1, which is the current palette. It may be considered slightly bad program habit to just assume that a1 points to the current palette like that, but coding demos and assembly in general depends on tight kept code that knows what it’s doing. Besides, the tutorials aren’t really for teaching you how to make good code; they are intended as basic introductions to various coding techniques.

That’s that, one easy effect achieved by manipulating the palette. If you want to fade to white, just set the temporary palette to the real palette, and increment until you reach $777. If you want to experiment, I suggest trying to implement the effect with a STe palette instead, the included picture has a STe palette so it’s ready to go. This should involve shifting the fourth bit of each colour intensity down as the first when adding to the colour intensity, and then shift it back. For the next tutorial, I think we’ll handle full screen scrolling, without moving any picture data!