using System; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms; using BizHawk.Client.Common; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy; namespace BizHawk.Client.EmuHawk { public partial class GBPrinterView : Form, IToolFormAutoConfig { private const int PaperWidth = 160; // the bg color private static readonly uint PaperColor = (uint)Color.AntiqueWhite.ToArgb(); private readonly ColorMatrix _paperAdjustment; [RequiredService] public IGameboyCommon Gb { get; private set; } // If we've connected the printer yet private bool _connected; // the entire bitmap private Bitmap _printerHistory; public GBPrinterView() { InitializeComponent(); // adjust the color of the printed output to be more papery _paperAdjustment = new ColorMatrix { Matrix00 = (0xFA - 0x10) / 255F, Matrix40 = 0x10 / 255F, Matrix11 = (0xEB - 0x10) / 255F, Matrix41 = 0x10 / 255F, Matrix22 = (0xD7 - 0x18) / 255F, Matrix42 = 0x18 / 255F }; paperView.ChangeBitmapSize(PaperWidth, PaperWidth); ClearPaper(); } private void GBPrinterView_FormClosed(object sender, FormClosedEventArgs e) { Gb?.SetPrinterCallback(null); } public bool UpdateBefore => false; public bool AskSaveChanges() => true; public void FastUpdate() { } public void NewUpdate(ToolFormUpdateType type) { } public void Restart() { // Really, there's not necessarily a reason to clear it at all, // since the paper would still be there, // but it just seems right to get a blank slate on reset. ClearPaper(); _connected = false; } public void UpdateValues() { // Automatically connect once the game is running if (!_connected) { Gb.SetPrinterCallback(OnPrint); _connected = true; } } // The printer callback that . See PrinterCallback for details. private void OnPrint(IntPtr image, byte height, byte topMargin, byte bottomMargin, byte exposure) { // In this implementation: // the bottom margin and top margin are just white lines at the top and bottom // exposure is ignored // The page received image var page = new Bitmap(PaperWidth, height); var bmp = page.LockBits(new Rectangle(0, 0, PaperWidth, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); for (int y = 0; y < height; y++) { for (int x = 0; x < PaperWidth; x++) { uint pixel; unsafe { // Pixel of the image; it's just sent from the core as a big bitmap that's 160 x height pixel = *(uint*)(image + (x + y * PaperWidth) * sizeof(uint)); } SetPixel(bmp, x, y, pixel); } } page.UnlockBits(bmp); // add it to the bottom of the history int oldHeight = _printerHistory.Height; ResizeHistory(_printerHistory.Height + page.Height + topMargin + bottomMargin); using (var g = Graphics.FromImage(_printerHistory)) { // Make it brown var a = new ImageAttributes(); a.SetColorMatrix(_paperAdjustment); g.DrawImage(page, new Rectangle(0, oldHeight + topMargin, page.Width, page.Height), 0F, 0F, page.Width, page.Height, GraphicsUnit.Pixel, a); g.Flush(); } RefreshView(); } /// /// Set a 2x pixel /// /// The bitmap data to draw to /// X position /// Y position /// The ARGB color to set that pixel to private unsafe void SetPixel(BitmapData bmp, int x, int y, uint c) { uint* pixel = (uint*)(bmp.Scan0 + x * 4 + y * bmp.Stride); *pixel = c; } private void ClearPaper() { ResizeHistory(8); RefreshView(); } private void ResizeHistory(int height) { // copy to a new image of height var newHistory = new Bitmap(PaperWidth, height); using (var g = Graphics.FromImage(newHistory)) { g.Clear(Color.FromArgb((int)PaperColor)); if (_printerHistory != null) { g.DrawImage(_printerHistory, Point.Empty); } g.Flush(); } _printerHistory?.Dispose(); _printerHistory = newHistory; // Update scrollbar, viewport is a square paperScroll.Maximum = Math.Max(0, height); } private void RefreshView() { using (var g = Graphics.FromImage(paperView.BMP)) { g.Clear(Color.FromArgb((int)PaperColor)); g.DrawImage(_printerHistory, new Point(0, -paperScroll.Value)); g.Flush(); } paperView.Refresh(); } private void saveImageToolStripMenuItem_Click(object sender, EventArgs e) { // slight hack to use the nice SaveFile() feature of a BmpView BmpView toSave = new BmpView(); toSave.ChangeBitmapSize(_printerHistory.Size); using (var g = Graphics.FromImage(toSave.BMP)) { g.DrawImage(_printerHistory, Point.Empty); g.Flush(); } toSave.SaveFile(); } private void copyToolStripMenuItem_Click(object sender, EventArgs e) { Clipboard.SetImage(_printerHistory); } private void PaperScroll_ValueChanged(object sender, EventArgs e) { RefreshView(); } } }