|
6 | 6 |
|
7 | 7 | public static class DrawingHelper
|
8 | 8 | {
|
9 |
| - private static String RESOURCE_PATH = "Loupedeck.VoiceMeeterPlugin.Resources"; |
10 |
| - |
11 |
| - public static SKPath RoundedRect(SKRect bounds, Int32 radius) |
12 |
| - { |
13 |
| - var diameter = radius * 2; |
14 |
| - SKRect arc = new SKRect(bounds.Left, bounds.Top, bounds.Left + diameter, bounds.Top + diameter); |
15 |
| - SKPath path = new SKPath(); |
16 |
| - |
17 |
| - if (radius == 0) |
18 |
| - { |
19 |
| - path.AddRect(bounds); |
20 |
| - return path; |
21 |
| - } |
22 |
| - |
23 |
| - // top left arc |
24 |
| - path.ArcTo(arc, 180, 90, false); |
25 |
| - |
26 |
| - // top right arc |
27 |
| - arc = new SKRect(bounds.Right - diameter, bounds.Top, bounds.Right, bounds.Top + diameter); |
28 |
| - path.ArcTo(arc, 270, 90, false); |
29 |
| - |
30 |
| - // bottom right arc |
31 |
| - arc = new SKRect(bounds.Right - diameter, bounds.Bottom - diameter, bounds.Right, bounds.Bottom); |
32 |
| - path.ArcTo(arc, 0, 90, false); |
33 |
| - |
34 |
| - // bottom left arc |
35 |
| - arc = new SKRect(bounds.Left, bounds.Bottom - diameter, bounds.Left + diameter, bounds.Bottom); |
36 |
| - path.ArcTo(arc, 90, 90, false); |
37 |
| - |
38 |
| - path.Close(); |
39 |
| - return path; |
40 |
| - } |
| 9 | + private static readonly String RESOURCE_PATH = "Loupedeck.VoiceMeeterPlugin.Resources"; |
41 | 10 |
|
42 | 11 | public static BitmapImage ReadImage(String imageName, String ext = "png", String addPath = "Images")
|
43 | 12 | => EmbeddedResources.ReadImage($"{RESOURCE_PATH}.{addPath}.{imageName}.{ext}");
|
44 | 13 |
|
45 |
| - public static BitmapBuilder LoadBitmapBuilder |
46 |
| - (String imageName = "clear", String text = null, BitmapColor? textColor = null, String ext = "png", |
47 |
| - String addPath = "Images") |
| 14 | + public static BitmapBuilder LoadBitmapBuilder(String imageName = "clear", String text = null, BitmapColor? textColor = null, String ext = "png", String addPath = "Images") |
48 | 15 | => LoadBitmapBuilder(ReadImage(imageName, ext, addPath), text, textColor);
|
49 | 16 |
|
50 |
| - public static BitmapBuilder LoadBitmapBuilder |
51 |
| - (BitmapImage image, String text = null, BitmapColor? textColor = null) |
| 17 | + public static BitmapBuilder LoadBitmapBuilder(BitmapImage image, String text = null, BitmapColor? textColor = null) |
52 | 18 | {
|
53 | 19 | var builder = new BitmapBuilder(80, 80);
|
54 |
| - |
55 | 20 | builder.Clear(BitmapColor.Black);
|
56 | 21 | builder.DrawImage(image);
|
57 | 22 |
|
58 | 23 | return text is null ? builder : builder.AddTextOutlined(text, textColor: textColor);
|
59 | 24 | }
|
60 | 25 |
|
61 |
| - public static BitmapImage LoadBitmapImage |
62 |
| - (String imageName = "clear", String text = null, BitmapColor? textColor = null, String ext = "png", |
63 |
| - String addPath = "Images") |
| 26 | + public static BitmapImage LoadBitmapImage(String imageName = "clear", String text = null, BitmapColor? textColor = null, String ext = "png", String addPath = "Images") |
64 | 27 | => LoadBitmapBuilder(imageName, text, textColor, ext, addPath).ToImage();
|
65 | 28 |
|
66 | 29 | public static BitmapImage LoadBitmapImage(BitmapImage image, String text = null, BitmapColor? textColor = null)
|
67 | 30 | => LoadBitmapBuilder(image, text, textColor).ToImage();
|
68 | 31 |
|
69 |
| - public static BitmapBuilder AddTextOutlined(this BitmapBuilder builder, String text, |
70 |
| - BitmapColor? outlineColor = null, |
71 |
| - BitmapColor? textColor = null, Int32 fontSize = 12) |
| 32 | + public static BitmapBuilder AddTextOutlined(this BitmapBuilder builder, String text, BitmapColor? outlineColor = null, BitmapColor? textColor = null, Int32 fontSize = 12) |
72 | 33 | {
|
73 |
| - // TODO: Make it outline |
74 | 34 | builder.DrawText(text, 0, -30, 80, 80, textColor, fontSize, 0, 0);
|
75 | 35 | return builder;
|
76 | 36 | }
|
77 | 37 |
|
78 | 38 | public static BitmapImage DrawDefaultImage(String innerText, String outerText, SKColor brushColor, Int32 width = 80, Int32 height = 80)
|
79 | 39 | {
|
80 |
| - // Set the dimensions and font |
81 | 40 | SKTypeface font = SKTypeface.FromFamilyName("Arial", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright);
|
82 |
| - var fontSize = 20; |
83 |
| - |
84 |
| - // Create the canvas and paint |
85 | 41 | var info = new SKImageInfo(width, height);
|
86 |
| - var surface = SKSurface.Create(info); |
| 42 | + using var surface = SKSurface.Create(info); |
87 | 43 | var canvas = surface.Canvas;
|
88 |
| - var paint = new SKPaint { Color = brushColor, IsAntialias = true, Typeface = font }; |
| 44 | + using var paint = new SKPaint(); |
| 45 | + paint.Color = brushColor; |
| 46 | + paint.IsAntialias = true; |
| 47 | + paint.Typeface = font; |
89 | 48 |
|
90 |
| - // Calculate the dimensions of the rounded rectangle outline |
91 | 49 | var rect = new SKRect(5, 20, width - 5, height - 20);
|
92 | 50 |
|
93 |
| - // Adjust the font size if necessary to fit the inner text within the dimensions of the rounded rectangle outline |
94 |
| - while (true) |
95 |
| - { |
96 |
| - paint.TextSize = fontSize; |
97 |
| - SKRect tb = new SKRect(); |
98 |
| - paint.MeasureText(innerText, ref tb); |
99 |
| - if (tb.Width < rect.Width - 5 && tb.Height < rect.Height) |
100 |
| - { |
101 |
| - break; |
102 |
| - } |
103 |
| - |
104 |
| - fontSize--; |
105 |
| - } |
| 51 | + paint.TextSize = GetOptimalFontSize(innerText, paint, rect); |
106 | 52 |
|
107 |
| - // Draw the rounded rectangle outline |
108 | 53 | var cornerRadius = Math.Min(width, height) / 2;
|
109 | 54 | paint.Style = SKPaintStyle.Stroke;
|
110 | 55 | paint.StrokeWidth = 2;
|
111 | 56 | canvas.DrawRoundRect(rect, cornerRadius, cornerRadius, paint);
|
112 | 57 | paint.Style = SKPaintStyle.Fill;
|
113 | 58 |
|
114 |
| - // Draw the inner text centered within the rounded rectangle outline |
115 | 59 | paint.TextAlign = SKTextAlign.Center;
|
116 |
| - paint.TextSize = fontSize; |
117 | 60 | canvas.DrawText(innerText, rect.MidX, rect.MidY - (paint.FontMetrics.Descent + paint.FontMetrics.Ascent) / 2, paint);
|
118 | 61 |
|
119 |
| - // Save the image to memory and return the memory streams |
120 | 62 | var image = surface.Snapshot();
|
121 | 63 | var data = image.Encode(SKEncodedImageFormat.Png, 100);
|
122 | 64 |
|
123 | 65 | return LoadBitmapImage(BitmapImage.FromArray(data.ToArray()), outerText);
|
124 | 66 | }
|
125 | 67 |
|
126 |
| - public static BitmapImage DrawVolumeBar(PluginImageSize imageSize, BitmapColor backgroundColor, BitmapColor foregroundColor, Single currentValue, Int32 minValue, Int32 maxValue, |
127 |
| - Int32 scaleFactor, String cmd, String name = "", Boolean drawValue = true) |
| 68 | + public static BitmapImage DrawVolumeBar(PluginImageSize imageSize, BitmapColor backgroundColor, BitmapColor foregroundColor, Single currentValue, Int32 minValue, Int32 maxValue, Int32 scaleFactor, String cmd, String name = "", Boolean drawValue = true) |
128 | 69 | {
|
129 |
| - // Prepare variables |
130 | 70 | var dim = imageSize.GetDimension();
|
131 |
| - var percentage = (currentValue - minValue) / (maxValue - minValue) * 100; |
| 71 | + var percentage = (currentValue - minValue) / (maxValue - minValue); |
132 | 72 | var height = (Int32)(dim * 0.9);
|
133 | 73 | var width = (Int32)(dim * 0.6);
|
134 |
| - var calculatedHeight = (Int32)(height * percentage / 100); |
| 74 | + var calculatedHeight = (Int32)(height * percentage); |
135 | 75 | var xCenter = dim / 2 - width / 2;
|
136 | 76 | var yCenter = dim / 2 + height / 2;
|
137 |
| - var builder = new BitmapBuilder(dim, dim); |
| 77 | + using var builder = new BitmapBuilder(dim, dim); |
138 | 78 |
|
139 |
| - // Reset to black |
140 | 79 | builder.Clear(BitmapColor.Black);
|
141 |
| - |
142 |
| - // Draw volume bar and border |
143 | 80 | builder.DrawRectangle(xCenter, yCenter, width, -height, backgroundColor);
|
144 | 81 | builder.FillRectangle(xCenter, yCenter, width, -calculatedHeight, backgroundColor);
|
145 | 82 |
|
146 |
| - // Draw value text at the center |
147 | 83 | if (drawValue)
|
148 | 84 | {
|
149 | 85 | builder.DrawText((currentValue / scaleFactor).ToString(CultureInfo.CurrentCulture), foregroundColor);
|
150 | 86 | }
|
151 | 87 |
|
152 |
| - const Int32 fontSize = 16; |
153 |
| - |
154 |
| - var cmdSize = GetFontSize(fontSize, cmd, dim); |
155 |
| - |
156 |
| - // Draw cmd text at the bottom |
| 88 | + var cmdSize = GetOptimalFontSize(cmd, dim: dim); |
157 | 89 | builder.DrawText(cmd, 0, dim / 2 - cmdSize / 2, dim, dim, foregroundColor, cmdSize, 0, 0);
|
158 | 90 |
|
159 |
| - // if name is available, draw it over the volume bar |
160 |
| - if (String.IsNullOrEmpty(name)) |
| 91 | + if (String.IsNullOrWhiteSpace(name)) |
161 | 92 | {
|
162 | 93 | return builder.ToImage();
|
163 | 94 | }
|
164 | 95 |
|
165 |
| - var nameSize = GetFontSize(fontSize, name, dim); |
166 |
| - |
167 |
| - // draw the text using the calculated font size |
| 96 | + var nameSize = GetOptimalFontSize(name, dim: dim); |
168 | 97 | builder.DrawText(name, 0, dim / 2 * -1 + nameSize / 2, dim, dim, foregroundColor, nameSize, 0, 0);
|
169 | 98 |
|
170 | 99 | return builder.ToImage();
|
171 | 100 | }
|
172 | 101 |
|
173 |
| - private static Int32 GetFontSize(Int32 fontSize, String text, Int32 dim) |
| 102 | + private static Int32 GetOptimalFontSize(String text, SKPaint paint = null, SKRect? rect = null, Int32? dim = null) |
174 | 103 | {
|
175 |
| - // create a SKPaint object for measuring the text |
176 |
| - var paint = new SKPaint { TextSize = fontSize, IsAntialias = true }; |
| 104 | + var minFontSize = 1; |
| 105 | + var maxFontSize = 16; |
| 106 | + SKRect textBounds = new SKRect(); |
177 | 107 |
|
178 |
| - // measure the size of the text |
179 |
| - var textBounds = new SKRect(); |
180 |
| - paint.MeasureText(text, ref textBounds); |
| 108 | + if (paint is null) |
| 109 | + { |
| 110 | + paint = new SKPaint { IsAntialias = true }; |
| 111 | + } |
181 | 112 |
|
182 |
| - // adjust the font size until the text fits within the bounds of the image |
183 |
| - while (textBounds.Width > dim || textBounds.Height > dim) |
| 113 | + while (minFontSize <= maxFontSize) |
184 | 114 | {
|
185 |
| - fontSize -= 1; |
186 |
| - paint.TextSize = fontSize; |
| 115 | + var midFontSize = (minFontSize + maxFontSize) / 2; |
| 116 | + paint.TextSize = midFontSize; |
187 | 117 | paint.MeasureText(text, ref textBounds);
|
| 118 | + |
| 119 | + var fits = false; |
| 120 | + |
| 121 | + if (rect.HasValue) |
| 122 | + { |
| 123 | + fits = textBounds.Width <= rect.Value.Width && textBounds.Height <= rect.Value.Height; |
| 124 | + } |
| 125 | + else if (dim.HasValue) |
| 126 | + { |
| 127 | + fits = textBounds.Width <= dim.Value && textBounds.Height <= dim.Value; |
| 128 | + } |
| 129 | + |
| 130 | + if (fits) |
| 131 | + { |
| 132 | + minFontSize = midFontSize + 1; |
| 133 | + } |
| 134 | + else |
| 135 | + { |
| 136 | + maxFontSize = midFontSize - 1; |
| 137 | + } |
188 | 138 | }
|
189 | 139 |
|
190 |
| - return fontSize; |
| 140 | + return maxFontSize; |
191 | 141 | }
|
192 | 142 |
|
| 143 | + |
| 144 | + |
193 | 145 | private static Int32 GetDimension(this PluginImageSize size) =>
|
194 | 146 | size switch
|
195 | 147 | {
|
|
0 commit comments