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,
authContext: viewModel.authContext,
input: .image(image),
sizeLimit: composeContentViewModel.sizeLimit,
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 UIKit
import AVKit
import SessionExporter
import MastodonCore
import SessionExporter
import Kingfisher
extension AttachmentViewModel {
func comporessVideo(url: URL) async throws -> URL {
func compressVideo(url: URL) async throws -> URL {
let urlAsset = AVURLAsset(url: url)
let exporter = NextLevelSessionExporter(withAsset: urlAsset)
exporter.outputFileType = .mp4
@ -92,3 +93,57 @@ extension AttachmentViewModel {
}
} // 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 {
switch input {
case .image(let image):
guard let data = image.pngData() else {
guard let data = image.normalized()?.pngData() else {
throw AttachmentError.invalidAttachmentType
}
return .image(data, imageKind: .png)

View File

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

View File

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

View File

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

View File

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