Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions AsyncImageView/Renderers/CacheRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public final class CacheRenderer<
public func renderImageWithData(_ data: Renderer.Data) -> SignalProducer<ImageResult, Renderer.Error> {
return SignalProducer { [cache = self.cache] in
Result(
cache.valueForKey(data)?.image.asCacheHit,
cache.valueForKey(data)?.asCacheHit,
failWith: CacheRendererError.imageNotFound
)
}
Expand All @@ -41,9 +41,11 @@ public final class CacheRenderer<
return renderer
.renderImageWithData(data)
.on(value: { [cache = self.cache] result in
cache.setValue(result, forKey: data)
if result.shouldCache {
cache.setValue(result, forKey: data)
}
})
.map { $0.image.asCacheMiss }
.map { $0.asCacheMiss }
}
}
}
Expand Down Expand Up @@ -86,18 +88,20 @@ private enum CacheRendererError: Error {
case imageNotFound
}

private extension UIImage {
private extension RenderResultType {
var asCacheHit: ImageResult {
return ImageResult(
image: self,
cacheHit: true
image: self.image,
cacheHit: true,
shouldCache: self.shouldCache
)
}

var asCacheMiss: ImageResult {
return ImageResult(
image: self,
cacheHit: false
image: self.image,
cacheHit: false,
shouldCache: self.shouldCache
)
}
}
3 changes: 2 additions & 1 deletion AsyncImageView/Renderers/FallbackRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ extension RenderResultType {
fileprivate var asResult: ImageResult {
return ImageResult(
image: self.image,
cacheHit: self.cacheHit
cacheHit: self.cacheHit,
shouldCache: self.shouldCache
)
}
}
Expand Down
107 changes: 79 additions & 28 deletions AsyncImageView/Renderers/ImageInflaterRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,53 @@ public final class ImageInflaterRenderer<Renderer: RendererType>: RendererType {
private let screenScale: CGFloat
private let opaque: Bool
private let contentMode: ContentMode
private let bitmapContextFactory: UIImage.BitmapContextFactory

public init(
public convenience init(
renderer: Renderer,
screenScale: CGFloat,
opaque: Bool,
contentMode: ContentMode = .defaultMode
) {
self.init(
renderer: renderer,
screenScale: screenScale,
opaque: opaque,
contentMode: contentMode,
bitmapContextFactory: UIImage.makeBitmapContext
)
}

internal init(
renderer: Renderer,
screenScale: CGFloat,
opaque: Bool,
contentMode: ContentMode = .defaultMode,
bitmapContextFactory: @escaping UIImage.BitmapContextFactory
) {
self.renderer = renderer
self.screenScale = screenScale
self.opaque = opaque
self.contentMode = contentMode
self.bitmapContextFactory = bitmapContextFactory
}

public func renderImageWithData(_ data: Data) -> SignalProducer<ImageResult, Error> {
return self.renderer.renderImageWithData(data)
.map { [screenScale = self.screenScale, opaque = self.opaque, contentMode = self.contentMode] result in
let inflatedImage = result.image.inflate(
.map { [screenScale = self.screenScale, opaque = self.opaque, contentMode = self.contentMode, bitmapContextFactory = self.bitmapContextFactory] result in
let inflationResult = result.image.inflate(
withSize: data.size,
scale: screenScale,
opaque: opaque,
contentMode: contentMode
contentMode: contentMode,
bitmapContextFactory: bitmapContextFactory
)

return ImageResult(image: inflatedImage, cacheHit: result.cacheHit)
return ImageResult(
image: inflationResult.image,
cacheHit: result.cacheHit,
shouldCache: result.shouldCache && inflationResult.didProcess
)
}
.start(on: QueueScheduler())
}
Expand Down Expand Up @@ -77,18 +100,43 @@ extension UIImage {
_ colorSpace: CGColorSpace,
_ bitmapInfo: UInt32
) -> CGContext?
internal typealias BitmapImageFactory = (_ context: CGContext) -> CGImage?

internal func inflate(
withSize size: CGSize,
scale: CGFloat,
opaque: Bool,
contentMode: ImageInflaterRendererContentMode
) -> UIImage {
internal static func makeBitmapContext(
width: Int,
height: Int,
bytesPerRow: Int,
colorSpace: CGColorSpace,
bitmapInfo: UInt32
) -> CGContext? {
return CGContext(
data: nil,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: bitmapInfo
)
}

internal static func makeBitmapImage(_ context: CGContext) -> CGImage? {
return context.makeImage()
}

internal func inflate(
withSize size: CGSize,
scale: CGFloat,
opaque: Bool,
contentMode: ImageInflaterRendererContentMode,
bitmapContextFactory: BitmapContextFactory = UIImage.makeBitmapContext
) -> BitmapContextProcessingResult {
return self.processImageWithBitmapContext(
withSize: size,
scale: scale,
opaque: opaque,
contentMode: contentMode,
bitmapContextFactory: bitmapContextFactory,
renderingBlock: { _, _, _, imageDrawing in
imageDrawing()
}
Expand All @@ -100,19 +148,10 @@ extension UIImage {
scale: CGFloat,
opaque: Bool,
contentMode: ImageInflaterRendererContentMode,
bitmapContextFactory: BitmapContextFactory = { width, height, bytesPerRow, colorSpace, bitmapInfo in
CGContext(
data: nil,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: bitmapInfo
)
},
bitmapContextFactory: BitmapContextFactory = UIImage.makeBitmapContext,
bitmapImageFactory: BitmapImageFactory = UIImage.makeBitmapImage,
renderingBlock: (_ image: UIImage, _ context: CGContext, _ contextSize: CGSize, _ imageDrawing: () -> Void) -> Void)
-> UIImage {
-> BitmapContextProcessingResult {
precondition(size.width > 0 && size.height > 0, "Invalid size: \(size.width)x\(size.height)")

let colorSpace = CGColorSpaceCreateDeviceRGB()
Expand All @@ -131,7 +170,7 @@ extension UIImage {
colorSpace,
bitmapInfo
) else {
return self
return BitmapContextProcessingResult(image: self, didProcess: false)
}

renderingBlock(
Expand All @@ -149,14 +188,26 @@ extension UIImage {
}
)

return UIImage(
cgImage: bitmapContext.makeImage()!,
scale: scale,
orientation: self.imageOrientation
guard let processedImage = bitmapImageFactory(bitmapContext) else {
return BitmapContextProcessingResult(image: self, didProcess: false)
}

return BitmapContextProcessingResult(
image: UIImage(
cgImage: processedImage,
scale: scale,
orientation: self.imageOrientation
),
didProcess: true
)
}
}

internal struct BitmapContextProcessingResult {
let image: UIImage
let didProcess: Bool
}

extension RendererType {
public func inflatedWithScale(
_ screenScale: CGFloat,
Expand Down
77 changes: 54 additions & 23 deletions AsyncImageView/Renderers/ImageProcessingRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,50 +13,81 @@ import ReactiveSwift
/// `RendererType` decorator that allows rendering a new image derived from the original one.
public final class ImageProcessingRenderer<Renderer: RendererType>: RendererType {
public typealias Block = (_ image: UIImage, _ context: CGContext, _ contextSize: CGSize, _ data: Renderer.Data, _ imageDrawingBlock: () -> Void) -> Void
public typealias Data = Renderer.Data
public typealias Error = Renderer.Error
public typealias RenderResult = ImageResult

private let renderer: Renderer
private let scale: CGFloat
private let opaque: Bool
private let contentMode: ImageInflaterRendererContentMode
private let contentMode: ImageInflaterRendererContentMode
private let renderingBlock: Block
private let bitmapImageFactory: UIImage.BitmapImageFactory

private let schedulerCreator: () -> Scheduler

public init(
public convenience init(
renderer: Renderer,
scale: CGFloat,
opaque: Bool,
contentMode: ImageInflaterRendererContentMode = .defaultMode,
renderingBlock: @escaping Block,
schedulerCreator: @escaping () -> Scheduler = { QueueScheduler() }) {
self.renderer = renderer
self.scale = scale
self.opaque = opaque
self.contentMode = contentMode
self.renderingBlock = renderingBlock
self.init(
renderer: renderer,
scale: scale,
opaque: opaque,
contentMode: contentMode,
renderingBlock: renderingBlock,
bitmapImageFactory: UIImage.makeBitmapImage,
schedulerCreator: schedulerCreator
)
}

self.schedulerCreator = schedulerCreator
internal init(
renderer: Renderer,
scale: CGFloat,
opaque: Bool,
contentMode: ImageInflaterRendererContentMode = .defaultMode,
renderingBlock: @escaping Block,
bitmapImageFactory: @escaping UIImage.BitmapImageFactory,
schedulerCreator: @escaping () -> Scheduler = { QueueScheduler() }
) {
self.renderer = renderer
self.scale = scale
self.opaque = opaque
self.contentMode = contentMode
self.renderingBlock = renderingBlock
self.bitmapImageFactory = bitmapImageFactory

self.schedulerCreator = schedulerCreator
}

public func renderImageWithData(_ data: Renderer.Data) -> SignalProducer<UIImage, Renderer.Error> {
public func renderImageWithData(_ data: Data) -> SignalProducer<ImageResult, Error> {
return self.renderer.renderImageWithData(data)
.observe(on: self.schedulerCreator())
.map { $0.image }
.map { [scale = self.scale, opaque = self.opaque, block = self.renderingBlock, contentMode = self.contentMode] image in
image.processImageWithBitmapContext(
withSize: data.size,
scale: scale,
.map { [scale = self.scale, opaque = self.opaque, block = self.renderingBlock, contentMode = self.contentMode, bitmapImageFactory = self.bitmapImageFactory] result in
let processingResult = result.image.processImageWithBitmapContext(
withSize: data.size,
scale: scale,
opaque: opaque,
contentMode: contentMode,
renderingBlock: { image, context, contextSize, imageDrawingBlock in
block(
image,
context,
contextSize,
data,
imageDrawingBlock
)
}
bitmapImageFactory: bitmapImageFactory,
renderingBlock: { image, context, contextSize, imageDrawingBlock in
block(
image,
context,
contextSize,
data,
imageDrawingBlock
)
}
)

return ImageResult(
image: processingResult.image,
cacheHit: result.cacheHit,
shouldCache: result.shouldCache && processingResult.didProcess
)
}
}
Expand Down
6 changes: 3 additions & 3 deletions AsyncImageView/Renderers/MulticastedRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ extension RendererType {
fileprivate func createProducerForRenderingData(_ data: Data) -> SignalProducer<ImageResult, Error> {
return self.renderImageWithData(data)
.flatMap(.concat) { result in
return SignalProducer([
ImageResult(image: result.image, cacheHit: false),
ImageResult(image: result.image, cacheHit: true)
return SignalProducer([
ImageResult(image: result.image, cacheHit: false, shouldCache: result.shouldCache),
ImageResult(image: result.image, cacheHit: true, shouldCache: result.shouldCache)
])
}
}
Expand Down
18 changes: 18 additions & 0 deletions AsyncImageView/Renderers/Renderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,34 @@ public protocol RenderResultType {
var cacheHit: Bool { get }
}

internal extension RenderResultType {
var shouldCache: Bool {
return (self as? CachePolicyProviding)?.shouldCache ?? true
}
}

public struct ImageResult: RenderResultType {
public let image: UIImage
public let cacheHit: Bool
internal let shouldCache: Bool

public init(image: UIImage, cacheHit: Bool) {
self.init(image: image, cacheHit: cacheHit, shouldCache: true)
}

internal init(image: UIImage, cacheHit: Bool, shouldCache: Bool) {
self.image = image
self.cacheHit = cacheHit
self.shouldCache = shouldCache
}
}

private protocol CachePolicyProviding {
var shouldCache: Bool { get }
}

extension ImageResult: CachePolicyProviding {}

extension UIImage: RenderResultType {
public var image: UIImage {
return self
Expand Down
Loading
Loading