CIToneCurve 应该适合这种事情。
- (UIImage*) applyToneCurveToImage:(UIImage*)image
{
CIContext* context = self.context;
CIImage* ciImage = [[CIImage alloc] initWithImage:image];
CIFilter* filter =
[CIFilter filterWithName:@"CIToneCurve"
keysAndValues:
kCIInputImageKey, ciImage,
@"inputPoint0",[CIVector vectorWithX:0.00 Y:0.3]
,@"inputPoint1",[CIVector vectorWithX:0.25 Y:0.4]
,@"inputPoint2",[CIVector vectorWithX:0.50 Y:0.5]
,@"inputPoint3",[CIVector vectorWithX:0.75 Y:0.6]
,@"inputPoint4",[CIVector vectorWithX:1.00 Y:0.7]
,nil];
CIImage* result = [filter valueForKey:kCIOutputImageKey];
CGImageRef cgImage = [context createCGImage:result
fromRect:[result extent]];
UIImage* filteredImage = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
return filteredImage;
}
self.context
可以是 CPU 或 GPU (EAGL) 上下文。这些点描述了色调曲线。X 是输入值,Y 是输出值。在此示例中,我们正在减小斜率,从而降低对比度。你也可以保持斜率不变,但切断极端,从而减少你的最大值和最小值。
以下是在 iPad mini 上处理 100 张图像的一些测量结果(每个设置平均有四个读数)。您会看到 GPU 不是灵丹妙药,大约 60-65% 的时间都在专注于移动图像数据。这些示例以 UIImage 开头和结尾。我用 CGImage 得到了非常相似的结果。
57x57px png 640x800px jpeg
UIImage->CIImage->UIImage (no filtering)
CPU 0.57s 2.83s
GPU 0.36s 2.83s
UIImage->CIImage->CIFilter->UIImage
CPU 0.75s 4.38s
GPU 0.58s 4.32s
更新
对于窗口范围之外的输入值为零的窗口和电平调整,您希望实现如下效果:
CGFloat w; //window
CGFloat l; //level
@"inputPoint0",[CIVector vectorWithX:0.0 Y:0.0]
,@"inputPoint1",[CIVector vectorWithX:l-w/2 Y:0.0]
,@"inputPoint2",[CIVector vectorWithX:l+w/2 Y:1.0]
,@"inputPoint3",[CIVector vectorWithX:l+w/2 Y:0.0]
,@"inputPoint4",[CIVector vectorWithX:1.0 Y:0.0]
在实践中,这是行不通的。这些点描述了一条样条曲线,它在方向急剧变化(例如 bcd)时表现不佳。(此外,c 点和 d 点(2 和 3)不能共享相同的 x 值,因此在任何情况下您都必须稍微增加一点,以便 dx = cx*1.01)
如果您使用多步过滤器,您可以获得结果。第一个过滤器使用CIToneCurve
适当(这是正确的窗口/级别算法,而不会尝试将最大级别降低到零)。
CIFilter* filter =
[CIFilter filterWithName:@"CIToneCurve"
keysAndValues:
kCIInputImageKey, ciImage,
@"inputPoint0",[CIVector vectorWithX:0.0 Y:0.0]
,@"inputPoint1",[CIVector vectorWithX:l-w/2 Y:0.0]
,@"inputPoint2",[CIVector vectorWithX:l Y:0.5]
,@"inputPoint3",[CIVector vectorWithX:l+w/2 Y:1.0]
,@"inputPoint4",[CIVector vectorWithX:1.0 Y:1.0]
我们制作过滤器的副本,因为我们将在最后一步再次需要它:
filter2 = [filter copy];
应用 CIColorControls 过滤器以最大化结果的对比度和亮度
filter = [CIFilter filterWithName:@"CIColorControls"
keysAndValues:kCIInputImageKey,
[filter valueForKey:kCIOutputImageKey], nil];
[filter setValue:[NSNumber numberWithFloat:-1]
forKey:@"inputBrightness"];
[filter setValue:[NSNumber numberWithFloat:4]
forKey:@"inputContrast"];
现在分色到 1 位调色板
filter = [CIFilter filterWithName:@"CIColorPosterize"
keysAndValues:kCIInputImageKey,
[filter valueForKey:kCIOutputImageKey], nil];
[filter setValue:@2 forKey:@"inputLevels"];
反转结果
filter = [CIFilter filterWithName:@"CIColorInvert"
keysAndValues:kCIInputImageKey,
[filter valueForKey:kCIOutputImageKey], nil];
现在我们使用这个结果作为窗口/水平图像的掩码,以便所有最大白色级别都减少到黑色。
filter = [CIFilter filterWithName:@"CIDarkenBlendMode"
keysAndValues:kCIInputImageKey,
[ filter valueForKey:kCIOutputImageKey], nil];
[filter setValue:[filter2 valueForKey:kCIOutputImageKey]
forKey:@"inputBackgroundImage"];
原来的
过滤器1CIToneCurve
过滤器2CIColorControls
过滤器3CIColorPosterize
过滤器4CIColorInvert
过滤器5CIDarkenBlendMode
如果您查看 Brad Larson's GPUImage
,您应该会发现它GPUImageLuminanceThresholdFilter
会更好地替换过滤器 2->5。