Skip to content

Commit 64e1292

Browse files
committed
Cleanup in IndexedImageWriter and add some safety
1 parent 062e3c2 commit 64e1292

File tree

1 file changed

+26
-134
lines changed

1 file changed

+26
-134
lines changed

types/IndexedImageWriter.bmx

Lines changed: 26 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,25 @@
11
Import "Utility.bmx"
22
Import "IndexedPixmap.bmx"
33

4-
'//// INDEXED IMAGE WRITER //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
4+
'//// RGB COLOR /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
55

66
Struct RGBColor
77
Field m_R:Byte
88
Field m_G:Byte
99
Field m_B:Byte
1010
EndStruct
1111

12-
'////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
12+
'//// INDEXED IMAGE WRITER //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1313

1414
Type IndexedImageWriter
15-
Field m_PalR:Byte[256]
16-
Field m_PalG:Byte[256]
17-
Field m_PalB:Byte[256]
18-
1915
Field m_Palette:RGBColor[256]
2016

21-
'Field m_CRCTable:Int[256]
22-
2317
'////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2418

2519
Method New()
2620
LoadDefaultPalette()
27-
Rem
28-
'Initialize CRC table
29-
For Local i:Int = 0 To 255
30-
Local value:Int = i
31-
For Local j:Int = 0 To 7
32-
If (value & $1) Then
33-
value = (value Shr 1) ~ $EDB88320 '~ for XOR
34-
Else
35-
value = (value Shr 1)
36-
EndIf
37-
Next
38-
m_CRCTable[i] = value
39-
Next
40-
EndRem
4121
EndMethod
4222

43-
'////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
44-
Rem
45-
Method GenerateCRC32FromBank:Int(bank:TBank)
46-
Local crcResult:Int = $FFFFFFFF
47-
For Local i:Int = 0 Until BankSize(bank)
48-
crcResult = (crcResult Shr 8) ~ m_CRCTable[PeekByte(bank, i) ~ (crcResult & $FF)]
49-
Next
50-
Return ~crcResult '~ for bitwise complement
51-
EndMethod
52-
EndRem
5323
'////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
5424

5525
Method LoadDefaultPalette()
@@ -59,13 +29,9 @@ EndRem
5929

6030
Local paletteStream:TStream = ReadFile("Incbin::Assets/Palette")
6131
For Local index:Int = 0 To 255
62-
m_PalR[index] = ReadByte(paletteStream)
63-
m_PalG[index] = ReadByte(paletteStream)
64-
m_PalB[index] = ReadByte(paletteStream)
65-
66-
m_Palette[index].m_R = m_PalR[index]
67-
m_Palette[index].m_G = m_PalG[index]
68-
m_Palette[index].m_B = m_PalB[index]
32+
m_Palette[index].m_R = ReadByte(paletteStream)
33+
m_Palette[index].m_G = ReadByte(paletteStream)
34+
m_Palette[index].m_B = ReadByte(paletteStream)
6935
Next
7036
CloseStream(paletteStream)
7137
EndMethod
@@ -85,9 +51,9 @@ EndRem
8551
Local bestIndex:Int = 0
8652
Local bestDistance:Int = 17000000 'Out of bounds color value (max is 16777215)
8753
For Local index:Int = 0 To 255
88-
Local redDiff:Int = Abs(((pixelData & $00FF0000) Shr 16) - m_PalR[index])
89-
Local greenDiff:Int = Abs(((pixelData & $FF00) Shr 8) - m_PalG[index])
90-
Local blueDiff:Int = Abs((pixelData & $FF) - m_PalB[index])
54+
Local redDiff:Int = Abs(((pixelData & $00FF0000) Shr 16) - m_Palette[index].m_R)
55+
Local greenDiff:Int = Abs(((pixelData & $FF00) Shr 8) - m_Palette[index].m_G)
56+
Local blueDiff:Int = Abs((pixelData & $FF) - m_Palette[index].m_B)
9157
Local distance:Int = (redDiff ^ 2) + (greenDiff ^ 2) + (blueDiff ^ 2)
9258

9359
If distance <= bestDistance Then
@@ -113,6 +79,9 @@ EndRem
11379

11480
'Begin writing BMP file manually
11581
Local outputStream:TStream = LittleEndianStream(WriteFile(filename)) 'Bitmap file data is stored in little-endian format (least-significant byte first)
82+
If Not outputStream Then
83+
Return False
84+
EndIf
11685

11786
'Bitmap File Header
11887
WriteShort(outputStream, 19778) 'File ID (2 bytes) - 19778 (decimal) or 42 4D (hex) or BM (ascii) for bitmap
@@ -133,12 +102,12 @@ EndRem
133102
WriteInt(outputStream, 256) 'Number of colors in the color palette (4 bytes)
134103
WriteInt(outputStream, 0) 'Number of important colors (4 bytes) - 0 when every color is important
135104

136-
'Color Table (4 bytes (ARGB) times the amount of colors in the palette)
137-
For Local index:Int = 0 To 255
138-
WriteByte(outputStream, m_PalB[index]) 'Blue (1 byte)
139-
WriteByte(outputStream, m_PalG[index]) 'Green (1 byte)
140-
WriteByte(outputStream, m_PalR[index]) 'Red (1 byte)
141-
WriteByte(outputStream, 0) 'Reserved (1 byte) - Alpha channel, irrelevant for indexed bitmaps
105+
'Color Table (4 bytes (RGBA) times the amount of colors in the palette)
106+
For Local paletteIndex:RGBColor = EachIn m_Palette
107+
WriteByte(outputStream, paletteIndex.m_R) 'Blue (1 byte)
108+
WriteByte(outputStream, paletteIndex.m_G) 'Green (1 byte)
109+
WriteByte(outputStream, paletteIndex.m_B) 'Red (1 byte)
110+
WriteByte(outputStream, 0) 'Reserved (1 byte) - Alpha channel, irrelevant for indexed bitmaps
142111
Next
143112

144113
'Pixel Array
@@ -162,87 +131,6 @@ EndRem
162131
EndIf
163132
EndMethod
164133

165-
'////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
166-
Rem
167-
Method WriteIndexedPNGFromPixmap:Int(sourcePixmap:TPixmap, filename:String)
168-
If filename = Null Then
169-
Return False
170-
Else
171-
Local pngWidth:Int = sourcePixmap.Width
172-
Local pngWidthM4:Int = ((pngWidth + 3) / 4) * 4 'pngWidth adjusted to be divisible by 4. Written file is spaghetti if not adjusted!
173-
Local pngHeight:Int = sourcePixmap.Height
174-
Local pngSizeTotal:Int = pngWidthM4 * pngHeight 'dimensions (with adjusted width)
175-
Local pngSizeTotalM4:Int = ((pngSizeTotal + 3) / 4) * 4 'bmpSizeTotal adjusted to be divisible by 4. Written file is spaghetti if not adjusted!
176-
177-
'Begin writing PNG file manually
178-
Local outputStream:TStream = BigEndianStream(WriteFile(filename)) 'PNG file data is stored in network byte order (big-endian)
179-
180-
'PNG file header
181-
WriteByte(outputStream, 137) 'Has the high bit set to detect transmission systems that do not support 8-bit data and to reduce the chance that a text file is mistakenly interpreted as a PNG, or vice versa. 89 (hex) (1 byte)
182-
WriteByte(outputStream, 80) 'File ID (in ASCII, the letters PNG). 50 4E 47 (hex) (3 bytes)
183-
WriteByte(outputStream, 78)
184-
WriteByte(outputStream, 71)
185-
WriteShort(outputStream, 3338) 'A DOS-style line ending (CRLF) to detect DOS-Unix line ending conversion of the data. 0D 0A (hex) (2 bytes)
186-
WriteByte(outputStream, 26) 'A byte that stops display of the file under DOS when the command type has been used—the end-of-file character. 1A (hex) (1 byte)
187-
WriteByte(outputStream, 10) 'A Unix-style line ending (LF) to detect Unix-DOS line ending conversion. 0A (hex) (1 byte)
188-
189-
'IHDR chunk (file properties)
190-
WriteInt(outputStream, 13) 'Chunk Length (4 bytes) - 13 bytes for IHDR
191-
WriteInt(outputStream, 1229472850) 'Chunk Type (4 bytes) - 1229472850 (decimal) or 49 48 44 52 (hex) or IHDR (ascii)
192-
193-
WriteInt(outputStream, pngWidth) 'Image Width (4 bytes)
194-
WriteInt(outputStream, pngHeight) 'Image Height (4 bytes)
195-
WriteByte(outputStream, 8) 'Bit Depth (1 byte)
196-
WriteByte(outputStream, 3) 'Color Type (1 byte) - 3 for indexed color
197-
WriteByte(outputStream, 0) 'Compression Method (1 byte)
198-
WriteByte(outputStream, 0) 'Filter Method (1 byte)
199-
WriteByte(outputStream, 0) 'Interlace Method (1 byte) - 0 for no interlace
200-
201-
'Figure out how to generate correct CRC
202-
WriteInt(outputStream, 0) 'CRC-32 checksum (4 bytes)
203-
204-
'PLTE chunk (color table)
205-
WriteInt(outputStream, 768) 'Chunk Length (4 bytes) - 4 bytes (ARGB) times the amount of colors in the palette = 1024 bytes
206-
WriteInt(outputStream, 1347179589) 'Chunk Type (4 bytes) - 1347179589 (decimal) or 50 4C 54 45 (hex) or PLTE (ascii)
207-
208-
For Local index:Int = 0 To 255
209-
WriteByte(outputStream, m_PalB[index]) 'Blue (1 byte)
210-
WriteByte(outputStream, m_PalG[index]) 'Green (1 byte)
211-
WriteByte(outputStream, m_PalR[index]) 'Red (1 byte)
212-
Next
213-
214-
'Figure out how to generate correct CRC
215-
WriteInt(outputStream, 0) 'CRC-32 checksum (4 bytes)
216-
217-
'IDAT chunk (pixel array)
218-
WriteInt(outputStream, pngSizeTotal) 'Chunk Length (4 bytes) - width times height of the image + padding
219-
WriteInt(outputStream, 1229209940) 'Chunk Type (4 bytes) - 1229209940 (decimal) or 49 44 41 54 (hex) or IDAT (ascii)
220-
221-
For Local pixelY:Int = pngHeight - 1 To 0 Step -1
222-
For Local pixelX:Int = 0 Until pngWidth
223-
If pixelX < pngWidth Then
224-
WriteByte(outputStream, ConvertColorToClosestIndex(ReadPixel(sourcePixmap, pixelX, pixelY)))
225-
'Else
226-
' WriteByte(outputStream, 0) 'Line padding
227-
EndIf
228-
Next
229-
Next
230-
231-
'Figure out how to generate correct CRC
232-
WriteInt(outputStream, 0) 'CRC-32 checksum (4 bytes)
233-
234-
'IEND chunk (EOF)
235-
WriteInt(outputStream, 0) 'Chunk Length (4 bytes) - 0 for IEND
236-
WriteInt(outputStream, 1229278788) 'Chunk Type (4 bytes) - 1229278788 (decimal) or 49 45 4E 44 (hex) or IEND (ascii)
237-
238-
'Figure out how to generate correct CRC
239-
WriteInt(outputStream, 0) 'CRC-32 checksum (4 bytes)
240-
241-
CloseStream(outputStream)
242-
Return True
243-
EndIf
244-
EndMethod
245-
EndRem
246134
'////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
247135

248136
Method WriteIndexedPNGFromPixmap:Int(sourcePixmap:TPixmap, filename:String, compression:Int = 5)
@@ -251,29 +139,33 @@ EndRem
251139
Else
252140
'Begin writing PNG file manually
253141
Local outputStream:TStream = WriteStream(filename)
142+
If Not outputStream Then
143+
Return False
144+
EndIf
254145

255146
Try
256147
Local pngPtr:Byte Ptr = png_create_write_struct("1.6.37", Null, Null, Null)
257148
Local pngInfoPtr:Byte Ptr = png_create_info_struct(pngPtr)
258149

259150
png_set_write_fn(pngPtr, outputStream, Utility.PNGWriteStream, Utility.PNGFlushStream)
260-
261151
png_set_compression_level(pngPtr, Utility.Clamp(compression, 0, 9))
262-
png_set_IHDR(pngPtr, pngInfoPtr, sourcePixmap.Width, sourcePixmap.Height, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT)
263152

264-
Local palettePtr:Byte Ptr = m_Palette
265-
png_set_PLTE(pngPtr, pngInfoPtr, palettePtr, 256);
153+
png_set_IHDR(pngPtr, pngInfoPtr, sourcePixmap.Width, sourcePixmap.Height, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT) 'IHDR chunk - file properties
154+
png_set_PLTE(pngPtr, pngInfoPtr, Byte Ptr(m_Palette), m_Palette.Length) 'PLTE chunk - color table
266155

156+
'Using the RGBA8888 source pixmap produces spaghetti output because of byte alignment, so conversion to RGB332 is needed
157+
'What happens is that 4 bytes are written when 1 is expected, so we're actually writing 4 pixels where the first pixel is correct and the following 3 are empty causing the output to stretch and spaghettify
267158
Local convertingPixmap:IndexedPixmap = New IndexedPixmap(sourcePixmap.Width, sourcePixmap.Height)
268159
For Local pixelY:Int = 0 Until sourcePixmap.Height
269160
For Local pixelX:Int = 0 Until sourcePixmap.Width
161+
'Convert the 32bit RGBA color value from the source pixmap to a 8bit index value and write it to the converting bitmap
270162
convertingPixmap.WritePixel(pixelX, pixelY, ConvertColorToClosestIndex(ReadPixel(sourcePixmap, pixelX, pixelY)))
271163
Next
272164
Next
273165

274166
Local rows:Byte Ptr[sourcePixmap.Height]
275167
For Local i = 0 Until sourcePixmap.Height
276-
rows[i] = convertingPixmap.PixelPtr(0, i)
168+
rows[i] = convertingPixmap.PixelPtr(0, i) 'Get row pointers from the converting bitmap (1 byte aligned so won't be spaghettified)
277169
Next
278170
png_set_rows(pngPtr, pngInfoPtr, rows)
279171

0 commit comments

Comments
 (0)