Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Programming transparent and gradient output without Cairo (C# X11) - a proof of concept

0.00/5 (No votes)
3 Aug 2015 1  
How to draw transparent and gradient output on a X11 window unsing the (low level API) XRender protocol extension for X11.

Download complete project for 32 bit (zip)

Download complete project for 64 bit (zip)

Introduction

This article shows how to access XRender extensions via Xlib/X11 protocol from C# using Mono Develop. A lot of API calls are ready-to-use defined and tested and some challenges are mastered, e.g., defining background pixmaps or drawing to foregrond for Xt/Athena widgets.

Since XRender extensions are based on Xlib/X11 protocol, the Xlib/X11 windows and Xm/Motif widgets are supported as well.

This article is aimed to verify that programming XRender extensions with C# can be easyly achieved (prove of concept). It provides a sample application's complete project for 32 bit and 64 bit.

Background

This is the fifth article in a series of articles examining X* API calls from C# using Mono Develop.
The first article was Programming Xlib with Mono Develop - Part 1: Low-level (proof of concept) and dealed with Xlib/X11. The second article was Programming Xlib with Mono Develop - Part 2: Athena widgets (proof of concept) and dealed with Xt/Athena. The third article was Programming Xlib with Mono Develop - Part 3: Motif widgets (proof of concept) and dealed with Xm/Motif. And the fourth article was Programming Xlib with Mono Develop - Part 4: FWF Xt widgets and dealed with Xfxf/Free Widget Foundation widgets. The current article goes ahead to the XRender extensions of the Xlib/X11 protocol and shows how to use transparent and gradient drawing from a modern language/IDE like C#/Mono.

Although the sample code is written for Xt/Athena widgets, it can be ported to Xlib/X11 windows, Xm/Motif widgets and FWF Xt widgets easily.

This article illustrates how easy programming XRender extensions, using transparent and gradient API calls, from C# is to achieve. Since user expect applications with appealing design, GUIs based on Xlib/X11 windows, Xt/Athena widgets, Xm/Motif widgets and FWF Xt widgets became boring. They lack of transparent, gradient and antialiasing drawing feasability. The XRender extensions support all of them, but only transparent and gradient drawing will be covered by this article (the antialiasing drawing is very low-level and elaborate to use - it should be aided by freetype library or be replaced by Xft). These might be two strong reasons to upgrade beloved but dusty Xlib/X11 windows, Xt/Athena widgets, Xm/Motif widgets or FWF Xt widgets based applications to a GUI with a modern look.

I just want to add, that most of the up-to-date X11 window managers, the Cairo library for X11-Backend (and therefore GTK+ UI as well), KDE (if Open GL is not available) and many other libraries use XRender extension, mostly in combination with XDamage extensions.

Using the code

The sample application has been developed from the Programming Xlib with Mono Develop - Part 2: Athena widgets (proof of concept) articele's sample applicaction and has been written with Mono Develop 2.4.1 for Mono 2.8.1 on OPEN SUSE 11.3 Linux 32 bit EN and GNOME desktop. Neither the port to any older nor to any newer version should be a problem.
The sample application's solution consists of four projects (the complete sources are provided for download):

  • X11Wrapper defines the function prototypes, structures and types for Xlib/X11 calls  to the libX11.so  (already known from the previous articles of this series)
  • XawNative contains a very little amount of native C code to access Athena widget class records and one sample that shows how to incorporate third party Xt widgets
  • XtWrapper defines the function prototypes, structures and types for Xt calls to the libXt.so and the function prototypes for Athena widget calls to the libXawNative.so (already known from the second article of this series)
  • XRenderExtension contains the sample application based on Xt/Athena widgets

The sample application is also tested with Mono Develop 3.0.6 for Mono 3.0.4 on OPEN SUSE 12.3 Linux 64 bit DE and GNOME desktop, IceWM, TWM und Xfce.

The only difference between the 32 bit and the 64 bit solution is the definition of some types, as already described in the first article of this series.

The application itself has 952 lines of code only. Some generic GUI code is released to several Xt* classes - not to wrap Xt/Athena widgets with C#, but to organize re-usable code. The function prototypes of the Xt/Athena and some necessary structures/enumerations are defined in the Xtlib class. The application shows:

  1. a menu bar with 3 buttons, where the first two buttons have a dropdown menu and the last button has a callback,
  2. a classical (before Xt Release 4) dropdown menu with three entries at the first menu button and a Xt Release 4 simple menu with four entries at the second menu button,
  3. a label widget to show text output as well as transparent and gradient functionality,
  4. an application icon with transparency (not all up to date windows managers display it, but Xfce does),
  5. a status bar with bitmap and label and
  6. a grab exclusive question dialog with bitmap, that can cancel application quit.

The first image shows the list of X11 server extensions, available for my OPEN SUSE 11.3 Linux 32 bit EN and GNOME desktop. The seventh from the bottom is RENDER - the extension this sample application utilizes. This display can be called via menu Background Pictures | No background picture.

The second image shows overlapping transparent rectangles (red, green and blue) that add their color values to yellow, turquoise, magenta, and white on OPEN SUSE 11.3 Linux 32 bit EN and GNOME desktop. This display can be called via menu Background Pictures | Semi-transparent rectangles overlapping.

The third image shows stacked transparent rectangles (starting with dark red, that fills the whole widget, and changing to blue, with concentric decreasing size) on OPEN SUSE 11.3 Linux 32 bit EN and GNOME desktop. The addition of the rectangle's color values changes the appearance from dark red outside via red and magenta to white inside. This display can be called via menu Background Pictures | Semi-transparent rectangles stacked.

The fourth image shows a vertical linear color gradient from red via green and blue back to red on OPEN SUSE 12.3 Linux 64 bit DE and Xfce desktop. This display can be called via menu Foreground gradients | Linear vertical color gradient.

The fifth image shows a centered conical color gradient from red via green and blue back to red on OPEN SUSE 12.3 Linux 64 bit DE and Xfce desktop. This display can be called via menu Foreground gradients | Conical color gradient.

The last mage shows a non-centered radial color gradient from red (inside) via green and blue back to red (outside) on OPEN SUSE 12.3 Linux 64 bit DE and Xfce desktop. This display can be called via menu Foreground gradients | Radial color gradient.

General functionality

The general functionality (menu, output area, status bar) is derived from the Programming Xlib with Mono Develop - Part 2: Athena widgets (proof of concept) articele's sample applicaction and mostly identical. A callback for the expose event of the output area widget has been added via translations/action registration, to draw the gradient samples to the foreground.

Only one problem solution is new and affects the root layout manager, that has been implemented as Xt/Athena Form widget: Form widgets provide constraint resources to anchor children either relative to each other (fromHoriz, fromVert) or relative to the form they are children of (left, top, right, bottom). This permits control over the children's position which isn't possible for Xt/Athena Box widget.

The dafault constraint resources are left, top, right and bottom set to XawRubber. This would be a good solution, if layout calculation would be done based on float or double precisition. But it is based on int and therefore every resizing of the application window causes the relatively small widgets to get even smaller (round off effect) and the relatively large widgets to get even larger (round up effect). This behaviour corrupts sooner or later every layout completely.

The sample application prevents this problem by explicitely setting initial width/height for relatively small widgets (e.g. StatusIcon) and anchor them to one border of the parent Form widget for both anchors of the same direction (e. g. left: ChainLeft and right: ChainLeft). This forces a fixed size to the relatively small widgets while rubber behaviour stays intact for relatively large widgets.

Transparent functionality

The tansparent drawing functionality is based on drawing into a picture. The picture is created on a pixmap basis.

But first the prerequisits are to check.

// Investigate installed X11 extensions *** without *** using any X11 extensions.
string listExtensions = String.Empty;
IntPtr pListExtensionNames = X11lib.XListExtensions (display, out listExtensions);
if (pListExtensionNames != IntPtr.Zero)
{
    if (!string.IsNullOrEmpty (listExtensions))
    {
        Arg[] outputLabelArgs    = {    new Arg(XtNames.XtNlabel, listExtensions + "\0") };
        Xtlib.XtSetValues (_output, outputLabelArgs, (XCardinal)1);
    }                

    X11lib.XFreeExtensionList (pListExtensionNames);
}

// Investigate installed XRender extension's version *** without *** using XRender extensions.
TInt majorOpcode;
TInt firstEvent;
TInt firstError;
if (X11lib.XQueryExtension (display, "RENDER", out majorOpcode, out firstEvent, out firstError) != true)
{
    Console.WriteLine ("ERROR: () - Call to XQueryExtension() for 'RENDER' failed.");
    return;
}

// Optionally investigate whether render extension is available on display using XRender extensions,
// the first event number used by the extension (note that Render currently uses no events)
// and the first error number used by the extension.
if (XRenderLib.XRenderQueryExtension (display, out firstEvent, out firstError) != true)
{
    Console.WriteLine ("ERROR: () - Call to XRenderQueryExtension() failed.");
    return;
}

// Optionally investigate installed XRender extension's version using XRender extensions.
TInt majorVersion = 0;
TInt minorVersion = 0;
if (XRenderLib.XRenderQueryVersion (display, out majorVersion, out minorVersion) <= 0)
{
    Console.WriteLine ("ERROR: () - Call to XRenderQueryVersion() failed.");
    return;
}
if ((int)majorVersion > 0 || ((int)majorVersion == 0 || (int)minorVersion >= 7))
{    ;
    // O.K. All functions, this sample is based on, are supported.
    // - XRenderFillRectangle/XRenderFillRectangles
    // - XRenderComposite
    // - XRenderCreateLinearGradient/XRenderCreateConicalGradient/XRenderCreateRadialGradient
    // - ...
}
else
{
    Console.WriteLine ("ERROR: draw() - Outdated version of XRender extension. Test might failed.");
}

Subsequently the required size for the picture to draw on can be investigated and a pixmap for the picture can be allocated.

int    width  = (int)Xtlib.XtGetValueOfDimension (_output, XtNames.XtNwidth);
int    height = (int)Xtlib.XtGetValueOfDimension (_output, XtNames.XtNheight);
int pixmapWidth  = Math.Max (1, width ); // Math.Max (1, ((int)(width  / 8)) * 8);
int pixmapHeight = Math.Max (1, height); // Math.Max (1, ((int)(height / 8)) * 8);

IntPtr pixmapBackground  = X11lib.XCreatePixmap (display, Xtlib.XtWindow(_output),
                                                 (TUint)pixmapWidth, (TUint)pixmapHeight, (TUint)24);
if (pixmapBackground == IntPtr.Zero)
{
    Console.WriteLine ("ERROR: () - Call to XCreatePixmap() failed.");
    return;
}

// ******************************************************************************************************
// Attention: Fill memory (behind pixmap) with zero to ensure a defined start value for raster operations.
IntPtr pGC = X11lib.XCreateGC (display, pixmapBackground, (TUlong)0, IntPtr.Zero);
X11lib.XSetForeground (display, pGC,
                       X11lib.XBlackPixelOfScreen (X11lib.XScreenOfDisplay (display, (TInt)0)));
X11lib.XFillRectangle (display, pixmapBackground, pGC, 0, 0, pixmapWidth, pixmapHeight);
X11lib.XFreeGC (display, pGC);        
// ******************************************************************************************************

The first stumbling block for a successful tansparent drawing is to miss the preparation of the pixmap's memory.

Now the picture format can be prepared and the picture can be allocated.

XRenderLib.XRenderPictFormat xRenderPictFormat =
    XRenderLib.XRenderFindVisualFormat (display, X11lib.XDefaultVisual (display, 0));
if (xRenderPictFormat.id != IntPtr.Zero)
{
    XRenderLib.XRenderPictureAttributes pictureAttributes = new XRenderLib.XRenderPictureAttributes();
    pictureAttributes.poly_edge = (TInt)X11.XRenderPolyEdge.PolyEdgeSmooth;
    pictureAttributes.poly_mode = (TInt)X11.XRenderPolyEdge.PolyModeImprecise;
    
    IntPtr picture =  XRenderLib.XRenderCreatePicture (display, pixmapBackground, ref xRenderPictFormat,
                                                       XRenderCreatePictureValueMask.CPPolyEdge |
                                                       XRenderCreatePictureValueMask.CPPolyMode,
                                                       ref pictureAttributes);
    if (picture != IntPtr.Zero)
    {

The second stumbling block for a successful tansparent drawing is not to use premultiplied color components. While the X11 XColor structure uses byte red, byte green and byte blue components, the XRender XRenderColor structure's ushort red, ushort green, ushort blue and ushort alpha components can be thought of XColor structure's components multiplied with alpha - like:
xRenderColor.red = (ushort)(((ushort) xColor.red) * (ushort)alpha)

        TUshort alphaStep = (TUshort)0x00FF;
        XRenderLib.XRenderColor colorR =
            new XRenderLib.XRenderColor ((TUshort)(255 * (long)alphaStep), // premultilied red
                                         (TUshort)(  0 * (long)alphaStep), // premultilied green
                                         (TUshort)(  0 * (long)alphaStep), // premultilied blue
                                         alphaStep);
        XRenderLib.XRenderColor colorG =
            new XRenderLib.XRenderColor ((TUshort)(  0 * (long)alphaStep), // premultilied red
                                         (TUshort)(255 * (long)alphaStep), // premultilied green
                                         (TUshort)(  0 * (long)alphaStep), // premultilied blue
                                         alphaStep);
        XRenderLib.XRenderColor colorB =
            new XRenderLib.XRenderColor ((TUshort)(  0 * (long)alphaStep), // premultilied red
                                         (TUshort)(  0 * (long)alphaStep), // premultilied green
                                         (TUshort)(255 * (long)alphaStep), // premultilied blue
                                         alphaStep);

        XRenderLib.XRenderFillRectangle (display, XRenderPictureOp.PictOpOver, picture, ref colorR,
                                         (TInt)(0), (TInt)(0),
                                         (TUint)(pixmapWidth), (TUint)(pixmapHeight / 3 + 30));
        XRenderLib.XRenderFillRectangle (display, XRenderPictureOp.PictOpOver, picture, ref colorG,
                                         (TInt)(0), (TInt)(pixmapHeight / 3 - 10),
                                         (TUint)(pixmapWidth / 2 + 20), (TUint)(pixmapHeight * 2/3 + 10));
        XRenderLib.XRenderFillRectangle (display, XRenderPictureOp.PictOpOver, picture, ref colorB,
                                         (TInt)(pixmapWidth / 2 - 20), (TInt)(pixmapHeight / 3 - 10),
                                         (TUint)(pixmapWidth / 2 + 20), (TUint)(pixmapHeight * 2/3 + 10));

And finally the pixmap, the picture has drawn on, must be assigned and cleanup must be done.

        // Assign background pixmap.
        X11lib.XSetWindowBackgroundPixmap (display, Xtlib.XtWindow(_output), pixmapBackground);
        // Apply background pixmap - invalidate the background.
        // X11lib.XClearWindow (display, Xtlib.XtWindow(_output)); // Doesn't provide invocation of Expose!
        X11lib.XClearArea (display, Xtlib.XtWindow(_output), (TInt)0, (TInt)0,
                           (TUint)pixmapWidth, (TUint)pixmapHeight, true);
        // Ensure processing.
        X11lib.XFlush (display);
    }
    // Finished drawing operations - picture is obsolete.
    XRenderLib.XRenderFreePicture(display, picture);
}
// Finished background pixmap handling - pixmap is obsolete.
X11lib.XFreePixmap (display, pixmapBackground);

Gradient functionality

The gradient drawing functionality is based on rendering the composite of present image and new image with a raster operation. The present image can be a window content or a pixmap, represented by the destination picture, and the new image is completely hidden by the XRender API, represented by the gradient picture.

The check of prerequisits is the same as for transparent drawing.

Subsequently the required size for the picture to render the composite on can be investigated, the picture format can be prepared and the destination picture can be allocated.

int    width  = (int)Xtlib.XtGetValueOfDimension (_output, XtNames.XtNwidth);
int    height = (int)Xtlib.XtGetValueOfDimension (_output, XtNames.XtNheight);
int pixmapWidth  = Math.Max (1, width ); // Math.Max (1, ((int)(width  / 8)) * 8);
int pixmapHeight = Math.Max (1, height); // Math.Max (1, ((int)(height / 8)) * 8);

XRenderLib.XRenderPictFormat xRenderPictFormat = XRenderLib.XRenderFindVisualFormat (display, visual);
if (xRenderPictFormat.id != IntPtr.Zero)
{
    XRenderLib.XRenderPictureAttributes pictureAttributes = new XRenderLib.XRenderPictureAttributes();

     /* This is the target- (or destination-) drawable, derived from the window content. */
     IntPtr destPict = XRenderLib.XRenderCreatePicture (display, window, ref xRenderPictFormat,
                                                         XRenderCreatePictureValueMask.CPNone,
                                                         ref pictureAttributes);
    
    if (destPict != IntPtr.Zero)
    {

Now the gradient picture can be prepared.

        X11.TInt[]                        aColorStops     = new X11.TInt[4]; /* XFixed */
        XRenderLib.XRenderColor[]        aColorList      = new XRenderLib.XRenderColor[4];
        XRenderLib.XLinearGradient        linearGradient  = new XRenderLib.XLinearGradient ();
       
        /* Offsets for color-stops have to be stated in normalized form,
        ** which means within the range of [0.0, 1.0f] */
        aColorStops[0] = XRenderLib.XDoubleToFixed (0.0f);
        aColorStops[1] = XRenderLib.XDoubleToFixed (0.33f);
        aColorStops[2] = XRenderLib.XDoubleToFixed (0.66f);
        aColorStops[3] = XRenderLib.XDoubleToFixed (1.0f);
        
        /* There's nothing much to say about a XRenderColor, each
        ** R/G/B/A-component is an premultiplied  unsigned int (16 bit) */
        aColorList[0].red   = (X11.TUshort)0xffff;
        aColorList[0].green = (X11.TUshort)0x0000;
        aColorList[0].blue =  (X11.TUshort)0x0000;
        aColorList[0].alpha = (X11.TUshort)0xffff;
        aColorList[1].red =   (X11.TUshort)0x0000;
        aColorList[1].green = (X11.TUshort)0xffff;
        aColorList[1].blue =  (X11.TUshort)0x0000;
        aColorList[1].alpha = (X11.TUshort)0xffff;
        aColorList[2].red =   (X11.TUshort)0x0000;
        aColorList[2].green = (X11.TUshort)0x0000;
        aColorList[2].blue =  (X11.TUshort)0xffff;
        aColorList[2].alpha = (X11.TUshort)0xffff;
        aColorList[3].red =   (X11.TUshort)0xffff;
        aColorList[3].green = (X11.TUshort)0x0000;
        aColorList[3].blue =  (X11.TUshort)0x0000;
        aColorList[3].alpha = (X11.TUshort)0xffff;

        /* Coordinates for the start- and end-point of the linear gradient are
        ** in  window-space, they are not normalized like in cairo... */
        linearGradient.p1.x = XRenderLib.XDoubleToFixed (0.0f);
        linearGradient.p1.y = XRenderLib.XDoubleToFixed (0.0f);
        linearGradient.p2.x = XRenderLib.XDoubleToFixed (0.0f);
        linearGradient.p2.y = XRenderLib.XDoubleToFixed ((double)pixmapHeight);

        IntPtr gradientPict = XRenderLib.XRenderCreateConicalGradient (display, ref conicalGradient,
                                                                       aColorStops,
                                                                       aColorList, (X11.TInt)4);

And finally the gradient picture must be rendered as composite to the destination picture and cleanup must be done.

        XRenderLib.XRenderComposite (display, X11.XRenderPictureOp.PictOpSrc,
                                     gradientPict, IntPtr.Zero, destPict,
                                     (X11.TInt)0, (X11.TInt)0,
                                     (X11.TInt)0, (X11.TInt)0,
                                     (X11.TInt)0, (X11.TInt)0,
                                     (X11.TUint)pixmapWidth, (X11.TUint)pixmapHeight);

        XRenderLib.XRenderFreePicture (display, destPict);
        XRenderLib.XRenderFreePicture (display, gradientPict);
    }
}

Points of Interest

This sample application demonstrates that only a little amout of code is required to provide transparent and gradient drawing features to Xt/Athena widget set based applications. The code integrates smooth into a typical Xt program structure and can easily be adopted for plain X11 applications and Xm/Motif applications.

History

  • 02. July 2015, This is the fifth article about native calls from C# and Mono Develop to X11 API. The next article might deal with Xft to draw antialiased text.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here