Capturing Screenshots of the Raspberry Pi


Last Time Around, I went through how to capture the screen on the Raspberry Pi. Well, capturing, and displaying on the same screen at the same time really isn’t that interesting. It becomes more fun to capture, and then share with your friends, or make movies or what have you.

This time around, I’ve actually captured, and saved to a file.

In order to do this, I had to introduce a couple new concepts.

First is the idea of PixelData. This is simply a data structure to hold onto some specified pixel data.

ffi.cdef[[
struct DMXPixelData {
	void *		Data;
	VC_IMAGE_TYPE_T	PixelFormat;
	int32_t		Width;
	int32_t		Height;
	int32_t		Pitch;
};
]]

local pixelSizes = {
	[tonumber(ffi.C.VC_IMAGE_RGB565)] = 2,
	[tonumber(ffi.C.VC_IMAGE_RGB888)] = 3,
}

local DMXPixelData = ffi.typeof("struct DMXPixelData");
local DMXPixelData_mt = {

	__gc = function(self)
		print("GC: DMXPixelMatrix");
		if self.Data ~= nil then
			ffi.C.free(self.Data);
		end
	end,

	__new = function(ct, width, height, pformat)
		pformat = pformat or ffi.C.VC_IMAGE_RGB565

		local sizeofpixel = pixelSizes[tonumber(pformat)];

		local pitch = ALIGN_UP(width*sizeofpixel, 32);
		local aligned_height = ALIGN_UP(height, 16);
		local dataPtr = ffi.C.calloc(pitch * height, 1);
		return ffi.new(ct, dataPtr, pformat, width, height, pitch);
	end,
}
ffi.metatype(DMXPixelData, DMXPixelData_mt);

The ‘__new’ metamethod is where all the action is at. You can do the following:

pixmap = DMXPixelData(640, 480)

And you’ll get a nice chunk of memory allocated of the appropriate size. You can go further and specify the pixel format (RGB565 or RGB888), but if you don’t, it will default to a conservative RGB565.

This is great. Now we have a place to store the pixels. But what pixels? When we did a screen capture, we captured into a DMXResource object. Well, that object doesn’t have ready made access to the pixel pointer, so what to do? Well, just like DMXResource has a CopyPixelData() function, it can have a ReadPixelData() function as well. In that way, we can read the pixel data out of a resource, and go ahead and do other things with it.

ReadPixelData = function(self, pixdata, p_rect)
  local p_rect = p_rect or VC_RECT_T(0,0,self.Width, self.Height);
  local pixdata = pixdata or self:CreateCompatiblePixmap(p_rect.width, p_rect.height);

  local success, err = DisplayManX.resource_read_data (self.Handle, p_rect, pixdata.Data, pixdata.Pitch)
  if success then
    return pixdata;
  end

  return false, result;
end

Alrighty, now we’re talking. With this routine, I can now read the pixel data out of any resource. There are two ways to use it. If you pass in your own DMXPixelData object (pixdata), then it will be filled in. If you don’t pass in anything, then a new PixelData object will be created by the ‘CreateCompatiblePixmap()’ function.

OK. So, we know how to capture, and now we know how to get our hands on the actual pixel data. Last we need a way to write this data out to a file. There are tons of graphics file formats, but I’ll stick to the most basic for this task:

local function WritePPM(filename, pixbuff)
    local r, c, val;

    local fp = io.open(filename, "wb")
    if not fp then
        return false
    end

    local header = string.format("P6\n%d %d\n255\n", pixbuff.Width, pixbuff.Height)
    fp:write(header);

    for row=0,pixbuff.Height-1 do
	local dataPtr = ffi.cast("char *",pixbuff.Data) + pixbuff.Pitch*row
    	local data = ffi.string(dataPtr, pixbuff.Width*3);
    	fp:write(data);
    end

    fp:close();
end

This is one of the oldest and most basic image file formats. The header is in plain text, giving the width and height of the image, and the maximum value to be found (255). This is followed by the actual pixel data, in R,G,B format, one byte per value. And that’s it. Of course, I converted this basic image into a .png file for display in this blog, but you can see how easy it is to accomplish.

So, altogether:

local ffi = require "ffi"
local DMX = require "DisplayManX"

local Display = DMXDisplay();
local screenWidth, screenHeight = Display:GetSize();
local ratio = screenWidth / screenHeight;
local displayHeight = 320;
local displayWidth = 640;

-- Create the view that will display the snapshot
local displayView = Display:CreateView(
	displayWidth, displayHeight, 
	0, screenHeight-displayHeight-1,
	0, ffi.C.VC_IMAGE_RGB888)

-- Do the snapshot
displayView:Hide();	
Display:Snapshot(displayView.Resource);
displayView:Show();

local pixeldata, err = displayView.Resource:ReadPixelData();
if pixeldata then
	-- Write the data out
	WritePPM("desktop.ppm", pixeldata);
end

And that’s all there is to it really. If you can take one snapshot of the screen, you can take multiples. You could take hundreds, and dump them into a directory, and use some tool that converts a series of images into an h.264 file if you like, and show some movies of your work.

This stuff is getting easier all the time.  After taming the basics of the bcm_host, screen captures are now possible, displaying simple windows is possible.  I’ve been looking into mouse and keyboard support.  I’ll tackle that next, as once you have this support, you can actually write some interesting interactive applications.

 


5 Comments on “Capturing Screenshots of the Raspberry Pi”

  1. MiCk says:

    okay sorry this is the script that work great, but when the display is rotate, it fail 😦

    I use raspbian with last firmware update !

    hope you can help

    thx

  2. brooc says:

    Can you please post a tutorial on how to compile your code and run it on the RPi please.
    Also I tried running lua2c on it and it doesn’t work (I am guessing it is because of various LUA environment problems), would it be possible for you to run lua2c and post the output, I know how to program in C but not LUA…
    I am trying to create a similar application that is written only in C. Can I contact you somehow for a little help?

  3. Sorry for the relative lack of response. I haven’t been playing with the Pi lately, so I haven’t given this much attention.

    lua2c won’t work because this code depends on LuaJIT, which is not strictly Lua code. (it has this thing called the FFI). It’s fairly short though. You could probably just translate it to C directly. Anywhere you see “end” replace it with ‘}’, and put ‘{‘ at the beginning of the block.

    Otherwise, the syntax is fairly simple to convert, and it even uses C structures.

  4. […] way back in the day, I did some early work on binding LuaJIT to Raspberry Pi video interfaces: Capturing Screenshots of the Raspberry Pi and Screencast of the […]


Leave a comment