Aller au contenu

Messages recommandés

Posté(e)

Bonjour,

 

Je viens de faire un petit plug-in pour Paint.NET.

 

Il permet de faire différentes corrections d'un seul coup sur une photo, et une correction de niveau qui ne modifie pas l'équilibre entre les couleurs (balance des blancs)

Les paramètres par défaut réalisent un auto-niveau mais sans modifier la température de couleur ou la balance des blancs.

image.png.2216a067dda9b888bf3c4a551545f6ff.png

 

Avantage de Paint.NET : quand on bouge les réglages le résultat est prévisualisé directement, et c'est assez rapide même sur une grosse image car Paint.NET découpe l'image en zones pour utiliser tous les cœurs du CPU.

 

Si vous le souhaitez, on peut ajuster la température de couleur et la saturation des couleurs.

C'est très utile pour améliorer des photos prises par temps gris, avec un éclairage froid, ou pour améliorer les couleurs d'une photo prise au moment de l'aube ou du crépuscule.

 

Pour les niveaux :

- On peut ramener la couleur la plus sombre de telle sorte que sa composante RVB la plus basse soit égale à 0 (le 3e paramètre)

- On peut ramener la couleur la plus claire de telle sorte que sa composante RVB la plus haute soit égale à 255 (le 4e paramètre)

C'est utile notamment pour les photos qui manquent de contraste, en particulier les photos de paysages affectées par le voile atmosphérique.

Si l'effet est trop prononcé il suffit de baisser les valeurs.

Pour une photo de nuit il vaut mieux mettre le paramètre "High levels to 255 output factor" à zéro ou bien une valeur pas trop élevée.

Si vous augmentez la valeur de ces deux paramètres au delà de 100, il y a un écrasement des tons les plus sombres ou les plus clairs.

Les 2 paramètres suivants permettent de pousser encore plus loin la correction des niveaux mais cela change l'équilibre des couleurs.

Si la photo contient déjà des pixels noirs ou blancs, ces fonctions sont sans effet ce qui est logique.

 

Enfin, le dernier paramètre est la correction gamma, elle permet de modifier les tons moyens ce qui permet d'éclaircir ou d'assombrir une photo sans écraser les tons clairs ni les tons sombres. Pour des photos où la dynamique peut être élevée c'est plus efficace que de jouer sur la luminosité et le contraste.

 

Vous pouvez le télécharger ici :

https://github.com/electroremy/Paint.NET_Plugin_PhotoLevelAndColorCorrection/releases/tag/Paint.NET_Plugin_PhotoLevelAndColorCorrection

Il suffit de décompresser le fichier ZIP, puis de lancer le fichier .BAT pour que le plugin soit ajouté à Paint.NET.

Bien sûr, fermer Paint.NET avant de lancer le fichier .BAT

 

Le plug-in a été écrit en C# avec le petit IDE CodeLab.

CodeLab est un petit add-on de Paint.NET qui permet de facilement créer des plug ins, pour ajouter ses propres fonctions à Paint.NET

Voici ce que ça donne :

Révélation
// Name: Photo colors and levels correction
// Submenu:
// Author: Rémy LUCAS
// Title: Photo colors and levels correction
// Version: 1.0
// Desc: Temperature, saturation, level and gamma correction
// Keywords: Color Temperature Saturation Level Gamma
// URL:
// Help:

/*
// Decorations available  :
IntSliderControl S1 = 0; // [0,100,1] S1
IntSliderControl S2 = 0; // [0,100,2] S2
IntSliderControl S3 = 0; // [0,100,3] S3
IntSliderControl S4 = 0; // [0,100,4] S4
IntSliderControl S5 = 0; // [0,100,5] S5
IntSliderControl S6 = 0; // [0,100,6] S6
IntSliderControl S7 = 0; // [0,100,7] S7
IntSliderControl S8 = 0; // [0,100,8] S8
IntSliderControl S9 = 0; // [0,100,9] S9
IntSliderControl S10 = 0; // [0,100,10] S10
IntSliderControl S11 = 0; // [0,100,11] S11
IntSliderControl S12 = 0; // [0,100,12] S12
*/

#region UICode
IntSliderControl TemperatureFact = 0; // [-20,20,9] Color temperature correction
IntSliderControl SaturationFact = 0; // [-100,100,3] Color saturation factor
IntSliderControl CorrLowLevels = 100; // [0,150] Low levels to 0 output factor
IntSliderControl CorrHighLevels = 100; // [0,150] High levels to 255 output factor
IntSliderControl IndRGBlow = 0; // [0,100] Low levels RGB independence factor
IntSliderControl IndRGBhigh = 0; // [0,100] High levels RGB independence factor
IntSliderControl Midtones = 128; // [0,255,5] Gamma correction (mid tones)
#endregion

// This single-threaded function is called after the UI changes and before the Render function is called
// The purpose is to prepare anything you'll need in the Render function
byte scan_minR;
byte scan_maxR;
byte scan_minG;
byte scan_maxG;
byte scan_minB;
byte scan_maxB;

byte minR;
byte maxR;
byte minG;
byte maxG;
byte minB;
byte maxB;
double KR,KB,KG;
bool scanoriginalOK = false;


double GammaCorrection;
double SaturationCorrection;

/*
double[] HistoR = new double[256];
double[] HistoG = new double[256];
double[] HistoB = new double[256];
*/

void PreRender(Surface dst, Surface src)
{
    byte R,G,B;

    //if (!scanoriginalOK) {
		scan_minR = 255;
		scan_minG = 255;
		scan_minB = 255;
		scan_maxR = 0;
		scan_maxG = 0;
		scan_maxB = 0;

		/*
		long[] HR = new long[256];
		long[] HG = new long[256];
		long[] HB = new long[256];
		int i;
		for (i=0;i<256;i++) {
			HR[i] = 0;
			HG[i] = 0;
			HB[i] = 0;
		}
		*/

		for (int y = src.Bounds.Top; y < src.Bounds.Bottom; ++y)
		{
			if (IsCancelRequested) return;

			for (int x = src.Bounds.Left; x < src.Bounds.Right; ++x)
			{
				// Get your source pixel
				ColorBgra sourcePixel = src[x,y];

				// Find min and max value for each RGB value :
				B = sourcePixel.B;
				G = sourcePixel.G;
				R = sourcePixel.R;
				if (scan_minR > R) scan_minR = R;
				if (scan_minG > G) scan_minG = G;
				if (scan_minB > B) scan_minB = B;
				if (scan_maxR < R) scan_maxR = R;
				if (scan_maxG < G) scan_maxG = G;
				if (scan_maxB < B) scan_maxB = B;

				/*
				// Histogram count :
				HR[R]++;
				HG[G]++;
				HB[B]++;
				*/
			}
		}

		
		/*
		long nbpix = (src.Bounds.Bottom- src.Bounds.Top) * (src.Bounds.Right-src.Bounds.Left);
		long HR_max = 0;
		long HG_max = 0;
		long HB_max = 0;
		for (i=0;i<256;i++) {
			if (HR[i] > HR_max) HR_max = HR[i];
			if (HG[i] > HG_max) HG_max = HG[i];
			if (HB[i] > HB_max) HB_max = HB[i];
		}
		double HR_K = 255 / HR_max;
		double HG_K = 255 / HG_max;
		double HB_K = 255 / HB_max;
		for (i=0;i<256;i++) {
			HistoR[i] = HR[i] * HR_K;
			HistoG[i] = HG[i] * HG_K;
			HistoB[i] = HB[i] * HB_K;
		}
		*/
		//scanoriginalOK = true;
   // }

    SaturationCorrection = 1+0.01*SaturationFact;


    double Rmini,Gmini,Bmini,Rmaxi,Gmaxi,Bmaxi;
    
    Rmini = (double)scan_minR * CorrLowLevels / 100;
    Gmini = (double)scan_minG * CorrLowLevels / 100;
    Bmini = (double)scan_minB * CorrLowLevels / 100;

    Rmaxi = (double)(255-scan_maxR) * CorrHighLevels / 100;
    Gmaxi = (double)(255-scan_maxG) * CorrHighLevels / 100;
    Bmaxi = (double)(255-scan_maxB) * CorrHighLevels / 100;

    double mini, maxi;

    mini = Rmini;
    if (mini>Gmini) mini = Gmini;
    if (mini>Bmini) mini = Bmini;

    Rmini = mini + (Rmini-mini) * IndRGBlow / 100;
    Gmini = mini + (Gmini-mini) * IndRGBlow / 100;
    Bmini = mini + (Bmini-mini) * IndRGBlow / 100;

    maxi = Rmaxi;
    if (maxi>Gmaxi) maxi = Gmaxi;
    if (maxi>Bmaxi) maxi = Bmaxi;

    Rmaxi = maxi + (Rmaxi-maxi) * IndRGBhigh / 100;
    Gmaxi = maxi + (Gmaxi-maxi) * IndRGBhigh / 100;
    Bmaxi = maxi + (Bmaxi-maxi) * IndRGBhigh / 100;

    Rmaxi = 255 - Rmaxi;
    Gmaxi = 255 - Gmaxi;
    Bmaxi = 255 - Bmaxi;

    if (Rmaxi > 255) Rmaxi = 255;
    if (Gmaxi > 255) Gmaxi = 255;
    if (Bmaxi > 255) Bmaxi = 255;
    if (Rmaxi < 0) Rmaxi = 0;
    if (Gmaxi < 0) Gmaxi = 0;
    if (Bmaxi < 0) Bmaxi = 0;
    if (Rmini > 255) Rmini = 255;
    if (Gmini > 255) Gmini = 255;
    if (Bmini > 255) Bmini = 255;
    if (Rmini < 0) Rmini = 0;
    if (Gmini < 0) Gmini = 0;
    if (Bmini < 0) Bmini = 0;


    maxR = (byte)Rmaxi;
    maxG = (byte)Gmaxi;
    maxB = (byte)Bmaxi;
    minR = (byte)Rmini;
    minG = (byte)Gmini;
    minB = (byte)Bmini;


    if (maxR-minR>0) {
        KR = (double)255 / (double)(maxR - minR);
    } else {
        KR=255;
    }
    if (maxG-minG>0) {
        KG = (double)255 / (double)(maxG - minG);
    } else {
        KG=255;
    }
    if (maxB-minB>0) {
        KB = (double)255 / (double)(maxB - minB);
    } else {
        KB=255;
    }

    double Gamma;
    double MidtoneNormal;
    Gamma = 1;
    Midtones = 255 - Midtones;
    MidtoneNormal = (double)Midtones / 255;
    if (Midtones < 128) {
        MidtoneNormal = MidtoneNormal * 2;
        Gamma = 1 + ( 9 * ( 1 - MidtoneNormal ) );
        if (Gamma>9.99) Gamma=9.99;
    } else if (Midtones > 128) {
        MidtoneNormal = ( MidtoneNormal * 2 ) - 1;
        Gamma = 1 - MidtoneNormal;
        if (Gamma<0.01) Gamma=0.01;
    }
    GammaCorrection = 1 / Gamma;

}

// Here is the main multi-threaded render function
// The dst canvas is broken up into rectangles and
// your job is to write to each pixel of that rectangle
void Render(Surface dst, Surface src, Rectangle rect)
{
    // uint seed = RandomNumber.InitializeSeed(RandomNumberRenderSeed, rect.Location);
    double R, G, B;
    double hue, saturation; //, value;
    Color color;
    int max, min;
    double v,p,q,t;
    double f;
    int hi;

    // Step through each row of the current rectangle
    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        if (IsCancelRequested) return;
        // Step through each pixel on the current row of the rectangle
        for (int x = rect.Left; x < rect.Right; x++)
        {
            ColorBgra SrcPixel = src[x,y];
            ColorBgra CurrentPixel = SrcPixel;
            /*
            ColorBgra CurrentPixel = SrcPixel;
            CurrentPixel.B = (byte) ((KB * (double)(SrcPixel.B - minB)));
            CurrentPixel.G = (byte) ((KG * (double)(SrcPixel.G - minG)));
            CurrentPixel.R = (byte) ((KR * (double)(SrcPixel.R - minR)));
            
            if (Midtones!=128) {
                CurrentPixel.R = (byte) (255 * ( Math.Pow( ( (double)CurrentPixel.R / 255 ), GammaCorrection ) ));
                CurrentPixel.G = (byte) (255 * ( Math.Pow( ( (double)CurrentPixel.G / 255 ), GammaCorrection ) ));
                CurrentPixel.B = (byte) (255 * ( Math.Pow( ( (double)CurrentPixel.B / 255 ), GammaCorrection ) ));
            }
            */
            
            // Temperature correction ================================================
            if (TemperatureFact!=0) {
                // thanks BoltBait
                CurrentPixel.R = Clamp2Byte(CurrentPixel.R + TemperatureFact); 
                CurrentPixel.B = Clamp2Byte(CurrentPixel.B - TemperatureFact); 
            }
            
            // Saturation correction =================================================
            if (SaturationFact!=0) {
                // RGB => HSV
                color = Color.FromArgb(CurrentPixel.R,CurrentPixel.G,CurrentPixel.B);
                max = Math.Max(color.R, Math.Max(color.G, color.B));
                min = Math.Min(color.R, Math.Min(color.G, color.B));
                hue = color.GetHue();
                saturation = (max == 0) ? 0 : 1d - (1d * min / max);

                saturation = saturation * SaturationCorrection;

                // HSV => RGB
                hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
                f = hue / 60 - Math.Floor(hue / 60);
                v = max;
                p = max * (1 - saturation);
                q = max * (1 - f * saturation);
                t = max * (1 - (1 - f) * saturation);

                if (hi == 0) {
                    R = v;
                    G = t;
                    B = p;
                    //color = Color.FromArgb(255, v, t, p);
                } else if (hi == 1) {
                    R = q;
                    G = v;
                    B = p;
                    //color = Color.FromArgb(255, q, v, p);
                } else if (hi == 2) {
                    R = p;
                    G = v;
                    B = t;
                    //color = Color.FromArgb(255, p, v, t);
                }else if (hi == 3) {
                    R = p;
                    G = q;
                    B = v;
                    //color = Color.FromArgb(255, p, q, v);
                }else if (hi == 4) {
                    R = t;
                    G = p;
                    B = v;
                    //color = Color.FromArgb(255, t, p, v);
                }else {
                    R = v;
                    G = p;
                    B = q;
                    //color = Color.FromArgb(255, v, p, q);
                }
            } else {
                R = CurrentPixel.R;
                G = CurrentPixel.G;
                B = CurrentPixel.B;
            }

            // Low and High level correction =========================================
            R = KR * (R - minR);
            G = KG * (G - minG);
            B = KB * (B - minB);
            
            // Gamma correction ======================================================
            if (Midtones!=128) {
                R = 255 * Math.Pow(R / 255, GammaCorrection);
                G = 255 * Math.Pow(G / 255, GammaCorrection);
                B = 255 * Math.Pow(B / 255, GammaCorrection);
            }

            // =======================================================================

            CurrentPixel.R = (byte)R;
            CurrentPixel.G = (byte)G;
            CurrentPixel.B = (byte)B;
            dst[x,y] = CurrentPixel;
        }
    }
}


private byte Clamp2Byte(int iValue) // thanks BoltBait
{
    if (iValue < 0) return 0;
    if (iValue > 255) return 255;
    return (byte)iValue;
}

 

 

 

Il y a une chose un peu chiante, c'est l'absence de raccourci clavier.

Une solution est d'utiliser le logiciel AutoHotKey

Ce logiciel permet de générer des séquences de touches à partir d'un raccourci clavier.

 

Voici la manip à faire pour que mon plug-in se lance avec le raccourci CTRL+Q dans Paint.NET, en réalisant une installation portable de AutoHotKey

- allez ici https://www.autohotkey.com/download/

- choisissez "download ZIP"

- décompressez le ZIP quelque part sur votre ordinateur

- dans le dossier où se trouve le fichier "AutoHotkey64.exe", créez un nouveau fichier texte "AutoHotkey64.ahk" (avec l'extension .ahk et pas .txt)

- éditez le fichier "AutoHotkey64.ahk" pour qu'il contienne :

#Requires AutoHotkey v2.0-beta
#Include UX\WindowSpy.ahk

#HotIf WinActive("ahk_exe paintdotnet.exe")
^q::Send "!up{Enter}"

 

Quelques explications :

La ligne

#HotIf WinActive("ahk_exe paintdotnet.exe")

Active uniquement le raccourci clavier pour Paint.NET, c'est important pour éviter de perturber les autres logiciels.

 

La ligne

^q::Send "!up{Enter}"

permet au raccourci clavier CTRL+Q de générer la séquence ALT + U + P  + ENTER qui lance mon plugin "Photo colors and levels correction" 

'^' = touche CTRL

'!' = touche ALT

 

Bien sûr, il faudra lancer "AutoHotkey64.exe" pour que ça fonctionne ; vous pouvez le lancer au démarrage de Windows ou fait un fichier .BAT pour le lancer en même temps que Paint.NET

 

AutoHotKey peut être pratique si vous avez des manipulations répétitives à faire avec certains logiciels.

 

A bientôt

 

 

 

Créer un compte ou se connecter pour commenter

Vous devez être membre afin de pouvoir déposer un commentaire

Créer un compte

Créez un compte sur notre communauté. C’est facile !

Créer un nouveau compte

Se connecter

Vous avez déjà un compte ? Connectez-vous ici.

Connectez-vous maintenant
×
×
  • Créer...