completely rethink waveform rendering (again)

There are 3 possible components to draw at each x-axis position: the waveform "line", the zero line and an outline/clip indicator.
We have to decide which of the 3 to draw at each position, pixel by pixel. This makes the rendering less efficient but it is
the only way I can see to do this correctly.
This commit is contained in:
Paul Davis 2014-06-23 14:57:55 -04:00
parent 9dd5e2769b
commit 87cc9f7d4f
2 changed files with 132 additions and 94 deletions

View file

@ -192,7 +192,7 @@ private:
void get_image (Cairo::RefPtr<Cairo::ImageSurface>& image, framepos_t start, framepos_t end, double& image_offset) const; void get_image (Cairo::RefPtr<Cairo::ImageSurface>& image, framepos_t start, framepos_t end, double& image_offset) const;
ArdourCanvas::Coord y_extent (double) const; ArdourCanvas::Coord y_extent (double, bool) const;
void draw_image (Cairo::RefPtr<Cairo::ImageSurface>&, ARDOUR::PeakData*, int) const; void draw_image (Cairo::RefPtr<Cairo::ImageSurface>&, ARDOUR::PeakData*, int) const;
}; };

View file

@ -319,9 +319,39 @@ WaveView::consolidate_image_cache () const
} }
} }
Coord
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.
*/
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));
} else {
pos = floor ((1.0-s) * (_height/2.0));
}
break;
}
return min (_height, (max (0.0, pos)));
}
struct LineTips { struct LineTips {
double top; double top;
double bot; double bot;
double spread;
bool clip_max; bool clip_max;
bool clip_min; bool clip_min;
@ -355,8 +385,11 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
if (_logscaled) { if (_logscaled) {
for (int i = 0; i < n_peaks; ++i) { for (int i = 0; i < n_peaks; ++i) {
tips[i].bot = height(); tips[i].bot = height();
tips[i].top = y_extent (alt_log_meter (fast_coefficient_to_dB (max (fabs (_peaks[i].max), fabs (_peaks[i].min))))); 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;
if (fabs (_peaks[i].max) >= clip_level) { if (fabs (_peaks[i].max) >= clip_level) {
tips[i].clip_max = true; tips[i].clip_max = true;
@ -366,9 +399,12 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
tips[i].clip_min = true; tips[i].clip_min = true;
} }
} }
} else {for (int i = 0; i < n_peaks; ++i) { } else {for (int i = 0; i < n_peaks; ++i) {
tips[i].bot = height(); tips[i].bot = height();
tips[i].top = y_extent (max (fabs (_peaks[i].max), fabs (_peaks[i].min))); 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) { if (fabs (_peaks[i].max) >= clip_level) {
tips[i].clip_max = true; tips[i].clip_max = true;
@ -384,35 +420,36 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
if (_logscaled) { if (_logscaled) {
for (int i = 0; i < n_peaks; ++i) { for (int i = 0; i < n_peaks; ++i) {
Coord top = _peaks[i].min; double top = _peaks[i].min;
Coord bot = _peaks[i].max; double bot = _peaks[i].max;
if (fabs (top) >= clip_level) { if (fabs (top) >= clip_level) {
tips[i].clip_max = true; tips[i].clip_max = true;
} }
if (fabs (bot) >= clip_level) { if (fabs (top) >= clip_level) {
tips[i].clip_min = true; tips[i].clip_min = true;
} }
if (top > 0.0) { if (top > 0.0) {
top = y_extent (alt_log_meter (fast_coefficient_to_dB (top))); top = alt_log_meter (fast_coefficient_to_dB (top));
} else if (top < 0.0) { } else if (top < 0.0) {
top = y_extent (-alt_log_meter (fast_coefficient_to_dB (-top))); top =-alt_log_meter (fast_coefficient_to_dB (-top));
} else { } else {
top = y_extent (0.0); top = 0.0;
} }
if (bot > 0.0) { if (bot > 0.0) {
bot = y_extent (alt_log_meter (fast_coefficient_to_dB (bot))); bot = alt_log_meter (fast_coefficient_to_dB (bot));
} else if (bot < 0.0) { } else if (bot < 0.0) {
bot = y_extent (-alt_log_meter (fast_coefficient_to_dB (-bot))); bot = -alt_log_meter (fast_coefficient_to_dB (-bot));
} else { } else {
bot = y_extent (0.0); bot = 0.0;
} }
tips[i].top = top; tips[i].spread = fabs (((1.0 - top) * (_height/2.0)) - ((1.0 - bot) * _height/2.0));
tips[i].bot = bot; tips[i].top = y_extent (top, false);
tips[i].bot = y_extent (bot, true);
} }
} else { } else {
@ -426,11 +463,11 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
tips[i].clip_min = true; tips[i].clip_min = true;
} }
tips[i].top = y_extent (_peaks[i].min); tips[i].top = y_extent (_peaks[i].min, false);
tips[i].bot = y_extent (_peaks[i].max); tips[i].bot = y_extent (_peaks[i].max, true);
tips[i].spread = fabs (((1.0 - _peaks[i].max) * (_height/2.0)) - ((1.0 - _peaks[i].min) * (_height/2.0)));
} }
} }
} }
@ -477,72 +514,94 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
/* draw the lines */ /* draw the lines */
if (_shape == WaveView::Rectified) {
for (int i = 0; i < n_peaks; ++i) {
context->move_to (i, tips[i].top); /* down 1 pixel */
context->line_to (i, tips[i].bot);
}
} else {
for (int i = 0; i < n_peaks; ++i) {
context->move_to (i, tips[i].top);
context->line_to (i, tips[i].bot);
}
}
context->stroke (); /* the height of the clip-indicator should be at most 7 pixels,
/* zero line goes next, so that the outline/clip is on top of it
*/
if (show_zero_line()) {
set_source_rgba (context, _zero_color);
context->set_line_width (1.0);
context->move_to (0, y_extent (0.0) + 0.5);
context->line_to (n_peaks, y_extent (0.0) + 0.5);
context->stroke ();
}
/* now add dots to the top and bottom of each line (this is
* modelled on pyramix, except that we also visual indicate
* clipping if it occurs).
*
* the height of the clip-indicator should be at most 7 pixels,
* or 5% of the height of the waveview item. * or 5% of the height of the waveview item.
*/ */
const double clip_height = min (7.0, ceil (_height * 0.05)); const double clip_height = min (7.0, ceil (_height * 0.05));
if (_shape == WaveView::Rectified) {
set_source_rgba (context, _outline_color); for (int i = 0; i < n_peaks; ++i) {
for (int i = 0; i < n_peaks; ++i) { if (tips[i].spread >= 2.0) {
context->move_to (i, tips[i].top);
context->line_to (i, tips[i].bot);
context->stroke ();
}
context->save ();
context->move_to (i, tips[i].top);
if (_global_show_waveform_clipping && (tips[i].clip_max || tips[i].clip_min)) {
/* clip-indicating upper terminal line */
set_source_rgba (context, _clip_color);
context->rel_line_to (0, clip_height);
context->stroke ();
} else {
/* normal upper terminal dot */
set_source_rgba (context, _outline_color);
context->rel_line_to (0, 1.0);
context->stroke ();
}
context->move_to (i, tips[i].top); context->restore ();
if (_global_show_waveform_clipping && ((_shape == WaveView::Rectified && (tips[i].clip_max || tips[i].clip_min)) || tips[i].clip_max)) {
/* clip-indicating upper terminal line */
set_source_rgba (context, _clip_color);
context->rel_line_to (0, clip_height);
context->stroke ();
set_source_rgba (context, _outline_color);
} else {
/* normal upper terminal dot */
context->rel_line_to (0, 1.0);
context->stroke ();
} }
} else {
for (int i = 0; i < n_peaks; ++i) {
context->move_to (i, tips[i].bot); if (tips[i].spread >= 3.0) {
if (_global_show_waveform_clipping && _shape != WaveView::Rectified && tips[i].clip_min) { context->move_to (i, tips[i].top);
/* clip-indicating lower terminal line */ context->line_to (i, tips[i].bot);
set_source_rgba (context, _clip_color); context->stroke ();
context->rel_line_to (0, -clip_height); }
context->stroke ();
set_source_rgba (context, _outline_color); if (tips[i].spread >= 3.0 && show_zero_line()) {
} else { context->save ();
/* normal lower terminal dot */ set_source_rgba (context, _zero_color);
context->rel_line_to (0, -1.0); context->move_to (0, _height/2.0);
context->stroke (); context->rel_line_to (0, 0.5);
context->stroke ();
context->restore ();
}
context->save ();
context->move_to (i, tips[i].top);
if (_global_show_waveform_clipping && ((_shape == WaveView::Rectified && (tips[i].clip_max || tips[i].clip_min)) || tips[i].clip_max)) {
/* clip-indicating upper terminal line */
set_source_rgba (context, _clip_color);
context->rel_line_to (0, clip_height);
context->stroke ();
} else {
/* normal upper terminal dot */
set_source_rgba (context, _outline_color);
context->rel_line_to (0, 1.0);
context->stroke ();
}
context->restore ();
if (tips[i].spread >= 2.0) {
context->save ();
context->move_to (i, tips[i].bot);
if (_global_show_waveform_clipping && _shape != WaveView::Rectified && tips[i].clip_min) {
/* clip-indicating lower terminal line */
set_source_rgba (context, _clip_color);
context->rel_line_to (0, -clip_height);
context->stroke ();
} else {
/* normal lower terminal dot */
set_source_rgba (context, _outline_color);
context->rel_line_to (0, -1.0);
context->stroke ();
}
context->restore ();
}
} }
} }
} }
void void
@ -839,27 +898,6 @@ WaveView::region_resized ()
end_change (); end_change ();
} }
Coord
WaveView::y_extent (double s) const
{
/* it is important that this returns an integral value, so that we
can ensure correct single pixel behaviour.
*/
Coord pos;
switch (_shape) {
case Rectified:
pos = floor (_height - (s * _height));
break;
default:
pos = floor ((1.0-s) * (_height / 2.0));
break;
}
return min (_height, (max (0.0, pos)));
}
void void
WaveView::set_global_gradient_depth (double depth) WaveView::set_global_gradient_depth (double depth)
{ {