ClassifyPhotoPlugin.swift 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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", sizeInfo: result.screenshotsSize) { 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", sizeInfo: result.similarPhotosSize) { 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, sizeInfo: result.locationsSize) { 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, sizeInfo: result.peopleSize) { 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(
  182. assets: [PHAsset],
  183. groupName: String,
  184. sizeInfo: ClassifyPhoto.PhotoSizeInfo,
  185. completion: @escaping ([String: Any]) -> Void
  186. ) {
  187. let photoProcessGroup = DispatchGroup()
  188. var photosData: [[String: Any]] = []
  189. for asset in assets {
  190. photoProcessGroup.enter()
  191. let options = PHContentEditingInputRequestOptions()
  192. options.isNetworkAccessAllowed = true
  193. asset.requestContentEditingInput(with: options) { (input, info) in
  194. defer { photoProcessGroup.leave() }
  195. if let input = input, let url = input.fullSizeImageURL {
  196. let photoInfo: [String: Any] = [
  197. "path": url.path,
  198. "id": asset.localIdentifier,
  199. "width": asset.pixelWidth,
  200. "height": asset.pixelHeight,
  201. "creationDate": asset.creationDate?.timeIntervalSince1970 ?? 0
  202. ]
  203. photosData.append(photoInfo)
  204. }
  205. }
  206. }
  207. photoProcessGroup.notify(queue: .main) {
  208. completion([
  209. "photos": photosData,
  210. "totalSize": sizeInfo.totalSize,
  211. "count": sizeInfo.count
  212. ])
  213. }
  214. }
  215. }