Getting Images onto a Cairo Surface.

Introduction.

This article is a classic example of me not being able to RTFM. I had got focused onto Cairo Surface, but there's a perfectly good method in gdk.Cairo - setSourcePixbuf() - to set a Pixbuf as source in a Cairo context. My apologies to the GTK developers. So you can basically forget what follows ;=(

You used to be able to create a gdk.Drawable, load an image into it, and then plaster that onto a Cairo Surface. You could then paint a target DrawingArea with the result.

Now there is only provision for directly loading a PNG file into a Cairo Surface, and the documentation in fact classifies that as a 'toy interface'. In 3.10 there is apparently a facility within Gdk to load a Pixmap into a Cairo Surface, thus putting us back close to square one but without the now abandoned Drawable. However, for some time into the future, many systems will not have GTK3.10. This can be detected via gtk.Version, and when it is not detected, if you want to load a .jpg file you will be SOL.

This article presents a way that can be used within the pre 3.10 limitations of Gdc and Cairo.

The Proposition.

I shall continue to talk about this in a somewhat neutral way language-wise. We can load any reasonable image file into a Gdk.Pixbuf, and there we can scale it and such. At the same time we can create a Cairo.ImageSurface of some specified size with bits set to unspecified values. We therefore are left with the remaining problem of transferring the bits from one to the other. Here's how it can be done. I'm not saying it is robust or portable - time will tell:

   ImageSurface surfaceFromPixbuf()
   {
      // We have a scaled pixbuf suitable prepared?
      if (spxb is null)
         return null;
         
      // Recover its parameters
      int channels = spxb.getNChannels();
      int hasAlpha = spxb.getHasAlpha();
      int sstride = spxb.getRowstride();
      int w = spxb.getWidth(), h = spxb.getHeight();
      
      // Decide on what format we'll try
      cairo_format_t format = (channels== 4)? cairo_format_t.ARGB32: cairo_format_t.RGB24;
      ImageSurface t = ImageSurface.create(format, w, h);
      // Just to be sure, get the stride as Cairo understands it
      int stride = t.getStride();
      
      // Now get pointers to the respective buffers
      ubyte* dp = t.getData();
      ubyte* pp = cast(ubyte*) spxb.getPixels();
      
      // Set the pixels in the surface to some predicatable value - white and fully opaque
      dp[0..h*stride]=255;
      
      // Now the strange bit - what Cairo describes as ARGB for instance, turns out
      // to be BGRA - this sounds a bit like an endian problem???
      for (int i = 0; i < h; i++)
      {
         ubyte* op = dp+(i*stride);
         ubyte* sp = pp+(i*sstride);
         for (int j = 0; j < w; j++)
         {
            if (channels == 4)
            {
               op[0] = sp[2];
               op[1] = sp[1];
               op[2] = sp[0];
               op[3] = sp[3];
            }
            else
            {
               // Even in the RGB24 case, 32 bits are used
               op[0] = sp[2];
               op[1] = sp[1];
               op[2] = sp[0];
               op[3] = 0;
            }
            op += 4;
            sp += channels;
         }
      }
      // This is important
      t.markDirty();
      return t;
   }
You should be able to get the idea from that which can then be converted to the language of your choice. But, you might ask, what is the strange language here. Well, it is D. Many of D'd proponents would say no, but I've deliberately written it so as to be C or C++ like.

It seems from the appearance of the code above that highlightjs does not know about D either ;=(

D is a cool language that has absorbed an enormous amount of effort and thought from its community, and in particular from Walter Bright.