Multi-platform .NET bindings to the Ultralight project.
at master 9.1 kB view raw
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}