Goal:
struct
from C to Golang using cgo.Disclaimer: This is my first functional C code, things may be wrong.
#include "GetPixel.h"
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
Display *display;
XImage *im;
void open_display()
{
// xlib: must be called before any other X* methods, enables multithreading for this client
XInitThreads();
// save display in a global variable so we don't allocate it per `get_pixel`
// TODO: figure out classes later (or are these C++ only? Can I use them in Golang?)
display = XOpenDisplay((char *) NULL);
}
void close_display()
{
// xlib: use XCloseDisplay instead of XFree or free for Display objects
XCloseDisplay(display);
}
void create_image(int x, int y, int w, int h)
{
// save image in a global variable so we don't allocate it per `get_pixel`
im = XGetImage(display, XRootWindow(display, XDefaultScreen(display)), x, y, w, h, AllPlanes, XYPixmap);
}
void destroy_image()
{
// xlib: use XDestroyImage instead of XFree or free for XImage objects
XDestroyImage(im);
}
void get_pixel(struct Colour3 *colour, int x, int y)
{
// TODO: could I return `c` without converting it to my struct?
XColor c;
c.pixel = XGetPixel(im, x, y);
XQueryColor(display, XDefaultColormap(display, XDefaultScreen(display)), &c);
// xlib: stored as values 0-65536
colour->r = c.red / 256;
colour->g = c.green / 256;
colour->b = c.blue / 256;
}
# Trial and Error:
# - Golang needs me to define crap in an H file
# - C needs me to define my struct in an H file
# - C needs me to use `typedef` and name my struct twice (???)
#ifndef __GETPIXEL_
#define __GETPIXEL_
typedef struct Colour3 {
int r, g, b ;
} Colour3 ; # redundant?
void open_display();
void close_display();
void create_image(int x, int y, int w, int h);
void destroy_image();
void get_pixel(struct Colour3 *colour, int x, int y);
#endif
package x11util
func Screenshot(sx, sy, w, h int, filename string) {
img := image.NewRGBA(image.Rectangle{
image.Point{sx, sy}, image.Point{w, h},
})
defer trace(sx, sy, w, h, filename)(img)
C.open_display()
C.create_image(C.int(sx), C.int(sy), C.int(w), C.int(h))
defer func() {
# does this work?
C.destroy_image()
C.close_display()
}()
# Trial and Error
# - C needs me to pass a pointer to a struct
p := C.Colour3{}
for x := sx; x < w; x++ {
for y := sy; y < h; y++ {
C.get_pixel(&p, C.int(x), C.int(y))
img.Set(x, y, color.RGBA{uint8(p.r), uint8(p.g), uint8(p.b), 255})
}
}
f, err := os.Create(filename)
if err != nil {
log.Error("unable to save screenshot", "filename", filename, "error", err)
}
defer f.Close()
png.Encode(f, img)
}
Sure, it works -- but takes between 30 and 55 seconds for a 1080p screen.
All the existing code, but instead of get_pixel
, let's try to get patches of (3x3) 9 pixels at a time, as an optimization.
# ... existing code ...
struct Colour3* get_pixel_3x3(int sx, int sy)
{
XColor c;
# the internet collectively defines this as "a way to define C arrays where the data remains after the function returns"
struct Colour3* pixels = (struct Colour3 *)malloc(9 * sizeof(Colour3));
for(int x=sx; x<sx+3; ++x)
{
for(int y=sy; y<sy+3; ++y)
{
# ... existing code from Attempt 1 ...
// Is this even the correct way to into C arrays?
pixels++;
}
}
return pixels;
}
# ... existing code ...
struct Colour3* get_pixel_3x3(int sx, int sy);
package x11util
func ScreenshotB(sx, sy, w, h int, filename string) {
# ... existing code ...
var i int
for x := sx; x < w; x += 3 {
for y := sy; y < h; y += 3 {
// returns an array of exactly 9 pixels
p := C.get_pixel_3x3(C.int(x), C.int(y))
// unsafely convert to an array we can use -- at least, this was supposed to work, but never did
// pb := (*[9]C.Colour3)(unsafe.Pointer(&p))
// convert to a slice we can use... with reflect -- this code is magic, have no idea how it works.
var pa []C.Colour3
sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&pa)))
sliceHeader.Cap = 9
sliceHeader.Len = 9
sliceHeader.Data = uintptr(unsafe.Pointer(&p))
// assign pixels from base (adding an offset 0-2) to print 3x3 blocks
for xo := 0; xo < 3; xo++ {
for yo := 0; yo < 3; yo++ {
img.Set(x+xo, y+yo, color.RGBA{uint8(pa[i].r), uint8(pa[i].g), uint8(pa[i].b), 255})
i++
}
}
i = 0
}
}
# ... existing code ...
}
This takes 20% less time to execute, the optimization is definitely there-- HOWEVER: the resulting image is a mess of either pink or yellow pixels, instead of the expected result. Which leads me to believe that I am reading random memory instead of my intention.
Since I know that reading a single pixel works and that the C loop works, I can only think that I totally misunderstand arrays in C, or how to pass them to Golang, or how to read/iterate them in Golang.
At this point, I have no idea what else to try, four pages of Stack Overflow and 20-ish pages of Googling has given me lots of different answers for the other direction (Go->C) -- but not a whole lot here. I could not find an example of C.GoBytes
that worked either.
Letting Golang handle allocation has simplified accessing the array. This code now works for 3x3, but fails when attempting to get "the whole screen at once" (see Attempt 4)
# ... existing code from Attempt 1 ...
# out-param instead of a return variable, let Golang allocate
void get_pixel_3x3(struct Colour3 *pixels, int sx, int sy)
{
XColor c;
for(int x=sx; x<sx+3; ++x)
{
for(int y=sy; y<sy+3; ++y)
{
# ... existing code from Attempt 2 ...
}
}
}
# ... existing code from Attempt 1 ...
void get_pixel_3x3(struct Colour3* pixels, int sx, int sy);
package x11util
func ScreenshotB(sx, sy, w, h int, filename string) {
# ... existing code from Attempt 1 ...
var i int
var p C.Colour3 // why does this even work?
for x := sx; x < w; x += 3 {
for y := sy; y < h; y += 3 {
// returns an array of 9 pixels
C.get_pixel_3x3(&p, C.int(x), C.int(y))
// unsafely convert to an array we can use
pb := (*[9]C.Colour3)(unsafe.Pointer(&p))
pa := pb[:] // seems to be required?
// assign pixels from base (adding an offset 0-2) to print 3x3 blocks
for xo := 0; xo < 3; xo++ {
for yo := 0; yo < 3; yo++ {
# ... existing code from Attempt 1 ...
}
}
i = 0
}
}
# ... existing code from Attempt 1 ...
}
Results in Segmentation Violation (SEGFAULT)
# ... existing code from Attempt 1 ...
void get_pixel_arbitrary(struct Colour3 *pixels, int sx, int sy, int w, int h)
{
XColor c;
for(int x=sx; x<sx+w; ++x)
{
for(int y=sy; y<sy+h; ++y)
{
# ... existing code from Attempt 3 ...
}
}
}
# ... existing code from Attempt 1 ...
void get_pixel_arbitrary(struct Colour3 *pixels, int sx, int sy, int w, int h);
package x11util
func ScreenshotC(sx, sy, w, h int, filename string) {
# ... existing code from Attempt 1 ...
var p C.Colour3 // I'm sure this is the culprit
// returns an array of "all the screens"
// 240x135x3x8 = 777600 (<768KB)
C.get_pixel_arbitrary(&p, 0, 0, C.int(w), C.int(h)) // segfault here
// unsafely convert to an array we can use
pb := (*[1 << 30]C.Colour3)(unsafe.Pointer(&p)) // internet showed this magic 1<<30 is required because Golang won't make an array with an unknown length
pa := pb[:w*h] // not sure if this is correct, but it doesn't matter yet, we don't get this far (segfault happens above.)
// assign pixels from base (adding an offset 0-2) to print 3x3 blocks
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
# ... existing code from Attempt 1 ...
}
}
# ... existing code from Attempt 1 ...
}
struct Colour3* get_pixel_arbitrary(int sx, int sy, int w, int h)
{
XColor c;
struct Colour3* pixels = (struct Colour3 *)malloc(w*h * sizeof(Colour3));
struct Colour3* start = pixels;
for(int x=sx; x<sx+w; ++x)
{
for(int y=sy; y<sy+h; ++y)
{
c.pixel = XGetPixel(im, x, y);
XQueryColor(display, XDefaultColormap(display, XDefaultScreen(display)), &c);
pixels->r = c.red / 256;
pixels->g = c.green / 256;
pixels->b = c.blue / 256;
pixels++;
}
}
return start;
}
p := C.get_pixel_arbitrary(0, 0, C.int(w), C.int(h))
// unsafely convert to an array we can use
pb := (*[1 << 30]C.Colour3)(unsafe.Pointer(&p))
pa := pb[: w*h : w*h] // magic :len:len notation shown in docs but not explained? (if [start:end] then [?:?:?])
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
img.Set(x, y, color.RGBA{uint8(pa[i].r), uint8(pa[i].g), uint8(pa[i].b), 255})
i++
}
}
// assume I should be freeing in C instead of here?
C.free(unsafe.Pointer(p))
Which produces a "stretched mess" which I then overflow on (now I have to assume I've done something wrong on the C side again, unless this is completely the wrong idea when it was mentioned I should allocate back in C?)
I am very willing to accept a guide with examples as the answer at this point, as there are things I'm missing surely -- however, when Googling (and even on GitHub) looking for examples of this, I couldn't find any. I would love to take the results of this thread and do just that :).
Most of the failures here centered on using unsafe.Pointer(&p)
for the returned C array. Since a C array is a pointer, p
is already of type *C.Colour3
. Using &p
is attempting to use the address of the p
variable itself, which could be anywhere in memory.
The correct form of the conversion will look like:
pa := (*[1 << 30]C.Colour3)(unsafe.Pointer(p))[:w*h:w*h]
See also What does (*[1 << 30]C.YourType) do exactly in CGo?