0

I have an overloaded method which gets the bytes in an uploaded image that seems to be causing my entire application to resize. This only seems to happen when the display scaling is anything other than 100%.

The weird thing is that the application resizes when it enters the overloaded method but before any actual code in the method is run. Even stranger, if I set the DPI scale to 100%, do the usual actions that would resize the form, and then switch the display scaling back to 125% then the resizing does not happen.

This is the method:

public static byte[] GetMultiPageImageBytes(byte[] imageBytes, double? rotationAngle, string subject)
{
    using (MemoryStream inStream = new MemoryStream(imageBytes))
    {
        return GetMultiPageImageBytes(inStream, rotationAngle, subject);
    }
}

and the overloaded method(where the resizing occurs):

public static byte[] GetMultiPageImageBytes(Stream inStream, double? rotationAngle, string subject)
{ /************************ APP RESIZES ON THIS LINE ************************/
    BitmapDecoder decoder = new TiffBitmapDecoder(inStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
    TiffBitmapEncoder encoder = new TiffBitmapEncoder();

    foreach (BitmapFrame frame in decoder.Frames)
    {
        BitmapMetadata metadata = null;
        if (subject != null)
        {
            metadata = new BitmapMetadata("tiff");
            metadata.Subject = subject;
        }

        TransformedBitmap rotatedImage = null;
        if (rotationAngle != null)
        {
            System.Windows.Media.RotateTransform transform = new System.Windows.Media.RotateTransform(rotationAngle.Value);
            rotatedImage = new TransformedBitmap(frame, transform);
        }

        BitmapSource sourceImage = rotatedImage != null ? (BitmapSource)rotatedImage : (BitmapSource)frame;
        BitmapFrame newFrame = BitmapFrame.Create(sourceImage, frame.Thumbnail, metadata, frame.ColorContexts);

        encoder.Frames.Add(newFrame);
    }

    using (MemoryStream outStream = new MemoryStream())
    {
        encoder.Save(outStream);
        return outStream.ToArray();
    }
}

I've tried SetProcessDPIAware() which got rid of the problem but that messed up the layout of dozens of other forms and components. The application is too big in order to optimize for DPI awareness at this point as I would need to reformat dozens of components.

I also tried removing the using statement in the original method which calls the overloaded method and just manually disposing the stream but that didn't work either:

// Didn't work

public static byte[] GetMultiPageImageBytes(byte[] imageBytes, double? rotationAngle, string subject)
{
    MemoryStream inStream = new MemoryStream(imageBytes);
    byte[] bytes;
    bytes = GetMultiPageImageBytes(inStream, rotationAngle, subject);
    inStream.Dispose();
    inStream.Close();
    return bytes;
}

Any ideas as to what might be causing this? Any help with this would be greatly appreciated!

Jimi
  • 29,621
  • 8
  • 43
  • 61
Ryker
  • 446
  • 3
  • 14
  • [DPI Awareness - Unaware in one Release, System Aware in the Other](https://stackoverflow.com/a/50276714/7444103). To sum it up: either make your app DpiAware (possibly, `PerMonitorV2`, using .Net Framework 4.7.2+)* or disable the automatic DpiAwareness introduced by PresentationFramework assemblies, as described in the last snippet. I suggest to make your app DpiAware, it will solve a lot of other problems related to Bitmap and units of measure. Yes, it requires some work. [*] Yes, the Docs say 4.7+, 4.7.2 or 4.8 is better. – Jimi Jan 21 '21 at 00:46
  • @Jimi I agree that that would be the most ideal solution. However, the problem with that is that the application has quite literally almost a hundred forms and dozens upon dozens of components so it would not be feasible for me to do so – Ryker Jan 21 '21 at 15:44
  • 1
    Then, as described, disable the automatic DpiAwareness introduced by the PresentationFramework assemblies as described in that post (very last paragraph). – Jimi Jan 21 '21 at 16:00
  • Ok I'll give that a go, thanks! – Ryker Jan 21 '21 at 16:29
  • @Jimi Disabling the DPI awareness worked great, thank you! I followed your advice but ended up using code from this one instead: https://stackoverflow.com/questions/43537990/wpf-clickonce-dpi-awareness-per-monitor-v2/43537991#43537991 – Ryker Jan 21 '21 at 17:47

2 Answers2

1

Partial answer - this is probably happening in a static constructor for some class that is first used in this method. Static constructors are guaranteed to have been called some time before the first instantiation of the class or call to one of its static methods. It's pretty typical to see this happen at the start of a method like this. So that explains the weird timing. I'm afraid I don't know enough about WinForm or WPF to explain the rest, so I'll not speculate.


Specifically, I'm suggesting that any of the following classes' static constructors would be expected to be invoked at the beginning of your second GetMultiPageImageBytes method, if they haven't been already:

  • TiffBitmapDecoder
  • TiffBitmapEncoder
  • BitmapMetadata
  • RotateTransform
  • TransformedBitmap
  • BitmapFrame
  • Any of their base classes

The .NET specification won't say exactly when the static constructor will be invoked, only that it will be invoked some time before the first instantiation of the class or call to one of its static methods. Typically the behaviour that you will see is that it will be invoked at the start of a method that is going to do one of those things. That's why I'm saying it is a plausible mechanism for strange stuff happening on the first line of your method, even though it hasn't done anything yet. It could well be that such a static constructor, or other code that it causes to run, is what's causing your strange behaviour.

Indeed, this answer suggests that loading any WPF classses causes the process to be set to DPI-aware. Based on that, it could be that your problem is that the WPF classes are simply not designed to be used independently in a non-WPF application. From the "WinForm" in your question title I assume that none of the rest of your app is WPF.

Weeble
  • 17,058
  • 3
  • 60
  • 75
  • I don't think there is any static constructor being called before the method because the class is a utility class (not sure if this is the right term? but it's basically just filled with helper methods. i.e: calculate age and format phone numbers). Thanks though! – Ryker Jan 20 '21 at 21:34
  • 1
    I've tried to clarify my answer. Does that make more sense? – Weeble Jan 21 '21 at 00:41
  • Yup, definitely thanks for that! I'll try to put breakpoints in the constructors of those classes to check. I also saw that post about loading WPF classes but I wasn't quite sure how I would go about identifying if a reference is WPF or WinForms? One of the examples given in this one: https://stackoverflow.com/questions/44547762/winforms-window-changes-dimensions-when-it-encounters-an-async-call?noredirect=1&lq=1 was `System.Windows.Forms` vs `System.Windows` but i tried looking at those two and was unable to find out how to tell – Ryker Jan 21 '21 at 15:39
0

Disabling the DPI awareness based on @Jimi suggestion worked for me. I ended up using code from this answer: WPF ClickOnce DPI awareness Per-Monitor v2 from @Marko

Ryker
  • 446
  • 3
  • 14
  • You just need to add this line: `[assembly: System.Windows.Media.DisableDpiAwareness]` in `AssemblyInfo.cs`. Based on how PresentationFramework assemblies are loaded and unloaded, the internal call to `SetProcessDPIAware()` these make may occur at a different time, so you may end up with some Forms that are rendered as the app was DpiAware, while the rest are rendered as intended. – Jimi Jan 21 '21 at 18:22