Multi-platform .NET bindings to the Ultralight project.
at master 212 lines 5.9 kB view raw
1using System; 2using System.IO; 3using System.Text; 4using SixLabors.ImageSharp; 5using SixLabors.ImageSharp.PixelFormats; 6using SixLabors.ImageSharp.Processing; 7using SixLabors.ImageSharp.Processing.Processors.Transforms; 8 9public static class Utilities { 10 11 public static string LoadEmbeddedUtf8String(string resName) { 12 using var stream = typeof(Utilities).Assembly.GetManifestResourceStream(resName); 13 if (stream == null) throw new FileNotFoundException($"Missing embedded resource: {resName}"); 14 15 return LoadString(stream); 16 } 17 18 public static string LoadString(Stream stream) { 19 using var sr = new StreamReader(stream, Encoding.UTF8, false, 16, true); 20 return sr.ReadToEnd(); 21 } 22 23 public static unsafe void RenderAnsi<TColor>(void* pixels, 24 uint w, uint h, 25 uint reduceLineCount = 0, int maxLineCount = -1, int maxWidth = -1, 26 bool borderless = false 27 ) where TColor : unmanaged, IPixel<TColor> { 28 var aspect = w / (double) h; 29 30 // get the console size 31 // @formatter:off 32 var cw = 0; 33 if (maxWidth < 0) 34 try { cw = Console.WindowWidth; } catch { cw = 72; } 35 else 36 cw = maxWidth; 37 38 var ch = 0; 39 if (maxLineCount < 0) 40 try { ch = Console.WindowHeight; } catch { ch = 25; } 41 else 42 ch = maxLineCount; 43 // @formatter:on 44 45 cw -= 1; 46 ch -= (int) reduceLineCount; 47 48 if (cw <= 0 || ch <= 0) 49 return; 50 51 // come up with an aperture that fits the console window (minus drawn borders, accounting for 2v/c) 52 var borderCost = borderless ? 0 : -2; 53 var wsq = borderCost + cw / aspect; 54 var hsq = borderCost + (ch * 2) * aspect; 55 var sq = Math.Min(wsq, hsq); 56 57 var aw = (int) Math.Floor(sq * aspect); 58 var ah = (int) Math.Floor(sq / aspect); 59 60 var pPixels = (byte*) pixels; 61 var span = new ReadOnlySpan<TColor>(pPixels, checked((int) (w * h))); 62 using var img = Image.LoadPixelData(span, (int) w, (int) h); 63 img.Mutate(x => x 64 .Resize(aw, ah, LanczosResampler.Lanczos3) 65 .Crop(aw, ah)); 66 using var stdOut = Console.OpenStandardOutput(256); 67 using var o = new BufferedStream(stdOut, (42 * aw * (ah / 2)) + 1); 68 69 void WriteTriplet(byte b) { 70 var ones = b % 10; 71 var tens = b / 10 % 10; 72 var hundreds = b / 100; 73 var anyHundreds = hundreds > 0; 74 if (anyHundreds) 75 o!.WriteByte((byte) ('0' + hundreds)); 76 if (anyHundreds || tens > 0) 77 o!.WriteByte((byte) ('0' + tens)); 78 o!.WriteByte((byte) ('0' + ones)); 79 } 80 81 // ╭ 82 void DrawTopLeftCorner() { 83 o.WriteByte(0xE2); 84 o.WriteByte(0x95); 85 o.WriteByte(0xAD); 86 } 87 88 // ╮ 89 void DrawTopRightCorner() { 90 o.WriteByte(0xE2); 91 o.WriteByte(0x95); 92 o.WriteByte(0xAE); 93 } 94 95 // ╰ 96 void DrawBottomLeftCorner() { 97 o.WriteByte(0xE2); 98 o.WriteByte(0x95); 99 o.WriteByte(0xB0); 100 } 101 102 // ╯ 103 void DrawBottomRightCorner() { 104 o.WriteByte(0xE2); 105 o.WriteByte(0x95); 106 o.WriteByte(0xAF); 107 } 108 109 // ─ x width 110 void DrawHorizontalFrame(int width) { 111 for (var i = 0; i < width; ++i) { 112 o.WriteByte(0xE2); 113 o.WriteByte(0x94); 114 o.WriteByte(0x80); 115 } 116 } 117 118 // │ 119 void DrawVerticalFrame() { 120 o.WriteByte(0xE2); 121 o.WriteByte(0x94); 122 o.WriteByte(0x82); 123 } 124 125 if (!borderless) { 126 DrawTopLeftCorner(); 127 DrawHorizontalFrame(aw + 1); 128 DrawTopRightCorner(); 129 o.WriteByte((byte) '\n'); 130 } 131 132 var lastY = ah & ~ 1; 133 for (var y = 0; y < lastY; y += 2) { 134 if (!borderless) 135 DrawVerticalFrame(); 136 // write 2 lines at a time 137 var u = img.GetPixelRowSpan(y); 138 var haveL = y + 1 < ah; 139 var l = haveL 140 ? img.GetPixelRowSpan(y + 1) 141 : new Span<TColor>(default, 0); 142 143 for (var x = 0; x < aw; ++x) { 144 // upper color 145 Rgba32 upx = default; 146 u[x].ToRgba32(ref upx); 147 var ua = 255.0f / upx.A; 148 o.WriteByte(0x1B); 149 o.WriteByte((byte) '['); 150 o.WriteByte((byte) '3'); 151 o.WriteByte((byte) '8'); 152 o.WriteByte((byte) ';'); 153 o.WriteByte((byte) '2'); 154 o.WriteByte((byte) ';'); 155 WriteTriplet((byte) MathF.Round(upx.R * ua, MidpointRounding.AwayFromZero)); 156 o.WriteByte((byte) ';'); 157 WriteTriplet((byte) MathF.Round(upx.G * ua, MidpointRounding.AwayFromZero)); 158 o.WriteByte((byte) ';'); 159 WriteTriplet((byte) MathF.Round(upx.B * ua, MidpointRounding.AwayFromZero)); 160 o.WriteByte((byte) 'm'); 161 162 if (!haveL) // full block 163 o.WriteByte(0xDB); 164 165 else { 166 // lower color 167 Rgba32 lpx = default; 168 l[x].ToRgba32(ref lpx); 169 var la = 255.0f / lpx.A; 170 o.WriteByte(0x1B); 171 o.WriteByte((byte) '['); 172 o.WriteByte((byte) '4'); 173 o.WriteByte((byte) '8'); 174 o.WriteByte((byte) ';'); 175 o.WriteByte((byte) '2'); 176 o.WriteByte((byte) ';'); 177 WriteTriplet((byte) Math.Round(lpx.R * la, MidpointRounding.AwayFromZero)); 178 o.WriteByte((byte) ';'); 179 WriteTriplet((byte) Math.Round(lpx.G * la, MidpointRounding.AwayFromZero)); 180 o.WriteByte((byte) ';'); 181 WriteTriplet((byte) Math.Round(lpx.B * la, MidpointRounding.AwayFromZero)); 182 o.WriteByte((byte) 'm'); 183 184 // half block 185 o.WriteByte(0xE2); 186 o.WriteByte(0x96); 187 o.WriteByte(0x80); 188 } 189 } 190 191 o.WriteByte(0x1B); 192 o.WriteByte((byte) '['); 193 o.WriteByte((byte) '0'); 194 o.WriteByte((byte) 'm'); 195 o.WriteByte((byte) ' '); 196 if (!borderless) 197 DrawVerticalFrame(); 198 o.WriteByte((byte) '\n'); 199 } 200 201 if (!borderless) { 202 DrawBottomLeftCorner(); 203 DrawHorizontalFrame(aw + 1); 204 DrawBottomRightCorner(); 205 o.WriteByte((byte) '\n'); 206 } 207 208 o.Flush(); 209 stdOut.Flush(); 210 } 211 212}