added: bitx11, a program that captures the X11 root window and write the output data to the bitlair led panel using Floy-Steinberg dithering
This commit is contained in:
parent
2b8a68b83f
commit
67bcbab47f
4 changed files with 448 additions and 0 deletions
319
src/bitx11/bitx11.cpp
Normal file
319
src/bitx11/bitx11.cpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "bitx11.h"
|
||||
#include "util/misc.h"
|
||||
#include "util/log.h"
|
||||
#include "util/timeutils.h"
|
||||
#include "util/inclstdint.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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<char*>(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()
|
||||
{
|
||||
}
|
||||
|
77
src/bitx11/bitx11.h
Normal file
77
src/bitx11/bitx11.h
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BITX11_H
|
||||
#define BITX11_H
|
||||
|
||||
#include "util/tcpsocket.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/Xrender.h>
|
||||
#include <X11/extensions/XShm.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
|
||||
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
|
31
src/bitx11/main.cpp
Normal file
31
src/bitx11/main.cpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "bitx11.h"
|
||||
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
CBitX11 bitx11(argc, argv);
|
||||
|
||||
bitx11.Setup();
|
||||
bitx11.Process();
|
||||
bitx11.Cleanup();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
21
wscript
21
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')
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue