Created
August 13, 2019 09:31
-
-
Save tuket/3b657d8ccadb54ca69a9c0ac4d387789 to your computer and use it in GitHub Desktop.
paint in zig using SDL2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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