$ git clone http://tcclient.ion.nu/tc_client.git
commit 75ac6fb98ecbb9f0c7aeedac287869e41ab12ada
Author: Alicia <...>
Date:   Sat Apr 30 11:45:30 2016 +0200

    tc_client-gtk: added postprocessing options for cams upon right-click, starting with brightness adjustment.

diff --git a/ChangeLog b/ChangeLog
index dcc870a..29a2096 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,7 @@
 0.39:
 modbot: only use youtube-dl's 'ytsearch:' for --get-id, and only if it doesn't appear to be an ID already.
 tc_client-gtk: handle only one sendmessage event at a time, and don't send empty lines.
+tc_client-gtk: added postprocessing options for cams upon right-click, starting with brightness adjustment.
 tc_client-gtk and camviewer: fixed compatibility with newer libavutil.
 0.38:
 Handle multi-line messages.
diff --git a/gtkgui.glade b/gtkgui.glade
index 88dbdf1..11054af 100644
--- a/gtkgui.glade
+++ b/gtkgui.glade
@@ -490,6 +490,29 @@
       </object>
     </child>
   </object>
+  <object class="GtkMenu" id="cam_menu">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkMenuItem" id="cam_menu_colors">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">Adjust color levels</property>
+        <property name="use_underline">True</property>
+      </object>
+    </child>
+  </object>
+  <object class="GtkAdjustment" id="camcolors_max_brightness">
+    <property name="upper">255</property>
+    <property name="value">255</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="camcolors_min_brightness">
+    <property name="upper">255</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
   <object class="GtkWindow" id="camselection">
     <property name="can_focus">False</property>
     <property name="modal">True</property>
@@ -1126,4 +1149,86 @@
       </object>
     </child>
   </object>
+  <object class="GtkWindow" id="cam_colors">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Color adjustment</property>
+    <property name="transient_for">main</property>
+    <signal name="delete-event" handler="gtk_widget_hide_on_delete" swapped="no"/>
+    <child>
+      <object class="GtkBox" id="box12">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkLabel" id="label23">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">Lower bound</property>
+            <property name="xalign">0</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkScale" id="scale1">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="adjustment">camcolors_min_brightness</property>
+            <property name="round_digits">1</property>
+            <property name="digits">0</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label24">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">Upper bound</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="GtkScale" id="scale2">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="adjustment">camcolors_max_brightness</property>
+            <property name="round_digits">1</property>
+            <property name="digits">0</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="camcolors_auto">
+            <property name="label" translatable="yes">Automatically adjust</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="xalign">0</property>
+            <property name="draw_indicator">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">4</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
 </interface>
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index 2d1b096..173827d 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -464,11 +464,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
     if(!idend){return 1;}
     idend[0]=0;
     camera_removebynick(nick); // Remove any duplicates
-    struct camera* cam=camera_new();
-    cam->frame=av_frame_alloc();
-    cam->dstframe=av_frame_alloc();
-    cam->nick=strdup(nick);
-    cam->id=strdup(id);
+    struct camera* cam=camera_new(nick, id);
     cam->vctx=avcodec_alloc_context3(data->vdecoder);
     avcodec_open2(cam->vctx, data->vdecoder, 0);
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
@@ -476,12 +472,6 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
     avcodec_open2(cam->actx, data->adecoder, 0);
     cam->samples=0;
 #endif
-    cam->cam=gtk_image_new();
-    cam->box=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
-    gtk_box_set_homogeneous(GTK_BOX(cam->box), 0);
-    gtk_box_pack_start(GTK_BOX(cam->box), cam->cam, 0, 0, 0);
-    cam->label=gtk_label_new(cam->nick);
-    gtk_box_pack_start(GTK_BOX(cam->box), cam->label, 0, 0, 0);
     gtk_box_pack_start(GTK_BOX(data->box), cam->box, 0, 0, 0);
     gtk_widget_show_all(cam->box);
     updatescaling(data, 0, 0);
@@ -491,19 +481,10 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
   }
   if(!strcmp(buf, "Starting outgoing media stream"))
   {
-    struct camera* cam=camera_new();
-    cam->frame=av_frame_alloc();
-    cam->dstframe=av_frame_alloc();
-    cam->nick=strdup(nickname);
-    cam->id=strdup("out");
+    struct camera* cam=camera_new(nickname, "out");
     cam->vctx=avcodec_alloc_context3(data->vdecoder);
     avcodec_open2(cam->vctx, data->vdecoder, 0);
     cam->actx=0;
-    cam->cam=gtk_image_new();
-    cam->box=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
-    gtk_box_pack_start(GTK_BOX(cam->box), cam->cam, 0, 0, 0);
-    cam->label=gtk_label_new(cam->nick);
-    gtk_box_pack_start(GTK_BOX(cam->box), cam->label, 0, 0, 0);
     gtk_box_pack_start(GTK_BOX(data->box), cam->box, 0, 0, 0);
     gtk_widget_show_all(cam->box);
     updatescaling(data, 0, 0);
@@ -608,6 +589,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
   struct SwsContext* swsctx=sws_getContext(cam->frame->width, cam->frame->height, cam->frame->format, scalewidth, scaleheight, AV_PIX_FMT_RGB24, 0, 0, 0, 0);
   sws_scale(swsctx, (const uint8_t*const*)cam->frame->data, cam->frame->linesize, 0, cam->frame->height, cam->dstframe->data, cam->dstframe->linesize);
   sws_freeContext(swsctx);
+  camera_postproc(cam, buf, scalewidth*scaleheight);
 
   GdkPixbuf* oldpixbuf=gtk_image_get_pixbuf(GTK_IMAGE(cam->cam));
   GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(cam->dstframe->data[0], GDK_COLORSPACE_RGB, 0, 8, scalewidth, scaleheight, cam->dstframe->linesize[0], freebuffer, 0);
@@ -1061,6 +1043,11 @@ int main(int argc, char** argv)
   g_signal_connect(item, "switch-page", G_CALLBACK(pm_select), 0);
   // Connect signal for captcha
   g_signal_connect(gtk_builder_get_object(gui, "captcha_done"), "clicked", G_CALLBACK(captcha_done), 0);
+  // Connect signals for camera postprocessing
+  g_signal_connect(gtk_builder_get_object(gui, "cam_menu_colors"), "activate", G_CALLBACK(gui_show_camcolors), 0);
+  g_signal_connect(gtk_builder_get_object(gui, "camcolors_min_brightness"), "value-changed", G_CALLBACK(camcolors_adjust_min), 0);
+  g_signal_connect(gtk_builder_get_object(gui, "camcolors_max_brightness"), "value-changed", G_CALLBACK(camcolors_adjust_max), 0);
+  g_signal_connect(gtk_builder_get_object(gui, "camcolors_auto"), "toggled", G_CALLBACK(camcolors_toggle_auto), 0);
   // Populate saved channels
   GtkWidget* startbox=GTK_WIDGET(gtk_builder_get_object(gui, "startbox"));
   int channelcount=config_get_int("channelcount");
diff --git a/utilities/gtk/gui.c b/utilities/gtk/gui.c
index 9831076..08f674b 100644
--- a/utilities/gtk/gui.c
+++ b/utilities/gtk/gui.c
@@ -18,11 +18,12 @@
 #include <string.h>
 #include <stdint.h>
 #include <gtk/gtk.h>
-#include "gui.h"
 #include "config.h"
 #include "logging.h"
 #include "compat.h"
 #include "userlist.h"
+#include "media.h"
+#include "gui.h"
 
 extern void startsession(GtkButton* button, void* x);
 GtkBuilder* gui;
@@ -452,3 +453,63 @@ void fontsize_set(double size)
     buffer_updatesize(userlist[i].pm_buffer);
   }
 }
+
+const char* menu_context_cam=0;
+gboolean gui_show_cam_menu(GtkWidget* widget, GdkEventButton* event, const char* id)
+{
+  if(event->button!=3){return 0;} // Only act on right-click
+  struct camera* cam=camera_find(id);
+  free((void*)menu_context_cam);
+  menu_context_cam=strdup(cam->id);
+  GtkMenu* menu=GTK_MENU(gtk_builder_get_object(gui, "cam_menu"));
+  gtk_menu_popup(menu, 0, 0, 0, 0, event->button, event->time);
+  return 1;
+}
+
+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")));
+  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);
+}
+
+void camcolors_adjust_min(GtkAdjustment* adjustment, void* x)
+{
+  GtkAdjustment* other=GTK_ADJUSTMENT(gtk_builder_get_object(gui, "camcolors_max_brightness"));
+  double value=gtk_adjustment_get_value(adjustment);
+  if(value>gtk_adjustment_get_value(other))
+  {
+    gtk_adjustment_set_value(other, value);
+  }
+  if(!menu_context_cam){return;}
+  struct camera* cam=camera_find(menu_context_cam);
+  if(!cam){return;}
+  cam->postproc.min_brightness=value;
+}
+
+void camcolors_adjust_max(GtkAdjustment* adjustment, void* x)
+{
+  GtkAdjustment* other=GTK_ADJUSTMENT(gtk_builder_get_object(gui, "camcolors_min_brightness"));
+  double value=gtk_adjustment_get_value(adjustment);
+  if(value<gtk_adjustment_get_value(other))
+  {
+    gtk_adjustment_set_value(other, value);
+  }
+  if(!menu_context_cam){return;}
+  struct camera* cam=camera_find(menu_context_cam);
+  if(!cam){return;}
+  cam->postproc.max_brightness=value;
+}
+
+void camcolors_toggle_auto(GtkToggleButton* button, void* x)
+{
+  if(!menu_context_cam){return;}
+  struct camera* cam=camera_find(menu_context_cam);
+  if(!cam){return;}
+  cam->postproc.autoadjust=gtk_toggle_button_get_active(button);
+}
diff --git a/utilities/gtk/gui.h b/utilities/gtk/gui.h
index 91a1699..a2b46ae 100644
--- a/utilities/gtk/gui.h
+++ b/utilities/gtk/gui.h
@@ -38,5 +38,10 @@ extern char pm_select(GtkNotebook* tabs, GtkWidget* tab, int num, void* x);
 extern void buffer_setup_colors(GtkTextBuffer* buffer);
 extern void buffer_updatesize(GtkTextBuffer* buffer);
 extern void fontsize_set(double size);
+extern gboolean gui_show_cam_menu(GtkWidget* widget, GdkEventButton* event, const char* id);
+extern void gui_show_camcolors(GtkMenuItem* menuitem, void* x);
+extern void camcolors_adjust_min(GtkAdjustment* adjustment, void* x);
+extern void camcolors_adjust_max(GtkAdjustment* adjustment, void* x);
+extern void camcolors_toggle_auto(GtkToggleButton* button, void* x);
 
 extern GtkBuilder* gui;
diff --git a/utilities/gtk/media.c b/utilities/gtk/media.c
index 948c622..59db50a 100644
--- a/utilities/gtk/media.c
+++ b/utilities/gtk/media.c
@@ -32,7 +32,11 @@
 #include "../compat.h"
 #include "gui.h"
 #include "media.h"
-struct camera campreview;
+struct camera campreview={
+  .postproc.min_brightness=0,
+  .postproc.max_brightness=255,
+  .postproc.autoadjust=0
+};
 struct camera* cams=0;
 unsigned int camcount=0;
 #ifdef _WIN32
@@ -136,15 +140,35 @@ struct camera* camera_findbynick(const char* nick)
   return 0;
 }
 
-struct camera* camera_new(void)
+struct camera* camera_new(const char* nick, const char* id)
 {
   ++camcount;
   cams=realloc(cams, sizeof(struct camera)*camcount);
+  struct camera* cam=&cams[camcount-1];
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
-  cams[camcount-1].samples=0;
-  cams[camcount-1].samplecount=0;
+  cam->samples=0;
+  cam->samplecount=0;
 #endif
-  return &cams[camcount-1];
+  cam->nick=strdup(nick);
+  cam->id=strdup(id);
+  cam->frame=av_frame_alloc();
+  cam->dstframe=av_frame_alloc();
+  cam->cam=gtk_image_new();
+  cam->box=gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+  gtk_box_set_homogeneous(GTK_BOX(cam->box), 0);
+  // Wrap cam image in an event box to catch (right) clicks
+  GtkWidget* eventbox=gtk_event_box_new();
+  gtk_container_add(GTK_CONTAINER(eventbox), cam->cam);
+  gtk_event_box_set_above_child(GTK_EVENT_BOX(eventbox), 1);
+  cam->label=gtk_label_new(cam->nick);
+  gtk_box_pack_start(GTK_BOX(cam->box), eventbox, 0, 0, 0);
+  gtk_box_pack_start(GTK_BOX(cam->box), cam->label, 0, 0, 0);
+  g_signal_connect(eventbox, "button-release-event", G_CALLBACK(gui_show_cam_menu), cam->id);
+  // Initialize postprocessing values
+  cam->postproc.min_brightness=0;
+  cam->postproc.max_brightness=255;
+  cam->postproc.autoadjust=0;
+  return cam;
 }
 
 void camera_cleanup(void)
@@ -325,3 +349,28 @@ const char* camselect_file(void)
   gtk_widget_destroy(dialog);
   return file;
 }
+
+void camera_postproc(struct camera* cam, unsigned char* buf, unsigned int count)
+{
+  if(cam->postproc.min_brightness==0 && cam->postproc.max_brightness==255 && !cam->postproc.autoadjust){return;}
+  unsigned char min=255;
+  unsigned char max=0;
+  unsigned int i;
+  for(i=0; i<count*3; ++i)
+  {
+    if(cam->postproc.autoadjust)
+    {
+      if(buf[i]<min){min=buf[i];}
+      if(buf[i]>max){max=buf[i];}
+    }
+    double v=((double)buf[i]-cam->postproc.min_brightness)*255/(cam->postproc.max_brightness-cam->postproc.min_brightness);
+    if(v<0){v=0;}
+    if(v>255){v=255;}
+    buf[i]=v;
+  }
+  if(cam->postproc.autoadjust)
+  {
+    cam->postproc.min_brightness=min;
+    cam->postproc.max_brightness=max;
+  }
+}
diff --git a/utilities/gtk/media.h b/utilities/gtk/media.h
index e94bb30..21ab1dd 100644
--- a/utilities/gtk/media.h
+++ b/utilities/gtk/media.h
@@ -14,6 +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/>.
 */
+#include <libavcodec/avcodec.h>
 struct camera
 {
   AVFrame* frame;
@@ -27,6 +28,12 @@ struct camera
   char* nick;
   GtkWidget* box; // holds label and cam
   GtkWidget* label;
+  struct
+  {
+    double min_brightness;
+    double max_brightness;
+    char autoadjust;
+  } postproc;
 };
 extern struct camera campreview;
 extern struct camera* cams;
@@ -45,10 +52,11 @@ extern void camera_remove(const char* nick);
 extern void camera_removebynick(const char* nick);
 extern struct camera* camera_find(const char* id);
 extern struct camera* camera_findbynick(const char* nick);
-extern struct camera* camera_new(void);
+extern struct camera* camera_new(const char* nick, const char* id);
 extern void camera_cleanup(void);
 extern GIOChannel* camthread(const char* name, AVCodec* vencoder, unsigned int delay);
 extern void camselect_change(GtkComboBox* combo, AVCodec* vencoder);
 extern gboolean camselect_cancel(GtkWidget* widget, void* x1, void* x2);
 extern void camselect_accept(GtkWidget* widget, AVCodec* vencoder);
 extern const char* camselect_file(void);
+extern void camera_postproc(struct camera* cam, unsigned char* buf, unsigned int count);