Start sketching out image editor.

This commit is contained in:
Matthew Chen 2018-12-14 12:05:08 -05:00
parent 7245307c92
commit 26a25f861b
5 changed files with 130 additions and 58 deletions

View File

@ -235,6 +235,7 @@
34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2A1F74C12700D7438D /* DebugUIStress.m */; };
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */; };
34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2F1F7ABCF800D7438D /* GifPickerLayout.swift */; };
34BEDB0E21C405B0007B0EAE /* ImageEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BEDB0D21C405B0007B0EAE /* ImageEditor.swift */; };
34C3C78D20409F320000134C /* Opening.m4r in Resources */ = {isa = PBXBuildFile; fileRef = 34C3C78C20409F320000134C /* Opening.m4r */; };
34C3C78F2040A4F70000134C /* sonarping.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 34C3C78E2040A4F70000134C /* sonarping.mp3 */; };
34C3C7922040B0DD0000134C /* OWSAudioPlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 34C3C7902040B0DC0000134C /* OWSAudioPlayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -906,6 +907,7 @@
34BECE2A1F74C12700D7438D /* DebugUIStress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIStress.m; sourceTree = "<group>"; };
34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerViewController.swift; sourceTree = "<group>"; };
34BECE2F1F7ABCF800D7438D /* GifPickerLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerLayout.swift; sourceTree = "<group>"; };
34BEDB0D21C405B0007B0EAE /* ImageEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageEditor.swift; sourceTree = "<group>"; };
34C3C78C20409F320000134C /* Opening.m4r */ = {isa = PBXFileReference; lastKnownFileType = file; path = Opening.m4r; sourceTree = "<group>"; };
34C3C78E2040A4F70000134C /* sonarping.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = sonarping.mp3; path = Signal/AudioFiles/sonarping.mp3; sourceTree = SOURCE_ROOT; };
34C3C7902040B0DC0000134C /* OWSAudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAudioPlayer.h; sourceTree = "<group>"; };
@ -1714,7 +1716,9 @@
34AC0A00211B39E700997B47 /* DisappearingTimerConfigurationView.swift */,
4CA46F49219C78050038ABDE /* GalleryRailView.swift */,
34AC0A08211B39E900997B47 /* GradientView.swift */,
34BEDB0C21C405B0007B0EAE /* ImageEditor */,
34AC0A06211B39E900997B47 /* OWSAlerts.swift */,
4C618198219DF03A009BD6B5 /* OWSButton.swift */,
34AC0A09211B39E900997B47 /* OWSFlatButton.swift */,
34AC09FE211B39E700997B47 /* OWSLayerView.swift */,
34AC0A03211B39E800997B47 /* OWSNavigationBar.swift */,
@ -1729,7 +1733,6 @@
34AC0A0D211B39EA00997B47 /* ThreadViewHelper.h */,
34AC0A0B211B39EA00997B47 /* ThreadViewHelper.m */,
34AC0A04211B39E800997B47 /* VideoPlayerView.swift */,
4C618198219DF03A009BD6B5 /* OWSButton.swift */,
);
path = Views;
sourceTree = "<group>";
@ -1858,6 +1861,14 @@
path = GifPicker;
sourceTree = "<group>";
};
34BEDB0C21C405B0007B0EAE /* ImageEditor */ = {
isa = PBXGroup;
children = (
34BEDB0D21C405B0007B0EAE /* ImageEditor.swift */,
);
path = ImageEditor;
sourceTree = "<group>";
};
34C3C78B20409F320000134C /* ringtoneSounds */ = {
isa = PBXGroup;
children = (
@ -3323,6 +3334,7 @@
34AC09E9211B39B100997B47 /* OWSTableViewController.m in Sources */,
346129F51FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m in Sources */,
45194F8F1FD71FF500333B2C /* ThreadUtil.m in Sources */,
34BEDB0E21C405B0007B0EAE /* ImageEditor.swift in Sources */,
451F8A3B1FD71297005CB9DA /* UIUtil.m in Sources */,
450C800F20AD1AB900F3A091 /* OWSWindowManager.m in Sources */,
454A965A1FD6017E008D2A0E /* SignalAttachment.swift in Sources */,

View File

@ -0,0 +1,54 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import UIKit
@objc public enum ImageEditorError: Int, Error {
case assertionError
case invalidInput
}
@objc
public class ImageEditorItem: NSObject {
@objc
public let itemId: String
@objc
public override required init() {
self.itemId = UUID().uuidString
super.init()
}
}
@objc
public class ImageEditorModel: NSObject {
private let srcImagePath: String
private let srcImageSize: CGSize
@objc
public required init(srcImagePath: String) throws {
self.srcImagePath = srcImagePath
let srcFileName = (srcImagePath as NSString).lastPathComponent
let srcFileExtension = (srcFileName as NSString).pathExtension
guard let mimeType = MIMETypeUtil.mimeType(forFileExtension: srcFileExtension) else {
Logger.error("Couldn't determine MIME type for file.")
throw ImageEditorError.invalidInput
}
guard MIMETypeUtil.isImage(mimeType) else {
Logger.error("Invalid MIME type: \(mimeType).")
throw ImageEditorError.invalidInput
}
let srcImageSize = NSData.imageSize(forFilePath: srcImagePath, mimeType: mimeType)
guard srcImageSize.width > 0, srcImageSize.height > 0 else {
Logger.error("Couldn't determine image size.")
throw ImageEditorError.invalidInput
}
self.srcImageSize = srcImageSize
super.init()
}
}

View File

@ -494,68 +494,13 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
}
return [self videoStillImage].size;
} else if ([self isImage] || [self isAnimated]) {
NSURL *_Nullable mediaUrl = self.originalMediaURL;
if (!mediaUrl) {
return CGSizeZero;
}
if (![self isValidImage]) {
return CGSizeZero;
}
// With CGImageSource we avoid loading the whole image into memory.
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)mediaUrl, NULL);
if (!source) {
OWSFailDebug(@"Could not load image: %@", mediaUrl);
return CGSizeZero;
}
NSDictionary *options = @{
(NSString *)kCGImageSourceShouldCache : @(NO),
};
NSDictionary *properties
= (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, 0, (CFDictionaryRef)options);
CGSize imageSize = CGSizeZero;
if (properties) {
NSNumber *orientation = properties[(NSString *)kCGImagePropertyOrientation];
NSNumber *width = properties[(NSString *)kCGImagePropertyPixelWidth];
NSNumber *height = properties[(NSString *)kCGImagePropertyPixelHeight];
if (width && height) {
imageSize = CGSizeMake(width.floatValue, height.floatValue);
if (orientation) {
imageSize =
[self applyImageOrientation:(UIImageOrientation)orientation.intValue toImageSize:imageSize];
}
} else {
OWSFailDebug(@"Could not determine size of image: %@", mediaUrl);
}
}
CFRelease(source);
return imageSize;
// imageSizeForFilePath checks validity.
return [NSData imageSizeForFilePath:self.originalFilePath mimeType:self.contentType];
} else {
return CGSizeZero;
}
}
- (CGSize)applyImageOrientation:(UIImageOrientation)orientation toImageSize:(CGSize)imageSize
{
switch (orientation) {
case UIImageOrientationUp: // EXIF = 1
case UIImageOrientationUpMirrored: // EXIF = 2
case UIImageOrientationDown: // EXIF = 3
case UIImageOrientationDownMirrored: // EXIF = 4
return imageSize;
case UIImageOrientationLeftMirrored: // EXIF = 5
case UIImageOrientationLeft: // EXIF = 6
case UIImageOrientationRightMirrored: // EXIF = 7
case UIImageOrientationRight: // EXIF = 8
return CGSizeMake(imageSize.height, imageSize.width);
default:
return imageSize;
}
}
- (BOOL)shouldHaveImageSize
{
return ([self isVideo] || [self isImage] || [self isAnimated]);

View File

@ -11,4 +11,7 @@
- (BOOL)ows_isValidImage;
- (BOOL)ows_isValidImageWithMimeType:(nullable NSString *)mimeType;
// Returns CGSizeZero on error.
+ (CGSize)imageSizeForFilePath:(NSString *)filePath mimeType:(NSString *)mimeType;
@end

View File

@ -312,4 +312,62 @@ typedef NS_ENUM(NSInteger, ImageFormat) {
return (width > 0 && width < kMaxValidSize && height > 0 && height < kMaxValidSize);
}
+ (CGSize)imageSizeForFilePath:(NSString *)filePath mimeType:(NSString *)mimeType
{
if (![NSData ows_isValidImageAtPath:filePath mimeType:mimeType]) {
OWSLogError(@"Invalid image.");
return CGSizeZero;
}
NSURL *url = [NSURL fileURLWithPath:filePath];
// With CGImageSource we avoid loading the whole image into memory.
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
if (!source) {
OWSFailDebug(@"Could not load image: %@", url);
return CGSizeZero;
}
NSDictionary *options = @{
(NSString *)kCGImageSourceShouldCache : @(NO),
};
NSDictionary *properties
= (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, 0, (CFDictionaryRef)options);
CGSize imageSize = CGSizeZero;
if (properties) {
NSNumber *orientation = properties[(NSString *)kCGImagePropertyOrientation];
NSNumber *width = properties[(NSString *)kCGImagePropertyPixelWidth];
NSNumber *height = properties[(NSString *)kCGImagePropertyPixelHeight];
if (width && height) {
imageSize = CGSizeMake(width.floatValue, height.floatValue);
if (orientation) {
imageSize = [self applyImageOrientation:(UIImageOrientation)orientation.intValue toImageSize:imageSize];
}
} else {
OWSFailDebug(@"Could not determine size of image: %@", url);
}
}
CFRelease(source);
return imageSize;
}
+ (CGSize)applyImageOrientation:(UIImageOrientation)orientation toImageSize:(CGSize)imageSize
{
switch (orientation) {
case UIImageOrientationUp: // EXIF = 1
case UIImageOrientationUpMirrored: // EXIF = 2
case UIImageOrientationDown: // EXIF = 3
case UIImageOrientationDownMirrored: // EXIF = 4
return imageSize;
case UIImageOrientationLeftMirrored: // EXIF = 5
case UIImageOrientationLeft: // EXIF = 6
case UIImageOrientationRightMirrored: // EXIF = 7
case UIImageOrientationRight: // EXIF = 8
return CGSizeMake(imageSize.height, imageSize.width);
default:
return imageSize;
}
}
@end