Skip to content

Commit

Permalink
feat(blur-processing): change to Metal API
Browse files Browse the repository at this point in the history
  • Loading branch information
PierrePerrin committed Aug 29, 2024
1 parent f26e7fa commit 25b20fc
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 112 deletions.
4 changes: 2 additions & 2 deletions ShadowView.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Pod::Spec.new do |s|

s.name = 'ShadowView'

s.version = '1.4.3'
s.version = '1.5.0'

s.summary = 'ShadowView is an iOS Shadow library that makes view\'s shadow implementation easy and sweet'

Expand All @@ -15,7 +15,7 @@ s.homepage = 'https://github.com/PierrePerrin/ShadowView'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Pierre Pierrin' => 'pierreperrin@outlook.com' }
s.source = { :git => 'https://github.com/PierrePerrin/ShadowView.git', :tag => s.version.to_s }
s.ios.deployment_target = '8.1'
s.ios.deployment_target = '12.0'
s.source_files = 'ShadowView/*.swift'

end
10 changes: 6 additions & 4 deletions ShadowView.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = ShadowView/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = PP.ShadowView;
Expand All @@ -539,7 +539,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = ShadowView/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = PP.ShadowView;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -554,6 +554,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
DEVELOPMENT_TEAM = PCKYQNTV3N;
INFOPLIST_FILE = ShadowViewTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = PP.ShadowViewTests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -567,6 +568,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
DEVELOPMENT_TEAM = PCKYQNTV3N;
INFOPLIST_FILE = ShadowViewTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = PP.ShadowViewTests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -581,7 +583,7 @@
CLANG_ENABLE_MODULES = YES;
DEVELOPMENT_TEAM = P5PSPS572B;
INFOPLIST_FILE = ShadowViewExample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = PP.ShadowViewExample.Pierre.Perrin;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -598,7 +600,7 @@
CLANG_ENABLE_MODULES = YES;
DEVELOPMENT_TEAM = P5PSPS572B;
INFOPLIST_FILE = ShadowViewExample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = PP.ShadowViewExample.Pierre.Perrin;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
4 changes: 2 additions & 2 deletions ShadowView/1.3/ShadowView.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|

s.name = 'ShadowView'
s.version = '1.3'
s.version = '1.5.0'
s.summary = 'ShadowView is an iOS Shadow library that makes view\'s shadow implementation easy and sweet'

s.description = <<-DESC
Expand All @@ -13,7 +13,7 @@ s.homepage = 'https://github.com/PierrePerrin/ShadowView'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Pierre Pierrin' => 'pierreperrin@outlook.com' }
s.source = { :git => 'https://github.com/PierrePerrin/ShadowView.git', :tag => s.version.to_s }
s.ios.deployment_target = '8.1'
s.ios.deployment_target = '12.0'
s.source_files = 'ShadowView/ShadowView/*.swift'

end
2 changes: 1 addition & 1 deletion ShadowView/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.4.2</string>
<string>1.5.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
Expand Down
86 changes: 38 additions & 48 deletions ShadowView/ShadowView+BlurProcess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,67 +8,57 @@

import UIKit

extension ShadowView{

internal func addImageView(){

extension ShadowView {
func addImageView() {
guard shadowImageView == nil else {
return
}

let imageView = UIImageView()
imageView.frame.size = frame.size.scaled(by: shadowScale)
imageView.center = CGPoint(x:bounds.midX,y:bounds.midY)
imageView.center = CGPoint(x: bounds.midX, y: bounds.midY)
imageView.layer.masksToBounds = false
shadowImageView = imageView
insertSubview(imageView,at:0)
insertSubview(imageView, at: 0)
}



public func updateShadow(dispatchQueue: DispatchQueue = .main){


public func updateShadow(dispatchQueue: DispatchQueue = .main) {
self.shadowImageView.image = nil
self.createLayerImage(dispatchQueue: dispatchQueue)
}

private func createLayerImage(dispatchQueue: DispatchQueue = .main){


private func createLayerImage(dispatchQueue: DispatchQueue = .global(qos: .userInitiated)) {
dispatchQueue.async { [weak self] in

guard let image = self?.asImage else { return }

let shadowColor = self?.shadowColor ?? .clear
let shadowSaturation = self?.shadowSaturation ?? 1
let scaleImageConstant = self?.scaleImageConstant ?? 1
let blurRadius = self?.blurRadius ?? 20

let containerLayer = CALayer()
let imageSize = image.size
containerLayer.frame = CGRect(origin: .zero, size: imageSize.scaled(by: scaleImageConstant))
containerLayer.backgroundColor = UIColor.clear.cgColor
let blurImageLayer = CALayer()
blurImageLayer.frame = CGRect(origin: .zero, size: imageSize)
blurImageLayer.position = CGPoint(x: containerLayer.bounds.midX, y: containerLayer.bounds.midY)
blurImageLayer.contents = image.applyBlurWithRadius(0, tintColor: shadowColor, saturationDeltaFactor: shadowSaturation)?.cgImage

blurImageLayer.masksToBounds = false
containerLayer.addSublayer(blurImageLayer)
let containerImage = containerLayer.asImage

let resizeImageConstant: CGFloat = 1
guard let resizedContainerImage = containerImage.resized(withPercentage: resizeImageConstant),
let blurredImage = resizedContainerImage.applyBlur(blurRadius: blurRadius)
else { return }

DispatchQueue.main.async { [weak self] in

guard let image = self?.asImage else { return }

let shadowColor = self?.shadowColor ?? .clear
let shadowSaturation = self?.shadowSaturation ?? 1
let scaleImageConstant = self?.scaleImageConstant ?? 1
let blurRadius = self?.blurRadius ?? 20
dispatchQueue.async { [weak self] in

let containerLayer = CALayer()
let imageSize = image.size
containerLayer.frame = CGRect(origin: .zero, size: imageSize.scaled(by:scaleImageConstant))
containerLayer.backgroundColor = UIColor.clear.cgColor
let blurImageLayer = CALayer()
blurImageLayer.frame = CGRect(origin: .zero,size: imageSize)
blurImageLayer.position = CGPoint(x:containerLayer.bounds.midX,y:containerLayer.bounds.midY)
blurImageLayer.contents = image.applyBlurWithRadius(0, tintColor: shadowColor, saturationDeltaFactor: shadowSaturation)?.cgImage

blurImageLayer.masksToBounds = false
containerLayer.addSublayer(blurImageLayer)
let containerImage = containerLayer.asImage


let resizeImageConstant :CGFloat = 1
guard let resizedContainerImage = containerImage.resized(withPercentage: resizeImageConstant),
let blurredImage = resizedContainerImage.applyBlur(blurRadius: blurRadius)
else { return }

DispatchQueue.main.async { [weak self] in
self?.layer.masksToBounds = false
self?.shadowImageView?.image = blurredImage
}
self?.layer.masksToBounds = false
self?.shadowImageView?.image = blurredImage
}
}

}
}
101 changes: 46 additions & 55 deletions ShadowView/UIImage+Size.swift
Original file line number Diff line number Diff line change
@@ -1,74 +1,65 @@
//
// UIImage+Blur.swift
// ShadowView
//
// Created by Pierre Perrin on 25/07/2017.
// Copyright © 2017 Pierreperrin. All rights reserved.
//

import UIKit
import CoreGraphics
import CoreImage
import Metal
import UIKit

extension UIImage{

/// Resize the image to a centain percentage
extension UIImage {
/// Resize the image to a certain percentage
///
/// - Parameter percentage: Percentage value
/// - Returns: UIImage(Optional)
func resized(withPercentage percentage: CGFloat) -> UIImage? {
let canvasSize = size.scaled(by: percentage)
UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: canvasSize))
return UIGraphicsGetImageFromCurrentImageContext()
return resized(toSize: size.scaled(by: percentage))
}


/// Resize the image to a certain width, maintaining aspect ratio
///
/// - Parameter width: Desired width
/// - Returns: UIImage(Optional)
func resized(toWidth width: CGFloat) -> UIImage? {
let canvasSize = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
let height = CGFloat(ceil(width / size.width * size.height))
return resized(toSize: CGSize(width: width, height: height))
}

/// Apply a Gaussian blur to the image using Metal GPU acceleration
///
/// - Parameter blurRadius: The blur radius
/// - Returns: UIImage(Optional)
func applyBlur(blurRadius: CGFloat) -> UIImage? {
guard let ciImage = CIImage(image: self) else { return nil }
guard let filter = CIFilter(name: "CIGaussianBlur") else { return nil }

filter.setValue(ciImage, forKey: kCIInputImageKey)
filter.setValue(blurRadius, forKey: kCIInputRadiusKey)

// Create a Metal-backed CIContext for GPU acceleration
guard let device = MTLCreateSystemDefaultDevice() else { return nil }
let context = CIContext(mtlDevice: device)

guard let output = filter.outputImage else { return nil }
guard let cgImage = context.createCGImage(output, from: ciImage.extent) else { return nil }

return UIImage(cgImage: cgImage)
}

/// General method for resizing an image to a specific size
///
/// - Parameter size: Target size
/// - Returns: UIImage(Optional)
private func resized(toSize size: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: canvasSize))
draw(in: CGRect(origin: .zero, size: size))
return UIGraphicsGetImageFromCurrentImageContext()
}


func applyBlur(blurRadius:CGFloat) -> UIImage?{

guard let ciImage = CIImage(image: self) else {return nil}

if let filter = CIFilter(name: "CIGaussianBlur") {

filter.setValue(ciImage, forKey: kCIInputImageKey)
filter.setValue(blurRadius, forKey: kCIInputRadiusKey)
let eaglContext =
EAGLContext(api: EAGLRenderingAPI.openGLES3)
?? EAGLContext(api: EAGLRenderingAPI.openGLES2)
?? EAGLContext(api: EAGLRenderingAPI.openGLES1)

let context = eaglContext == nil ?
CIContext(options: nil)
: CIContext(eaglContext: eaglContext!)

if let output = filter.outputImage,
let cgimg = context.createCGImage(output, from: ciImage.extent)
{
return UIImage(cgImage: cgimg)
}
}

return nil
}
}


extension CGSize {

/// Generates a new size that is this size scaled by a cerntain percentage
/// Generates a new size that is this size scaled by a certain percentage
///
/// - Parameter percentage: the percentage to scale to
/// - Returns: a new CGSize instance by scaling self by the given percentage
/// - Parameter percentage: The percentage to scale to
/// - Returns: A new CGSize instance by scaling self by the given percentage
func scaled(by percentage: CGFloat) -> CGSize {
return CGSize(width: width * percentage, height: height * percentage)
}

}

0 comments on commit 25b20fc

Please sign in to comment.