Jump to content
How to Install Plugins ×

Ink Sketch Effect Plugin - Now with Source Code!


BoltBait

Recommended Posts

 

Ink Sketch Effect

 

This effect will turn the photograph of my daughter (on the left) into the ink sketch (on the right):

 

InkSample.jpg

 

The user interface is adjustable:

 

InkSketchUI.jpg

 

The Idea

 

I thought that it would be easy to automate the steps that I had come up with for turning a picture into an ink sketch. It turned out that it wasn't too hard. Rick Brewster was nice enough to send me the source code to the Pencil Sketch effect from Paint.NET 3.05. From that, I learned how to combine preexisting effects and blending operations.

 

I'm still learning how to create complex effect filters for Paint.NET. I think this is one of my best yet.

 

The Effect DLL

 

NOTE: This effect is now included in Paint.NET. If you have upgraded to Paint.NET 3.10 or greater, you do not need to download this effect... you already have it!

 

Source Code

 

MadJik since you asked how to write composition type effects...

 

I thought I'd post this PM conversation between me and Rick. He gives some good information in there:

 

BoltBait said:

Can you send me the source code to the new pencil sketch effect? I have a feeling that I could learn a lot from that code.

You have often talked about utilizing other effects from within an effect. I want to learn how to do that. (I learn best from code examples.)

If you want to wait until after release, I understand. I just want to learn how do combine effects from within an effect.

Rick Brewster said:

You can look at the source code for Glow for another example of some effect composition.

The code isn't necessarily complicated. The main problem with the effect system in Paint.NET is that effects are not easily composable. To properly compose two or more effects in a rendering pipeline of sorts, you either have to (1) allocate a full Surface object for each stage of this pipeline (or at least 1 extra surface and then flip flop between the surfaces as you progress through the pipeline), or (2) have "insider knowledge" as to which effects or adjustments can be done in-place and still be correct. In the case of Pencil Sketch, the blur operation must be done first, after which the rest of the operations are done in-place at either the rectangle or the pixel level.

<source code to pencil sketch snipped>

BoltBait said:

Wow! Thanks!

It is my idea to automate this:

<!-- m -->http://paintdotnet.12.forumer.com/viewtopic.php?t=3674<!-- m -->

Do you think it would be possible/easy? Of course I could simplify the steps somewhat before I began...

Rick Brewster said:

Automating that will not be performant because of the memory use required. Right now if you want to chain together multiple effects you'll have to allocate multiple rendering surfaces that are the same size as the input and output surfaces. For example, effect 1 would have to render from srcArgs on to surface1, and then effect2 would have to render from surface1 to dstArgs, and then effect3 would have to render from dstArgs to surface1, etc. etc. The reason Pencil Sketch works is because Blur is executed first, and also because it is the only effect/adjustment in the action list that reads from multiple input pixels to produce a single output pixels. All the others are adjustments, which write to the same output pixel coordinate as the input pixel (adjustments are a bit misnomered in Paint.NET right now: they really should be implemented as UnaryPixelOps, not Effects with an attribute).

 

Well, in its current state, Rick was right. I thought about it for some time and I simplified the steps to the point that it was possible. And, I did it without needing to create additional layers.

 

Here are the simplified steps that my code simulates:

 

  1. Open an image
  2. Duplicate the layer
  3. Effects > Glow the bottom layer with radius 6 (default) and both other sliders tied together based on my Coloring slider
  4. Run the Toon effect on the top layer
  5. On the top layer, Adjustments > Black and White
  6. Then, Adjustments > Brightness / Contrast... set the contrast to 100% and adjust the brightness based on my Ink Outline slider
  7. Change the blending mode of the top layer to Darken

 

And, here is the code (NOTE: This is now a CodeLab script):

 

/* InkSketchEffect.cs
Copyright (c) 2007 David Issel
Contact info: BoltBait@hotmail.com http://www.BoltBait.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Name: Ink Sketch
// Submenu: Artistic
// Author: BoltBait
// Title: BoltBait's Ink Sketch Effect v1.0
// Desc: Convert your photo into an ink sketch.
// Keywords: Ink|Sketch
// URL: http://BoltBait.com/pdn
#region UICode
IntSliderControl Amount1=50; // [0,100,4] Ink Outline
IntSliderControl Amount2=50; // [0,100,3] Coloring
#endregion

// Setup for using pixel op
private UnaryPixelOps.Desaturate desaturateOp = new UnaryPixelOps.Desaturate();

// Setup for using Darken blend op
private BinaryPixelOp darkenOp = LayerBlendModeUtil.CreateCompositionOp(LayerBlendMode.Darken);

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

unsafe void Render(Surface dst, Surface src, Rectangle rect)
{
    const int size = 5; 

    int[,] conv = new int[size, size] { {-1,  -1,  -1,  -1, -1}, 
                                        {-1,  -1,  -1,  -1, -1}, 
                                        {-1,  -1,  30,  -1, -1}, 
                                        {-1,  -1,  -1,  -1, -1}, 
                                        {-1,  -1,  -5,  -1, -1}, }; 

    int radius = (size-1)/2; 
    
    RenderArgs dstArgs = new RenderArgs(dst);
    RenderArgs srcArgs = new RenderArgs(src);
    
    // Render colored pencil effect
    GlowEffect glowEffect = new GlowEffect();
    PropertyCollection glowProps = glowEffect.CreatePropertyCollection();
    PropertyBasedEffectConfigToken glowParameters = new PropertyBasedEffectConfigToken(glowProps);
    glowParameters.SetPropertyValue(GlowEffect.PropertyNames.Radius, 6);
    glowParameters.SetPropertyValue(GlowEffect.PropertyNames.Brightness, -(Amount2-50)*2);
    glowParameters.SetPropertyValue(GlowEffect.PropertyNames.Contrast, -(Amount2-50)*2);
    glowEffect.SetRenderInfo(glowParameters, dstArgs, srcArgs);
    glowEffect.Render(new Rectangle[1] {rect},0,1);

    // Now in the main render loop, the dst canvas has an glowed version of the src canvas
    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        if (IsCancelRequested) return;
        int top = y - radius, bottom = y + radius;
        if (top < 0) top = 0;
        if (bottom >= dstArgs.Height) bottom = dstArgs.Height - 1;
        ColorBgra* srcPtr = src.GetPointAddressUnchecked(rect.Left, y);
        ColorBgra* dstPtr = dst.GetPointAddressUnchecked(rect.Left, y);
        for (int x = rect.Left; x < rect.Right; x++)
        {
            // Calculate the Ink Outline
            int left = x - radius, right = x + radius;
            int c = 0, s = 0, r = 0, g = 0, b = 0;
            if (left < 0) left = 0;
            if (right >= dstArgs.Width) right = dstArgs.Width - 1;
            for (int v = top; v <= bottom; v++)
            {
                ColorBgra* pRow = srcArgs.Surface.GetRowAddressUnchecked(v);
                int j = v - y + radius;

                for (int u = left; u <= right; u++)
                {
                    int i1 = u - x + radius;
                    int w = conv[i1, j];
                    ColorBgra* pRef = pRow + u;
                    r += pRef->R * w;
                    g += pRef->G * w;
                    b += pRef->B * w;
                    s += w;
                    c++;
                }
            }
            ColorBgra TopLayer = ColorBgra.FromBgr(Clamp2Byte(b),Clamp2Byte(g),Clamp2Byte(r));

            // Adjust Brightness and Contrast of the Ink Sketch
            TopLayer = desaturateOp.Apply(TopLayer);
            if (TopLayer.R > (Amount1 * 255 / 100))
            {
                TopLayer = ColorBgra.White;
            }
            else
            {
                TopLayer = ColorBgra.Black;
            }

            // Mix the Ink Sketch with the Colored Pencil effect
            *dstPtr = darkenOp.Apply(*dstPtr, TopLayer);
            srcPtr++;
            dstPtr++;
        }
    }
}

 

Now, I can't wait to see what MadJik creates from this... :D

 

Link to comment
Share on other sites

No, BoltBait was the first to post. :P

I like it!

I am not a mechanism, I am part of the resistance;

I am an organism, an animal, a creature, I am a beast.

~ Becoming the Archetype

Link to comment
Share on other sites

Nicely done!

BK_BloodSaw_sig.png

- DO NOT contact me asking for the .pdn of my avatar or the PDN logo. Thank you. Have a nice day.

Link to comment
Share on other sites

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

BoltBait, could you help me in similar task?

I'm trying to implement complex effect. I need to run first effect (custom blur) on all SrcArgs.Surface pixels, put result to temporary surface and then run second effect (convolution) on it. That is similar process to this Ink Sketch plugin..

Edited by SilverGhost
Link to comment
Share on other sites

BoltBait, could you help me in similar task?

I'm trying to implement complex effect. I need to run first effect (custom blur) on all SrcArgs.Surface pixels, put result to temporary surface and then run second effect (convolution) on it. That is similar process to this Ink Sketch plugin..

Start a thread for your idea here: http://forums.getpaint.net/index.php?/forum/17-plugin-developers-central/

and we'll all see if we can help you.

I'm sure what you want can be done, it just may not be possible in CodeLab.

Link to comment
Share on other sites

  • 11 months 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...