Mirror of
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

colors nice, varibale fonts

+329 -115
+9 -115
ReleaseImageGenerator.API/Program.cs
··· 1 1 using SkiaSharp; 2 + using System; 3 + using Microsoft.AspNetCore.Mvc; 4 + using ReleaseImageGenerator.Domain.Implementations; 2 5 3 6 var builder = WebApplication.CreateBuilder(args); 4 7 5 8 // Add services to the container. 6 - // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 7 9 builder.Services.AddEndpointsApiExplorer(); 8 10 builder.Services.AddSwaggerGen(); 9 11 ··· 18 20 19 21 app.UseHttpsRedirection(); 20 22 21 - app.MapGet("/image-generator", (string text) => 23 + app.MapGet("/image-generator", (string? text, int? width, int? height, SupportedFonts? font) => 22 24 { 23 - text = string.IsNullOrEmpty(text) ? "Release 1.0" : text; 24 - 25 - int width = 1200; 26 - int height = 600; 27 - 28 - using var surface = SKSurface.Create(new SKImageInfo(width, height)); 29 - var canvas = surface.Canvas; 30 - 31 - // Fill with a random gradient background 32 - var random = new Random(); 33 - var paint = new SKPaint 34 - { 35 - Shader = SKShader.CreateLinearGradient( 36 - new SKPoint(0, 0), 37 - new SKPoint(width, height), 38 - new SKColor[] { 39 - RandomColor(random), 40 - RandomColor(random), 41 - RandomColor(random) 42 - }, 43 - SKShaderTileMode.Clamp 44 - ) 45 - }; 46 - canvas.DrawRect(0, 0, width, height, paint); 47 - 48 - // Add some noise 49 - AddNoise(canvas, width, height); 50 - 51 - // Draw a simple pattern 52 - // DrawPattern(canvas, width, height); 53 - 54 - // Load JetBrains Mono font 55 - var typeface = SKTypeface.FromFile("./fonts/JetBrainsMono-Bold.ttf"); 56 - 57 - // Calculate text size and position 58 - paint = new SKPaint 59 - { 60 - Color = SKColors.White, 61 - TextSize = 100, 62 - IsAntialias = true, 63 - Typeface = typeface ?? SKTypeface.Default 64 - }; 65 - 66 - var textWidth = paint.MeasureText(text); 67 - var textBounds = new SKRect(); 68 - paint.MeasureText(text, ref textBounds); 69 - 70 - float textX = (width - textWidth) / 2; 71 - float textY = height / 2 + textBounds.Height / 2; 72 - 73 - // Draw glassmorphism background 74 - var bgPaint = new SKPaint 75 - { 76 - Color = SKColors.White.WithAlpha(40), 77 - IsAntialias = true 78 - }; 79 - var borderPaint = new SKPaint 80 - { 81 - Color = SKColors.White.WithAlpha(100), 82 - IsStroke = true, 83 - StrokeWidth = 4, 84 - IsAntialias = true 85 - }; 86 - 87 - var padding = 40; 88 - var rect = new SKRect(textX - padding, textY + textBounds.Top - padding, textX + textWidth + padding, textY + padding); 89 - canvas.DrawRoundRect(rect, 20, 20, bgPaint); 90 - canvas.DrawRoundRect(rect, 20, 20, borderPaint); 91 - 92 - // Draw the text 93 - canvas.DrawText(text, textX, textY, paint); 94 - 95 - // Return as JPEG 96 - var stream = new MemoryStream(); 97 - using var image = surface.Snapshot(); 98 - using var data = image.Encode(SKEncodedImageFormat.Jpeg, 90); 99 - data.SaveTo(stream); 100 - return Results.File(stream.ToArray(), "image/jpeg"); 25 + var options = new ImageGeneratorOptions(text ?? "1.0", width ?? 1920, height ?? 1080, font ?? SupportedFonts.JETBRAINS_BOLD); 26 + var imageGenerator = new ImageGenerator(options); 27 + return Results.File(imageGenerator.GenerateImage().ToArray(), "image/jpeg"); 101 28 }) 102 - .WithName("ImageGenerator") 103 - .WithOpenApi(); 29 + .WithName("ImageGenerator") 30 + .WithOpenApi(); 104 31 105 32 app.Run(); 106 - 107 - static SKColor RandomColor(Random random) 108 - { 109 - return new SKColor( 110 - (byte)random.Next(256), 111 - (byte)random.Next(256), 112 - (byte)random.Next(256) 113 - ); 114 - } 115 - 116 - static void AddNoise(SKCanvas canvas, int width, int height) 117 - { 118 - var random = new Random(); 119 - var noisePaint = new SKPaint { Color = SKColors.White.WithAlpha(10) }; 120 - for (int i = 0; i < 1000; i++) 121 - { 122 - canvas.DrawPoint(random.Next(width), random.Next(height), noisePaint); 123 - } 124 - } 125 - 126 - static void DrawPattern(SKCanvas canvas, int width, int height) 127 - { 128 - var paint = new SKPaint 129 - { 130 - Color = SKColors.White.WithAlpha(20), 131 - StrokeWidth = 2 132 - }; 133 - 134 - for (int i = 0; i < width; i += 40) 135 - { 136 - canvas.DrawLine(i, 0, i, height, paint); 137 - } 138 - }
+4
ReleaseImageGenerator.API/ReleaseImageGenerator.API.csproj
··· 20 20 </Content> 21 21 </ItemGroup> 22 22 23 + <ItemGroup> 24 + <ProjectReference Include="..\ReleaseImageGenerator.Domain\ReleaseImageGenerator.Domain.csproj" /> 25 + </ItemGroup> 26 + 23 27 </Project>
ReleaseImageGenerator.API/fonts/ReadexPro-Bold.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/ReadexPro-ExtraLight.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/ReadexPro-Light.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/ReadexPro-Medium.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/ReadexPro-Regular.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/ReadexPro-SemiBold.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-Black.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-BlackItalic.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-Bold.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-BoldItalic.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-ExtraBold.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-ExtraBoldItalic.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-ExtraLight.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-ExtraLightItalic.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-Italic.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-Light.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-LightItalic.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-Medium.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-MediumItalic.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-Regular.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-SemiBold.ttf

This is a binary file and will not be displayed.

ReleaseImageGenerator.API/fonts/SourceCodePro-SemiBoldItalic.ttf

This is a binary file and will not be displayed.

+67
ReleaseImageGenerator.Domain/ColorGenerator.cs
··· 1 + using Wacton.Unicolour; 2 + 3 + namespace ReleaseImageGenerator.Domain; 4 + 5 + public static class ColorGenerator 6 + { 7 + public enum ColorLimitation 8 + { 9 + ALL, 10 + LIGHTER, 11 + NEUTRAL_LIGHTNESS, 12 + DARKER, 13 + SATURATED, 14 + NEUTRAL_SATURATION, 15 + DESATURATED, 16 + } 17 + 18 + public static Unicolour GetRandomColor(params ColorLimitation[] limitations) 19 + { 20 + var random = new Random(); 21 + var saturationRange = limitations?.Aggregate((0D, 1D), (current, limitation) => limitation switch 22 + { 23 + ColorLimitation.NEUTRAL_SATURATION => (0.25, 0.75), 24 + ColorLimitation.SATURATED => (0.5, 1D), 25 + ColorLimitation.DESATURATED => (0D, 0.5), 26 + _ => current 27 + }) ?? (0D, 1D); 28 + var lightnessRange = limitations?.Aggregate((0D, 1D), (current, limitation) => limitation switch 29 + { 30 + ColorLimitation.NEUTRAL_LIGHTNESS => (0.25, 0.75), 31 + ColorLimitation.LIGHTER => (0.5, 1D), 32 + ColorLimitation.DARKER => (0D, 0.5), 33 + _ => current 34 + }) ?? (0D, 1D); 35 + var color = new Unicolour(ColourSpace.Oklch, 36 + (lightnessRange.Item1 + random.NextDouble() * (lightnessRange.Item2 - lightnessRange.Item1 + double.Epsilon), 37 + saturationRange.Item1 + 38 + random.NextDouble() * (saturationRange.Item2 - saturationRange.Item1 + double.Epsilon), 39 + random.Next(0, 255) 40 + )).MapToRgbGamut(); 41 + return color; 42 + } 43 + 44 + public static Unicolour[] GetRandomPalette(Unicolour primaryColor, int numberOfColors = 5, int spread = 20, double alpha = 1D) 45 + { 46 + // Ensure we get an odd number of colors for better balance 47 + var halfPaletteSize = numberOfColors / 2; 48 + var colors = new List<Unicolour>(); 49 + 50 + // Get the HSL representation of the primary color 51 + var primaryHsl = primaryColor.Hsl; 52 + 53 + for (int i = -halfPaletteSize; i <= halfPaletteSize; i++) 54 + { 55 + // Create a new color with the shifted hue 56 + var shiftedColor = new Unicolour(ColourSpace.Hsl, primaryHsl.H + i * spread, primaryHsl.S, primaryHsl.L, alpha); 57 + colors.Add(shiftedColor); 58 + } 59 + 60 + return colors.ToArray(); 61 + } 62 + 63 + public static Unicolour[] GetRandomPalette(int numberOfColors) => 64 + GetRandomPalette(GetRandomColor(), numberOfColors); 65 + 66 + public static Unicolour[] GetRandomPalette() => GetRandomPalette(GetRandomColor()); 67 + }
+205
ReleaseImageGenerator.Domain/Implementations/ImageGenerator.cs
··· 1 + using ReleaseImageGenerator.Domain.Interfaces; 2 + using SkiaSharp; 3 + using Wacton.Unicolour; 4 + 5 + namespace ReleaseImageGenerator.Domain.Implementations; 6 + 7 + public class ImageGenerator : IImageGenerator 8 + { 9 + public string Text { get; set; } 10 + public int Width { get; set; } 11 + public int Height { get; set; } 12 + public SupportedFonts Font { get; set; } 13 + 14 + public ImageGenerator(ImageGeneratorOptions options) 15 + { 16 + Text = options.text; 17 + Width = options.width; 18 + Height = options.height; 19 + Font = options.font; 20 + } 21 + 22 + public MemoryStream GenerateImage() 23 + { 24 + using var surface = SKSurface.Create(new SKImageInfo(Width, Height)); 25 + var canvas = surface.Canvas; 26 + 27 + var random = new Random(); 28 + 29 + // Generate a harmonious color palette around the primary color 30 + var primaryColor = ColorGenerator.GetRandomColor(ColorGenerator.ColorLimitation.NEUTRAL_LIGHTNESS, 31 + ColorGenerator.ColorLimitation.NEUTRAL_SATURATION); 32 + 33 + var colorPalette = ColorGenerator.GetRandomPalette(primaryColor, 8).Select(UnicolourToSKColor).ToArray(); 34 + 35 + var backgroundRotationIsClockwise = random.Next(2) == 0; 36 + 37 + // Fill with a smooth gradient background based on the palette 38 + var paint = new SKPaint 39 + { 40 + Shader = SKShader.CreateLinearGradient( 41 + new SKPoint(0, random.Next(backgroundRotationIsClockwise ? -1000 : Height, backgroundRotationIsClockwise ? 0 : Height + 1000)), 42 + new SKPoint(Width, random.Next(backgroundRotationIsClockwise ? Height : -1000, backgroundRotationIsClockwise ? Height + 1000: 0)), 43 + colorPalette, 44 + SKShaderTileMode.Clamp 45 + ) 46 + }; 47 + 48 + var colorPalette2 = ColorGenerator.GetRandomPalette(primaryColor, 6, 60, 0.3D).Select(UnicolourToSKColor).ToArray(); 49 + var paint2 = new SKPaint 50 + { 51 + Shader = SKShader.CreateLinearGradient( 52 + new SKPoint(0, random.Next(!backgroundRotationIsClockwise ? -1000 : Height, !backgroundRotationIsClockwise ? 0 : Height + 1000)), 53 + new SKPoint(Width, random.Next(!backgroundRotationIsClockwise ? Height : -1000, !backgroundRotationIsClockwise ? Height + 1000: 0)), 54 + colorPalette2, 55 + SKShaderTileMode.Clamp 56 + ) 57 + }; 58 + 59 + var colorPalette3 = ColorGenerator.GetRandomPalette(primaryColor, 3, 50, 0.2D).Select(UnicolourToSKColor).ToArray(); 60 + var paint3 = new SKPaint 61 + { 62 + Shader = SKShader.CreateRadialGradient( 63 + new SKPoint(Width / 2, Height / 2), 64 + (float)Math.Sqrt(Math.Pow(Width, 2) + Math.Pow(Height, 2)) / 2, 65 + colorPalette3, 66 + SKShaderTileMode.Clamp 67 + ) 68 + }; 69 + canvas.DrawRect(0, 0, Width, Height, paint); 70 + canvas.DrawRect(0, 0, Width, Height, paint2); 71 + canvas.DrawRect(0, 0, Width, Height, paint3); 72 + 73 + // Add some noise 74 + AddNoise(canvas, Width, Height); 75 + 76 + // Load JetBrains Mono font 77 + var typeface = Font switch 78 + { 79 + SupportedFonts.READEX_BOLD => SKTypeface.FromFile("./fonts/ReadexPro-Bold.ttf"), 80 + SupportedFonts.READEX_MEDIUM => SKTypeface.FromFile("./fonts/ReadexPro-Medium.ttf"), 81 + SupportedFonts.READEX_LIGHT => SKTypeface.FromFile("./fonts/ReadexPro-Light.ttf"), 82 + SupportedFonts.JETBRAINS_BOLD => SKTypeface.FromFile("./fonts/JetbrainsMono-Bold.ttf"), 83 + SupportedFonts.JETBRAINS_MEDIUM => SKTypeface.FromFile("./fonts/JetbrainsMono-Medium.ttf"), 84 + SupportedFonts.JETBRAINS_LIGHT => SKTypeface.FromFile("./fonts/JetbrainsMono-Light.ttf"), 85 + SupportedFonts.SOURCE_CODE_BOLD => SKTypeface.FromFile("./fonts/SourceCodePro-Bold.ttf"), 86 + SupportedFonts.SOURCE_CODE_MEDIUM => SKTypeface.FromFile("./fonts/SourceCodePro-Medium.ttf"), 87 + SupportedFonts.SOURCE_CODE_LIGHT => SKTypeface.FromFile("./fonts/SourceCodePro-Light.ttf"), 88 + _ => SKTypeface.Default 89 + }; 90 + var fontsize = GetMaxFontSize(Width - Width / 3, typeface, Text, 1f, Width > Height ? Height / 3 : Width / 3); 91 + 92 + // Calculate text size and position 93 + paint = new SKPaint 94 + { 95 + Color = SKColors.White, 96 + TextSize = fontsize, 97 + IsAntialias = true, 98 + Typeface = typeface 99 + }; 100 + 101 + var textWidth = paint.MeasureText(Text); 102 + var textBounds = new SKRect(); 103 + paint.MeasureText(Text, ref textBounds); 104 + 105 + float textX = (Width - textWidth) / 2; 106 + float textY = Height / 2 + textBounds.Height / 2; 107 + 108 + // Draw glassmorphism background with smaller border and shadow 109 + var bgPaint = new SKPaint 110 + { 111 + Color = SKColors.White.WithAlpha(40), 112 + IsAntialias = true 113 + }; 114 + var borderPaint = new SKPaint 115 + { 116 + Color = SKColors.White.WithAlpha(80), 117 + IsStroke = true, 118 + StrokeWidth = 4, 119 + IsAntialias = true 120 + }; 121 + 122 + var shadowPaint = new SKPaint 123 + { 124 + Color = SKColors.Black.WithAlpha(50), 125 + MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 10) 126 + }; 127 + 128 + var padding = 80; 129 + var rect = new SKRect(textX - padding, textY + textBounds.Top - padding, textX + textWidth + padding, 130 + textY + padding); 131 + 132 + // Draw shadow in the direction of the light 133 + canvas.Translate(10, 10); 134 + canvas.DrawRoundRect(rect, 50, 50, shadowPaint); 135 + canvas.Translate(-10, -10); 136 + 137 + canvas.DrawRoundRect(rect, 50, 50, bgPaint); 138 + canvas.DrawRoundRect(rect, 50, 50, borderPaint); 139 + 140 + // Draw the text 141 + canvas.DrawText(Text, textX, textY, paint); 142 + 143 + // Return as JPEG 144 + var stream = new MemoryStream(); 145 + using var image = surface.Snapshot(); 146 + using var data = image.Encode(SKEncodedImageFormat.Jpeg, 90); 147 + data.SaveTo(stream); 148 + return stream; 149 + } 150 + 151 + SKColor UnicolourToSKColor(Unicolour unicolour) 152 + { 153 + return new SKColor( 154 + (byte)(unicolour.Rgb.R * 255), 155 + (byte)(unicolour.Rgb.G * 255), 156 + (byte)(unicolour.Rgb.B * 255), 157 + (byte)unicolour.Alpha.A255); 158 + } 159 + 160 + void AddNoise(SKCanvas canvas, int width, int height) 161 + { 162 + var random = new Random(); 163 + var noisePaint = new SKPaint { Color = SKColors.White.WithAlpha(20) }; 164 + for (int i = 0; i < 10000; i++) 165 + { 166 + canvas.DrawPoint(random.Next(width), random.Next(height), noisePaint); 167 + } 168 + } 169 + 170 + public float GetMaxFontSize(double sectorSize, SKTypeface typeface, string text, float degreeOfCertainty = 1f, float maxFont = 100f) 171 + { 172 + var max = maxFont; // The upper bound. We know the font size is below this value 173 + var min = 0f; // The lower bound, We know the font size is equal to or above this value 174 + var last = -1f; // The last calculated value. 175 + float value; 176 + while (true) 177 + { 178 + value = min + ((max - min) / 2); // Find the half way point between Max and Min 179 + using (SKFont ft = new SKFont(typeface, value)) 180 + using (SKPaint paint = new SKPaint(ft)) 181 + { 182 + if (paint.MeasureText(text) > sectorSize) // Measure the string size at this font size 183 + { 184 + // The text size is too large 185 + // therefore the max possible size is below value 186 + last = value; 187 + max = value; 188 + } 189 + else 190 + { 191 + // The text fits within the area 192 + // therefore the min size is above or equal to value 193 + min = value; 194 + 195 + // Check if this value is within our degree of certainty 196 + if (Math.Abs(last - value) <= degreeOfCertainty) 197 + return last; // Value is within certainty range, we found the best font size! 198 + 199 + //This font difference is not within our degree of certainty 200 + last = value; 201 + } 202 + } 203 + } 204 + } 205 + }
+16
ReleaseImageGenerator.Domain/Implementations/ImageGeneratorOptions.cs
··· 1 + namespace ReleaseImageGenerator.Domain.Implementations; 2 + 3 + public record ImageGeneratorOptions(string text, int width, int height, SupportedFonts font); 4 + 5 + public enum SupportedFonts 6 + { 7 + JETBRAINS_LIGHT, 8 + JETBRAINS_MEDIUM, 9 + JETBRAINS_BOLD, 10 + READEX_LIGHT, 11 + READEX_MEDIUM, 12 + READEX_BOLD, 13 + SOURCE_CODE_LIGHT, 14 + SOURCE_CODE_MEDIUM, 15 + SOURCE_CODE_BOLD, 16 + }
+8
ReleaseImageGenerator.Domain/Interfaces/IImageGenerator.cs
··· 1 + using ReleaseImageGenerator.Domain.Implementations; 2 + 3 + namespace ReleaseImageGenerator.Domain.Interfaces; 4 + 5 + public interface IImageGenerator 6 + { 7 + MemoryStream GenerateImage(); 8 + }
+14
ReleaseImageGenerator.Domain/ReleaseImageGenerator.Domain.csproj
··· 1 + <Project Sdk="Microsoft.NET.Sdk"> 2 + 3 + <PropertyGroup> 4 + <TargetFramework>net8.0</TargetFramework> 5 + <ImplicitUsings>enable</ImplicitUsings> 6 + <Nullable>enable</Nullable> 7 + </PropertyGroup> 8 + 9 + <ItemGroup> 10 + <PackageReference Include="SkiaSharp" Version="3.116.1" /> 11 + <PackageReference Include="Wacton.Unicolour" Version="5.0.0" /> 12 + </ItemGroup> 13 + 14 + </Project>
+6
ReleaseImageGenerator.sln
··· 2 2 Microsoft Visual Studio Solution File, Format Version 12.00 3 3 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReleaseImageGenerator.API", "ReleaseImageGenerator.API\ReleaseImageGenerator.API.csproj", "{6DAC702E-2CFB-41BA-813E-8352C60203AE}" 4 4 EndProject 5 + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReleaseImageGenerator.Domain", "ReleaseImageGenerator.Domain\ReleaseImageGenerator.Domain.csproj", "{3042559D-EEF7-4E6D-99E0-6D604E8C31B9}" 6 + EndProject 5 7 Global 6 8 GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 9 Debug|Any CPU = Debug|Any CPU ··· 12 14 {6DAC702E-2CFB-41BA-813E-8352C60203AE}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 15 {6DAC702E-2CFB-41BA-813E-8352C60203AE}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 16 {6DAC702E-2CFB-41BA-813E-8352C60203AE}.Release|Any CPU.Build.0 = Release|Any CPU 17 + {3042559D-EEF7-4E6D-99E0-6D604E8C31B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 + {3042559D-EEF7-4E6D-99E0-6D604E8C31B9}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 + {3042559D-EEF7-4E6D-99E0-6D604E8C31B9}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 + {3042559D-EEF7-4E6D-99E0-6D604E8C31B9}.Release|Any CPU.Build.0 = Release|Any CPU 15 21 EndGlobalSection 16 22 EndGlobal