using System; using System.Collections.Generic; using System.Linq; using System.Text; using BizHawk.Emulation.Common; using System.Runtime.InteropServices; using System.IO; namespace BizHawk.Client.Common { public class QuickBmpFile { #region structs [StructLayout(LayoutKind.Sequential, Pack = 1)] class BITMAPFILEHEADER { public ushort bfType; public uint bfSize; public ushort bfReserved1; public ushort bfReserved2; public uint bfOffBits; public BITMAPFILEHEADER() { bfSize = (uint)Marshal.SizeOf(this); } public static BITMAPFILEHEADER FromStream(Stream s) { var ret = GetObject(s); if (ret.bfSize != Marshal.SizeOf(typeof(BITMAPFILEHEADER))) throw new InvalidOperationException(); return ret; } } [StructLayout(LayoutKind.Sequential)] class BITMAPINFOHEADER { public uint biSize; public int biWidth; public int biHeight; public ushort biPlanes; public ushort biBitCount; public BitmapCompressionMode biCompression; public uint biSizeImage; public int biXPelsPerMeter; public int biYPelsPerMeter; public uint biClrUsed; public uint biClrImportant; public BITMAPINFOHEADER() { biSize = (uint)Marshal.SizeOf(this); } public static BITMAPINFOHEADER FromStream(Stream s) { var ret = GetObject(s); if (ret.biSize != Marshal.SizeOf(typeof(BITMAPINFOHEADER))) throw new InvalidOperationException(); return ret; } } enum BitmapCompressionMode : uint { BI_RGB = 0, BI_RLE8 = 1, BI_RLE4 = 2, BI_BITFIELDS = 3, BI_JPEG = 4, BI_PNG = 5 } #endregion private unsafe static byte[] GetBytes(object o) { byte[] ret = new byte[Marshal.SizeOf(o)]; fixed (byte* p = ret) { Marshal.StructureToPtr(o, (IntPtr)p, false); } return ret; } private unsafe static T GetObject(Stream s) { byte[] tmp = new byte[Marshal.SizeOf(typeof(T))]; s.Read(tmp, 0, tmp.Length); fixed (byte* p = tmp) { return (T)Marshal.PtrToStructure((IntPtr)p, typeof(T)); } } unsafe struct BMP { public int* Data; public int Width; public int Height; } static void Blit(BMP src, BMP dst) { if (src.Width == dst.Width && src.Height == dst.Height) Blit_Same(src, dst); else Blit_Any(src, dst); } unsafe static void Blit_Same(BMP src, BMP dst) { int* sp = src.Data + src.Width * (src.Height - 1); int* dp = dst.Data; for (int j = 0; j < src.Height; j++) { for (int i = 0; i < src.Width; i++) dp[i] = sp[i]; sp -= src.Width; dp += src.Width; } } unsafe static void Blit_Any(BMP src, BMP dst) { int w = dst.Width; int h = dst.Height; int in_w = src.Width; int in_h = src.Height; int* sp = src.Data; int* dp = dst.Data; // vflip along the way for (int j = h - 1; j >= 0; j--) { sp = src.Data + in_w * (j * in_h / h); for (int i = 0; i < w; i++) { dp[i] = sp[i * in_w / w]; } dp += w; } } public unsafe static bool Load(IVideoProvider v, Stream s) { var bf = BITMAPFILEHEADER.FromStream(s); var bi = BITMAPINFOHEADER.FromStream(s); if (bf.bfType != 0x4d42 || bf.bfOffBits != bf.bfSize + bi.biSize || bi.biPlanes != 1 || bi.biBitCount != 32 || bi.biCompression != BitmapCompressionMode.BI_RGB) return false; int in_w = bi.biWidth; int in_h = bi.biHeight; byte[] src = new byte[in_w * in_h * 4]; s.Read(src, 0, src.Length); int[] dst = v.GetVideoBuffer(); fixed (byte *srcp = src) fixed (int* dstp = dst) { using (new BizHawk.Common.SimpleTime("Blit")) Blit(new BMP { Data = (int*)srcp, Width = in_w, Height = in_h }, new BMP { Data = dstp, Width = v.BufferWidth, Height = v.BufferHeight, }); } return true; } public unsafe static void Save(IVideoProvider v, Stream s, int w, int h) { var bf = new BITMAPFILEHEADER(); var bi = new BITMAPINFOHEADER(); bf.bfType = 0x4d42; bf.bfOffBits = bf.bfSize + bi.biSize; bi.biPlanes = 1; bi.biBitCount = 32; // xrgb bi.biCompression = BitmapCompressionMode.BI_RGB; bi.biSizeImage = (uint)(w * h * 4); bi.biWidth = w; bi.biHeight = h; byte[] bfb = GetBytes(bf); byte[] bib = GetBytes(bi); s.Write(bfb, 0, bfb.Length); s.Write(bib, 0, bib.Length); int[] src = v.GetVideoBuffer(); byte[] dst = new byte[4 * w * h]; fixed (int* srcp = src) fixed (byte* dstp = dst) { using (new BizHawk.Common.SimpleTime("Blit")) Blit(new BMP { Data = srcp, Width = v.BufferWidth, Height = v.BufferHeight }, new BMP { Data = (int*)dstp, Width = w, Height = h, }); } s.Write(dst, 0, dst.Length); } } }