From 67bcbab47f3dea47bad65b6d7f2c16498e3c22d8 Mon Sep 17 00:00:00 2001 From: Bob van Loosen Date: Sun, 17 Feb 2013 17:15:44 +0100 Subject: [PATCH] added: bitx11, a program that captures the X11 root window and write the output data to the bitlair led panel using Floy-Steinberg dithering --- src/bitx11/bitx11.cpp | 319 ++++++++++++++++++++++++++++++++++++++++++ src/bitx11/bitx11.h | 77 ++++++++++ src/bitx11/main.cpp | 31 ++++ wscript | 21 +++ 4 files changed, 448 insertions(+) create mode 100644 src/bitx11/bitx11.cpp create mode 100644 src/bitx11/bitx11.h create mode 100644 src/bitx11/main.cpp diff --git a/src/bitx11/bitx11.cpp b/src/bitx11/bitx11.cpp new file mode 100644 index 0000000..801f714 --- /dev/null +++ b/src/bitx11/bitx11.cpp @@ -0,0 +1,319 @@ +/* + * bitvis + * Copyright (C) Bob 2012 + * + * bitvis is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bitvis is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "bitx11.h" +#include "util/misc.h" +#include "util/log.h" +#include "util/timeutils.h" +#include "util/inclstdint.h" + +#include +#include + +using namespace std; + +CBitX11::CBitX11(int argc, char *argv[]) +{ + m_port = 1337; + m_address = NULL; + m_fps = 30.0f; + m_destwidth = 120; + m_destheight = 48; + m_debug = false; + m_debugscale = 2; + + const char* flags = "p:a:f:d:"; + int c; + while ((c = getopt(argc, argv, flags)) != -1) + { + if (c == 'f') //fps + { + float fps; + if (!StrToFloat(string(optarg), fps) || fps <= 0) + { + LogError("Wrong argument \"%s\" for fps", optarg); + exit(1); + } + + m_fps = fps; + } + else if (c == 'p') //port + { + int port; + if (!StrToInt(string(optarg), port) || port < 0 || port > 65535) + { + LogError("Wrong argument \"%s\" for port", optarg); + exit(1); + } + + m_port = port; + } + else if (c == 'a') //address + { + m_address = optarg; + } + else if (c == 'd') //debug + { + m_debug = true; + + int scale; + if (!StrToInt(string(optarg), scale) || scale <= 0) + { + LogError("Wrong argument \"%s\" for debug scale", optarg); + exit(1); + } + + m_debugscale = scale; + } + } + + //if no address is specified, turn on the debug window instead + if (!m_address) + m_debug = true; + + m_dpy = NULL; + m_srcformat = NULL; + m_dstformat = NULL; + m_pixmap = None; + m_srcpicture = None; + m_dstpicture = None; + + memset(&m_pictattr, 0, sizeof(m_pictattr)); + m_pictattr.repeat = RepeatNone; + + memset(&m_transform, 0, sizeof(m_transform)); + memset(&m_debugtransform, 0, sizeof(m_debugtransform)); +} + +CBitX11::~CBitX11() +{ +} + +void CBitX11::Setup() +{ + m_dpy = XOpenDisplay(NULL); + if (m_dpy == NULL) + { + LogError("Unable to open display"); + exit(1); + } + + m_rootwin = RootWindow(m_dpy, DefaultScreen(m_dpy)); + XGetWindowAttributes(m_dpy, m_rootwin, &m_rootattr); + + m_pixmap = XCreatePixmap(m_dpy, m_rootwin, m_destwidth, m_destheight, m_rootattr.depth); + + m_srcformat = XRenderFindVisualFormat(m_dpy, m_rootattr.visual); + m_dstformat = XRenderFindVisualFormat(m_dpy, m_rootattr.visual); + m_srcpicture = XRenderCreatePicture(m_dpy, m_rootwin, m_srcformat, CPRepeat, &m_pictattr); + m_dstpicture = XRenderCreatePicture(m_dpy, m_pixmap, m_dstformat, CPRepeat, &m_pictattr); + + XRenderSetPictureFilter(m_dpy, m_srcpicture, "bilinear", NULL, 0); + + m_xim = XShmCreateImage(m_dpy, m_rootattr.visual, m_rootattr.depth, ZPixmap, NULL, &m_shmseginfo, m_destwidth, m_destheight); + m_shmseginfo.shmid = shmget(IPC_PRIVATE, m_xim->bytes_per_line * m_xim->height, IPC_CREAT | 0777); + m_shmseginfo.shmaddr = reinterpret_cast(shmat(m_shmseginfo.shmid, NULL, 0)); + m_xim->data = m_shmseginfo.shmaddr; + m_shmseginfo.readOnly = False; + XShmAttach(m_dpy, &m_shmseginfo); + + if (m_debug) + { + m_debugwindow = XCreateSimpleWindow(m_dpy, RootWindow(m_dpy, DefaultScreen(m_dpy)), + 0, 0, m_destwidth * m_debugscale, m_destheight * m_debugscale, 0, 0, 0); + XMapWindow(m_dpy, m_debugwindow); + XFlush(m_dpy); + + m_debuggc = XCreateGC(m_dpy, m_debugwindow, 0, NULL); + m_debugpixmap = XCreatePixmap(m_dpy, m_rootwin, m_destwidth, m_destheight, m_rootattr.depth); + m_debugsrcformat = XRenderFindVisualFormat(m_dpy, m_rootattr.visual); + m_debugdstformat = XRenderFindVisualFormat(m_dpy, m_rootattr.visual); + m_debugsrcpicture = XRenderCreatePicture(m_dpy, m_debugpixmap, m_debugsrcformat, CPRepeat, &m_pictattr); + m_debugdstpicture = XRenderCreatePicture(m_dpy, m_debugwindow, m_debugdstformat, CPRepeat, &m_pictattr); + XRenderSetPictureFilter(m_dpy, m_debugsrcpicture, "nearest", NULL, 0); + } +} + +void CBitX11::Process() +{ + int64_t looptime = GetTimeUs(); + for (;;) + { + if (!m_socket.IsOpen() && m_address) + { + if (m_socket.Open(m_address, m_port, 1000000) != SUCCESS) + { + LogError("Failed to connect: %s", m_socket.GetError().c_str()); + m_socket.Close(); + } + else + { + Log("Connected"); + } + } + + m_transform.matrix[0][0] = m_rootattr.width; + m_transform.matrix[1][1] = m_rootattr.width; + m_transform.matrix[2][2] = m_destwidth; + + //the aspect ratio of the root window is probably different from the target aspect ratio + //so clip parts from the top and bottom of the root window + int offset = (m_rootattr.height * m_destwidth / m_rootattr.width - m_destheight) / 2; + + //render the root window to m_pixmap using xrender + XRenderSetPictureTransform (m_dpy, m_srcpicture, &m_transform); + XRenderComposite(m_dpy, PictOpSrc, m_srcpicture, None, m_dstpicture, 0, offset, 0, 0, 0, 0, m_destwidth, m_destheight); + XShmGetImage(m_dpy, m_pixmap, m_xim, 0, 0, AllPlanes); + + //allocate 2 extra pixels on each line, and allocate one extra line + //since the Floy-Steinberg dithering writes to one line below the current pixel + //and writes to one pixel to the left and right of the current pixel + int planewidth = m_destwidth + 2; + int planeheight = m_destheight + 1; + int planes[2][planewidth * planeheight]; + int avg = 0; + //copy the red and green pixels to the planes, and calculate the average pixel value + for (int y = 0; y < m_destheight; y++) + { + uint8_t* ximline = (uint8_t*)m_xim->data + y * m_xim->bytes_per_line; + uint8_t* ximend = ximline + m_xim->bytes_per_line; + int* planeliner = planes[0] + y * planewidth + 1; + int* planelineg = planes[1] + y * planewidth + 1; + + while (ximline != ximend) + { + ximline++; + avg += *(planelineg++) = *(ximline++); + avg += *(planeliner++) = *(ximline++); + ximline++; + } + } + avg /= m_destwidth * m_destheight * 2; + + //quantize the planes, apply Floy-Steinberg dithering, and write into the buffer for the socket + CTcpData data; + data.SetData(":00"); + for (int y = 0; y < m_destheight; y++) + { + uint8_t ledline[m_destwidth / 4]; + memset(ledline, 0, sizeof(ledline)); + + for (int i = 0; i < 2; i++) + { + int* line = planes[i] + y * planewidth + 1; + int* lineend = line + m_destwidth; + int quantval; + int quanterror; + + uint8_t* ledpos = ledline; + int ledcounter = 0; + + while (line != lineend) + { + //if a pixel is higher than the average, set it to the max value + //this will turn on the led + if (*line > avg) + { + quantval = 255; + *ledpos |= 1 << ((ledcounter * 2) + (1 - i)); + } + else + { + quantval = 0; + } + + ledcounter++; + if (ledcounter == 4) + { + ledcounter = 0; + ledpos++; + } + + //apply Floyd-Steinberg dither + quanterror = *line - quantval; + *line = quantval; + + line[1] += quanterror * 7 / 16; + line[m_destwidth - 1] += quanterror * 3 / 16; + line[m_destwidth] += quanterror * 5 / 16; + line[m_destwidth + 1] += quanterror / 16; + + line++; + } + } + + //append the line to the buffer + data.SetData(ledline, sizeof(ledline), true); + } + + //add 10 zeros to the buffer in case the receiver is out of sync + uint8_t end[10] = {}; + data.SetData(end, sizeof(end), true); + + if (m_socket.IsOpen()) + { + if (m_socket.Write(data) != SUCCESS) + { + LogError("%s", m_socket.GetError().c_str()); + m_socket.Close(); + } + } + + if (m_debug) + { + //write the quantized and dithered planes back into the Ximage + for (int y = 0; y < m_destheight; y++) + { + uint8_t* ximline = (uint8_t*)m_xim->data + y * m_xim->bytes_per_line; + uint8_t* ximend = ximline + m_xim->bytes_per_line; + int* planeliner = planes[0] + y * planewidth + 1; + int* planelineg = planes[1] + y * planewidth + 1; + + while (ximline != ximend) + { + *(ximline++) = 0; + *(ximline++) = Clamp(*(planelineg++), 0, 255); + *(ximline++) = Clamp(*(planeliner++), 0, 255); + ximline++; + } + } + + //write the Ximage back into the pixmap + XShmPutImage(m_dpy, m_debugpixmap, m_debuggc, m_xim, 0, 0, 0, 0, m_destwidth, m_destheight, False); + + m_debugtransform.matrix[0][0] = m_destwidth; + m_debugtransform.matrix[1][1] = m_destwidth; + m_debugtransform.matrix[2][2] = m_destwidth * m_debugscale; + + //render the pixmap on the debug window, scaled by m_debugscale + XRenderSetPictureTransform (m_dpy, m_debugsrcpicture, &m_debugtransform); + XRenderComposite(m_dpy, PictOpSrc, m_debugsrcpicture, None, m_debugdstpicture, + 0, 0, 0, 0, 0, 0, m_destwidth * m_debugscale, m_destheight * m_debugscale); + + XFlush(m_dpy); + } + + looptime += Round64(1000000.0f / m_fps); + USleep(looptime - GetTimeUs()); + } +} + +void CBitX11::Cleanup() +{ +} + diff --git a/src/bitx11/bitx11.h b/src/bitx11/bitx11.h new file mode 100644 index 0000000..8bf9d50 --- /dev/null +++ b/src/bitx11/bitx11.h @@ -0,0 +1,77 @@ +/* + * bitvis + * Copyright (C) Bob 2012 + * + * bitvis is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bitvis is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef BITX11_H +#define BITX11_H + +#include "util/tcpsocket.h" + +#include +#include +#include +#include +#include + +class CBitX11 +{ + public: + CBitX11(int argc, char *argv[]); + ~CBitX11(); + + void Setup(); + void Process(); + void Cleanup(); + + private: + + int m_port; + const char* m_address; + float m_fps; + + CTcpClientSocket m_socket; + + Display* m_dpy; + Window m_rootwin; + XWindowAttributes m_rootattr; + + int m_destwidth; + int m_destheight; + + Pixmap m_pixmap; + XRenderPictFormat* m_srcformat; + XRenderPictFormat* m_dstformat; + Picture m_srcpicture; + Picture m_dstpicture; + XRenderPictureAttributes m_pictattr; + XTransform m_transform; + XShmSegmentInfo m_shmseginfo; + XImage* m_xim; + + bool m_debug; + int m_debugscale; + Window m_debugwindow; + GC m_debuggc; + Pixmap m_debugpixmap; + XRenderPictFormat* m_debugsrcformat; + XRenderPictFormat* m_debugdstformat; + Picture m_debugsrcpicture; + Picture m_debugdstpicture; + XTransform m_debugtransform; +}; + +#endif //BITX11_H diff --git a/src/bitx11/main.cpp b/src/bitx11/main.cpp new file mode 100644 index 0000000..1f802bd --- /dev/null +++ b/src/bitx11/main.cpp @@ -0,0 +1,31 @@ +/* + * bitx11 + * Copyright (C) Bob 2012 + * + * bitx11 is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bitx11 is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "bitx11.h" + +int main (int argc, char *argv[]) +{ + CBitX11 bitx11(argc, argv); + + bitx11.Setup(); + bitx11.Process(); + bitx11.Cleanup(); + + return 0; +} + diff --git a/wscript b/wscript index caaacea..09eeb64 100644 --- a/wscript +++ b/wscript @@ -18,11 +18,20 @@ def configure(conf): conf.check(header_name='jack/jack.h') conf.check(header_name='fftw3.h') conf.check(header_name='samplerate.h') + conf.check(header_name='sys/ipc.h') + conf.check(header_name='sys/shm.h') + + conf.check(header_name='X11/Xlib.h', auto_add_header_name=True) + conf.check(header_name='X11/extensions/Xrender.h') + conf.check(header_name='X11/extensions/XShm.h') conf.check(lib='fftw3', uselib_store='fftw3') conf.check(lib='fftw3f', uselib_store='fftw3f') conf.check(lib='jack', uselib_store='jack') conf.check(lib='samplerate', uselib_store='samplerate') + conf.check(lib='X11', uselib_store='X11') + conf.check(lib='Xext', uselib_store='Xext') + conf.check(lib='Xrender', uselib_store='Xrender') conf.check(lib='m', uselib_store='m', mandatory=False) conf.check(lib='pthread', uselib_store='pthread', mandatory=False) @@ -50,3 +59,15 @@ def build(bld): cxxflags='-Wall -g -DUTILNAMESPACE=BitVisUtil', target='bitvis') + bld.program(source='src/bitx11/main.cpp\ + src/bitx11/bitx11.cpp\ + src/util/log.cpp\ + src/util/misc.cpp\ + src/util/mutex.cpp\ + src/util/timeutils.cpp\ + src/util/tcpsocket.cpp', + use=['m', 'rt', 'X11', 'Xrender', 'Xext'], + includes='./src', + cxxflags='-Wall -g -DUTILNAMESPACE=BitX11Util', + target='bitx11') +