-
Posts
31 -
Joined
-
Last visited
-
Days Won
1
Posts posted by loupasc
-
-
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]; } } }
- 1
- 1
-
Hi MJW,
Thanks a lot, I think PreRender is a GO to solve this problem !
-
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]; } } }
-
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 };
-
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
- 2
New developer new plugin (Ordered Dither)
in Plugin Developer's Central
Posted
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