Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace default gtk color picker #1025

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
05a755b
Add color picker dialog
potatoes1286 Oct 6, 2024
17d5495
Improvements, formatting
potatoes1286 Oct 7, 2024
e51e670
formatting
potatoes1286 Oct 7, 2024
54a9472
remove debug writelines
potatoes1286 Oct 7, 2024
11c11ae
Remove collection literals
potatoes1286 Oct 7, 2024
2c4b49d
Add small mode; add ability to draw while picker active
potatoes1286 Oct 7, 2024
f0bf351
dotnet format
potatoes1286 Oct 7, 2024
81a1ef4
fix margin setting
potatoes1286 Oct 7, 2024
2222325
Fixes, improvements to ColorExtensions.cs
potatoes1286 Oct 11, 2024
e6394d8
Remove RgbColor; Change HsvColor to double
potatoes1286 Oct 11, 2024
317b07d
dotnet format
potatoes1286 Oct 11, 2024
7d8d0d1
Shift ColorExtensions.cs to CairoExtensions.cs
potatoes1286 Oct 11, 2024
ddd49f5
Fix bugs; add more methods to HsvColor.cs
potatoes1286 Oct 11, 2024
6cc8e6b
update test
potatoes1286 Oct 13, 2024
8ff7d65
add ColorTests.cs; improve CompareImages warning
potatoes1286 Oct 13, 2024
a9e0395
fixes; add opacity on unfocus
potatoes1286 Oct 13, 2024
bc1a4b5
improvements, formatting to HsvColor.cs, CairoExtensions.cs
potatoes1286 Oct 13, 2024
df12a8a
Merge branch 'master' into picker-2
potatoes1286 Oct 13, 2024
a49a978
Fix color picker always opening primary color; never secondary
potatoes1286 Nov 10, 2024
916d1e3
simplify handling of palette change; modify method inputs
potatoes1286 Nov 10, 2024
f3878ba
Streamline HsvColor
potatoes1286 Nov 10, 2024
8320855
Remove unnecessary code
potatoes1286 Nov 10, 2024
879330b
Merge remote-tracking branch 'origin/picker-2' into picker-2
potatoes1286 Nov 10, 2024
dc0bfd7
change vars to private/properties; change colorpickerslider constructor
potatoes1286 Dec 14, 2024
917406e
misc cleanup
potatoes1286 Dec 14, 2024
b5c4f69
merge WidgetExtensions into GtkExtensions
potatoes1286 Dec 14, 2024
1b57df9
dotnet format
potatoes1286 Dec 14, 2024
9c386b7
add icon, tooltip, shortcut, misc
potatoes1286 Dec 14, 2024
75007ac
remove erroring using
potatoes1286 Dec 14, 2024
a069ff4
add the actual icon
potatoes1286 Dec 14, 2024
2a775ec
improve color surface drawing
potatoes1286 Dec 19, 2024
f8b9ace
merge setup into constructor; remove add alpha checkbox
potatoes1286 Dec 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 273 additions & 0 deletions Pinta.Core/Extensions/ColorExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
using System;
using System.Globalization;
using Cairo;

namespace Pinta.Core.Extensions;

public static class ColorExtensions
{
/// <summary>
/// Returns the color value as a string in hex color format.
/// </summary>
/// <param name="addAlpha">If false, returns in format "RRGGBB" (Alpha will not affect result).<br/>
/// Otherwise, returns in format "RRGGBBAA".</param>
public static String ToHex (this Color c, bool addAlpha = true)
{
int r = Convert.ToInt32 (c.R * 255.0);
int g = Convert.ToInt32 (c.G * 255.0);
int b = Convert.ToInt32 (c.B * 255.0);
int a = Convert.ToInt32 (c.A * 255.0);

if (addAlpha)
return $"{r:X2}{g:X2}{b:X2}{a:X2}";
else
return $"{r:X2}{g:X2}{b:X2}";
}

/// <summary>
/// Returns a color from an RGBA hex color. Accepts the following formats:<br/>
/// RRGGBBAA<br/>
/// RRGGBB<br/>
/// RGB (Expands to RRGGBB)<br/>
/// RGBA (Expands to RRGGBBAA)<br/>
/// Will accept leading #.
/// </summary>
/// <param name="hex">Hex color as a string.</param>
/// <returns>Resulting color. If null, the method could not parse it.</returns>
public static Color? FromHex (String hex)
{
if (hex[0] == '#') {
Console.WriteLine (hex);
hex = hex.Remove (0, 1);
Console.WriteLine (hex);
}

// handle shorthand hex
if (hex.Length == 3)
hex = $"{hex[0]}{hex[0]}{hex[1]}{hex[1]}{hex[2]}{hex[2]}";
if (hex.Length == 4)
hex = $"{hex[0]}{hex[0]}{hex[1]}{hex[1]}{hex[2]}{hex[2]}{hex[3]}{hex[3]}";

if (hex.Length != 6 && hex.Length != 8)
return null;
try {
int r = int.Parse (hex.Substring (0, 2), NumberStyles.HexNumber);
int g = int.Parse (hex.Substring (2, 2), NumberStyles.HexNumber);
int b = int.Parse (hex.Substring (4, 2), NumberStyles.HexNumber);
int a = 255;
if (hex.Length > 6)
a = int.Parse (hex.Substring (5, 2), NumberStyles.HexNumber);

Console.WriteLine ($"{r}, {g}, {b}, {a}");

return new Color ().SetRgba (r / 255.0, g / 255.0, b / 255.0, a / 255.0);
} catch {
return null;
}
}

/// <summary>
/// Hue, Saturation, Value description of a color.<br/>
/// Hue varies from 0 - 360.<br/>
/// Saturation and value varies from 0 - 1.
/// </summary>
public struct Hsv
{
public readonly double h;
public readonly double s;
public readonly double v;

public Hsv (double h, double s, double v)
{
this.h = h;
this.s = s;
this.v = v;
}
}

/// <summary>
/// Copied from RgbColor.ToHsv<br/>
/// Returns the Cairo color in HSV value.
/// </summary>
/// <returns>HSV struct.
/// Hue varies from 0 - 360.<br/>
/// Saturation and value varies from 0 - 1.
/// </returns>
public static Hsv GetHsv (this Color c)
{
// In this function, R, G, and B values must be scaled
// to be between 0 and 1.
// HsvColor.Hue will be a value between 0 and 360, and
// HsvColor.Saturation and value are between 0 and 1.

double h, s, v;

double min = Math.Min (Math.Min (c.R, c.G), c.B);
double max = Math.Max (Math.Max (c.R, c.G), c.B);

double delta = max - min;

if (max == 0 || delta == 0) {
// R, G, and B must be 0, or all the same.
// In this case, S is 0, and H is undefined.
// Using H = 0 is as good as any...
s = 0;
h = 0;
v = max;
} else {
s = delta / max;
if (c.R == max) {
// Between Yellow and Magenta
h = (c.G - c.B) / delta;
} else if (c.G == max) {
// Between Cyan and Yellow
h = 2 + (c.B - c.R) / delta;
} else {
// Between Magenta and Cyan
h = 4 + (c.R - c.G) / delta;
}
v = max;
}
// Scale h to be between 0 and 360.
// This may require adding 360, if the value
// is negative.
h *= 60;

if (h < 0) {
h += 360;
}

// Scale to the requirements of this
// application. All values are between 0 and 255.
return new Hsv (h, s, v);
}

/// <summary>
/// Returns a copy of the original color, replacing provided RGBA components.
/// </summary>
/// <param name="r">Red component, 0 - 1</param>
/// <param name="g">Green component, 0 - 1</param>
/// <param name="b">Blue component, 0 - 1</param>
/// <param name="a">Alpha component, 0 - 1</param>
public static Color SetRgba (this Color c, double? r = null, double? g = null, double? b = null, double? a = null)
{
return new Color (r ?? c.R, g ?? c.G, b ?? c.B, a ?? c.A);
}

/// <summary>
/// Returns a copy of the original color, replacing provided HSV components.
/// </summary>
/// <param name="hue">Hue component, 0 - 360</param>
/// <param name="sat">Saturation component, 0 - 1</param>
/// <param name="value">Value component, 0 - 1</param>
/// <param name="alpha">Alpha component, 0 - 1</param>
public static Color SetHsv (this Color c, double? hue = null, double? sat = null, double? value = null, double? alpha = null)
{
var hsv = c.GetHsv ();

double h = hue ?? hsv.h;
double s = sat ?? hsv.s;
double v = value ?? hsv.v;
double a = alpha ?? c.A;

return FromHsv (h, s, v, a);
}

/// <summary>
/// Copied from HsvColor.ToRgb<br/>
/// Returns a Cairo color using the given HSV values.
/// </summary>
/// <param name="hue">Hue component, 0 - 360</param>
/// <param name="sat">Saturation component, 0 - 1</param>
/// <param name="value">Value component, 0 - 1</param>
/// <param name="alpha">Alpha component, 0 - 1</param>
public static Color FromHsv (double hue, double sat, double value, double alpha = 1)
{
double h = hue;
double s = sat;
double v = value;

// Stupid hack!
// If v or s is set to 0, it results in data loss for hue / sat. So we force it to be slightly above zero.
if (v == 0)
v = 0.0001;
if (s == 0)
s = 0.0001;

// HsvColor contains values scaled as in the color wheel.
// Scale Hue to be between 0 and 360. Saturation
// and value scale to be between 0 and 1.
h %= 360.0;

double r = 0;
double g = 0;
double b = 0;

if (s == 0) {
// If s is 0, all colors are the same.
// This is some flavor of gray.
r = v;
g = v;
b = v;
} else {
// The color wheel consists of 6 sectors.
// Figure out which sector you're in.
double sectorPos = h / 60;
int sectorNumber = (int) (Math.Floor (sectorPos));

// get the fractional part of the sector.
// That is, how many degrees into the sector
// are you?
double fractionalSector = sectorPos - sectorNumber;

// Calculate values for the three axes
// of the color.
double p = v * (1 - s);
double q = v * (1 - (s * fractionalSector));
double t = v * (1 - (s * (1 - fractionalSector)));

// Assign the fractional colors to r, g, and b
// based on the sector the angle is in.
switch (sectorNumber) {
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;
}
}
// return an RgbColor structure, with values scaled
// to be between 0 and 255.
return new Color (r, g, b, alpha);
}
}
23 changes: 23 additions & 0 deletions Pinta.Core/Extensions/WidgetExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Gtk;

namespace Pinta.Core.Extensions;

public static class WidgetExtensions
{
/// <summary>
/// Checks whether the mousePos (which is relative to topwidget) is within the area and returns its relative position to the area.
/// </summary>
/// <param name="widget">Drawing area where returns true if mouse inside.</param>
/// <param name="topWidget">The top widget. This is what the mouse position is relative to.</param>
/// <param name="mousePos">Position of the mouse relative to the top widget, usually obtained from Gtk.GestureClick</param>
/// <param name="relPos">Position of the mouse relative to the drawing area.</param>
/// <returns>Whether or not mouse position is within the drawing area.</returns>
public static bool IsMouseInDrawingArea (this Widget widget, Widget topWidget, PointD mousePos, out PointD relPos)
{
widget.TranslateCoordinates (topWidget, 0, 0, out double x, out double y);
relPos = new PointD ((mousePos.X - x), (mousePos.Y - y));
if (relPos.X >= 0 && relPos.X <= widget.GetWidth () && relPos.Y >= 0 && relPos.Y <= widget.GetHeight ())
return true;
return false;
}
}
Loading
Loading