Incorrect image colour rendering in Pro Tools

Hi

I’m observing an issue with image colours in Pro Tools being rendered too light. Anything drawn programatically is showing a different colour to if it’s drawing from an image. Only affects macOS. I have a small sample to illustrate the problem.

I’ve made an image where all pixels are RGB 127/127/127. I drop that into an ImageComponent which is in the centre of a plugin which flood fills itself in the paint call to the same RGB. I should not be able to see the image given it’s on a background the same colour. In Pro Tools though the image is rendering too light. You see a border of the correctly coloured background and a light central region which is the image.

You can see a comparison of the AAX vs a VST3 in Reaper, in Reaper it’s showing as expected but in Pro Tools there is a border because the image is rendered incorrectly.

If I enable an OpenGL context on the entire plug-in component then Pro Tools paints everything too light as opposed to just things from the image, as shown:

It suggests it’s nothing to do with the image format (I have tried using jpg and gif source files as well). I tried changing JUCE_USE_COREIMAGE_LOADER and this didn’t make a difference. The code is super simple:

Proj45colourTestAudioProcessorEditor::Proj45colourTestAudioProcessorEditor (Proj45colourTestAudioProcessor& p)
    : AudioProcessorEditor (&p), processor (p)
{
    juce::Image image_background = ImageCache::getFromMemory(BinaryData::img1_png, BinaryData::img1_pngSize);
    image_component.setImage(image_background);
    addAndMakeVisible(image_component);
    setSize (840, 600);
    // gl.attachTo(*this);
}
Proj45colourTestAudioProcessorEditor::~Proj45colourTestAudioProcessorEditor()
{
}
void Proj45colourTestAudioProcessorEditor::paint (Graphics& g)
{
    g.fillAll (juce::Colour::fromRGB(128, 128, 128));
}
void Proj45colourTestAudioProcessorEditor::resized()
{
    image_component.setSize(640, 400);
    image_component.setCentrePosition(getWidth() / 2, getHeight() / 2);
}

Any clues why this could be? I’ve noticed this on other vendor’s plug-ins that use Juce, and it’s been an issue on earlier releases of Juce and Pro Tools for me but I’m trying to get to the bottom of this one now since I am moving away from OpenGL in my plug-ins and am getting inconsistencies between the image and drawn areas which is less tolerable than everything simply being slightly the wrong colour.

Matt

Probably an issue related to color profile or embedded gamma in png

https://hsivonen.fi/png-gamma/

I tried all 4 permutations of the “Convert to sRGB” and “Embed Color profile” options in photoshop on a PNG export, so I don’t think it is that. (I also tried JPG and GIF with/without colour profiles). No change. Also this wouldn’t explain why when an OpenGL context is attached the entire UI draws the wrong colours in Pro Tools.

OpenGL is not color managed.
https://developer.apple.com/library/archive/technotes/tn2313/_index.html#//apple_ref/doc/uid/DTS40014694-CH1-NONCOLORMANAGEDFRAMEWORKS-OPENGL___EXPLICIT_COLOR_MANAGEMENT_EXAMPLE

In Pro Tools, if I add this and break on it:

image_background.getPixelAt(10, 10).getRed()

Then I see the value I expect, 127. The image is being loaded correctly. It is clearly drawing incorrectly though.

The issue here is hence color profile and not gamma issue in png file

See

OK, but taking OpenGL out of the situation for a moment. If I load an image that is one colour (and the image has no colour profile), pull out a pixel value and then paint the background that colour and then draw the image on top of it, how could you possibly see two different colours in the combined rendering?

Proj45colourTestAudioProcessorEditor::Proj45colourTestAudioProcessorEditor (Proj45colourTestAudioProcessor& p)
    : AudioProcessorEditor (&p), processor (p)
{
    image_background = ImageCache::getFromMemory(BinaryData::img2_png, BinaryData::img2_pngSize);
    setSize (840, 600);
}
Proj45colourTestAudioProcessorEditor::~Proj45colourTestAudioProcessorEditor()
{
}
void Proj45colourTestAudioProcessorEditor::paint (Graphics& g)
{
    g.fillAll (juce::Colour::fromRGB(image_background.getPixelAt(0, 0).getRed(),
                                     image_background.getPixelAt(0, 0).getGreen(),
                                     image_background.getPixelAt(0, 0).getBlue()));
    g.drawImageAt(image_background, 100, 100);
}
void Proj45colourTestAudioProcessorEditor::resized()
{
}

I’m not talking about the color profile of your image but the one of the display setup in the osx preferences.

if you use the core graphics renderer then it should indeed be the same color.

I can click through every colour profile on the iMac, the whole screen changes colours a little for different profiles, but the fact that in Pro Tools there are two different painted colours (background region vs image region) does not change, they stay relatively the same with respect to each other.

If I attach the GL context to only the image component, then the image component is drawn the same colour as the background (i.e. both the correct colour). So this suggests when Juce is drawing images in Pro Tools some kind of correction is being applied to them, but when you prevent that by attaching a GL context they come out the correct colour.

gl_context.attachTo(image_component);

Apply the GL context to the top level component and everything is consistent, but consistently the wrong colour.

gl_context.attachTo(*getTopLevelComponent());

How can that be?

what happens if you load your image from the paint method the first time ?
or at least delay the load of your image in visibilityChanged

I tried loading from the paint method, it made no difference (all of the 4 colour profile versions I saved out of Photoshop). I also tried loading straight from disk using PNGImageFormat().decodeImage instead of from the ImageCache in case that made a difference, but no change.

When you use only OpenGl, it display lighter meaning that the lighter color is when color profile is not applied.
Hence the issue here is that in Protools the color profile is not used for the image display but is for the fillAll.

I would step into the ImageComponent paint function see what differs in Protools
like in case of Protools, Image do not take into account the color profile of the display.

It ends up in a call to CGContextDrawImage (context, imageRect, image); in void CoreGraphicsContext::drawImage, I see no real evidence as I step through to that point that it’s doing anything suspicious to colour profiles, but am not too familiar with the APIs at this level.

Puzzling. It’s a job for the Scooby Juce gang

This is actually a macOS/Color Profile issue… I dealt with this issue and a client last week… and to prove it’s not a JUCE issue I created a test app and demonstrated to them that drawing a Photoshop image inside of JUCE and also opening it in Preview shows the “wrong” color:

In this image is the PNG opened in Preview (top left) and painted in my JUCE app (top right) and a filled rectangle painted in JUCE (bottom right).

Color Toolkit shows the value for all three on Mojave on my iMac in the bottom 3 values. The PNG was created in Photoshop and the color is 0xFF12403B. Interestingly enough GIMP shows the color value in its picker as 0xFF12403B.

I tried adjusting the Color Profile on my iMac and it didn’t change the value.

If I do the same test using macOS Lion (10.7.5) in a Parallels VM then all colors show as 0xFF12403B on the same iMac.

Rail

I found that the issue begins in Pro Tools 2019.5, with releases before that this effect doesn’t show up (the UI draws correctly just like it does in other hosts). I also spotted a few isolated colour changes in their UI when targeting stuff with the macOS color meter app, so I guess they’ve changed something about how the UI is being drawn and this is having some colour space impact somehow.

Digging a bit deeper into how the colour spaces are handled in Juce I found that swapping out occurrences of CGColorSpaceCreateDeviceRGB() with CGColorSpaceCreateWithName(kCGColorSpaceSRGB) has helped. Testing is so far is quite limited but I did try an old 2013 retina MBP and new iMac with success so I have some confidence this is a reasonable thing to be doing. If anybody knows more about how this stuff actually works it could be helpful as this was a stab-in-the-dark solution somewhat.

1 Like