🎉 Add dominat color extractor
Checks / Check Clang-Format (push) Has been cancelled
Checks / Check Comment Percentage (push) Has been cancelled
Checks / Run ShellCheck (push) Has been cancelled
Checks / Run Tests (gnome-wayland-nested, 32) (push) Has been cancelled
Checks / Run Tests (gnome-wayland-nested, 33) (push) Has been cancelled
Checks / Run Tests (gnome-wayland-nested, 34) (push) Has been cancelled
Checks / Run Tests (gnome-wayland-nested, 35) (push) Has been cancelled
Checks / Run Tests (gnome-wayland-nested, 36) (push) Has been cancelled

This commit is contained in:
Simon Schneegans
2022-05-21 11:08:50 +02:00
parent 703f990f7f
commit 38f67dc8cd
+177
View File
@@ -0,0 +1,177 @@
//////////////////////////////////////////////////////////////////////////////////////////
// ) ( //
// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< //
// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ //
// |__/ //
// Copyright (c) 2021 Simon Schneegans //
// Released under the GPLv3 or later. See LICENSE file for details. //
//////////////////////////////////////////////////////////////////////////////////////////
'use strict';
//////////////////////////////////////////////////////////////////////////////////////////
// https://github.com/home-sweet-gnome/dash-to-panel/blob/master/utils.js#L669
// DominantColorExtractor is adapted from https://github.com/micheleg/dash-to-dock
//////////////////////////////////////////////////////////////////////////////////////////
let themeLoader = null;
let iconCacheMap = new Map();
const MAX_CACHED_ITEMS = 1000;
const BATCH_SIZE_TO_DELETE = 50;
const DOMINANT_COLOR_ICON_SIZE = 64;
var DominantColorExtractor = class {
constructor(app) {
this._app = app;
}
// Try to get the pixel buffer for the current icon, if not fail gracefully
_getIconPixBuf() {
let iconTexture = this._app.create_icon_texture(16);
if (themeLoader === null) {
let ifaceSettings = new Gio.Settings({schema: 'org.gnome.desktop.interface'});
themeLoader = new Gtk.IconTheme(),
themeLoader.set_custom_theme(
ifaceSettings.get_string('icon-theme')); // Make sure the correct theme is loaded
}
// Unable to load the icon texture, use fallback
if (iconTexture instanceof St.Icon === false) {
return null;
}
iconTexture = iconTexture.get_gicon();
// Unable to load the icon texture, use fallback
if (iconTexture === null) {
return null;
}
if (iconTexture instanceof Gio.FileIcon) {
// Use GdkPixBuf to load the pixel buffer from the provided file path
return GdkPixbuf.Pixbuf.new_from_file(iconTexture.get_file().get_path());
}
// Get the pixel buffer from the icon theme
if (iconTexture instanceof Gio.ThemedIcon) {
let icon_info =
themeLoader.lookup_icon(iconTexture.get_names()[0], DOMINANT_COLOR_ICON_SIZE, 0);
if (icon_info !== null) return icon_info.load_icon();
}
return null;
}
// The backlight color choosing algorithm was mostly ported to javascript from the
// Unity7 C++ source of Canonicals:
// https://bazaar.launchpad.net/~unity-team/unity/trunk/view/head:/launcher/LauncherIcon.cpp
// so it more or less works the same way.
_getColorPalette() {
if (iconCacheMap.get(this._app.get_id())) {
// We already know the answer
return iconCacheMap.get(this._app.get_id());
}
let pixBuf = this._getIconPixBuf();
if (pixBuf == null) return null;
let pixels = pixBuf.get_pixels(), offset = 0;
let total = 0, rTotal = 0, gTotal = 0, bTotal = 0;
let resample_y = 1, resample_x = 1;
// Resampling of large icons
// We resample icons larger than twice the desired size, as the resampling
// to a size s
// DOMINANT_COLOR_ICON_SIZE < s < 2*DOMINANT_COLOR_ICON_SIZE,
// most of the case exactly DOMINANT_COLOR_ICON_SIZE as the icon size is tipycally
// a multiple of it.
let width = pixBuf.get_width();
let height = pixBuf.get_height();
// Resample
if (height >= 2 * DOMINANT_COLOR_ICON_SIZE)
resample_y = Math.floor(height / DOMINANT_COLOR_ICON_SIZE);
if (width >= 2 * DOMINANT_COLOR_ICON_SIZE)
resample_x = Math.floor(width / DOMINANT_COLOR_ICON_SIZE);
if (resample_x !== 1 || resample_y !== 1)
pixels = this._resamplePixels(pixels, resample_x, resample_y);
// computing the limit outside the for (where it would be repeated at each iteration)
// for performance reasons
let limit = pixels.length;
for (let offset = 0; offset < limit; offset += 4) {
let r = pixels[offset], g = pixels[offset + 1], b = pixels[offset + 2],
a = pixels[offset + 3];
let saturation = (Math.max(r, g, b) - Math.min(r, g, b));
let relevance = 0.1 * 255 * 255 + 0.9 * a * saturation;
rTotal += r * relevance;
gTotal += g * relevance;
bTotal += b * relevance;
total += relevance;
}
total = total * 255;
let r = rTotal / total, g = gTotal / total, b = bTotal / total;
let hsv = ColorUtils.RGBtoHSV(r * 255, g * 255, b * 255);
if (hsv.s > 0.15) hsv.s = 0.65;
hsv.v = 0.90;
let rgb = ColorUtils.HSVtoRGB(hsv.h, hsv.s, hsv.v);
// Cache the result.
let backgroundColor = {
lighter: ColorUtils.colorLuminance(rgb.r, rgb.g, rgb.b, 0.2),
original: ColorUtils.colorLuminance(rgb.r, rgb.g, rgb.b, 0),
darker: ColorUtils.colorLuminance(rgb.r, rgb.g, rgb.b, -0.5)
};
if (iconCacheMap.size >= MAX_CACHED_ITEMS) {
// delete oldest cached values (which are in order of insertions)
let ctr = 0;
for (let key of iconCacheMap.keys()) {
if (++ctr > BATCH_SIZE_TO_DELETE) break;
iconCacheMap.delete(key);
}
}
iconCacheMap.set(this._app.get_id(), backgroundColor);
return backgroundColor;
}
// Downsample large icons before scanning for the backlight color to
// improve performance.
_resamplePixels(pixels, resampleX, resampleY) {
let resampledPixels = [];
// computing the limit outside the for (where it would be repeated at each iteration)
// for performance reasons
let limit = pixels.length / (resampleX * resampleY) / 4;
for (let i = 0; i < limit; i++) {
let pixel = i * resampleX * resampleY;
resampledPixels.push(pixels[pixel * 4]);
resampledPixels.push(pixels[pixel * 4 + 1]);
resampledPixels.push(pixels[pixel * 4 + 2]);
resampledPixels.push(pixels[pixel * 4 + 3]);
}
return resampledPixels;
}
};