Skip to content

Instantly share code, notes, and snippets.

@oeloeloel
Last active November 26, 2021 07:11
Show Gist options
  • Save oeloeloel/37bae4a75b215325b39dc1e34a55750c to your computer and use it in GitHub Desktop.
Save oeloeloel/37bae4a75b215325b39dc1e34a55750c to your computer and use it in GitHub Desktop.
DragonRuby Pixel Arrays with C Extensions. Quick and Dirty example.
/*
DragonRuby C Extension Pixel Array
Written by @Akzidenz-Grotesk (with help from @AlexDenisov & @Kenneth | CANICVS)
Demonstrates some quick and pretty dirty image filters
Loads image files into Pixel Array
Performs image manipulation every tick
Returns a modified 100x100 pixel image to DragonRuby
*/
#ifndef NULL
#define NULL 0
#endif
typedef unsigned int Uint32;
extern void *(*drb_symbol_lookup)(const char *sym);
typedef void *(*drb_load_image_fn)(const char *fname, int *w, int *h);
typedef void (*drb_upload_pixel_array_fn)(const char *name, const int w, const int h, const Uint32 *pixels);
Uint32 *image = NULL; // source image pixel array
Uint32 *alpha = NULL; // target image with alpha channel
int im_w; // source image dimensions
int im_h;
int al_w; // target image dimensions
int al_h;
// called from DragonRuby
// loads the images and stores them for later use
__attribute__((annotate("drb_ffi:")))
void ext_load_image(char* im_fname, int *imw, int *imh, char* al_fname, int *alw, int *alh) {
static drb_load_image_fn drb_load_image = NULL;
if (!drb_load_image) {
drb_load_image = drb_symbol_lookup("drb_load_image");
}
if (drb_load_image) {
void *vpi = drb_load_image(im_fname, imw, imh); // load source image
if(vpi){
image = (Uint32*) vpi; // store image
im_w = *imw; // record dimensions
im_h = *imh;
}
void *vpa = drb_load_image(al_fname, alw, alh); // load target image (with alpha channel)
if(vpa){
alpha = (Uint32*) vpa;
al_w = *alw;
al_h = *alh;
}
}
}
// called from DragonRuby
// every tick to get changed image
// takes mouse location and required effect
__attribute__((annotate("drb_ffi:")))
void ext_update_image(int mouse_x, int mouse_y, int mode){
static drb_upload_pixel_array_fn drb_upload_pixel_array = NULL;
if (!drb_upload_pixel_array) {
drb_upload_pixel_array = drb_symbol_lookup("drb_upload_pixel_array");
if (!drb_upload_pixel_array) {
return;
}
}
int row = 0; // row (y) within target image
int col = 0; // col (x) within target image
int im_row; // row (y) within source image
int im_col; // col (x) within source image
Uint32 rgb; // current pixel colour without alpha
Uint32 output[al_w * al_h]; // pixel array to return
// loop through pixels in target image
for (int i = 0; i < (al_w * al_h); i++) {
col = i % al_w; // calculate row and col of current pixel in target
row = i / al_w;
// calculate row/col in source image corresponding to target image pixel
im_col = col + mouse_x;
im_row = row + mouse_y;
// if source image pixel is outside of the source image dimensions...
if (im_col < 0 || im_col > im_w - 1 || im_row < 0 || im_row > im_h - 1){
// outgoing RGB value will be grey background colour
rgb = 0x00CCCCCC;
}else{ // manipulate the pixel
// capture the current source pixel and remove the alpha channel
rgb = image[(im_col) + (im_row * im_w)] - 0xFF000000;
// capture the the red channel
Uint32 r = rgb % 256;
// zero the red channel
rgb -= r;
// capture the green channel
Uint32 g = rgb % 65536;
// zero the green channel
rgb -= g;
// capture the blue channel
Uint32 b = rgb % 16777216;
rgb -= b;
switch(mode){
case 0: // normal, capture source pixel RGB with alpha stripped
rgb = image[(im_col) + (im_row * im_w)] - 0xFF000000;
break;
case 1: // red-channel
rgb = r;
break;
case 2: // green-channel
rgb = g;
break;
case 3: // blue-channel
rgb = b;
break;
case 4: // desaturate (average RGB channels)
g = g / 256;
b = b / 65536;
r = (r + g + b) / 3; // There is a better way to do this
rgb = ((r * 65536) + (r * 256) + r);
break;
case 5: // threshold (snap pixels to black or white)
g = g / 256;
b = b / 65536;
r = (r + g + b) / 3; // There is a better way to do this
r = r < 128 ? 0 : 255;
rgb = ((r * 65536) + (r * 256) + r);
break;
case 6: // posterize (snap each colour channel in pixel to 0 or 255, yielding 8 possible colours)
g = g / 256;
b = b / 65536;
r = r < 128 ? 0 : 255;
g = g < 128 ? 0 : 255;
b = b < 128 ? 0 : 255;
rgb = ((b * 65536) + (g * 256) + r);
break;
}
}
output[i] = alpha[i] + rgb; // combine modified source RGB with target alpha
}
drb_upload_pixel_array("lena", al_w, al_h, output); // upload pixel array to DragonRuby
}
$gtk.reset
$gtk.ffi_misc.gtk_dlopen("ext")
include FFI::CExt
def tick args
args.outputs.background_color = [204, 204, 204]
prepare(args) if args.state.tick_count == 0
handle_input(args)
update(args)
end
def prepare(args)
# put the source image in the background (could fix these hard coded values)
args.outputs.static_sprites << [(args.grid.w - 512) / 2, (args.grid.h - 512) / 2, 512, 512, '/sprites/lena.jpg']
# tell C to load the pixel arrays
load_image_from_ext(args)
# setup names for the effects
args.state.modes = %w"normal red-channel green-channel blue-channel desaturate threshold posterize"
# set the mode to "normal"
args.state.mode_num = 0
# location of background image (could fix hard coded values)
args.state.im_x = (args.grid.w - 512) / 2
args.state.im_y = (args.grid.h - 512) / 2
end
# updates magnifying glass image every tick
def update(args)
m_x = args.inputs.mouse.x
m_y = args.inputs.mouse.y
# location of mouse relative to location of background image
# nasty hardcoded values to be fixed
m_x_off = m_x - args.state.im_x - 50
m_y_off = 516 - m_y + 50
# tell C to update the image (updates the :lena render target)
# casting ints to ints just to be sure
ext_update_image(m_x_off.to_i, m_y_off.to_i, args.state.mode_num.to_i)
# render the magnifying glass
args.outputs.primitives << [m_x - 100, m_y - 100, 200, 200, :lena].sprite
# draw a circle around the magnifying glass or... well it's not pretty
args.outputs.primitives << [m_x - 110, m_y - 110, 220, 220, 'sprites/round.png'].sprite
# display the name of the current effect
args.outputs.labels << [385, 650, "mode: #{args.state.modes[args.state.mode_num]}"]
end
# called at the start to tell C to load images
def load_image_from_ext(args)
# pointers which C will fill with image dimensions
# (these can be used to eliminate the ugly hard coded values above)
im_w = IntPointer.new
im_h = IntPointer.new
al_w = IntPointer.new
al_h = IntPointer.new
# make it so
ext_load_image("sprites/lena.jpg", im_w, im_h, "sprites/alpha.png", al_w, al_h)
# remember the dimensions
args.state.w = al_w.value
args.state.h = al_h.value
end
# click to change the current filter
def handle_input(args)
args.state.mode_num += 1 if args.inputs.mouse.click
args.state.mode_num = 0 if args.state.mode_num == args.state.modes.length
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment