LJIT2pixman – Drawing on Linux

On the Linux OS, libpixman is at the heart of doing some low level drawing. It’s very simple stuff like compositing, rendering of gradients and the like. It takes up the slack where hardware acceleration doesn’t exist. So, graphics libraries such as Cairo leverage libpixman at the bottom to take care of the basics. LJIT2pixman is a little project that delivers a fairly decent LuaJIT binding to that library.

Here’s one demo of what it can do.

checkerboard

Of course, given the title of this post, you know LuaJIT is involved, so you can expect that there’s some way of doing this in LuaJIT.

package.path = package.path..";../?.lua"

local ffi = require("ffi")
local bit = require("bit")
local band = bit.band

local pixman = require("pixman")()
local pixlib = pixman.Lib_pixman;
local ENUM = ffi.C
local utils = require("utils")
local save_image = utils.save_image;

local function D2F(d) return (pixman_double_to_fixed(d)) end

local function main (argc, argv)

	local WIDTH = 400;
	local HEIGHT = 400;
	local TILE_SIZE = 25;

    local trans = ffi.new("pixman_transform_t", { {
	    { D2F (-1.96830), D2F (-1.82250), D2F (512.12250)},
	    { D2F (0.00000), D2F (-7.29000), D2F (1458.00000)},
	    { D2F (0.00000), D2F (-0.00911), D2F (0.59231)},
	}});

    local checkerboard = pixlib.pixman_image_create_bits (ENUM.PIXMAN_a8r8g8b8,
					     WIDTH, HEIGHT,
					     nil, 0);

    local destination = pixlib.pixman_image_create_bits (ENUM.PIXMAN_a8r8g8b8,
					    WIDTH, HEIGHT,
					    nil, 0);

    for i = 0, (HEIGHT / TILE_SIZE)-1  do
		for j = 0, (WIDTH / TILE_SIZE)-1 do
	    	local u = (j + 1) / (WIDTH / TILE_SIZE);
	    	local v = (i + 1) / (HEIGHT / TILE_SIZE);
	    	local black = ffi.new("pixman_color_t", { 0, 0, 0, 0xffff });
	    	local white = ffi.new("pixman_color_t", {
				v * 0xffff,
				u * 0xffff,
				(1 - u) * 0xffff,
				0xffff });
	    	
	    	local c = white;

	    	if (band(j, 1) ~= band(i, 1)) then
				c = black;
	    	end

	    	local fill = pixlib.pixman_image_create_solid_fill (c);

	    	pixlib.pixman_image_composite (ENUM.PIXMAN_OP_SRC, fill, nil, checkerboard,
				    0, 0, 0, 0, j * TILE_SIZE, i * TILE_SIZE,
				    TILE_SIZE, TILE_SIZE);
		end
    end

    pixlib.pixman_image_set_transform (checkerboard, trans);
    pixlib.pixman_image_set_filter (checkerboard, ENUM.PIXMAN_FILTER_BEST, nil, 0);
    pixlib.pixman_image_set_repeat (checkerboard, ENUM.PIXMAN_REPEAT_NONE);

    pixlib.pixman_image_composite (ENUM.PIXMAN_OP_SRC,
			    checkerboard, nil, destination,
			    0, 0, 0, 0, 0, 0,
			    WIDTH, HEIGHT);

	save_image (destination, "checkerboard.ppm");

    return true;
end


main(#arg, arg)


With a couple of exceptions, the code looks almost exactly like its C based counterpart. I actually think this is a very good thing, because you can rapidly prototype something from a C coding example, but have all the support and protection that a dynamic language such as lua provides.

And here’s another:

conical-test

In this case is the conical-test.lua demo doing the work.

package.path = package.path..";../?.lua"

local ffi = require("ffi")
local bit = require("bit")
local band = bit.band
local lshift, rshift = bit.lshift, bit.rshift

local pixman = require("pixman")()
local pixlib = pixman.Lib_pixman;
local ENUM = ffi.C
local utils = require("utils")
local save_image = utils.save_image;
local libc = require("libc")

local SIZE = 128
local GRADIENTS_PER_ROW = 7
local NUM_GRADIENTS = 35

local NUM_ROWS = ((NUM_GRADIENTS + GRADIENTS_PER_ROW - 1) / GRADIENTS_PER_ROW)
local WIDTH = (SIZE * GRADIENTS_PER_ROW)
local HEIGHT = (SIZE * NUM_ROWS)

local function double_to_color(x)
    return (x*65536) - rshift( (x*65536), 16)
end

local function PIXMAN_STOP(offset,r,g,b,a)		
   return ffi.new("pixman_gradient_stop_t", { pixman_double_to_fixed (offset),		
	{					
	    double_to_color (r),		
		double_to_color (g),		
		double_to_color (b),		
		double_to_color (a)		
	}					
    });
end

local stops = ffi.new("pixman_gradient_stop_t[4]",{
    PIXMAN_STOP (0.25,       1, 0, 0, 0.7),
    PIXMAN_STOP (0.5,        1, 1, 0, 0.7),
    PIXMAN_STOP (0.75,       0, 1, 0, 0.7),
    PIXMAN_STOP (1.0,        0, 0, 1, 0.7)
});

local  NUM_STOPS = (ffi.sizeof (stops) / ffi.sizeof (stops[0]))


local function create_conical (index)
    local c = ffi.new("pixman_point_fixed_t")
    c.x = pixman_double_to_fixed (0);
    c.y = pixman_double_to_fixed (0);

    local angle = (0.5 / NUM_GRADIENTS + index / NUM_GRADIENTS) * 720 - 180;

    return pixlib.pixman_image_create_conical_gradient (c, pixman_double_to_fixed (angle), stops, NUM_STOPS);
end


local function main (argc, argv)

    local transform = ffi.new("pixman_transform_t");

    local dest_img = pixlib.pixman_image_create_bits (ENUM.PIXMAN_a8r8g8b8,
					 WIDTH, HEIGHT,
					 nil, 0);
 
    utils.draw_checkerboard (dest_img, 25, 0xffaaaaaa, 0xff888888);

    pixlib.pixman_transform_init_identity (transform);

    pixlib.pixman_transform_translate (NULL, transform,
				pixman_double_to_fixed (0.5),
				pixman_double_to_fixed (0.5));

    pixlib.pixman_transform_scale (nil, transform,
			    pixman_double_to_fixed (SIZE),
			    pixman_double_to_fixed (SIZE));
    pixlib.pixman_transform_translate (nil, transform,
				pixman_double_to_fixed (0.5),
				pixman_double_to_fixed (0.5));

    for i = 0, NUM_GRADIENTS-1 do
    
	   local column = i % GRADIENTS_PER_ROW;
	   local row = i / GRADIENTS_PER_ROW;

	   local src_img = create_conical (i); 
	   pixlib.pixman_image_set_repeat (src_img, ENUM.PIXMAN_REPEAT_NORMAL);
   
	   pixlib.pixman_image_set_transform (src_img, transform);
	
	   pixlib.pixman_image_composite32 (
	       ENUM.PIXMAN_OP_OVER, src_img, nil,dest_img,
	       0, 0, 0, 0, column * SIZE, row * SIZE,
	       SIZE, SIZE);
	
	   pixlib.pixman_image_unref (src_img);
    end

    save_image (dest_img, "conical-test.ppm");

    pixlib.pixman_image_unref (dest_img);

    return true;
end


main(#arg, arg)

linear-gradient

Linear Gradient demo

screen-test

screen demo (transparency).
Perhaps this is the easiest one of all. All the interesting functions are placed into the global namespace, so they can be accessed easily, just like everything in C is globally available.

package.path = package.path..";../?.lua"

local ffi = require("ffi")
local bit = require("bit")
local band = bit.band
local lshift, rshift = bit.lshift, bit.rshift

local pixman = require("pixman")()
local pixlib = pixman.Lib_pixman;
local ENUM = ffi.C
local utils = require("utils")
local save_image = utils.save_image;
local libc = require("libc")


local function main (argc, argv)

    WIDTH = 40
    HEIGHT = 40
    
    local src1 = ffi.cast("uint32_t *", libc.malloc (WIDTH * HEIGHT * 4));
    local src2 = ffi.cast("uint32_t *", libc.malloc (WIDTH * HEIGHT * 4));
    local src3 = ffi.cast("uint32_t *", libc.malloc (WIDTH * HEIGHT * 4));
    local dest = ffi.cast("uint32_t *", libc.malloc (3 * WIDTH * 2 * HEIGHT * 4));

    for i = 0, (WIDTH * HEIGHT)-1 do
	   src1[i] = 0x7ff00000;
	   src2[i] = 0x7f00ff00;
	   src3[i] = 0x7f0000ff;
    end

    for i = 0, (3 * WIDTH * 2 * HEIGHT)-1 do
	   dest[i] = 0x0;
    end

    local simg1 = pixman_image_create_bits (ENUM.PIXMAN_a8r8g8b8, WIDTH, HEIGHT, src1, WIDTH * 4);
    local simg2 = pixman_image_create_bits (ENUM.PIXMAN_a8r8g8b8, WIDTH, HEIGHT, src2, WIDTH * 4);
    local simg3 = pixman_image_create_bits (ENUM.PIXMAN_a8r8g8b8, WIDTH, HEIGHT, src3, WIDTH * 4);
    local dimg  = pixman_image_create_bits (ENUM.PIXMAN_a8r8g8b8, 3 * WIDTH, 2 * HEIGHT, dest, 3 * WIDTH * 4);

    pixman_image_composite (ENUM.PIXMAN_OP_SCREEN, simg1, NULL, dimg, 0, 0, 0, 0, WIDTH, HEIGHT / 4, WIDTH, HEIGHT);
    pixman_image_composite (ENUM.PIXMAN_OP_SCREEN, simg2, NULL, dimg, 0, 0, 0, 0, (WIDTH/2), HEIGHT / 4 + HEIGHT / 2, WIDTH, HEIGHT);
    pixman_image_composite (ENUM.PIXMAN_OP_SCREEN, simg3, NULL, dimg, 0, 0, 0, 0, (4 * WIDTH) / 3, HEIGHT, WIDTH, HEIGHT);

    save_image (dimg, "screen-test.ppm");
    
    return true;
end

main(#arg, arg)

I really like this style of rapid prototyping. The challenge I have otherwise is that it’s just too time consuming to consume things in their raw C form. Things like build systems, compiler versions, and other forms of magic always seem to get in the way. And if it’s not that stuff, it’s memory management, and figuring out the inevitable crashes.

Once you wrap a library up in a bit of lua goodness though, it becomes much more approachable. It may or may not be the most performant thing in the world, but you can worry about that later.

Having this style of rapid prototyping available saves tremendous amounts of time. Since you’re not wasting your time on the mundane (memory management, build system, compiler extensions and the like), you can spend much more time on doing mashups of the various pieces of technology at hand.

In this case it was libpixman. Previously I’ve tackled everything from hot plugging usb devices, to consuming keystrokes, and putting a window on the screen.

What’s next? Well, someone inquired as to whether I would be doing a Wayland binding. My response was essentially, “since I now have all the basics, there’s no need for wayland, I can just call what it was going to call directly.

And so it goes. One more library in the toolbox, more interesting demos generated.
 

Advertisements

One Comment on “LJIT2pixman – Drawing on Linux”

  1. […] your own graphics rendering engine from scratch, then you could enlist the power of libpixman (LJIT2pixman – Drawing on Linux), another low level portion of the graphics pipeline on Linux.  With libpixman, you can take this […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s