element-x-ios/ElementX/Sources/Other/ImageAnonymizer.swift

124 lines
4.4 KiB
Swift

//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import UIKit
import Vision
enum ImageAnonymizerError: Error {
case noCgImageBased
}
enum ImageAnonymizer {
private static var allowedTextItems: [String] = [
"#",
"@",
"%",
"&",
"+",
"-",
"_",
"\"",
"?",
"*"
]
static func anonymizedImage(from image: UIImage,
confidenceLevel: Float = 0.5,
fillColor: UIColor = .red) async throws -> UIImage {
guard let cgImage = image.cgImage else {
throw ImageAnonymizerError.noCgImageBased
}
// create a handler with cgImage
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
var observations: [VNDetectedObjectObservation] = []
// create a text request
let textRequest = VNRecognizeTextRequest { request, error in
guard let results = request.results as? [VNRecognizedTextObservation],
error == nil else {
return
}
observations.append(contentsOf: results)
}
textRequest.recognitionLevel = .accurate
textRequest.revision = VNRecognizeTextRequestRevision2
// create a face request
let faceRequest = VNDetectFaceRectanglesRequest { request, error in
guard let results = request.results as? [VNFaceObservation],
error == nil else {
return
}
observations.append(contentsOf: results)
}
// revision3 doesn't work!
faceRequest.revision = VNDetectFaceRectanglesRequestRevision2
#if targetEnvironment(simulator)
// Avoid `Could not create inference context` errors on Apple Silicon
// https://www.caseyliss.com/2022/6/20/feedback-is-broken-stop-trying-to-make-radar-happen
faceRequest.usesCPUOnly = true
#endif
// perform requests
try handler.perform([
textRequest,
faceRequest
])
return render(image: image,
confidenceLevel: confidenceLevel,
fillColor: fillColor,
observations: observations)
}
private static func render(image: UIImage,
confidenceLevel: Float,
fillColor: UIColor,
observations: [VNDetectedObjectObservation]) -> UIImage {
let size = image.size
let result = UIGraphicsImageRenderer(size: size).image { rendererContext in
// first draw self
image.draw(in: CGRect(origin: .zero, size: size))
// set fill color
fillColor.setFill()
for observation in observations {
guard observation.confidence >= confidenceLevel else {
// ensure observation's confidence level
continue
}
if let textObservation = observation as? VNRecognizedTextObservation,
let text = textObservation.topCandidates(1).first?.string {
if Double(text) != nil || Self.allowedTextItems.contains(text) {
continue
}
}
let box = observation.boundingBox
// boc is normalized (and in starts from the lower left corner)
// convert it to a rect in the image
let rect = CGRect(x: box.minX * size.width,
y: size.height - box.maxY * size.height,
width: box.width * size.width,
height: box.height * size.height)
rendererContext.fill(rect)
}
}
return result
}
}