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.Dithering;
7using SixLabors.ImageSharp.Processing.Processors.Transforms;
8
9namespace ImpromptuNinjas.UltralightSharp.Demo {
10
11 public static partial class DemoProgram {
12
13 public static void GetConsoleSize(out int width, out int height) {
14 width = 0;
15 try { width = Console.WindowWidth; }
16 catch {
17 /* ok */
18 }
19
20 if (width <= 0) width = 78;
21
22 height = 0;
23 try { height = Console.WindowHeight; }
24 catch {
25 /* ok */
26 }
27
28 if (height <= 0) height = 24;
29 }
30
31 public static unsafe int RenderAnsi<TColor>(Stream o, IntPtr pixels,
32 uint w, uint h,
33 uint reduceLineCount = 0, int maxLineCount = -1, int maxWidth = -1,
34 bool borderless = false, AnsiColors colors = AnsiColors.TrueColor,
35 IDither? customDither = null, float customDitherScale = 1f
36 ) where TColor : unmanaged, IPixel<TColor> {
37 GetConsoleSize(out var cw, out var ch);
38
39 if (maxWidth >= 0)
40 cw = maxWidth;
41
42 if (maxLineCount >= 0)
43 ch = maxLineCount;
44
45 cw -= 1;
46 ch -= (int) reduceLineCount;
47
48 if (cw == 0 || ch == 0) return 0;
49
50 var aw = cw;
51 var ah = ch * 2;
52
53 var pPixels = (byte*) pixels;
54 var span = new ReadOnlySpan<TColor>(pPixels, checked((int) (w * h)));
55 using var img = Image.LoadPixelData(span, (int) w, (int) h);
56 img.Mutate(x => x
57 .Resize(aw, ah, LanczosResampler.Lanczos3)
58 //.Crop(aw, ah)
59 );
60
61 IndexedImageFrame<TColor>? indexedImg = null;
62 var isTrueColor = colors == AnsiColors.TrueColor;
63 if (!isTrueColor) {
64 switch (colors) {
65 case AnsiColors.Palette16: {
66 var opts = AnsiPalette16.Options;
67 if (customDither != null) {
68 opts.Dither = customDither;
69 opts.DitherScale = customDitherScale;
70 }
71
72 indexedImg = AnsiPalette16
73 .CreatePixelSpecificQuantizer<TColor>(Configuration.Default, opts)
74 .QuantizeFrame(img.Frames[0], new Rectangle(0, 0, img.Width, img.Height));
75 break;
76 }
77 case AnsiColors.Palette256: {
78 var opts = AnsiPalette256.Options;
79 if (customDither != null) {
80 opts.Dither = customDither;
81 opts.DitherScale = customDitherScale;
82 }
83
84 indexedImg = AnsiPalette256
85 .CreatePixelSpecificQuantizer<TColor>(Configuration.Default, opts)
86 .QuantizeFrame(img.Frames[0], new Rectangle(0, 0, img.Width, img.Height));
87 break;
88 }
89 }
90 }
91
92 void WriteNumberTriplet(byte b) {
93 var ones = b % 10;
94 var tens = b / 10 % 10;
95 var hundreds = b / 100;
96 var anyHundreds = hundreds > 0;
97 if (anyHundreds)
98 o!.WriteByte((byte) ('0' + hundreds));
99 if (anyHundreds || tens > 0)
100 o!.WriteByte((byte) ('0' + tens));
101 o!.WriteByte((byte) ('0' + ones));
102 }
103
104 // ╭
105 void DrawTopLeftCorner() {
106 o.WriteByte(0xE2);
107 o.WriteByte(0x95);
108 o.WriteByte(0xAD);
109 }
110
111 // ╮
112 void DrawTopRightCorner() {
113 o.WriteByte(0xE2);
114 o.WriteByte(0x95);
115 o.WriteByte(0xAE);
116 }
117
118 // ╰
119 void DrawBottomLeftCorner() {
120 o.WriteByte(0xE2);
121 o.WriteByte(0x95);
122 o.WriteByte(0xB0);
123 }
124
125 // ╯
126 void DrawBottomRightCorner() {
127 o.WriteByte(0xE2);
128 o.WriteByte(0x95);
129 o.WriteByte(0xAF);
130 }
131
132 // ─ x width
133 void DrawHorizontalFrame(int width) {
134 for (var i = 0; i < width; ++i) {
135 o.WriteByte(0xE2);
136 o.WriteByte(0x94);
137 o.WriteByte(0x80);
138 }
139 }
140
141 // │
142 void DrawVerticalFrame() {
143 o.WriteByte(0xE2);
144 o.WriteByte(0x94);
145 o.WriteByte(0x82);
146 }
147
148 if (!borderless) {
149 DrawTopLeftCorner();
150 DrawHorizontalFrame(aw + 1);
151 DrawTopRightCorner();
152 o.WriteByte((byte) '\n');
153 }
154
155 var lastY = ah & ~ 1;
156 for (var y = 0; y < lastY; y += 2) {
157 if (!borderless)
158 DrawVerticalFrame();
159 // write 2 lines at a time
160 var haveL = y + 1 < ah;
161
162 var u = isTrueColor
163 ? img.GetPixelRowSpan(y)
164 : default;
165 var l = haveL && isTrueColor
166 ? img.GetPixelRowSpan(y + 1)
167 : default;
168
169 var up = !isTrueColor
170 ? indexedImg!.GetPixelRowSpan(y)
171 : default;
172 var lp = haveL && !isTrueColor
173 ? indexedImg!.GetPixelRowSpan(y + 1)
174 : default;
175
176 for (var x = 0; x < aw; ++x) {
177 // upper color
178 switch (colors) {
179 case AnsiColors.Palette16: {
180 var upx = (byte) (30 + up[x]);
181 if (upx > 37) upx += 52;
182 o.WriteByte(0x1B);
183 o.WriteByte((byte) '[');
184 WriteNumberTriplet(upx);
185 o.WriteByte((byte) 'm');
186 break;
187 }
188 case AnsiColors.Palette256: {
189 var upx = up[x];
190 o.WriteByte(0x1B);
191 o.WriteByte((byte) '[');
192 o.WriteByte((byte) '3');
193 o.WriteByte((byte) '8');
194 o.WriteByte((byte) ';');
195 o.WriteByte((byte) '5');
196 o.WriteByte((byte) ';');
197 WriteNumberTriplet(upx);
198 o.WriteByte((byte) 'm');
199 break;
200 }
201 case AnsiColors.TrueColor: {
202 Rgba32 upx = default;
203 u[x].ToRgba32(ref upx);
204 var ua = 255.0f / upx.A;
205 o.WriteByte(0x1B);
206 o.WriteByte((byte) '[');
207 o.WriteByte((byte) '3');
208 o.WriteByte((byte) '8');
209 o.WriteByte((byte) ';');
210 o.WriteByte((byte) '2');
211 o.WriteByte((byte) ';');
212 WriteNumberTriplet((byte) MathF.Round(upx.R * ua, MidpointRounding.AwayFromZero));
213 o.WriteByte((byte) ';');
214 WriteNumberTriplet((byte) MathF.Round(upx.G * ua, MidpointRounding.AwayFromZero));
215 o.WriteByte((byte) ';');
216 WriteNumberTriplet((byte) MathF.Round(upx.B * ua, MidpointRounding.AwayFromZero));
217 o.WriteByte((byte) 'm');
218 break;
219 }
220 }
221
222 if (!haveL) // full block
223 {
224 o.WriteByte(0xE2);
225 o.WriteByte(0x96);
226 o.WriteByte(0x88);
227 }
228
229 else {
230 // lower color
231 switch (colors) {
232 case AnsiColors.Palette16: {
233 var lpx = (byte) (40 + lp[x]);
234 if (lpx > 47) lpx += 52;
235 o.WriteByte(0x1B);
236 o.WriteByte((byte) '[');
237 WriteNumberTriplet(lpx);
238 o.WriteByte((byte) 'm');
239 break;
240 }
241 case AnsiColors.Palette256: {
242 var lpx = lp[x];
243 o.WriteByte(0x1B);
244 o.WriteByte((byte) '[');
245 o.WriteByte((byte) '4');
246 o.WriteByte((byte) '8');
247 o.WriteByte((byte) ';');
248 o.WriteByte((byte) '5');
249 o.WriteByte((byte) ';');
250 WriteNumberTriplet(lpx);
251 o.WriteByte((byte) 'm');
252 break;
253 }
254 case AnsiColors.TrueColor: {
255 Rgba32 lpx = default;
256 l[x].ToRgba32(ref lpx);
257 var la = 255.0f / lpx.A;
258 o.WriteByte(0x1B);
259 o.WriteByte((byte) '[');
260 o.WriteByte((byte) '4');
261 o.WriteByte((byte) '8');
262 o.WriteByte((byte) ';');
263 o.WriteByte((byte) '2');
264 o.WriteByte((byte) ';');
265 WriteNumberTriplet((byte) Math.Round(lpx.R * la, MidpointRounding.AwayFromZero));
266 o.WriteByte((byte) ';');
267 WriteNumberTriplet((byte) Math.Round(lpx.G * la, MidpointRounding.AwayFromZero));
268 o.WriteByte((byte) ';');
269 WriteNumberTriplet((byte) Math.Round(lpx.B * la, MidpointRounding.AwayFromZero));
270 o.WriteByte((byte) 'm');
271 break;
272 }
273 }
274
275 // half block
276 o.WriteByte(0xE2);
277 o.WriteByte(0x96);
278 o.WriteByte(0x80);
279 }
280 }
281
282 o.WriteByte(0x1B);
283 o.WriteByte((byte) '[');
284 o.WriteByte((byte) '0');
285 o.WriteByte((byte) 'm');
286 o.WriteByte((byte) ' ');
287 if (!borderless)
288 DrawVerticalFrame();
289 o.WriteByte((byte) '\n');
290 }
291
292 if (!borderless) {
293 DrawBottomLeftCorner();
294 DrawHorizontalFrame(aw + 1);
295 DrawBottomRightCorner();
296 o.WriteByte((byte) '\n');
297 }
298
299 //o.Flush();
300
301 return lastY;
302 }
303
304 }
305
306}