Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
//
2
//  UIImage+AlamofireImage.swift
3
//
4
//  Copyright (c) 2015 Alamofire Software Foundation (http://alamofire.org/)
5
//
6
//  Permission is hereby granted, free of charge, to any person obtaining a copy
7
//  of this software and associated documentation files (the "Software"), to deal
8
//  in the Software without restriction, including without limitation the rights
9
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
//  copies of the Software, and to permit persons to whom the Software is
11
//  furnished to do so, subject to the following conditions:
12
//
13
//  The above copyright notice and this permission notice shall be included in
14
//  all copies or substantial portions of the Software.
15
//
16
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
//  THE SOFTWARE.
23
//
24
 
25
#if os(iOS) || os(tvOS) || os(watchOS)
26
 
27
import Alamofire
28
import CoreGraphics
29
import Foundation
30
import UIKit
31
 
32
// MARK: Initialization
33
 
34
private let lock = NSLock()
35
 
36
extension UIImage: AlamofireExtended {}
37
extension AlamofireExtension where ExtendedType: UIImage {
38
    /// Initializes and returns the image object with the specified data in a thread-safe manner.
39
    ///
40
    /// It has been reported that there are thread-safety issues when initializing large amounts of images
41
    /// simultaneously. In the event of these issues occurring, this method can be used in place of
42
    /// the `init?(data:)` method.
43
    ///
44
    /// - parameter data: The data object containing the image data.
45
    ///
46
    /// - returns: An initialized `UIImage` object, or `nil` if the method failed.
47
    public static func threadSafeImage(with data: Data) -> UIImage? {
48
        lock.lock()
49
        let image = UIImage(data: data)
50
        lock.unlock()
51
 
52
        return image
53
    }
54
 
55
    /// Initializes and returns the image object with the specified data and scale in a thread-safe manner.
56
    ///
57
    /// It has been reported that there are thread-safety issues when initializing large amounts of images
58
    /// simultaneously. In the event of these issues occurring, this method can be used in place of
59
    /// the `init?(data:scale:)` method.
60
    ///
61
    /// - parameter data:  The data object containing the image data.
62
    /// - parameter scale: The scale factor to assume when interpreting the image data. Applying a scale factor of 1.0
63
    ///                    results in an image whose size matches the pixel-based dimensions of the image. Applying a
64
    ///                    different scale factor changes the size of the image as reported by the size property.
65
    ///
66
    /// - returns: An initialized `UIImage` object, or `nil` if the method failed.
67
    public static func threadSafeImage(with data: Data, scale: CGFloat) -> UIImage? {
68
        lock.lock()
69
        let image = UIImage(data: data, scale: scale)
70
        lock.unlock()
71
 
72
        return image
73
    }
74
}
75
 
76
extension UIImage {
77
    @available(*, deprecated, message: "Replaced by `UIImage.af.threadSafeImage(with:)`")
78
    public static func af_threadSafeImage(with data: Data) -> UIImage? {
79
        af.threadSafeImage(with: data)
80
    }
81
 
82
    @available(*, deprecated, message: "Replaced by `UIImage.af.threadSafeImage(with:scale:)`")
83
    public static func af_threadSafeImage(with data: Data, scale: CGFloat) -> UIImage? {
84
        af.threadSafeImage(with: data, scale: scale)
85
    }
86
}
87
 
88
// MARK: - Inflation
89
 
90
extension AlamofireExtension where ExtendedType: UIImage {
91
    /// Returns whether the image is inflated.
92
    public var isInflated: Bool {
93
        get {
94
            if let isInflated = objc_getAssociatedObject(type, &AssociatedKeys.isInflated) as? Bool {
95
                return isInflated
96
            } else {
97
                return false
98
            }
99
        }
100
        nonmutating set {
101
            objc_setAssociatedObject(type, &AssociatedKeys.isInflated, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
102
        }
103
    }
104
 
105
    /// Inflates the underlying compressed image data to be backed by an uncompressed bitmap representation.
106
    ///
107
    /// Inflating compressed image formats (such as PNG or JPEG) can significantly improve drawing performance as it
108
    /// allows a bitmap representation to be constructed in the background rather than on the main thread.
109
    public func inflate() {
110
        guard !isInflated else { return }
111
 
112
        isInflated = true
113
        _ = type.cgImage?.dataProvider?.data
114
    }
115
}
116
 
117
extension UIImage {
118
    @available(*, deprecated, message: "Replaced by `image.af.isInflated`")
119
    public var af_inflated: Bool {
120
        af.isInflated
121
    }
122
 
123
    @available(*, deprecated, message: "Replaced by `image.af.inflate()`")
124
    public func af_inflate() {
125
        af.inflate()
126
    }
127
}
128
 
129
// MARK: - Alpha
130
 
131
extension AlamofireExtension where ExtendedType: UIImage {
132
    /// Returns whether the image contains an alpha component.
133
    public var containsAlphaComponent: Bool {
134
        let alphaInfo = type.cgImage?.alphaInfo
135
 
136
        return (
137
            alphaInfo == .first ||
138
                alphaInfo == .last ||
139
                alphaInfo == .premultipliedFirst ||
140
                alphaInfo == .premultipliedLast
141
        )
142
    }
143
 
144
    /// Returns whether the image is opaque.
145
    public var isOpaque: Bool { !containsAlphaComponent }
146
}
147
 
148
extension UIImage {
149
    @available(*, deprecated, message: "Replaced by `image.af.containsAlphaComponent`")
150
    public var af_containsAlphaComponent: Bool { af.containsAlphaComponent }
151
 
152
    @available(*, deprecated, message: "Replaced by `image.af.isOpaque`")
153
    public var af_isOpaque: Bool { af.isOpaque }
154
}
155
 
156
// MARK: - Scaling
157
 
158
extension AlamofireExtension where ExtendedType: UIImage {
159
    /// Returns a new version of the image scaled to the specified size.
160
    ///
161
    /// - Parameters:
162
    ///   - size: The size to use when scaling the new image.
163
    ///   - scale: The scale to set for the new image. Defaults to `nil` which will maintain the current image scale.
164
    ///
165
    /// - Returns: The new image object.
166
    public func imageScaled(to size: CGSize, scale: CGFloat? = nil) -> UIImage {
167
        assert(size.width > 0 && size.height > 0, "You cannot safely scale an image to a zero width or height")
168
 
169
        UIGraphicsBeginImageContextWithOptions(size, isOpaque, scale ?? type.scale)
170
        type.draw(in: CGRect(origin: .zero, size: size))
171
 
172
        let scaledImage = UIGraphicsGetImageFromCurrentImageContext() ?? type
173
        UIGraphicsEndImageContext()
174
 
175
        return scaledImage
176
    }
177
 
178
    /// Returns a new version of the image scaled from the center while maintaining the aspect ratio to fit within
179
    /// a specified size.
180
    ///
181
    /// The resulting image contains an alpha component used to pad the width or height with the necessary transparent
182
    /// pixels to fit the specified size. In high performance critical situations, this may not be the optimal approach.
183
    /// To maintain an opaque image, you could compute the `scaledSize` manually, then use the `af.imageScaledToSize`
184
    /// method in conjunction with a `.Center` content mode to achieve the same visual result.
185
    ///
186
    /// - Parameters:
187
    ///   - size: The size to use when scaling the new image.
188
    ///   - scale: The scale to set for the new image. Defaults to `nil` which will maintain the current image scale.
189
    ///
190
    /// - Returns: A new image object.
191
    public func imageAspectScaled(toFit size: CGSize, scale: CGFloat? = nil) -> UIImage {
192
        assert(size.width > 0 && size.height > 0, "You cannot safely scale an image to a zero width or height")
193
 
194
        let imageAspectRatio = type.size.width / type.size.height
195
        let canvasAspectRatio = size.width / size.height
196
 
197
        var resizeFactor: CGFloat
198
 
199
        if imageAspectRatio > canvasAspectRatio {
200
            resizeFactor = size.width / type.size.width
201
        } else {
202
            resizeFactor = size.height / type.size.height
203
        }
204
 
205
        let scaledSize = CGSize(width: type.size.width * resizeFactor, height: type.size.height * resizeFactor)
206
        let origin = CGPoint(x: (size.width - scaledSize.width) / 2.0, y: (size.height - scaledSize.height) / 2.0)
207
 
208
        UIGraphicsBeginImageContextWithOptions(size, false, scale ?? type.scale)
209
        type.draw(in: CGRect(origin: origin, size: scaledSize))
210
 
211
        let scaledImage = UIGraphicsGetImageFromCurrentImageContext() ?? type
212
        UIGraphicsEndImageContext()
213
 
214
        return scaledImage
215
    }
216
 
217
    /// Returns a new version of the image scaled from the center while maintaining the aspect ratio to fill a
218
    /// specified size. Any pixels that fall outside the specified size are clipped.
219
    ///
220
    /// - Parameters:
221
    ///   - size: The size to use when scaling the new image.
222
    ///   - scale: The scale to set for the new image. Defaults to `nil` which will maintain the current image scale.
223
    ///
224
    /// - Returns: A new image object.
225
    public func imageAspectScaled(toFill size: CGSize, scale: CGFloat? = nil) -> UIImage {
226
        assert(size.width > 0 && size.height > 0, "You cannot safely scale an image to a zero width or height")
227
 
228
        let imageAspectRatio = type.size.width / type.size.height
229
        let canvasAspectRatio = size.width / size.height
230
 
231
        var resizeFactor: CGFloat
232
 
233
        if imageAspectRatio > canvasAspectRatio {
234
            resizeFactor = size.height / type.size.height
235
        } else {
236
            resizeFactor = size.width / type.size.width
237
        }
238
 
239
        let scaledSize = CGSize(width: type.size.width * resizeFactor, height: type.size.height * resizeFactor)
240
        let origin = CGPoint(x: (size.width - scaledSize.width) / 2.0, y: (size.height - scaledSize.height) / 2.0)
241
 
242
        UIGraphicsBeginImageContextWithOptions(size, isOpaque, scale ?? type.scale)
243
        type.draw(in: CGRect(origin: origin, size: scaledSize))
244
 
245
        let scaledImage = UIGraphicsGetImageFromCurrentImageContext() ?? type
246
        UIGraphicsEndImageContext()
247
 
248
        return scaledImage
249
    }
250
}
251
 
252
extension UIImage {
253
    @available(*, deprecated, message: "Replaced by `image.af.imageScale(to:scale:)`")
254
    public func af_imageScaled(to size: CGSize, scale: CGFloat? = nil) -> UIImage {
255
        af.imageScaled(to: size, scale: scale)
256
    }
257
 
258
    @available(*, deprecated, message: "Replaced by `image.af.imageAspectScale(toFit:scale:)`")
259
    public func af_imageAspectScaled(toFit size: CGSize, scale: CGFloat? = nil) -> UIImage {
260
        af.imageAspectScaled(toFit: size, scale: scale)
261
    }
262
 
263
    @available(*, deprecated, message: "Replaced by `image.af.imageAspectScale(toFill:scale:)`")
264
    public func af_imageAspectScaled(toFill size: CGSize, scale: CGFloat? = nil) -> UIImage {
265
        af.imageAspectScaled(toFill: size, scale: scale)
266
    }
267
}
268
 
269
// MARK: - Rounded Corners
270
 
271
extension AlamofireExtension where ExtendedType: UIImage {
272
    /// Returns a new version of the image with the corners rounded to the specified radius.
273
    ///
274
    /// - Parameters:
275
    ///   - radius:                   The radius to use when rounding the new image.
276
    ///   - divideRadiusByImageScale: Whether to divide the radius by the image scale. Set to `true` when the image has
277
    ///                               the same resolution for all screen scales such as @1x, @2x and @3x (i.e. single
278
    ///                               image from web server). Set to `false` for images loaded from an asset catalog
279
    ///                               with varying resolutions for each screen scale. `false` by default.
280
    ///
281
    /// - Returns: A new image object.
282
    public func imageRounded(withCornerRadius radius: CGFloat, divideRadiusByImageScale: Bool = false) -> UIImage {
283
        let size = type.size
284
        let scale = type.scale
285
 
286
        UIGraphicsBeginImageContextWithOptions(size, false, scale)
287
 
288
        let scaledRadius = divideRadiusByImageScale ? radius / scale : radius
289
 
290
        let clippingPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint.zero, size: size), cornerRadius: scaledRadius)
291
        clippingPath.addClip()
292
 
293
        type.draw(in: CGRect(origin: CGPoint.zero, size: size))
294
 
295
        let roundedImage = UIGraphicsGetImageFromCurrentImageContext()!
296
        UIGraphicsEndImageContext()
297
 
298
        return roundedImage
299
    }
300
 
301
    /// Returns a new version of the image rounded into a circle.
302
    ///
303
    /// - Returns: A new image object.
304
    public func imageRoundedIntoCircle() -> UIImage {
305
        let size = type.size
306
        let radius = min(size.width, size.height) / 2.0
307
        var squareImage: UIImage = type
308
 
309
        if size.width != size.height {
310
            let squareDimension = min(size.width, size.height)
311
            let squareSize = CGSize(width: squareDimension, height: squareDimension)
312
            squareImage = imageAspectScaled(toFill: squareSize)
313
        }
314
 
315
        UIGraphicsBeginImageContextWithOptions(squareImage.size, false, type.scale)
316
 
317
        let clippingPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint.zero, size: squareImage.size),
318
                                        cornerRadius: radius)
319
 
320
        clippingPath.addClip()
321
 
322
        squareImage.draw(in: CGRect(origin: CGPoint.zero, size: squareImage.size))
323
 
324
        let roundedImage = UIGraphicsGetImageFromCurrentImageContext()!
325
        UIGraphicsEndImageContext()
326
 
327
        return roundedImage
328
    }
329
}
330
 
331
extension UIImage {
332
    @available(*, deprecated, message: "Replaced by `image.af.imageRounded(withCornerRadius:divideRadiusByImageScale:)`")
333
    public func af_imageRounded(withCornerRadius radius: CGFloat, divideRadiusByImageScale: Bool = false) -> UIImage {
334
        af.imageRounded(withCornerRadius: radius, divideRadiusByImageScale: divideRadiusByImageScale)
335
    }
336
 
337
    @available(*, deprecated, message: "Replaced by `image.af.imageRoundedIntoCircle()`")
338
    public func af_imageRoundedIntoCircle() -> UIImage {
339
        af.imageRoundedIntoCircle()
340
    }
341
}
342
 
343
#endif
344
 
345
#if os(iOS) || os(tvOS)
346
 
347
import CoreImage
348
 
349
// MARK: - Core Image Filters
350
 
351
extension AlamofireExtension where ExtendedType: UIImage {
352
    /// Returns a new version of the image using a CoreImage filter with the specified name and parameters.
353
    ///
354
    /// - Parameters:
355
    ///   - name:       The name of the CoreImage filter to use on the new image.
356
    ///   - parameters: The parameters to apply to the CoreImage filter.
357
    ///
358
    /// - Returns: A new image object, or `nil` if the filter failed for any reason.
359
    public func imageFiltered(withCoreImageFilter name: String, parameters: [String: Any]? = nil) -> UIImage? {
360
        var image: CoreImage.CIImage? = type.ciImage
361
 
362
        if image == nil, let CGImage = type.cgImage {
363
            image = CoreImage.CIImage(cgImage: CGImage)
364
        }
365
 
366
        guard let coreImage = image else { return nil }
367
 
368
        let context = CIContext(options: [.priorityRequestLow: true])
369
 
370
        var parameters: [String: Any] = parameters ?? [:]
371
        parameters[kCIInputImageKey] = coreImage
372
 
373
        guard let filter = CIFilter(name: name, parameters: parameters) else { return nil }
374
        guard let outputImage = filter.outputImage else { return nil }
375
 
376
        let cgImageRef = context.createCGImage(outputImage, from: outputImage.extent)
377
 
378
        return UIImage(cgImage: cgImageRef!, scale: type.scale, orientation: type.imageOrientation)
379
    }
380
}
381
 
382
extension UIImage {
383
    @available(*, deprecated, message: "Replaced by `image.af.imageFiltered(withCoreImageFilter:parameters:)`")
384
    public func af_imageFiltered(withCoreImageFilter name: String, parameters: [String: Any]? = nil) -> UIImage? {
385
        af.imageFiltered(withCoreImageFilter: name, parameters: parameters)
386
    }
387
}
388
 
389
#endif
390
 
391
// MARK: -
392
 
393
private enum AssociatedKeys {
394
    static var isInflated = "UIImage.af.isInflated"
395
}