Change image resizing/quality behavior, preferring smaller images in the common case.

pull/1/head
Matthew Chen 7 years ago
parent be5ac85276
commit 125aabb0a3

@ -2481,7 +2481,10 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
}
[dataSource setSourceFilename:filename];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:type];
// "Document picker" attachments _SHOULD NOT_ be resized, if possible.
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource
dataUTI:type
attachmentQuality:TSAttachmentQualityOriginal];
[self tryToSendAttachmentIfApproved:attachment];
}
@ -2605,10 +2608,12 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
OWSAssert([NSThread isMainThread]);
if (imageFromCamera) {
// "Camera" attachments _SHOULD_ be resized, if possible.
SignalAttachment *attachment =
[SignalAttachment imageAttachmentWithImage:imageFromCamera
dataUTI:(NSString *)kUTTypeJPEG
filename:filename];
filename:filename
attachmentQuality:TSAttachmentQualityCompact];
if (!attachment || [attachment hasError]) {
DDLogWarn(@"%@ %s Invalid attachment: %@.",
self.logTag,
@ -2655,8 +2660,11 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
DataSource *_Nullable dataSource =
[DataSourceValue dataSourceWithData:imageData utiType:dataUTI];
[dataSource setSourceFilename:filename];
// "Camera Roll" attachments _SHOULD_ be resized, if possible.
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:dataUTI];
[SignalAttachment attachmentWithDataSource:dataSource
dataUTI:dataUTI
attachmentQuality:TSAttachmentQualityCompact];
[self dismissViewControllerAnimated:YES
completion:^{
OWSAssert([NSThread isMainThread]);
@ -2745,7 +2753,8 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
[dataSource setShouldDeleteOnDeallocation];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource
dataUTI:(NSString *)kUTTypeMPEG4];
dataUTI:(NSString *)kUTTypeMPEG4
attachmentQuality:TSAttachmentQualityOriginal];
if (!attachment || [attachment hasError]) {
DDLogError(@"%@ %s Invalid attachment: %@.",
self.logTag,
@ -3847,8 +3856,9 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
TSOutgoingMessage *message;
if ([text lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >= kOversizeTextMessageSizeThreshold) {
DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:text];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource
dataUTI:kOversizeTextAttachmentUTI
attachmentQuality:TSAttachmentQualityOriginal];
message =
[ThreadUtil sendMessageWithAttachment:attachment inThread:self.thread messageSender:self.messageSender];
} else {

@ -378,7 +378,9 @@ NS_ASSUME_NONNULL_BEGIN
NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:filename.pathExtension];
DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath];
[dataSource setSourceFilename:filename];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource
dataUTI:utiType
attachmentQuality:TSAttachmentQualityOriginal];
OWSAssert(attachment);
if ([attachment hasError]) {
DDLogError(@"attachment[%@]: %@", [attachment sourceFilename], [attachment errorName]);
@ -641,8 +643,9 @@ NS_ASSUME_NONNULL_BEGIN
}
DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:message];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource
dataUTI:kOversizeTextAttachmentUTI
attachmentQuality:TSAttachmentQualityOriginal];
[ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender];
}
@ -668,7 +671,9 @@ NS_ASSUME_NONNULL_BEGIN
OWSMessageSender *messageSender = [Environment current].messageSender;
DataSource *_Nullable dataSource =
[DataSourceValue dataSourceWithData:[self createRandomNSDataOfSize:length] utiType:uti];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:uti];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource
dataUTI:uti
attachmentQuality:TSAttachmentQualityOriginal];
[ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender ignoreErrors:YES];
}
+ (OWSSignalServiceProtosEnvelope *)createEnvelopeForThread:(TSThread *)thread

@ -53,13 +53,31 @@ extension SignalAttachmentError: LocalizedError {
}
}
enum TSImageQuality {
case uncropped
@objc
public enum TSImageQuality: UInt {
case original
case high
case mediumHigh
case medium
case mediumLow
case low
}
@objc
public enum TSAttachmentQuality: UInt {
case original
case compact
func imageQuality() -> TSImageQuality {
switch self {
case .original:
return .original
case .compact:
return .mediumHigh
}
}
}
// Represents a possible attachment to upload.
// The attachment may be invalid.
//
@ -448,7 +466,8 @@ public class SignalAttachment: NSObject {
return nil
}
let dataSource = DataSourceValue.dataSource(with:data, utiType: dataUTI)
return imageAttachment(dataSource : dataSource, dataUTI : dataUTI)
// Pasted images should not be resized, if possible.
return imageAttachment(dataSource : dataSource, dataUTI : dataUTI, attachmentQuality:.original)
}
}
for dataUTI in videoUTISet {
@ -507,7 +526,7 @@ public class SignalAttachment: NSObject {
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
@objc
public class func imageAttachment(dataSource: DataSource?, dataUTI: String) -> SignalAttachment {
public class func imageAttachment(dataSource: DataSource?, dataUTI: String, attachmentQuality: TSAttachmentQuality) -> SignalAttachment {
assert(dataUTI.count > 0)
assert(dataSource != nil)
@ -546,7 +565,7 @@ public class SignalAttachment: NSObject {
}
attachment.cachedImage = image
if isInputImageValidOutputImage(image: image, dataSource: dataSource, dataUTI: dataUTI) {
if isInputImageValidOutputImage(image: image, dataSource: dataSource, dataUTI: dataUTI, attachmentQuality:attachmentQuality) {
if let sourceFilename = dataSource.sourceFilename,
let sourceFileExtension = sourceFilename.fileExtension,
["heic", "heif"].contains(sourceFileExtension.lowercased()) {
@ -570,19 +589,14 @@ public class SignalAttachment: NSObject {
return attachment
}
Logger.verbose("\(TAG) Compressing attachment as image/jpeg")
return compressImageAsJPEG(image : image, attachment : attachment, filename:dataSource.sourceFilename)
Logger.verbose("\(TAG) Compressing attachment as image/jpeg, \(dataSource.dataLength()) bytes")
return compressImageAsJPEG(image : image, attachment : attachment, filename:dataSource.sourceFilename, attachmentQuality:attachmentQuality)
}
}
private class func defaultImageUploadQuality() -> TSImageQuality {
// Currently default to a original image quality and size.
return .uncropped
}
// If the proposed attachment already conforms to the
// file size and content size limits, don't recompress it.
private class func isInputImageValidOutputImage(image: UIImage?, dataSource: DataSource?, dataUTI: String) -> Bool {
private class func isInputImageValidOutputImage(image: UIImage?, dataSource: DataSource?, dataUTI: String, attachmentQuality: TSAttachmentQuality) -> Bool {
guard let image = image else {
return false
}
@ -593,8 +607,9 @@ public class SignalAttachment: NSObject {
return false
}
let imageQuality = attachmentQuality.imageQuality()
let maxSize = maxSizeForImage(image: image,
imageUploadQuality:defaultImageUploadQuality())
imageUploadQuality:imageQuality)
if image.size.width <= maxSize &&
image.size.height <= maxSize &&
dataSource.dataLength() <= kMaxFileSizeImage {
@ -608,7 +623,7 @@ public class SignalAttachment: NSObject {
// NOTE: The attachment returned by this method may nil or not be valid.
// Check the attachment's error property.
@objc
public class func imageAttachment(image: UIImage?, dataUTI: String, filename: String?) -> SignalAttachment {
public class func imageAttachment(image: UIImage?, dataUTI: String, filename: String?, attachmentQuality: TSAttachmentQuality) -> SignalAttachment {
assert(dataUTI.count > 0)
guard let image = image else {
@ -626,13 +641,13 @@ public class SignalAttachment: NSObject {
attachment.cachedImage = image
Logger.verbose("\(TAG) Writing \(attachment.mimeType) as image/jpeg")
return compressImageAsJPEG(image : image, attachment : attachment, filename:filename)
return compressImageAsJPEG(image : image, attachment : attachment, filename:filename, attachmentQuality:attachmentQuality)
}
private class func compressImageAsJPEG(image: UIImage, attachment: SignalAttachment, filename: String?) -> SignalAttachment {
private class func compressImageAsJPEG(image: UIImage, attachment: SignalAttachment, filename: String?, attachmentQuality: TSAttachmentQuality) -> SignalAttachment {
assert(attachment.error == nil)
var imageUploadQuality = defaultImageUploadQuality()
var imageUploadQuality = attachmentQuality.imageQuality()
while true {
let maxSize = maxSizeForImage(image: image, imageUploadQuality:imageUploadQuality)
@ -649,6 +664,7 @@ public class SignalAttachment: NSObject {
guard let dataSource = DataSourceValue.dataSource(with:jpgImageData, fileExtension:"jpg") else {
attachment.error = .couldNotConvertToJpeg
Logger.verbose("\(TAG) Could not convert \(attachment.mimeType) to image/jpeg")
return attachment
}
@ -659,6 +675,7 @@ public class SignalAttachment: NSObject {
if UInt(jpgImageData.count) <= kMaxFileSizeImage {
let recompressedAttachment = SignalAttachment(dataSource : dataSource, dataUTI: kUTTypeJPEG as String)
recompressedAttachment.cachedImage = dstImage
Logger.verbose("\(TAG) Converted \(attachment.mimeType) to image/jpeg, \(jpgImageData.count) bytes")
return recompressedAttachment
}
@ -666,14 +683,19 @@ public class SignalAttachment: NSObject {
// continue to try again by progressively reducing the
// image upload quality.
switch imageUploadQuality {
case .uncropped:
case .original:
imageUploadQuality = .high
case .high:
imageUploadQuality = .mediumHigh
case .mediumHigh:
imageUploadQuality = .medium
case .medium:
imageUploadQuality = .mediumLow
case .mediumLow:
imageUploadQuality = .low
case .low:
attachment.error = .fileSizeTooLarge
Logger.verbose("\(TAG) Image too large to convert \(attachment.mimeType) to image/jpeg")
return attachment
}
}
@ -697,12 +719,16 @@ public class SignalAttachment: NSObject {
private class func maxSizeForImage(image: UIImage, imageUploadQuality: TSImageQuality) -> CGFloat {
switch imageUploadQuality {
case .uncropped:
case .original:
return max(image.size.width, image.size.height)
case .high:
return 2048
case .mediumHigh:
return 1536
case .medium:
return 1024
case .mediumLow:
return 768
case .low:
return 512
}
@ -710,12 +736,16 @@ public class SignalAttachment: NSObject {
private class func jpegCompressionQuality(imageUploadQuality: TSImageQuality) -> CGFloat {
switch imageUploadQuality {
case .uncropped:
case .original:
return 1
case .high:
return 0.9
case .mediumHigh:
return 0.7
case .medium:
return 0.5
case .mediumLow:
return 0.4
case .low:
return 0.3
}
@ -790,9 +820,9 @@ public class SignalAttachment: NSObject {
// NOTE: The attachment returned by this method may not be valid.
// Check the attachment's error property.
@objc
public class func attachment(dataSource: DataSource?, dataUTI: String) -> SignalAttachment {
public class func attachment(dataSource: DataSource?, dataUTI: String, attachmentQuality: TSAttachmentQuality) -> SignalAttachment {
if inputImageUTISet.contains(dataUTI) {
return imageAttachment(dataSource : dataSource, dataUTI : dataUTI)
return imageAttachment(dataSource : dataSource, dataUTI : dataUTI, attachmentQuality:attachmentQuality)
} else if videoUTISet.contains(dataUTI) {
return videoAttachment(dataSource : dataSource, dataUTI : dataUTI)
} else if audioUTISet.contains(dataUTI) {
@ -805,7 +835,8 @@ public class SignalAttachment: NSObject {
@objc
public class func empty() -> SignalAttachment {
return SignalAttachment.attachment(dataSource : DataSourceValue.emptyDataSource(),
dataUTI: kUTTypeContent as String)
dataUTI: kUTTypeContent as String,
attachmentQuality:.original)
}
// MARK: Helper Methods

@ -435,7 +435,7 @@ public class ShareViewController: UINavigationController, ShareViewDelegate, SAE
}
}
let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: specificUTIType)
let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: specificUTIType, attachmentQuality:.compact)
return attachment
}

Loading…
Cancel
Save