2 Copyright (C) 2003-2006 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 <gdkmm/rectangle.h>
31 #include <gtkmm2ext/fastmeter.h>
32 #include <gtkmm2ext/utils.h>
34 #define UINT_TO_RGB(u,r,g,b) { (*(r)) = ((u)>>16)&0xff; (*(g)) = ((u)>>8)&0xff; (*(b)) = (u)&0xff; }
35 #define UINT_TO_RGBA(u,r,g,b,a) { UINT_TO_RGB(((u)>>8),r,g,b); (*(a)) = (u)&0xff; }
39 using namespace Gtkmm2ext;
42 int FastMeter::min_pattern_metric_size = 16;
43 int FastMeter::max_pattern_metric_size = 1024;
44 bool FastMeter::no_rgba_overlay = false;
46 FastMeter::Pattern10Map FastMeter::vm_pattern_cache;
47 FastMeter::PatternBgMap FastMeter::vb_pattern_cache;
49 FastMeter::Pattern10Map FastMeter::hm_pattern_cache;
50 FastMeter::PatternBgMap FastMeter::hb_pattern_cache;
52 FastMeter::FastMeter (long hold, unsigned long dimen, Orientation o, int len,
53 int clr0, int clr1, int clr2, int clr3,
54 int clr4, int clr5, int clr6, int clr7,
58 float stp0, float stp1,
59 float stp2, float stp3,
73 last_peak_rect.width = 0;
74 last_peak_rect.height = 0;
78 no_rgba_overlay = ! Glib::getenv("NO_METER_SHADE").empty();
102 set_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
110 if (orientation == Vertical) {
113 fgpattern = request_vertical_meter(pixwidth, pixheight, _clr, _stp, _styleflags);
114 bgpattern = request_vertical_background (pixwidth, pixheight, _bgc, false);
119 fgpattern = request_horizontal_meter(pixwidth, pixheight, _clr, _stp, _styleflags);
120 bgpattern = request_horizontal_background (pixwidth, pixheight, _bgc, false);
123 pixrect.width = pixwidth;
124 pixrect.height = pixheight;
126 request_width = pixrect.width;
127 request_height= pixrect.height;
132 FastMeter::~FastMeter ()
137 FastMeter::flush_pattern_cache ()
139 hb_pattern_cache.clear();
140 hm_pattern_cache.clear();
141 vb_pattern_cache.clear();
142 vm_pattern_cache.clear();
145 Cairo::RefPtr<Cairo::Pattern>
146 FastMeter::generate_meter_pattern (
147 int width, int height, int *clr, float *stp, int styleflags, bool horiz)
151 const double soft = 3.0 / (double) height;
152 const double offs = -1.0 / (double) height;
154 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
157 Cairo coordinate space goes downwards as y value goes up, so invert
158 knee-based positions by using (1.0 - y)
161 UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
162 cairo_pattern_add_color_stop_rgb (pat, 0.0,
163 r/255.0, g/255.0, b/255.0);
165 knee = offs + stp[3] / 115.0f; // -0dB
167 UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
168 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
169 r/255.0, g/255.0, b/255.0);
171 UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
172 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
173 r/255.0, g/255.0, b/255.0);
175 knee = offs + stp[2]/ 115.0f; // -3dB || -2dB
177 UINT_TO_RGBA (clr[6], &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[5], &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[1] / 115.0f; // -9dB
187 UINT_TO_RGBA (clr[4], &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[3], &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[0] / 115.0f; // -18dB
197 UINT_TO_RGBA (clr[2], &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[1], &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 UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
206 cairo_pattern_add_color_stop_rgb (pat, 1.0,
207 r/255.0, g/255.0, b/255.0);
209 if ((styleflags & 1) && !no_rgba_overlay) {
210 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
211 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 0.0, 0.0, 0.0, 0.15);
212 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.4, 1.0, 1.0, 1.0, 0.05);
213 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0.0, 0.0, 0.0, 0.25);
215 cairo_surface_t* surface;
217 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
218 tc = cairo_create (surface);
219 cairo_set_source (tc, pat);
220 cairo_rectangle (tc, 0, 0, width, height);
222 cairo_pattern_destroy (pat);
224 cairo_set_source (tc, shade_pattern);
225 cairo_rectangle (tc, 0, 0, width, height);
227 cairo_pattern_destroy (shade_pattern);
229 if (styleflags & 2) { // LED stripes
231 cairo_set_line_width(tc, 1.0);
232 cairo_set_source_rgba(tc, .0, .0, .0, 0.4);
233 //cairo_set_operator (tc, CAIRO_OPERATOR_SOURCE);
234 for (float y=0.5; y < height; y+= 2.0) {
235 cairo_move_to(tc, 0, y);
236 cairo_line_to(tc, width, y);
242 pat = cairo_pattern_create_for_surface (surface);
244 cairo_surface_destroy (surface);
248 cairo_surface_t* surface;
250 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
251 tc = cairo_create (surface);
254 cairo_matrix_init_rotate (&m, -M_PI/2.0);
255 cairo_matrix_translate (&m, -height, 0);
256 cairo_pattern_set_matrix (pat, &m);
257 cairo_set_source (tc, pat);
258 cairo_rectangle (tc, 0, 0, height, width);
260 cairo_pattern_destroy (pat);
261 pat = cairo_pattern_create_for_surface (surface);
263 cairo_surface_destroy (surface);
265 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
271 Cairo::RefPtr<Cairo::Pattern>
272 FastMeter::generate_meter_background (
273 int width, int height, int *clr, bool shade, bool horiz)
275 guint8 r0,g0,b0,r1,g1,b1,a;
277 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
279 UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
280 UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
282 cairo_pattern_add_color_stop_rgb (pat, 0.0,
283 r1/255.0, g1/255.0, b1/255.0);
285 cairo_pattern_add_color_stop_rgb (pat, 1.0,
286 r0/255.0, g0/255.0, b0/255.0);
288 if (shade && !no_rgba_overlay) {
289 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
290 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
291 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
292 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
294 cairo_surface_t* surface;
296 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
297 tc = cairo_create (surface);
298 cairo_set_source (tc, pat);
299 cairo_rectangle (tc, 0, 0, width, height);
301 cairo_set_source (tc, shade_pattern);
302 cairo_rectangle (tc, 0, 0, width, height);
305 cairo_pattern_destroy (pat);
306 cairo_pattern_destroy (shade_pattern);
308 pat = cairo_pattern_create_for_surface (surface);
311 cairo_surface_destroy (surface);
315 cairo_surface_t* surface;
317 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
318 tc = cairo_create (surface);
321 cairo_matrix_init_rotate (&m, -M_PI/2.0);
322 cairo_matrix_translate (&m, -height, 0);
323 cairo_pattern_set_matrix (pat, &m);
324 cairo_set_source (tc, pat);
325 cairo_rectangle (tc, 0, 0, height, width);
327 cairo_pattern_destroy (pat);
328 pat = cairo_pattern_create_for_surface (surface);
330 cairo_surface_destroy (surface);
333 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
338 Cairo::RefPtr<Cairo::Pattern>
339 FastMeter::request_vertical_meter(
340 int width, int height, int *clr, float *stp, int styleflags)
342 height = max(height, min_pattern_metric_size);
343 height = min(height, max_pattern_metric_size);
345 const Pattern10MapKey key (width, height,
346 stp[0], stp[1], stp[2], stp[3],
347 clr[0], clr[1], clr[2], clr[3],
348 clr[4], clr[5], clr[6], clr[7],
349 clr[8], clr[9], styleflags);
351 Pattern10Map::iterator i;
352 if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
355 // TODO flush pattern cache if it gets too large
357 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
358 width, height, clr, stp, styleflags, false);
359 vm_pattern_cache[key] = p;
364 Cairo::RefPtr<Cairo::Pattern>
365 FastMeter::request_vertical_background(
366 int width, int height, int *bgc, bool /*shade */)
368 height = max(height, min_pattern_metric_size);
369 height = min(height, max_pattern_metric_size);
372 const PatternBgMapKey key (width, height, bgc[0], bgc[1], false);
373 PatternBgMap::iterator i;
374 if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
377 // TODO flush pattern cache if it gets too large
379 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
380 width, height, bgc, false, false);
381 vb_pattern_cache[key] = p;
386 Cairo::RefPtr<Cairo::Pattern>
387 FastMeter::request_horizontal_meter(
388 int width, int height, int *clr, float *stp, int styleflags)
390 width = max(width, min_pattern_metric_size);
391 width = min(width, max_pattern_metric_size);
393 const Pattern10MapKey key (width, height,
394 stp[0], stp[1], stp[2], stp[3],
395 clr[0], clr[1], clr[2], clr[3],
396 clr[4], clr[5], clr[6], clr[7],
397 clr[8], clr[9], styleflags);
399 Pattern10Map::iterator i;
400 if ((i = hm_pattern_cache.find (key)) != hm_pattern_cache.end()) {
403 // TODO flush pattern cache if it gets too large
405 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
406 height, width, clr, stp, styleflags, true);
408 hm_pattern_cache[key] = p;
412 Cairo::RefPtr<Cairo::Pattern>
413 FastMeter::request_horizontal_background(
414 int width, int height, int *bgc, bool /* shade */)
416 width = max(width, min_pattern_metric_size);
417 width = min(width, max_pattern_metric_size);
420 const PatternBgMapKey key (width, height, bgc[0], bgc[1], false);
421 PatternBgMap::iterator i;
422 if ((i = hb_pattern_cache.find (key)) != hb_pattern_cache.end()) {
425 // TODO flush pattern cache if it gets too large
427 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
428 height, width, bgc, false, true);
430 hb_pattern_cache[key] = p;
438 FastMeter::set_hold_count (long val)
452 FastMeter::on_size_request (GtkRequisition* req)
454 if (orientation == Vertical) {
455 vertical_size_request (req);
457 horizontal_size_request (req);
462 FastMeter::vertical_size_request (GtkRequisition* req)
464 req->height = request_height;
465 req->height = max(req->height, min_pattern_metric_size);
466 req->height = min(req->height, max_pattern_metric_size);
469 req->width = request_width;
473 FastMeter::horizontal_size_request (GtkRequisition* req)
475 req->width = request_width;
476 req->width = max(req->width, min_pattern_metric_size);
477 req->width = min(req->width, max_pattern_metric_size);
480 req->height = request_height;
484 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
486 if (orientation == Vertical) {
487 vertical_size_allocate (alloc);
489 horizontal_size_allocate (alloc);
495 FastMeter::vertical_size_allocate (Gtk::Allocation &alloc)
497 if (alloc.get_width() != request_width) {
498 alloc.set_width (request_width);
501 int h = alloc.get_height();
502 h = max (h, min_pattern_metric_size + 2);
503 h = min (h, max_pattern_metric_size + 2);
505 if (h != alloc.get_height()) {
506 alloc.set_height (h);
509 if (pixheight != h) {
510 fgpattern = request_vertical_meter (request_width, h, _clr, _stp, _styleflags);
511 bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, false);
513 pixwidth = request_width;
516 CairoWidget::on_size_allocate (alloc);
520 FastMeter::horizontal_size_allocate (Gtk::Allocation &alloc)
522 if (alloc.get_height() != request_height) {
523 alloc.set_height (request_height);
526 int w = alloc.get_width();
527 w = max (w, min_pattern_metric_size + 2);
528 w = min (w, max_pattern_metric_size + 2);
530 if (w != alloc.get_width()) {
535 fgpattern = request_horizontal_meter (w, request_height, _clr, _stp, _styleflags);
536 bgpattern = request_horizontal_background (w, request_height, highlight ? _bgh : _bgc, false);
538 pixheight = request_height;
541 CairoWidget::on_size_allocate (alloc);
545 FastMeter::render (cairo_t* cr, cairo_rectangle_t* area)
547 if (orientation == Vertical) {
548 return vertical_expose (cr, area);
550 return horizontal_expose (cr, area);
555 FastMeter::vertical_expose (cairo_t* cr, cairo_rectangle_t* area)
558 // GdkRectangle background;
559 // GdkRectangle eventarea;
561 //cairo_set_source_rgb (cr, 0, 0, 0); // black
562 //rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
565 top_of_meter = (gint) floor (pixheight * current_level);
567 /* reset the height & origin of the rect that needs to show the pixbuf
570 pixrect.height = top_of_meter;
571 pixrect.y = pixheight - top_of_meter;
575 // background.width = pixrect.width;
576 // background.height = pixheight - top_of_meter;
578 // eventarea.x = area->x;
579 // eventarea.y = area->y;
580 // eventarea.width = area->width;
581 // eventarea.height = area->height;
583 // Switching to CAIRO we would like to draw on the container's bkg.
584 // if (gdk_rectangle_intersect (&background, &eventarea, &intersection)) {
585 // cairo_set_source (cr, bgpattern->cobj());
586 // cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
590 // MEMO: Normaly MATURE OS clips so called invalidated rects itself making APP free of
591 // heavy operations which OS does with graphic HW
593 // NOTE FROM PAUL: GTK does clip already. The invalidated rect isn't the only area
594 // we want to clip to however, which is why this object/class is called FastMeter.
595 // I have left stuff commented out as I found it when I merged from Ardour in August 2014,
596 // but this commenting and the previous MEMO comment represent a misunderstanding
597 // of what this code is doing.
599 // if (gdk_rectangle_intersect (&pixrect, &eventarea, &intersection)) {
600 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
601 //cairo_set_source (cr, fgpattern->cobj());
602 cairo_set_source_rgba (cr, 0.69, 0.69, 0.69, 1);
603 cairo_rectangle (cr, pixrect.x, pixrect.y, pixrect.width, pixrect.height);
610 last_peak_rect.x = 0;
611 last_peak_rect.width = pixwidth;
612 last_peak_rect.y = max(0, pixheight - (gint) floor (pixheight * current_peak));
613 if (bright_hold || (_styleflags & 2)) {
614 last_peak_rect.height = max(0, min(3, pixheight - last_peak_rect.y ));
616 last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y ));
619 cairo_set_source (cr, fgpattern->cobj());
620 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
622 if (bright_hold && !no_rgba_overlay) {
623 cairo_fill_preserve (cr);
624 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
629 last_peak_rect.width = 0;
630 last_peak_rect.height = 0;
635 FastMeter::horizontal_expose (cairo_t* cr, cairo_rectangle_t* area)
639 //cairo_set_source_rgb (cr, 0, 0, 0); // black
640 //rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
643 right_of_meter = (gint) floor (pixwidth * current_level);
645 /* reset the height & origin of the rect that needs to show the pixbuf
648 pixrect.width = right_of_meter;
653 last_peak_rect.y = 1;
654 last_peak_rect.height = pixheight;
655 const int xpos = floor (pixwidth * current_peak);
656 if (bright_hold || (_styleflags & 2)) {
657 last_peak_rect.width = min(3, xpos );
659 last_peak_rect.width = min(2, xpos );
661 last_peak_rect.x = 1 + max(0, xpos - last_peak_rect.width);
663 cairo_set_source (cr, fgpattern->cobj());
664 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
666 if (bright_hold && !no_rgba_overlay) {
667 cairo_fill_preserve (cr);
668 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
673 last_peak_rect.width = 0;
674 last_peak_rect.height = 0;
679 FastMeter::set (float lvl, float peak)
681 float old_level = current_level;
682 float old_peak = current_peak;
684 if (pixwidth <= 0 || pixheight <=0) return;
687 if (lvl >= current_peak) {
689 hold_state = hold_cnt;
692 if (hold_state > 0) {
693 if (--hold_state == 0) {
706 if (current_level == old_level && current_peak == old_peak && (hold_state == 0 || peak != -1)) {
710 Glib::RefPtr<Gdk::Window> win;
712 if ((win = get_window()) == 0) {
717 if (orientation == Vertical) {
718 queue_vertical_redraw (win, old_level);
720 queue_horizontal_redraw (win, old_level);
725 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
729 gint new_top = (gint) floor (pixheight * current_level);
732 rect.width = pixwidth;
733 rect.height = new_top;
734 rect.y = pixheight - new_top;
736 if (current_level > old_level) {
737 /* colored/pixbuf got larger, just draw the new section */
738 /* rect.y stays where it is because of X coordinates */
739 /* height of invalidated area is between new.y (smaller) and old.y
741 X coordinates just make my brain hurt.
743 rect.height = pixrect.y - rect.y;
745 /* it got smaller, compute the difference */
746 /* rect.y becomes old.y (the smaller value) */
748 /* rect.height is the old.y (smaller) minus the new.y (larger)
750 rect.height = pixrect.height - rect.height;
753 GdkRegion* region = 0;
756 if (rect.height != 0) {
758 /* ok, first region to draw ... */
760 region = gdk_region_rectangle (&rect);
764 /* redraw the last place where the last peak hold bar was;
765 the next expose will draw the new one whether its part of
766 expose region or not.
769 if (last_peak_rect.width * last_peak_rect.height != 0) {
771 region = gdk_region_new ();
774 gdk_region_union_with_rect (region, &last_peak_rect);
777 if (hold_state && current_peak > 0) {
779 region = gdk_region_new ();
783 rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
784 if (bright_hold || (_styleflags & 2)) {
785 rect.height = max(0, min(3, pixheight - last_peak_rect.y -1 ));
787 rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
789 rect.width = pixwidth;
790 gdk_region_union_with_rect (region, &rect);
794 gdk_window_invalidate_region (win->gobj(), region, true);
797 gdk_region_destroy(region);
803 FastMeter::queue_horizontal_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
807 gint new_right = (gint) floor (pixwidth * current_level);
809 rect.height = pixheight;
812 if (current_level > old_level) {
813 rect.x = 1 + pixrect.width;
814 /* colored/pixbuf got larger, just draw the new section */
815 rect.width = new_right - pixrect.width;
817 /* it got smaller, compute the difference */
818 rect.x = 1 + new_right;
819 /* rect.height is the old.x (smaller) minus the new.x (larger) */
820 rect.width = pixrect.width - new_right;
823 GdkRegion* region = 0;
826 if (rect.height != 0) {
828 /* ok, first region to draw ... */
830 region = gdk_region_rectangle (&rect);
834 /* redraw the last place where the last peak hold bar was;
835 the next expose will draw the new one whether its part of
836 expose region or not.
839 if (last_peak_rect.width * last_peak_rect.height != 0) {
841 region = gdk_region_new ();
844 gdk_region_union_with_rect (region, &last_peak_rect);
847 if (hold_state && current_peak > 0) {
849 region = gdk_region_new ();
853 rect.height = pixheight;
854 const int xpos = floor (pixwidth * current_peak);
855 if (bright_hold || (_styleflags & 2)) {
856 rect.width = min(3, xpos);
858 rect.width = min(2, xpos);
860 rect.x = 1 + max(0, xpos - rect.width);
861 gdk_region_union_with_rect (region, &rect);
865 gdk_window_invalidate_region (win->gobj(), region, true);
868 gdk_region_destroy(region);
874 FastMeter::set_highlight (bool onoff)
876 if (highlight == onoff) {
880 if (orientation == Vertical) {
881 bgpattern = request_vertical_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, false);
883 bgpattern = request_horizontal_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, false);