$ git clone http://gcodetool.ion.nu/gcodetool.git
commit 6015be093b34dafc0bdafdae4c3197c2e8820339
Author: Alicia <...>
Date:   Thu Oct 17 22:49:26 2019 +0200

    Added an SDL output method to gcodetool preview.

diff --git a/Makefile b/Makefile
index cc05f2c..a3565c3 100644
--- a/Makefile
+++ b/Makefile
@@ -2,11 +2,22 @@ PREFIX=/usr
 BINDIR=$(PREFIX)/bin
 SHAREDIR=$(PREFIX)/share
 CFLAGS=-Wall -g3
-gcodetool: main.o gcode.o mirror.o move.o info.o preview.o preview_term.o preview_pnm.o
- $(CC) $^ -lm -o $@
+OBJECTS=main.o gcode.o mirror.o move.o info.o preview.o preview_term.o preview_pnm.o
+ifneq ($(shell pkg-config $(PCFLAGS) --libs sdl2 2> /dev/null),)
+  LIBS+=$(shell pkg-config $(PCFLAGS) --libs sdl2)
+  CFLAGS+=$(shell pkg-config $(PCFLAGS) --cflags sdl2) -DHAS_SDL=1
+  OBJECTS+=preview_sdl.o
+endif
+
+gcodetool: $(OBJECTS)
+ $(CC) $^ $(LIBS) -lm -o $@
 static: CC+=-static
+static: PCFLAGS+=--static
 static: gcodetool
 
 install: gcodetool
  install -D gcodetool $(BINDIR)/gcodetool
  install -D gcodetool.thumbnailer $(SHAREDIR)/thumbnailers/gcodetool.thumbnailer
+
+clean:
+ rm -f $(OBJECTS) gcodetool
diff --git a/main.c b/main.c
index e307c5c..82310cb 100644
--- a/main.c
+++ b/main.c
@@ -55,11 +55,11 @@ int main(int argc, char** argv)
            "mirror[XYZ]\n"
            "move[XYZ]\n"
            "info\n"
+           "preview\n"
 "\nTODO:\n"
 "rotate[XYZ]\n"
 "speed (change speeds for selectable cases like travel, extrude)\n"
 "remove (exclude parts of the object)\n"
-"preview, somehow\n"
 "scale (will also need to scale E)\n", argv[0]);
 #ifdef WIN32 // Foolproofing
     if(argc==1)
diff --git a/preview.c b/preview.c
index 90b22ea..b59c37a 100644
--- a/preview.c
+++ b/preview.c
@@ -24,6 +24,7 @@
 #include "preview.h"
 extern struct img* gtool_preview_term(int width, int height, const char* filename);
 extern struct img* gtool_preview_pnm(int width, int height, const char* filename);
+extern struct img* gtool_preview_sdl(int width, int height, const char* filename);
 
 struct line // TODO: Consider non-planar printing?
 {
@@ -70,6 +71,40 @@ static void vector_draw(struct vectors* v, double startx, double starty, double
   l->y[1]=endy;
 }
 
+static void cb_drawline(struct img* img, int* x, int* y, unsigned char r, unsigned char g, unsigned char b, double linewidth) // Generic line drawing implementation for display methods that don't have their own line drawing functions
+{
+  double stepx;
+  double stepy;
+  double steps;
+  // Figure out whether to go x/y or y/x
+  if(fabs(x[0]-x[1])>fabs(y[0]-y[1]))
+  {
+    steps=fabs(x[0]-x[1]);
+    stepx=(x[1]>x[0])?1:-1;
+    stepy=(y[1]-y[0])/steps;
+  }else{
+    steps=fabs(y[0]-y[1]);
+    stepy=(y[1]>y[0])?1:-1;
+    stepx=(x[1]-x[0])/steps;
+  }
+  // Expand the line perpendicular to the line's angle
+  double lineangle=atan2(stepx,stepy);
+  double linestepx=sin(lineangle+M_PI_2);
+  double linestepy=cos(lineangle+M_PI_2);
+  int m;
+  for(m=0; m<linewidth; ++m)
+  {
+    double k;
+    for(k=0; k<steps; ++k)
+    {
+      unsigned int px=x[0]+stepx*k+linestepx*(m-linewidth/2);
+      unsigned int py=y[0]+stepy*k+linestepy*(m-linewidth/2);
+      if(px>=img->width || py>=img->height){continue;}
+      img->drawpixel(img, px, py, r, g, b);
+    }
+  }
+}
+
 int gtool_preview(const char* filename, struct img* img, double angle, double zrot)
 {
   struct gcode* gcode=gcode_read(filename);
@@ -83,11 +118,21 @@ int gtool_preview(const char* filename, struct img* img, double angle, double zr
   struct vectors vectors={0,0,0};
   double max[]={NAN,NAN,NAN};
   double min[]={NAN,NAN,NAN};
+//  int bary[]={img->height/2,img->height/2};
+//  int bari=0; // Part of experimental progress bar below
   // Loop through gcode and analyze commands
   while((cmd=gcode_readcommand(gcode)))
   {
     // Show progress if outputting to a tty
     if(tty){printf("%.2f%%\r", (double)gcode->pos*100/(double)gcode->size);}
+/*
+    if(!((++bari)%1000)) // Experimental graphical progress bar. Doesn't work well across display methods
+    {
+      int barx[]={0,((double)gcode->pos*(double)img->width)/gcode->size};
+      img->drawline(img, barx, bary, 0,255,0, img->height/5);
+      img->display(img);
+    }
+*/
     if(cmd->key=='G' && cmd->num<=1)
     {
       double xval=gcommand_getparam(cmd, 'X');
@@ -122,7 +167,7 @@ int gtool_preview(const char* filename, struct img* img, double angle, double zr
   gcode_free(gcode);
   double center[]={(min[0]+max[0])/2, (min[1]+max[1])/2, (min[2]+max[2])/2};
   // Figure out how to scale things
-  double scale=0; // max[0];
+  double scale=0;
   unsigned int i;
   for(i=0; i<3; ++i)
   {
@@ -131,7 +176,7 @@ int gtool_preview(const char* filename, struct img* img, double angle, double zr
   }
   scale=(img->width>img->height)?(img->height/scale):(img->width/scale);
   scale*=sin(45);
-// TODO: Intermediate bitmap for shading? (plus then the final bitmap can be navigated with less re-processing. but that would be pixely)
+// TODO: Intermediate bitmap for shading? (plus then the final bitmap can be navigated with less re-processing. but that could be pixely)
   while(1)
   {
     if(fabs(angle)>=M_PI){angle=(angle>0)?-M_PI:M_PI;}
@@ -159,41 +204,14 @@ l->y[1]=sin(zrot)*cy(oldl->y[1])+cos(zrot)*cx(oldl->x[1]);
 l->x[1]=sin(zrot+M_PI_2)*cy(oldl->y[1])+cos(zrot+M_PI_2)*cx(oldl->x[1]);
 l->y[0]=l->y[0]*cos(angle)+(vectors.layers[i].z-center[2])*scale*sin(angle);
 l->y[1]=l->y[1]*cos(angle)+(vectors.layers[i].z-center[2])*scale*sin(angle);
-#define polcmp(n) ((n[0]>=0&&n[1]>=0) || (n[0]<0&&n[1]<0))
+#define polcmp(n) ((n[0]>=0&&n[1]>=0) || (n[0]<0&&n[1]<0)) // Polarity comparison
         // Skip lines that are just out of view (saves resources when zoomed in far)
         if(fabs(l->x[0])>img->width/2 && fabs(l->x[1])>img->width/2 && polcmp(l->x)){continue;}
         if(fabs(l->y[0])>img->height/2 && fabs(l->y[1])>img->height/2 && polcmp(l->y)){continue;}
-        double stepx;
-        double stepy;
-        double steps;
-        // Figure out whether to go x/y or y/x
-        if(fabs(l->x[0]-l->x[1])>fabs(l->y[0]-l->y[1]))
-        {
-          steps=fabs(l->x[0]-l->x[1]);
-          stepx=(l->x[1]>l->x[0])?1:-1;
-          stepy=(l->y[1]-l->y[0])/steps;
-        }else{
-          steps=fabs(l->y[0]-l->y[1]);
-          stepy=(l->y[1]>l->y[0])?1:-1;
-          stepx=(l->x[1]-l->x[0])/steps;
-        }
-// Expand the line perpendicular to the line's angle
-double lineangle=atan2(stepx,stepy);
-double linestepx=sin(lineangle+M_PI_2);
-double linestepy=cos(lineangle+M_PI_2);
-int m;
-for(m=0; m<linescale; ++m)
-{
-        double k;
-        for(k=0; k<steps; ++k)
-        {
-          unsigned int x=(l->x[0]+img->width/2)+stepx*k+linestepx*(m-linescale/2);
-          unsigned int y=(l->y[0]+img->height/2)+stepy*k+linestepy*(m-linescale/2);
-          if(x>=img->width || y>=img->height){continue;}
-          img->drawpixel(img, x, y, c, c/1.05, c/2);
-        }
+        int x[]={l->x[0]+img->width/2, l->x[1]+img->width/2};
+        int y[]={l->y[0]+img->height/2, l->y[1]+img->height/2};
+        img->drawline(img, x, y, c, c/1.05, c/2, linescale);
       }
-}
       if(fabs(angle)>M_PI_2){i=vectors.layercount-1-i;}
     }
     img->display(img);
@@ -244,8 +262,17 @@ int gtool_preview_arg(int argc, char** argv)
     }
     else if((!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) && i+2<argc)
     {
-      if(!strcmp(argv[i+1], "term")){imginit=gtool_preview_term;}
-      else if(!strcmp(argv[i+1], "pnm")){imginit=gtool_preview_pnm;}
+      if(!strcmp(argv[i+1], "pnm")){imginit=gtool_preview_pnm;}
+#ifndef WIN32
+      else if(!strcmp(argv[i+1], "term")){imginit=gtool_preview_term;}
+#endif
+#if HAS_SDL
+      else if(!strcmp(argv[i+1], "sdl")){imginit=gtool_preview_sdl;}
+#endif
+      else{
+        printf("Unknown output method '%s'\n", argv[i+1]);
+        return 1;
+      }
       i+=2;
       outfile=argv[i];
     }
@@ -271,14 +298,30 @@ int gtool_preview_arg(int argc, char** argv)
            "TODO: scale etc.\n"
            "\n"
            "Available output methods:\n"
+#ifndef WIN32
            "term   (display using ANSI escape codes, use arrows and +/- to navigate)\n"
+#endif
            "pnm\n"
+#ifdef HAS_SDL
+           "sdl    (use arrows and +/- to navigate)\n"
+#endif
       ,argv[0], argv[1]);
     return -1;
   }
   if(!strncmp(filename, "file://", 7)){filename=&filename[7];}
-// TODO: Mechanism for selecting method by what's available. Not relevant until there's another direct display method
-  if(!imginit){imginit=gtool_preview_term;}
-  struct img* img=imginit(width,height,outfile);
+  struct img* img;
+  if(imginit)
+  {
+    img=imginit(width,height,outfile);
+  }else{ // Select a method by what's available
+#ifdef HAS_SDL
+    if(!(img=gtool_preview_sdl(width,height,0)))
+#endif
+#ifndef WIN32
+    if(!(img=gtool_preview_term(width,height,0)))
+#endif
+    img=gtool_preview_pnm(width, height, "preview.pnm"); // Last resort
+  }
+  if(!img->drawline){img->drawline=cb_drawline;}
   return gtool_preview(filename, img, rx, rz);
 }
diff --git a/preview.h b/preview.h
index 4e664eb..57874bc 100644
--- a/preview.h
+++ b/preview.h
@@ -33,6 +33,7 @@ struct img
   int width;
   int height;
   void(*drawpixel)(struct img*,int x, int y, unsigned char r, unsigned char g, unsigned char b);
+  void(*drawline)(struct img*,int* x, int* y, unsigned char r, unsigned char g, unsigned char b, double linewidth);
   void(*clear)(struct img*);
   void(*display)(struct img*);
   enum previewcontrol(*input)(struct img*);
diff --git a/preview_pnm.c b/preview_pnm.c
index 0893ffa..bb6297d 100644
--- a/preview_pnm.c
+++ b/preview_pnm.c
@@ -70,6 +70,7 @@ static void cb_clean(struct img* imgp)
 struct img* gtool_preview_pnm(int width, int height, const char* filename)
 {
   struct img_pnm* img=malloc(sizeof(struct img_pnm));
+  memset(img, 0, sizeof(struct img_pnm));
   if(filename && strcmp(filename, "-"))
   {
     img->out=fopen(filename, "w");
@@ -78,11 +79,8 @@ struct img* gtool_preview_pnm(int width, int height, const char* filename)
     img->out=stdout;
     img->bin=0;
   }
-  if(!width || !height) // Make up sizes if not specified
-  {
-    if(!width){width=1024;}
-    if(!height){height=1024;}
-  }
+  if(!width){width=1024;} // Make up sizes if not specified
+  if(!height){height=1024;}
   // Allocate buffer
   img->img.width=width;
   img->img.height=height;
diff --git a/preview_sdl.c b/preview_sdl.c
new file mode 100644
index 0000000..24b7ea8
--- /dev/null
+++ b/preview_sdl.c
@@ -0,0 +1,114 @@
+/*
+    gcodetool, a utility for analyzing/modifying already sliced parts
+    Copyright (C) 2019  alicia@ion.nu
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <SDL.h>
+#include "preview.h"
+
+struct img_sdl
+{
+  struct img img;
+  SDL_Window* win;
+  SDL_Renderer* renderer;
+};
+
+static void cb_drawline(struct img* imgp,int* x, int* y, unsigned char r, unsigned char g, unsigned char b, double linewidth)
+{
+  struct img_sdl* img=(struct img_sdl*)imgp;
+  SDL_SetRenderDrawColor(img->renderer, r, g, b, SDL_ALPHA_OPAQUE);
+  double lineangle=atan2(x[0]-x[1],y[0]-y[1]);
+  double linestepx=sin(lineangle+M_PI_2);
+  double linestepy=cos(lineangle+M_PI_2);
+  int m;
+  for(m=0; m<linewidth; ++m)
+  {
+    int xs[]={x[0]+linestepx*(m-linewidth/2),
+              x[1]+linestepx*(m-linewidth/2)};
+    int ys[]={y[0]+linestepy*(m-linewidth/2),
+              y[1]+linestepy*(m-linewidth/2)};
+    SDL_RenderDrawLine(img->renderer, xs[0], ys[0], xs[1], ys[1]);
+  }
+}
+
+static void cb_clear(struct img* imgp)
+{
+  struct img_sdl* img=(struct img_sdl*)imgp;
+  SDL_SetRenderDrawColor(img->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
+  SDL_RenderClear(img->renderer);
+}
+
+static void cb_display(struct img* imgp)
+{
+  struct img_sdl* img=(struct img_sdl*)imgp;
+  SDL_RenderPresent(img->renderer);
+}
+
+static void cb_clean(struct img* imgp)
+{
+  struct img_sdl* img=(struct img_sdl*)imgp;
+  SDL_DestroyWindow(img->win);
+  SDL_DestroyRenderer(img->renderer);
+  free(img);
+}
+
+static enum previewcontrol cb_input(struct img* imgp)
+{
+  enum previewcontrol ret=PREVIEW_NOINPUT;
+  SDL_Event e;
+  while(SDL_PollEvent(&e))
+  {
+    if(e.type==SDL_QUIT){return PREVIEW_QUIT;}
+    if(e.type==SDL_KEYDOWN)
+    {
+      if(e.key.keysym.sym==SDLK_RIGHT){ret=PREVIEW_ZRP;}
+      else if(e.key.keysym.sym==SDLK_LEFT){ret=PREVIEW_ZRM;}
+      else if(e.key.keysym.sym==SDLK_UP){ret=PREVIEW_XRP;}
+      else if(e.key.keysym.sym==SDLK_DOWN){ret=PREVIEW_XRM;}
+      else if(e.key.keysym.sym==SDLK_PLUS || e.key.keysym.sym==SDLK_KP_PLUS){ret=PREVIEW_ZOOMP;}
+      else if(e.key.keysym.sym==SDLK_MINUS || e.key.keysym.sym==SDLK_KP_MINUS){ret=PREVIEW_ZOOMM;}
+      else if(e.key.keysym.sym==SDLK_q){return PREVIEW_QUIT;}
+    }
+  }
+  SDL_Delay(10);
+  return ret;
+}
+
+struct img* gtool_preview_sdl(int width, int height, const char* filename)
+{
+  SDL_Init(SDL_INIT_VIDEO);
+  struct img_sdl* img=malloc(sizeof(struct img_sdl));
+  memset(img, 0, sizeof(struct img_sdl));
+  if(!width){width=800;} // Make up sizes if not specified
+  if(!height){height=600;}
+  img->img.width=width;
+  img->img.height=height;
+  // Set up SDL window
+  img->win=SDL_CreateWindow("gcodetool preview", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN);
+  if(!img->win){free(img); return 0;}
+  img->renderer=SDL_CreateRenderer(img->win, -1, SDL_RENDERER_SOFTWARE); // Software-only is faster the way we're doing it
+  if(!img->renderer){SDL_DestroyWindow(img->win); free(img); return 0;}
+  SDL_RenderClear(img->renderer);
+  // Set callbacks
+  img->img.drawline=cb_drawline;
+  img->img.clear=cb_clear;
+  img->img.display=cb_display;
+  img->img.input=cb_input;
+  img->img.clean=cb_clean;
+  return (struct img*)img;
+}
diff --git a/preview_term.c b/preview_term.c
index c11f5d3..c491ea9 100644
--- a/preview_term.c
+++ b/preview_term.c
@@ -15,6 +15,7 @@
     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
+#ifndef WIN32
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -108,6 +109,7 @@ static void cb_clean(struct img* imgp)
 struct img* gtool_preview_term(int width, int height, const char* filename)
 {
   struct img_term* img=malloc(sizeof(struct img_term));
+  memset(img, 0, sizeof(struct img_term));
   if(filename && strcmp(filename, "-"))
   {
     img->fd=open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0644);
@@ -142,3 +144,4 @@ struct img* gtool_preview_term(int width, int height, const char* filename)
   img->img.clean=cb_clean;
   return (struct img*)img;
 }
+#endif