love2d / love

LÖVE is an awesome 2D game framework for Lua.
https://love2d.org
Other
4.82k stars 387 forks source link

[12.0] EXR encode/export produces swizzled image content (ABGR instead of RGBA) #2070

Closed Froyok closed 1 month ago

Froyok commented 3 months ago

When exporting an EXR image from Love, the content of the file shows the wrong colors and transparency. All the image reader I tried (XnView, Substance Designer, Krita, Tacent view) show the EXR file incorrectly as well, so to me this is Love not exporting/writing the EXR properly. After investigating a bit, it seems the RGBA channels are swizzled as ABGR.


With the following example, I compensate the swizzle via the shader before hand which allows me to compensate the issue:

local ShaderVertex = [[
    #pragma language glsl4
    varying vec4 VarScreenPosition;
    varying vec2 VarVertexCoord;

    void vertexmain()
    {
        VarVertexCoord = vec2(
            (love_VertexID << 1) & 2,
            love_VertexID & 2
        );

        VarScreenPosition = vec4(
            VarVertexCoord.xy * vec2(2.0, -2.0) + vec2(-1.0, 1.0),
            0,
            1
        );

        gl_Position = VarScreenPosition;
    }
]]

local ShaderFragment = [[
    #pragma language glsl4
    varying vec2 VarVertexCoord;

    uniform float Swizzle;

    out vec4 FragColor;
    void pixelmain()
    {
        vec2 UV = VarVertexCoord.xy;

        vec4 Output = vec4(
            UV.x,
            UV.y,
            1.0 - UV.y,
            1.0
        );

        if( Swizzle > 0.0 )
        {
            Output = Output.abgr;
        }

        FragColor = Output;
    }
]]

function love.load( Args )
    Shader = love.graphics.newShader(
        ShaderFragment,
        ShaderVertex
    )

    Canvas = love.graphics.newCanvas(
        512, 512,
        {
            type        = "2d",
            format      = "rgba16f",
            mipmaps     = "none"
        }
    )

    ----------------------------------
    -- Render default version
    ----------------------------------
    love.graphics.setShader( Shader )
    love.graphics.setBlendMode( "none" )

    love.graphics.setCanvas( Canvas )
    Shader:send( "Swizzle", 0.0 )
    love.graphics.drawFromShader( "triangles", 3, 1 )
    love.graphics.setCanvas()

    local Data = love.graphics.readbackTexture( Canvas )

    local File = love.filesystem.newFile( "output_default.exr" )
    File:open( 'w' )
    File:write( Data:encode("exr") )
    File:close()

    ----------------------------------
    -- Render swizzled version
    ----------------------------------
    love.graphics.setCanvas( Canvas )
    Shader:send( "Swizzle", 1.0 )
    love.graphics.drawFromShader( "triangles", 3, 1 )
    love.graphics.setCanvas()

    Data = love.graphics.readbackTexture( Canvas )

    File = love.filesystem.newFile( "output_swizzled.exr" )
    File:open( 'w' )
    File:write( Data:encode("exr") )
    File:close()

    love.event.quit( 0 )
end

Results (converted in PNG to be visible here):


System details:

Froyok commented 3 months ago

I wonder if the issue is here: https://github.com/love2d/love/blob/657b3de9d9cd585d2ff73a196e3fc66a15eb6267/src/modules/image/magpie/EXRHandler.cpp#L310 Looking around on other implementations (relying on tinyexr like Love), they note: // Must be (A)BGR order, since most of EXR viewers expect this channel order. (from here, another example here).

slime73 commented 3 months ago

Apple's EXR decoder they use is working as expected on my Mac (the default and swizzled results are the opposite of your pic), so that comment is probably right. I bet most EXR viewers on Linux are using the same decoding library that isn't handling RGBA channel order correctly.

Froyok commented 3 months ago

Looks like Godot had the same issue (and in this case Blender and Gimp expected the swizzled result too): https://github.com/godotengine/godot/issues/55472

Froyok commented 3 months ago

I ran my example script on Windows and opened the exr resulting files in Substance Designer (Windows version too) and they show the same issue as in my original post.

I was pretty sure already that Designer wasn't relying on a system library like you mentioned, but I wanted to check just to be sure. On Designer we use FreeImage (which itself relies on OpenEXR).

I don't know what Apple is doing, but it seems to be going the opposite way of everybody (as usual :grin: ).

slime73 commented 3 months ago

Yeah Apple often uses its own proprietary decoders rather than third party ones, for images. love is actually doing the right thing here as far as I know (it should even be decoding files correctly that display wrong in things that use FreeImage), but I'll still put in a workaround to make things display right when loaded with the broken decoders.

Froyok commented 3 months ago

According to OpenEXR doc: Channels are stored in alphabetical order, according to channel names. Within a channel, pixels are stored left to right. (from here).

So my guess on what is happening here is that the order in which the channels are written in the file is not ABGR, and most decoders just get the list of channels as-is assuming that order (without looking at the actual channel name). If the Apple decoder doesn't do this assumption and properly check the name, then it explains why the result are inverted.

So I think the change should be quite simple here and require to just change the order (like they did on Godot).

If I use exrheader tool to print info on the exr files (with test file from Designer and Love), the channels are listed in the AGBR order.

Froyok commented 3 months ago

Another remark on this: would you consider exposing some parameters ? Ideally it would be nice to specify the compression scheme. Saving a roughly 1024x512 pixels image on my side takes quite a few ms to do, I wonder if no compression could improve that (since in that case I don't care about the footprint).

Froyok commented 2 months ago

So I did more investigation and tests:

I have no idea what Apple does, but if even with the help of the official OpenEXR library we cannot easily open EXR files then in my opinion it is simply broken as-is. :(

I think I will look into using OpenEXR directly myself to export the data.

Froyok commented 1 month ago

Just tried out the fix today and I can confirm it works as expected now. Thx ! :)