ImageKnifePro 源码解读(八):GPU 滤镜与 CPU 变换
ImageKnifePro 把所有图形变换收归到 C++ Native 层,分出 GPU 和 CPU 两条管线。GPU 管线通过 OpenGL ES 离屏渲染执行片段着色器,CPU 管线在像素缓冲区上直接操作或调用系统 API。两条管线统一挂在 最顶层的 具体的 GPU 变换子类(如 着色器编译失败时会输出 GL InfoLog 用于调试。链接成功后直接 所有 GPU 滤镜都是 以 从 继承自 手写逐像素操作。 调用 PixelMap 系统 API。 调用 Effect 系统 API。 Mask 变换。 EGL 上下文的位置是这个类的关键设计决策。 所有 GPU 变换在同一个线程、同一个 EGL 上下文中串联执行。GPU 变换的 这种分层让 GPU 和 CPU 变换可以混合串联。一条典型的变换链可以是:blur(GPU) -> crop_circle(CPU) -> brightness(GPU) -> crop_square(CPU)。GPU 变换通过 在 以上就是本篇内容的所有了~有什么问题欢迎在评论区提出 项目地址:ImageKnifeProTransformationBase 下面,由 MultiTransformation 做链式编排,对上层完全透明。一、三层继承——Transformation、TransformationBase、TransformationGPU
Transformation 是纯接口,定义了两个纯虚函数:Transform(task) 执行变换,GetTransformInfo() 返回变换描述字符串。后者在缓存系统中用于生成 memory key,不同参数的同类变换需要返回不同的字符串(比如 "Default Blur:10" 和 "Default Blur:25"),以保证缓存不会混淆。TransformationBase 承接 Transformation,把 Transform 实现为 final 方法。它的内部流程分三个阶段。BeforeProcess(task) 在整次变换开始前执行一次,GPU 变换在这里初始化 GL 环境,返回 false 则中止变换。然后根据帧数选择处理路径:单帧走 Process(pixelmapIn, pixelmapOut),多帧走 TransformAnimation(task, count) 逐帧循环调用 Process。AfterProcess() 变换结束后执行一次,通过 EndCallback 的析构函数自动触发——保证即使 Process 中途 return 也能执行清理逻辑。Process 是纯虚函数,子类在这里做实际的像素处理。输出参数 pixelmapOut 如果写回 nullptr,表示变换在原图上原地进行;如果创建了新的 PixelMap,TransformationBase 会用新的 ImageData 替换 task->product.imageData。TransformationGPU 在 TransformationBase 和具体 GPU 变换之间插入了一层。它持有一个 shared_ptr<GPUTransform>,把三个钩子的实现模板化了:bool BeforeProcess(std::shared_ptr<ImageKnifeTask> task) final {
if (filter_->InitGL()) return true;
task->EchoError("GL Init Failed");
return false;
}
std::string Process(OH_PixelmapNative* pixelmapIn, OH_PixelmapNative **pixelmapOut) final {
PixemapInfo info = ParsePixelmapInfoReadPixels(pixelmapIn);
if (info.innerBuffer == nullptr) return "Read Pixels Failed";
filter_->Rendering(info.innerBuffer, info.width, info.height);
CreatePixelmapWithBuffer(info.innerBuffer, info, pixelmapOut);
free(info.innerBuffer);
return std::string();
}
void AfterProcess() final {
filter_->DeleteGL();
}TransformationBlur、TransformationBrighten)只需在构造函数中创建对应的 GPUFilter 并调用 SetGpuFilter,不再操心 GL 生命周期和像素读写。二、GPUTransform——OpenGL ES 离屏渲染管线
GPUTransform 基类把离屏渲染拆成初始化、数据绑定、渲染执行、资源释放四个阶段。InitGL() 依次执行三步。TextureInit() 创建两个纹理——imageTextureId_ 接收原始图像像素,fboTextureId_ 作为 FBO 的颜色附着点,两个纹理都设置了 GL_REPEAT 环绕模式和 GL_LINEAR 过滤模式。BufferInit() 创建三个 VBO 和一个 VAO:第一个 VBO 存顶点坐标(四个顶点覆盖 NDC 空间的 [-1,1] 范围),第二个 VBO 存纹理坐标,第三个 VBO 存索引数据(两个三角形组成一个四边形)。VAO 把顶点属性绑定到 location 0(vec3 位置)和 location 1(vec2 纹理坐标)。BufferInit 最后还创建了 FBO。CreateProgram() 编译链接着色器——顶点着色器由基类的 GetVertexShaderString() 提供(一个标准的直通着色器),片段着色器由子类的 GetFragmentShaderString() 返回。glUseProgram,并在 InitGL 中把纹理采样器 s_TextureMap 绑定到 GL_TEXTURE0。最后调用子类的 UniformValue() 设置滤镜参数。Rendering() 是逐帧调用的渲染入口。处理 GIF 时 TransformationBase 会循环调用 Process,每帧都走一次 Rendering。具体步骤:先调用 UniformValueWithImageSize() 设置与图片尺寸相关的 uniform;然后 SetImageData() 把像素数据上传到 imageTextureId_,同时将 fboTextureId_ 附着到 FBO 并分配与图片同尺寸的存储;接着 glViewport 设置渲染区域,glDrawElements 执行渲染;最后 glReadPixels 从 FBO 读回处理后的像素到原 buffer 中。glReadPixels 是一个同步调用,GPU 必须完成渲染管线的所有工作后才能返回数据。代码注释中提到了 PBO(Pixel Buffer Object)作为潜在优化方向——PBO 可以异步发起像素传输,减少 CPU 阻塞,但代价是多占一份显存。DeleteGL() 按 Program、Texture、VBO、VAO、FBO 的顺序释放所有 GL 资源。它不销毁 EGL 上下文,因为上下文的生命周期由外层的 ImageKnifeEGL 管理。三、13 种 GPU 滤镜
GPUTransform 的子类,核心工作量在于编写 GLSL 片段着色器。着色器字符串通过虚函数 GetFragmentShaderString() 返回,增加新滤镜只需添加一个子类,已有代码无需改动。GPUBlurFilter 实现一维高斯模糊。权重函数 getWeight(i) 用高斯公式 (1/(sqrt(2*PI*sigma^2))) * exp(-i^2/(2*sigma^2)) 计算每个采样点的权重,sigma 取 blurRadius / 3.0。沿 blurOffset 方向正反各采样 blurRadius 次,加权求和得到输出颜色。GPUKuwaharaFilter 把以当前像素为中心的方形区域分成四个象限,分别统计均值和方差,选方差最小的象限均值作为输出颜色——这种非线性滤波能保留边缘同时平滑区域内部细节。它的实现比其他滤镜复杂得多:需要在 UniformValueWithImageSize 中传入图片宽高计算纹素步进值。GPUSwirlFilter 以极坐标方式旋转纹理采样坐标,angle 和 radius 参数控制旋转强度和范围。GPUSketchFilter 和 GPUToonFilter 需要 3x3 纹理采样顶点着色器来做 Sobel 边缘检测。其余滤镜如 Brighten、GrayScale、Invert、Sepia、Pixelation、Vignette、Mirror 的着色器都比较短小,核心逻辑只有几行 GLSL 代码。TransformationBlur 为例,整个类的代码非常精简:class TransformationBlur : public TransformationGPU {
public:
explicit TransformationBlur(std::shared_ptr<TransformationOption> option)
{
int radius = defaultRadius;
float offsetX = defaultOffset, offsetY = defaultOffset;
if (option->f32.size() >= 1) radius = option->f32[0];
if (option->f32.size() >= 3) {
offsetX = option->f32[1];
offsetY = option->f32[2];
}
info_ = "Default Blur:" + std::to_string(radius);
SetGpuFilter(std::make_shared<GPUBlurFilter>(radius, offsetX, offsetY));
}
};TransformationOption.f32 读取参数,构造对应的 GPUFilter,设置变换信息字符串——三步完成一个滤镜的注册。四、CPU 变换——逐像素操作与系统 API
TransformationBase 但不经过 TransformationGPU 的变换类在 CPU 上完成像素处理。按实现方式可以分成三类。TransformationCropCircle 把图片裁成最大内切圆。算法先计算圆心和半径,然后按行预计算圆弧的左右边界。对于 y 从 0 到 centerY 的每一行,通过圆方程 dx = sqrt(r^2 - dy^2) 得到该行在圆弧上的 x 坐标范围。利用上下对称性同时处理两行。每行只遍历圆外像素(将 RGBA 四通道全部置 0),圆内像素保持不变。TransformationCropCircleWithBorder 在此基础上增加了边框绘制,预计算内外两组圆弧边界,中间区域填充用户指定的 RGB 颜色。TransformationCropSquare 直接调用 OH_PixelmapNative_Crop,取 min(width, height) 为正方形边长,居中裁切。没有像素拷贝,也不创建新的 PixelMap。TransformationCrop 支持 Top、Center、Bottom 三种裁切位置,先 OH_PixelmapNative_Scale 缩放再 OH_PixelmapNative_Crop 裁切。TransformationGaussianBlur 通过 native_effect/effect_filter.h 调用 HarmonyOS 原生的高斯模糊能力。由于 Effect API 非线程安全,内部用了一把静态 mutex 做串行化保护。与 GPU 版的 TransformationBlur 相比,TransformationGaussianBlur 做的是完整的二维高斯卷积,模糊效果更平滑。TransformationMask 从 TransformationOption.imageSrc 加载遮罩图片。BeforeProcess 阶段判断来源类型——PixelMap 直接取引用,URI 或 Resource 通过 ImageKnifeDispatcher::LoadFromImageSourceSync 同步加载。Process 阶段把遮罩缩放到与目标图片同尺寸,逐像素遍历:遮罩 alpha 通道非零的像素覆盖到目标图片对应位置。五、MultiTransformation——链式组合与 EGL 上下文管理
MultiTransformation 持有一个 vector<shared_ptr<Transformation>>,在 Transform() 中逐个执行变换。执行前检查请求是否已销毁或出现致命错误,支持中途中断。Transform() 在进入循环前创建 ImageKnifeEGL 对象,是栈上局部变量:void MultiTransformation::Transform(std::shared_ptr<ImageKnifeTask> task) {
ImageKnifeEGL egl;
IMAGE_KNIFE_CHECK(!egl.Init(), "EGL Init Failed");
for (auto transformation : transformations_) {
// ...
bool result = transformation->Transform(task);
// ...
}
}BeforeProcess 只做纹理、Buffer、Program 的初始化,不创建 EGL 上下文;AfterProcess 只释放 GL 资源不销毁上下文。CPU 变换完全不感知 EGL 的存在,直接在 buffer 上操作。当 MultiTransformation 函数返回时,栈上的 ImageKnifeEGL 对象析构,EGL 上下文随之释放——RAII 保证了无论变换链中途中断还是正常结束都能正确清理。ParsePixelmapInfoReadPixels 读出像素交给 GPU 处理,CreatePixelmapWithBuffer 把结果写回新的 PixelMap。下一个 CPU 变换再从新的 imageData 中读出像素做处理。串联顺序由用户在配置中指定,框架不做任何限制。MultiTransformation 还为每个变换采集了耗时信息(TransformationInfo),记录变换类型、开始时间、结束时间和是否成功,方便上层做性能监控。六、transformation_factory——按类型创建变换
TransformationFactory::CreateTransformation 接收 shared_ptr<TransformationOption>,内部维护一个 unordered_map<TransformationType, Builder> 静态映射表。当前注册了 20 种变换类型:12 种走 GPU 管线(Blur、Brighten、GrayScale、Invert、Kuwahara、Pixelation、Sepia、Sketch、Swirl、Toon、Vignette、Mirror),6 种走 CPU 管线(GaussianBlur、CropCircle、CropCircleWithBorder、CropSquare、Crop 的三种模式、Mask),加上 UNKNOWN 返回 nullptr。TransformationOption 包含三类参数。type 指定变换类型枚举。f32 是 vector<float> 承载数值参数——模糊半径、亮度值、旋转角度、边框宽度、颜色 RGB 值等都通过这个数组传递,不同变换按约定的索引位置读取。imageSrc 用于 Mask 变换指定遮罩图片来源。ImageKnifeDispatcher::TransformImage 中,单个变换和多重变换的调用路径有微妙差异:单个变换会在调用处创建 ImageKnifeEGL;多重变换则由 MultiTransformation::Transform 内部创建。两条路径都保证了 EGL 上下文在变换期间可用、变换结束后释放。