2

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

ivpe
  • 47
  • 5
  • I didn't think that's what aliasing is. You mean it's pixelized? – sehe Nov 29 '20 at 19:55
  • @sehe , yes, pixelized. It looks like pixels on circle shape border is missing alpha channel. As you can see, result line width is a bit higher than original image has. – ivpe Nov 29 '20 at 20:06
  • For some reason in areas where original image pixels was antialiased (shape borders), alpha channel value is clamped to 1.0f. Smth like: `result_alpha = (original_alpha > 0.0f) ? 1.0f : 0.0f` – ivpe Nov 29 '20 at 20:14

1 Answers1

2

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.


sehe
  • 374,641
  • 47
  • 450
  • 633
  • yes. For sure, I just missing ampersand in question description sample code, its looking like mem leak;) Unfortunetly, same way as before I have no direct access to draw code and all I can do - generate numpy array of pixels and set icon pixels from it. Here I am limited in data type usages (as you can see, I need int32_t 1D-array). But your post is usefull as well, because it shows much more convient and right way to realize it, Thank you! – ivpe Nov 30 '20 at 15:31
  • 1
    Sure. I was just helping you realize the problem exists outside the code shown. You can post other questions if you get stuck finding the cause further down. [I didn't assume the ampersand was missing in your real code as you would have surely seen different symptoms. To be completely sure, the memory leak was independent from that, but perhaps that's also less relevant] – sehe Nov 30 '20 at 15:34
  • Pretty terrible situation with documentation (as well as for `boost` at all;). I got a crazy idea looking at `read_and_convert_view` usage in your code. Can it be used to generate previews (smaller versions of image) using smth like `bg::resize_view`? For example I have dataset of 500+ 8K images and I need generate previews for them. Previous implementation works good, it was < 30sec (running from SSD) but I think it can be better. – ivpe Nov 30 '20 at 20:42
  • 1
    Yes. [padding] [padding] Seriously, if you want a review of that, consider posting on [CodeReview](http://codereview.stackexchange.com) – sehe Nov 30 '20 at 22:28
  • Going deeper, I realized it using libpng itself. After reading documentation of libpng, I found the issue can be solved using `png_set_alpha_mode(png_ptr, PNG_ALPHA_PREMULTIPLIED, 1.0f);` Strange that boost::gil do not provide such possibility. – ivpe Dec 03 '20 at 12:48
  • @vanyOk To be honest, it sounds like that should do nothing (considering alpha is very much visibly enabled already), and the Boost GIL tests come with comprehensive tests for every color under the sun: https://github.com/boostorg/gil/blob/develop/test/extension/io/png/png_read_test.cpp/ It is simply more likely that there is a misunderstanding down the road. If you're more comfortable/find it easier to work with libpng docs, by all means, go that route. – sehe Dec 03 '20 at 22:38
  • I can see there used to be a bug with grayscale PNG with alphs at one time, which has added those to the tests https://github.com/boostorg/gil/pull/118 – sehe Dec 03 '20 at 22:41
  • Yes, you right, for current task I rather would use libpng directly, its easy. http://www.libpng.org/pub/png/libpng-manual.txt Also, according to documentation "This is the 'OPTIMIZED' mode. For this mode a pixel is treated as opaque only if the alpha value is equal to the maximum value." So due to docs, libpng itself can write alpha channel values correct but still need `png_set_alpha_mode` call if you need to use them in your application. I like the way of boost::gil, but not in this specific case (when I need only png's) files with very specific output format (int32_t array) – ivpe Dec 04 '20 at 15:05
  • It's still weird. The information is clearly there, so it SHOULD also be in those int32 values. I suspect you can easily prove it by looking at a specific pixel and dumping the values. Mmm. Maybe I will myself – sehe Dec 04 '20 at 15:08
  • 1
    Maybe offtopic, but looks like `boost::gil` - its abandoned project. The most interesting that its the ONLY one image processing library which glue a few of native format libraries and allows io from streams (for example, OpenCV do not allow this and OpenImageIO allows but in a very limited way, not for all image types) – ivpe Dec 04 '20 at 15:10
  • Yes, also you can temporary modify your local gil io reader with / without `png_set_alpha_mode ` to see a difference – ivpe Dec 04 '20 at 15:11
  • And compare not resulted file, but an array of `int32_t` – ivpe Dec 04 '20 at 15:13
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/225530/discussion-between-sehe-and-vanyok). – sehe Dec 04 '20 at 15:13