$ git clone http://tcclient.ion.nu/tc_client.git
commit 212de87c85c09bf1201f558038346fa46c6ecbc3
Author: Alicia <...>
Date:   Thu Sep 22 18:21:37 2016 +0200

    tc_client-gtk: added greenscreen postprocessor.

diff --git a/ChangeLog b/ChangeLog
index e124c21..043c7f1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,7 @@
 0.40:
 tc_client-gtk: merged camera_remove() and camera_removebynick() into a single function, merged the deallocation of camera data into camera_free()
 tc_client-gtk: moved the postprocessing code into its own source file.
+tc_client-gtk: added greenscreen postprocessor.
 0.39:
 Added a /closecam command to stop receiving a cam stream.
 Use uintX_t for endianness functions instead of unsigned long*x int.
diff --git a/gtkgui.glade b/gtkgui.glade
index dc2df3f..3018d71 100644
--- a/gtkgui.glade
+++ b/gtkgui.glade
@@ -912,6 +912,21 @@
       </object>
     </child>
   </object>
+  <object class="GtkAdjustment" id="greenscreen_tolerance_h">
+    <property name="upper">255</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="greenscreen_tolerance_s">
+    <property name="upper">255</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="greenscreen_tolerance_v">
+    <property name="upper">255</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
   <object class="GtkImage" id="image1">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
@@ -1343,6 +1358,178 @@
             <property name="position">7</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkExpander" id="expander2">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="resize_toplevel">True</property>
+            <child>
+              <object class="GtkBox" id="box14">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkFileChooserButton" id="greenscreen_filechooser">
+                    <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>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="box15">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkLabel" id="label28">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Green:</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkColorButton" id="greenscreen_colorpicker">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="rgba">rgb(0,255,0)</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label29">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Variation tolerance:</property>
+                    <property name="xalign">0</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label30">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Hue</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScale" id="scale3">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="adjustment">greenscreen_tolerance_h</property>
+                    <property name="round_digits">1</property>
+                    <property name="digits">0</property>
+                    <property name="value_pos">left</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">4</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label31">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Saturation</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">5</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScale" id="scale4">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="adjustment">greenscreen_tolerance_s</property>
+                    <property name="round_digits">1</property>
+                    <property name="digits">0</property>
+                    <property name="value_pos">left</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">6</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label32">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Value</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">7</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScale" id="scale5">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="adjustment">greenscreen_tolerance_v</property>
+                    <property name="round_digits">1</property>
+                    <property name="digits">0</property>
+                    <property name="value_pos">left</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">8</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+            <child type="label">
+              <object class="GtkLabel" id="label27">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Greenscreen</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">8</property>
+          </packing>
+        </child>
       </object>
     </child>
   </object>
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index 16068cd..59ee2d4 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -1045,6 +1045,11 @@ 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, "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);
+  g_signal_connect(gtk_builder_get_object(gui, "greenscreen_tolerance_v"), "value-changed", G_CALLBACK(gui_set_greenscreen_tolerance), (void*)2);
   // Connect signal for hiding cameras
   g_signal_connect(gtk_builder_get_object(gui, "cam_menu_hide"), "activate", G_CALLBACK(gui_hide_cam), 0);
   // Load placeholder animation for cameras (no video data yet or mic only)
diff --git a/utilities/gtk/gui.c b/utilities/gtk/gui.c
index 836fa0a..0546891 100644
--- a/utilities/gtk/gui.c
+++ b/utilities/gtk/gui.c
@@ -485,13 +485,25 @@ 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);
   adjustment=GTK_ADJUSTMENT(gtk_builder_get_object(gui, "camcolors_max_brightness"));
   gtk_adjustment_set_value(adjustment, cam->postproc.max_brightness);
   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(gui, "camcolors_auto")), cam->postproc.autoadjust);
+  // Flip controls
   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);
+  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,
+                  .alpha=1.0};
+  gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(gtk_builder_get_object(gui, "greenscreen_colorpicker")), &color);
+  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]);
 }
 
 void camcolors_adjust_min(GtkAdjustment* adjustment, void* x)
@@ -552,3 +564,38 @@ void gui_hide_cam(GtkMenuItem* menuitem, void* x)
   dprintf(tc_client_in[1], "/closecam %s\n", cam->nick);
   camera_remove(menu_context_cam, 0);
 }
+
+void gui_set_greenscreen_img(GtkFileChooserButton* 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);
+}
+
+void gui_set_greenscreen_color(GtkColorButton* button, void* x)
+{
+  if(!menu_context_cam){return;}
+  struct camera* cam=camera_find(menu_context_cam);
+  if(!cam){return;}
+  GdkRGBA color;
+  gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(button), &color);
+  // Convert to HSV but also keep the RGB for later
+  cam->postproc.greenscreen_color[0]=(color.red*255);
+  cam->postproc.greenscreen_color[1]=(color.green*255);
+  cam->postproc.greenscreen_color[2]=(color.blue*255);
+  rgb_to_hsv(cam->postproc.greenscreen_color[0], cam->postproc.greenscreen_color[1], cam->postproc.greenscreen_color[2], &cam->postproc.greenscreen_hsv[0], &cam->postproc.greenscreen_hsv[1], &cam->postproc.greenscreen_hsv[2]);
+}
+
+void gui_set_greenscreen_tolerance(GtkAdjustment* adjustment, void* x)
+{
+  if(!menu_context_cam){return;}
+  struct camera* cam=camera_find(menu_context_cam);
+  if(!cam){return;}
+  cam->postproc.greenscreen_tolerance[(intptr_t)x]=gtk_adjustment_get_value(adjustment);
+}
diff --git a/utilities/gtk/gui.h b/utilities/gtk/gui.h
index 8d3c111..281c189 100644
--- a/utilities/gtk/gui.h
+++ b/utilities/gtk/gui.h
@@ -45,5 +45,8 @@ 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_color(GtkColorButton* button, void* x);
+extern void gui_set_greenscreen_tolerance(GtkAdjustment* adjustment, void* x);
 
 extern GtkBuilder* gui;
diff --git a/utilities/gtk/postproc.c b/utilities/gtk/postproc.c
index c18c996..625f31a 100644
--- a/utilities/gtk/postproc.c
+++ b/utilities/gtk/postproc.c
@@ -19,6 +19,85 @@
 #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))
+
+void rgb_to_hsv(int r, int g, int b, unsigned char* h, unsigned char* s, unsigned char* v)
+{
+  int minrgb=min(r, min(g, b));
+  int maxrgb=max(r, max(g, b));
+  *v=maxrgb;
+  if(minrgb==maxrgb){*h=0; *s=0; return;} // Grayscale
+  int diff=(r==minrgb)?g-b:((b==minrgb)?r-g:b-r);
+  int huebase=(r==minrgb)?3:((b==minrgb)?1:5);
+  int range=maxrgb-minrgb;
+  *h=huebase*42-diff*42/range;
+  *s=(unsigned int)(maxrgb-minrgb)*255/maxrgb;
+}
+
 void postproc_init(struct postproc_ctx* pp)
 {
   pp->min_brightness=0;
@@ -26,6 +105,14 @@ void postproc_init(struct postproc_ctx* pp)
   pp->autoadjust=0;
   pp->flip_horizontal=0;
   pp->flip_vertical=0;
+  pp->greenscreen=0;
+  pp->greenscreen_tolerance[0]=0;
+  pp->greenscreen_tolerance[1]=0;
+  pp->greenscreen_tolerance[2]=0;
+  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)
@@ -80,8 +167,38 @@ void postprocess(struct postproc_ctx* pp, unsigned char* buf, unsigned int width
       memcpy(&buf[((height-y-1)*width+x)*3], pixel, 3);
     }
   }
+  if(pp->greenscreen)
+  {
+    GdkPixbuf* img=img_get(pp->greenscreen);
+    guchar* pixels=gdk_pixbuf_get_pixels(img);
+    unsigned int x;
+    unsigned int y;
+    unsigned int gswidth=gdk_pixbuf_get_width(img);
+    unsigned int gsheight=gdk_pixbuf_get_height(img);
+    unsigned int gsx, gsy;
+    unsigned char* gscolor=pp->greenscreen_hsv;
+    for(y=0; y<height; ++y)
+    for(x=0; x<width; ++x)
+    {
+      unsigned char h,s,v;
+      rgb_to_hsv(buf[(y*width+x)*3], buf[(y*width+x)*3+1], buf[(y*width+x)*3+2], &h, &s, &v);
+      #define tolerance(a, b, margin) (abs(((int)(a))-((int)(b)))<=margin)
+      if((tolerance(h, gscolor[0], pp->greenscreen_tolerance[0]) ||
+          tolerance(((int)h+126)%252, ((int)gscolor[0]+126)%252, pp->greenscreen_tolerance[0])) && // For differences between red (beginning of spectrum) and purple (end of spectrum)
+         tolerance(s, gscolor[1], pp->greenscreen_tolerance[1]) &&
+         tolerance(v, gscolor[2], pp->greenscreen_tolerance[2]))
+      {
+        // TODO: Antialiasing?
+        gsx=x*gswidth/width;
+        gsy=y*gsheight/height;
+        memcpy(&buf[(y*width+x)*3], &pixels[(gsy*gswidth+gsx)*3], 3);
+      }
+    }
+  }
 }
 
 void postproc_free(struct postproc_ctx* pp)
 {
+  if(pp->greenscreen){img_free(pp->greenscreen);}
+  free((void*)pp->greenscreen_filename);
 }
diff --git a/utilities/gtk/postproc.h b/utilities/gtk/postproc.h
index 9dcfff0..2d52fd0 100644
--- a/utilities/gtk/postproc.h
+++ b/utilities/gtk/postproc.h
@@ -14,6 +14,13 @@
     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;
+};
+
 struct postproc_ctx
 {
   double min_brightness;
@@ -21,8 +28,17 @@ struct postproc_ctx
   char autoadjust;
   char flip_horizontal;
   char flip_vertical;
+  struct img* greenscreen;
+  int greenscreen_tolerance[3];
+  unsigned char greenscreen_color[3];
+  unsigned char greenscreen_hsv[3];
+  const char* greenscreen_filename;
 };
 
+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);
 extern void postproc_free(struct postproc_ctx* pp);