Trace images work. Opacity slider works. Entering coordinates with the mouse works.

This commit is contained in:
Scott Duensing 2022-11-27 20:32:10 -06:00
parent 7551144902
commit b4f7e96158
7 changed files with 8131 additions and 97 deletions

View file

@ -26,12 +26,13 @@ project(joeydev C)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(SOURCE_FILES set(SOURCE_FILES
src/main.c src/main.c
src/utils.c src/utils.c
src/joeydev.c src/joeydev.c
src/vector.c src/vector.c
src/array.c src/array.c
src/draw.c src/draw.c
src/image.c
) )
add_executable(${CMAKE_PROJECT_NAME} ${SOURCE_FILES}) add_executable(${CMAKE_PROJECT_NAME} ${SOURCE_FILES})

31
include/image.h Normal file
View file

@ -0,0 +1,31 @@
/*
* JoeyDev
* Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#ifndef IMAGE_H
#define IMAGE_H
#define STBI_NO_HDR
#include "../thirdparty/stb_image.h"
#endif //IMAGE_H

View file

@ -378,7 +378,7 @@ void jlDrawPixelSet(jlContextT *c, jint16 x, jint16 y) {
c->_pixels[offset + 1] = c->_jlPalette[c->_jlDrawColor].g; c->_pixels[offset + 1] = c->_jlPalette[c->_jlDrawColor].g;
c->_pixels[offset + 2] = c->_jlPalette[c->_jlDrawColor].r; c->_pixels[offset + 2] = c->_jlPalette[c->_jlDrawColor].r;
// We're using CAIRO_FORMAT_RGB24 so the upper 8 bits are not used. // We're using CAIRO_FORMAT_RGB24 so the upper 8 bits are not used.
//c->_pixels[offset + 3] = 255; // This is alpha in CAIRO_FORMAT_ARGB32 mode. c->_pixels[offset + 3] = 255; // This is alpha in CAIRO_FORMAT_ARGB32 mode.
} }

25
src/image.c Normal file
View file

@ -0,0 +1,25 @@
/*
* JoeyDev
* Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#define STB_IMAGE_IMPLEMENTATION
#define STBI_NO_HDR
#include "../thirdparty/stb_image.h"

View file

@ -33,12 +33,15 @@
#include "vector.h" #include "vector.h"
#include "utils.h" #include "utils.h"
#include "draw.h" #include "draw.h"
#include "image.h"
#define SSM(m, w, l) scintilla_send_message(self->sci, m, w, l) #define SSM(m, w, l) scintilla_send_message(self->sci, m, w, l)
#define MARGIN_SCRIPT_FOLD_INDEX 1 #define MARGIN_SCRIPT_FOLD_INDEX 1
#define MARKER_ERROR_ARROW 0 #define MARKER_ERROR_ARROW 0
#define MARKER_ERROR_HIGHLIGHT 1 #define MARKER_ERROR_HIGHLIGHT 1
#define PREVIEW_WIDTH 640
#define PREVIEW_HEIGHT 400
enum PassE { enum PassE {
@ -57,9 +60,11 @@ typedef struct VectorDataS {
void *pLexer; void *pLexer;
int id; int id;
cairo_surface_t *surface; cairo_surface_t *surface;
cairo_t *cr; cairo_surface_t *scaled;
unsigned char *surfacePointer; cairo_surface_t *target;
cairo_surface_t *trace;
jlContextT *jlc; jlContextT *jlc;
double traceImagePercent;
} VectorDataT; } VectorDataT;
@ -79,7 +84,9 @@ static int _nextEditorId = 0;
EVENT gboolean drawVectorImageDraw(GtkWidget *widget, cairo_t *cr, gpointer userData); EVENT gboolean drawVectorImageDraw(GtkWidget *widget, cairo_t *cr, gpointer userData);
EVENT void drawVectorImageClick(GtkWidget *object, GdkEventButton *event, gpointer userData);
EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotification *notifyData, gpointer userData); EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotification *notifyData, gpointer userData);
EVENT void fileVectorTraceImageFileSet(GtkWidget *object, gpointer userData);
EVENT void menuVectorFileClose(GtkWidget *object, gpointer userData); EVENT void menuVectorFileClose(GtkWidget *object, gpointer userData);
static gboolean parseBox(PassT pass, char **tokenEnd, VectorDataT *self); static gboolean parseBox(PassT pass, char **tokenEnd, VectorDataT *self);
static gboolean parseCircle(PassT pass, char **tokenEnd, VectorDataT *self); static gboolean parseCircle(PassT pass, char **tokenEnd, VectorDataT *self);
@ -99,6 +106,7 @@ static gboolean parserGetWord(char *word, char **tokenEnd);
static gboolean parserGetX(char **tokenEnd, int *x); static gboolean parserGetX(char **tokenEnd, int *x);
static gboolean parserGetXY(char **tokenEnd, int *x, int *y); static gboolean parserGetXY(char **tokenEnd, int *x, int *y);
static gboolean parserGetXYZ(char **tokenEnd, int *x, int *y, int *z); static gboolean parserGetXYZ(char **tokenEnd, int *x, int *y, int *z);
EVENT void scaleVectorTraceImageValueChanged(GtkWidget *object, gpointer userData);
EVENT gboolean winVectorClose(GtkWidget *object, gpointer userData); EVENT gboolean winVectorClose(GtkWidget *object, gpointer userData);
static void winVectorDelete(gpointer userData); static void winVectorDelete(gpointer userData);
@ -107,25 +115,73 @@ EVENT gboolean drawVectorImageDraw(GtkWidget *widget, cairo_t *cr, gpointer user
VectorDataT *self = (VectorDataT *)userData; VectorDataT *self = (VectorDataT *)userData;
int width = cairo_image_surface_get_width(self->surface); int width = cairo_image_surface_get_width(self->surface);
int height = cairo_image_surface_get_height(self->surface); int height = cairo_image_surface_get_height(self->surface);
cairo_t *myCr;
size_t offset;
int blend;
unsigned char *scaledPointer;
unsigned char *tracePointer;
unsigned char *targetPointer;
(void)widget; (void)widget;
(void)userData; (void)userData;
// Copy JoeyLib output to a surface scaled to match the preview.
cairo_surface_mark_dirty(self->surface); cairo_surface_mark_dirty(self->surface);
myCr = cairo_create(self->scaled);
cairo_scale(myCr, (double)((double)PREVIEW_WIDTH / (double)width), (double)((double)PREVIEW_HEIGHT / (double)height));
cairo_set_source_surface(myCr, self->surface, 0, 0);
cairo_pattern_set_filter(cairo_get_source(myCr), CAIRO_FILTER_NEAREST);
cairo_set_operator(myCr, CAIRO_OPERATOR_SOURCE);
cairo_paint(myCr);
cairo_destroy(myCr);
cairo_save(cr); // Get ready for more JoeyLib drawing.
cairo_set_source_surface(cr, self->surface, 0, 0);
cairo_rectangle(cr, 0, 0, width, height);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_fill(cr);
cairo_restore(cr);
cairo_surface_flush(self->surface); cairo_surface_flush(self->surface);
cairo_save(cr);
scaledPointer = cairo_image_surface_get_data(self->scaled);
targetPointer = cairo_image_surface_get_data(self->target);
if (self->trace != NULL) tracePointer = cairo_image_surface_get_data(self->trace);
// Do our blending by hand.
offset = 0;
while (offset < PREVIEW_HEIGHT * PREVIEW_WIDTH * 4) {
if (self->trace != NULL) {
targetPointer[offset] = ((tracePointer[offset] * self->traceImagePercent) + (scaledPointer[offset] * (1.0 - self->traceImagePercent)));
} else {
targetPointer[offset] = scaledPointer[offset];
}
offset++;
}
cairo_surface_mark_dirty(self->target);
cairo_set_source_surface(cr, self->target, 0, 0);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr);
cairo_restore(cr);
return FALSE; return FALSE;
} }
EVENT void drawVectorImageClick(GtkWidget *object, GdkEventButton *event, gpointer userData) {
VectorDataT *self = (VectorDataT *)userData;
char temp[8];
(void)object;
if (event->type == GDK_DOUBLE_BUTTON_PRESS) {
snprintf(temp, 8, "%d,%d", (int)(event->x * 0.5), (int)(event->y * 0.5));
SSM(SCI_ADDTEXT, strlen(temp), (sptr_t)temp);
SSM(SCI_GRABFOCUS, 0, 0);
}
}
EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotification *notifyData, gpointer userData) { EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotification *notifyData, gpointer userData) {
VectorDataT *self = (VectorDataT *)userData; VectorDataT *self = (VectorDataT *)userData;
@ -151,7 +207,7 @@ EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotifi
case SCN_MARGINCLICK: case SCN_MARGINCLICK:
switch (notifyData->margin) { switch (notifyData->margin) {
case MARGIN_SCRIPT_FOLD_INDEX: case MARGIN_SCRIPT_FOLD_INDEX:
SSM(SCI_TOGGLEFOLD, lineNumber, (sptr_t) 0); SSM(SCI_TOGGLEFOLD, lineNumber, (sptr_t)0);
break; break;
} }
break; break;
@ -159,6 +215,63 @@ EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotifi
} }
EVENT void fileVectorTraceImageFileSet(GtkWidget *object, gpointer userData) {
VectorDataT *self = (VectorDataT *)userData;
char *filename;
int x;
int y;
int n;
size_t offset;
unsigned char *image;
unsigned char *pixels;
cairo_surface_t *temp;
cairo_t *cr;
filename = (char *)gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(object));
image = stbi_load(filename, &x, &y, &n, 4); // Cairo is always 32 bit even with no alpha. Derp.
DEL(filename);
if (image != NULL) {
// Create Cairo surface the same size as the loaded image.
temp = cairo_image_surface_create(CAIRO_FORMAT_RGB24, x, y);
// Copy loaded pixels into Cairo surface. OF COURSE the pixel order is backwards in Cairo.
cairo_surface_flush(temp);
pixels = cairo_image_surface_get_data(temp);
offset = 0;
while (offset < x * y * 4) {
pixels[offset + 2] = image[offset ]; // R
pixels[offset + 1] = image[offset + 1]; // G
pixels[offset ] = image[offset + 2]; // B
pixels[offset + 4] = image[offset + 3]; // A
offset += 4;
}
cairo_surface_mark_dirty(temp);
// Release loaded image.
stbi_image_free(image);
// (Re)create the trace surface we're actually going to display.
if (self->trace != NULL) cairo_surface_destroy(self->trace);
self->trace = cairo_image_surface_create(CAIRO_FORMAT_RGB24, PREVIEW_WIDTH, PREVIEW_HEIGHT);
// Create Cairo context.
cr = cairo_create(self->trace);
// Copy and resize image in surface to our trace surface.
cairo_scale(cr, (double)((double)PREVIEW_WIDTH / (double)x), (double)((double)PREVIEW_HEIGHT / (double)y));
cairo_set_source_surface(cr, temp, 0, 0);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr);
// Clean up.
cairo_destroy(cr);
cairo_surface_destroy(temp);
}
}
EVENT void menuVectorFileClose(GtkWidget *object, gpointer userData) { EVENT void menuVectorFileClose(GtkWidget *object, gpointer userData) {
VectorDataT *self = (VectorDataT *)userData; VectorDataT *self = (VectorDataT *)userData;
@ -449,7 +562,7 @@ static gboolean parseReset(PassT pass, char **tokenEnd, VectorDataT *self) {
// Reset draw context. // Reset draw context.
jlContextDel(&self->jlc); jlContextDel(&self->jlc);
self->jlc = jlContextNew(self->surfacePointer); self->jlc = jlContextNew(cairo_image_surface_get_data(self->surface));
switch (pass) { switch (pass) {
case PASS_DRAW: case PASS_DRAW:
@ -640,6 +753,18 @@ static gboolean parserGetXYZ(char **tokenEnd, int *x, int *y, int *z) {
} }
EVENT void scaleVectorTraceImageValueChanged(GtkWidget *object, gpointer userData) {
VectorDataT *self = (VectorDataT *)userData;
(void)object;
self->traceImagePercent = gtk_range_get_value(GTK_RANGE(object)) * 0.01;
// Refresh widget.
gtk_widget_queue_draw(self->drawVectorImage);
}
EVENT gboolean winVectorClose(GtkWidget *object, gpointer userData) { EVENT gboolean winVectorClose(GtkWidget *object, gpointer userData) {
// userData is not reliable due to menuVectorFileClose and util indirectly calling us. // userData is not reliable due to menuVectorFileClose and util indirectly calling us.
VectorDataT *self = (VectorDataT *)utilGetWindowData(object); VectorDataT *self = (VectorDataT *)utilGetWindowData(object);
@ -660,9 +785,9 @@ EVENT gboolean winVectorClose(GtkWidget *object, gpointer userData) {
void winVectorCreate(void) { void winVectorCreate(void) {
VectorDataT *self; VectorDataT *self;
char *widgetNames[] = { "winVector", "boxVectorForEditor", "drawVectorImage", NULL }; char *widgetNames[] = { "winVector", "boxVectorForEditor", "drawVectorImage", NULL };
GtkWidget **widgets[] = { NULL, NULL, NULL }; GtkWidget **widgets[] = { NULL, NULL, NULL };
// Set up instance data. // Set up instance data.
self = NEW(VectorDataT); self = NEW(VectorDataT);
@ -674,6 +799,10 @@ void winVectorCreate(void) {
widgets[2] = &self->drawVectorImage; widgets[2] = &self->drawVectorImage;
utilGetWidgetsFromMemory(widgetNames, widgets, EMBEDDED(___ui_Vector_glade), self); utilGetWidgetsFromMemory(widgetNames, widgets, EMBEDDED(___ui_Vector_glade), self);
// Add missing event to drawVectorImage
gtk_widget_add_events(self->drawVectorImage, GDK_BUTTON_PRESS_MASK);
g_signal_connect(G_OBJECT(self->drawVectorImage), "button-press-event", G_CALLBACK(drawVectorImageClick), self);
// Create Scintilla editor. // Create Scintilla editor.
self->editor = scintilla_new(); self->editor = scintilla_new();
self->sci = SCINTILLA(self->editor); self->sci = SCINTILLA(self->editor);
@ -728,18 +857,23 @@ void winVectorCreate(void) {
"palette 15 as 15,0,0\n" "palette 15 as 15,0,0\n"
"color 15\n" "color 15\n"
"box 0,0 to 319,199\n" "box 0,0 to 319,199\n"
"color 14\n"
"line 27,22 to 289,24 to 297,169 to 27,166 to 27,23\n"
"color 11\n"
"circle 50 at 159,95\n"
); );
// Connect editor to our code. // Connect editor to our code.
g_signal_connect(G_OBJECT(self->editor), "sci-notify", G_CALLBACK(editorVectorNotify), self); g_signal_connect(G_OBJECT(self->editor), "sci-notify", G_CALLBACK(editorVectorNotify), self);
// Create our drawing surface and context. // Create our drawing surface and context.
self->surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 320, 200); self->surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 320, 200);
self->cr = cairo_create(self->surface); self->scaled = cairo_image_surface_create(CAIRO_FORMAT_RGB24, PREVIEW_WIDTH, PREVIEW_HEIGHT);
self->trace = NULL;
self->target = cairo_image_surface_create(CAIRO_FORMAT_RGB24, PREVIEW_WIDTH, PREVIEW_HEIGHT);
cairo_surface_flush(self->surface); cairo_surface_flush(self->surface);
self->surfacePointer = cairo_image_surface_get_data(self->surface); self->jlc = jlContextNew(cairo_image_surface_get_data(self->surface));
self->jlc = jlContextNew(self->surfacePointer); self->traceImagePercent = 50 * 0.01;
// Register window & show it. // Register window & show it.
utilWindowRegister(self); utilWindowRegister(self);
gtk_widget_show_all(self->windowData.window); gtk_widget_show_all(self->windowData.window);
@ -755,8 +889,10 @@ static void winVectorDelete(gpointer userData) {
utilWindowUnRegister(userData); utilWindowUnRegister(userData);
jlContextDel(&self->jlc); jlContextDel(&self->jlc);
cairo_destroy(self->cr);
cairo_surface_destroy(self->surface); cairo_surface_destroy(self->surface);
cairo_surface_destroy(self->scaled);
cairo_surface_destroy(self->target);
if (self->trace != NULL) cairo_surface_destroy(self->trace);
DEL(self); DEL(self);
} }

7897
thirdparty/stb_image.h vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -25,8 +25,8 @@
<object class="GtkWindow" id="winVector"> <object class="GtkWindow" id="winVector">
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="title" translatable="yes">Vector</property> <property name="title" translatable="yes">Vector</property>
<property name="default-width">640</property> <property name="default-width">800</property>
<property name="default-height">480</property> <property name="default-height">500</property>
<signal name="delete-event" handler="winVectorClose" swapped="no"/> <signal name="delete-event" handler="winVectorClose" swapped="no"/>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
@ -145,36 +145,6 @@
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="GtkMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">_View</property>
<property name="use-underline">True</property>
<child type="submenu">
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkMenuItem" id="menuVectorViewZoom1">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Zoom 1x (Normal)</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuVectorViewZoom2">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Zoom 2x</property>
<property name="use-underline">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child> <child>
<object class="GtkMenuItem"> <object class="GtkMenuItem">
<property name="visible">True</property> <property name="visible">True</property>
@ -211,7 +181,7 @@
<property name="baseline-position">top</property> <property name="baseline-position">top</property>
<child> <child>
<object class="GtkBox" id="boxVectorImage"> <object class="GtkBox" id="boxVectorImage">
<property name="width-request">320</property> <property name="width-request">640</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
@ -220,8 +190,8 @@
<property name="baseline-position">top</property> <property name="baseline-position">top</property>
<child> <child>
<object class="GtkDrawingArea" id="drawVectorImage"> <object class="GtkDrawingArea" id="drawVectorImage">
<property name="width-request">320</property> <property name="width-request">640</property>
<property name="height-request">200</property> <property name="height-request">400</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
@ -235,24 +205,9 @@
</packing> </packing>
</child> </child>
<child> <child>
<!-- n-columns=2 n-rows=2 --> <object class="GtkBox">
<object class="GtkGrid">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Image Opacity:</property>
<property name="justify">right</property>
<property name="single-line-mode">True</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="visible">True</property> <property name="visible">True</property>
@ -263,23 +218,9 @@
<property name="single-line-mode">True</property> <property name="single-line-mode">True</property>
</object> </object>
<packing> <packing>
<property name="left-attach">0</property> <property name="expand">False</property>
<property name="top-attach">1</property> <property name="fill">True</property>
</packing> <property name="position">0</property>
</child>
<child>
<object class="GtkScale" id="scaleVectorMainImage">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="adjustment">adjustmentVectorMainImage</property>
<property name="round-digits">0</property>
<property name="digits">0</property>
<property name="value-pos">right</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -291,10 +232,12 @@
<property name="round-digits">0</property> <property name="round-digits">0</property>
<property name="digits">0</property> <property name="digits">0</property>
<property name="value-pos">right</property> <property name="value-pos">right</property>
<signal name="value-changed" handler="scaleVectorTraceImageValueChanged" swapped="no"/>
</object> </object>
<packing> <packing>
<property name="left-attach">1</property> <property name="expand">False</property>
<property name="top-attach">1</property> <property name="fill">True</property>
<property name="position">2</property>
</packing> </packing>
</child> </child>
</object> </object>
@ -329,6 +272,7 @@
<property name="create-folders">False</property> <property name="create-folders">False</property>
<property name="filter">imageFilter</property> <property name="filter">imageFilter</property>
<property name="title" translatable="yes">Open Trace Image</property> <property name="title" translatable="yes">Open Trace Image</property>
<signal name="file-set" handler="fileVectorTraceImageFileSet" swapped="no"/>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>