Swiftgram/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift
2024-03-20 17:21:16 +04:00

632 lines
29 KiB
Swift

import Foundation
import UIKit
import Display
import TelegramCore
import SwiftSignalKit
import AsyncDisplayKit
import Postbox
import StickerResources
import AccountContext
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import TelegramPresentationData
import ShimmerEffect
import StickerPeekUI
import TextFormat
import Accelerate
final class StickerPackPreviewInteraction {
var previewedItem: StickerPreviewPeekItem?
var reorderingFileId: MediaId?
var playAnimatedStickers: Bool
let addStickerPack: (StickerPackCollectionInfo, [StickerPackItem]) -> Void
let removeStickerPack: (StickerPackCollectionInfo) -> Void
let emojiSelected: (String, ChatTextInputTextCustomEmojiAttribute) -> Void
let emojiLongPressed: (String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void
let addPressed: () -> Void
init(playAnimatedStickers: Bool, addStickerPack: @escaping (StickerPackCollectionInfo, [StickerPackItem]) -> Void, removeStickerPack: @escaping (StickerPackCollectionInfo) -> Void, emojiSelected: @escaping (String, ChatTextInputTextCustomEmojiAttribute) -> Void, emojiLongPressed: @escaping (String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void, addPressed: @escaping () -> Void) {
self.playAnimatedStickers = playAnimatedStickers
self.addStickerPack = addStickerPack
self.removeStickerPack = removeStickerPack
self.emojiSelected = emojiSelected
self.emojiLongPressed = emojiLongPressed
self.addPressed = addPressed
}
}
final class StickerPackPreviewGridItem: GridItem {
let context: AccountContext
let stickerItem: StickerPackItem?
let interaction: StickerPackPreviewInteraction
let theme: PresentationTheme
let isPremium: Bool
let isLocked: Bool
let isEmpty: Bool
let isEditable: Bool
let isEditing: Bool
let isAdd: Bool
let section: GridSection? = nil
init(context: AccountContext, stickerItem: StickerPackItem?, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isPremium: Bool, isLocked: Bool, isEmpty: Bool, isEditable: Bool, isEditing: Bool, isAdd: Bool = false) {
self.context = context
self.stickerItem = stickerItem
self.interaction = interaction
self.theme = theme
self.isPremium = isPremium
self.isLocked = isLocked
self.isEmpty = isEmpty
self.isEditable = isEditable
self.isEditing = isEditing
self.isAdd = isAdd
}
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
let node = StickerPackPreviewGridItemNode()
node.setup(context: self.context, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isLocked: self.isLocked, isPremium: self.isPremium, isEmpty: self.isEmpty, isEditable: self.isEditable, isEditing: self.isEditing, isAdd: self.isAdd)
return node
}
func update(node: GridItemNode) {
guard let node = node as? StickerPackPreviewGridItemNode else {
assertionFailure()
return
}
node.setup(context: self.context, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isLocked: self.isLocked, isPremium: self.isPremium, isEmpty: self.isEmpty, isEditable: self.isEditable, isEditing: self.isEditing, isAdd: self.isAdd)
}
}
private let textFont = Font.regular(20.0)
final class StickerPackPreviewGridItemNode: GridItemNode {
private var currentState: (AccountContext, StickerPackItem?, Bool, Bool)?
private var isLocked: Bool?
private var isPremium: Bool?
private var isEditable: Bool?
private var isEmpty: Bool?
private let containerNode: ASDisplayNode
private let imageNode: TransformImageNode
private var animationNode: AnimatedStickerNode?
private var placeholderNode: StickerShimmerEffectNode
private var lockBackground: UIImageView?
private var lockIconNode: ASImageNode?
private var theme: PresentationTheme?
private var isEditing = false
private var averageColor: UIColor?
override var isVisibleInGrid: Bool {
didSet {
let visibility = self.isVisibleInGrid && (self.interaction?.playAnimatedStickers ?? true)
if visibility && self.setupTimestamp == nil {
self.setupTimestamp = CACurrentMediaTime()
}
if let animationNode = self.animationNode {
animationNode.visibility = visibility
}
}
}
private var currentIsPreviewing = false
private let stickerFetchedDisposable = MetaDisposable()
private let effectFetchedDisposable = MetaDisposable()
var interaction: StickerPackPreviewInteraction?
var stickerPackItem: StickerPackItem? {
return self.currentState?.1
}
var isAdd: Bool {
return self.currentState?.2 == true
}
override init() {
self.containerNode = ASDisplayNode()
self.imageNode = TransformImageNode()
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
self.placeholderNode = StickerShimmerEffectNode()
self.placeholderNode.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.imageNode)
self.containerNode.addSubnode(self.placeholderNode)
var firstTime = true
self.imageNode.imageUpdated = { [weak self] image in
guard let strongSelf = self, let image else {
return
}
if let stickerItem = strongSelf.currentState?.1 {
if stickerItem.file.isVideoSticker || stickerItem.file.isAnimatedSticker {
strongSelf.removePlaceholder(animated: !firstTime)
} else {
let current = CACurrentMediaTime()
if let setupTimestamp = strongSelf.setupTimestamp, current - setupTimestamp > 0.3 {
strongSelf.removePlaceholder(animated: true)
} else {
strongSelf.removePlaceholder(animated: false)
}
}
}
firstTime = false
if let self, self.isPremium == true || self.isEditable == true, let averageColor = getAverageColor(image: image) {
self.averageColor = averageColor
self.lockBackground?.tintColor = averageColor
self.lockBackground?.alpha = 1.0
}
}
}
deinit {
self.stickerFetchedDisposable.dispose()
self.effectFetchedDisposable.dispose()
}
private func removePlaceholder(animated: Bool) {
guard self.placeholderNode.alpha != 0 else {
return
}
if !animated {
self.placeholderNode.removeFromSupernode()
} else {
self.placeholderNode.alpha = 0.0
self.placeholderNode.allowsGroupOpacity = true
self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
self?.placeholderNode.removeFromSupernode()
self?.placeholderNode.allowsGroupOpacity = false
})
}
}
override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
}
@objc private func handleAddTap() {
self.interaction?.addPressed()
}
private var setupTimestamp: Double?
func setup(context: AccountContext, stickerItem: StickerPackItem?, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isLocked: Bool, isPremium: Bool, isEmpty: Bool, isEditable: Bool, isEditing: Bool, isAdd: Bool) {
self.interaction = interaction
self.theme = theme
let isFirstTime = self.currentState == nil
if isAdd {
if !isFirstTime {
return
}
let color = theme.actionSheet.controlAccentColor
self.imageNode.setSignal(.single({ arguments in
let drawingContext = DrawingContext(size: arguments.imageSize, opaque: false)
let size = arguments.imageSize
drawingContext?.withContext({ context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
context.setFillColor(color.withMultipliedAlpha(0.1).cgColor)
context.fillEllipse(in: CGRect(origin: .zero, size: size).insetBy(dx: 4.0, dy: 4.0))
context.setFillColor(color.cgColor)
let plusSize = CGSize(width: 3.0, height: 21.0)
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height), cornerRadius: plusSize.width / 2.0).cgPath)
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width), cornerRadius: plusSize.width / 2.0).cgPath)
context.fillPath()
UIGraphicsPopContext()
})
return drawingContext
}))
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleAddTap)))
self.currentState = (context, nil, true, false)
self.setNeedsLayout()
return
}
if interaction.reorderingFileId != nil {
self.isHidden = stickerItem?.file.fileId == interaction.reorderingFileId
} else {
self.isHidden = false
}
if self.currentState == nil || self.currentState!.0 !== context || self.currentState!.1 != stickerItem || self.isLocked != isLocked || self.isPremium != isPremium || self.isEmpty != isEmpty || self.isEditing != isEditing || self.isEditable != isEditable {
self.isLocked = isLocked
self.isPremium = isPremium
self.isEditable = isEditable
if isPremium || isEditing {
let lockBackground: UIImageView
let lockIconNode: ASImageNode
if let currentBackground = self.lockBackground, let currentIcon = self.lockIconNode {
lockBackground = currentBackground
lockIconNode = currentIcon
} else {
lockBackground = UIImageView()
lockBackground.alpha = self.averageColor != nil ? 1.0 : 0.0
lockBackground.tintColor = self.averageColor ?? .white
lockBackground.clipsToBounds = true
lockBackground.isUserInteractionEnabled = false
lockIconNode = ASImageNode()
lockIconNode.displaysAsynchronously = false
if isEditing {
lockIconNode.image = generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
context.addEllipse(in: CGRect(x: 5.5, y: 11.0, width: 3.0, height: 3.0))
context.fillPath()
context.addEllipse(in: CGRect(x: size.width / 2.0 - 1.5, y: 11.0, width: 3.0, height: 3.0))
context.fillPath()
context.addEllipse(in: CGRect(x: size.width - 3.0 - 5.5, y: 11.0, width: 3.0, height: 3.0))
context.fillPath()
})
} else {
lockIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white)
}
self.lockBackground = lockBackground
self.lockIconNode = lockIconNode
self.view.addSubview(lockBackground)
lockBackground.addSubview(lockIconNode.view)
if !isFirstTime {
lockBackground.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
}
}
} else if let lockBackground = self.lockBackground {
self.lockBackground = nil
self.lockIconNode = nil
lockBackground.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
lockBackground.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false, completion: { _ in
lockBackground.removeFromSuperview()
})
}
if let stickerItem = stickerItem {
let visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
if visibility && self.setupTimestamp == nil {
self.setupTimestamp = CACurrentMediaTime()
}
if stickerItem.file.isAnimatedSticker || stickerItem.file.isVideoSticker {
let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
if stickerItem.file.isVideoSticker {
self.imageNode.setSignal(chatMessageSticker(account: context.account, userLocation: .other, file: stickerItem.file, small: true))
} else {
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .other, file: stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
}
if self.animationNode == nil {
let animationNode = DefaultAnimatedStickerNodeImpl()
self.animationNode = animationNode
self.containerNode.insertSubnode(animationNode, aboveSubnode: self.imageNode)
animationNode.started = { [weak self] in
guard let strongSelf = self else {
return
}
self?.imageNode.isHidden = true
let current = CACurrentMediaTime()
if let setupTimestamp = strongSelf.setupTimestamp, current - setupTimestamp > 0.3 {
if !strongSelf.placeholderNode.alpha.isZero {
strongSelf.removePlaceholder(animated: true)
}
} else {
strongSelf.removePlaceholder(animated: false)
}
}
}
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))
self.animationNode?.setup(source: AnimatedStickerResourceSource(account: context.account, resource: stickerItem.file.resource, isVideo: stickerItem.file.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .cached)
self.animationNode?.visibility = visibility
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start())
if stickerItem.file.isPremiumSticker, let effect = stickerItem.file.videoThumbnails.first {
self.effectFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: stickerPackFileReference(stickerItem.file), resource: effect.resource).start())
}
} else {
if let animationNode = self.animationNode {
animationNode.visibility = false
self.animationNode = nil
animationNode.removeFromSupernode()
}
self.imageNode.setSignal(chatMessageSticker(account: context.account, userLocation: .other, file: stickerItem.file, small: true))
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start())
}
} else {
if isEmpty {
if !self.placeholderNode.alpha.isZero {
self.placeholderNode.alpha = 0.0
self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
}
} else {
self.placeholderNode.alpha = 1.0
}
}
self.animationNode?.alpha = isLocked ? 0.5 : 1.0
self.imageNode.alpha = isLocked ? 0.5 : 1.0
self.currentState = (context, stickerItem, false, isEditing)
self.setNeedsLayout()
}
self.isEmpty = isEmpty
if self.isEditing != isEditing {
self.isEditing = isEditing
if self.isEditing {
self.startShaking()
} else {
self.containerNode.layer.removeAnimation(forKey: "shaking_position")
self.containerNode.layer.removeAnimation(forKey: "shaking_rotation")
}
}
}
private func startShaking() {
func degreesToRadians(_ x: CGFloat) -> CGFloat {
return .pi * x / 180.0
}
let duration: Double = 0.4
let displacement: CGFloat = 1.0
let degreesRotation: CGFloat = 2.0
let negativeDisplacement = -1.0 * displacement
let position = CAKeyframeAnimation.init(keyPath: "position")
position.beginTime = 0.8
position.duration = duration
position.values = [
NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)),
NSValue(cgPoint: CGPoint(x: 0, y: 0)),
NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: 0)),
NSValue(cgPoint: CGPoint(x: 0, y: negativeDisplacement)),
NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement))
]
position.calculationMode = .linear
position.isRemovedOnCompletion = false
position.repeatCount = Float.greatestFiniteMagnitude
position.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100))
position.isAdditive = true
let transform = CAKeyframeAnimation.init(keyPath: "transform")
transform.beginTime = 2.6
transform.duration = 0.3
transform.valueFunction = CAValueFunction(name: CAValueFunctionName.rotateZ)
transform.values = [
degreesToRadians(-1.0 * degreesRotation),
degreesToRadians(degreesRotation),
degreesToRadians(-1.0 * degreesRotation)
]
transform.calculationMode = .linear
transform.isRemovedOnCompletion = false
transform.repeatCount = Float.greatestFiniteMagnitude
transform.isAdditive = true
transform.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100))
self.containerNode.layer.add(position, forKey: "shaking_position")
self.containerNode.layer.add(transform, forKey: "shaking_rotation")
}
override func layout() {
super.layout()
let bounds = self.bounds
self.containerNode.frame = bounds
let boundsSide = min(bounds.size.width - 14.0, bounds.size.height - 14.0)
var boundingSize = CGSize(width: boundsSide, height: boundsSide)
if let (_, item, isAdd, _) = self.currentState {
if isAdd {
let imageSize = CGSize(width: 512, height: 512).aspectFitted(boundingSize)
let imageFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
self.imageNode.frame = imageFrame
return
} else if let item = item, let dimensions = item.file.dimensions?.cgSize {
if item.file.isPremiumSticker {
boundingSize = CGSize(width: boundingSize.width * 1.1, height: boundingSize.width * 1.1)
}
let imageSize = dimensions.aspectFitted(boundingSize)
let imageFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
self.imageNode.frame = imageFrame
if let animationNode = self.animationNode {
animationNode.frame = imageFrame
animationNode.updateLayout(size: imageSize)
}
}
}
let imageFrame = self.imageNode.frame
let placeholderFrame = imageFrame
self.placeholderNode.frame = imageFrame
if let theme = self.theme, let (context, stickerItem, _, _) = self.currentState, let item = stickerItem {
self.placeholderNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), data: item.file.immediateThumbnailData, size: placeholderFrame.size, enableEffect: context.sharedContext.energyUsageSettings.fullTranslucency)
}
if let lockBackground = self.lockBackground, let lockIconNode = self.lockIconNode {
let lockSize: CGSize
let lockBackgroundFrame: CGRect
if let (_, _, _, isEditing) = self.currentState, isEditing {
lockSize = CGSize(width: 24.0, height: 24.0)
lockBackgroundFrame = CGRect(origin: CGPoint(x: 3.0, y: 3.0), size: lockSize)
} else {
lockSize = CGSize(width: 16.0, height: 16.0)
lockBackgroundFrame = CGRect(origin: CGPoint(x: bounds.width - lockSize.width - 1.0, y: bounds.height - lockSize.height - 1.0), size: lockSize)
}
if lockBackground.image == nil {
lockBackground.image = generateFilledCircleImage(diameter: lockSize.width, color: .white)?.withRenderingMode(.alwaysTemplate)
}
lockBackground.frame = lockBackgroundFrame
lockBackground.layer.cornerRadius = lockSize.width / 2.0
if #available(iOS 13.0, *) {
lockBackground.layer.cornerCurve = .circular
}
if let icon = lockIconNode.image {
let iconSize = CGSize(width: icon.size.width - 4.0, height: icon.size.height - 4.0)
lockIconNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((lockBackgroundFrame.width - iconSize.width) / 2.0), y: floorToScreenPixels((lockBackgroundFrame.height - iconSize.height) / 2.0)), size: iconSize)
}
}
}
override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
self.placeholderNode.updateAbsoluteRect(absoluteRect, within: containerSize)
}
func transitionNode() -> ASDisplayNode? {
return self
}
@objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {
}
func updatePreviewing(animated: Bool) {
var isPreviewing = false
if let (_, maybeItem, isAdd, _) = self.currentState, let interaction = self.interaction, let item = maybeItem {
if isAdd {
return
}
isPreviewing = interaction.previewedItem == .pack(item.file)
}
if self.currentIsPreviewing != isPreviewing {
self.currentIsPreviewing = isPreviewing
if isPreviewing {
self.layer.sublayerTransform = CATransform3DMakeScale(0.8, 0.8, 1.0)
if animated {
self.layer.animateSpring(from: 1.0 as NSNumber, to: 0.8 as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.4)
}
} else {
self.layer.sublayerTransform = CATransform3DIdentity
if animated {
self.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.5)
}
}
}
}
}
private func getAverageColor(image: UIImage) -> UIColor? {
let blurredWidth = 16
let blurredHeight = 16
let blurredBytesPerRow = blurredWidth * 4
guard let context = DrawingContext(size: CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight)), scale: 1.0, opaque: true, bytesPerRow: blurredBytesPerRow) else {
return nil
}
let size = CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight))
if let cgImage = image.cgImage {
context.withFlippedContext { c in
c.setFillColor(UIColor.white.cgColor)
c.fill(CGRect(origin: CGPoint(), size: size))
c.draw(cgImage, in: CGRect(origin: CGPoint(x: -size.width / 2.0, y: -size.height / 2.0), size: CGSize(width: size.width * 1.8, height: size.height * 1.8)))
}
}
var destinationBuffer = vImage_Buffer()
destinationBuffer.width = UInt(blurredWidth)
destinationBuffer.height = UInt(blurredHeight)
destinationBuffer.data = context.bytes
destinationBuffer.rowBytes = context.bytesPerRow
vImageBoxConvolve_ARGB8888(&destinationBuffer,
&destinationBuffer,
nil,
0, 0,
UInt32(15),
UInt32(15),
nil,
vImage_Flags(kvImageTruncateKernel))
let divisor: Int32 = 0x1000
let rwgt: CGFloat = 0.3086
let gwgt: CGFloat = 0.6094
let bwgt: CGFloat = 0.0820
let adjustSaturation: CGFloat = 1.7
let a = (1.0 - adjustSaturation) * rwgt + adjustSaturation
let b = (1.0 - adjustSaturation) * rwgt
let c = (1.0 - adjustSaturation) * rwgt
let d = (1.0 - adjustSaturation) * gwgt
let e = (1.0 - adjustSaturation) * gwgt + adjustSaturation
let f = (1.0 - adjustSaturation) * gwgt
let g = (1.0 - adjustSaturation) * bwgt
let h = (1.0 - adjustSaturation) * bwgt
let i = (1.0 - adjustSaturation) * bwgt + adjustSaturation
let satMatrix: [CGFloat] = [
a, b, c, 0,
d, e, f, 0,
g, h, i, 0,
0, 0, 0, 1
]
var matrix: [Int16] = satMatrix.map { value in
return Int16(value * CGFloat(divisor))
}
vImageMatrixMultiply_ARGB8888(&destinationBuffer, &destinationBuffer, &matrix, divisor, nil, nil, vImage_Flags(kvImageDoNotTile))
context.withFlippedContext { c in
c.setFillColor(UIColor.white.withMultipliedAlpha(0.1).cgColor)
c.fill(CGRect(origin: CGPoint(), size: size))
}
var sumR: UInt64 = 0
var sumG: UInt64 = 0
var sumB: UInt64 = 0
var sumA: UInt64 = 0
for y in 0 ..< blurredHeight {
let row = context.bytes.assumingMemoryBound(to: UInt8.self).advanced(by: y * blurredBytesPerRow)
for x in 0 ..< blurredWidth {
let pixel = row.advanced(by: x * 4)
sumB += UInt64(pixel.advanced(by: 0).pointee)
sumG += UInt64(pixel.advanced(by: 1).pointee)
sumR += UInt64(pixel.advanced(by: 2).pointee)
sumA += UInt64(pixel.advanced(by: 3).pointee)
}
}
sumR /= UInt64(blurredWidth * blurredHeight)
sumG /= UInt64(blurredWidth * blurredHeight)
sumB /= UInt64(blurredWidth * blurredHeight)
sumA /= UInt64(blurredWidth * blurredHeight)
sumA = 255
return UIColor(red: CGFloat(sumR) / 255.0, green: CGFloat(sumG) / 255.0, blue: CGFloat(sumB) / 255.0, alpha: CGFloat(sumA) / 255.0)
}