Search code examples
c++boostboost-gil

Boost::GIL: reading *.png image with alpha channel is missing antialiasing


I'm using boost 1.74.

So, without exception catches and rest stuff my actual code looks like:

typedef std::vector<int32_t> FlatINT32TArr;

using PreviewImageT = bg::rgba8_image_t;
using PreviewViewT = bg::rgba8_view_t;
using PreviewPixelT = bg::rgba8_pixel_t;

void read_pixel_arr(FlatINT32TArr r_preview_flat_arr, const std::wstring& filepath)
{
  std::ifstream byte_stream(filepath, std::ios::binary);

  PreviewImageT image;

  bg::read_and_convert_image(
    byte_stream, image, bg::image_read_settings<bg::png_tag>());

  const int image_width = (int)image.width();
  const int image_height = (int)image.height();

  const int preview_pixel_count = image_width * image_height;

  PreviewViewT preview_view;
  PreviewPixelT* buff1 = new PreviewPixelT[preview_pixel_count];
  preview_view = bg::interleaved_view(
    image_width, image_height, buff1,
    image_width * sizeof(PreviewPixelT));

  bg::copy_and_convert_pixels(
    bg::flipped_up_down_view(const_view(image)), preview_view);

  r_preview_flat_arr = FlatINT32TArr(preview_pixel_count);
  memcpy(
    &r_preview_flat_arr[0],
    &preview_view[0],
    sizeof(PreviewPixelT) * preview_pixel_count
  );
}

It reads the * .png image file and converts it to int32_t array. (The array is then used to generate the OpenGL texture).

So, the original image file is:

Original

It was exported from Adobe Illustrator with alpha channel, deinterlaced.

Here I got few issues I can't solve:

  • Subsampling.

enter image description here - here is result. As you can see, image is so much aliased as a stairs, why? How to solve it?

  • Interlacing.

enter image description here - here is result. As you can see, is there one more line from image bottom on the top. Temporary it was solved by exporting deinterlaced image but it is not the best way. How can I solve it using gil?

One more test: Original one (with alpha channel gradient):

enter image description here

Result:

enter image description here


Solution

  • There seems to be something wrong after the conversion shown.

    When I save the preview data as PNG:

    bg::write_view("output.png", preview_view, bg::png_tag{});
    

    I get the expected output:

    enter image description here

    That's the preview buffer (buff1) precisely as you copy it in the flat int32 vector.

    Note also

    • the return parameter was passed by value, meaning it would not actually be changed by read_pixel_arr (added & for pass-by-reference)
    • there was a memory leak, because buff1 was never freed. Below, I fixed it with a unique_ptr.

    Live On Coliru

    void read_pixel_arr(FlatINT32TArr& r_preview_flat_arr, std::string filepath) {
        std::ifstream byte_stream(filepath, std::ios::binary);
    
        bg::image_read_settings<bg::png_tag> settings;
        //settings._read_transparency_data = true;
    
        PreviewImageT image;
        bg::read_and_convert_image(byte_stream, image, settings);
    
        auto width   = image.width();
        auto height  = image.height();
        auto npixels = width * height;
    
        auto buff1 = std::make_unique<PreviewPixelT[]>(npixels);
        auto preview_view = bg::interleaved_view(
                width, height, buff1.get(),
                width * sizeof(PreviewPixelT));
        assert(buff1.get() == &preview_view[0]); // checking understanding
    
        bg::copy_and_convert_pixels(
                bg::flipped_up_down_view(const_view(image)), preview_view);
    
        r_preview_flat_arr = FlatINT32TArr(npixels);
        memcpy(r_preview_flat_arr.data(), 
               buff1.get(),
               sizeof(PreviewPixelT) * npixels);
    
        bg::write_view("output.png", preview_view, bg::png_tag{});
    }
    
    int main() {
        FlatINT32TArr v(1 << 20);
        read_pixel_arr(v, "sample.png");
    }
    

    Much Simplified

    Using read_image_info you can avoid allocating the buffer 3 times and copying it as many times:

    1. for the image buffer (which is only used to copy from and detect width/height)
    2. for the buff1 (that was originally leaked as well)
    3. for the flat array (std::vector)

    Instead, let's detect the dimension from the file info, and read directly into the flat array:

    auto flatvector_view(FlatINT32TArr& v, long width) {
        return bg::interleaved_view(
           width, v.size()/width,
           reinterpret_cast<PreviewPixelT*>(v.data()),
           width * sizeof(PreviewPixelT));
    }
    
    long read_pixel_arr(FlatINT32TArr& r_pixeldata, std::string filepath) {
        bg::image_read_settings<bg::png_tag> settings;
        auto info   = bg::read_image_info(filepath, settings);
        auto width  = info._info._width;
        auto height = info._info._height;
        r_pixeldata.resize(width * height);
    
        bg::read_and_convert_view(filepath,
            bg::flipped_up_down_view(flatvector_view(r_pixeldata, width)),
            settings);
    
        return width;
    }
    
    int main() {
        FlatINT32TArr v;
        auto width = read_pixel_arr(v, "sample.png");
    
        bg::write_view("output.png", flatvector_view(v, width), bg::png_tag{});
    }
    

    Note that the reinterpret_cast is guarded by the static_assert. It is logically equivalent to your memcpy type-punning.