Jump to content
How to Install Plugins ×

Shadow / Highlight Recovery v2.2 (2008-12-24)


Tanel

Recommended Posts

  • 1 month later...
  • 5 months later...

Well, the code itself is not particularly elegant nor educative. Most part of the rendering is just brutal math, without comments about ongoing. That's because I modelled the effect in Excel and just translated the formulas into C#.

Anyway, here is the rough description of the process.

1. Blur the image from src to dst

2. Calculate the brightness of blurred pixels on dst

3. Define a tone curve for brightening/darkening

4. Calculate the amount of brightening/darkening needed for particular pixel, by evaluating the blurred pixel value in relation to the tone curve

5. Apply brightening/darkening to original src pixels and put them to dst

Blur is the key of that effect. Calculating the needed amount of brightening from blurred surface helps to preserve original contrast and texture of the subjects.

Downside is the halos appearing sometimes around large high-contrast subjects.

Here is the main render class from the VS2005 project. I warned you! :wink:

using System;
using System.Collections;
using System.Drawing;
using PaintDotNet;
using PaintDotNet.Effects;
using PaintDotNet.PropertySystem;

namespace ShadowHighlight
{
   // [EffectCategory(EffectCategory.Adjustment)] // used to be in Adjustments menu
   public class EffectPlugin
       : PaintDotNet.Effects.Effect
   {
       public static string StaticName { get { return "Shadow / Highlight Recovery"; } }

       public static Bitmap StaticImage { get { return new Bitmap(typeof(EffectPlugin), "EffectPluginIcon.png"); } }

       //public static string StaticSubMenuName  { get { return null; } } // Use for no submenu 
       public static string StaticSubMenuName { get { return SubmenuNames.Photo; } }

       public EffectPlugin()
           : base(StaticName, StaticImage, StaticSubMenuName, EffectFlags.Configurable)
       {
           this.blurEffect = new GaussianBlurEffect();
           this.blurProps = this.blurEffect.CreatePropertyCollection();
       }

       public override EffectConfigDialog CreateConfigDialog()
       {
           return new EffectPluginConfigDialog();
       }

       private GaussianBlurEffect blurEffect;
       private PropertyCollection blurProps;
       private UnaryPixelOps.Identity Nop = new UnaryPixelOps.Identity(); // this op returns original image when "Review" button is pressed

       public override unsafe void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length)
       {
           EffectPluginConfigToken token = (EffectPluginConfigToken)parameters;

           int myred = token.Redvalue; // Color mask
           int mygreen = token.Greenvalue; // Color mask
           int myblue = token.Bluevalue; // Color mask
           int ha0 = token.Highlights;
           int ht = token.High_T; // Highlights tonal range
           int sa0 = token.Shadows;
           int st = token.Shadows_T; // Shadows tonal range
           int lvl = token.Slevels; // Smoothing
           int sat0 = token.Hsaturation; // Highlights saturation
           int sats0 = token.Ssaturation; // Shadows saturation
           int amp = token.Deepshadow; // Compression
           bool check = token.checkBox2; // that's linked to Review button pressed state

           int rad = token.Radius;
           PropertyBasedEffectConfigToken blurToken = new PropertyBasedEffectConfigToken(this.blurProps);
           blurToken.SetPropertyValue(GaussianBlurEffect.PropertyNames.Radius, token.Radius);
           this.blurEffect.SetRenderInfo(blurToken, dstArgs, srcArgs);
           base.OnSetRenderInfo(blurToken, dstArgs, srcArgs);

           Surface dst = dstArgs.Surface;
           Surface src = srcArgs.Surface;


           if (check == false) // "REVIEW" BUTTON IS PRESSED -> ORIGINAL IMAGE IS SHOWN
           {
               Nop.Apply(dst, src, rois, startIndex, length);
           }

           else  // "REVIEW" BUTTON IS NOT PRESSED -> EFFECT IS RUNNING
           {
               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* srcPtr = src.GetPointAddressUnchecked(roi.Left, y);
                       ColorBgra* dstPtr = dst.GetPointAddressUnchecked(roi.Left, y);
                       ColorBgra CurrentPixel;
                       ColorBgra OrigPixel;
                       byte ao, ry, by, gy; 
                       float r, g, b, ro, go, bo, vf, vfo, cr, cg, cb, c0, ampx,
                       rx, bx, gx, ha00, sa00, ha, sa, hs, vfoh, cr1, cg1, cb1, cx, rh00, rh1, rhx, gh00, gh1, 
                       ghx, bh00, bh1, bhx, sat, sats, satgs, satgs1, rs00, rs1, rs2, rs3, rsx, gs00, gs1, gs2, gs3, 
                       gsx, bs00, bs1, bs2, bs3, bsx, stx, htx, dsa0, dsa1, dsa12, dsax, dsax1, dha1, dha0, dha12, dhax, 
                       vfx00, vfx0, vfx;

                       for (int x = roi.Left; x < roi.Right; ++x)
                       {
                           CurrentPixel = *dstPtr;
                           r = (float)CurrentPixel.R;
                           g = (float)CurrentPixel.G;
                           b = (float)CurrentPixel.B;
                           vf = ((r + g +  / 3f);

                           OrigPixel = *srcPtr;
                           ro = (float)OrigPixel.R;
                           go = (float)OrigPixel.G;
                           bo = (float)OrigPixel.B;
                           ao = OrigPixel.A;

                           ampx = amp / 10f;
                           ha = ha0 / 100f;
                           ha00 = ha0 * 1f;
                           stx = st * 1.1f;
                           htx = ht * 1f;
                           sa = sa0 / 100f;
                           sa00 = sa0 * 1f;
                           sat = sat0 / 100f;
                           sats = sats0 / 100f;

                           vfo = ((ro + go + bo) / 3f);


                           // HALO INTENSITY
                           vfx00 = (vf - vfo);
                           vfx0 = (vfx00 * (float)lvl / 100f);
                           vfx = (vfo + vfx0);

                           // COLOR MASK
                           cr = myred / 100f;
                           cg = mygreen / 100f;
                           cb = myblue / 100f;
                           c0 = vf; 
                           cr1 = (r * cr / c0); 
                           cg1 = (g * cg / c0); 
                           cb1 = (b * cb / c0); 
                           cx = ((cr1 + cg1 + cb1) / 3f);


                           // SHADOWS
                           satgs = 1f; // thought I might want to tune this
                           float s0 = 0;
                           if (sa0 > 0)
                               s0 = 1f;
                           dsa0 = (1f + (100f - stx) * 2f / 100f);
                           dsa1 = (50f / (vfx + 10f) - 0.3f * dsa0); // this is our "tone cuve" (actually factor by which Amount control is pre-amplified)
                           if (dsa1 < 0)
                               dsa1 = 0;
                           dsa12 = Math.Min((ampx + 1f) / 3f, dsa1); 
                           dsax = (1f + dsa12 * satgs); 
                           dsax1 = (1f + dsa12 * cx * sa);
                           satgs1 = (s0 * dsax1 + (satgs + 1f) * (1f - s0)); 

                           rs00 = (ro + vfo * (satgs1 - 1f));
                           rs1 = (ro * satgs1 - rs00);
                           rs2 = (rs00 + rs1 * sats); 
                           rs3 = (ro + (ro - vfo) * sats * dsa12 * cx * (satgs1 - 1f));
                           rsx = ((rs2 * s0 + rs3 * (1f - s0)) / ro);

                           gs00 = (go + vfo * (satgs1 - 1f));
                           gs1 = (go * satgs1 - gs00);
                           gs2 = (gs00 + gs1 * sats); 
                           gs3 = (go + (go - vfo) * sats * dsa12 * cx * (satgs1 - 1f));
                           gsx = ((gs2 * s0 + gs3 * (1f - s0)) / go);

                           bs00 = (bo + vfo * (satgs1 - 1f));
                           bs1 = (bo * satgs1 - bs00);
                           bs2 = (bs00 + bs1 * sats); 
                           bs3 = (bo + (bo - vfo) * sats * dsa12 * cx * (satgs1 - 1f));
                           bsx = ((bs2 * s0 + bs3 * (1f - s0)) / bo);


                           //HIGHLIGHTS
                           vfoh = (255f - vfo);
                           dha0 = (1f + (100f - htx) * 2f / 100f);
                           dha1 = (50f / (255f - vfx + 10f) - 0.3f * dha0);
                           if (dha1 < 0)
                               dha1 = 0;
                           dha12 = Math.Min((ampx + 1f) / 3f, dha1);
                           dhax = (1f + dha12 * satgs * cx * ha);

                           hs = (vfoh * dhax - vfoh); 

                           rh00 = (vfo - (vfo - ro * rsx) * sat * dha12 * cx);
                           rh1 = (hs + vfo - rh00); 
                           rhx = (1f - rh1 / rh00);

                           gh00 = (vfo - (vfo - go * gsx) * sat * dha12 * cx); 
                           gh1 = (hs + vfo - gh00); 
                           ghx = (1f - gh1 / gh00);

                           bh00 = (vfo - (vfo - bo * bsx) * sat * dha12 * cx); 
                           bh1 = (hs + vfo - bh00); 
                           bhx = (1f - bh1 / bh00);


                           // FINAL RGB VALUES
                           rx = ro * rsx * rhx; 
                           gx = go * gsx * ghx; 
                           bx = bo * bsx * bhx; 

                           ry = Utility.ClampToByte(rx);
                           gy = Utility.ClampToByte(gx);
                           by = Utility.ClampToByte(bx);
                           OrigPixel = ColorBgra.FromBgra(by, gy, ry, ao);

                           *dstPtr = OrigPixel;

                           ++srcPtr;
                           ++dstPtr;

                       }
                   }
               }
           }
       }
   }
}

Link to comment
Share on other sites

  • 6 months later...

Hi, I'm kinda new to Paint.net and I haven't gotten quite used to everything. I want to learn how to remove the shine from my face, but I'm stuck. Nothing I've tried seems to work, so I did a search and someone mentioned using the Shadows and Highlights plugin you created.

Can you please help me?

Link to comment
Share on other sites

I've had this plugin for over a year, and have neglected commenting on it - but what a great one it is!

I use this all the time in photo editing - and the results are so high quality!

Thanks for a wonderful tool :)

Jaykaye - you might want to try using the Clone Stamp tool - play around with it and I think you'll be able to find the results you are looking for. This plugin is more for restoration in my opinion.

"pyrochild, you're my favorite person ever. We should go snowboarding some time."~ 007 Nab. Ish.

PDN Gallery | I Made a Deviant Art!

Link to comment
Share on other sites

  • 2 weeks later...
  • 1 year later...

I downloaded this plugin today and there is a problem with the UI. In the Basic Mode, the upper half of the slider knob for the Highlights adjustment is missing and there is no "Highlights" label to indicate that's what the middle slider is for. In the Adavanced Mode, the upper half of the slider knob for the Tonal Range adjustment is missing and there is no "Tonal Range" label to indicate that's what that slider is for. (I am including a screen capture file showing this)

It's not clear whether the plugin is malfunctioning as well. It doesn't seem to remove shadows and highlights as portrayed in your screen shots, but that could be the result my own failings

I've downloaded the plugin pack several times, thinking maybe I made a mistake in the process that could have corrupted the plugins, but the result was the same each time; and the color mixer plugin works fine.

Any suggestions ?

.

post-81070-129705734249_thumb.png

Link to comment
Share on other sites

  • 4 years later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...