Enterprise Forever

:UK => Programming => Topic started by: ssr86 on 2015.November.10. 22:26:46

Title: Some palette-swapping methods for sprites
Post by: ssr86 on 2015.November.10. 22:26:46
Imagine a team-sports game where one team should have red uniforms and the other blue. Or maybe you'd want to have some of the baddies appear in different colors? Or just have some sprites(/letters/digits) appear to flash... One way to obtain this effect would be using a lookup-table for translating each byte of the drawn sprite to a new "reordered" color palette. You could also use a separate set of the same sprites but with changed color usages I present here one other method for this. It's somewhat limited but maybe someone finds it useful nonetheless.

Basically the method could be broken into three steps:

1. store all data for the sprite rotated one bit left (right) - like performing a rlca (rrca) instruction on all its bytes
2. before drawing a byte of the sprite data rotate it using rlca (rrca)
3. have a separate drawing routine for each "palette".

Below I assume using mode 0.

The table shows how the colors change when we rotate data stored in standard form (not pre-rotated):

Code: [Select]
3-2-1-0  || 2-1-0-3 || 0-3-2-1 || 1-0-3-2
 standard || 2x_rlca || 2x_rrca || 4x_rrca = 4x_rlca
    0     ||    0    ||    0    ||    0
    1     ||    2    ||    8    ||    4
    2     ||    4    ||    1    ||    8
    3     ||    6    ||    9    ||    c
    4     ||    8    ||    2    ||    1
    5     ||    a    ||    a    ||    5
    6     ||    c    ||    3    ||    9
    7     ||    e    ||    b    ||    d
    8     ||    1    ||    4    ||    2
    9     ||    3    ||    c    ||    6
    a     ||    5    ||    5    ||    a
    b     ||    7    ||    d    ||    e
    c     ||    9    ||    6    ||    3
    d     ||    b    ||    e    ||    7
    e     ||    d    ||    7    ||    b
    f     ||    f    ||    f    ||    f
Note that color 0 and f are constant for all 4 palettes.
Standard order of color bits in a mode 0 pixel is 3-2-1-0, hence I write 3-2-1-0 to denote the standard palette. Rotating the byte by 2 bits will change the order of the color bits of pixels without repositiong the pixels (as when you rotate the pixelbyte by an odd number of bits).

Why storing the data in pre-rotated form?
The initial rotation allows us to even the drawing times for 2 of the possible palettes - the standard palette [3-2-1-0] and 2x_rlca [2-1-0-3] or 2x_rrca [0-3-2-1] - depending on the direction of the initial rotation of data. Both drawing routines need then just one additional rotation instruction. Although for sprites that wouldn't use palette changes it'd be better to store them normally and have a separate standard drawing routine that doesn't use the additional rotation...

Code for drawing one opaque pixel byte:
Code: [Select]
;; hl=^sprite_data
;; de=^screen
ld a,(hl)
ld (de),a
inc de
inc hl

Code for drawing one masked pixel byte:

Code: [Select]
;; hl=^sprite_data
;; de=^screen
;; bc=^mask_table
ld c,(hl)    ;; get pixel byte and use it as mask_table offset
ld a,(bc)    ;; get mask of the pixel byte
and (hl)    ;; mask it
or c           ;;
rlca/rrca    ;; rotate it
ld (de),a    ;; save to screen
inc de        ;; go to next screen position
inc hl        ;; go to next pixel byte

Without the preliminary rotation of data, one routine would need 2 rotations and the other none.
[Note that in the case of routine with one rotation we could also try and use self modyfying code to change all rrca to rlca or vice versa... Although I don't know if this would be worth anything...]
The other two palettes need 3 rotations of each byte, so they're are slower to obtain.

The code for drawing one pixel byte:
Code: [Select]
;; hl=^sprite_data
;; de=^screen
ld a,(hl)
ld (de),a
inc de
inc hl

In other words:
There are 4 such "palettes" in mode 0 (2 in mode 1) but only 2 can be obtained with just 1 additional instruction (rlca/rrca) in the pixel-byte drawing code. The other two would need adding three rotation instructions to the drawing code. Of course if we would have only one set of sprites (say the player character) that is unique to the game (that is no other character in the game uses his data) then we could use one separate routine that rotates each byte of the sprite data buffer by 2 bits for changing his palette permanently... Repeating this operation four times would return the sprite to its standard palette.

Such routine would run the code below for each byte of the sprite data:
Code: [Select]
;; hl=^sprite_data
ld a,(hl)    ;; get sprite data
rlca/rrca    ;; rotate 1 bit
rlca/rrca    ;; rotate 1 bit
ld (hl),a    ;; save sprite data
inc hl

Other possible usage:

This method allows "color-flashing" of a sprite. This could be achieved in three ways:
1. Adding a separate routine that would rotate every byte of the sprite data (the sprite's buffer). After four calls the sprite would return to its normal palette.
2. Similar to the above but this time we want the subroutine to rotate the screen data where the sprite was drawn. This assumes that you are not redrawing the sprite during the flashing (because you would erase the effect). Also you can't touch the bytes that where masked or skipped during drawing of the sprite because you would corrupt the background.
I think this method should be useful if you wanted to make the time or score display flashing. If the timer would be redrawn once per second then you could rotate the screen data bytes where the digits were drawn - if the digits were not masked... Although I think that it is possible to modificate the routine for masked sprites - you would have to skip rotating the transparent bytes of the sprite and for the masked bytes you would have to mask them again to obtain the part of the byte which belongs to the sprite, rotate that and combine it with the background part of the byte...
3. Having all 4 drawing subroutines and drawing the sprite using a different routine every time. 


Comparison to the other two approaches (that I could think of):
1. using lookup table
- with the presented method you don't need lookup tables so you save some memory (each table would take 256 bytes and you need at least one)
- both methods would need a separate routine for each palette, although with lookup tables you could actually get away with just one routine by changing the pointer to the color conversion table before each call
- lookup tables allow more freedom with the palette choices (only four restricted palettes for the presented method and you have to choose your initial palette wisely)
- using lookup tables is slower

2. one set of sprites for each palette
- would be the fastest approach but the memory cost would be just too expensive for heavily animated objects

Other disadvantages:
- can't use ldi for drawing opaque bytes
Title: Re: Some palette-swapping methods for sprites
Post by: ssr86 on 2015.November.10. 22:28:22
Instead of bit rotations we could or/and eventually xor. This method would be have equal execution times for all possible palettes. It won't be faster than the rlca/rrca method as we also add one additional instruction to the code for drawing a byte of the sprite data. If we have a free register that we can use to load the value to use for the additional logical operation then the time would be the same as for the rotation method. If we don't have a spare register to use then we would have to use the more expensive or/and/xor value which would add an additional nop for each byte...
Also note that the resulting palettes for or/and won't have 16 different colors... however all xor palettes for are 16 color.

If we were drawing masked sprites then we have to choose the x such that the color used for transparency will stay unchanged... But maybe I'm wrong here...

The only rule is that the or/and byte value has to be of the form xxyyzztt (that is if you don't want to end up with a different palette for the right pixels and left pixels).
So there are 16 possibilities for each logical operation, but many of them are not very useful...

In the table below I list all the possible reorderings:
(x[=x3-x2-x1-x0] means that the used value is %x3-x3-x2-x2-x1-x1-x0-x0)
Code: [Select]
 x    color     ORx      ANDx     XORx
                [id]      [1]      [id]
0000  0-1-2-3  0-1-2-3  0-0-0-0  0-1-2-3
      4-5-6-7  4-5-6-7  0-0-0-0  4-5-6-7
      8-9-a-b  8-9-a-b  0-0-0-0  8-9-a-b
      c-d-e-f  c-d-e-f  0-0-0-0  c-d-e-f

                 [8]      [2]      [16]
0001  0-1-2-3  1-1-3-3  0-1-0-1  1-0-2-3  
      4-5-6-7  5-5-7-7  0-1-0-1  5-4-7-6
      8-9-a-b  9-9-b-b  0-1-0-1  9-8-b-a
      c-d-e-f  d-d-f-f  0-1-0-1  d-c-f-e

                 [8]      [2]      [16]
0010  0-1-2-3  2-3-2-3  0-0-2-2  2-3-0-1
      4-5-6-7  6-7-6-7  0-0-2-2  6-7-4-5
      8-9-a-b  a-b-a-b  0-0-2-2  a-b-8-9
      c-d-e-f  e-f-e-f  0-0-2-2  e-f-c-d

                 [4]      [4]      [16]
0011  0-1-2-3  3-3-3-3  0-1-2-3  3-2-1-0
      4-5-6-7  7-7-7-7  0-1-2-3  7-6-5-4
      8-9-a-b  b-b-b-b  0-1-2-3  b-a-9-8
      c-d-e-f  f-f-f-f  0-1-2-3  f-e-d-c

                 [8]      [2]      [16]
0100  0-1-2-3  4-5-6-7  0-0-0-0  4-5-6-7
      4-5-6-7  4-5-6-7  4-4-4-4  0-1-2-3
      8-9-a-b  c-d-e-f  0-0-0-0  c-d-e-f
      c-d-e-f  c-d-e-f  4-4-4-4  8-9-a-b

                 [4]      [4]      [16]
0101  0-1-2-3  5-5-7-7  0-1-0-1  5-4-7-6
      4-5-6-7  5-5-7-7  4-5-4-5  1-0-3-2
      8-9-a-b  d-d-e-e  0-1-0-1  d-c-f-e
      c-d-e-f  d-d-e-e  4-5-4-5  9-8-d-c

                 [4]      [4]      [16]
0110  0-1-2-3  6-7-6-7  0-0-2-2  6-7-4-5
      4-5-6-7  6-7-6-7  4-4-6-6  2-3-0-1
      8-9-a-b  e-f-e-f  0-0-2-2  e-f-c-d
      c-d-e-f  e-f-e-f  4-4-6-6  a-b-8-9

                 [2]      [8]      [16]
0111  0-1-2-3  7-7-7-7  0-1-2-3  7-6-5-4
      4-5-6-7  7-7-7-7  4-5-6-7  3-2-1-0
      8-9-a-b  f-f-f-f  0-1-2-3  f-e-d-c
      c-d-e-f  f-f-f-f  4-5-6-7  7-6-5-4

                 [8]      [2]      [16]
1000  0-1-2-3  8-9-a-b  0-0-0-0  8-9-a-b
      4-5-6-7  c-d-e-f  0-0-0-0  c-d-e-f
      8-9-a-b  8-9-a-b  8-8-8-8  0-1-2-3
      c-d-e-f  c-d-e-f  8-8-8-8  4-5-6-7

                 [4]      [4]      [16]
1001  0-1-2-3  9-9-b-b  0-1-0-1  9-8-b-a
      4-5-6-7  d-d-f-f  0-1-0-1  d-c-e-f
      8-9-a-b  9-9-b-b  8-9-8-9  1-0-2-3
      c-d-e-f  d-d-f-f  8-9-8-9  5-4-6-7

                 [4]      [4]      [16]
1010  0-1-2-3  a-b-a-b  0-0-2-2  a-b-8-9
      4-5-6-7  e-f-e-f  0-0-2-2  e-f-c-d
      8-9-a-b  a-b-a-b  8-8-a-a  2-3-0-1
      c-d-e-f  e-f-e-f  8-8-a-a  a-b-8-9

                 [2]      [8]      [16]
1011  0-1-2-3  b-b-b-b  0-1-2-3  b-a-9-8
      4-5-6-7  f-f-f-f  0-1-2-3  f-e-d-c
      8-9-a-b  b-b-b-b  8-9-a-b  7-6-5-4
      c-d-e-f  f-f-f-f  8-9-a-b  3-2-1-0

                 [4]      [4]      [16]
1100  0-1-2-3  c-d-e-f  0-0-0-0  c-d-e-f
      4-5-6-7  c-d-e-f  4-4-4-4  8-9-a-b
      8-9-a-b  c-d-e-f  8-8-8-8  4-5-6-7
      c-d-e-f  c-d-e-f  c-c-c-c  0-1-2-3

                 [2]      [8]      [16]
1101  0-1-2-3  d-d-f-f  0-1-0-1  d-c-f-e
      4-5-6-7  d-d-f-f  4-5-4-5  9-8-b-a
      8-9-a-b  d-d-f-f  8-9-8-9  5-4-7-6
      c-d-e-f  d-d-f-f  c-d-c-d  1-0-3-2

                 [2]      [8]      [16]
1110  0-1-2-3  e-f-e-f  0-0-2-2  e-f-c-d
      4-5-6-7  e-f-e-f  4-4-6-6  a-b-8-9
      8-9-a-b  e-f-e-f  8-8-a-a  6-7-4-5
      c-d-e-f  e-f-e-f  c-c-e-e  2-3-0-1

                 [1]      [id]     [16]
1111  0-1-2-3  f-f-f-f  0-1-2-3  f-e-d-c
      4-5-6-7  f-f-f-f  4-5-6-7  b-a-9-8
      8-9-a-b  f-f-f-f  8-9-a-b  7-6-5-4
      c-d-e-f  f-f-f-f  c-d-e-f  3-2-1-0
If you could use a register for the value for or/and/xor then changing palette would only require changing it's value before calling the drawing routine. If you can't do that then you would have to write a separate routine for each palette or use self-modyfing code to replace all the values in the loop.

Additional observation regarding the and/or method:

If you use colors 0, 3, 5, 6, 9, a, c, f for your sprite's colors.


Let's denote such "standard" sprite palette as 0-3-5-6-9-a-c-f
Then looking at the "and palette" table (if you want color 0 to stay put - good if you want to use it for transparency and masking)
You get four 8-color sprite-palette reorderings that may be useful.

For "and" these are (x and the resulting sprite palette):
0111 : 0-3-5-6-1-2-4-7
1011 : 0-3-1-2-9-a-8-b
1101 : 0-1-5-4-9-8-c-d
1111 : 0-3-5-6-9-a-c-f (id)
And maybe someone can find more useful combinations.
Note that the second color (actually - the third and fifth too) is different in only one palette - this could be used as skin color for example...I think...

The palettes created with xor are permutations, so no color stays in place... I guess that without repeating some colors in the set palette these "palette reorderings" won't be very useful for in-game sprites

However for hattrick I already know that I will be using lookup tables for palette swapping (to limit the number of stored sprite frames) as I think the other methods shown are too restrictive...