waveform drawing details:
authorRobin Gareus <robin@gareus.org>
Fri, 12 Sep 2014 23:34:27 +0000 (01:34 +0200)
committerRobin Gareus <robin@gareus.org>
Fri, 12 Sep 2014 23:48:10 +0000 (01:48 +0200)
* proper y-pixel alignment (+.5px offset)
* outline: draw dots (not 1px lines)
* shape: round towards peak (use signal, not top/bot)
* honor 2px red selection border
* work-around canvas rect +1 issue
* always draw clipping line towards center
* draw at most one clip-line at either side of 0.
* exact 1px wide zero line
* fix spread calculation for rectified view

libs/canvas/wave_view.cc

index 6adb62005d715e5683fa16fbfcdb032378807356..545e69ca4366728d92fd5a0f8dd09e65ca0f6fbf 100644 (file)
@@ -317,32 +317,40 @@ WaveView::consolidate_image_cache () const
 }
 
 Coord
-WaveView::y_extent (double s, bool round_to_lower_edge) const
+WaveView::y_extent (double s, bool /*round_to_lower_edge*/) const
 {
        /* it is important that this returns an integral value, so that we 
-          can ensure correct single pixel behaviour.
+        * can ensure correct single pixel behaviour.
+        *
+        * we need (_height - max(wave_line_width))
+        * wave_line_width == 1 IFF top==bottom (1 sample per pixel or flat line)
+        * wave_line_width == 2 otherwise
+        * then round away from the zero line, towards peak
         */
-
-       Coord pos;
-
-       switch (_shape) {
-       case Rectified:
-               if (round_to_lower_edge) {
-                       pos = ceil (_height - (s * _height));
-               } else {
-                       pos = floor (_height - (s * _height));
-               }
-               break;
-       default:
-               if (round_to_lower_edge) {
-                       pos = ceil ((1.0-s) * (_height/2.0));
+       if (_shape == Rectified) {
+               // we only ever have 1 point and align to the bottom (not center)
+               return floor ((1.0 - s) * (_height - 2.0));
+       } else {
+               /* currently canvas rectangle is off-by-one and we
+                * cannot draw a pixel at 0 (-.5 .. +.5) without it being
+                * clipped. A value 1.0 (ideally one point at y=0) ends
+                * up a pixel down. and a value of -1.0 (ideally y = _height-1)
+                * currently is on the bottom separator line :(
+                * So to make the complete waveform appear centered in
+                * a region, we translate by +.5 (instead of -.5)
+                * and waste two pixel of height: -4 (instad of -2)
+                *
+                * This needs fixing in canvas/rectangle the intersect
+                * functions and probably a couple of other places as well...
+                */
+               Coord pos;
+               if (s < 0) {
+                       pos = ceil  ((1.0 - s) * .5 * (_height - 4.0));
                } else {
-                       pos = floor ((1.0-s) * (_height/2.0));
+                       pos = floor ((1.0 - s) * .5 * (_height - 4.0));
                }
-               break;
+               return min (_height - 4.0, (max (0.0, pos)));
        }
-
-       return min (_height, (max (0.0, pos)));
 }
 
 struct LineTips {
@@ -405,10 +413,10 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
                if (_logscaled) {
                        for (int i = 0; i < n_peaks; ++i) {
 
-                               tips[i].bot = height();
+                               tips[i].bot = height() - 1.0;
                                const double p = alt_log_meter (fast_coefficient_to_dB (max (fabs (_peaks[i].max), fabs (_peaks[i].min))));
                                tips[i].top = y_extent (p, false);
-                               tips[i].spread = (1.0 - p) * _height;
+                               tips[i].spread = p * (_height - 1.0);
 
                                if (fabs (_peaks[i].max) >= clip_level) {
                                        tips[i].clip_max = true;
@@ -421,33 +429,40 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
 
                } else {for (int i = 0; i < n_peaks; ++i) {
 
-                               tips[i].bot = height();
-                               tips[i].top = y_extent (max (fabs (_peaks[i].max), fabs (_peaks[i].min)), true);
-                               tips[i].spread = (1.0 - fabs(_peaks[i].max - _peaks[i].min)) * _height;
-
-                               if (fabs (_peaks[i].max) >= clip_level) {
+                               tips[i].bot = height() - 1.0;
+                               const double p = max(fabs (_peaks[i].max), fabs (_peaks[i].min));
+                               tips[i].top = y_extent (p, false);
+                               tips[i].spread = p * (_height - 2.0);
+                               if (p >= clip_level) {
                                        tips[i].clip_max = true;
                                }
-
-                               if (fabs (_peaks[i].min) >= clip_level) {
-                                       tips[i].clip_min = true;
-                               }
                        }
+
                }
 
        } else {
 
                if (_logscaled) {
                        for (int i = 0; i < n_peaks; ++i) {
-                               double top = _peaks[i].min;
-                               double bot = _peaks[i].max;
+                               double top = _peaks[i].max;
+                               double bot = _peaks[i].min;
 
-                               if (fabs (top) >= clip_level) {
-                                       tips[i].clip_max = true;
+                               if (_peaks[i].max > 0 && _peaks[i].min > 0) {
+                                       if (fabs (_peaks[i].max) >= clip_level) {
+                                               tips[i].clip_max = true;
+                                       }
                                }
-
-                               if (fabs (top) >= clip_level) {
-                                       tips[i].clip_min = true;
+                               else if (_peaks[i].max < 0 && _peaks[i].min < 0) {
+                                       if (fabs (_peaks[i].min) >= clip_level) {
+                                               tips[i].clip_min = true;
+                                       }
+                               } else {
+                                       if (fabs (_peaks[i].max) >= clip_level) {
+                                               tips[i].clip_max = true;
+                                       }
+                                       if (fabs (_peaks[i].min) >= clip_level) {
+                                               tips[i].clip_min = true;
+                                       }
                                }
 
                                if (top > 0.0) {
@@ -473,17 +488,26 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
 
                } else {
                        for (int i = 0; i < n_peaks; ++i) {
-
-                               if (fabs (_peaks[i].max) >= clip_level) {
-                                       tips[i].clip_max = true;
+                               if (_peaks[i].max > 0 && _peaks[i].min > 0) {
+                                       if (fabs (_peaks[i].max) >= clip_level) {
+                                               tips[i].clip_max = true;
+                                       }
                                }
-
-                               if (fabs (_peaks[i].min) >= clip_level) {
-                                       tips[i].clip_min = true;
+                               else if (_peaks[i].max < 0 && _peaks[i].min < 0) {
+                                       if (fabs (_peaks[i].min) >= clip_level) {
+                                               tips[i].clip_min = true;
+                                       }
+                               } else {
+                                       if (fabs (_peaks[i].max) >= clip_level) {
+                                               tips[i].clip_max = true;
+                                       }
+                                       if (fabs (_peaks[i].min) >= clip_level) {
+                                               tips[i].clip_min = true;
+                                       }
                                }
 
-                               tips[i].top = y_extent (_peaks[i].min, false);
-                               tips[i].bot = y_extent (_peaks[i].max, true);
+                               tips[i].top = y_extent (_peaks[i].max, false);
+                               tips[i].bot = y_extent (_peaks[i].min, true);
                                tips[i].spread = fabs (tips[i].top - tips[i].bot);
                        }
                        
@@ -499,16 +523,17 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
        /* ensure single-pixel lines */
                
        wave_context->set_line_width (1.0);
-       wave_context->translate (0.5, 0.0);
+       wave_context->translate (0.5, +0.5);
 
+       outline_context->set_line_cap (Cairo::LINE_CAP_ROUND);
        outline_context->set_line_width (1.0);
-       outline_context->translate (0.5, 0.0);
+       outline_context->translate (0.5, +0.5);
 
        clip_context->set_line_width (1.0);
-       clip_context->translate (0.5, 0.0);
+       clip_context->translate (0.5, +0.5);
 
        zero_context->set_line_width (1.0);
-       zero_context->translate (0.5, 0.0);
+       zero_context->translate (0.5, +0.5);
 
        /* the height of the clip-indicator should be at most 7 pixels,
         * or 5% of the height of the waveview item.
@@ -547,19 +572,19 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
 
                        /* waveform line */
 
-                       if (tips[i].spread >= 2.0) {
+                       if (tips[i].spread >= 1.0) {
                                wave_context->move_to (i, tips[i].top);
                                wave_context->line_to (i, tips[i].bot);
                        }
 
-                       if (_global_show_waveform_clipping && (tips[i].clip_max || tips[i].clip_min)) {
+                       if (_global_show_waveform_clipping && (tips[i].clip_max)) {
                                clip_context->move_to (i, tips[i].top);
                                /* clip-indicating upper terminal line */
-                               clip_context->rel_line_to (0, min (clip_height, floor (tips[i].spread)));
+                               clip_context->rel_line_to (0, min (clip_height, ceil(tips[i].spread + .5)));
                        } else {
                                outline_context->move_to (i, tips[i].top);
                                /* normal upper terminal dot */
-                               outline_context->rel_line_to (0, 1.0);
+                               outline_context->close_path ();
                        }
                }
 
@@ -568,22 +593,22 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
                outline_context->stroke ();
 
        } else {
+               const double height_2 = (_height - 4.0) * .5;
 
                for (int i = 0; i < n_peaks; ++i) {
 
                        /* waveform line */
 
-                       if (tips[i].spread >= 3.0) {
+                       if (tips[i].spread >= 2.0) {
                                wave_context->move_to (i, tips[i].top);
                                wave_context->line_to (i, tips[i].bot);
                        }
-
                        if (i > 0) {
-                               if (tips[i-1].top < tips[i].bot) {
+                               if (tips[i-1].top + 2 < tips[i].bot) {
                                        wave_context->move_to (i-1, tips[i-1].top);
                                        wave_context->line_to (i, tips[i].bot);
                                }
-                               else if (tips[i-1].bot > tips[i].top) {
+                               else if (tips[i-1].bot > tips[i].top + 2) {
                                        wave_context->move_to (i-1, tips[i-1].bot);
                                        wave_context->line_to (i, tips[i].top);
                                }
@@ -592,36 +617,40 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
                        /* zero line */
 
                        if (tips[i].spread >= 5.0 && show_zero_line()) {
-                               zero_context->move_to (i, _height/2.0);
-                               zero_context->rel_line_to (0, 0.5);
-                       }
-                       
-                       /* upper outline/clip indicator */
-
-                       if (_global_show_waveform_clipping && tips[i].clip_max) {
-                               clip_context->move_to (i, tips[i].top);
-                               /* clip-indicating upper terminal line */
-                               clip_context->rel_line_to (0, min (clip_height, floor (tips[i].spread)));
-                       } else {
-                               outline_context->move_to (i, tips[i].top);
-                               /* normal upper terminal dot */
-                               outline_context->rel_line_to (0, 1.0);
+                               zero_context->move_to (i, floor(height_2));
+                               zero_context->rel_line_to (1.0, 0);
                        }
 
-                       if (tips[i].spread >= 2.0) {
-
+                       if (tips[i].spread > 1.0) {
                                /* lower outline/clip indicator */
-
                                if (_global_show_waveform_clipping && tips[i].clip_min) {
                                        clip_context->move_to (i, tips[i].bot);
                                        /* clip-indicating lower terminal line */
-                                       clip_context->rel_line_to (0, -(min (clip_height, floor (tips[i].spread))));
+                                       const double sign = tips[i].bot > height_2 ? -1 : 1;
+                                       clip_context->rel_line_to (0, sign * min (clip_height, ceil (tips[i].spread + .5)));
                                } else {
                                        outline_context->move_to (i, tips[i].bot);
                                        /* normal lower terminal dot */
-                                       outline_context->rel_line_to (0, -1.0);
+                                       outline_context->close_path ();
+                               }
+                       } else {
+                               if (tips[i].clip_min) {
+                                       // make sure we draw the clip
+                                       tips[i].clip_max = true;
                                }
                        }
+
+                       /* upper outline/clip indicator */
+                       if (_global_show_waveform_clipping && tips[i].clip_max) {
+                               clip_context->move_to (i, tips[i].top);
+                               /* clip-indicating upper terminal line */
+                               const double sign = tips[i].top > height_2 ? -1 : 1;
+                               clip_context->rel_line_to (0, sign * min(clip_height, ceil(tips[i].spread + .5)));
+                       } else {
+                               outline_context->move_to (i, tips[i].top);
+                               /* normal upper terminal dot */
+                               outline_context->close_path ();
+                       }
                }
 
                wave_context->stroke ();