我正在开发一个着色器来渲染具有胶片帧效果的图像。我制作了以下着色器:
extern "C" {
namespace coreimage {
float4 reassemble(sampler source, sampler frame, float imgWidth, float perFrameSize, float epsilon, destination dest) {
float2 coord = dest.coord();
float4 srcExtent = source.extent();
float4 pixel = float4(0, 1, 0, 1);
float sideFrameSize = epsilon + (imgWidth - srcExtent.z)/2 - perFrameSize;
float offset = sideFrameSize + perFrameSize - epsilon;
if (coord.x < sideFrameSize) {
pixel = source.sample(source.transform(float2(coord.x + srcExtent.x + srcExtent.z - sideFrameSize, coord.y)));
}
else if (coord.x >= offset && coord.x <= srcExtent.z + offset) {
pixel = source.sample(source.transform(float2(coord.x + srcExtent.x - offset, coord.y)));
}
else if (coord.x >= srcExtent.z + offset + perFrameSize - epsilon) {
pixel = source.sample(source.transform(float2(coord.x + srcExtent.x - srcExtent.z - offset - perFrameSize + epsilon, coord.y)));
}
float4 framePixel = frame.sample(frame.transform(coord));
return mix(pixel, framePixel, framePixel.a);
}
}
}
尝试将裁剪集成到管道中时会出现问题。当我在调用内核函数之前添加作物时,一切都会按预期运行。但是,如果我从核函数中裁剪输出图像,则似乎首先裁剪源图像,然后着色器在裁剪后的图像上运行。
if let source = self.kernel.apply(extent: frameImage.extent,
roiCallback: { _, rect -> CGRect in
return rect
},
arguments: [scaled.transformed(by: .init(translationX: 0, y: perFrameWidth - vOffset)), frameImage, image.extent.width, perFrameWidth, hOffset]) {
return source.cropped(to: frameImage.extent.insetBy(dx: 80, dy: 200))
}
虽然这可能是一种优化,但却相当出乎意料。如何修改此行为以确保裁剪着色器的输出图像而不是原始图像?我们将非常感谢您的见解。
我想更详细地解释这个问题。着色器的第一个参数是图像,需要注意的是,我不会直接裁剪它。相反,裁剪发生在输出图像上。奇怪的是,我传递给着色器的图像似乎也被裁剪了。更重要的是,我尝试在调用着色器之后进行裁剪,这很奇怪,着色器收到裁剪后的图像。
正如您猜对的那样,Core Image 在这里执行了优化:它将您对图像执行的不同过滤器和操作“串联”成尽可能少的处理操作。特别是对于裁剪,它尝试仅处理最终结果中可见的像素。 (过时的)核心图像编程指南中对此进行了概述。 为此,它从后到前分析滤波器图,以找出计算所需输出图像需要输入图像的哪一部分。这称为“感兴趣区域”(ROI),它通常取决于应生成的输出区域(“定义域”,DOD)。
here也对此进行了描述。
作为过滤器开发人员,您有责任告诉 Core Image 您的内核需要读取输入图像 (ROI) 的哪个区域才能生成给定的输出区域。这就是内核调用中的 roiCallback
参数的用途:对于给定的输入索引(如果您的内核采用多个输入图像)和给定的输出区域 (DOD),您必须返回该输入图像的 ROI .
在之前的代码中,您告诉 Core Image 您的内核执行 1:1 映射,即对于给定的输出区域,您只需要从输入图像中读取相同的区域。这意味着 Core Image 也仅从输入中“加载”该区域并将其传递到内核。
,您的内核实际上在与输出不同的位置对输入图像进行采样
coord
,因此提供的输入区域是不够的。
如果您将 roiCallback
写成如下所示,则您是在告诉 Core Image 您始终需要 整个 输入图像,无论要生成输出的哪个区域。这样,您的内核始终具有可用于采样的输入图像的所有像素。
let inputExtent = frameImage.extent // best do that outside of the block to not capture the input image inside the ROI callback
let roiCallback = { _, _ in
return inputExtent
}
请注意,这种方法也不理想,因为 Core Image 现在无法再优化。它始终需要加载整个图像,并可能使用管道中任何先前的过滤器对其进行处理,无论实际输出有多小。理想情况下,您会计算出您的过滤器对于给定 DOD
真正需要多少 ROI,并将其返回到您的
roiCallback
中。