├── .gitignore
├── .swift-version
├── .travis.yml
├── LICENSE
├── README.md
├── Source
├── Toucan-Info.plist
├── Toucan.h
├── Toucan.swift
└── tvOS
│ ├── Toucan.tvOS-Info.plist
│ └── ToucanTVOS.h
├── Tests
├── Assets
│ ├── Landscape.jpg
│ ├── OctagonMask.png
│ └── Portrait.jpg
├── Info.plist
├── MaskingTests.swift
├── ResizeTests.swift
└── ToucanTestCase.swift
├── Toucan.podspec
├── Toucan.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcshareddata
│ └── xcschemes
│ │ ├── Toucan.tvOS.xcscheme
│ │ └── Toucan.xcscheme
└── xcuserdata
│ ├── bkirchner.xcuserdatad
│ └── xcschemes
│ │ └── xcschememanagement.plist
│ └── bunneyapps.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── Toucan.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── ToucanPlayground.playground
├── Contents.swift
├── Resources
│ ├── Landscape.jpg
│ ├── OctagonMask.png
│ └── Portrait.jpg
├── contents.xcplayground
├── playground.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── ToucanPlayground.xccheckout
└── timeline.xctimeline
└── assets
├── bird.png
├── examples
├── Mask-Custom.jpg
├── Mask-Ellipse-Border.jpg
├── Mask-Ellipse-Circle.jpg
├── Mask-Ellipse.jpg
├── Mask-Path.jpg
├── Mask-RoundedRect-Border.jpg
├── Mask-RoundedRect.jpg
├── Resize-Clip.jpg
├── Resize-Crop.jpg
└── Resize-Scale.jpg
├── toucan.png
└── toucan.psd
/.gitignore:
--------------------------------------------------------------------------------
1 | *.xcuserstate
2 | xcuserdata
3 | *.xccheckout
4 | build
5 | .idea
6 | docs
7 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode10.1
3 | before_install:
4 | - gem install xcpretty
5 | script:
6 | - set -o pipefail
7 |
8 | - xcodebuild -project Toucan.xcodeproj -scheme "Toucan" test -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone XS" ONLY_ACTIVE_ARCH=NO | xcpretty -c
9 | - xcodebuild -project Toucan.xcodeproj -scheme "Toucan.tvOS" build -destination "platform=tvOS Simulator,name=Apple TV" ONLY_ACTIVE_ARCH=NO | xcpretty -c
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014-2016 Gavin Bunney, Simple Labs (http://thesimplelab.co)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://travis-ci.org/gavinbunney/Toucan)
4 | [](https://cocoapods.org/pods/Toucan)
5 | [](https://github.com/Carthage/Carthage)
6 |
7 | Toucan is a Swift library that provides a clean, quick API for processing images. It greatly simplifies the production of images, supporting resizing, cropping and stylizing your images.
8 |
9 | ## Features ##
10 |
11 | - Easy and smart resizing
12 | - Elliptical and rounded rect masking
13 | - Mask with custom images
14 | - Chainable image processing stages
15 |
16 | ## Requirements ##
17 |
18 | - Xcode 10.0+
19 | - iOS 11.0+
20 |
21 | ## Setup ##
22 |
23 | * Install using CocoaPods: [https://cocoapods.org/pods/Toucan](https://cocoapods.org/pods/Toucan)
24 | * or manually include the `Toucan` framework by dragging it into your project and import the library in your code using `import Toucan`
25 |
26 | ## Toucan Usage ##
27 |
28 | Toucan provides two methods of interaction - either through wrapping an single image within a Toucan instance, or through the static functions, providing an image for each invocation. This allows for some very flexible usage.
29 |
30 | Create an instance wrapper for easy method chaining:
31 |
32 | ```swift
33 | let resizedAndMaskedImage = Toucan(image: myImage).resize(CGSize(width: 100, height: 150)).maskWithEllipse().image
34 | ```
35 |
36 | Or, using static methods when you need a single operation:
37 |
38 | ```swift
39 | let resizedImage = Toucan.Resize.resizeImage(myImage, size: CGSize(width: 100, height: 150))
40 | let resizedAndMaskedImage = Toucan.maskWithEllipse(resizedImage)
41 | ```
42 |
43 | Typically, the instance version is a bit cleaner to use, and the one you want.
44 |
45 | ## Resizing ##
46 |
47 | Resize the contained image to the specified size. Depending on what `fitMode` is supplied, the image may be clipped, cropped or scaled.
48 |
49 | ```swift
50 | Toucan(image: myImage).resize(size: CGSize, fitMode: Toucan.Resize.FitMode)
51 | ```
52 |
53 | ### Fit Mode ###
54 |
55 | FitMode drives the resizing process to determine what to do with an image to make it fit the given size bounds.
56 |
57 | Example | Mode
58 | ---- | ---------
59 | |**Clip Mode**
`Toucan.Resize.FitMode.Clip`
Resizes the image to fit within the width and height boundaries without cropping or distorting the image.
`Toucan(image: portraitImage).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.Clip).image`
60 | |**Crop Mode**
`Toucan.Resize.FitMode.crop`
Resizes the image to fill the width and height boundaries and crops any excess image data.
`Toucan(image: portraitImage).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.Crop).image`
61 | |**Scale Mode**
`Toucan.Resize.FitMode.scale`
Scales the image to fit the constraining dimensions exactly.
`Toucan(image: portraitImage).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.Scale).image`
62 |
63 |
64 | ## Masking ##
65 |
66 | Alter the original image with a mask; supports ellipse, rounded rect and image masks.
67 |
68 | ### Ellipse Mask ###
69 |
70 | Example | Function
71 | ---- | ---------
72 | |Mask the given image with an ellipse. Allows specifying an additional border to draw on the clipped image. For a circle, ensure the image width and height are equal!
`Toucan(image: myImage).maskWithEllipse().image`
73 | |When specifying a border width, it is draw on the clipped image.
`Toucan(image: myImage).maskWithEllipse(borderWidth: 10, borderColor: UIColor.yellowColor()).image`
74 |
75 | ### Path Mask ###
76 |
77 | Example | Function
78 | ---- | ---------
79 | |Mask the given image with a path. The path will be scaled to fit the image correctly!
`path.moveToPoint(CGPointMake(0, 50))`
`path.addLineToPoint(CGPointMake(50, 0))`
`path.addLineToPoint(CGPointMake(100, 50))`
`path.addLineToPoint(CGPointMake(50, 100))`
`path.closePath()`
`Toucan(image: myImage).maskWithPath(path: path).image`
80 | |Mask the given image with a path provided via a closure. This allows you to construct your path relative to the bounds of the image!
`Toucan(image: myImage).maskWithPathClosure(path: (rect: CGRect) -> (UIBezierPath)).image`
81 |
82 | ### Rounded Rect Mask ###
83 |
84 | Example | Function
85 | ---- | ---------
86 | |Mask the given image with a rounded rectangle border. Allows specifying an additional border to draw on the clipped image.
`Toucan(image: myImage).maskWithRoundedRect(cornerRadius: 30).image`
87 | |When specifying a border width, it is draw on the clipped rounded rect.
`Toucan(image: myImage).maskWithRoundedRect(cornerRadius: 30, borderWidth: 10, borderColor: UIColor.purpleColor()).image`
88 |
89 | ### Image Mask ###
90 |
91 | Example | Function
92 | ---- | ---------
93 | |Mask the given image with another image mask. Note that the areas in the original image that correspond to the black areas of the mask show through in the resulting image. The areas that correspond to the white areas of the mask aren’t painted. The areas that correspond to the gray areas in the mask are painted using an intermediate alpha value that’s equal to 1 minus the image mask sample value.
`Toucan(image: myImage).maskWithImage(maskImage: octagonMask).image`
94 |
95 | ---
96 |
97 | ## Example Images ##
98 |
99 | Example images used under Creative Commons with thanks to:
100 |
101 | - [David Amsler](https://www.flickr.com/photos/amslerpix/13685763725/in/photolist-mRn7Kx-mRnin2-nzyjCg-m3eSyR-nGRbHm-m5NTzH-nBs2zA-n1vE5X-oenJtQ-mp1vjZ-mp1HxX-niw2vi-mp2vTv-mPxFPE-oo51aY-onZZZx-m3ypFM-kPP6St-o7cw7M-HUV9E-bXegkJ-kcTTki-kcTRDT-e1HGVe-7FG1t5-e3jPE6-e9YgDw-c3rhzL-3evWDz-7n3iKL-e3jY8R-e3jPXz-9biMcK-5nqaP6-a1z87J-bXei17-6q25KQ-cYu7Nw-9Gsrmz-9EiTHi-5R2w7E-fFFT8i-a1z9vq-diYNrA-diYQP6-diYQHc-6q276y-cb1FqQ-d9yGhj-nb4XbV)
102 | - [Sheila Sund](https://www.flickr.com/photos/sheila_sund/8540775223/in/photolist-mRn7Kx-mRnin2-nzyjCg-m3eSyR-nGRbHm-m5NTzH-nBs2zA-n1vE5X-oenJtQ-mp1vjZ-mp1HxX-niw2vi-mp2vTv-mPxFPE-oo51aY-onZZZx-m3ypFM-kPP6St-o7cw7M-HUV9E-bXegkJ-kcTTki-kcTRDT-e1HGVe-7FG1t5-e3jPE6-e9YgDw-c3rhzL-3evWDz-7n3iKL-e3jY8R-e3jPXz-9biMcK-5nqaP6-a1z87J-bXei17-6q25KQ-cYu7Nw-9Gsrmz-9EiTHi-5R2w7E-fFFT8i-a1z9vq-diYNrA-diYQP6-diYQHc-6q276y-cb1FqQ-d9yGhj-nb4XbV/)
103 |
104 |
105 | ## Contributing ##
106 |
107 | 1. Please fork this project
108 | 2. Implement new methods or changes in the `Toucan.swift` file.
109 | 3. Write tests in the `ToucanTests` folder.
110 | 4. Write appropriate docs and comments in the README.md
111 | 5. Submit a pull request.
112 |
113 |
114 | ## Contact ##
115 |
116 | Raise an [Issue](https://github.com/gavinbunney/Toucan/issues) or hit me up on Twitter [@gavinbunney](https://twitter.com/gavinbunney)
117 |
118 |
119 | ## License ##
120 |
121 | Toucan is released under an MIT license. See LICENSE for more information.
122 |
--------------------------------------------------------------------------------
/Source/Toucan-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Source/Toucan.h:
--------------------------------------------------------------------------------
1 | // Toucan.h
2 | //
3 | // Copyright (c) 2014-2019 Gavin Bunney
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | #import
24 |
25 | //! Project version number for Toucan.
26 | FOUNDATION_EXPORT double ToucanVersionNumber;
27 |
28 | //! Project version string for Toucan.
29 | FOUNDATION_EXPORT const unsigned char ToucanVersionString[];
30 |
--------------------------------------------------------------------------------
/Source/Toucan.swift:
--------------------------------------------------------------------------------
1 | // Toucan.swift
2 | //
3 | // Copyright (c) 2014-2019 Gavin Bunney
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | import UIKit
24 | import CoreGraphics
25 |
26 | /**
27 | Toucan - Fabulous Image Processing in Swift.
28 |
29 | The Toucan class provides two methods of interaction - either through an instance, wrapping an single image,
30 | or through the static functions, providing an image for each invocation.
31 |
32 | This allows for some flexible usage. Using static methods when you need a single operation:
33 | let resizedImage = Toucan.resize(myImage, size: CGSize(width: 100, height: 150))
34 |
35 | Or create an instance for easy method chaining:
36 | let resizedAndMaskedImage = Toucan(withImage: myImage).resize(CGSize(width: 100, height: 150)).maskWithEllipse().image
37 | */
38 | public class Toucan : NSObject {
39 |
40 | #if swift(>=4.2)
41 | internal typealias ImageOrientation = UIImage.Orientation
42 | #else
43 | internal typealias ImageOrientation = UIImageOrientation
44 | #endif
45 |
46 | public var image : UIImage?
47 |
48 | public init(image withImage: UIImage) {
49 | self.image = withImage
50 | }
51 |
52 | // MARK: - Resize
53 |
54 | /**
55 | Resize the contained image to the specified size. Depending on what fitMode is supplied, the image
56 | may be clipped, cropped or scaled. @see documentation on FitMode.
57 |
58 | The current image on this toucan instance is replaced with the resized image.
59 |
60 | - parameter size: Size to resize the image to
61 | - parameter fitMode: How to handle the image resizing process
62 |
63 | - returns: Self, allowing method chaining
64 | */
65 | public func resize(_ size: CGSize, fitMode: Toucan.Resize.FitMode = .clip) -> Toucan {
66 | if let image = self.image {
67 | self.image = Toucan.Resize.resizeImage(image, size: size, fitMode: fitMode)
68 | }
69 | return self
70 | }
71 |
72 | /**
73 | Resize the contained image to the specified size by resizing the image to fit
74 | within the width and height boundaries without cropping or scaling the image.
75 |
76 | The current image on this toucan instance is replaced with the resized image.
77 |
78 | - parameter size: Size to resize the image to
79 |
80 | - returns: Self, allowing method chaining
81 | */
82 | @objc
83 | public func resizeByClipping(_ size: CGSize) -> Toucan {
84 | if let image = self.image {
85 | self.image = Toucan.Resize.resizeImage(image, size: size, fitMode: .clip)
86 | }
87 | return self
88 | }
89 |
90 | /**
91 | Resize the contained image to the specified size by resizing the image to fill the
92 | width and height boundaries and crops any excess image data.
93 | The resulting image will match the width and height constraints without scaling the image.
94 |
95 | The current image on this toucan instance is replaced with the resized image.
96 |
97 | - parameter size: Size to resize the image to
98 |
99 | - returns: Self, allowing method chaining
100 | */
101 | @objc
102 | public func resizeByCropping(_ size: CGSize) -> Toucan {
103 | if let image = self.image {
104 | self.image = Toucan.Resize.resizeImage(image, size: size, fitMode: .crop)
105 | }
106 | return self
107 | }
108 |
109 | /**
110 | Resize the contained image to the specified size by scaling the image to fit the
111 | constraining dimensions exactly.
112 |
113 | The current image on this toucan instance is replaced with the resized image.
114 |
115 | - parameter size: Size to resize the image to
116 |
117 | - returns: Self, allowing method chaining
118 | */
119 | @objc
120 | public func resizeByScaling(_ size: CGSize) -> Toucan {
121 | if let image = self.image {
122 | self.image = Toucan.Resize.resizeImage(image, size: size, fitMode: .scale)
123 | }
124 | return self
125 | }
126 |
127 | /**
128 | Container struct for all things Resize related
129 | */
130 | public struct Resize {
131 |
132 | /**
133 | FitMode drives the resizing process to determine what to do with an image to
134 | make it fit the given size bounds.
135 |
136 | - Clip: Resizes the image to fit within the width and height boundaries without cropping or scaling the image.
137 |
138 | - Crop: Resizes the image to fill the width and height boundaries and crops any excess image data.
139 |
140 | - Scale: Scales the image to fit the constraining dimensions exactly.
141 | */
142 | public enum FitMode {
143 | /**
144 | Resizes the image to fit within the width and height boundaries without cropping or scaling the image.
145 | The resulting image is assured to match one of the constraining dimensions, while
146 | the other dimension is altered to maintain the same aspect ratio of the input image.
147 | */
148 | case clip
149 |
150 | /**
151 | Resizes the image to fill the width and height boundaries and crops any excess image data.
152 | The resulting image will match the width and height constraints without scaling the image.
153 | */
154 | case crop
155 |
156 | /**
157 | Scales the image to fit the constraining dimensions exactly.
158 | */
159 | case scale
160 | }
161 |
162 | /**
163 | Resize an image to the specified size. Depending on what fitMode is supplied, the image
164 | may be clipped, cropped or scaled. @see documentation on FitMode.
165 |
166 | - parameter image: Image to Resize
167 | - parameter size: Size to resize the image to
168 | - parameter fitMode: How to handle the image resizing process
169 |
170 | - returns: Resized image
171 | */
172 | public static func resizeImage(_ image: UIImage, size: CGSize, fitMode: FitMode = .clip) -> UIImage? {
173 |
174 | let imgRef = Util.CGImageWithCorrectOrientation(image)
175 | let originalWidth = CGFloat(imgRef.width)
176 | let originalHeight = CGFloat(imgRef.height)
177 | let widthRatio = size.width / originalWidth
178 | let heightRatio = size.height / originalHeight
179 |
180 | let scaleRatio = fitMode == .clip ? min(heightRatio, widthRatio) : max(heightRatio, widthRatio)
181 |
182 | let resizedImageBounds = CGRect(x: 0, y: 0, width: round(originalWidth * scaleRatio), height: round(originalHeight * scaleRatio))
183 | let resizedImage = Util.drawImageInBounds(image, bounds: resizedImageBounds)
184 | guard resizedImage != nil else {
185 | return nil
186 | }
187 |
188 | switch (fitMode) {
189 | case .clip:
190 | return resizedImage
191 | case .crop:
192 | let croppedRect = CGRect(x: (resizedImage!.size.width - size.width) / 2,
193 | y: (resizedImage!.size.height - size.height) / 2,
194 | width: size.width, height: size.height)
195 | return Util.croppedImageWithRect(resizedImage!, rect: croppedRect)
196 | case .scale:
197 | return Util.drawImageInBounds(resizedImage!, bounds: CGRect(x: 0, y: 0, width: size.width, height: size.height))
198 | }
199 | }
200 | }
201 |
202 | // MARK: - Mask
203 |
204 | /**
205 | Mask the contained image with another image mask.
206 | Note that the areas in the original image that correspond to the black areas of the mask
207 | show through in the resulting image. The areas that correspond to the white areas of
208 | the mask aren’t painted. The areas that correspond to the gray areas in the mask are painted
209 | using an intermediate alpha value that’s equal to 1 minus the image mask sample value.
210 |
211 | - parameter maskImage: Image Mask to apply to the Image
212 |
213 | - returns: Self, allowing method chaining
214 | */
215 | public func maskWithImage(maskImage : UIImage) -> Toucan {
216 | if let image = self.image {
217 | self.image = Toucan.Mask.maskImageWithImage(image, maskImage: maskImage)
218 | }
219 | return self
220 | }
221 |
222 | /**
223 | Mask the contained image with an ellipse.
224 | Allows specifying an additional border to draw on the clipped image.
225 | For a circle, ensure the image width and height are equal!
226 |
227 | - parameter borderWidth: Optional width of the border to apply - default 0
228 | - parameter borderColor: Optional color of the border - default White
229 |
230 | - returns: Self, allowing method chaining
231 | */
232 | public func maskWithEllipse(borderWidth: CGFloat = 0, borderColor: UIColor = UIColor.white) -> Toucan {
233 | if let image = self.image {
234 | self.image = Toucan.Mask.maskImageWithEllipse(image, borderWidth: borderWidth, borderColor: borderColor)
235 | }
236 | return self
237 | }
238 |
239 | /**
240 | Mask the contained image with a path (UIBezierPath) that will be scaled to fit the image.
241 |
242 | - parameter path: UIBezierPath to mask the image
243 |
244 | - returns: Self, allowing method chaining
245 | */
246 | public func maskWithPath(path: UIBezierPath) -> Toucan {
247 | if let image = self.image {
248 | self.image = Toucan.Mask.maskImageWithPath(image, path: path)
249 | }
250 | return self
251 | }
252 |
253 | /**
254 | Mask the contained image with a path (UIBezierPath) which is provided via a closure.
255 |
256 | - parameter path: closure that returns a UIBezierPath. Using a closure allows the user to provide the path after knowing the size of the image
257 |
258 | - returns: Self, allowing method chaining
259 | */
260 | public func maskWithPathClosure(path: (_ rect: CGRect) -> (UIBezierPath)) -> Toucan {
261 | if let image = self.image {
262 | self.image = Toucan.Mask.maskImageWithPathClosure(image, pathInRect: path)
263 | }
264 | return self
265 | }
266 |
267 | /**
268 | Mask the contained image with a rounded rectangle border.
269 | Allows specifying an additional border to draw on the clipped image.
270 |
271 | - parameter cornerRadius: Radius of the rounded rect corners
272 | - parameter borderWidth: Optional width of border to apply - default 0
273 | - parameter borderColor: Optional color of the border - default White
274 |
275 | - returns: Self, allowing method chaining
276 | */
277 | public func maskWithRoundedRect(cornerRadius: CGFloat, borderWidth: CGFloat = 0, borderColor: UIColor = UIColor.white) -> Toucan {
278 | if let image = self.image {
279 | self.image = Toucan.Mask.maskImageWithRoundedRect(image, cornerRadius: cornerRadius, borderWidth: borderWidth, borderColor: borderColor)
280 | }
281 | return self
282 | }
283 |
284 | /**
285 | Container struct for all things Mask related
286 | */
287 | public struct Mask {
288 |
289 | /**
290 | Mask the given image with another image mask.
291 | Note that the areas in the original image that correspond to the black areas of the mask
292 | show through in the resulting image. The areas that correspond to the white areas of
293 | the mask aren’t painted. The areas that correspond to the gray areas in the mask are painted
294 | using an intermediate alpha value that’s equal to 1 minus the image mask sample value.
295 |
296 | - parameter image: Image to apply the mask to
297 | - parameter maskImage: Image Mask to apply to the Image
298 |
299 | - returns: Masked image
300 | */
301 | public static func maskImageWithImage(_ image: UIImage, maskImage: UIImage) -> UIImage? {
302 |
303 | let imgRef = Util.CGImageWithCorrectOrientation(image)
304 | let maskRef = maskImage.cgImage
305 |
306 | let mask = CGImage(maskWidth: (maskRef?.width)!,
307 | height: (maskRef?.height)!,
308 | bitsPerComponent: (maskRef?.bitsPerComponent)!,
309 | bitsPerPixel: (maskRef?.bitsPerPixel)!,
310 | bytesPerRow: (maskRef?.bytesPerRow)!,
311 | provider: (maskRef?.dataProvider!)!, decode: nil, shouldInterpolate: false);
312 |
313 | let masked = imgRef.masking(mask!);
314 |
315 | return Util.drawImageWithClosure(size: image.size, scale: image.scale) { (size: CGSize, context: CGContext) -> UIImage? in
316 |
317 | // need to flip the transform matrix, CoreGraphics has (0,0) in lower left when drawing image
318 | context.scaleBy(x: 1, y: -1)
319 | context.translateBy(x: 0, y: -size.height)
320 |
321 | context.draw(masked!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height));
322 |
323 | let image : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
324 | UIGraphicsEndImageContext()
325 |
326 | return image
327 | }
328 | }
329 |
330 | /**
331 | Mask the given image with an ellipse.
332 | Allows specifying an additional border to draw on the clipped image.
333 | For a circle, ensure the image width and height are equal!
334 |
335 | - parameter image: Image to apply the mask to
336 | - parameter borderWidth: Optional width of the border to apply - default 0
337 | - parameter borderColor: Optional color of the border - default White
338 |
339 | - returns: Masked image
340 | */
341 | public static func maskImageWithEllipse(_ image: UIImage,
342 | borderWidth: CGFloat = 0, borderColor: UIColor = UIColor.white) -> UIImage? {
343 |
344 | let imgRef = Util.CGImageWithCorrectOrientation(image)
345 | let size = CGSize(width: CGFloat(imgRef.width) / image.scale, height: CGFloat(imgRef.height) / image.scale)
346 |
347 | return Util.drawImageWithClosure(size: size, scale: image.scale) { (size: CGSize, context: CGContext) -> UIImage? in
348 |
349 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
350 |
351 | context.addEllipse(in: rect)
352 | context.clip()
353 | image.draw(in: rect)
354 |
355 | if (borderWidth > 0) {
356 | context.setStrokeColor(borderColor.cgColor);
357 | context.setLineWidth(borderWidth);
358 | context.addEllipse(in: CGRect(x: borderWidth / 2,
359 | y: borderWidth / 2,
360 | width: size.width - borderWidth,
361 | height: size.height - borderWidth));
362 | context.strokePath();
363 | }
364 |
365 | let image : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
366 | UIGraphicsEndImageContext()
367 |
368 | return image
369 | }
370 | }
371 |
372 | /**
373 | Mask the given image with a path(UIBezierPath) that will be scaled to fit the image.
374 |
375 | - parameter image: Image to apply the mask to
376 | - parameter path: UIBezierPath to make as the mask
377 |
378 | - returns: Masked image
379 | */
380 | public static func maskImageWithPath(_ image: UIImage,
381 | path: UIBezierPath) -> UIImage? {
382 |
383 | let imgRef = Util.CGImageWithCorrectOrientation(image)
384 | let size = CGSize(width: CGFloat(imgRef.width) / image.scale, height: CGFloat(imgRef.height) / image.scale)
385 |
386 | return Util.drawImageWithClosure(size: size, scale: image.scale) { (size: CGSize, context: CGContext) -> UIImage? in
387 |
388 | let boundSize = path.bounds.size
389 |
390 | let pathRatio = boundSize.width / boundSize.height
391 | let imageRatio = size.width / size.height
392 |
393 |
394 | if pathRatio > imageRatio {
395 | //scale based on width
396 | let scale = size.width / boundSize.width
397 | path.apply(CGAffineTransform(scaleX: scale, y: scale))
398 | path.apply(CGAffineTransform(translationX: 0, y: (size.height - path.bounds.height) / 2.0))
399 | } else {
400 | //scale based on height
401 | let scale = size.height / boundSize.height
402 | path.apply(CGAffineTransform(scaleX: scale, y: scale))
403 | path.apply(CGAffineTransform(translationX: (size.width - path.bounds.width) / 2.0, y: 0))
404 | }
405 |
406 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
407 |
408 | context.addPath(path.cgPath)
409 | context.clip()
410 | image.draw(in: rect)
411 |
412 | let image : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
413 | UIGraphicsEndImageContext()
414 |
415 | return image
416 | }
417 | }
418 |
419 | /**
420 | Mask the given image with a path(UIBezierPath) provided via a closure. This allows the user to get the size of the image before computing their path variable.
421 |
422 | - parameter image: Image to apply the mask to
423 | - parameter path: UIBezierPath to make as the mask
424 |
425 | - returns: Masked image
426 | */
427 | public static func maskImageWithPathClosure(_ image: UIImage,
428 | pathInRect:(_ rect: CGRect) -> (UIBezierPath)) -> UIImage? {
429 |
430 | let imgRef = Util.CGImageWithCorrectOrientation(image)
431 | let size = CGSize(width: CGFloat(imgRef.width) / image.scale, height: CGFloat(imgRef.height) / image.scale)
432 |
433 | return maskImageWithPath(image, path: pathInRect(CGRect(x: 0, y: 0, width: size.width, height: size.height)))
434 | }
435 |
436 | /**
437 | Mask the given image with a rounded rectangle border.
438 | Allows specifying an additional border to draw on the clipped image.
439 |
440 | - parameter image: Image to apply the mask to
441 | - parameter cornerRadius: Radius of the rounded rect corners
442 | - parameter borderWidth: Optional width of border to apply - default 0
443 | - parameter borderColor: Optional color of the border - default White
444 |
445 | - returns: Masked image
446 | */
447 | public static func maskImageWithRoundedRect(_ image: UIImage, cornerRadius: CGFloat,
448 | borderWidth: CGFloat = 0, borderColor: UIColor = UIColor.white) -> UIImage? {
449 |
450 | let imgRef = Util.CGImageWithCorrectOrientation(image)
451 | let size = CGSize(width: CGFloat(imgRef.width) / image.scale, height: CGFloat(imgRef.height) / image.scale)
452 |
453 | return Util.drawImageWithClosure(size: size, scale: image.scale) { (size: CGSize, context: CGContext) -> UIImage? in
454 |
455 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
456 |
457 | UIBezierPath(roundedRect:rect, cornerRadius: cornerRadius).addClip()
458 | image.draw(in: rect)
459 |
460 | if (borderWidth > 0) {
461 | context.setStrokeColor(borderColor.cgColor);
462 | context.setLineWidth(borderWidth);
463 |
464 | let borderRect = CGRect(x: 0, y: 0,
465 | width: size.width, height: size.height)
466 |
467 | let borderPath = UIBezierPath(roundedRect: borderRect, cornerRadius: cornerRadius)
468 | borderPath.lineWidth = borderWidth * 2
469 | borderPath.stroke()
470 | }
471 |
472 | let image : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
473 | UIGraphicsEndImageContext()
474 |
475 | return image
476 | }
477 | }
478 | }
479 |
480 | // MARK: - Layer
481 |
482 | /**
483 | Overlay an image ontop of the current image.
484 |
485 | - parameter image: Image to be on the bottom layer
486 | - parameter overlayImage: Image to be on the top layer, i.e. drawn on top of image
487 | - parameter overlayFrame: Frame of the overlay image
488 |
489 | - returns: Self, allowing method chaining
490 | */
491 | public func layerWithOverlayImage(_ overlayImage: UIImage, overlayFrame: CGRect) -> Toucan {
492 | if let image = self.image {
493 | self.image = Toucan.Layer.overlayImage(image, overlayImage:overlayImage, overlayFrame:overlayFrame)
494 | }
495 | return self
496 | }
497 |
498 | /**
499 | Container struct for all things Layer related.
500 | */
501 | public struct Layer {
502 |
503 | /**
504 | Overlay the given image into a new layout ontop of the image.
505 |
506 | - parameter image: Image to be on the bottom layer
507 | - parameter overlayImage: Image to be on the top layer, i.e. drawn on top of image
508 | - parameter overlayFrame: Frame of the overlay image
509 |
510 | - returns: Masked image
511 | */
512 | public static func overlayImage(_ image: UIImage, overlayImage: UIImage, overlayFrame: CGRect) -> UIImage? {
513 | let imgRef = Util.CGImageWithCorrectOrientation(image)
514 | let size = CGSize(width: CGFloat(imgRef.width) / image.scale, height: CGFloat(imgRef.height) / image.scale)
515 |
516 | return Util.drawImageWithClosure(size: size, scale: image.scale) { (size: CGSize, context: CGContext) -> UIImage? in
517 |
518 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
519 |
520 | image.draw(in: rect)
521 | overlayImage.draw(in: overlayFrame);
522 |
523 | let image : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
524 | UIGraphicsEndImageContext()
525 |
526 | return image
527 | }
528 | }
529 | }
530 |
531 | /**
532 | Container struct for internally used utility functions.
533 | */
534 | internal struct Util {
535 |
536 | /**
537 | Get the CGImage of the image with the orientation fixed up based on EXF data.
538 | This helps to normalise input images to always be the correct orientation when performing
539 | other core graphics tasks on the image.
540 |
541 | - parameter image: Image to create CGImageRef for
542 |
543 | - returns: CGImageRef with rotated/transformed image context
544 | */
545 | static func CGImageWithCorrectOrientation(_ image : UIImage) -> CGImage {
546 |
547 | if (image.imageOrientation == ImageOrientation.up) {
548 | return image.cgImage!
549 | }
550 |
551 | var transform : CGAffineTransform = CGAffineTransform.identity;
552 |
553 | switch (image.imageOrientation) {
554 | case ImageOrientation.right, ImageOrientation.rightMirrored:
555 | transform = transform.translatedBy(x: 0, y: image.size.height)
556 | transform = transform.rotated(by: .pi / -2.0)
557 | break
558 | case ImageOrientation.left, ImageOrientation.leftMirrored:
559 | transform = transform.translatedBy(x: image.size.width, y: 0)
560 | transform = transform.rotated(by: .pi / 2.0)
561 | break
562 | case ImageOrientation.down, ImageOrientation.downMirrored:
563 | transform = transform.translatedBy(x: image.size.width, y: image.size.height)
564 | transform = transform.rotated(by: .pi)
565 | break
566 | default:
567 | break
568 | }
569 |
570 | switch (image.imageOrientation) {
571 | case ImageOrientation.rightMirrored, ImageOrientation.leftMirrored:
572 | transform = transform.translatedBy(x: image.size.height, y: 0);
573 | transform = transform.scaledBy(x: -1, y: 1);
574 | break
575 | case ImageOrientation.downMirrored, ImageOrientation.upMirrored:
576 | transform = transform.translatedBy(x: image.size.width, y: 0);
577 | transform = transform.scaledBy(x: -1, y: 1);
578 | break
579 | default:
580 | break
581 | }
582 |
583 | let contextWidth : Int
584 | let contextHeight : Int
585 |
586 | switch (image.imageOrientation) {
587 | case ImageOrientation.left, ImageOrientation.leftMirrored,
588 | ImageOrientation.right, ImageOrientation.rightMirrored:
589 | contextWidth = (image.cgImage?.height)!
590 | contextHeight = (image.cgImage?.width)!
591 | break
592 | default:
593 | contextWidth = (image.cgImage?.width)!
594 | contextHeight = (image.cgImage?.height)!
595 | break
596 | }
597 |
598 | let context : CGContext = CGContext(data: nil, width: contextWidth, height: contextHeight,
599 | bitsPerComponent: image.cgImage!.bitsPerComponent,
600 | bytesPerRow: 0,
601 | space: image.cgImage!.colorSpace!,
602 | bitmapInfo: image.cgImage!.bitmapInfo.rawValue)!;
603 |
604 | context.concatenate(transform);
605 | context.draw(image.cgImage!, in: CGRect(x: 0, y: 0, width: CGFloat(contextWidth), height: CGFloat(contextHeight)));
606 |
607 | let cgImage = context.makeImage();
608 | return cgImage!;
609 | }
610 |
611 | /**
612 | Draw the image within the given bounds (i.e. resizes)
613 |
614 | - parameter image: Image to draw within the given bounds
615 | - parameter bounds: Bounds to draw the image within
616 |
617 | - returns: Resized image within bounds
618 | */
619 | static func drawImageInBounds(_ image: UIImage, bounds : CGRect) -> UIImage? {
620 | return drawImageWithClosure(size: bounds.size, scale: image.scale) { (size: CGSize, context: CGContext) -> UIImage? in
621 | image.draw(in: bounds)
622 |
623 | let image : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
624 | UIGraphicsEndImageContext()
625 |
626 | return image
627 | };
628 | }
629 |
630 | /**
631 | Crop the image within the given rect (i.e. resizes and crops)
632 |
633 | - parameter image: Image to clip within the given rect bounds
634 | - parameter rect: Bounds to draw the image within
635 |
636 | - returns: Resized and cropped image
637 | */
638 | static func croppedImageWithRect(_ image: UIImage, rect: CGRect) -> UIImage? {
639 | return drawImageWithClosure(size: rect.size, scale: image.scale) { (size: CGSize, context: CGContext) -> UIImage? in
640 | let drawRect = CGRect(x: -rect.origin.x, y: -rect.origin.y, width: image.size.width, height: image.size.height)
641 | context.clip(to: CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height))
642 | image.draw(in: drawRect)
643 |
644 | let image : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
645 | UIGraphicsEndImageContext()
646 |
647 | return image
648 | };
649 | }
650 |
651 | /**
652 | Closure wrapper around image context - setting up, ending and grabbing the image from the context.
653 |
654 | - parameter size: Size of the graphics context to create
655 | - parameter closure: Closure of magic to run in a new context
656 |
657 | - returns: Image pulled from the end of the closure
658 | */
659 | static func drawImageWithClosure(size: CGSize!, scale: CGFloat, closure: @escaping (_ size: CGSize, _ context: CGContext) -> UIImage?) -> UIImage? {
660 |
661 | guard size.width > 0.0 && size.height > 0.0 else {
662 | print("WARNING: Invalid size requested: \(size.width) x \(size.height) - must not be 0.0 in any dimension")
663 | return nil
664 | }
665 |
666 | UIGraphicsBeginImageContextWithOptions(size, false, scale)
667 | guard let context = UIGraphicsGetCurrentContext() else {
668 | print("WARNING: Graphics context is nil!")
669 | return nil
670 | }
671 |
672 | return closure(size, context)
673 | }
674 | }
675 | }
676 |
--------------------------------------------------------------------------------
/Source/tvOS/Toucan.tvOS-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Source/tvOS/ToucanTVOS.h:
--------------------------------------------------------------------------------
1 | // ToucanTVOS.h
2 | //
3 | // Copyright (c) 2014-2019 Gavin Bunney
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | #import
24 |
25 | //! Project version number for ToucanTVOS.
26 | FOUNDATION_EXPORT double ToucanTVOSVersionNumber;
27 |
28 | //! Project version string for ToucanTVOS.
29 | FOUNDATION_EXPORT const unsigned char ToucanTVOSVersionString[];
30 |
31 | // In this header, you should import all the public headers of your framework using statements like #import
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Tests/Assets/Landscape.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/Tests/Assets/Landscape.jpg
--------------------------------------------------------------------------------
/Tests/Assets/OctagonMask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/Tests/Assets/OctagonMask.png
--------------------------------------------------------------------------------
/Tests/Assets/Portrait.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/Tests/Assets/Portrait.jpg
--------------------------------------------------------------------------------
/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Tests/MaskingTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2014-2019 Gavin Bunney
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import UIKit
23 | import XCTest
24 | import Toucan
25 |
26 | class MaskingTests : ToucanTestCase {
27 |
28 | func testMaskWithEllipse() {
29 | let masked = Toucan(image: landscapeImage).maskWithEllipse().image!
30 |
31 | let cornerRGBA = getPixelRGBA(masked, point: CGPoint(x: 0, y: 0))
32 | XCTAssertEqual(cornerRGBA.alpha, 0.0 as CGFloat, "Check corner is transparent")
33 |
34 | let centerRGBA = getPixelRGBA(masked, point: CGPoint(x: masked.size.width / 2, y: masked.size.height / 2))
35 | XCTAssertEqual(centerRGBA.alpha, 255.0 as CGFloat, "Check center is not transparent")
36 | }
37 |
38 | func testMaskWithPath() {
39 | let path = UIBezierPath()
40 | path.move(to: CGPoint(x: 0, y: 50))
41 | path.addLine(to: CGPoint(x: 50, y: 0))
42 | path.addLine(to: CGPoint(x: 100, y: 50))
43 | path.addLine(to: CGPoint(x: 50, y: 100))
44 | path.close()
45 | let masked2 = Toucan(image: landscapeImage).resize(CGSize(width: 300, height: 250), fitMode: Toucan.Resize.FitMode.scale).maskWithPath(path: path).image!
46 |
47 | let cornerRGBA = getPixelRGBA(masked2, point: CGPoint(x: 0, y: 0))
48 | XCTAssertEqual(cornerRGBA.alpha, 0.0 as CGFloat, "Check corner is transparent")
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Tests/ResizeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2014-2019 Gavin Bunney
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import UIKit
23 | import XCTest
24 | import Toucan
25 |
26 | class ResizeTests : ToucanTestCase {
27 |
28 | func testResizePortraitClipped() {
29 | let resized = Toucan(image: portraitImage).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.clip).image!
30 | XCTAssertEqual(resized.size.width, CGFloat(369), "Verify width clipped, so smaller than original")
31 | XCTAssertEqual(resized.size.height, CGFloat(500), "Verify height largest, so equal")
32 | }
33 |
34 | func testResizeLandscapeClipped() {
35 | let resized = Toucan(image: landscapeImage).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.clip).image!
36 | XCTAssertEqual(resized.size.width, CGFloat(500), "Verify width largest, so value same")
37 | XCTAssertEqual(resized.size.height, CGFloat(335), "Verify height smallest, so clipped lower")
38 | }
39 |
40 | func testResizeSquareClipped() {
41 | let resized = Toucan(image: maskImage).resize(CGSize(width: 350, height: 350), fitMode: Toucan.Resize.FitMode.clip).image!
42 | XCTAssertEqual(resized.size.width, CGFloat(350), "Verify width not changed")
43 | XCTAssertEqual(resized.size.height, resized.size.width, "Verify height same as width")
44 | }
45 |
46 | func testResizePortraitCropped() {
47 | let resized = Toucan(image: portraitImage).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.crop).image!
48 | XCTAssertEqual(resized.size, CGSize(width: 500, height: 500), "Verify size equal")
49 | }
50 |
51 | func testResizeLandscapeCropped() {
52 | let resized = Toucan(image: landscapeImage).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.crop).image!
53 | XCTAssertEqual(resized.size, CGSize(width: 500, height: 500), "Verify size equal")
54 | }
55 |
56 | func testResizeSquareCropped() {
57 | let resized = Toucan(image: maskImage).resize(CGSize(width: 300, height: 300), fitMode: Toucan.Resize.FitMode.crop).image!
58 | XCTAssertEqual(resized.size, CGSize(width: 300, height: 300), "Verify size equal")
59 | }
60 |
61 | func testResizePortraitScaled() {
62 | let resized = Toucan(image: portraitImage).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.scale).image!
63 | XCTAssertEqual(resized.size, CGSize(width: 500, height: 500), "Verify size equal")
64 | }
65 |
66 | func testResizeLandscapeScaled() {
67 | let resized = Toucan(image: landscapeImage).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.scale).image!
68 | XCTAssertEqual(resized.size, CGSize(width: 500, height: 500), "Verify size equal")
69 | }
70 |
71 | func testResizeSquareScaled() {
72 | let resized = Toucan(image: maskImage).resize(CGSize(width: 250, height: 250), fitMode: Toucan.Resize.FitMode.scale).image!
73 | XCTAssertEqual(resized.size, CGSize(width: 250, height: 250), "Verify size equal")
74 | }
75 |
76 | func testResizeInvalidSize() {
77 | let resized = Toucan(image: maskImage).resize(CGSize(width: 0, height: 250), fitMode: Toucan.Resize.FitMode.scale).image
78 | XCTAssertNil(resized)
79 |
80 | let resized2 = Toucan(image: maskImage).resize(CGSize(width: 250, height: 0), fitMode: Toucan.Resize.FitMode.scale).image
81 | XCTAssertNil(resized2)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Tests/ToucanTestCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2014-2019 Gavin Bunney
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 |
22 | import UIKit
23 | import XCTest
24 |
25 | class ToucanTestCase : XCTestCase {
26 |
27 | internal var portraitImage : UIImage {
28 | let imageData = try? Data(contentsOf: Bundle(for: ToucanTestCase.self).url(forResource: "Portrait", withExtension: "jpg")!)
29 | let image = UIImage(data: imageData!)
30 | XCTAssertEqual(image!.size, CGSize(width: 1593, height: 2161), "Verify portrait image size")
31 | return image!
32 | }
33 |
34 | internal var landscapeImage : UIImage {
35 | let imageData = try? Data(contentsOf: Bundle(for: ToucanTestCase.self).url(forResource: "Landscape", withExtension: "jpg")!)
36 | let image = UIImage(data: imageData!)
37 | XCTAssertEqual(image!.size, CGSize(width: 3872, height: 2592), "Verify landscape image size")
38 | return image!
39 | }
40 |
41 | internal var maskImage : UIImage {
42 | let imageData = try? Data(contentsOf: Bundle(for: ToucanTestCase.self).url(forResource: "OctagonMask", withExtension: "png")!)
43 | let image = UIImage(data: imageData!)
44 | XCTAssertEqual(image!.size, CGSize(width: 500, height: 500), "Verify mask image size")
45 | return image!
46 | }
47 |
48 | internal func getPixelRGBA(_ image: UIImage, point: CGPoint) -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
49 | let pixelData : CFData = image.cgImage!.dataProvider!.data!
50 | let data: UnsafePointer = CFDataGetBytePtr(pixelData)
51 |
52 | let pixelInfo: Int = ((image.cgImage!.width * Int(point.y)) + Int(point.x)) * 4
53 |
54 | let red = CGFloat((data[pixelInfo]))
55 | let green = CGFloat((data[pixelInfo + 1]))
56 | let blue = CGFloat((data[pixelInfo + 2]))
57 | let alpha = CGFloat((data[pixelInfo + 3]))
58 |
59 | return (red, green, blue, alpha)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Toucan.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'Toucan'
3 | s.version = '1.1.0'
4 | s.license = 'MIT'
5 | s.summary = 'Fabulous Image Processing in Swift'
6 | s.homepage = 'https://github.com/gavinbunney/Toucan'
7 | s.social_media_url = 'http://twitter.com/gavinbunney'
8 | s.authors = { 'Gavin Bunney' => 'gavin@bunney.net.au' }
9 | s.source = { :git => 'https://github.com/gavinbunney/Toucan.git', :tag => s.version }
10 |
11 | s.ios.deployment_target = '8.0'
12 | s.tvos.deployment_target = '9.0'
13 |
14 | s.source_files = 'Source/*.swift'
15 | s.swift_version = '4.0'
16 |
17 | s.requires_arc = true
18 | end
19 |
--------------------------------------------------------------------------------
/Toucan.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 74BE12931C39F80A00C9521C /* Toucan.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 740AC3EA1999799900AB9A86 /* Toucan.framework */; };
11 | 74BE12941C3A017600C9521C /* Toucan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FBD0DE1A66512A009B7BC5 /* Toucan.swift */; };
12 | 74BE12991C3A025700C9521C /* ToucanTVOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 74BE12971C3A025700C9521C /* ToucanTVOS.h */; };
13 | 74FBD0DF1A66512A009B7BC5 /* Toucan.h in Headers */ = {isa = PBXBuildFile; fileRef = 74FBD0DD1A66512A009B7BC5 /* Toucan.h */; settings = {ATTRIBUTES = (Public, ); }; };
14 | 74FBD0E01A66512A009B7BC5 /* Toucan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FBD0DE1A66512A009B7BC5 /* Toucan.swift */; };
15 | 74FBD0EF1A665152009B7BC5 /* MaskingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FBD0E51A665140009B7BC5 /* MaskingTests.swift */; };
16 | 74FBD0F01A665155009B7BC5 /* ResizeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FBD0E61A665140009B7BC5 /* ResizeTests.swift */; };
17 | 74FBD0F11A665158009B7BC5 /* ToucanTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FBD0E71A665140009B7BC5 /* ToucanTestCase.swift */; };
18 | 74FBD0F21A66515F009B7BC5 /* Portrait.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 74FBD0E41A665140009B7BC5 /* Portrait.jpg */; };
19 | 74FBD0F31A665162009B7BC5 /* OctagonMask.png in Resources */ = {isa = PBXBuildFile; fileRef = 74FBD0E31A665140009B7BC5 /* OctagonMask.png */; };
20 | 74FBD0F41A665164009B7BC5 /* Landscape.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 74FBD0E21A665140009B7BC5 /* Landscape.jpg */; };
21 | /* End PBXBuildFile section */
22 |
23 | /* Begin PBXContainerItemProxy section */
24 | 74724DFA1999D58D0040117C /* PBXContainerItemProxy */ = {
25 | isa = PBXContainerItemProxy;
26 | containerPortal = 740AC3E11999799900AB9A86 /* Project object */;
27 | proxyType = 1;
28 | remoteGlobalIDString = 740AC3E91999799900AB9A86;
29 | remoteInfo = Toucan;
30 | };
31 | /* End PBXContainerItemProxy section */
32 |
33 | /* Begin PBXFileReference section */
34 | 740AC3EA1999799900AB9A86 /* Toucan.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Toucan.framework; sourceTree = BUILT_PRODUCTS_DIR; };
35 | 740AC3F51999799900AB9A86 /* ToucanTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ToucanTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
36 | 74BE12971C3A025700C9521C /* ToucanTVOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ToucanTVOS.h; sourceTree = ""; };
37 | 74BE129A1C3A027B00C9521C /* Toucan.tvOS-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Toucan.tvOS-Info.plist"; sourceTree = ""; };
38 | 74BE129B1C3A029700C9521C /* Toucan-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Toucan-Info.plist"; path = "Source/Toucan-Info.plist"; sourceTree = SOURCE_ROOT; };
39 | 74FBD0DD1A66512A009B7BC5 /* Toucan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Toucan.h; path = Source/Toucan.h; sourceTree = SOURCE_ROOT; };
40 | 74FBD0DE1A66512A009B7BC5 /* Toucan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Toucan.swift; path = Source/Toucan.swift; sourceTree = SOURCE_ROOT; };
41 | 74FBD0E21A665140009B7BC5 /* Landscape.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = Landscape.jpg; sourceTree = ""; };
42 | 74FBD0E31A665140009B7BC5 /* OctagonMask.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = OctagonMask.png; sourceTree = ""; };
43 | 74FBD0E41A665140009B7BC5 /* Portrait.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = Portrait.jpg; sourceTree = ""; };
44 | 74FBD0E51A665140009B7BC5 /* MaskingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MaskingTests.swift; path = Tests/MaskingTests.swift; sourceTree = SOURCE_ROOT; };
45 | 74FBD0E61A665140009B7BC5 /* ResizeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ResizeTests.swift; path = Tests/ResizeTests.swift; sourceTree = SOURCE_ROOT; };
46 | 74FBD0E71A665140009B7BC5 /* ToucanTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ToucanTestCase.swift; path = Tests/ToucanTestCase.swift; sourceTree = SOURCE_ROOT; };
47 | 74FBD0EE1A66514E009B7BC5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Tests/Info.plist; sourceTree = SOURCE_ROOT; };
48 | C4D770E41C2BF2C800B15C64 /* Toucan.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Toucan.framework; sourceTree = BUILT_PRODUCTS_DIR; };
49 | /* End PBXFileReference section */
50 |
51 | /* Begin PBXFrameworksBuildPhase section */
52 | 740AC3E61999799900AB9A86 /* Frameworks */ = {
53 | isa = PBXFrameworksBuildPhase;
54 | buildActionMask = 2147483647;
55 | files = (
56 | );
57 | runOnlyForDeploymentPostprocessing = 0;
58 | };
59 | 740AC3F21999799900AB9A86 /* Frameworks */ = {
60 | isa = PBXFrameworksBuildPhase;
61 | buildActionMask = 2147483647;
62 | files = (
63 | 74BE12931C39F80A00C9521C /* Toucan.framework in Frameworks */,
64 | );
65 | runOnlyForDeploymentPostprocessing = 0;
66 | };
67 | C4D770E01C2BF2C800B15C64 /* Frameworks */ = {
68 | isa = PBXFrameworksBuildPhase;
69 | buildActionMask = 2147483647;
70 | files = (
71 | );
72 | runOnlyForDeploymentPostprocessing = 0;
73 | };
74 | /* End PBXFrameworksBuildPhase section */
75 |
76 | /* Begin PBXGroup section */
77 | 740AC3E01999799900AB9A86 = {
78 | isa = PBXGroup;
79 | children = (
80 | 740AC3EC1999799900AB9A86 /* Source */,
81 | 740AC3F61999799900AB9A86 /* Tests */,
82 | 740AC3EB1999799900AB9A86 /* Products */,
83 | );
84 | sourceTree = "";
85 | };
86 | 740AC3EB1999799900AB9A86 /* Products */ = {
87 | isa = PBXGroup;
88 | children = (
89 | 740AC3EA1999799900AB9A86 /* Toucan.framework */,
90 | 740AC3F51999799900AB9A86 /* ToucanTests.xctest */,
91 | C4D770E41C2BF2C800B15C64 /* Toucan.framework */,
92 | );
93 | name = Products;
94 | sourceTree = "";
95 | };
96 | 740AC3EC1999799900AB9A86 /* Source */ = {
97 | isa = PBXGroup;
98 | children = (
99 | 74BE12951C3A025700C9521C /* tvOS */,
100 | 74FBD0DD1A66512A009B7BC5 /* Toucan.h */,
101 | 74FBD0DE1A66512A009B7BC5 /* Toucan.swift */,
102 | 74BE129B1C3A029700C9521C /* Toucan-Info.plist */,
103 | );
104 | name = Source;
105 | path = Toucan;
106 | sourceTree = "";
107 | };
108 | 740AC3F61999799900AB9A86 /* Tests */ = {
109 | isa = PBXGroup;
110 | children = (
111 | 74FBD0E11A665140009B7BC5 /* Assets */,
112 | 74FBD0E51A665140009B7BC5 /* MaskingTests.swift */,
113 | 74FBD0E61A665140009B7BC5 /* ResizeTests.swift */,
114 | 74FBD0E71A665140009B7BC5 /* ToucanTestCase.swift */,
115 | 740AC3F71999799900AB9A86 /* Supporting Files */,
116 | );
117 | name = Tests;
118 | path = ToucanTests;
119 | sourceTree = "";
120 | };
121 | 740AC3F71999799900AB9A86 /* Supporting Files */ = {
122 | isa = PBXGroup;
123 | children = (
124 | 74FBD0EE1A66514E009B7BC5 /* Info.plist */,
125 | );
126 | name = "Supporting Files";
127 | sourceTree = "";
128 | };
129 | 74BE12951C3A025700C9521C /* tvOS */ = {
130 | isa = PBXGroup;
131 | children = (
132 | 74BE129A1C3A027B00C9521C /* Toucan.tvOS-Info.plist */,
133 | 74BE12971C3A025700C9521C /* ToucanTVOS.h */,
134 | );
135 | name = tvOS;
136 | path = Source/tvOS;
137 | sourceTree = SOURCE_ROOT;
138 | };
139 | 74FBD0E11A665140009B7BC5 /* Assets */ = {
140 | isa = PBXGroup;
141 | children = (
142 | 74FBD0E21A665140009B7BC5 /* Landscape.jpg */,
143 | 74FBD0E31A665140009B7BC5 /* OctagonMask.png */,
144 | 74FBD0E41A665140009B7BC5 /* Portrait.jpg */,
145 | );
146 | name = Assets;
147 | path = Tests/Assets;
148 | sourceTree = SOURCE_ROOT;
149 | };
150 | /* End PBXGroup section */
151 |
152 | /* Begin PBXHeadersBuildPhase section */
153 | 740AC3E71999799900AB9A86 /* Headers */ = {
154 | isa = PBXHeadersBuildPhase;
155 | buildActionMask = 2147483647;
156 | files = (
157 | 74FBD0DF1A66512A009B7BC5 /* Toucan.h in Headers */,
158 | );
159 | runOnlyForDeploymentPostprocessing = 0;
160 | };
161 | C4D770E11C2BF2C800B15C64 /* Headers */ = {
162 | isa = PBXHeadersBuildPhase;
163 | buildActionMask = 2147483647;
164 | files = (
165 | 74BE12991C3A025700C9521C /* ToucanTVOS.h in Headers */,
166 | );
167 | runOnlyForDeploymentPostprocessing = 0;
168 | };
169 | /* End PBXHeadersBuildPhase section */
170 |
171 | /* Begin PBXNativeTarget section */
172 | 740AC3E91999799900AB9A86 /* Toucan */ = {
173 | isa = PBXNativeTarget;
174 | buildConfigurationList = 740AC3FD1999799900AB9A86 /* Build configuration list for PBXNativeTarget "Toucan" */;
175 | buildPhases = (
176 | 740AC3E51999799900AB9A86 /* Sources */,
177 | 740AC3E61999799900AB9A86 /* Frameworks */,
178 | 740AC3E71999799900AB9A86 /* Headers */,
179 | 740AC3E81999799900AB9A86 /* Resources */,
180 | );
181 | buildRules = (
182 | );
183 | dependencies = (
184 | );
185 | name = Toucan;
186 | productName = Toucan;
187 | productReference = 740AC3EA1999799900AB9A86 /* Toucan.framework */;
188 | productType = "com.apple.product-type.framework";
189 | };
190 | 740AC3F41999799900AB9A86 /* ToucanTests */ = {
191 | isa = PBXNativeTarget;
192 | buildConfigurationList = 740AC4001999799900AB9A86 /* Build configuration list for PBXNativeTarget "ToucanTests" */;
193 | buildPhases = (
194 | 740AC3F11999799900AB9A86 /* Sources */,
195 | 740AC3F21999799900AB9A86 /* Frameworks */,
196 | 740AC3F31999799900AB9A86 /* Resources */,
197 | );
198 | buildRules = (
199 | );
200 | dependencies = (
201 | 74724DFB1999D58D0040117C /* PBXTargetDependency */,
202 | );
203 | name = ToucanTests;
204 | productName = ToucanTests;
205 | productReference = 740AC3F51999799900AB9A86 /* ToucanTests.xctest */;
206 | productType = "com.apple.product-type.bundle.unit-test";
207 | };
208 | C4D770E31C2BF2C800B15C64 /* Toucan.tvOS */ = {
209 | isa = PBXNativeTarget;
210 | buildConfigurationList = C4D770EB1C2BF2C800B15C64 /* Build configuration list for PBXNativeTarget "Toucan.tvOS" */;
211 | buildPhases = (
212 | C4D770DF1C2BF2C800B15C64 /* Sources */,
213 | C4D770E01C2BF2C800B15C64 /* Frameworks */,
214 | C4D770E11C2BF2C800B15C64 /* Headers */,
215 | C4D770E21C2BF2C800B15C64 /* Resources */,
216 | );
217 | buildRules = (
218 | );
219 | dependencies = (
220 | );
221 | name = Toucan.tvOS;
222 | productName = ToucanTVOS;
223 | productReference = C4D770E41C2BF2C800B15C64 /* Toucan.framework */;
224 | productType = "com.apple.product-type.framework";
225 | };
226 | /* End PBXNativeTarget section */
227 |
228 | /* Begin PBXProject section */
229 | 740AC3E11999799900AB9A86 /* Project object */ = {
230 | isa = PBXProject;
231 | attributes = {
232 | LastSwiftUpdateCheck = 0700;
233 | LastUpgradeCheck = 0910;
234 | ORGANIZATIONNAME = "Gavin Bunney";
235 | TargetAttributes = {
236 | 740AC3E91999799900AB9A86 = {
237 | CreatedOnToolsVersion = 6.0;
238 | LastSwiftMigration = 0910;
239 | };
240 | 740AC3F41999799900AB9A86 = {
241 | CreatedOnToolsVersion = 6.0;
242 | LastSwiftMigration = 0910;
243 | };
244 | C4D770E31C2BF2C800B15C64 = {
245 | CreatedOnToolsVersion = 7.2;
246 | LastSwiftMigration = 0800;
247 | };
248 | };
249 | };
250 | buildConfigurationList = 740AC3E41999799900AB9A86 /* Build configuration list for PBXProject "Toucan" */;
251 | compatibilityVersion = "Xcode 8.0";
252 | developmentRegion = English;
253 | hasScannedForEncodings = 0;
254 | knownRegions = (
255 | en,
256 | );
257 | mainGroup = 740AC3E01999799900AB9A86;
258 | productRefGroup = 740AC3EB1999799900AB9A86 /* Products */;
259 | projectDirPath = "";
260 | projectRoot = "";
261 | targets = (
262 | 740AC3E91999799900AB9A86 /* Toucan */,
263 | C4D770E31C2BF2C800B15C64 /* Toucan.tvOS */,
264 | 740AC3F41999799900AB9A86 /* ToucanTests */,
265 | );
266 | };
267 | /* End PBXProject section */
268 |
269 | /* Begin PBXResourcesBuildPhase section */
270 | 740AC3E81999799900AB9A86 /* Resources */ = {
271 | isa = PBXResourcesBuildPhase;
272 | buildActionMask = 2147483647;
273 | files = (
274 | );
275 | runOnlyForDeploymentPostprocessing = 0;
276 | };
277 | 740AC3F31999799900AB9A86 /* Resources */ = {
278 | isa = PBXResourcesBuildPhase;
279 | buildActionMask = 2147483647;
280 | files = (
281 | 74FBD0F41A665164009B7BC5 /* Landscape.jpg in Resources */,
282 | 74FBD0F21A66515F009B7BC5 /* Portrait.jpg in Resources */,
283 | 74FBD0F31A665162009B7BC5 /* OctagonMask.png in Resources */,
284 | );
285 | runOnlyForDeploymentPostprocessing = 0;
286 | };
287 | C4D770E21C2BF2C800B15C64 /* Resources */ = {
288 | isa = PBXResourcesBuildPhase;
289 | buildActionMask = 2147483647;
290 | files = (
291 | );
292 | runOnlyForDeploymentPostprocessing = 0;
293 | };
294 | /* End PBXResourcesBuildPhase section */
295 |
296 | /* Begin PBXSourcesBuildPhase section */
297 | 740AC3E51999799900AB9A86 /* Sources */ = {
298 | isa = PBXSourcesBuildPhase;
299 | buildActionMask = 2147483647;
300 | files = (
301 | 74FBD0E01A66512A009B7BC5 /* Toucan.swift in Sources */,
302 | );
303 | runOnlyForDeploymentPostprocessing = 0;
304 | };
305 | 740AC3F11999799900AB9A86 /* Sources */ = {
306 | isa = PBXSourcesBuildPhase;
307 | buildActionMask = 2147483647;
308 | files = (
309 | 74FBD0EF1A665152009B7BC5 /* MaskingTests.swift in Sources */,
310 | 74FBD0F11A665158009B7BC5 /* ToucanTestCase.swift in Sources */,
311 | 74FBD0F01A665155009B7BC5 /* ResizeTests.swift in Sources */,
312 | );
313 | runOnlyForDeploymentPostprocessing = 0;
314 | };
315 | C4D770DF1C2BF2C800B15C64 /* Sources */ = {
316 | isa = PBXSourcesBuildPhase;
317 | buildActionMask = 2147483647;
318 | files = (
319 | 74BE12941C3A017600C9521C /* Toucan.swift in Sources */,
320 | );
321 | runOnlyForDeploymentPostprocessing = 0;
322 | };
323 | /* End PBXSourcesBuildPhase section */
324 |
325 | /* Begin PBXTargetDependency section */
326 | 74724DFB1999D58D0040117C /* PBXTargetDependency */ = {
327 | isa = PBXTargetDependency;
328 | target = 740AC3E91999799900AB9A86 /* Toucan */;
329 | targetProxy = 74724DFA1999D58D0040117C /* PBXContainerItemProxy */;
330 | };
331 | /* End PBXTargetDependency section */
332 |
333 | /* Begin XCBuildConfiguration section */
334 | 740AC3FB1999799900AB9A86 /* Debug */ = {
335 | isa = XCBuildConfiguration;
336 | buildSettings = {
337 | ALWAYS_SEARCH_USER_PATHS = NO;
338 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
339 | CLANG_CXX_LIBRARY = "libc++";
340 | CLANG_ENABLE_MODULES = YES;
341 | CLANG_ENABLE_OBJC_ARC = YES;
342 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
343 | CLANG_WARN_BOOL_CONVERSION = YES;
344 | CLANG_WARN_COMMA = YES;
345 | CLANG_WARN_CONSTANT_CONVERSION = YES;
346 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
347 | CLANG_WARN_EMPTY_BODY = YES;
348 | CLANG_WARN_ENUM_CONVERSION = YES;
349 | CLANG_WARN_INFINITE_RECURSION = YES;
350 | CLANG_WARN_INT_CONVERSION = YES;
351 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
352 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
353 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
354 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
355 | CLANG_WARN_STRICT_PROTOTYPES = YES;
356 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
357 | CLANG_WARN_UNREACHABLE_CODE = YES;
358 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
359 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
360 | COPY_PHASE_STRIP = NO;
361 | CURRENT_PROJECT_VERSION = 1;
362 | ENABLE_STRICT_OBJC_MSGSEND = YES;
363 | ENABLE_TESTABILITY = YES;
364 | GCC_C_LANGUAGE_STANDARD = gnu99;
365 | GCC_DYNAMIC_NO_PIC = NO;
366 | GCC_NO_COMMON_BLOCKS = YES;
367 | GCC_OPTIMIZATION_LEVEL = 0;
368 | GCC_PREPROCESSOR_DEFINITIONS = (
369 | "DEBUG=1",
370 | "$(inherited)",
371 | );
372 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
375 | GCC_WARN_UNDECLARED_SELECTOR = YES;
376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
377 | GCC_WARN_UNUSED_FUNCTION = YES;
378 | GCC_WARN_UNUSED_VARIABLE = YES;
379 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
380 | MTL_ENABLE_DEBUG_INFO = YES;
381 | ONLY_ACTIVE_ARCH = YES;
382 | SDKROOT = iphoneos;
383 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
384 | TARGETED_DEVICE_FAMILY = "1,2";
385 | VERSIONING_SYSTEM = "apple-generic";
386 | VERSION_INFO_PREFIX = "";
387 | };
388 | name = Debug;
389 | };
390 | 740AC3FC1999799900AB9A86 /* Release */ = {
391 | isa = XCBuildConfiguration;
392 | buildSettings = {
393 | ALWAYS_SEARCH_USER_PATHS = NO;
394 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
395 | CLANG_CXX_LIBRARY = "libc++";
396 | CLANG_ENABLE_MODULES = YES;
397 | CLANG_ENABLE_OBJC_ARC = YES;
398 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
399 | CLANG_WARN_BOOL_CONVERSION = YES;
400 | CLANG_WARN_COMMA = YES;
401 | CLANG_WARN_CONSTANT_CONVERSION = YES;
402 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
403 | CLANG_WARN_EMPTY_BODY = YES;
404 | CLANG_WARN_ENUM_CONVERSION = YES;
405 | CLANG_WARN_INFINITE_RECURSION = YES;
406 | CLANG_WARN_INT_CONVERSION = YES;
407 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
408 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
409 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
410 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
411 | CLANG_WARN_STRICT_PROTOTYPES = YES;
412 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
413 | CLANG_WARN_UNREACHABLE_CODE = YES;
414 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
415 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
416 | COPY_PHASE_STRIP = YES;
417 | CURRENT_PROJECT_VERSION = 1;
418 | ENABLE_NS_ASSERTIONS = NO;
419 | ENABLE_STRICT_OBJC_MSGSEND = YES;
420 | GCC_C_LANGUAGE_STANDARD = gnu99;
421 | GCC_NO_COMMON_BLOCKS = YES;
422 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
423 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
424 | GCC_WARN_UNDECLARED_SELECTOR = YES;
425 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
426 | GCC_WARN_UNUSED_FUNCTION = YES;
427 | GCC_WARN_UNUSED_VARIABLE = YES;
428 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
429 | MTL_ENABLE_DEBUG_INFO = NO;
430 | SDKROOT = iphoneos;
431 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
432 | TARGETED_DEVICE_FAMILY = "1,2";
433 | VALIDATE_PRODUCT = YES;
434 | VERSIONING_SYSTEM = "apple-generic";
435 | VERSION_INFO_PREFIX = "";
436 | };
437 | name = Release;
438 | };
439 | 740AC3FE1999799900AB9A86 /* Debug */ = {
440 | isa = XCBuildConfiguration;
441 | buildSettings = {
442 | APPLICATION_EXTENSION_API_ONLY = YES;
443 | CLANG_ENABLE_MODULES = YES;
444 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
445 | DEFINES_MODULE = YES;
446 | DYLIB_COMPATIBILITY_VERSION = 1;
447 | DYLIB_CURRENT_VERSION = 1;
448 | DYLIB_INSTALL_NAME_BASE = "@rpath";
449 | INFOPLIST_FILE = "$(SRCROOT)/Source/Toucan-Info.plist";
450 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
451 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
452 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
453 | PRODUCT_BUNDLE_IDENTIFIER = com.github.gavinbunney.Toucan;
454 | PRODUCT_NAME = "$(TARGET_NAME)";
455 | SKIP_INSTALL = YES;
456 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
457 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
458 | SWIFT_VERSION = 4.2;
459 | };
460 | name = Debug;
461 | };
462 | 740AC3FF1999799900AB9A86 /* Release */ = {
463 | isa = XCBuildConfiguration;
464 | buildSettings = {
465 | APPLICATION_EXTENSION_API_ONLY = YES;
466 | CLANG_ENABLE_MODULES = YES;
467 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
468 | DEFINES_MODULE = YES;
469 | DYLIB_COMPATIBILITY_VERSION = 1;
470 | DYLIB_CURRENT_VERSION = 1;
471 | DYLIB_INSTALL_NAME_BASE = "@rpath";
472 | INFOPLIST_FILE = "$(SRCROOT)/Source/Toucan-Info.plist";
473 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
474 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
475 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
476 | PRODUCT_BUNDLE_IDENTIFIER = com.github.gavinbunney.Toucan;
477 | PRODUCT_NAME = "$(TARGET_NAME)";
478 | SKIP_INSTALL = YES;
479 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
480 | SWIFT_VERSION = 4.2;
481 | };
482 | name = Release;
483 | };
484 | 740AC4011999799900AB9A86 /* Debug */ = {
485 | isa = XCBuildConfiguration;
486 | buildSettings = {
487 | FRAMEWORK_SEARCH_PATHS = "";
488 | GCC_PREPROCESSOR_DEFINITIONS = (
489 | "DEBUG=1",
490 | "$(inherited)",
491 | );
492 | INFOPLIST_FILE = Tests/Info.plist;
493 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
494 | PRODUCT_BUNDLE_IDENTIFIER = "au.net.bunney.$(PRODUCT_NAME:rfc1034identifier)";
495 | PRODUCT_NAME = "$(TARGET_NAME)";
496 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
497 | SWIFT_VERSION = 4.0;
498 | };
499 | name = Debug;
500 | };
501 | 740AC4021999799900AB9A86 /* Release */ = {
502 | isa = XCBuildConfiguration;
503 | buildSettings = {
504 | FRAMEWORK_SEARCH_PATHS = "";
505 | INFOPLIST_FILE = Tests/Info.plist;
506 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
507 | PRODUCT_BUNDLE_IDENTIFIER = "au.net.bunney.$(PRODUCT_NAME:rfc1034identifier)";
508 | PRODUCT_NAME = "$(TARGET_NAME)";
509 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
510 | SWIFT_VERSION = 4.0;
511 | };
512 | name = Release;
513 | };
514 | C4D770E91C2BF2C800B15C64 /* Debug */ = {
515 | isa = XCBuildConfiguration;
516 | buildSettings = {
517 | APPLICATION_EXTENSION_API_ONLY = YES;
518 | CODE_SIGN_IDENTITY = "iPhone Developer";
519 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
520 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
521 | DEBUG_INFORMATION_FORMAT = dwarf;
522 | DEFINES_MODULE = YES;
523 | DYLIB_COMPATIBILITY_VERSION = 1;
524 | DYLIB_CURRENT_VERSION = 1;
525 | DYLIB_INSTALL_NAME_BASE = "@rpath";
526 | GCC_NO_COMMON_BLOCKS = YES;
527 | INFOPLIST_FILE = "$(SRCROOT)/Source/tvOS/Toucan.tvOS-Info.plist";
528 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
529 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
530 | PRODUCT_BUNDLE_IDENTIFIER = com.github.gavinbunney.Toucan.tvOS;
531 | PRODUCT_NAME = Toucan;
532 | SDKROOT = appletvos;
533 | SKIP_INSTALL = YES;
534 | SWIFT_VERSION = 4.0;
535 | TARGETED_DEVICE_FAMILY = 3;
536 | TVOS_DEPLOYMENT_TARGET = 9.0;
537 | };
538 | name = Debug;
539 | };
540 | C4D770EA1C2BF2C800B15C64 /* Release */ = {
541 | isa = XCBuildConfiguration;
542 | buildSettings = {
543 | APPLICATION_EXTENSION_API_ONLY = YES;
544 | CODE_SIGN_IDENTITY = "iPhone Developer";
545 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
546 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
547 | COPY_PHASE_STRIP = NO;
548 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
549 | DEFINES_MODULE = YES;
550 | DYLIB_COMPATIBILITY_VERSION = 1;
551 | DYLIB_CURRENT_VERSION = 1;
552 | DYLIB_INSTALL_NAME_BASE = "@rpath";
553 | GCC_NO_COMMON_BLOCKS = YES;
554 | INFOPLIST_FILE = "$(SRCROOT)/Source/tvOS/Toucan.tvOS-Info.plist";
555 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
556 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
557 | PRODUCT_BUNDLE_IDENTIFIER = com.github.gavinbunney.Toucan.tvOS;
558 | PRODUCT_NAME = Toucan;
559 | SDKROOT = appletvos;
560 | SKIP_INSTALL = YES;
561 | SWIFT_VERSION = 4.0;
562 | TARGETED_DEVICE_FAMILY = 3;
563 | TVOS_DEPLOYMENT_TARGET = 9.0;
564 | };
565 | name = Release;
566 | };
567 | /* End XCBuildConfiguration section */
568 |
569 | /* Begin XCConfigurationList section */
570 | 740AC3E41999799900AB9A86 /* Build configuration list for PBXProject "Toucan" */ = {
571 | isa = XCConfigurationList;
572 | buildConfigurations = (
573 | 740AC3FB1999799900AB9A86 /* Debug */,
574 | 740AC3FC1999799900AB9A86 /* Release */,
575 | );
576 | defaultConfigurationIsVisible = 0;
577 | defaultConfigurationName = Release;
578 | };
579 | 740AC3FD1999799900AB9A86 /* Build configuration list for PBXNativeTarget "Toucan" */ = {
580 | isa = XCConfigurationList;
581 | buildConfigurations = (
582 | 740AC3FE1999799900AB9A86 /* Debug */,
583 | 740AC3FF1999799900AB9A86 /* Release */,
584 | );
585 | defaultConfigurationIsVisible = 0;
586 | defaultConfigurationName = Release;
587 | };
588 | 740AC4001999799900AB9A86 /* Build configuration list for PBXNativeTarget "ToucanTests" */ = {
589 | isa = XCConfigurationList;
590 | buildConfigurations = (
591 | 740AC4011999799900AB9A86 /* Debug */,
592 | 740AC4021999799900AB9A86 /* Release */,
593 | );
594 | defaultConfigurationIsVisible = 0;
595 | defaultConfigurationName = Release;
596 | };
597 | C4D770EB1C2BF2C800B15C64 /* Build configuration list for PBXNativeTarget "Toucan.tvOS" */ = {
598 | isa = XCConfigurationList;
599 | buildConfigurations = (
600 | C4D770E91C2BF2C800B15C64 /* Debug */,
601 | C4D770EA1C2BF2C800B15C64 /* Release */,
602 | );
603 | defaultConfigurationIsVisible = 0;
604 | defaultConfigurationName = Release;
605 | };
606 | /* End XCConfigurationList section */
607 | };
608 | rootObject = 740AC3E11999799900AB9A86 /* Project object */;
609 | }
610 |
--------------------------------------------------------------------------------
/Toucan.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Toucan.xcodeproj/xcshareddata/xcschemes/Toucan.tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
34 |
35 |
41 |
42 |
43 |
44 |
45 |
46 |
57 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
76 |
82 |
83 |
84 |
85 |
87 |
88 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/Toucan.xcodeproj/xcshareddata/xcschemes/Toucan.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
35 |
41 |
42 |
43 |
44 |
45 |
51 |
52 |
53 |
54 |
58 |
59 |
60 |
61 |
72 |
73 |
79 |
80 |
81 |
82 |
83 |
84 |
90 |
91 |
97 |
98 |
99 |
100 |
102 |
103 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/Toucan.xcodeproj/xcuserdata/bkirchner.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SuppressBuildableAutocreation
6 |
7 | 740AC3E91999799900AB9A86
8 |
9 | primary
10 |
11 |
12 | 740AC3F41999799900AB9A86
13 |
14 | primary
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Toucan.xcodeproj/xcuserdata/bunneyapps.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Toucan.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 740AC3E91999799900AB9A86
16 |
17 | primary
18 |
19 |
20 | 740AC3F41999799900AB9A86
21 |
22 | primary
23 |
24 |
25 | C4D770E31C2BF2C800B15C64
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Toucan.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Toucan.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ToucanPlayground.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | // ToucanPlayground.playground
2 | //
3 | // Copyright (c) 2014-2019 Gavin Bunney
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | import UIKit
24 | import Toucan
25 |
26 | //
27 | // Toucan Playground!
28 | //
29 | // Quickly explore the different featurs of Toucan.
30 | //
31 |
32 | let portraitImage = UIImage(named: "Portrait.jpg")
33 | let landscapeImage = UIImage(named: "Landscape.jpg")
34 | let octagonMask = UIImage(named: "OctagonMask.png")
35 |
36 | // ------------------------------------------------------------
37 | // Resizing
38 | // ------------------------------------------------------------
39 |
40 | // Crop will resize to fit one dimension, then crop the other
41 | Toucan(image: portraitImage!).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.crop).image
42 |
43 | // Clip will resize so one dimension is equal to the size, the other shrunk down to retain aspect ratio
44 | Toucan(image: portraitImage!).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.clip).image
45 |
46 | // Scale will resize so the image fits exactly, altering the aspect ratio
47 | Toucan(image: portraitImage!).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.scale).image
48 |
49 | // ------------------------------------------------------------
50 | // Masking
51 | // ------------------------------------------------------------
52 |
53 | let landscapeCropped = Toucan(image: landscapeImage!).resize(CGSize(width: 500, height: 500), fitMode: Toucan.Resize.FitMode.crop).image!
54 |
55 | // We can mask with an ellipse!
56 | Toucan(image: landscapeImage!).maskWithEllipse().image
57 |
58 | // Demonstrate creating a circular mask -> resizes to a square image then mask with an ellipse
59 | Toucan(image: landscapeCropped).maskWithEllipse().image!
60 |
61 | // Mask with borders too!
62 | Toucan(image: landscapeCropped).maskWithEllipse(borderWidth: 10, borderColor: UIColor.yellow).image!
63 |
64 | // Rounded Rects are all in style
65 | Toucan(image: landscapeCropped).maskWithRoundedRect(cornerRadius: 30).image!
66 |
67 | // And can be fancy with borders
68 | Toucan(image: landscapeCropped).maskWithRoundedRect(cornerRadius: 30, borderWidth: 10, borderColor: UIColor.purple).image!
69 |
70 | // Masking with an custom image mask
71 | Toucan(image: landscapeCropped).maskWithImage(maskImage: octagonMask!).image!
72 |
73 | //testing the path stuff
74 | let path = UIBezierPath()
75 | path.move(to: CGPoint(x: 0, y: 50))
76 | path.addLine(to: CGPoint(x: 50, y: 0))
77 | path.addLine(to: CGPoint(x: 100, y: 50))
78 | path.addLine(to: CGPoint(x: 50, y: 100))
79 | path.close()
80 | Toucan(image: landscapeCropped).maskWithPath(path: path).image!
81 |
82 | Toucan(image: landscapeCropped).maskWithPathClosure(path: {(rect) -> (UIBezierPath) in
83 | return UIBezierPath(roundedRect: rect, cornerRadius: 50.0)
84 | }).image!
85 |
86 |
87 | // ------------------------------------------------------------
88 | // Layers
89 | // ------------------------------------------------------------
90 |
91 | // We can draw ontop of another image
92 | Toucan(image: portraitImage!).layerWithOverlayImage(octagonMask!, overlayFrame: CGRect(x: 450, y: 400, width: 200, height: 200)).image!
93 |
94 |
95 |
--------------------------------------------------------------------------------
/ToucanPlayground.playground/Resources/Landscape.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/ToucanPlayground.playground/Resources/Landscape.jpg
--------------------------------------------------------------------------------
/ToucanPlayground.playground/Resources/OctagonMask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/ToucanPlayground.playground/Resources/OctagonMask.png
--------------------------------------------------------------------------------
/ToucanPlayground.playground/Resources/Portrait.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/ToucanPlayground.playground/Resources/Portrait.jpg
--------------------------------------------------------------------------------
/ToucanPlayground.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ToucanPlayground.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ToucanPlayground.playground/playground.xcworkspace/xcshareddata/ToucanPlayground.xccheckout:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDESourceControlProjectFavoriteDictionaryKey
6 |
7 | IDESourceControlProjectIdentifier
8 | 1BCA2CB4-74C8-4AC4-AA61-B68013EA2B1F
9 | IDESourceControlProjectName
10 | ToucanPlayground
11 | IDESourceControlProjectOriginsDictionary
12 |
13 | 80ADD6617E2E5B252D87FAA27AAF85DB06DC7FEF
14 | https://github.com/waterskier2007/Toucan.git
15 |
16 | IDESourceControlProjectPath
17 | ToucanPlayground.playground
18 | IDESourceControlProjectRelativeInstallPathDictionary
19 |
20 | 80ADD6617E2E5B252D87FAA27AAF85DB06DC7FEF
21 | ..
22 |
23 | IDESourceControlProjectURL
24 | https://github.com/waterskier2007/Toucan.git
25 | IDESourceControlProjectVersion
26 | 111
27 | IDESourceControlProjectWCCIdentifier
28 | 80ADD6617E2E5B252D87FAA27AAF85DB06DC7FEF
29 | IDESourceControlProjectWCConfigurations
30 |
31 |
32 | IDESourceControlRepositoryExtensionIdentifierKey
33 | public.vcs.git
34 | IDESourceControlWCCIdentifierKey
35 | 80ADD6617E2E5B252D87FAA27AAF85DB06DC7FEF
36 | IDESourceControlWCCName
37 | Toucan
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/ToucanPlayground.playground/timeline.xctimeline:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/assets/bird.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/bird.png
--------------------------------------------------------------------------------
/assets/examples/Mask-Custom.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/examples/Mask-Custom.jpg
--------------------------------------------------------------------------------
/assets/examples/Mask-Ellipse-Border.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/examples/Mask-Ellipse-Border.jpg
--------------------------------------------------------------------------------
/assets/examples/Mask-Ellipse-Circle.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/examples/Mask-Ellipse-Circle.jpg
--------------------------------------------------------------------------------
/assets/examples/Mask-Ellipse.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/examples/Mask-Ellipse.jpg
--------------------------------------------------------------------------------
/assets/examples/Mask-Path.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/examples/Mask-Path.jpg
--------------------------------------------------------------------------------
/assets/examples/Mask-RoundedRect-Border.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/examples/Mask-RoundedRect-Border.jpg
--------------------------------------------------------------------------------
/assets/examples/Mask-RoundedRect.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/examples/Mask-RoundedRect.jpg
--------------------------------------------------------------------------------
/assets/examples/Resize-Clip.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/examples/Resize-Clip.jpg
--------------------------------------------------------------------------------
/assets/examples/Resize-Crop.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/examples/Resize-Crop.jpg
--------------------------------------------------------------------------------
/assets/examples/Resize-Scale.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/examples/Resize-Scale.jpg
--------------------------------------------------------------------------------
/assets/toucan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/toucan.png
--------------------------------------------------------------------------------
/assets/toucan.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gavinbunney/Toucan/7c0ef6aa14d98835f4ed747848964b39b2d2077b/assets/toucan.psd
--------------------------------------------------------------------------------