Difference between revisions of "FF7/Field/Background"

From Final Fantasy Inside
< FF7‎ | Field
Jump to navigation Jump to search
my_wiki>Synergy Blades
m (Section 9: Background (Terrence Ferguson))
m (4 revisions imported)
 
(2 intermediate revisions by one other user not shown)
Line 1: Line 1:
=== Section 9: Background ([[User:Terence Fergusson|Terence Fergusson]]) ===
+
=== Section 9: Background ([[User:Terence_Fergusson|Terence Fergusson]]) ===
  
 
Firstly, a number of variables.
 
Firstly, a number of variables.
  
At offset $28, a Word = background width (BGWidth)
+
At offset $28, a Word = background width (BGWidth) At offset $2A, a Word = background height (BGHeight) At offset $2C, a Word = number of background sprites (NumBGSprites) At offset $32, the background sprite data. See below for format (each sprite is 52 bytes long) After the background sprite data, another $7 bytes, unknown purpose. Then (ie. at offset $32 + NumBGSprites*52 + $7) a Word = number of 2nd layer background sprites (NumBG2Sprites) Then another $12 bytes, unknown purpose. Then (ie. at offset $32 + NumBGSprites*52 + $1B) the background layer 2 sprite data. See below for format. Then another $3D bytes, unknown purpose. Then (ie. at offset $32 + NumBGSprites*52 + NumBG2Sprites*52 + $58) the raw image data.
At offset $2A, a Word = background height (BGHeight)
 
At offset $2C, a Word = number of background sprites (NumBGSprites)
 
At offset $32, the background sprite data. See below for format (each sprite is 52 bytes long)
 
After the background sprite data, another $7 bytes, unknown purpose.
 
Then (ie. at offset $32 + NumBGSprites*52 + $7) a Word = number of 2nd layer background sprites (NumBG2Sprites)
 
Then another $12 bytes, unknown purpose.
 
Then (ie. at offset $32 + NumBGSprites*52 + $1B) the background layer 2 sprite data. See below for format.
 
Then another $3D bytes, unknown purpose.
 
Then (ie. at offset $32 + NumBGSprites*52 + NumBG2Sprites*52 + $58) the raw image data.
 
  
==== Background format ====
+
==== Background format ====
  
 
FF7 stores its backgrounds in a rather complex format. Basically, you have the data split up into various sections:
 
FF7 stores its backgrounds in a rather complex format. Basically, you have the data split up into various sections:
  
1) Palette. List of colours.
+
1) Palette. List of colours. 2) Background sprites, layers 1 and 2. Just references to other bits of data. 3) Raw image data. Palettized data (ie. "grayscale" if viewed directly).
2) Background sprites, layers 1 and 2. Just references to other bits of data.
 
3) Raw image data. Palettized data (ie. "grayscale" if viewed directly).
 
  
 
Each background sprite represents a 16x16 pixel block on the finished background. The sprite essentially contains the following information:
 
Each background sprite represents a 16x16 pixel block on the finished background. The sprite essentially contains the following information:
  
- "Target" block, ie. where on the background to draw this 16x16 square
+
- "Target" block, ie. where on the background to draw this 16x16 square - "Source" block, ie. where on the raw image data to take the pixels from - Palette "page", ie. which 256-colour palette block to apply to the raw image data
- "Source" block, ie. where on the raw image data to take the pixels from
 
- Palette "page", ie. which 256-colour palette block to apply to the raw image data
 
  
 
This is a very efficient way to store the image; on the one hand, it's in 16-bit colour, far better than just palettizing the whole image (ie. 256 colours over the *whole* background). On the other hand, each 16x16 pixel block takes much less space than if you'd stored it directly in 16-bit colour format. It isn't, however, easy to decode or (especially) encode.
 
This is a very efficient way to store the image; on the one hand, it's in 16-bit colour, far better than just palettizing the whole image (ie. 256 colours over the *whole* background). On the other hand, each 16x16 pixel block takes much less space than if you'd stored it directly in 16-bit colour format. It isn't, however, easy to decode or (especially) encode.
  
Format of a background sprite:  
+
Format of a background sprite:
  
  Type  
+
  Type
 
   TFF7BgSprite = packed record
 
   TFF7BgSprite = packed record
 
     ZZ1,X,Y:  Smallint;
 
     ZZ1,X,Y:  Smallint;
Line 46: Line 33:
 
     OffX,OffY:  Longint;
 
     OffX,OffY:  Longint;
 
     ZZ6: Smallint;
 
     ZZ6: Smallint;
   end;  
+
   end;
  
ZZ1,2,3,4,5,6: Unknown data
+
ZZ1,2,3,4,5,6: Unknown data X,Y: Target position SrcX,SrcY: Source position Pal: Which palette page to use Flags: Indicate special effects ... not really understood properly. Page: Which image source page to use Sfx: More special effects? NA: Unknown OffX,OffY: Unknown
X,Y: Target position
 
SrcX,SrcY: Source position
 
Pal: Which palette page to use
 
Flags: Indicate special effects ... not really understood properly.
 
Page: Which image source page to use
 
Sfx: More special effects?
 
NA: Unknown
 
OffX,OffY: Unknown  
 
  
 
The image source data is split up into 256x256 pixel pages; that's why as well as a source X and Y, you also have a source page (which 256x256 block to take data from). On the other hand, the destination background is stored as one big bitmap with no limits on size, so there, you just have a target X/Y position which can be used directly.
 
The image source data is split up into 256x256 pixel pages; that's why as well as a source X and Y, you also have a source page (which 256x256 block to take data from). On the other hand, the destination background is stored as one big bitmap with no limits on size, so there, you just have a target X/Y position which can be used directly.
  
Also, note that each source image data "page" is preceded by 6 bytes of header.  
+
Also, note that each source image data "page" is preceded by 6 bytes of header.
  
So, say the raw image data starts at offset ImageData. Given a background sprite, the offset where that sprites data starts is:  
+
So, say the raw image data starts at offset ImageData. Given a background sprite, the offset where that sprites data starts is:
  
 
StartOffset := (Page shl 16) or ((SrcY shl 8) or SrcX) + (Page+1)*6;
 
StartOffset := (Page shl 16) or ((SrcY shl 8) or SrcX) + (Page+1)*6;
+
 
 
this is equivalent to
 
this is equivalent to
  
 
StartOffset := (Page * $FFFF) + (SrcY * $FF) + SrcX + (Page+1)*6;
 
StartOffset := (Page * $FFFF) + (SrcY * $FF) + SrcX + (Page+1)*6;
  
(Page shl 16), (Page*$FFFF): Each page takes up 256x256 = $FFFF bytes, so skip that many for each page.  
+
(Page shl 16), (Page*$FFFF): Each page takes up 256x256 = $FFFF bytes, so skip that many for each page.
  
(SrcY shl 8), (SrcY*$FF): Each pixel row takes up 256 = $FF bytes, so skip that many to get to the right row.  
+
(SrcY shl 8), (SrcY*$FF): Each pixel row takes up 256 = $FF bytes, so skip that many to get to the right row.
  
SrcX: Taken directly.  
+
SrcX: Taken directly.
  
(Page+1)*6: Skip 6 bytes of header per page. (Page+1) since even page 0 has 6 bytes preceding it.  
+
(Page+1)*6: Skip 6 bytes of header per page. (Page+1) since even page 0 has 6 bytes preceding it.
  
 
Incidentally, the shifts are used in preference to multiplication since shifting is more efficient. Shifting on a computer is equivalent to multiplying/dividing by 2, 4, 8, ...
 
Incidentally, the shifts are used in preference to multiplication since shifting is more efficient. Shifting on a computer is equivalent to multiplying/dividing by 2, 4, 8, ...
  
For the destination, note that you can use X/Y directly; however 0,0 appears, I think, to be at the image centre, not at the top/bottom left corner like with most programs.  
+
For the destination, note that you can use X/Y directly; however 0,0 appears, I think, to be at the image centre, not at the top/bottom left corner like with most programs.
  
 
So, now you know:
 
So, now you know:
  
-Where the raw image data for that sprite starts
+
-Where the raw image data for that sprite starts -Where you're drawing it to -Which palette page to use
-Where you're drawing it to
 
-Which palette page to use  
 
  
 
Now, you just copy the pixels across, filtering the palette into it. IE:
 
Now, you just copy the pixels across, filtering the palette into it. IE:
Line 92: Line 69:
 
Read a pixel from source image (one byte). Set current colour to that colour in palette. Draw onto target.
 
Read a pixel from source image (one byte). Set current colour to that colour in palette. Draw onto target.
  
So, if you read a byte = 55 from the source image, you'd draw colour 55 in the selected palette to the target bitmap.  
+
So, if you read a byte = 55 from the source image, you'd draw colour 55 in the selected palette to the target bitmap.
  
==== Other points ====
+
==== Other points ====
  
Currently, Qhimm's (and therefore mine too) source code doesn't draw a sprite if the Sfx is non-zero; this is because we don't understand what it does.  
+
Currently, Qhimm's (and therefore mine too) source code doesn't draw a sprite if the Sfx is non-zero; this is because we don't understand what it does.
  
All variables above (Page, Pal, X, Y) start from ZERO; palette page zero is the first one, page one is the next one, etc.  
+
All variables above (Page, Pal, X, Y) start from ZERO; palette page zero is the first one, page one is the next one, etc.
  
 
The image data in FF7 palettes is stored in reverse order; ie. on Windows, data is stored Red first, then Green, then Blue. FF7 stores it the opposite way around, so you need to exchange the red/blue data. Here's how I do it in Cosmo:
 
The image data in FF7 palettes is stored in reverse order; ie. on Windows, data is stored Red first, then Green, then Blue. FF7 stores it the opposite way around, so you need to exchange the red/blue data. Here's how I do it in Cosmo:
  
  DCol := 0;
+
  DCol := 0;
  DCol := DCol or ( (Col^ and $1F) shl 10 );
+
  DCol := DCol or ( (Col^ and $1F) shl 10 );
  DCol := DCol or (Col^ and $3E0);
+
  DCol := DCol or (Col^ and $3E0);
  DCol := DCol or ( (Col^ and $7C00) shr 10);
+
  DCol := DCol or ( (Col^ and $7C00) shr 10);
  
Converts Col, an FF7 colour, into DCol, a (16-bit) Delphi colour.  
+
Converts Col, an FF7 colour, into DCol, a (16-bit) Delphi colour.
  
The first background sprites are drawn "behind" the layer 2 sprites.
+
The first background sprites are drawn "behind" the layer 2 sprites. Variable conventions; I'm using Delphi names, which are as follows:
Variable conventions; I'm using Delphi names, which are as follows:  
 
  
Byte:           8 bit, unsigned, integer
+
Byte: 8 bit, unsigned, integer Word: 16 bit, unsigned, integer Smallint: 16 bit, signed, integer Integer/Longint: 32 bit, signed, integer
Word:           16 bit, unsigned, integer
 
Smallint:       16 bit, signed, integer
 
Integer/Longint: 32 bit, signed, integer  
 
  
(Unsigned = positive values only. Signed can hold positive or negative values).  
+
(Unsigned = positive values only. Signed can hold positive or negative values).
  
 
Also, whenever I use numbers with $ signs above, it means I'm using hex values (hexadecimal).
 
Also, whenever I use numbers with $ signs above, it means I'm using hex values (hexadecimal).

Latest revision as of 05:14, 23 May 2019

Section 9: Background (Terence Fergusson)

Firstly, a number of variables.

At offset $28, a Word = background width (BGWidth) At offset $2A, a Word = background height (BGHeight) At offset $2C, a Word = number of background sprites (NumBGSprites) At offset $32, the background sprite data. See below for format (each sprite is 52 bytes long) After the background sprite data, another $7 bytes, unknown purpose. Then (ie. at offset $32 + NumBGSprites*52 + $7) a Word = number of 2nd layer background sprites (NumBG2Sprites) Then another $12 bytes, unknown purpose. Then (ie. at offset $32 + NumBGSprites*52 + $1B) the background layer 2 sprite data. See below for format. Then another $3D bytes, unknown purpose. Then (ie. at offset $32 + NumBGSprites*52 + NumBG2Sprites*52 + $58) the raw image data.

Background format

FF7 stores its backgrounds in a rather complex format. Basically, you have the data split up into various sections:

1) Palette. List of colours. 2) Background sprites, layers 1 and 2. Just references to other bits of data. 3) Raw image data. Palettized data (ie. "grayscale" if viewed directly).

Each background sprite represents a 16x16 pixel block on the finished background. The sprite essentially contains the following information:

- "Target" block, ie. where on the background to draw this 16x16 square - "Source" block, ie. where on the raw image data to take the pixels from - Palette "page", ie. which 256-colour palette block to apply to the raw image data

This is a very efficient way to store the image; on the one hand, it's in 16-bit colour, far better than just palettizing the whole image (ie. 256 colours over the *whole* background). On the other hand, each 16x16 pixel block takes much less space than if you'd stored it directly in 16-bit colour format. It isn't, however, easy to decode or (especially) encode.

Format of a background sprite:

Type
  TFF7BgSprite = packed record
    ZZ1,X,Y:  Smallint;
    ZZ2:  Array[0..1] of Smallint;
    SrcX,SrcY:  Smallint;
    ZZ3:        Array[0..3] of Smallint;
    Pal:        Smallint;
    Flags:      Word;
    ZZ4: Array[0..2] of Smallint;
    Page,Sfx:   Smallint;
    NA:         Longint;
    ZZ5: Smallint;
    OffX,OffY:  Longint;
    ZZ6: Smallint;
  end;

ZZ1,2,3,4,5,6: Unknown data X,Y: Target position SrcX,SrcY: Source position Pal: Which palette page to use Flags: Indicate special effects ... not really understood properly. Page: Which image source page to use Sfx: More special effects? NA: Unknown OffX,OffY: Unknown

The image source data is split up into 256x256 pixel pages; that's why as well as a source X and Y, you also have a source page (which 256x256 block to take data from). On the other hand, the destination background is stored as one big bitmap with no limits on size, so there, you just have a target X/Y position which can be used directly.

Also, note that each source image data "page" is preceded by 6 bytes of header.

So, say the raw image data starts at offset ImageData. Given a background sprite, the offset where that sprites data starts is:

StartOffset := (Page shl 16) or ((SrcY shl 8) or SrcX) + (Page+1)*6;

this is equivalent to

StartOffset := (Page * $FFFF) + (SrcY * $FF) + SrcX + (Page+1)*6;

(Page shl 16), (Page*$FFFF): Each page takes up 256x256 = $FFFF bytes, so skip that many for each page.

(SrcY shl 8), (SrcY*$FF): Each pixel row takes up 256 = $FF bytes, so skip that many to get to the right row.

SrcX: Taken directly.

(Page+1)*6: Skip 6 bytes of header per page. (Page+1) since even page 0 has 6 bytes preceding it.

Incidentally, the shifts are used in preference to multiplication since shifting is more efficient. Shifting on a computer is equivalent to multiplying/dividing by 2, 4, 8, ...

For the destination, note that you can use X/Y directly; however 0,0 appears, I think, to be at the image centre, not at the top/bottom left corner like with most programs.

So, now you know:

-Where the raw image data for that sprite starts -Where you're drawing it to -Which palette page to use

Now, you just copy the pixels across, filtering the palette into it. IE:

Read a pixel from source image (one byte). Set current colour to that colour in palette. Draw onto target.

So, if you read a byte = 55 from the source image, you'd draw colour 55 in the selected palette to the target bitmap.

Other points

Currently, Qhimm's (and therefore mine too) source code doesn't draw a sprite if the Sfx is non-zero; this is because we don't understand what it does.

All variables above (Page, Pal, X, Y) start from ZERO; palette page zero is the first one, page one is the next one, etc.

The image data in FF7 palettes is stored in reverse order; ie. on Windows, data is stored Red first, then Green, then Blue. FF7 stores it the opposite way around, so you need to exchange the red/blue data. Here's how I do it in Cosmo:

DCol := 0;
DCol := DCol or ( (Col^ and $1F) shl 10 );
DCol := DCol or (Col^ and $3E0);
DCol := DCol or ( (Col^ and $7C00) shr 10);

Converts Col, an FF7 colour, into DCol, a (16-bit) Delphi colour.

The first background sprites are drawn "behind" the layer 2 sprites. Variable conventions; I'm using Delphi names, which are as follows:

Byte: 8 bit, unsigned, integer Word: 16 bit, unsigned, integer Smallint: 16 bit, signed, integer Integer/Longint: 32 bit, signed, integer

(Unsigned = positive values only. Signed can hold positive or negative values).

Also, whenever I use numbers with $ signs above, it means I'm using hex values (hexadecimal).