diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index 0b9653a496..6f5f713d0e 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -104,8 +104,8 @@ 09F79A0921C829C700820234 /* GalleryNavigationRecipientNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79A0821C829C700820234 /* GalleryNavigationRecipientNode.swift */; }; 09F79A0B21C832F400820234 /* WebSearchGalleryFooterContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79A0A21C832F400820234 /* WebSearchGalleryFooterContentNode.swift */; }; 09F79A0D21C88E8900820234 /* LegacyWebSearchEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79A0C21C88E8900820234 /* LegacyWebSearchEditor.swift */; }; - 09F85B9F21E76ADB00D73170 /* BlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F85B9721E76AD900D73170 /* BlurView.swift */; }; 09F85BA521E7821500D73170 /* ThemeGridSelectionPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F85BA421E7821500D73170 /* ThemeGridSelectionPanelNode.swift */; }; + 09F85BA721E7DA5F00D73170 /* BlurredImageNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F85BA621E7DA5F00D73170 /* BlurredImageNode.swift */; }; 09FE756D2153F5F900A3120F /* CallRouteActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */; }; 9F06830921A404AB001D8EDB /* NotificationExceptionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830821A404AB001D8EDB /* NotificationExceptionControllerNode.swift */; }; 9F06830B21A404C4001D8EDB /* NotificationExcetionSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830A21A404C4001D8EDB /* NotificationExcetionSettingsController.swift */; }; @@ -1217,8 +1217,8 @@ 09F79A0821C829C700820234 /* GalleryNavigationRecipientNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryNavigationRecipientNode.swift; sourceTree = ""; }; 09F79A0A21C832F400820234 /* WebSearchGalleryFooterContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSearchGalleryFooterContentNode.swift; sourceTree = ""; }; 09F79A0C21C88E8900820234 /* LegacyWebSearchEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyWebSearchEditor.swift; sourceTree = ""; }; - 09F85B9721E76AD900D73170 /* BlurView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurView.swift; sourceTree = ""; }; 09F85BA421E7821500D73170 /* ThemeGridSelectionPanelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeGridSelectionPanelNode.swift; sourceTree = ""; }; + 09F85BA621E7DA5F00D73170 /* BlurredImageNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurredImageNode.swift; sourceTree = ""; }; 09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallRouteActionSheetItem.swift; sourceTree = ""; }; 9F06830821A404AB001D8EDB /* NotificationExceptionControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExceptionControllerNode.swift; sourceTree = ""; }; 9F06830A21A404C4001D8EDB /* NotificationExcetionSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExcetionSettingsController.swift; sourceTree = ""; }; @@ -3109,7 +3109,7 @@ D06F31E52135A41C001A0F12 /* ThemeSettingsBrightnessItem.swift */, D005809F21DCF0A200CB7CD3 /* WallpaperListPreviewController.swift */, D00580A121DCF0B700CB7CD3 /* WallpaperListPreviewControllerNode.swift */, - 09F85B9721E76AD900D73170 /* BlurView.swift */, + 09F85BA621E7DA5F00D73170 /* BlurredImageNode.swift */, ); name = Themes; sourceTree = ""; @@ -5454,6 +5454,7 @@ D0EC6D6E1EB9F58800EBF1C3 /* AuthorizationSequencePhoneEntryControllerNode.swift in Sources */, D0B85C211FF70BEC00E795B4 /* AuthorizationSequenceAwaitingAccountResetControllerNode.swift in Sources */, D0EC6D6F1EB9F58800EBF1C3 /* AuthorizationSequenceCodeEntryController.swift in Sources */, + 09F85BA721E7DA5F00D73170 /* BlurredImageNode.swift in Sources */, D0C26D5E1FDF49E7004ABF18 /* DateFormat.swift in Sources */, D0068FA821760FA300D1B315 /* StoreDownloadedMedia.swift in Sources */, D0EC6D701EB9F58800EBF1C3 /* AuthorizationSequenceCodeEntryControllerNode.swift in Sources */, @@ -5680,7 +5681,6 @@ D0EC6DD71EB9F58900EBF1C3 /* VerticalListContextResultsChatInputPanelItem.swift in Sources */, D0EC6DD81EB9F58900EBF1C3 /* VerticalListContextResultsChatInputPanelButtonItem.swift in Sources */, D04281F4200E5AB0009DDE36 /* ChatRecentActionsController.swift in Sources */, - 09F85B9F21E76ADB00D73170 /* BlurView.swift in Sources */, D0B2F76220506E2A00D3BFB9 /* MediaInputSettings.swift in Sources */, 09FE756D2153F5F900A3120F /* CallRouteActionSheetItem.swift in Sources */, D0BFAE4E20AB1D7B00793CF2 /* DisabledContextResultsChatInputContextPanelNode.swift in Sources */, diff --git a/TelegramUI/BlurView.swift b/TelegramUI/BlurView.swift deleted file mode 100755 index f540a02f51..0000000000 --- a/TelegramUI/BlurView.swift +++ /dev/null @@ -1,333 +0,0 @@ -import UIKit -import Accelerate - -extension CGImage { - var area: Int { - return width * height - } - - private var size: CGSize { - return CGSize(width: width, height: height) - } - - private var bytes: Int { - return bytesPerRow * height - } - - private func imageBuffer(from data: UnsafeMutableRawPointer!) -> vImage_Buffer { - return vImage_Buffer(data: data, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow) - } - - func blurred(with boxSize: UInt32, iterations: Int, blendColor: UIColor?, blendMode: CGBlendMode) -> CGImage? { - guard let providerData = dataProvider?.data else { - return nil - } - - let inData = malloc(bytes) - var inBuffer = imageBuffer(from: inData) - - let outData = malloc(bytes) - var outBuffer = imageBuffer(from: outData) - - let tempSize = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, nil, 0, 0, boxSize, boxSize, nil, vImage_Flags(kvImageEdgeExtend + kvImageGetTempBufferSize)) - let tempData = malloc(tempSize) - - defer { - free(inData) - free(outData) - free(tempData) - } - - let source = CFDataGetBytePtr(providerData) - memcpy(inBuffer.data, source, bytes) - - for _ in 0.. UIImage? { - guard let cgImage = cgImage else { - return nil - } - - if cgImage.area <= 0 || radius <= 0 { - return self - } - - var boxSize = UInt32(radius * scale * ratio) - if boxSize % 2 == 0 { - boxSize += 1 - } - - return cgImage.blurred(with: boxSize, iterations: iterations, blendColor: color, blendMode: mode).map { - UIImage(cgImage: $0, scale: scale, orientation: imageOrientation) - } - } -} - -extension CGContext { - static func imageContext(rect: CGRect, opaque: Bool) -> CGContext? { - UIGraphicsBeginImageContextWithOptions(rect.size, opaque, 1.0) - guard let context = UIGraphicsGetCurrentContext() else { - return nil - } - - context.translateBy(x: -rect.origin.x, y: -rect.origin.y) - context.interpolationQuality = .default - - return context - } - - func makeImage(with blendColor: UIColor?, blendMode: CGBlendMode, size: CGSize) -> CGImage? { - if let color = blendColor { - setFillColor(color.cgColor) - setBlendMode(blendMode) - fill(CGRect(origin: .zero, size: size)) - } - - return makeImage() - } -} - -private extension CGRect { - func rectangle(_ s: CGSize) -> CGRect { - let x = origin.x / s.width - let y = origin.y / s.height - let width = size.width / s.width - let height = size.height / s.height - return CGRect(x: x, y: y, width: width, height: height) - } -} - -class BlurLayer: CALayer { - private static let blurRadiusKey = "blurRadius" - private static let blurLayoutKey = "blurLayout" - @NSManaged var blurRadius: CGFloat - @NSManaged private var blurLayout: CGFloat - - private var fromBlurRadius: CGFloat? - var presentationRadius: CGFloat { - if let radius = fromBlurRadius { - if let layer = presentation() { - return layer.blurRadius - } else { - return radius - } - } else { - return blurRadius - } - } - - override class func needsDisplay(forKey key: String) -> Bool { - if key == blurRadiusKey || key == blurLayoutKey { - return true - } - return super.needsDisplay(forKey: key) - } - - open override func action(forKey event: String) -> CAAction? { - if event == BlurLayer.blurRadiusKey { - fromBlurRadius = nil - - if let action = super.action(forKey: "opacity") as? CABasicAnimation { - fromBlurRadius = (presentation() ?? self).blurRadius - - action.keyPath = event - action.fromValue = fromBlurRadius - return action - } - } - - if event == BlurLayer.blurLayoutKey, let action = super.action(forKey: "opacity") as? CABasicAnimation { - action.keyPath = event - action.fromValue = 0 - action.toValue = 1 - return action - } - - return super.action(forKey: event) - } -} - -extension BlurLayer { - func draw(_ image: UIImage, fixes isFixes: Bool, baseLayer: CALayer?) { - contents = image.cgImage - contentsScale = image.scale - - if isFixes, let blurLayer = presentation() { - contentsRect = blurLayer.convert(blurLayer.bounds, to: baseLayer).rectangle(image.size) - } - } - - func refresh() { - fromBlurRadius = nil - } - - func animate() { - UIView.performWithoutAnimation { - blurLayout = 0 - } - blurLayout = 1 - } - - func render(in context: CGContext, for layer: CALayer) { - layer.render(in: context) - } -} - -open class DynamicBlurView: UIView { - open override class var layerClass : AnyClass { - return BlurLayer.self - } - - private var staticImage: UIImage? - private var displayLink: CADisplayLink? - private var blurLayer: BlurLayer { - return layer as! BlurLayer - } - private let mainQueue = DispatchQueue.main - private let globalQueue: DispatchQueue = { - if #available (iOS 8.0, *) { - return .global(qos: .userInteractive) - } else { - return .global(priority: .high) - } - }() - private var renderingTarget: UIView? { - if isDeepRendering { - return window - } else { - return superview - } - } - - - open var drawsAsynchronously: Bool = false - open var blurRadius: CGFloat { - set { blurLayer.blurRadius = newValue } - get { return blurLayer.blurRadius } - } - /// Blend color. - open var blendColor: UIColor? - /// Blend mode. - open var blendMode: CGBlendMode = .plusLighter - /// Default is 3. - open var iterations: Int = 3 - /// If the view want to render beyond the layer, should be true. - open var isDeepRendering: Bool = false - /// When none of tracking mode, it can change the radius of blur with the ratio. Should set from 0 to 1. - open var blurRatio: CGFloat = 1 { - didSet { - if let image = staticImage, oldValue != blurRatio { - draw(image, blurRadius: blurRadius, fixes: false, baseLayer: renderingTarget?.layer) - } - } - } - - public override init(frame: CGRect) { - super.init(frame: frame) - isUserInteractionEnabled = false - } - - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - isUserInteractionEnabled = false - } - - open override func didMoveToWindow() { - super.didMoveToWindow() - - if let view = renderingTarget, window != nil { - staticImage = snapshotImage(for: view.layer, conversion: !isDeepRendering) - } - } - - open override func didMoveToSuperview() { - super.didMoveToSuperview() - - if superview == nil { - displayLink?.invalidate() - displayLink = nil - } else { - linkForDisplay() - } - } - - private func async(on queue: DispatchQueue, actions: @escaping () -> Void) { - queue.async(execute: actions) - } - - private func sync(on queue: DispatchQueue, actions: () -> Void) { - queue.sync(execute: actions) - } - - private func draw(_ image: UIImage, blurRadius radius: CGFloat, fixes isFixes: Bool, baseLayer: CALayer?) { - async(on: globalQueue) { [weak self] in - if let me = self, let blurredImage = image.blurred(radius: radius, iterations: me.iterations, ratio: me.blurRatio, blendColor: me.blendColor, blendMode: me.blendMode) { - me.sync(on: me.mainQueue) { - me.blurLayer.draw(blurredImage, fixes: isFixes, baseLayer: baseLayer) - } - } - } - } - - private func blurLayerRect(to layer: CALayer, conversion: Bool) -> CGRect { - if conversion { - let presentationLayer = blurLayer.presentation() ?? blurLayer - return presentationLayer.convert(presentationLayer.bounds, to: layer) - } else { - return layer.bounds - } - } - - private func snapshotImage(for layer: CALayer, conversion: Bool) -> UIImage? { - let rect = blurLayerRect(to: layer, conversion: conversion) - guard let context = CGContext.imageContext(rect: rect, opaque: isOpaque) else { - return nil - } - - blurLayer.render(in: context, for: layer) - - defer { - UIGraphicsEndImageContext() - } - - return UIGraphicsGetImageFromCurrentImageContext() - } -} - -extension DynamicBlurView { - open override func display(_ layer: CALayer) { - let blurRadius = blurLayer.presentationRadius - let isFixes = isDeepRendering && staticImage != nil - if let view = renderingTarget, let image = staticImage ?? snapshotImage(for: view.layer, conversion: !isFixes) { - draw(image, blurRadius: blurRadius, fixes: isFixes, baseLayer: view.layer) - } - } -} - -extension DynamicBlurView { - private func linkForDisplay() { - displayLink?.invalidate() - displayLink = UIScreen.main.displayLink(withTarget: self, selector: #selector(DynamicBlurView.displayDidRefresh(_:))) - displayLink?.add(to: .main, forMode: RunLoop.Mode(rawValue: "")) - } - - @objc private func displayDidRefresh(_ displayLink: CADisplayLink) { - display(layer) - } -} diff --git a/TelegramUI/BlurredImageNode.swift b/TelegramUI/BlurredImageNode.swift new file mode 100644 index 0000000000..be882b3fc1 --- /dev/null +++ b/TelegramUI/BlurredImageNode.swift @@ -0,0 +1,52 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Accelerate + + +private func imageBuffer(from data: UnsafeMutableRawPointer!, width: vImagePixelCount, height: vImagePixelCount, rowBytes: Int) -> vImage_Buffer { + return vImage_Buffer(data: data, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: rowBytes) +} + +private func blurredImage(image: CGImage, boxSize: UInt32, iterations: Int) -> CGImage? { + guard let providerData = image.dataProvider?.data else { + return nil + } + + let bytes = image.bytesPerRow * image.height + let inData = malloc(bytes) + var inBuffer = imageBuffer(from: inData, width: vImagePixelCount(image.width), height: vImagePixelCount(image.height), rowBytes: image.bytesPerRow) + + let outData = malloc(bytes) + var outBuffer = imageBuffer(from: outData, width: vImagePixelCount(image.width), height: vImagePixelCount(image.height), rowBytes: image.bytesPerRow) + + let tempSize = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, nil, 0, 0, boxSize, boxSize, nil, vImage_Flags(kvImageEdgeExtend + kvImageGetTempBufferSize)) + let tempData = malloc(tempSize) + + defer { + free(inData) + free(outData) + free(tempData) + } + + let source = CFDataGetBytePtr(providerData) + memcpy(inBuffer.data, source, bytes) + + for _ in 0..(.white) @@ -49,7 +49,7 @@ private final class WallpaperBackgroundNode: ASDisplayNode { self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter) self.statusNode.isUserInteractionEnabled = false - self.blurView = DynamicBlurView() + //self.blurView = DynamicBlurView() super.init() @@ -175,41 +175,41 @@ private final class WallpaperBackgroundNode: ASDisplayNode { } func setBlurEnabled(_ enabled: Bool, animated: Bool) { - if enabled { - self.blurView.frame = self.imageNode.frame - self.blurView.drawsAsynchronously = true - if self.blurView.superview == nil { - self.view.addSubview(self.blurView) - } - - if animated { - self.blurView.blurRadius = 0.0 - UIView.animate(withDuration: 0.3) { - self.blurView.blurRadius = 15.0 - } - } else { - self.blurView.blurRadius = 15.0 - } - } else { - if self.blurView.superview != nil { - if animated { - UIView.animate(withDuration: 0.3, animations: { - self.blurView.blurRadius = 0.0 - }) { finished in - if finished { - self.blurView.removeFromSuperview() - } - } - } else { - self.blurView.removeFromSuperview() - } - } - } +// if enabled { +// //self.blurView.frame = self.imageNode.frame +// self.blurView.drawsAsynchronously = true +// if self.blurView.superview == nil { +// self.view.addSubview(self.blurView) +// } +// +// if animated { +// self.blurView.blurRadius = 0.0 +// UIView.animate(withDuration: 0.3) { +// self.blurView.blurRadius = 15.0 +// } +// } else { +// self.blurView.blurRadius = 15.0 +// } +// } else { +// if self.blurView.superview != nil { +// if animated { +// UIView.animate(withDuration: 0.3, animations: { +// self.blurView.blurRadius = 0.0 +// }) { finished in +// if finished { +// self.blurView.removeFromSuperview() +// } +// } +// } else { +// self.blurView.removeFromSuperview() +// } +// } +// } } func updateLayout(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.imageNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.blurView.frame = self.imageNode.frame + //self.blurView.frame = self.imageNode.frame let progressDiameter: CGFloat = 50.0 self.statusNode.frame = CGRect(x: layout.safeInsets.left + floorToScreenPixels((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - progressDiameter) / 2.0), y: floorToScreenPixels((layout.size.height - progressDiameter) / 2.0), width: progressDiameter, height: progressDiameter)