CoreImage:CIColorKernel.init 与 .metallib 导致 EXEC_BAD_ACCESS
CoreImage: CIColorKernel.init with .metallib causes EXEC_BAD_ACCESS
我使用 Metal Shader Language 为 CoreImage 制作了一个 Perlin Noise 过滤器。我的第一个版本运行良好,允许我指定 2 种颜色用作 low
和 high
。我的下一步是让它接受颜色图,以便它可以产生多色输出。颜色映射版本编译正常,但是当我的 CIFilter 子类尝试使用 init(functionName:fromMetalLibraryData:)
方法从它创建 CIColorKernel 时,我得到一个 EXEC_BAD_ACCESS
和 code=1
。知道我的颜色映射实现可能会导致这种情况吗?
这是简单的 2 色版本:
#include <CoreImage/CoreImage.h>
using namespace metal;
constant int p[512] = {151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180};
float fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
float grad(int hash, float x, float y, float z) {
int h = hash & 15;
float u = h < 8 ? x : y;
float v = h < 4 ? y : h == 12 || h == 14 ? x : z;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
float lerp(float t, float x, float y) { return x + t * (y - x); }
float noise(float x, float y, float z) {
int X = (int)floor(x) & 255;
int Y = (int)floor(y) & 255;
int Z = (int)floor(z) & 255;
x -= floor(x);
y -= floor(y);
z -= floor(z);
float u = fade(x);
float v = fade(y);
float w = fade(z);
int A = p[X ] + Y;
int AA = p[A ] + Z;
int AB = p[A + 1] + Z;
int B = p[X + 1] + Y;
int BA = p[B ] + Z;
int BB = p[B + 1] + Z;
float result = lerp(w, lerp(v, lerp(u, grad(p[AA ], x , y , z ), // AND ADD
grad(p[BA ], x-1, y , z )), // BLENDED
lerp(u, grad(p[AB ], x , y-1, z ), // RESULTS
grad(p[BB ], x-1, y-1, z ))),// FROM 8
lerp(v, lerp(u, grad(p[AA+1], x , y , z-1 ), // CORNERS
grad(p[BA+1], x-1, y , z-1 )), // OF CUBE
lerp(u, grad(p[AB+1], x , y-1, z-1 ),
grad(p[BB+1], x-1, y-1, z-1 ))));
return (result + 1.0) / 2.0;
}
extern "C" float4 PerlinNoise (float4 lowColor, float4 highColor, float offsetX, float offsetY, float offsetZ, float scale, float contrast, coreimage::destination dest)
{
float val = noise(dest.coord().x * scale + offsetX, dest.coord().y * scale + offsetY, offsetZ);
return mix(lowColor, highColor, pow(val, contrast));
}
这里是彩图版本:
#include <CoreImage/CoreImage.h>
using namespace metal;
constant uint8_t p[512] = {151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180};
float fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
float grad(uint8_t hash, float x, float y, float z) {
uint8_t h = hash & 15;
float u = h < 8 ? x : y;
float v = h < 4 ? y : h == 12 || h == 14 ? x : z;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
float lerp(float t, float x, float y) { return x + t * (y - x); }
float noise(float x, float y, float z) {
uint8_t X = (uint8_t)floor(x) & 255;
uint8_t Y = (uint8_t)floor(y) & 255;
uint8_t Z = (uint8_t)floor(z) & 255;
x -= floor(x);
y -= floor(y);
z -= floor(z);
float u = fade(x);
float v = fade(y);
float w = fade(z);
uint8_t A = p[X ] + Y;
uint8_t AA = p[A ] + Z;
uint8_t AB = p[A + 1] + Z;
uint8_t B = p[X + 1] + Y;
uint8_t BA = p[B ] + Z;
uint8_t BB = p[B + 1] + Z;
float result = lerp(w, lerp(v, lerp(u, grad(p[AA ], x , y , z ), // AND ADD
grad(p[BA ], x-1, y , z )), // BLENDED
lerp(u, grad(p[AB ], x , y-1, z ), // RESULTS
grad(p[BB ], x-1, y-1, z ))),// FROM 8
lerp(v, lerp(u, grad(p[AA+1], x , y , z-1 ), // CORNERS
grad(p[BA+1], x-1, y , z-1 )), // OF CUBE
lerp(u, grad(p[AB+1], x , y-1, z-1 ),
grad(p[BB+1], x-1, y-1, z-1 ))));
return (result + 1.0) / 2.0;
}
float4 colormapLookup(float value, size_t count, float indices[], float4 colormap[]) {
// If the value is at the outside boundary, just return the boundary color.
if (value < indices[0]) {
return colormap[0];
} else if (value >= indices[count - 1]) {
return colormap[count - 1];
}
// Find which 2 indices the value falls between.
size_t index = 0;
while (value < indices[index] && index < count - 1) {
index++;
}
float startIndex = indices[index];
float endIndex = indices[index+1];
// Calculate the normalized offset between those indies.
float offset = (value - startIndex) / (endIndex - startIndex);
// Return the blended color for that offest.
return mix(colormap[index], colormap[index+1], offset);
}
extern "C" float4 PerlinNoise (size_t count, float indices[], float4 colormap[], float offsetX, float offsetY, float offsetZ, float scale, float contrast, coreimage::destination dest)
{
float val = noise(dest.coord().x * scale + offsetX, dest.coord().y * scale + offsetY, offsetZ);
return colormapLookup(pow(val, contrast), count, indices, colormap);
}
这是我尝试实例化 CIColorKernel 的方法。 EXEC_BAD_ACCESS
发生在调用 CIColorKernel init 函数的行上:
static var kernel: CIColorKernel? = {
guard let url = Bundle.main.url(forResource: "PerlinNoiseGenerator", withExtension: "ci.metallib") else { return nil }
do {
let data = try Data(contentsOf: url)
return try CIColorKernel(functionName: "PerlinNoise", fromMetalLibraryData: data)
} catch {
print("[ERROR] Failed to create CIColorKernel: \(error)")
}
return nil
}()
编辑:在实例化 CIColorKernel 的地方添加了 Swift 代码。
根据 Frank 的评论,我将我的 Perlin Noise 滤镜恢复到其原始功能状态,而是使用可以生成自己的渐变的 CIColorMap
滤镜的改进版本作为后续步骤应用颜色图基于 [CGFloat: CIColor] 字典的图像。
import CoreImage
class ImprovedColorMap: CIFilter {
enum ColorMapError: Error {
case unableToCreateCGContext
case unableToCreateCGGradient
}
private var _colorMap: [CGFloat: CIColor] = [
0.0: .black,
1.0: .white,
]
private var gradientImage: CIImage = CIImage()
private var mapFilter: CIFilter? = CIFilter(name: "CIColorMap")
var inputImage: CIImage?
var inputColorMap: [CGFloat: CIColor] {
get { _colorMap }
set {
guard newValue.keys.count >= 2 else {
print("ERROR: Color map must have at least 2 entries. Change will be ignored.")
return
}
_colorMap = newValue
generateGradient()
}
}
override var outputImage: CIImage? {
guard let filter = mapFilter else {
return nil
}
filter.setValue(gradientImage, forKey: kCIInputGradientImageKey)
filter.setValue(inputImage, forKey: kCIInputImageKey)
return filter.outputImage
}
init(colorMap: [CGFloat: CIColor]? = nil) {
if let colorMap = colorMap {
_colorMap = colorMap
}
super.init()
generateGradient()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func generateGradient() {
DispatchQueue.global(qos: .default).async {
let colorSpace = CGColorSpaceCreateDeviceRGB()
guard let context = CGContext(data: nil, width: 512, height: 16, bitsPerComponent: 8, bytesPerRow: 512 * 4, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
print("ERROR: Could not create CGContext.")
return
}
var locations: [CGFloat] = []
var components: [CGFloat] = []
for (location, color) in self._colorMap {
locations.append(location)
components.append(contentsOf: color.floatComponents)
}
guard let gradient = CGGradient(colorSpace: colorSpace, colorComponents: components, locations: locations, count: locations.count) else {
print("ERROR: Could not create CGGradient.")
return
}
context.drawLinearGradient(gradient, start: .zero, end: CGPoint(x: 512.0, y: 0.0), options: [])
guard let image = context.makeImage() else {
print("ERROR: Failed to create image from context.")
return
}
DispatchQueue.main.async {
self.gradientImage = CIImage(cgImage: image)
}
}
}
}
这很好用,除了现在我意识到传统的 Perlin 噪声对于看起来逼真的地形来说太平滑了,所以接下来我将继续创建一个 Simplex 噪声金属内核。
我使用 Metal Shader Language 为 CoreImage 制作了一个 Perlin Noise 过滤器。我的第一个版本运行良好,允许我指定 2 种颜色用作 low
和 high
。我的下一步是让它接受颜色图,以便它可以产生多色输出。颜色映射版本编译正常,但是当我的 CIFilter 子类尝试使用 init(functionName:fromMetalLibraryData:)
方法从它创建 CIColorKernel 时,我得到一个 EXEC_BAD_ACCESS
和 code=1
。知道我的颜色映射实现可能会导致这种情况吗?
这是简单的 2 色版本:
#include <CoreImage/CoreImage.h>
using namespace metal;
constant int p[512] = {151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180};
float fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
float grad(int hash, float x, float y, float z) {
int h = hash & 15;
float u = h < 8 ? x : y;
float v = h < 4 ? y : h == 12 || h == 14 ? x : z;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
float lerp(float t, float x, float y) { return x + t * (y - x); }
float noise(float x, float y, float z) {
int X = (int)floor(x) & 255;
int Y = (int)floor(y) & 255;
int Z = (int)floor(z) & 255;
x -= floor(x);
y -= floor(y);
z -= floor(z);
float u = fade(x);
float v = fade(y);
float w = fade(z);
int A = p[X ] + Y;
int AA = p[A ] + Z;
int AB = p[A + 1] + Z;
int B = p[X + 1] + Y;
int BA = p[B ] + Z;
int BB = p[B + 1] + Z;
float result = lerp(w, lerp(v, lerp(u, grad(p[AA ], x , y , z ), // AND ADD
grad(p[BA ], x-1, y , z )), // BLENDED
lerp(u, grad(p[AB ], x , y-1, z ), // RESULTS
grad(p[BB ], x-1, y-1, z ))),// FROM 8
lerp(v, lerp(u, grad(p[AA+1], x , y , z-1 ), // CORNERS
grad(p[BA+1], x-1, y , z-1 )), // OF CUBE
lerp(u, grad(p[AB+1], x , y-1, z-1 ),
grad(p[BB+1], x-1, y-1, z-1 ))));
return (result + 1.0) / 2.0;
}
extern "C" float4 PerlinNoise (float4 lowColor, float4 highColor, float offsetX, float offsetY, float offsetZ, float scale, float contrast, coreimage::destination dest)
{
float val = noise(dest.coord().x * scale + offsetX, dest.coord().y * scale + offsetY, offsetZ);
return mix(lowColor, highColor, pow(val, contrast));
}
这里是彩图版本:
#include <CoreImage/CoreImage.h>
using namespace metal;
constant uint8_t p[512] = {151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180};
float fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
float grad(uint8_t hash, float x, float y, float z) {
uint8_t h = hash & 15;
float u = h < 8 ? x : y;
float v = h < 4 ? y : h == 12 || h == 14 ? x : z;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
float lerp(float t, float x, float y) { return x + t * (y - x); }
float noise(float x, float y, float z) {
uint8_t X = (uint8_t)floor(x) & 255;
uint8_t Y = (uint8_t)floor(y) & 255;
uint8_t Z = (uint8_t)floor(z) & 255;
x -= floor(x);
y -= floor(y);
z -= floor(z);
float u = fade(x);
float v = fade(y);
float w = fade(z);
uint8_t A = p[X ] + Y;
uint8_t AA = p[A ] + Z;
uint8_t AB = p[A + 1] + Z;
uint8_t B = p[X + 1] + Y;
uint8_t BA = p[B ] + Z;
uint8_t BB = p[B + 1] + Z;
float result = lerp(w, lerp(v, lerp(u, grad(p[AA ], x , y , z ), // AND ADD
grad(p[BA ], x-1, y , z )), // BLENDED
lerp(u, grad(p[AB ], x , y-1, z ), // RESULTS
grad(p[BB ], x-1, y-1, z ))),// FROM 8
lerp(v, lerp(u, grad(p[AA+1], x , y , z-1 ), // CORNERS
grad(p[BA+1], x-1, y , z-1 )), // OF CUBE
lerp(u, grad(p[AB+1], x , y-1, z-1 ),
grad(p[BB+1], x-1, y-1, z-1 ))));
return (result + 1.0) / 2.0;
}
float4 colormapLookup(float value, size_t count, float indices[], float4 colormap[]) {
// If the value is at the outside boundary, just return the boundary color.
if (value < indices[0]) {
return colormap[0];
} else if (value >= indices[count - 1]) {
return colormap[count - 1];
}
// Find which 2 indices the value falls between.
size_t index = 0;
while (value < indices[index] && index < count - 1) {
index++;
}
float startIndex = indices[index];
float endIndex = indices[index+1];
// Calculate the normalized offset between those indies.
float offset = (value - startIndex) / (endIndex - startIndex);
// Return the blended color for that offest.
return mix(colormap[index], colormap[index+1], offset);
}
extern "C" float4 PerlinNoise (size_t count, float indices[], float4 colormap[], float offsetX, float offsetY, float offsetZ, float scale, float contrast, coreimage::destination dest)
{
float val = noise(dest.coord().x * scale + offsetX, dest.coord().y * scale + offsetY, offsetZ);
return colormapLookup(pow(val, contrast), count, indices, colormap);
}
这是我尝试实例化 CIColorKernel 的方法。 EXEC_BAD_ACCESS
发生在调用 CIColorKernel init 函数的行上:
static var kernel: CIColorKernel? = {
guard let url = Bundle.main.url(forResource: "PerlinNoiseGenerator", withExtension: "ci.metallib") else { return nil }
do {
let data = try Data(contentsOf: url)
return try CIColorKernel(functionName: "PerlinNoise", fromMetalLibraryData: data)
} catch {
print("[ERROR] Failed to create CIColorKernel: \(error)")
}
return nil
}()
编辑:在实例化 CIColorKernel 的地方添加了 Swift 代码。
根据 Frank 的评论,我将我的 Perlin Noise 滤镜恢复到其原始功能状态,而是使用可以生成自己的渐变的 CIColorMap
滤镜的改进版本作为后续步骤应用颜色图基于 [CGFloat: CIColor] 字典的图像。
import CoreImage
class ImprovedColorMap: CIFilter {
enum ColorMapError: Error {
case unableToCreateCGContext
case unableToCreateCGGradient
}
private var _colorMap: [CGFloat: CIColor] = [
0.0: .black,
1.0: .white,
]
private var gradientImage: CIImage = CIImage()
private var mapFilter: CIFilter? = CIFilter(name: "CIColorMap")
var inputImage: CIImage?
var inputColorMap: [CGFloat: CIColor] {
get { _colorMap }
set {
guard newValue.keys.count >= 2 else {
print("ERROR: Color map must have at least 2 entries. Change will be ignored.")
return
}
_colorMap = newValue
generateGradient()
}
}
override var outputImage: CIImage? {
guard let filter = mapFilter else {
return nil
}
filter.setValue(gradientImage, forKey: kCIInputGradientImageKey)
filter.setValue(inputImage, forKey: kCIInputImageKey)
return filter.outputImage
}
init(colorMap: [CGFloat: CIColor]? = nil) {
if let colorMap = colorMap {
_colorMap = colorMap
}
super.init()
generateGradient()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func generateGradient() {
DispatchQueue.global(qos: .default).async {
let colorSpace = CGColorSpaceCreateDeviceRGB()
guard let context = CGContext(data: nil, width: 512, height: 16, bitsPerComponent: 8, bytesPerRow: 512 * 4, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
print("ERROR: Could not create CGContext.")
return
}
var locations: [CGFloat] = []
var components: [CGFloat] = []
for (location, color) in self._colorMap {
locations.append(location)
components.append(contentsOf: color.floatComponents)
}
guard let gradient = CGGradient(colorSpace: colorSpace, colorComponents: components, locations: locations, count: locations.count) else {
print("ERROR: Could not create CGGradient.")
return
}
context.drawLinearGradient(gradient, start: .zero, end: CGPoint(x: 512.0, y: 0.0), options: [])
guard let image = context.makeImage() else {
print("ERROR: Failed to create image from context.")
return
}
DispatchQueue.main.async {
self.gradientImage = CIImage(cgImage: image)
}
}
}
}
这很好用,除了现在我意识到传统的 Perlin 噪声对于看起来逼真的地形来说太平滑了,所以接下来我将继续创建一个 Simplex 噪声金属内核。