Sign in to follow this  
Followers 0
BoltBait

Ink Sketch Effect Plugin - Now with Source Code!

13 posts in this topic

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:

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.

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>

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...

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] Ink Outline
IntSliderControl Amount2=50; // [0,100] 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
0

Share this post


Link to post
Share on other sites

From another thread...

I'm still learning how to create complex effects...

I'm learning too, so I would much appreciate to see the source, for my self learning!

MadJik, I have added the Ink Sketch souce code to the first post.

Enjoy. :D

0

Share this post


Link to post
Share on other sites

It so nice! :D

Thanks fo toturial! Yuo realy help me!

______________________________________________________________________

<Despammed by EER>

0

Share this post


Link to post
Share on other sites

this is a great information that made me know a lot about this tool and different tricky features. now i may better use it in multiple applications.

thanks :)

0

Share this post


Link to post
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..

Edited by SilverGhost
0

Share this post


Link to post
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.

0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0