2015-02-01 22:49:53 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
2017-04-14 19:59:01 +00:00
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
2017-05-18 16:36:38 +00:00
|
|
|
|
using BizHawk.Common;
|
2017-04-14 19:59:01 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
2015-02-01 22:49:53 +00:00
|
|
|
|
|
2017-05-19 18:17:07 +00:00
|
|
|
|
// ReSharper disable InconsistentNaming
|
|
|
|
|
// ReSharper disable StyleCop.SA1304
|
|
|
|
|
// ReSharper disable StyleCop.SA1307
|
|
|
|
|
// ReSharper disable StyleCop.SA1401
|
2015-02-01 22:49:53 +00:00
|
|
|
|
namespace BizHawk.Client.Common
|
|
|
|
|
{
|
|
|
|
|
public class QuickBmpFile
|
|
|
|
|
{
|
2017-04-14 19:59:01 +00:00
|
|
|
|
#region Structs
|
|
|
|
|
|
2015-02-01 22:49:53 +00:00
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
2017-06-27 22:14:15 +00:00
|
|
|
|
private class BITMAPFILEHEADER
|
2015-02-01 22:49:53 +00:00
|
|
|
|
{
|
|
|
|
|
public ushort bfType;
|
2017-06-27 22:14:15 +00:00
|
|
|
|
public uint bfSize;
|
2015-02-01 22:49:53 +00:00
|
|
|
|
public ushort bfReserved1;
|
|
|
|
|
public ushort bfReserved2;
|
|
|
|
|
public uint bfOffBits;
|
|
|
|
|
|
2017-06-27 22:14:15 +00:00
|
|
|
|
public BITMAPFILEHEADER()
|
2015-02-01 22:49:53 +00:00
|
|
|
|
{
|
|
|
|
|
bfSize = (uint)Marshal.SizeOf(this);
|
|
|
|
|
}
|
2015-02-02 00:25:50 +00:00
|
|
|
|
|
2019-12-31 22:24:46 +00:00
|
|
|
|
/// <exception cref="InvalidOperationException">misformatted data</exception>
|
2017-06-27 22:14:15 +00:00
|
|
|
|
public static BITMAPFILEHEADER FromStream(Stream s)
|
2015-02-02 00:25:50 +00:00
|
|
|
|
{
|
2017-06-27 22:14:15 +00:00
|
|
|
|
var ret = GetObject<BITMAPFILEHEADER>(s);
|
|
|
|
|
if (ret.bfSize != Marshal.SizeOf(typeof(BITMAPFILEHEADER)))
|
2017-04-14 19:59:01 +00:00
|
|
|
|
{
|
2015-02-02 00:25:50 +00:00
|
|
|
|
throw new InvalidOperationException();
|
2017-04-14 19:59:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-02-02 00:25:50 +00:00
|
|
|
|
return ret;
|
|
|
|
|
}
|
2015-02-01 22:49:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-27 22:14:15 +00:00
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
|
|
|
private class BITMAPINFOHEADER
|
2015-02-01 22:49:53 +00:00
|
|
|
|
{
|
2017-06-27 22:14:15 +00:00
|
|
|
|
public uint biSize;
|
|
|
|
|
|
2015-02-01 22:49:53 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2017-06-27 22:14:15 +00:00
|
|
|
|
public BITMAPINFOHEADER()
|
2015-02-01 22:49:53 +00:00
|
|
|
|
{
|
|
|
|
|
biSize = (uint)Marshal.SizeOf(this);
|
|
|
|
|
}
|
2015-02-02 00:25:50 +00:00
|
|
|
|
|
2019-12-31 22:24:46 +00:00
|
|
|
|
/// <exception cref="InvalidOperationException">misformatted data</exception>
|
2017-06-27 22:14:15 +00:00
|
|
|
|
public static BITMAPINFOHEADER FromStream(Stream s)
|
2015-02-02 00:25:50 +00:00
|
|
|
|
{
|
2017-06-27 22:14:15 +00:00
|
|
|
|
var ret = GetObject<BITMAPINFOHEADER>(s);
|
|
|
|
|
if (ret.biSize != Marshal.SizeOf(typeof(BITMAPINFOHEADER)))
|
2017-04-14 19:59:01 +00:00
|
|
|
|
{
|
2015-02-02 00:25:50 +00:00
|
|
|
|
throw new InvalidOperationException();
|
2017-04-14 19:59:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-02-02 00:25:50 +00:00
|
|
|
|
return ret;
|
|
|
|
|
}
|
2015-02-01 22:49:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
private enum BitmapCompressionMode : uint
|
2015-02-01 22:49:53 +00:00
|
|
|
|
{
|
|
|
|
|
BI_RGB = 0,
|
|
|
|
|
BI_RLE8 = 1,
|
|
|
|
|
BI_RLE4 = 2,
|
|
|
|
|
BI_BITFIELDS = 3,
|
|
|
|
|
BI_JPEG = 4,
|
|
|
|
|
BI_PNG = 5
|
|
|
|
|
}
|
2017-04-14 19:59:01 +00:00
|
|
|
|
|
2015-02-01 22:49:53 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
private static unsafe byte[] GetBytes(object o)
|
2015-02-01 22:49:53 +00:00
|
|
|
|
{
|
|
|
|
|
byte[] ret = new byte[Marshal.SizeOf(o)];
|
|
|
|
|
fixed (byte* p = ret)
|
|
|
|
|
{
|
|
|
|
|
Marshal.StructureToPtr(o, (IntPtr)p, false);
|
|
|
|
|
}
|
2017-05-17 18:18:26 +00:00
|
|
|
|
|
2015-02-01 22:49:53 +00:00
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
private static unsafe T GetObject<T>(Stream s)
|
2015-02-02 00:25:50 +00:00
|
|
|
|
{
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
private unsafe struct BMP
|
2015-02-17 02:31:56 +00:00
|
|
|
|
{
|
|
|
|
|
public int* Data;
|
|
|
|
|
public int Width;
|
|
|
|
|
public int Height;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
private static void Blit(BMP src, BMP dst)
|
2015-02-17 02:31:56 +00:00
|
|
|
|
{
|
|
|
|
|
if (src.Width == dst.Width && src.Height == dst.Height)
|
2017-04-14 19:59:01 +00:00
|
|
|
|
{
|
2015-02-17 02:31:56 +00:00
|
|
|
|
Blit_Same(src, dst);
|
2017-04-14 19:59:01 +00:00
|
|
|
|
}
|
2015-02-17 02:31:56 +00:00
|
|
|
|
else
|
2017-04-14 19:59:01 +00:00
|
|
|
|
{
|
2015-02-17 02:31:56 +00:00
|
|
|
|
Blit_Any(src, dst);
|
2017-04-14 19:59:01 +00:00
|
|
|
|
}
|
2015-02-17 02:31:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
private static unsafe void Blit_Same(BMP src, BMP dst)
|
2015-02-17 02:31:56 +00:00
|
|
|
|
{
|
2017-04-14 19:59:01 +00:00
|
|
|
|
int* sp = src.Data + (src.Width * (src.Height - 1));
|
2015-02-17 02:31:56 +00:00
|
|
|
|
int* dp = dst.Data;
|
|
|
|
|
for (int j = 0; j < src.Height; j++)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < src.Width; i++)
|
2017-04-14 19:59:01 +00:00
|
|
|
|
{
|
2015-02-17 02:31:56 +00:00
|
|
|
|
dp[i] = sp[i];
|
2017-04-14 19:59:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-02-17 02:31:56 +00:00
|
|
|
|
sp -= src.Width;
|
|
|
|
|
dp += src.Width;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
private static unsafe void Blit_Any(BMP src, BMP dst)
|
2015-02-17 02:31:56 +00:00
|
|
|
|
{
|
|
|
|
|
int w = dst.Width;
|
|
|
|
|
int h = dst.Height;
|
2017-05-19 18:17:07 +00:00
|
|
|
|
int inW = src.Width;
|
|
|
|
|
int inH = src.Height;
|
2015-02-17 02:31:56 +00:00
|
|
|
|
int* sp = src.Data;
|
|
|
|
|
int* dp = dst.Data;
|
|
|
|
|
|
|
|
|
|
// vflip along the way
|
|
|
|
|
for (int j = h - 1; j >= 0; j--)
|
|
|
|
|
{
|
2017-05-19 18:17:07 +00:00
|
|
|
|
sp = src.Data + (inW * (j * inH / h));
|
2015-02-17 02:31:56 +00:00
|
|
|
|
for (int i = 0; i < w; i++)
|
|
|
|
|
{
|
2017-05-19 18:17:07 +00:00
|
|
|
|
dp[i] = sp[i * inW / w];
|
2015-02-17 02:31:56 +00:00
|
|
|
|
}
|
2017-05-17 18:18:26 +00:00
|
|
|
|
|
2015-02-17 02:31:56 +00:00
|
|
|
|
dp += w;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
private static unsafe void Blit_Any_NoFlip(BMP src, BMP dst)
|
2015-07-21 23:17:29 +00:00
|
|
|
|
{
|
2015-07-22 00:42:22 +00:00
|
|
|
|
int w = dst.Width;
|
|
|
|
|
int h = dst.Height;
|
2017-05-19 18:17:07 +00:00
|
|
|
|
int inW = src.Width;
|
|
|
|
|
int inH = src.Height;
|
2015-07-22 00:42:22 +00:00
|
|
|
|
int* sp = src.Data;
|
|
|
|
|
int* dp = dst.Data;
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < h; j++)
|
2015-07-21 23:17:29 +00:00
|
|
|
|
{
|
2017-05-19 18:17:07 +00:00
|
|
|
|
sp = src.Data + (inW * (j * inH / h));
|
2015-07-22 00:42:22 +00:00
|
|
|
|
for (int i = 0; i < w; i++)
|
2015-07-21 23:17:29 +00:00
|
|
|
|
{
|
2017-05-19 18:17:07 +00:00
|
|
|
|
dp[i] = sp[i * inW / w];
|
2015-07-22 00:42:22 +00:00
|
|
|
|
}
|
2017-05-17 18:18:26 +00:00
|
|
|
|
|
2015-07-22 00:42:22 +00:00
|
|
|
|
dp += w;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
public static unsafe void Copy(IVideoProvider src, IVideoProvider dst)
|
2015-07-22 00:42:22 +00:00
|
|
|
|
{
|
|
|
|
|
if (src.BufferWidth == dst.BufferWidth && src.BufferHeight == dst.BufferHeight)
|
|
|
|
|
{
|
|
|
|
|
Array.Copy(src.GetVideoBuffer(), dst.GetVideoBuffer(), src.GetVideoBuffer().Length);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fixed (int* srcp = src.GetVideoBuffer(), dstp = dst.GetVideoBuffer())
|
2015-07-21 23:17:29 +00:00
|
|
|
|
{
|
2015-07-22 00:42:22 +00:00
|
|
|
|
Blit_Any_NoFlip(new BMP
|
|
|
|
|
{
|
|
|
|
|
Data = srcp,
|
|
|
|
|
Width = src.BufferWidth,
|
|
|
|
|
Height = src.BufferHeight
|
|
|
|
|
},
|
|
|
|
|
new BMP
|
|
|
|
|
{
|
|
|
|
|
Data = dstp,
|
|
|
|
|
Width = dst.BufferWidth,
|
|
|
|
|
Height = dst.BufferHeight
|
|
|
|
|
});
|
|
|
|
|
}
|
2015-07-21 23:17:29 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-22 22:02:38 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// if passed to QuickBMPFile.Load(), will size itself to match the incoming bmp
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class LoadedBMP : IVideoProvider
|
|
|
|
|
{
|
2017-04-15 20:37:30 +00:00
|
|
|
|
public int[] VideoBuffer { get; set; }
|
2017-04-14 19:59:01 +00:00
|
|
|
|
|
2017-04-15 20:37:30 +00:00
|
|
|
|
public int[] GetVideoBuffer()
|
|
|
|
|
{
|
|
|
|
|
return VideoBuffer;
|
|
|
|
|
}
|
2017-04-14 19:59:01 +00:00
|
|
|
|
|
2017-04-15 20:37:30 +00:00
|
|
|
|
public int VirtualWidth => BufferWidth;
|
2017-04-14 19:59:01 +00:00
|
|
|
|
|
2017-04-15 20:37:30 +00:00
|
|
|
|
public int VirtualHeight => BufferHeight;
|
2017-04-14 19:59:01 +00:00
|
|
|
|
|
2017-04-15 20:37:30 +00:00
|
|
|
|
public int BufferWidth { get; set; }
|
2015-07-22 22:02:38 +00:00
|
|
|
|
public int BufferHeight { get; set; }
|
2017-04-14 19:59:01 +00:00
|
|
|
|
public int BackgroundColor => unchecked((int)0xff000000);
|
2017-05-05 16:21:37 +00:00
|
|
|
|
|
2019-12-31 22:24:46 +00:00
|
|
|
|
/// <exception cref="InvalidOperationException">always</exception>
|
2017-05-05 16:25:38 +00:00
|
|
|
|
public int VsyncNumerator
|
2017-05-05 16:21:37 +00:00
|
|
|
|
{
|
|
|
|
|
get { throw new InvalidOperationException(); }
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-31 22:24:46 +00:00
|
|
|
|
/// <exception cref="InvalidOperationException">always</exception>
|
2017-05-05 16:25:38 +00:00
|
|
|
|
public int VsyncDenominator
|
2017-05-05 16:21:37 +00:00
|
|
|
|
{
|
|
|
|
|
get { throw new InvalidOperationException(); }
|
|
|
|
|
}
|
2015-07-22 22:02:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
public static unsafe bool Load(IVideoProvider v, Stream s)
|
2015-02-02 00:25:50 +00:00
|
|
|
|
{
|
2017-06-27 22:14:15 +00:00
|
|
|
|
var bf = BITMAPFILEHEADER.FromStream(s);
|
|
|
|
|
var bi = BITMAPINFOHEADER.FromStream(s);
|
2015-02-02 00:25:50 +00:00
|
|
|
|
if (bf.bfType != 0x4d42
|
|
|
|
|
|| bf.bfOffBits != bf.bfSize + bi.biSize
|
|
|
|
|
|| bi.biPlanes != 1
|
|
|
|
|
|| bi.biBitCount != 32
|
|
|
|
|
|| bi.biCompression != BitmapCompressionMode.BI_RGB)
|
2017-04-14 19:59:01 +00:00
|
|
|
|
{
|
2015-02-02 00:25:50 +00:00
|
|
|
|
return false;
|
2017-04-14 19:59:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-19 18:17:07 +00:00
|
|
|
|
int inW = bi.biWidth;
|
|
|
|
|
int inH = bi.biHeight;
|
2015-02-02 00:25:50 +00:00
|
|
|
|
|
2017-05-19 18:17:07 +00:00
|
|
|
|
byte[] src = new byte[inW * inH * 4];
|
2015-02-02 00:25:50 +00:00
|
|
|
|
s.Read(src, 0, src.Length);
|
2015-07-22 22:02:38 +00:00
|
|
|
|
if (v is LoadedBMP)
|
|
|
|
|
{
|
|
|
|
|
var l = v as LoadedBMP;
|
2017-05-19 18:17:07 +00:00
|
|
|
|
l.BufferWidth = inW;
|
|
|
|
|
l.BufferHeight = inH;
|
|
|
|
|
l.VideoBuffer = new int[inW * inH];
|
2015-07-22 22:02:38 +00:00
|
|
|
|
}
|
2017-04-14 19:59:01 +00:00
|
|
|
|
|
2015-02-02 00:25:50 +00:00
|
|
|
|
int[] dst = v.GetVideoBuffer();
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
fixed (byte* srcp = src)
|
2015-02-02 00:25:50 +00:00
|
|
|
|
fixed (int* dstp = dst)
|
|
|
|
|
{
|
2017-05-18 16:36:38 +00:00
|
|
|
|
using (new SimpleTime("Blit"))
|
|
|
|
|
{
|
|
|
|
|
Blit(new BMP
|
|
|
|
|
{
|
|
|
|
|
Data = (int*)srcp,
|
2017-05-19 18:17:07 +00:00
|
|
|
|
Width = inW,
|
|
|
|
|
Height = inH
|
2017-05-18 16:36:38 +00:00
|
|
|
|
},
|
|
|
|
|
new BMP
|
|
|
|
|
{
|
|
|
|
|
Data = dstp,
|
|
|
|
|
Width = v.BufferWidth,
|
|
|
|
|
Height = v.BufferHeight,
|
|
|
|
|
});
|
|
|
|
|
}
|
2015-02-02 00:25:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
public static unsafe void Save(IVideoProvider v, Stream s, int w, int h)
|
2015-02-01 22:49:53 +00:00
|
|
|
|
{
|
2017-06-27 22:14:15 +00:00
|
|
|
|
var bf = new BITMAPFILEHEADER();
|
|
|
|
|
var bi = new BITMAPINFOHEADER();
|
2015-02-01 22:49:53 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2017-05-18 16:36:38 +00:00
|
|
|
|
using (new SimpleTime("Blit"))
|
|
|
|
|
{
|
|
|
|
|
Blit(new BMP
|
|
|
|
|
{
|
|
|
|
|
Data = srcp,
|
|
|
|
|
Width = v.BufferWidth,
|
|
|
|
|
Height = v.BufferHeight
|
|
|
|
|
},
|
|
|
|
|
new BMP
|
|
|
|
|
{
|
|
|
|
|
Data = (int*)dstp,
|
|
|
|
|
Width = w,
|
|
|
|
|
Height = h,
|
|
|
|
|
});
|
|
|
|
|
}
|
2015-02-01 22:49:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.Write(dst, 0, dst.Length);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|