$ git clone https://librecgm.ion.nu/librecgm.git
commit 2da2264ab4ad67ebf6fac9f691c218ff5cb92ea2
Author: Alicia <...>
Date:   Sat Sep 25 23:47:10 2021 +0200

    Made libraries optional.

diff --git a/Makefile b/Makefile
index be66e6f..0d76203 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,23 @@
 PREFIX=/usr
 GLADEPATH=$(PREFIX)/share/librecgm/gui.glade
-LIBS=$(shell pkg-config --libs gdk-pixbuf-2.0 libv4l2 gtk+-3.0 freetype2 fontconfig)
-CFLAGS=$(shell pkg-config --cflags gdk-pixbuf-2.0 libv4l2 gtk+-3.0 freetype2 fontconfig) -g3 -Wall -Wextra -DGLADEPATH='"$(GLADEPATH)"'
+CFLAGS=-g3 -Wall -Wextra -DGLADEPATH='"$(GLADEPATH)"'
 OBJ=findrect.o img.o read.o main.o cam.o gui.o data.o
 
+optionallibs=$(shell pkg-config --libs $(1) 2> /dev/null)
+optionalcflags=$(shell pkg-config --cflags $(1) 2> /dev/null && echo '-DENABLE_$(2)' || echo 'Warning: building without $(3) due to missing one or more of $(1)' >&2)
+# Labels
+LIBS+=$(call optionallibs,freetype2 fontconfig)
+CFLAGS+=$(call optionalcflags,freetype2 fontconfig,LABELS,labels)
+# Webcam
+LIBS+=$(call optionallibs,libv4l2)
+CFLAGS+=$(call optionalcflags,libv4l2,V4L2,webcam support)
+# GUI
+LIBS+=$(call optionallibs,gtk+-3.0)
+CFLAGS+=$(call optionalcflags,gtk+-3.0,GUI,GUI)
+# Image formats (beyond PNM)
+LIBS+=$(call optionallibs,gdk-pixbuf-2.0)
+CFLAGS+=$(call optionalcflags,gdk-pixbuf-2.0,GDKPIXBUF,support for more image formats)
+
 librecgm: $(OBJ)
  $(CC) $^ $(LIBS) -o $@
 
diff --git a/cam.c b/cam.c
index 144a3ff..a161938 100644
--- a/cam.c
+++ b/cam.c
@@ -16,15 +16,19 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 #include <stdlib.h>
+#include <string.h>
 #include <fcntl.h>
+#ifdef ENABLE_V4L2
 #include <linux/videodev2.h>
 #include <libv4l2.h>
+#endif
 #include "img.h"
 #include "cam.h"
 
 struct cam* cam_init(const char* dev)
 {
   struct cam* cam=malloc(sizeof(struct cam));
+#ifdef ENABLE_V4L2
   int fd=v4l2_open(dev, O_RDWR);
   struct v4l2_format fmt;
   fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
@@ -39,22 +43,34 @@ struct cam* cam_init(const char* dev)
   cam->fd=fd;
   cam->w=fmt.fmt.pix.width;
   cam->h=fmt.fmt.pix.height;
+#else
+  // Bogus values to prevent division by 0
+  cam->w=16;
+  cam->h=16;
+#endif
   return cam;
 }
 
 void cam_read(struct cam* cam, void* pixels)
 {
+#ifdef ENABLE_V4L2
   v4l2_read(cam->fd, pixels, cam->w*cam->h*3);
+#else
+  memset(pixels, 128, 16*16*3);
+#endif
 }
 
 void cam_free(struct cam* cam)
 {
+#ifdef ENABLE_V4L2
   v4l2_close(cam->fd);
   free(cam);
+#endif
 }
 
 void cam_grab(struct img* img, const char* dev)
 {
+#ifdef ENABLE_V4L2
   struct cam* cam=cam_init(dev);
   img->w=cam->w;
   img->h=cam->h;
@@ -69,4 +85,5 @@ void cam_grab(struct img* img, const char* dev)
     cam_read(cam, img->pixels);
   }
   cam_free(cam);
+#endif
 }
diff --git a/findrect.c b/findrect.c
index a147ee0..474c899 100644
--- a/findrect.c
+++ b/findrect.c
@@ -129,7 +129,7 @@ static int area(int* rect)
 
 int* findrect(struct img* img)
 {
-  static int rect[8];
+  static int rect[8]={0};
   int x,y;
   int min=255*3, max=0;
   for(y=0; y<img->h; y++)
@@ -144,7 +144,7 @@ int* findrect(struct img* img)
   }
   int edge=(min*2+max)/3; // Median color value to determine if a pixel hits the screen (except a little lower which gives more accurate results)
   int i;
-  int drect[8];
+  int drect[8]={0};
   for(i=0; i<4; i++)
   {
     if(!search(img, &rect[i*2], edge, i)){printf("Failed to find corner %i\n", i);}
diff --git a/gui.c b/gui.c
index 54a8ad0..8e59e17 100644
--- a/gui.c
+++ b/gui.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/>.
 */
+#ifdef ENABLE_GUI
 #include <stdio.h>
 #include <gtk/gtk.h>
 #include "cam.h"
@@ -259,3 +260,4 @@ void gui_run(int** onlygrab, int unitmax)
   g_timeout_add_full(G_PRIORITY_LOW, 1000/10, G_SOURCE_FUNC(updatecam), onlygrab, 0);
   gtk_main();
 }
+#endif
diff --git a/img.c b/img.c
index caac43b..1555851 100644
--- a/img.c
+++ b/img.c
@@ -18,9 +18,11 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
-#include <ft2build.h>
-#include FT_FREETYPE_H
-#include <fontconfig/fontconfig.h>
+#ifdef ENABLE_LABELS
+  #include <ft2build.h>
+  #include FT_FREETYPE_H
+  #include <fontconfig/fontconfig.h>
+#endif
 #include "img.h"
 
 void createimg(struct img* img, int w, int h)
@@ -89,8 +91,39 @@ void writepnm(struct img* img, const char* name)
   fclose(f);
 }
 
+void readpnm(struct img* img, const char* name)
+{
+  FILE* f=fopen(name, "r");
+  char head[32];
+  fgets(head, 32, f);
+  if(head[0]!='P' || head[1]!='6' || (head[2]!='\r' && head[2]!='\n'))
+  {
+    fprintf(stderr, "Unsupported PNM image format. Only 'P6' binary PNM is supported in LibreCGM built without gdk-pixbuf support.\n");
+    return;
+  }
+  while(fgets(head, 32, f)){if(head[0]!='#'){break;}}
+  char* space;
+  int w=strtol(head, &space, 0);
+  if(!space || space[0]!=' ')
+  {
+    fprintf(stderr, "Invalid PNM, expected width and height separated by space\n");
+    return;
+  }
+  int h=strtol(&space[1], 0, 0);
+  while(fgets(head, 32, f)){if(head[0]!='#'){break;}}
+  if(strncmp(head, "255", 3))
+  {
+    fprintf(stderr, "Only 24-bit colors supported by builtin PNM reader (expected '255', got '%s')\n", head);
+    return;
+  }
+  createimg(img, w, h);
+  fread(img->pixels, img->rowstride, img->h, f);
+  fclose(f);
+}
+
 static void text(struct img* img, int x, int y, const char* text)
 {
+#ifdef ENABLE_LABELS
   static FT_Library ft=0;
   if(!ft && FT_Init_FreeType(&ft))
   {
@@ -150,6 +183,7 @@ static void text(struct img* img, int x, int y, const char* text)
     x+=face->glyph->advance.x/64;
     y+=face->glyph->advance.y/64;
   }
+#endif
 }
 
 void drawgraph(struct img* img, int h, int listlen, int* list)
diff --git a/img.h b/img.h
index 6fdea4f..b275a17 100644
--- a/img.h
+++ b/img.h
@@ -26,5 +26,6 @@ struct img
 extern void createimg(struct img* img, int w, int h);
 extern void drawline(struct img* img, int* start, int* end, int color);
 extern void writepnm(struct img* img, const char* name);
+extern void readpnm(struct img* img, const char* name);
 extern void drawgraph(struct img* img, int h, int listlen, int* list);
 #define pixel(img,x,y) (&(img)->pixels[(y)*(img)->rowstride+(x)*(img)->pixstride])
diff --git a/main.c b/main.c
index 04eaca9..85a3b90 100644
--- a/main.c
+++ b/main.c
@@ -15,7 +15,12 @@
     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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef ENABLE_GDKPIXBUF
 #include <gdk-pixbuf/gdk-pixbuf.h>
+#endif
 #include "img.h"
 #include "data.h"
 extern int* readgraph(struct img* img, int* rect, int max);
@@ -34,7 +39,9 @@ static void helptext(void)
   printf("Usage: librecgm [options]\n\n"
          "Valid options:\n"
          "Inputs: (specifying one of these implies --nogui)\n"
+#ifdef ENABLE_V4L2
          "--cam <device>     = Scan values from the given V4L2 device (e.g. /dev/video0)\n"
+#endif
          "--img <image file> = Scan values from image file\n"
          "--data <file>      = Load blood sugar values from file\n\n"
          "Outputs:\n"
@@ -43,7 +50,9 @@ static void helptext(void)
          "                       (written to stdout by default)\n\n"
          "--mgdl             = Use mg/dL units instead of mmol/L\n"
          "--nogui            = Do not use the GUI\n"
-         "-h/--help          = Show this help text\n");
+         "-h/--help          = Show this help text\n\n"
+         "LibreCGM is free/libre software licensed under the GNU Affero General Public License version 3.\n"
+         "For more information see https://librecgm.ion.nu\n");
 }
 
 int main(int argc, char** argv)
@@ -71,22 +80,32 @@ int main(int argc, char** argv)
   int listlen=283;
   if(gui)
   {
+  #ifdef ENABLE_GUI
     char onlygrab=out_data||out_graph;
     gui_run(onlygrab?&list:0, unitmax);
     if(!onlygrab){return 0;}
+  #else
+    fprintf(stderr, "This copy of LibreCGM was built without GUI support. See the help text (run 'librecgm --help') for further information.\nResuming in commandline mode.\n");
+  #endif
   }
 
   struct img img={0};
   if(!list)switch(inputtype)
   {
   case in_v4l2:
+  #ifdef ENABLE_V4L2
     cam_grab(&img, input);
     list=readgraph(&img, 0, unitmax*10);
 //  if(img.pixels){writepnm(&img, "read.pnm");}
     free(img.pixels);
     break;
+  #else
+    fprintf(stderr, "This copy of LibreCGM was built without webcam support. Please specify an image file instead.\nSee the help text (run 'librecgm --help') for further information.\n");
+    return 1;
+  #endif
   case in_img:
     {
+    #ifdef ENABLE_GDKPIXBUF
       GdkPixbuf* img_=gdk_pixbuf_new_from_file(input, 0);
       if(!img_){printf("Failed to open image '%s'\n", input); return 1;}
       img.pixels=gdk_pixbuf_get_pixels(img_);
@@ -97,6 +116,12 @@ int main(int argc, char** argv)
       list=readgraph(&img, 0, unitmax*10);
 //  if(img.pixels){writepnm(&img, "read.pnm");}
       g_object_unref(img_);
+    #else
+      fprintf(stderr, "This copy of LibreCGM was built without gdk-pixbuf for support for additional image formats. Attempting to load as PNM.\n");
+      readpnm(&img, input);
+      if(!img.pixels){return 1;}
+      list=readgraph(&img, 0, unitmax*10);
+    #endif
     }
     break;
   case in_data:
@@ -115,26 +140,41 @@ int main(int argc, char** argv)
   // Output: graph
   if(out_graph)
   {
+  #ifdef ENABLE_GDKPIXBUF
     GdkPixbuf* img_=gdk_pixbuf_new(GDK_COLORSPACE_RGB, 0, 8, 800, 600);
     img.pixels=gdk_pixbuf_get_pixels(img_);
     img.w=gdk_pixbuf_get_width(img_);
     img.h=gdk_pixbuf_get_height(img_);
     img.rowstride=gdk_pixbuf_get_rowstride(img_);
     img.pixstride=gdk_pixbuf_get_n_channels(img_);
+  #else
+    fprintf(stderr, "This copy of LibreCGM was built without gdk-pixbuf for support for additional image formats. Writing PNM image.\n");
+    createimg(&img, 800, 600);
+  #endif
     memset(img.pixels, 255, img.h*img.rowstride); // Clear to white
-//    drawgraph(&img, 210, listlen, list);
     drawgraph(&img, unitmax*10, listlen, list);
     char* ext=strrchr(out_graph, '.');
     if(!ext) // Default to PNG
     {
       char path2[strlen(out_graph)+strlen(".png0")];
+    #ifdef ENABLE_GDKPIXBUF
       sprintf(path2, "%s.png", out_graph);
       gdk_pixbuf_save(img_, path2, "png", 0, (void*)0);
+    #else
+      sprintf(path2, "%s.pnm", out_graph);
+      writepnm(&img, path2);
+    #endif
     }else{
+    #ifdef ENABLE_GDKPIXBUF
       ext=&ext[1];
-      gdk_pixbuf_save(img_, out_graph, ext, 0, (void*)0);
+      if(!gdk_pixbuf_save(img_, out_graph, ext, 0, (void*)0) && !strcasecmp(ext, "pnm")){writepnm(&img, out_graph);}
+    #else
+      writepnm(&img, out_graph);
+    #endif
     }
+    #ifdef ENABLE_GDKPIXBUF
     g_object_unref(img_);
+    #endif
   }
   // Cleanup
   free(list);