I would like render into the statusicon of my application.
I know I can make the statusicon display a pixbuf by setting statusIconPixbuf.
And I can create an empty pixbuf via pixbufNew and do things like filling it with a single color.
But how do render into that pixbuf using cairo?
Or is pixbuf not the right thing to use? Is there a better way to render into the statusicon?
Using a Pixbuf
i need a way to render into it.
The following solution takes a cairo Rendering render :: Render a
and the desired X and Y dimension of the (square) Pixbuf
(You can change this if you need to create non-square Pixbufs.)
import qualified Foreign.Ptr as Pointer
import qualified ByteString as B
renderPixbuf :: Int -> Render a -> IO Pixbuf
renderPixbuf size render = withImageSurface FormatARGB32 size size $ \surface -> do
renderWith surface render
stride <- imageSurfaceGetStride surface
surfaceData <- swapRB stride <$> imageSurfaceGetData surface
B.useAsCStringLen surfaceData $ \(pointer, _) -> do
let pointer' = Pointer.castPtr pointer
pixbufNewFromData pointer' ColorspaceRgb True 8 size size stride
It uses the function withImageSurface to create a surface for cairo to render to and then calls renderWith to do the actual rendering specified by render
.
The next two lines extract the image stride, i.e. the number of bytes in a line and the actual image data as ByteString.
swapRB
is a function that transforms the ByteString because somehow the red and blue channel are in the wrong order. See below for how this is done.
In B.useAsCStringLen it gets low-level: It taks the ByteString returned by imageSurfaceGetData and converts it to a Ptr CUChar
to create a new Pixbuf using pixbufNewFromData.
That's it.
swapRB
is defined as follows:
import Data.Word (Word8)
import Data.List (unfoldr)
splitAtIfNotNull :: Int -> B.ByteString -> Maybe (B.ByteString,B.ByteString)
splitAtIfNotNull i string
| B.null string = Nothing
| otherwise = Just $ B.splitAt i string
mapChunks :: (B.ByteString -> B.ByteString) -> Int -> B.ByteString -> B.ByteString
mapChunks mapper chunkSize = B.concat . map mapper . unfoldr (splitAtIfNotNull chunkSize)
swapRB :: Int -> B.ByteString -> B.ByteString
swapRB = mapChunks swapRBforLine
where swapRBforLine :: B.ByteString -> B.ByteString
swapRBforLine = mapChunks (B.pack . swapRBforPixel . B.unpack) 4
swapRBforPixel :: [Word8] -> [Word8]
swapRBforPixel [b,g,r,a] = [r,g,b,a]
swapRBforPixel other = other
It splits the ByteString of pixeldata into lines, then splits the lines into pixels each consisting of 4 bytes: one byte each for the channels red, green, blue, alpha. Innermost is the actual swapping:
swapRBforPixel [b,g,r,a] = [r,g,b,a]