What is wrong with this sepia tone conversion algorithm?

前端 未结 3 831
刺人心
刺人心 2021-02-11 01:41

I seem to have a sepia tone that is almost working properly. For some reason a portion of the image turns out to be lime green! Does anyone know what i might be doing wrong? Met

相关标签:
3条回答
  • 2021-02-11 01:56

    You have 2 problems in your algo (at least, if you follow algo description from here).

    First, as others pointed out, you have byte type overflow. Second, all your output color values must be based on the input color values, not calculated sequentially.

    Here's the fixed main loop code:

            for (int i = 0; i < rgbValues.Length; i += 4)
            {
                int inputRed = rgbValues[i + 2];
                int inputGreen = rgbValues[i + 1];
                int inputBlue = rgbValues[i + 0];
    
                rgbValues[i + 2] = (byte) Math.Min(255, (int)((.393 * inputRed) + (.769 * inputGreen) + (.189 * inputBlue))); //red
                rgbValues[i + 1] = (byte) Math.Min(255, (int)((.349 * inputRed) + (.686 * inputGreen) + (.168 * inputBlue))); //green
                rgbValues[i + 0] = (byte) Math.Min(255, (int)((.272 * inputRed) + (.534 * inputGreen) + (.131 * inputBlue))); //blue
            }
    

    Note that inside the Min function I cast the color value from double to int otherwise Min(double, double) overload is called and 255 gets first converted to double and then possibly back to byte, involving an extra rounding.

    In case if someone needs a sample console app sepia converter, here's the final code I have:

    namespace ConsoleApplication8_Sepia
    {
        using System;
        using System.Drawing;
        using System.Drawing.Imaging;
    
        class Program
        {
            static void Main(string[] args)
            {
                Bitmap b = (Bitmap)Bitmap.FromFile("c:\\temp\\source.jpg");
                SepiaBitmap(b);
                b.Save("c:\\temp\\destination.jpg", ImageFormat.Jpeg);
            }
    
            private static void SepiaBitmap(Bitmap bmp)
            {
                Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
                BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
                IntPtr ptr = bmpData.Scan0;
    
                int numPixels = bmpData.Width * bmp.Height;
                int numBytes = numPixels * 4;
                byte[] rgbValues = new byte[numBytes];
    
                System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, numBytes);
                for (int i = 0; i < rgbValues.Length; i += 4)
                {
                    int inputRed = rgbValues[i + 2];
                    int inputGreen = rgbValues[i + 1];
                    int inputBlue = rgbValues[i + 0];
    
                    rgbValues[i + 2] = (byte)Math.Min(255, (int)((.393 * inputRed) + (.769 * inputGreen) + (.189 * inputBlue))); //red
                    rgbValues[i + 1] = (byte)Math.Min(255, (int)((.349 * inputRed) + (.686 * inputGreen) + (.168 * inputBlue))); //green
                    rgbValues[i + 0] = (byte)Math.Min(255, (int)((.272 * inputRed) + (.534 * inputGreen) + (.131 * inputBlue))); //blue
                }
    
                System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, numBytes);
                bmp.UnlockBits(bmpData);
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-11 02:09

    To fix the issue, change the loop like this:

    for (int i = 0; i < rgbValues.Length; i += 4)
    {
        int red = rgbValues[i + 2];
        int green = rgbValues[i + 1];
        int blue = rgbValues[i + 0];
    
        rgbValues[i + 2] = (byte)Math.Min((.393 * red) + (.769 * green) + (.189 * blue), 255.0); // red
        rgbValues[i + 1] = (byte)Math.Min((.349 * red) + (.686 * green) + (.168 * blue), 255.0); // green
        rgbValues[i + 0] = (byte)Math.Min((.272 * red) + (.534 * green) + (.131 * blue), 255.0); // blue
    }
    

    There are arithmetic overflows occurring in your calculations, that's the reason for the wrong colors. An expression of type double is being explicitly cast to byte before it is compared to 255, therefore it will never be greater than 255.

    0 讨论(0)
  • 2021-02-11 02:14

    Your values are overflowing and wrapping around.

    Your attempt to protect against this with (rgbValues[i + 0]) > 255 has no effect because a byte[] cannot store values over 255 anyway, so the values overflowed and wrapped as soon as you put them in rgbValues. You need to clamp them before storing them in the array. C# has a function Math.Min() that would be excellent for this purpose.

    On the other hand, given that you are getting overflow, you probably want to fix that in the first place — clamping will create an "overexposure" effect (because overexposure is clamping), which is probably undesirable. Adjust your coefficients so that you are changing the color but not changing the (perceived) brightness (I don't have a reference for this; sorry).

    As an entirely separate problem, as noted by @Yacoder, your first line modifies the inputs the second one uses, and so on, so your calculation will be off. You need to either the three inputs or the three outputs in temporary variables.

    You might also want to look into whether System.Drawing.Imaging has a color matrix image transformation operation, because that's what you're doing by hand here, and the system-provided version will probably be faster. (I don't know C# so I can't comment on that.)

    0 讨论(0)
提交回复
热议问题