mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2025-02-02 18:36:44 +01:00
Merge pull request #674 from mastodon/fix-665-image-downscaling
Fix no downscaling for raw image from camera issue
This commit is contained in:
commit
0d01e2ad23
@ -209,6 +209,7 @@ extension ComposeViewController {
|
||||
api: viewModel.context.apiService,
|
||||
authContext: viewModel.authContext,
|
||||
input: .image(image),
|
||||
sizeLimit: composeContentViewModel.sizeLimit,
|
||||
delegate: composeContentViewModel
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 = max((sizeLimit.image ?? 10 * 1024 * 1024), 1 * 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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user