Rework image compression / upload

This commit is contained in:
Thomas Ricouard 2023-03-11 13:38:08 +01:00
parent 0d2454886e
commit f172d6d4a6
4 changed files with 105 additions and 14 deletions

View file

@ -0,0 +1,50 @@
{
"pins" : [
{
"identity" : "emojitext",
"kind" : "remoteSourceControl",
"location" : "https://github.com/divadretlaw/EmojiText",
"state" : {
"revision" : "b5b0a30933a6dcb6601ad3625690a823fa3f6965",
"version" : "2.6.0"
}
},
{
"identity" : "keychain-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/evgenyneu/keychain-swift",
"state" : {
"branch" : "master",
"revision" : "c1fde55798b164cad44b5e23cfa2f0f1ebcd76af"
}
},
{
"identity" : "nuke",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Nuke",
"state" : {
"revision" : "6241e100294a2aa70d1811641585ab7da780bd0f",
"version" : "12.0.0"
}
},
{
"identity" : "swiftsoup",
"kind" : "remoteSourceControl",
"location" : "https://github.com/scinfu/SwiftSoup.git",
"state" : {
"revision" : "f707b8680cddb96dc1855632340a572ef37bbb98",
"version" : "2.5.3"
}
},
{
"identity" : "swiftui-shimmer",
"kind" : "remoteSourceControl",
"location" : "https://github.com/markiv/SwiftUI-Shimmer",
"state" : {
"revision" : "965a7cbcbf094cbcf22b9251a2323bdc3432e171",
"version" : "1.1.0"
}
}
],
"version" : 2
}

View file

@ -7,7 +7,49 @@ actor StatusEditorCompressor {
case noData case noData
} }
func compressImage(_ image: UIImage) async throws -> Data { func compressImageFrom(url: URL) async -> Data? {
return await withCheckedContinuation{ continuation in
let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else {
continuation.resume(returning: nil)
return
}
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: 4096,
] as CFDictionary
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else {
continuation.resume(returning: nil)
return
}
let data = NSMutableData()
guard let imageDestination = CGImageDestinationCreateWithData(data, UTType.jpeg.identifier as CFString, 1, nil) else {
continuation.resume(returning: nil)
return
}
let isPNG: Bool = {
guard let utType = cgImage.utType else { return false }
return (utType as String) == UTType.png.identifier
}()
let destinationProperties = [
kCGImageDestinationLossyCompressionQuality: isPNG ? 1.0 : 0.75
] as CFDictionary
CGImageDestinationAddImage(imageDestination, cgImage, destinationProperties)
CGImageDestinationFinalize(imageDestination)
continuation.resume(returning: data as Data)
}
}
func compressImageForUpload(_ image: UIImage) async throws -> Data {
var image = image var image = image
if image.size.height > 5000 || image.size.width > 5000 { if image.size.height > 5000 || image.size.width > 5000 {
image = image.resized(to: .init(width: image.size.width / 4, image = image.resized(to: .init(width: image.size.width / 4,

View file

@ -61,12 +61,14 @@ enum StatusEditorUTTypeSupported: String, CaseIterable {
func loadItemContent(item: NSItemProvider) async throws -> Any? { func loadItemContent(item: NSItemProvider) async throws -> Any? {
// Many warnings here about non-sendable type `[AnyHashable: Any]?` crossing // Many warnings here about non-sendable type `[AnyHashable: Any]?` crossing
// actor boundaries. Many Radars have been filed. // actor boundaries. Many Radars have been filed.
let result = try await item.loadItem(forTypeIdentifier: rawValue)
if isVideo, let transferable = await getVideoTransferable(item: item) { if isVideo, let transferable = await getVideoTransferable(item: item) {
return transferable return transferable
} else if isGif, let transferable = await getGifTransferable(item: item) { } else if isGif, let transferable = await getGifTransferable(item: item) {
return transferable return transferable
} else if let transferable = await getImageTansferable(item: item) {
return transferable
} }
let result = try await item.loadItem(forTypeIdentifier: rawValue)
if self == .jpeg || self == .png || self == .tiff || self == .image || self == .uiimage || self == .adobeRawImage { if self == .jpeg || self == .png || self == .tiff || self == .image || self == .uiimage || self == .adobeRawImage {
if let image = result as? UIImage { if let image = result as? UIImage {
return image return image
@ -79,8 +81,6 @@ enum StatusEditorUTTypeSupported: String, CaseIterable {
let image = UIImage(data: data) let image = UIImage(data: data)
{ {
return image return image
} else if let transferable = await getImageTansferable(item: item) {
return transferable
} }
} }
if let url = result as? URL { if let url = result as? URL {
@ -149,9 +149,6 @@ struct MovieFileTranseferable: Transferable {
struct ImageFileTranseferable: Transferable { struct ImageFileTranseferable: Transferable {
let url: URL let url: URL
lazy var data: Data? = try? Data(contentsOf: url)
lazy var image: UIImage? = UIImage(data: data ?? Data())
static var transferRepresentation: some TransferRepresentation { static var transferRepresentation: some TransferRepresentation {
FileRepresentation(contentType: .image) { image in FileRepresentation(contentType: .image) { image in
SentTransferredFile(image.url) SentTransferredFile(image.url)

View file

@ -379,6 +379,7 @@ public class StatusEditorViewModel: NSObject, ObservableObject {
let handledItemType = StatusEditorUTTypeSupported(rawValue: identifier) let handledItemType = StatusEditorUTTypeSupported(rawValue: identifier)
{ {
do { do {
let compressor = StatusEditorCompressor()
let content = try await handledItemType.loadItemContent(item: item) let content = try await handledItemType.loadItemContent(item: item)
if let text = content as? String { if let text = content as? String {
initialText += "\(text) " initialText += "\(text) "
@ -388,9 +389,9 @@ public class StatusEditorViewModel: NSObject, ObservableObject {
gifTransferable: nil, gifTransferable: nil,
mediaAttachment: nil, mediaAttachment: nil,
error: nil)) error: nil))
} else if var content = content as? ImageFileTranseferable, } else if let content = content as? ImageFileTranseferable,
let image = content.image let compressedData = await compressor.compressImageFrom(url: content.url),
{ let image = UIImage(data: compressedData) {
mediasImages.append(.init(image: image, mediasImages.append(.init(image: image,
movieTransferable: nil, movieTransferable: nil,
gifTransferable: nil, gifTransferable: nil,
@ -532,7 +533,6 @@ public class StatusEditorViewModel: NSObject, ObservableObject {
Task { Task {
var medias: [StatusEditorMediaContainer] = [] var medias: [StatusEditorMediaContainer] = []
for media in selectedMedias { for media in selectedMedias {
print(media.supportedContentTypes)
var file: (any Transferable)? var file: (any Transferable)?
if file == nil { if file == nil {
@ -545,8 +545,10 @@ public class StatusEditorViewModel: NSObject, ObservableObject {
file = try? await media.loadTransferable(type: ImageFileTranseferable.self) file = try? await media.loadTransferable(type: ImageFileTranseferable.self)
} }
if var imageFile = file as? ImageFileTranseferable, let compressor = StatusEditorCompressor()
let image = imageFile.image if let imageFile = file as? ImageFileTranseferable,
let compressedData = await compressor.compressImageFrom(url: imageFile.url),
let image = UIImage(data: compressedData)
{ {
medias.append(.init(image: image, medias.append(.init(image: image,
movieTransferable: nil, movieTransferable: nil,
@ -602,7 +604,7 @@ public class StatusEditorViewModel: NSObject, ObservableObject {
if let index = indexOf(container: newContainer) { if let index = indexOf(container: newContainer) {
let compressor = StatusEditorCompressor() let compressor = StatusEditorCompressor()
if let image = originalContainer.image { if let image = originalContainer.image {
let imageData = try await compressor.compressImage(image) let imageData = try await compressor.compressImageForUpload(image)
let uploadedMedia = try await uploadMedia(data: imageData, mimeType: "image/jpeg") let uploadedMedia = try await uploadMedia(data: imageData, mimeType: "image/jpeg")
mediasImages[index] = .init(image: mode.isInShareExtension ? originalContainer.image : nil, mediasImages[index] = .init(image: mode.isInShareExtension ? originalContainer.image : nil,
movieTransferable: nil, movieTransferable: nil,