using System; namespace Oni.Imaging { internal class Surface { private int width; private int height; private int stride; private int pixelSize; private SurfaceFormat format; private byte[] data; public Surface(int width, int height) : this(width, height, SurfaceFormat.RGBA) { } public Surface(int width, int height, SurfaceFormat format) { if (format == SurfaceFormat.DXT1) { width = Math.Max(width, 4); height = Math.Max(height, 4); } this.width = width; this.height = height; this.format = format; pixelSize = GetPixelSize(format); stride = pixelSize * width; data = new byte[GetDataSize(width, height, format)]; } public Surface(int width, int height, SurfaceFormat format, byte[] data) { if (format == SurfaceFormat.DXT1) { width = Math.Max(width, 4); height = Math.Max(height, 4); } this.width = width; this.height = height; this.format = format; this.data = data; pixelSize = GetPixelSize(format); stride = pixelSize * width; } private static int GetDataSize(int width, int height, SurfaceFormat format) { switch (format) { case SurfaceFormat.BGRA4444: case SurfaceFormat.BGRX5551: case SurfaceFormat.BGRA5551: return width * height * 2; case SurfaceFormat.BGRX: case SurfaceFormat.BGRA: case SurfaceFormat.RGBX: case SurfaceFormat.RGBA: return width * height * 4; case SurfaceFormat.DXT1: return width * height / 2; default: throw new NotSupportedException(string.Format("Unsupported texture format {0}", format)); } } private static int GetPixelSize(SurfaceFormat format) { switch (format) { case SurfaceFormat.BGRA4444: case SurfaceFormat.BGRX5551: case SurfaceFormat.BGRA5551: return 2; case SurfaceFormat.BGRX: case SurfaceFormat.BGRA: case SurfaceFormat.RGBX: case SurfaceFormat.RGBA: return 4; case SurfaceFormat.DXT1: return 2; default: throw new NotSupportedException(string.Format("Unsupported texture format {0}", format)); } } public int Width => width; public int Height => height; public SurfaceFormat Format => format; public byte[] Data => data; public bool HasAlpha { get { switch (format) { case SurfaceFormat.BGRA: case SurfaceFormat.RGBA: case SurfaceFormat.BGRA4444: case SurfaceFormat.BGRA5551: return true; default: return false; } } } public void CleanupAlpha() { if (format != SurfaceFormat.BGRA5551 && format != SurfaceFormat.RGBA && format != SurfaceFormat.BGRA) return; if (!HasTransparentPixels()) { switch (format) { case SurfaceFormat.BGRA5551: format = SurfaceFormat.BGRX5551; break; case SurfaceFormat.BGRA: format = SurfaceFormat.BGRX; break; case SurfaceFormat.RGBA: format = SurfaceFormat.RGBX; break; } } } public bool HasTransparentPixels() { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Color c = GetPixel(x, y); if (c.A != 255) return true; } } return false; } public Color this[int x, int y] { get { if (x < 0 || width <= x || y < 0 || height <= y) return Color.Black; return GetPixel(x, y); } set { if (x < 0 || width <= x || y < 0 || height <= y) return; SetPixel(x, y, value); } } public void FlipVertical() { var temp = new byte[stride]; for (int y = 0; y < height / 2; y++) { int ry = height - y - 1; Array.Copy(data, y * stride, temp, 0, stride); Array.Copy(data, ry * stride, data, y * stride, stride); Array.Copy(temp, 0, data, ry * stride, stride); } } public void FlipHorizontal() { for (int y = 0; y < height; y++) { for (int x = 0; x < width / 2; x++) { int rx = width - x - 1; Color c1 = GetPixel(x, y); Color c2 = GetPixel(rx, y); SetPixel(x, y, c2); SetPixel(rx, y, c1); } } } public void Rotate90() { for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if (x <= y) continue; var c1 = GetPixel(x, y); var c2 = GetPixel(y, x); SetPixel(x, y, c2); SetPixel(y, x, c1); } } } public Surface Convert(SurfaceFormat dstFormat) { Surface dst; if (format == dstFormat) { dst = new Surface(width, height, dstFormat, (byte[])data.Clone()); } else if (dstFormat == SurfaceFormat.DXT1) { dst = Dxt1.Compress(this); } else if (format == SurfaceFormat.DXT1) { dst = Dxt1.Decompress(this, dstFormat); } else { dst = new Surface(width, height, dstFormat); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) dst.SetPixel(x, y, GetPixel(x, y)); } } return dst; } public Surface Resize(int newWidth, int newHeight) { if (newWidth > width || newHeight > height) throw new NotImplementedException(); var dst = new Surface(newWidth, newHeight, format); if (newWidth * 2 == width && newHeight * 2 == height) { Halfsize(dst); return dst; } float sx = (float)width / dst.width; float sy = (float)height / dst.height; for (int dstY = 0; dstY < dst.height; dstY++) { float top = dstY * sy; float bottom = top + sy; int yTop = (int)top; int yBottom = (int)(bottom - 0.001f); float topWeight = 1.0f - (top - yTop); float bottomWeight = bottom - yBottom; for (int dstX = 0; dstX < dst.width; dstX++) { float left = dstX * sx; float right = left + sx; int xLeft = (int)left; int xRight = (int)(right - 0.001f); float leftWeight = 1.0f - (left - xLeft); float rightWeight = right - xRight; var sum = GetVector4(xLeft, yTop) * (leftWeight * topWeight); sum += GetVector4(xRight, yTop) * (rightWeight * topWeight); sum += GetVector4(xLeft, yBottom) * (leftWeight * bottomWeight); sum += GetVector4(xRight, yBottom) * (rightWeight * bottomWeight); for (int y = yTop + 1; y < yBottom; y++) { sum += GetVector4(xLeft, y) * leftWeight; sum += GetVector4(xRight, y) * rightWeight; } for (int x = xLeft + 1; x < xRight; x++) { sum += GetVector4(x, yTop) * topWeight; sum += GetVector4(x, yBottom) * bottomWeight; } for (int y = yTop + 1; y < yBottom; y++) { for (int x = xLeft + 1; x < xRight; x++) sum += GetVector4(x, y); } float area = (right - left) * (bottom - top); dst.SetPixel(dstX, dstY, new Color(sum / area)); } } return dst; } private void Halfsize(Surface dst) { int halfWidth = dst.width; int halfHeight = dst.height; for (int dstY = 0; dstY < halfHeight; dstY++) { int yTop = dstY * 2; int yBottom = yTop + 1; for (int dstX = 0; dstX < halfWidth; dstX++) { int xLeft = dstX * 2; int xRight = xLeft + 1; var sum = GetVector4(xLeft, yTop); sum += GetVector4(xRight, yTop); sum += GetVector4(xLeft, yBottom); sum += GetVector4(xRight, yBottom); dst.SetPixel(dstX, dstY, new Color(sum / 4.0f)); } } } private Vector4 GetVector4(int x, int y) { return GetPixel(x, y).ToVector4(); } private Color GetPixel(int x, int y) { int i = x * pixelSize + y * stride; switch (format) { case SurfaceFormat.BGRA4444: return Color.ReadBgra4444(data, i); case SurfaceFormat.BGRX5551: return Color.ReadBgrx5551(data, i); case SurfaceFormat.BGRA5551: return Color.ReadBgra5551(data, i); case SurfaceFormat.BGR565: return Color.ReadBgr565(data, i); case SurfaceFormat.BGRX: return Color.ReadBgrx(data, i); case SurfaceFormat.BGRA: return Color.ReadBgra(data, i); case SurfaceFormat.RGBX: return Color.ReadRgbx(data, i); case SurfaceFormat.RGBA: return Color.ReadRgba(data, i); default: throw new NotSupportedException(string.Format("Unsupported texture format {0}", format)); } } private void SetPixel(int x, int y, Color color) { int i = x * pixelSize + y * stride; switch (format) { case SurfaceFormat.BGRA4444: Color.WriteBgra4444(color, data, i); return; case SurfaceFormat.BGRX5551: Color.WriteBgrx5551(color, data, i); return; case SurfaceFormat.BGRA5551: Color.WriteBgra5551(color, data, i); return; case SurfaceFormat.BGR565: Color.WriteBgr565(color, data, i); return; case SurfaceFormat.BGRX: Color.WriteBgrx(color, data, i); return; case SurfaceFormat.BGRA: Color.WriteBgra(color, data, i); return; case SurfaceFormat.RGBX: Color.WriteRgbx(color, data, i); return; case SurfaceFormat.RGBA: Color.WriteRgba(color, data, i); return; default: throw new NotSupportedException(string.Format("Unsupported texture format {0}", format)); } } public void Fill(int x, int y, int width, int height, Color color) { for (int px = x; px < x + width; px++) { for (int py = y; py < y + height; py++) SetPixel(px, py, color); } } } }