Files
SM64Lib/SM64Lib.N64Graphics/N64Graphics.cs

525 lines
21 KiB
C#

using System.Drawing;
namespace SM64Lib.N64Graphics
{
public enum N64Codec { RGBA16, RGBA32, IA16, IA8, IA4, I8, I4, CI8, CI4, ONEBPP };
public enum N64IMode { AlphaCopyIntensity, AlphaBinary, AlphaOne };
public class N64Graphics
{
private static int SCALE_5_8(int val)
{
return (val * 0xFF) / 0x1F;
}
private static byte SCALE_8_5(byte val)
{
return (byte)((((val) + 4) * 0x1F) / 0xFF);
}
private static byte SCALE_8_4(byte val)
{
return (byte)(val / 0x11);
}
private static int SCALE_3_8(byte val)
{
return (val * 0xFF) / 0x7;
}
private static byte SCALE_8_3(byte val)
{
return (byte)(val / 0x24);
}
public static Color RGBA16Color(byte c0, byte c1)
{
int r = SCALE_5_8((c0 & 0xF8) >> 3);
int g = SCALE_5_8(((c0 & 0x07) << 2) | ((c1 & 0xC0) >> 6));
int b = SCALE_5_8((c1 & 0x3E) >> 1);
int a = ((c1 & 0x1) > 0) ? 255 : 0;
return Color.FromArgb(a, r, g, b);
}
public static void ColorRGBA16(Color col, out byte c0, out byte c1)
{
byte r, g, b;
r = SCALE_8_5(col.R);
g = SCALE_8_5(col.G);
b = SCALE_8_5(col.B);
c0 = (byte)((r << 3) | (g >> 2));
c1 = (byte)(((g & 0x3) << 6) | (b << 1) | ((col.A > 0) ? 1 : 0));
}
public static Color RGBA16Color(byte[] data, int pixOffset)
{
byte c0 = data[pixOffset];
byte c1 = data[pixOffset + 1];
return RGBA16Color(c0, c1);
}
public static Color RGBA32Color(byte[] data, int pixOffset)
{
int r, g, b, a;
r = data[pixOffset];
g = data[pixOffset + 1];
b = data[pixOffset + 2];
a = data[pixOffset + 3];
return Color.FromArgb(a, r, g, b);
}
public static Color IA16Color(byte[] data, int pixOffset)
{
int i = data[pixOffset];
int a = data[pixOffset + 1];
return Color.FromArgb(a, i, i, i);
}
public static Color IA8Color(byte[] data, int pixOffset)
{
int i, a;
byte c = data[pixOffset];
i = (c >> 4) * 0x11;
a = (c & 0xF) * 0x11;
return Color.FromArgb(a, i, i, i);
}
public static Color IA4Color(byte[] data, int pixOffset, int nibble)
{
int shift = (1 - nibble) * 4;
int i, a;
int val = (data[pixOffset] >> shift) & 0xF;
i = SCALE_3_8((byte)(val >> 1));
a = (val & 0x1) > 0 ? 0xFF : 0x00;
return Color.FromArgb(a, i, i, i);
}
public static Color I8Color(byte[] data, int pixOffset, N64IMode mode = N64IMode.AlphaCopyIntensity)
{
int i = data[pixOffset];
int a = i;
switch (mode)
{
case N64IMode.AlphaBinary: a = (i == 0) ? 0 : 0xFF; break;
case N64IMode.AlphaCopyIntensity: a = i; break;
case N64IMode.AlphaOne: a = 0xFF; break;
}
return Color.FromArgb(a, i, i, i);
}
public static Color I4Color(byte[] data, int pixOffset, int nibble, N64IMode mode = N64IMode.AlphaCopyIntensity)
{
int shift = (1 - nibble) * 4;
int i = (data[pixOffset] >> shift) & 0xF;
i *= 0x11;
int a = i;
switch (mode)
{
case N64IMode.AlphaBinary: a = (i == 0) ? 0 : 0xFF; break;
case N64IMode.AlphaCopyIntensity: a = i; break;
case N64IMode.AlphaOne: a = 0xFF; break;
}
return Color.FromArgb(a, i, i, i);
}
public static Color CI8Color(byte[] data, byte[] palette, int pixOffset)
{
byte c0, c1;
int palOffset = 2 * data[pixOffset];
c0 = palette[palOffset];
c1 = palette[palOffset + 1];
return RGBA16Color(c0, c1);
}
public static Color CI4Color(byte[] data, byte[] palette, int pixOffset, int nibble)
{
byte c0, c1;
int shift = (1 - nibble) * 4;
int palOffset = 2 * ((data[pixOffset] >> shift) & 0xF);
c0 = palette[palOffset];
c1 = palette[palOffset + 1];
return RGBA16Color(c0, c1);
}
public static Color BPPColor(byte[] data, int pixOffset, int bit)
{
int i, a;
int val = (data[pixOffset] >> (7 - bit)) & 0x1;
i = a = val == 0 ? 0x00 : 0xFF;
return Color.FromArgb(a, i, i, i);
}
// return number of bytes needed to encode numPixels using codec
public static int PixelsToBytes(N64Codec codec, int numPixels)
{
int numBytes = 0;
switch (codec)
{
case N64Codec.RGBA16: numBytes = numPixels * 2; break;
case N64Codec.RGBA32: numBytes = numPixels * 4; break;
case N64Codec.IA16: numBytes = numPixels * 2; break;
case N64Codec.IA8: numBytes = numPixels; break;
case N64Codec.IA4: numBytes = numPixels / 2; break;
case N64Codec.I8: numBytes = numPixels; break;
case N64Codec.I4: numBytes = numPixels / 2; break;
case N64Codec.CI8: numBytes = numPixels; break;
case N64Codec.CI4: numBytes = numPixels / 2; break;
case N64Codec.ONEBPP: numBytes = numPixels / 8; break;
}
return numBytes;
}
public static string CodecString(N64Codec codec)
{
switch (codec)
{
case N64Codec.RGBA16: return "rgba16";
case N64Codec.RGBA32: return "rgba32";
case N64Codec.IA16: return "ia16";
case N64Codec.IA8: return "ia8";
case N64Codec.IA4: return "ia4";
case N64Codec.I8: return "i8";
case N64Codec.I4: return "i4";
case N64Codec.CI8: return "ci8";
case N64Codec.CI4: return "ci4";
case N64Codec.ONEBPP: return "1bpp";
}
return "unk";
}
public static N64Codec StringCodec(string str)
{
switch (str.ToLower())
{
default:
case "rgba16": return N64Codec.RGBA16;
case "rgba32": return N64Codec.RGBA32;
case "ia16": return N64Codec.IA16;
case "ia8": return N64Codec.IA8;
case "ia4": return N64Codec.IA4;
case "i8": return N64Codec.I8;
case "i4": return N64Codec.I4;
case "ci8": return N64Codec.CI8;
case "ci4": return N64Codec.CI4;
case "1bpp": return N64Codec.ONEBPP;
}
}
public static Color MakeColor(byte[] data, byte[] palette, int offset, int select, N64Codec codec, N64IMode mode)
{
Color color;
switch (codec)
{
case N64Codec.RGBA16:
color = RGBA16Color(data, offset);
break;
case N64Codec.RGBA32:
color = RGBA32Color(data, offset);
break;
case N64Codec.IA16:
color = IA16Color(data, offset);
break;
case N64Codec.IA8:
color = IA8Color(data, offset);
break;
case N64Codec.IA4:
color = IA4Color(data, offset, select);
break;
case N64Codec.I8:
color = I8Color(data, offset, mode);
break;
case N64Codec.I4:
color = I4Color(data, offset, select, mode);
break;
case N64Codec.CI8:
color = CI8Color(data, palette, offset);
break;
case N64Codec.CI4:
color = CI4Color(data, palette, offset, select);
break;
case N64Codec.ONEBPP:
color = BPPColor(data, offset, select);
break;
default:
color = RGBA16Color(data, offset);
break;
}
return color;
}
public static void RenderTexture(Graphics g, byte[] data, byte[] palette, int offset, int width, int height, int scale, N64Codec codec, N64IMode mode)
{
Brush brush;
for (int h = 0; h < height; h++)
{
for (int w = 0; w < width; w++)
{
int pixOffset = (h * width + w);
int bytesPerPix = 1;
int select = 0;
switch (codec)
{
case N64Codec.RGBA16: bytesPerPix = 2; pixOffset *= bytesPerPix; break;
case N64Codec.RGBA32: bytesPerPix = 4; pixOffset *= bytesPerPix; break;
case N64Codec.IA16: bytesPerPix = 2; pixOffset *= bytesPerPix; break;
case N64Codec.IA8: break;
case N64Codec.IA4:
select = pixOffset & 0x1;
pixOffset /= 2;
break;
case N64Codec.I8: break;
case N64Codec.I4:
case N64Codec.CI4:
select = pixOffset & 0x1;
pixOffset /= 2;
break;
case N64Codec.CI8: break;
case N64Codec.ONEBPP:
select = pixOffset & 0x7;
pixOffset /= 8;
break;
}
pixOffset += offset;
if (data.Length > pixOffset + bytesPerPix - 1)
{
brush = new SolidBrush(MakeColor(data, palette, pixOffset, select, codec, mode));
g.FillRectangle(brush, w * scale, h * scale, scale, scale);
}
}
}
}
// return palette index of matching c0/c1 16-bit dataset, or -1 if not found
private static int paletteIndex(byte[] pal, int palCount, byte c0, byte c1)
{
for (int i = 0; i < palCount; i++)
{
if (pal[2 * i] == c0 && pal[2 * i + 1] == c1)
{
return i;
}
}
return -1;
}
public static void Convert(ref byte[] imageData, ref byte[] paletteData, N64Codec codec, Bitmap bm)
{
int numPixels = bm.Width * bm.Height;
imageData = new byte[PixelsToBytes(codec, numPixels)];
int palCount = 0;
switch (codec)
{
case N64Codec.RGBA16:
for (int y = 0; y < bm.Height; y++)
{
for (int x = 0; x < bm.Width; x++)
{
Color col = bm.GetPixel(x, y);
byte c0;
byte c1;
ColorRGBA16(col, out c0, out c1);
int idx = 2 * (y * bm.Width + x);
imageData[idx + 0] = c0;
imageData[idx + 1] = c1;
}
}
break;
case N64Codec.RGBA32:
for (int y = 0; y < bm.Height; y++)
{
for (int x = 0; x < bm.Width; x++)
{
Color col = bm.GetPixel(x, y);
int idx = 4 * (y * bm.Width + x);
imageData[idx + 0] = col.R;
imageData[idx + 1] = col.G;
imageData[idx + 2] = col.B;
imageData[idx + 3] = col.A;
}
}
break;
case N64Codec.IA16:
for (int y = 0; y < bm.Height; y++)
{
for (int x = 0; x < bm.Width; x++)
{
Color col = bm.GetPixel(x, y);
int sum = col.R + col.G + col.B;
byte intensity = (byte)(sum / 3);
byte alpha = col.A;
int idx = 2 * (y * bm.Width + x);
imageData[idx + 0] = intensity;
imageData[idx + 1] = alpha;
}
}
break;
case N64Codec.IA8:
for (int y = 0; y < bm.Height; y++)
{
for (int x = 0; x < bm.Width; x++)
{
Color col = bm.GetPixel(x, y);
int sum = col.R + col.G + col.B;
byte intensity = SCALE_8_4((byte)(sum / 3));
byte alpha = SCALE_8_4(col.A);
int idx = y * bm.Width + x;
imageData[idx] = (byte)((intensity << 4) | alpha);
}
}
break;
case N64Codec.IA4:
for (int y = 0; y < bm.Height; y++)
{
for (int x = 0; x < bm.Width; x++)
{
Color col = bm.GetPixel(x, y);
int sum = col.R + col.G + col.B;
byte intensity = SCALE_8_3((byte)(sum / 3));
byte alpha = (byte)(col.A > 0 ? 1 : 0);
int idx = y * bm.Width + x;
byte old = imageData[idx / 2];
if ((idx % 2) > 0)
{
imageData[idx / 2] = (byte)((old & 0xF0) | (intensity << 1) | alpha);
}
else
{
imageData[idx / 2] = (byte)((old & 0x0F) | (((intensity << 1) | alpha) << 4));
}
}
}
break;
case N64Codec.I8:
for (int y = 0; y < bm.Height; y++)
{
for (int x = 0; x < bm.Width; x++)
{
Color col = bm.GetPixel(x, y);
int sum = col.R + col.G + col.B;
byte intensity = (byte)(sum / 3);
int idx = y * bm.Width + x;
imageData[idx] = intensity;
}
}
break;
case N64Codec.I4:
for (int y = 0; y < bm.Height; y++)
{
for (int x = 0; x < bm.Width; x++)
{
Color col = bm.GetPixel(x, y);
int sum = col.R + col.G + col.B;
byte intensity = SCALE_8_4((byte)(sum / 3));
int idx = y * bm.Width + x;
byte old = imageData[idx / 2];
if ((idx % 2) > 0)
{
imageData[idx / 2] = (byte)((old & 0xF0) | intensity);
}
else
{
imageData[idx / 2] = (byte)((old & 0x0F) | (intensity << 4));
}
}
}
break;
case N64Codec.CI4:
paletteData = new byte[16 * 2];
for (int y = 0; y < bm.Height; y++)
{
for (int x = 0; x < bm.Width; x++)
{
Color col = bm.GetPixel(x, y);
byte r, g, b;
r = SCALE_8_5(col.R);
g = SCALE_8_5(col.G);
b = SCALE_8_5(col.B);
byte c0 = (byte)((r << 3) | (g >> 2));
byte c1 = (byte)(((g & 0x3) << 6) | (b << 1) | ((col.A > 0) ? 1 : 0));
int idx = y * bm.Width + x;
int palIdx = paletteIndex(paletteData, palCount, c0, c1);
if (palIdx < 0)
{
if (palCount < paletteData.Length / 2)
{
palIdx = palCount;
paletteData[2 * palCount] = c0;
paletteData[2 * palCount + 1] = c1;
palCount++;
}
else
{
palIdx = 0;
// TODO: out of palette entries. error or pick closest?
}
}
byte old = imageData[idx / 2];
if ((idx % 2) > 0)
{
imageData[idx / 2] = (byte)((old & 0xF0) | (byte)palIdx);
}
else
{
imageData[idx / 2] = (byte)((old & 0x0F) | ((byte)palIdx << 4));
}
}
}
break;
case N64Codec.CI8:
paletteData = new byte[256 * 2];
for (int y = 0; y < bm.Height; y++)
{
for (int x = 0; x < bm.Width; x++)
{
Color col = bm.GetPixel(x, y);
byte r, g, b;
r = SCALE_8_5(col.R);
g = SCALE_8_5(col.G);
b = SCALE_8_5(col.B);
byte c0 = (byte)((r << 3) | (g >> 2));
byte c1 = (byte)(((g & 0x3) << 6) | (b << 1) | ((col.A > 0) ? 1 : 0));
int idx = y * bm.Width + x;
int palIdx = paletteIndex(paletteData, palCount, c0, c1);
if (palIdx < 0)
{
if (palCount < paletteData.Length / 2)
{
palIdx = palCount;
paletteData[2 * palCount] = c0;
paletteData[2 * palCount + 1] = c1;
palCount++;
}
else
{
palIdx = 0;
// TODO: out of palette entries. error or pick closest?
}
}
imageData[idx] = (byte)palIdx;
}
}
break;
case N64Codec.ONEBPP:
for (int y = 0; y < bm.Height; y++)
{
for (int x = 0; x < bm.Width; x++)
{
Color col = bm.GetPixel(x, y);
int sum = col.R + col.G + col.B;
byte intensity = (sum > 0) ? (byte)1 : (byte)0;
int idx = y * bm.Width + x;
byte old = imageData[idx / 8];
int bit = idx % 8;
int mask = ~(1 << bit);
imageData[idx / 8] = (byte)((old & mask) | (intensity << bit));
}
}
break;
}
}
}
}