diff --git a/LeadKit/LeadKit.xcodeproj/project.pbxproj b/LeadKit/LeadKit.xcodeproj/project.pbxproj index 775e04b5..7e06260f 100644 --- a/LeadKit/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit/LeadKit.xcodeproj/project.pbxproj @@ -7,15 +7,28 @@ objects = { /* Begin PBXBuildFile section */ + 67186B181EB1DC0500CFAFFB /* ResizeDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67186B171EB1DC0500CFAFFB /* ResizeDrawingOperation.swift */; }; 6727419D1E65B99E0075836A /* MappableUserDefaultsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6727419C1E65B99E0075836A /* MappableUserDefaultsTests.swift */; }; 672741A01E65C1E00075836A /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6727419F1E65C1E00075836A /* Post.swift */; }; 674743941E929A5A00B47671 /* PaginationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674743931E929A5A00B47671 /* PaginationViewModelTests.swift */; }; + 674E7E651EB0F2E300D13340 /* UIImage+SupportExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674E7E641EB0F2E300D13340 /* UIImage+SupportExtensions.swift */; }; 675D24B21E9234BB00E92D1F /* PaginationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675D24B11E9234BB00E92D1F /* PaginationViewModel.swift */; }; 675FB4251EA7797C0075BF3D /* Mutex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675FB4241EA7797C0075BF3D /* Mutex.swift */; }; + 676D177E1EAE0661002E19F9 /* ResizeContentMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 676D177D1EAE0661002E19F9 /* ResizeContentMode.swift */; }; + 676D17811EAE137B002E19F9 /* CGSize+Resize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 676D17801EAE137B002E19F9 /* CGSize+Resize.swift */; }; 67788F9F1E69661800484DEE /* CGFloat+Pixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67788F9E1E69661800484DEE /* CGFloat+Pixels.swift */; }; 678A202A1E93C1A900787562 /* PaginationTableViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678A20291E93C1A900787562 /* PaginationTableViewWrapper.swift */; }; 679DE4901E9588B6006F25FE /* SupportProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 679DE48F1E9588B6006F25FE /* SupportProtocol.swift */; }; 679DE4941E9613ED006F25FE /* UIScrollView+Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 679DE4931E9613ED006F25FE /* UIScrollView+Support.swift */; }; + 67A7B1911EAF5F4900E5BC59 /* ImageDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1901EAF5F4900E5BC59 /* ImageDrawingOperation.swift */; }; + 67A7B1931EAF5F6A00E5BC59 /* TemplateDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1921EAF5F6A00E5BC59 /* TemplateDrawingOperation.swift */; }; + 67A7B1951EAF5F9B00E5BC59 /* CGSize+CGContextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1941EAF5F9B00E5BC59 /* CGSize+CGContextSize.swift */; }; + 67A7B1971EAF5FF600E5BC59 /* DrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1961EAF5FF600E5BC59 /* DrawingOperation.swift */; }; + 67A7B1991EAF602900E5BC59 /* RoundDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1981EAF602900E5BC59 /* RoundDrawingOperation.swift */; }; + 67A7B19B1EAF60B100E5BC59 /* BorderDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B19A1EAF60B100E5BC59 /* BorderDrawingOperation.swift */; }; + 67A7B19F1EAF646400E5BC59 /* PaddingDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B19E1EAF646400E5BC59 /* PaddingDrawingOperation.swift */; }; + 67A7B1A11EAF67AE00E5BC59 /* SolidFillDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1A01EAF67AE00E5BC59 /* SolidFillDrawingOperation.swift */; }; + 67A7B1A31EAF6B4600E5BC59 /* CALayerDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A7B1A21EAF6B4600E5BC59 /* CALayerDrawingOperation.swift */; }; 67B3057B1E8A8727008169CA /* TestView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 67B3057A1E8A8727008169CA /* TestView.xib */; }; 67B3057D1E8A8735008169CA /* TestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67B3057C1E8A8735008169CA /* TestView.swift */; }; 67B3057F1E8A8804008169CA /* LoadFromNibTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67B3057E1E8A8804008169CA /* LoadFromNibTests.swift */; }; @@ -59,10 +72,6 @@ 78A0FCC81DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A0FCC61DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift */; }; 78A74EA91C6B373700FE9724 /* UIView+DefaultXibName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A74EA81C6B373700FE9724 /* UIView+DefaultXibName.swift */; }; 78B036411DA4D7060021D5CC /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036401DA4D7060021D5CC /* UIImage+Extensions.swift */; }; - 78B036431DA4FEC90021D5CC /* CGImage+Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036421DA4FEC90021D5CC /* CGImage+Transform.swift */; }; - 78B036451DA561D00021D5CC /* CGImage+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036441DA561D00021D5CC /* CGImage+Utils.swift */; }; - 78B036471DA5624D0021D5CC /* CGImage+Creation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036461DA5624D0021D5CC /* CGImage+Creation.swift */; }; - 78B036491DA562C30021D5CC /* CGImage+Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036481DA562C30021D5CC /* CGImage+Template.swift */; }; 78B0364B1DA61EDE0021D5CC /* CGImage+Crop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B0364A1DA61EDE0021D5CC /* CGImage+Crop.swift */; }; 78B0FC7D1C6B2BE200358B64 /* LogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B0FC7C1C6B2BE200358B64 /* LogFormatter.swift */; }; 78B0FC7F1C6B2C4D00358B64 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B0FC7E1C6B2C4D00358B64 /* Log.swift */; }; @@ -109,16 +118,29 @@ /* Begin PBXFileReference section */ 12F36034A5278991B658B53E /* Pods_LeadKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LeadKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 67186B171EB1DC0500CFAFFB /* ResizeDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResizeDrawingOperation.swift; sourceTree = ""; }; 671FF1611EAA264B001B882C /* iOS.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = iOS.playground; sourceTree = ""; }; 6727419C1E65B99E0075836A /* MappableUserDefaultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappableUserDefaultsTests.swift; sourceTree = ""; }; 6727419F1E65C1E00075836A /* Post.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = ""; }; 674743931E929A5A00B47671 /* PaginationViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationViewModelTests.swift; sourceTree = ""; }; + 674E7E641EB0F2E300D13340 /* UIImage+SupportExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+SupportExtensions.swift"; sourceTree = ""; }; 675D24B11E9234BB00E92D1F /* PaginationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationViewModel.swift; sourceTree = ""; }; 675FB4241EA7797C0075BF3D /* Mutex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mutex.swift; sourceTree = ""; }; + 676D177D1EAE0661002E19F9 /* ResizeContentMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResizeContentMode.swift; sourceTree = ""; }; + 676D17801EAE137B002E19F9 /* CGSize+Resize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGSize+Resize.swift"; sourceTree = ""; }; 67788F9E1E69661800484DEE /* CGFloat+Pixels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat+Pixels.swift"; sourceTree = ""; }; 678A20291E93C1A900787562 /* PaginationTableViewWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationTableViewWrapper.swift; sourceTree = ""; }; 679DE48F1E9588B6006F25FE /* SupportProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SupportProtocol.swift; sourceTree = ""; }; 679DE4931E9613ED006F25FE /* UIScrollView+Support.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Support.swift"; sourceTree = ""; }; + 67A7B1901EAF5F4900E5BC59 /* ImageDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageDrawingOperation.swift; sourceTree = ""; }; + 67A7B1921EAF5F6A00E5BC59 /* TemplateDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemplateDrawingOperation.swift; sourceTree = ""; }; + 67A7B1941EAF5F9B00E5BC59 /* CGSize+CGContextSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGSize+CGContextSize.swift"; sourceTree = ""; }; + 67A7B1961EAF5FF600E5BC59 /* DrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawingOperation.swift; sourceTree = ""; }; + 67A7B1981EAF602900E5BC59 /* RoundDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundDrawingOperation.swift; sourceTree = ""; }; + 67A7B19A1EAF60B100E5BC59 /* BorderDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BorderDrawingOperation.swift; sourceTree = ""; }; + 67A7B19E1EAF646400E5BC59 /* PaddingDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaddingDrawingOperation.swift; sourceTree = ""; }; + 67A7B1A01EAF67AE00E5BC59 /* SolidFillDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SolidFillDrawingOperation.swift; sourceTree = ""; }; + 67A7B1A21EAF6B4600E5BC59 /* CALayerDrawingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CALayerDrawingOperation.swift; sourceTree = ""; }; 67B3057A1E8A8727008169CA /* TestView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TestView.xib; sourceTree = ""; }; 67B3057C1E8A8735008169CA /* TestView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestView.swift; sourceTree = ""; }; 67B3057E1E8A8804008169CA /* LoadFromNibTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadFromNibTests.swift; sourceTree = ""; }; @@ -162,10 +184,6 @@ 78A0FCC61DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "StoryboardProtocol+Extensions.swift"; sourceTree = ""; }; 78A74EA81C6B373700FE9724 /* UIView+DefaultXibName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+DefaultXibName.swift"; path = "LeadKit/Extensions/UIView/UIView+DefaultXibName.swift"; sourceTree = SOURCE_ROOT; }; 78B036401DA4D7060021D5CC /* UIImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = ""; }; - 78B036421DA4FEC90021D5CC /* CGImage+Transform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Transform.swift"; sourceTree = ""; }; - 78B036441DA561D00021D5CC /* CGImage+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Utils.swift"; sourceTree = ""; }; - 78B036461DA5624D0021D5CC /* CGImage+Creation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Creation.swift"; sourceTree = ""; }; - 78B036481DA562C30021D5CC /* CGImage+Template.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Template.swift"; sourceTree = ""; }; 78B0364A1DA61EDE0021D5CC /* CGImage+Crop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Crop.swift"; sourceTree = ""; }; 78B0FC7C1C6B2BE200358B64 /* LogFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogFormatter.swift; sourceTree = ""; }; 78B0FC7E1C6B2C4D00358B64 /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; @@ -252,6 +270,15 @@ path = Concurrency; sourceTree = ""; }; + 676D177F1EAE1364002E19F9 /* CGSize */ = { + isa = PBXGroup; + children = ( + 676D17801EAE137B002E19F9 /* CGSize+Resize.swift */, + 67A7B1941EAF5F9B00E5BC59 /* CGSize+CGContextSize.swift */, + ); + path = CGSize; + sourceTree = ""; + }; 67788F9D1E6965F800484DEE /* CGFloat */ = { isa = PBXGroup; children = ( @@ -268,6 +295,21 @@ path = Support; sourceTree = ""; }; + 67A7B18F1EAF5F2200E5BC59 /* DrawingOperations */ = { + isa = PBXGroup; + children = ( + 67A7B1901EAF5F4900E5BC59 /* ImageDrawingOperation.swift */, + 67A7B1921EAF5F6A00E5BC59 /* TemplateDrawingOperation.swift */, + 67A7B1981EAF602900E5BC59 /* RoundDrawingOperation.swift */, + 67A7B19A1EAF60B100E5BC59 /* BorderDrawingOperation.swift */, + 67A7B19E1EAF646400E5BC59 /* PaddingDrawingOperation.swift */, + 67A7B1A01EAF67AE00E5BC59 /* SolidFillDrawingOperation.swift */, + 67A7B1A21EAF6B4600E5BC59 /* CALayerDrawingOperation.swift */, + 67186B171EB1DC0500CFAFFB /* ResizeDrawingOperation.swift */, + ); + path = DrawingOperations; + sourceTree = ""; + }; 67B305791E8A8727008169CA /* Views */ = { isa = PBXGroup; children = ( @@ -322,6 +364,7 @@ children = ( 78753E231DE58A5D006BC0FB /* CursorError.swift */, 7873D14E1E1127BC001816EB /* LeadKitError.swift */, + 676D177D1EAE0661002E19F9 /* ResizeContentMode.swift */, ); path = Enums; sourceTree = ""; @@ -331,6 +374,7 @@ children = ( 67DC650A1E979BFD002F2FFF /* Views */, 78011AB11D48B53600EA16A2 /* Api */, + 67A7B18F1EAF5F2200E5BC59 /* DrawingOperations */, ); path = Structures; sourceTree = ""; @@ -347,10 +391,6 @@ isa = PBXGroup; children = ( 780D23421DA412470084620D /* CGImage+Alpha.swift */, - 78B036421DA4FEC90021D5CC /* CGImage+Transform.swift */, - 78B036441DA561D00021D5CC /* CGImage+Utils.swift */, - 78B036461DA5624D0021D5CC /* CGImage+Creation.swift */, - 78B036481DA562C30021D5CC /* CGImage+Template.swift */, 78B0364A1DA61EDE0021D5CC /* CGImage+Crop.swift */, ); path = CGImage; @@ -556,6 +596,7 @@ 780D23441DA416E80084620D /* CGContext */, 67788F9D1E6965F800484DEE /* CGFloat */, 780D23411DA412330084620D /* CGImage */, + 676D177F1EAE1364002E19F9 /* CGSize */, 789CC6091DE584C000F789D3 /* CursorType */, 78C36F7C1D801E2F00E7EBEA /* Double */, 787783611CA03C84001CDC9B /* IndexPath */, @@ -565,8 +606,8 @@ 780F56C81E0D76A5004530B6 /* Sequence */, 78A0FCC41DC366A10070B5E1 /* StoryboardProtocol */, 787783651CA04D14001CDC9B /* String */, - CAE698BF1E965AE9000394B0 /* TableDirector */, 679DE4921E9613ED006F25FE /* Support */, + CAE698BF1E965AE9000394B0 /* TableDirector */, EF2921A41E16595100E8F43B /* TimeInterval */, 67DC65071E979BA9002F2FFF /* UIActivityIndicator */, E126CBB11DB68D9A00E1B2F8 /* UICollectionView */, @@ -602,6 +643,7 @@ 67B856E21E923BE600F54304 /* ResettableType.swift */, 679DE48F1E9588B6006F25FE /* SupportProtocol.swift */, 67DC65031E979B34002F2FFF /* LoadingIndicatorProtocol.swift */, + 67A7B1961EAF5FF600E5BC59 /* DrawingOperation.swift */, ); path = Protocols; sourceTree = ""; @@ -641,6 +683,7 @@ isa = PBXGroup; children = ( 78B036401DA4D7060021D5CC /* UIImage+Extensions.swift */, + 674E7E641EB0F2E300D13340 /* UIImage+SupportExtensions.swift */, ); path = UIImage; sourceTree = ""; @@ -937,6 +980,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 67A7B1971EAF5FF600E5BC59 /* DrawingOperation.swift in Sources */, 787D874A1E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift in Sources */, 780F56CA1E0D76B8004530B6 /* Sequence+ConcurrentMap.swift in Sources */, 7837F60F1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift in Sources */, @@ -950,11 +994,11 @@ 78753E2C1DE58BF9006BC0FB /* StaticCursor.swift in Sources */, 78D4B54A1DA64EAB005B0764 /* Any+TypeName.swift in Sources */, 78CFEE571C5C45E500F50370 /* XibNameProtocol.swift in Sources */, + 67A7B1A11EAF67AE00E5BC59 /* SolidFillDrawingOperation.swift in Sources */, 67DC650C1E979C0A002F2FFF /* AnyLoadingIndicator.swift in Sources */, 788EC15A1CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift in Sources */, 787783671CA04D4A001CDC9B /* String+SizeCalculation.swift in Sources */, 7873D1511E112B0D001816EB /* Any+Cast.swift in Sources */, - 78B036431DA4FEC90021D5CC /* CGImage+Transform.swift in Sources */, 78011A641D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift in Sources */, 678A202A1E93C1A900787562 /* PaginationTableViewWrapper.swift in Sources */, 679DE4901E9588B6006F25FE /* SupportProtocol.swift in Sources */, @@ -962,21 +1006,28 @@ CAA707D51E2E614E0022D732 /* ModuleConfigurator.swift in Sources */, 675D24B21E9234BB00E92D1F /* PaginationViewModel.swift in Sources */, 78B0FC811C6B2CD500358B64 /* App.swift in Sources */, - 78B036491DA562C30021D5CC /* CGImage+Template.swift in Sources */, + 676D177E1EAE0661002E19F9 /* ResizeContentMode.swift in Sources */, CAE698C61E96775F000394B0 /* String+Extensions.swift in Sources */, 7873D14F1E1127BC001816EB /* LeadKitError.swift in Sources */, + 674E7E651EB0F2E300D13340 /* UIImage+SupportExtensions.swift in Sources */, + 67A7B1991EAF602900E5BC59 /* RoundDrawingOperation.swift in Sources */, 78753E301DE594B4006BC0FB /* MapCursor.swift in Sources */, 679DE4941E9613ED006F25FE /* UIScrollView+Support.swift in Sources */, 780D23461DA416F80084620D /* CGContext+Initializers.swift in Sources */, + 67A7B19B1EAF60B100E5BC59 /* BorderDrawingOperation.swift in Sources */, 95B39A861D9D51250057BD54 /* String+Localization.swift in Sources */, 78C36F7E1D801E3E00E7EBEA /* Double+Rounding.swift in Sources */, 67DC65061E979B70002F2FFF /* UIView+LoadingIndicator.swift in Sources */, + 67186B181EB1DC0500CFAFFB /* ResizeDrawingOperation.swift in Sources */, 787609221E1403830093CE36 /* Observable+DeferredJust.swift in Sources */, 67B305841E8A92E8008169CA /* XibView.swift in Sources */, 78C54AFD1E432EEF0051EFBA /* UIViewController+TopVisibleViewController.swift in Sources */, 67788F9F1E69661800484DEE /* CGFloat+Pixels.swift in Sources */, + 67A7B1951EAF5F9B00E5BC59 /* CGSize+CGContextSize.swift in Sources */, 783AF06B1E41CE6C00EC5ADE /* Observable+ToastErrorLogging.swift in Sources */, + 67A7B1A31EAF6B4600E5BC59 /* CALayerDrawingOperation.swift in Sources */, 78CFEE561C5C45E500F50370 /* ReuseIdentifierProtocol.swift in Sources */, + 67A7B19F1EAF646400E5BC59 /* PaddingDrawingOperation.swift in Sources */, 78A0FCC81DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift in Sources */, 78B036411DA4D7060021D5CC /* UIImage+Extensions.swift in Sources */, 78A0FCC71DC366A10070B5E1 /* StoryboardProtocol+DefaultBundle.swift in Sources */, @@ -985,6 +1036,8 @@ 78C36F811D8021DD00E7EBEA /* UIColor+Hex.swift in Sources */, 78CFEE5B1C5C45E500F50370 /* ViewModelProtocol.swift in Sources */, EF5FB5691E0141610030E4BE /* UIView+Rotation.swift in Sources */, + 67A7B1911EAF5F4900E5BC59 /* ImageDrawingOperation.swift in Sources */, + 67A7B1931EAF5F6A00E5BC59 /* TemplateDrawingOperation.swift in Sources */, 783AF06D1E41CF5B00EC5ADE /* NetworkService.swift in Sources */, 780F56CC1E0D7ACA004530B6 /* ObservableMappable.swift in Sources */, 780D23431DA412470084620D /* CGImage+Alpha.swift in Sources */, @@ -1003,15 +1056,14 @@ 67B856E31E923BE600F54304 /* ResettableType.swift in Sources */, 787783631CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift in Sources */, 675FB4251EA7797C0075BF3D /* Mutex.swift in Sources */, - 78B036471DA5624D0021D5CC /* CGImage+Creation.swift in Sources */, 789CC6081DE5835600F789D3 /* CursorType.swift in Sources */, 67DC650F1E979D0C002F2FFF /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift in Sources */, 67DC65041E979B34002F2FFF /* LoadingIndicatorProtocol.swift in Sources */, 78B0364B1DA61EDE0021D5CC /* CGImage+Crop.swift in Sources */, EDF3DE3F1EA4F2E80016F729 /* UIViewController+DefaultXibName.swift in Sources */, - 78B036451DA561D00021D5CC /* CGImage+Utils.swift in Sources */, 78CFEE591C5C45E500F50370 /* StoryboardIdentifierProtocol.swift in Sources */, 78011AB31D48B53600EA16A2 /* ApiRequestParameters.swift in Sources */, + 676D17811EAE137B002E19F9 /* CGSize+Resize.swift in Sources */, 78B0FC7D1C6B2BE200358B64 /* LogFormatter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Utils.swift b/LeadKit/LeadKit/Enums/ResizeContentMode.swift similarity index 57% rename from LeadKit/LeadKit/Extensions/CGImage/CGImage+Utils.swift rename to LeadKit/LeadKit/Enums/ResizeContentMode.swift index 9e78407e..03a65d1b 100644 --- a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Utils.swift +++ b/LeadKit/LeadKit/Enums/ResizeContentMode.swift @@ -2,7 +2,7 @@ // Copyright (c) 2017 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal +// of this software and associated documentation files (the Software), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is @@ -11,7 +11,7 @@ // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER @@ -20,24 +20,14 @@ // THE SOFTWARE. // -import CoreGraphics - -// The bitmapInfo value are hard-coded to prevent an "unsupported parameter combination" error - -public let alphaBitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) -public let opaqueBitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.none.rawValue) - -public enum ImageContentMode { +/// A type representing an possible strategies which can be used to resize content. +/// +/// - scaleToFill: The option to scale the content to fit the size of itself +/// by changing the aspect ratio of the content if necessary. +/// - scaleAspectFit: The option to scale the content to fit the size of the view +/// by maintaining the aspect ratio. Any remaining area of the view’s bounds is transparent. +/// - scaleAspectFill: The option to scale the content to fill the size of the view. +/// Some portion of the content may be clipped to fill the view’s bounds. +public enum ResizeMode { case scaleToFill, scaleAspectFit, scaleAspectFill } - -public extension CGImage { - - /** - - returns: bounds of image. - */ - var bounds: CGRect { - return CGRect(origin: CGPoint.zero, size: CGSize(width: width, height: height)) - } - -} diff --git a/LeadKit/LeadKit/Extensions/CGContext/CGContext+Initializers.swift b/LeadKit/LeadKit/Extensions/CGContext/CGContext+Initializers.swift index 3328a61e..4730ff15 100644 --- a/LeadKit/LeadKit/Extensions/CGContext/CGContext+Initializers.swift +++ b/LeadKit/LeadKit/Extensions/CGContext/CGContext+Initializers.swift @@ -22,14 +22,23 @@ import CoreGraphics +extension CGBitmapInfo { + + // The bitmapInfo value are hard-coded to prevent an "unsupported parameter combination" error + + static let alphaBitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) + static let opaqueBitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.none.rawValue) + +} + public extension CGContext { - /** - method which creates an instance of CGContext with parameters taken from a given image - - - parameter forCGImage: CGImage instance from which the parameters will be taken - - parameter fallbackColorSpace: fallback color space if image doesn't have it - */ + /// Creates a bitmap graphics context with parameters taken from a given image. + /// + /// - Parameters: + /// - cgImage: CGImage instance from which the parameters will be taken. + /// - fallbackColorSpace: Fallback color space if image doesn't have it. + /// - Returns: A new bitmap context, or NULL if a context could not be created. public static func create(forCGImage cgImage: CGImage, fallbackColorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB()) -> CGContext? { @@ -40,20 +49,20 @@ public extension CGContext { bitsPerComponent: cgImage.bitsPerComponent) } - /** - method which creates an instance of CGContext - - - parameter width: The width, in pixels, of the required bitmap. - - parameter height: The height, in pixels, of the required bitmap. - - parameter bitmapInfo: Constants that specify whether the bitmap should contain an alpha channel, - the alpha channel’s relative location in a pixel, - and information about whether the pixel components are floating-point or integer values. - - parameter colorSpace: The color space to use for the bitmap context. - - parameter bitsPerComponent: The number of bits to use for each component of a pixel in memory. - */ + /// Creates a bitmap graphics context. + /// + /// - Parameters: + /// - width: The width, in pixels, of the required bitmap. + /// - height: The height, in pixels, of the required bitmap. + /// - bitmapInfo: Constants that specify whether the bitmap should contain an alpha channel, + /// the alpha channel’s relative location in a pixel, + /// and information about whether the pixel components are floating-point or integer values. + /// - colorSpace: The color space to use for the bitmap context. + /// - bitsPerComponent: The number of bits to use for each component of a pixel in memory. + /// - Returns: A new bitmap context, or NULL if a context could not be created. public static func create(width: Int, height: Int, - bitmapInfo: CGBitmapInfo = alphaBitmapInfo, + bitmapInfo: CGBitmapInfo = .alphaBitmapInfo, colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB(), bitsPerComponent: Int = 8) -> CGContext? { diff --git a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Alpha.swift b/LeadKit/LeadKit/Extensions/CGImage/CGImage+Alpha.swift index ebeac4f9..a17cdf6e 100644 --- a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Alpha.swift +++ b/LeadKit/LeadKit/Extensions/CGImage/CGImage+Alpha.swift @@ -24,9 +24,7 @@ import CoreGraphics public extension CGImage { - /** - - returns: true if the image has an alpha layer. - */ + /// A Boolean value indicating whether the image data has an alpha channel. public var hasAlpha: Bool { switch alphaInfo { case .first, .last, .premultipliedFirst, .premultipliedLast: @@ -36,18 +34,4 @@ public extension CGImage { } } - /** - - returns: a copy of the given image, adding an alpha channel if it doesn't already have one. - */ - public func applyAlpha() -> CGImage? { - guard !hasAlpha else { - return self - } - - let ctx = CGContext.create(width: width, height: height, bitmapInfo: alphaBitmapInfo) - ctx?.draw(self, in: bounds) - - return ctx?.makeImage() - } - } diff --git a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Creation.swift b/LeadKit/LeadKit/Extensions/CGImage/CGImage+Creation.swift deleted file mode 100644 index 3273da34..00000000 --- a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Creation.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright (c) 2017 Touch Instinct -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import CoreGraphics - -public extension CGImage { - - /** - method which creates new CGImage instance filled by given color - - - parameter color: color to fill - - parameter width: width of new image - - parameter height: height of new image - - parameter opaque: a flag indicating whether the bitmap is opaque (default: False) - - - returns: new instanse of UIImage with given size and color - */ - public static func create(color: CGColor, - width: Int, - height: Int, - opaque: Bool = false) -> CGImage? { - - let context = CGContext.create(width: width, - height: height, - bitmapInfo: opaque ? opaqueBitmapInfo : alphaBitmapInfo) - - guard let ctx = context else { - return nil - } - - ctx.setFillColor(color) - ctx.fill(CGRect(origin: CGPoint.zero, size: CGSize(width: width, height: height))) - - return ctx.makeImage() - } - - /** - creates an image from a UIView. - - - parameter fromView: The source view. - - - returns A new image - */ - public static func create(fromView view: UIView) -> CGImage? { - let size = view.bounds.size - - let ctxWidth = Int(ceil(size.width)) - let ctxHeight = Int(ceil(size.height)) - - guard let ctx = CGContext.create(width: ctxWidth, height: ctxHeight) else { - return nil - } - - ctx.translateBy(x: 0, y: size.height) - ctx.scaleBy(x: 1.0, y: -1.0) - - view.layer.render(in: ctx) - - return ctx.makeImage() - } - -} diff --git a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Crop.swift b/LeadKit/LeadKit/Extensions/CGImage/CGImage+Crop.swift index 367a8f3b..2ebffa4c 100644 --- a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Crop.swift +++ b/LeadKit/LeadKit/Extensions/CGImage/CGImage+Crop.swift @@ -24,11 +24,9 @@ import CoreGraphics public extension CGImage { - /** - crop image to square from center - - - returns: cropped image - */ + /// Crop image to square from center. + /// + /// - Returns: A new cropped image or nil if something goes wrong. public func cropFromCenterToSquare() -> CGImage? { let shortest = min(width, height) @@ -46,4 +44,25 @@ public extension CGImage { return cropping(to: cropRect) } + /// Crop image with given margin values. + /// + /// - Parameters: + /// - top: Top margin. + /// - left: Left margin. + /// - bottom: Bottom margin. + /// - right: Right margin. + /// - Returns: A new CGImage cropped with given paddings or nil if something goes wrong. + public func crop(top: CGFloat = 0, + left: CGFloat = 0, + bottom: CGFloat = 0, + right: CGFloat = 0) -> CGImage? { + + let rect = CGRect(x: left, + y: top, + width: CGFloat(width) - left - right, + height: CGFloat(height) - top - bottom) + + return cropping(to: rect) + } + } diff --git a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Transform.swift b/LeadKit/LeadKit/Extensions/CGImage/CGImage+Transform.swift deleted file mode 100644 index f0675fe2..00000000 --- a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Transform.swift +++ /dev/null @@ -1,203 +0,0 @@ -// -// Copyright (c) 2017 Touch Instinct -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import CoreGraphics - -public extension CGImage { - - /** - creates a new image with rounded corners. - - - parameter withRadius: The corner radius. - - - returns: A new image - */ - public func round(withRadius radius: CGFloat) -> CGImage? { - guard let ctx = CGContext.create(forCGImage: self) ?? CGContext.create(width: width, height: height) else { - return nil - } - - ctx.addPath(UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath) - ctx.clip() - ctx.draw(self, in: bounds) - - return ctx.makeImage() - } - - /** - creates a new image with a border. - - - parameter width: The size of the border. - - parameter color: The color of the border. - - parameter radius: The corner radius. - - parameter extendSize: Extend result image size and don't overlap source image by border. - - - returns: A new image - */ - public func applyBorder(width border: CGFloat, - color: CGColor, - radius: CGFloat = 0, - extendSize: Bool = false) -> CGImage? { - - let offset = extendSize ? border : 0 - - let newWidth = CGFloat(width) + offset * 2 - let newHeight = CGFloat(height) + offset * 2 - - let ctxWidth = Int(ceil(newWidth)) - let ctxHeight = Int(ceil(newHeight)) - - let ctxRect: CGRect = CGRect(origin: CGPoint.zero, size: CGSize(width: newWidth, height: newHeight)) - - let context = CGContext.create(width: ctxWidth, - height: ctxHeight, - bitmapInfo: bitmapInfo, - colorSpace: colorSpace ?? CGColorSpaceCreateDeviceRGB(), - bitsPerComponent: bitsPerComponent) - - guard let ctx = context ?? CGContext.create(width: width, height: height) else { - return nil - } - - ctx.draw(self, in: CGRect(x: offset, y: offset, width: CGFloat(width), height: CGFloat(height))) - - ctx.setStrokeColor(color) - - let widthDiff = CGFloat(ctxWidth) - newWidth // difference between context width and real width - let heightDiff = CGFloat(ctxWidth) - newWidth // difference between context height and real height - - let inset = ctxRect.insetBy(dx: border / 2 + widthDiff, dy: border / 2 + heightDiff) - - if radius != 0 { - ctx.setLineWidth(border) - ctx.addPath(UIBezierPath(roundedRect: inset, cornerRadius: radius).cgPath) - ctx.strokePath() - } else { - ctx.stroke(inset, width: border) - } - - return ctx.makeImage() - } - - /** - creates a resized copy of an image. - - - parameter newSize: the new size of the image. - - parameter contentMode: the way to handle the content in the new size. - - - returns: a new image - */ - public func resize(newSize: CGSize, contentMode: ImageContentMode = .scaleToFill) -> CGImage? { - let ctxWidth = Int(ceil(newSize.width)) - let ctxHeight = Int(ceil(newSize.height)) - - let context = CGContext.create(width: ctxWidth, - height: ctxHeight, - bitmapInfo: bitmapInfo, - colorSpace: colorSpace ?? CGColorSpaceCreateDeviceRGB(), - bitsPerComponent: bitsPerComponent) - - guard let ctx = context ?? CGContext.create(width: ctxWidth, height: ctxHeight) else { - return nil - } - - let horizontalRatio = newSize.width / CGFloat(width) - let verticalRatio = newSize.height / CGFloat(height) - - let ratio: CGFloat - - switch contentMode { - case .scaleToFill: - ratio = 1 - case .scaleAspectFill: - ratio = max(horizontalRatio, verticalRatio) - case .scaleAspectFit: - ratio = min(horizontalRatio, verticalRatio) - } - - let newImageWidth = contentMode == .scaleToFill ? newSize.width : CGFloat(width) * ratio - let newImageHeight = contentMode == .scaleToFill ? newSize.height : CGFloat(height) * ratio - - let originX: CGFloat - let originY: CGFloat - - if newImageWidth > newSize.width { - originX = (newSize.width - newImageWidth) / 2 - } else if newImageWidth < newSize.width { - originX = newSize.width / 2 - newImageWidth / 2 - } else { - originX = 0 - } - - if newImageHeight > newSize.height { - originY = (newSize.height - newImageHeight) / 2 - } else if newImageHeight < newSize.height { - originY = newSize.height / 2 - newImageHeight / 2 - } else { - originY = 0 - } - - let rect = CGRect(origin: CGPoint(x: originX, y: originY), - size: CGSize(width: newImageWidth, height: newImageHeight)) - - ctx.interpolationQuality = .high - ctx.draw(self, in: rect) - - return ctx.makeImage() - } - - /** - returns a copy of the image with border of the given size added around its edges. - - - parameter padding: The padding amount. - - - returns: A new image. - */ - public func applyPadding(_ padding: CGFloat) -> CGImage? { - let ctxWidth = Int(ceil(CGFloat(width) + padding * 2)) - let ctxHeight = Int(ceil(CGFloat(height) + padding * 2)) - - let context = CGContext.create(width: ctxWidth, - height: ctxHeight, - bitmapInfo: bitmapInfo, - colorSpace: colorSpace ?? CGColorSpaceCreateDeviceRGB(), - bitsPerComponent: bitsPerComponent) - - guard let ctx = context ?? CGContext.create(width: ctxWidth, height: ctxHeight) else { - return nil - } - - // Draw the image in the center of the context, leaving a gap around the edges - let imageLocation = CGRect(x: padding, - y: padding, - width: CGFloat(width), - height: CGFloat(height)) - - ctx.addRect(imageLocation) - ctx.clip() - - ctx.draw(self, in: imageLocation) - - return ctx.makeImage() - } - -} diff --git a/LeadKit/LeadKit/Extensions/CGSize/CGSize+CGContextSize.swift b/LeadKit/LeadKit/Extensions/CGSize/CGSize+CGContextSize.swift new file mode 100644 index 00000000..5d6aef4c --- /dev/null +++ b/LeadKit/LeadKit/Extensions/CGSize/CGSize+CGContextSize.swift @@ -0,0 +1,31 @@ +// +// Copyright (c) 2017 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import CoreGraphics.CGGeometry + +public extension CGSize { + + var ceiledContextSize: CGContextSize { + return (width: Int(ceil(width)), height: Int(ceil(height))) + } + +} diff --git a/LeadKit/LeadKit/Extensions/CGSize/CGSize+Resize.swift b/LeadKit/LeadKit/Extensions/CGSize/CGSize+Resize.swift new file mode 100644 index 00000000..84f7909c --- /dev/null +++ b/LeadKit/LeadKit/Extensions/CGSize/CGSize+Resize.swift @@ -0,0 +1,74 @@ +// +// Copyright (c) 2017 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import CoreGraphics.CGGeometry + +public extension CGSize { + + /// Calculates rect "inside" CGSize to match requested size with accordance to resize mode. + /// + /// - Parameters: + /// - newSize: Requested new size. + /// - resizeMode: Resize mode to use. + /// - Returns: A new CGRect instance matching request parameters. + public func resizeRect(forNewSize newSize: CGSize, resizeMode: ResizeMode) -> CGRect { + let horizontalRatio = newSize.width / width + let verticalRatio = newSize.height / height + + let ratio: CGFloat + + switch resizeMode { + case .scaleToFill: + ratio = 1 + case .scaleAspectFill: + ratio = max(horizontalRatio, verticalRatio) + case .scaleAspectFit: + ratio = min(horizontalRatio, verticalRatio) + } + + let newWidth = resizeMode == .scaleToFill ? newSize.width : width * ratio + let newHeight = resizeMode == .scaleToFill ? newSize.height : height * ratio + + let originX: CGFloat + let originY: CGFloat + + if newWidth > newSize.width { + originX = (newSize.width - newWidth) / 2 + } else if newWidth < newSize.width { + originX = newSize.width / 2 - newWidth / 2 + } else { + originX = 0 + } + + if newHeight > newSize.height { + originY = (newSize.height - newHeight) / 2 + } else if newHeight < newSize.height { + originY = newSize.height / 2 - newHeight / 2 + } else { + originY = 0 + } + + return CGRect(origin: CGPoint(x: originX, y: originY), + size: CGSize(width: newWidth, height: newHeight)) + } + +} diff --git a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Extensions.swift b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Extensions.swift index abfaa5c2..5de1f826 100644 --- a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Extensions.swift +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Extensions.swift @@ -22,148 +22,218 @@ import UIKit +@available(iOS 10.0, *) public extension UIImage { - /** - method which creates new UIImage instance filled by given color + /// Creates an image filled by given color. + /// + /// - Parameters: + /// - color: The color to fill + /// - size: The size of an new image. + /// - scale: The scale of image. + /// - Returns: A new instanse of UIImage with given size and color. + public static func imageWith(color: UIColor, size: CGSize) -> UIImage { + let width = Int(ceil(size.width)) + let height = Int(ceil(size.height)) - - parameter color: color to fill - - parameter size: size of new image + let operation = SolidFillDrawingOperation(color: color.cgColor, width: width, height: height) - - returns: new instanse of UIImage with given size and color - */ + return operation.imageFromNewRenderer(scale: UIScreen.main.scale) + } - public convenience init?(color: UIColor, size: CGSize) { - let cgImage = CGImage.create(color: color.cgColor, - width: Int(ceil(size.width)), - height: Int(ceil(size.height))) + /// Creates an image from a UIView. + /// + /// - Parameter fromView: The source view. + /// - Returns: A new instance of UIImage or nil if something goes wrong. + public static func imageFrom(view: UIView) -> UIImage { + let operation = CALayerDrawingOperation(layer: view.layer, size: view.bounds.size) + return operation.imageFromNewRenderer(scale: UIScreen.main.scale) + } + + /// Render current template UIImage into new image using given color. + /// + /// - Parameter color: Color to fill template image. + /// - Returns: A new UIImage rendered with given color. + public func renderTemplate(withColor color: UIColor) -> UIImage { guard let image = cgImage else { - return nil + return self } - self.init(cgImage: image) + let operation = TemplateDrawingOperation(image: image, + imageSize: size, + color: color.cgColor) + + return operation.imageFromNewRenderer(scale: scale) } - /** - creates an image from a UIView. - - - parameter fromView: The source view. - - - returns A new image or nil if something goes wrong. - */ - - public convenience init?(fromView view: UIView) { - guard let cgImage = CGImage.create(fromView: view) else { - return nil - } - - self.init(cgImage: cgImage) - } - - /** - method which render current template CGImage into new image using given color - - - parameter withColor: color which used to fill template image - - - returns: new CGImage rendered with given color or nil if something goes wrong - */ - public func renderTemplate(withColor color: UIColor) -> UIImage? { - return cgImage?.renderTemplate(withColor: color.cgColor)?.uiImage - } - - /** - creates a new image with rounded corners and border. - - - parameter cornerRadius: The corner radius. - - parameter border: The size of the border. - - parameter color: The color of the border. - - parameter extendSize: Extend result image size and don't overlap source image by border. - - - returns: A new image - */ + /// Creates a new image with rounded corners and border. + /// + /// - Parameters: + /// - cornerRadius: The corner radius. + /// - borderWidth: The size of the border. + /// - color: The color of the border. + /// - extendSize: Extend result image size and don't overlap source image by border. + /// - Returns: A new image with rounded corners. public func roundCorners(cornerRadius: CGFloat, borderWidth: CGFloat, color: UIColor, - extendSize: Bool = false) -> UIImage? { + extendSize: Bool = false) -> UIImage { - let rounded = cgImage?.round(withRadius: cornerRadius) + guard let image = cgImage else { + return self + } - return rounded?.applyBorder(width: borderWidth, - color: color.cgColor, - radius: cornerRadius, - extendSize: extendSize)?.uiImage + let roundOperation = RoundDrawingOperation(image: image, + imageSize: size, + radius: cornerRadius) + + guard let roundImage = roundOperation.imageFromNewRenderer(scale: scale).cgImage else { + return self + } + + let borderOperation = BorderDrawingOperation(image: roundImage, + imageSize: size, + border: borderWidth, + color: color.cgColor, + radius: cornerRadius, + extendSize: extendSize) + + return borderOperation.imageFromNewRenderer(scale: scale) } - /** - creates a new circle image. - - - returns: A new image - */ - public func roundCornersToCircle() -> UIImage? { - return cgImage?.round(withRadius: CGFloat(min(size.width, size.height) / 2))?.uiImage - } - - /** - creates a new circle image with a border. - - - parameter border: CGFloat The size of the border. - - parameter color: UIColor The color of the border. - - parameter extendSize: Extend result image size and don't overlap source image by border. - - - returns: UIImage? - */ - public func roundCornersToCircle(borderWidth: CGFloat, - borderColor: UIColor, - extendSize: Bool = false) -> UIImage? { + /// Creates a new circle image. + /// + /// - Returns: A new circled image. + public func roundCornersToCircle() -> UIImage { + guard let image = cgImage else { + return self + } let radius = CGFloat(min(size.width, size.height) / 2) - let rounded = cgImage?.round(withRadius: radius) - return rounded?.applyBorder(width: borderWidth, - color: borderColor.cgColor, - radius: radius, - extendSize: extendSize)?.uiImage + let operation = RoundDrawingOperation(image: image, + imageSize: size, + radius: radius) + + return operation.imageFromNewRenderer(scale: scale).redraw() } - /** - creates a resized copy of an image. + /// Creates a new circle image with a border. + /// + /// - Parameters: + /// - borderWidth: The size of the border. + /// - borderColor: The color of the border. + /// - extendSize: Extend result image size and don't overlap source image by border (default = false). + /// - Returns: A new image with rounded corners or nil if something goes wrong. + public func roundCornersToCircle(borderWidth: CGFloat, + borderColor: UIColor, + extendSize: Bool = false) -> UIImage { - - parameter newSize: the new size of the image. - - parameter contentMode: the way to handle the content in the new size. + guard let image = cgImage else { + return self + } - - returns: a new image - */ - public func resize(newSize: CGSize, contentMode: ImageContentMode = .scaleToFill) -> UIImage? { - return cgImage?.resize(newSize: newSize, contentMode: contentMode)?.uiImage + let radius = CGFloat(min(size.width, size.height) / 2) + + let roundOperation = RoundDrawingOperation(image: image, + imageSize: size, + radius: radius) + + guard let roundImage = roundOperation.imageFromNewRenderer(scale: scale).cgImage else { + return self + } + + let borderOperation = BorderDrawingOperation(image: roundImage, + imageSize: size, + border: borderWidth, + color: borderColor.cgColor, + radius: radius, + extendSize: extendSize) + + return borderOperation.imageFromNewRenderer(scale: scale) } - /** - creates a cropped copy of an image. + /// Creates a resized copy of an image. + /// + /// - Parameters: + /// - newSize: The new size of the image. + /// - contentMode: The way to handle the content in the new size. + /// - cropToImageBounds: Should output image size match resized image size. + /// Note: If passed true with ResizeMode.scaleAspectFit content mode it will give the original image. + /// - Returns: A new image scaled to new size. + public func resize(newSize: CGSize, + contentMode: ResizeMode = .scaleToFill, + cropToImageBounds: Bool = false) -> UIImage { - - parameter to: The bounds of the rectangle inside the image. + guard let image = cgImage else { + return self + } - - returns: A new image - */ - public func crop(to bounds: CGRect) -> UIImage? { - return cgImage?.cropping(to: bounds)?.uiImage + let operation = ResizeDrawingOperation(image: image, + imageSize: size, + preferredNewSize: newSize, + resizeMode: contentMode, + cropToImageBounds: cropToImageBounds) + + return operation.imageFromNewRenderer(scale: scale).redraw() } - /** - crop image to square from center + /// Adds an alpha channel if UIImage doesn't already have one. + /// + /// - Returns: A copy of the given image, adding an alpha channel if it doesn't already have one. + public func applyAlpha() -> UIImage { + guard let image = cgImage, !image.hasAlpha else { + return self + } - - returns: cropped image - */ - public func cropFromCenterToSquare() -> UIImage? { - return cgImage?.cropFromCenterToSquare()?.uiImage + let operation = ImageDrawingOperation(image: image, + newSize: size, + opaque: false) + + return operation.imageFromNewRenderer(scale: scale).redraw() + } + + /// Creates a copy of the image with border of the given size added around its edges. + /// + /// - Parameter padding: The padding amount. + /// - Returns: A new padded image or nil if something goes wrong. + public func applyPadding(_ padding: CGFloat) -> UIImage { + guard let image = cgImage else { + return self + } + + let operation = PaddingDrawingOperation(image: image, imageSize: size, padding: padding) + + return operation.imageFromNewRenderer(scale: scale).redraw() + } + + /// Workaround to fix flipped image rendering (by Y) + private func redraw() -> UIImage { + guard let image = cgImage else { + return self + } + + let operation = ImageDrawingOperation(image: image, newSize: size) + + return operation.imageFromNewRenderer(scale: scale) } } -public extension CGImage { +@available(iOS 10.0, *) +private extension DrawingOperation { - public var uiImage: UIImage { - return UIImage(cgImage: self) + func imageFromNewRenderer(scale: CGFloat) -> UIImage { + let ctxSize = contextSize + let size = CGSize(width: ctxSize.width, height: ctxSize.height) + let format = UIGraphicsImageRendererFormat() + format.opaque = opaque + format.scale = scale + + return UIGraphicsImageRenderer(size: size, format: format).image { + self.apply(in: $0.cgContext) + } } } diff --git a/LeadKit/LeadKit/Extensions/UIImage/UIImage+SupportExtensions.swift b/LeadKit/LeadKit/Extensions/UIImage/UIImage+SupportExtensions.swift new file mode 100644 index 00000000..3db45324 --- /dev/null +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+SupportExtensions.swift @@ -0,0 +1,257 @@ +// +// Copyright (c) 2017 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit + +public extension Support where Base: UIImage { + + /// Creates an image filled by given color. + /// + /// - Parameters: + /// - color: The color to fill + /// - size: The size of an new image. + /// - Returns: A new instanse of UIImage with given size and color or nil if something goes wrong. + public static func imageWith(color: UIColor, size: CGSize) -> Support? { + let width = Int(ceil(size.width)) + let height = Int(ceil(size.height)) + + let operation = SolidFillDrawingOperation(color: color.cgColor, width: width, height: height) + + return operation.imageFromNewContext(scale: UIScreen.main.scale)?.support + } + + /// Creates an image from a UIView. + /// + /// - Parameter fromView: The source view. + /// - Returns: A new instance of UIImage or nil if something goes wrong. + public static func imageFrom(view: UIView) -> Support? { + let layerDrawingOperation = CALayerDrawingOperation(layer: view.layer, size: view.bounds.size) + + return layerDrawingOperation.imageFromNewContext(scale: UIScreen.main.scale)?.support.flipY() + } + + /// Render current template UIImage into new image using given color. + /// + /// - Parameter color: Color to fill template image. + /// - Returns: A new UIImage rendered with given color or nil if something goes wrong. + public func renderTemplate(withColor color: UIColor) -> Support? { + guard let image = base.cgImage else { + return Support(base) + } + + let operation = TemplateDrawingOperation(image: image, + imageSize: base.size, + color: color.cgColor) + + return operation.imageFromNewContext(scale: base.scale)?.support + } + + /// Creates a new image with rounded corners and border. + /// + /// - Parameters: + /// - cornerRadius: The corner radius. + /// - borderWidth: The size of the border. + /// - color: The color of the border. + /// - extendSize: Extend result image size and don't overlap source image by border. + /// - Returns: A new image with rounded corners or nil if something goes wrong. + public func roundCorners(cornerRadius: CGFloat, + borderWidth: CGFloat, + color: UIColor, + extendSize: Bool = false) -> Support? { + + guard let image = base.cgImage else { + return Support(base) + } + + let roundOperation = RoundDrawingOperation(image: image, + imageSize: base.size, + radius: cornerRadius) + + guard let roundImage = roundOperation.cgImageFromNewContext(scale: base.scale) else { + return nil + } + + let borderOperation = BorderDrawingOperation(image: roundImage, + imageSize: base.size, + border: borderWidth, + color: color.cgColor, + radius: cornerRadius, + extendSize: extendSize) + + return borderOperation.imageFromNewContext(scale: base.scale)?.support + } + + /// Creates a new circle image. + /// + /// - Returns: A new circled image or nil if something goes wrong. + public func roundCornersToCircle() -> Support? { + guard let image = base.cgImage else { + return Support(base) + } + + let radius = CGFloat(min(base.size.width, base.size.height) / 2) + + let operation = RoundDrawingOperation(image: image, + imageSize: base.size, + radius: radius) + + return operation.imageFromNewContext(scale: base.scale)?.support + } + + /// Creates a new circle image with a border. + /// + /// - Parameters: + /// - borderWidth: The size of the border. + /// - borderColor: The color of the border. + /// - extendSize: Extend result image size and don't overlap source image by border (default = false). + /// - Returns: A new image with rounded corners or nil if something goes wrong. + public func roundCornersToCircle(borderWidth: CGFloat, + borderColor: UIColor, + extendSize: Bool = false) -> Support? { + + guard let image = base.cgImage else { + return Support(base) + } + + let radius = CGFloat(min(base.size.width, base.size.height) / 2) + + let roundOperation = RoundDrawingOperation(image: image, + imageSize: base.size, + radius: radius) + + guard let roundImage = roundOperation.cgImageFromNewContext(scale: base.scale) else { + return nil + } + + let borderOperation = BorderDrawingOperation(image: roundImage, + imageSize: base.size, + border: borderWidth, + color: borderColor.cgColor, + radius: radius, + extendSize: extendSize) + + return borderOperation.imageFromNewContext(scale: base.scale)?.support + } + + /// Creates a resized copy of an image. + /// + /// - Parameters: + /// - newSize: The new size of the image. + /// - contentMode: The way to handle the content in the new size. + /// - cropToImageBounds: Should output image size match resized image size. + /// Note: If passed true with ResizeMode.scaleAspectFit content mode it will give the original image. + /// - Returns: A new image scaled to new size. + public func resize(newSize: CGSize, + contentMode: ResizeMode = .scaleToFill, + cropToImageBounds: Bool = false) -> Support? { + + guard let image = base.cgImage else { + return Support(base) + } + + let operation = ResizeDrawingOperation(image: image, + imageSize: base.size, + preferredNewSize: newSize, + resizeMode: contentMode, + cropToImageBounds: cropToImageBounds) + + return operation.imageFromNewContext(scale: base.scale)?.support + } + + /// Adds an alpha channel if UIImage doesn't already have one. + /// + /// - Returns: A copy of the given image, adding an alpha channel if it doesn't already have one. + public func applyAlpha() -> Support? { + guard let image = base.cgImage, !image.hasAlpha else { + return Support(base) + } + + let operation = ImageDrawingOperation(image: image, + newSize: base.size, + opaque: false) + + return operation.imageFromNewContext(scale: base.scale)?.support + } + + /// Creates a copy of the image with border of the given size added around its edges. + /// + /// - Parameter padding: The padding amount. + /// - Returns: A new padded image or nil if something goes wrong. + public func applyPadding(_ padding: CGFloat) -> Support? { + guard let image = base.cgImage else { + return Support(base) + } + + let operation = PaddingDrawingOperation(image: image, + imageSize: base.size, + padding: padding) + + return operation.imageFromNewContext(scale: base.scale)?.support + } + + private func flipY() -> Support? { + guard let image = base.cgImage else { + return Support(base) + } + + let flipOperation = ImageDrawingOperation(image: image, + newSize: base.size, + origin: .zero, + opaque: false, + flipY: true) + + return flipOperation.imageFromNewContext(scale: base.scale)?.support + } + +} + +private extension DrawingOperation { + + func cgImageFromNewContext(scale: CGFloat) -> CGImage? { + let ctxSize = contextSize + + let intScale = Int(scale) + + let context = CGContext.create(width: ctxSize.width * intScale, + height: ctxSize.height * intScale, + bitmapInfo: opaque ? .opaqueBitmapInfo : .alphaBitmapInfo) + + guard let ctx = context else { + return nil + } + + ctx.scaleBy(x: scale, y: scale) + + apply(in: ctx) + + return ctx.makeImage() + } + + func imageFromNewContext(scale: CGFloat) -> UIImage? { + guard let image = cgImageFromNewContext(scale: scale) else { + return nil + } + + return UIImage(cgImage: image, scale: scale, orientation: .up) + } + +} diff --git a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Template.swift b/LeadKit/LeadKit/Protocols/DrawingOperation.swift similarity index 51% rename from LeadKit/LeadKit/Extensions/CGImage/CGImage+Template.swift rename to LeadKit/LeadKit/Protocols/DrawingOperation.swift index b1380e28..9a67f2e1 100644 --- a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Template.swift +++ b/LeadKit/LeadKit/Protocols/DrawingOperation.swift @@ -2,7 +2,7 @@ // Copyright (c) 2017 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal +// of this software and associated documentation files (the Software), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is @@ -11,7 +11,7 @@ // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER @@ -22,32 +22,29 @@ import CoreGraphics -public extension CGImage { +public typealias CGContextSize = (width: Int, height: Int) - /** - method which render current template CGImage into new image using given color +/// Abstract drawing operation that can can be applied in CGContext. +/// Reports required size and opaque attibute for context. +public protocol DrawingOperation { - - parameter withColor: color which used to fill template image + /// Required context size. + var contextSize: CGContextSize { get } - - returns: new CGImage rendered with given color or nil if something goes wrong - */ - public func renderTemplate(withColor color: CGColor) -> CGImage? { - guard let ctx = CGContext.create(forCGImage: self) ?? CGContext.create(width: width, height: height) else { - return nil - } + /// Reports: should context be opaque. + var opaque: Bool { get } - let imageRect = bounds + /// Applies drawing operation in given CGContext instance. + /// + /// - Parameter context: CGContext to perform drawing manipulations. + func apply(in context: CGContext) - ctx.setFillColor(color) +} - ctx.translateBy(x: 0, y: CGFloat(height)) - ctx.scaleBy(x: 1.0, y: -1.0) - ctx.clip(to: imageRect, mask: self) - ctx.fill(imageRect) +extension DrawingOperation { - ctx.setBlendMode(.multiply) - - return ctx.makeImage() + var opaque: Bool { + return false } } diff --git a/LeadKit/LeadKit/Structures/DrawingOperations/BorderDrawingOperation.swift b/LeadKit/LeadKit/Structures/DrawingOperations/BorderDrawingOperation.swift new file mode 100644 index 00000000..885449c4 --- /dev/null +++ b/LeadKit/LeadKit/Structures/DrawingOperations/BorderDrawingOperation.swift @@ -0,0 +1,88 @@ +// +// Copyright (c) 2017 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import CoreGraphics + +struct BorderDrawingOperation: DrawingOperation { + + private let image: CGImage + private let imageSize: CGSize + private let border: CGFloat + private let color: CGColor + private let radius: CGFloat + private let extendSize: Bool + + public init(image: CGImage, + imageSize: CGSize, + border: CGFloat, + color: CGColor, + radius: CGFloat, + extendSize: Bool) { + + self.image = image + self.imageSize = imageSize + self.border = border + self.color = color + self.radius = radius + self.extendSize = extendSize + } + + public var contextSize: CGContextSize { + let offset = extendSize ? border : 0 + + let width = imageSize.width + offset * 2 + let height = imageSize.height + offset * 2 + + return CGSize(width: width, height: height).ceiledContextSize + } + + public func apply(in context: CGContext) { + let offset = extendSize ? border : 0 + + let newWidth = imageSize.width + offset * 2 + let newHeight = imageSize.height + offset * 2 + + let ctxSize = contextSize + + let ctxRect = CGRect(origin: .zero, size: CGSize(width: ctxSize.width, height: ctxSize.height)) + + let imageRect = CGRect(x: offset, y: offset, width: imageSize.width, height: imageSize.height) + + context.draw(image, in: imageRect) + + context.setStrokeColor(color) + + let widthDiff = CGFloat(ctxSize.width) - newWidth // difference between context width and real width + let heightDiff = CGFloat(ctxSize.height) - newHeight // difference between context height and real height + + let inset = ctxRect.insetBy(dx: border / 2 + widthDiff, dy: border / 2 + heightDiff) + + if radius != 0 { + context.setLineWidth(border) + context.addPath(UIBezierPath(roundedRect: inset, cornerRadius: radius).cgPath) + context.strokePath() + } else { + context.stroke(inset, width: border) + } + } + +} diff --git a/LeadKit/LeadKit/Structures/DrawingOperations/CALayerDrawingOperation.swift b/LeadKit/LeadKit/Structures/DrawingOperations/CALayerDrawingOperation.swift new file mode 100644 index 00000000..d6abf448 --- /dev/null +++ b/LeadKit/LeadKit/Structures/DrawingOperations/CALayerDrawingOperation.swift @@ -0,0 +1,43 @@ +// +// Copyright (c) 2017 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import CoreGraphics + +struct CALayerDrawingOperation: DrawingOperation { + + private let layer: CALayer + private let size: CGSize + + public init(layer: CALayer, size: CGSize) { + self.layer = layer + self.size = size + } + + public var contextSize: CGContextSize { + return size.ceiledContextSize + } + + public func apply(in context: CGContext) { + layer.render(in: context) + } + +} diff --git a/LeadKit/LeadKit/Structures/DrawingOperations/ImageDrawingOperation.swift b/LeadKit/LeadKit/Structures/DrawingOperations/ImageDrawingOperation.swift new file mode 100644 index 00000000..9831c050 --- /dev/null +++ b/LeadKit/LeadKit/Structures/DrawingOperations/ImageDrawingOperation.swift @@ -0,0 +1,60 @@ +// +// Copyright (c) 2017 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import CoreGraphics + +struct ImageDrawingOperation: DrawingOperation { + + private let image: CGImage + private let newSize: CGSize + private let origin: CGPoint + public let opaque: Bool + private let flipY: Bool + + public init(image: CGImage, + newSize: CGSize, + origin: CGPoint = .zero, + opaque: Bool = false, + flipY: Bool = false) { + + self.image = image + self.newSize = newSize + self.origin = origin + self.opaque = opaque + self.flipY = flipY + } + + public var contextSize: CGContextSize { + return newSize.ceiledContextSize + } + + public func apply(in context: CGContext) { + if flipY { + context.translateBy(x: 0, y: newSize.height) + context.scaleBy(x: 1.0, y: -1.0) + } + + context.interpolationQuality = .high + context.draw(image, in: CGRect(origin: origin, size: newSize)) + } + +} diff --git a/LeadKit/LeadKit/Structures/DrawingOperations/PaddingDrawingOperation.swift b/LeadKit/LeadKit/Structures/DrawingOperations/PaddingDrawingOperation.swift new file mode 100644 index 00000000..89103c29 --- /dev/null +++ b/LeadKit/LeadKit/Structures/DrawingOperations/PaddingDrawingOperation.swift @@ -0,0 +1,57 @@ +// +// Copyright (c) 2017 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import CoreGraphics + +struct PaddingDrawingOperation: DrawingOperation { + + private let image: CGImage + private let imageSize: CGSize + private let padding: CGFloat + + public init(image: CGImage, imageSize: CGSize, padding: CGFloat) { + self.image = image + self.imageSize = imageSize + self.padding = padding + } + + public var contextSize: CGContextSize { + let width = Int(ceil(imageSize.width + padding * 2)) + let height = Int(ceil(imageSize.height + padding * 2)) + + return (width: width, height: height) + } + + public func apply(in context: CGContext) { + // Draw the image in the center of the context, leaving a gap around the edges + let imageLocation = CGRect(x: padding, + y: padding, + width: imageSize.width, + height: imageSize.height) + + context.addRect(imageLocation) + context.clip() + + context.draw(image, in: imageLocation) + } + +} diff --git a/LeadKit/LeadKit/Structures/DrawingOperations/ResizeDrawingOperation.swift b/LeadKit/LeadKit/Structures/DrawingOperations/ResizeDrawingOperation.swift new file mode 100644 index 00000000..ed7885c1 --- /dev/null +++ b/LeadKit/LeadKit/Structures/DrawingOperations/ResizeDrawingOperation.swift @@ -0,0 +1,55 @@ +// +// Copyright (c) 2017 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import CoreGraphics + +struct ResizeDrawingOperation: DrawingOperation { + + private let image: CGImage + private let drawRect: CGRect + public let contextSize: CGContextSize + + public init(image: CGImage, + imageSize: CGSize, + preferredNewSize: CGSize, + resizeMode: ResizeMode, + cropToImageBounds: Bool = false) { + + self.image = image + + let resizedRect = imageSize.resizeRect(forNewSize: preferredNewSize, resizeMode: resizeMode) + + if cropToImageBounds { + drawRect = CGRect(origin: .zero, size: resizedRect.size) + contextSize = resizedRect.size.ceiledContextSize + } else { + drawRect = resizedRect + contextSize = preferredNewSize.ceiledContextSize + } + } + + public func apply(in context: CGContext) { + context.interpolationQuality = .high + context.draw(image, in: drawRect) + } + +} diff --git a/LeadKit/LeadKit/Structures/DrawingOperations/RoundDrawingOperation.swift b/LeadKit/LeadKit/Structures/DrawingOperations/RoundDrawingOperation.swift new file mode 100644 index 00000000..b5867a88 --- /dev/null +++ b/LeadKit/LeadKit/Structures/DrawingOperations/RoundDrawingOperation.swift @@ -0,0 +1,49 @@ +// +// Copyright (c) 2017 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import CoreGraphics + +struct RoundDrawingOperation: DrawingOperation { + + private let image: CGImage + private let imageSize: CGSize + private let radius: CGFloat + + public init(image: CGImage, imageSize: CGSize, radius: CGFloat) { + self.image = image + self.imageSize = imageSize + self.radius = radius + } + + public var contextSize: CGContextSize { + return imageSize.ceiledContextSize + } + + public func apply(in context: CGContext) { + let imageLocation = CGRect(origin: .zero, size: imageSize) + + context.addPath(UIBezierPath(roundedRect: imageLocation, cornerRadius: radius).cgPath) + context.clip() + context.draw(image, in: imageLocation) + } + +} diff --git a/LeadKit/LeadKit/Structures/DrawingOperations/SolidFillDrawingOperation.swift b/LeadKit/LeadKit/Structures/DrawingOperations/SolidFillDrawingOperation.swift new file mode 100644 index 00000000..ed2d6326 --- /dev/null +++ b/LeadKit/LeadKit/Structures/DrawingOperations/SolidFillDrawingOperation.swift @@ -0,0 +1,46 @@ +// +// Copyright (c) 2017 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import CoreGraphics + +struct SolidFillDrawingOperation: DrawingOperation { + + private let color: CGColor + private let width: Int + private let height: Int + + public init(color: CGColor, width: Int, height: Int) { + self.color = color + self.width = width + self.height = height + } + + public var contextSize: CGContextSize { + return (width: width, height: height) + } + + public func apply(in context: CGContext) { + context.setFillColor(color) + context.fill(CGRect(origin: .zero, size: CGSize(width: width, height: height))) + } + +} diff --git a/LeadKit/LeadKit/Structures/DrawingOperations/TemplateDrawingOperation.swift b/LeadKit/LeadKit/Structures/DrawingOperations/TemplateDrawingOperation.swift new file mode 100644 index 00000000..db406707 --- /dev/null +++ b/LeadKit/LeadKit/Structures/DrawingOperations/TemplateDrawingOperation.swift @@ -0,0 +1,54 @@ +// +// Copyright (c) 2017 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import CoreGraphics + +struct TemplateDrawingOperation: DrawingOperation { + + private let image: CGImage + private let imageSize: CGSize + private let color: CGColor + + public init(image: CGImage, imageSize: CGSize, color: CGColor) { + self.image = image + self.imageSize = imageSize + self.color = color + } + + public var contextSize: CGContextSize { + return imageSize.ceiledContextSize + } + + public func apply(in context: CGContext) { + let imageRect = CGRect(origin: .zero, size: imageSize) + + context.setFillColor(color) + + context.translateBy(x: 0, y: imageSize.height) + context.scaleBy(x: 1.0, y: -1.0) + context.clip(to: imageRect, mask: image) + context.fill(imageRect) + + context.setBlendMode(.multiply) + } + +}