├── .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 | ![Toucan: Fabulous Image Processing in Swift](https://raw.githubusercontent.com/gavinbunney/Toucan/master/assets/toucan.png) 2 | 3 | [![Build Status](https://travis-ci.org/gavinbunney/Toucan.svg)](https://travis-ci.org/gavinbunney/Toucan) 4 | [![CocoaPods](https://img.shields.io/cocoapods/v/Toucan.svg?style=flat)](https://cocoapods.org/pods/Toucan) 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](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](https://raw.githubusercontent.com/gavinbunney/Toucan/master/assets/examples/Resize-Clip.jpg)|**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](https://raw.githubusercontent.com/gavinbunney/Toucan/master/assets/examples/Resize-Crop.jpg)|**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](https://raw.githubusercontent.com/gavinbunney/Toucan/master/assets/examples/Resize-Scale.jpg)|**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 | ![Ellipse Mask](https://raw.githubusercontent.com/gavinbunney/Toucan/master/assets/examples/Mask-Ellipse-Circle.jpg)|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 | ![Ellipse Mask w. Border](https://raw.githubusercontent.com/gavinbunney/Toucan/master/assets/examples/Mask-Ellipse-Border.jpg)|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 | ![Path Mask](https://raw.githubusercontent.com/gavinbunney/Toucan/master/assets/examples/Mask-Path.jpg)|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 | ![Path Mask w. Closure](https://raw.githubusercontent.com/gavinbunney/Toucan/master/assets/examples/Mask-Path.jpg)|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 | ![Rounded Rect Mask](https://raw.githubusercontent.com/gavinbunney/Toucan/master/assets/examples/Mask-RoundedRect.jpg)|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 | ![Rounded Rect Mask w. Border](https://raw.githubusercontent.com/gavinbunney/Toucan/master/assets/examples/Mask-RoundedRect-Border.jpg)|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 | ![Image Mask](https://raw.githubusercontent.com/gavinbunney/Toucan/master/assets/examples/Mask-Custom.jpg)|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 --------------------------------------------------------------------------------