Commit 32a0013f authored by shenyong's avatar shenyong

权限页面

parent 59bbb91d
......@@ -106,6 +106,12 @@ class CompressController : BaseViewController {
/// 获取当前页面数据
func getViewData(){
if PhotoAndVideoMananger.mananger.permissionStatus == .denied{
loadPermissView(CGRect(x: 0, y: 200, width: ScreenW, height: 450))
return
}
self.resourceData.removeAll()
let datas = Singleton.shared.resourceModel
if datas.count > 0 {
......
......@@ -137,7 +137,7 @@ class HomeInfoViewController:BaseViewController {
// 没有订阅
let view : AdvTipDeleteView = AdvTipDeleteView(frame: self.view.bounds)
view.dataSource = array as! [AssetModel]
view.dataSource = array
// 获取当前免费次数
let freeCount = AdvManager.shared.defaultFreeTimes
......@@ -152,7 +152,7 @@ class HomeInfoViewController:BaseViewController {
}else {
// 获取次数对应的删除照片数量
var freeDeleteCount = AdvManager.shared.advDeleteResouceDic[freeCount]!
let tempArray = array as! [AssetModel]
let tempArray = array
if freeCount > 1 {
// 如果是前两次,可以免费删除5张照片
if tempArray.count > freeDeleteCount {
......@@ -166,7 +166,7 @@ class HomeInfoViewController:BaseViewController {
}else {
// 如果小于直接删除
HomePayViewController.show {
deleteOp(imgs: array as! [AssetModel],isAfterAdv: false)
deleteOp(imgs: array,isAfterAdv: false)
}
}
......@@ -247,6 +247,7 @@ class HomeInfoViewController:BaseViewController {
label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
label.textAlignment = .center
label.isHidden = true
return label
}()
......@@ -367,7 +368,11 @@ class HomeInfoViewController:BaseViewController {
//设置空白页
func setDefaultPage(){
DispatchQueue.main.async {
if PhotoAndVideoMananger.mananger.permissionStatus == .denied{
self.loadPermissView()
}else{
if self.ids?.count == 0 {
if self.type == .SimilarVideos{
self.defaultImageView.image = UIImage(named: "img_vedio_defpage")
......@@ -384,6 +389,7 @@ class HomeInfoViewController:BaseViewController {
}
}
}
}
@objc func seletedAllBtnClick() {
......@@ -400,3 +406,14 @@ class HomeInfoViewController:BaseViewController {
}
}
extension UIViewController{
func loadPermissView(_ frame:CGRect = CGRect(x: 0, y: 100+kSafeAreaInsets.top, width: ScreenW, height: 450)){
let permissionView = Bundle.main.loadNibNamed("PMPermissionView", owner: nil)?.last as! PMPermissionView
permissionView.frame = frame
self.view.addSubview(permissionView)
}
}
......@@ -185,6 +185,9 @@ class HomePhotosDetailViewController : BaseViewController {
//设置空白页
func setDefaultPage(){
DispatchQueue.main.async {
if PhotoAndVideoMananger.mananger.permissionStatus == .denied{
self.loadPermissView()
}else{
if self.resourceData.count == 0 {
self.defaultImageView.isHidden = false
self.defaultTipLabel.isHidden = false
......@@ -199,6 +202,8 @@ class HomePhotosDetailViewController : BaseViewController {
self.deleteButton.isHidden = false
}
}
}
}
override func viewDidLoad() {
......
......@@ -518,6 +518,9 @@ extension HomeVideoDetailController:WaterfallMutiSectionDelegate,UICollectionVie
func setDefaultPage(){
DispatchQueue.main.async {
if PhotoAndVideoMananger.mananger.permissionStatus == .denied{
self.loadPermissView()
}else{
if self.resourceData.count == 0 {
self.defaultImageView.isHidden = false
self.defaultTipLabel.isHidden = false
......@@ -533,6 +536,7 @@ extension HomeVideoDetailController:WaterfallMutiSectionDelegate,UICollectionVie
}
}
}
}
func updateCurrentPageWhenDeleteAny(){
......
......@@ -281,6 +281,7 @@ class HomeViewController:BaseViewController {
Singleton.shared.startCountdown {}
if !isShowCharge {
NotificationManager().configNotifications()
return
}
......
......@@ -19,7 +19,7 @@ class HomeInfoView :UIView{
var callBack:callBack<Any> = {text in}
var deleteCallBack:callBack<Any> = {array in }
var deleteCallBack:callBack<[AssetModel]> = {array in }
lazy var tableView:UITableView = {
......
......@@ -25,5 +25,4 @@ class PMPermissionView: UIView {
}
}
}
}
......@@ -11,28 +11,29 @@
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="EwM-B0-fXo" customClass="PMPermissionView" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="529" height="489"/>
<rect key="frame" x="0.0" y="0.0" width="543" height="449"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="icon_queshengtu" translatesAutoresizingMaskIntoConstraints="NO" id="qhV-IF-vsx">
<rect key="frame" x="170" y="100" width="189" height="189"/>
<rect key="frame" x="177" y="100" width="189" height="189"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Access permission is required to start scan" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ci8-h6-fiE">
<rect key="frame" x="22" y="297" width="485" height="20"/>
<rect key="frame" x="22" y="297" width="499" height="20"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="16"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e8v-Xy-wvP">
<rect key="frame" x="24" y="320" width="480" height="38.333333333333314"/>
<string key="text">Phone Manager We need access to all the photos
in your photo library</string>
<fontDescription key="fontDescription" type="boldSystem" pointSize="16"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Phone Manager We need access to all the photos in your photo library" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e8v-Xy-wvP">
<rect key="frame" x="109" y="320" width="325" height="38.333333333333314"/>
<constraints>
<constraint firstAttribute="width" constant="325" id="7Q3-fW-f7G"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fRi-wi-J8I">
<rect key="frame" x="170" y="371.33333333333331" width="189" height="46"/>
<rect key="frame" x="177" y="371.33333333333331" width="189" height="46"/>
<color key="backgroundColor" red="0.0" green="0.50980392159999999" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="46" id="2ml-bk-VZ7"/>
......@@ -57,16 +58,15 @@ in your photo library</string>
<constraint firstItem="fRi-wi-J8I" firstAttribute="centerX" secondItem="qhV-IF-vsx" secondAttribute="centerX" id="06a-VZ-yHW"/>
<constraint firstItem="ci8-h6-fiE" firstAttribute="leading" secondItem="Z86-W8-437" secondAttribute="leading" constant="22" id="2pS-Hl-sgb"/>
<constraint firstItem="ci8-h6-fiE" firstAttribute="centerX" secondItem="qhV-IF-vsx" secondAttribute="centerX" id="Fv3-iO-5BI"/>
<constraint firstItem="e8v-Xy-wvP" firstAttribute="leading" secondItem="Z86-W8-437" secondAttribute="leading" constant="24" id="Ygj-Ix-aYR"/>
<constraint firstItem="e8v-Xy-wvP" firstAttribute="centerX" secondItem="ci8-h6-fiE" secondAttribute="centerX" id="OZD-Bq-U2s"/>
<constraint firstItem="qhV-IF-vsx" firstAttribute="top" secondItem="EwM-B0-fXo" secondAttribute="top" constant="100" id="fx5-wC-f0q"/>
<constraint firstItem="fRi-wi-J8I" firstAttribute="top" secondItem="e8v-Xy-wvP" secondAttribute="bottom" constant="13" id="jys-rP-uUI"/>
<constraint firstItem="Z86-W8-437" firstAttribute="trailing" secondItem="e8v-Xy-wvP" secondAttribute="trailing" constant="25" id="p6c-uL-oEh"/>
<constraint firstItem="e8v-Xy-wvP" firstAttribute="top" secondItem="ci8-h6-fiE" secondAttribute="bottom" constant="3" id="tgi-kq-3Ap"/>
<constraint firstItem="ci8-h6-fiE" firstAttribute="top" secondItem="qhV-IF-vsx" secondAttribute="bottom" constant="8" id="uKW-kw-KCJ"/>
<constraint firstItem="Z86-W8-437" firstAttribute="trailing" secondItem="ci8-h6-fiE" secondAttribute="trailing" constant="22" id="wMs-cZ-eGL"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="284.73282442748092" y="-315.14084507042253"/>
<point key="canvasLocation" x="295.41984732824426" y="-329.22535211267609"/>
</view>
</objects>
<resources>
......
......@@ -58,7 +58,7 @@
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XPg-Ph-1k0">
<rect key="frame" x="15" y="876" width="410" height="46"/>
<rect key="frame" x="15" y="870" width="410" height="46"/>
<color key="backgroundColor" red="0.0" green="0.50980392156862742" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="46" id="DDB-jE-Azu"/>
......@@ -82,7 +82,7 @@
<constraint firstAttribute="trailing" secondItem="OOD-68-wMD" secondAttribute="trailing" constant="25" id="Vcx-gz-lAI"/>
<constraint firstItem="XMa-dY-JFG" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="40" id="a0g-kE-ugQ"/>
<constraint firstItem="LIQ-mA-CKi" firstAttribute="leading" secondItem="5sP-3G-R5V" secondAttribute="leading" id="cqt-w5-eff"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="XPg-Ph-1k0" secondAttribute="bottom" id="dX6-cv-Gto" customClass="ScreenHeightRatioConstraint" customModule="PhoneManager" customModuleProvider="target"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="XPg-Ph-1k0" secondAttribute="bottom" constant="6" id="dX6-cv-Gto" customClass="ScreenHeightRatioConstraint" customModule="PhoneManager" customModuleProvider="target"/>
<constraint firstItem="dRr-c6-YoB" firstAttribute="top" secondItem="5sP-3G-R5V" secondAttribute="bottom" constant="20" id="eD3-DG-qkC" customClass="ScreenHeightRatioConstraint" customModule="PhoneManager" customModuleProvider="target"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="5sP-3G-R5V" secondAttribute="trailing" constant="53" id="h2C-hf-maH"/>
<constraint firstItem="OOD-68-wMD" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="25" id="jJ3-Yj-u0w"/>
......
......@@ -29,7 +29,7 @@
</constraints>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ybT-3z-Wb2">
<rect key="frame" x="15" y="852" width="400" height="46"/>
<rect key="frame" x="15" y="846" width="400" height="46"/>
<color key="backgroundColor" red="0.0" green="0.50980392159999999" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="46" id="4Qc-vw-6p8"/>
......@@ -62,7 +62,7 @@
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="VkR-UI-FBK" secondAttribute="trailing" constant="25" id="4JU-rc-hns"/>
<constraint firstItem="ybT-3z-Wb2" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="15" id="IIW-NA-SQU"/>
<constraint firstItem="QGK-yy-jOF" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="Kee-LL-a9m"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="ybT-3z-Wb2" secondAttribute="bottom" id="NWj-ld-9UY"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="ybT-3z-Wb2" secondAttribute="bottom" constant="6" id="NWj-ld-9UY"/>
<constraint firstItem="VkR-UI-FBK" firstAttribute="top" secondItem="h8I-lL-ELV" secondAttribute="bottom" constant="18" id="aYz-ey-Yc7"/>
<constraint firstItem="QGK-yy-jOF" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" constant="46" id="cHD-Cc-XiT"/>
<constraint firstItem="h8I-lL-ELV" firstAttribute="top" secondItem="QGK-yy-jOF" secondAttribute="bottom" constant="55" id="dDM-Pu-5g1"/>
......
......@@ -40,7 +40,7 @@
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="KHL-A7-P8d">
<rect key="frame" x="15" y="772" width="363" height="46"/>
<rect key="frame" x="15" y="766" width="363" height="46"/>
<color key="backgroundColor" red="0.0" green="0.50980392159999999" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="46" id="bCa-jh-IT1"/>
......@@ -61,7 +61,7 @@
<constraint firstAttribute="trailing" secondItem="KHL-A7-P8d" secondAttribute="trailing" constant="15" id="MaN-MT-FnY"/>
<constraint firstItem="dcU-ld-SUp" firstAttribute="top" secondItem="OpM-5k-AoO" secondAttribute="bottom" constant="80" id="Okf-QC-Jrn" customClass="ScreenHeightRatioConstraint" customModule="PhoneManager" customModuleProvider="target"/>
<constraint firstItem="KHL-A7-P8d" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="15" id="Rl4-Mh-CiF"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="KHL-A7-P8d" secondAttribute="bottom" id="SCz-tZ-pgH"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="KHL-A7-P8d" secondAttribute="bottom" constant="6" id="SCz-tZ-pgH"/>
<constraint firstItem="dcU-ld-SUp" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="USR-RC-nO5"/>
<constraint firstItem="LeZ-aM-wDE" firstAttribute="top" secondItem="dcU-ld-SUp" secondAttribute="bottom" constant="18" id="Zga-Sv-DYE" customClass="ScreenHeightRatioConstraint" customModule="PhoneManager" customModuleProvider="target"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="LeZ-aM-wDE" secondAttribute="trailing" constant="25" id="lAh-or-e83"/>
......
......@@ -10,18 +10,18 @@ class NotificationManager {
func configNotifications(){
if let secondlaunch = UserDefaults.standard.value(forKey: "notifications_is_secondlaunch") as? Bool{
if secondlaunch{
Print("二次启动,获取通知权限")
// if let secondlaunch = UserDefaults.standard.value(forKey: "notifications_is_secondlaunch") as? Bool{
// if secondlaunch{
// Print("二次启动,获取通知权限")
// scheduleLocalNotifications()
// }
// }else{
// UserDefaults.standard.setValue(true, forKey: "notifications_is_secondlaunch")
// UserDefaults.standard.synchronize()
// Print("启动第一次,记录下")
// }
scheduleLocalNotifications()
}
}else{
UserDefaults.standard.setValue(true, forKey: "notifications_is_secondlaunch")
UserDefaults.standard.synchronize()
Print("启动第一次,记录下")
}
}
// 调度本地通知
func scheduleLocalNotifications() {
......
......@@ -55,6 +55,8 @@ class PhotoAndVideoMananger {
var ids:[String] = []
var permissionStatus:PHAuthorizationStatus = .notDetermined
// 定义
private let hashDistance = 100
......@@ -91,10 +93,12 @@ class PhotoAndVideoMananger {
class func getPrivacy(suc:@escaping callBack<Any> = {text in}) {
PHPhotoLibrary.requestAuthorization { status in
PhotoAndVideoMananger.mananger.permissionStatus = status
switch status {
case .authorized:
// 用户授权访问照片库,可以继续操作
Print("用户授权访问照片库,可以继续操作")
suc(PrivacyType.authorized)
case .denied:
Print("用户拒绝访问或者权限受限")
......
//
// PhotoSimilarOptimizer.swift
// PhotoSimilar
//
// Created by edy on 2024/1/24.
//
import Foundation
import Photos
import CoreImage
import UIKit
class PhotoSimilarOptimizer {
static let shared = PhotoSimilarOptimizer()
private init() {}
// MARK: - 配置参数
private let timeWindowInSeconds: TimeInterval = 600 // 10分钟时间窗口
private let fileSizeThreshold: Double = 0.3 // 文件大小相差阈值(30%)
private let resolutionThreshold: Double = 0.2 // 分辨率相差阈值(20%)
private let hashDistanceThreshold: Int = 20 // pHash汉明距离阈值
// MARK: - 缓存
private var hashCache: [String: String] = [:] // 图片hash缓存
private let cacheQueue = DispatchQueue(label: "com.photoSimilar.cache")
// MARK: - 主要处理流程
func findSimilarAssets(in assets: [PHAsset],
progressHandler: (([PHAsset]) -> Void)?,
completionHandler: (([[PHAsset]]) -> Void)?) {
// 1. 按时间窗口预分组
let timeGroups = groupAssetsByTimeWindow(assets)
// 2. 创建并发处理队列
let processingQueue = OperationQueue()
processingQueue.maxConcurrentOperationCount = 4
let dispatchGroup = DispatchGroup()
var finalGroups: [[PHAsset]] = []
let resultQueue = DispatchQueue(label: "com.photoSimilar.result")
// 3. 处理每个时间窗口
for timeGroup in timeGroups {
dispatchGroup.enter()
processingQueue.addOperation {
// 3.1 按文件大小预分组
let sizeGroups = self.groupAssetsBySize(timeGroup)
// 3.2 在每个大小组内进行相似度比较
for sizeGroup in sizeGroups {
let similarGroups = self.findSimilarInGroup(sizeGroup)
// 3.3 合并结果
if !similarGroups.isEmpty {
resultQueue.sync {
for group in similarGroups {
finalGroups.append(group)
DispatchQueue.main.async {
progressHandler?(group)
}
}
}
}
}
dispatchGroup.leave()
}
}
// 4. 完成处理
dispatchGroup.notify(queue: .main) {
completionHandler?(finalGroups)
}
}
// MARK: - 辅助方法
// 按时间窗口分组
private func groupAssetsByTimeWindow(_ assets: [PHAsset]) -> [[PHAsset]] {
let sortedAssets = assets.sorted { ($0.creationDate ?? Date()) > ($1.creationDate ?? Date()) }
var timeGroups: [[PHAsset]] = []
var currentGroup: [PHAsset] = []
var lastTimestamp: Date?
for asset in sortedAssets {
let currentTime = asset.creationDate ?? Date()
if let lastTime = lastTimestamp {
let timeDiff = abs(currentTime.timeIntervalSince(lastTime))
if timeDiff > timeWindowInSeconds {
if !currentGroup.isEmpty {
timeGroups.append(currentGroup)
currentGroup = []
}
}
}
currentGroup.append(asset)
lastTimestamp = currentTime
}
if !currentGroup.isEmpty {
timeGroups.append(currentGroup)
}
return timeGroups
}
// 按文件大小分组
private func groupAssetsBySize(_ assets: [PHAsset]) -> [[PHAsset]] {
var sizeGroups: [[PHAsset]] = []
var processedAssets = Set<String>()
for asset in assets {
if processedAssets.contains(asset.localIdentifier) {
continue
}
var currentGroup = [asset]
processedAssets.insert(asset.localIdentifier)
// 查找大小相近的资产
for compareAsset in assets {
if processedAssets.contains(compareAsset.localIdentifier) {
continue
}
if isFileSizeSimilar(asset, compareAsset) {
currentGroup.append(compareAsset)
processedAssets.insert(compareAsset.localIdentifier)
}
}
if currentGroup.count > 1 {
sizeGroups.append(currentGroup)
}
}
return sizeGroups
}
// 文件大小比较
private func isFileSizeSimilar(_ asset1: PHAsset, _ asset2: PHAsset) -> Bool {
let size1 = Double(asset1.pixelWidth * asset1.pixelHeight)
let size2 = Double(asset2.pixelWidth * asset2.pixelHeight)
let ratio = abs(size1 - size2) / max(size1, size2)
return ratio <= fileSizeThreshold
}
// 在组内查找相似资产
private func findSimilarInGroup(_ assets: [PHAsset]) -> [[PHAsset]] {
var similarGroups: [[PHAsset]] = []
var processedAssets = Set<String>()
for asset in assets {
if processedAssets.contains(asset.localIdentifier) {
continue
}
var currentGroup = [asset]
processedAssets.insert(asset.localIdentifier)
for compareAsset in assets {
if processedAssets.contains(compareAsset.localIdentifier) {
continue
}
if areAssetsSimilar(asset, compareAsset) {
currentGroup.append(compareAsset)
processedAssets.insert(compareAsset.localIdentifier)
}
}
if currentGroup.count > 1 {
similarGroups.append(currentGroup)
}
}
return similarGroups
}
// 相似度比较
private func areAssetsSimilar(_ asset1: PHAsset, _ asset2: PHAsset) -> Bool {
// 1. 检查分辨率
if !isResolutionSimilar(asset1, asset2) {
return false
}
// 2. 计算并比较pHash
return compareAssetHashes(asset1, asset2)
}
// 分辨率比较
private func isResolutionSimilar(_ asset1: PHAsset, _ asset2: PHAsset) -> Bool {
let res1 = Double(asset1.pixelWidth * asset1.pixelHeight)
let res2 = Double(asset2.pixelWidth * asset2.pixelHeight)
let ratio = abs(res1 - res2) / max(res1, res2)
return ratio <= resolutionThreshold
}
// 比较图片哈希值
private func compareAssetHashes(_ asset1: PHAsset, _ asset2: PHAsset) -> Bool {
let options = PHImageRequestOptions()
options.isSynchronous = true
options.version = .original
let targetSize = CGSize(width: 32, height: 32)
var hash1: String?
var hash2: String?
// 从缓存获取或计算hash
let id1 = asset1.localIdentifier
let id2 = asset2.localIdentifier
cacheQueue.sync {
hash1 = hashCache[id1]
hash2 = hashCache[id2]
}
if hash1 == nil {
PHImageManager.default().requestImage(for: asset1, targetSize: targetSize, contentMode: .aspectFit, options: options) { image, _ in
if let image = image {
hash1 = self.calculateImageHash(image)
self.cacheQueue.sync {
self.hashCache[id1] = hash1
}
}
}
}
if hash2 == nil {
PHImageManager.default().requestImage(for: asset2, targetSize: targetSize, contentMode: .aspectFit, options: options) { image, _ in
if let image = image {
hash2 = self.calculateImageHash(image)
self.cacheQueue.sync {
self.hashCache[id2] = hash2
}
}
}
}
guard let h1 = hash1, let h2 = hash2 else { return false }
let distance = calculateHammingDistance(h1, h2)
return distance < hashDistanceThreshold
}
// 计算图片hash
private func calculateImageHash(_ image: UIImage) -> String {
guard let cgImage = image.cgImage else { return "" }
let ciImage = CIImage(cgImage: cgImage)
guard let filter = CIFilter(name: "CIPhotoEffectNoir"),
let outputImage = filter.outputImage else {
return ""
}
filter.setValue(ciImage, forKey: kCIInputImageKey)
let context = CIContext()
guard let scaledImage = context.createCGImage(outputImage, from: outputImage.extent),
let pixelData = UIImage(cgImage: scaledImage).cgImage?.dataProvider?.data,
let data = CFDataGetBytePtr(pixelData) else {
return ""
}
var pixels = Array(repeating: UInt8(0), count: 1024)
for i in 0..<32 {
for j in 0..<32 {
let pixelIndex = (i * 32 + j) * 4
let gray = UInt8(
0.299 * Double(data[pixelIndex]) +
0.587 * Double(data[pixelIndex + 1]) +
0.114 * Double(data[pixelIndex + 2])
)
pixels[i * 32 + j] = gray
}
}
let average = UInt8(pixels.reduce(0, { UInt32($0) + UInt32($1) }) / UInt32(pixels.count))
return pixels.map { $0 > average ? "1" : "0" }.joined()
}
// 计算汉明距离
private func calculateHammingDistance(_ hash1: String, _ hash2: String) -> Int {
guard hash1.count == hash2.count else { return Int.max }
return zip(hash1, hash2).filter { $0 != $1 }.count
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment