Created
May 7, 2016 02:06
-
-
Save usagirei/b9e97afbe8f042ec73f2ce73ea4d110c to your computer and use it in GitHub Desktop.
BIOS Style Bitmap Blitting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Drawing; | |
using System.Drawing.Drawing2D; | |
using System.Drawing.Imaging; | |
using System.IO; | |
using System.Runtime.InteropServices; | |
using System.Text; | |
namespace ConsoleApplication2 | |
{ | |
internal class VgaBuffer : IDisposable | |
{ | |
public enum FontPages | |
{ | |
Red, | |
Green, | |
Blue | |
} | |
public enum VgaColors : byte | |
{ | |
Black = 0, | |
DarkRed = 1, | |
DarkGreen = 2, | |
DarkYellow = 3, | |
DarkBlue = 4, | |
DarkMagenta = 5, | |
DarkCyan = 6, | |
DarkGray = 7, | |
// | |
Gray = 8, | |
Red = 9, | |
Green = 10, | |
Yellow = 11, | |
Blue = 12, | |
Magenta = 13, | |
Cyan = 14, | |
White = 15, | |
} | |
private readonly Color[] _bitmapData; | |
private readonly Bitmap _buffer; | |
private readonly int _buffH; | |
private readonly int _buffW; | |
private readonly int _charH; | |
private readonly int _charW; | |
private readonly Encoding _cp437 = Encoding.GetEncoding("CP437"); | |
private readonly Color[] _fontData; | |
private readonly Graphics _graphics; | |
private readonly Color[] _lineData; | |
private readonly Stack<Coord> _savedCursorPositions = new Stack<Coord>(); | |
private GCHandle _bitmapHandle; | |
private int _cursorX; | |
private int _cursorY; | |
private GCHandle _fontHandle; | |
private int _frameCount; | |
private GCHandle _lineHandle; | |
public Color[] Colors = new Color[16]; | |
public string OutputPath { get; set; } | |
public VgaColors Foreground { get; set; } = VgaColors.DarkGray; | |
public VgaColors Background { get; set; } = VgaColors.Black; | |
public FontPages FontPage { get; set; } = FontPages.Red; | |
public VgaBuffer(int sx, int sy, int dx = 8, int dy = 16) | |
{ | |
GenColors(0, 187, 85, 255); | |
int dataW = sx * dx; | |
int dataH = sy * dy; | |
int dataS = dataW * 3; | |
_lineData = new Color[dy * dataW]; | |
_bitmapData = new Color[dataH * dataW]; | |
_fontData = new Color[(dx * 32) * (dy * 8)]; | |
_lineHandle = GCHandle.Alloc(_lineData, GCHandleType.Pinned); | |
_bitmapHandle = GCHandle.Alloc(_bitmapData, GCHandleType.Pinned); | |
_fontHandle = GCHandle.Alloc(_fontData, GCHandleType.Pinned); | |
_buffer = new Bitmap(dataW, dataH, dataS, PixelFormat.Format24bppRgb, _bitmapHandle.AddrOfPinnedObject()); | |
_graphics = Graphics.FromImage(_buffer); | |
_buffH = sy; | |
_buffW = sx; | |
_charW = dx; | |
_charH = dy; | |
OutputPath = "Output"; | |
} | |
public void Dispose() | |
{ | |
_bitmapHandle.Free(); | |
_fontHandle.Free(); | |
_lineHandle.Free(); | |
} | |
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] | |
private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); | |
public bool LoadFont(string file) | |
{ | |
using (var fs = File.OpenRead(file)) | |
{ | |
var fontBmp = (Bitmap) Image.FromStream(fs); | |
var expectedW = _charW * 32; | |
var expectedH = _charH * 8; | |
if (fontBmp.Width != expectedW || fontBmp.Height != expectedH) | |
return false; | |
var bmpd = fontBmp.LockBits(new Rectangle(0, 0, expectedW, expectedH), | |
ImageLockMode.ReadOnly, | |
PixelFormat.Format24bppRgb); | |
CopyMemory(_fontHandle.AddrOfPinnedObject(), bmpd.Scan0, (uint) (expectedW * expectedH * 3)); | |
fontBmp.UnlockBits(bmpd); | |
fontBmp.Dispose(); | |
} | |
return true; | |
} | |
private byte[] GetBytes(string s) | |
{ | |
return _cp437.GetBytes(s); | |
} | |
public void WriteLine(string s) | |
{ | |
Write(s); | |
OffsetCursorPos(-_cursorX, 1); | |
} | |
public void Write(string s) | |
{ | |
var bytes = GetBytes(s); | |
foreach (var b in bytes) | |
{ | |
if (b == 10 || b == 13) | |
{ | |
SetCursorPos(0, _cursorY + 1); | |
continue; | |
} | |
int fontX, fontY; | |
GetFontIndexes(b, out fontX, out fontY); | |
DrawGlyph(fontX, fontY, _cursorX, _cursorY); | |
OffsetCursorPos(1, 0); | |
} | |
} | |
public void DrawGlyph(int fontX, int fontY, int screenX, int screenY) | |
{ | |
var fore = Colors[(int) Foreground]; | |
var back = Colors[(int) Background]; | |
DrawGlyph(fontX, fontY, screenX, screenY, fore, back); | |
} | |
public void DrawSprite(int fontX, int fontY, int spriteW, int spriteH, int screenX, int screenY) | |
{ | |
var fore = Colors[(int) Foreground]; | |
var back = Colors[(int) Background]; | |
DrawSprite(fontX, fontY, screenX, screenY, spriteW, spriteH, fore, back); | |
} | |
private void DrawSprite(int fontX, | |
int fontY, | |
int screenX, | |
int screenY, | |
int spriteW, | |
int spriteH, | |
Color fore, | |
Color back) | |
{ | |
for (int y = 0; y < spriteH; y++) | |
{ | |
for (int x = 0; x < spriteW; x++) | |
{ | |
DrawGlyph(fontX + x, fontY + y, screenX + x, screenY + y, fore, back); | |
} | |
} | |
} | |
public void OffsetCursorPos(int dx, int dy) | |
{ | |
SetCursorPos(_cursorX + dx, _cursorY + dy); | |
} | |
public void SetCursorPos(int x, int y) | |
{ | |
_cursorX = x; | |
_cursorY = y; | |
if (_cursorX < 0) | |
{ | |
_cursorX = _buffW - 1; | |
_cursorY--; | |
} | |
else if (_cursorX >= _buffW) | |
{ | |
var dx = _cursorX % _buffW; | |
var dy = _cursorX / _buffW; | |
_cursorX = dx % _buffW; | |
_cursorY += dy; | |
} | |
if (_cursorY < 0) | |
{ | |
_cursorY = 0; | |
} | |
else if (_cursorY >= _buffH) | |
{ | |
var dy = _cursorY - (_buffH - 1); | |
Scroll(dy); | |
_cursorY = (_buffH - 1); | |
} | |
} | |
public void Scroll(int n = 1) | |
{ | |
if (!_lineData[0].Equals(Colors[(int) Background])) | |
{ | |
for (int i = 0; i < _lineData.Length; i++) | |
_lineData[i] = Colors[(int) Background]; | |
} | |
uint lineSz = (uint) (_buffW * _charW * _charH * 3); | |
for (int i = 0; i < _buffH; i++) | |
{ | |
int dst = (int) (lineSz * i); | |
int src = (int) (lineSz * (i + n)); | |
var dstPtr = _bitmapHandle.AddrOfPinnedObject() + dst; | |
var srcPtr = _bitmapHandle.AddrOfPinnedObject() + src; | |
if (_buffH - i > n) | |
CopyMemory(dstPtr, srcPtr, lineSz); | |
else | |
CopyMemory(dstPtr, _lineHandle.AddrOfPinnedObject(), lineSz); | |
} | |
} | |
private static void GetFontIndexes(byte c, out int fontX, out int fontY) | |
{ | |
fontX = c % 32; | |
fontY = c / 32; | |
} | |
private void DrawGlyph(int fontX, int fontY, int screenX, int screenY, Color foreColor, Color backColor) | |
{ | |
if (fontX >= 32 || fontY >= 8 || screenX >= _buffW || screenY >= _buffH) | |
return; | |
int trueSrcY = fontY * _charH; | |
int trueSrcX = fontX * _charW; | |
int trueDstY = screenY * _charH; | |
int trueDstX = screenX * _charW; | |
int srcW = _charW * 32; | |
int dstW = _charW * _buffW; | |
for (int y = 0; y < _charH; y++) | |
{ | |
int srcOff = srcW * (trueSrcY + y) + trueSrcX; | |
int dstOff = dstW * (trueDstY + y) + trueDstX; | |
for (int x = 0; x < _charW; x++) | |
{ | |
int trueSrcOff = srcOff + x; | |
int trueDstOff = dstOff + x; | |
var srcCol = _fontData[trueSrcOff]; | |
int channel; | |
switch (FontPage) | |
{ | |
default: | |
case FontPages.Red: | |
channel = srcCol.R; | |
break; | |
case FontPages.Green: | |
channel = srcCol.G; | |
break; | |
case FontPages.Blue: | |
channel = srcCol.B; | |
break; | |
} | |
_bitmapData[trueDstOff] = channel < 127 | |
? backColor | |
: foreColor; | |
} | |
} | |
} | |
public void GenColors(byte dLo, byte dHi, byte bLo, byte bHi) | |
{ | |
for (var j = 0; j <= 1; j++) | |
{ | |
byte o = j == 0 | |
? dLo | |
: bLo; | |
byte l = j == 0 | |
? dHi | |
: bHi; | |
for (var i = 0; i <= 7; i++) | |
{ | |
var b = ((i >> 0x02) & 0x01) == 1 | |
? l | |
: o; | |
var g = ((i >> 0x01) & 0x01) == 1 | |
? l | |
: o; | |
var r = ((i >> 0x00) & 0x01) == 1 | |
? l | |
: o; | |
Colors[8 * j + i] = new Color(r, g, b); | |
} | |
} | |
} | |
public void ExportFrame(int times = 1) | |
{ | |
for (int i = 0; i < times; i++) | |
{ | |
Directory.CreateDirectory(OutputPath); | |
_buffer.Save(Path.Combine(OutputPath, $"Frame_{_frameCount:D3}.png")); | |
_frameCount++; | |
} | |
} | |
public void Clear() | |
{ | |
throw new NotImplementedException(); | |
} | |
public void GetCursorPos(out int x, out int y) | |
{ | |
x = _cursorX; | |
y = _cursorY; | |
} | |
public void SaveCursorPos() | |
{ | |
_savedCursorPositions.Push(new Coord(_cursorX, _cursorY)); | |
} | |
public void RestoreCursorPos() | |
{ | |
var c = _savedCursorPositions.Peek(); | |
_cursorX = c.X; | |
_cursorY = c.Y; | |
} | |
public void PopCursorPosition() | |
{ | |
_savedCursorPositions.Pop(); | |
} | |
public void WriteLine() | |
{ | |
SetCursorPos(0, _cursorY + 1); | |
} | |
} | |
internal struct Coord | |
{ | |
public int X; | |
public int Y; | |
public Coord(int x, int y) : this() | |
{ | |
X = x; | |
Y = y; | |
} | |
} | |
[StructLayout(LayoutKind.Explicit)] | |
internal struct Color | |
{ | |
[FieldOffset(0)] public readonly byte B; | |
[FieldOffset(1)] public readonly byte G; | |
[FieldOffset(2)] public readonly byte R; | |
public Color(byte r, byte g, byte b) : this() | |
{ | |
B = b; | |
R = r; | |
G = g; | |
} | |
public override bool Equals(object obj) | |
{ | |
return base.Equals(obj); | |
} | |
public bool Equals(Color other) | |
{ | |
return B == other.B && G == other.G && R == other.R; | |
} | |
public override int GetHashCode() | |
{ | |
unchecked | |
{ | |
var hashCode = B.GetHashCode(); | |
hashCode = (hashCode * 397) ^ G.GetHashCode(); | |
hashCode = (hashCode * 397) ^ R.GetHashCode(); | |
return hashCode; | |
} | |
} | |
public override string ToString() | |
{ | |
return $"#{R:X2}{G:X2}{B:X2}"; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment