Detect text from picture
This commit is contained in:
parent
5cc2a5a3ec
commit
fd18fab063
|
@ -81,6 +81,7 @@
|
||||||
"attachment.edit.description" = "Describe for the visually impaired";
|
"attachment.edit.description" = "Describe for the visually impaired";
|
||||||
"attachment.edit.description.audio" = "Describe for people with hearing loss";
|
"attachment.edit.description.audio" = "Describe for people with hearing loss";
|
||||||
"attachment.edit.description.video" = "Describe for people with hearing loss or visual impairment";
|
"attachment.edit.description.video" = "Describe for people with hearing loss or visual impairment";
|
||||||
|
"attachment.edit.detect-text-from-picture" = "Detect text from picture";
|
||||||
"attachment.edit.title" = "Edit media";
|
"attachment.edit.title" = "Edit media";
|
||||||
"attachment.edit.thumbnail.prompt" = "Drag the circle on the preview to choose the focal point which will always be in view on all thumbnails";
|
"attachment.edit.thumbnail.prompt" = "Drag the circle on the preview to choose the focal point which will always be in view on all thumbnails";
|
||||||
"attachment.sensitive-content" = "Sensitive content";
|
"attachment.sensitive-content" = "Sensitive content";
|
||||||
|
|
|
@ -2,10 +2,15 @@
|
||||||
|
|
||||||
import AVKit
|
import AVKit
|
||||||
import Combine
|
import Combine
|
||||||
|
import SDWebImage
|
||||||
import UIKit
|
import UIKit
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
import Vision
|
||||||
|
|
||||||
final class EditAttachmentViewController: UIViewController {
|
final class EditAttachmentViewController: UIViewController {
|
||||||
|
private let textView = UITextView()
|
||||||
|
private let detectTextFromPictureButton = UIButton(type: .system)
|
||||||
|
private let detectTextFromPictureProgressView = UIProgressView()
|
||||||
private let viewModel: AttachmentViewModel
|
private let viewModel: AttachmentViewModel
|
||||||
private let parentViewModel: CompositionViewModel
|
private let parentViewModel: CompositionViewModel
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
@ -88,8 +93,6 @@ final class EditAttachmentViewController: UIViewController {
|
||||||
describeLabel.text = NSLocalizedString("attachment.edit.description", comment: "")
|
describeLabel.text = NSLocalizedString("attachment.edit.description", comment: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
let textView = UITextView()
|
|
||||||
|
|
||||||
stackView.addArrangedSubview(textView)
|
stackView.addArrangedSubview(textView)
|
||||||
textView.adjustsFontForContentSizeCategory = true
|
textView.adjustsFontForContentSizeCategory = true
|
||||||
textView.font = .preferredFont(forTextStyle: .body)
|
textView.font = .preferredFont(forTextStyle: .body)
|
||||||
|
@ -100,12 +103,31 @@ final class EditAttachmentViewController: UIViewController {
|
||||||
textView.text = viewModel.editingDescription
|
textView.text = viewModel.editingDescription
|
||||||
textView.accessibilityLabel = describeLabel.text
|
textView.accessibilityLabel = describeLabel.text
|
||||||
|
|
||||||
|
let lowerStackView = UIStackView()
|
||||||
|
|
||||||
|
stackView.addArrangedSubview(lowerStackView)
|
||||||
|
lowerStackView.spacing = .defaultSpacing
|
||||||
|
|
||||||
let remainingCharactersLabel = UILabel()
|
let remainingCharactersLabel = UILabel()
|
||||||
|
|
||||||
stackView.addArrangedSubview(remainingCharactersLabel)
|
lowerStackView.addArrangedSubview(remainingCharactersLabel)
|
||||||
remainingCharactersLabel.adjustsFontForContentSizeCategory = true
|
remainingCharactersLabel.adjustsFontForContentSizeCategory = true
|
||||||
remainingCharactersLabel.font = .preferredFont(forTextStyle: .subheadline)
|
remainingCharactersLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||||
|
|
||||||
|
lowerStackView.addArrangedSubview(detectTextFromPictureButton)
|
||||||
|
detectTextFromPictureButton.setTitle(
|
||||||
|
NSLocalizedString("attachment.edit.detect-text-from-picture", comment: ""),
|
||||||
|
for: .normal)
|
||||||
|
detectTextFromPictureButton.titleLabel?.adjustsFontSizeToFitWidth = true
|
||||||
|
detectTextFromPictureButton.titleLabel?.numberOfLines = 0
|
||||||
|
detectTextFromPictureButton.addAction(
|
||||||
|
UIAction { [weak self] _ in self?.detectTextFromPicture() },
|
||||||
|
for: .touchUpInside)
|
||||||
|
detectTextFromPictureButton.isHidden = viewModel.attachment.type != .image
|
||||||
|
|
||||||
|
stackView.addArrangedSubview(detectTextFromPictureProgressView)
|
||||||
|
detectTextFromPictureProgressView.isHidden = true
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
stackView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
|
stackView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
|
||||||
stackView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: .defaultSpacing),
|
stackView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: .defaultSpacing),
|
||||||
|
@ -157,3 +179,89 @@ extension EditAttachmentViewController: UITextViewDelegate {
|
||||||
viewModel.editingDescription = textView.text
|
viewModel.editingDescription = textView.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension EditAttachmentViewController {
|
||||||
|
enum TextDetectionOutput {
|
||||||
|
case progress(Double)
|
||||||
|
case result(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectTextFromPicture() {
|
||||||
|
SDWebImageManager.shared.loadImage(
|
||||||
|
with: viewModel.attachment.url,
|
||||||
|
options: [],
|
||||||
|
progress: nil) { image, _, _, _, _, _ in
|
||||||
|
guard let cgImage = image?.cgImage else { return }
|
||||||
|
|
||||||
|
self.detectText(cgImage: cgImage)
|
||||||
|
.sink { [weak self] in
|
||||||
|
if case let .failure(error) = $0 {
|
||||||
|
self?.present(alertItem: .init(error: error))
|
||||||
|
}
|
||||||
|
} receiveValue: { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
switch $0 {
|
||||||
|
case let .progress(progress):
|
||||||
|
self.detectTextFromPictureButton.isHidden = true
|
||||||
|
self.detectTextFromPictureProgressView.isHidden = false
|
||||||
|
self.detectTextFromPictureProgressView.progress = Float(progress)
|
||||||
|
case let .result(result):
|
||||||
|
self.detectTextFromPictureButton.isHidden = false
|
||||||
|
self.detectTextFromPictureProgressView.isHidden = true
|
||||||
|
self.textView.text += result
|
||||||
|
self.textViewDidChange(self.textView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &self.cancellables)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectText(cgImage: CGImage) -> AnyPublisher<TextDetectionOutput, Error> {
|
||||||
|
let subject = PassthroughSubject<TextDetectionOutput, Error>()
|
||||||
|
|
||||||
|
let recognizeTextRequest = VNRecognizeTextRequest { request, error in
|
||||||
|
if let error = error {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
subject.send(completion: .failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let recognizedTextObservations = request.results as? [VNRecognizedTextObservation] ?? []
|
||||||
|
let result = recognizedTextObservations
|
||||||
|
.compactMap { $0.topCandidates(1).first?.string }
|
||||||
|
.joined(separator: " ")
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
subject.send(.result(result))
|
||||||
|
subject.send(completion: .finished)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recognizeTextRequest.recognitionLevel = .accurate
|
||||||
|
recognizeTextRequest.usesLanguageCorrection = true
|
||||||
|
recognizeTextRequest.progressHandler = { _, progress, error in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let error = error {
|
||||||
|
subject.send(completion: .failure(error))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
subject.send(.progress(progress))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
|
||||||
|
|
||||||
|
do {
|
||||||
|
try handler.perform([recognizeTextRequest])
|
||||||
|
} catch {
|
||||||
|
subject.send(completion: .failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
return subject.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue