Skip to content

Commit 6b1c875

Browse files
authored
Merge pull request #672 from t1m0thyj/feat/replace-wpf-with-skia
Use SkiaSharp instead of WPF to render theme previews
2 parents 0e83fa8 + 5f27c9b commit 6b1c875

13 files changed

+1171
-831
lines changed

.github/workflows/codeql.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,16 @@ jobs:
3030
- name: Checkout repository
3131
uses: actions/checkout@v6
3232

33+
- name: Setup .NET
34+
uses: actions/setup-dotnet@v5
35+
with:
36+
dotnet-version: 10.0.x
37+
3338
- name: Initialize CodeQL
3439
uses: github/codeql-action/init@v4
3540
with:
3641
languages: ${{ matrix.language }}
42+
queries: +security-extended
3743

3844
- name: Autobuild
3945
run: |

src/COM/DesktopWallpaper.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ public struct COLORREF
1616
public byte B;
1717
}
1818

19+
[StructLayout(LayoutKind.Sequential)]
20+
public struct RECT
21+
{
22+
public int Left;
23+
public int Top;
24+
public int Right;
25+
public int Bottom;
26+
}
27+
1928
public enum DesktopSlideshowOptions
2029
{
2130
DSO_SHUFFLEIMAGES = 0x01,
@@ -59,7 +68,7 @@ public interface IDesktopWallpaper
5968

6069
uint GetMonitorDevicePathCount();
6170

62-
System.Windows.Rect GetMonitorRECT([MarshalAs(UnmanagedType.LPWStr)] string monitorID);
71+
RECT GetMonitorRECT([MarshalAs(UnmanagedType.LPWStr)] string monitorID);
6372

6473
void SetBackgroundColor([MarshalAs(UnmanagedType.U4)] COLORREF color);
6574

src/Skia/ImageCache.cs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Reflection;
10+
using System.Windows.Forms;
11+
using SkiaSharp;
12+
13+
namespace WinDynamicDesktop.Skia
14+
{
15+
sealed class ImageCache
16+
{
17+
readonly int maxWidth;
18+
readonly int maxHeight;
19+
readonly object cacheLock = new object();
20+
readonly Dictionary<Uri, SKImage> images = new Dictionary<Uri, SKImage>();
21+
22+
public SKImage this[Uri uri]
23+
{
24+
get
25+
{
26+
lock (cacheLock)
27+
{
28+
if (images.TryGetValue(uri, out var image))
29+
{
30+
return image;
31+
}
32+
33+
var img = CreateImage(uri);
34+
if (img != null)
35+
{
36+
images.Add(uri, img);
37+
}
38+
return img;
39+
}
40+
}
41+
}
42+
43+
public void Clear()
44+
{
45+
lock (cacheLock)
46+
{
47+
foreach (var image in images.Values)
48+
{
49+
image?.Dispose();
50+
}
51+
images.Clear();
52+
}
53+
GC.Collect();
54+
}
55+
56+
public ImageCache(bool limitDecodeSize = true)
57+
{
58+
if (limitDecodeSize)
59+
{
60+
int maxArea = 0;
61+
foreach (Screen screen in Screen.AllScreens)
62+
{
63+
int area = screen.Bounds.Width * screen.Bounds.Height;
64+
if (area > maxArea)
65+
{
66+
maxArea = area;
67+
maxWidth = screen.Bounds.Width;
68+
maxHeight = screen.Bounds.Height;
69+
}
70+
}
71+
}
72+
else
73+
{
74+
maxWidth = int.MaxValue;
75+
maxHeight = int.MaxValue;
76+
}
77+
}
78+
79+
private SKImage CreateImage(Uri uri)
80+
{
81+
try
82+
{
83+
Stream stream = null;
84+
85+
if (uri.IsAbsoluteUri && uri.Scheme == "file")
86+
{
87+
string path = uri.LocalPath;
88+
if (File.Exists(path))
89+
{
90+
stream = File.OpenRead(path);
91+
}
92+
}
93+
else if (!uri.IsAbsoluteUri)
94+
{
95+
// Embedded resource
96+
stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(uri.OriginalString);
97+
}
98+
99+
if (stream == null)
100+
{
101+
return null;
102+
}
103+
104+
using (stream)
105+
{
106+
using (var codec = SKCodec.Create(stream))
107+
{
108+
if (codec == null)
109+
{
110+
return null;
111+
}
112+
113+
var info = codec.Info;
114+
115+
// Calculate target dimensions
116+
int targetWidth = info.Width;
117+
int targetHeight = info.Height;
118+
119+
if (info.Width > maxWidth || info.Height > maxHeight)
120+
{
121+
float scale = Math.Min((float)maxWidth / info.Width, (float)maxHeight / info.Height);
122+
targetWidth = (int)(info.Width * scale);
123+
targetHeight = (int)(info.Height * scale);
124+
}
125+
126+
// Decode at native size
127+
using (var sourceBitmap = new SKBitmap(info))
128+
{
129+
if (codec.GetPixels(sourceBitmap.Info, sourceBitmap.GetPixels()) != SKCodecResult.Success)
130+
{
131+
return null;
132+
}
133+
134+
// If scaling is needed, create scaled version with high quality
135+
if (targetWidth != sourceBitmap.Width || targetHeight != sourceBitmap.Height)
136+
{
137+
using (var scaledBitmap = new SKBitmap(targetWidth, targetHeight, info.ColorType, info.AlphaType))
138+
{
139+
sourceBitmap.ScalePixels(scaledBitmap, new SKSamplingOptions(SKCubicResampler.Mitchell));
140+
var image = SKImage.FromBitmap(scaledBitmap);
141+
return image;
142+
}
143+
}
144+
else
145+
{
146+
// No scaling needed
147+
var image = SKImage.FromBitmap(sourceBitmap);
148+
return image;
149+
}
150+
}
151+
}
152+
}
153+
}
154+
catch
155+
{
156+
return null;
157+
}
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)