Artifacts in Sprite

I have the same sprite renderered in two different areas of the code. (Two different screens). Both are the same scale. Same blend mode. One has an outline on the bottom, the other doesn’t.

I am pretty sure that I have set both Sprite nodes up the same, but something in the process is obviously different but I can’t quite put my finger on it…

Any pointers…?

CocosV4, OSX, Metal.


Looks like one has premultiplied alpha, and the other doesn’t, and the usual give-away is the grey outline that shouldn’t be there. Are you creating a sprite (or saving it to storage) using RenderTexture?

If you’re trying to load a texture that already has the alpha premultiplied, then you need to make sure you load it like this:

auto* texture = textureCache->getTextureForKey(pngFilename);
if (texture == nullptr)
{
    auto* image = new Image();
    Image::setPNGPremultipliedAlphaEnabled(false); // This is confusing, because it means APPLY PMA AFTER LOADING, and we don't want it to apply PMA to an image that is already PMA
    image->initWithImageFile(pngFilename);
    Image::setPNGPremultipliedAlphaEnabled(true); // Reset it back to what it should be
    texture = textureCache->addImage(image, pngFilename);
    CC_SAFE_RELEASE(image);
}

There is also a fix for Cocos2d-x v3 and v4 regarding saving images on iOS, where iOS was forcefully converting the image, which is not the correct behavior.

If you’re just working with RenderTexture in memory, then the same applies since it is still related to premultiplied alpha, so just check how you’re using the images, but it could very well still be a bug in the engine code.

I concur that it has something to do with PreMultiplied alpha because if I turn that option on in TexturePacker I see the problem on both screens. However what I don’t understand is that these are bother rendering the SAME texture (I think)…

This is my graphics loading.

// load images
for (std::string file_name : {"rest-0", "rest-1", "language-0", "terrain/terrain-0", "terrain/terrain-1" }) {
    Director::getInstance()->getTextureCache()->addImageAsync(file_name + ".png", [&,file_name](Texture2D* loaded_texture) {
               
        SpriteFrameCache::getInstance()->addSpriteFramesWithFile(file_name + ".plist", loaded_texture);
                    
        monitor->Update("Loaded " + file_name,1);
        count++;
        if ( count == 5 ) {
            std::lock_guard<std::mutex> guard(mutex);
            isDataLoaded = true;
            condition.notify_one();
        }
    });
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

Here is the instance that works…

// Terrain Image
imgTerrain = Sprite::create();
imgTerrain->setScale(1.0f);
imgTerrain->setBlendFunc(cocos2d::BlendFunc::ALPHA_NON_PREMULTIPLIED);
scrollView->addChild(imgTerrain);
y = RES(TERRAIN_Y) - imgTerrain->getContentSize().height;
x = RES(TERRAIN_X) - imgTerrain->getContentSize().width/2;
uihelper::PositionParentTopRight(imgTerrain,x,y);

… later in code, d->filename will be the name of the image as identified by the texture packer plist

imgTerrain->initWithSpriteFrameName(d->file);
imgTerrain->setVisible(true);

Now the one that doesn’t work

auto image = Sprite::createWithSpriteFrameName( imagename );
image->setScale(1.0f);
image->setBlendFunc(cocos2d::BlendFunc::ALPHA_NON_PREMULTIPLIED);
image->setScale( image->getScale() * scale);
image->setPosition(options->generator->NormaliseXPosition(x), getContentSize().height - y);
image->setAnchorPoint(Vec2(0.5,0)); // bottom center
addChild(image);

The real difference between the two is that one is created as a node and updated a little later, whereas the second is created and added in the same stretch of code. However I have tried both approaches on the one that doesn’t work.

What happens if you comment out the image->setBlendFunc(cocos2d::BlendFunc::ALPHA_NON_PREMULTIPLIED); in the section of code that shows the sprite with the problem?

That fixes the line - but breaks the shader when I add it back. I still don’t understand the difference between the two setups - same texture.

I was curious if it would fix it, and since it did fix the PMA issue, there is something going on between the two sections of code that use that texture. The best thing to do would be to step through the code in a debugger, from the loading all the way to the second instance where it has the problem, and see what values the texture has internally.

Pay close attention to what happens in Texture2D::initWithData(...) etc, and go from there.

So looking at it in detail, the engine pre multiplies the alpha on textures… if it doesn’t all hell breaks loose and the speed and quality is not very good. So aside from trying to find out the difference between those two use of textures it might just be easier to accept pre multiply and work out how to change my shader to handle it.

For reference this is my shader. I think there is a better way to do this. Basically I am using greyscale image (albeit I’m not saving it as a greyscale PNG) and using the component value of go 0 - 255 to display the image int two colours. where 0 is 100% colour1 and 255 is 100% colour 2.

uniform vec4 p_left;
uniform vec4 p_right  ;
uniform float p_alpha  ;

void main()
{
    vec4 c = texture2D(u_texture, cc_FragTexCoord1).rgba;
    vec4 n;
    vec4 m = vec4( 0.0, 0.0, 0.0, 0.0 ) ;

    m.g = 1.0 - c.g ;
    m.a = c.a;
    
    n = vec4(
        (p_right.r*c.g)+(p_left.r*m.g),
        (p_right.g*c.g)+(p_left.g*m.g),
        (p_right.b*c.g)+(p_left.b*m.g), m.a) ;
    
    n.a = n.a * p_alpha;

    gl_FragColor = n ;

}

So this code doesn’t make any sense to me in the engine.

bool Image::initWithPngData(const unsigned char* data, ssize_t dataLen)
{
    ....
     // premultiplied alpha for RGBA8888
    if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
    {
        if (PNG_PREMULTIPLIED_ALPHA_ENABLED)
        {
            premultiplyAlpha();
        }
        else
        {
#if CC_ENABLE_PREMULTIPLIED_ALPHA != 0
            _hasPremultipliedAlpha = true;
#endif
        }
    }
}

If PNG_PREMULTIPLIED_ALPHA_ENABLED is turned on, it processes the image and converts it to pre-multiplied alpha. However if it’s not turned on, it sets the flag on the image to say that it is.

Output of the two objects while running.

sprite_texture_logs.zip (5.3 KB)

That code, while it looks a bit odd, is basically doing this:

Firstly, PNG_PREMULTIPLIED_ALPHA_ENABLED is pointing to a global static which is set via Image::setPNGPremultipliedAlphaEnabled(true | false);. The default value is true, so all images being loaded are assumed to be non-PMA.

If PNG_PREMULTIPLIED_ALPHA_ENABLED is true, then the image being loaded is assumed not be to PMA, and PMA is applied to it via premultiplyAlpha(), and that function sets _hasPremultipliedAlpha = true when it’s completed processing.

If PNG_PREMULTIPLIED_ALPHA_ENABLED is false, then no processing will take place, so we go into the else condition, being:

        else
        {
#if CC_ENABLE_PREMULTIPLIED_ALPHA != 0
            _hasPremultipliedAlpha = true;
#endif
        }

Here it is setting _hasPremultipliedAlpha based on whether the engine is using PMA textures or non-PMA textures in memory. The default is that it uses PMA textures.

Regardless which path it takes in that conditional check, both result in setting _hasPremultipliedAlpha = true. One path processes the image to apply PMA, the other does not. It’s useful when you have an image on disk or in memory that already has PMA, and you don’t want it processed again, since it would result in incorrect output.

So, in my earlier code snippet, this is an example of the image on disk being PMA, and I don’t want it to be processed again:

    Image::setPNGPremultipliedAlphaEnabled(false); // set PNG_PREMULTIPLIED_ALPHA_ENABLED = false
    image->initWithImageFile(pngFilename); // image file already PMA
    Image::setPNGPremultipliedAlphaEnabled(true); // set PNG_PREMULTIPLIED_ALPHA_ENABLED = true (default)

…which will end up in the else block:

        if (PNG_PREMULTIPLIED_ALPHA_ENABLED)
        {
            premultiplyAlpha();
        }
        else
        {
#if CC_ENABLE_PREMULTIPLIED_ALPHA != 0
            _hasPremultipliedAlpha = true;
#endif
        }

See if this shader works (untested):

uniform vec4 p_left;
uniform vec4 p_right  ;
uniform float p_alpha  ;

#ifndef saturate
#define saturate(v) clamp(v, 0.0, 1.0)
#endif

void main()
{
    vec4 c = texture2D(u_texture, cc_FragTexCoord1).rgba;
    vec4 n;
    vec4 m = vec4( 0.0, 0.0, 0.0, 0.0 ) ;

    vec3 fragRGB = c.rgb; // current color

    if (c.a == 0.0)
    {
        gl_FragColor = c
        return;
    }

    // convert to non-PMA
    fragRGB = saturate(fragRGB / c.a);

    m.g = 1.0 - fragRGB.g;
    m.a = c.a;
    
    n = vec4(
        (p_right.r*fragRGB.g)+(p_left.r*m.g),
        (p_right.g*fragRGB.g)+(p_left.g*m.g),
        (p_right.b*fragRGB.g)+(p_left.b*m.g), m.a) ;
    
    n.a = n.a * p_alpha;
    
    fragRGB = n.rgb * n.a; // Premultiply alpha

    gl_FragColor = vec4(fragRGB.rgb, n.a);
}

You basically need to convert it back to non-PMA, do your processing, then convert it back to PMA. You may have to remove the setBlendFunc(cocos2d::BlendFunc::ALPHA_NON_PREMULTIPLIED); calls, except I think there is still something wrong with the code that is displaying the images, since one works and the other doesn’t. The shader may only be part of the solution.

That is perfect thanks - everything except the if (c.a == 0.0) chunk (taking into account the missing semicolon ) - which results is nothing being displayed. But remove that and it is spot on! Really much appreciate it.

I’m happy to spend more time searching for the issue if that helps locate a possible anomaly - or all my code is on GitHub.

I’m glad it worked, and sorry about the missing semi-colon… I did warn you it wasn’t tested :wink:

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.