loupasc Posted May 13, 2020 Share Posted May 13, 2020 (edited) 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 May 13, 2020 by loupasc Orthographic fixes Quote Link to comment Share on other sites More sharing options...
MJW Posted May 14, 2020 Share Posted May 14, 2020 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. Quote Link to comment Share on other sites More sharing options...
loupasc Posted May 14, 2020 Author Share Posted May 14, 2020 Hi MJW, Thanks a lot, I think PreRender is a GO to solve this problem ! Quote Link to comment Share on other sites More sharing options...
loupasc Posted May 15, 2020 Author Share Posted May 15, 2020 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 1 1 Quote Link to comment Share on other sites More sharing options...
AndrewDavid Posted November 23, 2021 Share Posted November 23, 2021 @Ego Eram Reputo A hidden gem not found in Plugin Index. Can you move it? 1 Quote Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted November 24, 2021 Share Posted November 24, 2021 Is it finished/ready for release? Quote ebook: Mastering Paint.NET | resources: Plugin Index | Stereogram Tut | proud supporter of Codelab plugins: EER's Plugin Pack | Planetoid | StickMan | WhichSymbol+ | Dr Scott's Markup Renderer | CSV Filetype | dwarf horde plugins: Plugin Browser | ShapeMaker Link to comment Share on other sites More sharing options...
AndrewDavid Posted November 24, 2021 Share Posted November 24, 2021 It's not all it could be. I am trying to improve it. Without the Author active, should I take ownership? Quote Link to comment Share on other sites More sharing options...
NinthDesertDude Posted November 25, 2021 Share Posted November 25, 2021 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 1 Quote Link to comment Share on other sites More sharing options...
Reptillian Posted November 25, 2021 Share Posted November 25, 2021 (edited) In my opinion, it can be rewritten entirely from scratch without even needing to use @loupasc code at all. I don't know if a simplified or easier to read solution would be slower. Edited November 25, 2021 by Reptillian Quote G'MIC Filter Developer Link to comment Share on other sites More sharing options...
AndrewDavid Posted November 25, 2021 Share Posted November 25, 2021 @Reptillian Th feature I would like to see added is the ability to reset the choices made. 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. Pleas join the discussion over here Quote Link to comment Share on other sites More sharing options...
MJW Posted November 25, 2021 Share Posted November 25, 2021 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.) Quote Link to comment Share on other sites More sharing options...
AndrewDavid Posted November 25, 2021 Share Posted November 25, 2021 When building a plugin in Codelab, there are four choices when you select the Color Wheel. One is with a reset button(Default). 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. Quote Link to comment Share on other sites More sharing options...
MJW Posted November 25, 2021 Share Posted November 25, 2021 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.) 1 Quote Link to comment Share on other sites More sharing options...
toe_head2001 Posted November 25, 2021 Share Posted November 25, 2021 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. Quote My Gallery | My Plugin Pack Layman's Guide to CodeLab Link to comment Share on other sites More sharing options...
midora Posted November 25, 2021 Share Posted November 25, 2021 Check for all controls supporting a reset button. That's different to OptionBased where all controls (but not panels) support a reset button (plus a reset all button). Quote Link to comment Share on other sites More sharing options...
AndrewDavid Posted November 25, 2021 Share Posted November 25, 2021 @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. Quote Link to comment Share on other sites More sharing options...
BoltBait Posted November 25, 2021 Share Posted November 25, 2021 19 minutes ago, AndrewDavid said: I notice these code lines starting to appear in the New Codelab Reveal hidden contents public GradientMapEffectPlugin() : base(StaticName, StaticIcon, SubmenuName, new EffectOptions() { Flags = EffectFlags.Configurable }) { instanceSeed = unchecked((int)DateTime.Now.Ticks); } along with this Reveal hidden contents [assembly: AssemblyMetadata("BuiltByCodeLab", "Version=6.4.7995.19949")] 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. 1 Quote Click to play: Download: BoltBait's Plugin Pack | CodeLab | and how about a Computer Dominos Game Link to comment Share on other sites More sharing options...
Rick Brewster Posted November 26, 2021 Share Posted November 26, 2021 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. Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
user.by Posted January 6, 2022 Share Posted January 6, 2022 I think need Slider and Reset Buttons for each one setting. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.