onevcat 7 лет назад
Родитель
Сommit
b5b21bc692

+ 3 - 1
Demo/Demo/Kingfisher-Demo/ViewControllers/Processor/ProcessorCollectionViewController.swift

@@ -47,7 +47,9 @@ class ProcessorCollectionViewController: UICollectionViewController {
         (TintImageProcessor(tint: UIColor.red.withAlphaComponent(0.5)), "Tint"),
         (ColorControlsProcessor(brightness: 0.0, contrast: 1.1, saturation: 1.1, inputEV: 1.0), "Vibrancy"),
         (BlackWhiteProcessor(), "B&W"),
-        (CroppingImageProcessor(size: CGSize(width: 100, height: 100)), "Cropping")
+        (CroppingImageProcessor(size: CGSize(width: 100, height: 100)), "Cropping"),
+        (DownsamplingImageProcessor(size: CGSize(width: 25, height: 25)), "Downsampling"),
+        (BlurImageProcessor(blurRadius: 5) >> RoundCornerImageProcessor(cornerRadius: 20), "Blur + Round Corner")
     ]
     
     override func viewDidLoad() {

+ 31 - 0
Sources/Image/Image.swift

@@ -260,4 +260,35 @@ extension KingfisherWrapper where Base: Image {
         }
         return image
     }
+    
+    /// Creates a downsampled image from given data to a certain size and scale.
+    ///
+    /// - Parameters:
+    ///   - data: The image data contains a JPEG or PNG image.
+    ///   - pointSize: The target size in point to which the image should be downsampled.
+    ///   - scale: The scale of result image.
+    /// - Returns: A downsampled `Image` object following the input conditions.
+    ///
+    /// - Note:
+    /// Different from image `resize` methods, downsampling will not render the original
+    /// input image in pixel format. It does downsampling from the image data, so it is much
+    /// more memory efficient and friendly. Choose to use downsampling as possible as you can.
+    ///
+    /// The input size should be smaller than the size of input image. If it is larger than the
+    /// original image size, the result image will be the same size of input without downsampling.
+    public static func downsampledImage(data: Data, to pointSize: CGSize, scale: CGFloat) -> Image? {
+        let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
+        guard let imageSource = CGImageSourceCreateWithData(data as CFData, imageSourceOptions) else {
+            return nil
+        }
+        
+        let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
+        let downsampleOptions = [
+            kCGImageSourceCreateThumbnailFromImageAlways: true,
+            kCGImageSourceShouldCacheImmediately: true,
+            kCGImageSourceCreateThumbnailWithTransform: true,
+            kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
+        let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions)!
+        return KingfisherWrapper.image(cgImage: downsampledImage, scale: scale, refImage: nil)
+    }
 }

+ 51 - 1
Sources/Image/ImageProcessor.swift

@@ -435,6 +435,8 @@ public enum ContentMode {
 }
 
 /// Processor for resizing images.
+/// If you need to resize a data represented image to a smaller size, use `DownsamplingImageProcessor`
+/// instead, which is more efficient and takes less memory.
 public struct ResizingImageProcessor: ImageProcessor {
     
     /// Identifier of the processor.
@@ -712,7 +714,7 @@ public struct CroppingImageProcessor: ImageProcessor {
     /// See `CroppingImageProcessor.init(size:anchor:)` for more.
     public let anchor: CGPoint
     
-    /// Creates a `CroppingImageProcessor`
+    /// Creates a `CroppingImageProcessor`.
     ///
     /// - Parameters:
     ///   - size: Target size of output image should be.
@@ -755,6 +757,54 @@ public struct CroppingImageProcessor: ImageProcessor {
     }
 }
 
+/// Processor for downsampling an image. Compared to `ResizingImageProcessor`, this processor
+/// does not render the images to resize. Instead, it downsample the input data directly to an
+/// image. It is a more efficient than `ResizingImageProcessor`.
+///
+/// - Note:
+/// Downsampling only happens when this processor used as the first processor in a processing
+/// pipeline, when the input `ImageProcessItem` is an `.data` value. If appending to anyother
+/// processor, it falls back to use the normal rendering resizing behavior.
+///
+/// Only CG-based images are supported. Animated images (like GIF) is not supported.
+public struct DownsamplingImageProcessor: ImageProcessor {
+    
+    /// Target size of output image should be. It should be smaller than the size of
+    /// input image. If it is larger, the result image will be the same size of input
+    /// data without downsampling.
+    public let size: CGSize
+    
+    /// Identifier of the processor.
+    /// - Note: See documentation of `ImageProcessor` protocol for more.
+    public let identifier: String
+    
+    /// Creates a `DownsamplingImageProcessor`.
+    ///
+    /// - Parameter size: The target size of the downsample operation.
+    public init(size: CGSize) {
+        self.size = size
+        self.identifier = "com.onevcat.Kingfisher.DownsamplingImageProcessor(\(size))"
+    }
+    
+    /// Processes the input `ImageProcessItem` with this processor.
+    ///
+    /// - Parameters:
+    ///   - item: Input item which will be processed by `self`.
+    ///   - options: Options when processing the item.
+    /// - Returns: The processed image.
+    ///
+    /// - Note: See documentation of `ImageProcessor` protocol for more.
+    public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> Image? {
+        switch item {
+        case .image(let image):
+            return image.kf.scaled(to: options.scaleFactor)
+                        .kf.resize(to: size, for: .none)
+        case .data(let data):
+            return KingfisherWrapper.downsampledImage(data: data, to: size, scale: options.scaleFactor)
+        }
+    }
+}
+
 /// Concatenates two `ImageProcessor`s. `ImageProcessor.append(another:)` is used internally.
 ///
 /// - Parameters:

+ 26 - 0
Tests/KingfisherTests/ImageExtensionTests.swift

@@ -288,4 +288,30 @@ class ImageExtensionTests: XCTestCase {
         XCTAssertEqual(normalImage, testImage)
         #endif
     }
+    
+    func testDownsampling() {
+        let size = CGSize(width: 15, height: 15)
+        XCTAssertEqual(testImage.size, CGSize(width: 64, height: 64))
+        let image = KingfisherWrapper<Image>.downsampledImage(data: testImageData, to: size, scale: 1)
+        XCTAssertEqual(image?.size, size)
+        XCTAssertEqual(image?.kf.scale, 1.0)
+        
+        let largerSize = CGSize(width: 100, height: 100)
+        let largerImage = KingfisherWrapper<Image>.downsampledImage(data: testImageData, to: largerSize, scale: 1)
+        // You can not "downsample" an image to a larger size.
+        XCTAssertEqual(largerImage?.size, CGSize(width: 64, height: 64))
+    }
+    
+    func testDownsamplingWithScale() {
+        let size = CGSize(width: 15, height: 15)
+        XCTAssertEqual(testImage.size, CGSize(width: 64, height: 64))
+        let image = KingfisherWrapper<Image>.downsampledImage(data: testImageData, to: size, scale: 2)
+        #if os(macOS)
+        XCTAssertEqual(image?.size, CGSize(width: 30, height: 30))
+        XCTAssertEqual(image?.kf.scale, 1.0)
+        #else
+        XCTAssertEqual(image?.size, size)
+        XCTAssertEqual(image?.kf.scale, 2.0)
+        #endif
+    }
 }