-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathThumbnailProvider.swift
More file actions
128 lines (108 loc) · 5.31 KB
/
Copy pathThumbnailProvider.swift
File metadata and controls
128 lines (108 loc) · 5.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//
// ThumbnailProvider.swift
// zxql-thumbnail-extension
//
// Created by Andrew Dunbar on 31/1/2026.
//
import Cocoa
import QuickLookThumbnailing
extension RandomAccessCollection {
subscript(o offset: Int) -> Element {
return self[index(startIndex, offsetBy: offset)]
}
subscript(o offset: Int, l length: Int) -> SubSequence {
let startIndex = self.index(self.startIndex, offsetBy: offset)
let endIndex = self.index(startIndex, offsetBy: length)
return self[startIndex..<endIndex]
}
}
class ThumbnailProvider: QLThumbnailProvider {
override func provideThumbnail(for request: QLFileThumbnailRequest, _ handler: @escaping (QLThumbnailReply?, Error?) -> Void) {
do {
let data = try Data(contentsOf: request.fileURL)
guard data.count == 49179 else {
throw NSError(domain: "ThumbnailProvider", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid .sna file"])
}
let width = 256
let height = 192
let bitmap = NSBitmapImageRep(
bitmapDataPlanes: nil,
pixelsWide: width,
pixelsHigh: height,
bitsPerSample: 8,
samplesPerPixel: 3,
hasAlpha: false,
isPlanar: false,
colorSpaceName: .calibratedRGB,
bitmapFormat: [],
bytesPerRow: width * 3,
bitsPerPixel: 24
)!
let displayStart = 27
let displayLength = 32 * 192
let attributeStart = displayStart + displayLength
let attributeLength = 32 * 24
let displayData = data[o: displayStart, l: displayLength]
let attributeData = data[o: attributeStart, l: attributeLength]
guard let bitmapPtr = bitmap.bitmapData else {
throw NSError(domain: "ThumbnailProvider", code: -2, userInfo: [NSLocalizedDescriptionKey: "Failed to create bitmap"])
}
for charY in 0..<24 {
for charX in 0..<32 {
let attr = attributeData[o: charY * 32 + charX]
let ink = attr & 0x07
let inkB = UInt8(bitPattern: -Int8(ink & 0b001))
let inkR = UInt8(bitPattern: -Int8(ink & 0b010)>>1)
let inkG = UInt8(bitPattern: -Int8(ink & 0b100)>>2)
let paper = (attr>>3) & 0x07
let paperB = UInt8(bitPattern: -Int8(paper & 0b001))
let paperR = UInt8(bitPattern: -Int8(paper & 0b010)>>1)
let paperG = UInt8(bitPattern: -Int8(paper & 0b100)>>2)
let bright = (attr & 0x40) != 0
for pixY in 0..<8 {
let y = charY * 8 + pixY
let specY = (y & 0b11000000) | ((y & 0b00000111) << 3) | ((y & 0b00111000) >> 3)
let byte = displayData[o: specY * 32 + charX]
let off = y * 256 * 3 + charX * 8 * 3
for bit in 0..<8 {
var (r, g, b) = ((0b10000000 >> bit) & byte) != 0 ? (inkR, inkG, inkB) : (paperR, paperG, paperB)
if !bright {
r = (r >> 2) + (r >> 1) + (r >> 3)
g = (g >> 2) + (g >> 1) + (g >> 3)
b = (b >> 2) + (b >> 1) + (b >> 3)
}
let pixelOffset = off + bit * 3
bitmapPtr[pixelOffset] = r
bitmapPtr[pixelOffset + 1] = g
bitmapPtr[pixelOffset + 2] = b
}
}
}
}
let image = NSImage(size: bitmap.size)
image.addRepresentation(bitmap)
// Set context size respecting aspect ratio (256:192 = 4:3)
// Must be between minimumSize and maximumSize per Apple docs
let minSize = request.minimumSize
let maxSize = request.maximumSize
let aspectRatio = CGFloat(height) / CGFloat(width)
// Calculate size that fits within constraints while preserving aspect
var contextSize = maxSize
let proposedHeight = aspectRatio * maxSize.width
if proposedHeight <= maxSize.height {
contextSize.height = max(proposedHeight.rounded(.down), minSize.height)
} else {
contextSize.width = maxSize.height / aspectRatio
contextSize.width = max(contextSize.width.rounded(.down), minSize.width)
}
let reply = QLThumbnailReply(contextSize: contextSize, currentContextDrawing: { () -> Bool in
image.draw(in: NSRect(origin: .zero, size: contextSize))
return true
})
handler(reply, nil)
} catch {
// Silently reject invalid files - macOS will use other handlers
handler(nil, error)
}
}
}