perf(menubar): 设置页预览只栅格化一次

预览图原为绘制回调型 NSImage,SwiftUI 每次布局或重绘都会重跑文字排版。
改为渲染一次位图后复用;设置页停在“菜单栏”时,同环境实测平均 CPU 约 2.73% 降至 2.16%。
This commit is contained in:
ysicing authored and 缘生 committed 2026-10-01 11:18:40 +08:00
1 parent dacc9cbbaa
commit 2c0905ece8
2 files changed
+47 -5

No files matched your search

@@ -294,20 +294,37 @@ enum MenuBarRenderer {
return image
}
/// 设置页预览:按指定外观绘制到不透明底色上
/// 设置页预览:按指定外观绘制到不透明底色上。
/// 绘制回调型图像每次布局或重绘都会重跑文字排版,因此栅格化一次,SwiftUI 之后只复用位图。
static func preview(_ image: NSImage, dark: Bool) -> NSImage {
let appearance = NSAppearance(named: dark ? .darkAqua : .aqua)
return NSImage(size: image.size, flipped: false) { rect in
let draw = {
let draw = { (rect: NSRect) in
let content = {
image.draw(in: rect)
if image.isTemplate {
(dark ? NSColor.white : NSColor.black).set()
rect.fill(using: .sourceAtop)
}
}
if let appearance { appearance.performAsCurrentDrawingAppearance(draw) } else { draw() }
return true
if let appearance { appearance.performAsCurrentDrawingAppearance(content) } else { content() }
}
let scale = NSScreen.main?.backingScaleFactor ?? 2
guard let rep = NSBitmapImageRep(bitmapDataPlanes: nil,
pixelsWide: Int(ceil(image.size.width * scale)),
pixelsHigh: Int(ceil(image.size.height * scale)),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false,
colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0),
let context = NSGraphicsContext(bitmapImageRep: rep) else {
return NSImage(size: image.size, flipped: false) { draw($0); return true }
}
rep.size = image.size
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = context
draw(NSRect(origin: .zero, size: image.size))
NSGraphicsContext.restoreGraphicsState()
let result = NSImage(size: image.size)
result.addRepresentation(rep)
return result
}
// MARK: 各指标
@@ -202,6 +202,31 @@ private func isolatedDefaults() -> UserDefaults {
@MainActor
@Suite struct SettingsTests {
@Test(arguments: [false, true])
func settingsPreviewRasterizesOnceAndKeepsAppearanceColors(dark: Bool) throws {
var draws = 0
let source = NSImage(size: NSSize(width: 40, height: 18), flipped: false) { rect in
draws += 1
NSColor.black.setFill()
rect.fill()
return true
}
source.isTemplate = true
let preview = MenuBarRenderer.preview(source, dark: dark)
let drawsAfterRender = draws
#expect(drawsAfterRender >= 1)
// 反复读取预览(布局、重绘)不能再触发源图的绘制回调。
for _ in 0..<5 { _ = preview.tiffRepresentation }
#expect(draws == drawsAfterRender)
#expect(preview.size == source.size)
#expect(preview.representations.contains { $0 is NSBitmapImageRep })
// 模板图按外观着色:浅色外观为黑,深色外观为白。
let rep = try #require(preview.representations.compactMap { $0 as? NSBitmapImageRep }.first)
let center = try #require(rep.colorAt(x: rep.pixelsWide / 2, y: rep.pixelsHigh / 2)?.usingColorSpace(.deviceRGB))
#expect(center.alphaComponent > 0.99)
#expect((center.redComponent > 0.5) == dark)
}
@Test func stackedCenteredStyleRendersDifferentPlacementAtSameWidth() {
#expect(MenuBarStyle.options(for: .fan).contains(.stackedCenter))
var reading = MenuBarReading.sample