bool ImageProcessor::blur()

in RenderScriptMigrationSample/app/src/main/cpp/ImageProcessor.cpp [203:260]


bool ImageProcessor::blur(float radius, int outputIndex) {
    RET_CHECK(1.0f <= radius && radius <= 25.0f);

    // Calculate gaussian kernel, this is equivalent to ComputeGaussianWeights at
    // https://cs.android.com/android/platform/superproject/+/master:frameworks/rs/cpu_ref/rsCpuIntrinsicBlur.cpp;l=57
    constexpr float e = 2.718281828459045f;
    constexpr float pi = 3.1415926535897932f;
    float sigma = 0.4f * radius + 0.6f;
    float coeff1 = 1.0f / (std::sqrtf(2.0f * pi) * sigma);
    float coeff2 = -1.0f / (2.0f * sigma * sigma);
    int32_t iRadius = static_cast<int>(std::ceilf(radius));
    float normalizeFactor = 0.0f;
    for (int r = -iRadius; r <= iRadius; r++) {
        const float value = coeff1 * std::powf(e, coeff2 * static_cast<float>(r * r));
        mBlurData.kernel[r + iRadius] = value;
        normalizeFactor += value;
    }
    normalizeFactor = 1.0f / normalizeFactor;
    for (int r = -iRadius; r <= iRadius; r++) {
        mBlurData.kernel[r + iRadius] *= normalizeFactor;
    }
    RET_CHECK(mBlurUniformBuffer->copyFrom(&mBlurData));

    // Apply a two-pass blur algorithm: a horizontal blur kernel followed by a vertical
    // blur kernel. This is equivalent to, but more efficient than applying a 2D blur
    // filter in a single pass. The two-pass blur algorithm has two kernels, each of
    // time complexity O(iRadius), while the single-pass algorithm has only one kernel,
    // but the time complexity is O(iRadius^2).
    auto cmd = mCommandBuffer->handle();
    RET_CHECK(beginOneTimeCommandBuffer(cmd));

    // The temp image is used as an output storage image in the first pass.
    mTempImage->recordLayoutTransitionBarrier(cmd, VK_IMAGE_LAYOUT_GENERAL, /*preserveData=*/false);

    // First pass: apply a horizontal gaussian blur.
    mBlurHorizontalPipeline->recordComputeCommands(cmd, &iRadius, *mInputImage, *mTempImage,
                                                   mBlurUniformBuffer.get());

    // The temp image is used as an input sampled image in the second pass,
    // and the staging image is used as an output storage image.
    mTempImage->recordLayoutTransitionBarrier(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
    mStagingOutputImage->recordLayoutTransitionBarrier(cmd, VK_IMAGE_LAYOUT_GENERAL,
                                                       /*preserveData=*/false);

    // Second pass: apply a vertical gaussian blur.
    mBlurVerticalPipeline->recordComputeCommands(cmd, &iRadius, *mTempImage, *mStagingOutputImage,
                                                 mBlurUniformBuffer.get());

    // Prepare for image copying from the staging image to the output image.
    mStagingOutputImage->recordLayoutTransitionBarrier(cmd, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);

    // Copy staging image to output image.
    recordImageCopyingCommand(cmd, *mStagingOutputImage, *mOutputImages[outputIndex]);

    // Submit to queue.
    RET_CHECK(endAndSubmitCommandBuffer(cmd, mContext->queue()));
    return true;
}