poppy
poppy

Reputation: 297

[OpenCv][C++] Creating a smooth, airbrush-like brush stroke, similar to Photoshop?

I have a circular brush of with a diameter of 200px and hardness of 0 (the brush is a circular gradient). The spacing between each brush is 25% of the brush diameter. However, when I compare the stroke my program draws and the stroke Photoshop draws, where all settings are equal... enter image description here It is clear that photoshop's is much smoother! I can't reduce the spacing because that causes the edges to become harder

How can i make my stroke like photoshop's? Here is the relevant code from my program...

//defining a circle
Mat alphaBrush(2*outerRadius,2*outerRadius,CV_32FC1);
float floatInnerRadius = outerRadius * hardness;
for(int i = 0; i < alphaBrush.rows; i++ ){
    for(int j=0; j<alphaBrush.cols; j++ ){
        int x = outerRadius - i;
        int y = outerRadius - j;
        float radius=hypot((float) x, (float) y );
        auto& pixel = alphaBrush.at<float>(i,j);
        if(radius>outerRadius){ pixel=0.0; continue;}      // transparent
        if(radius<floatInnerRadius){ pixel=1.0; continue;}      // solid
        pixel=1-((radius-floatInnerRadius)/(outerRadius-floatInnerRadius));  // partial
    }
}

/*
(...irrelevant stuff)
*/

//drawing the brush onto the canvas
for (int j = 0; j < inMatROI.rows; j++) {
            Vec3b *thisBgRow = inMatROI.ptr<Vec3b>(j);
            float *thisAlphaRow = brushROI.ptr<float>(j);
            for (int i = 0; i < inMatROI.cols; i++) {
                for (int c = 0; c < 3; c++) {
                    thisBgRow[i][c] = saturate_cast<uchar>((brightness * thisAlphaRow[i]) + ((1.0 - thisAlphaRow[i]) * thisBgRow[i][c]));
                }
            }
        }

I have also tried resultValue = max(backgroundValue, brushValue), but the intersection between the two circles is pretty obvious.

Upvotes: 0

Views: 1602

Answers (1)

Micka
Micka

Reputation: 20160

this is the approach, drawing a solid thin line and afterwards computing the distance of each pixel to that line. As you can see there are some artifacts, probably mostly because of only approximated distance values from cv::distanceTransform. If you compute the distances precisely (and maybe in double precision) you should get very smooth results.

enter image description here

int main()
{
    cv::Mat canvas = cv::Mat(768, 768, CV_8UC3, cv::Scalar::all(255));

    cv::Mat canvasMask = cv::Mat::zeros(canvas.size(), CV_8UC1);

    // make sure the stroke has always a size of >= 2, otherwise will be cv::line way not work...
    std::vector<cv::Point> strokeSampling;
    strokeSampling.push_back(cv::Point(250, 100));

    strokeSampling.push_back(cv::Point(250, 200));
    strokeSampling.push_back(cv::Point(600, 300));
    strokeSampling.push_back(cv::Point(600, 400));
    strokeSampling.push_back(cv::Point(250, 500));

    strokeSampling.push_back(cv::Point(250, 650));

    for (int i = 0; i < strokeSampling.size() - 1; ++i)
        cv::line(canvasMask, strokeSampling[i], strokeSampling[i + 1], cv::Scalar::all(255));

    // computing a distance map:
    cv::Mat tmp1 = 255 - canvasMask;
    cv::Mat distMap;
    cv::distanceTransform(tmp1, distMap, CV_DIST_L2, CV_DIST_MASK_PRECISE);

    float outerRadius = 50;
    float innerRadius = 10;
    cv::Scalar strokeColor = cv::Scalar::all(0);

    for (int y = 0; y < distMap.rows; ++y)
        for (int x = 0; x < distMap.cols; ++x)
        {
            float percentage = 0.0f;
            float radius = distMap.at<float>(y, x);

            if (radius>outerRadius){ percentage = 0.0; }      // transparent
            else
            if (radius<innerRadius){ percentage = 1.0; }      // solid
            else
            {
                percentage = 1 - ((radius - innerRadius) / (outerRadius - innerRadius));  // partial
            }

            if (percentage > 0)
            {
                // here you could use the canvasMask if you like to, instead of directly drawing on the canvas
                cv::Vec3b canvasColor = canvas.at<cv::Vec3b>(y, x);
                cv::Vec3b cColor = cv::Vec3b(strokeColor[0], strokeColor[1], strokeColor[2]);
                canvas.at<cv::Vec3b>(y, x) = percentage*cColor + (1 - percentage) * canvasColor;
            }
        }

    cv::imshow("out", canvas);
    cv::imwrite("C:/StackOverflow/Output/stroke.png", canvas);
    cv::waitKey(0);

}

Upvotes: 1

Related Questions