From 020ea512f4dd65a0ce4e8a4b340c315acb09719c Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Thu, 27 Apr 2017 09:37:23 +0300 Subject: [PATCH] new API for drawing --- LeadKit/LeadKit.xcodeproj/project.pbxproj | 72 +++-- .../CGContext/CGContext+Initializers.swift | 11 +- .../Extensions/CGImage/CGImage+Alpha.swift | 15 - .../Extensions/CGImage/CGImage+Creation.swift | 76 ------ .../CGImage/CGImage+Transform.swift | 157 ----------- .../CGSize/CGSize+CGContextSize.swift | 31 +++ .../UIImage/UIImage+Extensions.swift | 211 +++++++++------ .../UIImage/UIImage+SupportExtensions.swift | 256 ++++++++++++++++++ .../DrawingOperation.swift} | 40 +-- .../BorderDrawingOperation.swift | 88 ++++++ .../CALayerDrawingOperation.swift} | 30 +- .../ImageDrawingOperation.swift | 48 ++++ .../PaddingDrawingOperation.swift | 57 ++++ .../RoundDrawingOperation.swift | 49 ++++ .../SolidFillDrawingOperation.swift | 46 ++++ .../TemplateDrawingOperation.swift | 54 ++++ 16 files changed, 862 insertions(+), 379 deletions(-) delete mode 100644 LeadKit/LeadKit/Extensions/CGImage/CGImage+Creation.swift delete mode 100644 LeadKit/LeadKit/Extensions/CGImage/CGImage+Transform.swift create mode 100644 LeadKit/LeadKit/Extensions/CGSize/CGSize+CGContextSize.swift create mode 100644 LeadKit/LeadKit/Extensions/UIImage/UIImage+SupportExtensions.swift rename LeadKit/LeadKit/{Extensions/CGImage/CGImage+Template.swift => Protocols/DrawingOperation.swift} (52%) create mode 100644 LeadKit/LeadKit/Structures/DrawingOperations/BorderDrawingOperation.swift rename LeadKit/LeadKit/{Extensions/CGImage/CGImage+Utils.swift => Structures/DrawingOperations/CALayerDrawingOperation.swift} (58%) create mode 100644 LeadKit/LeadKit/Structures/DrawingOperations/ImageDrawingOperation.swift create mode 100644 LeadKit/LeadKit/Structures/DrawingOperations/PaddingDrawingOperation.swift create mode 100644 LeadKit/LeadKit/Structures/DrawingOperations/RoundDrawingOperation.swift create mode 100644 LeadKit/LeadKit/Structures/DrawingOperations/SolidFillDrawingOperation.swift create mode 100644 LeadKit/LeadKit/Structures/DrawingOperations/TemplateDrawingOperation.swift diff --git a/LeadKit/LeadKit.xcodeproj/project.pbxproj b/LeadKit/LeadKit.xcodeproj/project.pbxproj index 341c8773..087cd04c 100644 --- a/LeadKit/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit/LeadKit.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 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 */; }; @@ -18,6 +19,15 @@ 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 */; }; @@ -61,10 +71,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 */; }; @@ -115,6 +121,7 @@ 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 = ""; }; @@ -123,6 +130,15 @@ 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 = ""; }; @@ -166,10 +182,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 = ""; }; @@ -260,6 +272,7 @@ isa = PBXGroup; children = ( 676D17801EAE137B002E19F9 /* CGSize+Resize.swift */, + 67A7B1941EAF5F9B00E5BC59 /* CGSize+CGContextSize.swift */, ); path = CGSize; sourceTree = ""; @@ -280,6 +293,20 @@ 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 */, + ); + path = DrawingOperations; + sourceTree = ""; + }; 67B305791E8A8727008169CA /* Views */ = { isa = PBXGroup; children = ( @@ -344,6 +371,7 @@ children = ( 67DC650A1E979BFD002F2FFF /* Views */, 78011AB11D48B53600EA16A2 /* Api */, + 67A7B18F1EAF5F2200E5BC59 /* DrawingOperations */, ); path = Structures; sourceTree = ""; @@ -360,10 +388,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; @@ -616,6 +640,7 @@ 67B856E21E923BE600F54304 /* ResettableType.swift */, 679DE48F1E9588B6006F25FE /* SupportProtocol.swift */, 67DC65031E979B34002F2FFF /* LoadingIndicatorProtocol.swift */, + 67A7B1961EAF5FF600E5BC59 /* DrawingOperation.swift */, ); path = Protocols; sourceTree = ""; @@ -655,6 +680,7 @@ isa = PBXGroup; children = ( 78B036401DA4D7060021D5CC /* UIImage+Extensions.swift */, + 674E7E641EB0F2E300D13340 /* UIImage+SupportExtensions.swift */, ); path = UIImage; sourceTree = ""; @@ -903,7 +929,7 @@ }; 782B1B3D1C7343CD003F8A95 /* Tailor */ = { isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; + buildActionMask = 8; files = ( ); inputPaths = ( @@ -911,13 +937,13 @@ name = Tailor; outputPaths = ( ); - runOnlyForDeploymentPostprocessing = 0; + runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; shellScript = "if hash tailor 2>/dev/null; then\n tailor --except=trailing-whitespace,forced-type-cast\nelse\n echo \"warning: Please install Tailor from https://tailor.sh\"\nfi"; }; 782B1B3E1C7343E0003F8A95 /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; + buildActionMask = 8; files = ( ); inputPaths = ( @@ -925,7 +951,7 @@ name = SwiftLint; outputPaths = ( ); - runOnlyForDeploymentPostprocessing = 0; + runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"SwiftLint does not exist, download from https://github.com/realm/SwiftLint\"\nfi"; }; @@ -951,6 +977,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 */, @@ -964,11 +991,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 */, @@ -976,13 +1003,15 @@ 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 */, @@ -990,8 +1019,11 @@ 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 */, @@ -1000,6 +1032,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 */, @@ -1018,13 +1052,11 @@ 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 */, diff --git a/LeadKit/LeadKit/Extensions/CGContext/CGContext+Initializers.swift b/LeadKit/LeadKit/Extensions/CGContext/CGContext+Initializers.swift index 3ff5bbb4..4730ff15 100644 --- a/LeadKit/LeadKit/Extensions/CGContext/CGContext+Initializers.swift +++ b/LeadKit/LeadKit/Extensions/CGContext/CGContext+Initializers.swift @@ -22,6 +22,15 @@ 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 { /// Creates a bitmap graphics context with parameters taken from a given image. @@ -53,7 +62,7 @@ public extension CGContext { /// - 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 e2f51fce..a17cdf6e 100644 --- a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Alpha.swift +++ b/LeadKit/LeadKit/Extensions/CGImage/CGImage+Alpha.swift @@ -34,19 +34,4 @@ public extension CGImage { } } - /// Adds an alpha channel to image if it doesn't already have one. - /// - /// - Returns: A copy of the given image, adding an alpha channel if it doesn't already have one - /// or nil if something goes wrong. - 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 37eb9957..00000000 --- a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Creation.swift +++ /dev/null @@ -1,76 +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 new CGImage instance filled by given color. - /// - /// - Parameters: - /// - color: Color to fill. - /// - width: Width of new image. - /// - height: Height of new image. - /// - opaque: A flag indicating whether the bitmap is opaque (default: false). - /// - Returns: A new instanse of UIImage with given size and color or nil if something goes wrong. - 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 given UIView. - /// - /// - Parameter view: The source view. - /// - Returns: A new image created from the given view or nil if something goes wrong. - 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+Transform.swift b/LeadKit/LeadKit/Extensions/CGImage/CGImage+Transform.swift deleted file mode 100644 index f78c631b..00000000 --- a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Transform.swift +++ /dev/null @@ -1,157 +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 radius: The corner radius. - /// - Returns: New image with rounded corners or nil if something goes wrong. - 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. - /// - /// - Parameters: - /// - border: The size of the border. - /// - color: The color of the border. - /// - radius: The corner radius. - /// - extendSize: Extend result image size and don't overlap source image by border. - /// - Returns: A new image with border or nil if something goes wrong.. - 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(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. - /// - /// - Parameters: - /// - newSize: The new size of the image. - /// - origin: The point where to place resized image - /// - Returns: A new resized image or nil if something goes wrong. - public func resize(to newSize: CGSize, usingOrigin origin: CGPoint = .zero) -> 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 resizeRect = CGRect(origin: origin, - size: CGSize(width: ctxWidth, height: ctxHeight)) - - ctx.interpolationQuality = .high - ctx.draw(self, in: resizeRect) - - return ctx.makeImage() - } - - /// 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) -> 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/UIImage/UIImage+Extensions.swift b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Extensions.swift index 916ade1a..4913c053 100644 --- a/LeadKit/LeadKit/Extensions/UIImage/UIImage+Extensions.swift +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+Extensions.swift @@ -22,6 +22,7 @@ import UIKit +@available(iOS 10.0, *) public extension UIImage { /// Creates an image filled by given color. @@ -29,37 +30,41 @@ public extension UIImage { /// - 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 convenience init?(color: UIColor, size: CGSize) { - let cgImage = CGImage.create(color: color.cgColor, - width: Int(ceil(size.width)), - height: Int(ceil(size.height))) + /// - 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)) - guard let image = cgImage else { - return nil - } + let operation = SolidFillDrawingOperation(color: color.cgColor, width: width, height: height) - self.init(cgImage: image) + return operation.imageFromNewRenderer(scale: UIScreen.main.scale) } /// Creates an image from a UIView. /// /// - Parameter fromView: The source view. /// - Returns: A new instance of UIImage or nil if something goes wrong. - public convenience init?(fromView view: UIView) { - guard let cgImage = CGImage.create(fromView: view) else { - return nil - } + public static func imageFrom(view: UIView) -> UIImage { + let operation = CALayerDrawingOperation(layer: view.layer, size: view.bounds.size) - self.init(cgImage: cgImage) + 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 or nil if something goes wrong. - public func renderTemplate(withColor color: UIColor) -> UIImage? { - return cgImage?.renderTemplate(withColor: color.cgColor)?.uiImage + /// - Returns: A new UIImage rendered with given color. + public func renderTemplate(withColor color: UIColor) -> UIImage { + guard let image = cgImage else { + return self + } + + let operation = TemplateDrawingOperation(image: image, + imageSize: size, + color: color.cgColor) + + return operation.imageFromNewRenderer(scale: scale) } /// Creates a new image with rounded corners and border. @@ -69,25 +74,49 @@ public extension UIImage { /// - 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 or nil if something goes wrong. + /// - 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 circled image or nil if something goes wrong. - public func roundCornersToCircle() -> UIImage? { - return cgImage?.round(withRadius: CGFloat(min(size.width, size.height) / 2))?.uiImage + /// - 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 operation = RoundDrawingOperation(image: image, + imageSize: size, + radius: radius) + + return operation.imageFromNewRenderer(scale: scale) } /// Creates a new circle image with a border. @@ -99,15 +128,30 @@ public extension UIImage { /// - Returns: A new image with rounded corners or nil if something goes wrong. public func roundCornersToCircle(borderWidth: CGFloat, borderColor: UIColor, - extendSize: Bool = false) -> UIImage? { + extendSize: Bool = false) -> 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 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 resized copy of an image. @@ -116,71 +160,84 @@ public extension UIImage { /// - newSize: The new size of the image. /// - contentMode: The way to handle the content in the new size. /// - Returns: A new image scaled to new size. - public func resize(newSize: CGSize, contentMode: ResizeMode = .scaleToFill) -> UIImage? { + public func resize(newSize: CGSize, contentMode: ResizeMode = .scaleToFill) -> UIImage { + guard let image = cgImage else { + return self + } + let resizedRect = size.resizeRect(forNewSize: newSize, resizeMode: contentMode) - return cgImage?.resize(to: resizedRect.size, usingOrigin: resizedRect.origin)?.uiImage + let operation = ImageDrawingOperation(image: image, + newSize: resizedRect.size, + origin: resizedRect.origin) + + return operation.imageFromNewRenderer(scale: scale) } /// Creates a resized copy of image using scaleAspectFit strategy. /// /// - Parameter preferredNewSize: The preferred new size of image. /// - Returns: A new image which size may be less then or equals to given preferredSize. - public func resizeAspectFit(preferredNewSize: CGSize) -> UIImage? { - let resizeRect = size.resizeRect(forNewSize: preferredNewSize, resizeMode: .scaleAspectFit) + public func resizeAspectFit(preferredNewSize: CGSize) -> UIImage { + guard let image = cgImage else { + return self + } - return cgImage?.resize(to: resizeRect.size)?.uiImage - } + let resizedRect = size.resizeRect(forNewSize: preferredNewSize, resizeMode: .scaleAspectFit) - /// Creates a cropped copy of image using the data contained within a subregion of an existing image. - /// - /// - Parameter bounds: A rectangle whose coordinates specify the area to create an image from. - /// - Returns: A new image that specifies a subimage of the image. - /// If the rect parameter defines an area that is not in the image, returns NULL. - public func crop(to bounds: CGRect) -> UIImage? { - return cgImage?.cropping(to: bounds)?.uiImage - } + let operation = ImageDrawingOperation(image: image, + newSize: resizedRect.size, + origin: .zero) - /// Crop image with given margin values. - /// - /// - Parameters: - /// - top: Top margin. - /// - left: Left margin. - /// - bottom: Bottom margin. - /// - right: Right margin. - /// - Returns: A new image 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) -> UIImage? { - - return cgImage?.crop(top: top, - left: left, - bottom: bottom, - right: right)?.uiImage - } - - /// Crop image to square from center. - /// - /// - Returns: A new cropped image. - public func cropFromCenterToSquare() -> UIImage? { - return cgImage?.cropFromCenterToSquare()?.uiImage + return operation.imageFromNewRenderer(scale: scale) } /// 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? { - return cgImage?.applyAlpha()?.uiImage + public func applyAlpha() -> UIImage { + guard let image = cgImage, !image.hasAlpha else { + return self + } + + let operation = ImageDrawingOperation(image: image, + newSize: size, + opaque: false) + + return operation.imageFromNewRenderer(scale: scale) + } + + /// 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) } } -public extension CGImage { +@available(iOS 10.0, *) +private extension DrawingOperation { - /// Creates new UIImage instance from CGImage. - 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 + + let renderer = UIGraphicsImageRenderer(size: size, format: format) + + return renderer.image { context in + self.apply(in: context.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..a3dd984d --- /dev/null +++ b/LeadKit/LeadKit/Extensions/UIImage/UIImage+SupportExtensions.swift @@ -0,0 +1,256 @@ +// +// 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) -> UIImage? { + 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) + } + + /// 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.imageFromNewContext(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 or nil if something goes wrong. + public func renderTemplate(withColor color: UIColor) -> UIImage? { + guard let image = base.cgImage else { + return base + } + + let operation = TemplateDrawingOperation(image: image, + imageSize: base.size, + color: color.cgColor) + + return operation.imageFromNewContext(scale: base.scale) + } + + /// 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) -> UIImage? { + + guard let image = base.cgImage else { + return 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) + } + + /// Creates a new circle image. + /// + /// - Returns: A new circled image or nil if something goes wrong. + public func roundCornersToCircle() -> UIImage? { + guard let image = base.cgImage else { + return 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) + } + + /// 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? { + + guard let image = base.cgImage else { + return 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) + } + + /// 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. + /// - Returns: A new image scaled to new size. + public func resize(newSize: CGSize, contentMode: ResizeMode = .scaleToFill) -> UIImage? { + guard let image = base.cgImage else { + return base + } + + let resizedRect = base.size.resizeRect(forNewSize: newSize, resizeMode: contentMode) + + let operation = ImageDrawingOperation(image: image, + newSize: resizedRect.size, + origin: resizedRect.origin) + + return operation.imageFromNewContext(scale: base.scale) + } + + /// Creates a resized copy of image using scaleAspectFit strategy. + /// + /// - Parameter preferredNewSize: The preferred new size of image. + /// - Returns: A new image which size may be less then or equals to given preferredSize. + public func resizeAspectFit(preferredNewSize: CGSize) -> UIImage? { + guard let image = base.cgImage else { + return base + } + + let resizedRect = base.size.resizeRect(forNewSize: preferredNewSize, resizeMode: .scaleAspectFit) + + let operation = ImageDrawingOperation(image: image, + newSize: resizedRect.size, + origin: .zero) + + return operation.imageFromNewContext(scale: base.scale) + } + + /// 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 = base.cgImage, !image.hasAlpha else { + return base + } + + let operation = ImageDrawingOperation(image: image, + newSize: base.size, + opaque: false) + + return operation.imageFromNewContext(scale: base.scale) + } + + /// 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 = base.cgImage else { + return base + } + + let operation = PaddingDrawingOperation(image: image, + imageSize: base.size, + padding: padding) + + return operation.imageFromNewContext(scale: base.scale) + } + +} + +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 52% rename from LeadKit/LeadKit/Extensions/CGImage/CGImage+Template.swift rename to LeadKit/LeadKit/Protocols/DrawingOperation.swift index 6d5bc2a6..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,29 +22,29 @@ import CoreGraphics -public extension CGImage { +public typealias CGContextSize = (width: Int, height: Int) - /// Renders 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 { + + /// Required context size. + var contextSize: CGContextSize { get } + + /// Reports: should context be opaque. + var opaque: Bool { get } + + /// Applies drawing operation in given CGContext instance. /// - /// - Parameter color: Color used to fill template image - /// - Returns: A new CGImage instance 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 - } + /// - Parameter context: CGContext to perform drawing manipulations. + func apply(in context: CGContext) - let imageRect = bounds +} - ctx.setFillColor(color) +extension DrawingOperation { - ctx.translateBy(x: 0, y: CGFloat(height)) - ctx.scaleBy(x: 1.0, y: -1.0) - ctx.clip(to: imageRect, mask: self) - ctx.fill(imageRect) - - 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..332d13fd --- /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 (width: Int(ceil(width)), height: Int(ceil(height))) + } + + 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/Extensions/CGImage/CGImage+Utils.swift b/LeadKit/LeadKit/Structures/DrawingOperations/CALayerDrawingOperation.swift similarity index 58% rename from LeadKit/LeadKit/Extensions/CGImage/CGImage+Utils.swift rename to LeadKit/LeadKit/Structures/DrawingOperations/CALayerDrawingOperation.swift index e5ff3085..2d8e8e2c 100644 --- a/LeadKit/LeadKit/Extensions/CGImage/CGImage+Utils.swift +++ b/LeadKit/LeadKit/Structures/DrawingOperations/CALayerDrawingOperation.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,21 +22,25 @@ import CoreGraphics -// The bitmapInfo value are hard-coded to prevent an "unsupported parameter combination" error +struct CALayerDrawingOperation: DrawingOperation { -public let alphaBitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) -public let opaqueBitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.none.rawValue) + private let layer: CALayer + private let size: CGSize -public extension CGImage { - - /// Size of image. - var size: CGSize { - return CGSize(width: width, height: height) + public init(layer: CALayer, size: CGSize) { + self.layer = layer + self.size = size } - /// Bounds of image. - var bounds: CGRect { - return CGRect(origin: .zero, size: size) + public var contextSize: CGContextSize { + return size.ceiledContextSize + } + + public func apply(in context: CGContext) { + context.translateBy(x: 0, y: size.height) + context.scaleBy(x: 1.0, y: -1.0) + + 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..aa0f654d --- /dev/null +++ b/LeadKit/LeadKit/Structures/DrawingOperations/ImageDrawingOperation.swift @@ -0,0 +1,48 @@ +// +// 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 + + public init(image: CGImage, newSize: CGSize, origin: CGPoint = .zero, opaque: Bool = true) { + self.image = image + self.newSize = newSize + self.origin = origin + self.opaque = opaque + } + + public var contextSize: CGContextSize { + return newSize.ceiledContextSize + } + + public func apply(in context: CGContext) { + 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/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) + } + +}