diff --git a/FaceExplorer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FaceExplorer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2b83e1a..aa0bacd 100644 --- a/FaceExplorer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/FaceExplorer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "42598adf0e546dd7490b40b2fd0f5298dee9c098f71dcdbfcb5cc4c07573f374", "pins" : [ { "identity" : "sqlite.swift", @@ -6,9 +7,9 @@ "location" : "https://github.com/stephencelis/SQLite.swift", "state" : { "branch" : "master", - "revision" : "926b87da689bc68159298627aa97f20c24ddd2d8" + "revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8" } } ], - "version" : 2 + "version" : 3 } diff --git a/FaceExplorer/Model/Face.swift b/FaceExplorer/Model/Face.swift index 9ebc9eb..5695d41 100644 --- a/FaceExplorer/Model/Face.swift +++ b/FaceExplorer/Model/Face.swift @@ -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 + } } diff --git a/FaceExplorer/Model/ModelData.swift b/FaceExplorer/Model/ModelData.swift index e4c8154..83b87ef 100755 --- a/FaceExplorer/Model/ModelData.swift +++ b/FaceExplorer/Model/ModelData.swift @@ -1,6 +1,7 @@ import Foundation import Combine import SQLite +typealias Expression = SQLite.Expression final class ModelData: ObservableObject { @Published var persons: [Person] = [] @@ -205,32 +206,32 @@ func getName(face: Row, personsDict: [Int: Row]) -> String { func getPersons(path: String) throws -> [Person] { var personsSet: Set = [] - let db = try Connection(path, readonly: true) - let persons = Table("ZPERSON") - let pk = Expression("Z_PK") - let fullName = Expression("ZFULLNAME") - let faceCount = Expression("ZFACECOUNT") - let mergeTargetPerson = Expression("ZMERGETARGETPERSON") - let type = Expression("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("Z_PK") + let fullName = Expression("ZFULLNAME") + let faceCount = Expression("ZFACECOUNT") + let mergeTargetPerson = Expression("ZMERGETARGETPERSON") + let type = Expression("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) } diff --git a/FaceExplorer/Views/Faces/FaceCard.swift b/FaceExplorer/Views/Faces/FaceCard.swift index 68148ef..1323723 100644 --- a/FaceExplorer/Views/Faces/FaceCard.swift +++ b/FaceExplorer/Views/Faces/FaceCard.swift @@ -17,7 +17,6 @@ struct FaceCard: View { var body: some View { VStack { face.image - .resizable() .interpolation(.low) .cornerRadius(5) .frame(width: 150, height: 150)