Is it possible to "erase" some pixels from a sprite?

Is it possible to "erase" some pixels from a sprite?
0.0 0

#1

If I have a sprite (png), is it possible to delete some of them (make them transparent) and re-update the sprite with that modified texture? :slight_smile:


#2

Yes, you can do this. Something like this:

img = new Image();
img->initWithImageFile(filename);

CCLOG("%s loaded. Size: %dx%d. Bits per pixel: %d", filename.c_str(), img->getWidth(), img->getHeight(), img->getBitPerPixel());

data = img->getData(); //pixel data

Now, modify pixel data as you wish. (memset is fast for this kind of tasks).

After you’re done create texture from img and then sprite from texture:

texture = new Texture2D();
texture->initWithImage(img);
sprite = Sprite::createWithTexture(texture);

#3

@piotrros This is something good to document in the Programmers Guide. Could you write out a small example (just what you did above, but change some pixels) using this:


#4

check this post on how to make pixels transparent:

P.S: there is also Texture2D::updateWithData() method for texture data manipulation which typically used with RenderTexture.


#5

What is “this” ? :slight_smile:

Anyway I’ve created small example:

HelloWorldScene.h:

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"
USING_NS_CC;

class HelloWorld : public cocos2d::Layer
{
public:
    static cocos2d::Scene* createScene();

    virtual bool init();
    
    void setColor4B(int x, int y, Color4B color, unsigned char* data);
    
    // implement the "static create()" method manually
    CREATE_FUNC(HelloWorld);
    
    Image* img;
    unsigned char* data;
    Texture2D* texture;
    Sprite* sprite;
    
};

#endif // __HELLOWORLD_SCENE_H__

HelloWorldScene.cpp:

#include "HelloWorldScene.h"
#include "SimpleAudioEngine.h"

USING_NS_CC;

Scene* HelloWorld::createScene()
{
    // 'scene' is an autorelease object
    auto scene = Scene::create();
    
    // 'layer' is an autorelease object
    auto layer = HelloWorld::create();

    // add layer as a child to scene
    scene->addChild(layer);

    // return the scene
    return scene;
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }
    
    auto visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();
    
    std::string filename = "HelloWorld.png";
    img = new Image();
    img->initWithImageFile(filename);
    
    CCLOG("%s loaded. Size: %dx%d. Bits per pixel: %d", filename.c_str(), img->getWidth(), img->getHeight(), img->getBitPerPixel());
    
    data = img->getData(); //pixel data
    
    memset(data + img->getDataLen() / 4, 255, img->getDataLen() / 4); //white rectangle
    setColor4B(83, 109, Color4B::RED, data); //single red pixel
    
    texture = new Texture2D();
    texture->initWithImage(img);
    auto sprite = Sprite::createWithTexture(texture);

    sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
    addChild(sprite);
    
    setColor4B(85, 109, Color4B::GREEN, data); //single green pixel
    //we have to update texture
//    texture->updateWithData(data, 0, 0, img->getWidth(), img->getHeight()); //here's how to update whole data
    texture->updateWithData(data, 85, 109, 1, 1); //you can also update only part of it to increase performance
    
    return true;
}

void HelloWorld::setColor4B(int x, int y, Color4B color, unsigned char* data){
    int index = (x + y * img->getWidth()) << 2; //the same as * 4
    data[index] = color.r;
    data[index + 1] = color.g;
    data[index + 2] = color.b;
    data[index + 3] = color.a;
}

TextureDrawing.zip (4.8 MB)


#6

@piotrros. This was the blue alien graphic I put in the post. I’ll run this example. Thanks!


#7

ahaha I see now. I used hello world image from template instead :slight_smile:


#8

hi @piotrros. I have extended your code & i tried to make a color Book using Flood fill algorithm chk this link.

Please refer my code & i have also updated the code

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }
    
    auto visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();
    
    registerTouch();
    
//    auto bg = Sprite::create("SCN_bg.png");
//    bg->setPosition(Vec2(visibleSize.width/2, visibleSize.height/2));
//    this->addChild(bg);
    
    std::string filename = "SCN_Clear_apple.png";
    img = new Image();
    img->initWithImageFile(filename);
    
    CCLOG("%s loaded. Size: %dx%d. Bits per pixel: %d", filename.c_str(), img->getWidth(), img->getHeight(), img->getBitPerPixel());
    
    if(img->hasAlpha())
        rgbProfile = 4;
    else
        rgbProfile = 3;
    
    //data = new unsigned char[img->getDataLen() * rgbProfile];
    data = img->getData(); //pixel data
    
    width = img->getWidth();
    height = img->getHeight();
    
    log("Data Length %zd ", img->getDataLen());
    
    log("%d * %d", width, height);
    
    texture = new Texture2D();
    texture->initWithImage(img);
    
    sprite = Sprite::createWithTexture(texture);
    
    sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
    addChild(sprite);
    
//    memset(data + img->getDataLen() / 4, 255, img->getDataLen() / 4); //white rectangle
//    setColor4B(83, 109, Color4B::RED, data); //single red pixel
    
    //setColor4B(382, 640, Color4B::RED, data); //single green pixel
    //we have to update texture
    //texture->updateWithData(data, 0, 0, img->getWidth(), img->getHeight()); //here's how to update whole data
    //texture->updateWithData(data, 83, 109, 1, 1); //you can also update only part of it to increase performance
    
    return true;
}

void HelloWorld::setColor4B(int x, int y, Color4B color, unsigned char* data)
{
    
     int index = (x + y * img->getWidth()) << 2; //the same as * 4
     data[index] = color.r;
     data[index + 1] = color.g;
     data[index + 2] = color.b;
     data[index + 3] = color.a;
    
    //texture->updateWithData(data, x, y, 1, 1);
    
//    for(int i = 0; i < width; ++i)
//    {
//        for(int j = 0; j < height; ++j)
//        {
//            int index = (i + j * width) << 2; //the same as * 4
//            data[index] = color.r;
//            data[index + 1] = color.g;
//            data[index + 2] = color.b;
//            data[index + 3] = color.a;
//        }
//    }
}

void HelloWorld::FloodFillBorder(int cx, int cy, cocos2d::Color4B fillColor, cocos2d::Color4B borderColor)
{
    std::queue<Point> pixelPoints;
    
    int noOfPixels = width * height;
    
    std::vector<bool> vCheckedPixels(noOfPixels, false);
    
    pixelPoints.push(Point(cx, cy));
    
    while(pixelPoints.size() > 0)
    {
        Vec2 current = pixelPoints.back();
        pixelPoints.pop();
        
        for(int i = current.x; i < width; ++i)
        {
            int index = (i + (int)current.y * width) << 2;
            unsigned char * pixel = &data[index];
            // You can see/change pixels' RGBA value(0-255) here !
            Color4B tempValues; //  pixel value at this position
            
            tempValues.r = data[index];
            tempValues.g = data[index + 1];
            tempValues.b = data[index + 2];
            tempValues.a = data[index + 3];
            
            if( pixel == nullptr || tempValues == borderColor )
                break;
            
//            data[index] = fillColor.r;
//            data[index + 1] = fillColor.g;
//            data[index + 2] = fillColor.b;
//            data[index + 3] = fillColor.a;
            
            vCheckedPixels[i + ((int)current.y * width)] = true;
            
            //texture->updateWithData(data, current.x, current.y, 1, 1);
            
            setColor4B(current.x, current.y, fillColor, data);
            
            if(current.y + 1 < height)
            {
                int topIndex =  (i + ((int)current.y * width + width)) << 2;
                //unsigned char topPixel = data[topIndex];
                Color4B topPixelColor;
                topPixelColor.r = data[topIndex];
                topPixelColor.g = data[topIndex + 1];
                topPixelColor.b = data[topIndex + 2];
                topPixelColor.a = data[topIndex + 3];
                
                if(vCheckedPixels[i + ((int)current.y * width) + width] == false && topPixelColor != borderColor)
                {
                    pixelPoints.push(Point(i, current.y + 1));
                    //pixelPoints.push(Point(i, topIndex));
                }
            }
            if(current.y - 1 >= 0)
            {
                int bottomIndex =  (i + ((int)current.y * width - width)) << 2;
                //unsigned char topPixel = data[topIndex];
                Color4B bottomPixelColor;
                bottomPixelColor.r = data[bottomIndex];
                bottomPixelColor.g = data[bottomIndex + 1];
                bottomPixelColor.b = data[bottomIndex + 2];
                bottomPixelColor.a = data[bottomIndex + 3];
                
                if(vCheckedPixels[i + ((int)current.y * width) - width] == false && bottomPixelColor != borderColor)
                {
                    pixelPoints.push(Point(i, current.y + 1));
                    //pixelPoints.push(Point(i, bottomIndex));
                }
            }
        }
        
        for(int i = current.x - 1; i >= 0; --i)
        {
            int index = (i + ((int)current.y * width)) << 2;
            unsigned char * pixel = &data[index];
            // You can see/change pixels' RGBA value(0-255) here !
            Color4B tempValues; //  pixel value at this position
            tempValues.r = data[index];
            tempValues.g = data[index + 1];
            tempValues.b = data[index + 2];
            tempValues.a = data[index + 3];
            
            if( pixel == nullptr || tempValues == borderColor )
                break;
            
//            data[index] = fillColor.r;
//            data[index + 1] = fillColor.g;
//            data[index + 2] = fillColor.b;
//            data[index + 3] = fillColor.a;
            
            //texture->updateWithData(data, current.x, current.y, 1, 1);
            setColor4B(current.x, current.y, fillColor, data);
            
            vCheckedPixels[i + ((int)current.y * width)] = true;
            
            if(current.y + 1 < height)
            {
                int topIndex =  (i + ((int)current.y * width) + width) << 2;
                //unsigned char topPixel = data[topIndex];
                Color4B topPixelColor;
                topPixelColor.r = data[topIndex];
                topPixelColor.g = data[topIndex + 1];
                topPixelColor.b = data[topIndex + 2];
                topPixelColor.b = data[topIndex + 3];
                
                if(vCheckedPixels[i + ((int)current.y * width) + width] == false && topPixelColor != borderColor)
                {
                    pixelPoints.push(Point(i, current.y + 1));
                    //pixelPoints.push(Point(i, topIndex));
                }
            }
            if(current.y - 1 >= 0)
            {
                int bottomIndex =  (i + ((int)current.y * width - width)) << 2;
                //unsigned char topPixel = data[topIndex];
                Color4B bottomPixelColor;
                bottomPixelColor.r = data[bottomIndex];
                bottomPixelColor.g = data[bottomIndex + 1];
                bottomPixelColor.b = data[bottomIndex + 2];
                bottomPixelColor.a = data[bottomIndex + 3];
                
                if(vCheckedPixels[i + ((int)current.y * width) - width] == false && bottomPixelColor != borderColor)
                {
                    pixelPoints.push(Point(i, current.y + 1));
                    //pixelPoints.push(Point(i, bottomIndex));
                }
            }
        }
        
        //texture->updateWithData(data, current.x, current.y, 1, 1);
    }
    
    //texture->updateWithData(data, 0, 0, width, height);
}

void HelloWorld::registerTouch()
{
    auto touchListener = EventListenerTouchOneByOne::create();
    touchListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
    Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(touchListener, this);
    
    auto keyListener = EventListenerKeyboard::create();
    keyListener->onKeyReleased = CC_CALLBACK_2(HelloWorld::onKeyReleased, this);
    Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(keyListener, this);
}

bool HelloWorld::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event)
{
    auto touchLocation = touch->getLocation();
    touchLocation = this->convertToNodeSpace(touchLocation);
    
    if(sprite->getBoundingBox().containsPoint(touchLocation))
    {
        int actualTexCoordinateX;
        int actualTexCoordinateY;
        
        int paddingX, paddingY;
        
        paddingX = sprite->getPosition().x - width/2;
        paddingY = sprite->getPosition().y - height/2;
        
        actualTexCoordinateX = touchLocation.x - paddingX;
        actualTexCoordinateY = touchLocation.y - paddingY;
        
        FloodFillBorder(actualTexCoordinateX, actualTexCoordinateY, Color4B::RED, Color4B::BLACK);
        
        //sprite->setTexture(texture);
        
        //FloodFillBorder(actualTexCoordinateX, 100, Color4B::GREEN, Color4B::BLACK);
    }
    
    return true;
    
}

If confused please check the complete classed & resource folder which i have attached.

Archive.zip (533.0 KB)


#9

Sorry in hurry i forgot to mention that i am not able to do flood fill color. What am i doing wrong.
Please point out. I know it might be a silly mistake, but couldn’t make out.


#10

I’ve used this method to implement color book as well :slight_smile:

I can’t share my code, but I’ve used the algorithm from here:
http://lodev.org/cgtutor/floodfill.html

And they work just fine.


#11

Hi,

I dont want your code :grinning: As i have already put much time in it. I am just trying to figure out why texture is not updating. I have uncommented the line texture->updateWithData(data, current.x, current.y, 1,1); When i debug then also i can get the exact number of pixels. What i am thinking is either i am getting the data wrong or i am accessing it in a wrong way.

Basically i am not able to understand this line

int index = (i + (int)current.y * width) << 2;

why we are using the left shift bit operator.


#12

<< 2 is the same as * 4, it’s just faster :slight_smile:

You can replace it with * 4 and it’ll work the same.

setColor4B is fine.

I’ve ran your example as Mac OS Desktop and it’s freezing the app.

I’m pretty sure your flood fill implementation is wrong.

Try implementing this algorithm:

Scanline Floodfill Algorithm With Stack (floodFillScanlineStack)

from the link I posted above.

It’s not that hard :wink: