fix: no downscaling for raw image from camera issue

This commit is contained in:
CMK 2022-11-22 15:58:31 +08:00
parent d98306b505
commit f784df912d
8 changed files with 101 additions and 16 deletions

View File

@ -209,6 +209,7 @@ extension ComposeViewController {
api: viewModel.context.apiService, api: viewModel.context.apiService,
authContext: viewModel.authContext, authContext: viewModel.authContext,
input: .image(image), input: .image(image),
sizeLimit: composeContentViewModel.sizeLimit,
delegate: composeContentViewModel delegate: composeContentViewModel
) )
} }

View File

@ -16,3 +16,14 @@ extension UIImage {
} }
} }
extension UIImage {
public func normalized() -> UIImage? {
if imageOrientation == .up { return self }
UIGraphicsBeginImageContext(size)
draw(in: CGRect(origin: CGPoint.zero, size: size))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}

View File

@ -8,11 +8,12 @@
import os.log import os.log
import UIKit import UIKit
import AVKit import AVKit
import SessionExporter
import MastodonCore import MastodonCore
import SessionExporter
import Kingfisher
extension AttachmentViewModel { extension AttachmentViewModel {
func comporessVideo(url: URL) async throws -> URL { func compressVideo(url: URL) async throws -> URL {
let urlAsset = AVURLAsset(url: url) let urlAsset = AVURLAsset(url: url)
let exporter = NextLevelSessionExporter(withAsset: urlAsset) let exporter = NextLevelSessionExporter(withAsset: urlAsset)
exporter.outputFileType = .mp4 exporter.outputFileType = .mp4
@ -92,3 +93,57 @@ extension AttachmentViewModel {
} }
} // end func } // end func
} }
extension AttachmentViewModel {
@AttachmentViewModelActor
func compressImage(data: Data, sizeLimit: SizeLimit) throws -> Output {
let maxPayloadSizeInBytes = sizeLimit.image ?? 10 * 1024 * 1024
guard let image = KFCrossPlatformImage(data: data)?.kf.normalized,
var imageData = image.kf.pngRepresentation()
else {
throw AttachmentError.invalidAttachmentType
}
repeat {
guard let image = KFCrossPlatformImage(data: imageData) else {
throw AttachmentError.invalidAttachmentType
}
if imageData.kf.imageFormat == .PNG {
// A. png image
if imageData.count > maxPayloadSizeInBytes {
guard let compressedJpegData = image.jpegData(compressionQuality: 0.8) else {
throw AttachmentError.invalidAttachmentType
}
os_log("%{public}s[%{public}ld], %{public}s: compress png %.2fMiB -> jpeg %.2fMiB", ((#file as NSString).lastPathComponent), #line, #function, Double(imageData.count) / 1024 / 1024, Double(compressedJpegData.count) / 1024 / 1024)
imageData = compressedJpegData
} else {
os_log("%{public}s[%{public}ld], %{public}s: png %.2fMiB", ((#file as NSString).lastPathComponent), #line, #function, Double(imageData.count) / 1024 / 1024)
break
}
} else {
// B. other image
if imageData.count > maxPayloadSizeInBytes {
let targetSize = CGSize(width: image.size.width * 0.8, height: image.size.height * 0.8)
let scaledImage = image.kf.resize(to: targetSize)
guard let compressedJpegData = scaledImage.jpegData(compressionQuality: 0.8) else {
throw AttachmentError.invalidAttachmentType
}
os_log("%{public}s[%{public}ld], %{public}s: compress jpeg %.2fMiB -> jpeg %.2fMiB", ((#file as NSString).lastPathComponent), #line, #function, Double(imageData.count) / 1024 / 1024, Double(compressedJpegData.count) / 1024 / 1024)
imageData = compressedJpegData
} else {
os_log("%{public}s[%{public}ld], %{public}s: jpeg %.2fMiB", ((#file as NSString).lastPathComponent), #line, #function, Double(imageData.count) / 1024 / 1024)
break
}
}
} while (imageData.count > maxPayloadSizeInBytes)
return .image(imageData, imageKind: imageData.kf.imageFormat == .PNG ? .png : .jpg)
}
}
@globalActor actor AttachmentViewModelActor {
static var shared = AttachmentViewModelActor()
}

View File

@ -16,7 +16,7 @@ extension AttachmentViewModel {
func load(input: Input) async throws -> Output { func load(input: Input) async throws -> Output {
switch input { switch input {
case .image(let image): case .image(let image):
guard let data = image.pngData() else { guard let data = image.normalized()?.pngData() else {
throw AttachmentError.invalidAttachmentType throw AttachmentError.invalidAttachmentType
} }
return .image(data, imageKind: .png) return .image(data, imageKind: .png)

View File

@ -48,8 +48,8 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
public let api: APIService public let api: APIService
public let authContext: AuthContext public let authContext: AuthContext
public let input: Input public let input: Input
public let sizeLimit: SizeLimit
@Published var caption = "" @Published var caption = ""
// @Published var sizeLimit = SizeLimit()
// output // output
@Published public private(set) var output: Output? @Published public private(set) var output: Output?
@ -77,11 +77,13 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
api: APIService, api: APIService,
authContext: AuthContext, authContext: AuthContext,
input: Input, input: Input,
sizeLimit: SizeLimit,
delegate: AttachmentViewModelDelegate delegate: AttachmentViewModelDelegate
) { ) {
self.api = api self.api = api
self.authContext = authContext self.authContext = authContext
self.input = input self.input = input
self.sizeLimit = sizeLimit
self.delegate = delegate self.delegate = delegate
super.init() super.init()
// end init // end init
@ -137,14 +139,17 @@ final public class AttachmentViewModel: NSObject, ObservableObject, Identifiable
var output = try await load(input: input) var output = try await load(input: input)
switch output { switch output {
case .image(let data, _):
self.output = output
self.update(uploadState: .compressing)
let compressedOutput = try await compressImage(data: data, sizeLimit: sizeLimit)
output = compressedOutput
case .video(let fileURL, let mimeType): case .video(let fileURL, let mimeType):
self.output = output self.output = output
self.update(uploadState: .compressing) self.update(uploadState: .compressing)
let compressedFileURL = try await comporessVideo(url: fileURL) let compressedFileURL = try await compressVideo(url: fileURL)
output = .video(compressedFileURL, mimeType: mimeType) output = .video(compressedFileURL, mimeType: mimeType)
try? FileManager.default.removeItem(at: fileURL) // remove old file try? FileManager.default.removeItem(at: fileURL) // remove old file
default:
break
} }
self.outputSizeInByte = output.asAttachment.sizeInByte.flatMap { Int64($0) } ?? 0 self.outputSizeInByte = output.asAttachment.sizeInByte.flatMap { Int64($0) } ?? 0
@ -262,19 +267,15 @@ extension AttachmentViewModel {
} }
} }
// not in using
public struct SizeLimit { public struct SizeLimit {
public let image: Int public let image: Int?
public let gif: Int public let video: Int?
public let video: Int
public init( public init(
image: Int = 10 * 1024 * 1024, // 10 MiB image: Int?,
gif: Int = 40 * 1024 * 1024, // 40 MiB video: Int?
video: Int = 40 * 1024 * 1024 // 40 MiB
) { ) {
self.image = image self.image = image
self.gif = gif
self.video = video self.video = video
} }
} }

View File

@ -435,6 +435,7 @@ extension ComposeContentViewController: PHPickerViewControllerDelegate {
api: viewModel.context.apiService, api: viewModel.context.apiService,
authContext: viewModel.authContext, authContext: viewModel.authContext,
input: .pickerResult(result), input: .pickerResult(result),
sizeLimit: viewModel.sizeLimit,
delegate: viewModel delegate: viewModel
) )
} }
@ -453,6 +454,7 @@ extension ComposeContentViewController: UIImagePickerControllerDelegate & UINavi
api: viewModel.context.apiService, api: viewModel.context.apiService,
authContext: viewModel.authContext, authContext: viewModel.authContext,
input: .image(image), input: .image(image),
sizeLimit: viewModel.sizeLimit,
delegate: viewModel delegate: viewModel
) )
viewModel.attachmentViewModels += [attachmentViewModel] viewModel.attachmentViewModels += [attachmentViewModel]
@ -473,6 +475,7 @@ extension ComposeContentViewController: UIDocumentPickerDelegate {
api: viewModel.context.apiService, api: viewModel.context.apiService,
authContext: viewModel.authContext, authContext: viewModel.authContext,
input: .url(url), input: .url(url),
sizeLimit: viewModel.sizeLimit,
delegate: viewModel delegate: viewModel
) )
viewModel.attachmentViewModels += [attachmentViewModel] viewModel.attachmentViewModels += [attachmentViewModel]

View File

@ -88,7 +88,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
// attachment // attachment
@Published public var attachmentViewModels: [AttachmentViewModel] = [] @Published public var attachmentViewModels: [AttachmentViewModel] = []
@Published public var maxMediaAttachmentLimit = 4 @Published public var maxMediaAttachmentLimit = 4
// @Published public internal(set) var isMediaValid = true @Published public internal(set) var maxImageMediaSizeLimitInByte = 10 * 1024 * 1024 // 10 MiB
// poll // poll
@Published public var isPollActive = false @Published public var isPollActive = false
@ -126,6 +126,14 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
@Published var isPollButtonEnabled = false @Published var isPollButtonEnabled = false
@Published public private(set) var shouldDismiss = true @Published public private(set) var shouldDismiss = true
// size limit
public var sizeLimit: AttachmentViewModel.SizeLimit {
AttachmentViewModel.SizeLimit(
image: maxImageMediaSizeLimitInByte,
video: nil
)
}
public init( public init(
context: AppContext, context: AppContext,
@ -252,6 +260,10 @@ public final class ComposeContentViewModel: NSObject, ObservableObject {
if let maxOptions = configuration.polls?.maxOptions { if let maxOptions = configuration.polls?.maxOptions {
maxPollOptionLimit = maxOptions maxPollOptionLimit = maxOptions
} }
// set photo attachment limit
if let imageSizeLimit = configuration.mediaAttachments?.imageSizeLimit {
maxImageMediaSizeLimitInByte = imageSizeLimit
}
// TODO: more limit // TODO: more limit
} }

View File

@ -276,6 +276,7 @@ extension ShareViewController {
api: context.apiService, api: context.apiService,
authContext: authContext, authContext: authContext,
input: .itemProvider(movieProvider), input: .itemProvider(movieProvider),
sizeLimit: .init(image: nil, video: nil),
delegate: composeContentViewModel delegate: composeContentViewModel
) )
composeContentViewModel.attachmentViewModels.append(attachmentViewModel) composeContentViewModel.attachmentViewModels.append(attachmentViewModel)
@ -285,6 +286,7 @@ extension ShareViewController {
api: context.apiService, api: context.apiService,
authContext: authContext, authContext: authContext,
input: .itemProvider(provider), input: .itemProvider(provider),
sizeLimit: .init(image: nil, video: nil),
delegate: composeContentViewModel delegate: composeContentViewModel
) )
} }