Black frames around sprites in Cocos2d-x v3

Sprites with alpha and non-integer position are drawn with black frame:
Cocos 3.1:
Cocos 3.1
Cocos 2.3.3:
Cocos 2.2.3

Here is code for repro:
Cocos 3.1:

    // Good sprite
    cocos2d::SpriteFrame* fr = new cocos2d::SpriteFrame();
    fr->initWithTextureFilename("Test_transparency1.png", cocos2d::Rect(0, 180, 142, 134));
    auto p = cocos2d::Sprite::createWithSpriteFrame(fr);
    p->setPosition(500, 200);
    this->addChild(p);

    // Bad sprite
    cocos2d::SpriteFrame* fr2 = new cocos2d::SpriteFrame();
    fr2->initWithTextureFilename("Test_transparency1.png", cocos2d::Rect(0, 180, 141, 134));
    auto p2 = cocos2d::Sprite::createWithSpriteFrame(fr2);
    p2->setPosition(500, 400);
    this->addChild(p2);

Cocos 2.2.3:

    // Good sprite
    cocos2d::CCSpriteFrame* fr = new cocos2d::CCSpriteFrame();
    fr->initWithTextureFilename("Test_transparency1.png", cocos2d::CCRect(0, 180, 142, 134));
    auto p = cocos2d::CCSprite::createWithSpriteFrame(fr);
    p->setPosition(ccp(500, 200));
    this->addChild(p);

    // Good sprite
    cocos2d::CCSpriteFrame* fr2 = new cocos2d::CCSpriteFrame();
    fr2->initWithTextureFilename("Test_transparency1.png", cocos2d::CCRect(0, 180, 141, 134));
    auto p2 = cocos2d::CCSprite::createWithSpriteFrame(fr2);
    p2->setPosition(ccp(500, 400));
    this->addChild(p2);

In cocos3 Position of the sprite is calculated from midpoint and if sprite has odd size (e.g. [141, 134]) it will not fit screen pixels because midpoint will be [70.5, 67].
This will force openGL to interpolate texture using GL_LINEAR by default (GL_NEAREST solves the problem but causes other aliasing problems) and transparent pixels are blended with visible ones on the border.
Macros CC_NODE_RENDER_SUBPIXEL=0 and CC_SPRITEBATCHNODE_RENDER_SUBPIXEL=0 do not solve issue also.

Note! Image is not premultiplied. Premultiplied images do not have this issue.


il_cocos2.png (63.1 KB)


il_cocos3.png (68.6 KB)


Test_transparency1.png (10.6 KB)

1 Like

In v2.x, CCImage will covert png to premultiplied alpha version.
In v3.0, these codes are removed, because it will cost more memory and waste CPU time.

So it is the difference between v2.x and v3.0.
I think there are ways to resolve it

  • use premultiply alpha png
  • make sure the midpoint is an integer
  • enable CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL in ccConfig.h

Hope it can help.
And please let me know if there is any other problem.

CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL Doesn’t fix the issue.
There is no posibility to control position of the srite.

Oh.
Sorry about that.
I have ask @Harrison to take a look.
He is expert of OpenGL.

Hi,
Non premultiplied alpha will lose some information in texture sampling.
Here is the link to demonstrate this.


In fact, a black border will appear if there is a sharp difference between alpha value of adjacent pixels and we use BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA).
So the cause of the back border in this test case is because the alpha value between the edge pixels and the outside of the edge (which is called GL_TEXTURE_BORDER_COLOR in openGL, not exist in GLES).
We are considering of adding premultiplied alpha back in our engine.
Now, these are two methods two try.

  1. Change the png file reading manually.
  2. Try to pack this image into a texture atlas, and do not place it to the edge. Then create the sprite from the textureAtlas.

Thanks.

how to make Premultiplied png?

and this problem in cocostudio animation too.
how to fix in making cocostudio animation?

@jamesYou Here is instruction for photoshop i found

  1. import/open a png with normal transparency
  2. duplicate the original layer
  3. create a new fill layer and fill with white under duplicated layer
  4. then merge the duplicated layer with the fill layer
  5. ctrl+click the original to load the transparency as a selection
  6. select the merged layer, and create a layer mask based on that selection (Layer/Layer mask/Reveal selection)

@zhangxm Thank you.

@dabingnn Thank you for feedback.
1 I’ve tried premultiplication both when loading (like cocos 2.2.3) and manually premultiplicated atlases. Both variants work.
2 This sprite is already in atlas. I’ve tried drawing spirets that not alligned to border of texture it does not help.

Please wait 24~36 hours, @dabingnn is on the flight from US back to China.

@teivaz
Sorry for my late reply.

  1. Now we can confirm that if we add premultiply alpha back for png file, it works correctly.
  2. We have reproduced the black border bug based on v3.x version. If we edit the texture file by photoshop, it works well, however, if we use illustrator, there is a black border. The reason for this is that, a sharp edge exists when we use illustrator. It is the intrinsic constrains of non premultiplied image.

I’ve solved this by manual premultiplication of image after atlas is loaded.
Can you tell me whether this feature will be returned in further versions of cocos?

Hi, we have added it back in pr: https://github.com/cocos2d/cocos2d-x/pull/6954

Thanks

Thank you, I was having the same visual artefacts on my sprites and adding premultipliedAlpha() back to CCImage solved the problem for me.

Will this be done in the final version of Cocos2d-x V3.2 ?
Or added as an option ?

Yep. It is added back in v3.2.

It looks like this problem still persists with Armatures loaded from PNG (and maybe pre-multiplied PVR) sprite sheets in Cocos v.3.2. I can see the border clearly in the CPP test Extensions > CocoStudioArmatureTest > “Test Direct Loading”, the border goes away when I change line 987 of CCImage.cpp to:

 // premultipliedAlpha();
 _preMulti = true;

But then doing so causes all the other PNGs to look funky. I know very little about BlendFuncs, but it looks like somewhere setBlendFunc( BlendFunc::ALPHA_NON_PREMULTIPLIED ) is called and persists.

On further investigation, it looks like the default set in CCDataReaderHelper.cpp is ALPHA_NON_PREMULTIPLIED, and if I change lines 1617 and 1618 to:

	frameData->blendFunc.src = (GLenum)(DICTOOL->getIntValue_json(json, A_BLEND_SRC, BlendFunc::ALPHA_PREMULTIPLIED.src));
frameData->blendFunc.dst = (GLenum)(DICTOOL->getIntValue_json(json, A_BLEND_DST, BlendFunc::ALPHA_PREMULTIPLIED.dst));

The borders go away. Is this left over from when the preMultipliedAlpha code was removed, or on purpose? Seems to me that since PNGs are programmatically premultiplied and PVRs are likely to be premultiplied in sprite sheets, then the defaults should be ALPHA_PREMULTIPLIED.

I found that I also have to set line 77 of CCBone.cpp to:

    _blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;

as this is the one used before the animation starts playing

@hawkwood
Thanks. We will check it.

I have already fixed this problem in this pr:
https://github.com/cocos2d/cocos2d-x/pull/7568​

And please let me know if there is any other problem.

@jyinkailej Thank you! I was still having a few issues with some older XML armatures, and the additional changes in your PR fixed them.

I’m using 3.3. I think I still get the black borders. I tried the same simple code as below in 2.2.3, it works fine. But same codes add sprites in 3.3, it will has black borders around the sprites.

CCSprite *d2 = CCSprite::create("dance2.png");
d2->setPosition(ccp(500, 300));
addChild(d2);

CCSprite *d7 = CCSprite::create("dance7.png");
d7->setPosition(ccp(450, 150));
addChild(d7);

CCSprite *d6 = CCSprite::create("dance6.png");
d6->setPosition(ccp(430, 250));
addChild(d6);

What do I miss when using 3.3?