image rotation

This commit is contained in:
Ivan Smolin 2017-12-20 16:09:26 +03:00
parent 99ef62dfea
commit 38eb97d60f
5 changed files with 354 additions and 183 deletions

View File

@ -274,6 +274,14 @@
671463CF1EB34B1E00EAB194 /* TestView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 671463B71EB34B1E00EAB194 /* TestView.xib */; };
67186B311EB248F100CFAFFB /* LeadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 67186B281EB248F100CFAFFB /* LeadKit.framework */; };
67186B3F1EB24A1900CFAFFB /* LeadKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 67186B201EB247A200CFAFFB /* LeadKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
672CC2A81FEA6A6A00EBFB0A /* RotateDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2A71FEA6A6A00EBFB0A /* RotateDrawingOperation.swift */; };
672CC2A91FEA6A7400EBFB0A /* RotateDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2A71FEA6A6A00EBFB0A /* RotateDrawingOperation.swift */; };
672CC2AA1FEA6A7500EBFB0A /* RotateDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2A71FEA6A6A00EBFB0A /* RotateDrawingOperation.swift */; };
672CC2AB1FEA6A7600EBFB0A /* RotateDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2A71FEA6A6A00EBFB0A /* RotateDrawingOperation.swift */; };
672CC2B31FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2B21FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift */; };
672CC2B41FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2B21FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift */; };
672CC2B51FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2B21FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift */; };
672CC2B61FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2B21FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift */; };
6740D5D21FABDA46006BB7C0 /* DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6740D5D11FABDA46006BB7C0 /* DataSourceProtocol.swift */; };
6740D5D31FABDA46006BB7C0 /* DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6740D5D11FABDA46006BB7C0 /* DataSourceProtocol.swift */; };
6740D5D41FABDA46006BB7C0 /* DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6740D5D11FABDA46006BB7C0 /* DataSourceProtocol.swift */; };
@ -558,6 +566,8 @@
67186B301EB248F100CFAFFB /* LeadKit iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "LeadKit iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
67186B411EB24AA000CFAFFB /* iOS.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = iOS.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
67186C1A1EB24B7800CFAFFB /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = "<group>"; };
672CC2A71FEA6A6A00EBFB0A /* RotateDrawingOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotateDrawingOperation.swift; sourceTree = "<group>"; };
672CC2B21FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FloatingPoint+DegreesRadiansConvertion.swift"; sourceTree = "<group>"; };
6740D5D11FABDA46006BB7C0 /* DataSourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSourceProtocol.swift; sourceTree = "<group>"; };
674AF55B1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIActivityIndicatorView+LoadingIndicator.swift"; sourceTree = "<group>"; };
6771DFD71EE99EBA002DCDAE /* DateFormattingService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateFormattingService.swift; sourceTree = "<group>"; };
@ -776,6 +786,7 @@
671461DA1EB3396E00EAB194 /* Extensions */ = {
isa = PBXGroup;
children = (
672CC2B11FEA727D00EBFB0A /* FloatingPoint */,
678B43F41FBA3D7C00D1F77D /* Views */,
67CB1BF81FAB793F0089D1B1 /* Pagination */,
67F139FF1FAB4FCC008175B4 /* Rx */,
@ -1028,6 +1039,7 @@
6714623B1EB3396E00EAB194 /* ImageDrawingOperation.swift */,
6714623C1EB3396E00EAB194 /* PaddingDrawingOperation.swift */,
6714623D1EB3396E00EAB194 /* ResizeDrawingOperation.swift */,
672CC2A71FEA6A6A00EBFB0A /* RotateDrawingOperation.swift */,
6714623E1EB3396E00EAB194 /* RoundDrawingOperation.swift */,
6714623F1EB3396E00EAB194 /* SolidFillDrawingOperation.swift */,
671462401EB3396E00EAB194 /* TemplateDrawingOperation.swift */,
@ -1112,6 +1124,14 @@
path = Tests;
sourceTree = "<group>";
};
672CC2B11FEA727D00EBFB0A /* FloatingPoint */ = {
isa = PBXGroup;
children = (
672CC2B21FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift */,
);
path = FloatingPoint;
sourceTree = "<group>";
};
674AF55A1EC45B1600038A8F /* UIActivityIndicatorView */ = {
isa = PBXGroup;
children = (
@ -2184,6 +2204,7 @@
6714639E1EB33AEB00EAB194 /* NetworkService+ActivityIndicator.swift in Sources */,
A6E0DDEF1F8A6C57002CA74E /* CellSeparatorType.swift in Sources */,
6714634C1EB3396E00EAB194 /* ReuseIdentifierProtocol.swift in Sources */,
672CC2A81FEA6A6A00EBFB0A /* RotateDrawingOperation.swift in Sources */,
671462F01EB3396E00EAB194 /* UIImage+SupportExtensions.swift in Sources */,
6771DFDE1EE99F6F002DCDAE /* DateFormattingArguments.swift in Sources */,
671462681EB3396E00EAB194 /* NetworkService.swift in Sources */,
@ -2195,6 +2216,7 @@
67F13A431FAB6256008175B4 /* PaginationLoadingViewModel.swift in Sources */,
67A1FF8F1EBCA09B00D6C89F /* UIImage+Spinner.swift in Sources */,
67CB1BFE1FAB79EE0089D1B1 /* UICollectionView+PaginationWrappable.swift in Sources */,
672CC2B31FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */,
671462901EB3396E00EAB194 /* CGImage+Crop.swift in Sources */,
671462FC1EB3396E00EAB194 /* UIView+XibNameProtocol.swift in Sources */,
671463841EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */,
@ -2321,6 +2343,7 @@
A676AE4D1F9810C1001F9214 /* Any+Cast.swift in Sources */,
67F13A3B1FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift in Sources */,
EFBE57D21EC35EF20040E00A /* Array+Extensions.swift in Sources */,
672CC2A91FEA6A7400EBFB0A /* RotateDrawingOperation.swift in Sources */,
67F139F71FAB4F22008175B4 /* TotalCountCursor.swift in Sources */,
671462821EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */,
671463561EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */,
@ -2333,6 +2356,7 @@
6714634E1EB3396E00EAB194 /* ReuseIdentifierProtocol.swift in Sources */,
6714626A1EB3396E00EAB194 /* NetworkService.swift in Sources */,
671463421EB3396E00EAB194 /* ModuleConfigurator.swift in Sources */,
672CC2B51FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */,
67F13A171FAB5A87008175B4 /* LoadingState.swift in Sources */,
671462921EB3396E00EAB194 /* CGImage+Crop.swift in Sources */,
671463861EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */,
@ -2399,6 +2423,7 @@
671462831EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */,
671463571EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */,
67F13A371FAB60C0008175B4 /* GeneralLoadingViewModel.swift in Sources */,
672CC2B61FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */,
67F139FE1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift in Sources */,
671463631EB3396E00EAB194 /* SupportProtocol.swift in Sources */,
671462871EB3396E00EAB194 /* CGContext+Initializers.swift in Sources */,
@ -2454,6 +2479,7 @@
6714625F1EB3396E00EAB194 /* LogFormatter.swift in Sources */,
6714630B1EB3396E00EAB194 /* UIView+Rotation.swift in Sources */,
6714626F1EB3396E00EAB194 /* XibView.swift in Sources */,
672CC2AA1FEA6A7500EBFB0A /* RotateDrawingOperation.swift in Sources */,
6714637F1EB3396E00EAB194 /* ImageDrawingOperation.swift in Sources */,
671463371EB3396E00EAB194 /* DrawingOperation.swift in Sources */,
67F13A221FAB5AE8008175B4 /* LoadingConfiguration.swift in Sources */,
@ -2521,6 +2547,7 @@
6771DFEB1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */,
67F13A3F1FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift in Sources */,
67A1FF951EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift in Sources */,
672CC2AB1FEA6A7600EBFB0A /* RotateDrawingOperation.swift in Sources */,
671462811EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */,
671463551EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */,
671463611EB3396E00EAB194 /* SupportProtocol.swift in Sources */,
@ -2536,6 +2563,7 @@
67051ADC1EBC7C36008EADC0 /* SpinnerView.swift in Sources */,
671462FD1EB3396E00EAB194 /* UIView+XibNameProtocol.swift in Sources */,
671463851EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */,
672CC2B41FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */,
671462D11EB3396E00EAB194 /* UIScrollView+Support.swift in Sources */,
671463911EB3396E00EAB194 /* TemplateDrawingOperation.swift in Sources */,
67F13A201FAB5AE8008175B4 /* LoadingConfiguration.swift in Sources */,

View File

@ -0,0 +1,39 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public extension FloatingPoint {
/// Converts degrees to radians
///
/// - Returns: radians
func degreesToRadians() -> Self {
return self * .pi / 180
}
/// Converts radians to degrees
///
/// - Returns: degrees
func radiansToDegrees() -> Self {
return self * 180 / .pi
}
}

View File

@ -44,7 +44,7 @@ public extension UIImage {
/// Creates an image from a UIView.
///
/// - Parameter fromView: The source view.
/// - Returns: A new instance of UIImage or nil if something goes wrong.
/// - Returns: A new instance of UIImage.
static func imageFrom(view: UIView) -> UIImage {
let operation = CALayerDrawingOperation(layer: view.layer, size: view.bounds.size)
@ -54,17 +54,15 @@ public extension UIImage {
/// Render current template UIImage into new image using given color.
///
/// - Parameter color: Color to fill template image.
/// - Returns: A new UIImage rendered with given color.
/// - Returns: A new UIImage rendered with given color or original image if something goes wrong.
func renderTemplate(withColor color: UIColor) -> UIImage {
guard let image = cgImage else {
return self
return withCGImage { image in
let operation = TemplateDrawingOperation(image: image,
imageSize: size,
color: color.cgColor)
return operation.imageFromNewRenderer(scale: scale)
}
let operation = TemplateDrawingOperation(image: image,
imageSize: size,
color: color.cgColor)
return operation.imageFromNewRenderer(scale: scale)
}
/// Creates a new image with rounded corners and border.
@ -74,49 +72,45 @@ public extension UIImage {
/// - borderWidth: The size of the border.
/// - color: The color of the border.
/// - extendSize: Extend result image size and don't overlap source image by border.
/// - Returns: A new image with rounded corners.
/// - Returns: A new image with rounded corners or original image if something goes wrong.
func roundCorners(cornerRadius: CGFloat,
borderWidth: CGFloat,
color: UIColor,
extendSize: Bool = false) -> UIImage {
guard let image = cgImage else {
return self
return withCGImage { image in
let roundOperation = RoundDrawingOperation(image: image,
imageSize: size,
radius: cornerRadius)
guard let roundImage = roundOperation.imageFromNewRenderer(scale: scale).cgImage else {
return self
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: size,
border: borderWidth,
color: color.cgColor,
radius: cornerRadius,
extendSize: extendSize)
return borderOperation.imageFromNewRenderer(scale: scale)
}
let roundOperation = RoundDrawingOperation(image: image,
imageSize: size,
radius: cornerRadius)
guard let roundImage = roundOperation.imageFromNewRenderer(scale: scale).cgImage else {
return self
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: size,
border: borderWidth,
color: color.cgColor,
radius: cornerRadius,
extendSize: extendSize)
return borderOperation.imageFromNewRenderer(scale: scale)
}
/// Creates a new circle image.
///
/// - Returns: A new circled image.
/// - Returns: A new circled image or original image if something goes wrong.
func roundCornersToCircle() -> UIImage {
guard let image = cgImage else {
return self
return withCGImage { image in
let radius = CGFloat(min(size.width, size.height) / 2)
let operation = RoundDrawingOperation(image: image,
imageSize: size,
radius: radius)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
let radius = CGFloat(min(size.width, size.height) / 2)
let operation = RoundDrawingOperation(image: image,
imageSize: size,
radius: radius)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
/// Creates a new circle image with a border.
@ -125,33 +119,31 @@ public extension UIImage {
/// - borderWidth: The size of the border.
/// - borderColor: The color of the border.
/// - extendSize: Extend result image size and don't overlap source image by border (default = false).
/// - Returns: A new image with rounded corners or nil if something goes wrong.
/// - Returns: A new image with rounded corners or original image if something goes wrong.
func roundCornersToCircle(borderWidth: CGFloat,
borderColor: UIColor,
extendSize: Bool = false) -> UIImage {
guard let image = cgImage else {
return self
return withCGImage { image in
let radius = CGFloat(min(size.width, size.height) / 2)
let roundOperation = RoundDrawingOperation(image: image,
imageSize: size,
radius: radius)
guard let roundImage = roundOperation.imageFromNewRenderer(scale: scale).cgImage else {
return self
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: size,
border: borderWidth,
color: borderColor.cgColor,
radius: radius,
extendSize: extendSize)
return borderOperation.imageFromNewRenderer(scale: scale)
}
let radius = CGFloat(min(size.width, size.height) / 2)
let roundOperation = RoundDrawingOperation(image: image,
imageSize: size,
radius: radius)
guard let roundImage = roundOperation.imageFromNewRenderer(scale: scale).cgImage else {
return self
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: size,
border: borderWidth,
color: borderColor.cgColor,
radius: radius,
extendSize: extendSize)
return borderOperation.imageFromNewRenderer(scale: scale)
}
/// Creates a resized copy of an image.
@ -161,62 +153,85 @@ public extension UIImage {
/// - contentMode: The way to handle the content in the new size.
/// - cropToImageBounds: Should output image size match resized image size.
/// Note: If passed true with ResizeMode.scaleAspectFit content mode it will give the original image.
/// - Returns: A new image scaled to new size.
/// - Returns: A new image scaled to new size or original image if something goes wrong.
func resize(newSize: CGSize,
contentMode: ResizeMode = .scaleToFill,
cropToImageBounds: Bool = false) -> UIImage {
guard let image = cgImage else {
return self
return withCGImage { image in
let operation = ResizeDrawingOperation(image: image,
imageSize: size,
preferredNewSize: newSize,
resizeMode: contentMode,
cropToImageBounds: cropToImageBounds)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
let operation = ResizeDrawingOperation(image: image,
imageSize: size,
preferredNewSize: newSize,
resizeMode: contentMode,
cropToImageBounds: cropToImageBounds)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
/// Adds an alpha channel if UIImage doesn't already have one.
///
/// - Returns: A copy of the given image, adding an alpha channel if it doesn't already have one.
func applyAlpha() -> UIImage {
guard let image = cgImage, !image.hasAlpha else {
return self
return withCGImage { image in
guard !image.hasAlpha else {
return self
}
let operation = ImageDrawingOperation(image: image,
newSize: size,
opaque: false)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
let operation = ImageDrawingOperation(image: image,
newSize: size,
opaque: false)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
/// Creates a copy of the image with border of the given size added around its edges.
///
/// - Parameter padding: The padding amount.
/// - Returns: A new padded image or nil if something goes wrong.
/// - Returns: A new padded image or original image if something goes wrong.
func applyPadding(_ padding: CGFloat) -> UIImage {
guard let image = cgImage else {
return self
return withCGImage { image in
let operation = PaddingDrawingOperation(image: image, imageSize: size, padding: padding)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
}
let operation = PaddingDrawingOperation(image: image, imageSize: size, padding: padding)
/// Creates a copy of the image rotated by the given amount of degrees.
///
/// - Parameters:
/// - degrees: The number of degrees.
/// - clockwise: Should rotate image clockwise.
/// - Returns: A new rotated image or original image if something goes wrong.
func rotate(degrees: CGFloat, clockwise: Bool = true) -> UIImage {
return withCGImage { image in
let radians = degrees.degreesToRadians()
return operation.imageFromNewRenderer(scale: scale).redraw()
let operation = RotateDrawingOperation(image: image,
imageSize: size,
radians: radians,
clockwise: clockwise)
return operation.imageFromNewRenderer(scale: scale)
}
}
/// Workaround to fix flipped image rendering (by Y)
private func redraw() -> UIImage {
return withCGImage { image in
let operation = ImageDrawingOperation(image: image, newSize: size)
return operation.imageFromNewRenderer(scale: scale)
}
}
private func withCGImage(_ actionClosure: (CGImage) -> UIImage) -> UIImage {
guard let image = cgImage else {
return self
}
let operation = ImageDrawingOperation(image: image, newSize: size)
return operation.imageFromNewRenderer(scale: scale)
return actionClosure(image)
}
}

View File

@ -44,9 +44,15 @@ public extension Support where Base: UIImage {
/// - Parameter fromView: The source view.
/// - Returns: A new instance of UIImage or nil if something goes wrong.
static func imageFrom(view: UIView) -> Support<UIImage>? {
let layerDrawingOperation = CALayerDrawingOperation(layer: view.layer, size: view.bounds.size)
let operation = CALayerDrawingOperation(layer: view.layer, size: view.bounds.size)
return layerDrawingOperation.imageFromNewContext(scale: UIScreen.main.scale)?.support.flipY()
guard let rotatedImage = operation.imageFromNewContext(scale: UIScreen.main.scale) else {
return nil
}
let flipOperation = rotatedImage.cgImage?.flipYOperation(size: rotatedImage.size)
return flipOperation?.imageFromNewContext(scale: rotatedImage.scale)?.support
}
/// Render current template UIImage into new image using given color.
@ -54,15 +60,19 @@ public extension Support where Base: UIImage {
/// - Parameter color: Color to fill template image.
/// - Returns: A new UIImage rendered with given color or nil if something goes wrong.
func renderTemplate(withColor color: UIColor) -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
return withCGImage { image in
let operation = TemplateDrawingOperation(image: image,
imageSize: base.size,
color: color.cgColor)
guard let templateImage = operation.imageFromNewContext(scale: base.scale) else {
return nil
}
let flipOperation = templateImage.cgImage?.flipYOperation(size: templateImage.size)
return flipOperation?.imageFromNewContext(scale: templateImage.scale)
}
let operation = TemplateDrawingOperation(image: image,
imageSize: base.size,
color: color.cgColor)
return operation.imageFromNewContext(scale: base.scale)?.support.flipY()
}
/// Creates a new image with rounded corners and border.
@ -78,43 +88,39 @@ public extension Support where Base: UIImage {
color: UIColor,
extendSize: Bool = false) -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
return withCGImage { image in
let roundOperation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: cornerRadius)
guard let roundImage = roundOperation.cgImageFromNewContext(scale: base.scale) else {
return nil
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: base.size,
border: borderWidth,
color: color.cgColor,
radius: cornerRadius,
extendSize: extendSize)
return borderOperation.imageFromNewContext(scale: base.scale)
}
let roundOperation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: cornerRadius)
guard let roundImage = roundOperation.cgImageFromNewContext(scale: base.scale) else {
return nil
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: base.size,
border: borderWidth,
color: color.cgColor,
radius: cornerRadius,
extendSize: extendSize)
return borderOperation.imageFromNewContext(scale: base.scale)?.support
}
/// Creates a new circle image.
///
/// - Returns: A new circled image or nil if something goes wrong.
func roundCornersToCircle() -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
return withCGImage { image in
let radius = CGFloat(min(base.size.width, base.size.height) / 2)
let operation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: radius)
return operation.imageFromNewContext(scale: base.scale)
}
let radius = CGFloat(min(base.size.width, base.size.height) / 2)
let operation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: radius)
return operation.imageFromNewContext(scale: base.scale)?.support
}
/// Creates a new circle image with a border.
@ -128,28 +134,26 @@ public extension Support where Base: UIImage {
borderColor: UIColor,
extendSize: Bool = false) -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
return withCGImage { image in
let radius = CGFloat(min(base.size.width, base.size.height) / 2)
let roundOperation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: radius)
guard let roundImage = roundOperation.cgImageFromNewContext(scale: base.scale) else {
return nil
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: base.size,
border: borderWidth,
color: borderColor.cgColor,
radius: radius,
extendSize: extendSize)
return borderOperation.imageFromNewContext(scale: base.scale)
}
let radius = CGFloat(min(base.size.width, base.size.height) / 2)
let roundOperation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: radius)
guard let roundImage = roundOperation.cgImageFromNewContext(scale: base.scale) else {
return nil
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: base.size,
border: borderWidth,
color: borderColor.cgColor,
radius: radius,
extendSize: extendSize)
return borderOperation.imageFromNewContext(scale: base.scale)?.support
}
/// Creates a resized copy of an image.
@ -164,32 +168,28 @@ public extension Support where Base: UIImage {
contentMode: ResizeMode = .scaleToFill,
cropToImageBounds: Bool = false) -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
return withCGImage { image in
let operation = ResizeDrawingOperation(image: image,
imageSize: base.size,
preferredNewSize: newSize,
resizeMode: contentMode,
cropToImageBounds: cropToImageBounds)
return operation.imageFromNewContext(scale: base.scale)
}
let operation = ResizeDrawingOperation(image: image,
imageSize: base.size,
preferredNewSize: newSize,
resizeMode: contentMode,
cropToImageBounds: cropToImageBounds)
return operation.imageFromNewContext(scale: base.scale)?.support
}
/// Adds an alpha channel if UIImage doesn't already have one.
///
/// - Returns: A copy of the given image, adding an alpha channel if it doesn't already have one.
func applyAlpha() -> Support<UIImage>? {
guard let image = base.cgImage, !image.hasAlpha else {
return Support<UIImage>(base)
return withCGImage { image in
let operation = ImageDrawingOperation(image: image,
newSize: base.size,
opaque: false)
return operation.imageFromNewContext(scale: base.scale)
}
let operation = ImageDrawingOperation(image: image,
newSize: base.size,
opaque: false)
return operation.imageFromNewContext(scale: base.scale)?.support
}
/// Creates a copy of the image with border of the given size added around its edges.
@ -197,29 +197,58 @@ public extension Support where Base: UIImage {
/// - Parameter padding: The padding amount.
/// - Returns: A new padded image or nil if something goes wrong.
func applyPadding(_ padding: CGFloat) -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
return withCGImage { image in
let operation = PaddingDrawingOperation(image: image,
imageSize: base.size,
padding: padding)
return operation.imageFromNewContext(scale: base.scale)
}
let operation = PaddingDrawingOperation(image: image,
imageSize: base.size,
padding: padding)
return operation.imageFromNewContext(scale: base.scale)?.support
}
private func flipY() -> Support<UIImage>? {
/// Creates a copy of the image rotated by the given amount of degrees.
///
/// - Parameters:
/// - degrees: The number of degrees.
/// - clockwise: Should rotate image clockwise.
/// - Returns: A new rotated image or nil if something goes wrong.
func rotate(degrees: CGFloat, clockwise: Bool = true) -> Support<UIImage>? {
return withCGImage { image in
let radians = degrees.degreesToRadians()
let operation = RotateDrawingOperation(image: image,
imageSize: base.size,
radians: radians,
clockwise: clockwise)
guard let rotatedImage = operation.imageFromNewContext(scale: base.scale) else {
return nil
}
let flipOperation = rotatedImage.cgImage?.flipYOperation(size: rotatedImage.size)
return flipOperation?.imageFromNewContext(scale: rotatedImage.scale)
}
}
private func withCGImage(_ actionClosure: (CGImage) -> UIImage?) -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
}
let flipOperation = ImageDrawingOperation(image: image,
newSize: base.size,
origin: .zero,
opaque: false,
flipY: true)
return actionClosure(image)?.support
}
return flipOperation.imageFromNewContext(scale: base.scale)?.support
}
private extension CGImage {
func flipYOperation(size: CGSize) -> ImageDrawingOperation {
return ImageDrawingOperation(image: self,
newSize: size,
origin: .zero,
opaque: false,
flipY: true)
}
}

View File

@ -0,0 +1,60 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
struct RotateDrawingOperation: DrawingOperation {
private let image: CGImage
private let imageSize: CGSize
private let radians: CGFloat
private let translateRect: CGRect
public init(image: CGImage, imageSize: CGSize, radians: CGFloat, clockwise: Bool = true) {
self.image = image
self.imageSize = imageSize
self.radians = clockwise ? radians : -radians
let transform = CGAffineTransform(rotationAngle: radians)
let imageRect = CGRect(origin: .zero, size: imageSize)
translateRect = CGRect(origin: .zero, size: imageRect.applying(transform).size)
}
public var contextSize: CGContextSize {
return translateRect.size.ceiledContextSize
}
public func apply(in context: CGContext) {
context.translateBy(x: translateRect.midX, y: translateRect.midY)
context.rotate(by: radians)
context.scaleBy(x: 1.0, y: -1.0)
let imageLocation = CGRect(origin: CGPoint(x: -imageSize.width / 2, y: -imageSize.height / 2),
size: imageSize)
context.draw(image, in: imageLocation)
}
}