// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 using System; using System.Collections.Generic; using System.Drawing; using ClassicalSharp.GraphicsAPI; #if ANDROID using Android.Graphics; #endif namespace ClassicalSharp { /// Class responsible for performing drawing operations on bitmaps /// and for converting bitmaps into graphics api textures. /// Uses GDI+ on Windows, uses Cairo on Mono. public abstract partial class IDrawer2D : IDisposable { protected IGraphicsApi graphics; public const float Offset = 1.3f; /// Whether chat text should be drawn and measuring using the currently bitmapped font, /// false uses the font supplied as the DrawTextArgs argument supplied to the function. public bool UseBitmappedChat = false; /// Whether the shadows behind text (that uses shadows) is fully black. public bool BlackTextShadows; /// Sets the underlying bitmap that drawing operations will be performed on. public abstract void SetBitmap(Bitmap bmp); /// Draws a 2D flat rectangle of the specified dimensions at the /// specified coordinates in the currently bound bitmap. public abstract void DrawRect(FastColour colour, int x, int y, int width, int height); /// Draws the outline of a 2D flat rectangle of the specified dimensions /// at the specified coordinates in the currently bound bitmap. public abstract void DrawRectBounds(FastColour colour, float lineWidth, int x, int y, int width, int height); /// Clears the entire bound bitmap to the specified colour. public abstract void Clear(FastColour colour); /// Clears the entire given area to the specified colour. public abstract void Clear(FastColour colour, int x, int y, int width, int height); /// Disposes of any resources used by this class that are associated with the underlying bitmap. public abstract void Dispose(); /// Returns a new bitmap that has 32-bpp pixel format. public abstract Bitmap ConvertTo32Bpp(Bitmap src); /// Returns a new bitmap that has 32-bpp pixel format. public void ConvertTo32Bpp(ref Bitmap src) { Bitmap newBmp = ConvertTo32Bpp(src); src.Dispose(); src = newBmp; } /// Draws a string using the specified arguments and font at the /// specified coordinates in the currently bound bitmap. public abstract void DrawText(ref DrawTextArgs args, int x, int y); /// Draws a string using the specified arguments and fonts at the /// specified coordinates in the currently bound bitmap, clipping if necessary. public abstract void DrawClippedText(ref DrawTextArgs args, int x, int y, float maxWidth, float maxHeight); /// Draws a string using the specified arguments and the current bitmapped font at the /// specified coordinates in the currently bound bitmap. public abstract void DrawBitmappedText(ref DrawTextArgs args, int x, int y); /// Draws a string using the specified arguments, using the specified font or /// the current bitmapped font depending on 'UseBitmappedChat', at the /// specified coordinates in the currently bound bitmap. public void DrawChatText(ref DrawTextArgs args, int windowX, int windowY) { if (!UseBitmappedChat) DrawText(ref args, windowX, windowY); else DrawBitmappedText(ref args, windowX, windowY); } /// Returns the size of a bitmap needed to contain the specified text with the given arguments. public abstract Size MeasureSize(ref DrawTextArgs args); /// Returns the size of a bitmap needed to contain the specified text with the given arguments, /// when drawn with the current bitmapped font. public abstract Size MeasureBitmappedSize(ref DrawTextArgs args); /// Returns the size of a bitmap needed to contain the specified text with the given arguments, /// when drawn with the specified font or the current bitmapped font depending on 'UseBitmappedChat'. public Size MeasureChatSize(ref DrawTextArgs args) { return !UseBitmappedChat ? MeasureSize(ref args) : MeasureBitmappedSize(ref args); } /// Draws the specified string from the arguments into a new bitmap, /// then creates a 2D texture with origin at the specified window coordinates. public Texture MakeTextTexture(ref DrawTextArgs args, int windowX, int windowY) { return MakeTextureImpl(ref args, windowX, windowY, false); } /// Draws the specified string from the arguments into a new bitmap, /// using the current bitmap font, then creates a 2D texture with origin at the /// specified window coordinates. public Texture MakeBitmappedTextTexture(ref DrawTextArgs args, int windowX, int windowY) { return MakeTextureImpl(ref args, windowX, windowY, true); } /// Draws the specified string from the arguments into a new bitmap, /// using the specified font or the current bitmapped font depending on 'UseBitmappedChat', /// then creates a 2D texture with origin at the specified window coordinates. public Texture MakeChatTextTexture(ref DrawTextArgs args, int windowX, int windowY) { return MakeTextureImpl(ref args, windowX, windowY, UseBitmappedChat); } Texture MakeTextureImpl(ref DrawTextArgs args, int windowX, int windowY, bool bitmapped) { Size size = bitmapped ? MeasureBitmappedSize(ref args) : MeasureSize(ref args); if (size == Size.Empty) return new Texture(-1, windowX, windowY, 0, 0, 1, 1); using (Bitmap bmp = CreatePow2Bitmap(size)) { SetBitmap(bmp); args.SkipPartsCheck = true; if (!bitmapped) DrawText(ref args, 0, 0); else DrawBitmappedText(ref args, 0, 0); Dispose(); return Make2DTexture(bmp, size, windowX, windowY); } } /// Disposes of all native resources used by this class. /// You will no longer be able to perform measuring or drawing calls after this. public abstract void DisposeInstance(); /// Creates a 2D texture with origin at the specified window coordinates. public Texture Make2DTexture(Bitmap bmp, Size used, int windowX, int windowY) { int texId = graphics.CreateTexture(bmp, true); return new Texture(texId, windowX, windowY, used.Width, used.Height, (float)used.Width / bmp.Width, (float)used.Height / bmp.Height); } /// Creates a power-of-2 sized bitmap larger or equal to to the given size. public static Bitmap CreatePow2Bitmap(Size size) { return Platform.CreateBmp(Utils.NextPowerOf2(size.Width), Utils.NextPowerOf2(size.Height)); } public FastColour[] Colours = new FastColour[256]; public IDrawer2D() { InitColours(); } public void InitColours() { for (int i = 0; i < Colours.Length; i++) Colours[i] = default(FastColour); for (int i = 0; i <= 9; i++) Colours['0' + i] = FastColour.GetHexEncodedCol(i, 191, 64); for (int i = 10; i <= 15; i++) { Colours['a' + i - 10] = FastColour.GetHexEncodedCol(i, 191, 64); Colours['A' + i - 10] = Colours['a' + i - 10]; } } protected List parts = new List(64); protected struct TextPart { public string Text; public FastColour Col; public TextPart(string text, FastColour col) { Text = text; Col = col; } } protected void GetTextParts(string value) { parts.Clear(); if (String.IsNullOrEmpty(value)) { } else if (value.IndexOf('&') == -1) { parts.Add(new TextPart(value, Colours['f'])); } else { SplitText(value); } } /// Splits the input string by recognised colour codes. (e.g &f) protected void SplitText(string value) { char code = 'f'; for (int i = 0; i < value.Length; i++) { int nextCol = value.IndexOf('&', i); int partLength = nextCol == -1 ? value.Length - i : nextCol - i; if (partLength > 0) { string part = value.Substring(i, partLength); parts.Add(new TextPart(part, Colours[code])); } i += partLength + 1; if (nextCol >= 0 && nextCol + 1 < value.Length) { if (!ValidColour(value[nextCol + 1])) { i--; // include character that isn't a valid colour. } else { code = value[nextCol + 1]; } } } } /// Returns whenever the given character is a valid colour code. public bool ValidColour(char c) { return (int)c < 256 && Colours[c].A > 0; } /// Returns the last valid colour code in the given input, /// or \0 if no valid colour code was found. public char LastColour(string input, int start) { if (start >= input.Length) start = input.Length - 1; for (int i = start; i >= 0; i--) { if (input[i] != '&') continue; if (i < input.Length - 1 && ValidColour(input[i + 1])) return input[i + 1]; } return '\0'; } public static bool IsWhiteColour(char c) { return c == '\0' || c == 'f' || c == 'F'; } public void ReducePadding(ref Texture tex, int point) { ReducePadding(ref tex, point, 4); } public void ReducePadding(ref int height, int point) { ReducePadding(ref height, point, 4); } public void ReducePadding(ref Texture tex, int point, int scale) { if (!UseBitmappedChat) return; point = AdjTextSize(point); int padding = (tex.Height - point) / scale; float vAdj = (float)padding / Utils.NextPowerOf2(tex.Height); tex.V1 += vAdj; tex.V2 -= vAdj; tex.Height -= (short)(padding * 2); } public void ReducePadding(ref int height, int point, int scale) { if (!UseBitmappedChat) return; point = AdjTextSize(point); int padding = (height - point) / scale; height -= padding * 2; } } }