├── .gitignore ├── MetalImage.podspec ├── MetalImage ├── Basic │ ├── MetalImageDevice.h │ ├── MetalImageDevice.m │ ├── MetalImageProtocol.h │ ├── MetalImageRenderProcess.h │ ├── MetalImageRenderProcess.m │ ├── MetalImageResource.h │ ├── MetalImageResource.m │ ├── MetalImageTexture.h │ ├── MetalImageTexture.m │ ├── MetalImageTextureCache.h │ └── MetalImageTextureCache.m ├── Category │ ├── NSBundle+MetalImageBundle.h │ └── NSBundle+MetalImageBundle.m ├── ExtensionFilter │ ├── Affine Transformation │ │ ├── MetalImageCropFilter.h │ │ └── MetalImageCropFilter.m │ ├── Composite │ │ ├── MetalImageiOSBlurFilter.h │ │ └── MetalImageiOSBlurFilter.m │ ├── Convolution │ │ ├── MetalImageConvolutionFilter.h │ │ ├── MetalImageConvolutionFilter.m │ │ ├── MetalImageGaussianBlurFilter.h │ │ ├── MetalImageGaussianBlurFilter.m │ │ ├── MetalImageSharpenFilter.h │ │ └── MetalImageSharpenFilter.m │ └── Pixel │ │ ├── MetalImageContrastFilter.h │ │ ├── MetalImageContrastFilter.m │ │ ├── MetalImageHueFilter.h │ │ ├── MetalImageHueFilter.m │ │ ├── MetalImageLookUpTableFilter.h │ │ ├── MetalImageLookUpTableFilter.m │ │ ├── MetalImageLuminanceFilter.h │ │ ├── MetalImageLuminanceFilter.m │ │ ├── MetalImageSaturationFilter.h │ │ └── MetalImageSaturationFilter.m ├── Filter │ ├── MetalImageFilter.h │ └── MetalImageFilter.m ├── Library │ ├── Basic.metal │ ├── ConvolutionShader.metal │ └── PixelShader.metal ├── MetalImage.h ├── Resource │ ├── 4*4_origin.png │ ├── 8*8_effect.png │ └── 8*8_origin.png ├── Source │ ├── MetalImageCamera.h │ ├── MetalImageCamera.m │ ├── MetalImagePicture.h │ ├── MetalImagePicture.m │ ├── MetalImageSource.h │ └── MetalImageSource.m └── Target │ ├── MetalImageMovieWriter.h │ ├── MetalImageMovieWriter.m │ ├── MetalImageTarget.h │ ├── MetalImageTarget.m │ ├── MetalImageView.h │ └── MetalImageView.m ├── README.md ├── iOS ├── MetalImageDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── MetalImageDemo.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── MetalImageDemo │ ├── 1.jpg │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── 1024.png │ │ │ ├── 120-1.png │ │ │ ├── 120.png │ │ │ ├── 180.png │ │ │ ├── 40.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── BasicViewController.h │ ├── BasicViewController.m │ ├── BasicViewController.xib │ ├── FilterViewController.h │ ├── FilterViewController.m │ ├── FilterViewController.xib │ ├── Info.plist │ ├── MPSFilterViewController.h │ ├── MPSFilterViewController.m │ ├── MPSFilterViewController.xib │ ├── MetalImageDemo-Bridging-Header.h │ ├── OSSignPostViewController.swift │ ├── OSSignPostViewController.xib │ ├── RecordViewController.h │ ├── RecordViewController.m │ ├── RecordViewController.xib │ ├── ViewController.h │ ├── ViewController.m │ └── main.m ├── Podfile ├── Podfile.lock └── Pods │ ├── Local Podspecs │ └── MetalImage.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ └── project.pbxproj │ └── Target Support Files │ ├── MetalImage │ ├── MetalImage-Info.plist │ ├── MetalImage-dummy.m │ ├── MetalImage-prefix.pch │ ├── MetalImage-umbrella.h │ ├── MetalImage.modulemap │ ├── MetalImage.xcconfig │ ├── ResourceBundle-MTMetalImageBundle-Info.plist │ ├── ResourceBundle-MetalImageBundle-Info.plist │ ├── ResourceBundle-MetalImageBundle-MetalImage-Info.plist │ ├── ResourceBundle-MetalImageLibrary-MetalImage-Info.plist │ └── ResourceBundle-MetalLibrary-MetalImage-Info.plist │ └── Pods-MetalImageDemo │ ├── Pods-MetalImageDemo-Info.plist │ ├── Pods-MetalImageDemo-acknowledgements.markdown │ ├── Pods-MetalImageDemo-acknowledgements.plist │ ├── Pods-MetalImageDemo-dummy.m │ ├── Pods-MetalImageDemo-frameworks-Debug-input-files.xcfilelist │ ├── Pods-MetalImageDemo-frameworks-Debug-output-files.xcfilelist │ ├── Pods-MetalImageDemo-frameworks-Release-input-files.xcfilelist │ ├── Pods-MetalImageDemo-frameworks-Release-output-files.xcfilelist │ ├── Pods-MetalImageDemo-frameworks.sh │ ├── Pods-MetalImageDemo-resources-Debug-input-files.xcfilelist │ ├── Pods-MetalImageDemo-resources-Debug-output-files.xcfilelist │ ├── Pods-MetalImageDemo-resources-Release-input-files.xcfilelist │ ├── Pods-MetalImageDemo-resources-Release-output-files.xcfilelist │ ├── Pods-MetalImageDemo-resources.sh │ ├── Pods-MetalImageDemo-umbrella.h │ ├── Pods-MetalImageDemo.debug.xcconfig │ ├── Pods-MetalImageDemo.modulemap │ └── Pods-MetalImageDemo.release.xcconfig └── icon.jpeg /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/objective-c 3 | # Edit at https://www.gitignore.io/?templates=objective-c 4 | 5 | ### Objective-C ### 6 | # Xcode 7 | # 8 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 9 | 10 | ## Build generated 11 | build/ 12 | DerivedData/ 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata/ 24 | 25 | ## Other 26 | *.moved-aside 27 | *.xccheckout 28 | *.xcscmblueprint 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | # CocoaPods 37 | # 38 | # We recommend against adding the Pods directory to your .gitignore. However 39 | # you should judge for yourself, the pros and cons are mentioned at: 40 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 41 | # 42 | # Pods/ 43 | # 44 | # Add this line if you want to avoid checking in source code from the Xcode workspace 45 | # *.xcworkspace 46 | 47 | # Carthage 48 | # 49 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 50 | # Carthage/Checkouts 51 | 52 | Carthage/Build 53 | 54 | # fastlane 55 | # 56 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 57 | # screenshots whenever they are needed. 58 | # For more information about the recommended setup visit: 59 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 60 | 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots/**/*.png 64 | fastlane/test_output 65 | 66 | # Code Injection 67 | # 68 | # After new code Injection tools there's a generated folder /iOSInjectionProject 69 | # https://github.com/johnno1962/injectionforxcode 70 | 71 | iOSInjectionProject/ 72 | 73 | ### Objective-C Patch ### 74 | 75 | # End of https://www.gitignore.io/api/objective-c 76 | -------------------------------------------------------------------------------- /MetalImage.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint MetalImage.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | s.name = "MetalImage" 12 | s.version = "0.0.1" 13 | s.summary = "A short description of MetalImage." 14 | s.description = <<-DESC 15 | MetalImage 为 iOS 平台上一个简单的Metal滤镜处理框架 16 | DESC 17 | 18 | s.homepage = "https://github.com/hello-david/MetalImage.git" 19 | s.license = { 20 | :type => 'Copyright', 21 | :text => <<-LICENSE 22 | © 2008-2018 David.Dai. All rights reserved. 23 | LICENSE 24 | } 25 | 26 | s.author = { "david.dai" => "hello.david.me@gmail.com" } 27 | s.platform = :ios, "9.0" 28 | s.source = { :git => "https://github.com/hello-david/MetalImage.git", :tag => "#{s.version}" } 29 | 30 | s.public_header_files = 'MetalImage/MetalImage.h' 31 | s.default_subspec = 'Core' 32 | 33 | # Core 34 | s.subspec 'Core' do |core| 35 | core.subspec 'Basic' do |basic| 36 | basic.public_header_files = 'MetalImage/Basic/**/*.{h}' 37 | basic.source_files = 'MetalImage/Basic/**/*.{h,m,cpp,mm}' 38 | end 39 | 40 | core.subspec 'Source' do |source| 41 | source.public_header_files = 'MetalImage/Source/**/*.{h}' 42 | source.source_files = 'MetalImage/Source/**/*.{h,m,cpp,mm}' 43 | end 44 | 45 | core.subspec 'Filter' do |filter| 46 | filter.public_header_files = 'MetalImage/Filter/**/*.{h}' 47 | filter.source_files = 'MetalImage/Filter/**/*.{h,m,cpp,mm}' 48 | end 49 | 50 | core.subspec 'Target' do |target| 51 | target.public_header_files = 'MetalImage/Target/**/*.{h}' 52 | target.source_files = 'MetalImage/Target/**/*.{h,m,cpp,mm}' 53 | end 54 | 55 | core.subspec 'Category' do |category| 56 | category.public_header_files = 'MetalImage/Category/**/*.{h}' 57 | category.source_files = 'MetalImage/Category/**/*.{h,m,cpp,mm}' 58 | end 59 | 60 | core.source_files = 'MetalImage/MetalImage.h' 61 | core.resource_bundles = { 62 | 'MetalLibrary' => [ 63 | 'MetalImage/Library/*.metal', 64 | 'MetalImage/Resource/*' 65 | ] 66 | } 67 | 68 | core.frameworks = "Metal", "MetalKit", "CoreVideo", "AVFoundation" 69 | end 70 | 71 | # 滤镜拓展 72 | s.subspec 'ExtensionFilter' do |extensionFilter| 73 | extensionFilter.public_header_files = 'MetalImage/ExtensionFilter/**/*.{h}' 74 | extensionFilter.source_files = 'MetalImage/ExtensionFilter/**/*.{h,m,cpp,mm}' 75 | extensionFilter.dependency 'MetalImage/Core' 76 | end 77 | 78 | s.requires_arc = true 79 | end 80 | -------------------------------------------------------------------------------- /MetalImage/Basic/MetalImageDevice.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageDevice.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/11. 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import "MetalImageTextureCache.h" 14 | 15 | #define METAL_SHADER_STRING(text) @ #text 16 | #define kMetalImageDefaultVertex @"oneInputVertex" 17 | #define kMetalImageDefaultFragment @"passthroughFragment" 18 | 19 | NS_ASSUME_NONNULL_BEGIN 20 | 21 | extern NSString* const MetalImageBundleName; 22 | 23 | @interface MetalImageDevice : NSObject 24 | @property (nonatomic, readonly) id device; 25 | @property (nonatomic, readonly) id commandQueue; 26 | @property (nonatomic, strong) id library; 27 | @property (nonatomic, assign) MTLPixelFormat pixelFormat; 28 | @property (nonatomic, strong) dispatch_queue_t concurrentQueue; 29 | @property (nonatomic, strong) MetalImageTextureCache *textureCache; 30 | @property (nonatomic, strong) MTKTextureLoader *textureLoader; 31 | 32 | + (instancetype)shared; 33 | @end 34 | 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /MetalImage/Basic/MetalImageDevice.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageDevice.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/11. 6 | // 7 | 8 | #import "MetalImageDevice.h" 9 | #import "NSBundle+MetalImageBundle.h" 10 | 11 | NSString *const MetalImageBundleName = @"MetalLibrary"; 12 | 13 | @interface MetalImageDevice() 14 | @property (nonatomic, strong) id device; 15 | @property (nonatomic, strong) id commandQueue; 16 | @end 17 | 18 | @implementation MetalImageDevice 19 | + (instancetype)shared { 20 | static dispatch_once_t onceToken; 21 | static MetalImageDevice *defaultDevice = nil; 22 | dispatch_once(&onceToken, ^{ 23 | defaultDevice = [[self alloc] init]; 24 | }); 25 | return defaultDevice; 26 | } 27 | 28 | - (instancetype)init { 29 | if (self = [super init]) { 30 | _device = MTLCreateSystemDefaultDevice(); 31 | static dispatch_once_t onceToken; 32 | static id defaultCommandQueue = nil; 33 | dispatch_once(&onceToken, ^{ 34 | defaultCommandQueue = [self.device newCommandQueue]; 35 | }); 36 | _commandQueue = defaultCommandQueue; 37 | _pixelFormat = MTLPixelFormatBGRA8Unorm; 38 | } 39 | return self; 40 | } 41 | 42 | - (MetalImageTextureCache *)textureCache { 43 | if (!_textureCache) { 44 | _textureCache = [[MetalImageTextureCache alloc] initWithDevice:_device]; 45 | } 46 | return _textureCache; 47 | } 48 | 49 | - (dispatch_queue_t)concurrentQueue { 50 | if (!_concurrentQueue) { 51 | _concurrentQueue = dispatch_queue_create("com.MetalImage.globalProcess", DISPATCH_QUEUE_CONCURRENT); 52 | } 53 | return _concurrentQueue; 54 | } 55 | 56 | - (MTKTextureLoader *)textureLoader { 57 | if (!_textureLoader) { 58 | _textureLoader = [[MTKTextureLoader alloc] initWithDevice:_device]; 59 | } 60 | return _textureLoader; 61 | } 62 | 63 | - (id)library { 64 | if (!_library) { 65 | NSString *bundlePath = [NSBundle metalImage_bundleWithName:MetalImageBundleName].bundlePath; 66 | NSString *defaultMetalFile = [bundlePath stringByAppendingPathComponent:@"default.metallib"]; 67 | NSError *error = nil; 68 | _library = [_device newLibraryWithFile:defaultMetalFile error:&error]; 69 | } 70 | return _library; 71 | } 72 | @end 73 | -------------------------------------------------------------------------------- /MetalImage/Basic/MetalImageProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageProtocol.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/11/29. 6 | // 7 | 8 | #import 9 | #import 10 | #import "MetalImageResource.h" 11 | 12 | @protocol MetalImageTarget; 13 | @protocol MetalImageSource; 14 | @protocol MetalImageRender; 15 | 16 | @protocol MetalImageSource 17 | /** 18 | * 转发资源 19 | */ 20 | - (void)send:(MetalImageResource *)resource withTime:(CMTime)time; 21 | 22 | /** 23 | * 资源接收对象管理 24 | */ 25 | - (void)addTarget:(id)target; 26 | - (void)removeTarget:(id)target; 27 | - (void)removeAllTarget; 28 | @end 29 | 30 | @protocol MetalImageTarget 31 | /** 32 | * 接收资源 33 | */ 34 | - (void)receive:(MetalImageResource *)resource withTime:(CMTime)time; 35 | @end 36 | 37 | @protocol MetalImageRender 38 | /** 39 | * 独立完整的渲染流程 40 | */ 41 | - (void)renderToResource:(MetalImageResource *)resource; 42 | 43 | /** 44 | * 是否支持只进行RenderCommandEncoder级别的渲染 45 | */ 46 | - (BOOL)supportProcessRenderCommandEncoderOnly; 47 | @end 48 | 49 | -------------------------------------------------------------------------------- /MetalImage/Basic/MetalImageRenderProcess.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageRenderProcess.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/5/16. 6 | // 7 | 8 | #import 9 | #import "MetalImageDevice.h" 10 | #import "MetalImageTexture.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | @class MetalImageRenderProcess; 14 | 15 | typedef void(^MetalImageRenderProcessBlock)(id renderEncoder); 16 | typedef void(^MetalImageRenderCompletionBlock)(MetalImageTexture *renderedTexture); 17 | 18 | @interface MetalImageRenderProcess : NSObject 19 | @property (nonatomic, assign) CGSize targetSize; 20 | @property (nonatomic, readonly) MetalImageTexture *texture; // 当前纹理 21 | @property (nonatomic, readonly) MetalImageTexture *targetTexture; // 渲染结果纹理 22 | @property (nonatomic, readonly) id positionBuffer; // 整个渲染空间 23 | @property (nonatomic, readonly) id textureCoorBuffer; // 整张图片 24 | 25 | - (instancetype)initWithTexture:(MetalImageTexture *)texture; 26 | 27 | /** 28 | * 在同一个CommandBuffer中进行渲染 29 | * 30 | * @param processing 给外部一个RenderEncoder并利用它实现Draw-Call 31 | * 32 | * @discussion 33 | * 会自动交换目标纹理,不要在外部调用[renderEncoder endEncoding] 34 | */ 35 | - (void)addRenderProcess:(MetalImageRenderProcessBlock)processing; 36 | 37 | /** 38 | * 将会Commit内部的Commandbuffer 39 | * 40 | * @discussion 41 | * 在使用外部的CommandBuffer时有enqueue需要先把内置CommandBuffer的提交了 42 | */ 43 | - (void)commitRender; 44 | - (void)commitRenderWaitUntilFinish:(BOOL)waitUntilFinish completion:(MetalImageRenderCompletionBlock _Nullable)completion; 45 | 46 | /** 47 | * 替换这个资源对象的纹理,用于原始纹理和目标纹理交换 48 | * 49 | * @param texture 目标纹理 50 | */ 51 | - (void)swapTexture:(MetalImageTexture *)texture; 52 | @end 53 | 54 | NS_ASSUME_NONNULL_END 55 | -------------------------------------------------------------------------------- /MetalImage/Basic/MetalImageRenderProcess.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageRenderProcess.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/5/16. 6 | // 7 | 8 | #import "MetalImageRenderProcess.h" 9 | 10 | @interface MetalImageRenderProcess() 11 | @property (nonatomic, strong) MTLRenderPassDescriptor *renderPassDecriptor; 12 | @property (nonatomic, strong) id renderCommandBuffer; 13 | @property (nonatomic, strong) id renderEncoder; 14 | 15 | @property (nonatomic, strong) MetalImageTexture *texture; 16 | @property (nonatomic, strong) MetalImageTexture *targetTexture; 17 | @property (nonatomic, strong) id positionBuffer; 18 | @property (nonatomic, strong) id textureCoorBuffer; 19 | @end 20 | 21 | @implementation MetalImageRenderProcess 22 | 23 | - (instancetype)initWithTexture:(MetalImageTexture *)texture { 24 | if (self = [super init]) { 25 | _texture = texture; 26 | _targetSize = CGSizeMake(texture.width, texture.height); 27 | } 28 | return self; 29 | } 30 | 31 | - (void)dealloc { 32 | [[MetalImageDevice shared].textureCache cacheTexture:self.texture]; 33 | } 34 | 35 | - (void)addRenderProcess:(MetalImageRenderProcessBlock)processing { 36 | @autoreleasepool { 37 | if (processing) { 38 | processing(self.renderEncoder); 39 | } 40 | 41 | _targetTexture.orientation = _texture.orientation; 42 | [_renderEncoder endEncoding]; 43 | [self swapTexture:_targetTexture]; 44 | 45 | _renderEncoder = nil; 46 | _targetTexture = nil; 47 | } 48 | } 49 | 50 | - (void)commitRender { 51 | [self commitRenderWaitUntilFinish:NO completion:nil]; 52 | } 53 | 54 | - (void)commitRenderWaitUntilFinish:(BOOL)waitUntilFinish completion:(MetalImageRenderCompletionBlock)completion { 55 | if (!_renderCommandBuffer || _renderCommandBuffer.status > MTLCommandBufferStatusEnqueued) { 56 | _renderCommandBuffer = nil; 57 | return; 58 | } 59 | 60 | if (completion) { 61 | __weak typeof(self) weakSelf = self; 62 | [_renderCommandBuffer addCompletedHandler:^(id _Nonnull commandbuffer) { 63 | completion(weakSelf.texture); 64 | }]; 65 | } 66 | 67 | [_renderCommandBuffer commit]; 68 | !waitUntilFinish ? : [_renderCommandBuffer waitUntilCompleted]; 69 | _renderEncoder = nil; 70 | _renderCommandBuffer = nil; 71 | _targetTexture = nil; 72 | } 73 | 74 | - (void)swapTexture:(MetalImageTexture *)texture { 75 | [[MetalImageDevice shared].textureCache cacheTexture:self.texture]; 76 | 77 | self.texture = nil; 78 | self.texture = texture; 79 | _targetSize = CGSizeMake(texture.width, texture.height); 80 | } 81 | 82 | - (void)setTargetSize:(CGSize)targetSize { 83 | if (!CGSizeEqualToSize(_targetSize, targetSize)) { 84 | [self commitRenderWaitUntilFinish:YES completion:nil]; 85 | _targetSize = targetSize; 86 | } 87 | } 88 | 89 | #pragma mark - Getter 90 | - (MTLRenderPassDescriptor *)renderPassDecriptor { 91 | if (!_renderPassDecriptor) { 92 | _renderPassDecriptor = [[MTLRenderPassDescriptor alloc] init]; 93 | _renderPassDecriptor.colorAttachments[0].loadAction = MTLLoadActionClear; 94 | _renderPassDecriptor.colorAttachments[0].storeAction = MTLStoreActionStore; 95 | _renderPassDecriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1); 96 | } 97 | return _renderPassDecriptor; 98 | } 99 | 100 | - (id)renderCommandBuffer { 101 | if (!_renderCommandBuffer) { 102 | _renderCommandBuffer = [[MetalImageDevice shared].commandQueue commandBuffer]; 103 | } 104 | return _renderCommandBuffer; 105 | } 106 | 107 | - (id)renderEncoder { 108 | if (!_renderEncoder) { 109 | self.renderPassDecriptor.colorAttachments[0].texture = self.targetTexture.metalTexture; 110 | _renderEncoder = [self.renderCommandBuffer renderCommandEncoderWithDescriptor:self.renderPassDecriptor]; 111 | } 112 | return _renderEncoder; 113 | } 114 | 115 | - (MetalImageTexture *)targetTexture { 116 | if (!_targetTexture) { 117 | CGSize targetSize = CGSizeEqualToSize(_targetSize, CGSizeZero) ? _texture.size : _targetSize; 118 | _targetTexture = [[MetalImageDevice shared].textureCache fetchTexture:targetSize 119 | pixelFormat:_texture.metalTexture.pixelFormat]; 120 | _targetTexture.orientation = _texture.orientation; 121 | } 122 | return _targetTexture; 123 | } 124 | 125 | - (id)positionBuffer { 126 | if (!_positionBuffer) { 127 | MetalImageCoordinate position = [self.texture texturePositionToSize:self.texture.size contentMode:MetalImageContentModeScaleToFill]; 128 | _positionBuffer = [[MetalImageDevice shared].device newBufferWithBytes:&position length:sizeof(position) options:0]; 129 | _positionBuffer.label = @"Default Position"; 130 | } 131 | return _positionBuffer; 132 | } 133 | 134 | - (id)textureCoorBuffer { 135 | if (!_textureCoorBuffer) { 136 | MetalImageCoordinate textureCoor = [self.texture textureCoordinatesToOrientation:self.texture.orientation]; 137 | _textureCoorBuffer = [[MetalImageDevice shared].device newBufferWithBytes:&textureCoor length:sizeof(textureCoor) options:0]; 138 | _textureCoorBuffer.label = @"Default Texture Coordinates"; 139 | } 140 | return _textureCoorBuffer; 141 | } 142 | @end 143 | -------------------------------------------------------------------------------- /MetalImage/Basic/MetalImageResource.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageResource.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/11. 6 | // 7 | 8 | #import 9 | #import 10 | #import "MetalImageRenderProcess.h" 11 | 12 | typedef NS_ENUM(NSUInteger, MetalImageResourceType) { 13 | MetalImageResourceTypeImage, 14 | MetalImageResourceTypeAudio 15 | }; 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | @interface MetalImageResource : NSObject 20 | @property (nonatomic, readonly, nullable) CMSampleBufferRef audioBuffer; 21 | @property (nonatomic, readonly, nullable) MetalImageTexture *texture; 22 | @property (nonatomic, readonly, nullable) MetalImageRenderProcess *renderProcess; 23 | 24 | @property (nonatomic, assign) MetalImageResourceType type; 25 | @property (nonatomic, weak) dispatch_queue_t processingQueue; 26 | 27 | + (instancetype)audioResource:(CMSampleBufferRef)audioBuffer; 28 | + (instancetype)imageResource:(MetalImageTexture *)texture; 29 | 30 | - (nullable MetalImageResource *)newResourceFromSelf; 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /MetalImage/Basic/MetalImageResource.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageResource.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/11/30. 6 | // 7 | 8 | #import "MetalImageResource.h" 9 | @interface MetalImageResource() 10 | @property (nonatomic, assign) CMSampleBufferRef audioBuffer; 11 | @property (nonatomic, strong) MetalImageRenderProcess *renderProcess; 12 | @end 13 | 14 | @implementation MetalImageResource 15 | 16 | + (instancetype)audioResource:(CMSampleBufferRef)audioBuffer { 17 | return [[self alloc] initWithAudioBuffer:audioBuffer]; 18 | } 19 | 20 | + (instancetype)imageResource:(MetalImageTexture *)texture { 21 | return [[self alloc] initWithTexture:texture]; 22 | } 23 | 24 | - (void)dealloc { 25 | if (_audioBuffer) { 26 | CFRelease(_audioBuffer); 27 | } 28 | } 29 | 30 | - (instancetype)initWithAudioBuffer:(CMSampleBufferRef)audioBuffer { 31 | if (self = [super init]) { 32 | _type = MetalImageResourceTypeAudio; 33 | _audioBuffer = audioBuffer; 34 | CFRetain(_audioBuffer); 35 | } 36 | return self; 37 | } 38 | 39 | - (instancetype)initWithTexture:(MetalImageTexture *)texture { 40 | if (self = [super init]) { 41 | _type = MetalImageResourceTypeImage; 42 | _renderProcess = [[MetalImageRenderProcess alloc] initWithTexture:texture]; 43 | } 44 | return self; 45 | } 46 | 47 | - (MetalImageTexture *)texture { 48 | return self.renderProcess.texture; 49 | } 50 | 51 | - (MetalImageResource *)newResourceFromSelf { 52 | __block MetalImageResource *newResource = nil; 53 | if (MetalImageResourceTypeAudio == _type) { 54 | newResource = [[MetalImageResource alloc] initWithAudioBuffer:_audioBuffer]; 55 | } 56 | else if (MetalImageResourceTypeImage == _type) { 57 | // 拷贝之前先提交之前的渲染 58 | [self.renderProcess commitRender]; 59 | 60 | // 拷贝当前的纹理 61 | @autoreleasepool { 62 | MetalImageTexture *copyTexture = [[MetalImageDevice shared].textureCache fetchTexture:self.texture.size 63 | pixelFormat:self.texture.metalTexture.pixelFormat]; 64 | copyTexture.orientation = self.texture.orientation; 65 | newResource = [[MetalImageResource alloc] initWithTexture:copyTexture]; 66 | [copyTexture replaceTexture:self.texture.metalTexture]; 67 | } 68 | } 69 | 70 | return newResource; 71 | } 72 | @end 73 | -------------------------------------------------------------------------------- /MetalImage/Basic/MetalImageTexture.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageTexture.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/11. 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | 12 | typedef struct { 13 | float bottomLeftX; 14 | float bottomLeftY; 15 | 16 | float bottomRightX; 17 | float bottomRightY; 18 | 19 | float topLeftX; 20 | float topLeftY; 21 | 22 | float topRightX; 23 | float topRightY; 24 | } MetalImageCoordinate; 25 | 26 | typedef enum { 27 | MetalImageNoRotation, // 不旋转 28 | MetalImageRotateCounterclockwise,// 顺时针旋转90 29 | MetalImageRotateClockwise, // 逆时针旋转90 30 | MetalImageRotate180, // 顺时针180 31 | 32 | MetalImageFlipHorizonal, // 水平对称 33 | MetalImageFlipVertically, // 垂直对称 34 | 35 | MetalImageRotateClockwiseAndFlipVertically, // 顺时针并垂直对称 36 | MetalImageRotateClockwiseAndFlipHorizontally,// 顺时针并水平对称 37 | } MetalImageRotationMode; 38 | 39 | typedef enum { 40 | MetalImagePortrait, 41 | MetalImagePortraitUpsideDown, 42 | MetalImageLandscapeLeft, 43 | MetalImageLandscapeRight 44 | } MetalImageOrientation; 45 | 46 | typedef enum { 47 | MetalImageContentModeScaleToFill, // 拉伸图像,铺满全部渲染空间 48 | MetalImageContentModeScaleAspectFit, // 缩放图像,保持比例,可能不会填充满整个区域 49 | MetalImageContentModeScaleAspectFill // 缩放图像,保持比例,会填充整个区域 50 | } MetalImageContentMode; 51 | 52 | typedef enum { 53 | MetalImagContentBackgroundColor, 54 | MetalImagContentBackgroundFilter, 55 | } MetalImagContentBackground; 56 | 57 | NS_ASSUME_NONNULL_BEGIN 58 | 59 | @interface MetalImageTexture : NSObject 60 | @property (nonatomic, strong, readonly) id metalTexture; 61 | @property (nonatomic, assign, readonly) NSUInteger width; 62 | @property (nonatomic, assign, readonly) NSUInteger height; 63 | @property (nonatomic, assign, readonly) CGSize size; 64 | @property (nonatomic, assign, readonly) MTLPixelFormat pixelFormat; 65 | @property (nonatomic, assign, readonly) BOOL willCache; 66 | 67 | @property (nonatomic, assign) MetalImageOrientation orientation; 68 | @property (nonatomic, copy) NSString *cacheKey; 69 | 70 | - (instancetype)initWithTexture:(id)texutre orientation:(MetalImageOrientation)orientation willCache:(BOOL)willCache; 71 | - (MetalImageCoordinate)textureCoordinatesToOrientation:(MetalImageOrientation)orientation; 72 | - (MetalImageCoordinate)texturePositionToSize:(CGSize)targetSize contentMode:(MetalImageContentMode)contentMode; 73 | - (UIImage *)imageFromTexture; 74 | - (void)replaceTexture:(id)texture; 75 | 76 | + (void)textureCVPixelBufferProcess:(id)texture process:(void(^)(CVPixelBufferRef pixelBuffer))process; 77 | + (UIImage *)imageFromMTLTexture:(id)texture; 78 | @end 79 | 80 | NS_ASSUME_NONNULL_END 81 | -------------------------------------------------------------------------------- /MetalImage/Basic/MetalImageTextureCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageTextureCache.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/11. 6 | // 7 | 8 | #import 9 | #import "MetalImageTexture.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface MetalImageTextureCache : NSObject 14 | - (instancetype)initWithDevice:(id)device; 15 | 16 | /** 17 | * 从缓存池中获取一张纹理(线程安全) 18 | * 19 | * @param size 纹理大小 20 | * @param pixelFormat 纹理像素格式 21 | */ 22 | - (MetalImageTexture *)fetchTexture:(CGSize)size pixelFormat:(MTLPixelFormat)pixelFormat; 23 | 24 | /** 25 | * 缓存一张纹理(线程安全) 26 | * 27 | * @param texutre 纹理对象 28 | */ 29 | - (void)cacheTexture:(MetalImageTexture *)texutre; 30 | 31 | /** 32 | * 清楚缓存池中所有的纹理对象(线程安全) 33 | */ 34 | - (void)freeAllTexture; 35 | @end 36 | 37 | NS_ASSUME_NONNULL_END 38 | -------------------------------------------------------------------------------- /MetalImage/Basic/MetalImageTextureCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageTextureCache.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/11. 6 | // 7 | 8 | #import "MetalImageTextureCache.h" 9 | 10 | #define MetalImageTextureCacheKey(width, height, pixelFormat) [NSString stringWithFormat:@"width:%lud,height:%lud,pixelFormat:%lud",(unsigned long)width, (unsigned long)height, (unsigned long)pixelFormat] 11 | 12 | typedef struct { 13 | NSUInteger width; 14 | NSUInteger height; 15 | NSUInteger pixelFormat; 16 | } MetalImageTextureKey; 17 | 18 | static const void * kMetalImageTextureSpecificKey = &kMetalImageTextureSpecificKey; 19 | 20 | @interface MetalImageTextureCache() 21 | @property (nonatomic, strong) NSMutableDictionary *> *textureDic; 22 | @property (nonatomic, strong) NSMutableDictionary *textureDescDic; 23 | @property (nonatomic, strong) id device; 24 | 25 | @property (nonatomic, assign) MetalImageTextureKey lastKey; 26 | @property (nonatomic, copy) NSString *lastKeyStr; 27 | @property (nonatomic, strong) dispatch_queue_t cacheQueue; 28 | @end 29 | 30 | @implementation MetalImageTextureCache 31 | 32 | - (instancetype)initWithDevice:(id)device { 33 | self = [super init]; 34 | if (self) { 35 | _device = device; 36 | _textureDic = [[NSMutableDictionary alloc] init]; 37 | _textureDescDic = [[NSMutableDictionary alloc] init]; 38 | _cacheQueue = dispatch_queue_create("com.MetalImage.TextureCache", DISPATCH_QUEUE_SERIAL); 39 | dispatch_queue_set_specific(_cacheQueue, kMetalImageTextureSpecificKey, (__bridge void * _Nullable)(self), NULL); 40 | } 41 | return self; 42 | } 43 | 44 | - (MetalImageTexture *)fetchTexture:(CGSize)size pixelFormat:(MTLPixelFormat)pixelFormat { 45 | 46 | __block MetalImageTexture *texture = nil; 47 | dispatch_block_t fetchBlock = ^{ 48 | NSUInteger width = (NSUInteger)size.width; 49 | NSUInteger height = (NSUInteger)size.height; 50 | NSString *key = nil; 51 | 52 | if (width == self.lastKey.width && height == self.lastKey.height && self.lastKey.pixelFormat == pixelFormat && self.lastKeyStr) { 53 | key = self.lastKeyStr; 54 | } 55 | else { 56 | key = MetalImageTextureCacheKey(width, height, pixelFormat); 57 | MetalImageTextureKey lastedKey = {width, height, pixelFormat}; 58 | self.lastKey = lastedKey; 59 | self.lastKeyStr = key; 60 | } 61 | 62 | NSMutableArray *textureArray = [self.textureDic objectForKey:key]; 63 | if (!textureArray) { 64 | textureArray = [[NSMutableArray alloc] init]; 65 | [self.textureDic setObject:textureArray forKey:key]; 66 | } 67 | 68 | texture = [textureArray lastObject]; 69 | if (texture) { 70 | [textureArray removeLastObject]; 71 | } 72 | else { 73 | MTLTextureDescriptor *textureDesc = [self.textureDescDic objectForKey:key]; 74 | if (!textureDesc) { 75 | textureDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixelFormat 76 | width:width 77 | height:height 78 | mipmapped:NO]; 79 | textureDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite | MTLTextureUsageRenderTarget; 80 | [self.textureDescDic setObject:textureDesc forKey:key]; 81 | } 82 | 83 | id metalTexture = [self.device newTextureWithDescriptor:textureDesc]; 84 | texture = [[MetalImageTexture alloc] initWithTexture:metalTexture orientation:MetalImagePortrait willCache:YES]; 85 | texture.cacheKey = key; 86 | } 87 | }; 88 | 89 | if (dispatch_get_specific(kMetalImageTextureSpecificKey) != NULL) { 90 | fetchBlock(); 91 | } else { 92 | dispatch_sync(self.cacheQueue, fetchBlock); 93 | } 94 | 95 | return texture; 96 | } 97 | 98 | - (void)cacheTexture:(MetalImageTexture *)texutre { 99 | if (!texutre.willCache || !texutre) { 100 | return; 101 | } 102 | 103 | dispatch_async(self.cacheQueue, ^{ 104 | NSString *key = texutre.cacheKey ? texutre.cacheKey : MetalImageTextureCacheKey(texutre.width, texutre.height, texutre.pixelFormat); 105 | NSMutableArray *textureArray = [self.textureDic objectForKey:key]; 106 | if (!textureArray) { 107 | textureArray = [[NSMutableArray alloc] init]; 108 | [self.textureDic setObject:textureArray forKey:key]; 109 | } 110 | 111 | if (![textureArray containsObject:texutre]) { 112 | [textureArray insertObject:texutre atIndex:0]; 113 | } 114 | }); 115 | } 116 | 117 | - (void)freeAllTexture { 118 | dispatch_block_t deleteBlock = ^{ 119 | [self.textureDic removeAllObjects]; 120 | [self.textureDescDic removeAllObjects]; 121 | self.lastKeyStr = nil; 122 | }; 123 | 124 | if (dispatch_get_specific(kMetalImageTextureSpecificKey) != NULL) { 125 | deleteBlock(); 126 | } else { 127 | dispatch_sync(self.cacheQueue, deleteBlock); 128 | } 129 | } 130 | @end 131 | -------------------------------------------------------------------------------- /MetalImage/Category/NSBundle+MetalImageBundle.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+MetalImageBundle.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/6/28. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface NSBundle(MetalImageBundle) 13 | + (NSBundle *)metalImage_bundleWithName:(NSString *)bundleName; 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /MetalImage/Category/NSBundle+MetalImageBundle.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+MetalImageBundle.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/6/28. 6 | // 7 | 8 | #import "NSBundle+MetalImageBundle.h" 9 | 10 | @implementation NSBundle(MetalImageBundle) 11 | + (NSBundle *)metalImage_bundleWithName:(NSString *)bundleName { 12 | if (!bundleName) { 13 | return nil; 14 | } 15 | 16 | if ([bundleName containsString:@".bundle"]) { 17 | bundleName = [bundleName componentsSeparatedByString:@".bundle"].firstObject; 18 | } 19 | 20 | // 兼容cocoapods framework方式引入的时候Bundle地址 21 | NSURL *associateBundleURL = [[NSBundle mainBundle] URLForResource:bundleName withExtension:@"bundle"]; 22 | if (!associateBundleURL) { 23 | associateBundleURL = [[NSBundle mainBundle] URLForResource:@"Frameworks" withExtension:nil]; 24 | associateBundleURL = [associateBundleURL URLByAppendingPathComponent:@"MetalImage"]; 25 | associateBundleURL = [associateBundleURL URLByAppendingPathExtension:@"framework"]; 26 | NSBundle *associateBunle = [NSBundle bundleWithURL:associateBundleURL]; 27 | associateBundleURL = [associateBunle URLForResource:bundleName withExtension:@"bundle"]; 28 | } 29 | 30 | return associateBundleURL ? [NSBundle bundleWithURL:associateBundleURL] : nil; 31 | } 32 | @end 33 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Affine Transformation/MetalImageCropFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageCropFilter.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/12. 6 | // 7 | 8 | #import "MetalImageFilter.h" 9 | @interface MetalImageCropFilter : MetalImageFilter 10 | @property (nonatomic, assign) CGRect cropRegion; 11 | 12 | - (instancetype)initWithCropRegin:(CGRect)cropRegion; 13 | @end 14 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Affine Transformation/MetalImageCropFilter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageCropFilter.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/12. 6 | // 7 | 8 | #import "MetalImageCropFilter.h" 9 | 10 | @interface MetalImageCropFilter() 11 | @property (nonatomic, strong) id cropTextureCoord; 12 | @end 13 | 14 | @implementation MetalImageCropFilter 15 | 16 | - (instancetype)initWithCropRegin:(CGRect)cropRegion { 17 | self = [super initWithVertexFunction:kMetalImageDefaultVertex 18 | fragmentFunction:kMetalImageDefaultFragment 19 | library:[MetalImageDevice shared].library]; 20 | if (self) { 21 | self.cropRegion = cropRegion; 22 | } 23 | return self; 24 | } 25 | 26 | - (void)setCropRegion:(CGRect)cropRegion { 27 | if (CGRectEqualToRect(cropRegion, _cropRegion)) { 28 | return; 29 | } 30 | _cropRegion = cropRegion; 31 | _cropTextureCoord = nil; 32 | } 33 | 34 | - (void)receive:(MetalImageResource *)resource withTime:(CMTime)time { 35 | if (resource.type != MetalImageResourceTypeImage) { 36 | [self send:resource withTime:time]; 37 | return; 38 | } 39 | 40 | float width = CGRectGetWidth(_cropRegion) > resource.texture.width ? resource.texture.width : CGRectGetWidth(_cropRegion); 41 | float height = CGRectGetHeight(_cropRegion) > resource.texture.height ? resource.texture.height : CGRectGetHeight(_cropRegion); 42 | resource.renderProcess.targetSize = CGSizeMake(width, height); 43 | 44 | [super receive:resource withTime:time]; 45 | } 46 | 47 | - (void)renderToCommandEncoder:(id)renderEncoder withResource:(nonnull MetalImageResource *)resource { 48 | if (MetalImageResourceTypeImage != resource.type) { 49 | return; 50 | } 51 | 52 | #if DEBUG 53 | renderEncoder.label = NSStringFromClass([self class]); 54 | [renderEncoder pushDebugGroup:@"Crop Draw"]; 55 | #endif 56 | 57 | if (!_cropTextureCoord) { 58 | float x = _cropRegion.origin.x / resource.texture.width; 59 | float y = _cropRegion.origin.y / resource.texture.height; 60 | float width = _cropRegion.size.width / resource.texture.width; 61 | float height = _cropRegion.size.height / resource.texture.height; 62 | 63 | x = x > 1.0 ? 1.0 : x; 64 | y = y > 1.0 ? 1.0 : y; 65 | width = width > 1.0 ? 1.0 : width; 66 | height = height > 1.0 ? 1.0 : height; 67 | 68 | MetalImageCoordinate textureCoor; 69 | float leftX = x; 70 | float topY = 1.0 - y; 71 | float rightX = (x + width > 1.0) ? 1.0 : x + width; 72 | float bottomY = (1.0 - y - height < 0.0) ? 0.0 : 1.0 - y - height; 73 | 74 | textureCoor.topLeftX = leftX; 75 | textureCoor.topLeftY = topY; 76 | textureCoor.topRightX = rightX; 77 | textureCoor.topRightY = topY; 78 | textureCoor.bottomLeftX = leftX; 79 | textureCoor.bottomLeftY = bottomY; 80 | textureCoor.bottomRightX = rightX; 81 | textureCoor.bottomRightY = bottomY; 82 | 83 | _cropTextureCoord = [[MetalImageDevice shared].device newBufferWithBytes:&textureCoor length:sizeof(textureCoor) options:0]; 84 | } 85 | 86 | [renderEncoder setRenderPipelineState:self.target.pielineState]; 87 | [renderEncoder setVertexBuffer:resource.renderProcess.positionBuffer offset:0 atIndex:0]; 88 | [renderEncoder setVertexBuffer:_cropTextureCoord offset:0 atIndex:1]; 89 | [renderEncoder setFragmentTexture:resource.texture.metalTexture atIndex:0]; 90 | [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 91 | 92 | #if DEBUG 93 | [renderEncoder popDebugGroup]; 94 | #endif 95 | } 96 | @end 97 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Composite/MetalImageiOSBlurFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageiOSBlurFilter.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/1/4. 6 | // 7 | 8 | #import "MetalImageFilter.h" 9 | #import "MetalImageLuminanceFilter.h" 10 | #import "MetalImageSaturationFilter.h" 11 | #import "MetalImageGaussianBlurFilter.h" 12 | 13 | @interface MetalImageiOSBlurFilter : NSObject 14 | @property (nonatomic, assign) float blurRadiusInPixels;// 高斯模糊半径,默认8.0,当为0.0时无模糊效果 15 | @property (nonatomic, assign) float texelSpacingMultiplier;// 采样步长,默认1.0,当为0.0时无模糊效果 16 | @property (nonatomic, assign) float saturation;// 饱和度,默认0.0不调整,建议[-1.0, 1.0] 17 | @property (nonatomic, assign) float luminance;// 亮度,默认0.0不调整,建议[-1.0, 1.0] 18 | 19 | - (void)renderToResource:(MetalImageResource *)resource; 20 | @end 21 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Composite/MetalImageiOSBlurFilter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageiOSBlurFilter.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/1/4. 6 | // 7 | 8 | #import "MetalImageiOSBlurFilter.h" 9 | 10 | @interface MetalImageiOSBlurFilter() 11 | @property (nonatomic, strong) MetalImageLuminanceFilter *luminanceFilter; 12 | @property (nonatomic, strong) MetalImageSaturationFilter *saturationFilter; 13 | @property (nonatomic, strong) MetalImageGaussianBlurFilter *gaussinBlurFiter; 14 | @property (nonatomic, strong) MetalImageSource *source; 15 | @end 16 | 17 | @implementation MetalImageiOSBlurFilter 18 | 19 | - (instancetype)init { 20 | if (self = [super init]) { 21 | _luminanceFilter = [[MetalImageLuminanceFilter alloc] init]; 22 | _saturationFilter = [[MetalImageSaturationFilter alloc] init]; 23 | _gaussinBlurFiter = [[MetalImageGaussianBlurFilter alloc] init]; 24 | _source = [[MetalImageSource alloc] init]; 25 | 26 | [_saturationFilter addTarget:_gaussinBlurFiter]; 27 | [_gaussinBlurFiter addTarget:_luminanceFilter]; 28 | 29 | self.blurRadiusInPixels = 8.0; 30 | self.texelSpacingMultiplier = 1.0; 31 | self.saturation = 1.0; 32 | self.luminance = 0.0; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)receive:(MetalImageResource *)resource withTime:(CMTime)time { 38 | if (resource.type != MetalImageResourceTypeImage) { 39 | [self send:resource withTime:time]; 40 | return; 41 | } 42 | 43 | [self.saturationFilter receive:resource withTime:time]; 44 | } 45 | 46 | - (void)renderToResource:(MetalImageResource *)resource { 47 | if (MetalImageResourceTypeImage != resource.type) { 48 | return; 49 | } 50 | 51 | [resource.renderProcess commitRender]; 52 | id commandBuffer1 = [[MetalImageDevice shared].commandQueue commandBuffer]; 53 | [commandBuffer1 enqueue]; 54 | [self.saturationFilter encodeToCommandBuffer:commandBuffer1 withResource:resource]; 55 | [commandBuffer1 commit]; 56 | [commandBuffer1 waitUntilCompleted]; 57 | 58 | id commandBuffer2 = [[MetalImageDevice shared].commandQueue commandBuffer]; 59 | [commandBuffer2 enqueue]; 60 | [self.gaussinBlurFiter encodeToCommandBuffer:commandBuffer2 withResource:resource]; 61 | [commandBuffer2 commit]; 62 | [commandBuffer2 waitUntilCompleted]; 63 | 64 | id commandBuffer3 = [[MetalImageDevice shared].commandQueue commandBuffer]; 65 | [commandBuffer3 enqueue]; 66 | [self.luminanceFilter encodeToCommandBuffer:commandBuffer3 withResource:resource]; 67 | [commandBuffer3 commit]; 68 | [commandBuffer3 waitUntilCompleted]; 69 | } 70 | 71 | - (BOOL)supportProcessRenderCommandEncoderOnly { 72 | return NO; 73 | } 74 | 75 | #pragma mark - 属性设置 76 | -(void)setBlurRadiusInPixels:(float)blurRadiusInPixels { 77 | _blurRadiusInPixels = blurRadiusInPixels; 78 | self.gaussinBlurFiter.blurRadiusInPixels = blurRadiusInPixels; 79 | } 80 | 81 | - (void)setSaturation:(float)saturation { 82 | _saturation = saturation; 83 | self.saturationFilter.saturation = saturation; 84 | } 85 | 86 | - (void)setLuminance:(float)luminance { 87 | _luminance = luminance; 88 | self.luminanceFilter.rangeReductionFactor = luminance; 89 | } 90 | 91 | - (void)setTexelSpacingMultiplier:(float)texelSpacingMultiplier { 92 | _texelSpacingMultiplier = texelSpacingMultiplier; 93 | self.gaussinBlurFiter.texelSpacingMultiplier = texelSpacingMultiplier; 94 | } 95 | 96 | #pragma mark - 链路协议 97 | - (void)addTarget:(id)target { 98 | [self.source addTarget:target]; 99 | [self.luminanceFilter addTarget:target]; 100 | } 101 | 102 | - (void)removeTarget:(id)target { 103 | [self.source removeTarget:target]; 104 | [self.luminanceFilter removeTarget:target]; 105 | } 106 | 107 | - (void)removeAllTarget { 108 | [self.source removeAllTarget]; 109 | [self.luminanceFilter removeAllTarget]; 110 | } 111 | 112 | - (void)send:(MetalImageResource *)resource withTime:(CMTime)time { 113 | [self.saturationFilter send:resource withTime:time]; 114 | } 115 | @end 116 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Convolution/MetalImageConvolutionFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageConvolutionFilter.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/4/1. 6 | // 7 | 8 | #import "MetalImageFilter.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface MetalImageConvolutionFilter : MetalImageFilter 13 | @property (readonly, nonatomic) NSUInteger kernelHeight; 14 | @property (readonly, nonatomic) NSUInteger kernelWidth; 15 | 16 | + (instancetype _Nullable)filterWithKernelWidth:(NSUInteger)kernelWidth 17 | kernelHeight:(NSUInteger)kernelHeight 18 | weights:(const float*)kernelWeights; 19 | 20 | + (NSString *)vertexShaderWithKernelWidth:(NSUInteger)kernelWidth 21 | kernelHeight:(NSUInteger)kernelHeight; 22 | 23 | + (NSString *)fragmentShaderWithKernelWidth:(NSUInteger)kernelWidth 24 | kernelHeight:(NSUInteger)kernelHeight 25 | weights:(const float *)kernelWeights; 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Convolution/MetalImageGaussianBlurFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageGaussianBlurFilter.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/1/4. 6 | // 7 | 8 | #import "MetalImageFilter.h" 9 | 10 | @interface MetalImageGaussianBlurFilter : MetalImageFilter 11 | @property (nonatomic, assign) float blurRadiusInPixels;// 默认4.0 12 | @property (nonatomic, assign) float texelSpacingMultiplier;// 默认2.0 13 | 14 | + (NSString *)vertexShaderForBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma; 15 | + (NSString *)fragmentShaderForBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma; 16 | @end 17 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Convolution/MetalImageSharpenFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageSharpenFilter.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/1/4. 6 | // 7 | 8 | #import "MetalImageFilter.h" 9 | 10 | @interface MetalImageSharpenFilter : MetalImageFilter 11 | @property (assign, nonatomic) float sharpness;// 建议[-4.0, 4.0], 默认0.0 12 | @end 13 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Convolution/MetalImageSharpenFilter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageSharpenFilter.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/1/4. 6 | // 7 | 8 | #import "MetalImageSharpenFilter.h" 9 | typedef struct MetalImageSharpenFilterArg { 10 | float imageWidthFactor; 11 | float imageHeightFactor; 12 | float sharpenss; 13 | } MetalImageSharpenFilterArg; 14 | 15 | @interface MetalImageSharpenFilter() 16 | @property (nonatomic, assign) MetalImageSharpenFilterArg sharpenArg; 17 | @end 18 | 19 | @implementation MetalImageSharpenFilter 20 | - (instancetype)init { 21 | self = [super initWithVertexFunction:@"sharpenVertex" 22 | fragmentFunction:@"sharpenFragment" 23 | library:[MetalImageDevice shared].library]; 24 | if (self) { 25 | _sharpenArg.sharpenss = 0.0; 26 | _sharpenArg.imageHeightFactor = 0.0;// 单位像素高 27 | _sharpenArg.imageWidthFactor = 0.0;// 单位像素宽 28 | } 29 | return self; 30 | } 31 | 32 | - (void)setSharpness:(float)sharpness { 33 | _sharpness = sharpness; 34 | _sharpenArg.sharpenss = sharpness; 35 | } 36 | 37 | - (void)renderToCommandEncoder:(id)renderEncoder withResource:(nonnull MetalImageResource *)resource { 38 | if (MetalImageResourceTypeImage != resource.type) { 39 | return; 40 | } 41 | 42 | // 目标大小变了 43 | if ((1.0 / resource.renderProcess.targetSize.width != _sharpenArg.imageWidthFactor) || (1.0 / resource.renderProcess.targetSize.height != _sharpenArg.imageHeightFactor)) { 44 | _sharpenArg.imageWidthFactor = 1.0 / resource.renderProcess.targetSize.width; 45 | _sharpenArg.imageHeightFactor = 1.0 / resource.renderProcess.targetSize.height; 46 | } 47 | 48 | #if DEBUG 49 | renderEncoder.label = NSStringFromClass([self class]); 50 | [renderEncoder pushDebugGroup:@"Sharpen Draw"]; 51 | #endif 52 | 53 | [renderEncoder setRenderPipelineState:self.target.pielineState]; 54 | [renderEncoder setVertexBuffer:resource.renderProcess.positionBuffer offset:0 atIndex:0]; 55 | [renderEncoder setVertexBuffer:resource.renderProcess.textureCoorBuffer offset:0 atIndex:1]; 56 | [renderEncoder setVertexBytes:&_sharpenArg length:sizeof(_sharpenArg) atIndex:2]; 57 | [renderEncoder setFragmentTexture:resource.texture.metalTexture atIndex:0]; 58 | [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 59 | 60 | #if DEBUG 61 | [renderEncoder popDebugGroup]; 62 | #endif 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Pixel/MetalImageContrastFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageContrastFilter.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/3/24. 6 | // 7 | 8 | #import "MetalImageFilter.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface MetalImageContrastFilter : MetalImageFilter 13 | @property (assign, nonatomic) float contrast;// 建议[0.0, 2.0], 默认1.0 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Pixel/MetalImageContrastFilter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageContrastFilter.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/3/24. 6 | // 7 | 8 | #import "MetalImageContrastFilter.h" 9 | 10 | @implementation MetalImageContrastFilter 11 | - (instancetype)init { 12 | self = [super initWithVertexFunction:kMetalImageDefaultVertex 13 | fragmentFunction:@"contrastFragment" 14 | library:[MetalImageDevice shared].library]; 15 | if (self) { 16 | _contrast = 1.0; 17 | } 18 | return self; 19 | } 20 | 21 | - (void)renderToCommandEncoder:(id)renderEncoder withResource:(MetalImageResource *)resource { 22 | if (resource.type != MetalImageResourceTypeImage) { 23 | return; 24 | } 25 | 26 | #if DEBUG 27 | renderEncoder.label = NSStringFromClass([self class]); 28 | [renderEncoder pushDebugGroup:@"Contrast Draw"]; 29 | #endif 30 | 31 | [renderEncoder setRenderPipelineState:self.target.pielineState]; 32 | [renderEncoder setVertexBuffer:resource.renderProcess.positionBuffer offset:0 atIndex:0]; 33 | [renderEncoder setVertexBuffer:resource.renderProcess.textureCoorBuffer offset:0 atIndex:1]; 34 | [renderEncoder setFragmentBytes:&_contrast length:sizeof(_contrast) atIndex:2]; 35 | [renderEncoder setFragmentTexture:resource.texture.metalTexture atIndex:0]; 36 | [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 37 | 38 | #if DEBUG 39 | [renderEncoder popDebugGroup]; 40 | #endif 41 | } 42 | @end 43 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Pixel/MetalImageHueFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageHueFilter.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/3/25. 6 | // 7 | 8 | #import "MetalImageFilter.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface MetalImageHueFilter : MetalImageFilter 13 | @property (assign, nonatomic) float hue;// 建议[-1.0, 1.0], 默认0.0 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Pixel/MetalImageHueFilter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageHueFilter.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/3/25. 6 | // 7 | 8 | #import "MetalImageHueFilter.h" 9 | 10 | @implementation MetalImageHueFilter 11 | - (instancetype)init { 12 | self = [super initWithVertexFunction:kMetalImageDefaultVertex 13 | fragmentFunction:@"hueFragment" 14 | library:[MetalImageDevice shared].library]; 15 | if (self) { 16 | _hue = 0.0; 17 | } 18 | return self; 19 | } 20 | 21 | - (void)renderToCommandEncoder:(id)renderEncoder withResource:(MetalImageResource *)resource { 22 | if (MetalImageResourceTypeImage != resource.type) { 23 | return; 24 | } 25 | 26 | #if DEBUG 27 | renderEncoder.label = NSStringFromClass([self class]); 28 | [renderEncoder pushDebugGroup:@"Hue Draw"]; 29 | #endif 30 | 31 | [renderEncoder setRenderPipelineState:self.target.pielineState]; 32 | [renderEncoder setVertexBuffer:resource.renderProcess.positionBuffer offset:0 atIndex:0]; 33 | [renderEncoder setVertexBuffer:resource.renderProcess.textureCoorBuffer offset:0 atIndex:1]; 34 | [renderEncoder setFragmentBytes:&_hue length:sizeof(_hue) atIndex:2]; 35 | [renderEncoder setFragmentTexture:resource.texture.metalTexture atIndex:0]; 36 | [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 37 | 38 | #if DEBUG 39 | [renderEncoder popDebugGroup]; 40 | #endif 41 | } 42 | @end 43 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Pixel/MetalImageLookUpTableFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageLookUpTableFilter.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/7/4. 6 | // 7 | 8 | #import "MetalImageFilter.h" 9 | #import "MetalImageTexture.h" 10 | 11 | typedef NS_ENUM(NSUInteger, MetalImageLUTFilterType) { 12 | MetalImageLUTFilterType8_8, // 晶格数为8*8,使用左上角坐标系 13 | MetalImageLUTFilterType4_4 // 晶格数为4*4,使用左上角坐标系 14 | }; 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | @interface MetalImageLookUpTableFilter : MetalImageFilter 19 | @property (nonatomic, strong, readonly) id lookUpTableTexture; 20 | @property (nonatomic, assign, readonly) MetalImageLUTFilterType type; 21 | @property (nonatomic, assign) float intensity; 22 | 23 | - (instancetype)initWithLutTexture:(id)lutTexture type:(MetalImageLUTFilterType)type; 24 | - (void)replaceLutTexture:(id)lutTexture type:(MetalImageLUTFilterType)type; 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Pixel/MetalImageLookUpTableFilter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageLookUpTableFilter.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/7/4. 6 | // 7 | 8 | #import "MetalImageLookUpTableFilter.h" 9 | 10 | typedef struct { 11 | unsigned int maxColorValue; // lut每个分量的有多少种颜色 12 | unsigned int latticeCount; // 每排晶格数量 13 | unsigned int width; // lut宽度 14 | unsigned int height; // lut高度 15 | } MetalImageLutInfo; 16 | 17 | @interface MetalImageLookUpTableFilter() 18 | @property (nonatomic, strong) id lookUpTableTexture; 19 | @property (nonatomic, assign) MetalImageLutInfo lutInfo; 20 | @property (nonatomic, assign) MetalImageLUTFilterType type; 21 | @end 22 | 23 | @implementation MetalImageLookUpTableFilter 24 | 25 | - (instancetype)initWithLutTexture:(id)lutTexture type:(MetalImageLUTFilterType)type { 26 | self = [super initWithVertexFunction:kMetalImageDefaultVertex 27 | fragmentFunction:@"lookUpTableFragment" 28 | library:[MetalImageDevice shared].library]; 29 | if (self) { 30 | [self replaceLutTexture:lutTexture type:type]; 31 | _intensity = 0.0; 32 | } 33 | return self; 34 | } 35 | 36 | - (void)replaceLutTexture:(id)lutTexture type:(MetalImageLUTFilterType)type { 37 | _lookUpTableTexture = lutTexture; 38 | _type = type; 39 | 40 | unsigned int latticeCount, maxColorValue; 41 | switch (type) { 42 | case MetalImageLUTFilterType8_8: 43 | latticeCount = 8; 44 | break; 45 | case MetalImageLUTFilterType4_4: 46 | latticeCount = 4; 47 | break; 48 | default: 49 | latticeCount = 8; 50 | break; 51 | } 52 | 53 | maxColorValue = (unsigned int)lutTexture.width / latticeCount - 1; 54 | _lutInfo = (MetalImageLutInfo){ 55 | maxColorValue, 56 | latticeCount, 57 | (unsigned int)lutTexture.width, 58 | (unsigned int)lutTexture.height 59 | }; 60 | } 61 | 62 | - (void)renderToCommandEncoder:(id)renderEncoder withResource:(nonnull MetalImageResource *)resource { 63 | if (MetalImageResourceTypeImage != resource.type) { 64 | return; 65 | } 66 | 67 | #if DEBUG 68 | renderEncoder.label = NSStringFromClass([self class]); 69 | [renderEncoder pushDebugGroup:@"LUT Draw"]; 70 | #endif 71 | 72 | [renderEncoder setRenderPipelineState:self.target.pielineState]; 73 | [renderEncoder setVertexBuffer:resource.renderProcess.positionBuffer offset:0 atIndex:0]; 74 | [renderEncoder setVertexBuffer:resource.renderProcess.textureCoorBuffer offset:0 atIndex:1]; 75 | [renderEncoder setFragmentBytes:&_intensity length:sizeof(_intensity) atIndex:2]; 76 | [renderEncoder setFragmentBytes:&_lutInfo length:sizeof(_lutInfo) atIndex:3]; 77 | [renderEncoder setFragmentTexture:resource.texture.metalTexture atIndex:0]; 78 | [renderEncoder setFragmentTexture:_lookUpTableTexture atIndex:1]; 79 | [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 80 | 81 | #if DEBUG 82 | [renderEncoder popDebugGroup]; 83 | #endif 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Pixel/MetalImageLuminanceFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageLuminanceFilter.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/12. 6 | // 7 | 8 | #import "MetalImageFilter.h" 9 | 10 | @interface MetalImageLuminanceFilter : MetalImageFilter 11 | @property (assign, nonatomic) float rangeReductionFactor;// 建议[-1.0, 1.0],默认0.0 12 | @end 13 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Pixel/MetalImageLuminanceFilter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageLuminanceFilter.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/12. 6 | // 7 | 8 | #import "MetalImageLuminanceFilter.h" 9 | 10 | @implementation MetalImageLuminanceFilter 11 | - (instancetype)init { 12 | self = [super initWithVertexFunction:kMetalImageDefaultVertex 13 | fragmentFunction:@"luminanceRangeFragment" 14 | library:[MetalImageDevice shared].library]; 15 | if (self) { 16 | _rangeReductionFactor = 0.0; 17 | } 18 | return self; 19 | } 20 | 21 | - (void)renderToCommandEncoder:(id)renderEncoder withResource:(nonnull MetalImageResource *)resource { 22 | if (MetalImageResourceTypeImage != resource.type) { 23 | return; 24 | } 25 | 26 | #if DEBUG 27 | renderEncoder.label = NSStringFromClass([self class]); 28 | [renderEncoder pushDebugGroup:@"Luminance Range Draw"]; 29 | #endif 30 | 31 | [renderEncoder setRenderPipelineState:self.target.pielineState]; 32 | [renderEncoder setVertexBuffer:resource.renderProcess.positionBuffer offset:0 atIndex:0]; 33 | [renderEncoder setVertexBuffer:resource.renderProcess.textureCoorBuffer offset:0 atIndex:1]; 34 | [renderEncoder setFragmentBytes:&_rangeReductionFactor length:sizeof(_rangeReductionFactor) atIndex:2]; 35 | [renderEncoder setFragmentTexture:resource.texture.metalTexture atIndex:0]; 36 | [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 37 | 38 | #if DEBUG 39 | [renderEncoder popDebugGroup]; 40 | #endif 41 | } 42 | @end 43 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Pixel/MetalImageSaturationFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageSaturationFilter.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/12. 6 | // 7 | 8 | #import "MetalImageFilter.h" 9 | 10 | @interface MetalImageSaturationFilter : MetalImageFilter 11 | @property (assign, nonatomic) float saturation; // 建议[0.0, 2.0], 默认1.0 12 | @end 13 | -------------------------------------------------------------------------------- /MetalImage/ExtensionFilter/Pixel/MetalImageSaturationFilter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageSaturationFilter.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/12. 6 | // 7 | 8 | #import "MetalImageSaturationFilter.h" 9 | @implementation MetalImageSaturationFilter 10 | - (instancetype)init { 11 | self = [super initWithVertexFunction:kMetalImageDefaultVertex 12 | fragmentFunction:@"saturationFragment" 13 | library:[MetalImageDevice shared].library]; 14 | if (self) { 15 | _saturation = 1.0; 16 | } 17 | return self; 18 | } 19 | 20 | - (void)renderToCommandEncoder:(id)renderEncoder withResource:(nonnull MetalImageResource *)resource { 21 | if (MetalImageResourceTypeImage != resource.type) { 22 | return; 23 | } 24 | 25 | #if DEBUG 26 | renderEncoder.label = NSStringFromClass([self class]); 27 | [renderEncoder pushDebugGroup:@"Saturation Draw"]; 28 | #endif 29 | 30 | [renderEncoder setRenderPipelineState:self.target.pielineState]; 31 | [renderEncoder setVertexBuffer:resource.renderProcess.positionBuffer offset:0 atIndex:0]; 32 | [renderEncoder setVertexBuffer:resource.renderProcess.textureCoorBuffer offset:0 atIndex:1]; 33 | [renderEncoder setFragmentBytes:&_saturation length:sizeof(_saturation) atIndex:2]; 34 | [renderEncoder setFragmentTexture:resource.texture.metalTexture atIndex:0]; 35 | [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 36 | 37 | #if DEBUG 38 | [renderEncoder popDebugGroup]; 39 | #endif 40 | } 41 | @end 42 | -------------------------------------------------------------------------------- /MetalImage/Filter/MetalImageFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageFilter.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/11/29. 6 | // 7 | 8 | #import 9 | #import "MetalImageProtocol.h" 10 | #import "MetalImageSource.h" 11 | #import "MetalImageTarget.h" 12 | #import "MetalImageResource.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | @class MetalImageFilter; 16 | typedef void(^MetalImageFilterBlock)(BOOL beforeFilter, MetalImageResource *resource, MetalImageFilter *filter); 17 | 18 | @interface MetalImageFilter : NSObject 19 | @property (nonatomic, readonly) MetalImageSource *source; 20 | @property (nonatomic, readonly) MetalImageTarget *target; 21 | @property (nonatomic, copy, nullable) MetalImageFilterBlock filterChainProcessHook; 22 | 23 | - (instancetype)initWithVertexFunction:(NSString *)vertexFunction 24 | fragmentFunction:(NSString *)fragmentFunction 25 | library:(id)library; 26 | 27 | /** 28 | * CommandBuffer层级的渲染 29 | * 30 | * @param commandBuffer 外部生成的commandBuffer 31 | * @param resource 待渲染的资源 32 | * 33 | * @dicussion 34 | * 内部包含一个或多个RenderCommandEncoder并渲染后交换纹理保证输入输出有序 35 | */ 36 | - (void)encodeToCommandBuffer:(id)commandBuffer withResource:(MetalImageResource *)resource; 37 | 38 | /** 39 | * CommandEncoder层级的渲染 40 | * 41 | * @param renderEncoder 外部生成的renderCommandEncoder 42 | * @param resource 待渲染的资源 43 | * 44 | * @discussion 45 | * 内部包含一个或多个Draw-Call渲染不会进行结果纹理交换 46 | */ 47 | - (void)renderToCommandEncoder:(id)renderEncoder withResource:(MetalImageResource *)resource; 48 | @end 49 | 50 | NS_ASSUME_NONNULL_END 51 | -------------------------------------------------------------------------------- /MetalImage/Filter/MetalImageFilter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageFilter.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/11/29. 6 | // 7 | 8 | #import "MetalImageFilter.h" 9 | 10 | @interface MetalImageFilter() 11 | @property (nonatomic, strong) MetalImageSource *source; 12 | @property (nonatomic, strong) MetalImageTarget *target; 13 | @end 14 | 15 | @implementation MetalImageFilter 16 | - (instancetype)init { 17 | return [self initWithVertexFunction:kMetalImageDefaultVertex 18 | fragmentFunction:kMetalImageDefaultFragment 19 | library:[MetalImageDevice shared].library]; 20 | } 21 | 22 | - (instancetype)initWithVertexFunction:(NSString *)vertexFunction 23 | fragmentFunction:(NSString *)fragmentFunction 24 | library:(id)library { 25 | if (self = [super init]) { 26 | _target = [[MetalImageTarget alloc] initWithVertexFunction:vertexFunction 27 | fragmentFunction:fragmentFunction 28 | library:library 29 | enableBlend:NO]; 30 | _source = [[MetalImageSource alloc] init]; 31 | } 32 | return self; 33 | } 34 | 35 | #pragma mark - Target Protocol 36 | /** 37 | * 内部渲染处理不应该切换线程,需要并发渲染使用MTLParallelRenderCommandEncoder实现 38 | * 默认实现draw-call后交换纹理指针传给下一级 39 | */ 40 | - (void)receive:(MetalImageResource *)resource withTime:(CMTime)time { 41 | if (resource.type != MetalImageResourceTypeImage) { 42 | [self send:resource withTime:time]; 43 | return; 44 | } 45 | 46 | if (self.filterChainProcessHook) { 47 | self.filterChainProcessHook(YES, resource, self); 48 | } 49 | 50 | if ([self supportProcessRenderCommandEncoderOnly]) { 51 | __weak typeof(self) weakSelf = self; 52 | __weak typeof(resource) weakResource = resource; 53 | [resource.renderProcess addRenderProcess:^(id renderEncoder) { 54 | [weakSelf renderToCommandEncoder:renderEncoder withResource:weakResource]; 55 | }]; 56 | } else { 57 | // 先把之前的提交了 58 | [resource.renderProcess commitRender]; 59 | [self renderToResource:resource]; 60 | } 61 | 62 | if (self.filterChainProcessHook) { 63 | self.filterChainProcessHook(NO, resource, self); 64 | } 65 | 66 | !self.source.haveTarget ? [resource.renderProcess commitRender] : [self send:resource withTime:time]; 67 | } 68 | 69 | #pragma mark - Render Process 70 | - (void)encodeToCommandBuffer:(id)commandBuffer withResource:(MetalImageResource *)resource { 71 | if (MetalImageResourceTypeImage != resource.type) { 72 | return; 73 | } 74 | 75 | CGSize targetSize = CGSizeEqualToSize(self.target.size, CGSizeZero) ? resource.texture.size : self.target.size; 76 | MetalImageTexture *targetTexture = [[MetalImageDevice shared].textureCache fetchTexture:targetSize 77 | pixelFormat:resource.texture.metalTexture.pixelFormat]; 78 | targetTexture.orientation = resource.texture.orientation; 79 | self.target.renderPassDecriptor.colorAttachments[0].texture = targetTexture.metalTexture; 80 | 81 | // 实现一个renderEncoder流程(可能有多个Draw-Call) 82 | @autoreleasepool { 83 | id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:self.target.renderPassDecriptor]; 84 | [self renderToCommandEncoder:renderEncoder withResource:resource]; 85 | [renderEncoder endEncoding]; 86 | [resource.renderProcess swapTexture:targetTexture]; 87 | } 88 | } 89 | 90 | - (void)renderToCommandEncoder:(id)renderEncoder withResource:(MetalImageResource *)resource { 91 | if (MetalImageResourceTypeImage != resource.type) { 92 | return; 93 | } 94 | 95 | #if DEBUG 96 | renderEncoder.label = NSStringFromClass([self class]); 97 | [renderEncoder pushDebugGroup:@"Passthrough Draw"]; 98 | #endif 99 | 100 | [renderEncoder setRenderPipelineState:self.target.pielineState]; 101 | [renderEncoder setVertexBuffer:resource.renderProcess.positionBuffer offset:0 atIndex:0]; 102 | [renderEncoder setVertexBuffer:resource.renderProcess.textureCoorBuffer offset:0 atIndex:1]; 103 | [renderEncoder setFragmentTexture:resource.texture.metalTexture atIndex:0]; 104 | [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 105 | 106 | #if DEBUG 107 | [renderEncoder popDebugGroup]; 108 | #endif 109 | } 110 | 111 | #pragma mark - Render Protocol 112 | -(void)renderToResource:(MetalImageResource *)resource { 113 | if (MetalImageResourceTypeImage != resource.type) { 114 | return; 115 | } 116 | 117 | id commandBuffer = [[MetalImageDevice shared].commandQueue commandBuffer]; 118 | [commandBuffer enqueue]; 119 | [self encodeToCommandBuffer:commandBuffer withResource:resource]; 120 | [commandBuffer commit]; 121 | [commandBuffer waitUntilCompleted]; 122 | } 123 | 124 | - (BOOL)supportProcessRenderCommandEncoderOnly { 125 | return YES; 126 | } 127 | 128 | #pragma mark - Source Protocol 129 | - (void)send:(MetalImageResource *)resource withTime:(CMTime)time { 130 | [self.source send:resource withTime:time]; 131 | } 132 | 133 | - (void)addTarget:(id)target { 134 | [self.source addTarget:target]; 135 | } 136 | 137 | - (void)removeTarget:(id)target { 138 | [self.source removeTarget:target]; 139 | } 140 | 141 | - (void)removeAllTarget { 142 | [self.source removeAllTarget]; 143 | } 144 | @end 145 | -------------------------------------------------------------------------------- /MetalImage/Library/Basic.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Passthrough.metal 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/11/30. 6 | // 7 | 8 | #include 9 | using namespace metal; 10 | 11 | struct SingleInputVertexIO { 12 | float4 position [[position]]; 13 | float2 textureCoordinate [[user(texturecoord)]]; 14 | }; 15 | 16 | vertex SingleInputVertexIO oneInputVertex(device packed_float2 *position [[buffer(0)]], 17 | device packed_float2 *texturecoord [[buffer(1)]], 18 | uint vid [[vertex_id]]) { 19 | SingleInputVertexIO outputVertices; 20 | outputVertices.position = float4(position[vid], 0, 1.0); 21 | outputVertices.textureCoordinate = texturecoord[vid]; 22 | return outputVertices; 23 | } 24 | 25 | fragment half4 passthroughFragment(SingleInputVertexIO fragmentInput [[stage_in]], 26 | texture2d inputTexture [[texture(0)]]) { 27 | constexpr sampler quadSampler; 28 | half4 color = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate); 29 | return color; 30 | } 31 | -------------------------------------------------------------------------------- /MetalImage/Library/ConvolutionShader.metal: -------------------------------------------------------------------------------- 1 | // 2 | // ConvolutionShader.metal 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/3/27. 6 | // 7 | 8 | #include 9 | using namespace metal; 10 | 11 | #pragma mark - 锐化 12 | /** 13 | * 使用👇的边缘锐化卷积模板 14 | * 0, -1, 0, 15 | * -1, 5*n, -1, 16 | * 0, -1, 0 17 | */ 18 | struct SharpenVertexOutput { 19 | float4 position [[position]]; 20 | float2 textureCoordinate [[user(texturecoord)]]; 21 | 22 | float2 leftTextureCoordinate; 23 | float2 rightTextureCoordinate; 24 | float2 topTextureCoordinate; 25 | float2 bottomTextureCoordinate; 26 | 27 | float centerMultiplier; 28 | float edgeMultiplier; 29 | }; 30 | 31 | struct SharpenVertexParameter { 32 | float imageWidthFactor; 33 | float imageHeightFactor; 34 | float sharpness; 35 | }; 36 | 37 | vertex SharpenVertexOutput sharpenVertex(device packed_float2 *position [[buffer(0)]], 38 | device packed_float2 *texturecoord [[buffer(1)]], 39 | constant SharpenVertexParameter &sharpenPara [[buffer(2)]], 40 | uint vid [[vertex_id]]) { 41 | float2 texture2dCoord = texturecoord[vid]; 42 | 43 | SharpenVertexOutput sharpenIO; 44 | sharpenIO.position = float4(position[vid], 0, 1.0); 45 | 46 | float2 widthStep = float2(sharpenPara.imageWidthFactor, 0.0); 47 | float2 heightStep = float2(0.0, sharpenPara.imageHeightFactor); 48 | 49 | sharpenIO.textureCoordinate = texture2dCoord; 50 | sharpenIO.leftTextureCoordinate = texture2dCoord - widthStep; 51 | sharpenIO.rightTextureCoordinate = texture2dCoord + widthStep; 52 | sharpenIO.topTextureCoordinate = texture2dCoord + heightStep; 53 | sharpenIO.bottomTextureCoordinate = texture2dCoord - heightStep; 54 | 55 | sharpenIO.centerMultiplier = 1.0 + 4.0 * sharpenPara.sharpness; 56 | sharpenIO.edgeMultiplier = sharpenPara.sharpness; 57 | 58 | return sharpenIO; 59 | } 60 | 61 | fragment half4 sharpenFragment(SharpenVertexOutput fragmentInput [[stage_in]], 62 | texture2d inputTexture [[texture(0)]]) { 63 | constexpr sampler quadSampler; 64 | 65 | half3 textureColor = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate).rgb * fragmentInput.centerMultiplier; 66 | half3 leftTextureColor = inputTexture.sample(quadSampler, fragmentInput.leftTextureCoordinate).rgb * fragmentInput.edgeMultiplier; 67 | half3 rightTextureColor = inputTexture.sample(quadSampler, fragmentInput.rightTextureCoordinate).rgb * fragmentInput.edgeMultiplier; 68 | half3 topTextureColor = inputTexture.sample(quadSampler, fragmentInput.topTextureCoordinate).rgb * fragmentInput.edgeMultiplier; 69 | half3 bottomTextureColor = inputTexture.sample(quadSampler, fragmentInput.bottomTextureCoordinate).rgb * fragmentInput.edgeMultiplier; 70 | 71 | half4 color = half4((textureColor - (leftTextureColor + rightTextureColor + topTextureColor + bottomTextureColor)), inputTexture.sample(quadSampler, fragmentInput.bottomTextureCoordinate).a); 72 | 73 | return color; 74 | } 75 | -------------------------------------------------------------------------------- /MetalImage/Library/PixelShader.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Extension.metal 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/1/4. 6 | // 7 | 8 | #include 9 | using namespace metal; 10 | 11 | struct SingleInputVertexIO { 12 | float4 position [[position]]; 13 | float2 textureCoordinate [[user(texturecoord)]]; 14 | }; 15 | 16 | constant half3 luminanceWeighting = half3(0.2125, 0.7154, 0.0721); 17 | 18 | #pragma mark - 饱和度 19 | fragment half4 saturationFragment(SingleInputVertexIO fragmentInput [[stage_in]], 20 | constant float &saturation [[buffer(2)]], 21 | texture2d inputTexture [[texture(0)]]) { 22 | constexpr sampler quadSampler; 23 | 24 | half4 textureColor = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate); 25 | half luminance = dot(textureColor.rgb, luminanceWeighting); 26 | half3 greyScaleColor = half3(luminance); 27 | half4 color = half4(mix(greyScaleColor, textureColor.rgb, saturation), textureColor.w); 28 | 29 | return color; 30 | } 31 | 32 | #pragma mark - 亮度 33 | fragment half4 luminanceRangeFragment(SingleInputVertexIO fragmentInput [[stage_in]], 34 | constant float &rangeReduction [[buffer(2)]], 35 | texture2d inputTexture [[texture(0)]]) { 36 | constexpr sampler quadSampler; 37 | 38 | half4 textureColor = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate); 39 | half luminance = dot(textureColor.rgb, luminanceWeighting); 40 | half luminanceRatio = ((0.5 - luminance) * rangeReduction); 41 | half4 color = half4((textureColor.rgb) + (luminanceRatio), textureColor.w); 42 | 43 | return color; 44 | } 45 | 46 | #pragma mark - 对比度 47 | fragment half4 contrastFragment(SingleInputVertexIO fragmentInput [[stage_in]], 48 | constant float &contrast [[buffer(2)]], 49 | texture2d inputTexture [[texture(0)]]) { 50 | constexpr sampler quadSampler; 51 | half4 textureColor = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate); 52 | half4 color = half4(((textureColor.rgb - half3(0.5)) * contrast + half3(0.5)), textureColor.w); 53 | return color; 54 | } 55 | 56 | #pragma mark - 色调 57 | constant float4 kRGBToYPrime = float4(0.299, 0.587, 0.114, 0.0); 58 | constant float4 kRGBToI = float4(0.595716, -0.274453, -0.321263, 0.0); 59 | constant float4 kRGBToQ = float4(0.211456, -0.522591, 0.31135, 0.0); 60 | 61 | constant float4 kYIQToR = float4(1.0, 0.9563, 0.6210, 0.0); 62 | constant float4 kYIQToG = float4(1.0, -0.2721, -0.6474, 0.0); 63 | constant float4 kYIQToB = float4(1.0, -1.1070, 1.7046, 0.0); 64 | 65 | fragment float4 hueFragment(SingleInputVertexIO fragmentInput [[stage_in]], 66 | constant float &hueAdjust [[buffer(2)]], 67 | texture2d inputTexture [[texture(0)]]) { 68 | constexpr sampler quadSampler; 69 | float4 textureColor = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate); 70 | 71 | // Convert to YIQ 72 | float YPrime = dot(textureColor, kRGBToYPrime); 73 | float I = dot(textureColor, kRGBToI); 74 | float Q = dot(textureColor, kRGBToQ); 75 | 76 | // Calculate the hue and chroma 77 | float hue = atan2(Q, I); 78 | float chroma = sqrt(I * I + Q * Q); 79 | 80 | // Make the user's adjustments 81 | hue += (-hueAdjust); 82 | 83 | // Convert back to YIQ 84 | Q = chroma * sin (hue); 85 | I = chroma * cos (hue); 86 | 87 | // Convert back to RGB 88 | float4 yIQ = float4(YPrime, I, Q, 0.0); 89 | textureColor.r = dot(yIQ, kYIQToR); 90 | textureColor.g = dot(yIQ, kYIQToG); 91 | textureColor.b = dot(yIQ, kYIQToB); 92 | 93 | return textureColor; 94 | } 95 | 96 | #pragma mark - 颜色查找表 97 | struct LutInfo { 98 | unsigned int maxColorValue; // lut每个分量的有多少种颜色最大取值 99 | unsigned int latticeCount; // 每排晶格数量 100 | unsigned int width; // lut图片宽度 101 | unsigned int height; // lut图片高度 102 | }; 103 | fragment float4 lookUpTableFragment(SingleInputVertexIO fragmentInput [[stage_in]], 104 | constant float &intensity [[buffer(2)]], 105 | constant LutInfo &lutInfo [[buffer(3)]], 106 | texture2d inputTexture [[texture(0)]], 107 | texture2d lutTexture [[texture(1)]]) { 108 | constexpr sampler quadSampler; 109 | float4 px = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate); 110 | 111 | // B通道对应LUT上的数值 112 | float blueColor = px.b * lutInfo.maxColorValue; 113 | 114 | // 计算临近两个B通道所在的方形LUT单元格(从左到右从,上到下排列) 115 | float2 quad_l, quad_h; 116 | quad_l.y = floor(floor(blueColor) / lutInfo.latticeCount); 117 | quad_l.x = floor(blueColor) - quad_l.y * lutInfo.latticeCount; 118 | quad_h.y = floor(ceil(blueColor) / lutInfo.latticeCount); 119 | quad_h.x = ceil(blueColor) - (quad_h.y * lutInfo.latticeCount); 120 | 121 | // 单位像素上的中心偏移量 122 | float px_length = 1.0 / lutInfo.width; 123 | float cell_length = 1.0 / lutInfo.latticeCount; 124 | 125 | // 根据RG、B偏移量计算LUT上对应的(x,y) 126 | float2 lut_pos_l, lut_pos_h; 127 | lut_pos_l.x = (quad_l.x * cell_length) + px_length / 2.0 + ((cell_length - px_length) * px.r); 128 | lut_pos_l.y = (quad_l.y * cell_length) + px_length / 2.0 + ((cell_length - px_length) * px.g); 129 | lut_pos_h.x = (quad_h.x * cell_length) + px_length / 2.0 + ((cell_length - px_length) * px.r); 130 | lut_pos_h.y = (quad_h.y * cell_length) + px_length / 2.0 + ((cell_length - px_length) * px.g); 131 | 132 | // 获取映射的LUT颜色 133 | float4 graded_color_l = lutTexture.sample(quadSampler, lut_pos_l); 134 | float4 graded_color_h = lutTexture.sample(quadSampler, lut_pos_h); 135 | float4 graded_color = mix(graded_color_l, graded_color_h, fract(blueColor)); 136 | 137 | // 根据intensity定制效果程度 138 | float4 newColor = mix(px, float4(graded_color.rgb, px.w), intensity); 139 | return newColor; 140 | } 141 | -------------------------------------------------------------------------------- /MetalImage/MetalImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImage.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/11/26. 6 | // 7 | 8 | #import "NSBundle+MetalImageBundle.h" 9 | 10 | #import "MetalImageDevice.h" 11 | #import "MetalImageResource.h" 12 | #import "MetalImageTexture.h" 13 | #import "MetalImageTextureCache.h" 14 | #import "MetalImageProtocol.h" 15 | 16 | #import "MetalImageFilter.h" 17 | 18 | #import "MetalImageSource.h" 19 | #import "MetalImagePicture.h" 20 | #import "MetalImageCamera.h" 21 | 22 | #import "MetalImageTarget.h" 23 | #import "MetalImageMovieWriter.h" 24 | #import "MetalImageView.h" 25 | -------------------------------------------------------------------------------- /MetalImage/Resource/4*4_origin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/MetalImage/Resource/4*4_origin.png -------------------------------------------------------------------------------- /MetalImage/Resource/8*8_effect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/MetalImage/Resource/8*8_effect.png -------------------------------------------------------------------------------- /MetalImage/Resource/8*8_origin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/MetalImage/Resource/8*8_origin.png -------------------------------------------------------------------------------- /MetalImage/Source/MetalImageCamera.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageCamera.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/11/29. 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | #import "MetalImageSource.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface MetalImageCamera : NSObject 16 | @property (nonatomic, strong, readonly) MetalImageSource *source; 17 | 18 | - (instancetype)initWithSessionPreset:(AVCaptureSessionPreset)present 19 | cameraPosition:(AVCaptureDevicePosition)cameraPosition; 20 | 21 | - (void)startCapture; 22 | - (void)stopCapture; 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /MetalImage/Source/MetalImageCamera.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageCamera.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/11/29. 6 | // 7 | 8 | #import "MetalImageCamera.h" 9 | 10 | @interface MetalImageCamera() 11 | @property (nonatomic, strong) AVCaptureDevice *inputCamera; 12 | @property (nonatomic, strong) AVCaptureDevice *inputMic; 13 | @property (nonatomic, assign) AVCaptureSessionPreset inputPresent; 14 | @property (nonatomic, assign) AVCaptureDevicePosition inputPosition; 15 | 16 | @property (nonatomic, strong) AVCaptureSession *session; 17 | @property (nonatomic, strong) AVCaptureVideoDataOutput *imageOutput; 18 | @property (nonatomic, strong) AVCaptureAudioDataOutput *audioOutput; 19 | @property (nonatomic, strong) AVCaptureDeviceInput *imageInput; 20 | @property (nonatomic, strong) AVCaptureDeviceInput *audioInput; 21 | @property (nonatomic, assign) CVMetalTextureCacheRef textureCache; 22 | 23 | @property (nonatomic, strong) AVCaptureConnection *imageConnection; 24 | @property (nonatomic, strong) AVCaptureConnection *audioConnection; 25 | 26 | @property (nonatomic, strong) dispatch_queue_t audioCaptureQueue; 27 | @property (nonatomic, strong) dispatch_queue_t imageCaptureQueue; 28 | @property (nonatomic, strong) dispatch_queue_t renderQueue; 29 | @property (nonatomic, strong) dispatch_queue_t audioProcessQueue; 30 | 31 | @property (nonatomic, strong) MetalImageSource *source; 32 | @end 33 | 34 | @implementation MetalImageCamera 35 | 36 | - (instancetype)initWithSessionPreset:(AVCaptureSessionPreset)present 37 | cameraPosition:(AVCaptureDevicePosition)cameraPosition { 38 | if (self = [super init]) { 39 | self.inputPresent = present; 40 | self.inputPosition = cameraPosition; 41 | self.imageCaptureQueue = dispatch_queue_create("com.MetalImage.Camera.ImageCapture", DISPATCH_QUEUE_SERIAL); 42 | self.audioCaptureQueue = dispatch_queue_create("com.MetalImage.Camera.AudioCapture", DISPATCH_QUEUE_SERIAL); 43 | self.renderQueue = dispatch_queue_create("com.MetalImage.Camera.ImageRender", DISPATCH_QUEUE_SERIAL); 44 | self.audioProcessQueue = dispatch_queue_create("com.MetalImage.Camera.AudioProcess", DISPATCH_QUEUE_SERIAL); 45 | self.source = [[MetalImageSource alloc] init]; 46 | 47 | // 图像 48 | self.session = [[AVCaptureSession alloc] init]; 49 | self.inputCamera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 50 | self.imageInput = [AVCaptureDeviceInput deviceInputWithDevice:self.inputCamera error:nil]; 51 | self.imageOutput = [[AVCaptureVideoDataOutput alloc] init]; 52 | 53 | self.imageOutput.videoSettings = @{(__bridge NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA)}; 54 | [self.imageOutput setSampleBufferDelegate:self queue:self.imageCaptureQueue]; 55 | 56 | // 音频 57 | self.inputMic = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; 58 | self.audioInput = [AVCaptureDeviceInput deviceInputWithDevice:self.inputMic error:nil]; 59 | self.audioOutput = [[AVCaptureAudioDataOutput alloc] init]; 60 | [self.audioOutput setSampleBufferDelegate:self queue:self.audioCaptureQueue]; 61 | 62 | [self.session beginConfiguration]; 63 | self.session.sessionPreset = present; 64 | if ([self.session canAddInput:self.imageInput]) { 65 | [self.session addInput:self.imageInput]; 66 | } 67 | if ([self.session canAddOutput:self.imageOutput]) { 68 | [self.session addOutput:self.imageOutput]; 69 | } 70 | if ([self.session canAddInput:self.audioInput]) { 71 | [self.session addInput:self.audioInput]; 72 | } 73 | if ([self.session canAddOutput:self.audioOutput]) { 74 | [self.session addOutput:self.audioOutput]; 75 | } 76 | [self.session commitConfiguration]; 77 | 78 | self.imageConnection = [self.imageOutput connectionWithMediaType:AVMediaTypeVideo]; 79 | self.audioConnection = [self.audioOutput connectionWithMediaType:AVMediaTypeAudio]; 80 | 81 | CVReturn status = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, [MetalImageDevice shared].device, nil, &(_textureCache)); 82 | if (status != kCVReturnSuccess) { 83 | assert(!"Failed to create Metal texture cache"); 84 | } 85 | } 86 | return self; 87 | } 88 | 89 | - (void)dealloc { 90 | [self stopCapture]; 91 | } 92 | 93 | - (void)startCapture { 94 | if (!self.session.isRunning) { 95 | [self.session startRunning]; 96 | } 97 | } 98 | 99 | - (void)stopCapture { 100 | if (self.session.isRunning) { 101 | [self.session stopRunning]; 102 | } 103 | } 104 | 105 | - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(nonnull CMSampleBufferRef)sampleBuffer fromConnection:(nonnull AVCaptureConnection *)connection { 106 | // 图像 107 | if (connection == self.imageConnection) { 108 | CVImageBufferRef cameraFrame = CMSampleBufferGetImageBuffer(sampleBuffer); 109 | size_t frameWidth = CVPixelBufferGetWidth(cameraFrame); 110 | size_t frameHeight = CVPixelBufferGetHeight(cameraFrame); 111 | CMTime sampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); 112 | 113 | if (!cameraFrame) { 114 | return; 115 | } 116 | CFRetain(sampleBuffer); 117 | 118 | __weak typeof(self) weakSelf = self; 119 | CVPixelBufferLockBaseAddress(cameraFrame, 0); 120 | dispatch_sync(self.renderQueue, ^{ 121 | CVPixelBufferUnlockBaseAddress(cameraFrame, 0); 122 | 123 | // Create CVMetal Texture 124 | CVReturn cvret; 125 | CVMetalTextureRef metalTexutre = NULL; 126 | cvret = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, 127 | weakSelf.textureCache, 128 | cameraFrame, nil, 129 | MTLPixelFormatBGRA8Unorm, 130 | frameWidth, frameHeight, 131 | 0, 132 | &metalTexutre); 133 | if(cvret != kCVReturnSuccess) { 134 | assert(!"Failed to create Metal texture"); 135 | return; 136 | } 137 | 138 | // Get Metal Texture 139 | id texture = CVMetalTextureGetTexture(metalTexutre); 140 | if(!texture) { 141 | assert(!"Failed to get metal texture from CVMetalTextureRef"); 142 | return; 143 | }; 144 | 145 | CFRelease(metalTexutre); 146 | CFRelease(sampleBuffer); 147 | 148 | @autoreleasepool { 149 | MetalImageTexture *imageTexture = [[MetalImageTexture alloc] initWithTexture:texture 150 | orientation:MetalImageLandscapeLeft 151 | willCache:NO]; 152 | MetalImageResource *imageResource = [MetalImageResource imageResource:imageTexture]; 153 | imageResource.processingQueue = weakSelf.renderQueue; 154 | [weakSelf send:imageResource withTime:sampleTime]; 155 | } 156 | }); 157 | } 158 | // 音频 159 | else if (connection == self.audioConnection) { 160 | CFRetain(sampleBuffer); 161 | 162 | __weak typeof(self) weakSelf = self; 163 | dispatch_sync(self.audioProcessQueue, ^{ 164 | CMTime sampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); 165 | MetalImageResource *audioResource = [MetalImageResource audioResource:sampleBuffer]; 166 | [weakSelf send:audioResource withTime:sampleTime]; 167 | 168 | CFRelease(sampleBuffer); 169 | }); 170 | } 171 | } 172 | 173 | #pragma mark - Source Protocol 174 | - (void)send:(MetalImageResource *)resource withTime:(CMTime)time { 175 | [self.source send:resource withTime:time]; 176 | } 177 | 178 | - (void)addTarget:(id)target { 179 | [self.source addTarget:target]; 180 | } 181 | 182 | - (void)removeTarget:(id)target { 183 | [self.source removeTarget:target]; 184 | } 185 | 186 | - (void)removeAllTarget { 187 | [self.source removeAllTarget]; 188 | } 189 | @end 190 | -------------------------------------------------------------------------------- /MetalImage/Source/MetalImagePicture.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImagePicture.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/12. 6 | // 7 | 8 | #import 9 | #import "MetalImageSource.h" 10 | #import "MetalImageResource.h" 11 | #import "MetalImageFilter.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | typedef void(^MetalImagePictureProcessCompletion)(MetalImageResource * _Nullable processedTextureResource); 15 | 16 | @interface MetalImagePicture : NSObject 17 | @property (nonatomic, strong) UIImage *originImage; 18 | @property (nonatomic, strong, readonly) MetalImageSource *source; 19 | 20 | - (instancetype)initWithImage:(UIImage *)image; 21 | /** 22 | * 触发图像分发链路 23 | */ 24 | - (void)processImage; 25 | 26 | /** 27 | * 使用一组滤镜处理图像 28 | * 29 | * @param filters 一组滤镜 30 | * @param completion 滤镜处理结果 31 | */ 32 | - (void)processImageByFilters:(NSArray> *)filters completion:(MetalImagePictureProcessCompletion)completion; 33 | @end 34 | 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /MetalImage/Source/MetalImagePicture.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImagePicture.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/12. 6 | // 7 | 8 | #import "MetalImagePicture.h" 9 | 10 | @interface MetalImagePicture() 11 | @property (nonatomic, strong) MetalImageResource *resource; 12 | @property (nonatomic, strong) MetalImageSource *source; 13 | @end 14 | 15 | @implementation MetalImagePicture 16 | 17 | - (instancetype)initWithImage:(UIImage *)image { 18 | if (self = [super init]) { 19 | _originImage = image; 20 | _source = [[MetalImageSource alloc] init]; 21 | } 22 | return self; 23 | } 24 | 25 | - (void)setOriginImage:(UIImage *)originImage { 26 | _originImage = originImage; 27 | if (_originImage != nil) { 28 | __weak typeof(self) weakSelf = self; 29 | dispatch_barrier_async([MetalImageDevice shared].concurrentQueue, ^{ 30 | weakSelf.resource = nil; 31 | }); 32 | } 33 | } 34 | 35 | - (MetalImageResource *)resource { 36 | if (!_resource) { 37 | NSError *err = nil; 38 | id texutre = [[MetalImageDevice shared].textureLoader newTextureWithCGImage:[_originImage CGImage] options:NULL error:&err]; 39 | MetalImageTexture *metalTexture = [[MetalImageTexture alloc] initWithTexture:texutre orientation:MetalImagePortrait willCache:NO]; 40 | _resource = [MetalImageResource imageResource:metalTexture]; 41 | } 42 | return _resource; 43 | } 44 | 45 | - (void)processImage { 46 | if (!self.source.haveTarget || !_originImage) { 47 | return; 48 | } 49 | 50 | __weak typeof(self) weakSelf = self; 51 | dispatch_async([MetalImageDevice shared].concurrentQueue, ^{ 52 | @synchronized (self) { 53 | __strong typeof(weakSelf) strongSelf = weakSelf; 54 | if (!strongSelf) { 55 | return; 56 | } 57 | 58 | [strongSelf send:strongSelf.resource withTime:kCMTimeInvalid]; 59 | strongSelf.resource = nil; 60 | } 61 | }); 62 | } 63 | 64 | - (void)processImageByFilters:(NSArray> *)filters completion:(MetalImagePictureProcessCompletion)completion { 65 | if (!filters || !filters.count || !_originImage) { 66 | return; 67 | } 68 | 69 | __weak typeof(self) weakSelf = self; 70 | dispatch_async([MetalImageDevice shared].concurrentQueue, ^{ 71 | __strong typeof(weakSelf) strongSelf = weakSelf; 72 | if (!strongSelf) { 73 | !completion ? : completion(nil); 74 | return; 75 | } 76 | 77 | for (id filter in filters) { 78 | if ([filter isKindOfClass:[MetalImageFilter class]] && [filter supportProcessRenderCommandEncoderOnly]) { 79 | [strongSelf.resource.renderProcess addRenderProcess:^(id renderEncoder) { 80 | [(MetalImageFilter*)filter renderToCommandEncoder:renderEncoder withResource:strongSelf.resource]; 81 | }]; 82 | } else { 83 | [strongSelf.resource.renderProcess commitRenderWaitUntilFinish:YES completion:nil]; 84 | [filter renderToResource:strongSelf.resource]; 85 | } 86 | } 87 | [strongSelf.resource.renderProcess commitRenderWaitUntilFinish:YES completion:nil]; 88 | 89 | !completion ? : completion(strongSelf.resource); 90 | strongSelf.resource = nil; 91 | }); 92 | } 93 | 94 | #pragma mark - Source Protocol 95 | - (void)send:(MetalImageResource *)resource withTime:(CMTime)time { 96 | [self.source send:resource withTime:time]; 97 | } 98 | 99 | - (void)addTarget:(id)target { 100 | [self.source addTarget:target]; 101 | } 102 | 103 | - (void)removeTarget:(id)target { 104 | [self.source removeTarget:target]; 105 | } 106 | 107 | - (void)removeAllTarget { 108 | [self.source removeAllTarget]; 109 | } 110 | @end 111 | -------------------------------------------------------------------------------- /MetalImage/Source/MetalImageSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageSource.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/11/30. 6 | // 7 | 8 | #import 9 | #import "MetalImageProtocol.h" 10 | #import "MetalImageDevice.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface MetalImageSource : NSObject 15 | @property (nonatomic, assign, readonly) BOOL haveTarget; 16 | 17 | /** 18 | * 设置分发链路的目标 19 | * 第一目标为同步目标,使用同一份资源 20 | * 其他目标为异步目标,自动拷贝一份资源再传递下去 21 | * 22 | * @param target 遵循输出目标协议的对象 23 | */ 24 | - (void)addTarget:(id)target; 25 | 26 | /** 27 | * 删除转发目标 28 | * 29 | * @param target 遵循输出目标协议的对象 30 | */ 31 | - (void)removeTarget:(id)target; 32 | 33 | /** 34 | * 删除所有转发目标 35 | */ 36 | - (void)removeAllTarget; 37 | 38 | /** 39 | * 设置并行目标,当有2个以上目标时会自动拷贝一份资源再传递下去 40 | * 41 | * @param resource 转发资源 42 | * @param time 转发的时间 43 | */ 44 | - (void)send:(MetalImageResource *)resource withTime:(CMTime)time; 45 | 46 | /** 47 | * 这个分发节点的目标 48 | * 49 | * @return 分发目标 50 | */ 51 | - (NSArray> *)targets; 52 | @end 53 | 54 | NS_ASSUME_NONNULL_END 55 | -------------------------------------------------------------------------------- /MetalImage/Source/MetalImageSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageSource.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/11/30. 6 | // 7 | 8 | #import "MetalImageSource.h" 9 | 10 | @interface MetalImageSource() 11 | @property (nonatomic, strong) id syncTarget; 12 | @property (nonatomic, strong) NSMutableArray> *asncTargets; 13 | @end 14 | 15 | @implementation MetalImageSource 16 | 17 | - (NSMutableArray> *)asncTargets { 18 | if (!_asncTargets) { 19 | _asncTargets = [[NSMutableArray alloc] init]; 20 | } 21 | return _asncTargets; 22 | } 23 | 24 | - (BOOL)haveTarget { 25 | if (!_syncTarget && (!_asncTargets || !_asncTargets.count)) { 26 | return NO; 27 | } 28 | return YES; 29 | } 30 | 31 | - (void)addTarget:(id)target { 32 | if (!target) { 33 | return; 34 | } 35 | 36 | if (!_syncTarget) { 37 | _syncTarget = target; 38 | } else { 39 | [self.asncTargets addObject:target]; 40 | } 41 | } 42 | 43 | -(void)removeTarget:(id)target { 44 | if (!target) { 45 | return; 46 | } 47 | 48 | if (target == _syncTarget) { 49 | _syncTarget = nil; 50 | } 51 | 52 | if ([self.asncTargets containsObject:target]) { 53 | [self.asncTargets removeObject:target]; 54 | } 55 | } 56 | 57 | - (void)removeAllTarget { 58 | _syncTarget = nil; 59 | [_asncTargets removeAllObjects]; 60 | } 61 | 62 | - (void)send:(MetalImageResource *)resource withTime:(CMTime)time { 63 | if (!_asncTargets || !_asncTargets.count) { 64 | [_syncTarget receive:resource withTime:time]; 65 | return; 66 | } 67 | 68 | dispatch_queue_t processQueue = resource.processingQueue ? resource.processingQueue : [MetalImageDevice shared].concurrentQueue; 69 | id snycTarget = _syncTarget; 70 | 71 | for (NSUInteger index = 0; index < _asncTargets.count; index++) { 72 | id asyncTarget = [_asncTargets objectAtIndex:index]; 73 | @autoreleasepool { 74 | MetalImageResource *newResource = [resource newResourceFromSelf]; 75 | dispatch_async(processQueue, ^{ 76 | [asyncTarget receive:newResource withTime:time]; 77 | }); 78 | } 79 | } 80 | 81 | [snycTarget receive:resource withTime:time]; 82 | } 83 | 84 | - (NSArray> *)targets { 85 | NSMutableArray *array = @[].mutableCopy; 86 | if (_syncTarget) { 87 | [array addObject:_syncTarget]; 88 | } 89 | 90 | if (_asncTargets) { 91 | [array addObjectsFromArray:_asncTargets]; 92 | } 93 | return array; 94 | } 95 | @end 96 | -------------------------------------------------------------------------------- /MetalImage/Target/MetalImageMovieWriter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageMovieWriter.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/12/13. 6 | // 7 | 8 | #import 9 | #import 10 | #import "MetalImageProtocol.h" 11 | #import "MetalImageTarget.h" 12 | #import "MetalImageFilter.h" 13 | #import "MetalImageResource.h" 14 | 15 | #define kMetalImageMovieWriterCancelError [NSError errorWithDomain:@"MoiveWriterWriterError" code:-9001 userInfo:@{@"message" : @"WriterCanceled"}] 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | typedef void(^_Nullable MetalImageMovieWriterCompleteHandle)(NSError *_Nullable error); 19 | typedef void(^_Nullable MetalImageMovieWriterStartHandle)(NSError *_Nullable error); 20 | 21 | @interface MetalImageMovieWriter : NSObject 22 | @property (nonatomic, assign) MetalImageContentMode fillMode; 23 | @property (nonatomic, assign) MetalImagContentBackground backgroundType; 24 | @property (nonatomic, strong, nullable) id backgroundFilter; 25 | @property (nonatomic, strong) UIColor *backgroundColor; 26 | @property (nonatomic, assign) BOOL haveAudioTrack; 27 | 28 | @property (nonatomic, copy) MetalImageMovieWriterCompleteHandle completeHandle; 29 | @property (nonatomic, copy) MetalImageMovieWriterStartHandle startHandle; 30 | 31 | @property (nonatomic, assign, readonly) AVAssetWriterStatus status; 32 | @property (nonatomic, strong, readonly) NSURL *storageUrl; 33 | 34 | - (instancetype)initWithStorageUrl:(NSURL *)storageUrl size:(CGSize)size; 35 | 36 | /** 37 | * 开启录制 38 | */ 39 | - (void)startRecording; 40 | 41 | /** 42 | * 取消录制 43 | */ 44 | - (void)cancelRecording; 45 | 46 | /** 47 | * 结束录制 48 | */ 49 | - (void)finishRecording; 50 | @end 51 | 52 | NS_ASSUME_NONNULL_END 53 | -------------------------------------------------------------------------------- /MetalImage/Target/MetalImageTarget.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageTarget.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/1/3. 6 | // 7 | 8 | #import 9 | #import "MetalImageDevice.h" 10 | #import "MetalImageTexture.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface MetalImageTarget : NSObject 15 | @property (nonatomic, assign) MetalImageContentMode fillMode; 16 | @property (nonatomic, strong) id pielineState; 17 | @property (nonatomic, strong) MTLRenderPassDescriptor *renderPassDecriptor; 18 | @property (nonatomic, strong) id position; 19 | @property (nonatomic, strong) id textureCoord; 20 | @property (nonatomic, assign) CGSize size; 21 | 22 | - (instancetype)initWithDefaultLibraryWithVertex:(NSString *)vertexFunctionName 23 | fragment:(NSString *)fragmentFunctionName; 24 | 25 | - (instancetype)initWithDefaultLibraryWithVertex:(NSString *)vertexFunctionName 26 | fragment:(NSString *)fragmentFunctionName 27 | enableBlend:(BOOL)enableBlend; 28 | 29 | - (instancetype)initWithVertexFunction:(NSString *)vertexFunction 30 | fragmentFunction:(NSString *)fragmentFunction 31 | library:(id)library 32 | enableBlend:(BOOL)enableBlend; 33 | 34 | /** 35 | * 根据输入纹理更新目标纹理坐标/顶点坐标 36 | * 37 | * @param texture 输入纹理 38 | */ 39 | - (void)updateCoordinateIfNeed:(MetalImageTexture *)texture; 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /MetalImage/Target/MetalImageTarget.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageTarget.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2019/1/3. 6 | // 7 | 8 | #import "MetalImageTarget.h" 9 | #import "NSBundle+MetalImageBundle.h" 10 | 11 | typedef struct { 12 | CGSize textureSize; 13 | CGSize targetSize; 14 | MetalImageOrientation textureOrientation; 15 | MetalImageContentMode fillMode; 16 | } MetalImageBufferReuseInfo; 17 | 18 | @interface MetalImageTarget() 19 | @property (nonatomic, assign) MetalImageBufferReuseInfo bufferReuseInfo; 20 | @end 21 | 22 | @implementation MetalImageTarget 23 | 24 | - (instancetype)initWithDefaultLibraryWithVertex:(NSString *)vertexFunctionName fragment:(NSString *)fragmentFunctionName { 25 | return [self initWithDefaultLibraryWithVertex:vertexFunctionName fragment:fragmentFunctionName enableBlend:NO]; 26 | } 27 | 28 | - (instancetype)initWithDefaultLibraryWithVertex:(NSString *)vertexFunctionName 29 | fragment:(NSString *)fragmentFunctionName 30 | enableBlend:(BOOL)enableBlend { 31 | 32 | NSString *bundlePath = [NSBundle metalImage_bundleWithName:MetalImageBundleName].bundlePath; 33 | NSString *defaultMetalFile = [bundlePath stringByAppendingPathComponent:@"default.metallib"]; 34 | NSError *error = nil; 35 | idlibrary = [[MetalImageDevice shared].device newLibraryWithFile:defaultMetalFile error:&error]; 36 | if (error) { 37 | assert(!"Create library failed"); 38 | } 39 | 40 | return [self initWithVertexFunction:vertexFunctionName fragmentFunction:fragmentFunctionName library:library enableBlend:enableBlend]; 41 | } 42 | 43 | - (instancetype)initWithVertexFunction:(NSString *)vertexFunction 44 | fragmentFunction:(NSString *)fragmentFunction 45 | library:(id)library 46 | enableBlend:(BOOL)enableBlend { 47 | if (self = [super init]) { 48 | NSError *error = nil; 49 | MTLRenderPipelineDescriptor *renderPipelineDesc = [[MTLRenderPipelineDescriptor alloc] init]; 50 | renderPipelineDesc.vertexFunction = [library newFunctionWithName:vertexFunction]; 51 | renderPipelineDesc.fragmentFunction = [library newFunctionWithName:fragmentFunction]; 52 | renderPipelineDesc.colorAttachments[0].pixelFormat = [MetalImageDevice shared].pixelFormat; 53 | 54 | if (enableBlend) { 55 | // 结果色 = 源色 * 源因子 + 目标色 * 目标因子 56 | // 结果alpha = 源透明度 * 源因子 + 目标透明度 * 目标因子 57 | 58 | renderPipelineDesc.colorAttachments[0].blendingEnabled = YES; 59 | renderPipelineDesc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; 60 | renderPipelineDesc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; 61 | renderPipelineDesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; 62 | renderPipelineDesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha; 63 | renderPipelineDesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; 64 | renderPipelineDesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; 65 | } 66 | 67 | _pielineState = [[MetalImageDevice shared].device newRenderPipelineStateWithDescriptor:renderPipelineDesc error:&error]; 68 | if (error) { 69 | assert(!"Create piplinstate failed"); 70 | } 71 | 72 | _fillMode = MetalImageContentModeScaleToFill; 73 | } 74 | return self; 75 | } 76 | 77 | - (void)updateCoordinateIfNeed:(MetalImageTexture *)texture { 78 | BOOL textureSizeChange = !CGSizeEqualToSize(CGSizeMake(texture.width, texture.height), _bufferReuseInfo.textureSize); 79 | BOOL textureOritationChange = texture.orientation != _bufferReuseInfo.textureOrientation; 80 | BOOL targetSizeChange = !CGSizeEqualToSize(self.size, _bufferReuseInfo.targetSize); 81 | BOOL fillModeChange = _fillMode != _bufferReuseInfo.fillMode; 82 | 83 | if (!textureSizeChange && !textureOritationChange && !targetSizeChange && !fillModeChange) { 84 | return; 85 | } 86 | 87 | _bufferReuseInfo.fillMode = _fillMode; 88 | _bufferReuseInfo.targetSize = self.size; 89 | _bufferReuseInfo.textureOrientation = texture.orientation; 90 | _bufferReuseInfo.textureSize = CGSizeMake(texture.width, texture.height); 91 | 92 | // 将纹理旋转到正上方向再绘制到目标纹理中 93 | MetalImageCoordinate position = [texture texturePositionToSize:self.size contentMode:_fillMode]; 94 | MetalImageCoordinate textureCoor = [texture textureCoordinatesToOrientation:MetalImagePortrait]; 95 | 96 | _position = [[MetalImageDevice shared].device newBufferWithBytes:&position length:sizeof(position) options:0]; 97 | _textureCoord = [[MetalImageDevice shared].device newBufferWithBytes:&textureCoor length:sizeof(textureCoor) options:0]; 98 | } 99 | 100 | - (MTLRenderPassDescriptor *)renderPassDecriptor { 101 | if (!_renderPassDecriptor) { 102 | _renderPassDecriptor = [[MTLRenderPassDescriptor alloc] init]; 103 | _renderPassDecriptor.colorAttachments[0].loadAction = MTLLoadActionClear; 104 | _renderPassDecriptor.colorAttachments[0].storeAction = MTLStoreActionStore; 105 | _renderPassDecriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1); 106 | } 107 | return _renderPassDecriptor; 108 | } 109 | @end 110 | -------------------------------------------------------------------------------- /MetalImage/Target/MetalImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageView.h 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/11/29. 6 | // 7 | 8 | #import 9 | #import "MetalImageProtocol.h" 10 | #import "MetalImageTarget.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface MetalImageView : UIView 15 | @property (nonatomic, assign) MetalImageContentMode fillMode; 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /MetalImage/Target/MetalImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetalImageView.m 3 | // MetalImage 4 | // 5 | // Created by David.Dai on 2018/11/29. 6 | // 7 | 8 | #import "MetalImageView.h" 9 | 10 | @interface MetalImageView() 11 | @property (nonatomic, strong) CAMetalLayer *metalLayer; 12 | @property (nonatomic, strong) dispatch_queue_t displayQueue; 13 | @property (nonatomic, strong) MetalImageTarget *renderTarget; 14 | @property (nonatomic, strong) UIColor *metalBackgroundColor; 15 | @end 16 | 17 | @implementation MetalImageView 18 | - (instancetype)initWithFrame:(CGRect)frame { 19 | if (self = [super initWithFrame:frame]) { 20 | [self commitInit]; 21 | } 22 | return self; 23 | } 24 | 25 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 26 | if (self = [super initWithCoder:aDecoder]) { 27 | [self commitInit]; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)commitInit { 33 | _metalLayer = [[CAMetalLayer alloc] init]; 34 | _metalLayer.device = [MetalImageDevice shared].device; 35 | _metalLayer.pixelFormat = [MetalImageDevice shared].pixelFormat; 36 | _metalLayer.framebufferOnly = YES; 37 | _metalLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); 38 | [self.layer addSublayer:_metalLayer]; 39 | 40 | _displayQueue = dispatch_queue_create("com.MetalImage.DisplayView", DISPATCH_QUEUE_SERIAL); 41 | _renderTarget = [[MetalImageTarget alloc] initWithDefaultLibraryWithVertex:kMetalImageDefaultVertex 42 | fragment:kMetalImageDefaultFragment]; 43 | _renderTarget.fillMode = MetalImageContentModeScaleAspectFill; 44 | _renderTarget.size = CGSizeMake(self.metalLayer.frame.size.width * [UIScreen mainScreen].scale, 45 | self.metalLayer.frame.size.height * [UIScreen mainScreen].scale); 46 | } 47 | 48 | - (void)layoutSubviews { 49 | [super layoutSubviews]; 50 | self.metalLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); 51 | CGSize desSize = CGSizeMake(self.metalLayer.frame.size.width * [UIScreen mainScreen].scale, 52 | self.metalLayer.frame.size.height * [UIScreen mainScreen].scale); 53 | 54 | if (!CGSizeEqualToSize(self.renderTarget.size, desSize)) { 55 | self.renderTarget.size = desSize; 56 | } 57 | } 58 | 59 | - (MetalImageContentMode)fillMode { 60 | return _renderTarget.fillMode; 61 | } 62 | 63 | - (void)setFillMode:(MetalImageContentMode)fillMode { 64 | _renderTarget.fillMode = fillMode; 65 | } 66 | 67 | - (void)setBackgroundColor:(UIColor *)backgroundColor { 68 | [super setBackgroundColor:backgroundColor]; 69 | self.metalBackgroundColor = backgroundColor; 70 | } 71 | 72 | #pragma mark - Target Protocol 73 | - (void)receive:(MetalImageResource *)resource withTime:(CMTime)time { 74 | if (!resource || resource.type != MetalImageResourceTypeImage) { 75 | return; 76 | } 77 | 78 | [resource.renderProcess commitRender]; 79 | 80 | __weak typeof(self) weakSelf = self; 81 | dispatch_async(self.displayQueue, ^{ 82 | __strong typeof(weakSelf) strongSelf = weakSelf; 83 | 84 | @autoreleasepool { 85 | id drawable = [strongSelf.metalLayer nextDrawable]; 86 | if (drawable) { 87 | // View的Alpha使用layer的opaque实现而非开启这个Pieline的Blend 88 | MTLClearColor color = [strongSelf getMTLbackgroundColor]; 89 | if (strongSelf.metalLayer.opaque && color.alpha != 1.0) { 90 | strongSelf.metalLayer.opaque = NO; 91 | } else if (!strongSelf.metalLayer.opaque && color.alpha == 1.0) { 92 | strongSelf.metalLayer.opaque = YES; 93 | } 94 | 95 | strongSelf.renderTarget.renderPassDecriptor.colorAttachments[0].texture = [drawable texture]; 96 | strongSelf.renderTarget.renderPassDecriptor.colorAttachments[0].clearColor = color; 97 | [strongSelf.renderTarget updateCoordinateIfNeed:resource.texture];// 调整输入纹理绘制到目标纹理时的比例和方向 98 | 99 | id commandBuffer = [[MetalImageDevice shared].commandQueue commandBuffer]; 100 | [commandBuffer enqueue]; 101 | id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:strongSelf.renderTarget.renderPassDecriptor]; 102 | [strongSelf renderToCommandEncoder:renderEncoder withResource:resource]; 103 | [commandBuffer presentDrawable:drawable]; 104 | [commandBuffer addCompletedHandler:^(id _Nonnull buffer) { 105 | [[MetalImageDevice shared].textureCache cacheTexture:resource.texture]; 106 | }]; 107 | [commandBuffer commit]; 108 | } 109 | } 110 | }); 111 | } 112 | 113 | - (MTLClearColor)getMTLbackgroundColor { 114 | CGFloat components[4]; 115 | [self.metalBackgroundColor getRed:components green:components + 1 blue:components + 2 alpha:components + 3]; 116 | return MTLClearColorMake(components[0], components[1], components[2], components[3]); 117 | } 118 | 119 | #pragma mark - Render Process 120 | - (void)renderToCommandEncoder:(id)renderEncoder withResource:(MetalImageResource *)resource { 121 | #if DEBUG 122 | renderEncoder.label = NSStringFromClass([self class]); 123 | [renderEncoder pushDebugGroup:@"Display Draw"]; 124 | #endif 125 | 126 | [renderEncoder setRenderPipelineState:_renderTarget.pielineState]; 127 | [renderEncoder setVertexBuffer:_renderTarget.position offset:0 atIndex:0]; 128 | [renderEncoder setVertexBuffer:_renderTarget.textureCoord offset:0 atIndex:1]; 129 | [renderEncoder setFragmentTexture:resource.texture.metalTexture atIndex:0]; 130 | [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; 131 | 132 | #if DEBUG 133 | [renderEncoder popDebugGroup]; 134 | #endif 135 | [renderEncoder endEncoding]; 136 | } 137 | 138 | @end 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MetalImage 2 | ## 简介 3 | MetalImage是基于Metal框架实现的一套图像滤镜链处理框架。 4 | 5 | ### 功能 6 | * 目前实现主要的框架(Camera/Picture/MovieWriter/Filter/View等)和基本的滤镜 7 | 8 | ### 使用说明 9 | 10 | 1.相机画面分发到视图上 11 | ``` 12 | self.camera = [[MetalImageCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack]; 13 | MetalImageView *view = [[MetalImageView alloc] initWithFrame:CGRectMake(0, 0, 750 / 2, 1334 / 2)]; 14 | 15 | MetalImageFilter *filter = [[MetalImageFilter alloc] init]; 16 | [self.camera addTarget:filter]; 17 | 18 | [self.camera startCapture]; 19 | [self.view addSubview:view]; 20 | ``` 21 | 22 | 2.相机拍摄画面分发到视图和录制器上 23 | ``` 24 | self.camera = [[MetalImageCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack]; 25 | MetalImageView *view = [[MetalImageView alloc] initWithFrame:CGRectMake(0, 0, 750 / 2, 1334 / 2)]; 26 | MetalImageMovieWriter *movieWriter = [[MetalImageMovieWriter alloc] initWithStorageUrl:outputURL size:CGSizeMake(1080, 640)]; 27 | [self.camera addTarget:view]; 28 | [self.camera addTarget:movieWriter]; 29 | movieWriter.fillMode = MetalImageContentModeScaleAspectFit; 30 | movieWriter.backgroundType = MetalImagContentBackgroundFilter; 31 | movieWriter.backgroundFilter = [[MetalImageiOSBlurFilter alloc] init]; 32 | ((MetalImageiOSBlurFilter *)movieWriter.backgroundFilter).blurRadiusInPixels = 10.0; 33 | ((MetalImageiOSBlurFilter *)movieWriter.backgroundFilter).texelSpacingMultiplier = 2.0; 34 | ((MetalImageiOSBlurFilter *)movieWriter.backgroundFilter).saturation = 1.0; 35 | 36 | [movieWriter startRecording]; 37 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 38 | [movieWriter finishRecording]; 39 | }); 40 | 41 | [self.camera startCapture]; 42 | ``` 43 | 44 | 3.图片滤镜处理 45 | ``` 46 | MetalImageSharpenFilter *sharpen = [[MetalImageSharpenFilter alloc] init]; 47 | sharpen.sharpness = 2.0; 48 | [self.picture processImageByFilters:@[sharpen] completion:^(UIImage *processedImage) { 49 | 50 | }]; 51 | ``` 52 | 53 | #### 环境要求 54 | * iOS版本要求: >= 9.0 55 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/iOS/MetalImageDemo/1.jpg -------------------------------------------------------------------------------- /iOS/MetalImageDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2018/11/29. 6 | // Copyright © 2018 David. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2018/11/29. 6 | // Copyright © 2018 David. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/120-1.png -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "40.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "60.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "58.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "87.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "80.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "120.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "120-1.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "180.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "idiom" : "ipad", 53 | "size" : "20x20", 54 | "scale" : "1x" 55 | }, 56 | { 57 | "idiom" : "ipad", 58 | "size" : "20x20", 59 | "scale" : "2x" 60 | }, 61 | { 62 | "idiom" : "ipad", 63 | "size" : "29x29", 64 | "scale" : "1x" 65 | }, 66 | { 67 | "idiom" : "ipad", 68 | "size" : "29x29", 69 | "scale" : "2x" 70 | }, 71 | { 72 | "idiom" : "ipad", 73 | "size" : "40x40", 74 | "scale" : "1x" 75 | }, 76 | { 77 | "idiom" : "ipad", 78 | "size" : "40x40", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "idiom" : "ipad", 83 | "size" : "76x76", 84 | "scale" : "1x" 85 | }, 86 | { 87 | "idiom" : "ipad", 88 | "size" : "76x76", 89 | "scale" : "2x" 90 | }, 91 | { 92 | "idiom" : "ipad", 93 | "size" : "83.5x83.5", 94 | "scale" : "2x" 95 | }, 96 | { 97 | "size" : "1024x1024", 98 | "idiom" : "ios-marketing", 99 | "filename" : "1024.png", 100 | "scale" : "1x" 101 | } 102 | ], 103 | "info" : { 104 | "version" : 1, 105 | "author" : "xcode" 106 | } 107 | } -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/BasicViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BasicViewController.h 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2019/5/13. 6 | // Copyright © 2019 David. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface BasicViewController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/BasicViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BasicViewController.m 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2019/5/13. 6 | // Copyright © 2019 David. All rights reserved. 7 | // 8 | 9 | #import "BasicViewController.h" 10 | #import "MetalImageView.h" 11 | #import "MetalImageCamera.h" 12 | #import "MetalImagePicture.h" 13 | 14 | @interface BasicViewController () 15 | @property (weak, nonatomic) IBOutlet MetalImageView *firstFrameView; 16 | @property (weak, nonatomic) IBOutlet MetalImageView *secondFrameView; 17 | @property (weak, nonatomic) IBOutlet MetalImageView *thirdFrameView; 18 | 19 | @property (nonatomic, strong) MetalImageCamera *camera; 20 | @property (nonatomic, strong) MetalImagePicture *picture; 21 | @end 22 | 23 | @implementation BasicViewController 24 | 25 | - (void)viewDidLoad { 26 | [super viewDidLoad]; 27 | 28 | self.secondFrameView.fillMode = MetalImageContentModeScaleAspectFit; 29 | 30 | [self.camera addTarget:self.firstFrameView]; 31 | [self.camera addTarget:self.secondFrameView]; 32 | [self.camera startCapture]; 33 | 34 | [self.picture addTarget:self.thirdFrameView]; 35 | [self.picture processImage]; 36 | } 37 | 38 | - (MetalImageCamera *)camera { 39 | if (!_camera) { 40 | _camera = [[MetalImageCamera alloc] initWithSessionPreset:AVCaptureSessionPresetMedium cameraPosition:AVCaptureDevicePositionBack]; 41 | } 42 | return _camera; 43 | } 44 | 45 | - (MetalImagePicture *)picture { 46 | if (!_picture) { 47 | _picture = [[MetalImagePicture alloc] initWithImage:[UIImage imageNamed:@"1.jpg"]]; 48 | } 49 | return _picture; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/BasicViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/FilterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FilterViewController.h 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2019/5/13. 6 | // Copyright © 2019 David. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MetalImageProtocol.h" 11 | 12 | #import "MetalImageView.h" 13 | #import "MetalImageCamera.h" 14 | #import "MetalImagePicture.h" 15 | 16 | #import "MetalImageContrastFilter.h" 17 | #import "MetalImageHueFilter.h" 18 | #import "MetalImageSaturationFilter.h" 19 | #import "MetalImageLuminanceFilter.h" 20 | #import "MetalImageLookUpTableFilter.h" 21 | 22 | #import "MetalImageSharpenFilter.h" 23 | #import "MetalImageGaussianBlurFilter.h" 24 | #import "MetalImageConvolutionFilter.h" 25 | 26 | #import "MetalImageiOSBlurFilter.h" 27 | #import "MetalImageCropFilter.h" 28 | 29 | NS_ASSUME_NONNULL_BEGIN 30 | typedef struct { 31 | float min; 32 | float max; 33 | float current; 34 | } FileterNumericalValue; 35 | 36 | @interface FilterModel : NSObject 37 | @property (nonatomic, strong, readonly) NSArray *propertyName; 38 | @property (nonatomic, strong, readonly) NSArray *value; 39 | @property (nonatomic, strong) id filter; 40 | 41 | + (instancetype)filter:(id)filter 42 | effectProperty:(NSArray *)propertyName 43 | value:(NSArray *)value; 44 | @end 45 | 46 | @interface FilterViewController : UIViewController 47 | @property (nonatomic, assign) BOOL usePicture; 48 | + (instancetype)filterVCWithModel:(FilterModel *)filterModel; 49 | @end 50 | 51 | NS_ASSUME_NONNULL_END 52 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/FilterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FilterViewController.m 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2019/5/13. 6 | // Copyright © 2019 David. All rights reserved. 7 | // 8 | 9 | #import "FilterViewController.h" 10 | @interface FilterModel() 11 | @property (nonatomic, strong) NSArray *propertyName; 12 | @property (nonatomic, strong) NSArray *value; 13 | @end 14 | 15 | @implementation FilterModel 16 | + (instancetype)filter:(id)filter 17 | effectProperty:(NSArray *)propertyName 18 | value:(NSArray *)value { 19 | FilterModel *model = [[FilterModel alloc] init]; 20 | model.filter = filter; 21 | model.propertyName = propertyName; 22 | model.value = value; 23 | return model; 24 | } 25 | @end 26 | 27 | 28 | @interface FilterViewController () 29 | @property (weak, nonatomic) IBOutlet MetalImageView *firstFrameView; 30 | @property (weak, nonatomic) IBOutlet UISlider *firstSlider; 31 | @property (weak, nonatomic) IBOutlet UISlider *secondSlider; 32 | @property (weak, nonatomic) IBOutlet UISlider *thirdSlider; 33 | @property (weak, nonatomic) IBOutlet UILabel *firstLabel; 34 | @property (weak, nonatomic) IBOutlet UILabel *secondLabel; 35 | @property (weak, nonatomic) IBOutlet UILabel *thirdLabel; 36 | 37 | @property (nonatomic, strong) MetalImageCamera *camera; 38 | @property (nonatomic, strong) FilterModel *filterModel; 39 | 40 | @property (nonatomic, strong) MetalImagePicture *picture; 41 | @end 42 | 43 | @implementation FilterViewController 44 | 45 | + (instancetype)filterVCWithModel:(FilterModel *)filterModel { 46 | FilterViewController *vc = [[FilterViewController alloc] initWithNibName:@"FilterViewController" bundle:nil]; 47 | vc.filterModel = filterModel; 48 | return vc; 49 | } 50 | 51 | - (void)viewDidLoad { 52 | [super viewDidLoad]; 53 | 54 | if (self.usePicture) { 55 | [self.picture addTarget:(id)self.filterModel.filter]; 56 | [self.filterModel.filter addTarget:self.firstFrameView]; 57 | } else { 58 | [self.camera addTarget:(id)self.filterModel.filter]; 59 | [self.filterModel.filter addTarget:self.firstFrameView]; 60 | [self.camera startCapture]; 61 | } 62 | 63 | for (NSInteger index = 0; index < self.filterModel.propertyName.count; index ++) { 64 | FileterNumericalValue value; 65 | [self.filterModel.value[index] getValue:&value]; 66 | 67 | if (index == 0) { 68 | self.firstLabel.hidden = NO; 69 | self.firstSlider.hidden = NO; 70 | self.firstSlider.maximumValue = value.max; 71 | self.firstSlider.minimumValue = value.min; 72 | self.firstSlider.value = value.current; 73 | self.firstLabel.text = self.filterModel.propertyName[index]; 74 | } 75 | 76 | if (index == 1) { 77 | self.secondSlider.hidden = NO; 78 | self.secondLabel.hidden = NO; 79 | self.secondLabel.text = self.filterModel.propertyName[index]; 80 | self.secondSlider.maximumValue = value.max; 81 | self.secondSlider.minimumValue = value.min; 82 | self.secondSlider.value = value.current; 83 | } 84 | 85 | if (index == 2) { 86 | self.thirdSlider.hidden = NO; 87 | self.thirdLabel.hidden = NO; 88 | self.thirdLabel.text = self.filterModel.propertyName[index]; 89 | self.thirdSlider.maximumValue = value.max; 90 | self.thirdSlider.minimumValue = value.min; 91 | self.thirdSlider.value = value.current; 92 | } 93 | } 94 | } 95 | 96 | - (IBAction)sliderValueChange:(UISlider *)sender { 97 | if (sender == self.firstSlider) { 98 | [(NSObject *)self.filterModel.filter setValue:@(sender.value) forKey:self.filterModel.propertyName[0]]; 99 | } 100 | 101 | if (sender == self.secondSlider) { 102 | [(NSObject *)self.filterModel.filter setValue:@(sender.value) forKey:self.filterModel.propertyName[1]]; 103 | } 104 | 105 | if (sender == self.thirdSlider) { 106 | [(NSObject *)self.filterModel.filter setValue:@(sender.value) forKey:self.filterModel.propertyName[2]]; 107 | } 108 | 109 | if (self.usePicture) { 110 | [self.picture processImage]; 111 | } 112 | } 113 | 114 | - (void)viewDidLayoutSubviews { 115 | [super viewDidLayoutSubviews]; 116 | 117 | if (self.usePicture) { 118 | [self.picture processImage]; 119 | } 120 | } 121 | 122 | - (MetalImageCamera *)camera { 123 | if (!_camera) { 124 | _camera = [[MetalImageCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack]; 125 | } 126 | return _camera; 127 | } 128 | 129 | - (MetalImagePicture *)picture { 130 | if (!_picture) { 131 | _picture = [[MetalImagePicture alloc] initWithImage:[UIImage imageNamed:@"1.jpg"]]; 132 | } 133 | return _picture; 134 | } 135 | @end 136 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | 相机 25 | NSMicrophoneUsageDescription 26 | 麦克风 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/MPSFilterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MPSFilterViewController.h 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2019/5/20. 6 | // Copyright © 2019 David. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | typedef NS_ENUM(NSInteger, MPSFilterType) { 15 | MPSFilterTypeEdgeDetection, 16 | MPSFilterTypeGaussianBlur 17 | }; 18 | 19 | @interface MPSFilterViewController : UIViewController 20 | 21 | + (instancetype)filterWithType:(MPSFilterType)type; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/MPSFilterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MPSFilterViewController.m 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2019/5/20. 6 | // Copyright © 2019 David. All rights reserved. 7 | // 8 | 9 | #import "MPSFilterViewController.h" 10 | #import 11 | #import 12 | #import 13 | #import 14 | #import "MetalImageTexture.h" 15 | 16 | @interface MPSFilterViewController () 17 | @property (weak, nonatomic) IBOutlet UIImageView *imageView; 18 | @property (nonatomic, assign) MPSFilterType type; 19 | 20 | @property (nonatomic, strong) id device; 21 | @property (nonatomic, strong) id commandQueue; 22 | @property (nonatomic, strong) id sourceTexture; 23 | @property (nonatomic, strong) id destinationTexture; 24 | 25 | @property (nonatomic, strong) MPSKernel *mpsFilter; 26 | @end 27 | 28 | @implementation MPSFilterViewController 29 | 30 | + (instancetype)filterWithType:(MPSFilterType)type { 31 | MPSFilterViewController *vc = [[MPSFilterViewController alloc] init]; 32 | vc.type = type; 33 | return vc; 34 | } 35 | 36 | - (id)device { 37 | if (!_device) { 38 | _device = MTLCreateSystemDefaultDevice(); 39 | } 40 | return _device; 41 | } 42 | 43 | - (id)commandQueue { 44 | if (!_commandQueue) { 45 | _commandQueue = [self.device newCommandQueue]; 46 | } 47 | return _commandQueue; 48 | } 49 | 50 | - (id)sourceTexture { 51 | if (!_sourceTexture) { 52 | MTKTextureLoader *textureLoader = [[MTKTextureLoader alloc] initWithDevice:self.device]; 53 | _sourceTexture = [textureLoader newTextureWithCGImage:[[UIImage imageNamed:@"1.jpg"] CGImage] 54 | options:NULL 55 | error:nil]; 56 | } 57 | return _sourceTexture; 58 | } 59 | 60 | - (id)destinationTexture { 61 | if (!_destinationTexture) { 62 | MTLTextureDescriptor *textureDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:self.sourceTexture.pixelFormat 63 | width:self.sourceTexture.width 64 | height:self.sourceTexture.height 65 | mipmapped:NO]; 66 | textureDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite | MTLTextureUsageRenderTarget; 67 | _destinationTexture = [self.device newTextureWithDescriptor:textureDesc]; 68 | } 69 | return _destinationTexture; 70 | } 71 | 72 | - (MPSKernel *)mpsFilter { 73 | if (!_mpsFilter) { 74 | switch (self.type) { 75 | case MPSFilterTypeEdgeDetection: { 76 | const float weights[] = { 77 | -1, 0, 1, 78 | -2, 0, 2, 79 | -1, 0, 1 80 | }; 81 | MPSImageConvolution *convolition = [[MPSImageConvolution alloc] initWithDevice:self.device 82 | kernelWidth:3 83 | kernelHeight:3 84 | weights:weights]; 85 | _mpsFilter = convolition; 86 | break; 87 | } 88 | case MPSFilterTypeGaussianBlur: { 89 | MPSImageGaussianBlur *gaussian = [[MPSImageGaussianBlur alloc] initWithDevice:self.device sigma:10]; 90 | _mpsFilter = gaussian; 91 | break; 92 | } 93 | 94 | default: 95 | break; 96 | } 97 | } 98 | return _mpsFilter; 99 | } 100 | 101 | #pragma mark - 102 | - (void)viewDidLoad { 103 | [super viewDidLoad]; 104 | self.imageView.contentMode = UIViewContentModeScaleAspectFill; 105 | [self processImageWithType:self.type]; 106 | } 107 | 108 | - (void)processImageWithType:(MPSFilterType)type { 109 | id commandBuffer = [self.commandQueue commandBuffer]; 110 | [commandBuffer enqueue]; 111 | 112 | switch (type) { 113 | case MPSFilterTypeEdgeDetection: { 114 | [(MPSImageConvolution *)self.mpsFilter encodeToCommandBuffer:commandBuffer 115 | sourceTexture:self.sourceTexture 116 | destinationTexture:self.destinationTexture]; 117 | break; 118 | } 119 | case MPSFilterTypeGaussianBlur: { 120 | [(MPSImageGaussianBlur *)self.mpsFilter encodeToCommandBuffer:commandBuffer 121 | sourceTexture:self.sourceTexture 122 | destinationTexture:self.destinationTexture]; 123 | break; 124 | } 125 | 126 | default: 127 | break; 128 | } 129 | 130 | [commandBuffer addCompletedHandler:^(id _Nonnull buffer) { 131 | dispatch_async(dispatch_get_main_queue(), ^{ 132 | self.imageView.image = [MetalImageTexture imageFromMTLTexture:self.destinationTexture]; 133 | }); 134 | }]; 135 | [commandBuffer commit]; 136 | } 137 | 138 | @end 139 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/MPSFilterViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/MetalImageDemo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import 6 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/OSSignPostViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OSSignPostViewController.swift 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2019/6/28. 6 | // Copyright © 2019 David. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import os.signpost 11 | import MetalImage 12 | 13 | @available(iOS 12.0, *) 14 | class OSSignPostViewController: UIViewController { 15 | 16 | @IBOutlet weak var frameView: MetalImageView! 17 | 18 | private lazy var picture: MetalImagePicture = { 19 | return MetalImagePicture.init(image: UIImage.init(named: "1.jpg")!) 20 | }() 21 | 22 | private lazy var camera: MetalImageCamera = { 23 | return MetalImageCamera.init(sessionPreset: AVCaptureSession.Preset.hd1920x1080, cameraPosition: AVCaptureDevice.Position.back) 24 | }() 25 | 26 | private lazy var luminanceFilter: MetalImageLuminanceFilter = { 27 | let filter = MetalImageLuminanceFilter.init() 28 | filter.rangeReductionFactor = -0.5 29 | return filter 30 | }() 31 | 32 | private lazy var saturationFilter: MetalImageSaturationFilter = { 33 | let filter = MetalImageSaturationFilter.init() 34 | filter.saturation = 0.3 35 | return filter 36 | }() 37 | 38 | private lazy var gaussianFilter: MetalImageGaussianBlurFilter = { 39 | let filter = MetalImageGaussianBlurFilter.init() 40 | filter.blurRadiusInPixels = 8.0 41 | filter.texelSpacingMultiplier = 1.0 42 | return filter 43 | }() 44 | 45 | override func viewDidLoad() { 46 | super.viewDidLoad() 47 | camera.add(saturationFilter) 48 | saturationFilter.add(gaussianFilter) 49 | gaussianFilter.add(luminanceFilter) 50 | luminanceFilter.add(frameView) 51 | 52 | let filterChainProcessBlock: MetalImageFilterBlock = {(beforeFilter, textureReousrece, filter) in 53 | struct StaticVar { static var postId = OSSignpostID(log: .metalImage) } 54 | if beforeFilter { 55 | StaticVar.postId = OSSignpostID(log: .metalImage) 56 | os_signpost(.begin, log: .metalImage, name: "Filter", signpostID: StaticVar.postId, "b_%s", NSStringFromClass(filter.classForCoder)) 57 | return; 58 | } 59 | os_signpost(.end, log: .metalImage, name: "Filter", signpostID: StaticVar.postId, "e_%s", NSStringFromClass(filter.classForCoder)) 60 | } 61 | 62 | saturationFilter.filterChainProcessHook = filterChainProcessBlock 63 | gaussianFilter.filterChainProcessHook = filterChainProcessBlock 64 | luminanceFilter.filterChainProcessHook = filterChainProcessBlock 65 | camera.startCapture() 66 | } 67 | 68 | private func processImage() { 69 | luminanceFilter.rangeReductionFactor = Float.random(in: -1.0..<(-0.3)) 70 | saturationFilter.saturation = Float.random(in: 0.0..<2.0) 71 | 72 | os_signpost(.begin, log: .metalImage, name: "Process") 73 | picture.processImage(byFilters: [saturationFilter, gaussianFilter, luminanceFilter]) { [weak self] (textureResource) in 74 | guard textureResource != nil else { 75 | return 76 | } 77 | os_signpost(.end, log: .metalImage, name: "Process") 78 | self?.frameView.receive(textureResource!, with: CMTime.invalid) 79 | } 80 | } 81 | } 82 | 83 | extension OSLog { 84 | @available(iOS 10.0, *) 85 | static let metalImage = OSLog(subsystem: "com.metalImage", category: "Test") 86 | } 87 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/OSSignPostViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/RecordViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RecordViewController.h 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2019/5/13. 6 | // Copyright © 2019 David. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface RecordViewController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/RecordViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RecordViewController.m 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2019/5/13. 6 | // Copyright © 2019 David. All rights reserved. 7 | // 8 | 9 | #import "RecordViewController.h" 10 | #import "MetalImageView.h" 11 | #import "MetalImageCamera.h" 12 | #import "MetalImageMovieWriter.h" 13 | #import "MetalImageiOSBlurFilter.h" 14 | 15 | @interface RecordViewController () 16 | @property (weak, nonatomic) IBOutlet UIButton *recordBtn; 17 | @property (weak, nonatomic) IBOutlet MetalImageView *frameView; 18 | @property (nonatomic, strong) MetalImageCamera *camera; 19 | @property (nonatomic, strong) MetalImageMovieWriter *movieWriter; 20 | @end 21 | 22 | @implementation RecordViewController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | [self.camera addTarget:self.frameView]; 27 | [self.camera startCapture]; 28 | } 29 | 30 | - (IBAction)actionRecord:(id)sender { 31 | if ([sender isSelected]) { 32 | if (self.movieWriter.status == AVAssetWriterStatusWriting) { 33 | [self.movieWriter finishRecording]; 34 | [self.camera removeTarget:self.movieWriter]; 35 | self.movieWriter = nil; 36 | } 37 | 38 | [self.recordBtn setTitle:@"开始录制" forState:UIControlStateNormal]; 39 | [sender setSelected:NO]; 40 | } 41 | else { 42 | if (self.movieWriter.status != AVAssetWriterStatusWriting) { 43 | [self.camera addTarget:self.movieWriter]; 44 | [self.movieWriter startRecording]; 45 | } 46 | 47 | [self.recordBtn setTitle:@"停止录制" forState:UIControlStateNormal]; 48 | [sender setSelected:YES]; 49 | } 50 | } 51 | 52 | - (MetalImageCamera *)camera { 53 | if (!_camera) { 54 | _camera = [[MetalImageCamera alloc] initWithSessionPreset:AVCaptureSessionPreset1920x1080 cameraPosition:AVCaptureDevicePositionBack]; 55 | } 56 | return _camera; 57 | } 58 | 59 | - (MetalImageMovieWriter *)movieWriter { 60 | if (!_movieWriter) { 61 | NSString *videoFileDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]; 62 | NSString *videoFilePath = [videoFileDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"video_%d.mp4",0]]; 63 | NSLog(@"videoFilePath = %@",videoFilePath); 64 | NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:videoFilePath]; 65 | [self removeFile:outputURL]; 66 | 67 | _movieWriter = [[MetalImageMovieWriter alloc] initWithStorageUrl:outputURL size:CGSizeMake(1080, 640)]; 68 | _movieWriter.fillMode = MetalImageContentModeScaleAspectFit; 69 | _movieWriter.backgroundColor = [UIColor colorWithWhite:1 alpha:1.0]; 70 | } 71 | return _movieWriter; 72 | } 73 | 74 | - (void)removeFile:(NSURL *)fileURL { 75 | NSFileManager *fileManager = [NSFileManager defaultManager]; 76 | NSString *filePath = [fileURL path]; 77 | if ([fileManager fileExistsAtPath:filePath]) { 78 | NSError *error; 79 | [fileManager removeItemAtPath:filePath error:&error]; 80 | } 81 | } 82 | @end 83 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/RecordViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2018/11/29. 6 | // Copyright © 2018 David. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2018/11/29. 6 | // Copyright © 2018 David. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "BasicViewController.h" 11 | #import "RecordViewController.h" 12 | #import "FilterViewController.h" 13 | #import "MPSFilterViewController.h" 14 | #import "MetalImageDemo-Swift.h" 15 | #import "NSBundle+MetalImageBundle.h" 16 | 17 | @interface ViewController () 18 | @property (weak, nonatomic) IBOutlet UITableView *tableView; 19 | @property (nonatomic, strong) NSArray *dataSource; 20 | @end 21 | 22 | @implementation ViewController 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | 26 | self.tableView.delegate = self; 27 | self.tableView.dataSource = self; 28 | } 29 | 30 | - (NSArray *)dataSource { 31 | if (!_dataSource) { 32 | _dataSource = @[@[@"相机/图片显示", @"录制", @"性能调试"], 33 | @[@"饱和度", @"对比度", @"亮度", @"色调", @"锐化", @"高斯模糊", @"毛玻璃", @"边缘检测", @"LUT"], 34 | @[@"边缘检测", @"高斯模糊"]]; 35 | } 36 | return _dataSource; 37 | } 38 | 39 | #pragma mark - 选择列表回调 40 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 41 | if (section == 0) { 42 | return @"基础功能"; 43 | } 44 | 45 | if (section == 1) { 46 | return @"拓展滤镜效果"; 47 | } 48 | 49 | if (section == 2) { 50 | return @"MPS滤镜效果"; 51 | } 52 | 53 | return @""; 54 | } 55 | 56 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 57 | return self.dataSource.count; 58 | } 59 | 60 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 61 | return [self.dataSource objectAtIndex:section].count; 62 | } 63 | 64 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 65 | UITableViewCell *cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"OptionCell"]; 66 | cell.selectionStyle = UITableViewCellSelectionStyleDefault; 67 | cell.textLabel.font = [UIFont fontWithName:@"PingFangSC-Regular" size:14]; 68 | cell.textLabel.text = [[self.dataSource objectAtIndex:indexPath.section] objectAtIndex:indexPath.row]; 69 | return cell; 70 | } 71 | 72 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 73 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 74 | [self tableViewOptionEvent:indexPath.row section:indexPath.section]; 75 | } 76 | 77 | #pragma mark - 选择事件内部逻辑 78 | - (void)tableViewOptionEvent:(NSInteger)index section:(NSInteger)section { 79 | // 基础功能 80 | if (section == 0) { 81 | switch (index) { 82 | case 0: { 83 | BasicViewController *vc = [[BasicViewController alloc] initWithNibName:@"BasicViewController" bundle:nil]; 84 | [self.navigationController pushViewController:vc animated:YES]; 85 | break; 86 | } 87 | 88 | case 1: { 89 | RecordViewController *vc = [[RecordViewController alloc] initWithNibName:@"RecordViewController" bundle:nil]; 90 | [self.navigationController pushViewController:vc animated:YES]; 91 | break; 92 | } 93 | 94 | case 2: { 95 | if (@available(iOS 12.0, *)) { 96 | OSSignPostViewController *vc = [[OSSignPostViewController alloc] initWithNibName:@"OSSignPostViewController" bundle:nil]; 97 | [self.navigationController pushViewController:vc animated:YES]; 98 | } 99 | break; 100 | } 101 | default: 102 | break; 103 | } 104 | } 105 | 106 | // 拓展滤镜 107 | if (section == 1) { 108 | id filter = nil; 109 | NSArray *effectPropertyName = nil; 110 | NSArray *values = nil; 111 | BOOL usePicture = NO; 112 | switch (index) { 113 | case 0: { 114 | filter = [[MetalImageSaturationFilter alloc] init]; 115 | effectPropertyName = @[@"saturation"]; 116 | FileterNumericalValue value1 = {0.0, 2.0, 1.0}; 117 | values = @[[NSValue value:&value1 withObjCType:@encode(FileterNumericalValue)]]; 118 | break; 119 | } 120 | case 1: { 121 | filter = [[MetalImageContrastFilter alloc] init]; 122 | effectPropertyName = @[@"contrast"]; 123 | FileterNumericalValue value1 = {0.0, 2.0, 1.0}; 124 | values = @[[NSValue value:&value1 withObjCType:@encode(FileterNumericalValue)]]; 125 | break; 126 | } 127 | case 2: { 128 | filter = [[MetalImageLuminanceFilter alloc] init]; 129 | effectPropertyName = @[@"rangeReductionFactor"]; 130 | FileterNumericalValue value1 = {-1.0, 1.0, 0.0}; 131 | values = @[[NSValue value:&value1 withObjCType:@encode(FileterNumericalValue)]]; 132 | break; 133 | } 134 | case 3: { 135 | filter = [[MetalImageHueFilter alloc] init]; 136 | effectPropertyName = @[@"hue"]; 137 | FileterNumericalValue value1 = {-1.0, 1.0, 0.0}; 138 | values = @[[NSValue value:&value1 withObjCType:@encode(FileterNumericalValue)]]; 139 | break; 140 | } 141 | case 4: { 142 | filter = [[MetalImageSharpenFilter alloc] init]; 143 | effectPropertyName = @[@"sharpness"]; 144 | FileterNumericalValue value1 = {-4.0, 4.0, 0.0}; 145 | values = @[[NSValue value:&value1 withObjCType:@encode(FileterNumericalValue)]]; 146 | break; 147 | } 148 | case 5: { 149 | filter = [[MetalImageGaussianBlurFilter alloc] init]; 150 | effectPropertyName = @[@"blurRadiusInPixels", @"texelSpacingMultiplier"]; 151 | FileterNumericalValue value1 = {0.0, 8.0, 4.0}; 152 | FileterNumericalValue value2 = {0.0, 15.0, 2.0}; 153 | values = @[[NSValue value:&value1 withObjCType:@encode(FileterNumericalValue)], 154 | [NSValue value:&value2 withObjCType:@encode(FileterNumericalValue)]]; 155 | break; 156 | } 157 | case 6: { 158 | filter = [[MetalImageiOSBlurFilter alloc] init]; 159 | effectPropertyName = @[@"blurRadiusInPixels", @"saturation", @"luminance"]; 160 | FileterNumericalValue value1 = {0.0, 16.0, 8.0}; 161 | FileterNumericalValue value2 = {-1.0, 1.0, 0.0}; 162 | FileterNumericalValue value3 = {-1.0, 1.0, 0.0}; 163 | values = @[[NSValue value:&value1 withObjCType:@encode(FileterNumericalValue)], 164 | [NSValue value:&value2 withObjCType:@encode(FileterNumericalValue)], 165 | [NSValue value:&value3 withObjCType:@encode(FileterNumericalValue)]]; 166 | break; 167 | } 168 | case 7: { 169 | const float weights[] = { 170 | -1, 0, 1, 171 | -2, 0, 2, 172 | -1, 0, 1 173 | }; 174 | filter = [MetalImageConvolutionFilter filterWithKernelWidth:3 kernelHeight:3 weights:weights]; 175 | usePicture = YES; 176 | break; 177 | } 178 | case 8: { 179 | NSString *bundlePath = [NSBundle metalImage_bundleWithName:MetalImageBundleName].bundlePath; 180 | NSString *filePath = [bundlePath stringByAppendingPathComponent:@"8*8_effect.png"]; 181 | UIImage *lutImage = [UIImage imageWithContentsOfFile:filePath]; 182 | id lutTexture = [[MetalImageDevice shared].textureLoader newTextureWithCGImage:lutImage.CGImage options:nil error:nil]; 183 | filter = [[MetalImageLookUpTableFilter alloc] initWithLutTexture:lutTexture type:MetalImageLUTFilterType8_8]; 184 | effectPropertyName = @[@"intensity"]; 185 | FileterNumericalValue value1 = {0.0, 1.0, 0.0}; 186 | values = @[[NSValue value:&value1 withObjCType:@encode(FileterNumericalValue)]]; 187 | usePicture = YES; 188 | break; 189 | } 190 | default: 191 | break; 192 | } 193 | 194 | if (filter) { 195 | FilterModel *model = [FilterModel filter:filter effectProperty:effectPropertyName value:values]; 196 | FilterViewController *filterVC = [FilterViewController filterVCWithModel:model]; 197 | filterVC.usePicture = usePicture; 198 | [self.navigationController pushViewController:filterVC animated:YES]; 199 | } 200 | } 201 | 202 | // MPS滤镜 203 | if (section == 2) { 204 | MPSFilterViewController *filterVC = [MPSFilterViewController filterWithType:index]; 205 | [self.navigationController pushViewController:filterVC animated:YES]; 206 | } 207 | } 208 | 209 | @end 210 | -------------------------------------------------------------------------------- /iOS/MetalImageDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MetalImageDemo 4 | // 5 | // Created by David.Dai on 2018/11/29. 6 | // Copyright © 2018 David. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /iOS/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | install! 'cocoapods', :disable_input_output_paths => true 5 | 6 | target 'MetalImageDemo' do 7 | use_frameworks! 8 | pod 'MetalImage', :path => '../MetalImage.podspec', :subspecs => ['Core', 'ExtensionFilter'] 9 | end 10 | -------------------------------------------------------------------------------- /iOS/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - MetalImage/Core (0.0.1): 3 | - MetalImage/Core/Basic (= 0.0.1) 4 | - MetalImage/Core/Category (= 0.0.1) 5 | - MetalImage/Core/Filter (= 0.0.1) 6 | - MetalImage/Core/Source (= 0.0.1) 7 | - MetalImage/Core/Target (= 0.0.1) 8 | - MetalImage/Core/Basic (0.0.1) 9 | - MetalImage/Core/Category (0.0.1) 10 | - MetalImage/Core/Filter (0.0.1) 11 | - MetalImage/Core/Source (0.0.1) 12 | - MetalImage/Core/Target (0.0.1) 13 | - MetalImage/ExtensionFilter (0.0.1): 14 | - MetalImage/Core 15 | 16 | DEPENDENCIES: 17 | - MetalImage/Core (from `../MetalImage.podspec`) 18 | - MetalImage/ExtensionFilter (from `../MetalImage.podspec`) 19 | 20 | EXTERNAL SOURCES: 21 | MetalImage: 22 | :path: "../MetalImage.podspec" 23 | 24 | SPEC CHECKSUMS: 25 | MetalImage: ff5cff00bf12b2fa20d0c862bd7a351d90f9c8a6 26 | 27 | PODFILE CHECKSUM: 1da4ed937fefd8252589a9f564c6d8c68bd606bc 28 | 29 | COCOAPODS: 1.7.5 30 | -------------------------------------------------------------------------------- /iOS/Pods/Local Podspecs/MetalImage.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MetalImage", 3 | "version": "0.0.1", 4 | "summary": "A short description of MetalImage.", 5 | "description": "MetalImage 为 iOS 平台上一个简单的Metal滤镜处理框架", 6 | "homepage": "https://github.com/hello-david/MetalImage.git", 7 | "license": { 8 | "type": "Copyright", 9 | "text": " © 2008-2018 David.Dai. All rights reserved.\n" 10 | }, 11 | "authors": { 12 | "david.dai": "hello.david.me@gmail.com" 13 | }, 14 | "platforms": { 15 | "ios": "9.0" 16 | }, 17 | "source": { 18 | "git": "https://github.com/hello-david/MetalImage.git", 19 | "tag": "0.0.1" 20 | }, 21 | "public_header_files": "MetalImage/MetalImage.h", 22 | "default_subspecs": "Core", 23 | "requires_arc": true, 24 | "subspecs": [ 25 | { 26 | "name": "Core", 27 | "source_files": "MetalImage/MetalImage.h", 28 | "resource_bundles": { 29 | "MetalLibrary": [ 30 | "MetalImage/Library/*.metal", 31 | "MetalImage/Resource/*" 32 | ] 33 | }, 34 | "frameworks": [ 35 | "Metal", 36 | "MetalKit", 37 | "CoreVideo", 38 | "AVFoundation" 39 | ], 40 | "subspecs": [ 41 | { 42 | "name": "Basic", 43 | "public_header_files": "MetalImage/Basic/**/*.{h}", 44 | "source_files": "MetalImage/Basic/**/*.{h,m,cpp,mm}" 45 | }, 46 | { 47 | "name": "Source", 48 | "public_header_files": "MetalImage/Source/**/*.{h}", 49 | "source_files": "MetalImage/Source/**/*.{h,m,cpp,mm}" 50 | }, 51 | { 52 | "name": "Filter", 53 | "public_header_files": "MetalImage/Filter/**/*.{h}", 54 | "source_files": "MetalImage/Filter/**/*.{h,m,cpp,mm}" 55 | }, 56 | { 57 | "name": "Target", 58 | "public_header_files": "MetalImage/Target/**/*.{h}", 59 | "source_files": "MetalImage/Target/**/*.{h,m,cpp,mm}" 60 | }, 61 | { 62 | "name": "Category", 63 | "public_header_files": "MetalImage/Category/**/*.{h}", 64 | "source_files": "MetalImage/Category/**/*.{h,m,cpp,mm}" 65 | } 66 | ] 67 | }, 68 | { 69 | "name": "ExtensionFilter", 70 | "public_header_files": "MetalImage/ExtensionFilter/**/*.{h}", 71 | "source_files": "MetalImage/ExtensionFilter/**/*.{h,m,cpp,mm}", 72 | "dependencies": { 73 | "MetalImage/Core": [ 74 | 75 | ] 76 | } 77 | } 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /iOS/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - MetalImage/Core (0.0.1): 3 | - MetalImage/Core/Basic (= 0.0.1) 4 | - MetalImage/Core/Category (= 0.0.1) 5 | - MetalImage/Core/Filter (= 0.0.1) 6 | - MetalImage/Core/Source (= 0.0.1) 7 | - MetalImage/Core/Target (= 0.0.1) 8 | - MetalImage/Core/Basic (0.0.1) 9 | - MetalImage/Core/Category (0.0.1) 10 | - MetalImage/Core/Filter (0.0.1) 11 | - MetalImage/Core/Source (0.0.1) 12 | - MetalImage/Core/Target (0.0.1) 13 | - MetalImage/ExtensionFilter (0.0.1): 14 | - MetalImage/Core 15 | 16 | DEPENDENCIES: 17 | - MetalImage/Core (from `../MetalImage.podspec`) 18 | - MetalImage/ExtensionFilter (from `../MetalImage.podspec`) 19 | 20 | EXTERNAL SOURCES: 21 | MetalImage: 22 | :path: "../MetalImage.podspec" 23 | 24 | SPEC CHECKSUMS: 25 | MetalImage: ff5cff00bf12b2fa20d0c862bd7a351d90f9c8a6 26 | 27 | PODFILE CHECKSUM: 1da4ed937fefd8252589a9f564c6d8c68bd606bc 28 | 29 | COCOAPODS: 1.7.5 30 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/MetalImage/MetalImage-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.0.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/MetalImage/MetalImage-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_MetalImage : NSObject 3 | @end 4 | @implementation PodsDummy_MetalImage 5 | @end 6 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/MetalImage/MetalImage-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/MetalImage/MetalImage-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | #import "MetalImage.h" 14 | #import "MetalImageDevice.h" 15 | #import "MetalImageProtocol.h" 16 | #import "MetalImageRenderProcess.h" 17 | #import "MetalImageResource.h" 18 | #import "MetalImageTexture.h" 19 | #import "MetalImageTextureCache.h" 20 | #import "NSBundle+MetalImageBundle.h" 21 | #import "MetalImageFilter.h" 22 | #import "MetalImageCamera.h" 23 | #import "MetalImagePicture.h" 24 | #import "MetalImageSource.h" 25 | #import "MetalImageMovieWriter.h" 26 | #import "MetalImageTarget.h" 27 | #import "MetalImageView.h" 28 | #import "MetalImageCropFilter.h" 29 | #import "MetalImageiOSBlurFilter.h" 30 | #import "MetalImageConvolutionFilter.h" 31 | #import "MetalImageGaussianBlurFilter.h" 32 | #import "MetalImageSharpenFilter.h" 33 | #import "MetalImageContrastFilter.h" 34 | #import "MetalImageHueFilter.h" 35 | #import "MetalImageLookUpTableFilter.h" 36 | #import "MetalImageLuminanceFilter.h" 37 | #import "MetalImageSaturationFilter.h" 38 | 39 | FOUNDATION_EXPORT double MetalImageVersionNumber; 40 | FOUNDATION_EXPORT const unsigned char MetalImageVersionString[]; 41 | 42 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/MetalImage/MetalImage.modulemap: -------------------------------------------------------------------------------- 1 | framework module MetalImage { 2 | umbrella header "MetalImage-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/MetalImage/MetalImage.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MetalImage 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "CoreVideo" -framework "Metal" -framework "MetalKit" 4 | PODS_BUILD_DIR = ${BUILD_DIR} 5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/MetalImage/ResourceBundle-MTMetalImageBundle-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 0.0.1 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/MetalImage/ResourceBundle-MetalImageBundle-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 0.0.1 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/MetalImage/ResourceBundle-MetalImageBundle-MetalImage-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 0.0.1 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/MetalImage/ResourceBundle-MetalImageLibrary-MetalImage-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 0.0.1 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/MetalImage/ResourceBundle-MetalLibrary-MetalImage-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 0.0.1 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## MetalImage 5 | 6 | © 2008-2018 David.Dai. All rights reserved. 7 | 8 | Generated by CocoaPods - https://cocoapods.org 9 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | © 2008-2018 David.Dai. All rights reserved. 18 | 19 | License 20 | Copyright 21 | Title 22 | MetalImage 23 | Type 24 | PSGroupSpecifier 25 | 26 | 27 | FooterText 28 | Generated by CocoaPods - https://cocoapods.org 29 | Title 30 | 31 | Type 32 | PSGroupSpecifier 33 | 34 | 35 | StringsTable 36 | Acknowledgements 37 | Title 38 | Acknowledgements 39 | 40 | 41 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_MetalImageDemo : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_MetalImageDemo 5 | @end 6 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-frameworks-Debug-input-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${PODS_ROOT}/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-frameworks.sh 2 | ${BUILT_PRODUCTS_DIR}/MetalImage/MetalImage.framework -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-frameworks-Debug-output-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MetalImage.framework -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-frameworks-Release-input-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${PODS_ROOT}/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-frameworks.sh 2 | ${BUILT_PRODUCTS_DIR}/MetalImage/MetalImage.framework -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-frameworks-Release-output-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MetalImage.framework -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | 23 | # Used as a return value for each invocation of `strip_invalid_archs` function. 24 | STRIP_BINARY_RETVAL=0 25 | 26 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 27 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 28 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 29 | 30 | # Copies and strips a vendored framework 31 | install_framework() 32 | { 33 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 34 | local source="${BUILT_PRODUCTS_DIR}/$1" 35 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 36 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 37 | elif [ -r "$1" ]; then 38 | local source="$1" 39 | fi 40 | 41 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 42 | 43 | if [ -L "${source}" ]; then 44 | echo "Symlinked..." 45 | source="$(readlink "${source}")" 46 | fi 47 | 48 | # Use filter instead of exclude so missing patterns don't throw errors. 49 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 50 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 51 | 52 | local basename 53 | basename="$(basename -s .framework "$1")" 54 | binary="${destination}/${basename}.framework/${basename}" 55 | 56 | if ! [ -r "$binary" ]; then 57 | binary="${destination}/${basename}" 58 | elif [ -L "${binary}" ]; then 59 | echo "Destination binary is symlinked..." 60 | dirname="$(dirname "${binary}")" 61 | binary="${dirname}/$(readlink "${binary}")" 62 | fi 63 | 64 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 65 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 66 | strip_invalid_archs "$binary" 67 | fi 68 | 69 | # Resign the code if required by the build settings to avoid unstable apps 70 | code_sign_if_enabled "${destination}/$(basename "$1")" 71 | 72 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 73 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 74 | local swift_runtime_libs 75 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 76 | for lib in $swift_runtime_libs; do 77 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 78 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 79 | code_sign_if_enabled "${destination}/${lib}" 80 | done 81 | fi 82 | } 83 | 84 | # Copies and strips a vendored dSYM 85 | install_dsym() { 86 | local source="$1" 87 | if [ -r "$source" ]; then 88 | # Copy the dSYM into a the targets temp dir. 89 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 90 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 91 | 92 | local basename 93 | basename="$(basename -s .framework.dSYM "$source")" 94 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" 95 | 96 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 97 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 98 | strip_invalid_archs "$binary" 99 | fi 100 | 101 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then 102 | # Move the stripped file into its final destination. 103 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 104 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 105 | else 106 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 107 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" 108 | fi 109 | fi 110 | } 111 | 112 | # Copies the bcsymbolmap files of a vendored framework 113 | install_bcsymbolmap() { 114 | local bcsymbolmap_path="$1" 115 | local destination="${BUILT_PRODUCTS_DIR}" 116 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 117 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 118 | } 119 | 120 | # Signs a framework with the provided identity 121 | code_sign_if_enabled() { 122 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 123 | # Use the current code_sign_identity 124 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 125 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 126 | 127 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 128 | code_sign_cmd="$code_sign_cmd &" 129 | fi 130 | echo "$code_sign_cmd" 131 | eval "$code_sign_cmd" 132 | fi 133 | } 134 | 135 | # Strip invalid architectures 136 | strip_invalid_archs() { 137 | binary="$1" 138 | # Get architectures for current target binary 139 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 140 | # Intersect them with the architectures we are building for 141 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 142 | # If there are no archs supported by this binary then warn the user 143 | if [[ -z "$intersected_archs" ]]; then 144 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 145 | STRIP_BINARY_RETVAL=0 146 | return 147 | fi 148 | stripped="" 149 | for arch in $binary_archs; do 150 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 151 | # Strip non-valid architectures in-place 152 | lipo -remove "$arch" -output "$binary" "$binary" 153 | stripped="$stripped $arch" 154 | fi 155 | done 156 | if [[ "$stripped" ]]; then 157 | echo "Stripped $binary of architectures:$stripped" 158 | fi 159 | STRIP_BINARY_RETVAL=1 160 | } 161 | 162 | 163 | if [[ "$CONFIGURATION" == "Debug" ]]; then 164 | install_framework "${BUILT_PRODUCTS_DIR}/MetalImage/MetalImage.framework" 165 | fi 166 | if [[ "$CONFIGURATION" == "Release" ]]; then 167 | install_framework "${BUILT_PRODUCTS_DIR}/MetalImage/MetalImage.framework" 168 | fi 169 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 170 | wait 171 | fi 172 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-resources-Debug-input-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${PODS_ROOT}/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-resources.sh 2 | ${PODS_CONFIGURATION_BUILD_DIR}/MetalImage/MetalLibrary.bundle -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-resources-Debug-output-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MetalLibrary.bundle -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-resources-Release-input-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${PODS_ROOT}/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-resources.sh 2 | ${PODS_CONFIGURATION_BUILD_DIR}/MetalImage/MetalLibrary.bundle -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-resources-Release-output-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MetalLibrary.bundle -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then 12 | # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # resources to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 18 | 19 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 20 | > "$RESOURCES_TO_COPY" 21 | 22 | XCASSET_FILES=() 23 | 24 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 25 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 26 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 27 | 28 | case "${TARGETED_DEVICE_FAMILY:-}" in 29 | 1,2) 30 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 31 | ;; 32 | 1) 33 | TARGET_DEVICE_ARGS="--target-device iphone" 34 | ;; 35 | 2) 36 | TARGET_DEVICE_ARGS="--target-device ipad" 37 | ;; 38 | 3) 39 | TARGET_DEVICE_ARGS="--target-device tv" 40 | ;; 41 | 4) 42 | TARGET_DEVICE_ARGS="--target-device watch" 43 | ;; 44 | *) 45 | TARGET_DEVICE_ARGS="--target-device mac" 46 | ;; 47 | esac 48 | 49 | install_resource() 50 | { 51 | if [[ "$1" = /* ]] ; then 52 | RESOURCE_PATH="$1" 53 | else 54 | RESOURCE_PATH="${PODS_ROOT}/$1" 55 | fi 56 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 57 | cat << EOM 58 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 59 | EOM 60 | exit 1 61 | fi 62 | case $RESOURCE_PATH in 63 | *.storyboard) 64 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 65 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 66 | ;; 67 | *.xib) 68 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 69 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 70 | ;; 71 | *.framework) 72 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 73 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 74 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 75 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 76 | ;; 77 | *.xcdatamodel) 78 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 79 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 80 | ;; 81 | *.xcdatamodeld) 82 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 83 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 84 | ;; 85 | *.xcmappingmodel) 86 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 87 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 88 | ;; 89 | *.xcassets) 90 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 91 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 92 | ;; 93 | *) 94 | echo "$RESOURCE_PATH" || true 95 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 96 | ;; 97 | esac 98 | } 99 | if [[ "$CONFIGURATION" == "Debug" ]]; then 100 | install_resource "${PODS_CONFIGURATION_BUILD_DIR}/MetalImage/MetalLibrary.bundle" 101 | fi 102 | if [[ "$CONFIGURATION" == "Release" ]]; then 103 | install_resource "${PODS_CONFIGURATION_BUILD_DIR}/MetalImage/MetalLibrary.bundle" 104 | fi 105 | 106 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 107 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 108 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 109 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 110 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 111 | fi 112 | rm -f "$RESOURCES_TO_COPY" 113 | 114 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] 115 | then 116 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 117 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 118 | while read line; do 119 | if [[ $line != "${PODS_ROOT}*" ]]; then 120 | XCASSET_FILES+=("$line") 121 | fi 122 | done <<<"$OTHER_XCASSETS" 123 | 124 | if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then 125 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 126 | else 127 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" 128 | fi 129 | fi 130 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_MetalImageDemoVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_MetalImageDemoVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MetalImage" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MetalImage/MetalImage.framework/Headers" 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "CoreVideo" -framework "Metal" -framework "MetalImage" -framework "MetalKit" 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_MetalImageDemo { 2 | umbrella header "Pods-MetalImageDemo-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /iOS/Pods/Target Support Files/Pods-MetalImageDemo/Pods-MetalImageDemo.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MetalImage" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MetalImage/MetalImage.framework/Headers" 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "CoreVideo" -framework "Metal" -framework "MetalImage" -framework "MetalKit" 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | -------------------------------------------------------------------------------- /icon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-david/MetalImage/5724b8a01609b0a18832cdd98caa5c81555a0fce/icon.jpeg --------------------------------------------------------------------------------