From a1f858d3b207739e0719c1fc28003f1d9dd3965d Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Mon, 24 Jun 2013 16:28:53 -0400 Subject: [PATCH] an awful lot of tweaks to drawing details --- gtk2_ardour/audio_region_view.cc | 5 +- gtk2_ardour/editor_cursors.cc | 10 ++-- gtk2_ardour/tempo_lines.cc | 8 ++- gtk2_ardour/theme_manager.cc | 4 +- gtk2_ardour/time_axis_view_item.cc | 58 ++++++++++----------- gtk2_ardour/time_axis_view_item.h | 2 + libs/canvas/canvas.cc | 1 + libs/canvas/canvas/types.h | 9 ++++ libs/canvas/line.cc | 9 +++- libs/canvas/rectangle.cc | 83 ++++++++++-------------------- libs/canvas/types.cc | 36 +++++++++++++ libs/canvas/wave_view.cc | 74 ++++++++++++++------------ 12 files changed, 169 insertions(+), 130 deletions(-) diff --git a/gtk2_ardour/audio_region_view.cc b/gtk2_ardour/audio_region_view.cc index 47aebb765f..077755ff83 100644 --- a/gtk2_ardour/audio_region_view.cc +++ b/gtk2_ardour/audio_region_view.cc @@ -1208,14 +1208,13 @@ AudioRegionView::create_one_wave (uint32_t which, bool /*direct*/) v = min (1.0, v * 3.0); c = ArdourCanvas::hsv_to_color (h, s, v, _region->muted() ? MUTED_ALPHA : 1.0); - + wave->set_fill_color (c); } wave->set_clip_color (ARDOUR_UI::config()->get_canvasvar_WaveFormClip()); wave->set_zero_color (ARDOUR_UI::config()->get_canvasvar_ZeroLine()); - // CAIROCANVAS - // wave->property_zero_line() = true; + wave->set_show_zero_line (true); switch (Config->get_waveform_shape()) { case Rectified: diff --git a/gtk2_ardour/editor_cursors.cc b/gtk2_ardour/editor_cursors.cc index 9d8c255d2f..557120c1db 100644 --- a/gtk2_ardour/editor_cursors.cc +++ b/gtk2_ardour/editor_cursors.cc @@ -45,11 +45,11 @@ EditorCursor::EditorCursor (Editor& ed, bool (Editor::*callbck)(GdkEvent*,Ardour _time_bars_canvas_item.set_head_width (0, 16); _time_bars_canvas_item.set_head_outward (0, false); _time_bars_canvas_item.set_show_head (1, false); // head only - _time_bars_canvas_item.set_outline_width (1.5); + _time_bars_canvas_item.set_outline_width (0.5); _time_bars_canvas_item.set_data ("cursor", this); _track_canvas_item.set_data ("cursor", this); - _track_canvas_item.set_outline_width (1.5); + _track_canvas_item.set_outline_width (0.5); _time_bars_canvas_item.Event.connect (sigc::bind (sigc::mem_fun (ed, callbck), &_time_bars_canvas_item)); _track_canvas_item.Event.connect (sigc::bind (sigc::mem_fun (ed, callbck), &_track_canvas_item)); @@ -70,7 +70,11 @@ EditorCursor::set_position (framepos_t frame) { PositionChanged (frame); - double const new_pos = _editor.sample_to_pixel (frame); + /* See Cairo FAQ question on single pixel lines to understand + why we add 0.5 + */ + + double const new_pos = _editor.sample_to_pixel (frame) + 0.5; if (new_pos != _time_bars_canvas_item.x ()) { _time_bars_canvas_item.set_x (new_pos); diff --git a/gtk2_ardour/tempo_lines.cc b/gtk2_ardour/tempo_lines.cc index d73fb54b81..6bf7dfbde9 100644 --- a/gtk2_ardour/tempo_lines.cc +++ b/gtk2_ardour/tempo_lines.cc @@ -111,8 +111,12 @@ TempoLines::draw (const ARDOUR::TempoMap::BBTPointList::const_iterator& begin, line->set_ignore_events (true); } - line->set_x0 (xpos); - line->set_x1 (xpos); + /* move to 0.5 offset to ensure single pixel lines (see Cairo + * FAQ for info on why we do this). + */ + + line->set_x0 (xpos + 0.5); + line->set_x1 (xpos + 0.5); line->set_y0 (0.0); line->set_y1 (_height); line->set_outline_color (color); diff --git a/gtk2_ardour/theme_manager.cc b/gtk2_ardour/theme_manager.cc index 1fabfacb7e..947d92bddf 100644 --- a/gtk2_ardour/theme_manager.cc +++ b/gtk2_ardour/theme_manager.cc @@ -59,9 +59,9 @@ ThemeManager::ThemeManager() , light_button (_("Light Theme")) , reset_button (_("Restore Defaults")) , flat_buttons (_("Draw \"flat\" buttons")) - , waveform_gradient_depth (0, 1.0, 0.1) + , waveform_gradient_depth (0, 1.0, 0.05) , waveform_gradient_depth_label (_("Waveforms color gradient depth")) - , timeline_item_gradient_depth (0, 2.0, 0.1) + , timeline_item_gradient_depth (0, 1.0, 0.05) , timeline_item_gradient_depth_label (_("Timeline item gradient depth")) , all_dialogs (_("All floating windows are dialogs")) { diff --git a/gtk2_ardour/time_axis_view_item.cc b/gtk2_ardour/time_axis_view_item.cc index ebda7312d4..d8d3cee807 100644 --- a/gtk2_ardour/time_axis_view_item.cc +++ b/gtk2_ardour/time_axis_view_item.cc @@ -731,30 +731,15 @@ TimeAxisViewItem::set_colors() set_trim_handle_colors(); } -/** - * Sets the frame color depending on whether this item is selected - */ -void -TimeAxisViewItem::set_frame_color() +uint32_t +TimeAxisViewItem::get_fill_color () const { uint32_t f = 0; - if (!frame) { - return; - } - if (_selected) { f = ARDOUR_UI::config()->get_canvasvar_SelectedFrameBase(); - if (fill_opacity) { - f = UINT_RGBA_CHANGE_A (f, fill_opacity); - } - - if (!rect_visible) { - f = UINT_RGBA_CHANGE_A (f, 0); - } - } else { if (_recregion) { @@ -766,15 +751,32 @@ TimeAxisViewItem::set_frame_color() } else { f = fill_color; } + } + } - if (fill_opacity) { - f = UINT_RGBA_CHANGE_A (f, fill_opacity); - } + return f; +} - if (!rect_visible) { - f = UINT_RGBA_CHANGE_A (f, 0); - } - } +/** + * Sets the frame color depending on whether this item is selected + */ +void +TimeAxisViewItem::set_frame_color() +{ + uint32_t f = 0; + + if (!frame) { + return; + } + + f = get_fill_color (); + + if (fill_opacity) { + f = UINT_RGBA_CHANGE_A (f, fill_opacity); + } + + if (!rect_visible) { + f = UINT_RGBA_CHANGE_A (f, 0); } frame->set_fill_color (f); @@ -806,7 +808,7 @@ TimeAxisViewItem::set_frame_gradient () ArdourCanvas::Fill::StopList stops; double r, g, b, a; double h, s, v; - ArdourCanvas::Color f (frame->fill_color()); + ArdourCanvas::Color f (get_fill_color()); /* need to get alpha value */ ArdourCanvas::color_to_rgba (f, r, g, b, a); @@ -816,10 +818,8 @@ TimeAxisViewItem::set_frame_gradient () /* now a darker version */ ArdourCanvas::color_to_hsv (f, h, s, v); - s *= ARDOUR_UI::config()->get_timeline_item_gradient_depth(); - if (s > 1.0) { - s = 1.0; - } + + v = min (1.0, v * (1.0 - ARDOUR_UI::config()->get_timeline_item_gradient_depth())); ArdourCanvas::Color darker = ArdourCanvas::hsv_to_color (h, s, v, a); stops.push_back (std::make_pair (1.0, darker)); diff --git a/gtk2_ardour/time_axis_view_item.h b/gtk2_ardour/time_axis_view_item.h index fc9ab4106e..054d496013 100644 --- a/gtk2_ardour/time_axis_view_item.h +++ b/gtk2_ardour/time_axis_view_item.h @@ -74,6 +74,8 @@ class TimeAxisViewItem : public Selectable, public PBD::ScopedConnectionList void set_y (double); void set_color (Gdk::Color const &); + uint32_t get_fill_color () const; + ArdourCanvas::Item* get_canvas_frame(); ArdourCanvas::Group* get_canvas_group(); ArdourCanvas::Item* get_name_highlight(); diff --git a/libs/canvas/canvas.cc b/libs/canvas/canvas.cc index 04929ca1af..dab8ce6f68 100644 --- a/libs/canvas/canvas.cc +++ b/libs/canvas/canvas.cc @@ -80,6 +80,7 @@ Canvas::render (Rect const & area, Cairo::RefPtr const & context /* there's a common area between the root and the requested area, so render it. */ + _root.render (*draw, context); } } diff --git a/libs/canvas/canvas/types.h b/libs/canvas/canvas/types.h index 37c0c213ea..de98ddb18f 100644 --- a/libs/canvas/canvas/types.h +++ b/libs/canvas/canvas/types.h @@ -25,6 +25,12 @@ #include #include +#include + +namespace Cairo { + struct Context; +} + namespace ArdourCanvas { @@ -89,6 +95,9 @@ struct Rect bool contains (Duple) const; Rect fix () const; + Rect convert_to_device (Cairo::RefPtr) const; + Rect convert_to_user (Cairo::RefPtr) const; + Distance width () const { return x1 - x0; } diff --git a/libs/canvas/line.cc b/libs/canvas/line.cc index b6a802b8df..af2a0e47db 100644 --- a/libs/canvas/line.cc +++ b/libs/canvas/line.cc @@ -56,10 +56,15 @@ void Line::render (Rect const & /*area*/, Cairo::RefPtr context) const { setup_outline_context (context); + Duple p0 = item_to_window (Duple (_points[0].x, _points[0].y)); Duple p1 = item_to_window (Duple (_points[1].x, _points[1].y)); - context->move_to (p0.x, p0.y); - context->line_to (p1.x, p1.y); + + /* See Cairo FAQ on single pixel lines to understand why we add 0.5 + */ + + context->move_to (p0.x + 0.5, p0.y + 0.5); + context->line_to (p1.x + 0.5, p1.y + 0.5); context->stroke (); } diff --git a/libs/canvas/rectangle.cc b/libs/canvas/rectangle.cc index 6ce62b0144..9512b69417 100644 --- a/libs/canvas/rectangle.cc +++ b/libs/canvas/rectangle.cc @@ -68,69 +68,40 @@ Rectangle::render (Rect const & area, Cairo::RefPtr context) con draw.y0 = max (self.y0, max (0.0, draw.y0 - boundary)); draw.y1 = min (self.y1, min (2000.0, draw.y1 + boundary)); + Rect fill_rect = draw; + Rect stroke_rect = fill_rect.expand (0.5); + if (_fill) { setup_fill_context (context); - - context->rectangle (draw.x0, draw.y0, draw.width(), draw.height()); - - if (!_outline) { - context->fill (); - } else { - - /* special/common case: outline the entire rectangle is - * requested, so just use the same path for the fill - * and stroke. - */ - - if (_outline_what == What (LEFT|RIGHT|BOTTOM|TOP)) { - context->fill_preserve(); - setup_outline_context (context); - context->stroke (); - } else { - context->fill (); - } - } - } + context->rectangle (fill_rect.x0, fill_rect.y0, fill_rect.width(), fill_rect.height()); + context->fill (); + } if (_outline) { - + setup_outline_context (context); - if (_outline_what == What (LEFT|RIGHT|BOTTOM|TOP)) { - - /* if we filled and use full outline, we are already - * done. otherwise, draw the frame here. - */ - - if (!_fill) { - context->rectangle (draw.x0, draw.y0, draw.width(), draw.height()); - context->stroke (); - } - - } else { - - if (_outline_what & LEFT) { - context->move_to (draw.x0, draw.y0); - context->line_to (draw.x0, draw.y1); - } - - if (_outline_what & BOTTOM) { - context->move_to (draw.x0, draw.y1); - context->line_to (draw.x1, draw.y1); - } - - if (_outline_what & RIGHT) { - context->move_to (draw.x1, draw.y0); - context->line_to (draw.x1, draw.y1); - } - - if (_outline_what & TOP) { - context->move_to (draw.x0, draw.y0); - context->line_to (draw.x1, draw.y0); - } - - context->stroke (); + if (_outline_what & LEFT) { + context->move_to (stroke_rect.x0, stroke_rect.y0); + context->line_to (stroke_rect.x0, stroke_rect.y1); } + + if (_outline_what & BOTTOM) { + context->move_to (stroke_rect.x0, stroke_rect.y1); + context->line_to (stroke_rect.x1, stroke_rect.y1); + } + + if (_outline_what & RIGHT) { + context->move_to (stroke_rect.x1, stroke_rect.y0); + context->line_to (stroke_rect.x1, stroke_rect.y1); + } + + if (_outline_what & TOP) { + context->move_to (stroke_rect.x0, stroke_rect.y0); + context->line_to (stroke_rect.x1, stroke_rect.y0); + } + + context->stroke (); } } diff --git a/libs/canvas/types.cc b/libs/canvas/types.cc index dfd934b126..a8c690bbbe 100644 --- a/libs/canvas/types.cc +++ b/libs/canvas/types.cc @@ -20,6 +20,9 @@ #include #include #include + +#include + #include "canvas/types.h" using namespace std; @@ -118,6 +121,39 @@ Rect::fix () const return r; } +Rect +Rect::convert_to_device (Cairo::RefPtr c) const +{ + Coord xa, ya, xb, yb; + + xa = x0; + xb = x1; + ya = y0; + yb = y1; + + c->user_to_device (xa, ya); + c->user_to_device (xb, yb); + + return Rect (xa, ya, xb, yb); +} + + +Rect +Rect::convert_to_user (Cairo::RefPtr c) const +{ + Coord xa, ya, xb, yb; + + xa = x0; + xb = x1; + ya = y0; + yb = y1; + + c->device_to_user (xa, ya); + c->device_to_user (xb, yb); + + return Rect (xa, ya, xb, yb); +} + Duple ArdourCanvas::operator- (Duple const & o) { diff --git a/libs/canvas/wave_view.cc b/libs/canvas/wave_view.cc index 8a8b528b6f..2118838a03 100644 --- a/libs/canvas/wave_view.cc +++ b/libs/canvas/wave_view.cc @@ -178,6 +178,7 @@ WaveView::ensure_cache (framecnt_t start, framecnt_t end, sample_start = max ((framepos_t) 0, (center - canvas_samples)); sample_end = min (center + canvas_samples, _region->source_length (0)); +#if 0 if (sample_end <= sample_start) { cerr << "sample start = " << sample_start << endl; cerr << "center+ = " << center<< endl; @@ -188,13 +189,15 @@ WaveView::ensure_cache (framecnt_t start, framecnt_t end, cerr << "END: " << sample_end << endl; assert (false); } +#endif start = floor (sample_start / (double) _samples_per_pixel); end = ceil (sample_end / (double) _samples_per_pixel); assert (end > start); - cerr << name << " cache miss - new CE, span " << start << " .. " << end << " (" << sample_start << " .. " << sample_end << ")\n"; + // cerr << name << " cache miss - new CE, span " << start << " .. " << end << " (" << sample_start << " .. " << sample_end << ")\n"; + _cache = new CacheEntry (this, start, end, sample_start, sample_end); } @@ -254,7 +257,19 @@ WaveView::render (Rect const & area, Cairo::RefPtr context) cons // cerr << "Offset into image to place at zero: " << image_offset << endl; context->rectangle (draw_start, draw.y0, draw_end - draw_start, draw.height()); - context->set_source (_cache->image(), self.x0 + image_offset, self.y0); + + /* round image origin position to an exact pixel in device space to + * avoid blurring + */ + + double x = self.x0 + image_offset; + double y = self.y0; + context->user_to_device (x, y); + x = round (x); + y = round (y); + context->device_to_user (x, y); + + context->set_source (_cache->image(), x, y); context->fill (); } @@ -408,18 +423,6 @@ WaveView::region_resized () _pre_change_bounding_box = _bounding_box; - frameoffset_t s = _region->start(); - - if (s != _region_start) { - /* if the region start changes, the information we have - in the image cache is out of date and not useful - since it will fragmented into little pieces. invalidate - the cache. - */ - _region_start = _region->start(); - invalidate_whole_cache (); - } - _bounding_box_dirty = true; compute_bounding_box (); @@ -602,9 +605,6 @@ WaveView::CacheEntry::image () context->stroke (); #else - cerr << "draw, logscaled = " << _wave_view->_logscaled << " global " << WaveView::_global_logscaled << endl; - cerr << "gradient depth: " << _wave_view->gradient_depth() << endl; - boost::scoped_array tips (new LineTips[_n_peaks]); if (_wave_view->_shape == WaveView::Rectified) { @@ -656,8 +656,8 @@ WaveView::CacheEntry::image () } else { for (int i = 0; i < _n_peaks; ++i) { - tips[i].top = floor (position (_peaks[i].min)); - tips[i].bot = ceil (position (_peaks[i].max)); + tips[i].top = position (_peaks[i].min); + tips[i].bot = position (_peaks[i].max); } } } @@ -687,7 +687,7 @@ WaveView::CacheEntry::image () /* generate a new color for the middle of the gradient */ double h, s, v; color_to_hsv (_wave_view->_fill_color, h, s, v); - /* tone down the saturation */ + /* change v towards white */ v *= 1.0 - _wave_view->gradient_depth(); Color center = hsv_to_color (h, s, v, a); color_to_rgba (center, r, g, b, a); @@ -695,24 +695,26 @@ WaveView::CacheEntry::image () context->set_source (gradient); } else { - cerr << "\tno gradient\n"; set_source_rgba (context, _wave_view->_fill_color); } + /* ensure single-pixel lines */ + context->set_line_width (0.5); + context->translate (0.5, 0.0); /* draw the lines */ - + if (_wave_view->_shape == WaveView::Rectified) { for (int i = 0; i < _n_peaks; ++i) { - context->move_to (i + 0.5, tips[i].top + 0.5); /* down 1 pixel */ - context->line_to (i + 0.5, tips[i].bot); + context->move_to (i, tips[i].top); /* down 1 pixel */ + context->line_to (i, tips[i].bot); context->stroke (); } } else { for (int i = 0; i < _n_peaks; ++i) { - context->move_to (i + 0.5, tips[i].top + 0.5); /* down 1 pixel */ - context->line_to (i + 0.5, tips[i].bot - 0.5); /* up 1 pixel */ + context->move_to (i, tips[i].top); + context->line_to (i, tips[i].bot); context->stroke (); } } @@ -721,14 +723,16 @@ WaveView::CacheEntry::image () * modelled on pyramix, except that we add clipping indicators. */ - context->set_source_rgb (0, 0, 0); + context->set_source_rgba (0, 0, 0, 1.0); for (int i = 0; i < _n_peaks; ++i) { - context->rectangle (i + 0.5, tips[i].top, 0.5, 0.5); - context->fill (); + context->move_to (i, tips[i].top); + context->rel_line_to (0, 1.0); + context->stroke (); if (_wave_view->_shape != WaveView::Rectified) { - context->rectangle (i + 0.5, tips[i].bot, 0.5, 0.5); - context->fill (); + context->move_to (i, tips[i].bot); + context->rel_line_to (0, -1.0); + context->stroke (); } } #endif @@ -749,13 +753,17 @@ WaveView::CacheEntry::image () Coord WaveView::CacheEntry::position (double s) const { + /* it is important that this returns an integral value, so that we + can ensure correct single pixel behaviour. + */ + switch (_wave_view->_shape) { case Rectified: - return _wave_view->_height - (s * _wave_view->_height); + return floor (_wave_view->_height - (s * _wave_view->_height)); default: break; } - return (1.0-s) * (_wave_view->_height / 2.0); + return floor ((1.0-s) * (_wave_view->_height / 2.0)); } void -- 2.30.2