|
1 | 1 | // Untile/Unswizzle by xdaniel. Copyright(c) 2016 xdaniel(Daniel R.) / DigitalZero Domain. License: The MIT License (MIT) |
2 | 2 | // Tile/Swizzle by Ulysses (wdwxy12345@gmail.com). License: same as FreeMote |
| 3 | +// UntileTextureRvl/TileTextureRvl was fixed by Krisp (FreeMote#139) |
3 | 4 |
|
4 | 5 | using System; |
5 | 6 | using System.Drawing; |
|
9 | 10 | namespace FreeMote |
10 | 11 | { |
11 | 12 | /// <summary> |
12 | | - /// PS Related Post Process |
| 13 | + /// Texture Post Process for consoles |
13 | 14 | /// </summary> |
14 | 15 | public static class PostProcessing |
15 | 16 | { |
@@ -152,160 +153,219 @@ public static byte[] TileTexture(byte[] pixelData, int width, int height, int bi |
152 | 153 | /// <summary> |
153 | 154 | /// UnTile for Revolution (Wii) |
154 | 155 | /// </summary> |
155 | | - /// <param name="pixelData"></param> |
156 | | - /// <param name="width"></param> |
157 | | - /// <param name="height"></param> |
158 | | - /// <param name="bitDepth"></param> |
159 | | - /// <returns></returns> |
| 156 | + /// <param name="pixelData">The source tiled pixel data.</param> |
| 157 | + /// <param name="width">Image width.</param> |
| 158 | + /// <param name="height">Image height.</param> |
| 159 | + /// <param name="bitDepth">Image bit depth (e.g., 4 for CI4, 8 for CI8, 32 for RGBA8).</param> |
| 160 | + /// <returns>The untiled (linear) pixel data.</returns> |
160 | 161 | public static byte[] UntileTextureRvl(byte[] pixelData, int width, int height, int bitDepth = 32) |
161 | 162 | { |
162 | 163 | // ref: Revolution SDK Graphics Library (GX) |
| 164 | + // Determine tile dimensions based on bit depth. |
163 | 165 | int xTileSize = 4; |
164 | 166 | int yTileSize = 4; |
165 | 167 | if (bitDepth <= 8) |
166 | 168 | { |
167 | | - xTileSize = 8; // 4x8 |
| 169 | + xTileSize = 8; // 8x4 for CI8/I8 |
168 | 170 | } |
169 | | - |
170 | 171 | if (bitDepth <= 4) |
171 | 172 | { |
172 | | - yTileSize = 8; // 8x8 |
| 173 | + yTileSize = 8; // 8x8 for CI4/I4 |
173 | 174 | } |
174 | | - bool wide = width > height; |
| 175 | + |
175 | 176 | var byteSizePerPixel = bitDepth / 8.0f; |
176 | 177 | var scale = 1; |
177 | 178 | bool compactMode = false; |
178 | 179 | int bpp = bitDepth / 8; |
179 | 180 | byte[] untiledData; |
| 181 | + |
| 182 | + // Handle compact formats where one byte stores multiple pixels (e.g., 4bpp). |
180 | 183 | if (byteSizePerPixel < 1.0f) |
181 | 184 | { |
182 | 185 | compactMode = true; |
183 | | - scale = (int) (1.0f / byteSizePerPixel); |
| 186 | + scale = (int) (1.0f / byteSizePerPixel); // For 4bpp, scale is 2. |
184 | 187 | untiledData = new byte[width * height / scale]; |
185 | | - bpp = 1; |
186 | 188 | } |
187 | 189 | else |
188 | 190 | { |
189 | 191 | untiledData = new byte[width * height * bpp]; |
190 | 192 | } |
191 | 193 |
|
192 | | - int dataIndex = 0; |
| 194 | + int dataIndex = 0; // Tracks the current read position in the source tiled data. |
193 | 195 |
|
| 196 | + // Iterate through the image by tile blocks. |
194 | 197 | for (int yt = 0; yt < height; yt += yTileSize) |
195 | 198 | { |
196 | 199 | for (int xt = 0; xt < width; xt += xTileSize) |
197 | 200 | { |
| 201 | + // Iterate through pixels within a tile. |
198 | 202 | for (int y = yt; y < yt + yTileSize; y++) |
199 | 203 | { |
200 | 204 | for (int x = xt; x < xt + xTileSize; x++) |
201 | 205 | { |
202 | 206 | if (x >= width || y >= height) continue; |
203 | 207 |
|
204 | | - int pixelIndex = ((y * width) + x) * bpp; |
205 | | - |
206 | 208 | if (!compactMode) |
207 | 209 | { |
208 | | - if (dataIndex >= pixelData.Length) |
| 210 | + // Standard copy for formats with 1 or more bytes per pixel. |
| 211 | + int pixelIndex = ((y * width) + x) * bpp; |
| 212 | + if (dataIndex + bpp <= pixelData.Length && pixelIndex + bpp <= untiledData.Length) |
209 | 213 | { |
210 | | - break; |
| 214 | + Buffer.BlockCopy(pixelData, dataIndex, untiledData, pixelIndex, bpp); |
211 | 215 | } |
212 | | - Buffer.BlockCopy(pixelData, dataIndex, untiledData, pixelIndex, bpp); |
213 | 216 | dataIndex += bpp; |
214 | 217 | } |
215 | 218 | else |
216 | 219 | { |
217 | | - var currentPos = dataIndex / scale; |
218 | | - if (currentPos >= pixelData.Length) |
| 220 | + // Corrected logic for compact formats (e.g., CI4). |
| 221 | + // dataIndex tracks the current 4-bit pixel in the tiled source data. |
| 222 | + int srcByteIndex = dataIndex / scale; // Which source byte holds our 4-bit pixel. |
| 223 | + if (srcByteIndex >= pixelData.Length) continue; |
| 224 | + |
| 225 | + byte sourceByte = pixelData[srcByteIndex]; |
| 226 | + |
| 227 | + // Extract the correct 4-bit nibble from the source byte. |
| 228 | + // Even index = high bits, odd index = low bits. |
| 229 | + byte pixelValue = (dataIndex % 2 == 0) |
| 230 | + ? (byte) (sourceByte >> 4) // Get the left nibble. |
| 231 | + : (byte) (sourceByte & 0x0F); // Get the right nibble. |
| 232 | + |
| 233 | + // Calculate the destination linear pixel position. |
| 234 | + int destPixelLinearIndex = (y * width) + x; |
| 235 | + int destByteIndex = destPixelLinearIndex / scale; // Which byte to write to in the destination. |
| 236 | + int destNibblePos = destPixelLinearIndex % 2; // Which nibble (0 or 1) to write to. |
| 237 | + |
| 238 | + if (destByteIndex >= untiledData.Length) continue; |
| 239 | + |
| 240 | + // Place the 4-bit pixelValue into the correct nibble of the destination byte, |
| 241 | + // preserving the other nibble that might already be there. |
| 242 | + if (destNibblePos == 0) |
219 | 243 | { |
220 | | - return untiledData; |
| 244 | + // Even linear position: write to the high bits (left nibble). |
| 245 | + untiledData[destByteIndex] = (byte) ((untiledData[destByteIndex] & 0x0F) | (pixelValue << 4)); |
221 | 246 | } |
222 | | - var srcData = pixelData[currentPos]; |
223 | | - var dstPosition = pixelIndex / scale; |
224 | | - var dstSubIndex = pixelIndex % scale; |
225 | | - untiledData[dstPosition] = dstSubIndex == 0 |
226 | | - ? (byte) ((untiledData[dstPosition] & 0xF0) | srcData) |
227 | | - : (byte) ((untiledData[dstPosition] & 0x0F) | (srcData << 4)); |
| 247 | + else |
| 248 | + { |
| 249 | + // Odd linear position: write to the low bits (right nibble). |
| 250 | + untiledData[destByteIndex] = (byte) ((untiledData[destByteIndex] & 0xF0) | pixelValue); |
| 251 | + } |
| 252 | + |
| 253 | + // Move to the next 4-bit pixel in the source tile stream. |
228 | 254 | dataIndex++; |
229 | 255 | } |
230 | 256 | } |
231 | 257 | } |
232 | 258 | } |
233 | 259 | } |
234 | | - |
235 | 260 | return untiledData; |
236 | 261 | } |
237 | 262 |
|
| 263 | + /// <summary> |
| 264 | + /// Tile for Revolution (Wii) |
| 265 | + /// </summary> |
| 266 | + /// <param name="pixelData">The source linear pixel data.</param> |
| 267 | + /// <param name="width">Image width.</param> |
| 268 | + /// <param name="height">Image height.</param> |
| 269 | + /// <param name="bitDepth">Image bit depth (e.g., 4 for CI4, 8 for CI8, 32 for RGBA8).</param> |
| 270 | + /// <returns>The tiled pixel data.</returns> |
238 | 271 | public static byte[] TileTextureRvl(byte[] pixelData, int width, int height, int bitDepth = 32) |
239 | 272 | { |
| 273 | + // Determine tile dimensions based on bit depth. |
240 | 274 | int xTileSize = 4; |
241 | 275 | int yTileSize = 4; |
242 | 276 | if (bitDepth <= 8) |
243 | 277 | { |
244 | | - xTileSize = 8; // 4x8 |
| 278 | + xTileSize = 8; // 8x4 for CI8/I8 |
245 | 279 | } |
246 | | - |
247 | 280 | if (bitDepth <= 4) |
248 | 281 | { |
249 | | - yTileSize = 8; // 8x8 |
| 282 | + yTileSize = 8; // 8x8 for CI4/I4 |
250 | 283 | } |
251 | 284 |
|
252 | | - bool wide = width > height; |
253 | 285 | var byteSizePerPixel = bitDepth / 8.0f; |
254 | 286 | var scale = 1; |
255 | 287 | bool compactMode = false; |
256 | 288 | int bpp = bitDepth / 8; |
257 | 289 | byte[] tiledData; |
| 290 | + |
| 291 | + // Handle compact formats where one byte stores multiple pixels (e.g., 4bpp). |
258 | 292 | if (byteSizePerPixel < 1.0f) |
259 | 293 | { |
260 | 294 | compactMode = true; |
261 | | - scale = (int) (1.0f / byteSizePerPixel); |
| 295 | + scale = (int) (1.0f / byteSizePerPixel); // For 4bpp, scale is 2. |
262 | 296 | tiledData = new byte[width * height / scale]; |
263 | | - bpp = 1; |
264 | 297 | } |
265 | 298 | else |
266 | 299 | { |
267 | 300 | tiledData = new byte[width * height * bpp]; |
268 | 301 | } |
269 | 302 |
|
270 | | - int dataIndex = 0; |
| 303 | + int dataIndex = 0; // Tracks the current write position in the destination tiled data. |
271 | 304 |
|
| 305 | + // Iterate through the image by tile blocks. |
272 | 306 | for (int yt = 0; yt < height; yt += yTileSize) |
273 | 307 | { |
274 | 308 | for (int xt = 0; xt < width; xt += xTileSize) |
275 | 309 | { |
| 310 | + // Iterate through pixels within a tile. |
276 | 311 | for (int y = yt; y < yt + yTileSize; y++) |
277 | 312 | { |
278 | 313 | for (int x = xt; x < xt + xTileSize; x++) |
279 | 314 | { |
280 | 315 | if (x >= width || y >= height) continue; |
281 | 316 |
|
282 | | - int pixelIndex = ((y * width) + x) * bpp; |
283 | | - |
284 | 317 | if (!compactMode) |
285 | 318 | { |
286 | | - Buffer.BlockCopy(pixelData, pixelIndex, tiledData, dataIndex, bpp); |
| 319 | + // Standard copy for formats with 1 or more bytes per pixel. |
| 320 | + int pixelIndex = ((y * width) + x) * bpp; |
| 321 | + if (pixelIndex + bpp <= pixelData.Length && dataIndex + bpp <= tiledData.Length) |
| 322 | + { |
| 323 | + Buffer.BlockCopy(pixelData, pixelIndex, tiledData, dataIndex, bpp); |
| 324 | + } |
287 | 325 | dataIndex += bpp; |
288 | 326 | } |
289 | 327 | else |
290 | 328 | { |
291 | | - var currentPos = pixelIndex / scale; |
292 | | - if (currentPos >= tiledData.Length) |
| 329 | + // Corrected logic for tiling compact formats (e.g., CI4). |
| 330 | + // 1. Find and extract the 4-bit pixel from the linear source data (pixelData). |
| 331 | + int srcPixelLinearIndex = (y * width) + x; |
| 332 | + int srcByteIndex = srcPixelLinearIndex / scale; // Which source byte to read from. |
| 333 | + int srcNibblePos = srcPixelLinearIndex % 2; // Which nibble (0 or 1) to read. |
| 334 | + |
| 335 | + if (srcByteIndex >= pixelData.Length) continue; |
| 336 | + |
| 337 | + byte sourceByte = pixelData[srcByteIndex]; |
| 338 | + |
| 339 | + // Extract the correct 4-bit pixelValue. |
| 340 | + byte pixelValue = (srcNibblePos == 0) |
| 341 | + ? (byte) (sourceByte >> 4) // Get the left nibble. |
| 342 | + : (byte) (sourceByte & 0x0F); // Get the right nibble. |
| 343 | + |
| 344 | + // 2. Write this 4-bit pixel to the next available position in the tiled destination. |
| 345 | + int destByteIndex = dataIndex / scale; // Which destination byte to write to. |
| 346 | + int destNibblePos = dataIndex % 2; // Which nibble in that byte to write to. |
| 347 | + |
| 348 | + if (destByteIndex >= tiledData.Length) continue; |
| 349 | + |
| 350 | + // Place the pixelValue into the correct nibble, preserving the other nibble. |
| 351 | + if (destNibblePos == 0) |
293 | 352 | { |
294 | | - return tiledData; |
| 353 | + // Even dataIndex: write to the high bits (left nibble). |
| 354 | + tiledData[destByteIndex] = (byte) ((tiledData[destByteIndex] & 0x0F) | (pixelValue << 4)); |
295 | 355 | } |
296 | | - var srcData = pixelData[currentPos]; |
297 | | - var dstPosition = dataIndex / scale; |
298 | | - var dstSubIndex = dataIndex % scale; |
299 | | - tiledData[dstPosition] = dstSubIndex == 0 |
300 | | - ? (byte) ((tiledData[dstPosition] & 0xF0) | srcData) |
301 | | - : (byte) ((tiledData[dstPosition] & 0x0F) | (srcData << 4)); |
| 356 | + else |
| 357 | + { |
| 358 | + // Odd dataIndex: write to the low bits (right nibble). |
| 359 | + tiledData[destByteIndex] = (byte) ((tiledData[destByteIndex] & 0xF0) | pixelValue); |
| 360 | + } |
| 361 | + |
| 362 | + // Move to the next 4-bit pixel position in the destination tile stream. |
302 | 363 | dataIndex++; |
303 | 364 | } |
304 | 365 | } |
305 | 366 | } |
306 | 367 | } |
307 | 368 | } |
308 | | - |
309 | 369 | return tiledData; |
310 | 370 | } |
311 | 371 |
|
|
0 commit comments