//
// Copyright (C) 2014 Tom Beckmann
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
using Clutter;
namespace Gala.Plugins.PIP {
public class ShadowEffect : Effect {
private class Shadow {
public int users;
public Cogl.Texture texture;
public Shadow (Cogl.Texture _texture) {
texture = _texture;
users = 1;
}
}
// the sizes of the textures often repeat, especially for the background actor
// so we keep a cache to avoid creating the same texture all over again.
static Gee.HashMap shadow_cache;
static construct {
shadow_cache = new Gee.HashMap ();
}
public int shadow_size { get; construct; }
public int shadow_spread { get; construct; }
public float scale_factor { get; set; default = 1; }
public uint8 shadow_opacity { get; set; default = 255; }
Cogl.Pipeline pipeline;
string? current_key = null;
public ShadowEffect (int shadow_size, int shadow_spread) {
Object (shadow_size: shadow_size, shadow_spread: shadow_spread);
}
construct {
pipeline = new Cogl.Pipeline (Clutter.get_default_backend ().get_cogl_context ());
}
~ShadowEffect () {
if (current_key != null)
decrement_shadow_users (current_key);
}
Cogl.Texture? get_shadow (Cogl.Context context, int width, int height, int shadow_size, int shadow_spread) {
var old_key = current_key;
current_key = "%ix%i:%i:%i".printf (width, height, shadow_size, shadow_spread);
if (old_key == current_key)
return null;
if (old_key != null)
decrement_shadow_users (old_key);
Shadow? shadow = null;
if ((shadow = shadow_cache.@get (current_key)) != null) {
shadow.users++;
return shadow.texture;
}
// fill a new texture for this size
var buffer = new Granite.Drawing.BufferSurface (width, height);
buffer.context.rectangle (shadow_size - shadow_spread, shadow_size - shadow_spread,
width - shadow_size * 2 + shadow_spread * 2, height - shadow_size * 2 + shadow_spread * 2);
buffer.context.set_source_rgba (0, 0, 0, 0.7);
buffer.context.fill ();
buffer.exponential_blur (shadow_size / 2);
var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, width, height);
var cr = new Cairo.Context (surface);
cr.set_source_surface (buffer.surface, 0, 0);
cr.paint ();
try {
var texture = new Cogl.Texture2D.from_data (context, width, height, Cogl.PixelFormat.BGRA_8888_PRE,
surface.get_stride (), surface.get_data ());
shadow_cache.@set (current_key, new Shadow (texture));
return texture;
} catch (GLib.Error e) {
debug (e.message);
return null;
}
}
void decrement_shadow_users (string key) {
var shadow = shadow_cache.@get (key);
if (shadow == null)
return;
if (--shadow.users == 0)
shadow_cache.unset (key);
}
#if HAS_MUTTER40
public override void paint (Clutter.PaintNode node, Clutter.PaintContext context, Clutter.EffectPaintFlags flags) {
#else
public override void paint (Clutter.PaintContext context, EffectPaintFlags flags) {
#endif
var bounding_box = get_bounding_box ();
var shadow = get_shadow (context.get_framebuffer ().get_context (), (int) (bounding_box.x2 - bounding_box.x1),
(int) (bounding_box.y2 - bounding_box.y1), shadow_size, shadow_spread);
if (shadow != null)
pipeline.set_layer_texture (0, shadow);
var opacity = actor.get_paint_opacity () * shadow_opacity / 255;
var alpha = Cogl.Color.from_4ub (255, 255, 255, opacity);
alpha.premultiply ();
pipeline.set_color (alpha);
context.get_framebuffer ().draw_rectangle (pipeline, bounding_box.x1, bounding_box.y1, bounding_box.x2, bounding_box.y2);
actor.continue_paint (context);
}
public virtual ActorBox get_bounding_box () {
var size = shadow_size * scale_factor;
var bounding_box = ActorBox ();
bounding_box.set_origin (-size, -size);
bounding_box.set_size (actor.width + size * 2, actor.height + size * 2);
return bounding_box;
}
}
}