3

I'm trying to draw on a window with Gtkmm for C++, cairo::context, gdk::pixbuf. I've noticed that for some widths (in my example 298), instead of my image, I get some horizontal black lines (alternated with white stripes).

lines for width 298 on linux xfce

For other widths (in my example 300) I get a normal image. (I'm just drawing a yellow background in my example).

yellow for width 300 on windows 10

What am I doing wrong ? How can I obtain to draw correctly for any image width ?

I'm finding this behavior both on :

windows 10 / msys2 / gcc11.2 / std=c++20

opensuse 15.2 / gcc 11.2.1 / std=c++17

and on both I have library versions:

-I/usr/include/gtkmm-3.0 -L/usr/lib64 -I/usr/lib64/glibmm-2.4/include -I/usr/include/glibmm-2.4 -I/usr/lib64/glib-2.0/include -I/usr/include/glib-2.0 -I/usr/include/sigc++-2.0/ -I/usr/lib64/sigc++-2.0/include -I/usr/include/giomm-2.4 -I/usr/lib64/giomm-2.4/include -I/usr/include/gdkmm-3.0 -I/usr/lib64/gdkmm-3.0/include -I/usr/lib64/pangomm-1.4/include -I/usr/include/gtk-3.0/ -I/usr/include/pango-1.0 -I/usr/include/cairo -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/cairomm-1.0 -I/usr/include/cairomm-1.0/cairomm -I/usr/lib64/cairomm-1.0/include -I/usr/include/freetype2 -I/usr/lib64/gtkmm-3.0/include -I/usr/include/pangomm-1.4 -I/usr/include/harfbuzz -I/usr/include/atkmm-1.6 -I/usr/include/atk-1.0 -I/usr/lib64/atkmm-1.6/include -LC:/programs/msys64/mingw64/bin -lgtkmm-3.0 -lglibmm-2.4 -I/usr/include/sigc++-2.0 -lsigc-2.0 -lgdkmm-3.0 -latkmm-1.6 -lcairomm-1.0

Here's my code:

#include <gtkmm/application.h>
#include <gtkmm/fixed.h>
#include <gdkmm/pixbuf.h>
#include <gtkmm/window.h>
#include <cairomm/context.h>
#include <giomm/resource.h>
#include <gdkmm/general.h>
#include <iostream>

class TesterWindow : public Gtk::Window
{
    public:
    
        TesterWindow()
        {           
            int width = 298; // lines
//          int width = 300; // yellow
            int height = 300;
            
            set_size_request(width, height);
            
            try
            {
                m_image = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, width, height);

                auto pixels = m_image->get_pixels();
                int channels = 3;
                
                for(int i = 0; i < width * height * channels; ++i)
                {
                    switch(i % 3)
                    {
                        case 0 : pixels[i] = 255; break;
                        case 1 : pixels[i] = 255; break;
                        case 2 : pixels[i] = 0; break;
                        default : break;                
                    }
                }
            }
            catch(const Gio::ResourceError& ex)
            {
                std::cout << "ResourceError: " << ex.what() << std::endl;
            }
            catch(const Gdk::PixbufError& ex)
            {
                std::cout << "PixbufError: " << ex.what() << std::endl;
            }
        }
        
        void show() 
        {
            set_position(Gtk::WIN_POS_CENTER);
            show_all_children();
        }

        bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
        {
            if(!m_image) return false;

            Gdk::Cairo::set_source_pixbuf(cr, m_image, 0, 0);

            cr->paint();

            return true;
        }

    private:
    
        Glib::RefPtr<Gdk::Pixbuf> m_image;
};

int main(int argc, char** argv)
{
    auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example");
    
    TesterWindow window;
    
    window.show();

    return app->run(window);
}
crillion
  • 338
  • 3
  • 10
  • 1
    In certain cases, you have to consider that rows have to be stored with a certain alignment e.g. the number of bytes must be a multiple of 4. If you write R, G, B, this results in proper alignment for certain widths but not for all. 300 * 3 = 900 -> 900 % 4 = 0 but 298 * 3 = 894 -> 894 % 4 = 2. This means that 255, 255, 0 can become 0, 255, 255 for certain lines. That's just a guess but such issues I've seen more often. It's the first I would check for. – Scheff's Cat Jan 07 '22 at 11:00
  • 1
    FYI: [Gdk::Pixbuf::get_rowstride()](https://developer-old.gnome.org/gtkmm/stable/classGdk_1_1Pixbuf.html#aef37137e7f42a5734deb0f9be25ef030) – Scheff's Cat Jan 07 '22 at 11:02
  • 1
    From [gdk-pixbuf.c](https://github.com/krh/gtk/blob/bd84d95e63893ba80f94d7087b580ad575555334/gdk-pixbuf/gdk-pixbuf.c#L262): _`/* Always align rows to 32-bit boundaries */`_ (Actually, I was looking for some kind of sufficient doc. but found this "first class" info instead.) ;-) – Scheff's Cat Jan 07 '22 at 11:11

1 Answers1

3

I'm used to the fact that image rows are often (not always) stored with a certain alignment. Whenever I see an image that appears erroneously in stripes, the row alignment is the first thing I would check.

With this suspicion in mind, I look for some gdkmm (or Gdk) doc. Instead I found a comment in the Gdk source code.

GitHub: gdk-pixbuf.c:

    channels = has_alpha ? 4 : 3;
        rowstride = width * channels;
        if (rowstride / channels != width || rowstride + 3 < 0) /* overflow */
                return NULL;
        
    /* Always align rows to 32-bit boundaries */
    rowstride = (rowstride + 3) & ~3;

        bytes = height * rowstride;

The function Gdk::Pixbuf::get_rowstride() can be used to retrieve the number of bytes between two consecutive rows in the image buffer.

A fix of OP's code could look like:

auto pixels = m_image->get_pixels();
const int rowstride = m_image->get_rowstride();
const int channels = 3;
for (int y = 0; y < height; ++y) {
  auto rowpixels = pixels + y * rowstride;
  for (int x = 0; x < width * channels; x += channels) {
    rowpixels[x + 0] = 255;
    rowpixels[x + 1] = 255;
    rowpixels[x + 2] = 0;
  }
}

Notes:

  1. I transformed the inner loop a bit. (I consider branch-less inner loop bodies as performance friendlier.)

  2. I didn't compile nor debug my code. Please, take it with a grain of salt.

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56