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