Jump to content

New developer new plugin (Ordered Dither)


Recommended Posts

  

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 by loupasc
  • Like 2
Link to comment
Share on other sites

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 by loupasc
Link to comment
Share on other sites

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

Link to comment
Share on other sites

Looks good. You could use Switch-Case to determine your dither method in applyDither.

  • Like 1
Link to comment
Share on other sites

Submenu: Stylize

 

...and an icon :)

 

image.png

 

 

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.

Link to comment
Share on other sites

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

 

 

  • Upvote 1

G'MIC Filter Developer

Link to comment
Share on other sites

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

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

On 5/17/2020 at 5:52 AM, Ego Eram Reputo said:

Submenu: Stylize

 

...and an icon :)

 

image.png

 

 

 

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 by loupasc
Add sum result 65536 to 7471 + 38470 + 19595
Link to comment
Share on other sites

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 by loupasc
add getIntensityFromBgr implementation
Link to comment
Share on other sites

  • 1 year later...
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.

G'MIC Filter Developer

Link to comment
Share on other sites

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