Initial commit: application ColorPicker (palette, pipette ecran, couleur de dossier)

This commit is contained in:
Florian
2026-09-03 21:48:28 +02:00
commit 44c51398b8
13 changed files with 956 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*.exe
*.pdb
*.obj

45
LISEZMOI.txt Normal file
View File

@@ -0,0 +1,45 @@
ColorPicker - Palette de couleurs & couleur de dossier
========================================================
UTILISATION
-----------
Aucune installation requise : lancez simplement ColorPicker.exe.
Compatible Windows 10 et 11, 32 et 64 bits (necessite le .NET Framework 4.8,
present par defaut sur ces versions de Windows).
FONCTIONNALITES
----------------
1. Palette de couleurs complete (a gauche) : cliquez sur une case pour la
selectionner.
2. Couleur personnalisee / en dehors de la palette :
- Bouton "Preleve une couleur a l'ecran" : le curseur se transforme en
pipette, deplacez-le n'importe ou sur l'ecran (meme en dehors de
l'application) et cliquez pour capturer la couleur exacte du pixel
survole. Echap pour annuler.
- Bouton "Couleur personnalisee..." : ouvre le selecteur Windows complet
(roue de couleurs / valeurs RVB ou TSL precises).
- Vous pouvez aussi taper directement un code hexadecimal dans le champ
prevu puis valider avec Entree ou le bouton "OK".
3. Codes en temps reel : le code hexadecimal (#RRGGBB) et le code RGB
(R, G, B) s'affichent et se mettent a jour instantanement. Chaque champ
a son propre bouton "Copier" pour coller le code dans PowerShell, cmd,
Word, Excel, ou tout autre logiciel.
4. Couleur d'un dossier Windows :
- "Parcourir..." pour choisir le dossier a colorer.
- "Appliquer la couleur au dossier" applique la couleur actuellement
selectionnee a l'icone du dossier (icone generee et un fichier
desktop.ini sont crees dans le dossier, en fichiers caches/systeme).
- "Restaurer la couleur par defaut" supprime ces fichiers et redonne au
dossier son icone Windows standard.
- Si l'icone ne se met pas a jour immediatement dans l'Explorateur,
fermez puis rouvrez la fenetre du dossier (ou appuyez sur F5).
RECOMPILER LE PROJET
---------------------
Le code source complet se trouve dans le dossier src\. Pour recompiler,
executez build.bat (utilise le compilateur C# fourni avec le
.NET Framework, deja present sur Windows - aucun outil supplementaire
n'est necessaire).

26
app.manifest Normal file
View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" name="ColorPicker.App"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor, System</dpiAwareness>
</windowsSettings>
</application>
</assembly>

18
build.bat Normal file
View File

@@ -0,0 +1,18 @@
@echo off
setlocal
set CSC="%WINDIR%\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
if not exist %CSC% set CSC="%WINDIR%\Microsoft.NET\Framework\v4.0.30319\csc.exe"
%CSC% /nologo /platform:anycpu /target:winexe /out:ColorPicker.exe /win32manifest:app.manifest ^
/reference:System.dll /reference:System.Core.dll /reference:System.Drawing.dll /reference:System.Windows.Forms.dll ^
src\Program.cs src\MainForm.cs src\Palette.cs src\ScreenColorPickerForm.cs src\MagnifierForm.cs src\FolderColorizer.cs src\IconWriter.cs
if %ERRORLEVEL% NEQ 0 (
echo.
echo *** Echec de la compilation ***
exit /b 1
)
echo.
echo Compilation reussie : ColorPicker.exe
endlocal

189
src/FolderColorizer.cs Normal file
View File

@@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ColorPickerApp
{
/// <summary>Applique ou restaure une icone de dossier coloree via desktop.ini (aucune API Windows
/// ne permet de "teinter" nativement l'icone systeme : on genere une icone de remplacement).</summary>
internal static class FolderColorizer
{
private const string IconFileName = ".colorpicker_folder.ico";
private const string BackupSuffix = ".colorpicker_backup";
[DllImport("shell32.dll")]
private static extern void SHChangeNotify(int wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
private const int SHCNE_ASSOCCHANGED = 0x08000000;
private const int SHCNF_IDLIST = 0x0000;
public static void ApplyColor(string folderPath, Color color)
{
if (!Directory.Exists(folderPath))
throw new DirectoryNotFoundException(folderPath);
string iconPath = Path.Combine(folderPath, IconFileName);
string desktopIniPath = Path.Combine(folderPath, "desktop.ini");
var images = new List<Bitmap>
{
DrawFolderIcon(color, 16),
DrawFolderIcon(color, 32),
DrawFolderIcon(color, 48),
DrawFolderIcon(color, 256)
};
ClearReadOnlyAndHidden(iconPath);
IconWriter.WriteIcon(iconPath, images);
foreach (var b in images) b.Dispose();
File.SetAttributes(iconPath, FileAttributes.Hidden | FileAttributes.System);
// Conserver un desktop.ini existant (personnalise par l'utilisateur) si present et non genere par nous
if (File.Exists(desktopIniPath) && !IsOurDesktopIni(desktopIniPath))
{
File.Copy(desktopIniPath, desktopIniPath + BackupSuffix, true);
}
ClearReadOnlyAndHidden(desktopIniPath);
File.WriteAllText(desktopIniPath,
"[.ShellClassInfo]" + Environment.NewLine +
"IconResource=" + IconFileName + ",0" + Environment.NewLine +
"IconFile=" + IconFileName + Environment.NewLine +
"IconIndex=0" + Environment.NewLine +
"; genere par ColorPicker" + Environment.NewLine);
File.SetAttributes(desktopIniPath, FileAttributes.Hidden | FileAttributes.System);
var dirInfo = new DirectoryInfo(folderPath);
dirInfo.Attributes |= FileAttributes.ReadOnly;
NotifyShell(folderPath);
}
public static void RestoreDefault(string folderPath)
{
if (!Directory.Exists(folderPath))
throw new DirectoryNotFoundException(folderPath);
string iconPath = Path.Combine(folderPath, IconFileName);
string desktopIniPath = Path.Combine(folderPath, "desktop.ini");
string backupPath = desktopIniPath + BackupSuffix;
if (File.Exists(iconPath))
{
ClearReadOnlyAndHidden(iconPath);
File.Delete(iconPath);
}
if (File.Exists(desktopIniPath) && IsOurDesktopIni(desktopIniPath))
{
ClearReadOnlyAndHidden(desktopIniPath);
File.Delete(desktopIniPath);
if (File.Exists(backupPath))
{
File.Copy(backupPath, desktopIniPath, true);
File.SetAttributes(desktopIniPath, FileAttributes.Hidden | FileAttributes.System);
File.Delete(backupPath);
}
}
var dirInfo = new DirectoryInfo(folderPath);
dirInfo.Attributes &= ~FileAttributes.ReadOnly;
NotifyShell(folderPath);
}
public static bool HasCustomColor(string folderPath)
{
string desktopIniPath = Path.Combine(folderPath, "desktop.ini");
return File.Exists(desktopIniPath) && IsOurDesktopIni(desktopIniPath);
}
private static bool IsOurDesktopIni(string desktopIniPath)
{
try
{
string content = File.ReadAllText(desktopIniPath);
return content.IndexOf(IconFileName, StringComparison.OrdinalIgnoreCase) >= 0;
}
catch
{
return false;
}
}
private static void ClearReadOnlyAndHidden(string filePath)
{
if (File.Exists(filePath))
{
File.SetAttributes(filePath, FileAttributes.Normal);
}
}
private static void NotifyShell(string folderPath)
{
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero);
}
private static Bitmap DrawFolderIcon(Color color, int size)
{
var bmp = new Bitmap(size, size, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (var g = Graphics.FromImage(bmp))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.Clear(Color.Transparent);
float w = size, h = size;
Color body = color;
Color tab = ControlPaint.Light(color, 0.15f);
Color outline = ControlPaint.Dark(color, 0.25f);
float tabW = w * 0.45f, tabH = h * 0.18f;
float tabX = w * 0.06f, tabY = h * 0.18f;
var tabRect = new RectangleF(tabX, tabY, tabW, tabH);
float bodyX = w * 0.06f, bodyY = h * 0.30f;
float bodyW = w * 0.88f, bodyH = h * 0.56f;
var bodyRect = new RectangleF(bodyX, bodyY, bodyW, bodyH);
float radius = Math.Max(1f, size * 0.06f);
using (var tabPath = RoundedRect(tabRect, radius))
using (var tabBrush = new SolidBrush(tab))
{
g.FillPath(tabBrush, tabPath);
}
using (var bodyPath = RoundedRect(bodyRect, radius))
{
using (var bodyBrush = new LinearGradientBrush(bodyRect, ControlPaint.Light(body, 0.05f), ControlPaint.Dark(body, 0.05f), LinearGradientMode.Vertical))
{
g.FillPath(bodyBrush, bodyPath);
}
using (var pen = new Pen(outline, Math.Max(1f, size / 32f)))
{
g.DrawPath(pen, bodyPath);
}
}
}
return bmp;
}
private static GraphicsPath RoundedRect(RectangleF rect, float radius)
{
var path = new GraphicsPath();
float d = radius * 2;
path.AddArc(rect.X, rect.Y, d, d, 180, 90);
path.AddArc(rect.Right - d, rect.Y, d, d, 270, 90);
path.AddArc(rect.Right - d, rect.Bottom - d, d, d, 0, 90);
path.AddArc(rect.X, rect.Bottom - d, d, d, 90, 90);
path.CloseFigure();
return path;
}
}
}

52
src/IconWriter.cs Normal file
View File

@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace ColorPickerApp
{
/// <summary>Ecrit un fichier .ico multi-resolution a partir de bitmaps PNG (format ICO "Vista").</summary>
internal static class IconWriter
{
public static void WriteIcon(string path, IList<Bitmap> images)
{
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
using (var writer = new BinaryWriter(fs))
{
writer.Write((ushort)0); // reserved
writer.Write((ushort)1); // type = icon
writer.Write((ushort)images.Count);
var pngBlocks = new byte[images.Count][];
for (int i = 0; i < images.Count; i++)
{
using (var ms = new MemoryStream())
{
images[i].Save(ms, ImageFormat.Png);
pngBlocks[i] = ms.ToArray();
}
}
int offset = 6 + images.Count * 16;
for (int i = 0; i < images.Count; i++)
{
int size = images[i].Width; // carre
writer.Write((byte)(size >= 256 ? 0 : size));
writer.Write((byte)(size >= 256 ? 0 : size));
writer.Write((byte)0); // palette
writer.Write((byte)0); // reserved
writer.Write((ushort)1); // planes
writer.Write((ushort)32); // bitcount
writer.Write((uint)pngBlocks[i].Length);
writer.Write((uint)offset);
offset += pngBlocks[i].Length;
}
for (int i = 0; i < images.Count; i++)
{
writer.Write(pngBlocks[i]);
}
}
}
}
}

69
src/MagnifierForm.cs Normal file
View File

@@ -0,0 +1,69 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace ColorPickerApp
{
/// <summary>Petite fenetre flottante qui suit le curseur et affiche la couleur survolee.</summary>
internal sealed class MagnifierForm : Form
{
private readonly Panel _swatch;
private readonly Label _label;
public MagnifierForm()
{
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
StartPosition = FormStartPosition.Manual;
TopMost = true;
Size = new Size(150, 44);
BackColor = Color.FromArgb(32, 32, 32);
DoubleBuffered = true;
_swatch = new Panel { Location = new Point(6, 6), Size = new Size(32, 32), BorderStyle = BorderStyle.FixedSingle };
_label = new Label
{
Location = new Point(44, 6),
Size = new Size(100, 32),
ForeColor = Color.White,
Font = new Font("Consolas", 9F),
TextAlign = System.Drawing.ContentAlignment.MiddleLeft
};
Controls.Add(_swatch);
Controls.Add(_label);
}
public void UpdateDisplay(Point screenPoint, Color color)
{
_swatch.BackColor = color;
_label.Text = string.Format("#{0:X2}{1:X2}{2:X2}", color.R, color.G, color.B);
int x = screenPoint.X + 20;
int y = screenPoint.Y + 20;
var screen = Screen.FromPoint(screenPoint).Bounds;
if (x + Width > screen.Right) x = screenPoint.X - Width - 10;
if (y + Height > screen.Bottom) y = screenPoint.Y - Height - 10;
Location = new Point(x, y);
}
protected override bool ShowWithoutActivation
{
get { return true; }
}
protected override CreateParams CreateParams
{
get
{
const int WS_EX_NOACTIVATE = 0x08000000;
const int WS_EX_TOOLWINDOW = 0x00000080;
CreateParams cp = base.CreateParams;
cp.ExStyle |= WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW;
return cp;
}
}
}
}

302
src/MainForm.cs Normal file
View File

@@ -0,0 +1,302 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace ColorPickerApp
{
internal sealed class MainForm : Form
{
private FlowLayoutPanel _paletteFlow;
private Panel _preview;
private TextBox _hexBox;
private TextBox _rgbBox;
private TextBox _folderPathBox;
private Label _statusLabel;
private Color _currentColor = Color.FromArgb(0, 120, 215);
public MainForm()
{
BuildUi();
PopulatePalette();
SetCurrentColor(_currentColor);
}
private void BuildUi()
{
Text = "ColorPicker - Palette & Couleur de dossier";
StartPosition = FormStartPosition.CenterScreen;
ClientSize = new Size(760, 640);
MinimumSize = new Size(700, 600);
Font = new Font("Segoe UI", 9F);
// ---- Palette (gauche) ----
var paletteLabel = new Label
{
Text = "Palette de couleurs",
Location = new Point(16, 12),
AutoSize = true,
Font = new Font(Font, FontStyle.Bold)
};
_paletteFlow = new FlowLayoutPanel
{
Location = new Point(16, 36),
Size = new Size(400, 420),
AutoScroll = true,
BorderStyle = BorderStyle.FixedSingle,
BackColor = Color.White,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Bottom
};
// ---- Panneau droit : couleur courante ----
var rightX = 432;
var previewLabel = new Label { Text = "Couleur actuelle", Location = new Point(rightX, 12), AutoSize = true, Font = new Font(Font, FontStyle.Bold) };
_preview = new Panel
{
Location = new Point(rightX, 36),
Size = new Size(312, 80),
BorderStyle = BorderStyle.FixedSingle
};
var hexLabel = new Label { Text = "Code hexadécimal :", Location = new Point(rightX, 132), AutoSize = true };
_hexBox = new TextBox { Location = new Point(rightX, 152), Size = new Size(150, 24), Font = new Font("Consolas", 10F), MaxLength = 7 };
var hexCopyBtn = new Button { Text = "Copier", Location = new Point(rightX + 158, 151), Size = new Size(80, 26) };
var hexApplyBtn = new Button { Text = "OK", Location = new Point(rightX + 242, 151), Size = new Size(70, 26) };
var rgbLabel = new Label { Text = "Code RGB :", Location = new Point(rightX, 190), AutoSize = true };
_rgbBox = new TextBox { Location = new Point(rightX, 210), Size = new Size(150, 24), Font = new Font("Consolas", 10F), ReadOnly = true };
var rgbCopyBtn = new Button { Text = "Copier", Location = new Point(rightX + 158, 209), Size = new Size(80, 26) };
var eyedropperBtn = new Button
{
Text = "Prélever une couleur à l'écran",
Location = new Point(rightX, 254),
Size = new Size(312, 32)
};
var customColorBtn = new Button
{
Text = "Couleur personnalisée...",
Location = new Point(rightX, 292),
Size = new Size(312, 32)
};
// ---- Groupe couleur de dossier ----
var folderGroup = new GroupBox
{
Text = "Couleur d'un dossier de l'explorateur",
Location = new Point(16, 468),
Size = new Size(728, 140),
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom
};
_folderPathBox = new TextBox
{
Location = new Point(12, 28),
Size = new Size(500, 24),
ReadOnly = true,
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top
};
var browseBtn = new Button { Text = "Parcourir...", Location = new Point(520, 27), Size = new Size(100, 26), Anchor = AnchorStyles.Right | AnchorStyles.Top };
var refreshHintLabel = new Label
{
Text = "Astuce : si l'icône ne change pas immédiatement, fermez/rouvrez l'explorateur du dossier.",
Location = new Point(12, 58),
AutoSize = true,
ForeColor = Color.DimGray
};
var applyFolderBtn = new Button { Text = "Appliquer la couleur au dossier", Location = new Point(12, 88), Size = new Size(220, 32) };
var restoreFolderBtn = new Button { Text = "Restaurer la couleur par défaut", Location = new Point(240, 88), Size = new Size(220, 32) };
_statusLabel = new Label { Location = new Point(472, 92), Size = new Size(240, 24), ForeColor = Color.DarkGreen, Anchor = AnchorStyles.Right | AnchorStyles.Bottom };
folderGroup.Controls.Add(_folderPathBox);
folderGroup.Controls.Add(browseBtn);
folderGroup.Controls.Add(refreshHintLabel);
folderGroup.Controls.Add(applyFolderBtn);
folderGroup.Controls.Add(restoreFolderBtn);
folderGroup.Controls.Add(_statusLabel);
Controls.Add(paletteLabel);
Controls.Add(_paletteFlow);
Controls.Add(previewLabel);
Controls.Add(_preview);
Controls.Add(hexLabel);
Controls.Add(_hexBox);
Controls.Add(hexCopyBtn);
Controls.Add(hexApplyBtn);
Controls.Add(rgbLabel);
Controls.Add(_rgbBox);
Controls.Add(rgbCopyBtn);
Controls.Add(eyedropperBtn);
Controls.Add(customColorBtn);
Controls.Add(folderGroup);
// ---- Evenements ----
hexCopyBtn.Click += (s, e) => CopyToClipboard(_hexBox.Text);
rgbCopyBtn.Click += (s, e) => CopyToClipboard(_rgbBox.Text);
hexApplyBtn.Click += (s, e) => TryApplyHexInput();
_hexBox.KeyDown += (s, e) => { if (e.KeyCode == Keys.Enter) { TryApplyHexInput(); e.SuppressKeyPress = true; } };
eyedropperBtn.Click += (s, e) => LaunchEyedropper();
customColorBtn.Click += (s, e) => LaunchCustomColorDialog();
browseBtn.Click += (s, e) => BrowseFolder();
applyFolderBtn.Click += (s, e) => ApplyFolderColor();
restoreFolderBtn.Click += (s, e) => RestoreFolderColor();
}
private void PopulatePalette()
{
foreach (var color in Palette.Generate())
{
var swatch = new Panel
{
Size = new Size(26, 26),
Margin = new Padding(2),
BackColor = color,
BorderStyle = BorderStyle.FixedSingle,
Cursor = Cursors.Hand,
Tag = color
};
swatch.Click += (s, e) => SetCurrentColor((Color)((Panel)s).Tag);
_paletteFlow.Controls.Add(swatch);
}
}
private void SetCurrentColor(Color color)
{
_currentColor = color;
_preview.BackColor = color;
_hexBox.Text = string.Format("#{0:X2}{1:X2}{2:X2}", color.R, color.G, color.B);
_rgbBox.Text = string.Format("{0}, {1}, {2}", color.R, color.G, color.B);
}
private void TryApplyHexInput()
{
string text = _hexBox.Text.Trim();
if (!text.StartsWith("#")) text = "#" + text;
try
{
if (text.Length != 7) throw new FormatException();
int r = Convert.ToInt32(text.Substring(1, 2), 16);
int g = Convert.ToInt32(text.Substring(3, 2), 16);
int b = Convert.ToInt32(text.Substring(5, 2), 16);
SetCurrentColor(Color.FromArgb(r, g, b));
}
catch
{
MessageBox.Show(this, "Code hexadécimal invalide. Format attendu : #RRGGBB", "Erreur",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
SetCurrentColor(_currentColor);
}
}
private void CopyToClipboard(string text)
{
if (string.IsNullOrEmpty(text)) return;
try
{
Clipboard.SetText(text);
ShowStatus("Copié : " + text, false);
}
catch (Exception ex)
{
ShowStatus("Erreur copie : " + ex.Message, true);
}
}
private void LaunchEyedropper()
{
Hide();
try
{
using (var picker = new ScreenColorPickerForm())
{
picker.ShowDialog();
if (picker.PickedColor != Color.Empty)
{
SetCurrentColor(picker.PickedColor);
}
}
}
finally
{
Show();
Activate();
}
}
private void LaunchCustomColorDialog()
{
using (var dlg = new ColorDialog { FullOpen = true, Color = _currentColor, AnyColor = true })
{
if (dlg.ShowDialog(this) == DialogResult.OK)
{
SetCurrentColor(dlg.Color);
}
}
}
private void BrowseFolder()
{
using (var dlg = new FolderBrowserDialog { Description = "Choisir le dossier à colorer" })
{
if (dlg.ShowDialog(this) == DialogResult.OK)
{
_folderPathBox.Text = dlg.SelectedPath;
ShowStatus("", false);
}
}
}
private void ApplyFolderColor()
{
if (string.IsNullOrEmpty(_folderPathBox.Text))
{
ShowStatus("Sélectionnez d'abord un dossier.", true);
return;
}
try
{
FolderColorizer.ApplyColor(_folderPathBox.Text, _currentColor);
ShowStatus("Couleur appliquée au dossier.", false);
}
catch (Exception ex)
{
ShowStatus("Échec : " + ex.Message, true);
}
}
private void RestoreFolderColor()
{
if (string.IsNullOrEmpty(_folderPathBox.Text))
{
ShowStatus("Sélectionnez d'abord un dossier.", true);
return;
}
try
{
FolderColorizer.RestoreDefault(_folderPathBox.Text);
ShowStatus("Icône par défaut restaurée.", false);
}
catch (Exception ex)
{
ShowStatus("Échec : " + ex.Message, true);
}
}
private void ShowStatus(string message, bool isError)
{
_statusLabel.Text = message;
_statusLabel.ForeColor = isError ? Color.Firebrick : Color.DarkGreen;
}
}
}

62
src/Palette.cs Normal file
View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Drawing;
namespace ColorPickerApp
{
/// <summary>Genere une palette complete (teintes x nuances + niveaux de gris).</summary>
internal static class Palette
{
public static List<Color> Generate()
{
var colors = new List<Color>();
const int hueSteps = 12;
const int shadeSteps = 8;
for (int s = 0; s < shadeSteps; s++)
{
double lightness = 0.90 - (s * (0.80 / (shadeSteps - 1)));
for (int h = 0; h < hueSteps; h++)
{
double hue = (360.0 / hueSteps) * h;
colors.Add(FromHsl(hue, 0.75, lightness));
}
}
const int grayCount = 12;
for (int g = 0; g < grayCount; g++)
{
int v = (int)Math.Round(255.0 * g / (grayCount - 1));
colors.Add(Color.FromArgb(v, v, v));
}
return colors;
}
private static Color FromHsl(double h, double s, double l)
{
double c = (1 - Math.Abs(2 * l - 1)) * s;
double x = c * (1 - Math.Abs((h / 60.0) % 2 - 1));
double m = l - c / 2;
double r1, g1, b1;
if (h < 60) { r1 = c; g1 = x; b1 = 0; }
else if (h < 120) { r1 = x; g1 = c; b1 = 0; }
else if (h < 180) { r1 = 0; g1 = c; b1 = x; }
else if (h < 240) { r1 = 0; g1 = x; b1 = c; }
else if (h < 300) { r1 = x; g1 = 0; b1 = c; }
else { r1 = c; g1 = 0; b1 = x; }
int r = (int)Math.Round((r1 + m) * 255);
int g = (int)Math.Round((g1 + m) * 255);
int b = (int)Math.Round((b1 + m) * 255);
r = Math.Max(0, Math.Min(255, r));
g = Math.Max(0, Math.Min(255, g));
b = Math.Max(0, Math.Min(255, b));
return Color.FromArgb(r, g, b);
}
}
}

16
src/Program.cs Normal file
View File

@@ -0,0 +1,16 @@
using System;
using System.Windows.Forms;
namespace ColorPickerApp
{
internal static class Program
{
[STAThread]
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
namespace ColorPickerApp
{
/// <summary>
/// Superpose une fenetre transparente plein ecran (tous moniteurs) permettant
/// de capturer la couleur de n'importe quel pixel affiche a l'ecran.
/// </summary>
internal sealed class ScreenColorPickerForm : Form
{
private readonly Timer _timer;
private readonly MagnifierForm _magnifier;
private Color _hoverColor = Color.White;
private Point _lastScreenPoint;
public Color PickedColor { get; private set; }
public ScreenColorPickerForm()
{
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
StartPosition = FormStartPosition.Manual;
TopMost = true;
BackColor = Color.Black;
Opacity = 0.01; // fenetre quasi invisible, sert uniquement a capter la souris
Cursor = Cursors.Cross;
DoubleBuffered = true;
KeyPreview = true;
Bounds = SystemInformation.VirtualScreen;
_magnifier = new MagnifierForm();
_timer = new Timer { Interval = 40 };
_timer.Tick += (s, e) => UpdateHover();
MouseClick += OnMouseClick;
KeyDown += (s, e) => { if (e.KeyCode == Keys.Escape) { PickedColor = Color.Empty; Close(); } };
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
_magnifier.Show(this);
_timer.Start();
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
_timer.Stop();
_magnifier.Close();
base.OnFormClosed(e);
}
private void UpdateHover()
{
_lastScreenPoint = Cursor.Position;
try
{
using (var bmp = new Bitmap(1, 1, PixelFormat.Format32bppArgb))
{
using (var g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(_lastScreenPoint, Point.Empty, new Size(1, 1));
}
_hoverColor = bmp.GetPixel(0, 0);
}
}
catch
{
// ignore capture errors (ex: DPI change) et garder la derniere couleur connue
}
_magnifier.UpdateDisplay(_lastScreenPoint, _hoverColor);
}
private void OnMouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
PickedColor = _hoverColor;
Close();
}
else
{
PickedColor = Color.Empty;
Close();
}
}
}
}

34
tests/IconPreview.cs Normal file
View File

@@ -0,0 +1,34 @@
using System.Drawing;
using System.IO;
namespace ColorPickerApp
{
internal static class IconPreview
{
private static void Main(string[] args)
{
string dir = Path.Combine(Path.GetTempPath(), "cp_preview");
Directory.CreateDirectory(dir);
FolderColorizer.ApplyColor(dir, Color.FromArgb(220, 40, 60));
byte[] icoBytes = File.ReadAllBytes(Path.Combine(dir, ".colorpicker_folder.ico"));
int count = System.BitConverter.ToUInt16(icoBytes, 4);
// trouver l'entree 256x256 (largeur stockee = 0) avec le plus grand bytesInRes
int bestOffset = -1, bestSize = -1, bestLen = 0;
for (int i = 0; i < count; i++)
{
int entryOffset = 6 + i * 16;
int w = icoBytes[entryOffset];
int len = System.BitConverter.ToInt32(icoBytes, entryOffset + 8);
int off = System.BitConverter.ToInt32(icoBytes, entryOffset + 12);
if (w == 0 && len > bestLen) { bestLen = len; bestOffset = off; bestSize = w; }
}
var pngBytes = new byte[bestLen];
System.Array.Copy(icoBytes, bestOffset, pngBytes, 0, bestLen);
File.WriteAllBytes(Path.Combine(Path.GetTempPath(), "cp_icon_preview.png"), pngBytes);
FolderColorizer.RestoreDefault(dir);
Directory.Delete(dir, true);
}
}
}

46
tests/TestHarness.cs Normal file
View File

@@ -0,0 +1,46 @@
using System;
using System.Drawing;
using System.IO;
namespace ColorPickerApp
{
internal static class TestHarness
{
private static void Main(string[] args)
{
string testDir = Path.Combine(Path.GetTempPath(), "cp_test_" + Guid.NewGuid().ToString("N").Substring(0, 8));
Directory.CreateDirectory(testDir);
Console.WriteLine("Dossier de test: " + testDir);
FolderColorizer.ApplyColor(testDir, Color.FromArgb(220, 40, 60));
Console.WriteLine("Apres ApplyColor:");
foreach (var f in Directory.GetFiles(testDir, "*", SearchOption.TopDirectoryOnly))
{
Console.WriteLine(" " + f + " attrs=" + File.GetAttributes(f));
}
Console.WriteLine(" dossier attrs=" + new DirectoryInfo(testDir).Attributes);
Console.WriteLine(" HasCustomColor=" + FolderColorizer.HasCustomColor(testDir));
string iniPath = Path.Combine(testDir, "desktop.ini");
Console.WriteLine("--- desktop.ini ---");
Console.WriteLine(File.ReadAllText(iniPath));
string icoPath = Path.Combine(testDir, ".colorpicker_folder.ico");
var icoBytes = File.ReadAllBytes(icoPath);
Console.WriteLine("Taille .ico: " + icoBytes.Length + " octets, count entries=" + BitConverter.ToUInt16(icoBytes, 4));
FolderColorizer.RestoreDefault(testDir);
Console.WriteLine("Apres RestoreDefault:");
foreach (var f in Directory.GetFiles(testDir, "*", SearchOption.TopDirectoryOnly))
{
Console.WriteLine(" " + f);
}
Console.WriteLine(" dossier attrs=" + new DirectoryInfo(testDir).Attributes);
Console.WriteLine(" HasCustomColor=" + FolderColorizer.HasCustomColor(testDir));
Console.WriteLine("Fichiers restants: " + Directory.GetFiles(testDir).Length);
Directory.Delete(testDir, true);
Console.WriteLine("OK - test termine sans exception.");
}
}
}