mirror of
https://github.com/elementary/gala.git
synced 2024-09-11 15:15:39 +03:00
Granite drawing removals (#1428)
* Drawing: Migrate functions removed from Granite 7 Some drawing utilities used by Gala were removed from Granite 7: https://github.com/elementary/granite/pull/603 Add them to Gala. * Drawing: Use drawings functions from Gala instead of Granite
This commit is contained in:
parent
00b2fb7b71
commit
f74d8fdad5
708
lib/Drawing/BufferSurface.vala
Normal file
708
lib/Drawing/BufferSurface.vala
Normal file
@ -0,0 +1,708 @@
|
||||
/*
|
||||
* Copyright 2019 elementary, Inc. (https://elementary.io)
|
||||
* Copyright 2011-2013 Robert Dyer
|
||||
* Copyright 2011-2013 Rico Tzschichholz <ricotz@ubuntu.com>
|
||||
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
using Cairo;
|
||||
using Posix;
|
||||
|
||||
namespace Gala.Drawing {
|
||||
/**
|
||||
* A buffer containing an internal Cairo-usable surface and context, designed
|
||||
* for usage with large, rarely updated draw operations.
|
||||
*/
|
||||
public class BufferSurface : GLib.Object {
|
||||
private Surface _surface;
|
||||
/**
|
||||
* The {@link Cairo.Surface} which will store the results of all drawing operations
|
||||
* made with {@link Gala.Drawing.BufferSurface.context}.
|
||||
*/
|
||||
public Surface surface {
|
||||
get {
|
||||
if (_surface == null) {
|
||||
_surface = new ImageSurface (Format.ARGB32, width, height);
|
||||
}
|
||||
|
||||
return _surface;
|
||||
}
|
||||
private set { _surface = value; }
|
||||
}
|
||||
|
||||
/**
|
||||
* The width of the {@link Gala.Drawing.BufferSurface}, in pixels.
|
||||
*/
|
||||
public int width { get; private set; }
|
||||
/**
|
||||
* The height of the BufferSurface, in pixels.
|
||||
*/
|
||||
public int height { get; private set; }
|
||||
|
||||
private Context _context;
|
||||
/**
|
||||
* The {@link Cairo.Context} for the internal surface. All drawing operations done on this
|
||||
* {@link Gala.Drawing.BufferSurface} should use this context.
|
||||
*/
|
||||
public Cairo.Context context {
|
||||
get {
|
||||
if (_context == null) {
|
||||
_context = new Cairo.Context (surface);
|
||||
}
|
||||
|
||||
return _context;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new, empty {@link Gala.Drawing.BufferSurface} with the supplied dimensions.
|
||||
*
|
||||
* @param width the width of {@link Gala.Drawing.BufferSurface}, in pixels
|
||||
* @param height the height of the {@link Gala.Drawing.BufferSurface}, in pixels
|
||||
*/
|
||||
public BufferSurface (int width, int height) requires (width >= 0 && height >= 0) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new, empty {@link Gala.Drawing.BufferSurface} with the supplied dimensions, using
|
||||
* the supplied {@link Cairo.Surface} as a model.
|
||||
*
|
||||
* @param width the width of the new {@link Gala.Drawing.BufferSurface}, in pixels
|
||||
* @param height the height of the new {@link Gala.Drawing.BufferSurface}, in pixels
|
||||
* @param model the {@link Cairo.Surface} to use as a model for the internal {@link Cairo.Surface}
|
||||
*/
|
||||
public BufferSurface.with_surface (int width, int height, Surface model) requires (model != null) {
|
||||
this (width, height);
|
||||
surface = new Surface.similar (model, Content.COLOR_ALPHA, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new, empty {@link Gala.Drawing.BufferSurface} with the supplied dimensions, using
|
||||
* the supplied {@link Gala.Drawing.BufferSurface} as a model.
|
||||
*
|
||||
* @param width the width of the new {@link Gala.Drawing.BufferSurface}, in pixels
|
||||
* @param height the height of the new {@link Gala.Drawing.BufferSurface}, in pixels
|
||||
* @param model the {@link Gala.Drawing.BufferSurface} to use as a model for the internal {@link Cairo.Surface}
|
||||
*/
|
||||
public BufferSurface.with_buffer_surface (int width, int height, BufferSurface model) requires (model != null) {
|
||||
this (width, height);
|
||||
surface = new Surface.similar (model.surface, Content.COLOR_ALPHA, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the internal {@link Cairo.Surface}, making all pixels fully transparent.
|
||||
*/
|
||||
public void clear () {
|
||||
context.save ();
|
||||
|
||||
_context.set_source_rgba (0, 0, 0, 0);
|
||||
_context.set_operator (Operator.SOURCE);
|
||||
_context.paint ();
|
||||
|
||||
_context.restore ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Gdk.Pixbuf} from internal {@link Cairo.Surface}.
|
||||
*
|
||||
* @return the {@link Gdk.Pixbuf}
|
||||
*/
|
||||
public Gdk.Pixbuf load_to_pixbuf () {
|
||||
var image_surface = new ImageSurface (Format.ARGB32, width, height);
|
||||
var cr = new Cairo.Context (image_surface);
|
||||
|
||||
cr.set_operator (Operator.SOURCE);
|
||||
cr.set_source_surface (surface, 0, 0);
|
||||
cr.paint ();
|
||||
|
||||
var width = image_surface.get_width ();
|
||||
var height = image_surface.get_height ();
|
||||
|
||||
var pb = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, width, height);
|
||||
pb.fill (0x00000000);
|
||||
|
||||
uint8 *data = image_surface.get_data ();
|
||||
uint8 *pixels = pb.get_pixels ();
|
||||
var length = width * height;
|
||||
|
||||
if (image_surface.get_format () == Format.ARGB32) {
|
||||
for (var i = 0; i < length; i++) {
|
||||
// if alpha is 0 set nothing
|
||||
if (data[3] > 0) {
|
||||
pixels[0] = (uint8) (data[2] * 255 / data[3]);
|
||||
pixels[1] = (uint8) (data[1] * 255 / data[3]);
|
||||
pixels[2] = (uint8) (data[0] * 255 / data[3]);
|
||||
pixels[3] = data[3];
|
||||
}
|
||||
|
||||
pixels += 4;
|
||||
data += 4;
|
||||
}
|
||||
} else if (image_surface.get_format () == Format.RGB24) {
|
||||
for (var i = 0; i < length; i++) {
|
||||
pixels[0] = data[2];
|
||||
pixels[1] = data[1];
|
||||
pixels[2] = data[0];
|
||||
pixels[3] = data[3];
|
||||
|
||||
pixels += 4;
|
||||
data += 4;
|
||||
}
|
||||
}
|
||||
|
||||
return pb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Averages all the colors in the internal {@link Cairo.Surface}.
|
||||
*
|
||||
* @return the {@link Gala.Drawing.Color} with the averaged color
|
||||
*/
|
||||
public Drawing.Color average_color () {
|
||||
var b_total = 0.0;
|
||||
var g_total = 0.0;
|
||||
var r_total = 0.0;
|
||||
|
||||
var w = width;
|
||||
var h = height;
|
||||
|
||||
var original = new ImageSurface (Format.ARGB32, w, h);
|
||||
var cr = new Cairo.Context (original);
|
||||
|
||||
cr.set_operator (Operator.SOURCE);
|
||||
cr.set_source_surface (surface, 0, 0);
|
||||
cr.paint ();
|
||||
|
||||
uint8 *data = original.get_data ();
|
||||
var length = w * h;
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
uint8 b = data [0];
|
||||
uint8 g = data [1];
|
||||
uint8 r = data [2];
|
||||
|
||||
uint8 max = (uint8) double.max (r, double.max (g, b));
|
||||
uint8 min = (uint8) double.min (r, double.min (g, b));
|
||||
double delta = max - min;
|
||||
|
||||
var sat = delta == 0 ? 0.0 : delta / max;
|
||||
var score = 0.2 + 0.8 * sat;
|
||||
|
||||
b_total += b * score;
|
||||
g_total += g * score;
|
||||
r_total += r * score;
|
||||
|
||||
data += 4;
|
||||
}
|
||||
|
||||
return new Drawing.Color (
|
||||
r_total / uint8.MAX / length,
|
||||
g_total / uint8.MAX / length,
|
||||
b_total / uint8.MAX / length,
|
||||
1
|
||||
).set_val (0.8).multiply_sat (1.15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a blur operation on the internal {@link Cairo.Surface}, using the
|
||||
* fast-blur algorithm found here [[http://incubator.quasimondo.com/processing/superfastblur.pde]].
|
||||
*
|
||||
* @param radius the blur radius
|
||||
* @param process_count the number of times to perform the operation
|
||||
*/
|
||||
public void fast_blur (int radius, int process_count = 1) {
|
||||
if (radius < 1 || process_count < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var w = width;
|
||||
var h = height;
|
||||
var channels = 4;
|
||||
|
||||
if (radius > w - 1 || radius > h - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var original = new ImageSurface (Format.ARGB32, w, h);
|
||||
var cr = new Cairo.Context (original);
|
||||
|
||||
cr.set_operator (Operator.SOURCE);
|
||||
cr.set_source_surface (surface, 0, 0);
|
||||
cr.paint ();
|
||||
|
||||
uint8 *pixels = original.get_data ();
|
||||
var buffer = new uint8[w * h * channels];
|
||||
|
||||
var v_min = new int[int.max (w, h)];
|
||||
var v_max = new int[int.max (w, h)];
|
||||
|
||||
var div = 2 * radius + 1;
|
||||
var dv = new uint8[256 * div];
|
||||
|
||||
for (var i = 0; i < dv.length; i++) {
|
||||
dv[i] = (uint8) (i / div);
|
||||
}
|
||||
|
||||
while (process_count-- > 0) {
|
||||
for (var x = 0; x < w; x++) {
|
||||
v_min[x] = int.min (x + radius + 1, w - 1);
|
||||
v_max[x] = int.max (x - radius, 0);
|
||||
}
|
||||
|
||||
for (var y = 0; y < h; y++) {
|
||||
var a_sum = 0, r_sum = 0, g_sum = 0, b_sum = 0;
|
||||
|
||||
uint32 cur_pixel = y * w * channels;
|
||||
|
||||
a_sum += radius * pixels[cur_pixel + 0];
|
||||
r_sum += radius * pixels[cur_pixel + 1];
|
||||
g_sum += radius * pixels[cur_pixel + 2];
|
||||
b_sum += radius * pixels[cur_pixel + 3];
|
||||
|
||||
for (var i = 0; i <= radius; i++) {
|
||||
a_sum += pixels[cur_pixel + 0];
|
||||
r_sum += pixels[cur_pixel + 1];
|
||||
g_sum += pixels[cur_pixel + 2];
|
||||
b_sum += pixels[cur_pixel + 3];
|
||||
|
||||
cur_pixel += channels;
|
||||
}
|
||||
|
||||
cur_pixel = y * w * channels;
|
||||
|
||||
for (var x = 0; x < w; x++) {
|
||||
uint32 p1 = (y * w + v_min[x]) * channels;
|
||||
uint32 p2 = (y * w + v_max[x]) * channels;
|
||||
|
||||
buffer[cur_pixel + 0] = dv[a_sum];
|
||||
buffer[cur_pixel + 1] = dv[r_sum];
|
||||
buffer[cur_pixel + 2] = dv[g_sum];
|
||||
buffer[cur_pixel + 3] = dv[b_sum];
|
||||
|
||||
a_sum += pixels[p1 + 0] - pixels[p2 + 0];
|
||||
r_sum += pixels[p1 + 1] - pixels[p2 + 1];
|
||||
g_sum += pixels[p1 + 2] - pixels[p2 + 2];
|
||||
b_sum += pixels[p1 + 3] - pixels[p2 + 3];
|
||||
|
||||
cur_pixel += channels;
|
||||
}
|
||||
}
|
||||
|
||||
for (var y = 0; y < h; y++) {
|
||||
v_min[y] = int.min (y + radius + 1, h - 1) * w;
|
||||
v_max[y] = int.max (y - radius, 0) * w;
|
||||
}
|
||||
|
||||
for (var x = 0; x < w; x++) {
|
||||
var a_sum = 0, r_sum = 0, g_sum = 0, b_sum = 0;
|
||||
|
||||
uint32 cur_pixel = x * channels;
|
||||
|
||||
a_sum += radius * buffer[cur_pixel + 0];
|
||||
r_sum += radius * buffer[cur_pixel + 1];
|
||||
g_sum += radius * buffer[cur_pixel + 2];
|
||||
b_sum += radius * buffer[cur_pixel + 3];
|
||||
|
||||
for (var i = 0; i <= radius; i++) {
|
||||
a_sum += buffer[cur_pixel + 0];
|
||||
r_sum += buffer[cur_pixel + 1];
|
||||
g_sum += buffer[cur_pixel + 2];
|
||||
b_sum += buffer[cur_pixel + 3];
|
||||
|
||||
cur_pixel += w * channels;
|
||||
}
|
||||
|
||||
cur_pixel = x * channels;
|
||||
|
||||
for (var y = 0; y < h; y++) {
|
||||
uint32 p1 = (x + v_min[y]) * channels;
|
||||
uint32 p2 = (x + v_max[y]) * channels;
|
||||
|
||||
pixels[cur_pixel + 0] = dv[a_sum];
|
||||
pixels[cur_pixel + 1] = dv[r_sum];
|
||||
pixels[cur_pixel + 2] = dv[g_sum];
|
||||
pixels[cur_pixel + 3] = dv[b_sum];
|
||||
|
||||
a_sum += buffer[p1 + 0] - buffer[p2 + 0];
|
||||
r_sum += buffer[p1 + 1] - buffer[p2 + 1];
|
||||
g_sum += buffer[p1 + 2] - buffer[p2 + 2];
|
||||
b_sum += buffer[p1 + 3] - buffer[p2 + 3];
|
||||
|
||||
cur_pixel += w * channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
original.mark_dirty ();
|
||||
|
||||
context.set_operator (Operator.SOURCE);
|
||||
context.set_source_surface (original, 0, 0);
|
||||
context.paint ();
|
||||
context.set_operator (Operator.OVER);
|
||||
}
|
||||
|
||||
const int ALPHA_PRECISION = 16;
|
||||
const int PARAM_PRECISION = 7;
|
||||
|
||||
/**
|
||||
* Performs a blur operation on the internal {@link Cairo.Surface}, using an
|
||||
* exponential blurring algorithm. This method is usually the fastest
|
||||
* and produces good-looking results (though not quite as good as gaussian's).
|
||||
*
|
||||
* @param radius the blur radius
|
||||
*/
|
||||
public void exponential_blur (int radius) {
|
||||
if (radius < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var alpha = (int) ((1 << ALPHA_PRECISION) * (1.0 - Math.exp (-2.3 / (radius + 1.0))));
|
||||
var height = this.height;
|
||||
var width = this.width;
|
||||
|
||||
var original = new ImageSurface (Format.ARGB32, width, height);
|
||||
var cr = new Cairo.Context (original);
|
||||
|
||||
cr.set_operator (Operator.SOURCE);
|
||||
cr.set_source_surface (surface, 0, 0);
|
||||
cr.paint ();
|
||||
|
||||
uint8 *pixels = original.get_data ();
|
||||
|
||||
try {
|
||||
// Process Rows
|
||||
var th = new Thread<void*>.try (null, () => {
|
||||
exponential_blur_rows (pixels, width, height, 0, height / 2, 0, width, alpha);
|
||||
return null;
|
||||
});
|
||||
|
||||
exponential_blur_rows (pixels, width, height, height / 2, height, 0, width, alpha);
|
||||
th.join ();
|
||||
|
||||
// Process Columns
|
||||
var th2 = new Thread<void*>.try (null, () => {
|
||||
exponential_blur_columns (pixels, width, height, 0, width / 2, 0, height, alpha);
|
||||
return null;
|
||||
});
|
||||
|
||||
exponential_blur_columns (pixels, width, height, width / 2, width, 0, height, alpha);
|
||||
th2.join ();
|
||||
} catch (Error err) {
|
||||
warning (err.message);
|
||||
}
|
||||
|
||||
original.mark_dirty ();
|
||||
|
||||
context.set_operator (Operator.SOURCE);
|
||||
context.set_source_surface (original, 0, 0);
|
||||
context.paint ();
|
||||
context.set_operator (Operator.OVER);
|
||||
}
|
||||
|
||||
void exponential_blur_columns (
|
||||
uint8* pixels,
|
||||
int width,
|
||||
int height,
|
||||
int start_col,
|
||||
int end_col,
|
||||
int start_y,
|
||||
int end_y,
|
||||
int alpha
|
||||
) {
|
||||
for (var column_index = start_col; column_index < end_col; column_index++) {
|
||||
// blur columns
|
||||
uint8 *column = pixels + column_index * 4;
|
||||
|
||||
var z_alpha = column[0] << PARAM_PRECISION;
|
||||
var z_red = column[1] << PARAM_PRECISION;
|
||||
var z_green = column[2] << PARAM_PRECISION;
|
||||
var z_blue = column[3] << PARAM_PRECISION;
|
||||
|
||||
// Top to Bottom
|
||||
for (var index = width * (start_y + 1); index < (end_y - 1) * width; index += width) {
|
||||
exponential_blur_inner (&column[index * 4], ref z_alpha, ref z_red, ref z_green, ref z_blue, alpha);
|
||||
}
|
||||
|
||||
// Bottom to Top
|
||||
for (var index = (end_y - 2) * width; index >= start_y; index -= width) {
|
||||
exponential_blur_inner (&column[index * 4], ref z_alpha, ref z_red, ref z_green, ref z_blue, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void exponential_blur_rows (
|
||||
uint8* pixels,
|
||||
int width,
|
||||
int height,
|
||||
int start_row,
|
||||
int end_row,
|
||||
int start_x,
|
||||
int end_x,
|
||||
int alpha
|
||||
) {
|
||||
for (var row_index = start_row; row_index < end_row; row_index++) {
|
||||
// Get a pointer to our current row
|
||||
uint8* row = pixels + row_index * width * 4;
|
||||
|
||||
var z_alpha = row[start_x + 0] << PARAM_PRECISION;
|
||||
var z_red = row[start_x + 1] << PARAM_PRECISION;
|
||||
var z_green = row[start_x + 2] << PARAM_PRECISION;
|
||||
var z_blue = row[start_x + 3] << PARAM_PRECISION;
|
||||
|
||||
// Left to Right
|
||||
for (var index = start_x + 1; index < end_x; index++)
|
||||
exponential_blur_inner (&row[index * 4], ref z_alpha, ref z_red, ref z_green, ref z_blue, alpha);
|
||||
|
||||
// Right to Left
|
||||
for (var index = end_x - 2; index >= start_x; index--)
|
||||
exponential_blur_inner (&row[index * 4], ref z_alpha, ref z_red, ref z_green, ref z_blue, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
private static inline void exponential_blur_inner (
|
||||
uint8* pixel,
|
||||
ref int z_alpha,
|
||||
ref int z_red,
|
||||
ref int z_green,
|
||||
ref int z_blue,
|
||||
int alpha
|
||||
) {
|
||||
z_alpha += (alpha * ((pixel[0] << PARAM_PRECISION) - z_alpha)) >> ALPHA_PRECISION;
|
||||
z_red += (alpha * ((pixel[1] << PARAM_PRECISION) - z_red)) >> ALPHA_PRECISION;
|
||||
z_green += (alpha * ((pixel[2] << PARAM_PRECISION) - z_green)) >> ALPHA_PRECISION;
|
||||
z_blue += (alpha * ((pixel[3] << PARAM_PRECISION) - z_blue)) >> ALPHA_PRECISION;
|
||||
|
||||
pixel[0] = (uint8) (z_alpha >> PARAM_PRECISION);
|
||||
pixel[1] = (uint8) (z_red >> PARAM_PRECISION);
|
||||
pixel[2] = (uint8) (z_green >> PARAM_PRECISION);
|
||||
pixel[3] = (uint8) (z_blue >> PARAM_PRECISION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a blur operation on the internal {@link Cairo.Surface}, using a
|
||||
* gaussian blurring algorithm. This method is very slow, albeit producing
|
||||
* debatably the best-looking results, and in most cases developers should
|
||||
* use the exponential blurring algorithm instead.
|
||||
*
|
||||
* @param radius the blur radius
|
||||
*/
|
||||
public void gaussian_blur (int radius) {
|
||||
var gauss_width = radius * 2 + 1;
|
||||
var kernel = build_gaussian_kernel (gauss_width);
|
||||
|
||||
var width = this.width;
|
||||
var height = this.height;
|
||||
|
||||
var original = new ImageSurface (Format.ARGB32, width, height);
|
||||
var cr = new Cairo.Context (original);
|
||||
|
||||
cr.set_operator (Operator.SOURCE);
|
||||
cr.set_source_surface (surface, 0, 0);
|
||||
cr.paint ();
|
||||
|
||||
uint8 *src = original.get_data ();
|
||||
|
||||
var size = height * original.get_stride ();
|
||||
|
||||
var buffer_a = new double[size];
|
||||
var buffer_b = new double[size];
|
||||
|
||||
// Copy image to double[] for faster horizontal pass
|
||||
for (var i = 0; i < size; i++) {
|
||||
buffer_a[i] = (double) src[i];
|
||||
}
|
||||
|
||||
// Precompute horizontal shifts
|
||||
var shiftar = new int[int.max (width, height), gauss_width];
|
||||
for (var x = 0; x < width; x++)
|
||||
for (var k = 0; k < gauss_width; k++) {
|
||||
var shift = k - radius;
|
||||
if (x + shift <= 0 || x + shift >= width)
|
||||
shiftar[x, k] = 0;
|
||||
else
|
||||
shiftar[x, k] = shift * 4;
|
||||
}
|
||||
|
||||
try {
|
||||
// Horizontal Pass
|
||||
var th = new Thread<void*>.try (null, () => {
|
||||
gaussian_blur_horizontal (
|
||||
buffer_a,
|
||||
buffer_b,
|
||||
kernel,
|
||||
gauss_width,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
height / 2,
|
||||
shiftar
|
||||
);
|
||||
return null;
|
||||
});
|
||||
|
||||
gaussian_blur_horizontal (
|
||||
buffer_a,
|
||||
buffer_b,
|
||||
kernel,
|
||||
gauss_width,
|
||||
width,
|
||||
height,
|
||||
height / 2,
|
||||
height,
|
||||
shiftar
|
||||
);
|
||||
th.join ();
|
||||
|
||||
// Clear buffer
|
||||
memset (buffer_a, 0, sizeof (double) * size);
|
||||
|
||||
// Precompute vertical shifts
|
||||
shiftar = new int[int.max (width, height), gauss_width];
|
||||
for (var y = 0; y < height; y++)
|
||||
for (var k = 0; k < gauss_width; k++) {
|
||||
var shift = k - radius;
|
||||
if (y + shift <= 0 || y + shift >= height)
|
||||
shiftar[y, k] = 0;
|
||||
else
|
||||
shiftar[y, k] = shift * width * 4;
|
||||
}
|
||||
|
||||
// Vertical Pass
|
||||
var th2 = new Thread<void*>.try (null, () => {
|
||||
gaussian_blur_vertical (
|
||||
buffer_b,
|
||||
buffer_a,
|
||||
kernel,
|
||||
gauss_width,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
width / 2,
|
||||
shiftar
|
||||
);
|
||||
return null;
|
||||
});
|
||||
|
||||
gaussian_blur_vertical (
|
||||
buffer_b,
|
||||
buffer_a,
|
||||
kernel,
|
||||
gauss_width,
|
||||
width,
|
||||
height,
|
||||
width / 2,
|
||||
width,
|
||||
shiftar
|
||||
);
|
||||
th2.join ();
|
||||
} catch (Error err) {
|
||||
message (err.message);
|
||||
}
|
||||
|
||||
// Save blurred image to original uint8[]
|
||||
for (var i = 0; i < size; i++) {
|
||||
src[i] = (uint8) buffer_a[i];
|
||||
}
|
||||
|
||||
original.mark_dirty ();
|
||||
|
||||
context.set_operator (Operator.SOURCE);
|
||||
context.set_source_surface (original, 0, 0);
|
||||
context.paint ();
|
||||
context.set_operator (Operator.OVER);
|
||||
}
|
||||
|
||||
void gaussian_blur_horizontal (
|
||||
double* src,
|
||||
double* dest,
|
||||
double* kernel,
|
||||
int gauss_width,
|
||||
int width,
|
||||
int height,
|
||||
int start_row,
|
||||
int end_row,
|
||||
int[,] shift
|
||||
) {
|
||||
uint32 cur_pixel = start_row * width * 4;
|
||||
|
||||
for (var y = start_row; y < end_row; y++) {
|
||||
for (var x = 0; x < width; x++) {
|
||||
for (var k = 0; k < gauss_width; k++) {
|
||||
var source = cur_pixel + shift[x, k];
|
||||
|
||||
dest[cur_pixel + 0] += src[source + 0] * kernel[k];
|
||||
dest[cur_pixel + 1] += src[source + 1] * kernel[k];
|
||||
dest[cur_pixel + 2] += src[source + 2] * kernel[k];
|
||||
dest[cur_pixel + 3] += src[source + 3] * kernel[k];
|
||||
}
|
||||
|
||||
cur_pixel += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gaussian_blur_vertical (
|
||||
double* src,
|
||||
double* dest,
|
||||
double* kernel,
|
||||
int gauss_width,
|
||||
int width,
|
||||
int height,
|
||||
int start_col,
|
||||
int end_col,
|
||||
int[,] shift
|
||||
) {
|
||||
uint32 cur_pixel = start_col * 4;
|
||||
|
||||
for (var y = 0; y < height; y++) {
|
||||
for (var x = start_col; x < end_col; x++) {
|
||||
for (var k = 0; k < gauss_width; k++) {
|
||||
var source = cur_pixel + shift[y, k];
|
||||
|
||||
dest[cur_pixel + 0] += src[source + 0] * kernel[k];
|
||||
dest[cur_pixel + 1] += src[source + 1] * kernel[k];
|
||||
dest[cur_pixel + 2] += src[source + 2] * kernel[k];
|
||||
dest[cur_pixel + 3] += src[source + 3] * kernel[k];
|
||||
}
|
||||
|
||||
cur_pixel += 4;
|
||||
}
|
||||
cur_pixel += (width - end_col + start_col) * 4;
|
||||
}
|
||||
}
|
||||
|
||||
static double[] build_gaussian_kernel (int gauss_width) requires (gauss_width % 2 == 1) {
|
||||
var kernel = new double[gauss_width];
|
||||
|
||||
// Maximum value of curve
|
||||
var sd = 255.0;
|
||||
|
||||
// width of curve
|
||||
var range = gauss_width;
|
||||
|
||||
// Average value of curve
|
||||
var mean = range / sd;
|
||||
|
||||
for (var i = 0; i < gauss_width / 2 + 1; i++) {
|
||||
kernel[gauss_width - i - 1] = kernel[i] = Math.pow (
|
||||
Math.sin (((i + 1) * (Math.PI / 2) - mean) / range), 2
|
||||
) * sd;
|
||||
}
|
||||
|
||||
// normalize the values
|
||||
var gauss_sum = 0.0;
|
||||
|
||||
foreach (var d in kernel) {
|
||||
gauss_sum += d;
|
||||
}
|
||||
|
||||
for (var i = 0; i < kernel.length; i++) {
|
||||
kernel[i] = kernel[i] / gauss_sum;
|
||||
}
|
||||
|
||||
return kernel;
|
||||
}
|
||||
}
|
||||
}
|
552
lib/Drawing/Color.vala
Normal file
552
lib/Drawing/Color.vala
Normal file
@ -0,0 +1,552 @@
|
||||
/*
|
||||
* Copyright 2019-2021 elementary, Inc. (https://elementary.io)
|
||||
* Copyright 2011–2013 Robert Dyer
|
||||
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
using Gdk;
|
||||
|
||||
using Granite.Services;
|
||||
|
||||
namespace Gala.Drawing {
|
||||
/**
|
||||
* A class containing an RGBA color and methods for more powerful color manipulation.
|
||||
*/
|
||||
public class Color : GLib.Object, SettingsSerializable {
|
||||
/**
|
||||
* The value of the red channel, with 0 being the lowest value and 1.0 being the greatest value.
|
||||
*/
|
||||
public double R; // vala-lint=naming-convention
|
||||
|
||||
/**
|
||||
* The value of the green channel, with 0 being the lowest value and 1.0 being the greatest value.
|
||||
*/
|
||||
public double G; // vala-lint=naming-convention
|
||||
|
||||
/**
|
||||
* The value of the blue channel, with 0 being the lowest value and 1.0 being the greatest value.
|
||||
*/
|
||||
public double B; // vala-lint=naming-convention
|
||||
|
||||
/**
|
||||
* The value of the alpha channel, with 0 being the lowest value and 1.0 being the greatest value.
|
||||
*/
|
||||
public double A; // vala-lint=naming-convention
|
||||
|
||||
/**
|
||||
* Extracts the alpha value from the integer value
|
||||
* serialized by {@link Gala.Drawing.Color.to_int}.
|
||||
*
|
||||
* @return the alpha channel value as a uint8 ranging from 0 - 255.
|
||||
*/
|
||||
public static uint8 alpha_from_int (int color) {
|
||||
return (uint8)((color >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the red value from the integer value
|
||||
* serialized by {@link Gala.Drawing.Color.to_int}.
|
||||
*
|
||||
* @return the red channel value as a uint8 ranging from 0 - 255.
|
||||
*/
|
||||
public static uint8 red_from_int (int color) {
|
||||
return (uint8)((color >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the green value from the integer value
|
||||
* serialized by {@link Gala.Drawing.Color.to_int}.
|
||||
*
|
||||
* @return the green channel value as a uint8 ranging from 0 - 255.
|
||||
*/
|
||||
public static uint8 green_from_int (int color) {
|
||||
return (uint8)((color >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the blue value from the integer value
|
||||
* serialized by {@link Gala.Drawing.Color.to_int}.
|
||||
*
|
||||
* @return the blue channel value as a uint8 ranging from 0 - 255.
|
||||
*/
|
||||
public static uint8 blue_from_int (int color) {
|
||||
return (uint8)(color & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Gala.Drawing.Color} with the supplied values.
|
||||
*
|
||||
* @param R the value of the red channel as a double
|
||||
* @param G the value of the green channel as a double
|
||||
* @param B the value of the blue channel as a double
|
||||
* @param A the value of the alpha channel as a double
|
||||
*/
|
||||
public Color (double R, double G, double B, double A) { // vala-lint=naming-convention
|
||||
this.R = R;
|
||||
this.G = G;
|
||||
this.B = B;
|
||||
this.A = A;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Gala.Drawing.Color} from a {@link Gdk.RGBA}.
|
||||
*
|
||||
* @param color the {@link Gdk.RGBA}
|
||||
*/
|
||||
public Color.from_rgba (Gdk.RGBA color) {
|
||||
set_from_rgba (color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Gala.Drawing.Color} from a string.
|
||||
*
|
||||
* The string can be either one of:
|
||||
*
|
||||
* * A standard name (Taken from the X11 rgb.txt file).
|
||||
* * A hexadecimal value in the form “#rgb”, “#rrggbb”, “#rrrgggbbb” or ”#rrrrggggbbbb”
|
||||
* * A RGB color in the form “rgb(r,g,b)” (In this case the color will have full opacity)
|
||||
* * A RGBA color in the form “rgba(r,g,b,a)”
|
||||
*
|
||||
* For more details on formatting and how this function works see {@link Gdk.RGBA.parse}
|
||||
*
|
||||
* @param color the string specifying the color
|
||||
*/
|
||||
public Color.from_string (string color) {
|
||||
Gdk.RGBA rgba = Gdk.RGBA ();
|
||||
rgba.parse (color);
|
||||
set_from_rgba (rgba);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Gala.Drawing.Color} from an integer.
|
||||
*
|
||||
* This constructor should be used when deserializing the previously serialized
|
||||
* color by {@link Gala.Drawing.Color.to_int}.
|
||||
*
|
||||
* For more details on what format the color integer representation has, see {@link Gala.Drawing.Color.to_int}.
|
||||
*
|
||||
* If you would like to deserialize the A, R, G and B values from the integer without
|
||||
* creating a new instance of {@link Gala.Drawing.Color}, you can use the available
|
||||
* //*_from_int// static method collection such as {@link Gala.Drawing.Color.alpha_from_int}.
|
||||
*
|
||||
* @param color the integer specyfying the color
|
||||
*/
|
||||
public Color.from_int (int color) {
|
||||
R = (double)red_from_int (color) / (double)uint8.MAX;
|
||||
G = (double)green_from_int (color) / (double)uint8.MAX;
|
||||
B = (double)blue_from_int (color) / (double)uint8.MAX;
|
||||
A = (double)alpha_from_int (color) / (double)uint8.MAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the hue of this color to the supplied one.
|
||||
*
|
||||
* @param hue the hue to change this color to
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color set_hue (double hue) requires (hue >= 0 && hue <= 360) {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
h = hue;
|
||||
hsv_to_rgb (h, s, v, out R, out G, out B);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the saturation of this color to the supplied one.
|
||||
*
|
||||
* @param sat the saturation to change this color to
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color set_sat (double sat) requires (sat >= 0 && sat <= 1) {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
s = sat;
|
||||
hsv_to_rgb (h, s, v, out R, out G, out B);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the value of this color to the supplied one.
|
||||
*
|
||||
* @param val the value to change this color to
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color set_val (double val) requires (val >= 0 && val <= 1) {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
v = val;
|
||||
hsv_to_rgb (h, s, v, out R, out G, out B);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the value of the alpha channel.
|
||||
*
|
||||
* @param alpha the value of the alpha channel
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color set_alpha (double alpha) requires (alpha >= 0 && alpha <= 1) {
|
||||
A = alpha;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value.
|
||||
* @return the hue of this color, as a double value
|
||||
*/
|
||||
public double get_hue () {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value.
|
||||
* @return the saturation of this color, as a double value
|
||||
*/
|
||||
public double get_sat () {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value.
|
||||
*
|
||||
* @return the value of this color, as a double value
|
||||
*/
|
||||
public double get_val () {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the supplied hue value to this color's hue value.
|
||||
*
|
||||
* @param val the hue to add to this color's hue
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color add_hue (double val) {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
h = (((h + val) % 360) + 360) % 360;
|
||||
hsv_to_rgb (h, s, v, out R, out G, out B);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes this color's saturation to the supplied saturation, if it is greater than this color's saturation.
|
||||
*
|
||||
* @param sat the saturation to change this color to
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color set_min_sat (double sat) requires (sat >= 0 && sat <= 1) {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
s = double.max (s, sat);
|
||||
hsv_to_rgb (h, s, v, out R, out G, out B);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes this color's value to the supplied value, if it is greater than this color's value.
|
||||
*
|
||||
* @param val the value to change this color to
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color set_min_value (double val) requires (val >= 0 && val <= 1) {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
v = double.max (v, val);
|
||||
hsv_to_rgb (h, s, v, out R, out G, out B);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes this color's saturation to the supplied saturation, if it is smaller than this color's saturation.
|
||||
*
|
||||
* @param sat the hue to change this color to
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color set_max_sat (double sat) requires (sat >= 0 && sat <= 1) {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
s = double.min (s, sat);
|
||||
hsv_to_rgb (h, s, v, out R, out G, out B);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes this color's value to the supplied value, if it is smaller than this color's value.
|
||||
*
|
||||
* @param val the value to change this color to
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color set_max_val (double val) requires (val >= 0 && val <= 1) {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
v = double.min (v, val);
|
||||
hsv_to_rgb (h, s, v, out R, out G, out B);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies this color's saturation by the supplied amount.
|
||||
*
|
||||
* @param amount the amount to multiply the saturation by
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color multiply_sat (double amount) requires (amount >= 0) {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
s = double.min (1, s * amount);
|
||||
hsv_to_rgb (h, s, v, out R, out G, out B);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Brightens this color's value by the supplied amount.
|
||||
*
|
||||
* @param amount the amount to brighten the value by
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color brighten_val (double amount) requires (amount >= 0 && amount <= 1) {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
v = double.min (1, v + (1 - v) * amount);
|
||||
hsv_to_rgb (h, s, v, out R, out G, out B);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Darkens this color's value by the supplied amount.
|
||||
*
|
||||
* @param amount the amount to darken the value by
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color darken_val (double amount) requires (amount >= 0 && amount <= 1) {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
v = double.max (0, v - (1 - v) * amount);
|
||||
hsv_to_rgb (h, s, v, out R, out G, out B);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Darkens this color's value by the supplied amount * color's saturation.
|
||||
*
|
||||
* @param amount the amount to darken the value by
|
||||
*
|
||||
* @return the new {@link Gala.Drawing.Color}
|
||||
*/
|
||||
public Color darken_by_sat (double amount) requires (amount >= 0 && amount <= 1) {
|
||||
double h, s, v;
|
||||
rgb_to_hsv (R, G, B, out h, out s, out v);
|
||||
v = double.max (0, v - amount * s);
|
||||
hsv_to_rgb (h, s, v, out R, out G, out B);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
void rgb_to_hsv (
|
||||
double r, double g, double b, out double h, out double s, out double v
|
||||
) requires (r >= 0 && r <= 1) requires (g >= 0 && g <= 1) requires (b >= 0 && b <= 1) {
|
||||
var min = double.min (r, double.min (g, b));
|
||||
var max = double.max (r, double.max (g, b));
|
||||
|
||||
v = max;
|
||||
if (v == 0) {
|
||||
h = 0;
|
||||
s = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// normalize value to 1
|
||||
r /= v;
|
||||
g /= v;
|
||||
b /= v;
|
||||
|
||||
min = double.min (r, double.min (g, b));
|
||||
max = double.max (r, double.max (g, b));
|
||||
|
||||
var delta = max - min;
|
||||
s = delta;
|
||||
if (s == 0) {
|
||||
h = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// normalize saturation to 1
|
||||
r = (r - min) / delta;
|
||||
g = (g - min) / delta;
|
||||
b = (b - min) / delta;
|
||||
|
||||
if (max == r) {
|
||||
h = 0 + 60 * (g - b);
|
||||
if (h < 0)
|
||||
h += 360;
|
||||
} else if (max == g) {
|
||||
h = 120 + 60 * (b - r);
|
||||
} else {
|
||||
h = 240 + 60 * (r - g);
|
||||
}
|
||||
}
|
||||
|
||||
void hsv_to_rgb (
|
||||
double h, double s, double v, out double r, out double g, out double b
|
||||
) requires (h >= 0 && h <= 360) requires (s >= 0 && s <= 1) requires (v >= 0 && v <= 1) {
|
||||
r = 0;
|
||||
g = 0;
|
||||
b = 0;
|
||||
|
||||
if (s == 0) {
|
||||
r = v;
|
||||
g = v;
|
||||
b = v;
|
||||
} else {
|
||||
var sec_num = (int) Math.floor (h / 60);
|
||||
var frac_sec = h / 60.0 - sec_num;
|
||||
|
||||
var p = v * (1 - s);
|
||||
var q = v * (1 - s * frac_sec);
|
||||
var t = v * (1 - s * (1 - frac_sec));
|
||||
|
||||
switch (sec_num) {
|
||||
case 0:
|
||||
r = v;
|
||||
g = t;
|
||||
b = p;
|
||||
break;
|
||||
case 1:
|
||||
r = q;
|
||||
g = v;
|
||||
b = p;
|
||||
break;
|
||||
case 2:
|
||||
r = p;
|
||||
g = v;
|
||||
b = t;
|
||||
break;
|
||||
case 3:
|
||||
r = p;
|
||||
g = q;
|
||||
b = v;
|
||||
break;
|
||||
case 4:
|
||||
r = t;
|
||||
g = p;
|
||||
b = v;
|
||||
break;
|
||||
case 5:
|
||||
r = v;
|
||||
g = p;
|
||||
b = q;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public string settings_serialize () {
|
||||
return "%d;;%d;;%d;;%d".printf ((int) (R * uint8.MAX),
|
||||
(int) (G * uint8.MAX),
|
||||
(int) (B * uint8.MAX),
|
||||
(int) (A * uint8.MAX));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void settings_deserialize (string s) {
|
||||
var parts = s.split (";;");
|
||||
|
||||
R = double.min (uint8.MAX, double.max (0, int.parse (parts [0]))) / uint8.MAX;
|
||||
G = double.min (uint8.MAX, double.max (0, int.parse (parts [1]))) / uint8.MAX;
|
||||
B = double.min (uint8.MAX, double.max (0, int.parse (parts [2]))) / uint8.MAX;
|
||||
A = double.min (uint8.MAX, double.max (0, int.parse (parts [3]))) / uint8.MAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a textual specification of this in the form `rgb (r, g, b)` or `rgba (r, g, b, a)`,
|
||||
* where “r”, “g”, “b” and “a” represent the red, green, blue and alpha values respectively.
|
||||
*
|
||||
* r, g, and b are represented as integers in the range 0 to 255, and a is represented as
|
||||
* floating point value in the range 0 to 1.
|
||||
*
|
||||
* Note: that this string representation may lose some precision, since r, g and b are represented
|
||||
* as 8-bit integers. If this is a concern, you should use a different representation.
|
||||
*
|
||||
* This returns the same string as a {@link Gdk.RGBA} would return in {@link Gdk.RGBA.to_string}
|
||||
*
|
||||
* @return the text string
|
||||
*/
|
||||
public string to_string () {
|
||||
Gdk.RGBA rgba = {(float)R, (float)G, (float)B, (float)A};
|
||||
return rgba.to_string ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this to a 32 bit integer.
|
||||
*
|
||||
* This function can be useful for serializing the color so that it can be stored
|
||||
* and retrieved easily with hash tables and lists.
|
||||
*
|
||||
* The returned integer will contain the four channels
|
||||
* that define the {@link Gala.Drawing.Color} class: alpha, red, green and blue.
|
||||
*
|
||||
* Each channel is represented by 8 bits.
|
||||
* The first 8 bits of the integer conatin the alpha channel while all other 24 bits represent
|
||||
* red, green and blue channels respectively.
|
||||
*
|
||||
* The format written as a string would look like this:
|
||||
*
|
||||
* //AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB//
|
||||
*
|
||||
* where //A// is one bit of alpha chnnel, //R// of red channel, //G// of green channel and //B// of blue channel.
|
||||
*
|
||||
* @return a 32 bit integer representing this
|
||||
*/
|
||||
public int to_int () {
|
||||
uint8 red = (uint8)(R * uint8.MAX);
|
||||
uint8 green = (uint8)(G * uint8.MAX);
|
||||
uint8 blue = (uint8)(B * uint8.MAX);
|
||||
uint8 alpha = (uint8)(A * uint8.MAX);
|
||||
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
private void set_from_rgba (Gdk.RGBA color) {
|
||||
R = color.red;
|
||||
G = color.green;
|
||||
B = color.blue;
|
||||
A = color.alpha;
|
||||
}
|
||||
}
|
||||
}
|
81
lib/Drawing/Utilities.vala
Normal file
81
lib/Drawing/Utilities.vala
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2019 elementary, Inc. (https://elementary.io)
|
||||
* Copyright 2011-2013 Maxwell Barvian <maxwell@elementaryos.org>
|
||||
* Copyright Robert Dyer
|
||||
* SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* A utility class for frequently-performed drawing operations.
|
||||
*/
|
||||
public class Gala.Drawing.Utilities : GLib.Object {
|
||||
|
||||
/**
|
||||
* Adds a closed sub-path rounded rectangle of the given size and border radius to the current path
|
||||
* at position (x, y) in user-space coordinates.
|
||||
*
|
||||
* @param cr a {@link Cairo.Context}
|
||||
* @param x the X coordinate of the top left corner of the rounded rectangle
|
||||
* @param y the Y coordinate to the top left corner of the rounded rectangle
|
||||
* @param width the width of the rounded rectangle
|
||||
* @param height the height of the rounded rectangle
|
||||
* @param radius the border radius of the rounded rectangle
|
||||
*/
|
||||
public static void cairo_rounded_rectangle (
|
||||
Cairo.Context cr,
|
||||
double x,
|
||||
double y,
|
||||
double width,
|
||||
double height,
|
||||
double radius
|
||||
) {
|
||||
cr.move_to (x + radius, y);
|
||||
cr.arc (x + width - radius, y + radius, radius, Math.PI * 1.5, Math.PI * 2);
|
||||
cr.arc (x + width - radius, y + height - radius, radius, 0, Math.PI * 0.5);
|
||||
cr.arc (x + radius, y + height - radius, radius, Math.PI * 0.5, Math.PI);
|
||||
cr.arc (x + radius, y + radius, radius, Math.PI, Math.PI * 1.5);
|
||||
cr.close_path ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Averages the colors in the {@link Gdk.Pixbuf} and returns it.
|
||||
*
|
||||
* @param source the {@link Gdk.Pixbuf}
|
||||
*
|
||||
* @return the {@link Gala.Drawing.Color} containing the averaged color
|
||||
*/
|
||||
public static Drawing.Color average_color (Gdk.Pixbuf source) {
|
||||
var r_total = 0.0;
|
||||
var g_total = 0.0;
|
||||
var b_total = 0.0;
|
||||
|
||||
uint8* data_ptr = source.get_pixels ();
|
||||
double pixels = source.height * source.rowstride / source.n_channels;
|
||||
|
||||
for (var i = 0; i < pixels; i++) {
|
||||
var r = data_ptr [0];
|
||||
var g = data_ptr [1];
|
||||
var b = data_ptr [2];
|
||||
|
||||
var max = (uint8) double.max (r, double.max (g, b));
|
||||
var min = (uint8) double.min (r, double.min (g, b));
|
||||
double delta = max - min;
|
||||
|
||||
var sat = delta == 0 ? 0.0 : delta / max;
|
||||
var score = 0.2 + 0.8 * sat;
|
||||
|
||||
r_total += r * score;
|
||||
g_total += g * score;
|
||||
b_total += b * score;
|
||||
|
||||
data_ptr += source.n_channels;
|
||||
}
|
||||
|
||||
return new Drawing.Color (
|
||||
r_total / uint8.MAX / pixels,
|
||||
g_total / uint8.MAX / pixels,
|
||||
b_total / uint8.MAX / pixels,
|
||||
1
|
||||
).set_val (0.8).multiply_sat (1.15);
|
||||
}
|
||||
}
|
@ -3,6 +3,9 @@ gala_lib_sources = files(
|
||||
'AppCache.vala',
|
||||
'Constants.vala',
|
||||
'DragDropAction.vala',
|
||||
'Drawing/BufferSurface.vala',
|
||||
'Drawing/Color.vala',
|
||||
'Drawing/Utilities.vala',
|
||||
'Plugin.vala',
|
||||
'Utils.vala',
|
||||
'WindowIcon.vala',
|
||||
|
@ -138,7 +138,7 @@ public class Gala.Plugins.MaskCorners.Main : Gala.Plugin {
|
||||
}
|
||||
|
||||
private bool draw_cornermask (Cairo.Context context) {
|
||||
var buffer = new Granite.Drawing.BufferSurface (corner_radius, corner_radius);
|
||||
var buffer = new Drawing.BufferSurface (corner_radius, corner_radius);
|
||||
var buffer_context = buffer.context;
|
||||
|
||||
buffer_context.arc (corner_radius, corner_radius, corner_radius, Math.PI, 1.5 * Math.PI);
|
||||
|
@ -77,7 +77,7 @@ namespace Gala.Plugins.PIP {
|
||||
}
|
||||
|
||||
// fill a new texture for this size
|
||||
var buffer = new Granite.Drawing.BufferSurface (width, height);
|
||||
var buffer = new 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);
|
||||
|
@ -155,7 +155,7 @@ public class Gala.AccentColorManager : Object {
|
||||
}
|
||||
|
||||
private NamedColor? get_accent_color (ColorExtractor color_extractor) {
|
||||
var palette = new Gee.ArrayList<Granite.Drawing.Color> ();
|
||||
var palette = new Gee.ArrayList<Drawing.Color> ();
|
||||
for (int i = 0; i < theme_colors.length; i++) {
|
||||
palette.add (theme_colors[i].color);
|
||||
}
|
||||
@ -180,7 +180,7 @@ public class Gala.AccentColorManager : Object {
|
||||
}
|
||||
|
||||
private NamedColor? get_accent_color_based_on_primary_color (string primary_color) {
|
||||
var granite_primary_color = new Granite.Drawing.Color.from_string (primary_color);
|
||||
var granite_primary_color = new Drawing.Color.from_string (primary_color);
|
||||
var color_extractor = new ColorExtractor.from_primary_color (granite_primary_color);
|
||||
|
||||
return get_accent_color (color_extractor);
|
||||
|
@ -23,9 +23,9 @@ public class Gala.ColorExtractor : Object {
|
||||
private const double PERCENTAGE_SAMPLE_PIXELS = 0.01;
|
||||
|
||||
public Gdk.Pixbuf? pixbuf { get; construct set; }
|
||||
public Granite.Drawing.Color? primary_color { get; construct set; }
|
||||
public Drawing.Color? primary_color { get; construct set; }
|
||||
|
||||
private Gee.List<Granite.Drawing.Color> pixels;
|
||||
private Gee.List<Drawing.Color> pixels;
|
||||
|
||||
public ColorExtractor.from_pixbuf (Gdk.Pixbuf pixbuf) {
|
||||
Object (pixbuf: pixbuf);
|
||||
@ -33,14 +33,14 @@ public class Gala.ColorExtractor : Object {
|
||||
pixels = convert_pixels_to_rgb (pixbuf.get_pixels_with_length (), pixbuf.has_alpha);
|
||||
}
|
||||
|
||||
public ColorExtractor.from_primary_color (Granite.Drawing.Color primary_color) {
|
||||
public ColorExtractor.from_primary_color (Drawing.Color primary_color) {
|
||||
Object (primary_color: primary_color);
|
||||
|
||||
pixels = new Gee.ArrayList<Granite.Drawing.Color> ();
|
||||
pixels = new Gee.ArrayList<Drawing.Color> ();
|
||||
pixels.add (primary_color);
|
||||
}
|
||||
|
||||
public int get_dominant_color_index (Gee.List<Granite.Drawing.Color> palette) {
|
||||
public int get_dominant_color_index (Gee.List<Drawing.Color> palette) {
|
||||
int index = 0;
|
||||
var matches = new double[palette.size];
|
||||
|
||||
@ -75,8 +75,8 @@ public class Gala.ColorExtractor : Object {
|
||||
return index;
|
||||
}
|
||||
|
||||
private Gee.ArrayList<Granite.Drawing.Color> convert_pixels_to_rgb (uint8[] pixels, bool has_alpha) {
|
||||
var list = new Gee.ArrayList<Granite.Drawing.Color> ();
|
||||
private Gee.ArrayList<Drawing.Color> convert_pixels_to_rgb (uint8[] pixels, bool has_alpha) {
|
||||
var list = new Gee.ArrayList<Drawing.Color> ();
|
||||
|
||||
int factor = 3 + (int) has_alpha;
|
||||
int step_size = (int) (pixels.length / factor * PERCENTAGE_SAMPLE_PIXELS);
|
||||
@ -87,7 +87,7 @@ public class Gala.ColorExtractor : Object {
|
||||
double green = pixels[offset + 1] / 255.0;
|
||||
double blue = pixels[offset + 2] / 255.0;
|
||||
|
||||
list.add (new Granite.Drawing.Color (red, green, blue, 0.0));
|
||||
list.add (new Drawing.Color (red, green, blue, 0.0));
|
||||
}
|
||||
|
||||
return list;
|
||||
|
@ -22,7 +22,7 @@
|
||||
public class Gala.NamedColor : Object {
|
||||
public string name { get; construct set; }
|
||||
public string theme { get; construct set; }
|
||||
public Granite.Drawing.Color color { get; construct set; }
|
||||
public Drawing.Color color { get; construct set; }
|
||||
|
||||
public NamedColor (string name, string theme) {
|
||||
Object (
|
||||
|
@ -356,7 +356,7 @@ namespace Gala {
|
||||
);
|
||||
}
|
||||
|
||||
public static Granite.Drawing.Color get_accent_color_by_theme_name (string theme_name) {
|
||||
public static Drawing.Color get_accent_color_by_theme_name (string theme_name) {
|
||||
var label_widget_path = new Gtk.WidgetPath ();
|
||||
label_widget_path.append_type (GLib.Type.from_name ("label"));
|
||||
label_widget_path.iter_set_object_name (-1, "selection");
|
||||
@ -371,7 +371,7 @@ namespace Gala {
|
||||
Gtk.StateFlags.NORMAL
|
||||
);
|
||||
|
||||
return new Granite.Drawing.Color.from_rgba (rgba);
|
||||
return new Drawing.Color.from_rgba (rgba);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -357,7 +357,7 @@ namespace Gala {
|
||||
}
|
||||
|
||||
// more than one => we need a folder
|
||||
Granite.Drawing.Utilities.cairo_rounded_rectangle (
|
||||
Drawing.Utilities.cairo_rounded_rectangle (
|
||||
cr,
|
||||
0.5 * scale,
|
||||
0.5 * scale,
|
||||
@ -384,7 +384,7 @@ namespace Gala {
|
||||
cr.set_source (grad);
|
||||
cr.stroke ();
|
||||
|
||||
Granite.Drawing.Utilities.cairo_rounded_rectangle (
|
||||
Drawing.Utilities.cairo_rounded_rectangle (
|
||||
cr,
|
||||
1.5 * scale,
|
||||
1.5 * scale,
|
||||
@ -412,7 +412,7 @@ namespace Gala {
|
||||
|| workspace_index != manager.get_n_workspaces () - 1)
|
||||
return false;
|
||||
|
||||
var buffer = new Granite.Drawing.BufferSurface (SIZE * scale, SIZE * scale);
|
||||
var buffer = new Drawing.BufferSurface (SIZE * scale, SIZE * scale);
|
||||
var offset = (SIZE * scale) / 2 - (PLUS_WIDTH * scale) / 2;
|
||||
|
||||
buffer.context.rectangle (PLUS_WIDTH / 2 * scale - PLUS_SIZE / 2 * scale + 0.5 + offset,
|
||||
|
@ -80,7 +80,7 @@ namespace Gala {
|
||||
cr.paint ();
|
||||
cr.restore ();
|
||||
|
||||
Granite.Drawing.Utilities.cairo_rounded_rectangle (cr, 0, 0, width, height, border_radius);
|
||||
Drawing.Utilities.cairo_rounded_rectangle (cr, 0, 0, width, height, border_radius);
|
||||
cr.set_source_rgba (color.red, color.green, color.blue, COLOR_OPACITY);
|
||||
cr.fill ();
|
||||
|
||||
|
@ -153,7 +153,7 @@ namespace Gala {
|
||||
|
||||
// draw rect
|
||||
Clutter.cairo_set_source_color (ctx, accent_color);
|
||||
Granite.Drawing.Utilities.cairo_rounded_rectangle (ctx, 0, 0, width, height, rect_radius);
|
||||
Drawing.Utilities.cairo_rounded_rectangle (ctx, 0, 0, width, height, rect_radius);
|
||||
ctx.set_operator (Cairo.Operator.SOURCE);
|
||||
ctx.fill ();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user