Skip to content

Instantly share code, notes, and snippets.

@tuket
Created August 13, 2019 09:31
Show Gist options
  • Save tuket/3b657d8ccadb54ca69a9c0ac4d387789 to your computer and use it in GitHub Desktop.
Save tuket/3b657d8ccadb54ca69a9c0ac4d387789 to your computer and use it in GitHub Desktop.
paint in zig using SDL2
// compile in ubuntu:
// $ zig build-exe paint.zig --library SDL2 --library SDL2main --library c -isystem "/usr/include" --library-path "/usr/lib/x86_64-linux-gnu"
const std = @import("std");
const warn = std.debug.warn;
const fmt = std.fmt;
const c = @cImport({
@cInclude("SDL2/SDL.h");
});
const assert = @import("std").debug.assert;
const SDL_WINDOWPOS_UNDEFINED = @bitCast(c_int, c.SDL_WINDOWPOS_UNDEFINED_MASK);
const SDL_INIT_EVERYTHING =
c.SDL_INIT_TIMER |
c.SDL_INIT_AUDIO |
c.SDL_INIT_VIDEO |
c.SDL_INIT_EVENTS |
c.SDL_INIT_JOYSTICK |
c.SDL_INIT_HAPTIC |
c.SDL_INIT_GAMECONTROLLER;
extern fn SDL_PollEvent(event: *c.SDL_Event) c_int;
var W : usize = 600;
var H : usize = 600;
const Color = struct {
color : [4]u8 = [4]u8{0, 0, 0, 0},
pub fn r(self : Color) u8
{ return self.colorc[0]; }
pub fn g(self : Color) u8
{ return self.color[1]; }
pub fn b(self : Color) u8
{ return self.color[2]; }
pub fn a(self : Color) u8
{ return self.color[3]; }
pub fn setR(self : Color, val : u8) void
{ self.color[0] = val; }
pub fn setG(self : Color, val : u8) void
{ self.color[1] = val; }
pub fn setB(self : Color, val : u8) void
{ self.color[2] = val; }
pub fn setA(self : Color, val : u8) void
{ self.color[3] = val; }
pub fn white() Color {
var color = Color{.color = [4]u8{255, 255, 255, 255}};
return color;
}
pub fn black() Color {
var color = Color{.color = [4]u8{0, 0, 0, 255}};
return color;
}
};
const Vec2 = struct {
x : f64,
y : f64,
};
pub fn dot(a: Vec2, b: Vec2) f64 {
return a.x * b.x + a.y * b.y;
}
pub fn norm(a: Vec2) Vec2 {
const len = @sqrt(f64, dot(a, a));
return Vec2{
.x = a.x / len,
.y = a.y / len
};
}
pub fn scale(a: Vec2, b: f64) Vec2 {
return Vec2 {
.x = a.x * b,
.y = a.y * b
};
}
const PixelBuffers = struct {
const pixelsCapacity = 4000*4000;
buffers : [2][pixelsCapacity]Color,
frontIndex : usize,
w : usize,
h : usize,
pub fn init(self : *PixelBuffers, nW : usize, nH : usize) void {
self.*.frontIndex = 1;
self.*.w = nW;
self.*.h = nH;
var y : usize = 0;
while(y < nH) {
var x : usize = 0;
while(x < nW) {
self.*.buffers[0][x + nW * y] = Color.black();
self.*.buffers[1][x + nW * y] = Color.black();
x += 1;
}
y += 1;
}
}
pub fn write(self : *PixelBuffers, x : c_int, y : c_int, color : Color) void {
if(x >= 0 and y >= 0 and x < @intCast(c_int, W) and y < @intCast(c_int, H)) {
const ux = @intCast(usize, x);
const uy = @intCast(usize, y);
self.*.buffers[self.frontIndex][ux + self.*.w * uy] = color;
}
}
pub fn drawThickLine(self : *PixelBuffers,
xFrom : c_int, yFrom : c_int, xTo : c_int, yTo : c_int,
color : Color, thickness : f64) void
{
const thickness2 = thickness * thickness;
var x0 : c_int = undefined;
var x1 : c_int = undefined;
var y0 : c_int = undefined;
var y1 : c_int = undefined;
if(xFrom < xTo) {
x0 = xFrom;
x1 = xTo;
}
else {
x0 = xTo;
x1 = xFrom;
}
if(yFrom < yTo) {
y0 = yFrom;
y1 = yTo;
}
else {
y0 = yTo;
y1 = yFrom;
}
if(x0 == x1 and y0 == y1) {
return;
}
const intThickness = @floatToInt(c_int, @ceil(f64, thickness));
const X0 = x0 - intThickness;
const Y0 = y0 - intThickness;
const X1 = x1 + intThickness;
const Y1 = y1 + intThickness;
const v01 = Vec2 {
.x = @intToFloat(f64, xTo - xFrom),
.y = @intToFloat(f64, yTo - yFrom)
};
var iy : c_int = Y0;
while(iy <= Y1) {
const y : f64 = @intToFloat(f64, iy);
var ix : c_int = X0;
while(ix <= X1) {
const x : f64 = @intToFloat(f64, ix);
const v = Vec2 {
.x = x - @intToFloat(f64, xFrom),
.y = y - @intToFloat(f64, yFrom)
};
const h1 = dot(v, v);
const c1 = dot(norm(v01), v) * dot(norm(v01), v);
const distToLine2 : f64 = h1 - c1;
assert(distToLine2 > -0.001);
if(distToLine2 < thickness2) {
self.write(ix, iy, color);
}
ix += 1;
}
iy += 1;
}
}
pub fn drawLine(self : *PixelBuffers,
xFrom : c_int, yFrom : c_int, xTo : c_int, yTo : c_int,
color : Color) void
{
if(xFrom == xTo and yFrom == yTo) {
self.write(xFrom, yFrom, color);
}
var x0 : c_int = undefined;
var x1 : c_int = undefined;
var y0 : c_int = undefined;
var y1 : c_int = undefined;
var invX : bool = undefined;
var invY : bool = undefined;
if(xFrom < xTo) {
x0 = xFrom;
x1 = xTo;
invX = false;
}
else {
x0 = xTo;
x1 = xFrom;
invX = true;
}
if(yFrom < yTo) {
y0 = yFrom;
y1 = yTo;
invY = false;
}
else {
y0 = yTo;
y1 = yFrom;
invY = true;
}
if(x1 - x0 < y1 - y0) {
var y : c_int = y0;
while(y <= y1) {
const inc = @divFloor((x1 - x0 + 1) * (y - y0), y1 - y0 + 1);
const x = block: {
if(invX == invY) {
break :block x0 + inc;
}
else {
break :block x1 - inc;
}
};
write(self, x, y, color);
y += 1;
}
}
else {
var x : c_int = x0;
while(x <= x1) {
const inc = @divFloor((y1 - y0 + 1) * (x - x0), x1 - x0 + 1);
const y = block: {
if(invX == invY) {
break :block y0 + inc;
}
else {
break :block y1 - inc;
}
};
write(self, x, y, color);
x += 1;
}
}
}
pub fn resize(self : *PixelBuffers, nW : usize, nH : usize) void {
const front = self.*.frontIndex;
const back = front ^ 1;
var y : usize = 0;
while(y < nH) {
var x : usize = 0;
while(x < nW) {
self.*.buffers[back][x + nW * y] = Color.black();
x += 1;
}
y += 1;
}
y = 0;
while(y < std.math.min(self.*.h, nH)) {
var x : usize = 0;
while(x < std.math.min(self.*.w, nW)) {
self.*.buffers[back][x + nW * y] = self.*.buffers[front][x + self.*.w * y];
x += 1;
}
y += 1;
}
self.*.w = nW;
self.*.h = nH;
self.swapBuffers();
}
fn swapBuffers(self: *PixelBuffers) void {
self.*.frontIndex = self.*.frontIndex ^ 1;
}
pub fn updateTexture(self : *PixelBuffers, texture : *c.SDL_Texture) void {
var pixelsPtr = @ptrCast(*c_void, &self.*.buffers[self.frontIndex][0]);
_= c.SDL_UpdateTexture(texture,
0, pixelsPtr,
@intCast(c_int, self.w * @sizeOf(u32))
);
}
};
pub fn getMousePos(window: *c.SDL_Window, mx : *c_int, my : *c_int) void
{
_= c.SDL_GetGlobalMouseState(mx, my);
var wx : c_int = undefined;
var wy : c_int = undefined;
c.SDL_GetWindowPosition(window, &wx, &wy);
mx.* = mx.* - wx;
my.* = my.*- wy;
}
var pixels : PixelBuffers = undefined;
pub fn main() !void
{
if (c.SDL_Init(SDL_INIT_EVERYTHING) != 0) {
c.SDL_Log(c"Unable to initialize SDL: %s", c.SDL_GetError());
return error.SDLInitializationFailed;
}
defer c.SDL_Quit();
const window = c.SDL_CreateWindow(c"Paint",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
@intCast(c_int, W), @intCast(c_int, H),
c.SDL_WINDOW_OPENGL | c.SDL_WINDOW_RESIZABLE) orelse
{
c.SDL_Log(c"Unable to create window: %s", c.SDL_GetError());
return error.SDLInitializationFailed;
};
defer c.SDL_DestroyWindow(window);
const renderer = c.SDL_CreateRenderer(window, -1, 0) orelse {
c.SDL_Log(c"Unable to create renderer: %s", c.SDL_GetError());
return error.SDLInitializationFailed;
};
defer c.SDL_DestroyRenderer(renderer);
pixels.init(W, H);
var texture = c.SDL_CreateTexture(
renderer,
c.SDL_PIXELFORMAT_ABGR8888, c.SDL_TEXTUREACCESS_STATIC,
@intCast(c_int, W), @intCast(c_int, H));
if(texture != null) {
pixels.updateTexture(@ptrCast(*c.SDL_Texture, texture));
_= c.SDL_RenderClear(renderer);
_= c.SDL_RenderCopy(renderer, texture, 0, 0);
}
var mouseLeftPressed = false;
var mouseRightPressed = false;
var prevMouseX : c_int = undefined;
var prevMouseY : c_int = undefined;
var quit = false;
while (!quit) {
var cx : c_int = undefined;
var cy : c_int = undefined;
_= getMousePos(window, &cx, &cy);
var event: c.SDL_Event = undefined;
while (SDL_PollEvent(&event) != 0) {
switch (event.@"type") {
c.SDL_QUIT => {
quit = true;
},
c.SDL_MOUSEBUTTONUP => {
if(event.button.button == c.SDL_BUTTON_LEFT) {
mouseLeftPressed = false;
}
else if(event.button.button == c.SDL_BUTTON_RIGHT) {
mouseRightPressed = false;
}
},
c.SDL_MOUSEBUTTONDOWN => {
if(event.button.button == c.SDL_BUTTON_LEFT) {
if(!mouseLeftPressed) {
prevMouseX = cx;
prevMouseY = cy;
}
mouseLeftPressed = true;
}
else if(event.button.button == c.SDL_BUTTON_RIGHT) {
if(!mouseRightPressed) {
prevMouseX = cx;
prevMouseY = cy;
}
mouseRightPressed = true;
}
},
c.SDL_WINDOWEVENT => {
if(event.window.event == @intCast(u32, c.SDL_WINDOWEVENT_RESIZED)) {
W = @intCast(usize, event.window.data1);
H = @intCast(usize, event.window.data2);
pixels.resize(W, H);
c.SDL_DestroyTexture(texture);
texture = c.SDL_CreateTexture(
renderer,
c.SDL_PIXELFORMAT_ABGR8888, c.SDL_TEXTUREACCESS_STATIC,
@intCast(c_int, W), @intCast(c_int, H));
}
},
else => {},
}
}
if(mouseLeftPressed or mouseRightPressed) {
if(mouseLeftPressed) {
pixels.drawLine(prevMouseX, prevMouseY, cx, cy, Color.white());
}
else if(mouseRightPressed) {
pixels.drawThickLine(prevMouseX, prevMouseY, cx, cy, Color.black(), 6);
}
prevMouseX = cx;
prevMouseY = cy;
}
if(texture != null) {
pixels.updateTexture(@ptrCast(*c.SDL_Texture, texture));
_= c.SDL_RenderClear(renderer);
_= c.SDL_RenderCopy(renderer, texture, 0, 0);
}
_= c.SDL_RenderPresent(renderer);
c.SDL_Delay(1);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment