$ 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);