Jump to content

Tanel

Members
  • Content Count

    205
  • Joined

  • Last visited

  • Days Won

    3

Posts posted by Tanel

  1. Pyrochild helped me out with exactly the same problem when I tried to implement feather control into my "Color to Alpha" plugin (link to original discussion).

    Feel free to copy below code (visual studio). It's not cleaned up but has some comments; those marked with "???" are by pyrochild.

    Note that I have moved blurring into separate if-block which is ignored until feather token is >0. This is for better performance - because with larger images UI of Color to Alpha becomes quite laggy as soon as feather is on.

    using System;
    using System.Collections;
    using System.Drawing;
    using PaintDotNet;
    using PaintDotNet.Effects;
    using PaintDotNet.PropertySystem;
    using System.Collections.Generic;
    
    namespace ColorToAlpha
    {
       [EffectCategory(EffectCategory.Adjustment)]
       public class EffectPlugin
           : PaintDotNet.Effects.Effect
       {
           public static string StaticName { get { return "Color to Alpha"; } }
    
           public static Bitmap StaticImage { get { return new Bitmap(typeof(EffectPlugin), "EffectPluginIcon.png"); } }
    
           public static string StaticSubMenuName
           {
               get
               {
                   return null; // Use for no submenu
                   // return "My SubMenu"; // Use for custom submenu
               }
           }
    
           public EffectPlugin()
               : base(StaticName, StaticImage, StaticSubMenuName, EffectFlags.Configurable)
           {
               blurEffect = new GaussianBlurEffect();
           }
    
           public override EffectConfigDialog CreateConfigDialog()
           {
               return new ColorToAlpha();
           }
    
           private GaussianBlurEffect blurEffect;
    
           PropertyBasedEffectConfigToken blurToken;
           Surface tempSurface;
           RenderArgs temp;
           bool changed;
    
    
           protected override void OnSetRenderInfo(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs)
           {
               this.changed = true; 
    
               EffectPluginConfigToken token = (EffectPluginConfigToken)parameters;
    
               PropertyCollection blurProps = blurEffect.CreatePropertyCollection();
    
               blurToken = new PropertyBasedEffectConfigToken(blurProps);
               blurToken.SetPropertyValue(GaussianBlurEffect.PropertyNames.Radius, token.Feather);
               base.OnSetRenderInfo(parameters, dstArgs, srcArgs);
    
           }
    
           public override unsafe void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length)
           {
               EffectPluginConfigToken token = (EffectPluginConfigToken)parameters;
               int feax = token.Feather;
               bool inv = token.Invert;
               int basea = token.BaseAlpha;
               if (feax > 0) // THIS IS FOR FASTER PERFORMANCE WHILE FEATHER IS NOT USED
               {
                   PdnRegion selectionRegion = EnvironmentParameters.GetSelection(srcArgs.Bounds);
                   if (changed && tempSurface != null) // ??? I set changed = true in OnSetRenderInfo(), to indicate that the user has changed settings and therefore, invalidated our tempSurface. But if it's null it doesn't matter.
                   {
                       tempSurface.Dispose(); // ??? Clear up our resource usage. We have to be nice to our user's RAM.
                       tempSurface = null; // ??? Then set it to null so the next ifblock gets executed. I could combine this more elegantly, but the code here evolved organically from the old version, and I'm lazy.
                   }
                   if (tempSurface == null) // ??? If we don't have a tempSurface at this point (whether it's because we never made one, or just invalidated it above), we need to make one.
                   {
                       Rectangle[] gorois = selectionRegion.GetRegionScansInt(); // build new surface "tempSurface"
                       tempSurface = new Surface(srcArgs.Size);
                       temp = new RenderArgs(tempSurface);
    
                       RenderAlpha(parameters, tempSurface, srcArgs, gorois, 0, gorois.Length);
    
                       changed = false; // ??? Set changed to false since we've made our tempSurface and it's currently valid, until changed gets set to true again. This prevents us from creating a new overlay every time OnRender() is called
    
                       blurEffect.SetRenderInfo(blurToken, dstArgs, temp);
                   }
    
                   this.blurEffect.Render(rois, startIndex, length);
    
                   for (int i = startIndex; i < startIndex + length; ++i)
                   {
                       Rectangle roi = rois[i];
    
                       for (int y = roi.Top; y < roi.Bottom; ++y)
                       {
                           ColorBgra* dstPtr = dstArgs.Surface.GetPointAddress(roi.X, roi.Y);
                           ColorBgra NewPixel; 
                           ColorBgra OrigPixel;
                           byte a, ro, go, bo, ao, ry, by, gy, ay; // r, g, b,  
    
                           for (int x = roi.Left; x < roi.Right; ++x)
                           {
                               NewPixel = dstArgs.Surface[x, y]; 
                               //r = NewPixel.R;
                               //g = NewPixel.G;
                               //b = NewPixel.B;
                               a = NewPixel.R; // WE MADE GRAYSCALE IMAGE FROM ALPHA VALUES BEFORE BLURRING (see bottom of RenderAlpha), NOW WE TURN IT BACK TO ALPHA
    
                               OrigPixel = srcArgs.Surface[x, y];
                               ro = OrigPixel.R;
                               go = OrigPixel.G;
                               bo = OrigPixel.B;
                               ao = OrigPixel.A;
    
                               byte baseab = (byte)basea;
                               if (baseab > 0 && a < baseab) 
                               a = baseab;
    
                               // INVERT
                               if (inv == true)
                                   a = (byte)(255 - a);
    
                               if (selectionRegion.IsVisible(x, y))
                               {
                                   ry = Utility.ClampToByte(ro);
                                   gy = Utility.ClampToByte(go);
                                   by = Utility.ClampToByte(bo);
                                   ay = Utility.ClampToByte(a);
                                   NewPixel = ColorBgra.FromBgra(by, gy, ry, ay);
    
                                   *dstPtr = NewPixel;
                                   ++dstPtr;
                               }
                           }
                       }
                   }
               }
    
               else // feather token = 0; no need for 3rd surface rendering
               {
                   int hue1 = token.huefrom;
                   int hue2 = token.hueto;
                   int sat1 = token.satfrom;
                   int sat2 = token.satto;
                   int b1 = token.bnessfrom;
                   int b2 = token.bnessto;
                   int tol = token.Tolerance;
                   bool huebox = token.Huebox;
                   bool satbox = token.Satbox;
                   bool bnessbox = token.Bnessbox;
                   bool ovrride = token.Ovrride; // NEW
    
                   for (int i = startIndex; i < startIndex + length; ++i)
                   {
                       Rectangle roi = rois[i];
    
                       for (int y = roi.Top; y < roi.Bottom; ++y)
                       {
                           ColorBgra* srcPtr = srcArgs.Surface.GetPointAddress(roi.X, roi.Y);
                           ColorBgra* dstPtr = dstArgs.Surface.GetPointAddress(roi.X, roi.Y);
                           ColorBgra CurrentPixel;
                           ColorBgra OrigPixel;
                           byte ro, go, bo, ao, ry, by, gy, ay; // r, g, b, a, 
                           float amtx;
                           double ax, huej, huel, satj, satl, brtj, brtl, huej1, huel1, huej0, huel0, satj1, satl1, brtj1, brtl1, fn, ft, fu, fv, ffn, fft, ffu, ffv, fxx;
    
                           for (int x = roi.Left; x < roi.Right; ++x)
                           {
                               OrigPixel = srcArgs.Surface[x, y];
                               ro = OrigPixel.R;
                               go = OrigPixel.G;
                               bo = OrigPixel.B;
                               ao = OrigPixel.A;
                               amtx = 1 - basea / 255f; 
    
                               CurrentPixel = dstArgs.Surface[x, y];
    
                               // GET HUE: FROM UnaryPixelOps.cs:
                               HsvColor hsvColor = HsvColor.FromColor(OrigPixel.ToColor());
                               int hue = hsvColor.Hue;
                               int satur = hsvColor.Saturation;
                               int brt = (int)OrigPixel.GetIntensityByte();
    
                               if (hue1 > 359)
                                   hue1 = 359;
                               if (hue2 > 359)
                                   hue2 = 359;
    
                               double huearea = (hue2 - hue1);
                               if (hue2 < hue1)
                                   huearea += 360;
    
                               double hfea = tol;
                               if (tol > ((360 - huearea) / 2) * 255 / 360)
                                   hfea = ((360 - huearea) / 2) * 255 / 360; // adjust hue fading to avoid crossing over total 360
    
    
                               // virtually shift hue into "normal" min-max bounds in case hue_from > hue_to
                               double nhue0 = -(hue2 - hue1);
                               double nhue1 = (((nhue0 + Math.Abs(nhue0)) / 2) / 360);
                               double nhuex = (Math.Ceiling(nhue1)); // return 1 if hue_from > hue_to; else 0
                               int nhue = (int)((360 - hue1) * nhuex);
                               int huex = hue;
                               huex += nhue;
                               while (huex > 359)
                               {
                                   huex -= 360;
                               }
    
                               double hue22 = 360 - hue1;
                               double hue12 = 360 - hue2;
                               double huez = 360 - hue;
    
                               // hue limiter *while hue_from > hue_to*
                               huej = (hue2 + nhue - huex + 1 - 0.1); // huej = (hue2 + nhue - huex + 1 - 0.1);
                               huel = (((huej + Math.Abs(huej)) / 2) / huej); // hue_to limiter
    
                               // hue limiters *while hue_from < hue_to*
                               huej0 = ((hue2 - hue + 1 - 0.1) * (1 - nhuex)) + ((hue22 - huez + 1 - 0.1) * nhuex);
                               huel0 = (((huej0 + Math.Abs(huej0)) / 2) / huej0); // hue_to limiter
                               huej1 = ((hue - hue1 + 1 - 0.1) * (1 - nhuex)) + ((huez - hue12 + 1 - 0.1) * nhuex);
                               huel1 = (((huej1 + Math.Abs(huej1)) / 2) / huej1);// hue_from limiter
    
    
                               double huelx = (huel * nhuex + huel0 * huel1 * (1 - nhuex)); // use only one set of limiters from cases hue_from > hue_to OR hue_from < hue_to
    
                               satj = (sat2 - satur + 1 - 0.1);
                               satl = (((satj + Math.Abs(satj)) / 2) / satj); // saturation_to limiter
                               satj1 = (satur - sat1 + 1 - 0.1);
                               satl1 = (((satj1 + Math.Abs(satj1)) / 2) / satj1); // saturation_from limiter
    
                               brtj = (b2 - brt + 1 - 0.1);
                               brtl = (((brtj + Math.Abs(brtj)) / 2) / brtj); // brightness_to limiter
                               brtj1 = (brt - b1 + 1 - 0.1);
                               brtl1 = (((brtj1 + Math.Abs(brtj1)) / 2) / brtj1); // brightness_from limiter
    
                               double ax0 = (huelx * satl * brtl * satl1 * brtl1);
                               double ax1 = ((1 - ax0) * (1 - amtx) + ax0); // set alpha of non-picked pixels to inverse of amount
    
                               double ax0h = huelx; // double ax0h = (satl * brtl * satl1 * brtl1);
                               double ax1h = ((1 - ax0h) * (1 - amtx) + ax0h); // set alpha of non-picked pixels to inverse of amount
    
                               double ax0s = (satl * satl1); // double ax0s = (huelx * brtl * brtl1);
                               double ax1s = ((1 - ax0s) * (1 - amtx) + ax0s); // set alpha of non-picked pixels to inverse of amount
    
                               double ax0b = (brtl * brtl1); // double ax0b = (huelx * satl * satl1);
                               double ax1b = ((1 - ax0b) * (1 - amtx) + ax0b); //  set alpha of non-picked pixels to inverse of amount
    
    
                               // HUE FADING TO_LIMITER
                               double huej0f = (huej0 * 255 / 360);
                               if (huej0 - 1 + 0.1 + (hfea * 360 / 255) > 359) // XXXX TEST if (huej0 - 1 + 0.1 + (hfea * 360 / 255) > 360)
                                   huej0f = ((huej0 - 1 + 0.1 - 360) * 255 / 360); // XXX TEST huej0f = ((huej0 - 1 + 0.1 - 361) * 255 / 360); 
                               if (huej0 - 1 + 0.1 + (hfea * 360 / 255) < 0) // XXXX TEST if (huej0 - 1 + 0.1 + (hfea * 360 / 255) > 360)
                                   huej0f = ((huej0 - 1 + 0.1 + 360) * 255 / 360); // XXX TEST huej0f = ((huej0 - 1 + 0.1 - 361) * 255 / 360); 
    
                               fn = (((-huej0f + Math.Abs(-huej0f)) / 2) / -huej0f); //  * ax1h;
                               ft = (-huej0f / (hfea + 0.01)); // tol -> hfea
                               fu = (1 - ft * amtx);
                               fv = (fu * fn + ax0h * (1 - fn)); //(fu * fn * fo + ax1 * fm + ax1 * (1 - fn)); // 
    
                               // HUE FADING FROM_LIMITER
                               double huej1f = (huej1 * 255 / 360);
                               if (huej1 - 1 + 0.1 + (hfea * 360 / 255) > 359) //XXXX if (huej1 - 1 + 0.1 + (hfea * 360 / 255) > 360)
                                   huej1f = ((huej1 - 1 + 0.1 - 360) * 255 / 360); // XXX huej1f = ((huej1 - 1 + 0.1 - 361) * 255 / 360); 
                               if (huej1 - 1 + 0.1 + (hfea * 360 / 255) < 0) //XXXX if (huej1 - 1 + 0.1 + (hfea * 360 / 255) > 360)
                                   huej1f = ((huej1 - 1 + 0.1 + 360) * 255 / 360); // XXX huej1f = ((huej1 - 1 + 0.1 - 361) * 255 / 360); 
    
                               ffn = (((-huej1f + Math.Abs(-huej1f)) / 2) / -huej1f); //  *ax1h;
                               fft = (-huej1f / (hfea + 0.01)); // tol -> hfea
                               ffu = (1 - fft * amtx);
                               ffv = (ffu * ffn + ax0h * (1 - ffn)); //(ffu * ffn * ffo + ax1 * ffm + ax1 * (1 - ffn)); // 
                               fxx = (Math.Max(fv, ffv)); // HUE TO ALPHA MULTIPLIER
                               if (huebox == false)
                                   fxx = 1; // fxx = ax1h; // 
    
                               // SATURATION FADING TO_LIMITER
                               double satjf = (satj * 255 / 100); //  * ax1 ei sobi siia
                               double fns = (((-satjf + Math.Abs(-satjf)) / 2) / -satjf); //  * ax1s;
                               double fts = (-satjf / (tol + 0.01)); // tol -> hfea
                               double fus = (1 - fts * amtx);
                               double fvs = (fus * fns + ax0s * (1 - fns)); //double fvs = (fus * fns + ax1 * (1 - fns));
    
                               // SATURATION FADING FROM_LIMITER
                               double satj1f = (satj1 * 255 / 100); //  * ax1 ei sobi siia
                               double ffns = (((-satj1f + Math.Abs(-satj1f)) / 2) / -satj1f); //  * ax1s;
                               double ffts = (-satj1f / (tol + 0.01)); // tol -> hfea
                               double ffus = (1 - ffts * amtx);
                               double ffvs = (ffus * ffns + ax0s * (1 - ffns)); //(ffu * ffn * ffo + ax1 * ffm + ax1 * (1 - ffn)); // 
                               double fxxs = (Math.Max(fvs, ffvs)) * fxx; // SATURATION+HUE TO ALPHA MULTIPLIER
                               if (satbox == false)
                                   fxxs = fxx; // fxxs = ax1s * fxx; // 
    
    
                               // BRIGHTNESS FADING TO_LIMITER
                               double fnb = (((-brtj + Math.Abs(-brtj)) / 2) / -brtj); //  * ax1b;
                               double ftb = (-brtj / (tol + 0.01)); // tol -> hfea
                               double fub = (1 - ftb * amtx);
                               double fvb = (fub * fnb + ax0b * (1 - fnb)); //(fu * fn * fo + ax1 * fm + ax1 * (1 - fn)); // 
    
                               // BRIGHTNESS FADING FROM_LIMITER
                               double ffnb = (((-brtj1 + Math.Abs(-brtj1)) / 2) / -brtj1); //  * ax1b;
                               double fftb = (-brtj1 / (tol + 0.01)); // tol -> hfea
                               double ffub = (1 - fftb * amtx);
                               double ffvb = (ffub * ffnb + ax0b * (1 - ffnb)); //(ffu * ffn * ffo + ax1 * ffm + ax1 * (1 - ffn)); // 
                               double fxxb = (Math.Max(fvb, ffvb)) * fxxs; // BRIGHTNESS+HUE+SATURATION TO ALPHA MULTIPLIER
                               if (bnessbox == false)
                                   fxxb = fxxs; // fxxb = ax1b * fxxs;// 
    
    
                               // even out alpha for non-selected areas
                               double fxxx0 = Math.Ceiling(fxxb);
                               double fxxx = fxxb + (1 - amtx) * (1 - fxxx0);
    
                               if (fxxx < (1 - amtx))
                                   fxxx = (1 - amtx);
    
                               if (ovrride == true) // NEW
                                   ao = 255;
    
                               ax = (ao * fxxx); // FINAL ALPHA
    
                               byte baseab = (byte)basea;
                               if (baseab > 0 && ax < baseab) // REVISED 21-01-08
                                   ax = baseab;
    
                               // invert
                               if (inv == true)
                                   ax = (255 - ax);
    
    
                               ry = Utility.ClampToByte(ro);
                               gy = Utility.ClampToByte(go);
                               by = Utility.ClampToByte(bo);
                               ay = Utility.ClampToByte(ax);
                               CurrentPixel = ColorBgra.FromBgra(by, gy, ry, ay); 
    
                               *dstPtr = CurrentPixel;
                               ++dstPtr;
    
                           }
                       }
                   }
               }
           }
    
    
           private unsafe void RenderAlpha(EffectConfigToken parameters, Surface surface, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length)
           {
               EffectPluginConfigToken token = (EffectPluginConfigToken)parameters;
    
               int hue1 = token.huefrom;
               int hue2 = token.hueto;
               int sat1 = token.satfrom;
               int sat2 = token.satto;
               int b1 = token.bnessfrom;
               int b2 = token.bnessto;
               int tol = token.Tolerance;
               int basea = token.BaseAlpha; 
               basea += 1; // HEAL THE SHIFT IN RESULTS
               bool huebox = token.Huebox;
               bool satbox = token.Satbox;
               bool bnessbox = token.Bnessbox;
               bool ovrride = token.Ovrride; 
    
               for (int i = startIndex; i < startIndex + length; ++i)
               {
                   Rectangle roi = rois[i];
    
                   for (int y = roi.Top; y < roi.Bottom; ++y)
                   {
                       ColorBgra* ptr = surface.GetPointAddressUnchecked(roi.Left, y); 
                       ColorBgra OrigPixel;
                       byte ro, go, bo, ao, ry, by, gy, ay; // r, g, b, a, 
                       float amtx;
                       double ax, huej, huel, satj, satl, brtj, brtl, huej1, huel1, huej0, huel0, satj1, satl1, brtj1, brtl1, fn, ft, fu, fv, ffn, fft, ffu, ffv, fxx;
    
                       for (int x = roi.Left; x < roi.Right; ++x)
                       {
                           OrigPixel = srcArgs.Surface[x, y];
                           ro = OrigPixel.R;
                           go = OrigPixel.G;
                           bo = OrigPixel.B;
                           ao = OrigPixel.A;
                           amtx = 1 - basea / 255f; // amtx = amt / 100f;
    
                           // GET HUE: FROM UnaryPixelOps.cs:
                           HsvColor hsvColor = HsvColor.FromColor(OrigPixel.ToColor());
                           int hue = hsvColor.Hue;
                           int satur = hsvColor.Saturation;
                           int brt = (int)OrigPixel.GetIntensityByte();
    
                           if (hue1 > 359)
                               hue1 = 359;
                           if (hue2 > 359)
                               hue2 = 359;
    
                           double huearea = (hue2 - hue1);
                           if (hue2 < hue1)
                               huearea += 360;
    
                           double hfea = tol;
                           if (tol > ((360 - huearea) / 2) * 255 / 360)
                               hfea = ((360 - huearea) / 2) * 255 / 360; // adjust hue fading to avoid crossing over total 360
    
    
                           // virtually shift hue into "normal" min-max bounds in case hue_from > hue_to
                           double nhue0 = -(hue2 - hue1);
                           double nhue1 = (((nhue0 + Math.Abs(nhue0)) / 2) / 360);
                           double nhuex = (Math.Ceiling(nhue1)); // return 1 if hue_from > hue_to; else 0
                           int nhue = (int)((360 - hue1) * nhuex);
                           int huex = hue;
                           huex += nhue;
                           while (huex > 359)
                           {
                               huex -= 360;
                           }
    
                           double hue22 = 360 - hue1;
                           double hue12 = 360 - hue2;
                           double huez = 360 - hue;
    
                           // hue limiter *while hue_from > hue_to*
                           huej = (hue2 + nhue - huex + 1 - 0.1); // huej = (hue2 + nhue - huex + 1 - 0.1);
                           huel = (((huej + Math.Abs(huej)) / 2) / huej); // hue_to limiter
    
                           // hue limiters *while hue_from < hue_to*
                           huej0 = ((hue2 - hue + 1 - 0.1) * (1 - nhuex)) + ((hue22 - huez + 1 - 0.1) * nhuex);
                           huel0 = (((huej0 + Math.Abs(huej0)) / 2) / huej0); // hue_to limiter
                           huej1 = ((hue - hue1 + 1 - 0.1) * (1 - nhuex)) + ((huez - hue12 + 1 - 0.1) * nhuex);
                           huel1 = (((huej1 + Math.Abs(huej1)) / 2) / huej1);// hue_from limiter
    
    
                           double huelx = (huel * nhuex + huel0 * huel1 * (1 - nhuex)); // use only one set of limiters from cases hue_from > hue_to OR hue_from < hue_to
    
                           satj = (sat2 - satur + 1 - 0.1);
                           satl = (((satj + Math.Abs(satj)) / 2) / satj); // saturation_to limiter
                           satj1 = (satur - sat1 + 1 - 0.1);
                           satl1 = (((satj1 + Math.Abs(satj1)) / 2) / satj1); // saturation_from limiter
    
                           brtj = (b2 - brt + 1 - 0.1);
                           brtl = (((brtj + Math.Abs(brtj)) / 2) / brtj); // brightness_to limiter
                           brtj1 = (brt - b1 + 1 - 0.1);
                           brtl1 = (((brtj1 + Math.Abs(brtj1)) / 2) / brtj1); // brightness_from limiter
    
                           double ax0 = (huelx * satl * brtl * satl1 * brtl1);
                           double ax1 = ((1 - ax0) * (1 - amtx) + ax0); // set alpha of non-picked pixels to inverse of amount
    
                           double ax0h = huelx; // double ax0h = (satl * brtl * satl1 * brtl1);
                           double ax1h = ((1 - ax0h) * (1 - amtx) + ax0h); // set alpha of non-picked pixels to inverse of amount
    
                           double ax0s = (satl * satl1); // double ax0s = (huelx * brtl * brtl1);
                           double ax1s = ((1 - ax0s) * (1 - amtx) + ax0s); // set alpha of non-picked pixels to inverse of amount
    
                           double ax0b = (brtl * brtl1); // double ax0b = (huelx * satl * satl1);
                           double ax1b = ((1 - ax0b) * (1 - amtx) + ax0b); // set alpha of non-picked pixels to inverse of amount
    
    
    
                           // HUE FADING TO_LIMITER
                           double huej0f = (huej0 * 255 / 360);
                           if (huej0 - 1 + 0.1 + (hfea * 360 / 255) > 359) // XXXX TEST if (huej0 - 1 + 0.1 + (hfea * 360 / 255) > 360)
                               huej0f = ((huej0 - 1 + 0.1 - 360) * 255 / 360); // XXX TEST huej0f = ((huej0 - 1 + 0.1 - 361) * 255 / 360); 
                           if (huej0 - 1 + 0.1 + (hfea * 360 / 255) < 0) // XXXX TEST if (huej0 - 1 + 0.1 + (hfea * 360 / 255) > 360)
                               huej0f = ((huej0 - 1 + 0.1 + 360) * 255 / 360); // XXX TEST huej0f = ((huej0 - 1 + 0.1 - 361) * 255 / 360); 
    
                           fn = (((-huej0f + Math.Abs(-huej0f)) / 2) / -huej0f); //  * ax1h;
                           ft = (-huej0f / (hfea + 0.01)); // tol -> hfea
                           fu = (1 - ft * amtx);
                           fv = (fu * fn + ax0h * (1 - fn)); //(fu * fn * fo + ax1 * fm + ax1 * (1 - fn)); // 
    
                           // HUE FADING FROM_LIMITER
                           double huej1f = (huej1 * 255 / 360);
                           if (huej1 - 1 + 0.1 + (hfea * 360 / 255) > 359) //XXXX if (huej1 - 1 + 0.1 + (hfea * 360 / 255) > 360)
                               huej1f = ((huej1 - 1 + 0.1 - 360) * 255 / 360); // XXX huej1f = ((huej1 - 1 + 0.1 - 361) * 255 / 360); 
                           if (huej1 - 1 + 0.1 + (hfea * 360 / 255) < 0) //XXXX if (huej1 - 1 + 0.1 + (hfea * 360 / 255) > 360)
                               huej1f = ((huej1 - 1 + 0.1 + 360) * 255 / 360); // XXX huej1f = ((huej1 - 1 + 0.1 - 361) * 255 / 360); 
    
                           ffn = (((-huej1f + Math.Abs(-huej1f)) / 2) / -huej1f); //  *ax1h;
                           fft = (-huej1f / (hfea + 0.01)); // tol -> hfea
                           ffu = (1 - fft * amtx);
                           ffv = (ffu * ffn + ax0h * (1 - ffn)); //(ffu * ffn * ffo + ax1 * ffm + ax1 * (1 - ffn)); // 
                           fxx = (Math.Max(fv, ffv)); // HUE TO ALPHA MULTIPLIER
                           if (huebox == false)
                               fxx = 1; // fxx = ax1h; // 
    
                           // SATURATION FADING TO_LIMITER
                           double satjf = (satj * 255 / 100); //  * ax1 ei sobi siia
                           double fns = (((-satjf + Math.Abs(-satjf)) / 2) / -satjf); //  * ax1s;
                           double fts = (-satjf / (tol + 0.01)); // tol -> hfea
                           double fus = (1 - fts * amtx);
                           double fvs = (fus * fns + ax0s * (1 - fns)); //double fvs = (fus * fns + ax1 * (1 - fns));
    
                           // SATURATION FADING FROM_LIMITER
                           double satj1f = (satj1 * 255 / 100); //  * ax1 ei sobi siia
                           double ffns = (((-satj1f + Math.Abs(-satj1f)) / 2) / -satj1f); //  * ax1s;
                           double ffts = (-satj1f / (tol + 0.01)); // tol -> hfea
                           double ffus = (1 - ffts * amtx);
                           double ffvs = (ffus * ffns + ax0s * (1 - ffns)); //(ffu * ffn * ffo + ax1 * ffm + ax1 * (1 - ffn)); // 
                           double fxxs = (Math.Max(fvs, ffvs)) * fxx; // SATURATION+HUE TO ALPHA MULTIPLIER
                           if (satbox == false)
                               fxxs = fxx; // fxxs = ax1s * fxx; // 
    
    
                           // BRIGHTNESS FADING TO_LIMITER
                           double fnb = (((-brtj + Math.Abs(-brtj)) / 2) / -brtj); //  * ax1b;
                           double ftb = (-brtj / (tol + 0.01)); // tol -> hfea
                           double fub = (1 - ftb * amtx);
                           double fvb = (fub * fnb + ax0b * (1 - fnb)); //(fu * fn * fo + ax1 * fm + ax1 * (1 - fn)); // 
    
                           // BRIGHTNESS FADING FROM_LIMITER
                           double ffnb = (((-brtj1 + Math.Abs(-brtj1)) / 2) / -brtj1); //  * ax1b;
                           double fftb = (-brtj1 / (tol + 0.01)); // tol -> hfea
                           double ffub = (1 - fftb * amtx);
                           double ffvb = (ffub * ffnb + ax0b * (1 - ffnb)); //(ffu * ffn * ffo + ax1 * ffm + ax1 * (1 - ffn)); // 
                           double fxxb = (Math.Max(fvb, ffvb)) * fxxs; // BRIGHTNESS+HUE+SATURATION TO ALPHA MULTIPLIER
                           if (bnessbox == false)
                               fxxb = fxxs; // fxxb = ax1b * fxxs;// 
    
                           // even out alpha for non-selected areas
                           double fxxx0 = Math.Ceiling(fxxb);
                           double fxxx = fxxb + (1 - amtx) * (1 - fxxx0);
    
                           if (fxxx < (1 - amtx))
                               fxxx = (1 - amtx);
    
                           if (ovrride == true) // NEW
                               ao = 255;
    
                           ax = (ao * fxxx); // FINAL ALPHA
    
                           // TRICK TO BYPASS GAUSSIAN BLUR MISBEHAVIOUR ON SEMITRANSPARENT PIXELS (BUG IN PDN 3.22)
                           // TYPE OF BUG: ALPHA (and Value) OF SEMITRANSPARENT PIXELS IS REDUCED
                           // WE MAKE GRAYSCALE IMAGE WITH NO TRANSPARENCY, BASED ON ALPHA VALUES
                           // THEN WE BLUR THIS (CALLED FROM MAIN RENDER) WITHOUT MISTAKES AND TURN RESULT BACK TO ALPHA VALUES
                           ry = Utility.ClampToByte(ax);
                           gy = Utility.ClampToByte(ax);
                           by = Utility.ClampToByte(ax);
                           ay = Utility.ClampToByte(255);
                           OrigPixel = ColorBgra.FromBgra(by, gy, ry, ay); 
    
                           *ptr = OrigPixel;
                           ++ptr;
    
                       }
                   }
               }
           }
    
    
       }
    }

  2. For plugin developers being interested, here is the C# class for RGB to HSV transformations, operating with floats. It returns near perfect accuracy. I used it in my Color Mixer plugin.

    (Somehow I couldn't get ennixo's solution work as intended)

    RGBtoHSV.zipIf using it in your project, call the transformation as follows:

    RGBtoHSV.GetHSV(R, G, B, out H, out S, out V);

    or

    RGBtoHSV.GetRGB(H, S, V, out R, out G, out B);

    All RGB and HSV values to be (float).

  3. What overlapping effect, if any, is there between this plugin and the shadow/highlight plugin?

    Well, none really. They are completely different purpose tools (color manipulation vs brightness defects recovery).
    This is a great plugin! Very much like one I use to use in GIMP... only better!
    I would like to study that gimp plugin. Can you point me some details?
    Would it be possible to increase the control range from 0-200 to 0-255?
    Which control? Saturation range is 0-200 same as PdN original. Does anybody really need more?
  4. Thanks for support guys! :)

    Just note that it still needs few improvements.

    Beware that in *some* circumstances, especially when used on noisy photos, the brightness and contrast adjustments *may* bring up some nasty grain patterns at otherwise smooth looking areas (for example clouds). It's not a bug but I try to find some workaround later.

    Saturation and hue controls are still ok to use (those were the main purpose of this plugin anyway).

  5. This plugin is designed for fine color adjustments. It appears under Adjustments menu.

    Download as part of my plugin pack.

     

    Menu location: Adjustments.

    colormixer_ss.png

     

    Updated to version 1.2 (2008-12-24)!

    Changes: few code optimizations.

    Updated to version 1.1 (2008-08-31)!

    - Compatibility fix for paint.net 3.36

     

    You can adjust saturation, brightness, contrast and hue for each color (hue) range separately or all together.

    Amount of adjustment between the ranges is changed gradually, so there is no sharp cut-off, for example between red and yellow.

    Due to high precision calculations in the code, this plugin renders saturation and hue better than PdN's original Hue/Saturation effect. (the issue has been discussed here)

     

    Example:

    this pic asks for some saturation, but regular saturation adjustment is likely to make sky over-saturated. That's where Color Mixer comes handy.

    colormixer_sample1.jpg

    Settings used:

    Saturation: base 134, cyan 83, blue 74

    Brightness: cyan -13, blue -13

    Contrast: yellow -13, green -28

     

    I may want to improve the UI soon, so far all your requests are welcome.

    I have thought about placing all the hue nubs into a single continuous hue-gradient bar.

    Save/Load settings option is also on my wishlist (wish I knew how to do it :wink: ).

     

    • Upvote 1
  6. Simon, I guess you are following Tom Jackson's "How to start a fire" tutorial.

    In this case I suggest you to use linear transformation instead of curves. That is just few lines of code, easy to handle and basically same result.

    Here is a codelab code that imitates linear "curve" with middle control points located as:

    Red: 0, 0

    Green: 150, 0

    Blue: 215, 0

    // Author: Tanel Rüütli
    int Amount1=0; //[0,255]Red Control Point
    int Amount2=150; //[0,255]Green Control Point
    int Amount3=215; //[0,255]Blue Control Point
    
    void Render(Surface dst, Surface src, Rectangle rect)
    {
       Rectangle selection = this.EnvironmentParameters.GetSelection(src.Bounds).GetBoundsInt();
    
       ColorBgra CurrentPixel;
       for(int y = rect.Top; y < rect.Bottom; y++)
       {
           for (int x = rect.Left; x < rect.Right; x++)
           {
               CurrentPixel = src[x,y];
    
    // initial rgb values that you have after clouds effect
    byte r = (byte)CurrentPixel.R;
    byte g = (byte)CurrentPixel.G;
    byte b = (byte)CurrentPixel.B;
    byte a = (byte)CurrentPixel.A;
    
    // Return multipliers from our "Control Point" values
    double rx = 255 / (255 - Amount1 + 0.01);  // there is + 0.01 to avoid divide-by-zero exception when Amount is 255
    double gx = 255 / (255 - Amount2 + 0.01);  // there is + 0.01 to avoid divide-by-zero exception when Amount is 255
    double bx = 255 / (255 - Amount3 + 0.01);  // there is + 0.01 to avoid divide-by-zero exception when Amount is 255
    
    // Calculate new rgb values
    double r1 = 255 - rx * (double)(255 - r);
    double g1 = 255 - gx * (double)(255 - g);
    double b1 = 255 - bx * (double)(255 - ;
    
    // Final RGBA
    CurrentPixel.R = Utility.ClampToByte(r1);
    CurrentPixel.G = Utility.ClampToByte(g1);
    CurrentPixel.B = Utility.ClampToByte(b1);
    CurrentPixel.A = a;
    
               dst[x,y] = CurrentPixel;
           }
       }
    }
    

    You can test it on the gray fire image from Tom's tutorial.

    Effectively my code does the same as applying following curves setup (I used Curves+ for this example, because it allows "straight curves"):

    1728_df12ae2346c21b0c1813a3bf72d606f2

    If you tie the above Amount1, Amount2 and Amount3 with your plugin controls, or even better, a color wheel control (through opposite RGB values)* then user can easily change color of fire.

    * Edit:

    by "opposite RGB values" I mean Amount1=255-ColorWheel.R; Amount2=255-ColorWheel.G; Amount3=255-ColorWheel.B;

    this way the color of fire turns to same range as the selection in ColorWheel.

  7. Hi,

    If you have used MS Office programs then you know there is additional "paste special" function where you can specify which property of clipboard object will be pasted over destination (for example there is options text only, format, formulas, etc).

    I think it would be cool to have similar thing is PdN: you could choose between RGB, Alpha, Hue, Saturation, Value...

    For example if I chose Paste Special > Alpha then resulting image would keep it's original color values but apply Alpha from copied image.

    What do you think? Could someone make such plugin?

  8. Hi,

    while building and testing a new plugin, I discovered that Hue/Saturation effect in PdN renders somewhat jagged results, especially noticable on smooth gradients.

    I know that this is caused by RGB>HSV>RGB conversion which is not 100% accurate.

    I studied the PdN 3.22 source and came to conclusion that accuracy falls mostly during color values being thrown between several operations (UnaryPixelOps, RgbColor, HsvColor, ColorBgra). Current effect system passes color values between color operations by (int) or (byte). Therefore precision is rounded down repeatedly, resulting with coarse results.

    I thought that this can be improved. "Bad" rounding can be avoided with passing color values by (float).

    To test my theory, I collected the pieces of PdN 3.22 source and put together Codelab code to imitate the color operations. I replaced the (int) or (byte) conversions with (float) where possible. Where converting to (byte) was inevitable, I used round() function before conversion. (Sidenote: I haven't analysed the impact of each change separately, so some of those may be useless.)

    Results are very good. Gradients are rendered smoothly even after running the effect several times.

    You can download the codelab source with few comments in it (.cs file) and improved effect (.dll file) here:

    -- attachment removed 2008-12-24 --

    download the "Hue / Saturation+" as part of my plugin pack.

    To test it and compare with original effect I recommend this test image with color, saturation and brightness gradients.

    Here are some examples

    hsv_sample_1.png

    This is a piece of the linked testimage. See how gradients have got tiny steps in the middle pic. Even gray one which should not be affected by saturation adjustment. The right pic is still nicely smooth.

    Now a "real life subject": piece of sky from a photo. Sky is always smooth gradient in terms of color.

    hsv_sample_sky.png

    I added contrast to exaggerate the result. See how middle pic became blotchy in some areas, while the right pic still holds up the gradient quite well.

    One more issue which I *think* is wrong with original Hue/Saturation effect: order of processing.

    Currently the adjustments order is Saturation>Hue>Lightness.

    I think this is wrong because in case of extremely saturated colors (R, G or B channel at 255) the Saturation adjustment itself will change hue *before* Hue adjustment takes over. Some of you may think what's the difference, but there *is* difference because Saturation adjustment handles different colors differently. Correct adjustments order would be Hue>Saturation>Lightness, so that Saturation adjustment reads the intensity() value from Hue-adjusted color.

    For example, take the above linked test image and open original Hue/Saturation effect. play with hue slider - color wheels "roll" smoothly. Now dial in saturation 150 and play with hue slider again. No more smooth rolling - color areas get stretched and compressed while you change hue.

    Now try the same with "Hue/Saturation IMPROVED" effect where I have changed the order. See the difference?

    I hope the improvements can be included to PdN somehow.

    Please, your comments!

    hue_saturation_improved.zip

  9. Goodness, has this always been like this?
    Yeah, I always thought that the tooltips indicate features coming soon... until I peeked the source.
    Looking for features to cut? Apparently, no one's ever used that one. :D
    Hey, we never had chance to try! :?

    Color chooser is very useful for input and gamma points, especially if the color can be picked from original image, or from preview box. I hope eyedropper-style behaviour could be iplemented here also.

  10. Do you know if anyone has a plugin that will creat a soft white border around a picture (like a vignette effect only white) I will be shooting several weddings this year and some of the clients want this soft white border effect on several shots. It can be done with the filters I have but it's better done afterwards because the filter effect is part of the image that can't be changed.

    Check out this tutorial: viewtopic.php?f=12&t=5513#p57490

    Maybe Jesse get some ideas for new plugin there too.

×
×
  • Create New...