1
1
Import " Utility.bmx "
2
2
Import " IndexedPixmap.bmx "
3
3
4
- '//// INDEXED IMAGE WRITER //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
4
+ '//// RGB COLOR /////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
5
5
6
6
Struct RGBColor
7
7
Field m_R:Byte
8
8
Field m_G:Byte
9
9
Field m_B:Byte
10
10
EndStruct
11
11
12
- '////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
12
+ '//// INDEXED IMAGE WRITER //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
13
13
14
14
Type IndexedImageWriter
15
- Field m_PalR:Byte [256 ]
16
- Field m_PalG:Byte [256 ]
17
- Field m_PalB:Byte [256 ]
18
-
19
15
Field m_Palette:RGBColor [256 ]
20
16
21
- 'Field m_CRCTable:Int[256]
22
-
23
17
'////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
24
18
25
19
Method New ()
26
20
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
41
21
EndMethod
42
22
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
53
23
'////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
54
24
55
25
Method LoadDefaultPalette ()
@@ -59,13 +29,9 @@ EndRem
59
29
60
30
Local paletteStream:TStream = ReadFile(" Incbin::Assets/Palette" )
61
31
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)
69
35
Next
70
36
CloseStream(paletteStream)
71
37
EndMethod
85
51
Local bestIndex:Int = 0
86
52
Local bestDistance:Int = 17000000 'Out of bounds color value (max is 16777215)
87
53
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 )
91
57
Local distance:Int = (redDiff ^ 2 ) + (greenDiff ^ 2 ) + (blueDiff ^ 2 )
92
58
93
59
If distance <= bestDistance Then
@@ -113,6 +79,9 @@ EndRem
113
79
114
80
'Begin writing BMP file manually
115
81
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
116
85
117
86
'Bitmap File Header
118
87
WriteShort(outputStream, 19778 ) 'File ID (2 bytes) - 19778 (decimal) or 42 4D (hex) or BM (ascii) for bitmap
@@ -133,12 +102,12 @@ EndRem
133
102
WriteInt(outputStream, 256 ) 'Number of colors in the color palette (4 bytes)
134
103
WriteInt(outputStream, 0 ) 'Number of important colors (4 bytes) - 0 when every color is important
135
104
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
142
111
Next
143
112
144
113
'Pixel Array
@@ -162,87 +131,6 @@ EndRem
162
131
EndIf
163
132
EndMethod
164
133
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
246
134
'////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
247
135
248
136
Method WriteIndexedPNGFromPixmap :Int (sourcePixmap:TPixmap , filename:String , compression:Int = 5 )
@@ -251,29 +139,33 @@ EndRem
251
139
Else
252
140
'Begin writing PNG file manually
253
141
Local outputStream:TStream = WriteStream(filename)
142
+ If Not outputStream Then
143
+ Return False
144
+ EndIf
254
145
255
146
Try
256
147
Local pngPtr:Byte Ptr = png_create_write_struct(" 1.6.37" , Null , Null , Null )
257
148
Local pngInfoPtr:Byte Ptr = png_create_info_struct(pngPtr)
258
149
259
150
png_set_write_fn(pngPtr, outputStream, Utility.PNGWriteStream, Utility.PNGFlushStream)
260
-
261
151
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)
263
152
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
266
155
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
267
158
Local convertingPixmap:IndexedPixmap = New IndexedPixmap (sourcePixmap.Width, sourcePixmap.Height)
268
159
For Local pixelY:Int = 0 Until sourcePixmap.Height
269
160
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
270
162
convertingPixmap.WritePixel(pixelX, pixelY, ConvertColorToClosestIndex(ReadPixel(sourcePixmap, pixelX, pixelY)))
271
163
Next
272
164
Next
273
165
274
166
Local rows:Byte Ptr [ sourcePixmap.Height]
275
167
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)
277
169
Next
278
170
png_set_rows(pngPtr, pngInfoPtr, rows)
279
171
0 commit comments