ios CIKernel的Metal Shader的裁剪问题

qij5mzcb  于 2023-10-21  发布在  iOS
关注(0)|答案(1)|浏览(132)

我正在开发一个着色器来渲染具有电影帧效果的图像。我制作了以下着色器:

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);
        }
    }
}

它按预期运行,并生成the following image
尝试将裁剪集成到管道中时会出现问题。当我在调用内核函数之前添加裁剪时,一切都按预期运行。但是,如果我从内核函数中裁剪输出图像,则会出现首先裁剪源图像,然后着色器在裁剪后的图像上运行。

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))
                }

The resulting image由上面的代码生成。
虽然这可能是一个优化,但它是相当出乎意料的。如何修改此行为以确保裁剪着色器的输出图像,而不是原始图像?你的见解将不胜感激。
我想更详细地解释这个问题。着色器的第一个参数是图像,需要注意的是,我没有直接裁剪它。相反,裁剪发生在输出图像上。奇怪的是,我传递给着色器的图像似乎也被裁剪了。更重要的是,我尝试在调用着色器之后进行裁剪,这很奇怪,着色器接收到一个裁剪的图像。
Original image
Output image without crop
Output image with crop

2vuwiymt

2vuwiymt1#

正如你所猜的那样,Core Image在这里进行了优化:它将您对图像执行的不同过滤器和操作 * 连接到尽可能少的处理操作中。特别是对于裁剪,它试图只处理最终结果中可见的像素。这是在(旧的和过时的)核心图像编程指南中概述的。
为此,它从后到前分析滤波器图,以确定需要输入图像的哪一部分来计算所需的输出图像。这被称为 * 感兴趣区域 (ROI),它通常取决于应该产生的输出区域( 定义域 ,DOD)。这里也描述了这一点。
作为一个过滤器开发人员,你有责任告诉Core Image你的内核需要读取输入图像(ROI)的哪个区域来生成给定的输出区域。这就是内核调用中roiCallback参数的用途:对于给定的输入索引(如果内核需要多个输入图像)和给定的输出区域(DOD),您必须返回该输入图像的ROI。
在前面的代码中,您告诉Core Image您的内核执行1:1Map,即对于给定的输出区域,您只需要从输入图像中读取相同的区域。这意味着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
}

请注意,这种方法也不理想,因为核心图像现在不能优化了。它总是需要加载整个图像,并可能使用管道中的任何先前的过滤器对其进行处理,而不管实际输出有多小。理想情况下,你应该弄清楚你的过滤器对于给定的DOD真正需要什么样的ROI,并在你的roiCallback中返回。

相关问题