CIKernel 的 Metal Shader 的裁剪问题

问题描述 投票:0回答:1

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

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

ios metal core-image
1个回答
0
投票

正如您猜对的那样,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中。

© www.soinside.com 2019 - 2024. All rights reserved.