[xiph-commits] r13870 - trunk/sushivision

xiphmont at svn.xiph.org xiphmont at svn.xiph.org
Fri Sep 21 09:51:36 PDT 2007


Author: xiphmont
Date: 2007-09-21 09:51:36 -0700 (Fri, 21 Sep 2007)
New Revision: 13870

Added:
   trunk/sushivision/plane-2d.c
   trunk/sushivision/plane-bg.c
   trunk/sushivision/plane.h
Modified:
   trunk/sushivision/dimension.c
   trunk/sushivision/dimension.h
   trunk/sushivision/panel-2d.c
   trunk/sushivision/panel-2d.h
   trunk/sushivision/scale.h
   trunk/sushivision/sushivision.h
Log:
Nothing to see here


Modified: trunk/sushivision/dimension.c
===================================================================
--- trunk/sushivision/dimension.c	2007-09-21 09:41:31 UTC (rev 13869)
+++ trunk/sushivision/dimension.c	2007-09-21 16:51:36 UTC (rev 13870)
@@ -26,174 +26,152 @@
 #include <math.h>
 #include "internal.h"
 
-/* modules attempts to encapsulate and hide differences between the
-   different types of dimensions with respect to widgets, iterators,
-   shared dimensions */
+void _sv_dim_data_copy(sv_dim_data_t *dd,sv_dim_data_t *copy){
 
-/* A panel that supports discrete dimensions must deal with up to
-   three different scales for the discrete axis; the first is
-   pixel-oriented to the actually displayed panel, the second covers
-   the same range but is oriented to integer bin numbers in the
-   underlying data vector (often the same as the display), and the
-   third the same as the second, but over the absolute range [0 - n)
-   such that discrete dimensions will count from 0 in iteration. */
-/* data_w ignored except in the continuous case, where it may be used
-   to generate over/undersampled data scales. */
 
-int _sv_dim_scales(sv_dim_t *d,
-		   double lo,
-		   double hi,
-		   int panel_w, int data_w,
-		   int spacing,
-		   char *legend,
-		   _sv_scalespace_t *panel, 
-		   _sv_scalespace_t *data, 
-		   _sv_scalespace_t *iter){
+
+}
+
+void _sv_dim_data_clear(sv_dim_data_t *dd){
+
+}
+
+/* A panel must deal with two different scales for an axis; the first
+   is pixel-oriented to the actually displayed panel, the second
+   covers the same range but is oriented to integer bin numbers in the
+   underlying data vector (same as the display for continuous,
+   differnet for discrete) */
+
+_sv_scalespace_t _sv_dim_panelscale(sv_dim_data_t *dd,
+				    int panel_w, int flip){
   
-  /* the panel scales may be reversed (the easiest way to do y)
-     and/or the dimension may have an inverted scale. */
-  int pneg, dimneg;
+  int pneg=1, dimneg=1, spacing = 50;
+  double lo=dd->lo, hi=dd->hi;
+  _sv_scalespace_t ret={0};
   
-  if(lo>hi){ // == must be 1 to match scale gen code when width is 0
-    pneg = -1;
-  }else{
-    pneg = 1;
+  if(lo>hi) pneg = -1;
+  if(dd->floor > dd->ceil) dimneg = -1;
+  if(flip) pneg = -pneg;
+
+  switch(dd->type){
+  case SV_DIM_CONTINUOUS:
+    ret=_sv_scalespace_linear(lo, hi, panel_w, spacing);
+    break;
+
+  case SV_DIM_DISCRETE:
+    
+    int lo_i =  rint(lo * dd->denominator / dd->numerator);
+    int hi_i =  rint(hi * dd->denominator / dd->numerator);
+
+    ret = _sv_scalespace_linear((lo_i-pneg*.4) * dd->numerator / dd->denominator,
+				(hi_i+pneg*.4) * dd->numerator / dd->denominator,
+				panel_w, spacing);    
+    break;
+  case SV_DIM_PICKLIST:
+    fprintf(stderr,"ERROR: Cannot iterate over picklist dimension!\n"
+	    "\tA picklist dimension may not be a panel axis.\n");
+    break;
+
+  default:
+    fprintf(stderr,"ERROR: Unsupporrted dimension type in dimension_datascale.\n");
+    break;
+
   }
+
+  return ret;
+}
+
+_sv_scalespace_t _sv_dim_datascale(sv_dim_data_t *dd,
+				   _sv_scalespace_t *panel,
+				   int data_w,
+				   int floor){
   
-  if(d->scale->val_list[0] > d->scale->val_list[d->scale->vals-1]){
-    dimneg = -1;
-  }else{
-    dimneg = 1;
-  }
+  /* the panel scales may be reversed (the easiest way to do y)
+     and/or the dimension may have an inverted scale. */
+  int pneg=1, dimneg=1;
+  int panel_w = (panel?panel->pixels:0);
+  _sv_scalespace_t ret={0};
+  double lo=dd->lo, hi=dd->hi;
+
+  if(lo>hi) pneg = -1;
+  if(dd->floor > dd->ceil) dimneg = -1;
+  if(flip) pneg = -pneg;
       
-  switch(d->type){
+  switch(dd->type){
   case SV_DIM_CONTINUOUS:
-    {
-      //double ceil = d->scale->val_list[d->scale->vals-1] * dimneg;
-      double fl = ((d->flags & SV_DIM_ZEROINDEX) ? d->scale->val_list[0] : 0.);
 
-      if(panel)
-	*panel = _sv_scalespace_linear(lo, hi, panel_w, spacing, legend);
-
-      if(panel && panel_w == data_w){
+    ret = _sv_scalespace_linear(lo, hi, data_w, 1);
+    
+    if(panel  && data_w != panel_w){
 	
-	*iter = *data = *panel;
-
-      }else{
-	*data = _sv_scalespace_linear(lo, hi, data_w, 1, legend);
-	*iter = _sv_scalespace_linear(lo-fl, hi-fl, data_w, 1, legend);
+      /* if possible, the data/iterator scales should cover the entire pane exposed
+	 by the panel scale so long as there's room left to extend them without
+	 overflowing the lo/hi fenceposts */
+      while(1){
+	double panel2 = _sv_scalespace_value(panel,panel_w-1)*pneg;
+	double data2 = _sv_scalespace_value(data,data_w-1)*pneg;
 	
-	if(panel){
-	  /* if possible, the data/iterator scales should cover the entire pane exposed
-	     by the panel scale so long as there's room left to extend them without
-	     overflowing the lo/hi fenceposts */
-	  while(1){
-	    double panel2 = _sv_scalespace_value(panel,panel_w-1)*pneg;
-	    double data2 = _sv_scalespace_value(data,data_w-1)*pneg;
-	    
-	    if(data2>=panel2)break;
-	    data_w++;
-	  }
-	}
-
-	data->pixels = data_w;
-	iter->pixels = data_w;
+	if(data2>=panel2)break;
+	data_w++;
       }
+      
+      data->pixels = data_w;
     }
     break;
+    
   case SV_DIM_DISCRETE:
-    {
-      /* return a scale that when iterated will only hit values
-	 quantized to the discrete base */
-      /* what is the absolute base? */
-      int floor_i =  rint(d->scale->val_list[0] * d->private->discrete_denominator / 
-			  d->private->discrete_numerator);
-      int ceil_i =  rint(d->scale->val_list[d->scale->vals-1] * d->private->discrete_denominator / 
-			 d->private->discrete_numerator);
-      
-      int lo_i =  floor(lo * d->private->discrete_denominator / 
-			d->private->discrete_numerator);
-      int hi_i =  floor(hi * d->private->discrete_denominator / 
-			d->private->discrete_numerator);
+    
+    /* return a scale that when iterated will only hit values
+       quantized to the discrete base */
+    /* what is the absolute base? */
+    int floor_i =  rint(dd->floor * dd->denominator / dd->numerator);
+    int ceil_i =  rint(dd->ceil * dd->denominator / dd->numerator);
+    
+    int lo_i =  rint(lo * dd->denominator / dd->numerator);
+    int hi_i =  rint(hi * dd->denominator / dd->numerator);
 
-      if(floor_i < ceil_i){
-	if(lo_i < floor_i)lo_i = floor_i;
-	if(hi_i > ceil_i)hi_i = ceil_i;
-      }else{
-	if(lo_i > floor_i)lo_i = floor_i;
-	if(hi_i < ceil_i)hi_i = ceil_i;
-      }
-
-      if(panel){
-	*panel = _sv_scalespace_linear((double)(lo_i-pneg*.4) * d->private->discrete_numerator / 
-				       d->private->discrete_denominator,
-				       (double)(hi_i+pneg*.4) * d->private->discrete_numerator / 
-				       d->private->discrete_denominator,
-				       panel_w, spacing, legend);
-
-	/* if possible, the data/iterator scales should cover the entire pane exposed
-	   by the panel scale so long as there's room left to extend them without
-	   overflowing the lo/hi fenceposts */
-	double panel1 = _sv_scalespace_value(panel,0)*pneg;
-	double panel2 = _sv_scalespace_value(panel,panel->pixels)*pneg;
-	double data1 = (double)(lo_i-.49*pneg) * d->private->discrete_numerator / 
-	  d->private->discrete_denominator*pneg;
-	double data2 = (double)(hi_i-.51*pneg) * d->private->discrete_numerator / 
-	  d->private->discrete_denominator*pneg;
+    if(panel){
+      /* if possible, the data scale should cover the entire pane
+	 exposed by the panel scale so long as there's room left to
+	 extend them without overflowing the lo/hi fenceposts */
+      double panel1 = _sv_scalespace_value(panel,0)*pneg;
+      double panel2 = _sv_scalespace_value(panel,panel->pixels)*pneg;
+      double data1 = (double)(lo_i-.49*pneg) * dd->numerator / dd->denominator*pneg;
+      double data2 = (double)(hi_i-.51*pneg) * dd->numerator / dd->denominator*pneg;
 	
-	while(data1 > panel1 && lo_i*dimneg > floor_i*dimneg){
-	  lo_i -= pneg;
-	  data1 = (double)(lo_i-.49*pneg) * d->private->discrete_numerator / 
-	    d->private->discrete_denominator*pneg;
-	}
-	
-	while(data2 < panel2 && hi_i*dimneg <= ceil_i*dimneg){ // inclusive upper
-	  hi_i += pneg;
-	  data2 = (double)(hi_i-.51*pneg) * d->private->discrete_numerator / 
-	    d->private->discrete_denominator*pneg;
-	}
-	
-      }else{
-	hi_i += pneg; // because upper bound is inclusive, and this val is one-past
+      while(data1 > panel1 && lo_i*dimneg > floor_i*dimneg){
+	lo_i -= pneg;
+	data1 = (double)(lo_i-.49*pneg) * dd->numerator / dd->denominator*pneg;
       }
-
-      /* cosmetic adjustment complete, generate the scales */
-      data_w = abs(hi_i-lo_i);
-      if(!(d->flags & SV_DIM_ZEROINDEX))
-	floor_i = 0;
       
-      *data = _sv_scalespace_linear((double)lo_i * d->private->discrete_numerator / 
-				    d->private->discrete_denominator,
-				    (double)hi_i * d->private->discrete_numerator / 
-				    d->private->discrete_denominator,
-				    data_w, 1, legend);
+      while(data2 < panel2 && hi_i*dimneg <= ceil_i*dimneg){ // inclusive upper
+	hi_i += pneg;
+	data2 = (double)(hi_i-.51*pneg) * dd->numerator / dd->denominator*pneg;
+      }
       
-      if(d->flags & SV_DIM_MONOTONIC)
-	*iter = _sv_scalespace_linear(lo_i - floor_i, hi_i - floor_i,
-				      data_w, 1, legend);
-      else
-	*iter = _sv_scalespace_linear((double)(lo_i - floor_i) * 
-				      d->private->discrete_numerator / 
-				      d->private->discrete_denominator,
-				      (double)(hi_i - floor_i) * 
-				      d->private->discrete_numerator / 
-				      d->private->discrete_denominator,
-				      data_w, 1, legend);
-      break;
+    }else{
+      hi_i += pneg; // because upper bound is inclusive, and this val is one-past
     }
+
+    /* cosmetic adjustment complete, generate the scales */
+    data_w = abs(hi_i-lo_i);
+    
+    ret = _sv_scalespace_linear((double)lo_i * dd->numerator / dd->denominator,
+				(double)hi_i * dd->numerator / dd->denominator,
+				data_w, 1);
+    
     break;
+
   case SV_DIM_PICKLIST:
     fprintf(stderr,"ERROR: Cannot iterate over picklist dimension!\n"
 	    "\tA picklist dimension may not be a panel axis.\n");
-    data_w = 0;
     break;
   default:
     fprintf(stderr,"ERROR: Unsupporrted dimension type in dimension_datascale.\n");
-    data_w = 0;
     break;
   }
   
-  return data_w;
+  return ret;
 }
 
 int _sv_dim_scales_from_panel(sv_dim_t *d,
@@ -214,15 +192,15 @@
 			iter);
 }
 
-static double discrete_quantize_val(sv_dim_t *d, double val){
+static double quantize_val(sv_dim_t *d, double val){
   if(d->type == SV_DIM_DISCRETE){
-    val *= d->private->discrete_denominator;
-    val /= d->private->discrete_numerator;
+    val *= d->private->denominator;
+    val /= d->private->numerator;
     
     val = rint(val);
     
-    val *= d->private->discrete_numerator;
-    val /= d->private->discrete_denominator;
+    val *= d->private->numerator;
+    val /= d->private->denominator;
   }
   return val;
 }
@@ -236,7 +214,7 @@
   double val = _sv_slider_get_value(dw->scale,1);
   char buffer[80];
   
-  val = discrete_quantize_val(d,val);
+  val = quantize_val(d,val);
   
   if(buttonstate == 0){
     _sv_undo_push();
@@ -289,8 +267,8 @@
   double hi = _sv_slider_get_value(dw->scale,2);
   char buffer[80];
   
-  hi = discrete_quantize_val(d,hi);
-  lo = discrete_quantize_val(d,lo);
+  hi = quantize_val(d,hi);
+  lo = quantize_val(d,lo);
   
   if(buttonstate == 0){
     _sv_undo_push();
@@ -382,13 +360,13 @@
     _sv_slider_set_value(dw->scale,thumb,val);
     break;
   case SV_DIM_DISCRETE:
-    val *= d->private->discrete_denominator;
-    val /= d->private->discrete_numerator;
+    val *= d->private->denominator;
+    val /= d->private->numerator;
 
     val = rint(val);
 
-    val *= d->private->discrete_numerator;
-    val /= d->private->discrete_denominator;
+    val *= d->private->numerator;
+    val /= d->private->denominator;
 
     _sv_slider_set_value(dw->scale,thumb,val);
     break;
@@ -423,13 +401,13 @@
   if(!d->private->widgets){
     switch(thumb){
     case 0:
-      d->bracket[0] = discrete_quantize_val(d,val);
+      d->bracket[0] = quantize_val(d,val);
       break;
     case 1:
-      d->val = discrete_quantize_val(d,val);
+      d->val = quantize_val(d,val);
       break;
     case 2:
-      d->bracket[1] = discrete_quantize_val(d,val);
+      d->bracket[1] = quantize_val(d,val);
       break;
     default:
       errno = -EINVAL;
@@ -602,7 +580,7 @@
       dw->scale = _sv_slider_new((_sv_slice_t **)sl,3,d->scale->label_list,d->scale->val_list,
 				 d->scale->vals,0);
       if(d->type == SV_DIM_DISCRETE)
-	_sv_slider_set_quant(dw->scale,d->private->discrete_numerator,d->private->discrete_denominator);
+	_sv_slider_set_quant(dw->scale,d->private->numerator,d->private->denominator);
 
       _sv_slice_thumb_set((_sv_slice_t *)sl[0],v[0]);
       _sv_slice_thumb_set((_sv_slice_t *)sl[1],v[1]);
@@ -701,8 +679,8 @@
   d->legend = strdup(decl->label);
   d->type = SV_DIM_CONTINUOUS;
   d->private = calloc(1, sizeof(*d->private));
-  d->private->discrete_numerator = 1;
-  d->private->discrete_denominator = 1;
+  d->private->numerator = 1;
+  d->private->denominator = 1;
 
   // parse decllist
   for(i=0;i<decl->n;i++){
@@ -727,7 +705,7 @@
 	fprintf(stderr,"sushivision: Missing numerator value in \"%s\"\n.",name);
       }else{
 	d->type = SV_DIM_PICKLIST;
-	d->private->discrete_numerator = v;
+	d->private->numerator = v;
       }
 
     }else if(!strcmp(f,"denominator")){
@@ -737,7 +715,7 @@
 	fprintf(stderr,"sushivision: denominator value may not be zero\n.");
       }else{
 	d->type = SV_DIM_PICKLIST;
-	d->private->discrete_denominator = v;
+	d->private->denominator = v;
       }
 
     }else{

Modified: trunk/sushivision/dimension.h
===================================================================
--- trunk/sushivision/dimension.h	2007-09-21 09:41:31 UTC (rev 13869)
+++ trunk/sushivision/dimension.h	2007-09-21 16:51:36 UTC (rev 13870)
@@ -20,10 +20,17 @@
  */
 
 
+#define SV_DIM_NO_X 0x100
+#define SV_DIM_NO_Y 0x200
+
+enum sv_dim_type { SV_DIM_CONTINUOUS, 
+		   SV_DIM_DISCRETE, 
+		   SV_DIM_PICKLIST};
+
 typedef struct {
   sv_dim_list_t *dl;
   GtkWidget *t;
-
+  
   /* one or the other */
   _sv_slider_t *scale;
   GtkWidget *menu;
@@ -39,17 +46,47 @@
   void (*bracket_callback)(sv_dim_list_t *);
 } _sv_dim_widget_t;
 
-struct _sv_dim_internal {
-  long discrete_numerator;
-  long discrete_denominator;
+typedef struct sv_dim_data{ 
+
+  char *legend;
+  enum sv_dim_type type;
+
+  double floor;
+  double lo;
+  double val;
+  fouble hi;
+  double ceil;
   
+  long numerator;
+  long denominator;
+
+  unsigned flags;
+  
+} sv_dim_data_t;
+
+typedef struct sv_dim_lookup{ 
+
+  sv_scale_t *scale;
+  
+  int (*callback)(sv_dim_t *);
+
   int widgets;
   _sv_dim_widget_t **widget_list;
 
   int (*value_callback)(sv_dim_t *d, void *data);
   void *value_callback_data;
+
+} sv_dim_lookup_t;
+
+struct sv_dim{ 
+  int number;
+  char *name;
+  
+  sv_dim_data_t data;
+  sv_dim_look_t look;
 };
 
+
 extern int _sv_dim_scales(sv_dim_t *d,
 			  double lo,
 			  double hi,

Modified: trunk/sushivision/panel-2d.c
===================================================================
--- trunk/sushivision/panel-2d.c	2007-09-21 09:41:31 UTC (rev 13869)
+++ trunk/sushivision/panel-2d.c	2007-09-21 16:51:36 UTC (rev 13870)
@@ -84,56 +84,46 @@
 // GDK/API; it is already called implicitly in the main loop whenever
 // a task step completes.
 
-#define STATUS_IDLE 0
-#define STATUS_BUSY 1
-#define STATUS_WORKING 2
+static void payload_free(sv_dim_data_t *dd, int dims){
+  for(i=0;i<dims;i++)
+    _sv_dim_data_clear(dd+i);
+  free(dd);
+}
 
-typedef struct {
-  int               panel_w;
-  int               panel_h;
-  
-  char             *x_legend;
-  char             *y_legend;
-  int               x_dim;
-  int               y_dim;
-  int               dims;
-  double           *dim_lo;
-  double           *dim_v;
-  double           *dim_hi;
-  
+static int image_resize(sv_plane_t *pl, 
+			sv_panel_t *p){
+  return pl->c.image_resize(pl, p);
+}
 
+static int data_resize(sv_plane_t *pl, 
+		       sv_panel_t *p){
+  return pl->c.data_resize(pl, p);
+}
 
-} recompute_payload_t;
-
-static void recompute_payload_free(recompute_payload_t *payload){
-
-
-
+static int image_work(sv_plane_t *pl, 
+		      sv_panel_t *p){
+  return pl->c.image_work(pl, p);
 }
 
-// called from GDK/API; assumes GDK lock held 
-static void recompute_payload_create(recompute_payload_t *payload){
-
-
-
+static int data_work(sv_plane_t *pl, 
+		     sv_panel_t *p){
+  return pl->c.data_work(pl, p);
 }
 
+#define STATUS_IDLE 0
+#define STATUS_BUSY 1
+#define STATUS_WORKING 2
 
-
-
-
 static int plane_loop(sv_panel_t *p, int *next, 
 		      int(*function)(_sv_plane_t *, 
-				     sv_panel_t *, 
-				     int serialno)){
+				     sv_panel_t *)){
   int finishedflag=1;
   int last = *next;
-  int i = last;
   int serialno = p->serialno;
   do{
-    int status = function(p->plane_list[i],p,serialno);
-    if(++i>=p->planes)i=0;
-    *next=i;
+    int i = *next++;
+    if(*next>=p->planes)*next=0;
+    int status = function(p->plane_list[i],p);
     if(status == STATUS_WORKING) return STATUS_WORKING;
     if(status != STATUS_IDLE) finishedflag=0;
   }while(i!=last);
@@ -169,7 +159,13 @@
 
   // recomute setup
   if(p->recompute_pending){
-    recompute_payload_t *payload = p->recompute_payload;
+    int dims = p->recompute_dims;
+    sv_dim_data_t *payload = p->recompute_payload;
+    p->w = p->recompute_w;
+    p->h = p->recompute_h;
+    p->x_dim = p->recompute_xdim;
+    p->y_dim = p->recompute_ydim;
+
     p->recompute_pending=0;
     p->recompute_payload=NULL;
     pthread_mutex_unlock(p->payload_m);
@@ -181,13 +177,16 @@
     p->relegend=1;
     p->bgrender=0;
     p->image_next_plane=0;
+    
+    bg->c.recompute_setup(p->bg, p, payload, dims);
 
     for(i=0;i<p->planes;i++)
-      p->plane_list[i]->recompute_setup(p->plane_list[i], p, payload);
+      p->plane_list[i]->c.recompute_setup(p->plane_list[i], p, payload, dims);
 
     pthread_mutex_unlock(p->status_m);
     pthread_rwlock_unlock(p->panel_m);
-    recompute_payload_free(payload);
+    payload_free(payload,dims);
+
     return STATUS_WORKING;
   }
 
@@ -201,7 +200,7 @@
 
   // image resize
   if(p->image_resize){
-    status = plane_loop(p,&p->image_next_plane,plane_resize);
+    status = plane_loop(p,&p->image_next_plane,image_resize);
     if(status == STATUS_WORKING) return done_working(p);
     if(status == STATUS_IDLE){
       p->image_resize = 0;

Modified: trunk/sushivision/panel-2d.h
===================================================================
--- trunk/sushivision/panel-2d.h	2007-09-21 09:41:31 UTC (rev 13869)
+++ trunk/sushivision/panel-2d.h	2007-09-21 16:51:36 UTC (rev 13870)
@@ -19,146 +19,15 @@
  * 
  */
 
-// plane mutex locking order: forward in plane list, down the plane struct
-
-typedef union  _sv_plane _sv_plane_t;
-typedef struct _sv_plane_proto _sv_plane_proto_t;
-typedef struct _sv_plane_bg _sv_plane_bg_t;
-typedef struct _sv_plane_2d _sv_plane_2d_t;
-
-struct _sv_plane_proto {
-  // in common with all plane types
-
-  // only GTK/API manipulates the common data
-  // GTK/API read access: GDK lock
-  // GTK/API write access: GDK lock -> panel.memlock(write);
-  // worker read access: panel.memlock(read)
-  int              plane_type;
-  sv_obj_t        *o;
-  _sv_plane_t     *share_next;
-  _sv_plane_t     *share_prev;
-  _sv_panel2d_t   *panel;
-};
-
-struct _sv_plane_bg {
-  int              plane_type;
-  sv_obj_t        *o;
-  _sv_plane_t     *share_next;
-  _sv_plane_t     *share_prev;
-  _sv_panel2d_t   *panel;
-
-  // image data and concurrency tracking
-   _sv_ucolor_t   *image; // panel size
-  int              image_serialno;
-  int              image_waiting;
-  int              image_incomplete;
-  int              image_nextline;
-  unsigned char   *image_status; // rendering flags
-  pthread_rwlock_t image_m; 
-
-};
-
-struct sv_zmap {
-
-  double *label_vals;
-  int labels;
-  int neg;
-  double al;
-  double lo;
-  double hi;
-  double lodel;
-  double *labeldelB;
-  double *labelvalB;
-
-};
-
-// z-plane mutex policy: data -> image plane -> [bg mutextes] -> status
-struct _sv_plane_2d {
-  // in common with all plane types
-  // common fields locked via panel
-  int              plane_type; // == Z
-  sv_obj_t        *o;
-  _sv_plane_t     *share_next;
-  _sv_plane_t     *share_prev;
-  _sv_panel2d_t   *panel;
-
-  // subtype 
-  // objective data
-
-  // although we lock/protect the data and image memory allocation, we
-  // generally don't write-lock updates to the data/image planes.
-  // Because any write operation finishes with a status update that
-  // flushes changes out to the next stage and all data flows in only
-  // one direction in the rendering pipeline, any inconsistent/stale
-  // data is corrected as soon as complete data is available.  
-
-  float           *data;              // data size
-  _sv_scalespace_t data_x;
-  _sv_scalespace_t data_y;
-  _sv_scalespace_t data_x_it;
-  _sv_scalespace_t data_y_it;
-  _sv_ucolor_t    *image; // panel size;
-  _sv_scalespace_t image_x;
-  _sv_scalespace_t image_y;
-
-  // a data read lock is also used for coordinated non-exclusive
-  // writes to different parts of the array; data flow is set up such
-  // that reading inconsistent data/image values is only ever cosmetic
-  // and temporary; event ordering will always guarantee consistent
-  // values are flushed forward when a write is completed.  write
-  // locking is only used to enforce serialized access to prevent
-  // structural or control inconsistency.
-  pthread_rwlock_t  data_m; 
-
-  int              map_serialno;
-  int              task;
-  int              data_waiting; 
-  int              data_incomplete; 
-  int              data_next;
-  int              image_next;
-  int             *image_flags;
-  // status locked by panel
-
-  // resampling helpers; locked via data_lock/data_serialno
-  unsigned char   *resample_xdelA;
-  unsigned char   *resample_xdelB;
-  int             *resample_xnumA;
-  int             *resample_xnumB;
-  float            resample_xscalemul;
-
-  unsigned char   *resample_ydelA;
-  unsigned char   *resample_ydelB;
-  int             *resample_ynumA;
-  int             *resample_ynumB;
-  float           *resample_yscalemul;
-
-  // ui elements; use gdk lock
-  _sv_mapping_t   *mapping;
-  _sv_slider_t    *scale;
-  GtkWidget       *range_pulldown;
-  double           alphadel;
-
-};
-
-union {
-  _sv_plane_proto_t proto;
-  _sv_plane_bg_t bg;
-
-  _sv_plane_2d_t p2d;
-} _sv_plane;
-
 typedef struct {
-  pthread_rwlock_t activelock; 
-  pthread_mutex_t  panellock;
-  int              busy;
-
+  pthread_rwlock_t panel_m;
+  pthread_mutex_t  status_m;
+  pthread_mutex_t  payload_m;
+  
   // pending computation payload
   int               recompute_pending;
-  _sv_scalespace_t  plot_x;
-  _sv_scalespace_t  plot_y;
-  double           *dim_lo;
-  double           *dim_v;
-  double           *dim_hi;
+  int               recompute_dims;
+  sv_dim_data_t    *recompute_payload;
 
   // composite 'background' plane
   _sv_plane_bg_t *bg;

Added: trunk/sushivision/plane-2d.c
===================================================================
--- trunk/sushivision/plane-2d.c	                        (rev 0)
+++ trunk/sushivision/plane-2d.c	2007-09-21 16:51:36 UTC (rev 13870)
@@ -0,0 +1,2102 @@
+/*
+ *
+ *     sushivision copyright (C) 2006-2007 Monty <monty at xiph.org>
+ *
+ *  sushivision is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *   
+ *  sushivision is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *   
+ *  You should have received a copy of the GNU General Public License
+ *  along with sushivision; see the file COPYING.  If not, write to the
+ *  Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * 
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <math.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <gtk/gtk.h>
+#include <cairo-ft.h>
+#include "internal.h"
+
+// called from worker thread
+static void recompute_setup(sv_plane2d_t *pl, sv_panel_t *p, 
+			    sv_dim_data_t *payload, int dims){
+  sv_dim_data_t *ddx = payload+p->x_dim;
+  sv_dim_data_t *ddy = payload+p->y_dim;
+  int w = p->bg->image_x->pixels;
+  int h = p->bg->image_y->pixels;
+
+  pl->image_serialno++;
+  pl->data_x = _sv_dim_datascale(payload + x_dim, p->bg->image_x, 
+				 w * p->oversample_n / p->oversample_d, 0);
+  pl->data_y = _sv_dim_datascale(payload + y_dim, p->bg->image_y, 
+				 h * p->oversample_n / p->oversample_d, 1);
+
+  pl->data_waiting=0;
+  pl->data_incomplete=0;
+  pl->data_next=0;
+  pl->image_next=0;
+
+}
+
+// called from worker thread
+static int image_resize(sv_plane2d_t *pl, sv_panel_t *p){
+
+
+}
+
+// called from worker thread
+static int data_resize(sv_plane2d_t *pl, sv_panel_t *p){
+
+
+}
+
+// called from worker thread
+static int image_work(sv_plane2d_t *pl, sv_panel_t *p){
+
+
+}
+
+// called from worker thread
+static int data_work(sv_plane2d_t *pl, sv_panel_t *p){
+
+
+}
+
+// called from GTK/API
+static void plane_remap(sv_plane2d_t *pl, sv_panel_t *p){
+
+
+}
+
+
+
+
+
+
+
+// enter unlocked
+static void _sv_planez_compute_line(sv_panel_t *p, 
+				    _sv_plane2d_t *z,
+
+				    int serialno,
+
+				    int dw,
+				    int y,
+				    int x_d, 
+				    _sv_scalespace_t sxi,
+				    double *dim_vals, 
+				    _sv_bythread_cache_2d_t *c){
+
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int i,j;
+  
+  /* cache access is unlocked because the cache is private to this
+     worker thread */
+
+  for(j=0;j<dw;j++){
+    double *fout = c->fout;
+    sv_func_t **f = p2->used_function_list;
+    int *obj_y_off = p2->y_fout_offset;
+    int *onum = p2->y_obj_to_panel;
+    
+    /* by function */
+    dim_vals[x_d] = _sv_scalespace_value(&sxi,j);   
+    for(i=0;i<p2->used_functions;i++){
+      (*f)->callback(dim_vals,fout);
+      fout += (*f)->outputs;
+      f++;
+    }
+    
+    /* process function output by plane type/objective */
+    /* 2d panels currently only care about the Y output value */
+    
+    /* slider map */
+    for(i=0;i<p2->y_obj_num;i++){
+      float val = (float)_sv_slider_val_to_del(p2->range_scales[*onum++], c->fout[*obj_y_off++]);
+      if(isnan(val)){
+	c->y_map[i][j] = -1;
+      }else{
+	if(val<0)val=0;
+	if(val>1)val=1;
+	c->y_map[i][j] = rint(val * (256.f*256.f*256.f));
+      }
+    }
+  }
+
+  gdk_lock ();
+  if(p->private->plot_serialno == serialno){
+    for(j=0;j<p2->y_obj_num;j++){
+      int *d = p2->y_map[j] + y*dw;
+      int *td = c->y_map[j];
+      
+      memcpy(d,td,dw*sizeof(*d));
+      
+    }
+  }
+  gdk_unlock ();
+}
+
+// call with lock
+static void _sv_panel2d_clear_pane(sv_panel_t *p){
+
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int pw = p2->x.pixels;
+  int ph = p2->y.pixels;
+  int i;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  
+  for(i=0;i<p2->y_obj_num;i++){
+    // map is freed and nulled to avoid triggering a fast-scale on an empty data pane
+    if(p2->y_map[i])
+      free(p2->y_map[i]);
+    p2->y_map[i]=NULL;
+
+    // free y_planes so that initial remap doesn't waste time on mix
+    // op; they are recreated during remap at the point the y_todo
+    // vector indicates something to do
+    if(p2->y_planes[i])
+      free(p2->y_planes[i]);
+    p2->y_planes[i] = NULL;
+
+    // work vector is merely cleared
+    memset(p2->y_planetodo[i], 0, ph*sizeof(**p2->y_planetodo));
+  }
+
+  // clear the background surface 
+  if(plot->datarect)
+    memset(plot->datarect, 0, ph*pw*sizeof(*plot->datarect));
+
+  // the bg is not marked to be refreshed; computation setup will do
+  // that as part of the fast_scale, even if the fast_scale is
+  // short-circuited to a noop.
+}
+
+typedef struct{
+  double x;
+  double y;
+  double z;
+} compute_result;
+
+// used by the legend code. this lets us get away with having only a mapped display pane
+// call with lock
+static void _sv_panel2d_compute_point(sv_panel_t *p,sv_obj_t *o, double x, double y, compute_result *out){
+  double dim_vals[_sv_dimensions];
+  int i,j;
+  int pflag=0;
+  int eflag=0;
+
+  // fill in dimensions
+  int x_d = p->private->x_d->number;
+  int y_d = p->private->y_d->number;
+
+  for(i=0;i<_sv_dimensions;i++){
+    sv_dim_t *dim = _sv_dimension_list[i];
+    dim_vals[i]=dim->val;
+  }
+
+  gdk_unlock ();
+
+  dim_vals[x_d] = x;
+  dim_vals[y_d] = y;
+
+  *out = (compute_result){NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN};
+
+  // compute
+  for(i=0;i<_sv_functions;i++){
+    sv_func_t *f = _sv_function_list[i];
+    int compflag = 0;
+    double fout[f->outputs];
+    double val;
+
+    // compute and demultiplex output
+    for(j=0;j<o->outputs;j++){
+      if(o->function_map[j] == i){
+
+	if(!compflag) f->callback(dim_vals,fout);
+	compflag = 1;
+	
+	val = fout[o->output_map[j]];
+	switch(o->output_types[j]){
+	case 'X':
+	  out->x = val;
+	  break;
+	case 'Y':
+	  out->y = val;
+	  break;
+	case 'Z':
+	  out->z = val;
+	  break;
+	case 'E':
+	  if(eflag)
+	    out->e2 = val;
+	  else
+	    out->e1 = val;
+	  eflag = 1;
+	  break;
+	case 'P':
+	  if(pflag)
+	    out->p2 = val;
+	  else
+	    out->p1 = val;
+	  pflag = 1;
+	  break;
+	case 'M':
+	  out->m = val;
+	  break;
+	}
+      }
+    }
+  }
+  gdk_lock ();
+
+}
+
+/* functions that perform actual graphical rendering */
+
+static float _sv_panel2d_resample_helpers_init(_sv_scalespace_t *to, _sv_scalespace_t *from,
+					       unsigned char *delA, unsigned char *delB, 
+					       int *posA, int *posB,
+					       int xymul){
+  int i;
+  int dw = from->pixels;
+  int pw = to->pixels;
+
+  long scalenum = _sv_scalespace_scalenum(to,from);
+  long scaleden = _sv_scalespace_scaleden(to,from);
+  long del = _sv_scalespace_scaleoff(to,from);
+  int bin = del / scaleden;
+  del -= bin * scaleden; 
+  int discscale = (scaleden>scalenum?scalenum:scaleden);
+  int total = xymul*scalenum/discscale;
+
+  for(i=0;i<pw;i++){
+    long del2 = del + scalenum;
+    int sizeceil = (del2 + scaleden - 1)/ scaleden; // ceiling
+    int sizefloor = del2 / scaleden;
+
+    while(bin<0 && del2>scaleden){
+      bin++;
+      del = 0;
+      del2 -= scaleden;
+      sizeceil--;
+    }
+    
+    if(del2 > scaleden && bin>=0 && bin<dw){
+      int rem = total;
+
+      delA[i] = ((xymul * (scaleden - del)) + (discscale>>1)) / discscale;
+      posA[i] = bin;
+      rem -= delA[i];
+      rem -= xymul*(sizeceil-2);
+
+      while(bin+sizeceil>dw){
+	sizeceil--;
+	del2=0;
+      }
+
+      del2 %= scaleden;
+      if(rem<0){
+	delA[i] += rem;
+	delB[i] = 0;
+      }else{
+	delB[i] = rem; // don't leak 
+      }
+      posB[i] = bin+sizeceil;
+
+    }else{
+      if(bin<0 || bin>=dw){
+	delA[i] = 0;
+	posA[i] = 0;
+	delB[i] = 0;
+	posB[i] = 0;
+      }else{
+	delA[i] = xymul;
+	posA[i] = bin;
+	delB[i] = 0;
+	posB[i] = bin+1;
+	if(del2 == scaleden)del2=0;
+      }
+    }
+
+    bin += sizefloor;
+    del = del2;
+  }
+  return (float)xymul/total;
+}
+
+/* x resample helpers are put in the per-thread cache because locking it would
+   be relatively expensive. */
+// call while locked
+static void _sv_panel2d_resample_helpers_manage_x(sv_panel_t *p, _sv_bythread_cache_2d_t *c){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  if(p->private->plot_serialno != c->serialno){
+    int pw = p2->x.pixels;
+    c->serialno = p->private->plot_serialno;
+
+    if(c->xdelA)
+      free(c->xdelA);
+    if(c->xdelB)
+      free(c->xdelB);
+    if(c->xnumA)
+      free(c->xnumA);
+    if(c->xnumB)
+      free(c->xnumB);
+    
+    c->xdelA = calloc(pw,sizeof(*c->xdelA));
+    c->xdelB = calloc(pw,sizeof(*c->xdelB));
+    c->xnumA = calloc(pw,sizeof(*c->xnumA));
+    c->xnumB = calloc(pw,sizeof(*c->xnumB));
+    c->xscalemul = _sv_panel2d_resample_helpers_init(&p2->x, &p2->x_v, c->xdelA, c->xdelB, c->xnumA, c->xnumB, 17);
+  }
+}
+
+/* y resample is in the panel struct as per-row access is already locked */
+// call while locked
+static void _sv_panel2d_resample_helpers_manage_y(sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  if(p->private->plot_serialno != p2->resample_serialno){
+    int ph = p2->y.pixels;
+    p2->resample_serialno = p->private->plot_serialno;
+
+    if(p2->ydelA)
+      free(p2->ydelA);
+    if(p2->ydelB)
+      free(p2->ydelB);
+    if(p2->ynumA)
+      free(p2->ynumA);
+    if(p2->ynumB)
+      free(p2->ynumB);
+    
+    p2->ydelA = calloc(ph,sizeof(*p2->ydelA));
+    p2->ydelB = calloc(ph,sizeof(*p2->ydelB));
+    p2->ynumA = calloc(ph,sizeof(*p2->ynumA));
+    p2->ynumB = calloc(ph,sizeof(*p2->ynumB));
+    p2->yscalemul = _sv_panel2d_resample_helpers_init(&p2->y, &p2->y_v, p2->ydelA, p2->ydelB, p2->ynumA, p2->ynumB, 15);
+  }
+}
+
+static inline void _sv_panel2d_mapping_calc( void (*m)(int,int, _sv_lcolor_t *), 
+					     int low,
+					     float range,
+					     int in, 
+					     int alpha, 
+					     int mul, 
+					     _sv_lcolor_t *outc){
+  if(mul && in>=alpha){
+    int val = rint((in - low) * range);
+    if(val<0)val=0;
+    if(val>65536)val=65536;
+    m(val,mul,outc);
+  }
+}
+
+/* the data rectangle is data width/height mapped deltas.  we render
+   and subsample at the same time. */
+/* return: -1 == abort
+            0 == more work to be done in this plane
+	    1 == plane fully dispatched (possibly not complete) */
+
+/* enter with lock */
+static int _sv_panel2d_resample_render_y_plane_line(sv_panel_t *p, _sv_bythread_cache_2d_t *c, 
+						    int plot_serialno, int map_serialno, int y_no){
+  
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int objnum = p2->y_obj_to_panel[y_no]; 
+  int *in_data = p2->y_map[y_no];
+  int ph = p2->y.pixels;
+
+  if(!in_data || !c){
+    p->private->map_complete_count -= ph;
+    return 1;
+  }
+
+  unsigned char *todo = p2->y_planetodo[y_no];
+  int i = p2->y_next_line;
+  int j;
+
+  /* find a row that needs to be updated */
+  while(i<ph && !todo[i]){
+    p->private->map_complete_count--;
+    p2->y_next_line++;
+    i++;
+  }
+
+  if(i == ph) return 1;
+
+  p2->y_next_line++;
+
+  /* row [i] needs to be updated; marshal */
+  _sv_mapping_t *map = p2->mappings+objnum;
+  void (*mapfunc)(int,int, _sv_lcolor_t *) = map->mapfunc;
+  int ol_alpha = rint(p2->alphadel[y_no] * 16777216.f);
+  _sv_ucolor_t *panel = p2->y_planes[y_no];
+  int ol_low = rint(map->low * 16777216.f);
+  float ol_range = map->i_range * (1.f/256.f);
+
+  int pw = p2->x.pixels;
+  int dw = p2->x_v.pixels;
+  int dh = p2->y_v.pixels;
+  _sv_ccolor_t work[pw];
+
+  if(!panel)
+    panel = p2->y_planes[y_no] = calloc(pw*ph, sizeof(**p2->y_planes));
+
+  if(ph!=dh || pw!=dw){
+    /* resampled row computation; may involve multiple data rows */
+
+    _sv_panel2d_resample_helpers_manage_y(p);
+    _sv_panel2d_resample_helpers_manage_x(p,c);
+
+    float idel = p2->yscalemul * c->xscalemul;
+
+    /* by column */
+    int ydelA=p2->ydelA[i];
+    int ydelB=p2->ydelB[i];
+    int ystart=p2->ynumA[i];
+    int yend=p2->ynumB[i];
+    int lh = yend - ystart;
+    int data[lh*dw];
+
+    memcpy(data,in_data+ystart*dw,sizeof(data));
+
+    gdk_unlock();
+
+    unsigned char *xdelA = c->xdelA;
+    unsigned char *xdelB = c->xdelB;
+    int *xnumA = c->xnumA;
+    int *xnumB = c->xnumB;
+      
+    /* by panel col */
+    for(j=0;j<pw;j++){
+      
+      _sv_lcolor_t out = (_sv_lcolor_t){0,0,0,0}; 
+      int xstart = xnumA[j];
+      int xend = xnumB[j];
+      int dx = xstart;
+      int xA = xdelA[j];
+      int xB = xdelB[j];
+      int y = ystart;
+
+      // first line
+      if(y<yend){
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx++], ol_alpha, ydelA*xA, &out);
+	
+	for(; dx < xend-1; dx++)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelA*17, &out);
+	
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelA*xB, &out);
+	y++;
+      }
+
+      // mid lines
+      for(;y<yend-1;y++){
+	dx = xstart += dw;
+	xend += dw;
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx++], ol_alpha, 15*xA, &out);
+	
+	for(; dx < xend-1; dx++)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, 255, &out);
+	
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, 15*xB, &out);
+      }
+      
+      // last line
+      if(y<yend){
+	dx = xstart += dw;
+	xend += dw;
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx++], ol_alpha, ydelB*xA, &out);
+	
+	for(; dx < xend-1; dx++)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelB*17, &out);
+	
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelB*xB, &out);
+      }
+
+      work[j].a = (u_int32_t)(out.a*idel);
+      work[j].r = (u_int32_t)(out.r*idel);
+      work[j].g = (u_int32_t)(out.g*idel);
+      work[j].b = (u_int32_t)(out.b*idel);
+      
+    }
+
+  }else{
+    /* non-resampling render */
+
+    int data[dw];
+    memcpy(data,in_data+i*dw,sizeof(data));
+    gdk_unlock();      
+
+    for(j=0;j<pw;j++){
+
+      _sv_lcolor_t out = (_sv_lcolor_t){0,0,0,0};
+      _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[j], ol_alpha, 255, &out);
+	
+      work[j].a = (u_int32_t)(out.a);
+      work[j].r = (u_int32_t)(out.r);
+      work[j].g = (u_int32_t)(out.g);
+      work[j].b = (u_int32_t)(out.b);
+    }
+  }
+
+  gdk_lock ();  
+  if(plot_serialno != p->private->plot_serialno ||
+     map_serialno != p->private->map_serialno)
+    return -1;
+  memcpy(panel+i*pw,work,sizeof(work));
+  p2->bg_todo[i] = 1;
+  p2->y_planetodo[y_no][i] = 0;
+
+  // must be last; it indicates completion
+  p->private->map_complete_count--;
+  return 0;
+}
+
+static void render_checks(_sv_ucolor_t *c, int w, int y){
+  /* default checked background */
+  /* 16x16 'mid-checks' */ 
+  int x,j;
+  
+  int phase = (y>>4)&1;
+  for(x=0;x<w;){
+    u_int32_t phaseval = 0xff505050UL;
+    if(phase) phaseval = 0xff808080UL;
+    for(j=0;j<16 && x<w;j++,x++)
+      c[x].u = phaseval;
+    phase=!phase;
+  }
+}
+
+// enter with lock
+static int _sv_panel2d_render_bg_line(sv_panel_t *p, int plot_serialno, int map_serialno){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  if(plot_serialno != p->private->plot_serialno ||
+     map_serialno != p->private->map_serialno) return -1;
+  
+  int ph = p2->y.pixels;
+  int pw = p2->x.pixels;
+  unsigned char *todo = p2->bg_todo;
+  int i = p2->bg_next_line,j;
+  _sv_ucolor_t work_bg[pw];
+  _sv_ucolor_t work_pl[pw];
+  int bgmode = p->private->bg_type;
+
+  /* find a row that needs to be updated */
+  while(i<ph && !todo[i]){
+    p->private->map_complete_count--;
+    p2->bg_next_line++;
+    i++;
+  }
+
+  if(i == ph)
+    goto done;
+
+  if(i < p2->bg_first_line) p2->bg_first_line = i;
+  if(i+1 > p2->bg_last_line) p2->bg_last_line = i+1;
+  p2->bg_next_line++;
+
+  /* gray background checks */
+  gdk_unlock();
+
+  switch(bgmode){
+  case SV_BG_WHITE:
+    for(j=0;j<pw;j++)
+      work_bg[j].u = 0xffffffffU;
+    break;
+  case SV_BG_BLACK:
+    for(j=0;j<pw;j++)
+      work_bg[j].u = 0xff000000U;
+    break;
+  default:
+    render_checks(work_bg,pw,i);
+    break;
+  }
+
+  /* by objective */
+  for(j=0;j<p->objectives;j++){
+    int o_ynum = p2->y_obj_from_panel[j];
+    
+    gdk_lock();
+    if(plot_serialno != p->private->plot_serialno ||
+       map_serialno != p->private->map_serialno) return -1;
+
+    /**** mix Y plane */
+    
+    if(p2->y_planes[o_ynum]){
+      int x;
+      _sv_ucolor_t (*mixfunc)(_sv_ucolor_t,_sv_ucolor_t) = p2->mappings[j].mixfunc;
+      _sv_ucolor_t *rect = p2->y_planes[o_ynum] + i*pw;
+      memcpy(work_pl,rect,sizeof(work_pl));
+      
+      gdk_unlock();
+      for(x=0;x<pw;x++)
+	work_bg[x] = mixfunc(work_pl[x],work_bg[x]);
+    }else
+      gdk_unlock();
+
+    /**** mix Z plane */
+    
+    /**** mix vector plane */
+
+  }
+
+  gdk_lock();
+  if(plot_serialno != p->private->plot_serialno ||
+     map_serialno != p->private->map_serialno) return -1;
+
+    // rendered a line, get it on the screen */
+  
+  memcpy(plot->datarect+pw*i, work_bg, sizeof(work_bg));
+
+  p->private->map_complete_count--;
+
+ done:
+  if(p->private->map_complete_count)
+    return 1; // not done yet
+
+  return 0;
+}
+
+static void _sv_panel2d_mark_map_full(sv_panel_t *p);
+
+// enter with lock; returns zero if thread should sleep / get distracted
+static int _sv_panel2d_remap(sv_panel_t *p, _sv_bythread_cache_2d_t *thread_cache){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(!plot) goto abort;
+  int ph = plot->y.pixels;
+  int pw = plot->x.pixels;
+  
+  int plot_serialno = p->private->plot_serialno; 
+  int map_serialno = p->private->map_serialno; 
+
+  /* brand new remap indicated by the generic progress indicator being set to 0 */
+  if(p->private->map_progress_count == 0){
+
+    p->private->map_progress_count = 1; // 'in progress'
+    p->private->map_complete_count = p2->y_obj_num * ph; // count down to 0; 0 indicates completion
+
+    // set up Y plane rendering
+    p2->y_next_plane = 0;
+    p2->y_next_line = 0;
+
+    // bg mix
+    p2->bg_next_line = 0;
+    p2->bg_first_line = ph;
+    p2->bg_last_line = 0;
+
+    if(!p2->partial_remap)
+      _sv_panel2d_mark_map_full(p);
+  }
+
+  /* by plane, by line; each plane renders independently */
+  /* Y planes */
+  if(p2->y_planetodo){
+    if(p2->y_next_plane < p2->y_obj_num){
+      int status = _sv_panel2d_resample_render_y_plane_line(p, thread_cache, 
+							    plot_serialno, map_serialno, 
+							    p2->y_next_plane);
+      if(status == -1) goto abort;
+      if(status == 1){
+	p2->y_next_plane++;
+	p2->y_next_line = 0;
+      }
+      return 1;
+    }
+  }else{
+    p->private->map_complete_count = 0;
+  }
+
+  /* renders have been completely dispatched, but are they complete? */
+  /* the below is effectively a a thread join */
+  if(p2->bg_next_line == 0){
+
+    // join still needs to complete....
+    if(p->private->map_complete_count){
+      // nonzero complete count, not finished.  returning zero will cause
+      // this worker thread to sleep or go on to do other things.
+      return 0; 
+    }else{
+      // zero complete count, the planes are done; we can begin
+      // background render.  At least one thread is guaranteed to get
+      // here, which is enough; we can now wake the others [if they were
+      // asleep] and have them look for work here. */
+      p->private->map_complete_count = ph; // [ph] lines to render in bg plane
+      p2->bg_next_line = 0;
+
+      _sv_wake_workers();
+    }
+  }
+
+  /* mix new background, again line by line */
+  if(p2->bg_next_line < ph){
+    int status = _sv_panel2d_render_bg_line(p, plot_serialno, map_serialno);
+    if(status == -1) goto abort;
+    if(p->private->map_complete_count)return status;
+  }else
+    return 0; // nothing left to dispatch
+
+  // entirely finished.
+
+  // remap completed; flush background to screen
+  _sv_plot_expose_request_partial (plot,0,p2->bg_first_line,
+			       pw,p2->bg_last_line - p2->bg_first_line);
+  gdk_flush();
+
+  // clean bg todo list
+  memset(p2->bg_todo,0,ph*sizeof(*p2->bg_todo));
+
+  // clear 'panel in progress' flag
+  p2->partial_remap = 0;
+  _sv_panel_clean_map(p);
+  return 0;
+
+ abort:
+  // reset progress to 'start over'
+  return 1;
+}
+
+// looks like a cop-out but is actually the correct thing to do; the
+// data *must* be WYSIWYG from panel display.
+static void _sv_panel2d_print_bg(sv_panel_t *p, cairo_t *c){
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(!plot) return;
+
+  cairo_pattern_t *pattern = cairo_pattern_create_for_surface(plot->back);
+  cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
+  cairo_set_source(c,pattern);
+  cairo_paint(c);
+
+  cairo_pattern_destroy(pattern);
+}
+
+static void _sv_panel2d_print(sv_panel_t *p, cairo_t *c, int w, int h){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  double pw = p->private->graph->allocation.width;
+  double ph = p->private->graph->allocation.height;
+  double scale;
+  int i;
+  double maxlabelw=0;
+  double y;
+
+  if(w/pw < h/ph)
+    scale = w/pw;
+  else
+    scale = h/ph;
+
+  cairo_matrix_t m;
+  cairo_save(c);
+  cairo_get_matrix(c,&m);
+  cairo_matrix_scale(&m,scale,scale);
+  cairo_set_matrix(c,&m);
+  
+  _sv_plot_print(plot, c, ph*scale, (void(*)(void *, cairo_t *))_sv_panel2d_print_bg, p);
+  cairo_restore(c);
+
+  // find extents widths for objective scale labels
+  cairo_set_font_size(c,10);
+  for(i=0;i<p->objectives;i++){
+    cairo_text_extents_t ex;
+    sv_obj_t *o = p->objective_list[i].o;
+    cairo_text_extents(c, o->name, &ex);
+    if(ex.width > maxlabelw) maxlabelw=ex.width;
+  }
+
+
+  y = ph * scale + 10;
+
+  for(i=0;i<p->objectives;i++){
+    sv_obj_t *o = p->objective_list[i].o;
+    _sv_slider_t *s = p2->range_scales[i];
+    
+    // get scale height
+    double labelh = _sv_slider_print_height(s);
+    cairo_text_extents_t ex;
+    cairo_text_extents (c, o->name, &ex);
+
+    int lx = maxlabelw - ex.width;
+    int ly = labelh/2 + ex.height/2;
+    
+    // print objective labels
+    cairo_set_source_rgb(c,0.,0.,0.);
+    cairo_move_to (c, lx,ly+y);
+    cairo_show_text (c, o->name);
+
+    // draw slider
+    // set translation
+    cairo_save(c);
+    cairo_translate (c, maxlabelw + 10, y);
+    _sv_slider_print(s, c, pw*scale - maxlabelw - 10, labelh);
+    cairo_restore(c);
+
+    y += labelh;
+  }
+
+}
+
+// call while locked
+static void _sv_panel2d_mark_map_plane(sv_panel_t *p, int onum, int y, int z, int v){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int ph = p2->y.pixels;
+
+  if(y && p2->y_planetodo){
+    int y_no = p2->y_obj_from_panel[onum];
+    if(y_no>=0 && p2->y_planetodo[y_no])
+      memset(p2->y_planetodo[y_no],1,ph * sizeof(**p2->y_planetodo));
+  }
+  p2->partial_remap = 1;
+}
+
+// call while locked 
+static void _sv_panel2d_mark_map_line_y(sv_panel_t *p, int line){
+  // determine all panel lines this y data line affects
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int ph = p2->y.pixels;
+  int pw = p2->x.pixels;
+  int dw = p2->x_v.pixels;
+  int dh = p2->y_v.pixels;
+  int i,j;
+
+  p2->partial_remap = 1;
+
+  if(ph!=dh || pw!=dw){
+    /* resampled row computation; may involve multiple data rows */
+    if(p2->y_planetodo){
+      _sv_panel2d_resample_helpers_manage_y(p);
+      
+      for(i=0;i<ph;i++)
+	if(p2->ynumA[i]<=line &&
+	   p2->ynumB[i]>line){
+	  
+	  for(j=0;j<p2->y_obj_num;j++)
+	    if(p2->y_planetodo[j])
+	      p2->y_planetodo[j][i]=1;
+      }
+    }
+  }else{
+    if(p2->y_planetodo)
+      if(line>=0 && line<ph)
+	for(j=0;j<p2->y_obj_num;j++)
+	  if(p2->y_planetodo[j])
+	    p2->y_planetodo[j][line]=1;
+  }
+}
+
+// call while locked
+static void _sv_panel2d_mark_map_full(sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int ph = p2->y.pixels;
+  int i,j;
+
+  p2->partial_remap = 1;
+
+  if(p2->y_planetodo){
+    for(j=0;j<p2->y_obj_num;j++){
+      if(p2->y_planetodo[j]){
+	for(i=0;i<ph;i++){
+	  p2->y_planetodo[j][i]=1;
+	}
+      }
+    }
+  }
+}
+
+// enter with lock
+static void _sv_panel2d_update_legend(sv_panel_t *p){  
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(plot){
+    int i;
+    char buffer[320];
+    int depth = 0;
+    _sv_plot_legend_clear(plot);
+
+    // potentially add each dimension to the legend; add axis
+    // dimensions only if crosshairs are active
+
+    // display decimal precision relative to display scales
+    if(3-_sv_scalespace_decimal_exponent(&p2->x) > depth) 
+      depth = 3-_sv_scalespace_decimal_exponent(&p2->x);
+    if(3-_sv_scalespace_decimal_exponent(&p2->y) > depth) 
+      depth = 3-_sv_scalespace_decimal_exponent(&p2->y);
+    for(i=0;i<p->dimensions;i++){
+      sv_dim_t *d = p->dimension_list[i].d;
+      if( (d!=p->private->x_d && d!=p->private->y_d) ||
+	  plot->cross_active){
+	snprintf(buffer,320,"%s = %+.*f",
+		 p->dimension_list[i].d->legend,
+		 depth,
+		 p->dimension_list[i].d->val);
+	_sv_plot_legend_add(plot,buffer);
+      }
+    }
+    
+    // add each active objective plane to the legend
+    // choose the value under the crosshairs 
+    if(plot->cross_active){
+      // one space 
+      _sv_plot_legend_add(plot,NULL);
+
+      for(i=0;i<p->objectives;i++){
+	
+	if(!_sv_mapping_inactive_p(p2->mappings+i)){
+	  compute_result vals;
+	  _sv_panel2d_compute_point(p,p->objective_list[i].o, plot->selx, plot->sely, &vals);
+	  
+	  if(!isnan(vals.y)){
+	    
+	    snprintf(buffer,320,"%s = %f",
+		     p->objective_list[i].o->name,
+		     vals.y);
+	    _sv_plot_legend_add(plot,buffer);
+	  }
+	}
+      }
+    }
+  }
+}
+
+static void _sv_panel2d_mapchange_callback(GtkWidget *w,gpointer in){
+  sv_obj_list_t *optr = (sv_obj_list_t *)in;
+  //sv_obj_t *o = optr->o;
+  sv_panel_t *p = optr->p;
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int onum = optr - p->objective_list;
+
+  _sv_undo_push();
+  _sv_undo_suspend();
+
+  _sv_mapping_set_func(&p2->mappings[onum],gtk_combo_box_get_active(GTK_COMBO_BOX(w)));
+  
+  //redraw the map slider
+  _sv_slider_set_gradient(p2->range_scales[onum], &p2->mappings[onum]);
+
+  // in the event the mapping active state changed
+  _sv_panel_dirty_legend(p);
+
+  //redraw the plot
+  _sv_panel2d_mark_map_plane(p,onum,1,0,0);
+  _sv_panel_dirty_map(p);
+  _sv_undo_resume();
+}
+
+static void _sv_panel2d_map_callback(void *in,int buttonstate){
+  sv_obj_list_t *optr = (sv_obj_list_t *)in;
+  //sv_obj_t *o = optr->o;
+  sv_panel_t *p = optr->p;
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int onum = optr - p->objective_list;
+
+  if(buttonstate == 0){
+    _sv_undo_push();
+    _sv_undo_suspend();
+  }
+
+  // recache alpha del */
+  p2->alphadel[onum] = 
+    _sv_slider_val_to_del(p2->range_scales[onum],
+		      _sv_slider_get_value(p2->range_scales[onum],1));
+
+  // redraw the plot on motion
+  if(buttonstate == 1){
+    _sv_panel2d_mark_map_plane(p,onum,1,0,0);
+    _sv_panel_dirty_map(p);
+  }
+  if(buttonstate == 2)
+    _sv_undo_resume();
+}
+
+static void _sv_panel2d_update_xysel(sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int i;
+  // update which x/y buttons are pressable */
+  // enable/disable dimension slider thumbs
+
+  for(i=0;i<p->dimensions;i++){
+    if(p2->dim_xb[i] &&
+       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_xb[i]))){
+      // make the y insensitive
+      if(p2->dim_yb[i])
+	_gtk_widget_set_sensitive_fixup(p2->dim_yb[i],FALSE);
+
+      // set the x dim flag
+      p->private->x_d = p->dimension_list[i].d;
+      p2->x_scale = p->private->dim_scales[i];
+      p2->x_dnum = i;
+    }else{
+      // if there is a y, make it sensitive 
+      if(p2->dim_yb[i])
+	_gtk_widget_set_sensitive_fixup(p2->dim_yb[i],TRUE);
+    }
+    if(p2->dim_yb[i] &&
+       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_yb[i]))){
+      // make the x insensitive
+      if(p2->dim_xb[i])
+	_gtk_widget_set_sensitive_fixup(p2->dim_xb[i],FALSE);
+
+      // set the y dim
+      p->private->y_d = p->dimension_list[i].d;
+      p2->y_scale = p->private->dim_scales[i];
+      p2->y_dnum = i;
+    }else{
+      // if there is a x, make it sensitive 
+      if(p2->dim_xb[i])
+	_gtk_widget_set_sensitive_fixup(p2->dim_xb[i],TRUE);
+    }
+    if((p2->dim_xb[i] &&
+	gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_xb[i]))) ||
+       (p2->dim_yb[i] &&
+	gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_yb[i])))){
+      // make all thumbs visible 
+      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],0,1);
+      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],2,1);
+    }else{
+      // make bracket thumbs invisible */
+      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],0,0);
+      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],2,0);
+    }
+  } 
+}
+
+static int _v_swizzle(int y, int height){
+  int yy = height >> 5;
+  if(y < yy)
+    return (y<<5)+31;
+
+  y -= yy;
+  yy = (height+16) >> 5;
+  if(y < yy)
+    return (y<<5)+15;
+
+  y -= yy;
+  yy = (height+8) >> 4;
+  if(y < yy)
+    return (y<<4)+7;
+
+  y -= yy;
+  yy = (height+4) >> 3;
+  if(y < yy)
+    return (y<<3)+3;
+
+  y -= yy;
+  yy = (height+2) >> 2;
+  if(y < yy)
+    return (y<<2)+1;
+
+  y -= yy;
+  return y<<1;
+}
+
+// assumes data is locked
+static void _sv_panel2d_fast_scale_x(_sv_spinner_t *sp,
+				     int *data, 
+				     int w,
+				     int h,
+				     _sv_scalespace_t new,
+				     _sv_scalespace_t old){
+  int x,y;
+  int work[w];
+  int mapbase[w];
+  int mapdel[w];
+  double old_w = old.pixels;
+  double new_w = new.pixels;
+
+  double old_lo = _sv_scalespace_value(&old,0);
+  double old_hi = _sv_scalespace_value(&old,old_w);
+  double new_lo = _sv_scalespace_value(&new,0);
+  double new_hi = _sv_scalespace_value(&new,new_w);
+  double newscale = (new_hi-new_lo)/new_w;
+  double oldscale = old_w/(old_hi-old_lo);
+  for(x=0;x<w;x++){
+    double xval = (x)*newscale+new_lo;
+    double map = ((xval-old_lo)*oldscale);
+    int base = (int)floor(map);
+    int del = rint((map - floor(map))*64.f);
+    /* hack to overwhelm roundoff error; this is inside a purely
+       temporary cosmetic approximation anyway*/
+    if(base>0 && del==0){
+      mapbase[x]=base-1;
+      mapdel[x]=64;
+    }else{
+      mapbase[x]=base;
+      mapdel[x]=del;
+    }
+  }
+
+  for(y=0;y<h;y++){
+    int *data_line = data+y*w;
+    _sv_spinner_set_busy(sp);
+    for(x=0;x<w;x++){
+      if(mapbase[x]<0 || mapbase[x]>=(w-1)){
+	work[x]=-1;
+      }else{
+	int base = mapbase[x];
+	int A = data_line[base];
+	int B = data_line[base+1];
+	if(A<0 || B<0)
+	  work[x]=-1;
+	else
+	  work[x]= A + (((B - A)*mapdel[x])>>6);
+	
+      }
+    }
+    memcpy(data_line,work,w*(sizeof(*work)));
+  }   
+}
+
+static void _sv_panel2d_fast_scale_y(_sv_spinner_t *sp,
+				     int *olddata, 
+				     int *newdata, 
+				     int oldw,
+				     int neww,
+				     _sv_scalespace_t new,
+				     _sv_scalespace_t old){
+  int x,y;
+  int w = (oldw<neww?oldw:neww);
+
+  int old_h = old.pixels;
+  int new_h = new.pixels;
+
+  int mapbase[new_h];
+  int mapdel[new_h];
+
+  double old_lo = _sv_scalespace_value(&old,0);
+  double old_hi = _sv_scalespace_value(&old,(double)old_h);
+  double new_lo = _sv_scalespace_value(&new,0);
+  double new_hi = _sv_scalespace_value(&new,(double)new_h);
+  double newscale = (new_hi-new_lo)/new_h;
+  double oldscale = old_h/(old_hi-old_lo);
+  
+  for(y=0;y<new_h;y++){
+    double yval = (y)*newscale+new_lo;
+    double map = ((yval-old_lo)*oldscale);
+    int base = (int)floor(map);
+    int del = rint((map - floor(map))*64.);
+    /* hack to overwhelm roundoff error; this is inside a purely
+       temporary cosmetic approximation anyway */
+    if(base>0 && del==0){
+      mapbase[y]=base-1;
+      mapdel[y]=64;
+    }else{
+      mapbase[y]=base;
+      mapdel[y]=del;
+    }
+  }
+
+  
+  for(y=0;y<new_h;y++){
+    int base = mapbase[y];
+    int *new_column = &newdata[y*neww];
+    _sv_spinner_set_busy(sp);
+
+    if(base<0 || base>=(old_h-1)){
+      for(x=0;x<w;x++)
+	new_column[x] = -1;
+    }else{
+      int del = mapdel[y];
+      int *old_column = &olddata[base*oldw];
+
+      for(x=0;x<w;x++){
+	int A = old_column[x];
+	int B = old_column[x+oldw];
+	if(A<0 || B<0)
+	  new_column[x]=-1;
+	else
+	  new_column[x]= A + (((B-A)*del)>>6);
+      }
+    }
+  }
+}
+
+static void _sv_panel2d_fast_scale(_sv_spinner_t *sp, 
+				   int *newdata, 
+				   _sv_scalespace_t xnew,
+				   _sv_scalespace_t ynew,
+				   int *olddata,
+				   _sv_scalespace_t xold,
+				   _sv_scalespace_t yold){
+  
+  int new_w = xnew.pixels;
+  int new_h = ynew.pixels;
+  int old_w = xold.pixels;
+  int old_h = yold.pixels;
+
+  if(new_w > old_w){
+    _sv_panel2d_fast_scale_y(sp,olddata,newdata,old_w,new_w,ynew,yold);
+    _sv_panel2d_fast_scale_x(sp,newdata,new_w,new_h,xnew,xold);
+  }else{
+    _sv_panel2d_fast_scale_x(sp,olddata,old_w,old_h,xnew,xold);
+    _sv_panel2d_fast_scale_y(sp,olddata,newdata,old_w,new_w,ynew,yold);
+  }
+}
+
+// call only from main gtk thread
+static void _sv_panel2d_mark_recompute(sv_panel_t *p){
+  if(!p->private->realized) return;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(plot && GTK_WIDGET_REALIZED(GTK_WIDGET(plot))){
+    _sv_panel_dirty_plot(p);
+  }
+}
+
+static void _sv_panel2d_update_crosshairs(sv_panel_t *p){
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  double x=0,y=0;
+  int i;
+  
+  for(i=0;i<p->dimensions;i++){
+    sv_dim_t *d = p->dimension_list[i].d;
+    if(d == p->private->x_d)
+      x = d->val;
+    if(d == p->private->y_d)
+      y = d->val;
+    
+  }
+  
+  _sv_plot_set_crosshairs(plot,x,y);
+  _sv_panel_dirty_legend(p);
+}
+
+static void _sv_panel2d_center_callback(sv_dim_list_t *dptr){
+  sv_dim_t *d = dptr->d;
+  sv_panel_t *p = dptr->p;
+  int axisp = (d == p->private->x_d || d == p->private->y_d);
+
+  if(!axisp){
+    // mid slider of a non-axis dimension changed, rerender
+    _sv_panel2d_mark_recompute(p);
+  }else{
+    // mid slider of an axis dimension changed, move crosshairs
+    _sv_panel2d_update_crosshairs(p);
+  }
+}
+
+static void _sv_panel2d_bracket_callback(sv_dim_list_t *dptr){
+  sv_dim_t *d = dptr->d;
+  sv_panel_t *p = dptr->p;
+  int axisp = (d == p->private->x_d || d == p->private->y_d);
+
+  if(axisp)
+    _sv_panel2d_mark_recompute(p);
+    
+}
+
+static void _sv_panel2d_dimchange_callback(GtkWidget *button,gpointer in){
+  sv_panel_t *p = (sv_panel_t *)in;
+
+  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))){
+
+    _sv_undo_push();
+    _sv_undo_suspend();
+
+    _sv_plot_unset_box(PLOT(p->private->graph));
+    _sv_panel2d_update_xysel(p);
+
+    _sv_panel2d_clear_pane(p);
+    _sv_panel2d_mark_recompute(p);
+    _sv_panel2d_update_crosshairs(p);
+
+    _sv_undo_resume();
+  }
+}
+
+static void _sv_panel2d_crosshairs_callback(sv_panel_t *p){
+  double x=PLOT(p->private->graph)->selx;
+  double y=PLOT(p->private->graph)->sely;
+  int i;
+  
+  _sv_undo_push();
+  _sv_undo_suspend();
+
+  //plot_snap_crosshairs(PLOT(p->private->graph));
+
+  for(i=0;i<p->dimensions;i++){
+    sv_dim_t *d = p->dimension_list[i].d;
+    if(d == p->private->x_d){
+      _sv_dim_widget_set_thumb(p->private->dim_scales[i],1,x);
+    }
+
+    if(d == p->private->y_d){
+      _sv_dim_widget_set_thumb(p->private->dim_scales[i],1,y);
+    }
+    
+    p->private->oldbox_active = 0;
+  }
+
+  // dimension setting might have enforced granularity restrictions;
+  // have the display reflect that
+  x = p->private->x_d->val;
+  y = p->private->y_d->val;
+
+  _sv_plot_set_crosshairs(PLOT(p->private->graph),x,y);
+
+  _sv_panel_dirty_legend(p);
+  _sv_undo_resume();
+}
+
+static void _sv_panel2d_box_callback(void *in, int state){
+  sv_panel_t *p = (sv_panel_t *)in;
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  
+  switch(state){
+  case 0: // box set
+    _sv_undo_push();
+    _sv_plot_box_vals(plot,p2->oldbox);
+    p->private->oldbox_active = plot->box_active;
+    break;
+  case 1: // box activate
+    _sv_undo_push();
+    _sv_undo_suspend();
+
+    _sv_panel2d_crosshairs_callback(p);
+
+    _sv_dim_widget_set_thumb(p2->x_scale,0,p2->oldbox[0]);
+    _sv_dim_widget_set_thumb(p2->x_scale,2,p2->oldbox[1]);
+    _sv_dim_widget_set_thumb(p2->y_scale,0,p2->oldbox[2]);
+    _sv_dim_widget_set_thumb(p2->y_scale,2,p2->oldbox[3]);
+    p->private->oldbox_active = 0;
+    _sv_undo_resume();
+    break;
+  }
+  _sv_panel_update_menus(p);
+}
+
+void _sv_panel2d_maintain_cache(sv_panel_t *p, _sv_bythread_cache_2d_t *c, int w){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  
+  /* toplevel initialization */
+  if(c->fout == 0){
+    int i,count=0;
+    
+    /* allocate output temporary buffer */
+    for(i=0;i<p2->used_functions;i++){
+      int fnum = p2->used_function_list[i]->number;
+      sv_func_t *f = _sv_function_list[fnum];
+      count += f->outputs;
+    }
+    c->fout = calloc(count, sizeof(*c->fout));
+
+    /* objective line buffer index */
+    c->y_map = calloc(p2->y_obj_num,sizeof(*c->y_map));
+    for(i=0;i<p2->y_obj_num;i++)
+      c->y_map[i] = calloc(w,sizeof(**c->y_map));
+    c->storage_width = w;
+  }
+  
+  /* anytime the data width changes */
+  if(c->storage_width != w){
+    int i;
+    c->storage_width = w;
+    
+    for(i=0;i<p2->y_obj_num;i++)
+      c->y_map[i] = realloc(c->y_map[i],w*sizeof(**c->y_map));
+
+  }
+}
+
+
+// subtype entry point for plot remaps; lock held
+static int _sv_panel2d_map_redraw(sv_panel_t *p, _sv_bythread_cache_t *c){
+  return _sv_panel2d_remap(p,&c->p2);
+}
+
+// subtype entry point for legend redraws; lock held
+static int _sv_panel2d_legend_redraw(sv_panel_t *p){
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(p->private->legend_progress_count)return 0;
+  p->private->legend_progress_count++;
+  _sv_panel2d_update_legend(p);
+  _sv_panel_clean_legend(p);
+
+  gdk_unlock();
+  _sv_plot_draw_scales(plot);
+  gdk_lock();
+
+  _sv_plot_expose_request(plot);
+  return 1;
+}
+
+// subtype entry point for recomputation; lock held
+static int _sv_panel2d_compute(sv_panel_t *p,
+			       _sv_bythread_cache_t *c){
+
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot;
+  
+  int pw,ph,dw,dh,i,d;
+  int serialno;
+  double x_min, x_max;
+  double y_min, y_max;
+  int x_d=-1, y_d=-1;
+  _sv_scalespace_t sx,sx_v,sx_i;
+  _sv_scalespace_t sy,sy_v,sy_i;
+
+  plot = PLOT(p->private->graph);
+  pw = plot->x.pixels;
+  ph = plot->y.pixels;
+  
+  x_d = p->private->x_d->number;
+  y_d = p->private->y_d->number;
+
+  // beginning of computation init
+  if(p->private->plot_progress_count==0){
+    int remapflag = 0;
+
+    _sv_scalespace_t old_x = p2->x;
+    _sv_scalespace_t old_y = p2->y;
+    _sv_scalespace_t old_xv = p2->x_v;
+    _sv_scalespace_t old_yv = p2->y_v;
+
+    // generate new scales
+    _sv_dim_scales(p->private->x_d, 
+		   p->private->x_d->bracket[0],
+		   p->private->x_d->bracket[1],
+		   pw,pw * p->private->oversample_n / p->private->oversample_d,
+		   plot->scalespacing,
+		   p->private->x_d->legend,
+		   &sx,
+		   &sx_v,
+		   &sx_i);
+    _sv_dim_scales(p->private->y_d, 
+		   p->private->y_d->bracket[1],
+		   p->private->y_d->bracket[0],
+		   ph,ph * p->private->oversample_n / p->private->oversample_d,
+		   plot->scalespacing,
+		   p->private->y_d->legend,
+		   &sy,
+		   &sy_v,
+		   &sy_i);
+    
+    p2->x = sx;
+    p2->x_v = sx_v;
+    p2->x_i = sx_i;
+    p2->y = sy;
+    p2->y_v = sy_v;
+    p2->y_i = sy_i;
+
+    plot->x = sx;
+    plot->y = sy;
+    plot->x_v = sx_v;
+    plot->y_v = sy_v;
+
+    p->private->plot_progress_count++;
+    p->private->plot_serialno++; // we're about to free the old data rectangles
+
+    // realloc/fast scale the current data contents if appropriate
+    if(memcmp(&sx_v,&old_xv,sizeof(sx_v)) || memcmp(&sy_v,&old_yv,sizeof(sy_v))){
+      
+      // maintain data planes
+      for(i=0;i<p2->y_obj_num;i++){
+	// allocate new storage
+	int *newmap = calloc(sx_v.pixels*sy_v.pixels,sizeof(*newmap));
+	int *oldmap = p2->y_map[i];
+	int j;
+	
+	for(j=0;j<sx_v.pixels*sy_v.pixels;j++)
+	  newmap[j]=-1;
+	
+	// zoom scale data in map planes as placeholder for render
+	if(oldmap){
+	  _sv_panel2d_fast_scale(p->private->spinner,newmap, sx_v, sy_v,
+				 oldmap,old_xv, old_yv);
+	  free(oldmap);
+	}
+	p2->y_map[i] = newmap; 
+      }
+      remapflag = 1;
+    }
+
+    // realloc render planes if appropriate
+    if(memcmp(&sx,&old_x,sizeof(sx)) || memcmp(&sy,&old_y,sizeof(sy))){
+      for(i=0;i<p2->y_obj_num;i++){
+
+	// y planes
+	if(p2->y_planes[i])
+	  free(p2->y_planes[i]);
+	p2->y_planes[i] = calloc(sx.pixels*sy.pixels,sizeof(**p2->y_planes));
+
+	// todo lists
+	if(p2->y_planetodo[i])
+	  free(p2->y_planetodo[i]);
+	p2->y_planetodo[i] = calloc(sy.pixels,sizeof(**p2->y_planetodo));
+	
+      }
+
+      if(p2->bg_todo)
+	free(p2->bg_todo);
+      p2->bg_todo=calloc(ph,sizeof(*p2->bg_todo));
+      
+      remapflag = 1;
+    }
+
+    if(remapflag){
+      _sv_panel2d_mark_map_full(p);
+      _sv_panel_dirty_map(p);
+
+      gdk_unlock ();      
+      _sv_plot_draw_scales(plot); // this should happen outside lock
+      gdk_lock ();      
+    }
+
+    _sv_map_set_throttle_time(p); // swallow the first 'throttled' remap which would only be a single line;
+
+    return 1;
+  }else{
+    sx = p2->x;
+    sx_v = p2->x_v;
+    sx_i = p2->x_i;
+    sy = p2->y;
+    sy_v = p2->y_v;
+    sy_i = p2->y_i;
+    serialno = p->private->plot_serialno; 
+  }
+
+  dw = sx_v.pixels;
+  dh = sy_v.pixels;
+
+  if(p->private->plot_progress_count>dh) return 0;
+
+  _sv_panel2d_maintain_cache(p,&c->p2,dw);
+  
+  d = p->dimensions;
+
+  /* render using local dimension array; several threads will be
+     computing objectives */
+  double dim_vals[_sv_dimensions];
+  int y = _v_swizzle(p->private->plot_progress_count-1,dh);
+  p->private->plot_progress_count++;
+
+  x_min = _sv_scalespace_value(&p2->x_i,0);
+  x_max = _sv_scalespace_value(&p2->x_i,dw);
+
+  y_min = _sv_scalespace_value(&p2->y_i,0);
+  y_max = _sv_scalespace_value(&p2->y_i,dh);
+
+  // Initialize local dimension value array
+  for(i=0;i<_sv_dimensions;i++){
+    sv_dim_t *dim = _sv_dimension_list[i];
+    dim_vals[i]=dim->val;
+  }
+
+  /* unlock for computation */
+  gdk_unlock ();
+    
+  dim_vals[y_d]=_sv_scalespace_value(&sy_i, y);
+  _sv_panel2d_compute_line(p, serialno, dw, y, x_d, sx_i, dim_vals, &c->p2);
+
+  gdk_lock ();
+
+  if(p->private->plot_serialno == serialno){
+    p->private->plot_complete_count++;
+    _sv_panel2d_mark_map_line_y(p,y);
+    if(p->private->plot_complete_count>=dh){ 
+      _sv_panel_dirty_map(p);
+      _sv_panel_dirty_legend(p);
+      _sv_panel_clean_plot(p);
+    }else
+      _sv_panel_dirty_map_throttled(p); 
+  }
+
+  return 1;
+}
+
+// only called for resize events
+static void _sv_panel2d_recompute_callback(void *ptr){
+  sv_panel_t *p = (sv_panel_t *)ptr;
+  int i;
+
+  gdk_lock ();
+  _sv_panel2d_mark_recompute(p);
+  _sv_panel2d_compute(p,NULL); // initial scale setup
+
+  // temporary: blank background to checks
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  int pw = plot->x.pixels;
+  int ph = plot->y.pixels;
+  for(i=0;i<ph;i++)
+    render_checks((_sv_ucolor_t *)plot->datarect+pw*i, pw, i);
+  
+  gdk_unlock();
+}
+
+static void _sv_panel2d_undo_log(_sv_panel_undo_t *u, sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int i;
+
+  // alloc fields as necessary
+  
+  if(!u->mappings)
+    u->mappings =  calloc(p->objectives,sizeof(*u->mappings));
+  if(!u->scale_vals[0])
+    u->scale_vals[0] =  calloc(p->objectives,sizeof(**u->scale_vals));
+  if(!u->scale_vals[1])
+    u->scale_vals[1] =  calloc(p->objectives,sizeof(**u->scale_vals));
+  if(!u->scale_vals[2])
+    u->scale_vals[2] =  calloc(p->objectives,sizeof(**u->scale_vals));
+
+  // populate undo
+  for(i=0;i<p->objectives;i++){
+    u->mappings[i] = p2->mappings[i].mapnum;
+    u->scale_vals[0][i] = _sv_slider_get_value(p2->range_scales[i],0);
+    u->scale_vals[1][i] = _sv_slider_get_value(p2->range_scales[i],1);
+    u->scale_vals[2][i] = _sv_slider_get_value(p2->range_scales[i],2);
+  }
+  
+  u->x_d = p2->x_dnum;
+  u->y_d = p2->y_dnum;
+  u->box[0] = p2->oldbox[0];
+  u->box[1] = p2->oldbox[1];
+  u->box[2] = p2->oldbox[2];
+  u->box[3] = p2->oldbox[3];
+  u->box_active = p->private->oldbox_active;
+}
+
+static void _sv_panel2d_undo_restore(_sv_panel_undo_t *u, sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  int i;
+  
+  // go in through widgets
+  for(i=0;i<p->objectives;i++){
+    gtk_combo_box_set_active(GTK_COMBO_BOX(p2->range_pulldowns[i]),u->mappings[i]);
+    _sv_slider_set_value(p2->range_scales[i],0,u->scale_vals[0][i]);
+    _sv_slider_set_value(p2->range_scales[i],1,u->scale_vals[1][i]);
+    _sv_slider_set_value(p2->range_scales[i],2,u->scale_vals[2][i]);
+  }
+
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_xb[u->x_d]),TRUE);
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_yb[u->y_d]),TRUE);
+
+  _sv_panel2d_update_xysel(p);
+
+  if(u->box_active){
+    p2->oldbox[0] = u->box[0];
+    p2->oldbox[1] = u->box[1];
+    p2->oldbox[2] = u->box[2];
+    p2->oldbox[3] = u->box[3];
+    _sv_plot_box_set(plot,u->box);
+    p->private->oldbox_active = 1;
+  }else{
+    _sv_plot_unset_box(plot);
+    p->private->oldbox_active = 0;
+  }
+}
+
+static void _sv_panel2d_realize(sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int i;
+
+  _sv_undo_suspend();
+
+  p->private->toplevel = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  g_signal_connect_swapped (G_OBJECT (p->private->toplevel), "delete-event",
+			    G_CALLBACK (_sv_clean_exit), (void *)SIGINT);
+ 
+  // add border to sides with hbox/padding
+  GtkWidget *borderbox =  gtk_hbox_new(0,0);
+  gtk_container_add (GTK_CONTAINER (p->private->toplevel), borderbox);
+
+  // main layout vbox
+  p->private->topbox = gtk_vbox_new(0,0);
+  gtk_box_pack_start(GTK_BOX(borderbox), p->private->topbox, 1,1,4);
+  gtk_container_set_border_width (GTK_CONTAINER (p->private->toplevel), 1);
+
+  /* spinner, top bar */
+  {
+    GtkWidget *hbox = gtk_hbox_new(0,0);
+    gtk_box_pack_start(GTK_BOX(p->private->topbox), hbox, 0,0,0);
+    gtk_box_pack_end(GTK_BOX(hbox),GTK_WIDGET(p->private->spinner),0,0,0);
+  }
+
+  /* plotbox, graph */
+  {
+    p->private->graph = GTK_WIDGET(_sv_plot_new(_sv_panel2d_recompute_callback,p,
+						(void *)(void *)_sv_panel2d_crosshairs_callback,p,
+						_sv_panel2d_box_callback,p,0)); 
+    p->private->plotbox = p->private->graph;
+    gtk_box_pack_start(GTK_BOX(p->private->topbox), p->private->plotbox, 1,1,2);
+  }
+
+  /* obj box */
+  {
+    p2->obj_table = gtk_table_new(p->objectives, 5, 0);
+    gtk_box_pack_start(GTK_BOX(p->private->topbox), p2->obj_table, 0,0,1);
+
+    /* objective sliders */
+    p2->range_scales = calloc(p->objectives,sizeof(*p2->range_scales));
+    p2->range_pulldowns = calloc(p->objectives,sizeof(*p2->range_pulldowns));
+    p2->alphadel = calloc(p->objectives,sizeof(*p2->alphadel));
+    p2->mappings = calloc(p->objectives,sizeof(*p2->mappings));
+    for(i=0;i<p->objectives;i++){
+      GtkWidget **sl = calloc(3,sizeof(*sl));
+      sv_obj_t *o = p->objective_list[i].o;
+      int lo = o->scale->val_list[0];
+      int hi = o->scale->val_list[o->scale->vals-1];
+      
+      /* label */
+      GtkWidget *label = gtk_label_new(o->name);
+      gtk_misc_set_alignment(GTK_MISC(label),1.,.5);
+      gtk_table_attach(GTK_TABLE(p2->obj_table),label,0,1,i,i+1,
+		       GTK_FILL,0,8,0);
+      
+      /* mapping pulldown */
+      {
+	GtkWidget *menu=_gtk_combo_box_new_markup();
+	int j;
+	for(j=0;j<_sv_mapping_names();j++)
+	  gtk_combo_box_append_text (GTK_COMBO_BOX (menu), _sv_mapping_name(j));
+	gtk_combo_box_set_active(GTK_COMBO_BOX(menu),0);
+	g_signal_connect (G_OBJECT (menu), "changed",
+			  G_CALLBACK (_sv_panel2d_mapchange_callback), p->objective_list+i);
+	gtk_table_attach(GTK_TABLE(p2->obj_table),menu,4,5,i,i+1,
+			 GTK_SHRINK,GTK_SHRINK,0,0);
+	p2->range_pulldowns[i] = menu;
+      }
+
+      /* the range mapping slices/slider */ 
+      sl[0] = _sv_slice_new(_sv_panel2d_map_callback,p->objective_list+i);
+      sl[1] = _sv_slice_new(_sv_panel2d_map_callback,p->objective_list+i);
+      sl[2] = _sv_slice_new(_sv_panel2d_map_callback,p->objective_list+i);
+      
+      gtk_table_attach(GTK_TABLE(p2->obj_table),sl[0],1,2,i,i+1,
+		       GTK_EXPAND|GTK_FILL,0,0,0);
+      gtk_table_attach(GTK_TABLE(p2->obj_table),sl[1],2,3,i,i+1,
+		       GTK_EXPAND|GTK_FILL,0,0,0);
+      gtk_table_attach(GTK_TABLE(p2->obj_table),sl[2],3,4,i,i+1,
+		       GTK_EXPAND|GTK_FILL,0,0,0);
+      p2->range_scales[i] = _sv_slider_new((_sv_slice_t **)sl,3,o->scale->label_list,o->scale->val_list,
+				       o->scale->vals,_SV_SLIDER_FLAG_INDEPENDENT_MIDDLE);
+      gtk_table_set_col_spacing(GTK_TABLE(p2->obj_table),3,5);
+
+      _sv_slice_thumb_set((_sv_slice_t *)sl[0],lo);
+      _sv_slice_thumb_set((_sv_slice_t *)sl[1],lo);
+      _sv_slice_thumb_set((_sv_slice_t *)sl[2],hi);
+      _sv_mapping_setup(&p2->mappings[i],0.,1.,0);
+      _sv_slider_set_gradient(p2->range_scales[i], &p2->mappings[i]);
+    }
+  }
+
+  /* dims */
+  {
+    p2->dim_table = gtk_table_new(p->dimensions,4,0);
+    gtk_box_pack_start(GTK_BOX(p->private->topbox), p2->dim_table, 0,0,4);
+    
+    GtkWidget *first_x = NULL;
+    GtkWidget *first_y = NULL;
+    GtkWidget *pressed_y = NULL;
+    p->private->dim_scales = calloc(p->dimensions,sizeof(*p->private->dim_scales));
+    p2->dim_xb = calloc(p->dimensions,sizeof(*p2->dim_xb));
+    p2->dim_yb = calloc(p->dimensions,sizeof(*p2->dim_yb));
+    
+    for(i=0;i<p->dimensions;i++){
+      sv_dim_t *d = p->dimension_list[i].d;
+      
+      /* label */
+      GtkWidget *label = gtk_label_new(d->legend);
+      gtk_misc_set_alignment(GTK_MISC(label),1.,.5);
+      gtk_table_attach(GTK_TABLE(p2->dim_table),label,0,1,i,i+1,
+		       GTK_FILL,0,5,0);
+      
+      /* x/y radio buttons */
+      if(!(d->flags & SV_DIM_NO_X)){
+	if(first_x)
+	  p2->dim_xb[i] = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(first_x),"X");
+	else{
+	  first_x = p2->dim_xb[i] = gtk_radio_button_new_with_label(NULL,"X");
+	  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_xb[i]),TRUE);
+	}
+	gtk_table_attach(GTK_TABLE(p2->dim_table),p2->dim_xb[i],1,2,i,i+1,
+			 GTK_SHRINK,0,3,0);
+      }
+      
+      if(!(d->flags & SV_DIM_NO_Y)){
+	if(first_y)
+	  p2->dim_yb[i] = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(first_y),"Y");
+	else
+	  first_y = p2->dim_yb[i] = gtk_radio_button_new_with_label(NULL,"Y");
+	if(!pressed_y && p2->dim_xb[i]!=first_x){
+	  pressed_y = p2->dim_yb[i];
+	  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_yb[i]),TRUE);
+	}
+	gtk_table_attach(GTK_TABLE(p2->dim_table),p2->dim_yb[i],2,3,i,i+1,
+			 GTK_SHRINK,0,3,0);
+      }
+      
+      p->private->dim_scales[i] = 
+	_sv_dim_widget_new(p->dimension_list+i,_sv_panel2d_center_callback,_sv_panel2d_bracket_callback);
+      
+      gtk_table_attach(GTK_TABLE(p2->dim_table),
+		       p->private->dim_scales[i]->t,
+		       3,4,i,i+1,
+		       GTK_EXPAND|GTK_FILL,0,0,0);
+      
+    }
+    for(i=0;i<p->dimensions;i++){
+      if(p2->dim_xb[i])
+	g_signal_connect (G_OBJECT (p2->dim_xb[i]), "toggled",
+			  G_CALLBACK (_sv_panel2d_dimchange_callback), p);
+      if(p2->dim_yb[i])
+	g_signal_connect (G_OBJECT (p2->dim_yb[i]), "toggled",
+			  G_CALLBACK (_sv_panel2d_dimchange_callback), p);
+    }
+  }
+
+  _sv_panel2d_update_xysel(p);
+
+  gtk_widget_realize(p->private->toplevel);
+  gtk_widget_realize(p->private->graph);
+  gtk_widget_realize(GTK_WIDGET(p->private->spinner));
+  gtk_widget_show_all(p->private->toplevel);
+  _sv_panel2d_update_xysel(p); // yes, this was already done; however,
+			       // gtk clobbered the event setup on the
+			       // insensitive buttons when it realized
+			       // them.  This call will restore them.
+
+  _sv_undo_resume();
+}
+
+static int _sv_panel2d_save(sv_panel_t *p, xmlNodePtr pn){  
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int ret=0,i;
+
+  xmlNodePtr n;
+
+  xmlNewProp(pn, (xmlChar *)"type", (xmlChar *)"2d");
+
+  // box
+  if(p->private->oldbox_active){
+    xmlNodePtr boxn = xmlNewChild(pn, NULL, (xmlChar *) "box", NULL);
+    _xmlNewPropF(boxn, "x1", p2->oldbox[0]);
+    _xmlNewPropF(boxn, "x2", p2->oldbox[1]);
+    _xmlNewPropF(boxn, "y1", p2->oldbox[2]);
+    _xmlNewPropF(boxn, "y2", p2->oldbox[3]);
+  }
+
+  // objective map settings
+  for(i=0;i<p->objectives;i++){
+    sv_obj_t *o = p->objective_list[i].o;
+    xmlNodePtr on = xmlNewChild(pn, NULL, (xmlChar *) "objective", NULL);
+    _xmlNewPropI(on, "position", i);
+    _xmlNewPropI(on, "number", o->number);
+    _xmlNewPropS(on, "name", o->name);
+    _xmlNewPropS(on, "type", o->output_types);
+    
+    // right now Y is the only type; the below is Y-specific
+    n = xmlNewChild(on, NULL, (xmlChar *) "y-map", NULL);
+    _xmlNewPropS(n, "color", _sv_mapping_name(p2->mappings[i].mapnum));
+    _xmlNewPropF(n, "low-bracket", _sv_slider_get_value(p2->range_scales[i],0));
+    _xmlNewPropF(n, "alpha", _sv_slider_get_value(p2->range_scales[i],1));
+    _xmlNewPropF(n, "high-bracket", _sv_slider_get_value(p2->range_scales[i],2));
+  }
+
+  // x/y dim selection
+  n = xmlNewChild(pn, NULL, (xmlChar *) "axes", NULL);
+  _xmlNewPropI(n, "xpos", p2->x_dnum);
+  _xmlNewPropI(n, "ypos", p2->y_dnum);
+
+  return ret;
+}
+
+int _sv_panel2d_load(sv_panel_t *p,
+		     _sv_panel_undo_t *u,
+		     xmlNodePtr pn,
+		     int warn){
+  int i;
+
+  // check type
+  _xmlCheckPropS(pn,"type","2d", "Panel %d type mismatch in save file.",p->number,&warn);
+  
+  // box
+  u->box_active = 0;
+  _xmlGetChildPropFPreserve(pn, "box", "x1", &u->box[0]);
+  _xmlGetChildPropFPreserve(pn, "box", "x2", &u->box[1]);
+  _xmlGetChildPropFPreserve(pn, "box", "y1", &u->box[2]);
+  _xmlGetChildPropFPreserve(pn, "box", "y2", &u->box[3]);
+
+  xmlNodePtr n = _xmlGetChildS(pn, "box", NULL, NULL);
+  if(n){
+    u->box_active = 1;
+    xmlFree(n);
+  }
+  
+  // objective map settings
+  for(i=0;i<p->objectives;i++){
+    sv_obj_t *o = p->objective_list[i].o;
+    xmlNodePtr on = _xmlGetChildI(pn, "objective", "position", i);
+    if(!on){
+      _sv_first_load_warning(&warn);
+      fprintf(stderr,"No save data found for panel %d objective \"%s\".\n",p->number, o->name);
+    }else{
+      // check name, type
+      _xmlCheckPropS(on,"name",o->name, "Objectve position %d name mismatch in save file.",i,&warn);
+      _xmlCheckPropS(on,"type",o->output_types, "Objectve position %d type mismatch in save file.",i,&warn);
+      
+      // right now Y is the only type; the below is Y-specific
+      // load maptype, values
+      _xmlGetChildPropFPreserve(on, "y-map", "low-bracket", &u->scale_vals[0][i]);
+      _xmlGetChildPropFPreserve(on, "y-map", "alpha", &u->scale_vals[1][i]);
+      _xmlGetChildPropFPreserve(on, "y-map", "high-bracket", &u->scale_vals[2][i]);
+      _xmlGetChildMap(on, "y-map", "color", _sv_mapping_map(), &u->mappings[i],
+		     "Panel %d objective unknown mapping setting", p->number, &warn);
+
+      xmlFreeNode(on);
+    }
+  }
+
+  // x/y dim selection
+  _xmlGetChildPropIPreserve(pn, "axes", "xpos", &u->x_d);
+  _xmlGetChildPropI(pn, "axes", "ypos", &u->y_d);
+
+  return warn;
+}
+
+sv_panel_t *sv_panel_new_2d(int number,
+			    char *name, 
+			    char *objectivelist,
+			    char *dimensionlist,
+			    unsigned flags){
+  
+  int i,j;
+  sv_panel_t *p = _sv_panel_new(number,name,objectivelist,dimensionlist,flags);
+  if(!p)return NULL;
+
+  _sv_panel2d_t *p2 = calloc(1, sizeof(*p2));
+  int fout_offsets[_sv_functions];
+  
+  p->subtype = 
+    calloc(1, sizeof(*p->subtype)); /* the union is alloced not
+				       embedded as its internal
+				       structure must be hidden */  
+  p->subtype->p2 = p2;
+  p->type = SV_PANEL_2D;
+  p->private->bg_type = SV_BG_CHECKS;
+
+  // verify all the objectives have scales
+  for(i=0;i<p->objectives;i++){
+    if(!p->objective_list[i].o->scale){
+      fprintf(stderr,"All objectives in a 2d panel must have a scale\n");
+      errno = -EINVAL;
+      return NULL;
+    }
+  }
+
+  p->private->realize = _sv_panel2d_realize;
+  p->private->map_action = _sv_panel2d_map_redraw;
+  p->private->legend_action = _sv_panel2d_legend_redraw;
+  p->private->compute_action = _sv_panel2d_compute;
+  p->private->request_compute = _sv_panel2d_mark_recompute;
+  p->private->crosshair_action = _sv_panel2d_crosshairs_callback;
+  p->private->print_action = _sv_panel2d_print;
+  p->private->undo_log = _sv_panel2d_undo_log;
+  p->private->undo_restore = _sv_panel2d_undo_restore;
+  p->private->save_action = _sv_panel2d_save;
+  p->private->load_action = _sv_panel2d_load;
+
+  /* set up helper data structures for rendering */
+
+  /* determine which functions are actually needed; if it's referenced
+     by an objective, it's used.  Precache them in dense form. */
+  {
+    int fn = _sv_functions;
+    int used[fn],count=0,offcount=0;
+    memset(used,0,sizeof(used));
+    memset(fout_offsets,-1,sizeof(fout_offsets));
+    
+    for(i=0;i<p->objectives;i++){
+      sv_obj_t *o = p->objective_list[i].o;
+      for(j=0;j<o->outputs;j++)
+	used[o->function_map[j]]=1;
+    }
+
+    for(i=0;i<fn;i++)
+      if(used[i]){
+	sv_func_t *f = _sv_function_list[i];
+	fout_offsets[i] = offcount;
+	offcount += f->outputs;
+	count++;
+      }
+
+    p2->used_functions = count;
+    p2->used_function_list = calloc(count, sizeof(*p2->used_function_list));
+
+    for(count=0,i=0;i<fn;i++)
+     if(used[i]){
+        p2->used_function_list[count]=_sv_function_list[i];
+	count++;
+      }
+  }
+
+  /* set up computation/render helpers for Y planes */
+
+  /* set up Y object mapping index */
+  {
+    int yobj_count = 0;
+
+    for(i=0;i<p->objectives;i++){
+      sv_obj_t *o = p->objective_list[i].o;
+      if(o->private->y_func) yobj_count++;
+    }
+
+    p2->y_obj_num = yobj_count;
+    p2->y_obj_list = calloc(yobj_count, sizeof(*p2->y_obj_list));
+    p2->y_obj_to_panel = calloc(yobj_count, sizeof(*p2->y_obj_to_panel));
+    p2->y_obj_from_panel = calloc(p->objectives, sizeof(*p2->y_obj_from_panel));
+    
+    yobj_count=0;
+    for(i=0;i<p->objectives;i++){
+      sv_obj_t *o = p->objective_list[i].o;
+      if(o->private->y_func){
+	p2->y_obj_list[yobj_count] = o;
+	p2->y_obj_to_panel[yobj_count] = i;
+	p2->y_obj_from_panel[i] = yobj_count;
+	yobj_count++;
+      }else
+	p2->y_obj_from_panel[i] = -1;
+      
+    }
+  }
+  
+  /* set up function Y output value demultiplex helper */
+  {
+    p2->y_fout_offset = calloc(p2->y_obj_num, sizeof(*p2->y_fout_offset));
+    for(i=0;i<p2->y_obj_num;i++){
+      sv_obj_t *o = p2->y_obj_list[i];
+      int funcnum = o->private->y_func->number;
+      p2->y_fout_offset[i] = fout_offsets[funcnum] + o->private->y_fout;
+    }
+  }
+
+  p2->y_map = calloc(p2->y_obj_num,sizeof(*p2->y_map));
+  p2->y_planetodo = calloc(p2->y_obj_num,sizeof(*p2->y_planetodo));
+  p2->y_planes = calloc(p2->y_obj_num,sizeof(*p2->y_planes));
+
+  return p;
+}

Added: trunk/sushivision/plane-bg.c
===================================================================
--- trunk/sushivision/plane-bg.c	                        (rev 0)
+++ trunk/sushivision/plane-bg.c	2007-09-21 16:51:36 UTC (rev 13870)
@@ -0,0 +1,2083 @@
+/*
+ *
+ *     sushivision copyright (C) 2006-2007 Monty <monty at xiph.org>
+ *
+ *  sushivision is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *   
+ *  sushivision is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *   
+ *  You should have received a copy of the GNU General Public License
+ *  along with sushivision; see the file COPYING.  If not, write to the
+ *  Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * 
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <math.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <gtk/gtk.h>
+#include <cairo-ft.h>
+#include "internal.h"
+
+// called from worker thread
+static void recompute_setup(sv_plane2d_t *pl, sv_panel_t *p, 
+			    sv_dim_data_t *payload, int dims){
+  
+  pl->image_serialno++;
+  pl->image_x = _sv_dim_panelscale(payload + x_dim, p->w, 0);
+  pl->image_y = _sv_dim_panelscale(payload + y_dim, p->h, 1);
+
+}
+
+
+// called from worker thread
+static int image_work(sv_plane2d_t *pl, sv_panel_t *p){
+
+
+}
+
+// called from worker thread
+static int data_work(sv_plane2d_t *pl, sv_panel_t *p){
+
+
+}
+
+// called from GTK/API
+static void plane_remap(sv_plane2d_t *pl, sv_panel_t *p){
+
+
+}
+
+sv_plane_t *sv_plane2d_new(){
+
+
+}
+
+
+
+
+
+// enter unlocked
+static void _sv_planez_compute_line(sv_panel_t *p, 
+				    _sv_plane2d_t *z,
+
+				    int serialno,
+
+				    int dw,
+				    int y,
+				    int x_d, 
+				    _sv_scalespace_t sxi,
+				    double *dim_vals, 
+				    _sv_bythread_cache_2d_t *c){
+
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int i,j;
+  
+  /* cache access is unlocked because the cache is private to this
+     worker thread */
+
+  for(j=0;j<dw;j++){
+    double *fout = c->fout;
+    sv_func_t **f = p2->used_function_list;
+    int *obj_y_off = p2->y_fout_offset;
+    int *onum = p2->y_obj_to_panel;
+    
+    /* by function */
+    dim_vals[x_d] = _sv_scalespace_value(&sxi,j);   
+    for(i=0;i<p2->used_functions;i++){
+      (*f)->callback(dim_vals,fout);
+      fout += (*f)->outputs;
+      f++;
+    }
+    
+    /* process function output by plane type/objective */
+    /* 2d panels currently only care about the Y output value */
+    
+    /* slider map */
+    for(i=0;i<p2->y_obj_num;i++){
+      float val = (float)_sv_slider_val_to_del(p2->range_scales[*onum++], c->fout[*obj_y_off++]);
+      if(isnan(val)){
+	c->y_map[i][j] = -1;
+      }else{
+	if(val<0)val=0;
+	if(val>1)val=1;
+	c->y_map[i][j] = rint(val * (256.f*256.f*256.f));
+      }
+    }
+  }
+
+  gdk_lock ();
+  if(p->private->plot_serialno == serialno){
+    for(j=0;j<p2->y_obj_num;j++){
+      int *d = p2->y_map[j] + y*dw;
+      int *td = c->y_map[j];
+      
+      memcpy(d,td,dw*sizeof(*d));
+      
+    }
+  }
+  gdk_unlock ();
+}
+
+// call with lock
+static void _sv_panel2d_clear_pane(sv_panel_t *p){
+
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int pw = p2->x.pixels;
+  int ph = p2->y.pixels;
+  int i;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  
+  for(i=0;i<p2->y_obj_num;i++){
+    // map is freed and nulled to avoid triggering a fast-scale on an empty data pane
+    if(p2->y_map[i])
+      free(p2->y_map[i]);
+    p2->y_map[i]=NULL;
+
+    // free y_planes so that initial remap doesn't waste time on mix
+    // op; they are recreated during remap at the point the y_todo
+    // vector indicates something to do
+    if(p2->y_planes[i])
+      free(p2->y_planes[i]);
+    p2->y_planes[i] = NULL;
+
+    // work vector is merely cleared
+    memset(p2->y_planetodo[i], 0, ph*sizeof(**p2->y_planetodo));
+  }
+
+  // clear the background surface 
+  if(plot->datarect)
+    memset(plot->datarect, 0, ph*pw*sizeof(*plot->datarect));
+
+  // the bg is not marked to be refreshed; computation setup will do
+  // that as part of the fast_scale, even if the fast_scale is
+  // short-circuited to a noop.
+}
+
+typedef struct{
+  double x;
+  double y;
+  double z;
+} compute_result;
+
+// used by the legend code. this lets us get away with having only a mapped display pane
+// call with lock
+static void _sv_panel2d_compute_point(sv_panel_t *p,sv_obj_t *o, double x, double y, compute_result *out){
+  double dim_vals[_sv_dimensions];
+  int i,j;
+  int pflag=0;
+  int eflag=0;
+
+  // fill in dimensions
+  int x_d = p->private->x_d->number;
+  int y_d = p->private->y_d->number;
+
+  for(i=0;i<_sv_dimensions;i++){
+    sv_dim_t *dim = _sv_dimension_list[i];
+    dim_vals[i]=dim->val;
+  }
+
+  gdk_unlock ();
+
+  dim_vals[x_d] = x;
+  dim_vals[y_d] = y;
+
+  *out = (compute_result){NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN};
+
+  // compute
+  for(i=0;i<_sv_functions;i++){
+    sv_func_t *f = _sv_function_list[i];
+    int compflag = 0;
+    double fout[f->outputs];
+    double val;
+
+    // compute and demultiplex output
+    for(j=0;j<o->outputs;j++){
+      if(o->function_map[j] == i){
+
+	if(!compflag) f->callback(dim_vals,fout);
+	compflag = 1;
+	
+	val = fout[o->output_map[j]];
+	switch(o->output_types[j]){
+	case 'X':
+	  out->x = val;
+	  break;
+	case 'Y':
+	  out->y = val;
+	  break;
+	case 'Z':
+	  out->z = val;
+	  break;
+	case 'E':
+	  if(eflag)
+	    out->e2 = val;
+	  else
+	    out->e1 = val;
+	  eflag = 1;
+	  break;
+	case 'P':
+	  if(pflag)
+	    out->p2 = val;
+	  else
+	    out->p1 = val;
+	  pflag = 1;
+	  break;
+	case 'M':
+	  out->m = val;
+	  break;
+	}
+      }
+    }
+  }
+  gdk_lock ();
+
+}
+
+/* functions that perform actual graphical rendering */
+
+static float _sv_panel2d_resample_helpers_init(_sv_scalespace_t *to, _sv_scalespace_t *from,
+					       unsigned char *delA, unsigned char *delB, 
+					       int *posA, int *posB,
+					       int xymul){
+  int i;
+  int dw = from->pixels;
+  int pw = to->pixels;
+
+  long scalenum = _sv_scalespace_scalenum(to,from);
+  long scaleden = _sv_scalespace_scaleden(to,from);
+  long del = _sv_scalespace_scaleoff(to,from);
+  int bin = del / scaleden;
+  del -= bin * scaleden; 
+  int discscale = (scaleden>scalenum?scalenum:scaleden);
+  int total = xymul*scalenum/discscale;
+
+  for(i=0;i<pw;i++){
+    long del2 = del + scalenum;
+    int sizeceil = (del2 + scaleden - 1)/ scaleden; // ceiling
+    int sizefloor = del2 / scaleden;
+
+    while(bin<0 && del2>scaleden){
+      bin++;
+      del = 0;
+      del2 -= scaleden;
+      sizeceil--;
+    }
+    
+    if(del2 > scaleden && bin>=0 && bin<dw){
+      int rem = total;
+
+      delA[i] = ((xymul * (scaleden - del)) + (discscale>>1)) / discscale;
+      posA[i] = bin;
+      rem -= delA[i];
+      rem -= xymul*(sizeceil-2);
+
+      while(bin+sizeceil>dw){
+	sizeceil--;
+	del2=0;
+      }
+
+      del2 %= scaleden;
+      if(rem<0){
+	delA[i] += rem;
+	delB[i] = 0;
+      }else{
+	delB[i] = rem; // don't leak 
+      }
+      posB[i] = bin+sizeceil;
+
+    }else{
+      if(bin<0 || bin>=dw){
+	delA[i] = 0;
+	posA[i] = 0;
+	delB[i] = 0;
+	posB[i] = 0;
+      }else{
+	delA[i] = xymul;
+	posA[i] = bin;
+	delB[i] = 0;
+	posB[i] = bin+1;
+	if(del2 == scaleden)del2=0;
+      }
+    }
+
+    bin += sizefloor;
+    del = del2;
+  }
+  return (float)xymul/total;
+}
+
+/* x resample helpers are put in the per-thread cache because locking it would
+   be relatively expensive. */
+// call while locked
+static void _sv_panel2d_resample_helpers_manage_x(sv_panel_t *p, _sv_bythread_cache_2d_t *c){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  if(p->private->plot_serialno != c->serialno){
+    int pw = p2->x.pixels;
+    c->serialno = p->private->plot_serialno;
+
+    if(c->xdelA)
+      free(c->xdelA);
+    if(c->xdelB)
+      free(c->xdelB);
+    if(c->xnumA)
+      free(c->xnumA);
+    if(c->xnumB)
+      free(c->xnumB);
+    
+    c->xdelA = calloc(pw,sizeof(*c->xdelA));
+    c->xdelB = calloc(pw,sizeof(*c->xdelB));
+    c->xnumA = calloc(pw,sizeof(*c->xnumA));
+    c->xnumB = calloc(pw,sizeof(*c->xnumB));
+    c->xscalemul = _sv_panel2d_resample_helpers_init(&p2->x, &p2->x_v, c->xdelA, c->xdelB, c->xnumA, c->xnumB, 17);
+  }
+}
+
+/* y resample is in the panel struct as per-row access is already locked */
+// call while locked
+static void _sv_panel2d_resample_helpers_manage_y(sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  if(p->private->plot_serialno != p2->resample_serialno){
+    int ph = p2->y.pixels;
+    p2->resample_serialno = p->private->plot_serialno;
+
+    if(p2->ydelA)
+      free(p2->ydelA);
+    if(p2->ydelB)
+      free(p2->ydelB);
+    if(p2->ynumA)
+      free(p2->ynumA);
+    if(p2->ynumB)
+      free(p2->ynumB);
+    
+    p2->ydelA = calloc(ph,sizeof(*p2->ydelA));
+    p2->ydelB = calloc(ph,sizeof(*p2->ydelB));
+    p2->ynumA = calloc(ph,sizeof(*p2->ynumA));
+    p2->ynumB = calloc(ph,sizeof(*p2->ynumB));
+    p2->yscalemul = _sv_panel2d_resample_helpers_init(&p2->y, &p2->y_v, p2->ydelA, p2->ydelB, p2->ynumA, p2->ynumB, 15);
+  }
+}
+
+static inline void _sv_panel2d_mapping_calc( void (*m)(int,int, _sv_lcolor_t *), 
+					     int low,
+					     float range,
+					     int in, 
+					     int alpha, 
+					     int mul, 
+					     _sv_lcolor_t *outc){
+  if(mul && in>=alpha){
+    int val = rint((in - low) * range);
+    if(val<0)val=0;
+    if(val>65536)val=65536;
+    m(val,mul,outc);
+  }
+}
+
+/* the data rectangle is data width/height mapped deltas.  we render
+   and subsample at the same time. */
+/* return: -1 == abort
+            0 == more work to be done in this plane
+	    1 == plane fully dispatched (possibly not complete) */
+
+/* enter with lock */
+static int _sv_panel2d_resample_render_y_plane_line(sv_panel_t *p, _sv_bythread_cache_2d_t *c, 
+						    int plot_serialno, int map_serialno, int y_no){
+  
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int objnum = p2->y_obj_to_panel[y_no]; 
+  int *in_data = p2->y_map[y_no];
+  int ph = p2->y.pixels;
+
+  if(!in_data || !c){
+    p->private->map_complete_count -= ph;
+    return 1;
+  }
+
+  unsigned char *todo = p2->y_planetodo[y_no];
+  int i = p2->y_next_line;
+  int j;
+
+  /* find a row that needs to be updated */
+  while(i<ph && !todo[i]){
+    p->private->map_complete_count--;
+    p2->y_next_line++;
+    i++;
+  }
+
+  if(i == ph) return 1;
+
+  p2->y_next_line++;
+
+  /* row [i] needs to be updated; marshal */
+  _sv_mapping_t *map = p2->mappings+objnum;
+  void (*mapfunc)(int,int, _sv_lcolor_t *) = map->mapfunc;
+  int ol_alpha = rint(p2->alphadel[y_no] * 16777216.f);
+  _sv_ucolor_t *panel = p2->y_planes[y_no];
+  int ol_low = rint(map->low * 16777216.f);
+  float ol_range = map->i_range * (1.f/256.f);
+
+  int pw = p2->x.pixels;
+  int dw = p2->x_v.pixels;
+  int dh = p2->y_v.pixels;
+  _sv_ccolor_t work[pw];
+
+  if(!panel)
+    panel = p2->y_planes[y_no] = calloc(pw*ph, sizeof(**p2->y_planes));
+
+  if(ph!=dh || pw!=dw){
+    /* resampled row computation; may involve multiple data rows */
+
+    _sv_panel2d_resample_helpers_manage_y(p);
+    _sv_panel2d_resample_helpers_manage_x(p,c);
+
+    float idel = p2->yscalemul * c->xscalemul;
+
+    /* by column */
+    int ydelA=p2->ydelA[i];
+    int ydelB=p2->ydelB[i];
+    int ystart=p2->ynumA[i];
+    int yend=p2->ynumB[i];
+    int lh = yend - ystart;
+    int data[lh*dw];
+
+    memcpy(data,in_data+ystart*dw,sizeof(data));
+
+    gdk_unlock();
+
+    unsigned char *xdelA = c->xdelA;
+    unsigned char *xdelB = c->xdelB;
+    int *xnumA = c->xnumA;
+    int *xnumB = c->xnumB;
+      
+    /* by panel col */
+    for(j=0;j<pw;j++){
+      
+      _sv_lcolor_t out = (_sv_lcolor_t){0,0,0,0}; 
+      int xstart = xnumA[j];
+      int xend = xnumB[j];
+      int dx = xstart;
+      int xA = xdelA[j];
+      int xB = xdelB[j];
+      int y = ystart;
+
+      // first line
+      if(y<yend){
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx++], ol_alpha, ydelA*xA, &out);
+	
+	for(; dx < xend-1; dx++)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelA*17, &out);
+	
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelA*xB, &out);
+	y++;
+      }
+
+      // mid lines
+      for(;y<yend-1;y++){
+	dx = xstart += dw;
+	xend += dw;
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx++], ol_alpha, 15*xA, &out);
+	
+	for(; dx < xend-1; dx++)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, 255, &out);
+	
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, 15*xB, &out);
+      }
+      
+      // last line
+      if(y<yend){
+	dx = xstart += dw;
+	xend += dw;
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx++], ol_alpha, ydelB*xA, &out);
+	
+	for(; dx < xend-1; dx++)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelB*17, &out);
+	
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelB*xB, &out);
+      }
+
+      work[j].a = (u_int32_t)(out.a*idel);
+      work[j].r = (u_int32_t)(out.r*idel);
+      work[j].g = (u_int32_t)(out.g*idel);
+      work[j].b = (u_int32_t)(out.b*idel);
+      
+    }
+
+  }else{
+    /* non-resampling render */
+
+    int data[dw];
+    memcpy(data,in_data+i*dw,sizeof(data));
+    gdk_unlock();      
+
+    for(j=0;j<pw;j++){
+
+      _sv_lcolor_t out = (_sv_lcolor_t){0,0,0,0};
+      _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[j], ol_alpha, 255, &out);
+	
+      work[j].a = (u_int32_t)(out.a);
+      work[j].r = (u_int32_t)(out.r);
+      work[j].g = (u_int32_t)(out.g);
+      work[j].b = (u_int32_t)(out.b);
+    }
+  }
+
+  gdk_lock ();  
+  if(plot_serialno != p->private->plot_serialno ||
+     map_serialno != p->private->map_serialno)
+    return -1;
+  memcpy(panel+i*pw,work,sizeof(work));
+  p2->bg_todo[i] = 1;
+  p2->y_planetodo[y_no][i] = 0;
+
+  // must be last; it indicates completion
+  p->private->map_complete_count--;
+  return 0;
+}
+
+static void render_checks(_sv_ucolor_t *c, int w, int y){
+  /* default checked background */
+  /* 16x16 'mid-checks' */ 
+  int x,j;
+  
+  int phase = (y>>4)&1;
+  for(x=0;x<w;){
+    u_int32_t phaseval = 0xff505050UL;
+    if(phase) phaseval = 0xff808080UL;
+    for(j=0;j<16 && x<w;j++,x++)
+      c[x].u = phaseval;
+    phase=!phase;
+  }
+}
+
+// enter with lock
+static int _sv_panel2d_render_bg_line(sv_panel_t *p, int plot_serialno, int map_serialno){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  if(plot_serialno != p->private->plot_serialno ||
+     map_serialno != p->private->map_serialno) return -1;
+  
+  int ph = p2->y.pixels;
+  int pw = p2->x.pixels;
+  unsigned char *todo = p2->bg_todo;
+  int i = p2->bg_next_line,j;
+  _sv_ucolor_t work_bg[pw];
+  _sv_ucolor_t work_pl[pw];
+  int bgmode = p->private->bg_type;
+
+  /* find a row that needs to be updated */
+  while(i<ph && !todo[i]){
+    p->private->map_complete_count--;
+    p2->bg_next_line++;
+    i++;
+  }
+
+  if(i == ph)
+    goto done;
+
+  if(i < p2->bg_first_line) p2->bg_first_line = i;
+  if(i+1 > p2->bg_last_line) p2->bg_last_line = i+1;
+  p2->bg_next_line++;
+
+  /* gray background checks */
+  gdk_unlock();
+
+  switch(bgmode){
+  case SV_BG_WHITE:
+    for(j=0;j<pw;j++)
+      work_bg[j].u = 0xffffffffU;
+    break;
+  case SV_BG_BLACK:
+    for(j=0;j<pw;j++)
+      work_bg[j].u = 0xff000000U;
+    break;
+  default:
+    render_checks(work_bg,pw,i);
+    break;
+  }
+
+  /* by objective */
+  for(j=0;j<p->objectives;j++){
+    int o_ynum = p2->y_obj_from_panel[j];
+    
+    gdk_lock();
+    if(plot_serialno != p->private->plot_serialno ||
+       map_serialno != p->private->map_serialno) return -1;
+
+    /**** mix Y plane */
+    
+    if(p2->y_planes[o_ynum]){
+      int x;
+      _sv_ucolor_t (*mixfunc)(_sv_ucolor_t,_sv_ucolor_t) = p2->mappings[j].mixfunc;
+      _sv_ucolor_t *rect = p2->y_planes[o_ynum] + i*pw;
+      memcpy(work_pl,rect,sizeof(work_pl));
+      
+      gdk_unlock();
+      for(x=0;x<pw;x++)
+	work_bg[x] = mixfunc(work_pl[x],work_bg[x]);
+    }else
+      gdk_unlock();
+
+    /**** mix Z plane */
+    
+    /**** mix vector plane */
+
+  }
+
+  gdk_lock();
+  if(plot_serialno != p->private->plot_serialno ||
+     map_serialno != p->private->map_serialno) return -1;
+
+    // rendered a line, get it on the screen */
+  
+  memcpy(plot->datarect+pw*i, work_bg, sizeof(work_bg));
+
+  p->private->map_complete_count--;
+
+ done:
+  if(p->private->map_complete_count)
+    return 1; // not done yet
+
+  return 0;
+}
+
+static void _sv_panel2d_mark_map_full(sv_panel_t *p);
+
+// enter with lock; returns zero if thread should sleep / get distracted
+static int _sv_panel2d_remap(sv_panel_t *p, _sv_bythread_cache_2d_t *thread_cache){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(!plot) goto abort;
+  int ph = plot->y.pixels;
+  int pw = plot->x.pixels;
+  
+  int plot_serialno = p->private->plot_serialno; 
+  int map_serialno = p->private->map_serialno; 
+
+  /* brand new remap indicated by the generic progress indicator being set to 0 */
+  if(p->private->map_progress_count == 0){
+
+    p->private->map_progress_count = 1; // 'in progress'
+    p->private->map_complete_count = p2->y_obj_num * ph; // count down to 0; 0 indicates completion
+
+    // set up Y plane rendering
+    p2->y_next_plane = 0;
+    p2->y_next_line = 0;
+
+    // bg mix
+    p2->bg_next_line = 0;
+    p2->bg_first_line = ph;
+    p2->bg_last_line = 0;
+
+    if(!p2->partial_remap)
+      _sv_panel2d_mark_map_full(p);
+  }
+
+  /* by plane, by line; each plane renders independently */
+  /* Y planes */
+  if(p2->y_planetodo){
+    if(p2->y_next_plane < p2->y_obj_num){
+      int status = _sv_panel2d_resample_render_y_plane_line(p, thread_cache, 
+							    plot_serialno, map_serialno, 
+							    p2->y_next_plane);
+      if(status == -1) goto abort;
+      if(status == 1){
+	p2->y_next_plane++;
+	p2->y_next_line = 0;
+      }
+      return 1;
+    }
+  }else{
+    p->private->map_complete_count = 0;
+  }
+
+  /* renders have been completely dispatched, but are they complete? */
+  /* the below is effectively a a thread join */
+  if(p2->bg_next_line == 0){
+
+    // join still needs to complete....
+    if(p->private->map_complete_count){
+      // nonzero complete count, not finished.  returning zero will cause
+      // this worker thread to sleep or go on to do other things.
+      return 0; 
+    }else{
+      // zero complete count, the planes are done; we can begin
+      // background render.  At least one thread is guaranteed to get
+      // here, which is enough; we can now wake the others [if they were
+      // asleep] and have them look for work here. */
+      p->private->map_complete_count = ph; // [ph] lines to render in bg plane
+      p2->bg_next_line = 0;
+
+      _sv_wake_workers();
+    }
+  }
+
+  /* mix new background, again line by line */
+  if(p2->bg_next_line < ph){
+    int status = _sv_panel2d_render_bg_line(p, plot_serialno, map_serialno);
+    if(status == -1) goto abort;
+    if(p->private->map_complete_count)return status;
+  }else
+    return 0; // nothing left to dispatch
+
+  // entirely finished.
+
+  // remap completed; flush background to screen
+  _sv_plot_expose_request_partial (plot,0,p2->bg_first_line,
+			       pw,p2->bg_last_line - p2->bg_first_line);
+  gdk_flush();
+
+  // clean bg todo list
+  memset(p2->bg_todo,0,ph*sizeof(*p2->bg_todo));
+
+  // clear 'panel in progress' flag
+  p2->partial_remap = 0;
+  _sv_panel_clean_map(p);
+  return 0;
+
+ abort:
+  // reset progress to 'start over'
+  return 1;
+}
+
+// looks like a cop-out but is actually the correct thing to do; the
+// data *must* be WYSIWYG from panel display.
+static void _sv_panel2d_print_bg(sv_panel_t *p, cairo_t *c){
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(!plot) return;
+
+  cairo_pattern_t *pattern = cairo_pattern_create_for_surface(plot->back);
+  cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
+  cairo_set_source(c,pattern);
+  cairo_paint(c);
+
+  cairo_pattern_destroy(pattern);
+}
+
+static void _sv_panel2d_print(sv_panel_t *p, cairo_t *c, int w, int h){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  double pw = p->private->graph->allocation.width;
+  double ph = p->private->graph->allocation.height;
+  double scale;
+  int i;
+  double maxlabelw=0;
+  double y;
+
+  if(w/pw < h/ph)
+    scale = w/pw;
+  else
+    scale = h/ph;
+
+  cairo_matrix_t m;
+  cairo_save(c);
+  cairo_get_matrix(c,&m);
+  cairo_matrix_scale(&m,scale,scale);
+  cairo_set_matrix(c,&m);
+  
+  _sv_plot_print(plot, c, ph*scale, (void(*)(void *, cairo_t *))_sv_panel2d_print_bg, p);
+  cairo_restore(c);
+
+  // find extents widths for objective scale labels
+  cairo_set_font_size(c,10);
+  for(i=0;i<p->objectives;i++){
+    cairo_text_extents_t ex;
+    sv_obj_t *o = p->objective_list[i].o;
+    cairo_text_extents(c, o->name, &ex);
+    if(ex.width > maxlabelw) maxlabelw=ex.width;
+  }
+
+
+  y = ph * scale + 10;
+
+  for(i=0;i<p->objectives;i++){
+    sv_obj_t *o = p->objective_list[i].o;
+    _sv_slider_t *s = p2->range_scales[i];
+    
+    // get scale height
+    double labelh = _sv_slider_print_height(s);
+    cairo_text_extents_t ex;
+    cairo_text_extents (c, o->name, &ex);
+
+    int lx = maxlabelw - ex.width;
+    int ly = labelh/2 + ex.height/2;
+    
+    // print objective labels
+    cairo_set_source_rgb(c,0.,0.,0.);
+    cairo_move_to (c, lx,ly+y);
+    cairo_show_text (c, o->name);
+
+    // draw slider
+    // set translation
+    cairo_save(c);
+    cairo_translate (c, maxlabelw + 10, y);
+    _sv_slider_print(s, c, pw*scale - maxlabelw - 10, labelh);
+    cairo_restore(c);
+
+    y += labelh;
+  }
+
+}
+
+// call while locked
+static void _sv_panel2d_mark_map_plane(sv_panel_t *p, int onum, int y, int z, int v){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int ph = p2->y.pixels;
+
+  if(y && p2->y_planetodo){
+    int y_no = p2->y_obj_from_panel[onum];
+    if(y_no>=0 && p2->y_planetodo[y_no])
+      memset(p2->y_planetodo[y_no],1,ph * sizeof(**p2->y_planetodo));
+  }
+  p2->partial_remap = 1;
+}
+
+// call while locked 
+static void _sv_panel2d_mark_map_line_y(sv_panel_t *p, int line){
+  // determine all panel lines this y data line affects
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int ph = p2->y.pixels;
+  int pw = p2->x.pixels;
+  int dw = p2->x_v.pixels;
+  int dh = p2->y_v.pixels;
+  int i,j;
+
+  p2->partial_remap = 1;
+
+  if(ph!=dh || pw!=dw){
+    /* resampled row computation; may involve multiple data rows */
+    if(p2->y_planetodo){
+      _sv_panel2d_resample_helpers_manage_y(p);
+      
+      for(i=0;i<ph;i++)
+	if(p2->ynumA[i]<=line &&
+	   p2->ynumB[i]>line){
+	  
+	  for(j=0;j<p2->y_obj_num;j++)
+	    if(p2->y_planetodo[j])
+	      p2->y_planetodo[j][i]=1;
+      }
+    }
+  }else{
+    if(p2->y_planetodo)
+      if(line>=0 && line<ph)
+	for(j=0;j<p2->y_obj_num;j++)
+	  if(p2->y_planetodo[j])
+	    p2->y_planetodo[j][line]=1;
+  }
+}
+
+// call while locked
+static void _sv_panel2d_mark_map_full(sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int ph = p2->y.pixels;
+  int i,j;
+
+  p2->partial_remap = 1;
+
+  if(p2->y_planetodo){
+    for(j=0;j<p2->y_obj_num;j++){
+      if(p2->y_planetodo[j]){
+	for(i=0;i<ph;i++){
+	  p2->y_planetodo[j][i]=1;
+	}
+      }
+    }
+  }
+}
+
+// enter with lock
+static void _sv_panel2d_update_legend(sv_panel_t *p){  
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(plot){
+    int i;
+    char buffer[320];
+    int depth = 0;
+    _sv_plot_legend_clear(plot);
+
+    // potentially add each dimension to the legend; add axis
+    // dimensions only if crosshairs are active
+
+    // display decimal precision relative to display scales
+    if(3-_sv_scalespace_decimal_exponent(&p2->x) > depth) 
+      depth = 3-_sv_scalespace_decimal_exponent(&p2->x);
+    if(3-_sv_scalespace_decimal_exponent(&p2->y) > depth) 
+      depth = 3-_sv_scalespace_decimal_exponent(&p2->y);
+    for(i=0;i<p->dimensions;i++){
+      sv_dim_t *d = p->dimension_list[i].d;
+      if( (d!=p->private->x_d && d!=p->private->y_d) ||
+	  plot->cross_active){
+	snprintf(buffer,320,"%s = %+.*f",
+		 p->dimension_list[i].d->legend,
+		 depth,
+		 p->dimension_list[i].d->val);
+	_sv_plot_legend_add(plot,buffer);
+      }
+    }
+    
+    // add each active objective plane to the legend
+    // choose the value under the crosshairs 
+    if(plot->cross_active){
+      // one space 
+      _sv_plot_legend_add(plot,NULL);
+
+      for(i=0;i<p->objectives;i++){
+	
+	if(!_sv_mapping_inactive_p(p2->mappings+i)){
+	  compute_result vals;
+	  _sv_panel2d_compute_point(p,p->objective_list[i].o, plot->selx, plot->sely, &vals);
+	  
+	  if(!isnan(vals.y)){
+	    
+	    snprintf(buffer,320,"%s = %f",
+		     p->objective_list[i].o->name,
+		     vals.y);
+	    _sv_plot_legend_add(plot,buffer);
+	  }
+	}
+      }
+    }
+  }
+}
+
+static void _sv_panel2d_mapchange_callback(GtkWidget *w,gpointer in){
+  sv_obj_list_t *optr = (sv_obj_list_t *)in;
+  //sv_obj_t *o = optr->o;
+  sv_panel_t *p = optr->p;
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int onum = optr - p->objective_list;
+
+  _sv_undo_push();
+  _sv_undo_suspend();
+
+  _sv_mapping_set_func(&p2->mappings[onum],gtk_combo_box_get_active(GTK_COMBO_BOX(w)));
+  
+  //redraw the map slider
+  _sv_slider_set_gradient(p2->range_scales[onum], &p2->mappings[onum]);
+
+  // in the event the mapping active state changed
+  _sv_panel_dirty_legend(p);
+
+  //redraw the plot
+  _sv_panel2d_mark_map_plane(p,onum,1,0,0);
+  _sv_panel_dirty_map(p);
+  _sv_undo_resume();
+}
+
+static void _sv_panel2d_map_callback(void *in,int buttonstate){
+  sv_obj_list_t *optr = (sv_obj_list_t *)in;
+  //sv_obj_t *o = optr->o;
+  sv_panel_t *p = optr->p;
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int onum = optr - p->objective_list;
+
+  if(buttonstate == 0){
+    _sv_undo_push();
+    _sv_undo_suspend();
+  }
+
+  // recache alpha del */
+  p2->alphadel[onum] = 
+    _sv_slider_val_to_del(p2->range_scales[onum],
+		      _sv_slider_get_value(p2->range_scales[onum],1));
+
+  // redraw the plot on motion
+  if(buttonstate == 1){
+    _sv_panel2d_mark_map_plane(p,onum,1,0,0);
+    _sv_panel_dirty_map(p);
+  }
+  if(buttonstate == 2)
+    _sv_undo_resume();
+}
+
+static void _sv_panel2d_update_xysel(sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int i;
+  // update which x/y buttons are pressable */
+  // enable/disable dimension slider thumbs
+
+  for(i=0;i<p->dimensions;i++){
+    if(p2->dim_xb[i] &&
+       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_xb[i]))){
+      // make the y insensitive
+      if(p2->dim_yb[i])
+	_gtk_widget_set_sensitive_fixup(p2->dim_yb[i],FALSE);
+
+      // set the x dim flag
+      p->private->x_d = p->dimension_list[i].d;
+      p2->x_scale = p->private->dim_scales[i];
+      p2->x_dnum = i;
+    }else{
+      // if there is a y, make it sensitive 
+      if(p2->dim_yb[i])
+	_gtk_widget_set_sensitive_fixup(p2->dim_yb[i],TRUE);
+    }
+    if(p2->dim_yb[i] &&
+       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_yb[i]))){
+      // make the x insensitive
+      if(p2->dim_xb[i])
+	_gtk_widget_set_sensitive_fixup(p2->dim_xb[i],FALSE);
+
+      // set the y dim
+      p->private->y_d = p->dimension_list[i].d;
+      p2->y_scale = p->private->dim_scales[i];
+      p2->y_dnum = i;
+    }else{
+      // if there is a x, make it sensitive 
+      if(p2->dim_xb[i])
+	_gtk_widget_set_sensitive_fixup(p2->dim_xb[i],TRUE);
+    }
+    if((p2->dim_xb[i] &&
+	gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_xb[i]))) ||
+       (p2->dim_yb[i] &&
+	gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_yb[i])))){
+      // make all thumbs visible 
+      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],0,1);
+      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],2,1);
+    }else{
+      // make bracket thumbs invisible */
+      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],0,0);
+      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],2,0);
+    }
+  } 
+}
+
+static int _v_swizzle(int y, int height){
+  int yy = height >> 5;
+  if(y < yy)
+    return (y<<5)+31;
+
+  y -= yy;
+  yy = (height+16) >> 5;
+  if(y < yy)
+    return (y<<5)+15;
+
+  y -= yy;
+  yy = (height+8) >> 4;
+  if(y < yy)
+    return (y<<4)+7;
+
+  y -= yy;
+  yy = (height+4) >> 3;
+  if(y < yy)
+    return (y<<3)+3;
+
+  y -= yy;
+  yy = (height+2) >> 2;
+  if(y < yy)
+    return (y<<2)+1;
+
+  y -= yy;
+  return y<<1;
+}
+
+// assumes data is locked
+static void _sv_panel2d_fast_scale_x(_sv_spinner_t *sp,
+				     int *data, 
+				     int w,
+				     int h,
+				     _sv_scalespace_t new,
+				     _sv_scalespace_t old){
+  int x,y;
+  int work[w];
+  int mapbase[w];
+  int mapdel[w];
+  double old_w = old.pixels;
+  double new_w = new.pixels;
+
+  double old_lo = _sv_scalespace_value(&old,0);
+  double old_hi = _sv_scalespace_value(&old,old_w);
+  double new_lo = _sv_scalespace_value(&new,0);
+  double new_hi = _sv_scalespace_value(&new,new_w);
+  double newscale = (new_hi-new_lo)/new_w;
+  double oldscale = old_w/(old_hi-old_lo);
+  for(x=0;x<w;x++){
+    double xval = (x)*newscale+new_lo;
+    double map = ((xval-old_lo)*oldscale);
+    int base = (int)floor(map);
+    int del = rint((map - floor(map))*64.f);
+    /* hack to overwhelm roundoff error; this is inside a purely
+       temporary cosmetic approximation anyway*/
+    if(base>0 && del==0){
+      mapbase[x]=base-1;
+      mapdel[x]=64;
+    }else{
+      mapbase[x]=base;
+      mapdel[x]=del;
+    }
+  }
+
+  for(y=0;y<h;y++){
+    int *data_line = data+y*w;
+    _sv_spinner_set_busy(sp);
+    for(x=0;x<w;x++){
+      if(mapbase[x]<0 || mapbase[x]>=(w-1)){
+	work[x]=-1;
+      }else{
+	int base = mapbase[x];
+	int A = data_line[base];
+	int B = data_line[base+1];
+	if(A<0 || B<0)
+	  work[x]=-1;
+	else
+	  work[x]= A + (((B - A)*mapdel[x])>>6);
+	
+      }
+    }
+    memcpy(data_line,work,w*(sizeof(*work)));
+  }   
+}
+
+static void _sv_panel2d_fast_scale_y(_sv_spinner_t *sp,
+				     int *olddata, 
+				     int *newdata, 
+				     int oldw,
+				     int neww,
+				     _sv_scalespace_t new,
+				     _sv_scalespace_t old){
+  int x,y;
+  int w = (oldw<neww?oldw:neww);
+
+  int old_h = old.pixels;
+  int new_h = new.pixels;
+
+  int mapbase[new_h];
+  int mapdel[new_h];
+
+  double old_lo = _sv_scalespace_value(&old,0);
+  double old_hi = _sv_scalespace_value(&old,(double)old_h);
+  double new_lo = _sv_scalespace_value(&new,0);
+  double new_hi = _sv_scalespace_value(&new,(double)new_h);
+  double newscale = (new_hi-new_lo)/new_h;
+  double oldscale = old_h/(old_hi-old_lo);
+  
+  for(y=0;y<new_h;y++){
+    double yval = (y)*newscale+new_lo;
+    double map = ((yval-old_lo)*oldscale);
+    int base = (int)floor(map);
+    int del = rint((map - floor(map))*64.);
+    /* hack to overwhelm roundoff error; this is inside a purely
+       temporary cosmetic approximation anyway */
+    if(base>0 && del==0){
+      mapbase[y]=base-1;
+      mapdel[y]=64;
+    }else{
+      mapbase[y]=base;
+      mapdel[y]=del;
+    }
+  }
+
+  
+  for(y=0;y<new_h;y++){
+    int base = mapbase[y];
+    int *new_column = &newdata[y*neww];
+    _sv_spinner_set_busy(sp);
+
+    if(base<0 || base>=(old_h-1)){
+      for(x=0;x<w;x++)
+	new_column[x] = -1;
+    }else{
+      int del = mapdel[y];
+      int *old_column = &olddata[base*oldw];
+
+      for(x=0;x<w;x++){
+	int A = old_column[x];
+	int B = old_column[x+oldw];
+	if(A<0 || B<0)
+	  new_column[x]=-1;
+	else
+	  new_column[x]= A + (((B-A)*del)>>6);
+      }
+    }
+  }
+}
+
+static void _sv_panel2d_fast_scale(_sv_spinner_t *sp, 
+				   int *newdata, 
+				   _sv_scalespace_t xnew,
+				   _sv_scalespace_t ynew,
+				   int *olddata,
+				   _sv_scalespace_t xold,
+				   _sv_scalespace_t yold){
+  
+  int new_w = xnew.pixels;
+  int new_h = ynew.pixels;
+  int old_w = xold.pixels;
+  int old_h = yold.pixels;
+
+  if(new_w > old_w){
+    _sv_panel2d_fast_scale_y(sp,olddata,newdata,old_w,new_w,ynew,yold);
+    _sv_panel2d_fast_scale_x(sp,newdata,new_w,new_h,xnew,xold);
+  }else{
+    _sv_panel2d_fast_scale_x(sp,olddata,old_w,old_h,xnew,xold);
+    _sv_panel2d_fast_scale_y(sp,olddata,newdata,old_w,new_w,ynew,yold);
+  }
+}
+
+// call only from main gtk thread
+static void _sv_panel2d_mark_recompute(sv_panel_t *p){
+  if(!p->private->realized) return;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(plot && GTK_WIDGET_REALIZED(GTK_WIDGET(plot))){
+    _sv_panel_dirty_plot(p);
+  }
+}
+
+static void _sv_panel2d_update_crosshairs(sv_panel_t *p){
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  double x=0,y=0;
+  int i;
+  
+  for(i=0;i<p->dimensions;i++){
+    sv_dim_t *d = p->dimension_list[i].d;
+    if(d == p->private->x_d)
+      x = d->val;
+    if(d == p->private->y_d)
+      y = d->val;
+    
+  }
+  
+  _sv_plot_set_crosshairs(plot,x,y);
+  _sv_panel_dirty_legend(p);
+}
+
+static void _sv_panel2d_center_callback(sv_dim_list_t *dptr){
+  sv_dim_t *d = dptr->d;
+  sv_panel_t *p = dptr->p;
+  int axisp = (d == p->private->x_d || d == p->private->y_d);
+
+  if(!axisp){
+    // mid slider of a non-axis dimension changed, rerender
+    _sv_panel2d_mark_recompute(p);
+  }else{
+    // mid slider of an axis dimension changed, move crosshairs
+    _sv_panel2d_update_crosshairs(p);
+  }
+}
+
+static void _sv_panel2d_bracket_callback(sv_dim_list_t *dptr){
+  sv_dim_t *d = dptr->d;
+  sv_panel_t *p = dptr->p;
+  int axisp = (d == p->private->x_d || d == p->private->y_d);
+
+  if(axisp)
+    _sv_panel2d_mark_recompute(p);
+    
+}
+
+static void _sv_panel2d_dimchange_callback(GtkWidget *button,gpointer in){
+  sv_panel_t *p = (sv_panel_t *)in;
+
+  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))){
+
+    _sv_undo_push();
+    _sv_undo_suspend();
+
+    _sv_plot_unset_box(PLOT(p->private->graph));
+    _sv_panel2d_update_xysel(p);
+
+    _sv_panel2d_clear_pane(p);
+    _sv_panel2d_mark_recompute(p);
+    _sv_panel2d_update_crosshairs(p);
+
+    _sv_undo_resume();
+  }
+}
+
+static void _sv_panel2d_crosshairs_callback(sv_panel_t *p){
+  double x=PLOT(p->private->graph)->selx;
+  double y=PLOT(p->private->graph)->sely;
+  int i;
+  
+  _sv_undo_push();
+  _sv_undo_suspend();
+
+  //plot_snap_crosshairs(PLOT(p->private->graph));
+
+  for(i=0;i<p->dimensions;i++){
+    sv_dim_t *d = p->dimension_list[i].d;
+    if(d == p->private->x_d){
+      _sv_dim_widget_set_thumb(p->private->dim_scales[i],1,x);
+    }
+
+    if(d == p->private->y_d){
+      _sv_dim_widget_set_thumb(p->private->dim_scales[i],1,y);
+    }
+    
+    p->private->oldbox_active = 0;
+  }
+
+  // dimension setting might have enforced granularity restrictions;
+  // have the display reflect that
+  x = p->private->x_d->val;
+  y = p->private->y_d->val;
+
+  _sv_plot_set_crosshairs(PLOT(p->private->graph),x,y);
+
+  _sv_panel_dirty_legend(p);
+  _sv_undo_resume();
+}
+
+static void _sv_panel2d_box_callback(void *in, int state){
+  sv_panel_t *p = (sv_panel_t *)in;
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  
+  switch(state){
+  case 0: // box set
+    _sv_undo_push();
+    _sv_plot_box_vals(plot,p2->oldbox);
+    p->private->oldbox_active = plot->box_active;
+    break;
+  case 1: // box activate
+    _sv_undo_push();
+    _sv_undo_suspend();
+
+    _sv_panel2d_crosshairs_callback(p);
+
+    _sv_dim_widget_set_thumb(p2->x_scale,0,p2->oldbox[0]);
+    _sv_dim_widget_set_thumb(p2->x_scale,2,p2->oldbox[1]);
+    _sv_dim_widget_set_thumb(p2->y_scale,0,p2->oldbox[2]);
+    _sv_dim_widget_set_thumb(p2->y_scale,2,p2->oldbox[3]);
+    p->private->oldbox_active = 0;
+    _sv_undo_resume();
+    break;
+  }
+  _sv_panel_update_menus(p);
+}
+
+void _sv_panel2d_maintain_cache(sv_panel_t *p, _sv_bythread_cache_2d_t *c, int w){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  
+  /* toplevel initialization */
+  if(c->fout == 0){
+    int i,count=0;
+    
+    /* allocate output temporary buffer */
+    for(i=0;i<p2->used_functions;i++){
+      int fnum = p2->used_function_list[i]->number;
+      sv_func_t *f = _sv_function_list[fnum];
+      count += f->outputs;
+    }
+    c->fout = calloc(count, sizeof(*c->fout));
+
+    /* objective line buffer index */
+    c->y_map = calloc(p2->y_obj_num,sizeof(*c->y_map));
+    for(i=0;i<p2->y_obj_num;i++)
+      c->y_map[i] = calloc(w,sizeof(**c->y_map));
+    c->storage_width = w;
+  }
+  
+  /* anytime the data width changes */
+  if(c->storage_width != w){
+    int i;
+    c->storage_width = w;
+    
+    for(i=0;i<p2->y_obj_num;i++)
+      c->y_map[i] = realloc(c->y_map[i],w*sizeof(**c->y_map));
+
+  }
+}
+
+
+// subtype entry point for plot remaps; lock held
+static int _sv_panel2d_map_redraw(sv_panel_t *p, _sv_bythread_cache_t *c){
+  return _sv_panel2d_remap(p,&c->p2);
+}
+
+// subtype entry point for legend redraws; lock held
+static int _sv_panel2d_legend_redraw(sv_panel_t *p){
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(p->private->legend_progress_count)return 0;
+  p->private->legend_progress_count++;
+  _sv_panel2d_update_legend(p);
+  _sv_panel_clean_legend(p);
+
+  gdk_unlock();
+  _sv_plot_draw_scales(plot);
+  gdk_lock();
+
+  _sv_plot_expose_request(plot);
+  return 1;
+}
+
+// subtype entry point for recomputation; lock held
+static int _sv_panel2d_compute(sv_panel_t *p,
+			       _sv_bythread_cache_t *c){
+
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot;
+  
+  int pw,ph,dw,dh,i,d;
+  int serialno;
+  double x_min, x_max;
+  double y_min, y_max;
+  int x_d=-1, y_d=-1;
+  _sv_scalespace_t sx,sx_v,sx_i;
+  _sv_scalespace_t sy,sy_v,sy_i;
+
+  plot = PLOT(p->private->graph);
+  pw = plot->x.pixels;
+  ph = plot->y.pixels;
+  
+  x_d = p->private->x_d->number;
+  y_d = p->private->y_d->number;
+
+  // beginning of computation init
+  if(p->private->plot_progress_count==0){
+    int remapflag = 0;
+
+    _sv_scalespace_t old_x = p2->x;
+    _sv_scalespace_t old_y = p2->y;
+    _sv_scalespace_t old_xv = p2->x_v;
+    _sv_scalespace_t old_yv = p2->y_v;
+
+    // generate new scales
+    _sv_dim_scales(p->private->x_d, 
+		   p->private->x_d->bracket[0],
+		   p->private->x_d->bracket[1],
+		   pw,pw * p->private->oversample_n / p->private->oversample_d,
+		   plot->scalespacing,
+		   p->private->x_d->legend,
+		   &sx,
+		   &sx_v,
+		   &sx_i);
+    _sv_dim_scales(p->private->y_d, 
+		   p->private->y_d->bracket[1],
+		   p->private->y_d->bracket[0],
+		   ph,ph * p->private->oversample_n / p->private->oversample_d,
+		   plot->scalespacing,
+		   p->private->y_d->legend,
+		   &sy,
+		   &sy_v,
+		   &sy_i);
+    
+    p2->x = sx;
+    p2->x_v = sx_v;
+    p2->x_i = sx_i;
+    p2->y = sy;
+    p2->y_v = sy_v;
+    p2->y_i = sy_i;
+
+    plot->x = sx;
+    plot->y = sy;
+    plot->x_v = sx_v;
+    plot->y_v = sy_v;
+
+    p->private->plot_progress_count++;
+    p->private->plot_serialno++; // we're about to free the old data rectangles
+
+    // realloc/fast scale the current data contents if appropriate
+    if(memcmp(&sx_v,&old_xv,sizeof(sx_v)) || memcmp(&sy_v,&old_yv,sizeof(sy_v))){
+      
+      // maintain data planes
+      for(i=0;i<p2->y_obj_num;i++){
+	// allocate new storage
+	int *newmap = calloc(sx_v.pixels*sy_v.pixels,sizeof(*newmap));
+	int *oldmap = p2->y_map[i];
+	int j;
+	
+	for(j=0;j<sx_v.pixels*sy_v.pixels;j++)
+	  newmap[j]=-1;
+	
+	// zoom scale data in map planes as placeholder for render
+	if(oldmap){
+	  _sv_panel2d_fast_scale(p->private->spinner,newmap, sx_v, sy_v,
+				 oldmap,old_xv, old_yv);
+	  free(oldmap);
+	}
+	p2->y_map[i] = newmap; 
+      }
+      remapflag = 1;
+    }
+
+    // realloc render planes if appropriate
+    if(memcmp(&sx,&old_x,sizeof(sx)) || memcmp(&sy,&old_y,sizeof(sy))){
+      for(i=0;i<p2->y_obj_num;i++){
+
+	// y planes
+	if(p2->y_planes[i])
+	  free(p2->y_planes[i]);
+	p2->y_planes[i] = calloc(sx.pixels*sy.pixels,sizeof(**p2->y_planes));
+
+	// todo lists
+	if(p2->y_planetodo[i])
+	  free(p2->y_planetodo[i]);
+	p2->y_planetodo[i] = calloc(sy.pixels,sizeof(**p2->y_planetodo));
+	
+      }
+
+      if(p2->bg_todo)
+	free(p2->bg_todo);
+      p2->bg_todo=calloc(ph,sizeof(*p2->bg_todo));
+      
+      remapflag = 1;
+    }
+
+    if(remapflag){
+      _sv_panel2d_mark_map_full(p);
+      _sv_panel_dirty_map(p);
+
+      gdk_unlock ();      
+      _sv_plot_draw_scales(plot); // this should happen outside lock
+      gdk_lock ();      
+    }
+
+    _sv_map_set_throttle_time(p); // swallow the first 'throttled' remap which would only be a single line;
+
+    return 1;
+  }else{
+    sx = p2->x;
+    sx_v = p2->x_v;
+    sx_i = p2->x_i;
+    sy = p2->y;
+    sy_v = p2->y_v;
+    sy_i = p2->y_i;
+    serialno = p->private->plot_serialno; 
+  }
+
+  dw = sx_v.pixels;
+  dh = sy_v.pixels;
+
+  if(p->private->plot_progress_count>dh) return 0;
+
+  _sv_panel2d_maintain_cache(p,&c->p2,dw);
+  
+  d = p->dimensions;
+
+  /* render using local dimension array; several threads will be
+     computing objectives */
+  double dim_vals[_sv_dimensions];
+  int y = _v_swizzle(p->private->plot_progress_count-1,dh);
+  p->private->plot_progress_count++;
+
+  x_min = _sv_scalespace_value(&p2->x_i,0);
+  x_max = _sv_scalespace_value(&p2->x_i,dw);
+
+  y_min = _sv_scalespace_value(&p2->y_i,0);
+  y_max = _sv_scalespace_value(&p2->y_i,dh);
+
+  // Initialize local dimension value array
+  for(i=0;i<_sv_dimensions;i++){
+    sv_dim_t *dim = _sv_dimension_list[i];
+    dim_vals[i]=dim->val;
+  }
+
+  /* unlock for computation */
+  gdk_unlock ();
+    
+  dim_vals[y_d]=_sv_scalespace_value(&sy_i, y);
+  _sv_panel2d_compute_line(p, serialno, dw, y, x_d, sx_i, dim_vals, &c->p2);
+
+  gdk_lock ();
+
+  if(p->private->plot_serialno == serialno){
+    p->private->plot_complete_count++;
+    _sv_panel2d_mark_map_line_y(p,y);
+    if(p->private->plot_complete_count>=dh){ 
+      _sv_panel_dirty_map(p);
+      _sv_panel_dirty_legend(p);
+      _sv_panel_clean_plot(p);
+    }else
+      _sv_panel_dirty_map_throttled(p); 
+  }
+
+  return 1;
+}
+
+// only called for resize events
+static void _sv_panel2d_recompute_callback(void *ptr){
+  sv_panel_t *p = (sv_panel_t *)ptr;
+  int i;
+
+  gdk_lock ();
+  _sv_panel2d_mark_recompute(p);
+  _sv_panel2d_compute(p,NULL); // initial scale setup
+
+  // temporary: blank background to checks
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  int pw = plot->x.pixels;
+  int ph = plot->y.pixels;
+  for(i=0;i<ph;i++)
+    render_checks((_sv_ucolor_t *)plot->datarect+pw*i, pw, i);
+  
+  gdk_unlock();
+}
+
+static void _sv_panel2d_undo_log(_sv_panel_undo_t *u, sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int i;
+
+  // alloc fields as necessary
+  
+  if(!u->mappings)
+    u->mappings =  calloc(p->objectives,sizeof(*u->mappings));
+  if(!u->scale_vals[0])
+    u->scale_vals[0] =  calloc(p->objectives,sizeof(**u->scale_vals));
+  if(!u->scale_vals[1])
+    u->scale_vals[1] =  calloc(p->objectives,sizeof(**u->scale_vals));
+  if(!u->scale_vals[2])
+    u->scale_vals[2] =  calloc(p->objectives,sizeof(**u->scale_vals));
+
+  // populate undo
+  for(i=0;i<p->objectives;i++){
+    u->mappings[i] = p2->mappings[i].mapnum;
+    u->scale_vals[0][i] = _sv_slider_get_value(p2->range_scales[i],0);
+    u->scale_vals[1][i] = _sv_slider_get_value(p2->range_scales[i],1);
+    u->scale_vals[2][i] = _sv_slider_get_value(p2->range_scales[i],2);
+  }
+  
+  u->x_d = p2->x_dnum;
+  u->y_d = p2->y_dnum;
+  u->box[0] = p2->oldbox[0];
+  u->box[1] = p2->oldbox[1];
+  u->box[2] = p2->oldbox[2];
+  u->box[3] = p2->oldbox[3];
+  u->box_active = p->private->oldbox_active;
+}
+
+static void _sv_panel2d_undo_restore(_sv_panel_undo_t *u, sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  int i;
+  
+  // go in through widgets
+  for(i=0;i<p->objectives;i++){
+    gtk_combo_box_set_active(GTK_COMBO_BOX(p2->range_pulldowns[i]),u->mappings[i]);
+    _sv_slider_set_value(p2->range_scales[i],0,u->scale_vals[0][i]);
+    _sv_slider_set_value(p2->range_scales[i],1,u->scale_vals[1][i]);
+    _sv_slider_set_value(p2->range_scales[i],2,u->scale_vals[2][i]);
+  }
+
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_xb[u->x_d]),TRUE);
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_yb[u->y_d]),TRUE);
+
+  _sv_panel2d_update_xysel(p);
+
+  if(u->box_active){
+    p2->oldbox[0] = u->box[0];
+    p2->oldbox[1] = u->box[1];
+    p2->oldbox[2] = u->box[2];
+    p2->oldbox[3] = u->box[3];
+    _sv_plot_box_set(plot,u->box);
+    p->private->oldbox_active = 1;
+  }else{
+    _sv_plot_unset_box(plot);
+    p->private->oldbox_active = 0;
+  }
+}
+
+static void _sv_panel2d_realize(sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int i;
+
+  _sv_undo_suspend();
+
+  p->private->toplevel = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  g_signal_connect_swapped (G_OBJECT (p->private->toplevel), "delete-event",
+			    G_CALLBACK (_sv_clean_exit), (void *)SIGINT);
+ 
+  // add border to sides with hbox/padding
+  GtkWidget *borderbox =  gtk_hbox_new(0,0);
+  gtk_container_add (GTK_CONTAINER (p->private->toplevel), borderbox);
+
+  // main layout vbox
+  p->private->topbox = gtk_vbox_new(0,0);
+  gtk_box_pack_start(GTK_BOX(borderbox), p->private->topbox, 1,1,4);
+  gtk_container_set_border_width (GTK_CONTAINER (p->private->toplevel), 1);
+
+  /* spinner, top bar */
+  {
+    GtkWidget *hbox = gtk_hbox_new(0,0);
+    gtk_box_pack_start(GTK_BOX(p->private->topbox), hbox, 0,0,0);
+    gtk_box_pack_end(GTK_BOX(hbox),GTK_WIDGET(p->private->spinner),0,0,0);
+  }
+
+  /* plotbox, graph */
+  {
+    p->private->graph = GTK_WIDGET(_sv_plot_new(_sv_panel2d_recompute_callback,p,
+						(void *)(void *)_sv_panel2d_crosshairs_callback,p,
+						_sv_panel2d_box_callback,p,0)); 
+    p->private->plotbox = p->private->graph;
+    gtk_box_pack_start(GTK_BOX(p->private->topbox), p->private->plotbox, 1,1,2);
+  }
+
+  /* obj box */
+  {
+    p2->obj_table = gtk_table_new(p->objectives, 5, 0);
+    gtk_box_pack_start(GTK_BOX(p->private->topbox), p2->obj_table, 0,0,1);
+
+    /* objective sliders */
+    p2->range_scales = calloc(p->objectives,sizeof(*p2->range_scales));
+    p2->range_pulldowns = calloc(p->objectives,sizeof(*p2->range_pulldowns));
+    p2->alphadel = calloc(p->objectives,sizeof(*p2->alphadel));
+    p2->mappings = calloc(p->objectives,sizeof(*p2->mappings));
+    for(i=0;i<p->objectives;i++){
+      GtkWidget **sl = calloc(3,sizeof(*sl));
+      sv_obj_t *o = p->objective_list[i].o;
+      int lo = o->scale->val_list[0];
+      int hi = o->scale->val_list[o->scale->vals-1];
+      
+      /* label */
+      GtkWidget *label = gtk_label_new(o->name);
+      gtk_misc_set_alignment(GTK_MISC(label),1.,.5);
+      gtk_table_attach(GTK_TABLE(p2->obj_table),label,0,1,i,i+1,
+		       GTK_FILL,0,8,0);
+      
+      /* mapping pulldown */
+      {
+	GtkWidget *menu=_gtk_combo_box_new_markup();
+	int j;
+	for(j=0;j<_sv_mapping_names();j++)
+	  gtk_combo_box_append_text (GTK_COMBO_BOX (menu), _sv_mapping_name(j));
+	gtk_combo_box_set_active(GTK_COMBO_BOX(menu),0);
+	g_signal_connect (G_OBJECT (menu), "changed",
+			  G_CALLBACK (_sv_panel2d_mapchange_callback), p->objective_list+i);
+	gtk_table_attach(GTK_TABLE(p2->obj_table),menu,4,5,i,i+1,
+			 GTK_SHRINK,GTK_SHRINK,0,0);
+	p2->range_pulldowns[i] = menu;
+      }
+
+      /* the range mapping slices/slider */ 
+      sl[0] = _sv_slice_new(_sv_panel2d_map_callback,p->objective_list+i);
+      sl[1] = _sv_slice_new(_sv_panel2d_map_callback,p->objective_list+i);
+      sl[2] = _sv_slice_new(_sv_panel2d_map_callback,p->objective_list+i);
+      
+      gtk_table_attach(GTK_TABLE(p2->obj_table),sl[0],1,2,i,i+1,
+		       GTK_EXPAND|GTK_FILL,0,0,0);
+      gtk_table_attach(GTK_TABLE(p2->obj_table),sl[1],2,3,i,i+1,
+		       GTK_EXPAND|GTK_FILL,0,0,0);
+      gtk_table_attach(GTK_TABLE(p2->obj_table),sl[2],3,4,i,i+1,
+		       GTK_EXPAND|GTK_FILL,0,0,0);
+      p2->range_scales[i] = _sv_slider_new((_sv_slice_t **)sl,3,o->scale->label_list,o->scale->val_list,
+				       o->scale->vals,_SV_SLIDER_FLAG_INDEPENDENT_MIDDLE);
+      gtk_table_set_col_spacing(GTK_TABLE(p2->obj_table),3,5);
+
+      _sv_slice_thumb_set((_sv_slice_t *)sl[0],lo);
+      _sv_slice_thumb_set((_sv_slice_t *)sl[1],lo);
+      _sv_slice_thumb_set((_sv_slice_t *)sl[2],hi);
+      _sv_mapping_setup(&p2->mappings[i],0.,1.,0);
+      _sv_slider_set_gradient(p2->range_scales[i], &p2->mappings[i]);
+    }
+  }
+
+  /* dims */
+  {
+    p2->dim_table = gtk_table_new(p->dimensions,4,0);
+    gtk_box_pack_start(GTK_BOX(p->private->topbox), p2->dim_table, 0,0,4);
+    
+    GtkWidget *first_x = NULL;
+    GtkWidget *first_y = NULL;
+    GtkWidget *pressed_y = NULL;
+    p->private->dim_scales = calloc(p->dimensions,sizeof(*p->private->dim_scales));
+    p2->dim_xb = calloc(p->dimensions,sizeof(*p2->dim_xb));
+    p2->dim_yb = calloc(p->dimensions,sizeof(*p2->dim_yb));
+    
+    for(i=0;i<p->dimensions;i++){
+      sv_dim_t *d = p->dimension_list[i].d;
+      
+      /* label */
+      GtkWidget *label = gtk_label_new(d->legend);
+      gtk_misc_set_alignment(GTK_MISC(label),1.,.5);
+      gtk_table_attach(GTK_TABLE(p2->dim_table),label,0,1,i,i+1,
+		       GTK_FILL,0,5,0);
+      
+      /* x/y radio buttons */
+      if(!(d->flags & SV_DIM_NO_X)){
+	if(first_x)
+	  p2->dim_xb[i] = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(first_x),"X");
+	else{
+	  first_x = p2->dim_xb[i] = gtk_radio_button_new_with_label(NULL,"X");
+	  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_xb[i]),TRUE);
+	}
+	gtk_table_attach(GTK_TABLE(p2->dim_table),p2->dim_xb[i],1,2,i,i+1,
+			 GTK_SHRINK,0,3,0);
+      }
+      
+      if(!(d->flags & SV_DIM_NO_Y)){
+	if(first_y)
+	  p2->dim_yb[i] = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(first_y),"Y");
+	else
+	  first_y = p2->dim_yb[i] = gtk_radio_button_new_with_label(NULL,"Y");
+	if(!pressed_y && p2->dim_xb[i]!=first_x){
+	  pressed_y = p2->dim_yb[i];
+	  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_yb[i]),TRUE);
+	}
+	gtk_table_attach(GTK_TABLE(p2->dim_table),p2->dim_yb[i],2,3,i,i+1,
+			 GTK_SHRINK,0,3,0);
+      }
+      
+      p->private->dim_scales[i] = 
+	_sv_dim_widget_new(p->dimension_list+i,_sv_panel2d_center_callback,_sv_panel2d_bracket_callback);
+      
+      gtk_table_attach(GTK_TABLE(p2->dim_table),
+		       p->private->dim_scales[i]->t,
+		       3,4,i,i+1,
+		       GTK_EXPAND|GTK_FILL,0,0,0);
+      
+    }
+    for(i=0;i<p->dimensions;i++){
+      if(p2->dim_xb[i])
+	g_signal_connect (G_OBJECT (p2->dim_xb[i]), "toggled",
+			  G_CALLBACK (_sv_panel2d_dimchange_callback), p);
+      if(p2->dim_yb[i])
+	g_signal_connect (G_OBJECT (p2->dim_yb[i]), "toggled",
+			  G_CALLBACK (_sv_panel2d_dimchange_callback), p);
+    }
+  }
+
+  _sv_panel2d_update_xysel(p);
+
+  gtk_widget_realize(p->private->toplevel);
+  gtk_widget_realize(p->private->graph);
+  gtk_widget_realize(GTK_WIDGET(p->private->spinner));
+  gtk_widget_show_all(p->private->toplevel);
+  _sv_panel2d_update_xysel(p); // yes, this was already done; however,
+			       // gtk clobbered the event setup on the
+			       // insensitive buttons when it realized
+			       // them.  This call will restore them.
+
+  _sv_undo_resume();
+}
+
+static int _sv_panel2d_save(sv_panel_t *p, xmlNodePtr pn){  
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int ret=0,i;
+
+  xmlNodePtr n;
+
+  xmlNewProp(pn, (xmlChar *)"type", (xmlChar *)"2d");
+
+  // box
+  if(p->private->oldbox_active){
+    xmlNodePtr boxn = xmlNewChild(pn, NULL, (xmlChar *) "box", NULL);
+    _xmlNewPropF(boxn, "x1", p2->oldbox[0]);
+    _xmlNewPropF(boxn, "x2", p2->oldbox[1]);
+    _xmlNewPropF(boxn, "y1", p2->oldbox[2]);
+    _xmlNewPropF(boxn, "y2", p2->oldbox[3]);
+  }
+
+  // objective map settings
+  for(i=0;i<p->objectives;i++){
+    sv_obj_t *o = p->objective_list[i].o;
+    xmlNodePtr on = xmlNewChild(pn, NULL, (xmlChar *) "objective", NULL);
+    _xmlNewPropI(on, "position", i);
+    _xmlNewPropI(on, "number", o->number);
+    _xmlNewPropS(on, "name", o->name);
+    _xmlNewPropS(on, "type", o->output_types);
+    
+    // right now Y is the only type; the below is Y-specific
+    n = xmlNewChild(on, NULL, (xmlChar *) "y-map", NULL);
+    _xmlNewPropS(n, "color", _sv_mapping_name(p2->mappings[i].mapnum));
+    _xmlNewPropF(n, "low-bracket", _sv_slider_get_value(p2->range_scales[i],0));
+    _xmlNewPropF(n, "alpha", _sv_slider_get_value(p2->range_scales[i],1));
+    _xmlNewPropF(n, "high-bracket", _sv_slider_get_value(p2->range_scales[i],2));
+  }
+
+  // x/y dim selection
+  n = xmlNewChild(pn, NULL, (xmlChar *) "axes", NULL);
+  _xmlNewPropI(n, "xpos", p2->x_dnum);
+  _xmlNewPropI(n, "ypos", p2->y_dnum);
+
+  return ret;
+}
+
+int _sv_panel2d_load(sv_panel_t *p,
+		     _sv_panel_undo_t *u,
+		     xmlNodePtr pn,
+		     int warn){
+  int i;
+
+  // check type
+  _xmlCheckPropS(pn,"type","2d", "Panel %d type mismatch in save file.",p->number,&warn);
+  
+  // box
+  u->box_active = 0;
+  _xmlGetChildPropFPreserve(pn, "box", "x1", &u->box[0]);
+  _xmlGetChildPropFPreserve(pn, "box", "x2", &u->box[1]);
+  _xmlGetChildPropFPreserve(pn, "box", "y1", &u->box[2]);
+  _xmlGetChildPropFPreserve(pn, "box", "y2", &u->box[3]);
+
+  xmlNodePtr n = _xmlGetChildS(pn, "box", NULL, NULL);
+  if(n){
+    u->box_active = 1;
+    xmlFree(n);
+  }
+  
+  // objective map settings
+  for(i=0;i<p->objectives;i++){
+    sv_obj_t *o = p->objective_list[i].o;
+    xmlNodePtr on = _xmlGetChildI(pn, "objective", "position", i);
+    if(!on){
+      _sv_first_load_warning(&warn);
+      fprintf(stderr,"No save data found for panel %d objective \"%s\".\n",p->number, o->name);
+    }else{
+      // check name, type
+      _xmlCheckPropS(on,"name",o->name, "Objectve position %d name mismatch in save file.",i,&warn);
+      _xmlCheckPropS(on,"type",o->output_types, "Objectve position %d type mismatch in save file.",i,&warn);
+      
+      // right now Y is the only type; the below is Y-specific
+      // load maptype, values
+      _xmlGetChildPropFPreserve(on, "y-map", "low-bracket", &u->scale_vals[0][i]);
+      _xmlGetChildPropFPreserve(on, "y-map", "alpha", &u->scale_vals[1][i]);
+      _xmlGetChildPropFPreserve(on, "y-map", "high-bracket", &u->scale_vals[2][i]);
+      _xmlGetChildMap(on, "y-map", "color", _sv_mapping_map(), &u->mappings[i],
+		     "Panel %d objective unknown mapping setting", p->number, &warn);
+
+      xmlFreeNode(on);
+    }
+  }
+
+  // x/y dim selection
+  _xmlGetChildPropIPreserve(pn, "axes", "xpos", &u->x_d);
+  _xmlGetChildPropI(pn, "axes", "ypos", &u->y_d);
+
+  return warn;
+}
+
+sv_panel_t *sv_panel_new_2d(int number,
+			    char *name, 
+			    char *objectivelist,
+			    char *dimensionlist,
+			    unsigned flags){
+  
+  int i,j;
+  sv_panel_t *p = _sv_panel_new(number,name,objectivelist,dimensionlist,flags);
+  if(!p)return NULL;
+
+  _sv_panel2d_t *p2 = calloc(1, sizeof(*p2));
+  int fout_offsets[_sv_functions];
+  
+  p->subtype = 
+    calloc(1, sizeof(*p->subtype)); /* the union is alloced not
+				       embedded as its internal
+				       structure must be hidden */  
+  p->subtype->p2 = p2;
+  p->type = SV_PANEL_2D;
+  p->private->bg_type = SV_BG_CHECKS;
+
+  // verify all the objectives have scales
+  for(i=0;i<p->objectives;i++){
+    if(!p->objective_list[i].o->scale){
+      fprintf(stderr,"All objectives in a 2d panel must have a scale\n");
+      errno = -EINVAL;
+      return NULL;
+    }
+  }
+
+  p->private->realize = _sv_panel2d_realize;
+  p->private->map_action = _sv_panel2d_map_redraw;
+  p->private->legend_action = _sv_panel2d_legend_redraw;
+  p->private->compute_action = _sv_panel2d_compute;
+  p->private->request_compute = _sv_panel2d_mark_recompute;
+  p->private->crosshair_action = _sv_panel2d_crosshairs_callback;
+  p->private->print_action = _sv_panel2d_print;
+  p->private->undo_log = _sv_panel2d_undo_log;
+  p->private->undo_restore = _sv_panel2d_undo_restore;
+  p->private->save_action = _sv_panel2d_save;
+  p->private->load_action = _sv_panel2d_load;
+
+  /* set up helper data structures for rendering */
+
+  /* determine which functions are actually needed; if it's referenced
+     by an objective, it's used.  Precache them in dense form. */
+  {
+    int fn = _sv_functions;
+    int used[fn],count=0,offcount=0;
+    memset(used,0,sizeof(used));
+    memset(fout_offsets,-1,sizeof(fout_offsets));
+    
+    for(i=0;i<p->objectives;i++){
+      sv_obj_t *o = p->objective_list[i].o;
+      for(j=0;j<o->outputs;j++)
+	used[o->function_map[j]]=1;
+    }
+
+    for(i=0;i<fn;i++)
+      if(used[i]){
+	sv_func_t *f = _sv_function_list[i];
+	fout_offsets[i] = offcount;
+	offcount += f->outputs;
+	count++;
+      }
+
+    p2->used_functions = count;
+    p2->used_function_list = calloc(count, sizeof(*p2->used_function_list));
+
+    for(count=0,i=0;i<fn;i++)
+     if(used[i]){
+        p2->used_function_list[count]=_sv_function_list[i];
+	count++;
+      }
+  }
+
+  /* set up computation/render helpers for Y planes */
+
+  /* set up Y object mapping index */
+  {
+    int yobj_count = 0;
+
+    for(i=0;i<p->objectives;i++){
+      sv_obj_t *o = p->objective_list[i].o;
+      if(o->private->y_func) yobj_count++;
+    }
+
+    p2->y_obj_num = yobj_count;
+    p2->y_obj_list = calloc(yobj_count, sizeof(*p2->y_obj_list));
+    p2->y_obj_to_panel = calloc(yobj_count, sizeof(*p2->y_obj_to_panel));
+    p2->y_obj_from_panel = calloc(p->objectives, sizeof(*p2->y_obj_from_panel));
+    
+    yobj_count=0;
+    for(i=0;i<p->objectives;i++){
+      sv_obj_t *o = p->objective_list[i].o;
+      if(o->private->y_func){
+	p2->y_obj_list[yobj_count] = o;
+	p2->y_obj_to_panel[yobj_count] = i;
+	p2->y_obj_from_panel[i] = yobj_count;
+	yobj_count++;
+      }else
+	p2->y_obj_from_panel[i] = -1;
+      
+    }
+  }
+  
+  /* set up function Y output value demultiplex helper */
+  {
+    p2->y_fout_offset = calloc(p2->y_obj_num, sizeof(*p2->y_fout_offset));
+    for(i=0;i<p2->y_obj_num;i++){
+      sv_obj_t *o = p2->y_obj_list[i];
+      int funcnum = o->private->y_func->number;
+      p2->y_fout_offset[i] = fout_offsets[funcnum] + o->private->y_fout;
+    }
+  }
+
+  p2->y_map = calloc(p2->y_obj_num,sizeof(*p2->y_map));
+  p2->y_planetodo = calloc(p2->y_obj_num,sizeof(*p2->y_planetodo));
+  p2->y_planes = calloc(p2->y_obj_num,sizeof(*p2->y_planes));
+
+  return p;
+}

Added: trunk/sushivision/plane.h
===================================================================
--- trunk/sushivision/plane.h	                        (rev 0)
+++ trunk/sushivision/plane.h	2007-09-21 16:51:36 UTC (rev 13870)
@@ -0,0 +1,116 @@
+/*
+ *
+ *     sushivision copyright (C) 2006-2007 Monty <monty at xiph.org>
+ *
+ *  sushivision is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *   
+ *  sushivision is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *   
+ *  You should have received a copy of the GNU General Public License
+ *  along with sushivision; see the file COPYING.  If not, write to the
+ *  Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * 
+ */
+
+typedef union  sv_plane sv_plane_t;
+typedef struct sv_plane_bg sv_plane_bg_t;
+typedef struct sv_plane_2d sv_plane_2d_t;
+
+struct sv_zmap {
+
+  double *label_vals;
+  int labels;
+  int neg;
+  double al;
+  double lo;
+  double hi;
+  double lodel;
+  double *labeldelB;
+  double *labelvalB;
+
+};
+
+struct sv_plane_common {
+  int              plane_type;
+  sv_obj_t        *o;
+  sv_plane_t      *share_next;
+  sv_plane_t      *share_prev;
+  sv_panel_t      *panel;
+
+  void (*recompute_setup)(sv_plane_t *, sv_panel_t *, 
+			  sv_dim_data_t *, int dims);
+  int (*image_resize)(sv_plane_t *, sv_panel_t *);
+  int (*data_resize)(sv_plane_t *, sv_panel_t *);
+  int (*image_work)(sv_plane_t *, sv_panel_t *);
+  int (*data_work)(sv_plane_t *, sv_panel_t *);
+} sv_plane_common_t;
+
+struct sv_plane_bg {
+  sv_plane_common_t c;
+
+  // image data and concurrency tracking
+  sv_ucolor_t    *image;
+
+  // status
+  int              image_serialno;
+  int              image_waiting;
+  int              image_incomplete;
+  int              image_nextline;
+  sv_scalespace_t  image_x;
+  sv_scalespace_t  image_y;
+  unsigned char   *image_status; // rendering flags
+
+};
+
+struct sv_plane_2d {
+  sv_plane_common_t c;
+
+  // data
+  float           *data;  
+  sv_ucolor_t     *image; // panel size;
+
+  // status 
+  sv_scalespace_t  data_x;
+  sv_scalespace_t  data_y;
+  int              image_serialno;
+  int              data_waiting; 
+  int              data_incomplete; 
+  int              data_next;
+  int              image_next;
+  int             *image_flags;
+  
+  // remap payload
+
+  // resampling helpers
+  unsigned char   *resample_xdelA;
+  unsigned char   *resample_xdelB;
+  int             *resample_xnumA;
+  int             *resample_xnumB;
+  float            resample_xscalemul;
+
+  unsigned char   *resample_ydelA;
+  unsigned char   *resample_ydelB;
+  int             *resample_ynumA;
+  int             *resample_ynumB;
+  float           *resample_yscalemul;
+
+  // ui elements; use gdk lock
+  sv_mapping_t    *mapping;
+  sv_slider_t     *scale;
+  GtkWidget       *range_pulldown;
+  double           alphadel;
+
+};
+
+typedef union {
+  sv_plane_bg_t    pbg;
+  sv_plane_2d_t    p2d;
+} sv_plane_t;
+

Modified: trunk/sushivision/scale.h
===================================================================
--- trunk/sushivision/scale.h	2007-09-21 09:41:31 UTC (rev 13869)
+++ trunk/sushivision/scale.h	2007-09-21 16:51:36 UTC (rev 13870)
@@ -20,7 +20,6 @@
  */
 
 typedef struct {
-  char *legend;
   double lo;
   double hi;
   int neg;
@@ -39,7 +38,6 @@
   int massaged; // set if range had to be adjusted to avoid underflows
 } _sv_scalespace_t;
 
-extern char **_sv_scale_generate_labels(unsigned scalevals, double *scaleval_list);
 extern double _sv_scalespace_scaledel(_sv_scalespace_t *from, _sv_scalespace_t *to);
 extern long _sv_scalespace_scalenum(_sv_scalespace_t *from, _sv_scalespace_t *to);
 extern long _sv_scalespace_scaleden(_sv_scalespace_t *from, _sv_scalespace_t *to);
@@ -49,6 +47,6 @@
 extern double _sv_scalespace_pixel(_sv_scalespace_t *s, double val);
 extern int _sv_scalespace_mark(_sv_scalespace_t *s, int num);
 extern double _sv_scalespace_label(_sv_scalespace_t *s, int num, char *buffer);
-extern _sv_scalespace_t _sv_scalespace_linear (double lowpoint, double highpoint, int pixels, int max_spacing,char *name);
+extern _sv_scalespace_t _sv_scalespace_linear (double lowpoint, double highpoint, int pixels, int max_spacing);
 extern void _sv_scalespace_double(_sv_scalespace_t *s);
 extern int _sv_scalespace_decimal_exponent(_sv_scalespace_t *s);

Modified: trunk/sushivision/sushivision.h
===================================================================
--- trunk/sushivision/sushivision.h	2007-09-21 09:41:31 UTC (rev 13869)
+++ trunk/sushivision/sushivision.h	2007-09-21 16:51:36 UTC (rev 13870)
@@ -63,32 +63,6 @@
 
 /* dimensions ****************************************************/
 
-#define SV_DIM_NO_X 0x100
-#define SV_DIM_NO_Y 0x200
-#define SV_DIM_ZEROINDEX 0x1
-#define SV_DIM_MONOTONIC 0x2
-
-enum sv_dim_type { SV_DIM_CONTINUOUS, 
-			 SV_DIM_DISCRETE, 
-			 SV_DIM_PICKLIST};
-
-struct sv_dim{ 
-  int number;
-  char *name;
-  char *legend;
-  enum sv_dim_type type;
-
-  double bracket[2];
-  double val;
-
-  sv_scale_t *scale;
-  unsigned flags;
-  
-  int (*callback)(sv_dim_t *);
-  sv_dim_subtype_t *subtype;
-  sv_dim_internal_t *private;
-};
-
 sv_dim_t            *sv_dim_new (char *decl);
 
 sv_dim_t                *sv_dim (char *name);



More information about the commits mailing list