-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathFunctions.swift
365 lines (311 loc) · 13.9 KB
/
Functions.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
//
// Functions.swift
// Fast Local Laplacian Filters
//
// Created by David Sacco on 8/12/16.
// Copyright © 2016 David Sacco. All rights reserved.
//
import Foundation
import CoreImage
// GrayScale: Converts target CIImage to grayscale version.
// Inputs : inputImage: the image input, as a CIImage
// Outputs : CIImage
func GrayScale(inputImage: CIImage) -> CIImage
{
let GrayscaleFilter = GrayScaleFilter()
GrayscaleFilter.inputImage = inputImage
return GrayscaleFilter.outputImage
}
// GaussianFilter: Performs a Gaussian Filter by a 5-tap method as descriped in Burte & Adelson's paper. Note the forced padding to prevent a black border affect from transparent pixels.
// Inputs : inputImage: The input to be blurred, as a CIImage
func GaussianFilter(inputImage: CIImage) -> CIImage
{
let input = inputImage.imageByClampingToExtent().imageByCroppingToRect(CGRect(x: -5, y: -5, width: inputImage.extent.width+10, height: inputImage.extent.height+10))
let kernelValues: [CGFloat] = [
0.0025, 0.0125, 0.0200, 0.0125, 0.0025,
0.0125, 0.0625, 0.1000, 0.0625, 0.0125,
0.0200, 0.1000, 0.1600, 0.1000, 0.0200,
0.0125, 0.0625, 0.1000, 0.0625, 0.0125,
0.0025, 0.0125, 0.0200, 0.0125, 0.0025 ]
let weightMatrix = CIVector(values: kernelValues,
count: kernelValues.count)
let filter = CIFilter(name: "CIConvolution5X5",
withInputParameters: [
kCIInputImageKey: input,
kCIInputWeightsKey: weightMatrix])!
let final = filter.outputImage!
let rect = CGRect(x: 0, y: 0, width: inputImage.extent.size.width, height: inputImage.extent.size.height)
return final.imageByCroppingToRect(rect)
}
// LLFRemap: Performs the color remapping as described in the first Local Laplacian Filter paper.
// Inputs : inputImage: the image to be remapped.
// discrete: the sample gaussian coefficient, a value in [0,1].
// sigma_r: parameter that controls what is and isn't an edge.
// alpha: parameter that affects detail manipulation. 0<alpha<1 for enhancement, alpha>1 for diminishment.
// beta: parameter that affects tone manipulation. beta > 1 for inverse tone mapping, beta < 1 for tone mapping.
// Outputs : CIImage
func LLFRemap(inputImage: CIImage, discrete: CGFloat, sigma_r: CGFloat, alpha: CGFloat, beta: CGFloat) -> CIImage
{
let filter = LLFRemapFilter()
filter.inputImage = inputImage
filter.discrete = discrete
filter.sigma_r = sigma_r
filter.alpha = alpha
filter.beta = beta
return filter.outputImage
}
// resampleImage: Resamples the image to the target dimensions.
// Inputs : inputImage: the image to be resampled.
// sizeX: the desired width of the sampled image.
// sizeY: the desired height of the sampled image.
// Outputs : CIImage
func resampleImage(inputImage: CIImage, sizeX: CGFloat, sizeY: CGFloat) -> CIImage
{
let inputWidth : CGFloat = inputImage.extent.size.width
let inputHeight : CGFloat = inputImage.extent.size.height
let scaleX = sizeX/inputWidth
let scaleY = sizeY/inputHeight
let resamplefilter = ResampleFilter()
resamplefilter.inputImage = inputImage
resamplefilter.inputScaleX = scaleX
resamplefilter.inputScaleY = scaleY
return resamplefilter.outputImage
}
// Sum: Takes the pixel by pixel sum of two images.
// Inputs : imageOne: the first Image to be summed.
// imageTwo: the second Image to be summed.
// Outputs : CIImage
func Sum(imageOne:CIImage,imageTwo:CIImage) -> CIImage
{
let generalFilter = SumOfImagesFilter()
generalFilter.inputImage1 = imageOne
generalFilter.inputImage2 = imageTwo
return generalFilter.outputImage
}
// DifferenceA: Takes a sum of one image and a specific floating value, pixel by pixel. This is used for Greyscale images.
// Inputs : inputImage: the image to be added to.
// discrete: A value, in [0,1], to be added pixel by pixel to the image.
// Outputs : CIImage
func DifferenceA(inputImage:CIImage,discrete:CGFloat) -> CIImage
{
let generalFilter = DifferenceAFilter()
generalFilter.inputImage1 = inputImage
generalFilter.discrete = discrete
return generalFilter.outputImage
}
// DifferenceB: The difference between two images. Order here matters, but negative values are preserved in the algorithm.
// Inputs : imageOne: The image that is being subtracted from.
// imageTwo: The image that is imageOne is being subtracted by.
// Outputs : CIImage
func DifferenceB(imageOne:CIImage,imageTwo:CIImage) -> CIImage
{
let generalFilter = DifferenceBFilter()
generalFilter.inputImage1 = imageOne
generalFilter.inputImage2 = imageTwo
return generalFilter.outputImage
}
// FirstThird: This combines the first and third multiplied term in the FLLF algorithm.
// Inputs : image: Presumably a gaussian level image.
// discrete: a sampled gaussian coefficient.
// threshold: a threshold provided constant.
// Outputs : CIImage
func FirstThird(image: CIImage, discrete: CGFloat, threshold: CGFloat) -> CIImage
{
let filter = FirstThirdTermFilter()
filter.inputImage = image
filter.discrete = discrete
filter.threshold = threshold
return filter.outputImage
}
// LevelDimensions: Saves the sizes of the images in the pyramid at all levels, for reference.
// Inputs : image: The image that the pyramid is being computed from.
// levels: The number of levels in the pyramid
// Outputs : an Array of Arrays, each of which containing the width and height of each pyramid level.
func LevelDimensions(image: CIImage,levels:Int) -> [[CGFloat]]
{
let inputWidth : CGFloat = image.extent.width
let inputHeight : CGFloat = image.extent.height
var levelSizes : [[CGFloat]] = [[inputWidth,inputHeight]]
for j in 1...(levels-1)
{
let temp = [floor(inputWidth/pow(2.0,CGFloat(j))),floor(inputHeight/pow(2,CGFloat(j)))]
levelSizes.append(temp)
}
return levelSizes
}
// GaussianLevel: Computes a given level in the Gaussian Pyramid.
// Inputs : image: The image that the pyramid is formed from.
// level: The desired level to be computed.
// Outputs : CIImage
func GaussianLevel(image:CIImage, level:Int) -> CIImage
{
if level == 1 {
return image
}
else{
var GauPyrLevel : CIImage = image
let PyrLevel = LevelDimensions(image, levels: level)
var I : CIImage
var J : CIImage
for j in 2 ... level
{
J = GaussianFilter(GauPyrLevel)
I = resampleImage(J, sizeX: PyrLevel[j-1][0], sizeY: PyrLevel[j-1][1])
GauPyrLevel = I
}
return GauPyrLevel
}
}
// LaplacianLevel: Computes a given level in the Laplacian Pyramid.
// Inputs : image: The image that the pyramid is formed from.
// level: The desired level to be computed.
// Outputs : CIImage
func LaplacianLevel(image:CIImage,level:Int) -> CIImage
{
let PyrLevel = LevelDimensions(image, levels: level+1)
var LapPyrLevel : CIImage?
let GaussLevel = GaussianLevel(image, level: level)
let GaussLevelD = GaussianLevel(image, level: level+1)
let GaussLevelDU = resampleImage(GaussLevelD, sizeX: PyrLevel[level-1][0], sizeY: PyrLevel[level-1][1])
LapPyrLevel = DifferenceB(GaussLevel, imageTwo: GaussLevelDU)
return LapPyrLevel!
}
// AbsDifference: The absolute difference between an image and a value. This is used in the FLLF algorithm. The input image should be a gaussian level, and the discretisation value should be a sampled gaussian coefficient.
// Inputs : image1: The image to be subtacted from.
// discretisation: The value to subtract the image from.
// Outputs : CIImage
func AbsDifference(image1: CIImage, discretisation: CGFloat) -> CIImage
{
let absdiffFilter = AbsoluteDifference()
absdiffFilter.inputImage = image1
absdiffFilter.discrete = discretisation
return absdiffFilter.outputImage
}
// Threshold: Applies a threshold filter with given threshold. Used in the FLLF algorithm.
// Inputs : image: The input image.
// threshold: the threshold value.
// Outputs : CIImage
func Threshold(image: CIImage, threshold: CGFloat) -> CIImage
{
let thresholdFilter = ThresholdFilter()
thresholdFilter.inputImage = image
thresholdFilter.threshold = threshold
return thresholdFilter.outputImage
}
// OneMinusDivide: Applies 1 - (input)/value to the image. This is used in the FLLF algorithm
// Inputs : image: The input image.
// value: the value to be divided by.
// Outputs : CIImage
func OneMinusDivide(image: CIImage, value: CGFloat) -> CIImage
{
let OMFilter = OneMinusFilter()
OMFilter.inputImage = image
OMFilter.discrete = value
return OMFilter.outputImage
}
// Multiply2: Multiplies two images together, pixelwise.
// Inputs : imageOne: The first image to be multiplied.
// imageTwo: The second image to be multiplied.
// Outputs : CIImage
func Multiply2(imageOne: CIImage, imageTwo:CIImage) -> CIImage
{
let multiplyFilter = Multiply2Filter()
multiplyFilter.inputImage1 = imageOne
multiplyFilter.inputImage2 = imageTwo
return multiplyFilter.outputImage
}
// RGBRatio: Image to store the RGB Ratios of an image and it's corresponding grey image.
// Inputs : RGBImage: The input image with color.
// GrayImage: The grayscale version of RGBImage.
// Outputs : CIImage
func RGBRatio(RGBImage: CIImage,GrayImage: CIImage) -> CIImage
{
let RatioFilter = GreyscaleRGBRatioFilter()
RatioFilter.rgbImage = RGBImage
RatioFilter.gsImage = GrayImage
return RatioFilter.outputImage
}
// linspace: Function that splits up the interval [0,1] into N pieces, including 0 and 1.
// Inputs : N: The number of 'splits' to take upon [0,1].
// Outputs : A array containing values where [0,1] has been split up. N=3 yeilds {0,.5,1}.
public func linspace(N: Int) -> [CGFloat]
{
var discrete : [CGFloat] = []
let M = N
for j in 0 ... (M-1)
{
discrete.append(CGFloat(j)/(CGFloat(M)-1.0))
}
return discrete
}
// ImageRemap: This function hosts all of the remapping functions that FLLF.
// Inputs : image: the image to be remapped.
// name: A string, specifying the remapping function to be used. Current options are "LLF" and "Gaussian"
// discrete: sample value of gaussian coefficient.
// arguments: These are the arguments for the remapping function:
// "Gaussian": [sigma, factor]
// sigma: controls variance of neiboring pixels. suggest a value between 0.1 and 0.3.
// factor: controls how much is done. >0 for detail enhancement, <0 for detail reduction.
// "LLF": [sigma_r, alpha, beta]
// sigma_r: decides what is and isn't an edge/detail.
// alpha: controls detail manipulation
// beta: controla tone manipulation
// Outputs : CIImage
func ImageRemap(image: CIImage,name: String, discrete: CGFloat, arguments: [CGFloat]) -> CIImage
{
switch name {
case "Gaussian":
let sampleRemap = SampleRemapFilter()
sampleRemap.inputImage = DifferenceA(image, discrete: discrete)
sampleRemap.sigma = arguments[0]
sampleRemap.fact = arguments[1]
return sampleRemap.outputImage
case "LLF":
return LLFRemap(image, discrete: discrete, sigma_r: arguments[0], alpha: arguments[1], beta: arguments[2])
default:
print("Non-valid Remapping filter chosen")
return image
}
}
// BuildLaplacianLevel: Build a specified level in the output pyramid for FLLF.
// Inputs : image: The image to base the pyramid on.
// mapName: the name of the remapping function to be used.
// MappingArgs: the arguments for the specified mapName.
// discrete: the sampled gaussian coefficient.
// level: the level of the pyramid to be computed.
// Outputs : CIImage
func BuildLaplacianLevel(image: CIImage, mapName: String ,MappingArgs:[CGFloat], discrete:[CGFloat],level:Int)->CIImage
{
let discretisation_step = discrete[1]-discrete[0]
let inputGaussian = GaussianLevel(image, level: level)
//var outputLaplacian = LaplacianLevel(image, level: level)
var outputLaplacian = CIImage(color:CIColor(red: 0, green: 0, blue: 0)).imageByCroppingToRect(inputGaussian.extent)
for k in discrete
{
let Iremap = ImageRemap(image, name: mapName, discrete: k, arguments: MappingArgs)
let SecondTerm = LaplacianLevel(Iremap, level: level)
let TermToAdd = Multiply2(FirstThird(inputGaussian, discrete: k, threshold: discretisation_step), imageTwo: SecondTerm)
outputLaplacian = Sum(outputLaplacian,imageTwo: TermToAdd)
}
return outputLaplacian
}
// FastLocalLaplacianFilter: Performs a FLLF on the image.
// Inputs : inputImage: an input grayscale image to be filtered.
// mapName: the name of the remapping function.
// MappingArgs: the arguments for the corresponding mapName.
// numDiscrete: Number of intesities to sample from, as gaussian coefficients, reccomend setting to >10.
// numLevels: Number of levels in the pyramid. Reccomend setting 1 to 5 for performance, anything longer takes a while to render.
// Outputs : CIImage
public func FastLocalLaplacianFilter(inputImage: CIImage, mapName: String, MappingArgs: [CGFloat], numDiscrete: Int, numLevels: Int) -> CIImage
{
let numLev = numLevels
let numDisc = numDiscrete
let discretization = linspace(numDisc)
var output : CIImage = GaussianLevel(inputImage, level: numLev)
for j in 1...(numLev-1)
{
let tempLap = BuildLaplacianLevel(inputImage, mapName:mapName, MappingArgs: MappingArgs, discrete: discretization, level: numLev-j)
output = Sum(tempLap, imageTwo: resampleImage(output, sizeX: tempLap.extent.width, sizeY: tempLap.extent.height))
}
return output
}