Downsampled PNGs in Windows pixelated compared to Mac

I’m trying to draw some icons in my AlertWindows. They look wonderful on the Mac, and seriously pixelated on Windows.

The icons are all stored as 24-bit PNG with transparency, 512 x 512.

The examples here use the default JUCEAppIcon.png from the AudioPluginHost (also 512 x 512).

I notice that the screen snapshots I took are 96 dpi on Windows, and 144 dpi on Mac, but I don’t think that has anything to do with it.

First, the Mac:

Same thing, on Windows 10:

In fact, the text looks like crap on Windows as well. Is there some setting I’m missing?

Here is a portion of the relevant code for drawing the icons in my override of LookAndFeel::drawAlertBox():

    auto iconWidth = 64;
    Rectangle<int> iconRect (10, 10, iconWidth, iconWidth);
    auto appIcon = ImageCache::getFromMemory(BinaryData::JUCEAppIcon_png, BinaryData::JUCEAppIcon_pngSize);
    
    g.setImageResamplingQuality(Graphics::highResamplingQuality);
    
    g.drawImageWithin(appIcon, iconRect.getX(), iconRect.getY(), iconRect.getWidth(), iconRect.getHeight(), RectanglePlacement::fillDestination );
    
    Image typeIcon;
    switch (alert.getAlertType())
    {
        case AlertWindow::WarningIcon:
            typeIcon = ImageCache::getFromMemory(BinaryData::warning_png, BinaryData::warning_pngSize);
            break;
        case AlertWindow::QuestionIcon:  // we are using for Stop
            typeIcon = ImageCache::getFromMemory(BinaryData::stop_png, BinaryData::stop_pngSize);
            break;
        case AlertWindow::InfoIcon:
            typeIcon = ImageCache::getFromMemory(BinaryData::info_png, BinaryData::info_pngSize);
            break;
        default: // noIcon
            break;
    }
    
    if (typeIcon.isValid())
    {
        auto typeIconWIdth = 32;
        Rectangle<int> typeIconRect (iconRect.getCentreX() + 10, iconRect.getCentreY() + 10, typeIconWIdth, typeIconWIdth);
        g.drawImageWithin(typeIcon, typeIconRect.getX(), typeIconRect.getY(), typeIconRect.getWidth(), typeIconRect.getHeight(), RectanglePlacement::fillDestination );

    }

I tried g.setImageResamplingQuality(Graphics::highResamplingQuality) although I think I read that has no effect on downsampling. Anyway, no effect.

Is this I a plugin or standalone? The daws will render plugins in lower resolution on windows for some reason

@jakemumu - It’s a standalone (actually a GUI app).

This looks like your app isn’t DPI aware for some reason.

Do text and images render correctly (i.e. greater than 96 DPI) for you in the DemoRunner and the other standalone JUCE example projects?

Are you using a JUCE app template as a starting point?

Everything looks crap on windows, even fonts are barely readable there

2 Likes

@reuk - it seems the DemoRunner looks the same.

I did start this project with a JUCE GUI app template, but that was nearly 3 years ago with JUCE 5.4.4. I am presently on 6.1.6.

I am a Mac Developer primarily; I know not much about Windows (one of the reasons I was attracted to JUCE).

What can I check to see if I am “DPI-aware”? Do my Display settings in Windows affect this? I have an LG HDR 4K display…

I do have JUCE_WIN_PER_MONITOR_DPI_AWARE set to Default (Enabled).

I took this screen shot with Windows “Snip and Sketch”. When I open it in Photoshop (on the Mac), it shows me it is 96 ppi.

I just made a new default GUI App project. I put the JUCEAppIcon.png in there at both 512 size and 64 size. This is what it looks like with “Snip and Sketch” screen shot. Again, Photoshop shows 96 ppi (not sure if that is relevant). I have enlarged this by 200% without smoothing in order to examine pixelation of 64x64 size (small size sure looks crappy, but maybe that’s normal for Windows?)

code:

    g.setImageResamplingQuality(juce::Graphics::highResamplingQuality);
    
    auto appIcon = juce::ImageCache::getFromMemory(BinaryData::JUCEAppIcon_png, BinaryData::JUCEAppIcon_pngSize);

    auto iconWidth = 512;
    juce::Rectangle<int> iconRect (10, 10, iconWidth, iconWidth);
    g.drawImageWithin(appIcon, iconRect.getX(), iconRect.getY(), iconRect.getWidth(), iconRect.getHeight(), juce::RectanglePlacement::fillDestination );
    
    iconWidth = 64;
    iconRect.setSize(iconWidth, iconWidth);    
    g.drawImageWithin(appIcon, iconRect.getX(), iconRect.getY(), iconRect.getWidth(), iconRect.getHeight(), juce::RectanglePlacement::fillDestination );

So maybe I just don’t understand how Windows works with displays (as I said, I’m a Mac guy). I have a 32" LG 4K display.

I had my Display settings as:

Screenshot 2022-04-18 124038

With this setting, doing a screen snapshot is 96 dpi (and it looks like my first example).

Screenshot 2022-04-18 124108

If I set the Display settings to:

Screenshot 2022-04-18 121247

…the resulting screen grab is 144 dpi, and looks a bit better, especially the icons:

Screenshot 2022-04-18 121444

And if I set the display settings to 200%:

Screenshot 2022-04-18 121911

…the resulting screen grab is 196 dpi and it starts to at least look as clear as the Mac (but then it’s larger than I’d like on the screen):

Is scaling up the display to 200% the Windows equivalent of Mac Retina?

Mac retina is indeed 200% by default

1 Like

In my experience scaling images in Windows is utter crap, and it’s by design. See Jules reply here : Difference between macOS and Windows image scaling, is it normal?

For performance reasons the scaling is done without resampling. My solution was to use AVIR image sizing algorithm, specifically from Roland Rabien’s Gin module. I made a simple caching layer so that I would only need to do the calculation once for a given image/size and from then on could just paint the result from the cached juce::Image. With that my Windows UI’s looked equivalent to the macOS versions.

3 Likes

@asimilon - thank you for that! I installed gin and the gin_gui module, and the results of gin::applyResize() are extremely good!
png4

Can you share how you get this into the ImageCache so it only has to be done once? Do you add the image with addImageToCache()? How do I generate a hash code ( i have not ever done that so far)?

EDIT:

I guess you do something like this?


    auto hashCode = (juce::String("JUCEAppIcon_png") + "_iconCacheSalt").hashCode64();

    auto newImage = juce::ImageCache::getFromHashCode(hashCode);
    if (newImage.isNull())
    {
        newImage = gin::applyResize(appIcon, iconWidth, iconWidth);
        juce::ImageCache::addImageToCache(newImage, hashCode);
    }

    g.drawImageWithin(newImage, iconRect.getX() + 70, iconRect.getY(), iconRect.getWidth(), iconRect.getHeight(), juce::RectanglePlacement::fillDestination );

More or less, I wasn’t aware of juce::ImageCache and ended up rolling my own, the only real difference was including the image dimensions in the hash generated since my UIs have some different scale options.