X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fcanvas%2Fwave_view.cc;h=3521ef33cd20bc610a745ceb7196cf06c7baab64;hb=eff25b06f5feae81e1fb9bd323a555611f900eaf;hp=6adb62005d715e5683fa16fbfcdb032378807356;hpb=878797d13ef50792f134563b5739a3a3a4aa02e3;p=ardour.git diff --git a/libs/canvas/wave_view.cc b/libs/canvas/wave_view.cc index 6adb62005d..3521ef33cd 100644 --- a/libs/canvas/wave_view.cc +++ b/libs/canvas/wave_view.cc @@ -34,6 +34,7 @@ #include "canvas/wave_view.h" #include "canvas/utils.h" #include "canvas/canvas.h" +#include "canvas/colors.h" #include @@ -70,6 +71,7 @@ WaveView::WaveView (Canvas* c, boost::shared_ptr region) , _gradient_depth_independent (false) , _amplitude_above_axis (1.0) , _region_amplitude (_region->scale_amplitude ()) + , _start_shift (0.0) , _region_start (region->start()) { VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this)); @@ -123,7 +125,7 @@ WaveView::handle_visual_property_change () _gradient_depth = global_gradient_depth(); changed = true; } - + if (changed) { begin_visual_change (); invalidate_image_cache (); @@ -170,17 +172,11 @@ WaveView::set_samples_per_pixel (double samples_per_pixel) invalidate_image_cache (); _samples_per_pixel = samples_per_pixel; _bounding_box_dirty = true; - + end_change (); } } -static inline double -image_to_window (double wave_origin, double image_start) -{ - return wave_origin + image_start; -} - static inline double window_to_image (double wave_origin, double image_start) { @@ -215,6 +211,21 @@ WaveView::invalidate_image_cache () vector deletion_list; vector caches; + /* The source may have disappeared in the case of rec regions.*/ + if (_region->n_channels() == 0) { + std::map , std::vector >::iterator i; + for (i = _image_cache.begin(); i != _image_cache.end(); ++i) { + if (i->first.unique()) { + for (uint32_t n = 0; n < (*i).second.size (); ++n) { + (*i).second[n].image.clear (); + } + (*i).second.clear (); + _image_cache.erase(i->first); + } + } + return; + } + if (_image_cache.find (_region->audio_source ()) != _image_cache.end ()) { caches = _image_cache.find (_region->audio_source ())->second; } else { @@ -223,16 +234,15 @@ WaveView::invalidate_image_cache () for (uint32_t i = 0; i < caches.size (); ++i) { - if (_channel != caches[i].channel + if (_channel != caches[i].channel || _height != caches[i].height - || _region_amplitude != caches[i].amplitude + || _region_amplitude != caches[i].amplitude || _fill_color != caches[i].fill_color) { continue; } deletion_list.push_back (i); - } while (deletion_list.size() > 0) { @@ -246,7 +256,6 @@ WaveView::invalidate_image_cache () } else { _image_cache[_region->audio_source ()] = caches; } - } void @@ -262,7 +271,7 @@ WaveView::consolidate_image_cache () const for (uint32_t i = 0; i < caches.size (); ++i) { - if (_channel != caches[i].channel + if (_channel != caches[i].channel || _height != caches[i].height || _region_amplitude != caches[i].amplitude || _fill_color != caches[i].fill_color) { @@ -276,8 +285,8 @@ WaveView::consolidate_image_cache () const for (uint32_t j = i; j < caches.size (); ++j) { - if (i == j || _channel != caches[j].channel - || _height != caches[i].height + if (i == j || _channel != caches[j].channel + || _height != caches[i].height || _region_amplitude != caches[i].amplitude || _fill_color != caches[i].fill_color) { @@ -300,7 +309,7 @@ WaveView::consolidate_image_cache () const deletion_list.pop_back(); } - /* We don't care if this channel/height/amplitude has anything in the cache - just drop the Last Added entries + /* We don't care if this channel/height/amplitude has anything in the cache - just drop the Last Added entries until we reach a size where there is a maximum of CACHE_HIGH_WATER + other entries. */ @@ -317,32 +326,72 @@ 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. + /* it is important that this returns an integral value, so that we + * 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))); } +} + +void +WaveView::draw_absent_image (Cairo::RefPtr& image, PeakData* _peaks, int n_peaks) const +{ + Cairo::RefPtr stripe = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, _height); + + Cairo::RefPtr stripe_context = Cairo::Context::create (stripe); + stripe_context->set_antialias (Cairo::ANTIALIAS_NONE); + + uint32_t stripe_separation = 150; + double start = - floor (_height / stripe_separation) * stripe_separation; + int stripe_x = 0; + + while (start < n_peaks) { + + stripe_context->move_to (start, 0); + stripe_x = start + _height; + stripe_context->line_to (stripe_x, _height); + start += stripe_separation; + } + + stripe_context->set_source_rgba (1.0, 1.0, 1.0, 1.0); + stripe_context->set_line_cap (Cairo::LINE_CAP_SQUARE); + stripe_context->set_line_width(50); + stripe_context->stroke(); - return min (_height, (max (0.0, pos))); + Cairo::RefPtr context = Cairo::Context::create (image); + + context->set_source_rgba (1.0, 1.0, 0.0, 0.3); + context->mask (stripe, 0, 0); + context->fill (); } struct LineTips { @@ -351,7 +400,7 @@ struct LineTips { double spread; bool clip_max; bool clip_min; - + LineTips() : top (0.0), bot (0.0), clip_max (false), clip_min (false) {} }; @@ -361,8 +410,8 @@ struct ImageSet { Cairo::RefPtr clip; Cairo::RefPtr zero; - ImageSet() : - wave (0), outline (0), clip (0), zero (0) {} + ImageSet() : + wave (0), outline (0), clip (0), zero (0) {} }; void @@ -380,6 +429,10 @@ WaveView::draw_image (Cairo::RefPtr& image, PeakData* _peak Cairo::RefPtr outline_context = Cairo::Context::create (images.outline); Cairo::RefPtr clip_context = Cairo::Context::create (images.clip); Cairo::RefPtr zero_context = Cairo::Context::create (images.zero); + wave_context->set_antialias (Cairo::ANTIALIAS_NONE); + outline_context->set_antialias (Cairo::ANTIALIAS_NONE); + clip_context->set_antialias (Cairo::ANTIALIAS_NONE); + zero_context->set_antialias (Cairo::ANTIALIAS_NONE); boost::scoped_array tips (new LineTips[n_peaks]); @@ -405,48 +458,45 @@ WaveView::draw_image (Cairo::RefPtr& 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) { + if (_peaks[i].max >= clip_level) { tips[i].clip_max = true; } - if (fabs (_peaks[i].min) >= clip_level) { + if (-(_peaks[i].min) >= clip_level) { tips[i].clip_min = true; } } - } 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; + } else { + for (int i = 0; i < n_peaks; ++i) { - 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 >= clip_level) { + tips[i].clip_max = true; } - - if (fabs (top) >= clip_level) { + if (-(_peaks[i].min) >= clip_level) { tips[i].clip_min = true; } @@ -468,25 +518,23 @@ WaveView::draw_image (Cairo::RefPtr& image, PeakData* _peak tips[i].top = y_extent (top, false); tips[i].bot = y_extent (bot, true); - tips[i].spread = fabs (tips[i].top - tips[i].bot); - } + tips[i].spread = tips[i].bot - tips[i].top; + } } else { for (int i = 0; i < n_peaks; ++i) { - - if (fabs (_peaks[i].max) >= clip_level) { + if (_peaks[i].max >= clip_level) { tips[i].clip_max = true; } - - if (fabs (_peaks[i].min) >= clip_level) { + if (-(_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].spread = fabs (tips[i].top - tips[i].bot); + tips[i].top = y_extent (_peaks[i].max, false); + tips[i].bot = y_extent (_peaks[i].min, true); + tips[i].spread = tips[i].bot - tips[i].top; } - + } } Color alpha_one = rgba_to_color (0, 0, 0, 1.0); @@ -497,25 +545,26 @@ WaveView::draw_image (Cairo::RefPtr& image, PeakData* _peak set_source_rgba (zero_context, alpha_one); /* 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_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. */ const double clip_height = min (7.0, ceil (_height * 0.05)); - + bool draw_outline_as_wave = false; + /* 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 @@ -529,11 +578,11 @@ WaveView::draw_image (Cairo::RefPtr& image, PeakData* _peak we just draw the upper outline/clip indicator. With 2 pixels of spread, we draw the upper and lower outline clip - indicators. - - With 3 pixels of spread we draw the upper and lower outline/clip + indicators. + + With 3 pixels of spread we draw the upper and lower outline/clip indicators and at least 1 pixel of the waveform line. - + With 5 pixels of spread, we draw all components. We can do rectified as two separate passes because we have a much @@ -547,19 +596,19 @@ WaveView::draw_image (Cairo::RefPtr& 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,58 +617,76 @@ WaveView::draw_image (Cairo::RefPtr& 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); } - + /* draw square waves and other discontiguous points clearly */ if (i > 0) { - if (tips[i-1].top < tips[i].bot) { + if (tips[i-1].top + 2 < tips[i].top) { 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) { - wave_context->move_to (i-1, tips[i-1].bot); + wave_context->line_to (i-1, (tips[i].bot + tips[i-1].top)/2); + wave_context->move_to (i, (tips[i].bot + tips[i-1].top)/2); wave_context->line_to (i, tips[i].top); + } else if (tips[i-1].bot > tips[i].bot + 2) { + wave_context->move_to (i-1, tips[i-1].bot); + wave_context->line_to (i-1, (tips[i].top + tips[i-1].bot)/2); + wave_context->move_to (i, (tips[i].top + tips[i-1].bot)/2); + wave_context->line_to (i, tips[i].bot); } } /* 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) { + draw_outline_as_wave = false; /* 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); + outline_context->move_to (i, tips[i].bot + 0.5); /* normal lower terminal dot */ - outline_context->rel_line_to (0, -1.0); + outline_context->rel_line_to (0, -0.5); + } + } else { + draw_outline_as_wave = true; + 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 { + if (draw_outline_as_wave) { + wave_context->move_to (i, tips[i].top + 0.5); + /* special case where outline only is drawn. + is this correct? too short by 0.5? + */ + wave_context->rel_line_to (0, -0.5); + } else { + outline_context->move_to (i, tips[i].top + 0.5); + /* normal upper terminal dot */ + outline_context->rel_line_to (0, -0.5); } } } @@ -635,11 +702,11 @@ WaveView::draw_image (Cairo::RefPtr& image, PeakData* _peak /* Here we set a source colour and use the various components as a mask. */ if (gradient_depth() != 0.0) { - + Cairo::RefPtr gradient (Cairo::LinearGradient::create (0, 0, 0, _height)); - + double stops[3]; - + double r, g, b, a; if (_shape == Rectified) { @@ -659,9 +726,9 @@ WaveView::draw_image (Cairo::RefPtr& image, PeakData* _peak color_to_hsv (_fill_color, h, s, v); /* change v towards white */ v *= 1.0 - gradient_depth(); - Color center = hsv_to_color (h, s, v, a); + Color center = hsva_to_color (h, s, v, a); color_to_rgba (center, r, g, b, a); - + gradient->add_color_stop_rgba (stops[0], r, g, b, a); gradient->add_color_stop_rgba (stops[2], r, g, b, a); @@ -671,7 +738,7 @@ WaveView::draw_image (Cairo::RefPtr& image, PeakData* _peak } context->mask (images.wave, 0, 0); - context->fill (); + context->fill (); set_source_rgba (context, _outline_color); context->mask (images.outline, 0, 0); @@ -693,7 +760,7 @@ WaveView::get_image (Cairo::RefPtr& image, framepos_t start vector caches; if (_image_cache.find (_region->audio_source ()) != _image_cache.end ()) { - + caches = _image_cache.find (_region->audio_source ())->second; } @@ -701,7 +768,7 @@ WaveView::get_image (Cairo::RefPtr& image, framepos_t start */ for (uint32_t i = 0; i < caches.size (); ++i) { - if (_channel != caches[i].channel + if (_channel != caches[i].channel || _height != caches[i].height || _region_amplitude != caches[i].amplitude || _fill_color != caches[i].fill_color) { @@ -723,9 +790,9 @@ WaveView::get_image (Cairo::RefPtr& image, framepos_t start consolidate_image_cache (); /* sample position is canonical here, and we want to generate - * an image that spans about twice the canvas width + * an image that spans about twice the canvas width */ - + const framepos_t center = start + ((end - start) / 2); const framecnt_t canvas_samples = _canvas->visible_area().width() * _samples_per_pixel; /* one canvas width */ @@ -739,14 +806,19 @@ WaveView::get_image (Cairo::RefPtr& image, framepos_t start boost::scoped_array peaks (new PeakData[n_peaks]); - _region->read_peaks (peaks.get(), n_peaks, + framecnt_t peaks_read; + peaks_read = _region->read_peaks (peaks.get(), n_peaks, sample_start, sample_end - sample_start, - _channel, + _channel, _samples_per_pixel); image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, n_peaks, _height); - draw_image (image, peaks.get(), n_peaks); + if (peaks_read > 0) { + draw_image (image, peaks.get(), n_peaks); + } else { + draw_absent_image (image, peaks.get(), n_peaks); + } _image_cache[_region->audio_source ()].push_back (CacheEntry (_channel, _height, _region_amplitude, _fill_color, sample_start, sample_end, image)); @@ -766,29 +838,29 @@ WaveView::render (Rect const & area, Cairo::RefPtr context) cons return; } - Rect self = item_to_window (Rect (0.5, 0.0, _region->length() / _samples_per_pixel, _height)); + Rect self = item_to_window (Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height)); boost::optional d = self.intersection (area); if (!d) { return; } - + Rect draw = d.get(); /* window coordinates - pixels where x=0 is the left edge of the canvas * window. We round down in case we were asked to * draw "between" pixels at the start and/or end. */ - - const double draw_start = floor (draw.x0); + + double draw_start = floor (draw.x0); const double draw_end = floor (draw.x1); // cerr << "Need to draw " << draw_start << " .. " << draw_end << endl; - + /* image coordnates: pixels where x=0 is the start of this waveview, * wherever it may be positioned. thus image_start=N means "an image * that beings N pixels after the start of region that this waveview is - * representing. + * representing. */ const framepos_t image_start = window_to_image (self.x0, draw_start); @@ -799,7 +871,7 @@ WaveView::render (Rect const & area, Cairo::RefPtr context) cons /* sample coordinates - note, these are not subject to rounding error */ framepos_t sample_start = _region_start + (image_start * _samples_per_pixel); framepos_t sample_end = _region_start + (image_end * _samples_per_pixel); - + // cerr << "Sample space: " << sample_start << " .. " << sample_end << endl; Cairo::RefPtr image; @@ -809,6 +881,16 @@ WaveView::render (Rect const & area, Cairo::RefPtr context) cons // cerr << "Offset into image to place at zero: " << image_offset << endl; + if (_start_shift && (sample_start == _region_start) && (self.x0 == draw.x0)) { + /* we are going to draw the first pixel for this region, but + we may not want this to overlap a border around the + waveform. If so, _start_shift will be set. + */ + //cerr << name.substr (23) << " ss = " << sample_start << " rs = " << _region_start << " sf = " << _start_shift << " ds = " << draw_start << " self = " << self << " draw = " << draw << endl; + //draw_start += _start_shift; + //image_offset += _start_shift; + } + context->rectangle (draw_start, draw.y0, draw_end - draw_start, draw.height()); /* round image origin position to an exact pixel in device space to @@ -835,10 +917,10 @@ WaveView::compute_bounding_box () const } else { _bounding_box = boost::optional (); } - + _bounding_box_dirty = false; } - + void WaveView::set_height (Distance height) { @@ -1007,7 +1089,19 @@ WaveView::set_global_show_waveform_clipping (bool yn) { if (_global_show_waveform_clipping != yn) { _global_show_waveform_clipping = yn; - VisualPropertiesChanged (); /* EMIT SIGNAL */ + ClipLevelChanged (); } } +void +WaveView::set_start_shift (double pixels) +{ + if (pixels < 0) { + return; + } + + begin_visual_change (); + _start_shift = pixels; + end_visual_change (); +} +