ClassifyPhotoPlugin.swift 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import Flutter
  2. import Photos
  3. import UIKit
  4. public class ClassifyPhotoPlugin: NSObject, FlutterPlugin {
  5. var photoClassifier = ClassifyPhoto()
  6. public static func register(with registrar: FlutterPluginRegistrar) {
  7. let channel = FlutterMethodChannel(name: "classify_photo", binaryMessenger: registrar.messenger())
  8. let instance = ClassifyPhotoPlugin()
  9. registrar.addMethodCallDelegate(instance, channel: channel)
  10. }
  11. public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
  12. print("iOS: Received method call: \(call.method)")
  13. switch call.method {
  14. case "getPhoto":
  15. self.getPhoto(flutterResult: result)
  16. case "getPlatformVersion":
  17. result("iOS " + UIDevice.current.systemVersion)
  18. default:
  19. result(FlutterMethodNotImplemented)
  20. }
  21. }
  22. private func getPhoto(flutterResult: @escaping FlutterResult) {
  23. DispatchQueue.global(qos: .userInitiated).async { [weak self] in
  24. guard let self = self else { return }
  25. let fetchOptions = PHFetchOptions()
  26. let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
  27. photoClassifier.classifyPhotos(
  28. assets: allPhotos,
  29. progressHandler: { (stage, progress) in
  30. print("Progress: \(stage) - \(progress)")
  31. },
  32. completion: { result in
  33. var resultData: [[String: Any]] = []
  34. let mainGroup = DispatchGroup()
  35. // 处理截图
  36. mainGroup.enter()
  37. self.processPhotoGroup(assets: result.screenshots, groupName: "screenshots") { groupData in
  38. if !groupData.isEmpty {
  39. resultData.append(["group": groupData, "type": "screenshots"])
  40. }
  41. mainGroup.leave()
  42. }
  43. // 处理相似照片组
  44. for photoGroup in result.similarPhotos {
  45. mainGroup.enter()
  46. self.processPhotoGroup(assets: photoGroup, groupName: "similar") { groupData in
  47. if !groupData.isEmpty {
  48. resultData.append(["group": groupData, "type": "similar"])
  49. }
  50. mainGroup.leave()
  51. }
  52. }
  53. // 处理地点分组
  54. for (location, assets) in result.locations {
  55. mainGroup.enter()
  56. self.processPhotoGroup(assets: assets, groupName: location) { groupData in
  57. if !groupData.isEmpty {
  58. resultData.append(["group": groupData, "type": "location", "name": location])
  59. }
  60. mainGroup.leave()
  61. }
  62. }
  63. // 处理人物分组
  64. for (person, assets) in result.people {
  65. mainGroup.enter()
  66. self.processPhotoGroup(assets: assets, groupName: person) { groupData in
  67. if !groupData.isEmpty {
  68. resultData.append(["group": groupData, "type": "people"])
  69. }
  70. mainGroup.leave()
  71. }
  72. }
  73. mainGroup.notify(queue: .main) {
  74. print("Final result count: \(resultData.count)")
  75. flutterResult(resultData)
  76. }
  77. }
  78. )
  79. }
  80. // 在后台队列处理照片
  81. // DispatchQueue.global(qos: .userInitiated).async {
  82. // let fetchOptions = PHFetchOptions()
  83. // let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
  84. // var resultData: [[String: Any]] = []
  85. //
  86. // // 创建一个组来存储所有照片
  87. // var allPhotosGroup: [[String: Any]] = []
  88. // let semaphore = DispatchSemaphore(value: 0)
  89. //
  90. // allPhotos.enumerateObjects { (asset, index, stop) in
  91. // // 获取图片的文件URL
  92. // let options = PHContentEditingInputRequestOptions()
  93. // options.isNetworkAccessAllowed = true
  94. //
  95. // asset.requestContentEditingInput(with: options) { (input, info) in
  96. // if let input = input, let url = input.fullSizeImageURL {
  97. // let photoInfo: [String: Any] = [
  98. // "path": url.path,
  99. // "id": asset.localIdentifier,
  100. // "width": asset.pixelWidth,
  101. // "height": asset.pixelHeight,
  102. // "creationDate": asset.creationDate?.timeIntervalSince1970 ?? 0
  103. // ]
  104. // allPhotosGroup.append(photoInfo)
  105. // }
  106. // semaphore.signal()
  107. // }
  108. // semaphore.wait()
  109. // }
  110. //
  111. // if !allPhotosGroup.isEmpty {
  112. // resultData.append(["group": allPhotosGroup])
  113. // }
  114. //
  115. // // 在主线程返回结果
  116. // DispatchQueue.main.async {
  117. // flutterResult(resultData)
  118. // }
  119. // }
  120. // // 在后台队列处理照片
  121. // DispatchQueue.global(qos: .userInitiated).async { [weak self] in
  122. // guard let self = self else { return }
  123. //
  124. // let fetchOptions = PHFetchOptions()
  125. // let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
  126. //
  127. // photoClassifier.classifyPhotos(
  128. // assets: allPhotos,
  129. // progressHandler: { (stage, progress) in
  130. // // 进度更新
  131. // },
  132. // completion: { result in
  133. // var similarPhotos = result.similarPhotos
  134. // var resultData: [[String: Any]] = []
  135. //
  136. // for photoGroup in similarPhotos {
  137. // var groupData: [[String: Any]] = []
  138. // let semaphore = DispatchSemaphore(value: 0)
  139. //
  140. // for asset in photoGroup {
  141. // let options = PHImageRequestOptions()
  142. // options.deliveryMode = .highQualityFormat
  143. // options.isSynchronous = false
  144. //
  145. // PHImageManager.default().requestImage(
  146. // for: asset,
  147. // targetSize: CGSize(width: 800, height: 800),
  148. // contentMode: .aspectFit,
  149. // options: options
  150. // ) { image, info in
  151. // if let image = image,
  152. // let imageData = image.jpegData(compressionQuality: 0.8) {
  153. // let photoInfo: [String: Any] = [
  154. // "data": FlutterStandardTypedData(bytes: imageData),
  155. // "id": asset.localIdentifier,
  156. // "width": image.size.width,
  157. // "height": image.size.height,
  158. // "creationDate": asset.creationDate?.timeIntervalSince1970 ?? 0
  159. // ]
  160. // groupData.append(photoInfo)
  161. // }
  162. // semaphore.signal()
  163. // }
  164. // semaphore.wait()
  165. // }
  166. //
  167. // if !groupData.isEmpty {
  168. // resultData.append(["group": groupData])
  169. // }
  170. // }
  171. //
  172. // // 在主线程返回结果
  173. // DispatchQueue.main.async {
  174. // flutterResult(resultData)
  175. // }
  176. // }
  177. // )
  178. // }
  179. }
  180. // 辅助方法:处理照片组
  181. private func processPhotoGroup(assets: [PHAsset], groupName: String, completion: @escaping ([[String: Any]]) -> Void) {
  182. let photoProcessGroup = DispatchGroup()
  183. var groupData: [[String: Any]] = []
  184. for asset in assets {
  185. photoProcessGroup.enter()
  186. let options = PHContentEditingInputRequestOptions()
  187. options.isNetworkAccessAllowed = true
  188. asset.requestContentEditingInput(with: options) { (input, info) in
  189. defer { photoProcessGroup.leave() }
  190. if let input = input, let url = input.fullSizeImageURL {
  191. let photoInfo: [String: Any] = [
  192. "path": url.path,
  193. "id": asset.localIdentifier,
  194. "width": asset.pixelWidth,
  195. "height": asset.pixelHeight,
  196. "creationDate": asset.creationDate?.timeIntervalSince1970 ?? 0
  197. ]
  198. groupData.append(photoInfo)
  199. }
  200. }
  201. }
  202. photoProcessGroup.notify(queue: .main) {
  203. completion(groupData)
  204. }
  205. }
  206. }