├── .gitignore ├── README.md ├── build.sh ├── c-filters.c ├── init.lua └── types └── c-filters.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Surface filters for AwesomeWM 2 | 3 | ## Description 4 | 5 | This repository adds support for various pixel effects, such as blurring or recoloring, that can be used by simply wrapping a widget in them. 6 | 7 | ## Usage 8 | 9 | First, clone this repository to your awesome wm config directory: 10 | 11 | ```bash 12 | git clone https://github.com/ReadyWidgets/filters "${XDG_CONFIG_HOME:-$HOME/.config}/awesome/filters" 13 | ``` 14 | 15 | Next, compile `c-filters.c`: 16 | 17 | ```bash 18 | cd "${XDG_CONFIG_HOME:-$HOME/.config}/awesome/filters" 19 | bash build.sh 20 | ``` 21 | 22 | Then, you can use the filters using something like this: 23 | 24 | ```lua 25 | local mywidget = wibox.widget { 26 | { 27 | markup = "Hello, world!", 28 | halign = "center", 29 | valign = "center", 30 | widget = wibox.widget.textbox, 31 | }, 32 | radius = 20, 33 | opacity = 1.0, 34 | widget = filters.shadow, 35 | } 36 | ``` 37 | 38 | ## Available filters and properties 39 | 40 | - Blur 41 | - `radius: integer = 10` 42 | - `no_use_kernel: boolean = false` 43 | - Shadow 44 | - `radius: integer = 10` 45 | - `opacity: number = 1.0` 46 | - Tint 47 | - `color: string = "#000000"` 48 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd "$( dirname "${BASH_SOURCE[0]}" )" || exit 1 3 | 4 | file="c-filters.c" 5 | 6 | gcc -std=c11 -Werror-implicit-function-declaration -shared -fPIC "$(pkg-config --cflags --libs gtk+-3.0 glib-2.0 gio-2.0 gio-unix-2.0 luajit cairo)" -lm "$file" -o "${file%.c}.so" 7 | -------------------------------------------------------------------------------- /c-filters.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | typedef long long int i64; 15 | typedef long int i32; 16 | typedef int i16; 17 | typedef char i8; 18 | 19 | typedef unsigned long long int u64; 20 | typedef unsigned long int u32; 21 | typedef unsigned int u16; 22 | typedef unsigned char u8; 23 | 24 | #define Surface cairo_surface_t 25 | 26 | #define def(name) static int name(lua_State* L) 27 | 28 | #define new(type) malloc(sizeof(type)) 29 | 30 | #define for_range(loopvar, stop) for (u8 loopvar = 0; loopvar < stop; loopvar++) 31 | 32 | #define print(fmt, ...) printf(("\x1b[1;34mc_filter -> \x1b[0m" fmt "\n"), __VA_ARGS__) 33 | 34 | typedef struct Pixel { 35 | u8 red, green, blue, alpha; 36 | } __attribute__((packed)) Pixel; 37 | 38 | #define index_color(pixel, index) ((u8*)(pixel))[index] 39 | 40 | #define auto_pointer(type, name, size, value, block) { type* name = malloc(sizeof(type) * size); name = value; block; free(name); } 41 | 42 | /* 43 | // (surface: cairo.Surface, width: integer, height: integer) -> GdkPixbuf.Pixbuf 44 | def(export_surface_to_pixbuf) { 45 | Surface* surface = (Surface*)lua_touserdata(L, 1); 46 | int width = (i64)luaL_checknumber(L, 2); 47 | int height = (i64)luaL_checknumber(L, 3); 48 | 49 | GdkPixbuf* pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, width, height); 50 | 51 | lua_pushlightuserdata(L, pixbuf); 52 | 53 | return 1; 54 | } 55 | */ 56 | 57 | double clamp(double number, double floor, double ceiling) { 58 | if (number < floor) { 59 | return floor; 60 | } else if (number > ceiling) { 61 | return ceiling; 62 | } else { 63 | return number; 64 | } 65 | } 66 | 67 | i64 int_clamp(i64 number, i64 floor, i64 ceiling) { 68 | return (i64)clamp(number, floor, ceiling); 69 | } 70 | 71 | // (number: number, floor: number, ceiling: number) -> number 72 | def(export_clamp) { 73 | double number = luaL_checknumber(L, 1); 74 | double floor = luaL_checknumber(L, 2); 75 | double ceiling = luaL_checknumber(L, 3); 76 | 77 | double result = clamp(number, floor, ceiling); 78 | 79 | lua_pushnumber(L, result); 80 | 81 | return 1; 82 | } 83 | 84 | const double pi = 3.1415926535897932384626433832795; 85 | // Based on https://stackoverflow.com/a/8204867 86 | double* generate_blur_kernel(i32 radius, i32 sigma) { 87 | double* kernel = malloc(sizeof(double) * (radius * radius)); 88 | double mean = radius / 2; 89 | double sum = 0.0; 90 | 91 | for_range (x, radius) { 92 | for_range (y, radius) { 93 | i16 index = x + (y * radius); 94 | 95 | kernel[index] = exp( 96 | -0.5 * (pow((x - mean) / sigma, 2.0) + pow((y - mean) / sigma, 2.0)) 97 | ) / ( 98 | 2 * pi * sigma * sigma 99 | ); 100 | 101 | sum += kernel[index]; 102 | } 103 | } 104 | 105 | for_range (x, radius) { 106 | for_range (y, radius) { 107 | kernel[x + (y * radius)] /= sum; 108 | } 109 | } 110 | 111 | return kernel; 112 | } 113 | 114 | GdkPixbuf* blur_pixbuf(GdkPixbuf* pixbuf, const i32 radius, const bool no_use_kernel) { 115 | guint* _length = new(guint); 116 | 117 | Pixel* pixels = (Pixel*)gdk_pixbuf_get_pixels_with_length(pixbuf, _length); 118 | 119 | const u32 length = *_length / 4; 120 | free(_length); 121 | 122 | const u32 rowstride = gdk_pixbuf_get_rowstride(pixbuf); 123 | const u32 width = gdk_pixbuf_get_width(pixbuf); 124 | const u32 height = gdk_pixbuf_get_height(pixbuf); 125 | 126 | const u16 half = ceil(0.5 * radius); 127 | 128 | const double multiplier = 1.0f / (double)(radius * radius); 129 | 130 | Pixel* blurred_pixels = malloc(sizeof(Pixel) * length); 131 | 132 | double* kernel = generate_blur_kernel(radius, (radius / 2.0)); 133 | 134 | for_range (y, height) { 135 | for_range (x, width) { 136 | 137 | u32 index = x + (y * height); 138 | 139 | blurred_pixels[index].alpha = 0; 140 | blurred_pixels[index].red = 0; 141 | blurred_pixels[index].green = 0; 142 | blurred_pixels[index].blue = 0; 143 | 144 | u32 y_floor = clamp(y - half, 0, height); 145 | u32 y_ceil = clamp(y + half, 0, height); 146 | u32 x_floor = clamp(x - half, 0, width); 147 | u32 x_ceil = clamp(x + half, 0, width); 148 | 149 | for_range (channel_offset, 4) { 150 | u32 color_index = index * 4 + channel_offset; 151 | 152 | double current_color = 0; // index_color(blurred_pixels, color_index); 153 | 154 | if (no_use_kernel) { 155 | for (i32 y_offset = y_floor; y_offset < y_ceil; y_offset++) { 156 | for (i32 x_offset = x_floor; x_offset < x_ceil; x_offset++) { 157 | current_color += (double)index_color(pixels, ((x_offset + (y_offset * height)) * 4 + channel_offset)); 158 | } 159 | } 160 | 161 | current_color *= multiplier; 162 | } else { 163 | for_range (y_offset, radius) { 164 | for_range (x_offset, radius) { 165 | double kernel_value = kernel[x_offset + (y_offset * radius)]; 166 | current_color += index_color( 167 | pixels, 168 | ((int_clamp(x + (x_offset - half), 0, width) + ((int_clamp(y + (y_offset - half), 0, height)) * height)) * 4 + channel_offset) 169 | ) * kernel_value; 170 | } 171 | } 172 | } 173 | 174 | index_color(blurred_pixels, color_index) = (u8)current_color; 175 | } 176 | } 177 | } 178 | 179 | GdkPixbuf* blurred_pixbuf = gdk_pixbuf_new_from_data( 180 | (u8*)blurred_pixels, 181 | GDK_COLORSPACE_RGB, 182 | true, 183 | 8, 184 | width, 185 | height, 186 | rowstride, 187 | NULL, 188 | NULL 189 | ); 190 | 191 | free(kernel); 192 | 193 | return blurred_pixbuf; 194 | } 195 | 196 | def(export_blur_pixbuf) { 197 | GdkPixbuf* pixbuf = lua_touserdata(L, 1); 198 | i32 radius = luaL_checkint(L, 2); 199 | bool no_use_kernel = lua_toboolean(L, 3); 200 | 201 | GdkPixbuf* blurred_pixbuf = blur_pixbuf(pixbuf, radius, no_use_kernel); 202 | 203 | lua_pushlightuserdata(L, blurred_pixbuf); 204 | 205 | return 1; 206 | } 207 | 208 | typedef enum BlendingMode { 209 | ADD 210 | } BlendingMode; 211 | 212 | // Note: pixbuf_a and pixbuf_b MUST have the same dimensions 213 | GdkPixbuf* combine_pixbufs(GdkPixbuf* pixbuf_a, GdkPixbuf* pixbuf_b, const BlendingMode blending_mode) { 214 | guint* _length = new(guint); 215 | 216 | u8* pixels_a = gdk_pixbuf_get_pixels_with_length(pixbuf_a, _length); 217 | 218 | const u32 length = *_length; 219 | const u32 rowstride = gdk_pixbuf_get_rowstride(pixbuf_a); 220 | const u32 width = gdk_pixbuf_get_width(pixbuf_a); 221 | const u32 height = gdk_pixbuf_get_height(pixbuf_a); 222 | 223 | u8* pixels_b = gdk_pixbuf_get_pixels_with_length(pixbuf_b, _length); 224 | 225 | free(_length); 226 | 227 | u8* combined_pixels = malloc(sizeof(u8) * length); 228 | 229 | switch (blending_mode) { 230 | case ADD: 231 | for (u32 i = 0; i < length; i++) { 232 | combined_pixels[i] = clamp(pixels_a[i] + pixels_b[i], 0, 255); 233 | } 234 | 235 | break; 236 | } 237 | 238 | GdkPixbuf* combined_pixbuf = gdk_pixbuf_new_from_data( 239 | combined_pixels, 240 | GDK_COLORSPACE_RGB, 241 | true, 242 | 8, 243 | width, 244 | height, 245 | rowstride, 246 | NULL, 247 | NULL 248 | ); 249 | 250 | return combined_pixbuf; 251 | } 252 | 253 | def(export_combine_pixbufs) { 254 | GdkPixbuf* pixbuf_a = lua_touserdata(L, 1); 255 | GdkPixbuf* pixbuf_b = lua_touserdata(L, 2); 256 | BlendingMode mode = luaL_checkint(L, 3); 257 | 258 | GdkPixbuf* combined_pixbuf = combine_pixbufs(pixbuf_a, pixbuf_b, mode); 259 | 260 | lua_pushlightuserdata(L, combined_pixbuf); 261 | 262 | return 1; 263 | } 264 | 265 | GdkPixbuf* tint_pixbuf(GdkPixbuf* pixbuf, const u8 red, const u8 green, const u8 blue) { 266 | guint* _length = new(guint); 267 | 268 | Pixel* pixels = (Pixel*)gdk_pixbuf_get_pixels_with_length(pixbuf, _length); 269 | 270 | const u32 length = *_length / 4; 271 | const u32 rowstride = gdk_pixbuf_get_rowstride(pixbuf); 272 | const u32 width = gdk_pixbuf_get_width(pixbuf); 273 | const u32 height = gdk_pixbuf_get_height(pixbuf); 274 | 275 | free(_length); 276 | 277 | Pixel* tinted_pixels = malloc(sizeof(Pixel) * length); 278 | 279 | for (u32 i = 0; i < length; i++) { 280 | tinted_pixels[i].red = red; 281 | tinted_pixels[i].green = green; 282 | tinted_pixels[i].blue = blue; 283 | tinted_pixels[i].alpha = pixels[i].alpha; 284 | } 285 | 286 | GdkPixbuf* tinted_pixbuf = gdk_pixbuf_new_from_data( 287 | (u8*)tinted_pixels, 288 | GDK_COLORSPACE_RGB, 289 | true, 290 | 8, 291 | width, 292 | height, 293 | rowstride, 294 | NULL, 295 | NULL 296 | ); 297 | 298 | return tinted_pixbuf; 299 | } 300 | 301 | def(export_tint_pixbuf) { 302 | GdkPixbuf* pixbuf = lua_touserdata(L, 1); 303 | u8 red = luaL_checkint(L, 2); 304 | u8 green = luaL_checkint(L, 3); 305 | u8 blue = luaL_checkint(L, 4); 306 | 307 | GdkPixbuf* tinted_pixbuf = tint_pixbuf(pixbuf, red, green, blue); 308 | 309 | lua_pushlightuserdata(L, tinted_pixbuf); 310 | 311 | return 1; 312 | } 313 | 314 | GdkPixbuf* apply_multiplier_to_pixbuf(GdkPixbuf* pixbuf, const double red, const double green, const double blue, const double alpha) { 315 | guint* _length = new(guint); 316 | 317 | Pixel* pixels = (Pixel*)gdk_pixbuf_get_pixels_with_length(pixbuf, _length); 318 | 319 | const u32 length = *_length / 4; 320 | const u32 rowstride = gdk_pixbuf_get_rowstride(pixbuf); 321 | const u32 width = gdk_pixbuf_get_width(pixbuf); 322 | const u32 height = gdk_pixbuf_get_height(pixbuf); 323 | 324 | free(_length); 325 | 326 | Pixel* tinted_pixels = malloc(sizeof(Pixel) * length); 327 | 328 | for (u32 i = 0; i < length; i++) { 329 | tinted_pixels[i].red = clamp(pixels[i].red * red, 0, 255); 330 | tinted_pixels[i].green = clamp(pixels[i].green * green, 0, 255); 331 | tinted_pixels[i].blue = clamp(pixels[i].blue * blue, 0, 255); 332 | tinted_pixels[i].alpha = clamp(pixels[i].alpha * alpha, 0, 255); 333 | } 334 | 335 | GdkPixbuf* tinted_pixbuf = gdk_pixbuf_new_from_data( 336 | (u8*)tinted_pixels, 337 | GDK_COLORSPACE_RGB, 338 | true, 339 | 8, 340 | width, 341 | height, 342 | rowstride, 343 | NULL, 344 | NULL 345 | ); 346 | 347 | return tinted_pixbuf; 348 | } 349 | 350 | GdkPixbuf* add_shadow_to_pixbuf(GdkPixbuf* pixbuf, u32 radius, double opcaity) { 351 | GdkPixbuf* blurred_pixbuf = blur_pixbuf(pixbuf, radius, true); 352 | 353 | GdkPixbuf* tinted_pixbuf = apply_multiplier_to_pixbuf(blurred_pixbuf, 0.0, 0.0, 0.0, 0.75); 354 | g_free(blurred_pixbuf); 355 | 356 | GdkPixbuf* shaded_pixbuf = combine_pixbufs(pixbuf, tinted_pixbuf, ADD); 357 | g_free(tinted_pixbuf); 358 | 359 | return shaded_pixbuf; 360 | } 361 | 362 | def(export_add_shadow_to_pixbuf) { 363 | GdkPixbuf* pixbuf = lua_touserdata(L, 1); 364 | u32 radius = luaL_checkint(L, 2); 365 | double opcaity = lua_tonumber(L, 3); 366 | 367 | GdkPixbuf* shaded_pixbuf = add_shadow_to_pixbuf(pixbuf, radius, opcaity); 368 | 369 | lua_pushlightuserdata(L, shaded_pixbuf); 370 | 371 | return 1; 372 | } 373 | 374 | static const struct luaL_Reg filters[] = { 375 | //{ "surface_to_pixbuf", export_surface_to_pixbuf }, 376 | { "clamp", export_clamp }, 377 | { "blur_pixbuf", export_blur_pixbuf }, 378 | { "combine_pixbufs", export_combine_pixbufs }, 379 | { "tint_pixbuf", export_tint_pixbuf }, 380 | { "add_shadow_to_pixbuf", export_add_shadow_to_pixbuf }, 381 | { NULL, NULL } 382 | }; 383 | 384 | int luaopen_filters(lua_State* L) { 385 | luaL_newlib(L, filters); 386 | 387 | return 1; 388 | } 389 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | local wibox = require("wibox") 4 | 5 | local lgi = require("lgi") 6 | local Gdk, GdkPixbuf, cairo = lgi.Gdk, lgi.GdkPixbuf, lgi.cairo 7 | 8 | ---@type filters.c-filters 9 | local c_filters = require("filters.c-filters") 10 | 11 | local assert_param_type, fill_context_with_surface, gen_property, copy 12 | 13 | local export = {} 14 | 15 | --- Check if a given parameter is of the expected type, otherwise throw an error 16 | ---@param func_name string 17 | ---@param position integer 18 | ---@param wanted_type type 19 | ---@param value any 20 | function assert_param_type(func_name, position, wanted_type, value) 21 | local value_type = type(value) 22 | 23 | assert(value_type == wanted_type, ("Wrong type of parameter #%d passed to '%s' (expected %s, got %s)"):format(position, func_name, wanted_type, value_type)) 24 | end 25 | 26 | --- Convert a cairo surface into a Gdk pixel buffer 27 | ---@param surface cairo.Surface 28 | ---@param width integer 29 | ---@param height integer 30 | ---@return GdkPixbuf.Pixbuf 31 | function export.surface_to_pixbuf(surface, width, height) 32 | return Gdk.pixbuf_get_from_surface(surface, 0, 0, width, height) 33 | end 34 | 35 | ---@param pixbuf GdkPixbuf.Pixbuf 36 | ---@return cairo.Surface 37 | function export.pixbuf_to_surface(pixbuf) 38 | local surface = awesome.pixbuf_to_surface(pixbuf._native, gears.surface()) 39 | 40 | if cairo.Surface:is_type_of(surface) then 41 | return surface 42 | end 43 | 44 | return cairo.Surface(surface, true) 45 | end 46 | 47 | --- Fill a cairo context with a cairo surface 48 | ---@param cr cairo.Context 49 | ---@param surface cairo.Surface 50 | function fill_context_with_surface(cr, surface) 51 | cr:set_source_surface(surface, 0, 0) 52 | cr:paint() 53 | end 54 | 55 | function gen_property(object, property) 56 | assert_param_type("gen_property", 1, "table", object) 57 | assert_param_type("gen_property", 2, "string", property) 58 | 59 | object["get_" .. property] = function(self) 60 | --print("Called get_"..property.."()") 61 | return self._private[property] 62 | end 63 | 64 | object["set_" .. property] = function(self, value) 65 | --print("Called set_"..property.."(" .. tostring(value) .. ")") 66 | self._private[property] = value 67 | self:emit_signal(("property::" .. property), value) 68 | self._private.force_redraw = true 69 | self:emit_signal("widget::redraw_needed") 70 | end 71 | end 72 | 73 | do 74 | local base = { mt = {} } 75 | base.mt.__index = base.mt 76 | setmetatable(base, base.mt) 77 | 78 | function base:get_widget() 79 | return self._private.widget 80 | end 81 | 82 | function base:set_widget(widget) 83 | if self._private.child_redraw_listener == nil then 84 | function self._private.child_redraw_listener() 85 | self._private.force_redraw = true 86 | self:emit_signal("widget::redraw_needed") 87 | end 88 | end 89 | 90 | local child_redraw_listener = self._private.child_redraw_listener 91 | 92 | if self._private.widget ~= nil then 93 | self._private.widget:disconnect_signal("widget::redraw_needed", child_redraw_listener) 94 | end 95 | 96 | widget:connect_signal("widget::redraw_needed", child_redraw_listener) 97 | 98 | wibox.widget.base.set_widget_common(self, widget) 99 | end 100 | 101 | function base:get_children() 102 | return { self._private.widget } 103 | end 104 | 105 | function base:set_children(children) 106 | self:set_widget(children[1]) 107 | end 108 | 109 | function base:draw(context, cr, w, h) 110 | --do 111 | -- return fill_context_with_surface(cr, self._private.cached_surface) 112 | --end 113 | 114 | local child = self:get_widget() 115 | 116 | if child == nil then 117 | return 118 | end 119 | 120 | if (not self._private.force_redraw) and (self._private.cached_surface ~= nil) then 121 | fill_context_with_surface(cr, self._private.cached_surface) 122 | return 123 | end 124 | 125 | ---@type cairo.Surface 126 | local surface 127 | if self.on_draw ~= nil then 128 | surface = self:on_draw(cr, w, h, child) 129 | else 130 | surface = wibox.widget.draw_to_image_surface(child, w, h) 131 | end 132 | 133 | self._private.force_redraw = false 134 | self._private.cached_surface = surface 135 | 136 | fill_context_with_surface(cr, surface) 137 | end 138 | 139 | function base.mt.__call(cls, kwargs) 140 | if kwargs == nil then 141 | kwargs = {} 142 | end 143 | 144 | local self = gears.object { enable_properties = true } 145 | 146 | gears.table.crush(self, wibox.widget.base.make_widget()) 147 | gears.table.crush(self, cls) 148 | 149 | if cls.parse_kwargs ~= nil then 150 | cls.parse_kwargs(kwargs) 151 | end 152 | 153 | if self._private == nil then 154 | self._private = {} 155 | end 156 | 157 | --gears.table.crush(self, kwargs) 158 | for k, v in pairs(kwargs) do 159 | self[k] = v 160 | end 161 | 162 | return self 163 | end 164 | 165 | export.base = base 166 | end 167 | 168 | ---@generic T1 : table 169 | ---@param tb T1 170 | ---@return T1 171 | function copy(tb) 172 | local copy_of_tb = {} 173 | 174 | for k, v in pairs(tb) do 175 | copy_of_tb[k] = v 176 | end 177 | 178 | setmetatable(copy_of_tb, getmetatable(tb)) 179 | 180 | return copy_of_tb 181 | end 182 | 183 | do 184 | local blur = copy(export.base) 185 | 186 | gen_property(blur, "radius") 187 | gen_property(blur, "no_use_kernel") 188 | 189 | function blur:on_draw(cr, w, h, child) 190 | local pixbuf = export.surface_to_pixbuf(wibox.widget.draw_to_image_surface(child, w, h), w, h)._native 191 | 192 | local blurred_pixbuf = GdkPixbuf.Pixbuf(c_filters.blur_pixbuf(pixbuf, self.radius or 10, self.no_use_kernel or false)) 193 | 194 | return fill_context_with_surface(cr, export.pixbuf_to_surface(blurred_pixbuf)) 195 | end 196 | 197 | function blur.parse_kwargs(kwargs) 198 | kwargs.radius = kwargs.radius or 10 199 | kwargs.no_use_kernel = kwargs.no_use_kernel or false 200 | end 201 | 202 | export.blur = blur 203 | end 204 | 205 | do 206 | local shadow = copy(export.base) 207 | 208 | gen_property(shadow, "radius") 209 | gen_property(shadow, "opacity") 210 | 211 | function shadow:on_draw(cr, w, h, child) 212 | local pixbuf = export.surface_to_pixbuf(wibox.widget.draw_to_image_surface(child, w, h), w, h)._native 213 | 214 | local blurred_pixbuf = GdkPixbuf.Pixbuf(c_filters.add_shadow_to_pixbuf(pixbuf, self.radius or 10, self.opacity or 1.0)) 215 | 216 | return fill_context_with_surface(cr, export.pixbuf_to_surface(blurred_pixbuf)) 217 | end 218 | 219 | function shadow.parse_kwargs(kwargs) 220 | kwargs.radius = kwargs.radius or 10 221 | kwargs.opacity = kwargs.opacity or 1.0 222 | end 223 | 224 | export.shadow = shadow 225 | end 226 | 227 | do 228 | local tint = copy(export.base) 229 | 230 | gen_property(tint, "color") 231 | 232 | local floor = math.floor 233 | 234 | function tint:on_draw(cr, w, h, child) 235 | local pixbuf = export.surface_to_pixbuf(wibox.widget.draw_to_image_surface(child, w, h), w, h)._native 236 | 237 | local r, g, b, _ = gears.color.parse_color(self.color or "#000000") 238 | r = floor(r * 255) 239 | g = floor(g * 255) 240 | b = floor(b * 255) 241 | local blurred_pixbuf = GdkPixbuf.Pixbuf(c_filters.tint_pixbuf(pixbuf, r, g, b)) 242 | 243 | return fill_context_with_surface(cr, export.pixbuf_to_surface(blurred_pixbuf)) 244 | end 245 | 246 | function tint.parse_kwargs(kwargs) 247 | kwargs.color = kwargs.color or "#000000" 248 | end 249 | 250 | export.tint = tint 251 | end 252 | 253 | return export 254 | -------------------------------------------------------------------------------- /types/c-filters.lua: -------------------------------------------------------------------------------- 1 | ---@meta c-filters 2 | 3 | ---@class filters.c-filters 4 | local m = {} 5 | 6 | ---@param pixbuf userdata A native Pixbuf 7 | ---@param radius integer Blur radius in pixels 8 | ---@param no_use_kernel boolean If true, no kernel will be used; instead the pixels will simply get averaged 9 | function m.blur_pixbuf(pixbuf, radius, no_use_kernel) end 10 | 11 | ---@param pixbuf userdata A native Pixbuf 12 | ---@param radius integer Shadow radius in pixels 13 | ---@param opacity number Shadow opacity, from 0.0 to 1.0 14 | function m.add_shadow_to_pixbuf(pixbuf, radius, opacity) end 15 | 16 | ---@param pixbuf userdata A native Pixbuf 17 | ---@param red number 18 | ---@param green number 19 | ---@param blue number 20 | function m.tint_pixbuf(pixbuf, red, green, blue) end 21 | --------------------------------------------------------------------------------