$ git clone http://tcclient.ion.nu/tc_client.git
commit 6bec5cc9bfc377a99f855b48623caa7ebe2e716b
Author: Alicia <...>
Date:   Mon Sep 26 14:28:26 2016 +0200

    tc_client-gtk: changed the greenscreen postprocessor to use libcamera for the background, allowing you to use either another camera or an image through the virtual "Image" camera as background.

diff --git a/ChangeLog b/ChangeLog
index b7ae2d8..232150f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,7 @@ tc_client-gtk: added GTK+2 compatibility code related to the greenscreen postpro
 tc_client-gtk: changed camera input for broadcasting from using a thread to using g_timeout.
 tc_client-gtk: use the camera's own copy of its ID for g_timeout_add() rather than what camera_new() was called with. And in configure, include libavutil/mem.h if we fall back on av_freep()
 tc_client-gtk: cleaned up leftover windows compatibility code that is no longer necessary.
+tc_client-gtk: changed the greenscreen postprocessor to use libcamera for the background, allowing you to use either another camera or an image through the virtual "Image" camera as background.
 tc_client-gtk and camviewer: updated to libavcodec's avcodec_{send,receive}_{frame,packet} API.
 0.39:
 Added a /closecam command to stop receiving a cam stream.
diff --git a/gtkgui.glade b/gtkgui.glade
index 3018d71..141560f 100644
--- a/gtkgui.glade
+++ b/gtkgui.glade
@@ -1369,11 +1369,13 @@
                 <property name="can_focus">False</property>
                 <property name="orientation">vertical</property>
                 <child>
-                  <object class="GtkFileChooserButton" id="greenscreen_filechooser">
+                  <object class="GtkButton" id="greenscreenbutton">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="create_folders">False</property>
-                    <property name="title" translatable="yes">Select background</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <child>
+                      <placeholder/>
+                    </child>
                   </object>
                   <packing>
                     <property name="expand">False</property>
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index d978bbe..90691e7 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -1046,7 +1046,7 @@ int main(int argc, char** argv)
   g_signal_connect(gtk_builder_get_object(gui, "camcolors_auto"), "toggled", G_CALLBACK(camcolors_toggle_auto), 0);
   g_signal_connect(gtk_builder_get_object(gui, "camcolors_flip_horizontal"), "toggled", G_CALLBACK(camcolors_toggle_flip), 0);
   g_signal_connect(gtk_builder_get_object(gui, "camcolors_flip_vertical"), "toggled", G_CALLBACK(camcolors_toggle_flip), (void*)1);
-  g_signal_connect(gtk_builder_get_object(gui, "greenscreen_filechooser"), "file-set", G_CALLBACK(gui_set_greenscreen_img), 0);
+  g_signal_connect(gtk_builder_get_object(gui, "greenscreenbutton"), "clicked", G_CALLBACK(gui_set_greenscreen_img), 0);
   g_signal_connect(gtk_builder_get_object(gui, "greenscreen_colorpicker"), "color-set", G_CALLBACK(gui_set_greenscreen_color), 0);
   g_signal_connect(gtk_builder_get_object(gui, "greenscreen_tolerance_h"), "value-changed", G_CALLBACK(gui_set_greenscreen_tolerance), 0);
   g_signal_connect(gtk_builder_get_object(gui, "greenscreen_tolerance_s"), "value-changed", G_CALLBACK(gui_set_greenscreen_tolerance), (void*)1);
diff --git a/utilities/gtk/gui.c b/utilities/gtk/gui.c
index 0546891..ca4cbe8 100644
--- a/utilities/gtk/gui.c
+++ b/utilities/gtk/gui.c
@@ -28,6 +28,9 @@
 extern void startsession(GtkButton* button, void* x);
 extern int tc_client_in[2];
 GtkBuilder* gui;
+GtkWidget* gui_greenscreen_preview_img=0;
+unsigned int gui_greenscreen_preview_event=0;
+extern gboolean gui_greenscreen_preview(void* x);
 
 char autoscroll_before(GtkAdjustment* scroll)
 {
@@ -484,7 +487,6 @@ void gui_show_camcolors(GtkMenuItem* menuitem, void* x)
 {
   struct camera* cam=camera_find(menu_context_cam);
   if(!cam){return;}
-  gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(gui, "cam_colors")));
   // Set brightness controls
   GtkAdjustment* adjustment=GTK_ADJUSTMENT(gtk_builder_get_object(gui, "camcolors_min_brightness"));
   gtk_adjustment_set_value(adjustment, cam->postproc.min_brightness);
@@ -495,7 +497,22 @@ void gui_show_camcolors(GtkMenuItem* menuitem, void* x)
   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(gui, "camcolors_flip_horizontal")), cam->postproc.flip_horizontal);
   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(gui, "camcolors_flip_vertical")), cam->postproc.flip_vertical);
   // Greenscreen controls
-  gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(gtk_builder_get_object(gui, "greenscreen_filechooser")), cam->postproc.greenscreen_filename);
+  // Set the appropriate child widget for the greenscreenbutton and start the preview if applicable
+  GtkWidget* button=GTK_WIDGET(gtk_builder_get_object(gui, "greenscreenbutton"));
+  GtkWidget* child=gtk_bin_get_child(GTK_BIN(button));
+  if(child){gtk_widget_destroy(child);}
+  if(cam->postproc.greenscreen)
+  {
+    gui_greenscreen_preview_img=gtk_image_new();
+    gtk_container_add(GTK_CONTAINER(button), gui_greenscreen_preview_img);
+    if(!gui_greenscreen_preview_event)
+    {
+      gui_greenscreen_preview_event=g_timeout_add(100, gui_greenscreen_preview, 0);
+    }
+  }else{
+    gui_greenscreen_preview_img=0;
+    gtk_container_add(GTK_CONTAINER(button), gtk_label_new("Choose a greenscreen background"));
+  }
   GdkRGBA color={.red=(double)cam->postproc.greenscreen_color[0]/255,
                   .green=(double)cam->postproc.greenscreen_color[1]/255,
                   .blue=(double)cam->postproc.greenscreen_color[2]/255,
@@ -504,6 +521,8 @@ void gui_show_camcolors(GtkMenuItem* menuitem, void* x)
   gtk_adjustment_set_value(GTK_ADJUSTMENT(gtk_builder_get_object(gui, "greenscreen_tolerance_h")), cam->postproc.greenscreen_tolerance[0]);
   gtk_adjustment_set_value(GTK_ADJUSTMENT(gtk_builder_get_object(gui, "greenscreen_tolerance_s")), cam->postproc.greenscreen_tolerance[1]);
   gtk_adjustment_set_value(GTK_ADJUSTMENT(gtk_builder_get_object(gui, "greenscreen_tolerance_v")), cam->postproc.greenscreen_tolerance[2]);
+
+  gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(gui, "cam_colors")));
 }
 
 void camcolors_adjust_min(GtkAdjustment* adjustment, void* x)
@@ -565,17 +584,76 @@ void gui_hide_cam(GtkMenuItem* menuitem, void* x)
   camera_remove(menu_context_cam, 0);
 }
 
-void gui_set_greenscreen_img(GtkFileChooserButton* button, void* x)
+gboolean gui_greenscreen_preview(void* x)
+{
+  struct camera* cam;
+  if(!gui_greenscreen_preview_img || !menu_context_cam ||
+     !(cam=camera_find(menu_context_cam)) || !cam->postproc.greenscreen)
+  {
+    gui_greenscreen_preview_event=0;
+    return G_SOURCE_REMOVE;
+  }
+  GdkPixbuf* oldpixbuf=gtk_image_get_pixbuf(GTK_IMAGE(gui_greenscreen_preview_img));
+  GdkPixbuf* gdkframe=scaled_gdk_pixbuf_from_cam(cam->postproc.greenscreen, cam->postproc.greenscreen_size.width, cam->postproc.greenscreen_size.height, 160, 120);
+  gtk_image_set_from_pixbuf(GTK_IMAGE(gui_greenscreen_preview_img), gdkframe);
+  if(oldpixbuf){g_object_unref(oldpixbuf);}
+  return G_SOURCE_CONTINUE;
+}
+
+void gui_set_greenscreen_img_accept(CAM* img)
+{
+  if(!menu_context_cam){cam_close(img); return;}
+  struct camera* cam=camera_find(menu_context_cam);
+  if(!cam){cam_close(img); return;}
+  if(cam->postproc.greenscreen){cam_close(cam->postproc.greenscreen);}
+  cam->postproc.greenscreen=img;
+  cam->postproc.greenscreen_size.width=340;
+  cam->postproc.greenscreen_size.height=240;
+  cam_resolution(img, &cam->postproc.greenscreen_size.width, &cam->postproc.greenscreen_size.height);
+  GtkWidget* button=GTK_WIDGET(gtk_builder_get_object(gui, "greenscreenbutton"));
+  GtkWidget* child=gtk_bin_get_child(GTK_BIN(button));
+  if(child){gtk_widget_destroy(child);}
+  // Add a GtkImage to the button and start a g_timeout to show a preview on it
+  gui_greenscreen_preview_img=gtk_image_new();
+  gtk_container_add(GTK_CONTAINER(button), gui_greenscreen_preview_img);
+  gtk_widget_show(gui_greenscreen_preview_img);
+  if(!gui_greenscreen_preview_event)
+  {
+    gui_greenscreen_preview_event=g_timeout_add(100, gui_greenscreen_preview, 0);
+  }
+}
+
+void gui_set_greenscreen_img_cancel(void)
+{
+  if(gui_greenscreen_preview_event)
+  {
+    g_source_remove(gui_greenscreen_preview_event);
+    gui_greenscreen_preview_event=0;
+  }
+  GtkWidget* button=GTK_WIDGET(gtk_builder_get_object(gui, "greenscreenbutton"));
+  GtkWidget* child=gtk_bin_get_child(GTK_BIN(button));
+  if(child){gtk_widget_destroy(child);}
+  gui_greenscreen_preview_img=0;
+  GtkWidget* label=gtk_label_new("Choose a greenscreen background");
+  gtk_container_add(GTK_CONTAINER(button), label);
+  gtk_widget_show(label);
+  // Remove from camera
+  if(!menu_context_cam){return;}
+  struct camera* cam=camera_find(menu_context_cam);
+  if(!cam){return;}
+  if(cam->postproc.greenscreen)
+  {
+    cam_close(cam->postproc.greenscreen);
+    cam->postproc.greenscreen=0;
+  }
+}
+
+void gui_set_greenscreen_img(GtkButton* button, void* x)
 {
   if(!menu_context_cam){return;}
   struct camera* cam=camera_find(menu_context_cam);
   if(!cam){return;}
-  gchar* file=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(button));
-  if(cam->postproc.greenscreen){img_free(cam->postproc.greenscreen);}
-  free((void*)cam->postproc.greenscreen_filename);
-  cam->postproc.greenscreen=img_load(file);
-  cam->postproc.greenscreen_filename=strdup(file);
-  g_free(file);
+  camselect_open(gui_set_greenscreen_img_accept, gui_set_greenscreen_img_cancel);
 }
 
 void gui_set_greenscreen_color(GtkColorButton* button, void* x)
diff --git a/utilities/gtk/gui.h b/utilities/gtk/gui.h
index 281c189..3b63f62 100644
--- a/utilities/gtk/gui.h
+++ b/utilities/gtk/gui.h
@@ -45,7 +45,7 @@ extern void camcolors_adjust_max(GtkAdjustment* adjustment, void* x);
 extern void camcolors_toggle_auto(GtkToggleButton* button, void* x);
 extern void camcolors_toggle_flip(GtkToggleButton* button, void* vertical);
 extern void gui_hide_cam(GtkMenuItem* menuitem, void* x);
-extern void gui_set_greenscreen_img(GtkFileChooserButton* button, void* x);
+extern void gui_set_greenscreen_img(GtkButton* button, void* x);
 extern void gui_set_greenscreen_color(GtkColorButton* button, void* x);
 extern void gui_set_greenscreen_tolerance(GtkAdjustment* adjustment, void* x);
 
diff --git a/utilities/gtk/media.c b/utilities/gtk/media.c
index 36b39ef..ff95847 100644
--- a/utilities/gtk/media.c
+++ b/utilities/gtk/media.c
@@ -270,21 +270,8 @@ struct
 gboolean camselect_frame(void* x)
 {
   if(!camselect.current){return G_SOURCE_CONTINUE;}
-  void* buf=malloc(camselect.size.width*camselect.size.height*3);
-  cam_getframe(camselect.current, buf);
   GdkPixbuf* oldpixbuf=gtk_image_get_pixbuf(GTK_IMAGE(campreview.cam));
-  GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB, 0, 8, camselect.size.width, camselect.size.height, camselect.size.width*3, 0, freebuffer);
-  if(gdk_pixbuf_get_height(gdkframe)>PREVIEW_MAX_HEIGHT || gdk_pixbuf_get_width(gdkframe)>PREVIEW_MAX_WIDTH) // Scale if the input is huge
-  {
-    unsigned int width=gdk_pixbuf_get_width(gdkframe);
-    unsigned int height=gdk_pixbuf_get_height(gdkframe);
-    if(height*PREVIEW_MAX_WIDTH/width>PREVIEW_MAX_HEIGHT)
-    {
-      gdkframe=gdk_pixbuf_scale_simple(gdkframe, width*PREVIEW_MAX_HEIGHT/height, PREVIEW_MAX_HEIGHT, GDK_INTERP_BILINEAR);
-    }else{
-      gdkframe=gdk_pixbuf_scale_simple(gdkframe, PREVIEW_MAX_WIDTH, height*PREVIEW_MAX_WIDTH/width, GDK_INTERP_BILINEAR);
-    }
-  }
+  GdkPixbuf* gdkframe=scaled_gdk_pixbuf_from_cam(camselect.current, camselect.size.width, camselect.size.height, PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT);
   gtk_image_set_from_pixbuf(GTK_IMAGE(campreview.cam), gdkframe);
   g_object_unref(oldpixbuf);
   return G_SOURCE_CONTINUE;
@@ -300,7 +287,7 @@ void camselect_open(void(*cb)(CAM*), void(*ccb)(void))
     camselect.eventsource=g_timeout_add(100, camselect_frame, 0);
   }
   // Open the currently selected camera (usually starting with the top alternative)
-  if(camselect.current){cam_close(camselect.current);}
+  if(camselect.current){cam_close(camselect.current); camselect.current=0;}
   GtkComboBox* combo=GTK_COMBO_BOX(gtk_builder_get_object(gui, "camselect_combo"));
   camselect_change(combo, 0);
   GtkWidget* window=GTK_WIDGET(gtk_builder_get_object(gui, "camselection"));
@@ -468,3 +455,22 @@ gboolean camplaceholder_update(void* id)
   g_object_unref(oldpixbuf);
   return G_SOURCE_CONTINUE;
 }
+
+GdkPixbuf* scaled_gdk_pixbuf_from_cam(CAM* cam, unsigned int width, unsigned int height, unsigned int maxwidth, unsigned int maxheight)
+{
+  void* buf=malloc(width*height*3);
+  cam_getframe(cam, buf);
+  GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(buf, GDK_COLORSPACE_RGB, 0, 8, width, height, width*3, 0, freebuffer);
+  if(height>maxheight || width>maxwidth) // Scale if the input is huge
+  {
+    GdkPixbuf* oldframe=gdkframe;
+    if(height*maxwidth/width>maxheight)
+    {
+      gdkframe=gdk_pixbuf_scale_simple(gdkframe, width*maxheight/height, maxheight, GDK_INTERP_BILINEAR);
+    }else{
+      gdkframe=gdk_pixbuf_scale_simple(gdkframe, maxwidth, height*maxwidth/width, GDK_INTERP_BILINEAR);
+    }
+    g_object_unref(oldframe);
+  }
+  return gdkframe;
+}
diff --git a/utilities/gtk/media.h b/utilities/gtk/media.h
index 788b36b..913022f 100644
--- a/utilities/gtk/media.h
+++ b/utilities/gtk/media.h
@@ -67,3 +67,4 @@ extern const char* camselect_file(void);
 extern void camera_postproc(struct camera* cam, unsigned char* buf, unsigned int width, unsigned int height);
 extern void updatescaling(unsigned int width, unsigned int height, char changedcams);
 extern gboolean camplaceholder_update(void* id);
+extern GdkPixbuf* scaled_gdk_pixbuf_from_cam(CAM* cam, unsigned int width, unsigned int height, unsigned int maxwidth, unsigned int maxheight);
diff --git a/utilities/gtk/postproc.c b/utilities/gtk/postproc.c
index 625f31a..dcadafc 100644
--- a/utilities/gtk/postproc.c
+++ b/utilities/gtk/postproc.c
@@ -19,69 +19,6 @@
 #include <gdk-pixbuf/gdk-pixbuf.h>
 #include "postproc.h"
 
-// TODO: Replace this img_* API with libcamera
-struct img* img_load(const char* filename)
-{
-  struct img* this=malloc(sizeof(struct img));
-  this->animation=gdk_pixbuf_animation_new_from_file(filename, 0);
-  if(gdk_pixbuf_animation_is_static_image(this->animation))
-  {
-    this->img=gdk_pixbuf_animation_get_static_image(this->animation);
-    this->iter=0;
-    if(gdk_pixbuf_get_n_channels(this->img)==4)
-    {
-      unsigned char* pixels=gdk_pixbuf_get_pixels(this->img);
-      unsigned int pixelcount=gdk_pixbuf_get_width(this->img)*gdk_pixbuf_get_height(this->img);
-      unsigned int i;
-      for(i=0; i<pixelcount; ++i)
-      {
-        memmove(&pixels[i*3], &pixels[i*4], 3);
-      }
-    }
-  }else{
-    this->iter=gdk_pixbuf_animation_get_iter(this->animation, 0);
-    unsigned int w=gdk_pixbuf_animation_get_width(this->animation);
-    unsigned int h=gdk_pixbuf_animation_get_height(this->animation);
-    this->img=gdk_pixbuf_new(GDK_COLORSPACE_RGB, 0, 8, w, h);
-  }
-  return this;
-}
-
-GdkPixbuf* img_get(struct img* this)
-{
-  if(this->iter)
-  {
-    gdk_pixbuf_animation_iter_advance(this->iter, 0);
-    GdkPixbuf* img=gdk_pixbuf_animation_iter_get_pixbuf(this->iter);
-    if(gdk_pixbuf_get_n_channels(img)!=4){return img;}
-    unsigned int w=gdk_pixbuf_get_width(img);
-    unsigned int h=gdk_pixbuf_get_height(img);
-    unsigned int dstw=gdk_pixbuf_get_width(this->img);
-    unsigned int dsth=gdk_pixbuf_get_height(this->img);
-    unsigned char* pixels=gdk_pixbuf_get_pixels(img);
-    unsigned char* dstpixels=gdk_pixbuf_get_pixels(this->img);
-    unsigned int x, y;
-    for(y=0; y<h && y<dsth; ++y)
-    for(x=0; x<w && x<dstw; ++x)
-    {
-      memmove(&dstpixels[(y*dstw+x)*3], &pixels[(y*w+x)*4], 3);
-    }
-  }
-  return this->img;
-}
-
-void img_free(struct img* this)
-{
-  if(this->iter)
-  {
-    g_object_unref(this->iter);
-    g_object_unref(this->animation);
-    g_object_unref(this->img);
-  }else{
-    g_object_unref(this->animation);
-  }
-}
-
 #define max(a,b) ((a)>(b)?(a):(b))
 #define min(a,b) ((a)<(b)?(a):(b))
 
@@ -112,7 +49,6 @@ void postproc_init(struct postproc_ctx* pp)
   pp->greenscreen_color[0]=0;
   pp->greenscreen_color[1]=255;
   pp->greenscreen_color[2]=0;
-  pp->greenscreen_filename=0;
 }
 
 void postprocess(struct postproc_ctx* pp, unsigned char* buf, unsigned int width, unsigned int height)
@@ -169,12 +105,12 @@ void postprocess(struct postproc_ctx* pp, unsigned char* buf, unsigned int width
   }
   if(pp->greenscreen)
   {
-    GdkPixbuf* img=img_get(pp->greenscreen);
-    guchar* pixels=gdk_pixbuf_get_pixels(img);
+    unsigned char pixels[pp->greenscreen_size.width*pp->greenscreen_size.height*3];
+    cam_getframe(pp->greenscreen, pixels);
     unsigned int x;
     unsigned int y;
-    unsigned int gswidth=gdk_pixbuf_get_width(img);
-    unsigned int gsheight=gdk_pixbuf_get_height(img);
+    unsigned int gswidth=pp->greenscreen_size.width;
+    unsigned int gsheight=pp->greenscreen_size.height;
     unsigned int gsx, gsy;
     unsigned char* gscolor=pp->greenscreen_hsv;
     for(y=0; y<height; ++y)
@@ -199,6 +135,5 @@ void postprocess(struct postproc_ctx* pp, unsigned char* buf, unsigned int width
 
 void postproc_free(struct postproc_ctx* pp)
 {
-  if(pp->greenscreen){img_free(pp->greenscreen);}
-  free((void*)pp->greenscreen_filename);
+  if(pp->greenscreen){cam_close(pp->greenscreen);}
 }
diff --git a/utilities/gtk/postproc.h b/utilities/gtk/postproc.h
index 2d52fd0..cfc9736 100644
--- a/utilities/gtk/postproc.h
+++ b/utilities/gtk/postproc.h
@@ -14,12 +14,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/>.
 */
-struct img
-{
-  GdkPixbufAnimation* animation;
-  GdkPixbufAnimationIter* iter;
-  GdkPixbuf* img;
-};
+#include "../libcamera/camera.h"
 
 struct postproc_ctx
 {
@@ -28,16 +23,13 @@ struct postproc_ctx
   char autoadjust;
   char flip_horizontal;
   char flip_vertical;
-  struct img* greenscreen;
+  CAM* greenscreen;
   int greenscreen_tolerance[3];
   unsigned char greenscreen_color[3];
   unsigned char greenscreen_hsv[3];
-  const char* greenscreen_filename;
+  struct{unsigned int width;unsigned int height;} greenscreen_size;
 };
 
-extern struct img* img_load(const char* filename);
-extern GdkPixbuf* img_get(struct img* this);
-extern void img_free(struct img* this);
 extern void rgb_to_hsv(int r, int g, int b, unsigned char* h, unsigned char* s, unsigned char* v);
 extern void postproc_init(struct postproc_ctx* pp);
 extern void postprocess(struct postproc_ctx* pp, unsigned char* buf, unsigned int width, unsigned int height);