Multi-platform .NET bindings to the Ultralight project.
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}