mirror of
https://github.com/kuroppoi/entralinked.git
synced 2026-09-08 08:45:14 -05:00
Apply background to Pokédex skins on read
This commit is contained in:
@@ -4,6 +4,9 @@ import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Utility class for reading tiled images (C-Gear & Pokédex skin data) into a usable {@link BufferedImage}.
|
||||
*/
|
||||
@@ -15,42 +18,70 @@ public class TiledImageReader {
|
||||
public static final int COLOR_PALETTE_SIZE = 16;
|
||||
public static final int SCREEN_WIDTH = 256;
|
||||
public static final int SCREEN_HEIGHT = 192;
|
||||
public static final int SCREEN_TILE_COUNT = SCREEN_WIDTH * SCREEN_HEIGHT / TILE_SIZE;
|
||||
public static final int SCREEN_SIZE = SCREEN_WIDTH * SCREEN_HEIGHT;
|
||||
public static final int SCREEN_TILE_COUNT = SCREEN_SIZE / TILE_SIZE;
|
||||
private static final byte[] dexBackgroundColorIndices = new byte[SCREEN_SIZE];
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
|
||||
static {
|
||||
// Load Pokédex skin background color indices
|
||||
try(InputStream inputStream = TiledImageReader.class.getResourceAsStream("/zukan.bin")) {
|
||||
int currentIndex = 0;
|
||||
int valueAmount = -1;
|
||||
int value = -1;
|
||||
|
||||
// Read until end of stream
|
||||
while((valueAmount = inputStream.read()) != -1 && (value = inputStream.read()) != -1) {
|
||||
for(int i = 0; i < valueAmount; i++) {
|
||||
dexBackgroundColorIndices[currentIndex++] = (byte)(value & 63);
|
||||
}
|
||||
}
|
||||
} catch(IOException e) {
|
||||
logger.error("Could not load Pokédex background data", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #readTiledImage(InputStream, int, boolean)} with a tile count of 255.
|
||||
* Reads a C-Gear skin from the specified {@link InputStream}.
|
||||
*
|
||||
* @param normalizeIndices Should be {@code true} if the provided C-Gear skin data is from the original Black & White.
|
||||
* @return A {@link BufferedImage} representing the read C-Gear skin data.
|
||||
*/
|
||||
public static BufferedImage readCGearSkin(InputStream inputStream, boolean normalizeIndices) throws IOException {
|
||||
return readTiledImage(inputStream, 255, normalizeIndices);
|
||||
return readTiledImage(inputStream, 255, 0, null, normalizeIndices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #readTiledImage(InputStream, int, boolean)} with a tile count of 768 and index normalization disabled.
|
||||
* Reads a Pokédex skin from the specified {@link InputStream}.
|
||||
* If the skin in question is an overlay, the Pokédex background will be applied to the resulting image automatically.
|
||||
*
|
||||
* @return A {@link BufferedImage} representing the read Pokédex skin data.
|
||||
*/
|
||||
public static BufferedImage readDexSkin(InputStream inputStream) throws IOException {
|
||||
return readTiledImage(inputStream, 768, false);
|
||||
return readTiledImage(inputStream, 768, 64, dexBackgroundColorIndices, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads tiled image data from the provided {@link InputStream} and returns a {@link BufferedImage} representing the read image data.
|
||||
*
|
||||
* @param tileCount The number of tiles to be read. This should be equal to the maximum number of tiles for this image.
|
||||
* @param backgroundColorCount The number of background colors this image has.
|
||||
* @param backgroundColorIndices The background color indices of the background image. If {@code null}, no background will be used.
|
||||
* @param normalizedIndices Indicates that tile indices are not linear (Black & White C-Gear skins) and should be normalized.
|
||||
* @return A {@link BufferedImage} representing the read image data.
|
||||
*/
|
||||
public static BufferedImage readTiledImage(InputStream inputStream, int tileCount, boolean normalizeIndices) throws IOException {
|
||||
public static BufferedImage readTiledImage(InputStream inputStream,
|
||||
int tileCount, int backgroundColorCount, byte[] backgroundColorIndices, boolean normalizeIndices) throws IOException {
|
||||
BufferedImage image = new BufferedImage(SCREEN_WIDTH, SCREEN_HEIGHT, BufferedImage.TYPE_INT_RGB); // Result image
|
||||
int[] tileData = new int[tileCount * TILE_SIZE];
|
||||
int[] tileIndices = new int[tileCount]; // Tile index lookup table
|
||||
int[] colorPalette = new int[COLOR_PALETTE_SIZE];
|
||||
int[] backgroundColorPalette = new int[backgroundColorCount];
|
||||
boolean[] transparencyMarkers = new boolean[COLOR_PALETTE_SIZE]; // Keeps track of which colors should become background colors
|
||||
|
||||
// Read tile data.
|
||||
for(int i = 0; i < tileCount; i++) {
|
||||
// TODO This next loop is a bottleneck
|
||||
for(int j = 0; j < TILE_SIZE / 2; j++) {
|
||||
int paletteIndices = inputStream.read(); // Contains color palette indices for 2 adjacent pixels.
|
||||
tileData[i * TILE_SIZE + j * 2] = paletteIndices & (COLOR_PALETTE_SIZE - 1);
|
||||
@@ -61,17 +92,26 @@ public class TiledImageReader {
|
||||
tileIndices[i] = normalizeIndices ? i + i / 17 * 15 + 0xA0A0 : i;
|
||||
}
|
||||
|
||||
// Read color data.
|
||||
// Read foreground color data.
|
||||
for(int i = 0; i < COLOR_PALETTE_SIZE; i++) {
|
||||
int leftBits = inputStream.read();
|
||||
int rightBits = inputStream.read();
|
||||
|
||||
// So far, the least significant bits of colors that are replaced by background colors are always 0x2A.
|
||||
// TODO Needs more thorough testing -- is this check reliable? What do the most significant bits signify here?
|
||||
if(backgroundColorIndices != null && rightBits == 0x2A) {
|
||||
transparencyMarkers[i] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
colorPalette[i] = convertColor(leftBits | rightBits << 8);
|
||||
}
|
||||
|
||||
// Read background color data.
|
||||
// Pokédex skins contain room for 240 extra colors, 64 of which are defined and appear to be used as the
|
||||
// 'background' colors in cases where the skin is only an overlay that is displayed on top of the 'true' Pokédex.
|
||||
for(int i = 0; i < COLOR_PALETTE_SIZE; i++) {
|
||||
int color = inputStream.read() | inputStream.read() << 8;
|
||||
|
||||
// Convert BGR555 to RGB888
|
||||
int red = (color & 0x1F) << 3;
|
||||
int green = ((color & 0x3E0) >> 5) << 3;
|
||||
int blue = ((color & 0x7C00) >> 10) << 3;
|
||||
colorPalette[i] = (red << 16) | (green << 8) | blue;
|
||||
for(int i = 0; i < backgroundColorCount; i++) {
|
||||
backgroundColorPalette[i] = convertColor(inputStream.read() | inputStream.read() << 8);
|
||||
}
|
||||
|
||||
// Map tiles to the resulting image.
|
||||
@@ -107,10 +147,17 @@ public class TiledImageReader {
|
||||
case 12 -> TILE_SIZE - j - 1; // Flip horizontally & vertically
|
||||
default -> j; // Don't flip
|
||||
};
|
||||
|
||||
// Finally, set the pixel!
|
||||
|
||||
int pixelX = x + j % TILE_WIDTH;
|
||||
int pixelY = y + j / TILE_WIDTH;
|
||||
int paletteIndex = tileData[tileIndex * TILE_SIZE + tilePixelIndex];
|
||||
image.setRGB(x + j % TILE_WIDTH, y + j / TILE_WIDTH, colorPalette[paletteIndex]);
|
||||
|
||||
// If this color has been marked as a 'transparent' color, use the color from the background instead.
|
||||
int color = !transparencyMarkers[paletteIndex] ? colorPalette[paletteIndex]
|
||||
: backgroundColorPalette[backgroundColorIndices[pixelY * SCREEN_WIDTH + pixelX]];
|
||||
|
||||
// Finally, set the pixel!
|
||||
image.setRGB(pixelX, pixelY, color);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -120,11 +167,28 @@ public class TiledImageReader {
|
||||
int y = i * TILE_WIDTH / SCREEN_WIDTH * TILE_WIDTH;
|
||||
|
||||
for(int j = 0; j < TILE_SIZE; j++) {
|
||||
image.setRGB(x + j % TILE_WIDTH, y + j / TILE_WIDTH, colorPalette[tileData[i * TILE_SIZE + j]]);
|
||||
int pixelX = x + j % TILE_WIDTH;
|
||||
int pixelY = y + j / TILE_WIDTH;
|
||||
int paletteIndex = tileData[i * TILE_SIZE + j];
|
||||
int color = !transparencyMarkers[paletteIndex] ? colorPalette[paletteIndex]
|
||||
: backgroundColorPalette[backgroundColorIndices[pixelY * SCREEN_WIDTH + pixelX]];
|
||||
image.setRGB(pixelX, pixelY, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the input BGR555 color value to an RGB888 color value.
|
||||
*
|
||||
* @return The RGB888 color value.
|
||||
*/
|
||||
private static int convertColor(int color) {
|
||||
int red = (color & 0x1F) << 3;
|
||||
int green = ((color & 0x3E0) >> 5) << 3;
|
||||
int blue = ((color & 0x7C00) >> 10) << 3;
|
||||
return (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/main/resources/zukan.bin
Normal file
BIN
src/main/resources/zukan.bin
Normal file
Binary file not shown.
Reference in New Issue
Block a user