├── Playground.playground ├── Pages │ ├── Audio.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ ├── Sin.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ └── SinSpectrogram.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline ├── Resources │ ├── bonjour.wav │ ├── spectrogram.png │ └── toujours.wav └── contents.xcplayground ├── UIImage+Spectrogram.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── al.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── al.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── UIImage+Spectrogram.xcworkspace ├── contents.xcworkspacedata ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── al.xcuserdatad │ ├── UserInterfaceState.xcuserstate │ └── xcdebugger │ └── Breakpoints_v2.xcbkptlist ├── UIImage+Spectrogram ├── AcquisitionContext.swift ├── DataLoader.swift ├── EasyImagy │ ├── AnyImage.swift │ ├── Convolution.swift │ ├── EasyImagy.h │ ├── ExtrapolatedImage.swift │ ├── Extrapolation.swift │ ├── HigherOrderFunctions.swift │ ├── HigherOrderFunctions.swift.gyb │ ├── Image.swift │ ├── ImageAppKit.swift │ ├── ImageAppKit.swift.gyb │ ├── ImageCoreGraphics.swift │ ├── ImageCoreGraphics.swift.gyb │ ├── ImageFormat.swift │ ├── ImageInternal.swift │ ├── ImageOperators.swift │ ├── ImageOperators.swift.gyb │ ├── ImageProtocol.swift │ ├── ImageProtocolTyped.swift │ ├── ImageProtocolTyped.swift.gyb │ ├── ImageSlice.swift │ ├── ImageUIKit.swift │ ├── ImageUIKit.swift.gyb │ ├── Interpolation.swift │ ├── RGBA.swift │ ├── RGBAOperators.swift │ ├── RGBAOperators.swift.gyb │ ├── RGBATyped.swift │ ├── RGBATyped.swift.gyb │ ├── Resizing.swift │ ├── Rotation.swift │ ├── Summable.swift │ ├── Summable.swift.gyb │ └── Util.swift ├── Extensions.swift ├── FFT.swift ├── ImageExtension.swift ├── ImageUtils.swift ├── Info.plist ├── Rescale.swift ├── UIColorExtension.swift └── UIImage_Spectrogram.h └── UIImage_SpectrogramTests ├── Avatar.png ├── Info.plist ├── UIImage_SpectrogramTests.swift └── toujours.wav /Playground.playground/Pages/Audio.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import UIKit 4 | import UIImage_Spectrogram 5 | 6 | let acqCtx = AcquisitionContext(fftSize: 1024, sampleRate: 8000) 7 | acqCtx.frequencyStepForIndex(1) 8 | 9 | if let filePath = Bundle.main.url(forResource: "toujours", withExtension: "wav") { 10 | 11 | if let rawAudio = DataLoader.loadAudioSamplesArrayOf(Float.self, atUrl: filePath){ 12 | rawAudio.map{ $0 } 13 | 14 | let audioChunks = rawAudio.chunks(acqCtx.fftSize) 15 | 16 | 17 | } 18 | 19 | } 20 | /* 21 | let infos = acqCtx.durationInfosForSamples(rawAudio!) 22 | 23 | let signalAudio = rawAudio!.chunks(acqCtx.fftSize) 24 | let fftValues = signalAudio.map{ fft($0) } 25 | 26 | let realValues = fftValues.map{ Array($0.real[0..<$0.real.count/2]) } 27 | 28 | for i in 0..(uiImage: img!) 34 | 35 | let magnitudes = spectrogramValuesForSignal(input: rawAudio!, chunkSize: 512) 36 | let image2 = ImageUtils.drawMagnitudes(magnitudes: magnitudes, width: 640, height: 480) 37 | */ 38 | 39 | 40 | -------------------------------------------------------------------------------- /Playground.playground/Pages/Audio.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 20 | 21 | 26 | 27 | 32 | 33 | 37 | 38 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Playground.playground/Pages/Sin.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import Foundation 4 | import UIImage_Spectrogram 5 | 6 | let sineArraySize = 1024 * 1 7 | let frequency1:Float = 1.0 8 | let phase1:Float = 1.0 9 | let amplitude1:Float = 6.0 10 | let sineWave = (0.. 2 | 4 | 5 | 10 | 11 | 15 | 16 | 21 | 22 | 26 | 27 | 32 | 33 | 37 | 38 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Playground.playground/Pages/SinSpectrogram.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import Foundation 4 | import UIImage_Spectrogram 5 | 6 | let sineArraySize = 1024 * 20 7 | 8 | let frequency1:Float = 10.0 9 | let phase1:Float = 1.0 10 | let amplitude1:Float = 6.0 11 | 12 | let sineWave = (0.. 2 | 4 | 5 | 10 | 11 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Playground.playground/Resources/bonjour.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbanPerli/iOS-Spectrogram/d38129099cbaadc0d8c4feeff11318f744ece02e/Playground.playground/Resources/bonjour.wav -------------------------------------------------------------------------------- /Playground.playground/Resources/spectrogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbanPerli/iOS-Spectrogram/d38129099cbaadc0d8c4feeff11318f744ece02e/Playground.playground/Resources/spectrogram.png -------------------------------------------------------------------------------- /Playground.playground/Resources/toujours.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbanPerli/iOS-Spectrogram/d38129099cbaadc0d8c4feeff11318f744ece02e/Playground.playground/Resources/toujours.wav -------------------------------------------------------------------------------- /Playground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /UIImage+Spectrogram.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /UIImage+Spectrogram.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /UIImage+Spectrogram.xcodeproj/project.xcworkspace/xcuserdata/al.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbanPerli/iOS-Spectrogram/d38129099cbaadc0d8c4feeff11318f744ece02e/UIImage+Spectrogram.xcodeproj/project.xcworkspace/xcuserdata/al.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /UIImage+Spectrogram.xcodeproj/xcuserdata/al.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 24 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /UIImage+Spectrogram.xcodeproj/xcuserdata/al.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | UIImage+Spectrogram.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | UIImage_SpectrogramTests.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /UIImage+Spectrogram.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /UIImage+Spectrogram.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /UIImage+Spectrogram.xcworkspace/xcuserdata/al.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbanPerli/iOS-Spectrogram/d38129099cbaadc0d8c4feeff11318f744ece02e/UIImage+Spectrogram.xcworkspace/xcuserdata/al.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /UIImage+Spectrogram.xcworkspace/xcuserdata/al.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/AcquisitionContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AcquisitionContext.swift 3 | // UIImage+Spectrogram 4 | // 5 | // Created by AL on 03/09/2018. 6 | // Copyright © 2018 Alban. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class AcquisitionContext { 12 | 13 | public var fftSize:Int 14 | public var sampleRate:Float 15 | 16 | public init(fftSize: Int, sampleRate: Float) { 17 | self.fftSize = fftSize 18 | self.sampleRate = sampleRate 19 | } 20 | 21 | public func frequencyStepForIndex(_ index:Int) -> Float { 22 | return Float(index)*sampleRate / Float(fftSize) 23 | } 24 | 25 | public func indexForFrequency(_ targetFreq:Float) -> Int { 26 | var index = 0 27 | for i in 0...Int(fftSize) { 28 | index = i 29 | let freq = frequencyStepForIndex(i) 30 | if freq >= targetFreq { 31 | return index 32 | } 33 | } 34 | return index 35 | } 36 | 37 | public func durationInfosForSamples(_ samples:[Float]) -> (totalDuration:Float,chunkDuration:Float) { 38 | let chunkDuration = (1.0/(sampleRate/Float(fftSize))) 39 | let totalDuration = Float(samples.count)/sampleRate 40 | return (totalDuration,chunkDuration) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/DataLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataLoader.swift 3 | // OneWord 4 | // 5 | // Created by AL on 24/06/2018. 6 | // Copyright © 2018 Alban. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AVFoundation 11 | import Accelerate 12 | 13 | public class DataLoader { 14 | 15 | public class func urlForBundleFile(named:String,ofType:String) -> URL? { 16 | if let filepath = Bundle.main.path(forResource: named, ofType: ofType) { 17 | 18 | return URL(fileURLWithPath: filepath) 19 | }else{ 20 | return nil 21 | } 22 | } 23 | 24 | public class func documentsDirectory() -> URL { 25 | let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) 26 | return paths[0] 27 | } 28 | 29 | public class func saveArray(_ arrayToSave:[T], to fileName:String, with separator:String = ";") { 30 | 31 | let stringToSave = arrayToSave.map{ String(describing: $0) }.joined(separator: separator) 32 | 33 | let filePath = documentsDirectory().appendingPathComponent(fileName) 34 | print(filePath) 35 | do { 36 | try stringToSave.write(to: filePath, atomically: true, encoding: String.Encoding.utf8) 37 | } catch { 38 | print("Wrong perm") 39 | } 40 | 41 | } 42 | 43 | public class func loadAudioSamplesArrayOf(_ type:T.Type, atUrl url : URL, sampleRate:Double = 44100, channels:Int = 1 , interleaved:Bool = true) -> [T]? { 44 | 45 | do { 46 | 47 | if let file = try? AVAudioFile(forReading: url){ 48 | 49 | let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: Double(sampleRate), channels: AVAudioChannelCount(channels), interleaved: interleaved) 50 | 51 | if let buffer = AVAudioPCMBuffer(pcmFormat: format!, frameCapacity: AVAudioFrameCount(file.length)){ 52 | if ((try? file.read(into: buffer)) != nil) { 53 | 54 | let arraySize = Int(buffer.frameLength) 55 | 56 | switch type { 57 | case is Double.Type: 58 | let doublePointer = UnsafeMutablePointer.allocate(capacity: arraySize) 59 | vDSP_vspdp(buffer.floatChannelData![0], 1, doublePointer, 1, vDSP_Length(arraySize)) 60 | return Array(UnsafeBufferPointer(start: doublePointer, count:arraySize)) as? [T] 61 | case is Float.Type: 62 | return Array(UnsafeBufferPointer(start: buffer.floatChannelData![0], count:arraySize)) as? [T] 63 | default: return nil 64 | } 65 | } 66 | } 67 | } 68 | } catch let error as NSError { 69 | print("ERROR HERE", error.localizedDescription) 70 | } 71 | 72 | return nil 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/AnyImage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct AnyImage : ImageProtocol { 4 | private var box: AnyImageBox 5 | private let lock = NSLock() 6 | 7 | public init(_ image: I) where I.Pixel == Pixel { 8 | box = ImageBox(image) 9 | } 10 | 11 | public init(width: Int, height: Int, pixels: [Pixel]) { 12 | self.init(Image(width: width, height: height, pixels: pixels)) 13 | } 14 | 15 | public var xRange: CountableRange { 16 | return box.xRange 17 | } 18 | 19 | public var yRange: CountableRange { 20 | return box.yRange 21 | } 22 | 23 | public subscript(x: Int, y: Int) -> Pixel { 24 | get { 25 | return box[x, y] 26 | } 27 | 28 | set { 29 | lock.lock() 30 | defer { lock.unlock() } 31 | 32 | if !isKnownUniquelyReferenced(&box) { 33 | box = box.copied() 34 | } 35 | box[x, y] = newValue 36 | } 37 | } 38 | 39 | public subscript(xRange: CountableRange, yRange: CountableRange) -> ImageSlice { 40 | return box[xRange, yRange] 41 | } 42 | 43 | public func makeIterator() -> AnyIterator { 44 | return box.makeIterator() 45 | } 46 | } 47 | 48 | extension AnyImage { 49 | private class AnyImageBox { 50 | public var xRange: CountableRange { 51 | fatalError("Abstract") 52 | } 53 | 54 | public var yRange: CountableRange { 55 | fatalError("Abstract") 56 | } 57 | 58 | public subscript(x: Int, y: Int) -> Pixel { 59 | get { 60 | fatalError("Abstract") 61 | } 62 | 63 | set { 64 | fatalError("Abstract") 65 | } 66 | } 67 | 68 | public subscript(xRange: CountableRange, yRange: CountableRange) -> ImageSlice { 69 | fatalError("Abstract") 70 | } 71 | 72 | public func makeIterator() -> AnyIterator { 73 | fatalError("Abstract") 74 | } 75 | 76 | public func copied() -> AnyImageBox { 77 | fatalError("Abstract") 78 | } 79 | } 80 | 81 | private class ImageBox : AnyImageBox { 82 | private var base: I 83 | 84 | public init(_ base: I) { 85 | self.base = base 86 | } 87 | 88 | override public var xRange: CountableRange { 89 | return base.xRange 90 | } 91 | 92 | override public var yRange: CountableRange { 93 | return base.yRange 94 | } 95 | 96 | override public subscript(x: Int, y: Int) -> I.Pixel { 97 | get { 98 | return base[x, y] 99 | } 100 | 101 | set { 102 | base[x, y] = newValue 103 | } 104 | } 105 | 106 | override public subscript(xRange: CountableRange, yRange: CountableRange) -> ImageSlice { 107 | return base[xRange, yRange] 108 | } 109 | 110 | override public func makeIterator() -> AnyIterator { 111 | return AnyIterator(base.makeIterator()) 112 | } 113 | 114 | override public func copied() -> ImageBox { 115 | return ImageBox(base) 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/Convolution.swift: -------------------------------------------------------------------------------- 1 | extension ImageProtocol { 2 | internal func convoluted( 3 | with kernel: Kernel, 4 | extrapolatedBy extrapolationMethod: ExtrapolationMethod, 5 | toSummable: (Pixel) -> Summable, 6 | product: (Summable, W) -> Summable, 7 | zero: Summable, 8 | sum: (Summable, Summable) -> Summable, 9 | toOriginal: (Summable) -> Pixel 10 | ) -> Image where Kernel.Pixel == W { 11 | switch extrapolationMethod { 12 | case .filling(let value): 13 | return _convoluted(with: kernel, toSummable: toSummable, product: product, zero: zero, sum: sum, toOriginal: toOriginal) { x, y in 14 | extrapolatedPixelByFillingAt(x: x, y: y, by: value) 15 | } 16 | case .edge: 17 | return _convoluted(with: kernel, toSummable: toSummable, product: product, zero: zero, sum: sum, toOriginal: toOriginal) { x, y in 18 | extrapolatedPixelByEdgeAt(x: x, y: y, xRange: ClosedRange(xRange), yRange: ClosedRange(yRange)) 19 | } 20 | case .repeat: 21 | return _convoluted(with: kernel, toSummable: toSummable, product: product, zero: zero, sum: sum, toOriginal: toOriginal) { x, y in 22 | extrapolatedPixelByRepeatAt(x: x, y: y, minX: xRange.lowerBound, minY: yRange.lowerBound, width: width, height: height) 23 | } 24 | case .reflection: 25 | let doubleWidth = width * 2 26 | let doubleHeight = height * 2 27 | return _convoluted(with: kernel, toSummable: toSummable, product: product, zero: zero, sum: sum, toOriginal: toOriginal) { x, y in 28 | extrapolatedPixelByReflectionAt( 29 | x: x, 30 | y: y, 31 | minX: xRange.lowerBound, 32 | minY: yRange.lowerBound, 33 | doubleWidth: doubleWidth, 34 | doubleHeight: doubleHeight, 35 | doubleWidthMinusOne: doubleWidth - 1, 36 | doubleHeightMinusOne: doubleHeight - 1 37 | ) 38 | } 39 | } 40 | } 41 | 42 | private func _convoluted( 43 | with kernel: Kernel, 44 | toSummable: (Pixel) -> Summable, 45 | product: (Summable, W) -> Summable, 46 | zero: Summable, 47 | sum: (Summable, Summable) -> Summable, 48 | toOriginal: (Summable) -> Pixel, 49 | pixelAt: (Int, Int) -> Pixel 50 | ) -> Image where Kernel.Pixel == W { 51 | precondition(kernel.width % 2 == 1, "The width of the `kernel` must be odd: \(kernel.width)") 52 | precondition(kernel.height % 2 == 1, "The height of the `kernel` must be odd: \(kernel.height)") 53 | 54 | let xRange = self.xRange 55 | let yRange = self.yRange 56 | 57 | let kxRange = kernel.xRange 58 | let kyRange = kernel.yRange 59 | 60 | let hw = kxRange.count / 2 // halfWidth 61 | let hh = kyRange.count / 2 // halfHeight 62 | 63 | var pixels: [Pixel] = [] 64 | pixels.reserveCapacity(count) 65 | 66 | for y in yRange { 67 | for x in xRange { 68 | var weightedValues: [Summable] = [] 69 | for ky in kyRange { 70 | for kx in kxRange { 71 | let dx = (kx - kxRange.lowerBound) - hw 72 | let dy = (ky - kyRange.lowerBound) - hh 73 | let summablePixel = toSummable(pixelAt(x + dx, y + dy)) 74 | let weight = kernel[kx, ky] 75 | weightedValues.append(product(summablePixel, weight)) 76 | } 77 | } 78 | pixels.append(toOriginal(weightedValues.reduce(zero) { sum($0, $1) })) 79 | } 80 | } 81 | 82 | return Image(width: width, height: height, pixels: pixels) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/EasyImagy.h: -------------------------------------------------------------------------------- 1 | // 2 | // EasyImagy.h 3 | // EasyImagy 4 | // 5 | // Created by Yuta Koshizawa on 2017/10/04. 6 | // Copyright © 2017 koherent.org. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for EasyImagy. 12 | FOUNDATION_EXPORT double EasyImagyVersionNumber; 13 | 14 | //! Project version string for EasyImagy. 15 | FOUNDATION_EXPORT const unsigned char EasyImagyVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/ExtrapolatedImage.swift: -------------------------------------------------------------------------------- 1 | private func offset(_ range: CountableRange, _ offset: Int) -> CountableRange { 2 | return (range.lowerBound + offset) ..< (range.upperBound + offset) 3 | } 4 | 5 | internal struct ExtrapolatedImage : ImageProtocol { 6 | private var image: AnyImage 7 | public let xRange: CountableRange 8 | public let yRange: CountableRange 9 | private let extrapolationMethod: ExtrapolationMethod 10 | private var offsetX: Int 11 | private var offsetY: Int 12 | 13 | internal init(image: AnyImage, xRange: CountableRange, yRange: CountableRange, extrapolationMethod: ExtrapolationMethod, offsetX: Int = 0, offsetY: Int = 0) { 14 | self.image = image 15 | self.xRange = xRange 16 | self.yRange = yRange 17 | self.extrapolationMethod = extrapolationMethod 18 | self.offsetX = offsetX 19 | self.offsetY = offsetY 20 | } 21 | 22 | public subscript(x: Int, y: Int) -> Pixel { 23 | get { 24 | return image[x + offsetX, y + offsetY, extrapolatedBy: extrapolationMethod] 25 | } 26 | 27 | set { 28 | assert(xRange.contains(x), "`x` is out of bounds: \(x)") 29 | assert(yRange.contains(y), "`y` is out of bounds: \(y)") 30 | if !image.xRange.contains(x) || !image.yRange.contains(y) { 31 | var pixels = [Iterator.Element]() 32 | for y in yRange { 33 | for x in xRange { 34 | pixels.append(self[x, y]) 35 | } 36 | } 37 | image = AnyImage(Image(width: xRange.count, height: yRange.count, pixels: pixels)) 38 | offsetX = -xRange.lowerBound 39 | offsetY = -yRange.lowerBound 40 | } 41 | image[x + offsetX, y + offsetY] = newValue 42 | } 43 | } 44 | 45 | public subscript(xRange: CountableRange, yRange: CountableRange) -> ImageSlice { 46 | return ImageSlice(image: AnyImage(self), xRange: xRange, yRange: yRange) 47 | } 48 | 49 | public func makeIterator() -> AnyIterator { 50 | fatalError("This method is never called.") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/Extrapolation.swift: -------------------------------------------------------------------------------- 1 | public enum ExtrapolationMethod { 2 | case filling(Pixel) 3 | case edge 4 | case `repeat` 5 | case reflection 6 | } 7 | 8 | private func reminder(_ a: Int, _ b: Int) -> Int { 9 | let result = a % b 10 | return result < 0 ? result + b : result 11 | } 12 | 13 | extension ImageProtocol { 14 | public subscript(x: Int, y: Int, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> Pixel { 15 | switch extrapolationMethod { 16 | case .filling(let value): 17 | return extrapolatedPixelByFillingAt(x: x, y: y, by: value) 18 | case .edge: 19 | return extrapolatedPixelByEdgeAt(x: x, y: y, xRange: ClosedRange(xRange), yRange: ClosedRange(yRange)) 20 | case .repeat: 21 | return extrapolatedPixelByRepeatAt(x: x, y: y, minX: xRange.lowerBound, minY: yRange.lowerBound, width: width, height: height) 22 | case .reflection: 23 | let doubleWidth = width * 2 24 | let doubleHeight = height * 2 25 | return extrapolatedPixelByReflectionAt( 26 | x: x, 27 | y: y, 28 | minX: xRange.lowerBound, 29 | minY: yRange.lowerBound, 30 | doubleWidth: doubleWidth, 31 | doubleHeight: doubleHeight, 32 | doubleWidthMinusOne: doubleWidth - 1, 33 | doubleHeightMinusOne: doubleHeight - 1 34 | ) 35 | } 36 | } 37 | 38 | internal func extrapolatedPixelByFillingAt(x: Int, y: Int, by value: Pixel) -> Pixel { 39 | guard xRange.contains(x) && yRange.contains(y) else { 40 | return value 41 | } 42 | return self[x, y] 43 | } 44 | 45 | internal func extrapolatedPixelByEdgeAt(x: Int, y: Int, xRange: ClosedRange, yRange: ClosedRange) -> Pixel { 46 | return self[clamp(x, lower: xRange.lowerBound, upper: xRange.upperBound), clamp(y, lower: yRange.lowerBound, upper: yRange.upperBound)] 47 | } 48 | 49 | internal func extrapolatedPixelByRepeatAt(x: Int, y: Int, minX: Int, minY: Int, width: Int, height: Int) -> Pixel { 50 | let x2 = reminder(x - minX, width) + minX 51 | let y2 = reminder(y - minY, height) + minY 52 | return self[x2, y2] 53 | } 54 | 55 | internal func extrapolatedPixelByReflectionAt( 56 | x: Int, 57 | y: Int, 58 | minX: Int, 59 | minY: Int, 60 | doubleWidth: Int, 61 | doubleHeight: Int, 62 | doubleWidthMinusOne: Int, 63 | doubleHeightMinusOne: Int 64 | ) -> Pixel { 65 | let width = self.width 66 | let height = self.height 67 | let x2 = reminder(x - minX, doubleWidth) 68 | let y2 = reminder(y - minY, doubleHeight) 69 | let x3 = (x2 < width ? x2 : doubleWidthMinusOne - x2) + minX 70 | let y3 = (y2 < height ? y2 : doubleHeightMinusOne - y2) + minY 71 | return self[x3, y3] 72 | } 73 | } 74 | 75 | extension ImageProtocol { 76 | public subscript(xRange: CountableRange, yRange: CountableRange, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> ImageSlice { 77 | return ImageSlice( 78 | image: ExtrapolatedImage( 79 | image: AnyImage(self), 80 | xRange: xRange, 81 | yRange: yRange, 82 | extrapolationMethod: extrapolationMethod 83 | ), 84 | xRange: xRange, 85 | yRange: yRange 86 | ) 87 | } 88 | 89 | public subscript(xRange: R1, yRange: R2, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> ImageSlice where R1.Bound == Int, R2.Bound == Int { 90 | return self[countableRange(from: xRange, relativeTo: self.xRange), countableRange(from: yRange, relativeTo: self.yRange), extrapolatedBy: extrapolationMethod] 91 | } 92 | 93 | public subscript(xRange: R1, yRange: UnboundedRange, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> ImageSlice where R1.Bound == Int { 94 | return self[countableRange(from: xRange, relativeTo: self.xRange), self.yRange, extrapolatedBy: extrapolationMethod] 95 | } 96 | 97 | public subscript(xRange: UnboundedRange, yRange: R2, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> ImageSlice where R2.Bound == Int { 98 | return self[self.xRange, countableRange(from: yRange, relativeTo: self.yRange), extrapolatedBy: extrapolationMethod] 99 | } 100 | 101 | public subscript(xRange: UnboundedRange, yRange: UnboundedRange, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> ImageSlice { 102 | return self[self.xRange, self.yRange, extrapolatedBy: extrapolationMethod] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/HigherOrderFunctions.swift.gyb: -------------------------------------------------------------------------------- 1 | % image_types = ['Image', 'ImageSlice'] 2 | % basic_channel_types = [ 3 | % 'Int', 4 | % 'Int8', 5 | % 'Int16', 6 | % 'Int32', 7 | % 'Int64', 8 | % 'UInt', 9 | % 'UInt8', 10 | % 'UInt16', 11 | % 'UInt32', 12 | % 'UInt64', 13 | % 'Float', 14 | % 'Double', 15 | % 'Bool', 16 | % ] 17 | % basic_types = [f'RGBA<{t}>' for t in basic_channel_types] + basic_channel_types 18 | extension ImageProtocol { 19 | % for image_type in image_types: 20 | % for type in basic_types: 21 | % for target_type in basic_types: 22 | @_specialize(exported: true, where Self == ${image_type}<${type}>, T == ${target_type}) 23 | % end 24 | % end 25 | % end 26 | public func map(_ transform: (Pixel) throws -> T) rethrows -> Image { 27 | return Image(width: width, height: height, pixels: try map(transform)) 28 | } 29 | 30 | // No specialization for `Image` because it has special implementation 31 | % for type in basic_types: 32 | @_specialize(exported: true, where Self == ImageSlice<${type}>) 33 | % end 34 | public mutating func update(_ body: (inout Pixel) throws -> ()) rethrows { 35 | for y in yRange { 36 | for x in xRange { 37 | try body(&self[x, y]) 38 | } 39 | } 40 | } 41 | } 42 | 43 | // Special implementation for `Image` for performance 44 | #if swift(>=4.1) 45 | extension Image { 46 | % for type in basic_types: 47 | @_specialize(exported: true, where Pixel == ${type}) 48 | % end 49 | public mutating func update(_ body: (inout Pixel) throws -> ()) rethrows { 50 | let count = self.count 51 | guard count > 0 else { return } 52 | try pixels.withUnsafeMutableBufferPointer { 53 | for pointer in $0.baseAddress! ..< $0.baseAddress! + count { 54 | try body(&pointer.pointee) 55 | } 56 | } 57 | } 58 | } 59 | #endif 60 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/Image.swift: -------------------------------------------------------------------------------- 1 | public struct Image : ImageProtocol { 2 | public let width: Int 3 | public let height: Int 4 | internal var pixels: [Pixel] 5 | 6 | public init(width: Int, height: Int, pixels: [Pixel]) { 7 | precondition(width >= 0, "`width` must be greater than or equal to 0: \(width)") 8 | precondition(height >= 0, "`height` must be greater than or equal to 0: \(height)") 9 | let count = width * height 10 | precondition(pixels.count == count, "`pixels.count` (\(pixels.count)) must be equal to `width` (\(width)) * `height` (\(height)) (= \(count)).") 11 | 12 | self.width = width 13 | self.height = height 14 | self.pixels = pixels 15 | } 16 | 17 | public var xRange: CountableRange { 18 | return 0.. { 22 | return 0.. Pixel { 26 | get { 27 | return pixels[pixelIndexAt(x: x, y: y)] 28 | } 29 | set { 30 | pixels[pixelIndexAt(x: x, y: y)] = newValue 31 | } 32 | } 33 | 34 | public subscript(xRange: CountableRange, yRange: CountableRange) -> ImageSlice { 35 | return ImageSlice(image: self, xRange: xRange, yRange: yRange) 36 | } 37 | } 38 | 39 | extension Image { // Initializers for ImageSlice 40 | public init(_ imageSlice: ImageSlice) { 41 | self.init(width: imageSlice.width, height: imageSlice.height, pixels: Array(imageSlice)) 42 | } 43 | } 44 | 45 | extension Image { 46 | public func makeIterator() -> IndexingIterator<[Pixel]> { 47 | return pixels.makeIterator() 48 | } 49 | } 50 | 51 | extension Image { // Pointers 52 | public func withUnsafeBufferPointer(_ body: (UnsafeBufferPointer) throws -> R) rethrows -> R { 53 | return try pixels.withUnsafeBufferPointer(body) 54 | } 55 | 56 | public mutating func withUnsafeMutableBufferPointer(_ body: (inout UnsafeMutableBufferPointer) throws -> R) rethrows -> R { 57 | return try pixels.withUnsafeMutableBufferPointer(body) 58 | } 59 | 60 | public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { 61 | return try pixels.withUnsafeBytes(body) 62 | } 63 | 64 | public mutating func withUnsafeMutableBytes(_ body: (UnsafeMutableRawBufferPointer) throws -> R) rethrows -> R { 65 | return try pixels.withUnsafeMutableBytes(body) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/ImageAppKit.swift.gyb: -------------------------------------------------------------------------------- 1 | % types = ['UInt8', 'UInt16', 'UInt32', 'Float', 'Double', 'Bool'] 2 | #if os(macOS) 3 | import Foundation 4 | import AppKit 5 | % for is_rgba in [True, False]: 6 | % for type in types: 7 | % pixel_type = f'RGBA<{type}>' if is_rgba else type 8 | 9 | extension Image where Pixel == ${pixel_type} { 10 | public init(nsImage: NSImage) { 11 | if let cgImage: CGImage = nsImage.cgImage(forProposedRect: nil, context: nil, hints: nil) { 12 | self.init(cgImage: cgImage) 13 | } else { 14 | precondition(nsImage.size == .zero, "The `size` of the given `NSImage` instance (\(nsImage)) must be equal to `.zero` when the `cgImage` of the instance is `nil`.") 15 | self.init(width: 0, height: 0, pixels: []) 16 | } 17 | } 18 | 19 | private init?(nsImageOrNil: NSImage?) { 20 | guard let nsImage: NSImage = nsImageOrNil else { return nil } 21 | self.init(nsImage: nsImage) 22 | } 23 | 24 | public init?(named name: NSImage.Name) { 25 | self.init(nsImageOrNil: NSImage(named: name)) 26 | } 27 | 28 | public init?(contentsOfFile path: String) { 29 | self.init(nsImageOrNil: NSImage(contentsOfFile: path)) 30 | } 31 | 32 | public init?(data: Data) { 33 | self.init(nsImageOrNil: NSImage(data: data)) 34 | } 35 | 36 | public var nsImage: NSImage { 37 | return NSImage(cgImage: cgImage, size: .zero) 38 | } 39 | 40 | public func data(using format: Image.Format) -> Data? { 41 | guard width > 0 && height > 0 else { return nil } 42 | 43 | let imageRep = NSBitmapImageRep(cgImage: cgImage) 44 | imageRep.size = CGSize(width: CGFloat(width), height: CGFloat(height)) 45 | 46 | switch format { 47 | case .png: 48 | return imageRep.representation(using: .png, properties: [:]) 49 | case .jpeg(let compressionQuality): 50 | return imageRep.representation(using: .jpeg, properties: [.compressionFactor: NSNumber(value: compressionQuality)]) 51 | } 52 | } 53 | 54 | public func write(to url: URL, atomically: Bool, format: Image.Format) throws { 55 | guard let data = data(using: format) else { 56 | throw Image.Format.FormattingError>(image: self, format: format) 57 | } 58 | try data.write(to: url, options: atomically ? .atomic : .init(rawValue: 0)) 59 | } 60 | 61 | public func write(toFile path: S, atomically: Bool, format: Image.Format) throws { 62 | try write(to: URL(fileURLWithPath: String(path)), atomically: atomically, format: format) 63 | } 64 | } 65 | % end 66 | % end 67 | #endif 68 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/ImageCoreGraphics.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(macOS) || os(watchOS) || os(tvOS) 2 | import CoreGraphics 3 | import Foundation 4 | 5 | extension Image where Pixel == RGBA { 6 | private static var colorSpace: CGColorSpace { 7 | return CGColorSpaceCreateDeviceRGB() 8 | } 9 | 10 | private static var bitmapInfo: CGBitmapInfo { 11 | return CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) 12 | } 13 | 14 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 15 | let pixels: [RGBA] = Image.drawnPixels( 16 | width: width, 17 | height: height, 18 | defaultPixel: .transparent, 19 | colorSpace: Image>.colorSpace, 20 | bitmapInfo: Image>.bitmapInfo, 21 | minValue: .min, 22 | maxValue: .max, 23 | isEqual: ==, 24 | toSummable: Int.init, 25 | product: (*) as (Int, Int) -> Int, 26 | quotient: (/) as (Int, Int) -> Int, 27 | toOriginal: UInt8.init, 28 | setUp: setUp 29 | ) 30 | 31 | self.init(width: width, height: height, pixels: pixels) 32 | } 33 | 34 | public init(cgImage: CGImage) { 35 | let width = cgImage.width 36 | let height = cgImage.height 37 | 38 | self.init(width: width, height: height, setUp: { context in 39 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 40 | context.draw(cgImage, in: rect) 41 | }) 42 | } 43 | 44 | public var cgImage: CGImage { 45 | return Image.generatedCGImage( 46 | image: self, 47 | colorSpace: Image>.colorSpace, 48 | bitmapInfo: Image>.bitmapInfo, 49 | maxValue: .max, 50 | toSummable: Int.init, 51 | product: (*) as (Int, Int) -> Int, 52 | quotient: (/) as (Int, Int) -> Int, 53 | toOriginal: UInt8.init 54 | ) 55 | } 56 | } 57 | 58 | extension Image where Pixel == RGBA { 59 | private static var colorSpace: CGColorSpace { 60 | return CGColorSpaceCreateDeviceRGB() 61 | } 62 | 63 | private static var bitmapInfo: CGBitmapInfo { 64 | return CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) 65 | } 66 | 67 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 68 | let pixels: [RGBA] = Image.drawnPixels( 69 | width: width, 70 | height: height, 71 | defaultPixel: .transparent, 72 | colorSpace: Image>.colorSpace, 73 | bitmapInfo: Image>.bitmapInfo, 74 | minValue: .min, 75 | maxValue: .max, 76 | isEqual: ==, 77 | toSummable: Int.init, 78 | product: (*) as (Int, Int) -> Int, 79 | quotient: (/) as (Int, Int) -> Int, 80 | toOriginal: UInt16.init, 81 | setUp: setUp 82 | ) 83 | 84 | self.init(width: width, height: height, pixels: pixels) 85 | } 86 | 87 | public init(cgImage: CGImage) { 88 | let width = cgImage.width 89 | let height = cgImage.height 90 | 91 | self.init(width: width, height: height, setUp: { context in 92 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 93 | context.draw(cgImage, in: rect) 94 | }) 95 | } 96 | 97 | public var cgImage: CGImage { 98 | return Image.generatedCGImage( 99 | image: self, 100 | colorSpace: Image>.colorSpace, 101 | bitmapInfo: Image>.bitmapInfo, 102 | maxValue: .max, 103 | toSummable: Int.init, 104 | product: (*) as (Int, Int) -> Int, 105 | quotient: (/) as (Int, Int) -> Int, 106 | toOriginal: UInt16.init 107 | ) 108 | } 109 | } 110 | 111 | extension Image where Pixel == RGBA { 112 | private static var colorSpace: CGColorSpace { 113 | return CGColorSpaceCreateDeviceRGB() 114 | } 115 | 116 | private static var bitmapInfo: CGBitmapInfo { 117 | return CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) 118 | } 119 | 120 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 121 | let pixels: [RGBA] = Image.drawnPixels( 122 | width: width, 123 | height: height, 124 | defaultPixel: .transparent, 125 | colorSpace: Image>.colorSpace, 126 | bitmapInfo: Image>.bitmapInfo, 127 | minValue: .min, 128 | maxValue: .max, 129 | isEqual: ==, 130 | toSummable: Int64.init, 131 | product: (*) as (Int64, Int64) -> Int64, 132 | quotient: (/) as (Int64, Int64) -> Int64, 133 | toOriginal: UInt32.init, 134 | setUp: setUp 135 | ) 136 | 137 | self.init(width: width, height: height, pixels: pixels) 138 | } 139 | 140 | public init(cgImage: CGImage) { 141 | let width = cgImage.width 142 | let height = cgImage.height 143 | 144 | self.init(width: width, height: height, setUp: { context in 145 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 146 | context.draw(cgImage, in: rect) 147 | }) 148 | } 149 | 150 | public var cgImage: CGImage { 151 | return Image.generatedCGImage( 152 | image: self, 153 | colorSpace: Image>.colorSpace, 154 | bitmapInfo: Image>.bitmapInfo, 155 | maxValue: .max, 156 | toSummable: Int64.init, 157 | product: (*) as (Int64, Int64) -> Int64, 158 | quotient: (/) as (Int64, Int64) -> Int64, 159 | toOriginal: UInt32.init 160 | ) 161 | } 162 | } 163 | 164 | extension Image where Pixel == RGBA { 165 | private static var colorSpace: CGColorSpace { 166 | return CGColorSpaceCreateDeviceRGB() 167 | } 168 | 169 | private static var bitmapInfo: CGBitmapInfo { 170 | return CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) 171 | } 172 | 173 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 174 | let pixels: [RGBA] = Image.drawnPixels( 175 | width: width, 176 | height: height, 177 | defaultPixel: .transparent, 178 | colorSpace: Image>.colorSpace, 179 | bitmapInfo: Image>.bitmapInfo, 180 | minValue: .min, 181 | maxValue: .max, 182 | isEqual: ==, 183 | toSummable: Int.init, 184 | product: (*) as (Int, Int) -> Int, 185 | quotient: (/) as (Int, Int) -> Int, 186 | toOriginal: UInt8.init, 187 | setUp: setUp 188 | ) 189 | 190 | self.init(width: width, height: height, pixels: pixels.map { $0.map { Float($0) / 255 } }) 191 | } 192 | 193 | public init(cgImage: CGImage) { 194 | let width = cgImage.width 195 | let height = cgImage.height 196 | 197 | self.init(width: width, height: height, setUp: { context in 198 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 199 | context.draw(cgImage, in: rect) 200 | }) 201 | } 202 | 203 | public var cgImage: CGImage { 204 | return map { $0.map { UInt8(clamp($0, lower: 0.0, upper: 1.0) * 255) } }.cgImage 205 | } 206 | } 207 | 208 | extension Image where Pixel == RGBA { 209 | private static var colorSpace: CGColorSpace { 210 | return CGColorSpaceCreateDeviceRGB() 211 | } 212 | 213 | private static var bitmapInfo: CGBitmapInfo { 214 | return CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) 215 | } 216 | 217 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 218 | let pixels: [RGBA] = Image.drawnPixels( 219 | width: width, 220 | height: height, 221 | defaultPixel: .transparent, 222 | colorSpace: Image>.colorSpace, 223 | bitmapInfo: Image>.bitmapInfo, 224 | minValue: .min, 225 | maxValue: .max, 226 | isEqual: ==, 227 | toSummable: Int.init, 228 | product: (*) as (Int, Int) -> Int, 229 | quotient: (/) as (Int, Int) -> Int, 230 | toOriginal: UInt8.init, 231 | setUp: setUp 232 | ) 233 | 234 | self.init(width: width, height: height, pixels: pixels.map { $0.map { Double($0) / 255 } }) 235 | } 236 | 237 | public init(cgImage: CGImage) { 238 | let width = cgImage.width 239 | let height = cgImage.height 240 | 241 | self.init(width: width, height: height, setUp: { context in 242 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 243 | context.draw(cgImage, in: rect) 244 | }) 245 | } 246 | 247 | public var cgImage: CGImage { 248 | return map { $0.map { UInt8(clamp($0, lower: 0.0, upper: 1.0) * 255) } }.cgImage 249 | } 250 | } 251 | 252 | extension Image where Pixel == RGBA { 253 | private static var colorSpace: CGColorSpace { 254 | return CGColorSpaceCreateDeviceRGB() 255 | } 256 | 257 | private static var bitmapInfo: CGBitmapInfo { 258 | return CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) 259 | } 260 | 261 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 262 | let pixels: [RGBA] = Image.drawnPixels( 263 | width: width, 264 | height: height, 265 | defaultPixel: .transparent, 266 | colorSpace: Image>.colorSpace, 267 | bitmapInfo: Image>.bitmapInfo, 268 | minValue: .min, 269 | maxValue: .max, 270 | isEqual: ==, 271 | toSummable: Int.init, 272 | product: (*) as (Int, Int) -> Int, 273 | quotient: (/) as (Int, Int) -> Int, 274 | toOriginal: UInt8.init, 275 | setUp: setUp 276 | ) 277 | 278 | self.init(width: width, height: height, pixels: pixels.map { $0.map { $0 >= 128 } }) 279 | } 280 | 281 | public init(cgImage: CGImage) { 282 | let width = cgImage.width 283 | let height = cgImage.height 284 | 285 | self.init(width: width, height: height, setUp: { context in 286 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 287 | context.draw(cgImage, in: rect) 288 | }) 289 | } 290 | 291 | public var cgImage: CGImage { 292 | return map { $0.map { $0 ? (255 as UInt8) : (0 as UInt8) } }.cgImage 293 | } 294 | } 295 | 296 | extension Image where Pixel == UInt8 { 297 | private static var colorSpace: CGColorSpace { 298 | return CGColorSpaceCreateDeviceGray() 299 | } 300 | 301 | private static var bitmapInfo: CGBitmapInfo { 302 | return CGBitmapInfo() 303 | } 304 | 305 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 306 | let pixels: [UInt8] = Image.drawnPixels( 307 | width: width, 308 | height: height, 309 | defaultPixel: 0, 310 | colorSpace: Image.colorSpace, 311 | bitmapInfo: Image.bitmapInfo, 312 | setUp: setUp 313 | ) 314 | 315 | self.init(width: width, height: height, pixels: pixels) 316 | } 317 | 318 | public init(cgImage: CGImage) { 319 | let width = cgImage.width 320 | let height = cgImage.height 321 | 322 | self.init(width: width, height: height, setUp: { context in 323 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 324 | context.draw(cgImage, in: rect) 325 | }) 326 | } 327 | 328 | public var cgImage: CGImage { 329 | return Image.generatedCGImage( 330 | image: self, 331 | colorSpace: Image.colorSpace, 332 | bitmapInfo: Image.bitmapInfo 333 | ) 334 | } 335 | 336 | public func withCGImage(_ body: (CGImage) throws -> R) rethrows -> R { 337 | return try Image.withGeneratedCGImage( 338 | image: self, 339 | colorSpace: Image.colorSpace, 340 | bitmapInfo: Image.bitmapInfo, 341 | body: body 342 | ) 343 | } 344 | } 345 | 346 | extension Image where Pixel == UInt16 { 347 | private static var colorSpace: CGColorSpace { 348 | return CGColorSpaceCreateDeviceGray() 349 | } 350 | 351 | private static var bitmapInfo: CGBitmapInfo { 352 | return CGBitmapInfo() 353 | } 354 | 355 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 356 | let pixels: [UInt16] = Image.drawnPixels( 357 | width: width, 358 | height: height, 359 | defaultPixel: 0, 360 | colorSpace: Image.colorSpace, 361 | bitmapInfo: Image.bitmapInfo, 362 | setUp: setUp 363 | ) 364 | 365 | self.init(width: width, height: height, pixels: pixels) 366 | } 367 | 368 | public init(cgImage: CGImage) { 369 | let width = cgImage.width 370 | let height = cgImage.height 371 | 372 | self.init(width: width, height: height, setUp: { context in 373 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 374 | context.draw(cgImage, in: rect) 375 | }) 376 | } 377 | 378 | public var cgImage: CGImage { 379 | return Image.generatedCGImage( 380 | image: self, 381 | colorSpace: Image.colorSpace, 382 | bitmapInfo: Image.bitmapInfo 383 | ) 384 | } 385 | 386 | public func withCGImage(_ body: (CGImage) throws -> R) rethrows -> R { 387 | return try Image.withGeneratedCGImage( 388 | image: self, 389 | colorSpace: Image.colorSpace, 390 | bitmapInfo: Image.bitmapInfo, 391 | body: body 392 | ) 393 | } 394 | } 395 | 396 | extension Image where Pixel == UInt32 { 397 | private static var colorSpace: CGColorSpace { 398 | return CGColorSpaceCreateDeviceGray() 399 | } 400 | 401 | private static var bitmapInfo: CGBitmapInfo { 402 | return CGBitmapInfo() 403 | } 404 | 405 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 406 | let pixels: [UInt32] = Image.drawnPixels( 407 | width: width, 408 | height: height, 409 | defaultPixel: 0, 410 | colorSpace: Image.colorSpace, 411 | bitmapInfo: Image.bitmapInfo, 412 | setUp: setUp 413 | ) 414 | 415 | self.init(width: width, height: height, pixels: pixels) 416 | } 417 | 418 | public init(cgImage: CGImage) { 419 | let width = cgImage.width 420 | let height = cgImage.height 421 | 422 | self.init(width: width, height: height, setUp: { context in 423 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 424 | context.draw(cgImage, in: rect) 425 | }) 426 | } 427 | 428 | public var cgImage: CGImage { 429 | return Image.generatedCGImage( 430 | image: self, 431 | colorSpace: Image.colorSpace, 432 | bitmapInfo: Image.bitmapInfo 433 | ) 434 | } 435 | 436 | public func withCGImage(_ body: (CGImage) throws -> R) rethrows -> R { 437 | return try Image.withGeneratedCGImage( 438 | image: self, 439 | colorSpace: Image.colorSpace, 440 | bitmapInfo: Image.bitmapInfo, 441 | body: body 442 | ) 443 | } 444 | } 445 | 446 | extension Image where Pixel == Float { 447 | private static var colorSpace: CGColorSpace { 448 | return CGColorSpaceCreateDeviceGray() 449 | } 450 | 451 | private static var bitmapInfo: CGBitmapInfo { 452 | return CGBitmapInfo() 453 | } 454 | 455 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 456 | let pixels: [UInt8] = Image.drawnPixels( 457 | width: width, 458 | height: height, 459 | defaultPixel: 0, 460 | colorSpace: Image.colorSpace, 461 | bitmapInfo: Image.bitmapInfo, 462 | setUp: setUp 463 | ) 464 | 465 | self.init(width: width, height: height, pixels: pixels.map { Float($0) / 255 }) 466 | } 467 | 468 | public init(cgImage: CGImage) { 469 | let width = cgImage.width 470 | let height = cgImage.height 471 | 472 | self.init(width: width, height: height, setUp: { context in 473 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 474 | context.draw(cgImage, in: rect) 475 | }) 476 | } 477 | 478 | public var cgImage: CGImage { 479 | return map { UInt8(clamp($0, lower: 0.0, upper: 1.0) * 255) }.cgImage 480 | } 481 | } 482 | 483 | extension Image where Pixel == Double { 484 | private static var colorSpace: CGColorSpace { 485 | return CGColorSpaceCreateDeviceGray() 486 | } 487 | 488 | private static var bitmapInfo: CGBitmapInfo { 489 | return CGBitmapInfo() 490 | } 491 | 492 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 493 | let pixels: [UInt8] = Image.drawnPixels( 494 | width: width, 495 | height: height, 496 | defaultPixel: 0, 497 | colorSpace: Image.colorSpace, 498 | bitmapInfo: Image.bitmapInfo, 499 | setUp: setUp 500 | ) 501 | 502 | self.init(width: width, height: height, pixels: pixels.map { Double($0) / 255 }) 503 | } 504 | 505 | public init(cgImage: CGImage) { 506 | let width = cgImage.width 507 | let height = cgImage.height 508 | 509 | self.init(width: width, height: height, setUp: { context in 510 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 511 | context.draw(cgImage, in: rect) 512 | }) 513 | } 514 | 515 | public var cgImage: CGImage { 516 | return map { UInt8(clamp($0, lower: 0.0, upper: 1.0) * 255) }.cgImage 517 | } 518 | } 519 | 520 | extension Image where Pixel == Bool { 521 | private static var colorSpace: CGColorSpace { 522 | return CGColorSpaceCreateDeviceGray() 523 | } 524 | 525 | private static var bitmapInfo: CGBitmapInfo { 526 | return CGBitmapInfo() 527 | } 528 | 529 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 530 | let pixels: [UInt8] = Image.drawnPixels( 531 | width: width, 532 | height: height, 533 | defaultPixel: 0, 534 | colorSpace: Image.colorSpace, 535 | bitmapInfo: Image.bitmapInfo, 536 | setUp: setUp 537 | ) 538 | 539 | self.init(width: width, height: height, pixels: pixels.map { $0 >= 128 }) 540 | } 541 | 542 | public init(cgImage: CGImage) { 543 | let width = cgImage.width 544 | let height = cgImage.height 545 | 546 | self.init(width: width, height: height, setUp: { context in 547 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 548 | context.draw(cgImage, in: rect) 549 | }) 550 | } 551 | 552 | public var cgImage: CGImage { 553 | return map { $0 ? 255 as UInt8 : 0 as UInt8 }.cgImage 554 | } 555 | } 556 | #endif 557 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/ImageCoreGraphics.swift.gyb: -------------------------------------------------------------------------------- 1 | % uint_types = ['UInt8', 'UInt16', 'UInt32'] 2 | % float_types = ['Float', 'Double'] 3 | % type_to_summable = { 4 | % 'UInt8' : 'Int', 5 | % 'UInt16': 'Int', 6 | % 'UInt32': 'Int64', 7 | % } 8 | % 9 | #if os(iOS) || os(macOS) || os(watchOS) || os(tvOS) 10 | import CoreGraphics 11 | import Foundation 12 | % for type in uint_types: 13 | % summable = type_to_summable[type] 14 | 15 | extension Image where Pixel == RGBA<${type}> { 16 | private static var colorSpace: CGColorSpace { 17 | return CGColorSpaceCreateDeviceRGB() 18 | } 19 | 20 | private static var bitmapInfo: CGBitmapInfo { 21 | return CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) 22 | } 23 | 24 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 25 | let pixels: [RGBA<${type}>] = Image.drawnPixels( 26 | width: width, 27 | height: height, 28 | defaultPixel: .transparent, 29 | colorSpace: Image>.colorSpace, 30 | bitmapInfo: Image>.bitmapInfo, 31 | minValue: .min, 32 | maxValue: .max, 33 | isEqual: ==, 34 | toSummable: ${summable}.init, 35 | product: (*) as (${summable}, ${summable}) -> ${summable}, 36 | quotient: (/) as (${summable}, ${summable}) -> ${summable}, 37 | toOriginal: ${type}.init, 38 | setUp: setUp 39 | ) 40 | 41 | self.init(width: width, height: height, pixels: pixels) 42 | } 43 | 44 | public init(cgImage: CGImage) { 45 | let width = cgImage.width 46 | let height = cgImage.height 47 | 48 | self.init(width: width, height: height, setUp: { context in 49 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 50 | context.draw(cgImage, in: rect) 51 | }) 52 | } 53 | 54 | public var cgImage: CGImage { 55 | return Image.generatedCGImage( 56 | image: self, 57 | colorSpace: Image>.colorSpace, 58 | bitmapInfo: Image>.bitmapInfo, 59 | maxValue: .max, 60 | toSummable: ${summable}.init, 61 | product: (*) as (${summable}, ${summable}) -> ${summable}, 62 | quotient: (/) as (${summable}, ${summable}) -> ${summable}, 63 | toOriginal: ${type}.init 64 | ) 65 | } 66 | } 67 | % end 68 | % 69 | % for type in float_types: 70 | 71 | extension Image where Pixel == RGBA<${type}> { 72 | private static var colorSpace: CGColorSpace { 73 | return CGColorSpaceCreateDeviceRGB() 74 | } 75 | 76 | private static var bitmapInfo: CGBitmapInfo { 77 | return CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) 78 | } 79 | 80 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 81 | let pixels: [RGBA] = Image.drawnPixels( 82 | width: width, 83 | height: height, 84 | defaultPixel: .transparent, 85 | colorSpace: Image>.colorSpace, 86 | bitmapInfo: Image>.bitmapInfo, 87 | minValue: .min, 88 | maxValue: .max, 89 | isEqual: ==, 90 | toSummable: Int.init, 91 | product: (*) as (Int, Int) -> Int, 92 | quotient: (/) as (Int, Int) -> Int, 93 | toOriginal: UInt8.init, 94 | setUp: setUp 95 | ) 96 | 97 | self.init(width: width, height: height, pixels: pixels.map { $0.map { ${type}($0) / 255 } }) 98 | } 99 | 100 | public init(cgImage: CGImage) { 101 | let width = cgImage.width 102 | let height = cgImage.height 103 | 104 | self.init(width: width, height: height, setUp: { context in 105 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 106 | context.draw(cgImage, in: rect) 107 | }) 108 | } 109 | 110 | public var cgImage: CGImage { 111 | return map { $0.map { UInt8(clamp($0, lower: 0.0, upper: 1.0) * 255) } }.cgImage 112 | } 113 | } 114 | % end 115 | 116 | extension Image where Pixel == RGBA { 117 | private static var colorSpace: CGColorSpace { 118 | return CGColorSpaceCreateDeviceRGB() 119 | } 120 | 121 | private static var bitmapInfo: CGBitmapInfo { 122 | return CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) 123 | } 124 | 125 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 126 | let pixels: [RGBA] = Image.drawnPixels( 127 | width: width, 128 | height: height, 129 | defaultPixel: .transparent, 130 | colorSpace: Image>.colorSpace, 131 | bitmapInfo: Image>.bitmapInfo, 132 | minValue: .min, 133 | maxValue: .max, 134 | isEqual: ==, 135 | toSummable: Int.init, 136 | product: (*) as (Int, Int) -> Int, 137 | quotient: (/) as (Int, Int) -> Int, 138 | toOriginal: UInt8.init, 139 | setUp: setUp 140 | ) 141 | 142 | self.init(width: width, height: height, pixels: pixels.map { $0.map { $0 >= 128 } }) 143 | } 144 | 145 | public init(cgImage: CGImage) { 146 | let width = cgImage.width 147 | let height = cgImage.height 148 | 149 | self.init(width: width, height: height, setUp: { context in 150 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 151 | context.draw(cgImage, in: rect) 152 | }) 153 | } 154 | 155 | public var cgImage: CGImage { 156 | return map { $0.map { $0 ? (255 as UInt8) : (0 as UInt8) } }.cgImage 157 | } 158 | } 159 | % 160 | % for type in uint_types: 161 | 162 | extension Image where Pixel == ${type} { 163 | private static var colorSpace: CGColorSpace { 164 | return CGColorSpaceCreateDeviceGray() 165 | } 166 | 167 | private static var bitmapInfo: CGBitmapInfo { 168 | return CGBitmapInfo() 169 | } 170 | 171 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 172 | let pixels: [${type}] = Image.drawnPixels( 173 | width: width, 174 | height: height, 175 | defaultPixel: 0, 176 | colorSpace: Image<${type}>.colorSpace, 177 | bitmapInfo: Image<${type}>.bitmapInfo, 178 | setUp: setUp 179 | ) 180 | 181 | self.init(width: width, height: height, pixels: pixels) 182 | } 183 | 184 | public init(cgImage: CGImage) { 185 | let width = cgImage.width 186 | let height = cgImage.height 187 | 188 | self.init(width: width, height: height, setUp: { context in 189 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 190 | context.draw(cgImage, in: rect) 191 | }) 192 | } 193 | 194 | public var cgImage: CGImage { 195 | return Image.generatedCGImage( 196 | image: self, 197 | colorSpace: Image<${type}>.colorSpace, 198 | bitmapInfo: Image<${type}>.bitmapInfo 199 | ) 200 | } 201 | 202 | public func withCGImage(_ body: (CGImage) throws -> R) rethrows -> R { 203 | return try Image.withGeneratedCGImage( 204 | image: self, 205 | colorSpace: Image<${type}>.colorSpace, 206 | bitmapInfo: Image<${type}>.bitmapInfo, 207 | body: body 208 | ) 209 | } 210 | } 211 | % end 212 | % 213 | % for type in float_types: 214 | 215 | extension Image where Pixel == ${type} { 216 | private static var colorSpace: CGColorSpace { 217 | return CGColorSpaceCreateDeviceGray() 218 | } 219 | 220 | private static var bitmapInfo: CGBitmapInfo { 221 | return CGBitmapInfo() 222 | } 223 | 224 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 225 | let pixels: [UInt8] = Image.drawnPixels( 226 | width: width, 227 | height: height, 228 | defaultPixel: 0, 229 | colorSpace: Image.colorSpace, 230 | bitmapInfo: Image.bitmapInfo, 231 | setUp: setUp 232 | ) 233 | 234 | self.init(width: width, height: height, pixels: pixels.map { ${type}($0) / 255 }) 235 | } 236 | 237 | public init(cgImage: CGImage) { 238 | let width = cgImage.width 239 | let height = cgImage.height 240 | 241 | self.init(width: width, height: height, setUp: { context in 242 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 243 | context.draw(cgImage, in: rect) 244 | }) 245 | } 246 | 247 | public var cgImage: CGImage { 248 | return map { UInt8(clamp($0, lower: 0.0, upper: 1.0) * 255) }.cgImage 249 | } 250 | } 251 | % end 252 | 253 | extension Image where Pixel == Bool { 254 | private static var colorSpace: CGColorSpace { 255 | return CGColorSpaceCreateDeviceGray() 256 | } 257 | 258 | private static var bitmapInfo: CGBitmapInfo { 259 | return CGBitmapInfo() 260 | } 261 | 262 | private init(width: Int, height: Int, setUp: (CGContext) -> ()) { 263 | let pixels: [UInt8] = Image.drawnPixels( 264 | width: width, 265 | height: height, 266 | defaultPixel: 0, 267 | colorSpace: Image.colorSpace, 268 | bitmapInfo: Image.bitmapInfo, 269 | setUp: setUp 270 | ) 271 | 272 | self.init(width: width, height: height, pixels: pixels.map { $0 >= 128 }) 273 | } 274 | 275 | public init(cgImage: CGImage) { 276 | let width = cgImage.width 277 | let height = cgImage.height 278 | 279 | self.init(width: width, height: height, setUp: { context in 280 | let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)) 281 | context.draw(cgImage, in: rect) 282 | }) 283 | } 284 | 285 | public var cgImage: CGImage { 286 | return map { $0 ? 255 as UInt8 : 0 as UInt8 }.cgImage 287 | } 288 | } 289 | #endif 290 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/ImageFormat.swift: -------------------------------------------------------------------------------- 1 | extension Image { 2 | public enum Format { 3 | case png 4 | case jpeg(compressionQuality: Double) 5 | 6 | public struct FormattingError : Error { 7 | public let image: I 8 | public let format: Image.Format 9 | 10 | internal init(image: I, format: Image.Format) { 11 | self.image = image 12 | self.format = format 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/ImageInternal.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Image { 4 | internal func pixelIndexAt(x: Int, y: Int) -> Int { 5 | precondition(xRange.contains(x), "`x` is out of bounds: \(x)") 6 | precondition(yRange.contains(y), "`y` is out of bounds: \(y)") 7 | return y * width + x 8 | } 9 | } 10 | 11 | #if os(iOS) || os(OSX) || os(watchOS) || os(tvOS) 12 | 13 | import CoreGraphics 14 | 15 | extension Image { // RGBA 16 | internal static func drawnPixels( 17 | width: Int, 18 | height: Int, 19 | defaultPixel: RGBA, 20 | colorSpace: CGColorSpace, 21 | bitmapInfo: CGBitmapInfo, 22 | minValue: Channel, 23 | maxValue: Channel, 24 | isEqual: (Channel, Channel) -> Bool, 25 | toSummable: (Channel) -> Summable, 26 | product: (Summable, Summable) -> Summable, 27 | quotient: (Summable, Summable) -> Summable, 28 | toOriginal: (Summable) -> Channel, 29 | setUp: (CGContext) -> () 30 | ) -> [RGBA] { 31 | assert(width >= 0) 32 | assert(height >= 0) 33 | 34 | let bytesPerComponent = MemoryLayout.size 35 | 36 | let count = width * height 37 | var pixels = [RGBA](repeating: defaultPixel, count: count) 38 | 39 | let context = CGContext( 40 | data: &pixels, 41 | width: width, 42 | height: height, 43 | bitsPerComponent: bytesPerComponent * 8, 44 | bytesPerRow: bytesPerComponent * 4 * width, 45 | space: colorSpace, 46 | bitmapInfo: bitmapInfo.rawValue 47 | )! 48 | context.clear(CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height))) 49 | setUp(context) 50 | 51 | let maxSummable = toSummable(maxValue) 52 | for i in 0..( 57 | red: toOriginal(quotient(product(maxSummable, toSummable(pixel.red)), alpha)), 58 | green: toOriginal(quotient(product(maxSummable, toSummable(pixel.green)), alpha)), 59 | blue: toOriginal(quotient(product(maxSummable, toSummable(pixel.blue)), alpha)), 60 | alpha: pixel.alpha 61 | ) 62 | } 63 | } 64 | 65 | return pixels 66 | } 67 | 68 | internal static func generatedCGImage( 69 | image: Image>, 70 | colorSpace: CGColorSpace, 71 | bitmapInfo: CGBitmapInfo, 72 | maxValue: Channel, 73 | toSummable: (Channel) -> Summable, 74 | product: (Summable, Summable) -> Summable, 75 | quotient: (Summable, Summable) -> Summable, 76 | toOriginal: (Summable) -> Channel 77 | ) -> CGImage { 78 | let bytesPerComponent = MemoryLayout.size 79 | let bytesPerPixel = bytesPerComponent * 4 80 | let length = image.count * bytesPerPixel 81 | 82 | let maxSummable = toSummable(maxValue) 83 | 84 | var data = Data(count: length) 85 | data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in 86 | var pointer = bytes 87 | for pixel in image.pixels { 88 | let alphaInt = toSummable(pixel.alpha) 89 | pointer.pointee = toOriginal(quotient(product(toSummable(pixel.red), alphaInt), maxSummable)) 90 | pointer += 1 91 | pointer.pointee = toOriginal(quotient(product(toSummable(pixel.green), alphaInt), maxSummable)) 92 | pointer += 1 93 | pointer.pointee = toOriginal(quotient(product(toSummable(pixel.blue), alphaInt), maxSummable)) 94 | pointer += 1 95 | pointer.pointee = pixel.alpha 96 | pointer += 1 97 | } 98 | } 99 | let provider: CGDataProvider = CGDataProvider(data: data as CFData)! 100 | 101 | return CGImage( 102 | width: image.width, 103 | height: image.height, 104 | bitsPerComponent: bytesPerComponent * 8, 105 | bitsPerPixel: bytesPerPixel * 8, 106 | bytesPerRow: bytesPerPixel * image.width, 107 | space: colorSpace, 108 | bitmapInfo: bitmapInfo, 109 | provider: provider, 110 | decode: nil, 111 | shouldInterpolate: false, 112 | intent: CGColorRenderingIntent.defaultIntent 113 | )! 114 | } 115 | } 116 | 117 | extension Image { // Gray 118 | internal static func drawnPixels( 119 | width: Int, 120 | height: Int, 121 | defaultPixel: Pixel, 122 | colorSpace: CGColorSpace, 123 | bitmapInfo: CGBitmapInfo, 124 | setUp: (CGContext) -> () 125 | ) -> [Pixel] { 126 | assert(width >= 0) 127 | assert(height >= 0) 128 | 129 | let bytesPerComponent = MemoryLayout.size 130 | 131 | let count = width * height 132 | var pixels = [Pixel](repeating: defaultPixel, count: count) 133 | 134 | let context = CGContext( 135 | data: &pixels, 136 | width: width, 137 | height: height, 138 | bitsPerComponent: bytesPerComponent * 8, 139 | bytesPerRow: bytesPerComponent * width, 140 | space: colorSpace, 141 | bitmapInfo: bitmapInfo.rawValue 142 | )! 143 | context.clear(CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height))) 144 | setUp(context) 145 | 146 | return pixels 147 | } 148 | 149 | internal static func generatedCGImage( 150 | image: Image, 151 | colorSpace: CGColorSpace, 152 | bitmapInfo: CGBitmapInfo 153 | ) -> CGImage { 154 | let bytesPerPixel = MemoryLayout.size 155 | let length = image.count * bytesPerPixel 156 | 157 | let provider: CGDataProvider = CGDataProvider(data: Data( 158 | bytes: UnsafeMutableRawPointer(mutating: image.pixels), 159 | count: length 160 | ) as CFData)! 161 | 162 | return CGImage( 163 | width: image.width, 164 | height: image.height, 165 | bitsPerComponent: bytesPerPixel * 8, 166 | bitsPerPixel: bytesPerPixel * 8, 167 | bytesPerRow: bytesPerPixel * image.width, 168 | space: colorSpace, 169 | bitmapInfo: bitmapInfo, 170 | provider: provider, 171 | decode: nil, 172 | shouldInterpolate: false, 173 | intent: CGColorRenderingIntent.defaultIntent 174 | )! 175 | } 176 | 177 | internal static func withGeneratedCGImage( 178 | image: Image, 179 | colorSpace: CGColorSpace, 180 | bitmapInfo: CGBitmapInfo, 181 | body: (CGImage) throws -> R 182 | ) rethrows -> R { 183 | var image = image 184 | let bytesPerPixel = MemoryLayout.size 185 | let length = image.count * bytesPerPixel 186 | let width = image.width 187 | let height = image.height 188 | 189 | return try image.pixels.withUnsafeMutableBytes { bytes in 190 | let provider: CGDataProvider = CGDataProvider(data: Data( 191 | bytesNoCopy: bytes.baseAddress!, 192 | // `baseAddress` is `nil` if `bytes.count` is `0`. 193 | // However creating a `CGImage` with 0 pixels is illegal. 194 | // So calling this method with empty images are logic failures 195 | // and using `!` is justified. 196 | count: length, 197 | deallocator: .none 198 | ) as CFData)! 199 | 200 | let cgImage = CGImage( 201 | width: width, 202 | height: height, 203 | bitsPerComponent: bytesPerPixel * 8, 204 | bitsPerPixel: bytesPerPixel * 8, 205 | bytesPerRow: bytesPerPixel * width, 206 | space: colorSpace, 207 | bitmapInfo: bitmapInfo, 208 | provider: provider, 209 | decode: nil, 210 | shouldInterpolate: false, 211 | intent: CGColorRenderingIntent.defaultIntent 212 | )! 213 | 214 | return try body(cgImage) 215 | } 216 | } 217 | } 218 | 219 | #endif 220 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/ImageOperators.swift.gyb: -------------------------------------------------------------------------------- 1 | % types = [ 2 | % 'Numeric', 3 | % 'SignedNumeric', 4 | % 'BinaryInteger', 5 | % 'FixedWidthInteger', 6 | % 'FloatingPoint', 7 | % 'Equatable', 8 | % 'Comparable', 9 | % 'Bool', 10 | % 'RGBA', 11 | % 'RGBA', 12 | % 'RGBA', 13 | % 'RGBA', 14 | % 'RGBA', 15 | % 'RGBA', 16 | % 'RGBA', 17 | % 'RGBA', 18 | % 'RGBA', 19 | % 'RGBA', 20 | % 'RGBA', 21 | % 'RGBA', 22 | % 'RGBA', 23 | % ] 24 | % concrete_types = set([ 25 | % 'Bool', 26 | % 'RGBA', 27 | % 'RGBA', 28 | % 'RGBA', 29 | % 'RGBA', 30 | % 'RGBA', 31 | % 'RGBA', 32 | % 'RGBA', 33 | % 'RGBA', 34 | % 'RGBA', 35 | % 'RGBA', 36 | % 'RGBA', 37 | % 'RGBA', 38 | % 'RGBA', 39 | % ]) 40 | % type_to_operators = { 41 | % 'Numeric': ['+', '-', '*'], 42 | % 'SignedNumeric': [], 43 | % 'BinaryInteger': ['/', '%', '&', '|', '^', '<<', '>>'], 44 | % 'FixedWidthInteger': ['&+', '&-', '&*', '&<<', '&>>'], 45 | % 'FloatingPoint': ['/'], 46 | % 'Equatable': [], 47 | % 'Comparable': [], 48 | % 'Bool': ['&&', '||'], 49 | % 'RGBA': ['+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '&+', '&-', '&*', '&<<', '&>>'], 50 | % 'RGBA': ['+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '&+', '&-', '&*', '&<<', '&>>'], 51 | % 'RGBA': ['+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '&+', '&-', '&*', '&<<', '&>>'], 52 | % 'RGBA': ['+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '&+', '&-', '&*', '&<<', '&>>'], 53 | % 'RGBA': ['+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '&+', '&-', '&*', '&<<', '&>>'], 54 | % 'RGBA': ['+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '&+', '&-', '&*', '&<<', '&>>'], 55 | % 'RGBA': ['+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '&+', '&-', '&*', '&<<', '&>>'], 56 | % 'RGBA': ['+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '&+', '&-', '&*', '&<<', '&>>'], 57 | % 'RGBA': ['+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '&+', '&-', '&*', '&<<', '&>>'], 58 | % 'RGBA': ['+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '&+', '&-', '&*', '&<<', '&>>'], 59 | % 'RGBA': ['+', '-', '*', '/'], 60 | % 'RGBA': ['+', '-', '*', '/'], 61 | % 'RGBA': ['&&', '||'], 62 | % } 63 | % type_to_compound_assignment_operators = { 64 | % 'Numeric': ['+=', '-=', '*='], 65 | % 'SignedNumeric': [], 66 | % 'BinaryInteger': ['/=', '%=', '&=', '|=', '^=', '<<=', '>>='], 67 | % 'FixedWidthInteger': ['&<<=', '&>>='], 68 | % 'FloatingPoint': ['/='], 69 | % 'Equatable': [], 70 | % 'Comparable': [], 71 | % 'Bool': [], 72 | % 'RGBA': ['+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=', '&<<=', '&>>='], 73 | % 'RGBA': ['+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=', '&<<=', '&>>='], 74 | % 'RGBA': ['+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=', '&<<=', '&>>='], 75 | % 'RGBA': ['+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=', '&<<=', '&>>='], 76 | % 'RGBA': ['+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=', '&<<=', '&>>='], 77 | % 'RGBA': ['+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=', '&<<=', '&>>='], 78 | % 'RGBA': ['+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=', '&<<=', '&>>='], 79 | % 'RGBA': ['+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=', '&<<=', '&>>='], 80 | % 'RGBA': ['+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=', '&<<=', '&>>='], 81 | % 'RGBA': ['+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=', '&<<=', '&>>='], 82 | % 'RGBA': ['+=', '-=', '*=', '/='], 83 | % 'RGBA': ['+=', '-=', '*=', '/='], 84 | % 'RGBA': [], 85 | % } 86 | % type_to_equality_operators = { 87 | % 'Numeric': [], 88 | % 'SignedNumeric': [], 89 | % 'BinaryInteger': [], 90 | % 'FixedWidthInteger': [], 91 | % 'FloatingPoint': [], 92 | % 'Equatable': ['==', '!='], 93 | % 'Comparable': [], 94 | % 'Bool': [], 95 | % 'RGBA': ['==', '!='], 96 | % 'RGBA': ['==', '!='], 97 | % 'RGBA': ['==', '!='], 98 | % 'RGBA': ['==', '!='], 99 | % 'RGBA': ['==', '!='], 100 | % 'RGBA': ['==', '!='], 101 | % 'RGBA': ['==', '!='], 102 | % 'RGBA': ['==', '!='], 103 | % 'RGBA': ['==', '!='], 104 | % 'RGBA': ['==', '!='], 105 | % 'RGBA': ['==', '!='], 106 | % 'RGBA': ['==', '!='], 107 | % 'RGBA': ['==', '!='], 108 | % } 109 | % type_to_comparison_operators = { 110 | % 'Numeric': [], 111 | % 'SignedNumeric': [], 112 | % 'BinaryInteger': [], 113 | % 'FixedWidthInteger': [], 114 | % 'FloatingPoint': [], 115 | % 'Equatable': [], 116 | % 'Comparable': ['<', '<=', '>', '>='], 117 | % 'Bool': [], 118 | % 'RGBA': ['<', '<=', '>', '>='], 119 | % 'RGBA': ['<', '<=', '>', '>='], 120 | % 'RGBA': ['<', '<=', '>', '>='], 121 | % 'RGBA': ['<', '<=', '>', '>='], 122 | % 'RGBA': ['<', '<=', '>', '>='], 123 | % 'RGBA': ['<', '<=', '>', '>='], 124 | % 'RGBA': ['<', '<=', '>', '>='], 125 | % 'RGBA': ['<', '<=', '>', '>='], 126 | % 'RGBA': ['<', '<=', '>', '>='], 127 | % 'RGBA': ['<', '<=', '>', '>='], 128 | % 'RGBA': ['<', '<=', '>', '>='], 129 | % 'RGBA': ['<', '<=', '>', '>='], 130 | % 'RGBA': [], 131 | % } 132 | % type_to_prefix_operators = { 133 | % 'Numeric': ['+'], 134 | % 'SignedNumeric': ['-'], 135 | % 'BinaryInteger': [], 136 | % 'FixedWidthInteger': [], 137 | % 'FloatingPoint': [], 138 | % 'Equatable': [], 139 | % 'Comparable': [], 140 | % 'Bool': ['!'], 141 | % 'RGBA': ['+', '-'], 142 | % 'RGBA': ['+', '-'], 143 | % 'RGBA': ['+', '-'], 144 | % 'RGBA': ['+', '-'], 145 | % 'RGBA': ['+', '-'], 146 | % 'RGBA': ['+'], 147 | % 'RGBA': ['+'], 148 | % 'RGBA': ['+'], 149 | % 'RGBA': ['+'], 150 | % 'RGBA': ['+'], 151 | % 'RGBA': ['+', '-'], 152 | % 'RGBA': ['+', '-'], 153 | % 'RGBA': [], 154 | % } 155 | % type_to_concrete_types = { 156 | % 'Numeric': ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Float', 'Double'], 157 | % 'SignedNumeric': ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'Float', 'Double'], 158 | % 'BinaryInteger': ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64'], 159 | % 'FixedWidthInteger': ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64'], 160 | % 'FloatingPoint': ['Float', 'Double'], 161 | % 'Equatable': ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Float', 'Double', 'Bool'], 162 | % 'Comparable': ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Float', 'Double'], 163 | % 'Bool': [], 164 | % 'RGBA': [], 165 | % 'RGBA': [], 166 | % 'RGBA': [], 167 | % 'RGBA': [], 168 | % 'RGBA': [], 169 | % 'RGBA': [], 170 | % 'RGBA': [], 171 | % 'RGBA': [], 172 | % 'RGBA': [], 173 | % 'RGBA': [], 174 | % 'RGBA': [], 175 | % 'RGBA': [], 176 | % 'RGBA': [], 177 | % } 178 | % for i, type in enumerate(types): 179 | % if i > 0: 180 | 181 | % end 182 | % if type == 'Equatable': 183 | // FIXME: with conditional conformance 184 | % end 185 | extension ImageProtocol where Element ${'==' if type in concrete_types else ':'} ${type} { 186 | % first = True 187 | % for i, operator in enumerate(type_to_operators[type]): 188 | % if first: 189 | % first = False 190 | % else: 191 | 192 | % end 193 | % for concrete_type in type_to_concrete_types[type]: 194 | @_specialize(exported: true, where Self == Image<${concrete_type}>, I == Image<${concrete_type}>) 195 | @_specialize(exported: true, where Self == Image<${concrete_type}>, I == ImageSlice<${concrete_type}>) 196 | @_specialize(exported: true, where Self == ImageSlice<${concrete_type}>, I == Image<${concrete_type}>) 197 | @_specialize(exported: true, where Self == ImageSlice<${concrete_type}>, I == ImageSlice<${concrete_type}>) 198 | % end 199 | public static func ${operator}(lhs: Self, rhs: I) -> Image where I.Pixel == Pixel { 200 | precondition(lhs.width == rhs.width && lhs.height == rhs.height, "`${operator}` cannot be applied for images with different sizes: (\(lhs.width), \(lhs.height)), (\(rhs.width), \(rhs.height))") 201 | let pixels = zip(lhs, rhs).map { $0 ${operator} $1 } 202 | return Image(width: lhs.width, height: lhs.height, pixels: pixels) 203 | } 204 | % end 205 | % 206 | % for i, operator in enumerate(type_to_compound_assignment_operators[type]): 207 | % if first: 208 | % first = False 209 | % else: 210 | 211 | % end 212 | % for concrete_type in type_to_concrete_types[type]: 213 | @_specialize(exported: true, where Self == Image<${concrete_type}>, I == Image<${concrete_type}>) 214 | @_specialize(exported: true, where Self == Image<${concrete_type}>, I == ImageSlice<${concrete_type}>) 215 | @_specialize(exported: true, where Self == ImageSlice<${concrete_type}>, I == Image<${concrete_type}>) 216 | @_specialize(exported: true, where Self == ImageSlice<${concrete_type}>, I == ImageSlice<${concrete_type}>) 217 | % end 218 | public static func ${operator}(lhs: inout Self, rhs: I) where I.Pixel == Pixel { 219 | precondition(lhs.width == rhs.width && lhs.height == rhs.height, "`${operator}` cannot be applied for images with different sizes: (\(lhs.width), \(lhs.height)), (\(rhs.width), \(rhs.height))") 220 | for (y1, y2) in zip(lhs.yRange, rhs.yRange) { 221 | for (x1, x2) in zip(lhs.xRange, rhs.xRange) { 222 | lhs[x1, y1] ${operator} rhs[x2, y2] 223 | } 224 | } 225 | } 226 | % end 227 | % 228 | % for operator in type_to_equality_operators[type]: 229 | % if first: 230 | % first = False 231 | % else: 232 | 233 | % end 234 | % for concrete_type in type_to_concrete_types[type]: 235 | @_specialize(exported: true, where Self == Image<${concrete_type}>, I == Image<${concrete_type}>) 236 | @_specialize(exported: true, where Self == Image<${concrete_type}>, I == ImageSlice<${concrete_type}>) 237 | @_specialize(exported: true, where Self == ImageSlice<${concrete_type}>, I == Image<${concrete_type}>) 238 | @_specialize(exported: true, where Self == ImageSlice<${concrete_type}>, I == ImageSlice<${concrete_type}>) 239 | % end 240 | % initial, and_or, different_size, = ('true', '&&', 'false') if operator == '==' else ('false', '||', 'true') 241 | public static func ${operator}(lhs: Self, rhs: I) -> Bool where I.Pixel == Pixel { 242 | guard lhs.width == rhs.width && lhs.height == rhs.height else { return ${different_size} } 243 | return zip(lhs, rhs).reduce(${initial}) { $0 ${and_or} $1.0 ${operator} $1.1 } 244 | } 245 | % end 246 | % 247 | % for operator in type_to_comparison_operators[type]: 248 | % if first: 249 | % first = False 250 | % else: 251 | 252 | % end 253 | % for concrete_type in type_to_concrete_types[type]: 254 | @_specialize(exported: true, where Self == Image<${concrete_type}>, I == Image<${concrete_type}>) 255 | @_specialize(exported: true, where Self == Image<${concrete_type}>, I == ImageSlice<${concrete_type}>) 256 | @_specialize(exported: true, where Self == ImageSlice<${concrete_type}>, I == Image<${concrete_type}>) 257 | @_specialize(exported: true, where Self == ImageSlice<${concrete_type}>, I == ImageSlice<${concrete_type}>) 258 | % end 259 | % return_pixel_type = 'RGBA' if type.startswith('RGBA<') else 'Bool' 260 | public static func ${operator}(lhs: Self, rhs: I) -> Image<${return_pixel_type}> where I.Pixel == Pixel { 261 | precondition(lhs.width == rhs.width && lhs.height == rhs.height, "`${operator}` cannot be applied for images with different sizes: (\(lhs.width), \(lhs.height)), (\(rhs.width), \(rhs.height))") 262 | let pixels = zip(lhs, rhs).map { $0 ${operator} $1 } 263 | return Image<${return_pixel_type}>(width: lhs.width, height: lhs.height, pixels: pixels) 264 | } 265 | % end 266 | % 267 | % for operator in type_to_prefix_operators[type]: 268 | % if first: 269 | % first = False 270 | % else: 271 | 272 | % end 273 | % for concrete_type in type_to_concrete_types[type]: 274 | @_specialize(exported: true, where Self == Image<${concrete_type}>) 275 | @_specialize(exported: true, where Self == ImageSlice<${concrete_type}>) 276 | % end 277 | prefix public static func ${operator}(a: Self) -> Image { 278 | return Image(width: a.width, height: a.height, pixels: a.map { ${operator}$0 }) 279 | } 280 | % end 281 | } 282 | % end 283 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/ImageProtocol.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol ImageProtocol : Sequence { 4 | typealias Pixel = Iterator.Element 5 | 6 | init(width: Int, height: Int, pixels: [Pixel]) 7 | 8 | var width: Int { get } 9 | var height: Int { get } 10 | 11 | var xRange: CountableRange { get } 12 | var yRange: CountableRange { get } 13 | 14 | subscript(x: Int, y: Int) -> Pixel { get set } 15 | 16 | subscript(xRange: CountableRange, yRange: CountableRange) -> ImageSlice { get } 17 | subscript(xRange: R1, yRange: R2) -> ImageSlice where R1.Bound == Int, R2.Bound == Int { get } 18 | subscript(xRange: R1, yRange: UnboundedRange) -> ImageSlice where R1.Bound == Int { get } 19 | subscript(xRange: UnboundedRange, yRange: R2) -> ImageSlice where R2.Bound == Int { get } 20 | subscript(xRange: UnboundedRange, yRange: UnboundedRange) -> ImageSlice { get } 21 | 22 | func map(_ transform: (Pixel) throws -> T) rethrows -> Image 23 | mutating func update(_ body: (inout Pixel) throws -> ()) rethrows 24 | } 25 | 26 | extension ImageProtocol { 27 | public var width: Int { 28 | return xRange.count 29 | } 30 | 31 | public var height: Int { 32 | return yRange.count 33 | } 34 | 35 | public subscript(xRange: R1, yRange: R2) -> ImageSlice where R1.Bound == Int, R2.Bound == Int { 36 | return self[countableRange(from: xRange, relativeTo: self.xRange), countableRange(from: yRange, relativeTo: self.yRange)] 37 | } 38 | 39 | public subscript(xRange: R1, yRange: UnboundedRange) -> ImageSlice where R1.Bound == Int { 40 | return self[countableRange(from: xRange, relativeTo: self.xRange), self.yRange] 41 | } 42 | 43 | public subscript(xRange: UnboundedRange, yRange: R2) -> ImageSlice where R2.Bound == Int { 44 | return self[self.xRange, countableRange(from: yRange, relativeTo: self.yRange)] 45 | } 46 | 47 | public subscript(xRange: UnboundedRange, yRange: UnboundedRange) -> ImageSlice { 48 | return self[self.xRange, self.yRange] 49 | } 50 | } 51 | 52 | extension ImageProtocol { 53 | public init() { 54 | self.init(width: 0, height: 0, pixels: []) 55 | } 56 | 57 | public init(pixel: Pixel) { 58 | self.init(width: 1, height: 1, pixel: pixel) 59 | } 60 | 61 | public init(width: Int, height: Int, pixel: Pixel) { 62 | self.init(width: width, height: height, pixels: [Pixel](repeating: pixel, count: width * height)) 63 | } 64 | 65 | public init(width: Int, height: Int, pixels: S) where S.Element == Pixel { 66 | self.init(width: width, height: height, pixels: Array(pixels)) 67 | } 68 | 69 | public init(_ image: I) where I.Pixel == Pixel { 70 | self.init(width: image.width, height: image.height, pixels: image) 71 | } 72 | 73 | public init(width: Int, height: Int, pixelAt: (_ x: Int, _ y: Int) throws -> Pixel) rethrows { 74 | var pixels = [Pixel]() 75 | 76 | for y in 0.. Pixel? { 90 | guard xRange.contains(x) else { return nil } 91 | guard yRange.contains(y) else { return nil } 92 | return self[x, y] 93 | } 94 | } 95 | 96 | extension ImageProtocol { 97 | public func transposed() -> Image { 98 | var pixels = [Pixel]() 99 | 100 | for x in xRange { 101 | for y in yRange { 102 | pixels.append(self[x, y]) 103 | } 104 | } 105 | 106 | return Image(width: height, height: width, pixels: pixels) 107 | } 108 | 109 | public func xReversed() -> Image { 110 | var pixels = [Pixel]() 111 | 112 | for y in yRange { 113 | for x in xRange.reversed() { 114 | pixels.append(self[x, y]) 115 | } 116 | } 117 | 118 | return Image(width: width, height: height, pixels: pixels) 119 | } 120 | 121 | public func yReversed() -> Image { 122 | var pixels = [Pixel]() 123 | 124 | for y in yRange.reversed() { 125 | for x in xRange { 126 | pixels.append(self[x, y]) 127 | } 128 | } 129 | 130 | return Image(width: width, height: height, pixels: pixels) 131 | } 132 | 133 | public func rotated(byDegrees angle: Int) -> Image { 134 | precondition(angle % 90 == 0, "`angle` must be a multiple of 90: \(angle)") 135 | return rotated(byRightAngleInDegrees: angle) 136 | } 137 | 138 | internal func rotated(byRightAngleInDegrees angle: Int) -> Image { 139 | assert(angle % 90 == 0, "`angle` must be a multiple of 90: \(angle)") 140 | 141 | switch (angle / 90) % 4 { 142 | case 0: 143 | if let zelf = self as? Image { 144 | return zelf 145 | } else { 146 | return Image(self) 147 | } 148 | case 1, -3: 149 | var pixels = [Pixel]() 150 | 151 | for y in xRange { 152 | for x in yRange.reversed() { 153 | pixels.append(self[y, x]) 154 | } 155 | } 156 | 157 | return Image(width: height, height: width, pixels: pixels) 158 | case 2, -2: 159 | var pixels = [Pixel]() 160 | 161 | for y in yRange.reversed() { 162 | for x in xRange.reversed() { 163 | pixels.append(self[x, y]) 164 | } 165 | } 166 | 167 | return Image(width: width, height: height, pixels: pixels) 168 | case 3, -1: 169 | var pixels = [Pixel]() 170 | 171 | for y in xRange.reversed() { 172 | for x in yRange { 173 | pixels.append(self[y, x]) 174 | } 175 | } 176 | 177 | return Image(width: height, height: width, pixels: pixels) 178 | default: 179 | fatalError("Never reaches here.") 180 | } 181 | } 182 | 183 | public func rotated(by angle: Double, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> Image { 184 | return rotatedImageWith(angle: angle) { self[Int(round($0)), Int(round($1)), extrapolatedBy: extrapolationMethod] } 185 | } 186 | 187 | public func rotated(byDegrees angle: Double, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> Image { 188 | return rotated(by: angle / 180.0 * .pi, extrapolatedBy: extrapolationMethod) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/ImageProtocolTyped.swift.gyb: -------------------------------------------------------------------------------- 1 | % channel_types = [ 2 | % 'UInt8', 3 | % 'UInt16', 4 | % 'UInt32', 5 | % 'Int', 6 | % 'Float', 7 | % 'Double', 8 | % ] 9 | % types = [f'RGBA<{t}>' for t in channel_types] + channel_types 10 | % 11 | import Foundation 12 | % 13 | % for i, type in enumerate(types): 14 | % if i > 0: 15 | 16 | % end 17 | extension ImageProtocol where Element == ${type} { // Convolution 18 | @_specialize(exported: true, where Self == Image<${type}>, Kernel == Image) 19 | @_specialize(exported: true, where Self == Image<${type}>, Kernel == ImageSlice) 20 | @_specialize(exported: true, where Self == ImageSlice<${type}>, Kernel == Image) 21 | @_specialize(exported: true, where Self == ImageSlice<${type}>, Kernel == ImageSlice) 22 | public func convoluted(with kernel: Kernel, extrapolatedBy extrapolationMethod: ExtrapolationMethod<${type}> = .edge) -> Image<${type}> where Kernel.Pixel == Int { 23 | return self.convoluted(with: kernel, extrapolatedBy: extrapolationMethod, toSummable: { $0.summableI }, product: Pixel.productI, zero: Pixel.summableIZero, sum: +, toOriginal: Pixel.init(summableI:)) 24 | } 25 | 26 | @_specialize(exported: true, where Self == Image<${type}>, Kernel == Image) 27 | @_specialize(exported: true, where Self == Image<${type}>, Kernel == ImageSlice) 28 | @_specialize(exported: true, where Self == ImageSlice<${type}>, Kernel == Image) 29 | @_specialize(exported: true, where Self == ImageSlice<${type}>, Kernel == ImageSlice) 30 | public func convoluted(with kernel: Kernel, extrapolatedBy extrapolationMethod: ExtrapolationMethod<${type}> = .edge) -> Image<${type}> where Kernel.Pixel == Float { 31 | return self.convoluted(with: kernel, extrapolatedBy: extrapolationMethod, toSummable: { $0.summableF }, product: Pixel.productF, zero: Pixel.summableFZero, sum: +, toOriginal: Pixel.init(summableF:)) 32 | } 33 | 34 | @_specialize(exported: true, where Self == Image<${type}>, Kernel == Image) 35 | @_specialize(exported: true, where Self == Image<${type}>, Kernel == ImageSlice) 36 | @_specialize(exported: true, where Self == ImageSlice<${type}>, Kernel == Image) 37 | @_specialize(exported: true, where Self == ImageSlice<${type}>, Kernel == ImageSlice) 38 | public func convoluted(with kernel: Kernel, extrapolatedBy extrapolationMethod: ExtrapolationMethod<${type}> = .edge) -> Image<${type}> where Kernel.Pixel == Double { 39 | return self.convoluted(with: kernel, extrapolatedBy: extrapolationMethod, toSummable: { $0.summableD }, product: Pixel.productD, zero: Pixel.summableDZero, sum: +, toOriginal: Pixel.init(summableD:)) 40 | } 41 | } 42 | % end 43 | % 44 | % for i, type in enumerate(types): 45 | 46 | extension ImageProtocol where Element == ${type} { // Interpolation 47 | // Not implemented by default parameter values to improve performance especially when this `subscript` is called repeatedly 48 | public subscript(x: Double, y: Double) -> ${type} { 49 | return interpolatedPixelByBilinear(x: x, y: y, toSummable: { $0.summableD }, product: Pixel.productD, sum: +, toOriginal: Pixel.init(summableD:)) { self[$0, $1] } 50 | } 51 | 52 | public subscript(x: Double, y: Double, interpolatedBy interpolationMethod: InterpolationMethod) -> ${type} { 53 | switch interpolationMethod { 54 | case .nearestNeighbor: 55 | return interpolatedPixelByNearestNeighbor(x: x, y: y) { self[$0, $1] } 56 | case .bilinear: 57 | return interpolatedPixelByBilinear(x: x, y: y, toSummable: { $0.summableD }, product: Pixel.productD, sum: +, toOriginal: Pixel.init(summableD:)) { self[$0, $1] } 58 | } 59 | } 60 | 61 | public subscript(x: Double, y: Double, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> ${type} { 62 | return interpolatedPixelByBilinear(x: x, y: y, toSummable: { $0.summableD }, product: Pixel.productD, sum: +, toOriginal: Pixel.init(summableD:)) { self[$0, $1, extrapolatedBy: extrapolationMethod] } 63 | } 64 | 65 | public subscript(x: Double, y: Double, interpolatedBy interpolationMethod: InterpolationMethod, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> ${type} { 66 | switch interpolationMethod { 67 | case .nearestNeighbor: 68 | return interpolatedPixelByNearestNeighbor(x: x, y: y) { self[$0, $1, extrapolatedBy: extrapolationMethod] } 69 | case .bilinear: 70 | return interpolatedPixelByBilinear(x: x, y: y, toSummable: { $0.summableD }, product: Pixel.productD, sum: +, toOriginal: Pixel.init(summableD:)) { self[$0, $1, extrapolatedBy: extrapolationMethod] } 71 | } 72 | } 73 | } 74 | % end 75 | % 76 | % for i, type in enumerate(types): 77 | 78 | extension ImageProtocol where Element == ${type} { // Resizing 79 | % for j, explicit_interpolation in enumerate([False, True]): 80 | % if j > 0: 81 | 82 | % end 83 | public func resizedTo(width: Int, height: Int${', interpolatedBy interpolationMethod: InterpolationMethod' if explicit_interpolation else ''}) -> Image { 84 | let ox = xRange.lowerBound 85 | let oy = yRange.lowerBound 86 | % if explicit_interpolation: 87 | let isAntialiased: Bool 88 | if case .nearestNeighbor = interpolationMethod { 89 | isAntialiased = false 90 | } else { 91 | isAntialiased = true 92 | } 93 | % end 94 | % for has_offset in [False, True]: 95 | % if not has_offset: 96 | if ox == 0 && oy == 0 { 97 | % else: 98 | } else { 99 | let dox = Double(ox) 100 | let doy = Double(oy) 101 | % end 102 | return resizedTo( 103 | width: width, 104 | height: height, 105 | % if explicit_interpolation: 106 | isAntialiased: isAntialiased, 107 | % else: 108 | isAntialiased: true, 109 | % end 110 | toSummable: { $0.summableI }, 111 | zero: Pixel.summableIZero, 112 | sum: +, 113 | quotient: { a, b in Pixel.init(summableI: Pixel.quotientI(a, b)) }, 114 | pixelAt: { x, y in self[${'dox + x, doy + y' if has_offset else 'x, y'}${', interpolatedBy: interpolationMethod' if explicit_interpolation else ''}] }, 115 | extrapolatedPixelAt: { x, y in self[${'dox + x, doy + y' if has_offset else 'x, y'}${', interpolatedBy: interpolationMethod' if explicit_interpolation else ''}, extrapolatedBy: .edge] } 116 | ) 117 | % end 118 | } 119 | } 120 | % end 121 | } 122 | % end 123 | % 124 | % for i, type in enumerate(types): 125 | 126 | extension ImageProtocol where Element == ${type} { // Rotation 127 | public func rotated(byDegrees angle: Int) -> Image { 128 | if angle % 90 == 0 { 129 | return rotated(byRightAngleInDegrees: angle) 130 | } else { 131 | return rotated(byDegrees: Double(angle)) 132 | } 133 | } 134 | 135 | public func rotated(by angle: Double) -> Image { 136 | return rotatedImageWith(angle: angle) { self[$0, $1, interpolatedBy: .bilinear, extrapolatedBy: .filling(.selfZero)] } 137 | } 138 | 139 | public func rotated(byDegrees angle: Double) -> Image { 140 | return rotated(by: angle / 180.0 * .pi) 141 | } 142 | 143 | public func rotated(by angle: Double, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> Image { 144 | return rotatedImageWith(angle: angle) { self[$0, $1, interpolatedBy: .bilinear, extrapolatedBy: extrapolationMethod] } 145 | } 146 | 147 | public func rotated(byDegrees angle: Double, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> Image { 148 | return rotated(by: angle / 180.0 * .pi, extrapolatedBy: extrapolationMethod) 149 | } 150 | 151 | public func rotated(by angle: Double, interpolatedBy interpolationMethod: InterpolationMethod, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> Image { 152 | return rotatedImageWith(angle: angle) { self[$0, $1, interpolatedBy: interpolationMethod, extrapolatedBy: extrapolationMethod] } 153 | } 154 | 155 | public func rotated(byDegrees angle: Double, interpolatedBy interpolationMethod: InterpolationMethod, extrapolatedBy extrapolationMethod: ExtrapolationMethod) -> Image { 156 | return rotated(by: angle / 180.0 * .pi, interpolatedBy: interpolationMethod, extrapolatedBy: extrapolationMethod) 157 | } 158 | } 159 | % end 160 | 161 | extension ImageProtocol where Element == RGBA { // Rotation 162 | public func rotated(byDegrees angle: Int) -> Image { 163 | if angle % 90 == 0 { 164 | return rotated(byRightAngleInDegrees: angle) 165 | } else { 166 | return rotated(byDegrees: Double(angle)) 167 | } 168 | } 169 | 170 | public func rotated(by angle: Double) -> Image { 171 | return rotatedImageWith(angle: angle) { self[Int(round($0)), Int(round($1)), extrapolatedBy: .filling(RGBA(red: false, green: false, blue: false, alpha: false))] } 172 | } 173 | 174 | public func rotated(byDegrees angle: Double) -> Image { 175 | return rotated(by: angle / 180.0 * .pi) 176 | } 177 | } 178 | 179 | extension ImageProtocol where Element == Bool { // Rotation 180 | public func rotated(byDegrees angle: Int) -> Image { 181 | if angle % 90 == 0 { 182 | return rotated(byRightAngleInDegrees: angle) 183 | } else { 184 | return rotated(byDegrees: Double(angle)) 185 | } 186 | } 187 | 188 | public func rotated(by angle: Double) -> Image { 189 | return rotatedImageWith(angle: angle) { self[Int(round($0)), Int(round($1)), extrapolatedBy: .filling(false)] } 190 | } 191 | 192 | public func rotated(byDegrees angle: Double) -> Image { 193 | return rotated(by: angle / 180.0 * .pi) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/ImageSlice.swift: -------------------------------------------------------------------------------- 1 | public struct ImageSlice : ImageProtocol { 2 | private var image: AnyImage 3 | public let xRange: CountableRange 4 | public let yRange: CountableRange 5 | 6 | internal init(image: I, xRange: CountableRange, yRange: CountableRange) where I.Pixel == Pixel { 7 | precondition(image.xRange.isSuperset(of: xRange), "`xRange` is out of bounds: \(xRange)") 8 | precondition(image.yRange.isSuperset(of: yRange), "`yRange` is out of bounds: \(yRange)") 9 | self.image = AnyImage(image) 10 | self.xRange = xRange 11 | self.yRange = yRange 12 | } 13 | 14 | public init(width: Int, height: Int, pixels: [Pixel]) { 15 | self.init(image: Image(width: width, height: height, pixels: pixels), xRange: 0.. Pixel { 19 | get { 20 | precondition(xRange.contains(x), "`x` is out of bounds: \(x)") 21 | precondition(yRange.contains(y), "`y` is out of bounds: \(y)") 22 | return image[x, y] 23 | } 24 | 25 | set { 26 | precondition(xRange.contains(x), "`x` is out of bounds: \(x)") 27 | precondition(yRange.contains(y), "`y` is out of bounds: \(y)") 28 | image[x, y] = newValue 29 | } 30 | } 31 | 32 | public subscript(xRange: CountableRange, yRange: CountableRange) -> ImageSlice { 33 | precondition(self.xRange.isSuperset(of: xRange), "`xRange` is out of bounds: \(xRange)") 34 | precondition(self.xRange.isSuperset(of: yRange), "`yRange` is out of bounds: \(yRange)") 35 | return image[xRange, yRange] 36 | } 37 | } 38 | 39 | extension ImageSlice { 40 | public init(_ image: Image) { 41 | self.init(image: image, xRange: image.xRange, yRange: image.yRange) 42 | } 43 | } 44 | 45 | extension ImageSlice { 46 | public func makeIterator() -> PixelIterator { 47 | return PixelIterator(image: image, xRange: xRange, yRange: yRange) 48 | } 49 | } 50 | 51 | public struct PixelIterator: IteratorProtocol { 52 | public typealias Element = Pixel 53 | 54 | private let image: AnyImage 55 | 56 | private let xRange: CountableRange 57 | private let yRange: CountableRange 58 | 59 | private var x: Int 60 | private var y: Int 61 | 62 | internal init(image: AnyImage, xRange: CountableRange, yRange: CountableRange) { 63 | self.image = image 64 | 65 | self.xRange = xRange 66 | self.yRange = yRange 67 | 68 | self.x = xRange.lowerBound 69 | self.y = yRange.lowerBound 70 | } 71 | 72 | public mutating func next() -> Pixel? { 73 | if x == xRange.upperBound { 74 | x = xRange.lowerBound 75 | y += 1 76 | } 77 | 78 | guard y < yRange.upperBound else { return nil } 79 | defer { x += 1 } 80 | 81 | return image[x, y] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/ImageUIKit.swift.gyb: -------------------------------------------------------------------------------- 1 | % types = ['UInt8', 'UInt16', 'UInt32', 'Float', 'Double', 'Bool'] 2 | #if os(iOS) || os(watchOS) || os(tvOS) 3 | import Foundation 4 | import UIKit 5 | #if os(iOS) || os(tvOS) 6 | import CoreImage 7 | #endif 8 | 9 | #if os(watchOS) 10 | // Hack to simplify code 11 | private enum CIImage { 12 | var extent: CGRect { fatalError() } 13 | } 14 | private class CIContext { 15 | func createCGImage(_ image: CIImage, from: CGRect) -> CGImage? { return nil } 16 | } 17 | extension UIImage { 18 | fileprivate var ciImage: CIImage? { return nil } 19 | } 20 | #endif 21 | % for is_rgba in [True, False]: 22 | % for type in types: 23 | % pixel_type = f'RGBA<{type}>' if is_rgba else type 24 | 25 | extension Image where Pixel == ${pixel_type} { 26 | public init(uiImage: UIImage) { 27 | if let cgImage = uiImage.cgImage { 28 | self.init(cgImage: cgImage) 29 | } else if let ciImage = uiImage.ciImage { 30 | let context = CIContext() 31 | // Fails when the `ciImage` has an infinite extent. 32 | guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { 33 | fatalError("Failed to create a `CGImage` from an internal `CIImage` object from the given `UIImage` instance (\(uiImage)).") 34 | } 35 | self.init(cgImage: cgImage) 36 | } else { 37 | // This `gurad` can be replaced with `assert` if you are sure that the `size` is always equal to `.zero`. 38 | guard uiImage.size == .zero else { 39 | fatalError("The `size` of the given `UIImage` instance (\(uiImage)) is not equal to `.zero` though both the `cgImage` and the `ciImage` of the instance are `nil`.") 40 | } 41 | self.init(width: 0, height: 0, pixels: []) 42 | } 43 | } 44 | 45 | private init?(uiImageOrNil: UIImage?) { 46 | guard let uiImage: UIImage = uiImageOrNil else { return nil } 47 | self.init(uiImage: uiImage) 48 | } 49 | 50 | public init?(named name: String) { 51 | self.init(uiImageOrNil: UIImage(named: name)) 52 | } 53 | 54 | #if os(iOS) || os(tvOS) 55 | public init?(named name: String, in bundle: Bundle?, compatibleWith traitCollection: UITraitCollection?) { 56 | self.init(uiImageOrNil: UIImage(named: name, in: bundle, compatibleWith: traitCollection)) 57 | } 58 | #endif 59 | 60 | public init?(contentsOfFile path: String) { 61 | self.init(uiImageOrNil: UIImage(contentsOfFile: path)) 62 | } 63 | 64 | public init?(data: Data) { 65 | self.init(uiImageOrNil: UIImage(data: data)) 66 | } 67 | 68 | public var uiImage: UIImage { 69 | return UIImage(cgImage: cgImage) 70 | } 71 | 72 | public func data(using format: Image.Format) -> Data? { 73 | guard width > 0 && height > 0 else { return nil } 74 | 75 | switch format { 76 | case .png: 77 | return UIImagePNGRepresentation(uiImage) 78 | case .jpeg(let compressionQuality): 79 | return UIImageJPEGRepresentation(uiImage, CGFloat(compressionQuality)) 80 | } 81 | } 82 | 83 | public func write(to url: URL, atomically: Bool, format: Image.Format) throws { 84 | guard let data = data(using: format) else { 85 | throw Image.Format.FormattingError>(image: self, format: format) 86 | } 87 | try data.write(to: url, options: atomically ? .atomic : .init(rawValue: 0)) 88 | } 89 | 90 | public func write(toFile path: S, atomically: Bool, format: Image.Format) throws { 91 | try write(to: URL(fileURLWithPath: String(path)), atomically: atomically, format: format) 92 | } 93 | } 94 | % end 95 | % end 96 | #endif 97 | 98 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/Interpolation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum InterpolationMethod { 4 | case nearestNeighbor 5 | case bilinear 6 | // case bicubic // Unimplemented yet 7 | } 8 | 9 | extension ImageProtocol { 10 | internal func interpolatedPixelByNearestNeighbor(x: Double, y: Double, pixelAt: (Int, Int) -> Pixel) -> Pixel { 11 | let xi = Int(round(x)) 12 | let yi = Int(round(y)) 13 | return pixelAt(xi, yi) 14 | } 15 | 16 | internal func interpolatedPixelByBilinear( 17 | x: Double, 18 | y: Double, 19 | toSummable: (Pixel) -> Summable, 20 | product: (Summable, Double) -> Summable, 21 | sum: (Summable, Summable) -> Summable, 22 | toOriginal: (Summable) -> Pixel, 23 | pixelAt: (Int, Int) -> Pixel 24 | ) -> Pixel { 25 | let x0 = Int(floor(x)) 26 | let y0 = Int(floor(y)) 27 | let x1 = Int(ceil(x)) 28 | let y1 = Int(ceil(y)) 29 | 30 | let v00 = pixelAt(x0, y0) 31 | let v01 = pixelAt(x1, y0) 32 | let v10 = pixelAt(x0, y1) 33 | let v11 = pixelAt(x1, y1) 34 | 35 | let wx = x - Double(x0) 36 | let wy = y - Double(y0) 37 | let w00 = (1.0 - wx) * (1.0 - wy) 38 | let w01 = wx * (1.0 - wy) 39 | let w10 = (1.0 - wx) * wy 40 | let w11 = wx * wy 41 | 42 | return toOriginal(sum( 43 | sum(product(toSummable(v00), w00), product(toSummable(v01), w01)), 44 | sum(product(toSummable(v10), w10), product(toSummable(v11), w11)) 45 | )) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/RGBA.swift: -------------------------------------------------------------------------------- 1 | public struct RGBA { 2 | public var red: Channel 3 | public var green: Channel 4 | public var blue: Channel 5 | public var alpha: Channel 6 | 7 | public init(red: Channel, green: Channel, blue: Channel, alpha: Channel) { 8 | self.red = red 9 | self.green = green 10 | self.blue = blue 11 | self.alpha = alpha 12 | } 13 | } 14 | 15 | extension RGBA { // Additional initializers 16 | public init(gray: Channel, alpha: Channel) { 17 | self.init(red: gray, green: gray, blue: gray, alpha: alpha) 18 | } 19 | } 20 | 21 | extension RGBA { 22 | public func map(_ transform: (Channel) -> T) -> RGBA { 23 | return RGBA( 24 | red: transform(red), 25 | green: transform(green), 26 | blue: transform(blue), 27 | alpha: transform(alpha) 28 | ) 29 | } 30 | } 31 | 32 | extension RGBA where Channel == UInt8 { 33 | public init(_ rgbaInt: UInt32) { 34 | self.init(red: UInt8((rgbaInt >> 24) & 0xFF), green: UInt8((rgbaInt >> 16) & 0xFF), blue: UInt8((rgbaInt >> 8) & 0xFF), alpha: UInt8(rgbaInt & 0xFF)) 35 | } 36 | } 37 | 38 | extension RGBA : CustomStringConvertible { 39 | public var description: String { 40 | if let zelf = self as? RGBA { 41 | return String(format: "#%02X%02X%02X%02X", arguments: [zelf.red, zelf.green, zelf.blue, zelf.alpha]) 42 | } else { 43 | return "RGBA(red: \(red), green: \(green), blue: \(blue), alpha: \(alpha))" 44 | } 45 | } 46 | } 47 | 48 | extension RGBA : CustomDebugStringConvertible { 49 | public var debugDescription: String { 50 | return description 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/RGBAOperators.swift.gyb: -------------------------------------------------------------------------------- 1 | % types = [ 2 | % 'Numeric', 3 | % 'SignedNumeric', 4 | % 'BinaryInteger', 5 | % 'FixedWidthInteger', 6 | % 'FloatingPoint', 7 | % 'Equatable', 8 | % 'Comparable', 9 | % 'Bool', 10 | % ] 11 | % concrete_types = set([ 12 | % 'Bool', 13 | % ]) 14 | % type_to_operators = { 15 | % 'Numeric': ['+', '-', '*'], 16 | % 'SignedNumeric': [], 17 | % 'BinaryInteger': ['/', '%', '&', '|', '^', '<<', '>>'], 18 | % 'FixedWidthInteger': ['&+', '&-', '&*', '&<<', '&>>'], 19 | % 'FloatingPoint': ['/'], 20 | % 'Equatable': [], 21 | % 'Comparable': [], 22 | % 'Bool': ['&&', '||'], 23 | % } 24 | % type_to_compound_assignment_operators = { 25 | % 'Numeric': ['+=', '-=', '*='], 26 | % 'SignedNumeric': [], 27 | % 'BinaryInteger': ['/=', '%=', '&=', '|=', '^=', '<<=', '>>='], 28 | % 'FixedWidthInteger': ['&<<=', '&>>='], 29 | % 'FloatingPoint': ['/='], 30 | % 'Equatable': [], 31 | % 'Comparable': [], 32 | % 'Bool': [], 33 | % } 34 | % type_to_equality_operators = { 35 | % 'Numeric': [], 36 | % 'SignedNumeric': [], 37 | % 'BinaryInteger': [], 38 | % 'FixedWidthInteger': [], 39 | % 'FloatingPoint': [], 40 | % 'Equatable': ['==', '!='], 41 | % 'Comparable': [], 42 | % 'Bool': [], 43 | % } 44 | % type_to_comparison_operators = { 45 | % 'Numeric': [], 46 | % 'SignedNumeric': [], 47 | % 'BinaryInteger': [], 48 | % 'FixedWidthInteger': [], 49 | % 'FloatingPoint': [], 50 | % 'Equatable': [], 51 | % 'Comparable': ['<', '<=', '>', '>='], 52 | % 'Bool': [], 53 | % } 54 | % type_to_prefix_operators = { 55 | % 'Numeric': ['+'], 56 | % 'SignedNumeric': ['-'], 57 | % 'BinaryInteger': [], 58 | % 'FixedWidthInteger': [], 59 | % 'FloatingPoint': [], 60 | % 'Equatable': [], 61 | % 'Comparable': [], 62 | % 'Bool': ['!'], 63 | % } 64 | % type_to_concrete_types = { 65 | % 'Numeric': ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Float', 'Double'], 66 | % 'SignedNumeric': ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'Float', 'Double'], 67 | % 'BinaryInteger': ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64'], 68 | % 'FixedWidthInteger': ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64'], 69 | % 'FloatingPoint': ['Float', 'Double'], 70 | % 'Equatable': ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Float', 'Double', 'Bool'], 71 | % 'Comparable': ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Float', 'Double'], 72 | % 'Bool': [], 73 | % } 74 | % for i, type in enumerate(types): 75 | % if i > 0: 76 | 77 | % end 78 | % if type == 'Equatable': 79 | // FIXME: with conditional conformance 80 | % end 81 | extension RGBA where Channel ${'==' if type in concrete_types else ':'} ${type} { 82 | % first = True 83 | % for operator in type_to_operators[type]: 84 | % if first: 85 | % first = False 86 | % else: 87 | 88 | % end 89 | % for concrete_type in type_to_concrete_types[type]: 90 | @_specialize(exported: true, where Channel == ${concrete_type}) 91 | % end 92 | public static func ${operator}(lhs: RGBA, rhs: RGBA) -> RGBA { 93 | return RGBA(red: lhs.red ${operator} rhs.red, green: lhs.green ${operator} rhs.green, blue: lhs.blue ${operator} rhs.blue, alpha: lhs.alpha ${operator} rhs.alpha) 94 | } 95 | % end 96 | % 97 | % for operator in type_to_compound_assignment_operators[type]: 98 | % if first: 99 | % first = False 100 | % else: 101 | 102 | % end 103 | % for concrete_type in type_to_concrete_types[type]: 104 | @_specialize(exported: true, where Channel == ${concrete_type}) 105 | % end 106 | public static func ${operator}(lhs: inout RGBA, rhs: RGBA) { 107 | lhs.red ${operator} rhs.red 108 | lhs.green ${operator} rhs.green 109 | lhs.blue ${operator} rhs.blue 110 | lhs.alpha ${operator} rhs.alpha 111 | } 112 | % end 113 | % 114 | % for operator in type_to_equality_operators[type]: 115 | % if first: 116 | % first = False 117 | % else: 118 | 119 | % end 120 | % for concrete_type in type_to_concrete_types[type]: 121 | @_specialize(exported: true, where Channel == ${concrete_type}) 122 | % end 123 | % and_or = '&&' if operator == '==' else '||' 124 | public static func ${operator}(lhs: RGBA, rhs: RGBA) -> Bool { 125 | return lhs.red ${operator} rhs.red ${and_or} lhs.green ${operator} rhs.green ${and_or} lhs.blue ${operator} rhs.blue ${and_or} lhs.alpha ${operator} rhs.alpha 126 | } 127 | % end 128 | % 129 | % for operator in type_to_comparison_operators[type]: 130 | % if first: 131 | % first = False 132 | % else: 133 | 134 | % end 135 | % for concrete_type in type_to_concrete_types[type]: 136 | @_specialize(exported: true, where Channel == ${concrete_type}) 137 | % end 138 | public static func ${operator}(lhs: RGBA, rhs: RGBA) -> RGBA { 139 | return RGBA(red: lhs.red ${operator} rhs.red, green: lhs.green ${operator} rhs.green, blue: lhs.blue ${operator} rhs.blue, alpha: lhs.alpha ${operator} rhs.alpha) 140 | } 141 | % end 142 | % 143 | % for operator in type_to_prefix_operators[type]: 144 | % if first: 145 | % first = False 146 | % else: 147 | 148 | % end 149 | % for concrete_type in type_to_concrete_types[type]: 150 | @_specialize(exported: true, where Channel == ${concrete_type}) 151 | % end 152 | prefix public static func ${operator}(a: RGBA) -> RGBA { 153 | return RGBA(red: ${operator}a.red, green: ${operator}a.green, blue: ${operator}a.blue, alpha: ${operator}a.alpha) 154 | } 155 | % end 156 | } 157 | % end 158 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/RGBATyped.swift.gyb: -------------------------------------------------------------------------------- 1 | % types = ['UInt8', 'UInt16', 'UInt32', 'UInt64', 'Float', 'Double'] 2 | % type_to_max_value = { 3 | % 'UInt8' : 'UInt8.max', 4 | % 'UInt16' : 'UInt16.max', 5 | % 'UInt32' : 'UInt32.max', 6 | % 'UInt64' : 'UInt64.max', 7 | % 'Int' : '255', 8 | % 'Float' : '1', 9 | % 'Double' : '1', 10 | % } 11 | % summables = { 12 | % 'UInt8' : 'Int', 13 | % 'UInt16': 'Int', 14 | % 'UInt32': 'UInt64', 15 | % } 16 | % int_convertibles = {'UInt8', 'UInt16'} 17 | % 18 | % for i, type in enumerate(types): 19 | % if i > 0: 20 | 21 | % end 22 | extension RGBA where Channel == ${type} { 23 | public init(red: ${type}, green: ${type}, blue: ${type}) { 24 | self.init(red: red, green: green, blue: blue, alpha: ${type_to_max_value[type]}) 25 | } 26 | 27 | public init(gray: ${type}) { 28 | self.init(gray: gray, alpha: ${type_to_max_value[type]}) 29 | } 30 | 31 | public static var red: RGBA<${type}> { 32 | return RGBA<${type}>(red: ${type_to_max_value[type]}, green: 0, blue: 0) 33 | } 34 | 35 | public static var green: RGBA<${type}> { 36 | return RGBA<${type}>(red: 0, green: ${type_to_max_value[type]}, blue: 0) 37 | } 38 | 39 | public static var blue: RGBA<${type}> { 40 | return RGBA<${type}>(red: 0, green: 0, blue: ${type_to_max_value[type]}) 41 | } 42 | 43 | public static var black: RGBA<${type}> { 44 | return RGBA<${type}>(gray: 0) 45 | } 46 | 47 | public static var white: RGBA<${type}> { 48 | return RGBA<${type}>(gray: ${type_to_max_value[type]}) 49 | } 50 | 51 | public static var transparent: RGBA<${type}> { 52 | return RGBA<${type}>(gray: 0, alpha: 0) 53 | } 54 | 55 | public var gray: ${type} { 56 | % if type == 'UInt64': 57 | return (red / 3 + green / 3 + blue / 3) + (red % 3 + green % 3 + blue % 3) / 3 58 | % elif type in summables: 59 | % summable = summables[type] 60 | return ${type}((${summable}(red) + ${summable}(green) + ${summable}(blue)) / 3) 61 | % else: 62 | return (red + green + blue) / 3 63 | % end 64 | } 65 | % if type in int_convertibles: 66 | 67 | public init(red: Int, green: Int, blue: Int, alpha: Int) { 68 | self.init(red: ${type}(red), green: ${type}(green), blue: ${type}(blue), alpha: ${type}(alpha)) 69 | } 70 | 71 | public init(red: Int, green: Int, blue: Int) { 72 | self.init(red: ${type}(red), green: ${type}(green), blue: ${type}(blue)) 73 | } 74 | 75 | public init(gray: Int) { 76 | self.init(gray: ${type}(gray)) 77 | } 78 | 79 | public init(gray: Int, alpha: Int) { 80 | self.init(gray: ${type}(gray), alpha: ${type}(alpha)) 81 | } 82 | 83 | public var redInt: Int { 84 | get { 85 | return Int(red) 86 | } 87 | set { 88 | red = ${type}(newValue) 89 | } 90 | } 91 | 92 | public var greenInt: Int { 93 | get { 94 | return Int(green) 95 | } 96 | set { 97 | green = ${type}(newValue) 98 | } 99 | } 100 | 101 | public var blueInt: Int { 102 | get { 103 | return Int(blue) 104 | } 105 | set { 106 | blue = ${type}(newValue) 107 | } 108 | } 109 | 110 | public var alphaInt: Int { 111 | get { 112 | return Int(alpha) 113 | } 114 | set { 115 | alpha = ${type}(newValue) 116 | } 117 | } 118 | 119 | public var grayInt: Int { 120 | return (redInt + greenInt + blueInt) / 3 121 | } 122 | % end 123 | } 124 | % end 125 | % 126 | % numeric_types = ['Int', 'Int8', 'Int16', 'Int32', 'Int64', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Float', 'Double'] 127 | % for type in numeric_types: 128 | 129 | extension RGBA where Channel == ${type} { 130 | % for i, from_type in enumerate(numeric_types): 131 | % if i > 0: 132 | 133 | % end 134 | public init(_ rgba: RGBA<${from_type}>) { 135 | self.init(red: ${type}(rgba.red), green: ${type}(rgba.green), blue: ${type}(rgba.blue), alpha: ${type}(rgba.alpha)) 136 | } 137 | % end 138 | } 139 | % end 140 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/Resizing.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension ImageProtocol { 4 | public func resizedTo(width: Int, height: Int) -> Image { 5 | let ox = xRange.lowerBound 6 | let oy = yRange.lowerBound 7 | if ox == 0 && oy == 0 { 8 | return resizedTo( 9 | width: width, 10 | height: height, 11 | isAntialiased: false, 12 | toSummable: { _ -> Pixel in fatalError("Never reaches here.") }, 13 | zero: nil, 14 | sum: { _, _ -> Pixel in fatalError("Never reaches here.") }, 15 | quotient: { _, _ -> Pixel in fatalError("Never reaches here.") }, 16 | pixelAt: { x, y in self[Int(round(x)), Int(round(y))] }, 17 | extrapolatedPixelAt: { x, y in self[Int(round(x)), Int(round(y)), extrapolatedBy: .edge] } 18 | ) 19 | } else { 20 | let dox = Double(ox) 21 | let doy = Double(oy) 22 | return resizedTo( 23 | width: width, 24 | height: height, 25 | isAntialiased: false, 26 | toSummable: { _ -> Pixel in fatalError("Never reaches here.") }, 27 | zero: nil, 28 | sum: { _, _ -> Pixel in fatalError("Never reaches here.") }, 29 | quotient: { _, _ -> Pixel in fatalError("Never reaches here.") }, 30 | pixelAt: { x, y in self[Int(round(dox + x)), Int(round(doy + y))] }, 31 | extrapolatedPixelAt: { x, y in self[Int(round(dox + x)), Int(round(doy + y)), extrapolatedBy: .edge] } 32 | ) 33 | } 34 | } 35 | 36 | internal func resizedTo( 37 | width: Int, 38 | height: Int, 39 | isAntialiased: Bool, 40 | toSummable: (Pixel) -> Summable, 41 | zero: Summable?, 42 | sum: (Summable, Summable) -> Summable, 43 | quotient: (Summable, Int) -> Pixel, 44 | pixelAt: (Double, Double) -> Pixel, 45 | extrapolatedPixelAt: (Double, Double) -> Pixel 46 | ) -> Image { 47 | if width == 0 || height == 0 { 48 | return Image(width: width, height: height, pixels: []) 49 | } 50 | 51 | let xRange = self.xRange 52 | let yRange = self.yRange 53 | 54 | if width == xRange.count && height == yRange.count { 55 | if let image = self as? Image { 56 | return image 57 | } else { 58 | return Image(self) 59 | } 60 | } 61 | 62 | let isTargetWidthLarger = width >= xRange.count 63 | let isTargetHeightLarger = height >= yRange.count 64 | 65 | if !isAntialiased || (isTargetWidthLarger && isTargetHeightLarger) { 66 | return resizedByInterpolationTo(width: width, height: height, pixelAt: extrapolatedPixelAt) 67 | } 68 | 69 | let targetWidth = isTargetWidthLarger ? width : (xRange.count / width) * width 70 | let targetHeight = isTargetHeightLarger ? height : (yRange.count / height) * height 71 | 72 | let multiple: Image 73 | if width < xRange.count && height < yRange.count { 74 | multiple = resizedByInterpolationTo(width: targetWidth, height: targetHeight, pixelAt: pixelAt) 75 | } else { 76 | multiple = resizedByInterpolationTo(width: targetWidth, height: targetHeight, pixelAt: extrapolatedPixelAt) 77 | } 78 | return multiple.resizedByMeanTo( 79 | width: width, 80 | height: height, 81 | toSummable: toSummable, 82 | zero: zero!, 83 | sum: sum, 84 | quotient: quotient 85 | ) 86 | } 87 | 88 | private func resizedByInterpolationTo(width: Int, height: Int, pixelAt: (Double, Double) -> Pixel) -> Image { 89 | let sx = Double(xRange.count) / Double(width) 90 | let sy = Double(yRange.count) / Double(height) 91 | return Image(width: width, height: height, pixelAt: { x, y in 92 | pixelAt((Double(x) + 0.5) * sx - 0.5, (Double(y) + 0.5) * sy - 0.5) 93 | }) 94 | } 95 | 96 | private func resizedByMeanTo( 97 | width: Int, 98 | height: Int, 99 | toSummable: (Pixel) -> Summable, 100 | zero: Summable, 101 | sum: (Summable, Summable) -> Summable, 102 | quotient: (Summable, Int) -> Pixel 103 | ) -> Image { 104 | let xRange = self.xRange 105 | let yRange = self.yRange 106 | 107 | assert(width <= xRange.count && xRange.count % width == 0) 108 | assert(height <= yRange.count && yRange.count % height == 0) 109 | 110 | let sx = xRange.count / width 111 | let sy = yRange.count / height 112 | let n = sx * sy 113 | 114 | var pixels = [Pixel]() 115 | for y in 0..(width: width, height: height, pixels: pixels) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/Rotation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension ImageProtocol { 4 | internal func rotatedImageWith( 5 | angle: Double, 6 | pixelAt: (Double, Double) -> Pixel 7 | ) -> Image { 8 | let s = sin(angle) 9 | let c = cos(angle) 10 | 11 | let xRange = self.xRange 12 | let yRange = self.yRange 13 | 14 | let ox0 = Double(xRange.lowerBound) 15 | let oy0 = Double(yRange.lowerBound) 16 | 17 | let w0 = xRange.count 18 | let h0 = yRange.count 19 | 20 | let hw0 = Double(w0) / 2 21 | let hh0 = Double(h0) / 2 22 | 23 | let hw9d = Swift.max(abs(hw0 * c - hh0 * s), abs(hw0 * c - (-hh0) * s)) 24 | let hh9d = Swift.max(abs(hw0 * s + hh0 * c), abs(hw0 * s + (-hh0) * c)) 25 | 26 | let w9 = Int(round(hw9d * 2)) 27 | let h9 = Int(round(hh9d * 2)) 28 | 29 | let hw9 = Double(w9) / 2 30 | let hh9 = Double(h9) / 2 31 | 32 | return Image(width: w9, height: h9, pixelAt: { x9, y9 in 33 | let x2 = Double(x9) - (hw9 - 0.5) 34 | let y2 = Double(y9) - (hh9 - 0.5) 35 | 36 | let x1 = x2 * c - y2 * (-s) 37 | let y1 = x2 * (-s) + y2 * c 38 | 39 | let x0 = x1 + (hw0 - 0.5) 40 | let y0 = y1 + (hh0 - 0.5) 41 | 42 | return pixelAt(x0 + ox0, y0 + oy0) 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/Summable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal protocol _Summable { 4 | static func +(lhs: Self, rhs: Self) -> Self 5 | } 6 | extension Int: _Summable {} 7 | extension Int64: _Summable {} 8 | extension Float: _Summable {} 9 | extension Double: _Summable {} 10 | 11 | internal protocol _Multipliable { 12 | static func *(lhs: Self, rhs: Self) -> Self 13 | } 14 | extension Int: _Multipliable {} 15 | extension Int64: _Multipliable {} 16 | extension Float: _Multipliable {} 17 | extension Double: _Multipliable {} 18 | 19 | internal protocol _Dividable { 20 | static func /(lhs: Self, rhs: Self) -> Self 21 | } 22 | extension Int: _Dividable {} 23 | extension Int64: _Dividable {} 24 | extension Float: _Dividable {} 25 | extension Double: _Dividable {} 26 | 27 | internal func *(lhs: RGBA, rhs: T) -> RGBA { 28 | return RGBA(red: lhs.red * rhs, green: lhs.green * rhs, blue: lhs.blue * rhs, alpha: lhs.alpha * rhs) 29 | } 30 | 31 | internal func /(lhs: RGBA, rhs: T) -> RGBA { 32 | return RGBA(red: lhs.red / rhs, green: lhs.green / rhs, blue: lhs.blue / rhs, alpha: lhs.alpha / rhs) 33 | } 34 | 35 | extension RGBA where Channel == UInt8 { 36 | internal init(summableI: RGBA) { 37 | self = RGBA(summableI) 38 | } 39 | 40 | internal init(summableF: RGBA) { 41 | self = RGBA(summableF) 42 | } 43 | 44 | internal init(summableD: RGBA) { 45 | self = RGBA(summableD) 46 | } 47 | 48 | internal var summableI: RGBA { 49 | return RGBA(red: Int(red), green: Int(green), blue: Int(blue), alpha: Int(alpha)) 50 | } 51 | 52 | internal var summableF: RGBA { 53 | return RGBA(red: Float(red), green: Float(green), blue: Float(blue), alpha: Float(alpha)) 54 | } 55 | 56 | internal var summableD: RGBA { 57 | return RGBA(red: Double(red), green: Double(green), blue: Double(blue), alpha: Double(alpha)) 58 | } 59 | 60 | internal static var selfZero: RGBA { 61 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 62 | } 63 | 64 | 65 | internal static var summableIZero: RGBA { 66 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 67 | } 68 | 69 | internal static var summableFZero: RGBA { 70 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 71 | } 72 | 73 | internal static var summableDZero: RGBA { 74 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 75 | } 76 | 77 | internal static func productI(_ lhs: RGBA, _ rhs: Int) -> RGBA { 78 | return lhs * rhs 79 | } 80 | 81 | internal static func productF(_ lhs: RGBA, _ rhs: Float) -> RGBA { 82 | return lhs * rhs 83 | } 84 | 85 | internal static func productD(_ lhs: RGBA, _ rhs: Double) -> RGBA { 86 | return lhs * rhs 87 | } 88 | 89 | internal static func quotientI(_ lhs: RGBA, _ rhs: Int) -> RGBA { 90 | return lhs / rhs 91 | } 92 | 93 | internal static func quotientF(_ lhs: RGBA, _ rhs: Float) -> RGBA { 94 | return lhs / rhs 95 | } 96 | 97 | internal static func quotientD(_ lhs: RGBA, _ rhs: Double) -> RGBA { 98 | return lhs / rhs 99 | } 100 | } 101 | 102 | extension RGBA where Channel == UInt16 { 103 | internal init(summableI: RGBA) { 104 | self = RGBA(summableI) 105 | } 106 | 107 | internal init(summableF: RGBA) { 108 | self = RGBA(summableF) 109 | } 110 | 111 | internal init(summableD: RGBA) { 112 | self = RGBA(summableD) 113 | } 114 | 115 | internal var summableI: RGBA { 116 | return RGBA(red: Int(red), green: Int(green), blue: Int(blue), alpha: Int(alpha)) 117 | } 118 | 119 | internal var summableF: RGBA { 120 | return RGBA(red: Float(red), green: Float(green), blue: Float(blue), alpha: Float(alpha)) 121 | } 122 | 123 | internal var summableD: RGBA { 124 | return RGBA(red: Double(red), green: Double(green), blue: Double(blue), alpha: Double(alpha)) 125 | } 126 | 127 | internal static var selfZero: RGBA { 128 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 129 | } 130 | 131 | 132 | internal static var summableIZero: RGBA { 133 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 134 | } 135 | 136 | internal static var summableFZero: RGBA { 137 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 138 | } 139 | 140 | internal static var summableDZero: RGBA { 141 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 142 | } 143 | 144 | internal static func productI(_ lhs: RGBA, _ rhs: Int) -> RGBA { 145 | return lhs * rhs 146 | } 147 | 148 | internal static func productF(_ lhs: RGBA, _ rhs: Float) -> RGBA { 149 | return lhs * rhs 150 | } 151 | 152 | internal static func productD(_ lhs: RGBA, _ rhs: Double) -> RGBA { 153 | return lhs * rhs 154 | } 155 | 156 | internal static func quotientI(_ lhs: RGBA, _ rhs: Int) -> RGBA { 157 | return lhs / rhs 158 | } 159 | 160 | internal static func quotientF(_ lhs: RGBA, _ rhs: Float) -> RGBA { 161 | return lhs / rhs 162 | } 163 | 164 | internal static func quotientD(_ lhs: RGBA, _ rhs: Double) -> RGBA { 165 | return lhs / rhs 166 | } 167 | } 168 | 169 | extension RGBA where Channel == UInt32 { 170 | internal init(summableI: RGBA) { 171 | self = RGBA(summableI) 172 | } 173 | 174 | internal init(summableF: RGBA) { 175 | self = RGBA(summableF) 176 | } 177 | 178 | internal init(summableD: RGBA) { 179 | self = RGBA(summableD) 180 | } 181 | 182 | internal var summableI: RGBA { 183 | return RGBA(red: Int64(red), green: Int64(green), blue: Int64(blue), alpha: Int64(alpha)) 184 | } 185 | 186 | internal var summableF: RGBA { 187 | return RGBA(red: Float(red), green: Float(green), blue: Float(blue), alpha: Float(alpha)) 188 | } 189 | 190 | internal var summableD: RGBA { 191 | return RGBA(red: Double(red), green: Double(green), blue: Double(blue), alpha: Double(alpha)) 192 | } 193 | 194 | internal static var selfZero: RGBA { 195 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 196 | } 197 | 198 | 199 | internal static var summableIZero: RGBA { 200 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 201 | } 202 | 203 | internal static var summableFZero: RGBA { 204 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 205 | } 206 | 207 | internal static var summableDZero: RGBA { 208 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 209 | } 210 | 211 | internal static func productI(_ lhs: RGBA, _ rhs: Int) -> RGBA { 212 | return lhs * Int64(rhs) 213 | } 214 | 215 | internal static func productF(_ lhs: RGBA, _ rhs: Float) -> RGBA { 216 | return lhs * rhs 217 | } 218 | 219 | internal static func productD(_ lhs: RGBA, _ rhs: Double) -> RGBA { 220 | return lhs * rhs 221 | } 222 | 223 | internal static func quotientI(_ lhs: RGBA, _ rhs: Int) -> RGBA { 224 | return lhs / Int64(rhs) 225 | } 226 | 227 | internal static func quotientF(_ lhs: RGBA, _ rhs: Float) -> RGBA { 228 | return lhs / rhs 229 | } 230 | 231 | internal static func quotientD(_ lhs: RGBA, _ rhs: Double) -> RGBA { 232 | return lhs / rhs 233 | } 234 | } 235 | 236 | extension RGBA where Channel == Int { 237 | internal init(summableI: RGBA) { 238 | self = summableI 239 | } 240 | 241 | internal init(summableF: RGBA) { 242 | self = RGBA(summableF) 243 | } 244 | 245 | internal init(summableD: RGBA) { 246 | self = RGBA(summableD) 247 | } 248 | 249 | internal var summableI: RGBA { 250 | return self 251 | } 252 | 253 | internal var summableF: RGBA { 254 | return RGBA(red: Float(red), green: Float(green), blue: Float(blue), alpha: Float(alpha)) 255 | } 256 | 257 | internal var summableD: RGBA { 258 | return RGBA(red: Double(red), green: Double(green), blue: Double(blue), alpha: Double(alpha)) 259 | } 260 | 261 | internal static var selfZero: RGBA { 262 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 263 | } 264 | 265 | 266 | internal static var summableIZero: RGBA { 267 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 268 | } 269 | 270 | internal static var summableFZero: RGBA { 271 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 272 | } 273 | 274 | internal static var summableDZero: RGBA { 275 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 276 | } 277 | 278 | internal static func productI(_ lhs: RGBA, _ rhs: Int) -> RGBA { 279 | return lhs * rhs 280 | } 281 | 282 | internal static func productF(_ lhs: RGBA, _ rhs: Float) -> RGBA { 283 | return lhs * rhs 284 | } 285 | 286 | internal static func productD(_ lhs: RGBA, _ rhs: Double) -> RGBA { 287 | return lhs * rhs 288 | } 289 | 290 | internal static func quotientI(_ lhs: RGBA, _ rhs: Int) -> RGBA { 291 | return lhs / rhs 292 | } 293 | 294 | internal static func quotientF(_ lhs: RGBA, _ rhs: Float) -> RGBA { 295 | return lhs / rhs 296 | } 297 | 298 | internal static func quotientD(_ lhs: RGBA, _ rhs: Double) -> RGBA { 299 | return lhs / rhs 300 | } 301 | } 302 | 303 | extension RGBA where Channel == Float { 304 | internal init(summableI: RGBA) { 305 | self = summableI 306 | } 307 | 308 | internal init(summableF: RGBA) { 309 | self = summableF 310 | } 311 | 312 | internal init(summableD: RGBA) { 313 | self = RGBA(summableD) 314 | } 315 | 316 | internal var summableI: RGBA { 317 | return self 318 | } 319 | 320 | internal var summableF: RGBA { 321 | return self 322 | } 323 | 324 | internal var summableD: RGBA { 325 | return RGBA(red: Double(red), green: Double(green), blue: Double(blue), alpha: Double(alpha)) 326 | } 327 | 328 | internal static var selfZero: RGBA { 329 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 330 | } 331 | 332 | 333 | internal static var summableIZero: RGBA { 334 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 335 | } 336 | 337 | internal static var summableFZero: RGBA { 338 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 339 | } 340 | 341 | internal static var summableDZero: RGBA { 342 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 343 | } 344 | 345 | internal static func productI(_ lhs: RGBA, _ rhs: Int) -> RGBA { 346 | return lhs * Float(rhs) 347 | } 348 | 349 | internal static func productF(_ lhs: RGBA, _ rhs: Float) -> RGBA { 350 | return lhs * rhs 351 | } 352 | 353 | internal static func productD(_ lhs: RGBA, _ rhs: Double) -> RGBA { 354 | return lhs * rhs 355 | } 356 | 357 | internal static func quotientI(_ lhs: RGBA, _ rhs: Int) -> RGBA { 358 | return lhs / Float(rhs) 359 | } 360 | 361 | internal static func quotientF(_ lhs: RGBA, _ rhs: Float) -> RGBA { 362 | return lhs / rhs 363 | } 364 | 365 | internal static func quotientD(_ lhs: RGBA, _ rhs: Double) -> RGBA { 366 | return lhs / rhs 367 | } 368 | } 369 | 370 | extension RGBA where Channel == Double { 371 | internal init(summableI: RGBA) { 372 | self = summableI 373 | } 374 | 375 | internal init(summableF: RGBA) { 376 | self = summableF 377 | } 378 | 379 | internal init(summableD: RGBA) { 380 | self = summableD 381 | } 382 | 383 | internal var summableI: RGBA { 384 | return self 385 | } 386 | 387 | internal var summableF: RGBA { 388 | return self 389 | } 390 | 391 | internal var summableD: RGBA { 392 | return self 393 | } 394 | 395 | internal static var selfZero: RGBA { 396 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 397 | } 398 | 399 | 400 | internal static var summableIZero: RGBA { 401 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 402 | } 403 | 404 | internal static var summableFZero: RGBA { 405 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 406 | } 407 | 408 | internal static var summableDZero: RGBA { 409 | return RGBA(red: 0, green: 0, blue: 0, alpha: 0) 410 | } 411 | 412 | internal static func productI(_ lhs: RGBA, _ rhs: Int) -> RGBA { 413 | return lhs * Double(rhs) 414 | } 415 | 416 | internal static func productF(_ lhs: RGBA, _ rhs: Float) -> RGBA { 417 | return lhs * Double(rhs) 418 | } 419 | 420 | internal static func productD(_ lhs: RGBA, _ rhs: Double) -> RGBA { 421 | return lhs * rhs 422 | } 423 | 424 | internal static func quotientI(_ lhs: RGBA, _ rhs: Int) -> RGBA { 425 | return lhs / Double(rhs) 426 | } 427 | 428 | internal static func quotientF(_ lhs: RGBA, _ rhs: Float) -> RGBA { 429 | return lhs / Double(rhs) 430 | } 431 | 432 | internal static func quotientD(_ lhs: RGBA, _ rhs: Double) -> RGBA { 433 | return lhs / rhs 434 | } 435 | } 436 | 437 | extension UInt8 { 438 | internal init(summableI: Int) { 439 | self = UInt8(summableI) 440 | } 441 | 442 | internal init(summableF: Float) { 443 | self = UInt8(summableF) 444 | } 445 | 446 | internal init(summableD: Double) { 447 | self = UInt8(summableD) 448 | } 449 | 450 | internal var summableI: Int { 451 | return Int(self) 452 | } 453 | 454 | internal var summableF: Float { 455 | return Float(self) 456 | } 457 | 458 | internal var summableD: Double { 459 | return Double(self) 460 | } 461 | 462 | internal static var selfZero: UInt8 { 463 | return 0 464 | } 465 | 466 | 467 | internal static var summableIZero: Int { 468 | return 0 469 | 470 | } 471 | 472 | internal static var summableFZero: Float { 473 | return 0 474 | 475 | } 476 | 477 | internal static var summableDZero: Double { 478 | return 0 479 | 480 | } 481 | 482 | internal static func productI(_ lhs: Int, _ rhs: Int) -> Int { 483 | return lhs * rhs 484 | } 485 | 486 | internal static func productF(_ lhs: Float, _ rhs: Float) -> Float { 487 | return lhs * rhs 488 | } 489 | 490 | internal static func productD(_ lhs: Double, _ rhs: Double) -> Double { 491 | return lhs * rhs 492 | } 493 | 494 | internal static func quotientI(_ lhs: Int, _ rhs: Int) -> Int { 495 | return lhs / rhs 496 | } 497 | 498 | internal static func quotientF(_ lhs: Float, _ rhs: Float) -> Float { 499 | return lhs / rhs 500 | } 501 | 502 | internal static func quotientD(_ lhs: Double, _ rhs: Double) -> Double { 503 | return lhs / rhs 504 | } 505 | } 506 | 507 | extension UInt16 { 508 | internal init(summableI: Int) { 509 | self = UInt16(summableI) 510 | } 511 | 512 | internal init(summableF: Float) { 513 | self = UInt16(summableF) 514 | } 515 | 516 | internal init(summableD: Double) { 517 | self = UInt16(summableD) 518 | } 519 | 520 | internal var summableI: Int { 521 | return Int(self) 522 | } 523 | 524 | internal var summableF: Float { 525 | return Float(self) 526 | } 527 | 528 | internal var summableD: Double { 529 | return Double(self) 530 | } 531 | 532 | internal static var selfZero: UInt16 { 533 | return 0 534 | } 535 | 536 | 537 | internal static var summableIZero: Int { 538 | return 0 539 | 540 | } 541 | 542 | internal static var summableFZero: Float { 543 | return 0 544 | 545 | } 546 | 547 | internal static var summableDZero: Double { 548 | return 0 549 | 550 | } 551 | 552 | internal static func productI(_ lhs: Int, _ rhs: Int) -> Int { 553 | return lhs * rhs 554 | } 555 | 556 | internal static func productF(_ lhs: Float, _ rhs: Float) -> Float { 557 | return lhs * rhs 558 | } 559 | 560 | internal static func productD(_ lhs: Double, _ rhs: Double) -> Double { 561 | return lhs * rhs 562 | } 563 | 564 | internal static func quotientI(_ lhs: Int, _ rhs: Int) -> Int { 565 | return lhs / rhs 566 | } 567 | 568 | internal static func quotientF(_ lhs: Float, _ rhs: Float) -> Float { 569 | return lhs / rhs 570 | } 571 | 572 | internal static func quotientD(_ lhs: Double, _ rhs: Double) -> Double { 573 | return lhs / rhs 574 | } 575 | } 576 | 577 | extension UInt32 { 578 | internal init(summableI: Int64) { 579 | self = UInt32(summableI) 580 | } 581 | 582 | internal init(summableF: Float) { 583 | self = UInt32(summableF) 584 | } 585 | 586 | internal init(summableD: Double) { 587 | self = UInt32(summableD) 588 | } 589 | 590 | internal var summableI: Int64 { 591 | return Int64(self) 592 | } 593 | 594 | internal var summableF: Float { 595 | return Float(self) 596 | } 597 | 598 | internal var summableD: Double { 599 | return Double(self) 600 | } 601 | 602 | internal static var selfZero: UInt32 { 603 | return 0 604 | } 605 | 606 | 607 | internal static var summableIZero: Int64 { 608 | return 0 609 | 610 | } 611 | 612 | internal static var summableFZero: Float { 613 | return 0 614 | 615 | } 616 | 617 | internal static var summableDZero: Double { 618 | return 0 619 | 620 | } 621 | 622 | internal static func productI(_ lhs: Int64, _ rhs: Int) -> Int64 { 623 | return lhs * Int64(rhs) 624 | } 625 | 626 | internal static func productF(_ lhs: Float, _ rhs: Float) -> Float { 627 | return lhs * rhs 628 | } 629 | 630 | internal static func productD(_ lhs: Double, _ rhs: Double) -> Double { 631 | return lhs * rhs 632 | } 633 | 634 | internal static func quotientI(_ lhs: Int64, _ rhs: Int) -> Int64 { 635 | return lhs / Int64(rhs) 636 | } 637 | 638 | internal static func quotientF(_ lhs: Float, _ rhs: Float) -> Float { 639 | return lhs / rhs 640 | } 641 | 642 | internal static func quotientD(_ lhs: Double, _ rhs: Double) -> Double { 643 | return lhs / rhs 644 | } 645 | } 646 | 647 | extension Int { 648 | internal init(summableI: Int) { 649 | self = summableI 650 | } 651 | 652 | internal init(summableF: Float) { 653 | self = Int(summableF) 654 | } 655 | 656 | internal init(summableD: Double) { 657 | self = Int(summableD) 658 | } 659 | 660 | internal var summableI: Int { 661 | return self 662 | } 663 | 664 | internal var summableF: Float { 665 | return Float(self) 666 | } 667 | 668 | internal var summableD: Double { 669 | return Double(self) 670 | } 671 | 672 | internal static var selfZero: Int { 673 | return 0 674 | } 675 | 676 | 677 | internal static var summableIZero: Int { 678 | return 0 679 | 680 | } 681 | 682 | internal static var summableFZero: Float { 683 | return 0 684 | 685 | } 686 | 687 | internal static var summableDZero: Double { 688 | return 0 689 | 690 | } 691 | 692 | internal static func productI(_ lhs: Int, _ rhs: Int) -> Int { 693 | return lhs * rhs 694 | } 695 | 696 | internal static func productF(_ lhs: Float, _ rhs: Float) -> Float { 697 | return lhs * rhs 698 | } 699 | 700 | internal static func productD(_ lhs: Double, _ rhs: Double) -> Double { 701 | return lhs * rhs 702 | } 703 | 704 | internal static func quotientI(_ lhs: Int, _ rhs: Int) -> Int { 705 | return lhs / rhs 706 | } 707 | 708 | internal static func quotientF(_ lhs: Float, _ rhs: Float) -> Float { 709 | return lhs / rhs 710 | } 711 | 712 | internal static func quotientD(_ lhs: Double, _ rhs: Double) -> Double { 713 | return lhs / rhs 714 | } 715 | } 716 | 717 | extension Float { 718 | internal init(summableI: Float) { 719 | self = summableI 720 | } 721 | 722 | internal init(summableF: Float) { 723 | self = summableF 724 | } 725 | 726 | internal init(summableD: Double) { 727 | self = Float(summableD) 728 | } 729 | 730 | internal var summableI: Float { 731 | return self 732 | } 733 | 734 | internal var summableF: Float { 735 | return self 736 | } 737 | 738 | internal var summableD: Double { 739 | return Double(self) 740 | } 741 | 742 | internal static var selfZero: Float { 743 | return 0 744 | } 745 | 746 | 747 | internal static var summableIZero: Float { 748 | return 0 749 | 750 | } 751 | 752 | internal static var summableFZero: Float { 753 | return 0 754 | 755 | } 756 | 757 | internal static var summableDZero: Double { 758 | return 0 759 | 760 | } 761 | 762 | internal static func productI(_ lhs: Float, _ rhs: Int) -> Float { 763 | return lhs * Float(rhs) 764 | } 765 | 766 | internal static func productF(_ lhs: Float, _ rhs: Float) -> Float { 767 | return lhs * rhs 768 | } 769 | 770 | internal static func productD(_ lhs: Double, _ rhs: Double) -> Double { 771 | return lhs * rhs 772 | } 773 | 774 | internal static func quotientI(_ lhs: Float, _ rhs: Int) -> Float { 775 | return lhs / Float(rhs) 776 | } 777 | 778 | internal static func quotientF(_ lhs: Float, _ rhs: Float) -> Float { 779 | return lhs / rhs 780 | } 781 | 782 | internal static func quotientD(_ lhs: Double, _ rhs: Double) -> Double { 783 | return lhs / rhs 784 | } 785 | } 786 | 787 | extension Double { 788 | internal init(summableI: Double) { 789 | self = summableI 790 | } 791 | 792 | internal init(summableF: Double) { 793 | self = summableF 794 | } 795 | 796 | internal init(summableD: Double) { 797 | self = summableD 798 | } 799 | 800 | internal var summableI: Double { 801 | return self 802 | } 803 | 804 | internal var summableF: Double { 805 | return self 806 | } 807 | 808 | internal var summableD: Double { 809 | return self 810 | } 811 | 812 | internal static var selfZero: Double { 813 | return 0 814 | } 815 | 816 | 817 | internal static var summableIZero: Double { 818 | return 0 819 | 820 | } 821 | 822 | internal static var summableFZero: Double { 823 | return 0 824 | 825 | } 826 | 827 | internal static var summableDZero: Double { 828 | return 0 829 | 830 | } 831 | 832 | internal static func productI(_ lhs: Double, _ rhs: Int) -> Double { 833 | return lhs * Double(rhs) 834 | } 835 | 836 | internal static func productF(_ lhs: Double, _ rhs: Float) -> Double { 837 | return lhs * Double(rhs) 838 | } 839 | 840 | internal static func productD(_ lhs: Double, _ rhs: Double) -> Double { 841 | return lhs * rhs 842 | } 843 | 844 | internal static func quotientI(_ lhs: Double, _ rhs: Int) -> Double { 845 | return lhs / Double(rhs) 846 | } 847 | 848 | internal static func quotientF(_ lhs: Double, _ rhs: Float) -> Double { 849 | return lhs / Double(rhs) 850 | } 851 | 852 | internal static func quotientD(_ lhs: Double, _ rhs: Double) -> Double { 853 | return lhs / rhs 854 | } 855 | } 856 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/Summable.swift.gyb: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal protocol _Summable { 4 | static func +(lhs: Self, rhs: Self) -> Self 5 | } 6 | extension Int: _Summable {} 7 | extension Int64: _Summable {} 8 | extension Float: _Summable {} 9 | extension Double: _Summable {} 10 | 11 | internal protocol _Multipliable { 12 | static func *(lhs: Self, rhs: Self) -> Self 13 | } 14 | extension Int: _Multipliable {} 15 | extension Int64: _Multipliable {} 16 | extension Float: _Multipliable {} 17 | extension Double: _Multipliable {} 18 | 19 | internal protocol _Dividable { 20 | static func /(lhs: Self, rhs: Self) -> Self 21 | } 22 | extension Int: _Dividable {} 23 | extension Int64: _Dividable {} 24 | extension Float: _Dividable {} 25 | extension Double: _Dividable {} 26 | 27 | internal func *(lhs: RGBA, rhs: T) -> RGBA { 28 | return RGBA(red: lhs.red * rhs, green: lhs.green * rhs, blue: lhs.blue * rhs, alpha: lhs.alpha * rhs) 29 | } 30 | 31 | internal func /(lhs: RGBA, rhs: T) -> RGBA { 32 | return RGBA(red: lhs.red / rhs, green: lhs.green / rhs, blue: lhs.blue / rhs, alpha: lhs.alpha / rhs) 33 | } 34 | % 35 | % types = ['UInt8', 'UInt16', 'UInt32', 'Int', 'Float', 'Double'] 36 | % type_to_summables = { 37 | % 'UInt8' : ['Int', 'Float', 'Double'], 38 | % 'UInt16': ['Int', 'Float', 'Double'], 39 | % 'UInt32': ['Int64', 'Float', 'Double'], 40 | % 'Int' : ['Int', 'Float', 'Double'], 41 | % 'Float' : ['Float', 'Float', 'Double'], 42 | % 'Double': ['Double', 'Double', 'Double'], 43 | % } 44 | % summable_sybmols = ['I', 'F', 'D'] 45 | % summable_weights = ['Int', 'Float', 'Double'] 46 | % 47 | % for type in types: 48 | 49 | extension RGBA where Channel == ${type} { 50 | % summables = type_to_summables[type] 51 | % 52 | % for i, (summable_symbol, summable) in enumerate(zip(summable_sybmols, summables)): 53 | % if i > 0: 54 | 55 | % end 56 | internal init(summable${summable_symbol}: RGBA<${summable}>) { 57 | % if summable == type: 58 | self = summable${summable_symbol} 59 | % else: 60 | self = RGBA<${type}>(summable${summable_symbol}) 61 | % end 62 | } 63 | % end 64 | % 65 | % for summable_symbol, summable in zip(summable_sybmols, summables): 66 | 67 | internal var summable${summable_symbol}: RGBA<${summable}> { 68 | % if summable == type: 69 | return self 70 | % else: 71 | return RGBA<${summable}>(red: ${summable}(red), green: ${summable}(green), blue: ${summable}(blue), alpha: ${summable}(alpha)) 72 | % end 73 | } 74 | % end 75 | 76 | internal static var selfZero: RGBA<${type}> { 77 | return RGBA<${type}>(red: 0, green: 0, blue: 0, alpha: 0) 78 | } 79 | 80 | % for summable_symbol, summable in zip(summable_sybmols, summables): 81 | 82 | internal static var summable${summable_symbol}Zero: RGBA<${summable}> { 83 | return RGBA<${summable}>(red: 0, green: 0, blue: 0, alpha: 0) 84 | } 85 | % end 86 | % 87 | % for summable_symbol, summable_weight, summable in zip(summable_sybmols, summable_weights, summables): 88 | 89 | internal static func product${summable_symbol}(_ lhs: RGBA<${summable}>, _ rhs: ${summable_weight}) -> RGBA<${summable}> { 90 | % if summable_weight == summable: 91 | return lhs * rhs 92 | % else: 93 | return lhs * ${summable}(rhs) 94 | % end 95 | } 96 | % end 97 | % for summable_symbol, summable_weight, summable in zip(summable_sybmols, summable_weights, summables): 98 | 99 | internal static func quotient${summable_symbol}(_ lhs: RGBA<${summable}>, _ rhs: ${summable_weight}) -> RGBA<${summable}> { 100 | % if summable_weight == summable: 101 | return lhs / rhs 102 | % else: 103 | return lhs / ${summable}(rhs) 104 | % end 105 | } 106 | % end 107 | } 108 | % end 109 | % 110 | % for type in types: 111 | 112 | extension ${type} { 113 | % summables = type_to_summables[type] 114 | % 115 | % for i, (summable_symbol, summable) in enumerate(zip(summable_sybmols, summables)): 116 | % if i > 0: 117 | 118 | % end 119 | internal init(summable${summable_symbol}: ${summable}) { 120 | % if summable == type: 121 | self = summable${summable_symbol} 122 | % else: 123 | self = ${type}(summable${summable_symbol}) 124 | % end 125 | } 126 | % end 127 | % 128 | % for summable_symbol, summable in zip(summable_sybmols, summables): 129 | 130 | internal var summable${summable_symbol}: ${summable} { 131 | % if summable == type: 132 | return self 133 | % else: 134 | return ${summable}(self) 135 | % end 136 | } 137 | % end 138 | 139 | internal static var selfZero: ${type} { 140 | return 0 141 | } 142 | 143 | % for summable_symbol, summable in zip(summable_sybmols, summables): 144 | 145 | internal static var summable${summable_symbol}Zero: ${summable} { 146 | return 0 147 | 148 | } 149 | % end 150 | % 151 | % for summable_symbol, summable_weight, summable in zip(summable_sybmols, summable_weights, summables): 152 | 153 | internal static func product${summable_symbol}(_ lhs: ${summable}, _ rhs: ${summable_weight}) -> ${summable} { 154 | % if summable_weight == summable: 155 | return lhs * rhs 156 | % else: 157 | return lhs * ${summable}(rhs) 158 | % end 159 | } 160 | % end 161 | % 162 | % for summable_symbol, summable_weight, summable in zip(summable_sybmols, summable_weights, summables): 163 | 164 | internal static func quotient${summable_symbol}(_ lhs: ${summable}, _ rhs: ${summable_weight}) -> ${summable} { 165 | % if summable_weight == summable: 166 | return lhs / rhs 167 | % else: 168 | return lhs / ${summable}(rhs) 169 | % end 170 | } 171 | % end 172 | } 173 | % end 174 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/EasyImagy/Util.swift: -------------------------------------------------------------------------------- 1 | internal func getValidCount(_ range: CountableRange, maxCount: Int) -> Int { 2 | return max(min(range.upperBound, maxCount) - max(range.lowerBound, 0), 0) 3 | } 4 | 5 | internal func clamp(_ x: T, lower: T, upper: T) -> T { 6 | return min(max(x, lower), upper) 7 | } 8 | 9 | extension CountableRange { 10 | internal func isSuperset(of other: CountableRange) -> Bool { 11 | return lowerBound <= other.lowerBound && other.upperBound <= upperBound || other.isEmpty 12 | } 13 | } 14 | 15 | internal func countableRange(from range: R, relativeTo collection: CountableRange) -> CountableRange where R.Bound == Int { 16 | let all = Int.min ..< Int.max 17 | let boundedRange: Range = range.relative(to: all) 18 | let lowerBound: Int 19 | let upperBound: Int 20 | if boundedRange.lowerBound == .min { 21 | lowerBound = Swift.max(boundedRange.lowerBound, collection.lowerBound) 22 | } else { 23 | lowerBound = boundedRange.lowerBound 24 | } 25 | if boundedRange.upperBound == .max { 26 | upperBound = Swift.min(collection.upperBound, boundedRange.upperBound) 27 | } else { 28 | upperBound = boundedRange.upperBound 29 | } 30 | return lowerBound.., relativeTo collection: CountableRange) -> CountableRange { 34 | return range 35 | } 36 | 37 | internal func countableRange(from range: CountableClosedRange, relativeTo collection: CountableRange) -> CountableRange { 38 | return CountableRange(range) 39 | } 40 | 41 | internal func countableRange(from range: Range, relativeTo collection: CountableRange) -> CountableRange { 42 | return CountableRange(range) 43 | } 44 | 45 | internal func countableRange(from range: ClosedRange, relativeTo collection: CountableRange) -> CountableRange { 46 | return CountableRange(range) 47 | } 48 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // UIImage+Spectrogram 4 | // 5 | // Created by AL on 24/08/2018. 6 | // Copyright © 2018 Alban. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Double { 12 | func roundToDecimal(_ fractionDigits: Int) -> Double { 13 | let multiplier = pow(10, Double(fractionDigits)) 14 | return Darwin.round(self * multiplier) / multiplier 15 | } 16 | } 17 | 18 | public extension Float { 19 | func roundToDecimal(_ fractionDigits: Int) -> Float { 20 | let multiplier = pow(10, Float(fractionDigits)) 21 | return Darwin.round(self * multiplier) / multiplier 22 | } 23 | } 24 | 25 | public extension Array { 26 | func chunks(_ chunkSize: Int) -> [[Element]] { 27 | return stride(from: 0, to: self.count, by: chunkSize).map { 28 | Array(self[$0.. [[Float]] { 13 | 14 | let signalMean = meanOfSignal(input) 15 | let signal = substracValue(signalMean, toSignal: input) 16 | let windowedSignal = applyWindow(input) 17 | let signalAudio = windowedSignal.chunks(chunkSize) 18 | let normalizedMagnitudes:[[Float]] = signalAudio.map{ 19 | let fftValues = fft($0) 20 | let real = fftValues.real 21 | return normalizeFFT(squareValues(Array(real[0...real.count/2]))) 22 | } 23 | return normalizedMagnitudes 24 | } 25 | 26 | public func fft(_ input: [Float]) -> (real:[Float], img:[Float]) { 27 | 28 | var real = input 29 | let size = real.count 30 | 31 | var imaginary = [Float](repeating: 0.0, count: input.count) 32 | var splitComplex = DSPSplitComplex(realp: &real, imagp: &imaginary) 33 | 34 | let length = vDSP_Length(floor(log2(Float(size)))) 35 | let radix = FFTRadix(kFFTRadix5) 36 | let weights = vDSP_create_fftsetup(length, radix) 37 | 38 | vDSP_fft_zip(weights!, &splitComplex, 1, length, FFTDirection(FFT_FORWARD)) 39 | 40 | vDSP_destroy_fftsetup(weights) 41 | 42 | return (real,imaginary) 43 | } 44 | 45 | func applyWindow(_ signal:[Float]) -> [Float] { 46 | // // Hamming 47 | let size = signal.count 48 | var windowedSignal = [Float](repeating: 0.0, count: size) 49 | var window = [Float](repeating: 0.0, count: size) 50 | vDSP_hamm_window(&window, UInt(size), 0) 51 | vDSP_vmul(signal, 1, window, 1, &windowedSignal, 1, UInt(size)) 52 | return windowedSignal 53 | } 54 | 55 | func substracValue(_ values:Float, toSignal signal:[Float]) -> [Float] { 56 | 57 | let size = signal.count 58 | var sub:[Float] = [Float](repeating: -values, count: size) 59 | var substractedValues = [Float](repeating: 0.0, count: Int(size)) 60 | vDSP_vsub(signal, 1, &sub, 1, &substractedValues, 1, vDSP_Length(size)) 61 | return substractedValues 62 | } 63 | 64 | 65 | func meanOfSignal(_ values:[Float]) -> Float { 66 | var mean:Float = 0 67 | vDSP_meanv(values, 1, &mean, vDSP_Length(values.count)) 68 | return mean 69 | } 70 | 71 | public func normalizeFFT(_ values:[Float]) -> [Float] { 72 | 73 | var size = values.count 74 | var normalizedValues = [Float](repeating: 0.0, count: Int(size)) 75 | var origninalSize = Float(values.count*2) 76 | vDSP_vsmul(values, 1, &origninalSize, &normalizedValues, 1, vDSP_Length(Int(size))) 77 | 78 | return normalizedValues 79 | } 80 | 81 | func normalize(_ values:[Float]) -> [Float] { 82 | if let min = values.min(), 83 | let max = values.max() { 84 | 85 | return values.map{ Rescale(from: (min,max), to: (0,1)).rescale($0) } 86 | 87 | }else{ 88 | return values 89 | } 90 | 91 | } 92 | 93 | public func squareValues(_ values:[Float]) -> [Float] { 94 | let size = values.count 95 | var squaredValues = [Float](repeating: 0.0, count: size) 96 | vDSP_vsq(values, 1, &squaredValues, 1, vDSP_Length(size)) 97 | return squaredValues 98 | } 99 | 100 | public func ifft(_ real: [Float],img: [Float]) -> [Float] { 101 | 102 | var real = real 103 | var size:Float = Float(real.count) 104 | var normalizeSize = 1.0/size 105 | 106 | var imaginary = img 107 | 108 | vDSP_vsmul(real, 1, &normalizeSize, &real, 1, vDSP_Length(size)) 109 | vDSP_vsmul(imaginary, 1, &normalizeSize, &imaginary, 1, vDSP_Length(size)) 110 | 111 | var splitComplex = DSPSplitComplex(realp: &real, imagp: &imaginary) 112 | 113 | let length = vDSP_Length(floor(log2(Float(size)))) 114 | let radix = FFTRadix(kFFTRadix2) 115 | let weights = vDSP_create_fftsetup(length, radix) 116 | vDSP_fft_zip(weights!, &splitComplex, 1, length, FFTDirection(FFT_INVERSE)) 117 | 118 | vDSP_destroy_fftsetup(weights) 119 | 120 | return real 121 | } 122 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/ImageExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageExtension.swift 3 | // ImageDatasGetter 4 | // 5 | // Created by alban perli on 22.02.17. 6 | // Copyright © 2017 alban perli. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreImage 11 | 12 | // MARK: - Image Scaling. 13 | extension UIImage { 14 | 15 | class func processPixels(in image: UIImage) -> UIImage? { 16 | guard let inputCGImage = image.cgImage else { 17 | print("unable to get cgImage") 18 | return nil 19 | } 20 | let colorSpace = CGColorSpaceCreateDeviceRGB() 21 | let width = inputCGImage.width 22 | let height = inputCGImage.height 23 | let bytesPerPixel = 4 24 | let bitsPerComponent = 8 25 | let bytesPerRow = bytesPerPixel * width 26 | let bitmapInfo = RGBA32.bitmapInfo 27 | 28 | guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else { 29 | print("unable to create context") 30 | return nil 31 | } 32 | context.draw(inputCGImage, in: CGRect(x: 0, y: 0, width: width, height: height)) 33 | 34 | guard let buffer = context.data else { 35 | print("unable to get context data") 36 | return nil 37 | } 38 | 39 | let pixelBuffer = buffer.bindMemory(to: RGBA32.self, capacity: width * height) 40 | 41 | for row in 0 ..< Int(height) { 42 | for column in 0 ..< Int(width) { 43 | let offset = row * width + column 44 | if pixelBuffer[offset] == .black { 45 | pixelBuffer[offset] = .transparent 46 | } 47 | } 48 | } 49 | 50 | let outputCGImage = context.makeImage()! 51 | let outputImage = UIImage(cgImage: outputCGImage, scale: image.scale, orientation: image.imageOrientation) 52 | 53 | return outputImage 54 | } 55 | 56 | struct RGBA32: Equatable { 57 | private var color: UInt32 58 | 59 | var redComponent: UInt8 { 60 | return UInt8((color >> 24) & 255) 61 | } 62 | 63 | var greenComponent: UInt8 { 64 | return UInt8((color >> 16) & 255) 65 | } 66 | 67 | var blueComponent: UInt8 { 68 | return UInt8((color >> 8) & 255) 69 | } 70 | 71 | var alphaComponent: UInt8 { 72 | return UInt8((color >> 0) & 255) 73 | } 74 | 75 | init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) { 76 | color = (UInt32(red) << 24) | (UInt32(green) << 16) | (UInt32(blue) << 8) | (UInt32(alpha) << 0) 77 | } 78 | 79 | static let red = RGBA32(red: 255, green: 0, blue: 0, alpha: 255) 80 | static let green = RGBA32(red: 0, green: 255, blue: 0, alpha: 255) 81 | static let blue = RGBA32(red: 0, green: 0, blue: 255, alpha: 255) 82 | static let white = RGBA32(red: 255, green: 255, blue: 255, alpha: 255) 83 | static let black = RGBA32(red: 0, green: 0, blue: 0, alpha: 255) 84 | static let magenta = RGBA32(red: 255, green: 0, blue: 255, alpha: 255) 85 | static let yellow = RGBA32(red: 255, green: 255, blue: 0, alpha: 255) 86 | static let cyan = RGBA32(red: 0, green: 255, blue: 255, alpha: 255) 87 | static let transparent = RGBA32(red: 0, green: 255, blue: 255, alpha: 0) 88 | 89 | static let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue 90 | 91 | static func ==(lhs: RGBA32, rhs: RGBA32) -> Bool { 92 | return lhs.color == rhs.color 93 | } 94 | } 95 | 96 | func pixel(in image: UIImage, at point: CGPoint) -> (UInt8, UInt8, UInt8, UInt8)? { 97 | let width = Int(image.size.width) 98 | let height = Int(image.size.height) 99 | let x = Int(point.x) 100 | let y = Int(point.y) 101 | guard x < width && y < height else { 102 | return nil 103 | } 104 | guard let cfData:CFData = image.cgImage?.dataProvider?.data, let pointer = CFDataGetBytePtr(cfData) else { 105 | return nil 106 | } 107 | let bytesPerPixel = 4 108 | let offset = (x + y * width) * bytesPerPixel 109 | return (pointer[offset], pointer[offset + 1], pointer[offset + 2], pointer[offset + 3]) 110 | } 111 | 112 | // Retreive intensity (alpha) of each pixel from this image 113 | func pixelsArray()->(pixelValues: [Float], width: Int, height: Int){ 114 | 115 | let width = Int(self.size.width) 116 | let height = Int(self.size.height) 117 | var pixelsArray = [Float]() 118 | 119 | for i in 0.. UIImage { 135 | let context = CIContext(options: nil) 136 | 137 | let currentFilter = CIFilter(name: "CIPhotoEffectNoir") 138 | currentFilter?.setValue(CIImage(image:self), forKey: kCIInputImageKey) 139 | let output = currentFilter?.outputImage 140 | let cgImg = context.createCGImage(output!, from: (output?.extent)!) 141 | let processedImage = UIImage(cgImage: cgImg!) 142 | return processedImage 143 | } 144 | 145 | 146 | public func grayscaled() -> UIImage? { 147 | guard let cgImage = cgImage else { return nil } 148 | let colorSpace = CGColorSpaceCreateDeviceGray() 149 | let (width, height) = (Int(size.width), Int(size.height)) 150 | 151 | // Build context: one byte per pixel, no alpha 152 | guard let context = CGContext(data: nil, width: width, 153 | height: height, bitsPerComponent: 8, 154 | bytesPerRow: width, space: colorSpace, 155 | bitmapInfo: CGImageAlphaInfo.none.rawValue) 156 | else { return nil } 157 | 158 | // Draw to context 159 | let destination = CGRect(origin: .zero, size: size) 160 | context.draw(cgImage, in: destination) 161 | 162 | // Return the grayscale image 163 | guard let imageRef = context.makeImage() 164 | else { return nil } 165 | return UIImage(cgImage: imageRef) 166 | } 167 | 168 | 169 | 170 | func toSize(newSize: CGSize) -> UIImage { 171 | // Guard newSize is different 172 | guard self.size != newSize else { return self } 173 | 174 | UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0); 175 | self.draw(in: CGRect(x:0, y:0, width:newSize.width, height:newSize.height)) 176 | let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()! 177 | UIGraphicsEndImageContext() 178 | return newImage 179 | } 180 | 181 | // Extract sub image based on the given frame 182 | // x,y top left | x,y bottom right | pixels margin above this frame 183 | func extractFrame( topLeft: CGPoint, bottomRight: CGPoint, pixelMargin: CGFloat) -> UIImage { 184 | 185 | var topLeft = topLeft 186 | var bottomRight = bottomRight 187 | topLeft.x = topLeft.x - pixelMargin 188 | topLeft.y = topLeft.y - pixelMargin 189 | bottomRight.x = bottomRight.x + pixelMargin 190 | bottomRight.y = bottomRight.y + pixelMargin 191 | 192 | let size:CGSize = CGSize(width:bottomRight.x-topLeft.x, height:bottomRight.y-topLeft.y) 193 | let rect = CGRect(x:-topLeft.x, y:-topLeft.y, width:self.size.width, height:self.size.height) 194 | UIGraphicsBeginImageContext(size) 195 | 196 | self.draw(in: rect) 197 | 198 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 199 | UIGraphicsEndImageContext() 200 | 201 | return newImage! 202 | 203 | } 204 | 205 | /// Represents a scaling mode 206 | enum ScalingMode { 207 | case aspectFill 208 | case aspectFit 209 | 210 | /// Calculates the aspect ratio between two sizes 211 | /// 212 | /// - parameters: 213 | /// - size: the first size used to calculate the ratio 214 | /// - otherSize: the second size used to calculate the ratio 215 | /// 216 | /// - return: the aspect ratio between the two sizes 217 | func aspectRatio(between size: CGSize, and otherSize: CGSize) -> CGFloat { 218 | let aspectWidth = size.width/otherSize.width 219 | let aspectHeight = size.height/otherSize.height 220 | 221 | switch self { 222 | case .aspectFill: 223 | return max(aspectWidth, aspectHeight) 224 | case .aspectFit: 225 | return min(aspectWidth, aspectHeight) 226 | } 227 | } 228 | } 229 | 230 | /// Scales an image to fit within a bounds with a size governed by the passed size. Also keeps the aspect ratio. 231 | /// 232 | /// - parameter: 233 | /// - newSize: the size of the bounds the image must fit within. 234 | /// - scalingMode: the desired scaling mode 235 | /// 236 | /// - returns: a new scaled image. 237 | func scaled(to newSize: CGSize, scalingMode: UIImage.ScalingMode = .aspectFill) -> UIImage { 238 | 239 | let aspectRatio = scalingMode.aspectRatio(between: newSize, and: size) 240 | 241 | /* Build the rectangle representing the area to be drawn */ 242 | var scaledImageRect = CGRect.zero 243 | 244 | scaledImageRect.size.width = size.width * aspectRatio 245 | scaledImageRect.size.height = size.height * aspectRatio 246 | scaledImageRect.origin.x = (newSize.width - size.width * aspectRatio) / 2.0 247 | scaledImageRect.origin.y = (newSize.height - size.height * aspectRatio) / 2.0 248 | 249 | /* Draw and retrieve the scaled image */ 250 | UIGraphicsBeginImageContext(newSize) 251 | 252 | draw(in: scaledImageRect) 253 | let scaledImage = UIGraphicsGetImageFromCurrentImageContext() 254 | 255 | UIGraphicsEndImageContext() 256 | 257 | return scaledImage! 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/ImageUtils.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import CoreGraphics 3 | import CoreImage 4 | 5 | public class ImageUtils { 6 | 7 | public static func drawSpectrogram(magnitudes:[[Float]], sampleRate:Float, width:Int, height:Int) -> UIImage? { 8 | 9 | let image = ImageUtils.drawMagnitudes(magnitudes: magnitudes, width: width, height: height-60) 10 | let fftSize = magnitudes.first!.count 11 | let bandDuration = (1/(sampleRate/Float(fftSize))) 12 | let numberOfBand = magnitudes.count 13 | 14 | let allTimeSteps = (0.. UIImage? { 42 | 43 | let timeWidth = magnitudes.count 44 | let frequencyHeight = magnitudes.first!.count 45 | 46 | let pixels = magnitudes.map{ $0.map{ RGBA(gray: 1-$0) } } 47 | 48 | let flattenedPixels = pixels.flatMap{ $0 } 49 | 50 | var image = Image>(width: frequencyHeight, height:timeWidth , pixels: flattenedPixels) 51 | image = image.rotated(byDegrees: -90) 52 | 53 | if let w = width, 54 | let h = height { 55 | image = image.resizedTo(width: w, height: h) 56 | } 57 | 58 | return image.uiImage 59 | } 60 | 61 | public static func drawImageForMagnitudes(magsArray: [[Double]]) -> UIImage? { 62 | 63 | let imageSize = CGSize(width: magsArray.count-1, height: magsArray.first!.count) 64 | // Create a context of the starting image size and set it as the current one 65 | UIGraphicsBeginImageContext(imageSize) 66 | 67 | 68 | // Get the current context 69 | let context = UIGraphicsGetCurrentContext()! 70 | context.setFillColor(UIColor.white.cgColor) 71 | context.fill(CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height)) 72 | 73 | for x in 0.. UIColor { 88 | return UIColor(hue: 0.75, saturation: CGFloat(magnitude), brightness: 1.0, alpha: 1.0) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/Rescale.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Rescale.swift 3 | // UIImage+Spectrogram 4 | // 5 | // Created by AL on 25/08/2018. 6 | // Copyright © 2018 Alban. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Rescale { 12 | typealias RescaleDomain = (lowerBound: Type, upperBound: Type) 13 | 14 | var fromDomain: RescaleDomain 15 | var toDomain: RescaleDomain 16 | 17 | init(from: RescaleDomain, to: RescaleDomain) { 18 | self.fromDomain = from 19 | self.toDomain = to 20 | } 21 | 22 | func interpolate(_ x: Type ) -> Type { 23 | return self.toDomain.lowerBound * (1 - x) + self.toDomain.upperBound * x; 24 | } 25 | 26 | func uninterpolate(_ x: Type) -> Type { 27 | let b = (self.fromDomain.upperBound - self.fromDomain.lowerBound) != 0 ? self.fromDomain.upperBound - self.fromDomain.lowerBound : 1 / self.fromDomain.upperBound; 28 | return (x - self.fromDomain.lowerBound) / b 29 | } 30 | 31 | func rescale(_ x: Type ) -> Type { 32 | return interpolate( uninterpolate(x) ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/UIColorExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color.swift 3 | // UIImage+Spectrogram 4 | // 5 | // Created by AL on 26/08/2018. 6 | // Copyright © 2018 Alban. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIColor { 13 | // MARK: - UIColor+Percentage 14 | 15 | 16 | class func colorForProgress(progress:CGFloat) -> UIColor { 17 | 18 | var normalizedProgress = progress < 0 ? 0 : progress 19 | normalizedProgress = normalizedProgress > 1 ? 1 : normalizedProgress 20 | 21 | let R:CGFloat = 155.0 * normalizedProgress 22 | let G:CGFloat = 155.0 * (1 - normalizedProgress) 23 | let B:CGFloat = 0.0 24 | 25 | return UIColor(red: R / 255.0, green: G / 255.0, blue: B / 255.0, alpha: 1) 26 | } 27 | 28 | class func transitionColor(fromColor:UIColor, toColor:UIColor, progress:CGFloat) -> UIColor { 29 | 30 | var percentage = progress < 0 ? 0 : progress 31 | percentage = percentage > 1 ? 1 : percentage 32 | 33 | var fRed:CGFloat = 0 34 | var fBlue:CGFloat = 0 35 | var fGreen:CGFloat = 0 36 | var fAlpha:CGFloat = 0 37 | 38 | var tRed:CGFloat = 0 39 | var tBlue:CGFloat = 0 40 | var tGreen:CGFloat = 0 41 | var tAlpha:CGFloat = 0 42 | 43 | fromColor.getRed(&fRed, green: &fGreen, blue: &fBlue, alpha: &fAlpha) 44 | toColor.getRed(&tRed, green: &tGreen, blue: &tBlue, alpha: &tAlpha) 45 | 46 | let red:CGFloat = (percentage * (tRed - fRed)) + fRed; 47 | let green:CGFloat = (percentage * (tGreen - fGreen)) + fGreen; 48 | let blue:CGFloat = (percentage * (tBlue - fBlue)) + fBlue; 49 | let alpha:CGFloat = (percentage * (tAlpha - fAlpha)) + fAlpha; 50 | 51 | return UIColor(red: red, green: green, blue: blue, alpha: alpha) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /UIImage+Spectrogram/UIImage_Spectrogram.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage_Spectrogram.h 3 | // UIImage+Spectrogram 4 | // 5 | // Created by AL on 22/08/2018. 6 | // Copyright © 2018 Alban. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for UIImage_Spectrogram. 12 | FOUNDATION_EXPORT double UIImage_SpectrogramVersionNumber; 13 | 14 | //! Project version string for UIImage_Spectrogram. 15 | FOUNDATION_EXPORT const unsigned char UIImage_SpectrogramVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /UIImage_SpectrogramTests/Avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbanPerli/iOS-Spectrogram/d38129099cbaadc0d8c4feeff11318f744ece02e/UIImage_SpectrogramTests/Avatar.png -------------------------------------------------------------------------------- /UIImage_SpectrogramTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /UIImage_SpectrogramTests/UIImage_SpectrogramTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage_SpectrogramTests.swift 3 | // UIImage_SpectrogramTests 4 | // 5 | // Created by AL on 24/08/2018. 6 | // Copyright © 2018 Alban. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class UIImage_SpectrogramTests: XCTestCase { 12 | 13 | 14 | 15 | override func setUp() { 16 | super.setUp() 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | } 19 | 20 | override func tearDown() { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | super.tearDown() 23 | } 24 | 25 | func testAudioLoadingTypes() { 26 | let testBundle = Bundle(for: type(of: self)) 27 | if let filePath = testBundle.path(forResource: "bonjour", ofType: "wav") { 28 | let url = URL(fileURLWithPath: filePath) 29 | let rawAudioDouble = DataLoader.loadAudioSamplesArrayOf(Double.self, atUrl: url, sampleRate: 44100) 30 | let rawAudioFloat = DataLoader.loadAudioSamplesArrayOf(Float.self, atUrl: url, sampleRate: 44100) 31 | 32 | XCTAssert(rawAudioFloat?.count == rawAudioDouble?.count) 33 | 34 | for (d,f) in zip(rawAudioDouble!,rawAudioFloat!) { 35 | XCTAssert(Float(d.roundToDecimal(4)) == Float(f.roundToDecimal(4))) 36 | } 37 | 38 | }else{ 39 | XCTFail("Missing file in Test Target") 40 | } 41 | } 42 | 43 | func testFFTtoIFFT() { 44 | var samples = [Float](repeating: 0.0, count: 8) 45 | samples[1] = 1.0 46 | let fftValues = fft(samples) 47 | let real = fftValues.real 48 | let img = fftValues.img 49 | 50 | let signal = ifft(real, img: img) 51 | 52 | let roundedSignal = signal.map{ $0.roundToDecimal(3) } 53 | 54 | XCTAssert(roundedSignal == samples) 55 | } 56 | 57 | func testFFTFromAudio() { 58 | let testBundle = Bundle(for: type(of: self)) 59 | if let filePath = testBundle.path(forResource: "bonjour", ofType: "wav") { 60 | let url = URL(fileURLWithPath: filePath) 61 | let rawAudio = DataLoader.loadAudioSamplesArrayOf(Float.self, atUrl:url) 62 | XCTAssertNotNil(rawAudio) 63 | let signalAudio = rawAudio!.chunks(2048) 64 | let fftValues = signalAudio.map{ fft($0) } 65 | 66 | var i = 0 67 | for fftValue in fftValues { 68 | let real = fftValue.real 69 | let img = fftValue.img 70 | var signal = ifft(real, img: img) 71 | signal = signal.map{ $0.roundToDecimal(3) } 72 | let originalSignal = signalAudio[i].map{ $0.roundToDecimal(3) } 73 | XCTAssert(signal == originalSignal) 74 | i += 1 75 | } 76 | 77 | }else{ 78 | XCTFail("Missing file in Test Target") 79 | } 80 | } 81 | 82 | func testFFTFromImage() { 83 | let testBundle = Bundle(for: type(of: self)) 84 | if let filePath = testBundle.path(forResource: "Avatar", ofType: "png") { 85 | //let img = Image(contentsOfFile: filePath) 86 | //XCTAssertNotNil(img) 87 | //let image:UIImage = img!.uiImage 88 | 89 | let red:RGBA = RGBA(red: 0.8, green: 0, blue: 0, alpha: 1.0) 90 | let green:RGBA = RGBA(red: 0, green: 0.8, blue: 0, alpha: 1.0) 91 | let image = Image>(width: 8, height: 8, pixels: [ 92 | red,green,green,green,green,green,green,red, 93 | green,red,red,red,red,red,red,green, 94 | green,red,red,red,red,red,red,green, 95 | green,red,red,green,green,red,red,green, 96 | green,red,red,green,green,red,red,green, 97 | green,red,red,red,red,red,red,green, 98 | green,red,red,red,red,red,red,green, 99 | red,green,green,green,green,green,green,red 100 | ]) 101 | let m:UIImage = image.uiImage 102 | 103 | let pixelsFloat = image.pixels.map{ [$0.red,$0.green,$0.blue,$0.alpha] }.flatMap{ $0 } 104 | 105 | let signalImage = pixelsFloat 106 | let fftValues = fft(signalImage) 107 | 108 | var i = 0 109 | 110 | let real = fftValues.real 111 | let img = fftValues.img 112 | var signal = ifft(real, img: img) 113 | let chunkedSignal = signal.chunks(4) 114 | let rgba:[RGBA] = chunkedSignal.map{ RGBA(red: $0[0], green: $0[1], blue: $0[2], alpha: $0[3]) } 115 | 116 | let rebuildImage = Image>(width: 8, height: 8, pixels: rgba) 117 | 118 | let rebuildUIImage:UIImage = rebuildImage.uiImage 119 | 120 | print(rebuildUIImage) 121 | i += 1 122 | 123 | 124 | print(m) 125 | 126 | 127 | 128 | /* 129 | let pixelsAsSignal = image?.pixelsArray() 130 | 131 | let signalImage = pixelsAsSignal!.pixelValues.chunks(2048) 132 | let fftValues = signalImage.map{ fft($0) } 133 | 134 | var i = 0 135 | for fftValue in fftValues { 136 | let real = fftValue.real 137 | let img = fftValue.img 138 | var signal = ifft(real, img: img) 139 | signal = signal.map{ $0.roundToDecimal(3) } 140 | let originalSignal = signalImage[i].map{ $0.roundToDecimal(3) } 141 | XCTAssert(signal == originalSignal) 142 | i += 1 143 | } 144 | */ 145 | 146 | }else{ 147 | XCTFail("Missing file in Test Target") 148 | } 149 | } 150 | 151 | func testSpectro() { 152 | let testBundle = Bundle(for: type(of: self)) 153 | if let filePath = testBundle.path(forResource: "bonjour", ofType: "wav") { 154 | let url = URL(fileURLWithPath: filePath) 155 | var rawAudio = DataLoader.loadAudioSamplesArrayOf(Float.self, atUrl: url, sampleRate: 44100) 156 | rawAudio = Array(rawAudio![0..<2097152]) 157 | rawAudio = applyWindow(rawAudio!) 158 | let v = fft(rawAudio!).real 159 | 160 | XCTAssertNotNil(rawAudio) 161 | let magnitudes = spectrogramValuesForSignal(input: rawAudio!, chunkSize: 1024) 162 | print(magnitudes.count) 163 | let image = ImageUtils.drawSpectrogram(magnitudes: magnitudes,sampleRate: 44100, width: 640, height: 480) 164 | 165 | print(image) 166 | }else{ 167 | XCTFail("Missing file in Test Target") 168 | } 169 | } 170 | 171 | func testExample() { 172 | // This is an example of a functional test case. 173 | // Use XCTAssert and related functions to verify your tests produce the correct results. 174 | } 175 | 176 | func testPerformanceExample() { 177 | // This is an example of a performance test case. 178 | self.measure { 179 | // Put the code you want to measure the time of here. 180 | } 181 | } 182 | 183 | } 184 | -------------------------------------------------------------------------------- /UIImage_SpectrogramTests/toujours.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbanPerli/iOS-Spectrogram/d38129099cbaadc0d8c4feeff11318f744ece02e/UIImage_SpectrogramTests/toujours.wav --------------------------------------------------------------------------------