Cello adds a runtime layer on top of C. This is used to extend the C language in ways otherwise only possible by changing the compiler. Users declare runtime type variables linked to normal compile time types which contain information used to perform the new actions required.
Pointers in Cello are tagged with this extra information. It is stored in the memory just before the actual data which is pointed to. This means they are fully compatible with standard C pointers and can interoperate seamlessly.
The type information is mostly just a list of instances of type classes or interfaces. These prove to be an extremely powerful concept in Cello and are used for everything from Garbage Collection to Documentation. They allow you to use objects in terms of their behaviours. This is nice because it allows algorithms to be written in a generic way, only depending on inputs having specific behaviours or attributes, and not depending on their actual implementation.
Cello is pretty clever and it can automatically infer a whole bunch of these behaviours without the user having to provide them. Cello objects can be printed, compared, hashed, stored, garbage collected, ordered, copied and much more. In other words, Cello has batteries included.
Please start by following the instructions on the Installation page and then the instructions for making your first Cello program on the Cello World page. This will get you ready for programming with Cello.
Let's make some glitch art using Cello. We're going to build some new Cello types to do this and see how they interact with standard C. Our glitch art is going to be inspired by the artwork of Reddit user AMillionMonkeys. Who used Langton's Ant to generate the following images.
This is pretty cool - but we're going to use some different artwork as a base.
How about this beautiful
image of a Cello player by flikr user
Luke G. For the purposes of this
tutorial I've saved it as a .tga
file and downsampled it.
Cello objects start as standard C structures which we then add our runtime
information to. We must define our C structures without typedef
'ing them. For
example here is a struct
we might use to represent some image data.
struct Image {
uint64_t width;
uint64_t height;
unsigned char *data;
};
The C type is called struct Image
and all it does is record a width, height,
and pointer to some data. We can register this type with Cello using the
Cello
macro. We must call the Cello variable the same name as the C type, but
without the struct
prefix
struct Image {
uint64_t width;
uint64_t height;
unsigned char *data;
};
var Image = Cello(Image);
Now we have two things. We have the original C type called struct Image
and a C variable representing our Cello runtime type called Image
.
You'll notice the C type of the Cello runtime type object is var
. In Cello
var
just means void*
which is a generic pointer, but by convention we
use it to mean a pointer compatible with the Cello runtime.
For basic types this is all that is required to interact with Cello. We can already allocate objects of this type either on the heap or on the stack, show them, compare them, put them in collections, and much more.
/* Allocate on Stack or Heap */
struct Image* x = $(Image, 0, 0, NULL);
struct Image* y = new(Image);
/* Print */
print("This is an image: %$\n", x);
/* Compare */
print("Images %$ and %$ are equal? %s\n",
x, y, eq(x, y) ? $S("Yes") : $S("No"));
/* Put in an Array */
struct Array* a = new(Array, Image, x, y);
print("Array of Images: %$\n", a);
In fact almost all of the main type classes in Cello provide default implementations - but the real power of Cello comes when we start to customize these type class implementations.
Our C structure struct Image
has a pointer to some memory which might be
allocated by some other function. If we want to avoid memory leaks we need to
make sure we deallocate this memory when we are done with it. Here we can make
use of Cello to define a custom destructor for the Image
type.
void Image_Del(var self) {
struct Image* i = self;
free(i->data);
}
We can cast the input variable self
to the C type struct Image*
. This is
possible because Cello var
are fully compatible with
standard C pointers. So if you have a Cello var
pointer you know it is of a
certain C type (such as in the destructor here), it is completely safe to cast
it to that type and use the members directly. All we do then is call free
on
the data pointer.
To register this destructor with Cello we pass it to the Cello
macro as an
Instance
of the New
type class. Because we defined no custom constructor we
just put NULL
as that entry into the instance.
var Image = Cello(Image, Instance(New, NULL, Image_Del));
Now when the Cello Garbage Collector comes to deallocate our Image
objects it
will call this destructor and free any memory that has been allocated by it.
We want to write a .tga
parser to read in data for the Image
type. Rather
than read from the C FILE*
type we're going to read from a Cello var
type
that implements the Stream
class. This will allow us to read not just from
FILE*
but anything that is file-like, which could include sockets, streams,
processes, and a lot more. The Cello functions sread
and swrite
are used to
read and write data from file-like objects.
Here are some functions I've prepared for reading and writing 3-channel TGA files.
void Image_Load_TGA(struct Image* self, var stream) {
uint16_t width, height;
sseek(stream, 12, SEEK_SET);
sread(stream, &width, sizeof(uint16_t));
sseek(stream, 14, SEEK_SET);
sread(stream, &height, sizeof(uint16_t));
self->width = width;
self->height = height;
self->data = malloc(height * width * 3);
sseek(stream, 18, SEEK_SET);
sread(stream, self->data, height * width * 3);
}
void Image_Save_TGA(struct Image* self, var stream) {
unsigned char xa= self->width % 256;
unsigned char xb= (self->width-xa)/256;
unsigned char ya= self->height % 256;
unsigned char yb= (self->height-ya)/256;
unsigned char header[18] = {
0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, xa, xb, ya, yb, 24, 0};
swrite(stream, header, sizeof(header));
swrite(stream, self->data, self->width * self->height * 3);
}
Now we can write a little C program that reads in a TGA file and then just
saves it back out. We make use of the File
object from the Cello standard
library.
int main(int argc, char** argv) {
var f0 = new(File, $S("cello_original.tga"), $S("rb"));
var f1 = new(File, $S("cello_processed.tga"), $S("wb"));
var img = new(Image);
Image_Load_TGA(img, f0);
Image_Save_TGA(img, f1);
sclose(f0);
sclose(f1);
}
We can also use the with
macro to get files that close automatically at
scope exit just like in Python. The with
macro can even be stacked nicely.
int main(int argc, char** argv) {
with (f0 in new(File, $S("cello_original.tga"), $S("rb")))
with (f1 in new(File, $S("cello_processed.tga"), $S("wb"))) {
var img = new(Image);
Image_Load_TGA(img, f0);
Image_Save_TGA(img, f1);
}
}
Also notice that because of the Cello Garbage Collector we don't need to delete
eother of the two file objects or img
explicitly, and because the destructor
for img
will be called we don't have to clean up the memory it references
either.
Cello has something called Duck typing. This is the idea that an object is defined by it's behaviours and properties rather than it's type (if it walks like a duck and it quacks like a duck then it is a duck).
The Get
type class is usually used by collections such as Table
or Array
to get and set entries, but we can also use it to get
and set
pixels in our
Image
type. The advantage of doing this is that if we change our
representation of an Image
(for example adding more channels), as long as it
still provides a get
and a set
method our algorithm will still work.
First we need a key type. Let us define a basic 2D index type and register it
with Cello like we did for struct Image
.
struct Point {
uint64_t x, y;
};
var Point = Cello(Point);
And also let us define a color type we can return to the user.
struct Color {
char r, g, b;
};
var Color = Cello(Color);
To let us use get
and set
on our Image
type we have to implement a type
class called Get
. Here are some functions that match the signature of the
Get
class. The cast
function does runtime type checking to assert that the
pointer passed in is of the given type. This allows us to safely cast to
struct Point*
and struct Color*
for those arguments so we can use their
members directly.
var Image_Get(var self, var key) {
struct Image* i = self;
struct Point* p = cast(key, Point);
char r = i->data[0 + p->x*3 + p->y*i->width*3];
char g = i->data[1 + p->x*3 + p->y*i->width*3];
char b = i->data[2 + p->x*3 + p->y*i->width*3];
return new(Color, $(Color, r, g, b));
}
var Image_Set(var self, var key, var val) {
struct Image* i = self;
struct Point* p = cast(key, Point);
struct Color* c = cast(val, Color);
i->data[0 + p->x*3 + p->y*i->width*3] = c->r;
i->data[1 + p->x*3 + p->y*i->width*3] = c->g;
i->data[2 + p->x*3 + p->y*i->width*3] = c->b;
}
The reason we return new(Color, $(Color, r, g, b))
instead of just
$(Color, r, g, b)
in the Image_Get
function is because the $
macro does
allocation on the stack of the called function and gets a pointer to it. When
we return this value the memory it points to will already be deallocated as the
function has returned. For more information see
this
part of the FAQ.
Like before we need to register these functions with the Cello
macro for the
Image
type using the Get
type class. This class also has the functions
mem
and rem
, but we don't implement these so we'll just provide NULL
pointers.
var Image = Cello(Image,
Instance(New, NULL, Image_Del),
Instance(Get, Image_Get, Image_Set, NULL, NULL));
Now we can get onto defining our actual glitching routine. The algorithm will
be very simple. Starting from some pixel p
with some color c
, we first read
in the color at our location, set the color at our location to our current
color. We then decide on a direction to move (up, down, left, right). If the
direction to move is the same as the last direction we moved we keep our
current color, otherwise we swap it for the color we just read in. This means
pixels get dragged across the image.
To decide on our new direction we'll hash the current color as well as the
iteration number divided by eight. This means the algorithm should change
direction roughly every eight steps. By default the hash
function returns the
same number when used on an integer, so instead we will use the hash_data
function which performs a MurmurHash
on the raw input.
void Image_Glitch(struct Image* self) {
struct Point* point = $(Point,
rand() % self->width,
rand() % self->height);
var color = get(self, point);
uint64_t dir = 0;
for (uint64_t i = 0; i < 20000; i++) {
var temp = get(self, point);
set(self, point, color);
uint64_t next = (hash(color) ^ hash_data($I(i / 8), size(Int))) % 4;
switch (next) {
case 0: point->x++; break;
case 1: point->y++; break;
case 2: point->x--; break;
case 3: point->y--; break;
}
if (point->x < 0 or point->x >= self->width) {
point->x = rand() % self->width;
}
if (point->y < 0 or point->y >= self->height) {
point->y = rand() % self->height;
}
if (dir isnt next) { color = temp; dir = next; }
}
}
Now we just plug this into our main function and we're ready to go!
int main(int argc, char** argv) {
with (f0 in new(File, $S("cello_original.tga"), $S("rb")))
with (f1 in new(File, $S("cello_processed.tga"), $S("wb"))) {
var img = new(Image);
Image_Load_TGA(img, f0);
Image_Glitch(img);
Image_Save_TGA(img, f1);
}
}
We can upsample the result using imagemagick from the terminal.
convert cello_processed.tga -filter box -resize 250x280 cello_processed.png
As you can see changing the iteration count can give different interesting results.
We can also try it on some different images. Here is the same process applied to some other images from flikr users Lee Summers, David, Kristopher Chandroo, Tarheelcoxn.
Our process is iterative, so we can also generate a gifs. Let's save out
every 100th frame. All we need to do for this is put the following code inside
our for
loop.
if (i % 100 is 0) {
var filename = new(String);
print_to(filename, 0, "./frames_cello/%04d.tga", $I(i / 100));
with (f in new(File, filename, $S("wb"))) {
Image_Save_TGA(self, f);
}
}
What we do is generate a filename for each frame using the print_to
function.
This function is a lot like sprintf
but works for any type of object that
implements the Format
class, so you can use it to also print to stdout
or
any other file object.
Once all of these are saved out we can make use of imagemagick to generate a gif.
convert ./frames/*.tga -filter box -resize 250x280 cello_processed.gif
And there you have it, some glitch art made with Cello!
In this short introduction to Cello we made use of various features of Cello to easily create some glitch art. We created some new Cello objects, defined some type classes for them, made use of the Garbage Collector to clean up our resources once they were done, and used a couple of constructs from the Cello standard library.
I hope this guide has shown some of the potential for the high level things in Cello, how it easily interoperates with standard C, and that Cello programming can be fun.
If you're interested in learning more please check out the rest of the learning resources.
Here is the final program in all its glory.
#include "Cello.h"
struct Point {
int64_t x, y;
};
var Point = Cello(Point);
struct Color {
float r, g, b;
};
var Color = Cello(Color);
struct Image {
size_t width;
size_t height;
char* data;
};
var Image_Get(var self, var key) {
struct Image* i = self;
struct Point* p = cast(key, Point);
char r = i->data[0 + p->x*3 + p->y*i->width*3];
char g = i->data[1 + p->x*3 + p->y*i->width*3];
char b = i->data[2 + p->x*3 + p->y*i->width*3];
return new(Color, $(Color, r, g, b));
}
void Image_Set(var self, var key, var val) {
struct Image* i = self;
struct Point* p = cast(key, Point);
struct Color* c = cast(val, Color);
i->data[0 + p->x*3 + p->y*i->width*3] = c->r;
i->data[1 + p->x*3 + p->y*i->width*3] = c->g;
i->data[2 + p->x*3 + p->y*i->width*3] = c->b;
}
void Image_Del(var self) {
struct Image* i = self;
free(i->data);
}
void Image_Load_TGA(struct Image* self, var stream) {
uint16_t width, height;
sseek(stream, 12, SEEK_SET);
sread(stream, &width, sizeof(uint16_t));
sseek(stream, 14, SEEK_SET);
sread(stream, &height, sizeof(uint16_t));
self->width = width;
self->height = height;
self->data = malloc(height * width * 3);
sseek(stream, 18, SEEK_SET);
sread(stream, self->data, height * width * 3);
}
void Image_Save_TGA(struct Image* self, var stream) {
unsigned char xa= self->width % 256;
unsigned char xb= (self->width-xa)/256;
unsigned char ya= self->height % 256;
unsigned char yb= (self->height-ya)/256;
unsigned char header[18] = {
0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, xa, xb, ya, yb, 24, 0};
swrite(stream, header, sizeof(header));
swrite(stream, self->data, self->width * self->height * 3);
}
var Image = Cello(Image,
Instance(New, NULL, Image_Del),
Instance(Get, Image_Get, Image_Set, NULL, NULL));
void Image_Glitch(struct Image* self) {
struct Point* point = $(Point,
rand() % self->width,
rand() % self->height);
var color = get(self, point);
uint64_t dir = 0;
for (uint64_t i = 0; i < 20000; i++) {
var temp = get(self, point);
set(self, point, color);
uint64_t next = (hash(color) ^ hash_data($I(i / 8), size(Int))) % 4;
switch (next) {
case 0: point->x++; break;
case 1: point->y++; break;
case 2: point->x--; break;
case 3: point->y--; break;
}
if (point->x < 0 or point->x >= self->width) {
point->x = rand() % self->width;
}
if (point->y < 0 or point->y >= self->height) {
point->y = rand() % self->height;
}
if (dir isnt next) { color = temp; dir = next; }
if (i % 100 is 0) {
var filename = new(String);
print_to(filename, 0, "./frames_cello/%04d.tga", $I(i / 100));
with (f in new(File, filename, $S("wb"))) {
Image_Save_TGA(self, f);
}
}
}
}
int main(int argc, char** argv) {
system("mkdir -p frames_cello");
system("rm -f frames_cello/*.tga");
with (f0 in new(File, $S("./images/cello_original.tga"), $S("rb")))
with (f1 in new(File, $S("./images/cello_processed.tga"), $S("wb"))) {
var img = new(Image);
Image_Load_TGA(img, f0);
Image_Glitch(img);
Image_Save_TGA(img, f1);
}
system(
"convert ./frames_cello/*.tga -filter box -resize 250x280 "
"./images/cello_processed.gif");
return 0;
}