Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set the background color for a PNG with transparency

I am loading PNG images that have a transparency plane. When converted to grayscale, the transparent areas in the image appear as black, which seems to be the default background. I need them to be white instead. What can I do ?

[This is not the usual question on how to preserve transparency.]

like image 740
Yves Daoust Avatar asked Aug 10 '17 10:08

Yves Daoust


1 Answers

The most effective way (memory and CPU) would be to let libPNG do it, using png_set_background:

If you don't need, or can't handle, the alpha channel you can call png_set_background() to remove it by compositing against a fixed color. Don't call png_set_strip_alpha() to do this - it will leave spurious pixel values in transparent parts of this image.

png_set_background(png_ptr, &background_color,
   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1);

The background_color is an RGB or grayscale value according to the data format libpng will produce for you.

Unfortunately, the OpenCV wrapper around libPNG doesn't use this, so you'd have to patch in some rudimentary support yourself (hindered by the limited ability to pass additional options to imread).

Other possible approach would be to just write your own simple image loader using libPNG for this specific purpose.

If you can afford some waste, load it as BGRA, and do some post-processing. However I'd go a step further than the code referred to by Gabriel and incorporate the color conversion in it.

void remove_transparency(cv::Mat const& source
    , cv::Mat& destination
    , uint8_t background_color)
{
    CV_Assert(source.type() == CV_8UC4);

    destination.create(source.rows, source.cols, CV_8UC1);

    auto it_src(source.begin<cv::Vec4b>()), it_src_end(source.end<cv::Vec4b>());
    auto it_dest(destination.begin<uint8_t>());

    std::transform(it_src, it_src_end, it_dest
        , [background_color](cv::Vec4b const& v) -> uchar
        {
            // Conversion constants taken from cvtColor docs...
            float gray(v[0] * 0.114f + v[1] * 0.587f + v[2] * 0.299f);
            float alpha(v[3] / 255.0f);
            return cv::saturate_cast<uchar>(gray * alpha + background_color * (1 - alpha));
        }
        );
}

Of course, this is still single threaded, so let's leverage cv::parallel_for_ to improve it a bit further.

class ParallelRemoveTransparency
    : public cv::ParallelLoopBody
{
public:
    ParallelRemoveTransparency(cv::Mat const& source
        , cv::Mat& destination
        , uint8_t background_color)
        : source_(source)
        , destination_(destination)
        , background_color_(background_color)
    {
        CV_Assert(source.size == destination.size);
    }

    virtual void operator()(const cv::Range& range) const
    {
        cv::Mat4b roi_src(source_.rowRange(range));
        cv::Mat1b roi_dest(destination_.rowRange(range));

        std::transform(roi_src.begin(), roi_src.end(), roi_dest.begin()
            , [this](cv::Vec4b const& v) -> uint8_t {
                float gray(v[0] * 0.114f + v[1] * 0.587f + v[2] * 0.299f);
                float alpha(v[3] / 255.0f);
                return cv::saturate_cast<uint8_t>(gray * alpha + background_color_ * (1 - alpha));
            }
            );
    }

private:
    cv::Mat const& source_;
    cv::Mat& destination_;
    uint8_t background_color_;
};

void remove_transparency(cv::Mat const& source
    , cv::Mat& destination
    , uint8_t background_color)
{
    CV_Assert(source.type() == CV_8UC4);

    destination.create(source.rows, source.cols, CV_8UC1);

    ParallelRemoveTransparency parallel_impl(source, destination, background_color);
    cv::parallel_for_(cv::Range(0, source.rows), parallel_impl);
}

It turns out you need this in Python. Here's a quick little draft of an alternative:

import numpy as np
import cv2

def remove_transparency(source, background_color):
    source_img = cv2.cvtColor(source[:,:,:3], cv2.COLOR_BGR2GRAY)
    source_mask = source[:,:,3]  * (1 / 255.0)

    background_mask = 1.0 - source_mask

    bg_part = (background_color * (1 / 255.0)) * (background_mask)
    source_part = (source_img * (1 / 255.0)) * (source_mask)

    return np.uint8(cv2.addWeighted(bg_part, 255.0, source_part, 255.0, 0.0))


img = cv2.imread('smile.png', -1)
result = remove_transparency(img, 255)

cv2.imshow('', result)
cv2.waitKey()
like image 135
Dan Mašek Avatar answered Nov 14 '22 23:11

Dan Mašek