Skip to content

Instantly share code, notes, and snippets.

@usagirei
Created May 7, 2016 02:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save usagirei/b9e97afbe8f042ec73f2ce73ea4d110c to your computer and use it in GitHub Desktop.
Save usagirei/b9e97afbe8f042ec73f2ce73ea4d110c to your computer and use it in GitHub Desktop.
BIOS Style Bitmap Blitting
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