2 Copyright (C) 2003-2016 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
30 #include <gtkmm2ext/utils.h>
31 #include <gtkmm2ext/rgb_macros.h>
33 #include "canvas/canvas.h"
34 #include "canvas/utils.h"
35 #include "canvas/colors.h"
40 using namespace Gtkmm2ext;
42 using namespace ArdourCanvas;
43 using namespace ArdourSurface;
45 int Meter::min_pattern_metric_size = 16;
46 int Meter::max_pattern_metric_size = 1024;
47 bool Meter::no_rgba_overlay = false;
49 Meter::Pattern10Map Meter::vm_pattern_cache;
50 Meter::PatternBgMap Meter::vb_pattern_cache;
52 Meter::Pattern10Map Meter::hm_pattern_cache;
53 Meter::PatternBgMap Meter::hb_pattern_cache;
55 Meter::Meter (Item* parent, long hold, unsigned long dimen, Orientation o, int len,
56 int clr0, int clr1, int clr2, int clr3,
57 int clr4, int clr5, int clr6, int clr7,
61 float stp0, float stp1,
62 float stp2, float stp3,
68 , _styleflags(styleflags)
77 last_peak_rect.width = 0;
78 last_peak_rect.height = 0;
82 no_rgba_overlay = ! Glib::getenv("NO_METER_SHADE").empty();
113 if (orientation == Vertical) {
116 fgpattern = vertical_meter_pattern (pixwidth + 2, pixheight + 2, _clr, _stp, _styleflags);
117 bgpattern = vertical_background (pixwidth + 2, pixheight + 2, _bgc, false);
121 fgpattern = horizontal_meter_pattern (pixwidth + 2, pixheight + 2, _clr, _stp, _styleflags);
122 bgpattern = horizontal_background (pixwidth + 2, pixheight + 2, _bgc, false);
125 pixrect.width = pixwidth;
126 pixrect.height = pixheight;
130 Meter::compute_bounding_box () const
133 _bounding_box = boost::optional<Rect> ();
134 _bounding_box_dirty = false;
138 Rect r (0, 0, pixwidth + 2, pixheight + 2);
140 _bounding_box_dirty = false;
149 Meter::flush_pattern_cache () {
150 hb_pattern_cache.clear();
151 hm_pattern_cache.clear();
152 vb_pattern_cache.clear();
153 vm_pattern_cache.clear();
156 Cairo::RefPtr<Cairo::Pattern>
157 Meter::generate_meter_pattern (int width, int height, int *clr, float *stp, int styleflags, bool horiz)
161 const double soft = 3.0 / (double) height;
162 const double offs = -1.0 / (double) height;
164 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
167 Cairo coordinate space goes downwards as y value goes up, so invert
168 knee-based positions by using (1.0 - y)
171 UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
172 cairo_pattern_add_color_stop_rgb (pat, 0.0,
173 r/255.0, g/255.0, b/255.0);
175 knee = offs + stp[3] / 115.0f; // -0dB
177 UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
178 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
179 r/255.0, g/255.0, b/255.0);
181 UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
182 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
183 r/255.0, g/255.0, b/255.0);
185 knee = offs + stp[2]/ 115.0f; // -3dB || -2dB
187 UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
188 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
189 r/255.0, g/255.0, b/255.0);
191 UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
192 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
193 r/255.0, g/255.0, b/255.0);
195 knee = offs + stp[1] / 115.0f; // -9dB
197 UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
198 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
199 r/255.0, g/255.0, b/255.0);
201 UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
202 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
203 r/255.0, g/255.0, b/255.0);
205 knee = offs + stp[0] / 115.0f; // -18dB
207 UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
208 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
209 r/255.0, g/255.0, b/255.0);
211 UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
212 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
213 r/255.0, g/255.0, b/255.0);
215 UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
216 cairo_pattern_add_color_stop_rgb (pat, 1.0,
217 r/255.0, g/255.0, b/255.0);
219 if ((styleflags & 1) && !no_rgba_overlay) {
220 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
221 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 0.0, 0.0, 0.0, 0.15);
222 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.4, 1.0, 1.0, 1.0, 0.05);
223 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0.0, 0.0, 0.0, 0.25);
225 cairo_surface_t* surface;
227 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
228 tc = cairo_create (surface);
229 cairo_set_source (tc, pat);
230 cairo_rectangle (tc, 0, 0, width, height);
232 cairo_pattern_destroy (pat);
234 cairo_set_source (tc, shade_pattern);
235 cairo_rectangle (tc, 0, 0, width, height);
237 cairo_pattern_destroy (shade_pattern);
239 if (styleflags & 2) { // LED stripes
241 cairo_set_line_width(tc, 1.0);
242 cairo_set_source_rgba(tc, .0, .0, .0, 0.4);
243 //cairo_set_operator (tc, CAIRO_OPERATOR_SOURCE);
244 for (int i = 0; float y = 0.5 + i * 2.0; ++i) {
248 cairo_move_to(tc, 0, y);
249 cairo_line_to(tc, width, y);
255 pat = cairo_pattern_create_for_surface (surface);
257 cairo_surface_destroy (surface);
261 cairo_surface_t* surface;
263 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
264 tc = cairo_create (surface);
267 cairo_matrix_init_rotate (&m, -M_PI/2.0);
268 cairo_matrix_translate (&m, -height, 0);
269 cairo_pattern_set_matrix (pat, &m);
270 cairo_set_source (tc, pat);
271 cairo_rectangle (tc, 0, 0, height, width);
273 cairo_pattern_destroy (pat);
274 pat = cairo_pattern_create_for_surface (surface);
276 cairo_surface_destroy (surface);
278 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
284 Cairo::RefPtr<Cairo::Pattern>
285 Meter::generate_meter_background (int width, int height, int *clr, bool shade, bool horiz)
287 guint8 r0,g0,b0,r1,g1,b1,a;
289 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
291 UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
292 UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
294 cairo_pattern_add_color_stop_rgb (pat, 0.0,
295 r1/255.0, g1/255.0, b1/255.0);
297 cairo_pattern_add_color_stop_rgb (pat, 1.0,
298 r0/255.0, g0/255.0, b0/255.0);
300 if (shade && !no_rgba_overlay) {
301 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
302 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
303 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
304 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
306 cairo_surface_t* surface;
308 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
309 tc = cairo_create (surface);
310 cairo_set_source (tc, pat);
311 cairo_rectangle (tc, 0, 0, width, height);
313 cairo_set_source (tc, shade_pattern);
314 cairo_rectangle (tc, 0, 0, width, height);
317 cairo_pattern_destroy (pat);
318 cairo_pattern_destroy (shade_pattern);
320 pat = cairo_pattern_create_for_surface (surface);
323 cairo_surface_destroy (surface);
327 cairo_surface_t* surface;
329 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
330 tc = cairo_create (surface);
333 cairo_matrix_init_rotate (&m, -M_PI/2.0);
334 cairo_matrix_translate (&m, -height, 0);
335 cairo_pattern_set_matrix (pat, &m);
336 cairo_set_source (tc, pat);
337 cairo_rectangle (tc, 0, 0, height, width);
339 cairo_pattern_destroy (pat);
340 pat = cairo_pattern_create_for_surface (surface);
342 cairo_surface_destroy (surface);
345 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
350 Cairo::RefPtr<Cairo::Pattern>
351 Meter::vertical_meter_pattern (int width, int height, int *clr, float *stp, int styleflags)
353 height = max(height, min_pattern_metric_size);
354 height = min(height, max_pattern_metric_size);
356 const Pattern10MapKey key (width, height,
357 stp[0], stp[1], stp[2], stp[3],
358 clr[0], clr[1], clr[2], clr[3],
359 clr[4], clr[5], clr[6], clr[7],
360 clr[8], clr[9], styleflags);
362 Pattern10Map::iterator i;
363 if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
366 // TODO flush pattern cache if it gets too large
368 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (width, height, clr, stp, styleflags, false);
369 vm_pattern_cache[key] = p;
374 Cairo::RefPtr<Cairo::Pattern>
375 Meter::vertical_background (int width, int height, int *bgc, bool shade)
377 height = max(height, min_pattern_metric_size);
378 height = min(height, max_pattern_metric_size);
381 const PatternBgMapKey key (width, height, bgc[0], bgc[1], shade);
382 PatternBgMap::iterator i;
384 if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
387 // TODO flush pattern cache if it gets too large
389 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (width, height, bgc, shade, false);
390 vb_pattern_cache[key] = p;
395 Cairo::RefPtr<Cairo::Pattern>
396 Meter::horizontal_meter_pattern (int width, int height, int *clr, float *stp, int styleflags)
398 width = max(width, min_pattern_metric_size);
399 width = min(width, max_pattern_metric_size);
401 const Pattern10MapKey key (width, height,
402 stp[0], stp[1], stp[2], stp[3],
403 clr[0], clr[1], clr[2], clr[3],
404 clr[4], clr[5], clr[6], clr[7],
405 clr[8], clr[9], styleflags);
407 Pattern10Map::iterator i;
408 if ((i = hm_pattern_cache.find (key)) != hm_pattern_cache.end()) {
411 // TODO flush pattern cache if it gets too large
413 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (height, width, clr, stp, styleflags, true);
415 hm_pattern_cache[key] = p;
419 Cairo::RefPtr<Cairo::Pattern>
420 Meter::horizontal_background (int width, int height, int *bgc, bool shade)
422 width = max(width, min_pattern_metric_size);
423 width = min(width, max_pattern_metric_size);
426 const PatternBgMapKey key (width, height, bgc[0], bgc[1], shade);
427 PatternBgMap::iterator i;
428 if ((i = hb_pattern_cache.find (key)) != hb_pattern_cache.end()) {
431 // TODO flush pattern cache if it gets too large
433 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (height, width, bgc, shade, true);
435 hb_pattern_cache[key] = p;
441 Meter::set_hold_count (long val)
455 Meter::render (ArdourCanvas::Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
457 if (orientation == Vertical) {
458 return vertical_expose (area, context);
460 return horizontal_expose (area, context);
465 Meter::vertical_expose (ArdourCanvas::Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
468 Cairo::RectangleInt background;
469 Cairo::RectangleInt area_r;
473 area_r.width = area.width();
474 area_r.height = area.height();
476 context->set_source_rgb (0, 0, 0); // black
477 rounded_rectangle (context, 0, 0, pixwidth + 2, pixheight + 2, 2);
480 top_of_meter = (gint) floor (pixheight * current_level);
482 /* reset the height & origin of the rect that needs to show the pixbuf
485 pixrect.height = top_of_meter;
486 pixrect.y = 1 + pixheight - top_of_meter;
490 background.width = pixrect.width;
491 background.height = pixheight - top_of_meter;
493 /* translate so that item coordinates match window coordinates */
495 origin = item_to_window (origin);
496 context->translate (origin.x, origin.y);
498 Cairo::RefPtr<Cairo::Region> r1 = Cairo::Region::create (area_r);
499 r1->intersect (background);
502 Cairo::RectangleInt i (r1->get_extents ());
503 context->rectangle (i.x, i.y, i.width, i.height);
504 context->set_source (bgpattern);
508 Cairo::RefPtr<Cairo::Region> r2 = Cairo::Region::create (area_r);
509 r2->intersect (pixrect);
512 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
513 Cairo::RectangleInt i (r2->get_extents ());
514 context->rectangle (i.x, i.y, i.width, i.height);
515 context->set_source (fgpattern);
522 last_peak_rect.x = 1;
523 last_peak_rect.width = pixwidth;
524 last_peak_rect.y = max(1, 1 + pixheight - (int) floor (pixheight * current_peak));
525 if (_styleflags & 2) { // LED stripes
526 last_peak_rect.y = max(0, (last_peak_rect.y & (~1)));
528 if (bright_hold || (_styleflags & 2)) {
529 last_peak_rect.height = max(0, min(3, pixheight - last_peak_rect.y - 1 ));
531 last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y - 1 ));
534 context->set_source (fgpattern);
535 context->rectangle (last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
537 if (bright_hold && !no_rgba_overlay) {
538 context->fill_preserve ();
539 context->set_source_rgba (1.0, 1.0, 1.0, 0.3);
544 last_peak_rect.width = 0;
545 last_peak_rect.height = 0;
548 context->translate (-origin.x, -origin.y);
552 Meter::horizontal_expose (ArdourCanvas::Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
555 Cairo::RectangleInt background;
556 Cairo::RectangleInt area_r;
558 /* convert expose area back to item coordinate space */
559 Rect area2 = window_to_item (area);
562 /* create a Cairo object so that we can use intersect and Region */
565 area_r.width = area2.width();
566 area_r.height = area2.height();
568 /* draw the edge (rounded corners) */
569 context->set_source_rgb (0, 0, 0); // black
570 rounded_rectangle (context, 0, 0, pixwidth + 2, pixheight + 2, 2);
573 /* horizontal meter extends from left to right. Compute the right edge */
574 right_of_meter = (gint) floor (pixwidth * current_level);
576 /* reset the width the rect that needs to show the pattern of the meter */
577 pixrect.width = right_of_meter;
579 /* compute a rect for the part of the meter that is all background */
580 background.x = 1 + right_of_meter;
582 background.width = pixwidth - right_of_meter;
583 background.height = pixheight;
585 /* translate so that item coordinates match window coordinates */
587 origin = item_to_window (origin);
588 context->translate (origin.x, origin.y);
590 Cairo::RefPtr<Cairo::Region> r;
592 r = Cairo::Region::create (area_r);
593 r->intersect (background);
596 /* draw the background part */
597 Cairo::RectangleInt i (r->get_extents ());
598 context->set_source (bgpattern);
599 context->rectangle (i.x, i.y, i.width, i.height);
604 r = Cairo::Region::create (area_r);
605 r->intersect (pixrect);
608 // draw the part of the meter image that we need.
609 Cairo::RectangleInt i (r->get_extents ());
611 context->set_source (fgpattern);
612 context->rectangle (i.x, i.y, i.width, i.height);
619 last_peak_rect.y = 1;
620 last_peak_rect.height = pixheight;
621 const int xpos = floor (pixwidth * current_peak);
622 if (bright_hold || (_styleflags & 2)) {
623 last_peak_rect.width = min(3, xpos );
625 last_peak_rect.width = min(2, xpos );
627 last_peak_rect.x = 1 + max(0, xpos - last_peak_rect.width);
629 context->set_source (fgpattern);
630 context->rectangle (last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
632 if (bright_hold && !no_rgba_overlay) {
633 context->fill_preserve ();
634 context->set_source_rgba (1.0, 1.0, 1.0, 0.3);
639 last_peak_rect.width = 0;
640 last_peak_rect.height = 0;
643 context->translate (-origin.x, -origin.y);
647 Meter::set (float lvl, float peak)
649 float old_level = current_level;
650 float old_peak = current_peak;
652 if (pixwidth <= 0 || pixheight <=0) return;
655 if (lvl >= current_peak) {
657 hold_state = hold_cnt;
660 if (hold_state > 0) {
661 if (--hold_state == 0) {
674 const float pixscale = (orientation == Vertical) ? pixheight : pixwidth;
675 #define PIX(X) floor(pixscale * (X))
676 if (PIX(current_level) == PIX(old_level) && PIX(current_peak) == PIX(old_peak) && (hold_state == 0 || peak != -1)) {
680 if (orientation == Vertical) {
681 //queue_vertical_redraw (old_level);
683 //queue_horizontal_redraw (old_level);
690 Meter::queue_vertical_redraw (float old_level)
692 Cairo::RectangleInt rect;
694 gint new_top = (gint) floor (pixheight * current_level);
697 rect.width = pixwidth;
698 rect.height = new_top;
699 rect.y = 1 + pixheight - new_top;
701 if (current_level > old_level) {
702 /* colored/pixbuf got larger, just draw the new section */
703 /* rect.y stays where it is because of X coordinates */
704 /* height of invalidated area is between new.y (smaller) and old.y
706 X coordinates just make my brain hurt.
708 rect.height = pixrect.y - rect.y;
710 /* it got smaller, compute the difference */
711 /* rect.y becomes old.y (the smaller value) */
713 /* rect.height is the old.y (smaller) minus the new.y (larger)
715 rect.height = pixrect.height - rect.height;
718 Cairo::RefPtr<Cairo::Region> region;
721 if (rect.height != 0) {
723 /* ok, first region to draw ... */
725 region = Cairo::Region::create (rect);
729 /* redraw the last place where the last peak hold bar was;
730 the next expose will draw the new one whether its part of
731 expose region or not.
734 if (last_peak_rect.width * last_peak_rect.height != 0) {
736 region = Cairo::Region::create ();
739 region->do_union (last_peak_rect);
742 if (hold_state && current_peak > 0) {
744 region = Cairo::Region::create ();
748 rect.y = max(1, 1 + pixheight - (int) floor (pixheight * current_peak));
749 if (_styleflags & 2) { // LED stripes
750 rect.y = max(0, (rect.y & (~1)));
752 if (bright_hold || (_styleflags & 2)) {
753 rect.height = max(0, min(3, pixheight - last_peak_rect.y -1 ));
755 rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
757 rect.width = pixwidth;
758 region->do_union (rect);
762 if (visible() && _bounding_box && _canvas) {
763 Cairo::RectangleInt iri = region->get_extents();
764 Rect ir (iri.x, iri.y, iri.x + iri.width, iri.y + iri.height);
765 _canvas->request_redraw (item_to_window (ir));
771 Meter::queue_horizontal_redraw (float old_level)
773 Cairo::RectangleInt rect;
775 gint new_right = (gint) floor (pixwidth * current_level);
777 rect.height = pixheight;
780 if (current_level > old_level) {
781 rect.x = 1 + pixrect.width;
782 /* colored/pixbuf got larger, just draw the new section */
783 rect.width = new_right - pixrect.width;
785 /* it got smaller, compute the difference */
786 rect.x = 1 + new_right;
787 /* rect.height is the old.x (smaller) minus the new.x (larger) */
788 rect.width = pixrect.width - new_right;
791 Cairo::RefPtr<Cairo::Region> region;
794 if (rect.height != 0) {
796 /* ok, first region to draw ... */
798 region = Cairo::Region::create (rect);
802 /* redraw the last place where the last peak hold bar was;
803 the next expose will draw the new one whether its part of
804 expose region or not.
807 if (last_peak_rect.width * last_peak_rect.height != 0) {
809 region = Cairo::Region::create ();
812 region->do_union (last_peak_rect);
815 if (hold_state && current_peak > 0) {
817 region = Cairo::Region::create ();
821 rect.height = pixheight;
822 const int xpos = floor (pixwidth * current_peak);
823 if (bright_hold || (_styleflags & 2)) {
824 rect.width = min(3, xpos);
826 rect.width = min(2, xpos);
828 rect.x = 1 + max(0, xpos - rect.width);
829 region->do_union (rect);
833 if (visible() && _bounding_box && _canvas) {
834 Cairo::RectangleInt iri = region->get_extents();
835 Rect ir (iri.x, iri.y, iri.x + iri.width, iri.y + iri.height);
836 _canvas->request_redraw (item_to_window (ir));
842 Meter::set_highlight (bool onoff)
844 if (highlight == onoff) {
848 if (orientation == Vertical) {
849 bgpattern = vertical_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, highlight);
851 bgpattern = horizontal_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, highlight);