mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-26 23:55:53 +00:00
183 lines
6.0 KiB
Swift
183 lines
6.0 KiB
Swift
//
|
|
// ImagePicker.swift
|
|
// SimpleX
|
|
//
|
|
// Created by Evgeny on 23/03/2022.
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import PhotosUI
|
|
|
|
func dropPrefix(_ s: String, _ prefix: String) -> String {
|
|
s.hasPrefix(prefix) ? String(s.dropFirst(prefix.count)) : s
|
|
}
|
|
|
|
func dropImagePrefix(_ s: String) -> String {
|
|
dropPrefix(dropPrefix(s, "data:image/png;base64,"), "data:image/jpg;base64,")
|
|
}
|
|
|
|
private func resizeImage(_ image: UIImage, newBounds: CGRect, drawIn: CGRect) -> UIImage {
|
|
let format = UIGraphicsImageRendererFormat()
|
|
format.scale = 1.0
|
|
format.opaque = true
|
|
return UIGraphicsImageRenderer(bounds: newBounds, format: format).image { _ in
|
|
image.draw(in: drawIn)
|
|
}
|
|
}
|
|
|
|
func cropToSquare(_ image: UIImage) -> UIImage {
|
|
let size = image.size
|
|
let side = min(size.width, size.height)
|
|
let newSize = CGSize(width: side, height: side)
|
|
var origin = CGPoint.zero
|
|
if size.width > side {
|
|
origin.x -= (size.width - side) / 2
|
|
} else if size.height > side {
|
|
origin.y -= (size.height - side) / 2
|
|
}
|
|
return resizeImage(image, newBounds: CGRect(origin: .zero, size: newSize), drawIn: CGRect(origin: origin, size: size))
|
|
}
|
|
|
|
|
|
func reduceSize(_ image: UIImage, ratio: CGFloat) -> UIImage {
|
|
let newSize = CGSize(width: floor(image.size.width / ratio), height: floor(image.size.height / ratio))
|
|
let bounds = CGRect(origin: .zero, size: newSize)
|
|
return resizeImage(image, newBounds: bounds, drawIn: bounds)
|
|
}
|
|
|
|
func resizeImageToStrSize(_ image: UIImage, maxDataSize: Int) -> String? {
|
|
var img = image
|
|
var str = compressImageStr(img)
|
|
var dataSize = str?.count ?? 0
|
|
while dataSize != 0 && dataSize > maxDataSize {
|
|
let ratio = sqrt(Double(dataSize) / Double(maxDataSize))
|
|
let clippedRatio = min(ratio, 2.0)
|
|
img = reduceSize(img, ratio: clippedRatio)
|
|
str = compressImageStr(img)
|
|
dataSize = str?.count ?? 0
|
|
}
|
|
logger.debug("resizeImageToStrSize final \(dataSize)")
|
|
return str
|
|
}
|
|
|
|
func compressImageStr(_ image: UIImage, _ compressionQuality: CGFloat = 0.85) -> String? {
|
|
if let data = image.jpegData(compressionQuality: compressionQuality) {
|
|
return "data:image/jpg;base64,\(data.base64EncodedString())"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resizeImageToDataSize(_ image: UIImage, maxDataSize: Int) -> Data? {
|
|
var img = image
|
|
var data = img.jpegData(compressionQuality: 0.85)
|
|
var dataSize = data?.count ?? 0
|
|
while dataSize != 0 && dataSize > maxDataSize {
|
|
let ratio = sqrt(Double(dataSize) / Double(maxDataSize))
|
|
let clippedRatio = min(ratio, 2.0)
|
|
img = reduceSize(img, ratio: clippedRatio)
|
|
data = img.jpegData(compressionQuality: 0.85)
|
|
dataSize = data?.count ?? 0
|
|
}
|
|
logger.debug("resizeImageToDataSize final \(dataSize)")
|
|
return data
|
|
}
|
|
|
|
enum ImageSource {
|
|
case imageLibrary
|
|
case camera
|
|
}
|
|
|
|
struct LibraryImagePicker: UIViewControllerRepresentable {
|
|
typealias UIViewControllerType = PHPickerViewController
|
|
@Binding var image: UIImage?
|
|
var didFinishPicking: (_ didSelectItems: Bool) -> Void
|
|
|
|
class Coordinator: PHPickerViewControllerDelegate {
|
|
let parent: LibraryImagePicker
|
|
|
|
init(_ parent: LibraryImagePicker) {
|
|
self.parent = parent
|
|
}
|
|
|
|
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
|
parent.didFinishPicking(!results.isEmpty)
|
|
guard !results.isEmpty else {
|
|
return
|
|
}
|
|
|
|
if let chosenImageProvider = results.first?.itemProvider {
|
|
if chosenImageProvider.canLoadObject(ofClass: UIImage.self) {
|
|
chosenImageProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
|
|
DispatchQueue.main.async {
|
|
self?.loadImage(object: image, error: error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func loadImage(object: Any?, error: Error? = nil) {
|
|
if let error = error {
|
|
logger.error("Couldn't load image with error: \(error.localizedDescription)")
|
|
}
|
|
parent.image = object as? UIImage
|
|
}
|
|
}
|
|
|
|
func makeCoordinator() -> Coordinator {
|
|
Coordinator(self)
|
|
}
|
|
|
|
func makeUIViewController(context: Context) -> PHPickerViewController {
|
|
var config = PHPickerConfiguration()
|
|
config.filter = .images
|
|
config.selectionLimit = 1
|
|
let controller = PHPickerViewController(configuration: config)
|
|
controller.delegate = context.coordinator
|
|
return controller
|
|
}
|
|
|
|
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
|
|
|
|
}
|
|
}
|
|
|
|
|
|
struct CameraImagePicker: UIViewControllerRepresentable {
|
|
@Environment(\.presentationMode) var presentationMode
|
|
@Binding var image: UIImage?
|
|
|
|
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
|
|
let parent: CameraImagePicker
|
|
|
|
init(_ parent: CameraImagePicker) {
|
|
self.parent = parent
|
|
}
|
|
|
|
func imagePickerController(_ picker: UIImagePickerController,
|
|
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
|
|
if let uiImage = info[.originalImage] as? UIImage {
|
|
parent.image = uiImage
|
|
}
|
|
parent.presentationMode.wrappedValue.dismiss()
|
|
}
|
|
}
|
|
|
|
func makeCoordinator() -> Coordinator {
|
|
Coordinator(self)
|
|
}
|
|
|
|
func makeUIViewController(context: UIViewControllerRepresentableContext<CameraImagePicker>) -> UIImagePickerController {
|
|
let picker = UIImagePickerController()
|
|
picker.sourceType = .camera
|
|
picker.allowsEditing = false
|
|
picker.delegate = context.coordinator
|
|
return picker
|
|
}
|
|
|
|
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<CameraImagePicker>) {
|
|
|
|
}
|
|
}
|