Skip to content

Commit

Permalink
Upgrade SQlite, compute thumbnails, smooth scrolling
Browse files Browse the repository at this point in the history
  • Loading branch information
TimFelixBeyer committed Sep 17, 2024
1 parent d1d8c2a commit 13a9ac0
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"originHash" : "42598adf0e546dd7490b40b2fd0f5298dee9c098f71dcdbfcb5cc4c07573f374",
"pins" : [
{
"identity" : "sqlite.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/stephencelis/SQLite.swift",
"state" : {
"branch" : "master",
"revision" : "926b87da689bc68159298627aa97f20c24ddd2d8"
"revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8"
}
}
],
"version" : 2
"version" : 3
}
78 changes: 47 additions & 31 deletions FaceExplorer/Model/Face.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,43 +98,59 @@ class ImageLoader {
width: radius,
height: radius)

let trimmedImage = trim(image: image, rect: boundsRect)
return trimmedImage
let trimmedImage = image.trim(to: boundsRect)
let resizedImage = trimmedImage.resize(to: NSSize(width: 150, height: 150))

return resizedImage
}
}

func trim(image: NSImage, rect: CGRect) -> NSImage {
guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
return NSImage() // return an empty image or handle this error case appropriately.
extension NSImage {
func trimSlow(to rect: CGRect) -> NSImage {
// we use this function for faces that go outside the bounds of the image
// it is slower but returns correct results even for those cases.
let newImage = NSImage(size: rect.size)
newImage.lockFocus()
let destRect = CGRect(origin: .zero, size: rect.size)
self.draw(in: destRect, from: rect, operation: .copy, fraction: 1.0)
newImage.unlockFocus()
return newImage
}

let transformedRect = CGRect(
x: rect.origin.x,
y: CGFloat(cgImage.height) - rect.origin.y - rect.size.height,
width: rect.width, height:
rect.height
)
let xOutOfBounds = transformedRect.minX < 0 || transformedRect.maxX > CGFloat(cgImage.width)
let yOutOfBounds = transformedRect.minY < 0 || transformedRect.maxY > CGFloat(cgImage.height)
if xOutOfBounds || yOutOfBounds {
return trimSlow(image: image, rect: rect)
}
guard let croppedImage = cgImage.cropping(to: transformedRect) else {
return NSImage() // return an empty image or handle this error case appropriately.
func trim(to rect: CGRect) -> NSImage {
guard let cgImage = self.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
return NSImage() // return an empty image or handle this error case appropriately.
}

let transformedRect = CGRect(
x: rect.origin.x,
y: CGFloat(cgImage.height) - rect.origin.y - rect.size.height,
width: rect.width, height:
rect.height
)
let xOutOfBounds = transformedRect.minX < 0 || transformedRect.maxX > CGFloat(cgImage.width)
let yOutOfBounds = transformedRect.minY < 0 || transformedRect.maxY > CGFloat(cgImage.height)
if xOutOfBounds || yOutOfBounds {
return self.trimSlow(to: rect)
}
guard let croppedImage = cgImage.cropping(to: transformedRect) else {
return NSImage() // return an empty image or handle this error case appropriately.
}

// Convert back to NSImage
let trimmedImage = NSImage(cgImage: croppedImage, size: .zero)
return trimmedImage
}

// Convert back to NSImage
let trimmedImage = NSImage(cgImage: croppedImage, size: .zero)
return trimmedImage
}
func resize(to newSize: NSSize) -> NSImage? {
let newImage = NSImage(size: newSize)
newImage.lockFocus()

let rect = NSRect(origin: .zero, size: newSize)
NSGraphicsContext.current?.imageInterpolation = .high
self.draw(in: rect, from: NSRect(origin: .zero, size: self.size), operation: .copy, fraction: 1.0)

func trimSlow(image: NSImage, rect: CGRect) -> NSImage {
// we use this function for faces that go outside the bounds of the image
// it is slower but returns correct results even for those cases.
let result = NSImage(size: rect.size)
result.lockFocus()
let destRect = CGRect(origin: .zero, size: rect.size)
image.draw(in: destRect, from: rect, operation: .copy, fraction: 1.0)
result.unlockFocus()
return result
newImage.unlockFocus()
return newImage
}
}
53 changes: 27 additions & 26 deletions FaceExplorer/Model/ModelData.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import Combine
import SQLite
typealias Expression = SQLite.Expression

final class ModelData: ObservableObject {
@Published var persons: [Person] = []
Expand Down Expand Up @@ -205,32 +206,32 @@ func getName(face: Row, personsDict: [Int: Row]) -> String {
func getPersons(path: String) throws -> [Person] {
var personsSet: Set<Person> = []

let db = try Connection(path, readonly: true)
let persons = Table("ZPERSON")
let pk = Expression<Int>("Z_PK")
let fullName = Expression<String?>("ZFULLNAME")
let faceCount = Expression<Int>("ZFACECOUNT")
let mergeTargetPerson = Expression<Int?>("ZMERGETARGETPERSON")
let type = Expression<Int>("ZTYPE")

// Create a dictionary of all persons, with `pk` as the key, yields dramatic speedup (1.2s -> 0.05s)!
let personsDict = Dictionary(uniqueKeysWithValues:
try db.prepare(persons.select(pk, fullName, faceCount, mergeTargetPerson, type)).map {
($0[pk], $0)
}
)
for (_, currentPerson) in personsDict {
var mergedPerson = currentPerson

// We follow the merge chain to the end.
while let mergeTarget = mergedPerson[mergeTargetPerson] {
mergedPerson = personsDict[mergeTarget] ?? mergedPerson
}
// We add the person if they have photos with faces, some people have 0 faces registered.
if let fullName = mergedPerson[fullName], mergedPerson[faceCount] > 0 {
personsSet.insert(try Person(id: mergedPerson[pk], name: fullName, type: mergedPerson[type]))
}
}
let db = try Connection(path, readonly: true)
let persons = Table("ZPERSON")
let pk = Expression<Int>("Z_PK")
let fullName = Expression<String?>("ZFULLNAME")
let faceCount = Expression<Int>("ZFACECOUNT")
let mergeTargetPerson = Expression<Int?>("ZMERGETARGETPERSON")
let type = Expression<Int>("ZTYPE")

// Create a dictionary of all persons, with `pk` as the key, yields dramatic speedup (1.2s -> 0.05s)!
let personsDict = Dictionary(uniqueKeysWithValues:
try db.prepare(persons.select(pk, fullName, faceCount, mergeTargetPerson, type)).map {
($0[pk], $0)
}
)
for (_, currentPerson) in personsDict {
var mergedPerson = currentPerson

// We follow the merge chain to the end.
while let mergeTarget = mergedPerson[mergeTargetPerson] {
mergedPerson = personsDict[mergeTarget] ?? mergedPerson
}
// We add the person if they have photos with faces, some people have 0 faces registered.
if let fullName = mergedPerson[fullName], mergedPerson[faceCount] > 0 {
personsSet.insert(try Person(id: mergedPerson[pk], name: fullName, type: mergedPerson[type]))
}
}
return Array(personsSet)
}

Expand Down
1 change: 0 additions & 1 deletion FaceExplorer/Views/Faces/FaceCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ struct FaceCard: View {
var body: some View {
VStack {
face.image
.resizable()
.interpolation(.low)
.cornerRadius(5)
.frame(width: 150, height: 150)
Expand Down

0 comments on commit 13a9ac0

Please sign in to comment.