JpegBitmapEncoder / RenderTargetBitmap - excessive sharpening since .net 2 vs 4.7.2

87 views Asked by At

I've been working on migrating legacy ASP.Net/C# system to Azure. Initially just changing the minimal amount possible, but part of that is moving to updated .Net framework (2/3/3.5 => 4.7+)

Part of this is a tool which creates image previews in different sizes, watermarks some , sharpens then and then saves them.

Had a client complain the new images are way too sharpened.

After much experimenting - discovered that when I exclude the additional sharpening code (Aforge.Imaging) so none is done - the un-sharpened results of .Net 4.7.2 is WAY sharper than /Net 2

I developed copies of the program that did bare minimum - both in .Net 4.7.2 and 2.0, exact same code - and the images are drastically different.

I'm assuming something has changed within the framework ?

Is it possible to tone said sharpening down a bit ?

Basically - some sharpening is good - but it's taken it too far

Code takes a potentially unusual route to generating previews as in other areas (not in the test program) it does metadata/colour space manipulation - plus is rather old. Mostly using System.Windows.Media.Imaging

FYI imageQuality is 75 dpi is 72 imgsize is 320

The basic (not all) of the test programs :

    imageStream = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read, FileShare.Read);
    
    BitmapDecoder decoder = BitmapDecoder.Create(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
    myBitmapSource = decoder.Frames[0];

....


    BitmapFrame myBitmapSourceFrame = (BitmapFrame)myBitmapSource;
    destColorContext = new ColorContext(new Uri(iccTargetProfile));
    ColorContext sourceColorContext;
    try
    {
             sourceColorContext = myBitmapSourceFrame.ColorContexts[0];
    }
    catch (Exception Err)
    {//If no inbuilt profile then default to AdobeRGB
               sourceColorContext = new ColorContext(new Uri(iccDefaultProfile)); ;
    }
    
    ColorConvertedBitmap ccb = new ColorConvertedBitmap(myBitmapSource, sourceColorContext, destColorContext, PixelFormats.Pbgra32);
    
    myBitmapSource = ccb;

....


    double nPercent = ScalePercentage(myBitmapSource.PixelHeight, myBitmapSource.PixelWidth, imgSize, imgSize);
    int newHeight = Convert.ToInt16((double)myBitmapSource.PixelHeight * nPercent);
    int newWidth = Convert.ToInt16((double)myBitmapSource.PixelWidth * nPercent);
    
    //resize by re-drawing onto new Bitmap
    RenderTargetBitmap rtBitmap = new RenderTargetBitmap(
       newWidth /* PixelWidth */,
       newHeight /* PixelHeight */,
       imageDPI /* DpiX */,
       imageDPI /* DpiY */,
       PixelFormats.Default);
    
    DrawingVisual drawVisual = new DrawingVisual();
    
    using (DrawingContext dc = drawVisual.RenderOpen())
    {
            dc.DrawRectangle(new ImageBrush(myBitmapSource), null /* no pen */,
                                new Rect(0, 0, rtBitmap.Width, rtBitmap.Height));
    
    
    rtBitmap.Render(drawVisual);
    
    //WATERMARK STUFF HERE
    
    //Previous Sharpening stuff HERE
    
    List<ColorContext> colorContexts = new List<ColorContext>();
    colorContexts.Add(destColorContext);
    
    //encoder
    JpegBitmapEncoder output = new JpegBitmapEncoder();
    output.QualityLevel = imageQuality;
    //output.Frames.Add(BitmapFrame.Create(outFrame));
    output.Frames.Add(BitmapFrame.Create(rtBitmap, null, null, new System.Collections.ObjectModel.ReadOnlyCollection<ColorContext>(colorContexts)));
    
    
    string endImage = folderDest + fileNameOnly;
    
    using (FileStream saveFileStream = new FileStream(endImage, FileMode.Create, FileAccess.Write))
                        {
                            output.Save(saveFileStream);
                        }

So two pictures - unsharpened using the same code to create them....

.Net 2.0 .Net 2.0

.Net 4.7.2 .Net 4.7.2

Bothersome....

1

There are 1 answers

0
Daf On

Tried using assembly redirects to the older versions - but either that wouldn't work or cause more errors.

Fount out it was the RenderTargetBitmap causing it. I tried resizing using TransformedBitmap instead and that worked great! But I needed RenderTargetBitmap to do additional things (draw in a watermark).

So my eventual workaround was to use both TransformedBitmap initially to resize the original image, then RenderTargetBitmap to draw watermark on top. i.e. use TransformedBitmap to resize and pass the output of that to RenderTargetBitmap instead of the original BitmapSource

So main part became : (myBitmapSource is original image)

     double nPercent = ScalePercentage(myBitmapSource.PixelHeight,myBitmapSource.PixelWidth, maxHeight, maxWidth);
      int newHeight = Convert.ToInt16((double)myBitmapSource.PixelHeight * nPercent);
      int newWidth = Convert.ToInt16((double)myBitmapSource.PixelWidth * nPercent);
    
    //resize by re-drawing onto new Bitmap
     RenderTargetBitmap rtBitmap = new RenderTargetBitmap(
       newWidth /* PixelWidth */,
       newHeight /* PixelHeight */,
       imageDPI /* DpiX */,
       imageDPI /* DpiY */,
       PixelFormats.Default);
    
    //new resizing
    TransformedBitmap tranBitmap = new TransformedBitmap(myBitmapSource, new ScaleTransform(nPercent, nPercent));
    
    DrawingVisual drawVisual = new DrawingVisual();
    using (DrawingContext dc = drawVisual.RenderOpen())
    {
         //dc.DrawRectangle(new ImageBrush(myBitmapSource), null /* no pen */, new Rect(0, 0, rtBitmap.Width, rtBitmap.Height));
         dc.DrawImage(tranBitmap, new Rect(0, 0, rtBitmap.Width, rtBitmap.Height));

        //watermarking code.........
    }

    rtBitmap.Render(drawVisual);

    //then some colour context/profile stuff prepared before
    List<ColorContext> colorContexts = new List<ColorContext>();
    colorContexts.Add(destColorContext);


    //saving (simplified)
    JpegBitmapEncoder output = new JpegBitmapEncoder();
    output.QualityLevel = imageQuality;

    output.Frames.Add(BitmapFrame.Create(writeBitmap, null, null, new System.Collections.ObjectModel.ReadOnlyCollection<ColorContext>(colorContexts)));

   //Saving to memory stream and then copy to File Steam

It's not quite like the .Net 3.5 unsharpened output - is a little sharper - but MUCH better than over sharpened 4.7.2 code.

New output (without additional sharpening) New output (without additional sharpening)