loupasc Posted May 12, 2020 Share Posted May 12, 2020 (edited) Hello, Quick presentation 😀 I'm a french software engineer and I do photography. I wrote some image processing utility (Java) which includes dither, upscale, histogram matching... And I discover CodeLab today and it is nice ! I made a small plugin for dithering image with some parameters (monochrom/color, dither method, palette size...), I think you already have that kind of stuff but I have to start from somewhere. I will dig into CodeLab in order to provide more sophisticated filters. Here the sample image http://www.fraktales.net/dithering/ChromaCube.png And results: Output black and white | Lines method http://www.fraktales.net/dithering/ChromaCubeLinesBinary.bmp Output web safe | Lines method http://www.fraktales.net/dithering/ChromaCubeLinesColor.gif Output black and white with a custom "random" dither http://www.fraktales.net/dithering/ChromaCubeRandomBinary.bmp Output web safe with the random method http://www.fraktales.net/dithering/ChromaCubeRandomColor.gif // Name: Ordered dither // Submenu: // Author: Pascal Ollive // Title: Ordered dither // Version: 1.0 // Desc: Color reduction with dither // Keywords: ordered|dither|color|reduction // URL: // Help: #region UICode RadioButtonControl ditherMethod = 0; // Dither method|Checks|Dispersed|Arcade|Ordered|Lines|Custom|Random CheckboxControl isMonochrom = false; // Monochrom ListBoxControl Palette = 0; // Palette|Binary|EGA|Web-safe|12-bit #endregion private uint[] COLOR_COUNT = { 2, 4, 6, 16}; private byte[] CUSTOM_MATRIX = { 35, 15, 4, 23, 7, 28, 22, 9, 27, 14, 31, 2, 30, 19, 34, 6, 21, 13, 10, 1, 20, 29, 11, 33, 17, 32, 8, 18, 3, 26, 5, 25, 16, 36, 24, 12 }; private byte[] LINES_MATRIX = { 8, 9, 6, 7, 1, 0, 3, 2, 4, 5, 9, 8, 2, 3, 0, 1, 4, 5, 7, 6, 1, 0, 3, 2, 4, 5, 8, 9, 6, 7, 0, 1, 4, 5, 7, 6, 9, 8, 2, 3, 4, 5, 8, 9, 6, 7, 1, 0, 3, 2, 7, 6, 9, 8, 2, 3, 0, 1, 4, 5, 6, 7, 1, 0, 3, 2, 4, 5, 8, 9, 2, 3, 0, 1, 4, 5, 7, 6, 9, 8, 3, 2, 4, 5, 8, 9, 6, 7, 1, 0, 4, 5, 7, 6, 9, 8, 2, 3, 0, 1 }; private uint reverse(uint n) { // HD, Figure 7-1 n = (n & 0x55555555) << 1 | (n >> 1) & 0x55555555; n = (n & 0x33333333) << 2 | (n >> 2) & 0x33333333; n = (n & 0x0f0f0f0f) << 4 | (n >> 4) & 0x0f0f0f0f; n = (n << 24) | ((n & 0xff00) << 8) | ((n >> 8) & 0xff00) | (n >> 24); return n; } private int applyOrderedDither(int luma8bit, uint colorCount, uint coefCount, int ditherPattern) { uint pseudoColorCount = (colorCount - 1) * coefCount + 1; return (int) ((pseudoColorCount * luma8bit + 255 * ditherPattern - 1) / (255 * coefCount)); } private int applyRandomDither(int luma8bit, uint colorCount, int x, int y) { uint seed = (reverse((uint) y) >> 23) << 22 | reverse((uint) x) >> 10; // multiplicative congruential generator uint ditherPattern = (48271 * seed) % 2147483647; return (int) (((colorCount - 1) * luma8bit + (ditherPattern / 8421506)) / 255); } private int applyDither(int luma8bit, uint colorCount, int x, int y) { if (ditherMethod == 0) { // Checks return applyOrderedDither(luma8bit, colorCount, 2, (x ^ y) & 0x1); } if (ditherMethod == 1) { // Dispersed return applyOrderedDither(luma8bit, colorCount, 4, (((x ^ y) & 0x1) << 1) | (y & 0x1)); } if (ditherMethod == 2) { // Arcade return applyOrderedDither(luma8bit, colorCount, 8, 2 + (y & 0x3)); } if (ditherMethod == 3) { // Ordered return applyOrderedDither(luma8bit, colorCount, 5, LINES_MATRIX[20 * (y % 5) + 2 * (x % 5)] >> 1); } if (ditherMethod == 4) { // Lines return applyOrderedDither(luma8bit, colorCount, 10, LINES_MATRIX[10 * (y % 10) + (x % 10)]); } if (ditherMethod == 5) { // Custom return applyOrderedDither(luma8bit, colorCount, 36, CUSTOM_MATRIX[6 * (y % 6) + (x % 6)]); } return applyRandomDither(luma8bit, colorCount, x, y); } private ColorBgra processPixel(ColorBgra currentPixel, uint colorCount, int x, int y) { ColorBgra pixel = new ColorBgra(); pixel.A = 255; if (isMonochrom) { byte luma8bit = currentPixel.GetIntensityByte(); luma8bit = (byte) applyDither(luma8bit, colorCount, x, y); pixel.R = luma8bit; pixel.G = luma8bit; pixel.B = luma8bit; } else { pixel.R = (byte) applyDither(currentPixel.R, colorCount, x, y); pixel.G = (byte) applyDither(currentPixel.G, colorCount, x, y); pixel.B = (byte) applyDither(currentPixel.B, colorCount, x, y); } return pixel; } void Render(Surface dst, Surface src, Rectangle rect) { // Delete any of these lines you don't need Rectangle selection = EnvironmentParameters.SelectionBounds; uint colorCount = COLOR_COUNT[Palette]; uint colorScale = 255 / (colorCount - 1); for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { ColorBgra pixel = processPixel(src[x, y], colorCount, x, y); pixel.R = (byte) (colorScale * pixel.R); pixel.G = (byte) (colorScale * pixel.G); pixel.B = (byte) (colorScale * pixel.B); dst[x,y] = pixel; } } } PS : The two following are made with my stand alone application, I think It will be the next step A very accurate error diffusion method (Ostroukhov) http://www.fraktales.net/dithering/ChromaCubeHiQBinary.bmp (serpentine parsing) And another to create color reduced image Human Visual System Model dither http://www.fraktales.net/dithering/ChromaCubeHiQColor.gif (the best quality but very slow) [ EDIT ] Remove plugin ZIP since this version contains a bug fixed in the next version Edited May 13, 2020 by loupasc 2 Quote Link to comment Share on other sites More sharing options...
loupasc Posted May 13, 2020 Author Share Posted May 13, 2020 (edited) Custom Matrix coefficients are wrong. It should start from 0 and not from 1. It causes clipping issue, well it's fixed 😊 - private byte[] CUSTOM_MATRIX = { 35, 15, 4, 23, 7, 28, - 22, 9, 27, 14, 31, 2, - 30, 19, 34, 6, 21, 13, - 10, 1, 20, 29, 11, 33, - 17, 32, 8, 18, 3, 26, - 5, 25, 16, 36, 24, 12 }; + private byte[] CUSTOM_MATRIX = { 34, 14, 3, 22, 6, 27, + 21, 8, 26, 13, 30, 1, + 29, 18, 33, 5, 20, 12, + 9, 0, 19, 28, 10, 32, + 16, 31, 7, 17, 2, 25, + 4, 24, 15, 35, 23, 11 }; OrderedDither.zip Edited May 13, 2020 by loupasc Quote Link to comment Share on other sites More sharing options...
loupasc Posted May 16, 2020 Author Share Posted May 16, 2020 Here the last version of this plugin. I use the PreRender method for cleaner initialization. Matrix are created by a "factory" method. The custom matrix is larger and has blue noise pattern (void-and-cluster) And the HMI is slightly modified. // Name: Ordered dither // Submenu: // Author: Pascal Ollive // Title: Ordered dither // Version: 1.2 // Desc: Color reduction with dither // Keywords: ordered|dither|color|reduction // URL: // Help: #region UICode RadioButtonControl ditherMethod = 0; // Dither method|Checkerboard|Dispersed|Arcade|Ordered|Lines|Custom|Random CheckboxControl isMonochrom = false; // Monochrom IntSliderControl PaletteSize = 2; // [2,8] Palette #endregion private int colorScale = 255; private byte[] ditherMatrix = null; private static byte[] createDitherMatrix(byte ditherMethod) { if ((ditherMethod == 3) || (ditherMethod == 4)) { // Ordered // Lines return new byte[] { 8, 9, 6, 7, 1, 0, 3, 2, 4, 5, 9, 8, 2, 3, 0, 1, 4, 5, 7, 6, 1, 0, 3, 2, 4, 5, 8, 9, 6, 7, 0, 1, 4, 5, 7, 6, 9, 8, 2, 3, 4, 5, 8, 9, 6, 7, 1, 0, 3, 2, 7, 6, 9, 8, 2, 3, 0, 1, 4, 5, 6, 7, 1, 0, 3, 2, 4, 5, 8, 9, 2, 3, 0, 1, 4, 5, 7, 6, 9, 8, 3, 2, 4, 5, 8, 9, 6, 7, 1, 0, 4, 5, 7, 6, 9, 8, 2, 3, 0, 1 }; } if (ditherMethod == 5) { // Custom return new byte[] { 133, 43, 161, 232, 138, 109, 41, 247, 97, 217, 118, 204, 103, 62, 224, 108, 15, 238, 86, 61, 24, 201, 158, 5, 173, 32, 154, 16, 135, 166, 36, 182, 149, 200, 125, 171, 245, 71, 100, 223, 130, 68, 244, 54, 196, 83, 246, 70, 99, 55, 4, 212, 35, 140, 179, 47, 89, 209, 180, 96, 231, 3, 123, 211, 25, 242, 153, 95, 116, 194, 17, 248, 152, 13, 120, 37, 139, 177, 45, 156, 110, 184, 75, 218, 53, 236, 74, 107, 190, 57, 234, 164, 67, 216, 88, 228, 60, 132, 31, 170, 11, 159, 131, 38, 219, 137, 102, 9, 195, 112, 20, 191, 6, 249, 198, 117, 230, 91, 213, 167, 22, 72, 176, 250, 142, 52, 239, 148, 175, 98, 48, 80, 146, 34, 63, 115, 241, 199, 39, 87, 29, 207, 124, 76, 225, 134, 210, 18, 172, 251, 193, 2, 92, 151, 122, 226, 160, 93, 183, 40, 23, 162, 59, 233, 111, 78, 129, 163, 222, 56, 14, 188, 64, 8, 214, 113, 243, 90, 127, 186, 12, 206, 51, 33, 197, 85, 252, 136, 104, 240, 141, 69, 181, 1, 220, 42, 94, 155, 237, 143, 106, 174, 30, 208, 44, 165, 26, 202, 105, 147, 77, 169, 253, 119, 73, 7, 229, 66, 150, 114, 79, 227, 121, 49, 254, 28, 215, 128, 58, 27, 178, 203, 126, 19, 235, 168, 0, 189, 84, 157, 65, 192, 101, 10, 187, 221, 82, 145, 50, 185, 81, 46, 255, 144, 21, 205 }; } return null; } private uint reverse(uint n) { // Hacker's Delight, Figure 7-1 n = (n & 0x55555555) << 1 | (n >> 1) & 0x55555555; n = (n & 0x33333333) << 2 | (n >> 2) & 0x33333333; n = (n & 0x0f0f0f0f) << 4 | (n >> 4) & 0x0f0f0f0f; n = (n << 24) | ((n & 0xff00) << 8) | ((n >> 8) & 0xff00) | (n >> 24); return n; } // 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 int applyOrderedDither(byte luma8bit, int coefCount, int ditherPattern) { int pseudoColorCount = (PaletteSize - 1) * coefCount + 1; return (pseudoColorCount * luma8bit + 255 * ditherPattern - 1) / (255 * coefCount); } private int applyRandomDither(byte luma8bit, int x, int y) { uint seed = ((reverse((uint) y) >> 23) << 22 | reverse((uint) x) >> 10); // multiplicative congruential generator uint ditherPattern = (48271 * seed) % 2147483647; return (int) (((PaletteSize - 1) * luma8bit + (ditherPattern / 8421506)) / 255); } private int applyDither(byte luma8bit, int x, int y) { if (ditherMethod == 0) { // Checks return applyOrderedDither(luma8bit, 2, (x ^ y) & 0x1); } if (ditherMethod == 1) { // Dispersed return applyOrderedDither(luma8bit, 4, (((x ^ y) & 0x1) << 1) | (y & 0x1)); } if (ditherMethod == 2) { // Arcade return applyOrderedDither(luma8bit, 8, 2 + (y & 0x3)); } if (ditherMethod == 3) { // Ordered return applyOrderedDither(luma8bit, 5, ditherMatrix[20 * (y % 5) + 2 * (x % 5)] >> 1); } if (ditherMethod == 4) { // Lines return applyOrderedDither(luma8bit, 10, ditherMatrix[10 * (y % 10) + (x % 10)]); } if (ditherMethod == 5) { // Custom return applyOrderedDither(luma8bit, 256, ditherMatrix[((y & 0xf) << 4) + (x & 0xf)]); } return applyRandomDither(luma8bit, x, y); } void PreRender(Surface dst, Surface src) { colorScale = 255 / (PaletteSize - 1); ditherMatrix = createDitherMatrix(ditherMethod); } private ColorBgra processPixel(ColorBgra currentPixel, int x, int y) { if (isMonochrom) { byte luma8bit = rgbToGray(currentPixel.R, currentPixel.G, currentPixel.B, (byte) ((x ^ y) & 0x1)); luma8bit = (byte) applyDither(luma8bit, x, y); return ColorBgra.FromBgr(luma8bit, luma8bit, luma8bit); } return ColorBgra.FromBgr((byte) applyDither(currentPixel.B, x, y), (byte) applyDither(currentPixel.G, x, y), (byte) applyDither(currentPixel.R, x, y)); } 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 = processPixel(src[x, y], x, y); pixel.R = (byte) (colorScale * pixel.R); pixel.G = (byte) (colorScale * pixel.G); pixel.B = (byte) (colorScale * pixel.B); dst[x, y] = pixel; } } } OrderedDither.zip Quote Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted May 16, 2020 Share Posted May 16, 2020 Looks good. You could use Switch-Case to determine your dither method in applyDither. 1 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...
Ego Eram Reputo Posted May 17, 2020 Share Posted May 17, 2020 Submenu: Stylize ...and an icon 10 hours ago, loupasc said: // Luma(pixel) = 0.2126R + 0.7152G + 0.0722B I'm interested where these weighting values came from? I've been using this intensity or luminescense formula forever.... (iColor.R * 0.299) + (iColor.G * 0.587) + (iColor.B * 0.114) Edit: seems to make very little difference to the result. 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...
Reptillian Posted May 17, 2020 Share Posted May 17, 2020 13 minutes ago, Ego Eram Reputo said: I'm interested where these weighting values came from? I've been using this intensity or luminescense formula forever.... I'm interested as well. There's ITU BT 709, and there's ITU BT 601. Neither of them reflect these. However, I found this article - https://en.wikipedia.org/wiki/Relative_luminance 1 Quote G'MIC Filter Developer Link to comment Share on other sites More sharing options...
loupasc Posted May 17, 2020 Author Share Posted May 17, 2020 Hello ! I picked up these coefficients on wikipedia too in another page : https://en.wikipedia.org/wiki/Luma_(video)#Use_of_relative_luminance @Ego Eram Reputo thanks for your feedback I will update my plugin with your remarks Quote Link to comment Share on other sites More sharing options...
loupasc Posted May 18, 2020 Author Share Posted May 18, 2020 Plugin Ordered Dither v1.3 I change the menu location, I use your proposed icon and the if statement is replaced by a switch case. // Name: Ordered dither // Submenu: Stylize // Author: Pascal Ollive // Title: Ordered dither // Version: 1.3 // Desc: Color reduction with dither // Keywords: ordered|dither|color|reduction // URL: // Help: #region UICode RadioButtonControl ditherMethod = 0; // Dither method|Checkerboard|Dispersed|Arcade|Ordered|Lines|Custom|Random CheckboxControl isMonochrom = false; // Monochrom IntSliderControl PaletteSize = 2; // [2,8] Palette #endregion private int colorScale = 255; private byte[] ditherMatrix = null; private static byte[] createDitherMatrix(byte ditherMethod) { if ((ditherMethod == 3) || (ditherMethod == 4)) { // Ordered // Lines return new byte[] { 8, 9, 6, 7, 1, 0, 3, 2, 4, 5, 9, 8, 2, 3, 0, 1, 4, 5, 7, 6, 1, 0, 3, 2, 4, 5, 8, 9, 6, 7, 0, 1, 4, 5, 7, 6, 9, 8, 2, 3, 4, 5, 8, 9, 6, 7, 1, 0, 3, 2, 7, 6, 9, 8, 2, 3, 0, 1, 4, 5, 6, 7, 1, 0, 3, 2, 4, 5, 8, 9, 2, 3, 0, 1, 4, 5, 7, 6, 9, 8, 3, 2, 4, 5, 8, 9, 6, 7, 1, 0, 4, 5, 7, 6, 9, 8, 2, 3, 0, 1 }; } if (ditherMethod == 5) { // Custom return new byte[] { 133, 43, 161, 232, 138, 109, 41, 247, 97, 217, 118, 204, 103, 62, 224, 108, 15, 238, 86, 61, 24, 201, 158, 5, 173, 32, 154, 16, 135, 166, 36, 182, 149, 200, 125, 171, 245, 71, 100, 223, 130, 68, 244, 54, 196, 83, 246, 70, 99, 55, 4, 212, 35, 140, 179, 47, 89, 209, 180, 96, 231, 3, 123, 211, 25, 242, 153, 95, 116, 194, 17, 248, 152, 13, 120, 37, 139, 177, 45, 156, 110, 184, 75, 218, 53, 236, 74, 107, 190, 57, 234, 164, 67, 216, 88, 228, 60, 132, 31, 170, 11, 159, 131, 38, 219, 137, 102, 9, 195, 112, 20, 191, 6, 249, 198, 117, 230, 91, 213, 167, 22, 72, 176, 250, 142, 52, 239, 148, 175, 98, 48, 80, 146, 34, 63, 115, 241, 199, 39, 87, 29, 207, 124, 76, 225, 134, 210, 18, 172, 251, 193, 2, 92, 151, 122, 226, 160, 93, 183, 40, 23, 162, 59, 233, 111, 78, 129, 163, 222, 56, 14, 188, 64, 8, 214, 113, 243, 90, 127, 186, 12, 206, 51, 33, 197, 85, 252, 136, 104, 240, 141, 69, 181, 1, 220, 42, 94, 155, 237, 143, 106, 174, 30, 208, 44, 165, 26, 202, 105, 147, 77, 169, 253, 119, 73, 7, 229, 66, 150, 114, 79, 227, 121, 49, 254, 28, 215, 128, 58, 27, 178, 203, 126, 19, 235, 168, 0, 189, 84, 157, 65, 192, 101, 10, 187, 221, 82, 145, 50, 185, 81, 46, 255, 144, 21, 205 }; } return null; } private uint reverse(uint n) { // Hacker's Delight, Figure 7-1 n = (n & 0x55555555) << 1 | (n >> 1) & 0x55555555; n = (n & 0x33333333) << 2 | (n >> 2) & 0x33333333; n = (n & 0x0f0f0f0f) << 4 | (n >> 4) & 0x0f0f0f0f; n = (n << 24) | ((n & 0xff00) << 8) | ((n >> 8) & 0xff00) | (n >> 24); return n; } // 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 int applyOrderedDither(byte luma8bit, int coefCount, int ditherPattern) { int pseudoColorCount = (PaletteSize - 1) * coefCount + 1; return (pseudoColorCount * luma8bit + 255 * ditherPattern - 1) / (255 * coefCount); } private int applyRandomDither(byte luma8bit, int x, int y) { uint seed = ((reverse((uint) y) >> 23) << 22 | reverse((uint) x) >> 10); // multiplicative congruential generator uint ditherPattern = (48271 * seed) % 2147483647; return (int) (((PaletteSize - 1) * luma8bit + (ditherPattern / 8421506)) / 255); } private int applyDither(byte luma8bit, int x, int y) { switch (ditherMethod) { case 0: // Checkerboard return applyOrderedDither(luma8bit, 2, (x ^ y) & 0x1); case 1: // Dispersed return applyOrderedDither(luma8bit, 4, (((x ^ y) & 0x1) << 1) | (y & 0x1)); case 2: // Arcade return applyOrderedDither(luma8bit, 8, 2 + (y & 0x3)); case 3: // Ordered return applyOrderedDither(luma8bit, 5, ditherMatrix[20 * (y % 5) + 2 * (x % 5)] >> 1); case 4: // Lines return applyOrderedDither(luma8bit, 10, ditherMatrix[10 * (y % 10) + (x % 10)]); case 5: // Custom return applyOrderedDither(luma8bit, 256, ditherMatrix[((y & 0xf) << 4) + (x & 0xf)]); default: return applyRandomDither(luma8bit, x, y); } } void PreRender(Surface dst, Surface src) { colorScale = 255 / (PaletteSize - 1); ditherMatrix = createDitherMatrix(ditherMethod); } private ColorBgra processPixel(ColorBgra currentPixel, int x, int y) { if (isMonochrom) { byte luma8bit = rgbToGray(currentPixel.R, currentPixel.G, currentPixel.B, (byte) ((x ^ y) & 0x1)); luma8bit = (byte) applyDither(luma8bit, x, y); return ColorBgra.FromBgr(luma8bit, luma8bit, luma8bit); } return ColorBgra.FromBgr((byte) applyDither(currentPixel.B, x, y), (byte) applyDither(currentPixel.G, x, y), (byte) applyDither(currentPixel.R, x, y)); } 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 = processPixel(src[x, y], x, y); pixel.R = (byte) (colorScale * pixel.R); pixel.G = (byte) (colorScale * pixel.G); pixel.B = (byte) (colorScale * pixel.B); dst[x, y] = pixel; } } } OrderedDither.zip 1 1 Quote Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted May 18, 2020 Share Posted May 18, 2020 That looks really tidy ⭐ 1 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...
loupasc Posted May 25, 2020 Author Share Posted May 25, 2020 (edited) On 5/17/2020 at 5:52 AM, Ego Eram Reputo said: Submenu: Stylize ...and an icon I'm interested where these weighting values came from? I've been using this intensity or luminescense formula forever.... (iColor.R * 0.299) + (iColor.G * 0.587) + (iColor.B * 0.114) Edit: seems to make very little difference to the result. Hi, I noticed something about color to intensity conversion which is a bit disturbing for me but It might be insignificant. I use the debug mode: #if DEBUG Debug.WriteLine(ColorBgra.FromBgr(254, 255, 255).GetIntensityByte()); Debug.WriteLine(255 * ColorBgra.FromBgr(254, 255, 255).GetIntensity()); #endif The results 254 254,886 IntensityByte returns 254 but the expected value should be 255. I found this topic which explain the method to get intensity byte : https://forums.getpaint.net/topic/1119-colorbgracs/?tab=comments#comment-4952 I understand that Luma = 7471 * B + 38470 * G + 19595 * R >> 16 where 0.114 * 65536 = 7471 0.587 * 65536 = 38470 0.299 * 65536 = 19595 which seems right at the first sight (7471 + 38470 + 19595 = 65536). But something is missing causing an asymmetry between dark tones (0,1,2...) and bright tones (...,253,254,255). 255 - ColorBgra.FromBgr(1, 0, 0).GetIntensityByte() should be equals to ColorBgra.FromBgr(254, 255, 255).GetIntensityByte() It's because the implementation is considering the spreading of luma in values between 0 inclusive and 255 inclusive but It should be considered in values between 0 inclusive and 256 exclusive which produces different result because of the division by 65536 (right shift). Therefore intensities are not equally distributed across intensity range, 255 is wrongly represented. Consequently the sum of coefficients should not be equals to 65536 but equals to 65791 = 65536 + 256 - 1 (minus one for exclude the single value producing 256). I proposed these BGR coefficients : 0.114 * 65791= 7500 0.587 * 65791 = 38619 0.299 * 65791 = 19672 Applying these coefficients fix the distribution problem. Hope this help Edited May 25, 2020 by loupasc Add sum result 65536 to 7471 + 38470 + 19595 Quote Link to comment Share on other sites More sharing options...
loupasc Posted May 26, 2020 Author Share Posted May 26, 2020 (edited) Testing is doubting I wrote the following loops to measure the result between the two methods for returning integer luminosity with the one returning the intensity in floating point. #if DEBUG int diffCount = 0; for (int b = 0; b < 256; b++) { for (int g = 0; g < 256; g++) { for (int r = 0; r < 256; r++) { ColorBgra c = ColorBgra.FromBgr((byte)b, (byte)g, (byte) r); byte loupasc = getIntensityFromBgr(c); byte intensity = c.GetIntensityByte(); if (loupasc != intensity) { if (Math.Abs(255 * c.GetIntensity() - loupasc) < Math.Abs(255 * c.GetIntensity() - intensity)) { diffCount = diffCount + 1; } } } } } Debug.WriteLine("diffCount=" + diffCount); #endif private static byte getIntensityFromBgr(ColorBgra c) { return (byte) ((7500 * c.B + 38619 * c.G + 19672 * c.R) >> 16); } Results 7 004 946 number of colors when results are closer to GetIntensity with the proposed coefficients than GetIntensityByte. Changing the sign of the inequality 1 310 945 number of colors when results are less accurate with the proposed coefficients than GetIntensityByte. The rest is equality. Edited May 26, 2020 by loupasc add getIntensityFromBgr implementation Quote Link to comment Share on other sites More sharing options...
DJ_CYBERDAD Posted January 6, 2022 Share Posted January 6, 2022 I LOVE IT!!! NO, there are NOT enough weird dithering plugins!!!! Quote Link to comment Share on other sites More sharing options...
DJ_CYBERDAD Posted January 7, 2022 Share Posted January 7, 2022 okay, what would make this WAY MORE USABLE would be sliders for brightness and contrast (basic adjustment of image) process prior to the dithering effect. It's just too much work using trial and error otherwise. Quote Link to comment Share on other sites More sharing options...
Reptillian Posted January 7, 2022 Share Posted January 7, 2022 20 minutes ago, DJ_CYBERDAD said: okay, what would make this WAY MORE USABLE would be sliders for brightness and contrast (basic adjustment of image) process prior to the dithering effect. It's just too much work using trial and error otherwise. You can add that to the code. Quote G'MIC Filter Developer Link to comment Share on other sites More sharing options...
DJ_CYBERDAD Posted January 7, 2022 Share Posted January 7, 2022 5 hours ago, Reptillian said: You can add that to the code. nope, I sure *can't*. 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.