views:

299

answers:

2

What happens (only noticeable on certain images) is I will see a 1 pixel white border that is inset one pixel. It seems to happen in areas that are light but not white (e.g. the sky). It is similar to when something is oversharpened and a ghost border can be seen next to high contrast edges.

Here is the repro code that reproduces it perfectly. I'm using all the highest quality settings for scaling.

ImageCodecInfo encoder = null;
EncoderParameters encoderParams = null;

foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())
{
    if (codec.MimeType == "image/jpeg")
    {
     encoder = codec;

     // use highest quality compression settings
     encoderParams = new EncoderParameters(1);
     encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
     break;
    }
}

using (Bitmap input = (Bitmap)Bitmap.FromFile(inputPath, true))
{
    // shrink by multiple of 2
    Rectangle rect = new Rectangle(0, 0, input.Width/32, input.Height/32);

    using (Bitmap output = new Bitmap(rect.Width, rect.Height))
    {
     using (Graphics g = Graphics.FromImage(output))
     {
      // use highest quality settings (updated per Mark Ransom's answer)
      g.CompositingMode = CompositingMode.SourceCopy;
      g.InterpolationMode = InterpolationMode.HighQualityBicubic;
      g.PixelOffsetMode = PixelOffsetMode.HighQuality;
      g.SmoothingMode = SmoothingMode.HighQuality;

      g.DrawImage(input, rect);
     }

     output.Save(outputPath, encoder, encoderParams);
    }
}

Any ideas? I'm completely baffled. I've read through a ton of questions/answers and none of them seem to affect my situation.


Edit:

This is an example before image: http://img14.imageshack.us/img14/4174/mg1647.jpg

This is an example after image: http://img64.imageshack.us/img64/3156/afterringing.jpg

It is more pronounced with the original files (before the hosting service "optimizes" them), but you can see in the sky a lighter band one-pixel in on the smaller image.

+3  A: 

Try:

g.CompositingMode = CompositingMode.SourceCopy;

From my answer here, corrected for syntax.

The resizing is creating partial transparency around the border. Setting SourceCopy tells it to replace that partially transparent pixel with a fully opaque one.

Mark Ransom
+1 I agree with Mark, he actually beat me to it.
blesh
I think you are correct in that it is treating it as partially transparent. If I add `g.Clear(Color.Black);` before I draw it then the white is still there but now the edge-most pixel is darkened. Unfortunately setting `g.CompositingMode=CompositingMode.SourceCopy;` gets rid of any bleed-through from behind (e.g. the dark border) but it still has the 1 pixel inset white border.
McKAMEY
I don't see anyplace where you're defining `quality`, are you sure you're using the highest quality JPEG compression?
Mark Ransom
Yes I'm passing in 100L for quality. I'll update the example. Thanks for your help on this.
McKAMEY
+3  A: 

I finally found an article which talks about this.

Libor Tinka casually mentions this before going on to show his extensive set of filters which way outperform GDI+ scaling:

From his advice, it sounds like it is doing exactly what we suspected: it is pulling averaging detail from surrounding pixels beyond the edge of the image. This seems like a flaw in the algorithm to me, but that is open to debate. To solve this, there is an ImageAttributes class where you can specify that the pixels beyond are simply mirror images of the pixels within. Setting this seems to completely remove the ringing:

using (ImageAttributes wrapMode = new ImageAttributes())
{
    wrapMode.SetWrapMode(WrapMode.TileFlipXY);
    g.DrawImage(input, rect, 0, 0, input.Width, input.Height, GraphicsUnit.Pixel, wrapMode);
}

Huge thanks to both Libor Tinka for the solution, and to Mark Ransom for helping me think through this and for giving me the term "ringing" which was what made Libor Tinka's solution even show up in my searches.

McKAMEY
The root cause appears to be an overly aggressive filter in the resizing, coupled with inappropriate pixel masking at the edges. Pixels off the edge of the image shouldn't be contributing to the result at all, but obviously they are since wrapping fixes it. You can still see ringing in dark/light transitions, such as the roofline in your example.
Mark Ransom
What's really interesting is that the ringing along the roofline now appears to be fixed as well. It might be a side effect of adding in all the other `HighQuality` settings like `CompositingMode` and `PixelOffsetMode`. Current state of the resized images is much improved.
McKAMEY
This worked for me too - thanks
Graeme