From 3a7930088713a66c84ff3d276fb0da2ba3762d6f Mon Sep 17 00:00:00 2001 From: yoshikazu Date: Mon, 27 Jul 2026 15:41:29 +0000 Subject: [PATCH] Add thumbnail loader --- .../Photochk/Services/ThumbnailLoader.swift | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Sources/Photochk/Services/ThumbnailLoader.swift diff --git a/Sources/Photochk/Services/ThumbnailLoader.swift b/Sources/Photochk/Services/ThumbnailLoader.swift new file mode 100644 index 0000000..829d6d9 --- /dev/null +++ b/Sources/Photochk/Services/ThumbnailLoader.swift @@ -0,0 +1,46 @@ +import AppKit +import Foundation +import QuickLookThumbnailing + +/// サムネイル生成。QuickLook 任せなので HEIC や RAW もそのまま扱える。 +@MainActor +final class ThumbnailLoader { + static let shared = ThumbnailLoader() + + private let cache = NSCache() + + private init() { + cache.countLimit = 600 + } + + /// すでに読み込み済みなら即座に返す。スクロールで戻ってきたときに + /// 一瞬プレースホルダが出るのを防ぐために使う。 + func cached(for url: URL, size: CGFloat) -> NSImage? { + cache.object(forKey: Self.key(url, size)) + } + + func thumbnail(for url: URL, size: CGFloat) async -> NSImage? { + let key = Self.key(url, size) + if let cached = cache.object(forKey: key) { return cached } + + let scale = NSScreen.main?.backingScaleFactor ?? 2 + let request = QLThumbnailGenerator.Request( + fileAt: url, + size: CGSize(width: size, height: size), + scale: scale, + representationTypes: .thumbnail + ) + + guard let representation = try? await QLThumbnailGenerator.shared + .generateBestRepresentation(for: request) + else { return nil } + + let image = NSImage(cgImage: representation.cgImage, size: representation.contentRect.size) + cache.setObject(image, forKey: key) + return image + } + + private static func key(_ url: URL, _ size: CGFloat) -> NSString { + "\(url.path)|\(Int(size))" as NSString + } +}