Jump to content

Another plugin (GradientMap) and first difficulties


Recommended Posts

Hi,

 

I create a plugin for doing gradient mapping.

The gradient is constituted of 256 colors (one per gray level).

Indeed I want to avoid computing the gradient for each Render call.

So I chose the strategy to trigger the creation of the gradient on ihm change but I've done it in old school way by comparing previous values.

Is there a signal raised by the API which let me update on event instead ?

 

Gradient applied on the following image http://www.fraktales.net/photoprocessor/img/photo.jpg

and the result http://www.fraktales.net/photoprocessor/img/photo_infected.gif

 

// Name: Gradient map
// Submenu:
// Author: Pascal Ollive
// Title: Gradient map
// Version: 1.0
// Desc: Gradient map
// Keywords: gradient|map|color
// URL:
// Help:
#region UICode
ListBoxControl color1 = 0; // First color|Black|Blue|Green|Cyan|Jade|Red|Magenta|Pink|Amber|Yellow|White
ListBoxControl color2 = 1; // Second color|Black|Blue|Green|Cyan|Jade|Red|Magenta|Pink|Amber|Yellow|White
ListBoxControl color3 = 8; // Third color|Black|Blue|Green|Cyan|Jade|Red|Magenta|Pink|Amber|Yellow|White
ListBoxControl color4 = 10; // Fourth color|Black|Blue|Green|Cyan|Jade|Red|Magenta|Pink|Amber|Yellow|White
CheckboxControl isSmoothRender = false; // Smooth render
#endregion

private const byte RANGE = 85;

private byte[] RGBA_PALETTE = { 0x00, 0x00, 0x00, 0xFF,//Black
                                0x00, 0x00, 0xAA, 0xFF,//Blue
                                0x00, 0xFF, 0x00, 0xFF,//Green
                                0x00, 0xFF, 0xFF, 0xFF,//Cyan
                                0x55, 0xAA, 0x55, 0xFF,//Jade
                                0xFF, 0x00, 0x00, 0xFF,//Red
                                0xFF, 0x00, 0xFF, 0xFF,//Magenta
                                0xFF, 0x55, 0xAA, 0xFF,//Pink
                                0xFF, 0xAA, 0x00, 0xFF,//Amber
                                0xFF, 0xFF, 0x00, 0xFF,//Yellow
                                0xFF, 0xFF, 0xFF, 0xFF //White
                                };

//
// Initialization to extreme values in order to force
// gradient creation at the first render call
//
private byte previousColor1 = 255;
private byte previousColor2 = 255;
private byte previousColor3 = 255;
private byte previousColor4 = 255;
private bool lastRender = true;

// One color per intensity level
private ColorBgra[] gradient = new ColorBgra[256];

//
// Any hmi change triggers an update of the gradient
//
private bool hmiStateChanged() {
    if (color1 != previousColor1) {
        return true;
    }
    if (color2 != previousColor2) {
        return true;
    }
    if (color3 != previousColor3) {
        return true;
    }
    if (color4 != previousColor4) {
        return true;
    }
    // XOR => true when two bool are differents
    return isSmoothRender ^ lastRender;
}

// Luma(pixel) = 0.2126R + 0.7152G + 0.0722B
// Coefficients are scaled into "9-bit" integer
// The coefficient sum is equals to 513
// Luminosity is ranged between 0 (inclusive) and 255.5 (exclusive)
// Quick dither to deliver 8-bit value => Binary pattern is fast and "good enough"
private byte rgbToGray(byte r, byte g, byte b, byte binaryPattern) {
    return (byte) ((109 * r + 367 * g + 37 * b + 256 * binaryPattern) >> 9);
}

private ColorBgra getRgbColor(byte colorIndex1, byte colorIndex2, byte coef) {
    ColorBgra output = new ColorBgra();
    byte c1 = (byte) (colorIndex1 << 2);
    byte c2 = (byte) (colorIndex2 << 2);

    output.R = (byte) ((RGBA_PALETTE[c1    ] * (255 - coef) + RGBA_PALETTE[c2    ] * coef) / 255);
    output.G = (byte) ((RGBA_PALETTE[c1 + 1] * (255 - coef) + RGBA_PALETTE[c2 + 1] * coef) / 255);
    output.B = (byte) ((RGBA_PALETTE[c1 + 2] * (255 - coef) + RGBA_PALETTE[c2 + 2] * coef) / 255);
    output.A = 255;

    return output;
}

private ColorBgra[] getGradient() {
    if (hmiStateChanged()) {
        //
        // Fill gradient content on user action
        //
        byte[] gamma = new byte[RANGE];

        if (isSmoothRender) {
            for (byte i = 0; i < RANGE; i++) {
                gamma[i] = (byte) (3 * i);
            }            
        }
        else {
            for (byte i = 0; i < 21; i++) {
                gamma[i] = i;
            }
            gamma[21] = 22;
            gamma[22] = 25;
            for (byte i = 23; i < 43; i++) {
                gamma[i] = (byte) (5 * i - 86);
            }
            for (byte i = 43; i < RANGE; i++) {
                gamma[i] = (byte) (255 - gamma[RANGE - i]);
            }
        }
        for (byte i = 0; i < RANGE; i++) {
            gradient[i] = getRgbColor(color1, color2, gamma[i]);
        }
        for (byte i = 0; i < RANGE; i++) {
            gradient[RANGE + i] = getRgbColor(color2, color3, gamma[i]);
        }
        for (byte i = 0; i < RANGE; i++) {
            gradient[2 * RANGE + i] = getRgbColor(color3, color4, gamma[i]);
        }
        gradient[3 * RANGE] = getRgbColor(color4, 0, 0);
    }

    return gradient;
}

void Render(Surface dst, Surface src, Rectangle rect) {
    ColorBgra[] g = getGradient();

    for (int y = rect.Top; y < rect.Bottom; y++) {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; x++) {
            ColorBgra pixel = src[x,y];
            byte binaryPattern = (byte) ((x ^ y) & 0x1);
            byte luma8bit = rgbToGray(pixel.R, pixel.G, pixel.B, binaryPattern);            
            dst[x,y] = g[luma8bit];
        }
    }
}

 

GradientMap.zip

Edited by loupasc
Orthographic fixes
Link to comment
Share on other sites

From what I can see with a quick look at your code (my excuse if I missed something),  you should compute the gradient in PreRender(), where it's only done once each time a control is changed.  You could still do the compare-to-previous-value test, though I doubt it's worth the effort.

Link to comment
Share on other sites

Here the last version of the Gradient Map plugin. I put the initialization in the PreRender method and it works fine.

Again thanks for the support, much appreciated 🙂

// Name: Gradient map
// Submenu: Render
// Author: Pascal Ollive
// Title: Gradient map
// Version: 1.1
// Desc: Gradient map
// Keywords: gradient|map|color
// URL:
// Help:
#region UICode
ListBoxControl color1 = 0; // First color|Black|Blue|Green|Cyan|Jade|Red|Magenta|Pink|Amber|Yellow|White
ListBoxControl color2 = 1; // Second color|Black|Blue|Green|Cyan|Jade|Red|Magenta|Pink|Amber|Yellow|White
ListBoxControl color3 = 8; // Third color|Black|Blue|Green|Cyan|Jade|Red|Magenta|Pink|Amber|Yellow|White
ListBoxControl color4 = 10; // Fourth color|Black|Blue|Green|Cyan|Jade|Red|Magenta|Pink|Amber|Yellow|White
CheckboxControl isSmoothRender = false; // Smooth render
#endregion

private const byte RANGE = 85;

private byte[] RGBA_PALETTE = { 0x00, 0x00, 0x00, 0xFF,//Black
                                0x00, 0x00, 0xAA, 0xFF,//Blue
                                0x00, 0xFF, 0x00, 0xFF,//Green
                                0x00, 0xFF, 0xFF, 0xFF,//Cyan
                                0x55, 0xAA, 0x55, 0xFF,//Jade
                                0xFF, 0x00, 0x00, 0xFF,//Red
                                0xFF, 0x00, 0xFF, 0xFF,//Magenta
                                0xFF, 0x55, 0xAA, 0xFF,//Pink
                                0xFF, 0xAA, 0x00, 0xFF,//Amber
                                0xFF, 0xFF, 0x00, 0xFF,//Yellow
                                0xFF, 0xFF, 0xFF, 0xFF //White
                                };

// One color per intensity level
private ColorBgra[] gradient = new ColorBgra[256];

// Luma(pixel) = 0.2126R + 0.7152G + 0.0722B
// Coefficients are scaled into "9-bit" integer
// The coefficient sum is equals to 513
// Luminosity is ranged between 0 (inclusive) and 255.5 (exclusive)
// Quick dither to deliver 8-bit value => Binary pattern is fast and "good enough"
private byte rgbToGray(byte r, byte g, byte b, byte binaryPattern) {
    return (byte) ((109 * r + 367 * g + 37 * b + 256 * binaryPattern) >> 9);
}

private ColorBgra getRgbColor(byte colorIndex1, byte colorIndex2, byte coef) {
    ColorBgra output = new ColorBgra();
    byte c1 = (byte) (colorIndex1 << 2);
    byte c2 = (byte) (colorIndex2 << 2);

    output.R = (byte) ((RGBA_PALETTE[c1    ] * (255 - coef) + RGBA_PALETTE[c2    ] * coef) / 255);
    output.G = (byte) ((RGBA_PALETTE[c1 + 1] * (255 - coef) + RGBA_PALETTE[c2 + 1] * coef) / 255);
    output.B = (byte) ((RGBA_PALETTE[c1 + 2] * (255 - coef) + RGBA_PALETTE[c2 + 2] * coef) / 255);
    output.A = 255;

    return output;
}

void PreRender(Surface dst, Surface src) {
    //
    // Fill gradient content on user action
    //
    byte[] gamma = new byte[RANGE];
    if (isSmoothRender) {
        for (byte i = 0; i < RANGE; i++) {
            gamma[i] = (byte) (3 * i);
        }            
    }
    else {
        for (byte i = 0; i < 21; i++) {
            gamma[i] = i;
        }
        gamma[21] = 22;
        gamma[22] = 25;
        for (byte i = 23; i < 43; i++) {
            gamma[i] = (byte) (5 * i - 86);
        }
        for (byte i = 43; i < RANGE; i++) {
            gamma[i] = (byte) (255 - gamma[RANGE - i]);
        }
    }
    for (byte i = 0; i < RANGE; i++) {
        gradient[i] = getRgbColor(color1, color2, gamma[i]);
    }
    for (byte i = 0; i < RANGE; i++) {
        gradient[RANGE + i] = getRgbColor(color2, color3, gamma[i]);
    }
    for (byte i = 0; i < RANGE; i++) {
        gradient[2 * RANGE + i] = getRgbColor(color3, color4, gamma[i]);
    }
    gradient[3 * RANGE] = getRgbColor(color4, 0, 0);
}

void Render(Surface dst, Surface src, Rectangle rect) {
    for (int y = rect.Top; y < rect.Bottom; y++) {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; x++) {
            ColorBgra pixel = src[x,y];
            byte binaryPattern = (byte) ((x ^ y) & 0x1);
            byte luma8bit = rgbToGray(pixel.R, pixel.G, pixel.B, binaryPattern);            
            dst[x,y] = gradient[luma8bit];
        }
    }
}

 

GradientMap.zip

  • Like 1
  • Upvote 1
Link to comment
Share on other sites

  • 1 year later...

Is it finished/ready for release?

Link to comment
Share on other sites

3 hours ago, AndrewDavid said:

It's not all it could be. I am trying to improve it. Without the Author active, should I take ownership?

Up to the author to give official okay on source code for taking it forward, even if they shared it. At the same time, common sense says if they shared the whole source code they're probably okay with it. Could be they're not and only shared it to get feedback, though. I doubt he'll sue you but idk it's your call. Unless an admin refuses to let you too

  • Upvote 1
Link to comment
Share on other sites

6 hours ago, AndrewDavid said:

If you install and run the plugin, you will see the settings get locked in after the first run. It's  a challenge for all you contributors.

 

I'm still not quite sure what you want to do, but if it's to have the plugin always start with the default control settings instead of the settings it was last executed with, there's not really a challenge, because I'm pretty sure it simply can't be done in CodeLab or in any other way that uses Indirect UI. Likewise, if it's to have a button that resets all four controls to their defaults. (I probably should qualify that with "it can't be done in an allowable way." I suppose it might be possible using reflection.)

Link to comment
Share on other sites

When building a plugin in Codelab, there are four choices when you select the Color Wheel. One is with a reset button(Default). 

 

LandscapeWebSmall.png.1a9e2030c456f61ddb6d82011768afc2.png

When the code is generated I see this related to the ColorWheelControl
using ColorWheelControl = PaintDotNet.ColorBgra;
    props.Add(new Int32Property(PropertyNames.Amount4, ColorBgra.ToOpaqueInt32(PrimaryColor), 0, 0xffffff));
    configUI.SetPropertyControlType(PropertyNames.Amount4, PropertyControlType.ColorWheel);
    Amount4 = ColorBgra.FromOpaqueInt32(token.GetProperty<Int32Property>(PropertyNames.Amount4).Value);

 

I see nothing that refers to Reset. This leads me to believe that's its built into the ColorWheelControl object. That would then mean to me that it falls upon @Rick Brewster to build it into the ListBoxControl. Hence your thread title and my request. If there is code available, please enlighten us. I see new code being generated in Codelab such as "instanceSeed = unchecked((int)DateTime.Now.Ticks)"; A way of tracking the build of codelab used to create the plugin.

 

 

 

PaintNetSignature.png.6bca4e07f5d738b2436f83d0ce1b876f.png

Link to comment
Share on other sites

7 hours ago, AndrewDavid said:

see nothing that refers to Reset. This leads me to believe that's its built into the ColorWheelControl object.

 

Okay, I finally get what you mean, and it does seem like it might be a good idea. You want the option of having a reset button on the List Box control. I suppose the reason List Boxes don't have reset buttons is because the choices are a fairly small range of distinct values, instead of a large more-or-less continuous range of values. I must admit, I never really thought about the fact that List Boxes don't have reset buttons like most of the other controls.

 

EDIT: Probably not, but possibly List Boxes do have an optional reset button that's just normally disabled. For instance, Checkboxes can have a title above the checkbox just like other controls, but it's normally disabled. If someone else doesn't provide an answer to that, I'll try to look into it later today or tomorrow. If List Boxes can have a reset button, it would be up to BoltBait to provide access to it in CodeLab. (As you correctly surmised, the reset button is an integral part of a control, not a separate control connected to the main control.)

  • Thanks 1
Link to comment
Share on other sites

MJW is correct, a Reset button is not implemented in the StaticListDropDownPropertyControl.

 

 

7 hours ago, AndrewDavid said:

I see new code being generated in Codelab such as "instanceSeed = unchecked((int)DateTime.Now.Ticks)"; A way of tracking the build of codelab used to create the plugin.

 

Umm, what?  That code is used in conjunction with a ReSeed button.

(September 25th, 2023)  Sorry about any broken images in my posts. I am aware of the issue.

bp-sig.png
My Gallery  |  My Plugin Pack

Layman's Guide to CodeLab

Link to comment
Share on other sites

@toe_head2001

 

In its entirety. I notice these  code lines starting to appear in the New Codelab

 

Spoiler

public GradientMapEffectPlugin()

    : base(StaticName, StaticIcon, SubmenuName, new EffectOptions() { Flags = EffectFlags.Configurable })

{

    instanceSeed = unchecked((int)DateTime.Now.Ticks);

}

 

along with this

Spoiler

[assembly: AssemblyMetadata("BuiltByCodeLab""Version=6.4.7995.19949")]

 

I thought the 2 were related.

PaintNetSignature.png.6bca4e07f5d738b2436f83d0ce1b876f.png

Link to comment
Share on other sites

19 minutes ago, AndrewDavid said:

I notice these  code lines starting to appear in the New Codelab

 

  Reveal hidden contents

 

along with this

  Reveal hidden contents

 

I thought the 2 were related.


The first block appears if you include a reseed button in your UI. 

 

The second block was added by Rick to more easily detect plugins that need shims in order to work. 

  • Thanks 1
Link to comment
Share on other sites

Yes, the second block should make it easy in the future to auto-detect "okay so CodeLab vX.Y.Z.W generated code that is broken by a change in .NET 7.4 ..." and then I can implement a workaround for all affected plugins. Otherwise I have to find and maintain a list of every individual plugin.

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

  • 1 month 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...