From 416755f10695127302077b031ec2133f20182c7c Mon Sep 17 00:00:00 2001 From: SusanDoggie Date: Tue, 9 Jan 2018 12:48:45 +0800 Subject: [PATCH] image convolution filter --- Doggie.xcodeproj/project.pbxproj | 16 + .../Doggie/Accelerate/AccelerateWrapper.swift | 10 + .../Doggie/Accelerate/CircularConvolve.swift | 55 ++-- Sources/Doggie/ImageFilter/GaussianBlur.swift | 54 ++++ .../Doggie/ImageFilter/ImageConvolution.swift | 276 ++++++++++++++++++ 5 files changed, 388 insertions(+), 23 deletions(-) create mode 100644 Sources/Doggie/ImageFilter/GaussianBlur.swift create mode 100644 Sources/Doggie/ImageFilter/ImageConvolution.swift diff --git a/Doggie.xcodeproj/project.pbxproj b/Doggie.xcodeproj/project.pbxproj index 140b6682e..b6cf18663 100644 --- a/Doggie.xcodeproj/project.pbxproj +++ b/Doggie.xcodeproj/project.pbxproj @@ -93,6 +93,8 @@ 0A72EF181F54FED200F0550A /* SFNTCMAP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A72EF171F54FED200F0550A /* SFNTCMAP.swift */; }; 0A72EF1C1F55035200F0550A /* SFNTFontFace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A72EF1B1F55035200F0550A /* SFNTFontFace.swift */; }; 0A7314C01F3D7B720008461E /* ShapeSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7314BF1F3D7B720008461E /* ShapeSegment.swift */; }; + 0A74D68820047BA600330C86 /* ImageConvolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A74D68720047BA600330C86 /* ImageConvolution.swift */; }; + 0A74D68A20047BF700330C86 /* GaussianBlur.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A74D68920047BF700330C86 /* GaussianBlur.swift */; }; 0A776FC61D9A0BA5005FC88F /* c11_atomic.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A776FC31D9A0BA5005FC88F /* c11_atomic.h */; }; 0A78E1F91E98B20A00BD91AB /* Geometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A78E1F71E98B20A00BD91AB /* Geometry.swift */; }; 0A792C751FDA28DB003C0290 /* PathBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A792C741FDA28DB003C0290 /* PathBuilder.swift */; }; @@ -309,6 +311,8 @@ 0A72EF171F54FED200F0550A /* SFNTCMAP.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SFNTCMAP.swift; sourceTree = ""; }; 0A72EF1B1F55035200F0550A /* SFNTFontFace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SFNTFontFace.swift; sourceTree = ""; }; 0A7314BF1F3D7B720008461E /* ShapeSegment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ShapeSegment.swift; sourceTree = ""; }; + 0A74D68720047BA600330C86 /* ImageConvolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageConvolution.swift; sourceTree = ""; }; + 0A74D68920047BF700330C86 /* GaussianBlur.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GaussianBlur.swift; sourceTree = ""; }; 0A776FC31D9A0BA5005FC88F /* c11_atomic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = c11_atomic.h; sourceTree = ""; }; 0A776FC41D9A0BA5005FC88F /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; lineEnding = 0; path = module.modulemap; sourceTree = ""; }; 0A78E1F71E98B20A00BD91AB /* Geometry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Geometry.swift; sourceTree = ""; }; @@ -555,6 +559,7 @@ 08458ACE1E2E627600D01813 /* Image */, 0A94BE9E1F108E8100F1BCD2 /* ImageCodec */, 0A39A6B31ED9342C00804004 /* ImageContext */, + 0A74D68620047B7000330C86 /* ImageFilter */, 08458AD41E2E627600D01813 /* Maths */, 0A9AA83E1E54373E007A2B92 /* PDF */, 0A2107071E5BEEBB00F1E00E /* Shape */, @@ -732,6 +737,15 @@ path = SFNTFontFace; sourceTree = ""; }; + 0A74D68620047B7000330C86 /* ImageFilter */ = { + isa = PBXGroup; + children = ( + 0A74D68720047BA600330C86 /* ImageConvolution.swift */, + 0A74D68920047BF700330C86 /* GaussianBlur.swift */, + ); + path = ImageFilter; + sourceTree = ""; + }; 0A776FC21D9A0BA5005FC88F /* c11_atomic */ = { isa = PBXGroup; children = ( @@ -1220,6 +1234,7 @@ 08458B351E2E627600D01813 /* Ellipse.swift in Sources */, 0A8535351F5937DA006FB85D /* CFFFontFace.swift in Sources */, 08B68A321ED07A2400F8D423 /* ColorSpace.swift in Sources */, + 0A74D68820047BA600330C86 /* ImageConvolution.swift in Sources */, 0A39A6C51ED937C700804004 /* DrawGradient.swift in Sources */, 0A56E9111E35F02E00D93786 /* RectCollection.swift in Sources */, 0A52E2D91E6012140065EED6 /* PDFFilter.swift in Sources */, @@ -1240,6 +1255,7 @@ 0ABDF1AA1F2B34E6007EC7B2 /* ChromaticAdaptationAlgorithm.swift in Sources */, 08458B131E2E627600D01813 /* Fourier.swift in Sources */, 0AD3E6351F53DB6C003CC21A /* OpenTypeDecoder.swift in Sources */, + 0A74D68A20047BF700330C86 /* GaussianBlur.swift in Sources */, 0A72EF181F54FED200F0550A /* SFNTCMAP.swift in Sources */, 08458B611E2E627600D01813 /* SDTask.swift in Sources */, 0AE71A041EF51DF80086923C /* AnyColorSpace.swift in Sources */, diff --git a/Sources/Doggie/Accelerate/AccelerateWrapper.swift b/Sources/Doggie/Accelerate/AccelerateWrapper.swift index bf9b17f76..4279ad050 100644 --- a/Sources/Doggie/Accelerate/AccelerateWrapper.swift +++ b/Sources/Doggie/Accelerate/AccelerateWrapper.swift @@ -159,3 +159,13 @@ public func Radix2CircularConvolve(_ level: Int, _ signal: UnsafePointer, _ in_stride: Int, _ in_count: Int, _ n: Double, _ output: UnsafeMutablePointer, _ out_stride: Int) { input._reboundToDouble { _input in output._reboundToDouble { Radix2PowerCircularConvolve(level, _input, _input.successor(), in_stride << 1, in_count, n, $0, $0.successor(), out_stride << 1) } } } + +@_inlineable +public func Radix2FiniteImpulseFilter(_ level: Int, _ signal: UnsafePointer, _ signal_stride: Int, _ signal_count: Int, _ kernel: UnsafePointer, _ kernel_stride: Int, _ output: UnsafeMutablePointer, _ out_stride: Int) { + kernel._reboundToDouble { Radix2FiniteImpulseFilter(level, signal, signal_stride, signal_count, $0, $0.successor(), kernel_stride << 1, output, out_stride) } +} + +@_inlineable +public func Radix2FiniteImpulseFilter(_ level: Int, _ signal: UnsafePointer, _ signal_stride: Int, _ signal_count: Int, _ kernel: UnsafePointer, _ kernel_stride: Int, _ output: UnsafeMutablePointer, _ out_stride: Int) { + signal._reboundToDouble { _signal in kernel._reboundToDouble { _kernel in output._reboundToDouble { Radix2FiniteImpulseFilter(level, _signal, _signal.successor(), signal_stride << 1, signal_count, _kernel, _kernel.successor(), kernel_stride << 1, $0, $0.successor(), out_stride << 1) } } } +} diff --git a/Sources/Doggie/Accelerate/CircularConvolve.swift b/Sources/Doggie/Accelerate/CircularConvolve.swift index bb9de7b6b..3e709420a 100644 --- a/Sources/Doggie/Accelerate/CircularConvolve.swift +++ b/Sources/Doggie/Accelerate/CircularConvolve.swift @@ -187,7 +187,7 @@ public func Radix2PowerCircularConvolve(_ level: Int, _ } @_inlineable -public func Radix2FiniteImpulseFilter(_ level: Int, _ signal: UnsafePointer, _ signal_stride: Int, _ signal_count: Int, _ kernel: UnsafePointer, _ kernel_stride: Int, _ output: UnsafeMutablePointer, _ out_stride: Int) { +public func Radix2FiniteImpulseFilter(_ level: Int, _ signal: UnsafePointer, _ signal_stride: Int, _ signal_count: Int, _ kreal: UnsafePointer, _ kimag: UnsafePointer, _ kernel_stride: Int, _ output: UnsafeMutablePointer, _ out_stride: Int) where T : FloatingMathProtocol { let length = 1 << level let half = length >> 1 @@ -203,22 +203,24 @@ public func Radix2FiniteImpulseFilter(_ level: Int, _ signal: UnsafePointer, _ signal_stride: Int, _ signal_count: Int, _ kernel: UnsafePointer, _ kernel_stride: Int, _ output: UnsafeMutablePointer, _ out_stride: Int) { +public func Radix2FiniteImpulseFilter(_ level: Int, _ sreal: UnsafePointer, _ simag: UnsafePointer, _ signal_stride: Int, _ signal_count: Int, _ kreal: UnsafePointer, _ kimag: UnsafePointer, _ kernel_stride: Int, _ _real: UnsafeMutablePointer, _ _imag: UnsafeMutablePointer, _ out_stride: Int) where T : FloatingMathProtocol { let length = 1 << level if signal_count == 0 { - var output = output + var _real = _real + var _imag = _imag for _ in 0..(_ image: Image>, _ blur: Double) -> Image> { + + let t = 2 * blur * blur + let c = 1 / sqrt(.pi * t) + let _t = -1 / t + + let s = Int(ceil(6 * blur)) >> 1 + + let filter = (-s...s).map { exp(Double($0 * $0) * _t) as Double * c } + + return ImageConvolution(image, horizontal: filter, vertical: filter) +} + +@_inlineable +public func GaussianBlur(_ image: Image>, _ blur: Float) -> Image> { + + let t = 2 * blur * blur + let c = 1 / sqrt(.pi * t) + let _t = -1 / t + + let s = Int(ceil(6 * blur)) >> 1 + + let filter = (-s...s).map { exp(Float($0 * $0) * _t) as Float * c } + + return ImageConvolution(image, horizontal: filter, vertical: filter) +} diff --git a/Sources/Doggie/ImageFilter/ImageConvolution.swift b/Sources/Doggie/ImageFilter/ImageConvolution.swift new file mode 100644 index 000000000..73498e046 --- /dev/null +++ b/Sources/Doggie/ImageFilter/ImageConvolution.swift @@ -0,0 +1,276 @@ +// +// ImageConvolution.swift +// +// The MIT License +// Copyright (c) 2015 - 2018 Susan Cheng. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +@_versioned +@_inlineable +func _Radix2FiniteImpulseFilter(_ level: Int, _ row: Int, _ signal: UnsafePointer, _ signal_stride: Int, _ signal_row_stride: Int, _ signal_count: Int, _ kreal: UnsafePointer, _ kimag: UnsafePointer, _ kernel_stride: Int, _ kernel_row_stride: Int, _ output: UnsafeMutablePointer, _ out_stride: Int, _ out_row_stride: Int) where T : FloatingMathProtocol { + var signal = signal + var kreal = kreal + var kimag = kimag + var output = output + for _ in 0..(_ image: Image, _ horizontal_filter: [T], _ vertical_filter: [T]) -> Image where T : FloatingMathProtocol { + + let width = image.width + let height = image.height + let numberOfComponents = Pixel.numberOfComponents + + let n_width = width + horizontal_filter.count - 1 + let n_height = height + vertical_filter.count - 1 + + guard width > 0 && height > 0 else { return image } + + var result = Image(width: n_width, height: n_height, resolution: image.resolution, colorSpace: image.colorSpace, option: image.option) + + let length1 = FFTConvolveLength(width, horizontal_filter.count) + let length2 = FFTConvolveLength(height, vertical_filter.count) + + var buffer = MappedBuffer(repeating: 0, count: length1 + length2 + length1 * height + n_width * length2, option: image.option) + + buffer.withUnsafeMutableBufferPointer { + + guard let buffer = $0.baseAddress else { return } + + image.withUnsafeBytes { + + guard var source = $0.baseAddress?.assumingMemoryBound(to: T.self) else { return } + + result.withUnsafeMutableBytes { + + guard var output = $0.baseAddress?.assumingMemoryBound(to: T.self) else { return } + + let level1 = log2(length1) + let level2 = log2(length2) + + let _kreal1 = buffer + let _kimag1 = buffer + 1 + let _kreal2 = buffer + length1 + let _kimag2 = _kreal2 + 1 + let _temp1 = _kreal2 + length2 + let _temp2 = _temp1 + length1 * height + + HalfRadix2CooleyTukey(level1, horizontal_filter, 1, horizontal_filter.count, _kreal1, _kimag1, 2) + + var _length1 = T(length1) + Div(length1, _kreal1, _kimag1, 2, &_length1, 0, _kreal1, _kimag1, 2) + + HalfRadix2CooleyTukey(level2, vertical_filter, 1, vertical_filter.count, _kreal2, _kimag2, 2) + + var _length2 = T(length2) + Div(length2, _kreal2, _kimag2, 2, &_length2, 0, _kreal2, _kimag2, 2) + + for _ in 0..(_ image: Image, _ filter: [T]) -> Image where T : FloatingMathProtocol { + + let width = image.width + let height = image.height + let numberOfComponents = Pixel.numberOfComponents + + let n_width = width + filter.count - 1 + + guard width > 0 && height > 0 else { return image } + + var result = Image(width: n_width, height: height, resolution: image.resolution, colorSpace: image.colorSpace, option: image.option) + + let length = FFTConvolveLength(width, filter.count) + + var buffer = MappedBuffer(repeating: 0, count: length + length * height, option: image.option) + + buffer.withUnsafeMutableBufferPointer { + + guard let buffer = $0.baseAddress else { return } + + image.withUnsafeBytes { + + guard var source = $0.baseAddress?.assumingMemoryBound(to: T.self) else { return } + + result.withUnsafeMutableBytes { + + guard var output = $0.baseAddress?.assumingMemoryBound(to: T.self) else { return } + + let level = log2(length) + + let _kreal = buffer + let _kimag = buffer + 1 + let _temp = buffer + length + + HalfRadix2CooleyTukey(level, filter, 1, filter.count, _kreal, _kimag, 2) + + var _length = T(length) + Div(length, _kreal, _kimag, 2, &_length, 0, _kreal, _kimag, 2) + + for _ in 0..(_ image: Image, _ filter: [T]) -> Image where T : FloatingMathProtocol { + + let width = image.width + let height = image.height + let numberOfComponents = Pixel.numberOfComponents + + let n_height = height + filter.count - 1 + + guard width > 0 && height > 0 else { return image } + + var result = Image(width: width, height: n_height, resolution: image.resolution, colorSpace: image.colorSpace, option: image.option) + + let length = FFTConvolveLength(height, filter.count) + + var buffer = MappedBuffer(repeating: 0, count: length + length * width, option: image.option) + + buffer.withUnsafeMutableBufferPointer { + + guard let buffer = $0.baseAddress else { return } + + image.withUnsafeBytes { + + guard var source = $0.baseAddress?.assumingMemoryBound(to: T.self) else { return } + + result.withUnsafeMutableBytes { + + guard var output = $0.baseAddress?.assumingMemoryBound(to: T.self) else { return } + + let level = log2(length) + + let _kreal = buffer + let _kimag = buffer + 1 + let _temp = buffer + length + + HalfRadix2CooleyTukey(level, filter, 1, filter.count, _kreal, _kimag, 2) + + var _length = T(length) + Div(length, _kreal, _kimag, 2, &_length, 0, _kreal, _kimag, 2) + + for _ in 0..(_ image: Image>, horizontal horizontal_filter: [Double], vertical vertical_filter: [Double]) -> Image> { + return _ImageConvolution(image, horizontal_filter, vertical_filter) +} +@_inlineable +public func ImageConvolution(_ image: Image>, horizontal horizontal_filter: [Float], vertical vertical_filter: [Float]) -> Image> { + return _ImageConvolution(image, horizontal_filter, vertical_filter) +} +@_inlineable +public func ImageConvolutionHorizontal(_ image: Image>, _ filter: [Double]) -> Image> { + return _ImageConvolutionHorizontal(image, filter) +} +@_inlineable +public func ImageConvolutionHorizontal(_ image: Image>, _ filter: [Float]) -> Image> { + return _ImageConvolutionHorizontal(image, filter) +} +@_inlineable +public func ImageConvolutionVertical(_ image: Image>, _ filter: [Double]) -> Image> { + return _ImageConvolutionVertical(image, filter) +} +@_inlineable +public func ImageConvolutionVertical(_ image: Image>, _ filter: [Float]) -> Image> { + return _ImageConvolutionVertical(image, filter) +}