small-bitmap-editor/editor.c

1384 lines
35 KiB
C

/*
MIT License
Copyright (c) 2023-2024 erysdren (it/she/they)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
*
* EDITOR.C
*
*/
#include <stdio.h>
#include <stdint.h>
#include <limits.h>
#include "tinyfiledialogs.h"
#include "eui_sdl2.h"
#define WIDTH (640)
#define HEIGHT (480)
#define TITLE "small bitmap editor | by erysdren"
#define PIXEL_WIDTH (7)
#define PIXEL_HEIGHT (7)
#define BITMAP_WIDTH (64)
#define BITMAP_HEIGHT (64)
#define ENTRY_WIDTH (11)
#define ENTRY_HEIGHT (11)
#define MAX_UNDOS (16)
#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef CLAMP
#define CLAMP(a, min, max) MIN(MAX(a, min), max)
#endif
#ifndef SGN
#define SGN(x) ((x < 0) ? -1 : ((x > 0) ? 1 : 0))
#endif
#ifndef SQR
#define SQR(x) ((x) * (x))
#endif
enum {
REDO_NO,
REDO_YES
};
#define PIXEL(s, x, y) (((uint8_t *)((s)->pixels))[(y) * (s)->pitch + (x) * (s)->format->BytesPerPixel])
/* palette */
unsigned char palette[768] = {
0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x10, 0x10, 0x10, 0x18, 0x18, 0x18,
0x20, 0x20, 0x20, 0x28, 0x28, 0x28, 0x30, 0x30, 0x30, 0x38, 0x38, 0x38,
0x40, 0x40, 0x40, 0x48, 0x48, 0x48, 0x50, 0x50, 0x50, 0x58, 0x58, 0x58,
0x60, 0x60, 0x60, 0x68, 0x68, 0x68, 0x70, 0x70, 0x70, 0x78, 0x78, 0x78,
0x80, 0x80, 0x80, 0x88, 0x88, 0x88, 0x90, 0x90, 0x90, 0x98, 0x98, 0x98,
0xa0, 0xa0, 0xa0, 0xa8, 0xa8, 0xa8, 0xb0, 0xb0, 0xb0, 0xb8, 0xb8, 0xb8,
0xc0, 0xc0, 0xc0, 0xc8, 0xc8, 0xc8, 0xd0, 0xd0, 0xd0, 0xd8, 0xd8, 0xd8,
0xe0, 0xe0, 0xe0, 0xe8, 0xe8, 0xe8, 0xf0, 0xf0, 0xf0, 0xfc, 0xfc, 0xfc,
0x00, 0x00, 0x00, 0x04, 0x04, 0x08, 0x0c, 0x0c, 0x10, 0x10, 0x10, 0x18,
0x18, 0x18, 0x20, 0x1c, 0x1c, 0x28, 0x24, 0x24, 0x30, 0x28, 0x28, 0x38,
0x30, 0x30, 0x40, 0x34, 0x34, 0x48, 0x3c, 0x3c, 0x50, 0x44, 0x44, 0x58,
0x48, 0x48, 0x60, 0x50, 0x50, 0x68, 0x54, 0x54, 0x70, 0x5c, 0x5c, 0x78,
0x60, 0x60, 0x80, 0x68, 0x68, 0x88, 0x6c, 0x6c, 0x90, 0x74, 0x74, 0x98,
0x78, 0x78, 0xa0, 0x80, 0x80, 0xa8, 0x88, 0x88, 0xb0, 0x8c, 0x8c, 0xb8,
0x94, 0x94, 0xc0, 0x98, 0x98, 0xc8, 0xa0, 0xa0, 0xd0, 0xa4, 0xa4, 0xd8,
0xac, 0xac, 0xe0, 0xb0, 0xb0, 0xe8, 0xb8, 0xb8, 0xf0, 0xc0, 0xc0, 0xfc,
0x00, 0x00, 0x00, 0x08, 0x04, 0x04, 0x10, 0x0c, 0x08, 0x18, 0x10, 0x0c,
0x20, 0x18, 0x10, 0x28, 0x1c, 0x14, 0x30, 0x24, 0x18, 0x38, 0x28, 0x1c,
0x40, 0x30, 0x20, 0x48, 0x34, 0x24, 0x50, 0x3c, 0x28, 0x58, 0x44, 0x2c,
0x60, 0x48, 0x30, 0x68, 0x50, 0x34, 0x70, 0x54, 0x38, 0x78, 0x5c, 0x3c,
0x80, 0x60, 0x40, 0x88, 0x68, 0x44, 0x90, 0x6c, 0x48, 0x98, 0x74, 0x4c,
0xa0, 0x78, 0x50, 0xa8, 0x80, 0x54, 0xb0, 0x88, 0x58, 0xb8, 0x8c, 0x5c,
0xc0, 0x94, 0x60, 0xc8, 0x98, 0x64, 0xd0, 0xa0, 0x68, 0xd8, 0xa4, 0x6c,
0xe0, 0xac, 0x70, 0xe8, 0xb0, 0x74, 0xf0, 0xb8, 0x78, 0xfc, 0xc0, 0x80,
0x00, 0x00, 0x00, 0x08, 0x04, 0x00, 0x10, 0x08, 0x04, 0x18, 0x0c, 0x08,
0x20, 0x10, 0x0c, 0x28, 0x14, 0x0c, 0x30, 0x18, 0x10, 0x38, 0x1c, 0x14,
0x40, 0x20, 0x18, 0x48, 0x24, 0x18, 0x50, 0x28, 0x1c, 0x58, 0x2c, 0x20,
0x60, 0x30, 0x24, 0x68, 0x34, 0x28, 0x70, 0x38, 0x28, 0x78, 0x3c, 0x2c,
0x80, 0x40, 0x30, 0x88, 0x44, 0x34, 0x90, 0x48, 0x34, 0x98, 0x4c, 0x38,
0xa0, 0x50, 0x3c, 0xa8, 0x54, 0x40, 0xb0, 0x58, 0x44, 0xb8, 0x5c, 0x44,
0xc0, 0x60, 0x48, 0xc8, 0x64, 0x4c, 0xd0, 0x68, 0x50, 0xd8, 0x6c, 0x50,
0xe0, 0x70, 0x54, 0xe8, 0x74, 0x58, 0xf0, 0x78, 0x5c, 0xfc, 0x80, 0x60,
0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0x04, 0x04, 0x18, 0x08, 0x08,
0x20, 0x0c, 0x0c, 0x28, 0x0c, 0x0c, 0x30, 0x10, 0x10, 0x38, 0x14, 0x14,
0x40, 0x18, 0x18, 0x48, 0x18, 0x18, 0x50, 0x1c, 0x1c, 0x58, 0x20, 0x20,
0x60, 0x24, 0x24, 0x68, 0x28, 0x28, 0x70, 0x28, 0x28, 0x78, 0x2c, 0x2c,
0x80, 0x30, 0x30, 0x88, 0x34, 0x34, 0x90, 0x34, 0x34, 0x98, 0x38, 0x38,
0xa0, 0x3c, 0x3c, 0xa8, 0x40, 0x40, 0xb0, 0x44, 0x44, 0xb8, 0x44, 0x44,
0xc0, 0x48, 0x48, 0xc8, 0x4c, 0x4c, 0xd0, 0x50, 0x50, 0xd8, 0x50, 0x50,
0xe0, 0x54, 0x54, 0xe8, 0x58, 0x58, 0xf0, 0x5c, 0x5c, 0xfc, 0x60, 0x60,
0x00, 0x00, 0x00, 0x04, 0x04, 0x08, 0x08, 0x08, 0x10, 0x0c, 0x0c, 0x18,
0x10, 0x10, 0x20, 0x14, 0x14, 0x28, 0x18, 0x18, 0x30, 0x1c, 0x1c, 0x38,
0x20, 0x20, 0x40, 0x24, 0x24, 0x48, 0x28, 0x28, 0x50, 0x2c, 0x2c, 0x58,
0x30, 0x30, 0x60, 0x34, 0x34, 0x68, 0x38, 0x38, 0x70, 0x3c, 0x3c, 0x78,
0x40, 0x40, 0x80, 0x44, 0x44, 0x88, 0x48, 0x48, 0x90, 0x4c, 0x4c, 0x98,
0x50, 0x50, 0xa0, 0x54, 0x54, 0xa8, 0x58, 0x58, 0xb0, 0x5c, 0x5c, 0xb8,
0x60, 0x60, 0xc0, 0x64, 0x64, 0xc8, 0x68, 0x68, 0xd0, 0x6c, 0x6c, 0xd8,
0x70, 0x70, 0xe0, 0x74, 0x74, 0xe8, 0x78, 0x78, 0xf0, 0x80, 0x80, 0xfc,
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x10, 0x04, 0x08, 0x18, 0x08,
0x0c, 0x20, 0x0c, 0x0c, 0x28, 0x0c, 0x10, 0x30, 0x10, 0x14, 0x38, 0x14,
0x18, 0x40, 0x18, 0x18, 0x48, 0x18, 0x1c, 0x50, 0x1c, 0x20, 0x58, 0x20,
0x24, 0x60, 0x24, 0x28, 0x68, 0x28, 0x28, 0x70, 0x28, 0x2c, 0x78, 0x2c,
0x30, 0x80, 0x30, 0x34, 0x88, 0x34, 0x34, 0x90, 0x34, 0x38, 0x98, 0x38,
0x3c, 0xa0, 0x3c, 0x40, 0xa8, 0x40, 0x44, 0xb0, 0x44, 0x44, 0xb8, 0x44,
0x48, 0xc0, 0x48, 0x4c, 0xc8, 0x4c, 0x50, 0xd0, 0x50, 0x50, 0xd8, 0x50,
0x54, 0xe0, 0x54, 0x58, 0xe8, 0x58, 0x5c, 0xf0, 0x5c, 0x60, 0xfc, 0x60,
0x00, 0x00, 0x00, 0x08, 0x04, 0x08, 0x10, 0x08, 0x10, 0x18, 0x0c, 0x18,
0x20, 0x10, 0x20, 0x28, 0x14, 0x28, 0x30, 0x18, 0x30, 0x38, 0x1c, 0x38,
0x40, 0x20, 0x40, 0x48, 0x24, 0x48, 0x50, 0x28, 0x50, 0x58, 0x2c, 0x58,
0x60, 0x30, 0x60, 0x68, 0x34, 0x68, 0x70, 0x38, 0x70, 0x78, 0x3c, 0x78,
0x80, 0x40, 0x80, 0x88, 0x44, 0x88, 0x90, 0x48, 0x90, 0x98, 0x4c, 0x98,
0xa0, 0x50, 0xa0, 0xa8, 0x54, 0xa8, 0xb0, 0x58, 0xb0, 0xb8, 0x5c, 0xb8,
0xc0, 0x60, 0xc0, 0xc8, 0x64, 0xc8, 0xd0, 0x68, 0xd0, 0xd8, 0x6c, 0xd8,
0xe0, 0x70, 0xe0, 0xe8, 0x74, 0xe8, 0xf0, 0x78, 0xf0, 0xfc, 0x80, 0xfc
};
/* pen icon */
#define pen_width 16
#define pen_height 16
static unsigned char pen_bits[] = {
0xff, 0xff, 0x01, 0x80, 0x01, 0x80, 0x01, 0x82, 0x01, 0x87, 0x01, 0x8e,
0x81, 0x9c, 0xc1, 0x89, 0xe1, 0x83, 0xf1, 0x81, 0xf1, 0x80, 0x79, 0x80,
0x19, 0x80, 0x01, 0x80, 0x01, 0x80, 0xff, 0xff
};
/* selected pen icon */
#define pen_selected_width 16
#define pen_selected_height 16
static unsigned char pen_selected_bits[] = {
0x00, 0x00, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7d, 0xfe, 0x78, 0xfe, 0x71,
0x7e, 0x63, 0x3e, 0x76, 0x1e, 0x7c, 0x0e, 0x7e, 0x0e, 0x7f, 0x86, 0x7f,
0xe6, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0x00, 0x00
};
/* bucket icon */
#define bucket_width 16
#define bucket_height 16
static unsigned char bucket_bits[] = {
0xff, 0xff, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x89, 0x81, 0xd1, 0x83,
0x61, 0x9e, 0x71, 0xa4, 0x99, 0xa4, 0x0d, 0xae, 0x19, 0xab, 0xb1, 0xb9,
0xe1, 0x90, 0x41, 0x80, 0x01, 0x80, 0xff, 0xff
};
/* selected bucket icon */
#define bucket_selected_width 16
#define bucket_selected_height 16
static unsigned char bucket_selected_bits[] = {
0x00, 0x00, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0x76, 0x7e, 0x2e, 0x7c,
0x9e, 0x61, 0x8e, 0x5b, 0x66, 0x5b, 0xf2, 0x51, 0xe6, 0x54, 0x4e, 0x46,
0x1e, 0x6f, 0xbe, 0x7f, 0xfe, 0x7f, 0x00, 0x00
};
/* rect icon */
#define rect_width 16
#define rect_height 16
static unsigned char rect_bits[] = {
0xff, 0xff, 0x01, 0x80, 0x01, 0x80, 0xf9, 0x9f, 0x09, 0x90, 0x09, 0x90,
0x09, 0x90, 0x09, 0x90, 0x09, 0x90, 0x09, 0x90, 0x09, 0x90, 0x09, 0x90,
0xf9, 0x9f, 0x01, 0x80, 0x01, 0x80, 0xff, 0xff
};
/* selected rect icon */
#define rect_selected_width 16
#define rect_selected_height 16
static unsigned char rect_selected_bits[] = {
0x00, 0x00, 0xfe, 0x7f, 0xfe, 0x7f, 0x06, 0x60, 0xf6, 0x6f, 0xf6, 0x6f,
0xf6, 0x6f, 0xf6, 0x6f, 0xf6, 0x6f, 0xf6, 0x6f, 0xf6, 0x6f, 0xf6, 0x6f,
0x06, 0x60, 0xfe, 0x7f, 0xfe, 0x7f, 0x00, 0x00
};
/* filled rect icon */
#define filled_rect_width 16
#define filled_rect_height 16
static unsigned char filled_rect_bits[] = {
0xff, 0xff, 0x01, 0x80, 0x01, 0x80, 0xf9, 0x9f, 0xf9, 0x9f, 0xf9, 0x9f,
0xf9, 0x9f, 0xf9, 0x9f, 0xf9, 0x9f, 0xf9, 0x9f, 0xf9, 0x9f, 0xf9, 0x9f,
0xf9, 0x9f, 0x01, 0x80, 0x01, 0x80, 0xff, 0xff
};
/* selected filled rect icon */
#define filled_rect_selected_width 16
#define filled_rect_selected_height 16
static unsigned char filled_rect_selected_bits[] = {
0x00, 0x00, 0xfe, 0x7f, 0xfe, 0x7f, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60,
0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60,
0x06, 0x60, 0xfe, 0x7f, 0xfe, 0x7f, 0x00, 0x00
};
/* line icon */
#define line_width 16
#define line_height 16
static unsigned char line_bits[] = {
0xff, 0xff, 0x01, 0x80, 0x01, 0xb0, 0x01, 0xb8, 0x01, 0x9c, 0x01, 0x8e,
0x01, 0x87, 0x81, 0x83, 0xc1, 0x81, 0xe1, 0x80, 0x71, 0x80, 0x39, 0x80,
0x1d, 0x80, 0x0d, 0x80, 0x01, 0x80, 0xff, 0xff
};
/* selected line icon */
#define line_selected_width 16
#define line_selected_height 16
static unsigned char line_selected_bits[] = {
0x00, 0x00, 0xfe, 0x7f, 0xfe, 0x4f, 0xfe, 0x47, 0xfe, 0x63, 0xfe, 0x71,
0xfe, 0x78, 0x7e, 0x7c, 0x3e, 0x7e, 0x1e, 0x7f, 0x8e, 0x7f, 0xc6, 0x7f,
0xe2, 0x7f, 0xf2, 0x7f, 0xfe, 0x7f, 0x00, 0x00
};
/* bitmap */
static uint8_t bitmap[BITMAP_HEIGHT][BITMAP_WIDTH];
static uint8_t bitmap_history[MAX_UNDOS][BITMAP_HEIGHT][BITMAP_WIDTH];
static int current_bitmap = 0;
static int top_bitmap = 1;
/* temp layer */
int templayer[BITMAP_HEIGHT][BITMAP_WIDTH];
/* tools */
enum {
TOOL_PEN,
TOOL_LINE,
TOOL_FILL,
TOOL_RECT,
TOOL_FILLED_RECT
};
static int current_tool = TOOL_PEN;
static eui_color_t current_color = 0;
/* filter patterns for save/load dialogs */
static const char *filter_patterns[1] = {"*.bmp"};
static SDL_Window *window = NULL;
static SDL_Surface *surface8 = NULL;
static SDL_Surface *surface32 = NULL;
static SDL_Surface *clut = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_Texture *texture = NULL;
static SDL_Rect blit_rect;
static SDL_Color colors[256];
static Uint64 next_time = 0;
#define DELAY (15)
static Uint64 time_left(void)
{
Uint64 now = SDL_GetTicks64();
if (next_time <= now)
return 0;
else
return next_time - now;
}
static void speed_limiter(void)
{
if (next_time == 0)
next_time = SDL_GetTicks64() + DELAY;
SDL_Delay(time_left());
next_time += DELAY;
}
static int palette_search(SDL_Palette *palette, int r, int g, int b)
{
int i, pen = 0, dist = INT_MAX;
for (i = 0; i < palette->ncolors; i++)
{
int rank =
SQR(palette->colors[i].r - r) +
SQR(palette->colors[i].g - g) +
SQR(palette->colors[i].b - b);
if (rank < dist)
{
pen = i;
dist = rank;
}
}
return pen;
}
static SDL_Surface *generate_clut(SDL_Palette *palette)
{
SDL_Surface *clut = SDL_CreateRGBSurfaceWithFormat(0, palette->ncolors, palette->ncolors, 8, SDL_PIXELFORMAT_INDEX8);
SDL_SetSurfacePalette(clut, palette);
for (int y = 0; y < palette->ncolors; y++)
{
for (int x = 0; x < palette->ncolors; x++)
{
int r, g, b;
r = (palette->colors[x].r + palette->colors[y].r) >> 1;
g = (palette->colors[x].g + palette->colors[y].g) >> 1;
b = (palette->colors[x].b + palette->colors[y].b) >> 1;
PIXEL(clut, x, y) = palette_search(palette, r, g, b);
}
}
return clut;
}
/* setup editor colors */
static void setup_colors(SDL_Palette *palette)
{
eui_config_t *config;
/* set palette for screen */
SDL_SetSurfacePalette(surface8, palette);
/* regenerate clut */
if (clut) SDL_FreeSurface(clut);
clut = generate_clut(palette);
/* setup colors */
config = eui_get_config();
config->button.border_color = palette_search(palette, 0, 0, 0);
config->button.border_color_hover = palette_search(palette, 255, 255, 255);
config->button.bg_color = palette_search(palette, 255, 255, 255);
config->button.bg_color_hover = palette_search(palette, 0, 0, 0);
config->button.text_color = palette_search(palette, 0, 0, 0);
config->button.text_color_hover = palette_search(palette, 255, 255, 255);
}
/* do blend */
static Uint8 do_blend(int x, int y, eui_color_t color)
{
return PIXEL(clut, bitmap[y][x], color);
}
/* push bitmap to stack */
void bitmap_push(int redo)
{
int i;
/* it's a redo, not an edit */
if (redo)
{
/* already at the top of the stack */
if (current_bitmap >= top_bitmap)
return;
/* move to next bitmap */
current_bitmap++;
return;
}
if (current_bitmap == MAX_UNDOS - 1)
{
/* move bitmap stack down */
for (i = 0; i < current_bitmap; i++)
{
SDL_memcpy(&bitmap_history[i], &bitmap_history[i + 1], sizeof(bitmap_history[i]));
}
}
else
{
/* move to next bitmap */
current_bitmap++;
/* clear upper bitmap stack */
for (i = current_bitmap; i < MAX_UNDOS; i++)
{
SDL_memset(&bitmap_history[i], 0, sizeof(bitmap_history[i]));
}
}
/* copy current bitmap to next in stack */
top_bitmap = current_bitmap;
SDL_memcpy(&bitmap_history[current_bitmap], bitmap, sizeof(bitmap));
}
/* pop bitmap from stack */
void bitmap_pop(void)
{
if (!current_bitmap)
return;
current_bitmap--;}
/* undo button */
void button_undo(void *user)
{
EUI_UNUSED(user);
bitmap_pop();
SDL_memcpy(bitmap, &bitmap_history[current_bitmap], sizeof(bitmap));
}
/* redo button */
void button_redo(void *user)
{
EUI_UNUSED(user);
bitmap_push(REDO_YES);
SDL_memcpy(bitmap, &bitmap_history[current_bitmap], sizeof(bitmap));
}
/* clear button */
void button_clear(void *user)
{
EUI_UNUSED(user);
SDL_memset(&bitmap, 0, sizeof(bitmap));
SDL_memset(templayer, -1, sizeof(templayer));
bitmap_push(REDO_NO);
}
/* save button */
void button_save(void *user)
{
char *filename;
int y;
SDL_Surface *bmp;
uint8_t *ptr;
EUI_UNUSED(user);
filename = tinyfd_saveFileDialog("Save BMP", "image.bmp", 1, filter_patterns, "BMP Files");
SDL_PumpEvents();
SDL_FlushEvents(SDL_MOUSEMOTION, SDL_MOUSEWHEEL);
eui_clear_events();
if (filename == NULL)
return;
/* create bmp surface */
bmp = SDL_CreateRGBSurface(0, BITMAP_WIDTH, BITMAP_HEIGHT, 8, 0, 0, 0, 0);
if (bmp == NULL)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "SDL Error", SDL_GetError(), NULL);
return;
}
/* set palette */
SDL_SetPaletteColors(bmp->format->palette, colors, 0, 256);
/* copy in bitmap */
ptr = (uint8_t *)bmp->pixels;
for (y = 0; y < bmp->h; y++)
{
SDL_memcpy(ptr, &bitmap[y][0], bmp->w * sizeof(eui_color_t));
ptr += bmp->pitch;
}
/* save bmp to disk */
if (SDL_SaveBMP(bmp, filename) != 0)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "SDL Error", SDL_GetError(), NULL);
}
SDL_FreeSurface(bmp);
}
/* load button */
void button_load(void *user)
{
char *filename;
SDL_Surface *bmp;
int y;
uint8_t *ptr;
EUI_UNUSED(user);
filename = tinyfd_openFileDialog("Load BMP", "image.bmp", 1, filter_patterns, "BMP Files", 0);
SDL_PumpEvents();
SDL_FlushEvents(SDL_MOUSEMOTION, SDL_MOUSEWHEEL);
eui_clear_events();
if (filename == NULL)
return;
bmp = SDL_LoadBMP(filename);
if (bmp == NULL)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "SDL Error", SDL_GetError(), NULL);
return;
}
if (bmp->format->BytesPerPixel != sizeof(eui_color_t))
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Editor Error", "Image does not have the correct color depth", NULL);
return;
}
if (bmp->w != BITMAP_WIDTH || bmp->h != BITMAP_HEIGHT)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Editor Error", "Image does not have the correct dimensions", NULL);
return;
}
ptr = (uint8_t *)bmp->pixels;
for (y = 0; y < bmp->h; y++)
{
SDL_memcpy(&bitmap[y][0], ptr, bmp->w * sizeof(eui_color_t));
ptr += bmp->pitch;
}
/* setup palette */
setup_colors(bmp->format->palette);
SDL_FreeSurface(bmp);
}
/* flood fill part of bitmap */
#define PUSH(_x, _y) \
{ \
stack_count++; \
stack[stack_count].x = _x; \
stack[stack_count].y = _y; \
}
#define POP(_x, _y) \
{ \
_x = stack[stack_count].x; \
_y = stack[stack_count].y; \
stack[stack_count].x = 0; \
stack[stack_count].y = 0; \
stack_count--; \
}
void tool_fill(int x, int y, eui_color_t color)
{
static eui_vec2_t stack[BITMAP_WIDTH * BITMAP_HEIGHT * 4];
static int started;
int stack_count;
eui_color_t seed;
eui_color_t read;
/* user is not pressing any buttons */
if (!eui_get_button())
{
if (started)
{
/* push bitmap stack for undo/redo */
bitmap_push(REDO_NO);
/* turn off started flag */
started = EUI_FALSE;
}
return;
}
/* right click erases */
if (eui_get_button() & EUI_BUTTON_RIGHT)
color = 0;
/* already doing it, don't do it again */
if (started)
return;
/* get seed color */
seed = bitmap[y][x];
/* prevent stack overflow */
if (seed == color)
return;
/* push first position to stack */
stack_count = 0;
PUSH(x, y);
/* do flood fill */
while (stack_count)
{
/* read stack value */
POP(x, y);
/* out of bounds */
if (x < 0 || x >= BITMAP_WIDTH || y < 0 || y >= BITMAP_HEIGHT)
continue;
/* read pixel */
read = bitmap[y][x];
/* it's a match */
if (read == seed)
{
/* set pixel */
bitmap[y][x] = do_blend(x, y, color);
/* add neighboring pixel positions */
PUSH(x + 1, y);
PUSH(x - 1, y);
PUSH(x, y + 1);
PUSH(x, y - 1);
}
}
/* we're doing it */
started = EUI_TRUE;
}
#undef PUSH
#undef POP
/* plot pixel on bitmap */
void tool_pen(int x, int y, eui_color_t color)
{
static int started;
/* user is not pressing any buttons */
if (!eui_get_button())
{
if (started)
{
/* push bitmap stack for undo/redo */
bitmap_push(REDO_NO);
/* turn off started flag */
started = EUI_FALSE;
}
return;
}
/* right click erases */
if (eui_get_button() & EUI_BUTTON_RIGHT)
color = 0;
/* plot pixel */
bitmap[y][x] = do_blend(x, y, color);
/* we're doing it */
started = EUI_TRUE;
}
/* draw hollow rectangle on bitmap */
void tool_rect(int x, int y, eui_color_t color)
{
static eui_vec2_t startpos;
static eui_vec2_t endpos;
static int started;
int xx, yy;
int xsgn, ysgn;
/* user is not pressing a button */
if (!eui_get_button())
{
/* we've already staretd drawing, so write changes */
if (started)
{
/* write changes to bitmap */
for (yy = 0; yy < BITMAP_HEIGHT; yy++)
{
for (xx = 0; xx < BITMAP_WIDTH; xx++)
{
if (templayer[yy][xx] >= 0)
bitmap[yy][xx] = do_blend(xx, yy, templayer[yy][xx]);
}
}
/* push bitmap stack for undo/redo */
bitmap_push(REDO_NO);
/* turn off started flag */
started = EUI_FALSE;
}
/* clear temp layer */
SDL_memset(templayer, -1, sizeof(templayer));
return;
}
/* right click erases */
if (eui_get_button() & EUI_BUTTON_RIGHT)
color = 0;
/* already started drawing */
if (started)
{
/* set endpos */
endpos.x = x;
endpos.y = y;
/* get draw direction for lines */
xsgn = SGN(endpos.x - startpos.x);
ysgn = SGN(endpos.y - startpos.y);
/* clear temp layer */
SDL_memset(templayer, -1, sizeof(templayer));
/* vertical lines */
for (yy = startpos.y; yy != endpos.y; yy += ysgn)
{
if (yy < 0 || yy >= BITMAP_HEIGHT)
break;
templayer[yy][startpos.x] = color;
templayer[yy][endpos.x] = color;
}
/* horizontal lines */
for (xx = startpos.x; xx != endpos.x; xx += xsgn)
{
if (xx < 0 || xx >= BITMAP_WIDTH)
break;
templayer[startpos.y][xx] = color;
templayer[endpos.y][xx] = color;
}
/* fill last point */
templayer[endpos.y][endpos.x] = color;
}
else
{
/* set startpos */
startpos.x = x;
startpos.y = y;
/* user started drawing */
started = EUI_TRUE;
}
}
/* draw line with bresenham's algorithm */
void tool_line(int x, int y, eui_color_t color)
{
static eui_vec2_t startpos;
static eui_vec2_t endpos;
static int started;
int xx, yy;
/* user is not pressing a button */
if (!eui_get_button())
{
/* we've already staretd drawing, so write changes */
if (started)
{
/* write changes to bitmap */
for (yy = 0; yy < BITMAP_HEIGHT; yy++)
{
for (xx = 0; xx < BITMAP_WIDTH; xx++)
{
if (templayer[yy][xx] >= 0)
bitmap[yy][xx] = do_blend(xx, yy, templayer[yy][xx]);
}
}
/* push bitmap stack for undo/redo */
bitmap_push(REDO_NO);
/* turn off started flag */
started = EUI_FALSE;
}
/* clear temp layer */
SDL_memset(templayer, -1, sizeof(templayer));
return;
}
/* right click erases */
if (eui_get_button() & EUI_BUTTON_RIGHT)
color = 0;
/* already started drawing */
if (started)
{
eui_vec2_t dir, absdir, sgndir, point, temp;
int i;
/* set endpos */
endpos.x = x;
endpos.y = y;
/* clear temp layer */
SDL_memset(templayer, -1, sizeof(templayer));
/* do line drawing */
dir.x = endpos.x - startpos.x;
dir.y = endpos.y - startpos.y;
absdir.x = abs(dir.x);
absdir.y = abs(dir.y);
sgndir.x = SGN(dir.x);
sgndir.y = SGN(dir.y);
temp.x = absdir.x >> 1;
temp.y = absdir.y >> 1;
point.x = startpos.x;
point.y = startpos.y;
/* the line is more horizontal than vertical */
if (absdir.x >= absdir.y)
{
for (i = 0; i < absdir.x; i++)
{
temp.y += absdir.y;
if (temp.y >= absdir.x)
{
temp.y -= absdir.x;
point.y += sgndir.y;
}
point.x += sgndir.x;
if (point.y < 0 || point.y >= BITMAP_HEIGHT)
break;
if (point.x < 0 || point.x >= BITMAP_WIDTH)
break;
templayer[point.y][point.x] = color;
}
}
/* the line is more vertical than horizontal */
else
{
for (i = 0; i < absdir.y; i++)
{
temp.x += absdir.x;
if (temp.x >= absdir.y)
{
temp.x -= absdir.y;
point.x += sgndir.x;
}
point.y += sgndir.y;
if (point.y < 0 || point.y >= BITMAP_HEIGHT)
break;
if (point.x < 0 || point.x >= BITMAP_WIDTH)
break;
templayer[point.y][point.x] = color;
}
}
/* fill first point */
templayer[startpos.y][startpos.x] = color;
}
else
{
/* set startpos */
startpos.x = x;
startpos.y = y;
/* user started drawing */
started = EUI_TRUE;
}
}
/* draw filled rectangle on bitmap */
void tool_filled_rect(int x, int y, eui_color_t color)
{
static eui_vec2_t startpos;
static eui_vec2_t endpos;
static int started;
int xx, yy;
int xsgn, ysgn;
/* user is not pressing a button */
if (!eui_get_button())
{
/* we've already staretd drawing, so write changes */
if (started)
{
/* write changes to bitmap */
for (yy = 0; yy < BITMAP_HEIGHT; yy++)
{
for (xx = 0; xx < BITMAP_WIDTH; xx++)
{
if (templayer[yy][xx] >= 0)
bitmap[yy][xx] = do_blend(xx, yy, templayer[yy][xx]);
}
}
/* push bitmap stack for undo/redo */
bitmap_push(REDO_NO);
/* turn off started flag */
started = EUI_FALSE;
}
/* clear temp layer */
SDL_memset(templayer, -1, sizeof(templayer));
return;
}
/* right click erases */
if (eui_get_button() & EUI_BUTTON_RIGHT)
color = 0;
/* already started drawing */
if (started)
{
/* set endpos */
endpos.x = x;
endpos.y = y;
/* get draw direction for lines */
xsgn = SGN(endpos.x - startpos.x);
ysgn = SGN(endpos.y - startpos.y);
/* clear temp layer */
SDL_memset(templayer, -1, sizeof(templayer));
/* vertical lines */
for (yy = startpos.y; yy != endpos.y; yy += ysgn)
{
if (yy < 0 || yy >= BITMAP_HEIGHT)
break;
templayer[yy][startpos.x] = color;
templayer[yy][endpos.x] = color;
}
/* horizontal lines */
for (xx = startpos.x; xx != endpos.x; xx += xsgn)
{
if (xx < 0 || xx >= BITMAP_WIDTH)
break;
templayer[startpos.y][xx] = color;
templayer[endpos.y][xx] = color;
}
/* filling */
for (yy = startpos.y; yy != endpos.y; yy += ysgn)
{
if (yy < 0 || yy >= BITMAP_HEIGHT || xx < 0 || xx >= BITMAP_WIDTH)
break;
for (xx = startpos.x; xx != endpos.x; xx += xsgn)
{
templayer[yy][xx] = color;
}
}
/* fill last point */
templayer[endpos.y][endpos.x] = color;
}
else
{
/* set startpos */
startpos.x = x;
startpos.y = y;
/* user started drawing */
started = EUI_TRUE;
}
}
/* main */
int main(int argc, char **argv)
{
uint32_t format;
unsigned int rmask, gmask, bmask, amask;
int bpp;
eui_vec2_t bitmap_pos, bitmap_size;
eui_vec2_t cursor_pos;
eui_vec2_t selected_pixel;
eui_vec2_t current_color_pos;
eui_vec2_t current_color_size;
eui_vec2_t pixel_pos, pixel_size;
eui_vec2_t palette_pos, palette_size, palette_entry_size;
eui_vec2_t pos, size;
SDL_Event event;
int i;
int x, y;
EUI_UNUSED(argc);
EUI_UNUSED(argv);
/* init */
SDL_Init(SDL_INIT_VIDEO);
/* create window */
window = SDL_CreateWindow(TITLE,
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
WIDTH, HEIGHT,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI
);
/* create renderer */
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC);
SDL_RenderSetLogicalSize(renderer, WIDTH, HEIGHT);
SDL_RenderSetIntegerScale(renderer, SDL_TRUE);
SDL_SetWindowMinimumSize(window, WIDTH, HEIGHT);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
/* create our render surface */
surface8 = SDL_CreateRGBSurface(0, WIDTH, HEIGHT, 8, 0, 0, 0, 0);
SDL_FillRect(surface8, NULL, 0);
/* generate palette table */
for (i = 0; i < 256; i++)
{
colors[i].r = palette[i * 3];
colors[i].g = palette[i * 3 + 1];
colors[i].b = palette[i * 3 + 2];
colors[i].a = 255;
}
/* install palette */
SDL_SetPaletteColors(surface8->format->palette, colors, 0, 256);
/* setup editor colors */
setup_colors(surface8->format->palette);
/* create display surface */
format = SDL_GetWindowPixelFormat(window);
SDL_PixelFormatEnumToMasks(format, &bpp, &rmask, &gmask, &bmask, &amask);
surface32 = SDL_CreateRGBSurface(0, WIDTH, HEIGHT, bpp, rmask, gmask, bmask, amask);
texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
/* make sure relative mouse mode is disabled */
SDL_SetRelativeMouseMode(SDL_FALSE);
/* setup blit rect */
blit_rect.x = 0;
blit_rect.y = 0;
blit_rect.w = WIDTH;
blit_rect.h = HEIGHT;
/* clear bitmap */
SDL_memset(&bitmap, 0, sizeof(bitmap));
SDL_memset(templayer, -1, sizeof(templayer));
/* set default color */
current_color = 63;
/* main loop */
while (1)
{
/* parse sdl events */
while (SDL_PollEvent(&event))
{
eui_push_event_sdl2(&event);
switch (event.type)
{
case SDL_QUIT:
goto done;
}
}
/* do eui */
if (eui_begin_sdl2(surface8))
{
/* clear */
eui_clear(4);
/* grid background */
bitmap_size.x = PIXEL_WIDTH * BITMAP_WIDTH;
bitmap_size.y = PIXEL_HEIGHT * BITMAP_HEIGHT;
bitmap_pos.x = WIDTH - bitmap_size.x;
bitmap_pos.y = HEIGHT - bitmap_size.y;
eui_filled_box(bitmap_pos, bitmap_size, 0);
/* get cursor pos */
cursor_pos = eui_get_cursor_pos();
/* draw palette */
palette_entry_size.x = ENTRY_WIDTH;
palette_entry_size.y = ENTRY_HEIGHT;
palette_size.x = palette_entry_size.x * 16;
palette_size.y = palette_entry_size.y * 16;
palette_pos.x = (bitmap_pos.x / 2) - (palette_size.x / 2);
palette_pos.y = bitmap_pos.y;
for (y = 0; y < 16; y++)
{
for (x = 0; x < 16; x++)
{
eui_vec2_t pos;
pos.x = palette_pos.x + (x * palette_entry_size.x);
pos.y = palette_pos.y + (y * palette_entry_size.y);
/* draw palette entry */
eui_filled_box(pos, palette_entry_size, y * 16 + x);
}
}
/* draw selected color */
pos.x = palette_pos.x;
pos.y = palette_pos.y - 10;
eui_textf(pos, 31, "color=%03d", current_color);
current_color_pos.x = ((current_color % 16) * palette_entry_size.x) + palette_pos.x - 1;
current_color_pos.y = ((current_color / 16) * palette_entry_size.y) + palette_pos.y - 1;
current_color_size.x = palette_entry_size.x + 2;
current_color_size.y = palette_entry_size.y + 2;
eui_border_box(current_color_pos, current_color_size, 1, 255);
/* do color interaction */
if (eui_is_hovered(palette_pos, palette_size))
{
eui_vec2_t entry_pos, entry_size, selected_color;
entry_pos.x = cursor_pos.x - palette_pos.x;
entry_pos.y = cursor_pos.y - palette_pos.y;
selected_color.x = (entry_pos.x - (entry_pos.x % ENTRY_WIDTH)) / ENTRY_WIDTH;
selected_color.y = (entry_pos.y - (entry_pos.y % ENTRY_HEIGHT)) / ENTRY_HEIGHT;
selected_color.x = CLAMP(selected_color.x, 0, 15);
selected_color.y = CLAMP(selected_color.y, 0, 15);
entry_pos.x = (selected_color.x * ENTRY_WIDTH) + palette_pos.x;
entry_pos.y = (selected_color.y * ENTRY_HEIGHT) + palette_pos.y;
entry_pos.x -= 1;
entry_pos.y -= 1;
entry_size.x = palette_entry_size.x + 2;
entry_size.y = palette_entry_size.y + 2;
/* draw color outline */
eui_border_box(entry_pos, entry_size, 1, 255);
if (eui_get_button() & EUI_BUTTON_LEFT)
{
current_color = selected_color.y * 16 + selected_color.x;
}
}
/* draw bitmap */
pixel_size.x = PIXEL_WIDTH;
pixel_size.y = PIXEL_HEIGHT;
for (y = 0; y < BITMAP_HEIGHT; y++)
{
for (x = 0; x < BITMAP_WIDTH; x++)
{
/* pixel pos */
pixel_pos.x = bitmap_pos.x + (x * PIXEL_WIDTH);
pixel_pos.y = bitmap_pos.y + (y * PIXEL_HEIGHT);
/* draw pixel */
eui_filled_box(pixel_pos, pixel_size, bitmap[y][x]);
/* temp layer value */
if (templayer[y][x] >= 0)
eui_filled_box(pixel_pos, pixel_size, templayer[y][x]);
}
}
/* draw selected pixel */
if (eui_is_hovered(bitmap_pos, bitmap_size))
{
/* pixel size */
pixel_size.x = PIXEL_WIDTH + 2;
pixel_size.y = PIXEL_HEIGHT + 2;
/* pixel pos */
pixel_pos.x = cursor_pos.x - bitmap_pos.x;
pixel_pos.y = cursor_pos.y - bitmap_pos.y;
/* selected pixel */
selected_pixel.x = (pixel_pos.x - (pixel_pos.x % PIXEL_WIDTH)) / PIXEL_WIDTH;
selected_pixel.y = (pixel_pos.y - (pixel_pos.y % PIXEL_HEIGHT)) / PIXEL_HEIGHT;
selected_pixel.x = CLAMP(selected_pixel.x, 0, BITMAP_WIDTH - 1);
selected_pixel.y = CLAMP(selected_pixel.y, 0, BITMAP_HEIGHT - 1);
pixel_pos.x = (selected_pixel.x * PIXEL_WIDTH) + bitmap_pos.x;
pixel_pos.y = (selected_pixel.y * PIXEL_HEIGHT) + bitmap_pos.y;
pixel_pos.x -= 1;
pixel_pos.y -= 1;
/* draw pixel outline */
eui_border_box(pixel_pos, pixel_size, 1, 255);
/* draw help text */
pos.x = bitmap_pos.x;
pos.y = bitmap_pos.y - 20;
eui_textf(pos, 31, "pixel=%02dx%02d", selected_pixel.x, selected_pixel.y);
pos.x = bitmap_pos.x;
pos.y = bitmap_pos.y - 10;
eui_textf(pos, 31, "color=%03d", bitmap[selected_pixel.y][selected_pixel.x]);
/* do pixel interaction */
switch (current_tool)
{
case TOOL_PEN:
tool_pen(selected_pixel.x, selected_pixel.y, current_color);
break;
case TOOL_LINE:
tool_line(selected_pixel.x, selected_pixel.y, current_color);
break;
case TOOL_FILL:
tool_fill(selected_pixel.x, selected_pixel.y, current_color);
break;
case TOOL_RECT:
tool_rect(selected_pixel.x, selected_pixel.y, current_color);
break;
case TOOL_FILLED_RECT:
tool_filled_rect(selected_pixel.x, selected_pixel.y, current_color);
break;
}
}
else
{
/* draw help text */
pos.x = bitmap_pos.x;
pos.y = bitmap_pos.y - 20;
eui_text(pos, 31, "pixel=--x--");
pos.x = bitmap_pos.x;
pos.y = bitmap_pos.y - 10;
eui_text(pos, 31, "color=---");
/* clear temp layer */
SDL_memset(templayer, -1, sizeof(templayer));
}
/* move to top left alignment */
eui_set_align(EUI_ALIGN_END, EUI_ALIGN_START);
/* filled rect button */
pos.x = -4;
pos.y = (bitmap_pos.y / 2) - (filled_rect_height / 2);
size.x = filled_rect_width;
size.y = filled_rect_height;
if (current_tool == TOOL_FILLED_RECT)
eui_xbm(pos, 31, filled_rect_selected_width, filled_rect_selected_height, filled_rect_selected_bits);
else
eui_xbm(pos, 31, filled_rect_width, filled_rect_height, filled_rect_bits);
/* select filled rect */
if (eui_is_hovered(pos, size) && eui_get_button())
current_tool = TOOL_FILLED_RECT;
/* rect button */
pos.x = -1 * rect_width - 8;
pos.y = (bitmap_pos.y / 2) - (rect_height / 2);
size.x = rect_width;
size.y = rect_height;
if (current_tool == TOOL_RECT)
eui_xbm(pos, 31, rect_selected_width, rect_selected_height, rect_selected_bits);
else
eui_xbm(pos, 31, rect_width, rect_height, rect_bits);
/* select rect */
if (eui_is_hovered(pos, size) && eui_get_button())
current_tool = TOOL_RECT;
/* bucket button */
pos.x = -1 * (bucket_width * 2) - 12;
pos.y = (bitmap_pos.y / 2) - (pen_height / 2);
size.x = bucket_width;
size.y = bucket_height;
if (current_tool == TOOL_FILL)
eui_xbm(pos, 31, bucket_selected_width, bucket_selected_height, bucket_selected_bits);
else
eui_xbm(pos, 31, bucket_width, bucket_height, bucket_bits);
/* select bucket */
if (eui_is_hovered(pos, size) && eui_get_button())
current_tool = TOOL_FILL;
/* line button */
pos.x = -1 * (line_width * 3) - 16;
pos.y = (bitmap_pos.y / 2) - (line_height / 2);
size.x = line_width;
size.y = line_height;
if (current_tool == TOOL_LINE)
eui_xbm(pos, 31, line_selected_height, line_selected_height, line_selected_bits);
else
eui_xbm(pos, 31, line_width, line_height, line_bits);
/* select pen */
if (eui_is_hovered(pos, size) && eui_get_button())
current_tool = TOOL_LINE;
/* pen button */
pos.x = -1 * (pen_width * 4) - 20;
pos.y = (bitmap_pos.y / 2) - (pen_height / 2);
size.x = pen_width;
size.y = pen_height;
if (current_tool == TOOL_PEN)
eui_xbm(pos, 31, pen_selected_width, pen_selected_height, pen_selected_bits);
else
eui_xbm(pos, 31, pen_width, pen_height, pen_bits);
/* select pen */
if (eui_is_hovered(pos, size) && eui_get_button())
current_tool = TOOL_PEN;
/* move to bottom alignment */
eui_set_align(EUI_ALIGN_START, EUI_ALIGN_END);
/* undo button */
pos.x = 0;
pos.y = -48;
size.x = bitmap_pos.x / 2;
size.y = 24;
eui_button(pos, size, "Undo", button_undo, NULL);
/* redo button */
pos.x = bitmap_pos.x - size.x;
pos.y = -48;
eui_button(pos, size, "Redo", button_redo, NULL);
/* clear button */
pos.x = 0;
pos.y = -24;
size.x = bitmap_pos.x;
size.y = 24;
eui_button(pos, size, "Clear", button_clear, NULL);
/* save button */
pos.x = 0;
pos.y = 0;
size.x = bitmap_pos.x / 2;
size.y = 24;
eui_button(pos, size, "Save", button_save, NULL);
/* load button */
pos.x = bitmap_pos.x - size.x;
pos.y = 0;
eui_button(pos, size, "Load", button_load, NULL);
/* end eui */
eui_end();
}
/* blit to screen */
int dst_pitch;
void *dst_pixels;
if (SDL_LockTexture(texture, NULL, &dst_pixels, &dst_pitch) == 0)
{
SDL_BlitSurface(surface8, &blit_rect, surface32, &blit_rect);
SDL_memcpy(dst_pixels, surface32->pixels, surface32->pitch * surface32->h);
SDL_UnlockTexture(texture);
}
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
/* run speed limiter */
speed_limiter();
}
/* quit */
done:
SDL_FreeSurface(clut);
SDL_FreeSurface(surface8);
SDL_FreeSurface(surface32);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}