[solved ]Setting individual pixels in raw image data, not working?

how to set individual pixels in raw image data (unsigned char*).

this what i tried so far:

        Director::getInstance()->getRenderer()->render();
	auto img = rt->newImage();// data is from render texture
	int w = img->getWidth(), h = img->getHeight();
	auto data = img->getData();
	int len = img->getDataLen();

// iterate over each pixel in the image and set its value to green just for testing
	Color4B c(33, 123, 115, 1);
	for (int y = 0; y < h; y++) {
		for (int x = 0; x < w; x++) {
			data[y * w * 4 + x * 4 + 0] = c.r;
			data[y * w * 4 + x * 4 + 1] = c.g;
			data[y * w * 4 + x * 4 + 2] = c.b;
			data[y * w * 4 + x * 4 + 3] = c.a;
//tried this but no luck ...
/*data[(y * w) + x] = (c.a << 24) | (c.b << 16) | (c.g << 8) | c.r; */
		}
	}

	auto tex = new Texture2D();
        tex->initWithData(data, len, Texture2D::PixelFormat::RGBA8888, w, h, Size(w, h));
	spr = Sprite::createWithTexture(tex);
	spr->setPosition(convertToWindowSpace(center));
	addChild(spr);//the image either doesn't show up or it shows but with no changes

the image data is rgba8888 (32 bit).

problem solved am manipulating individual pixels and it’s all good i guess i should’ve never posted in the first place But !! this might help some one in the feature:

i converted the color values to hex decimal and it works find now:

Color4B c(22, 79, 152, 1);
for (int y = 0; y < h; y++) {
	for (int x = 0; x < w; x++) {
		data[(y * w + x) * 4] = ((c.r & 0xff) << 16) + ((c.g & 0xff) << 8) + (c.b & 0xff);
	}
}

PS: this video helped clarifying it a bit (Setting Pixel Colors)

1 Like

@Joseph39 This is how i am doing it for one of my game prototype

 if(img->hasAlpha())
        rgbProfile = 4;

    unsigned char *imgPixelData = new unsigned char[img->getDataLen()*rgbProfile]; // getting image pixel data
    imgPixelData = img->getData();

 for(int i=0;i<img->getWidth();i++)
    {
        for(int j=0;j<img->getHeight();j++)
        {
          
            unsigned char *pixel = imgPixelData + (i + j * img->getWidth()) * rgbProfile;
            // You can see/change pixels' RGBA value(0-255) here !
            Color4B tempValues; //  pixel value at this position
              tempValues.r = *pixel;
              tempValues.g = *(pixel + 1);
              tempValues.b = *(pixel + 2) ;
              tempValues.a = *(pixel + 3);

             // temporary changing all pixels to fully transparent whose alpha is below 180
            if(tempValues.a <=180){
                tempValues.a = 255;
            }
           
            m_TextureData[i +j*img->getWidth()] = tempValues;
        }
    }

i am modifying my image data on the run and then creating a image out it, m_TextureData is my array which holds image rawdata(inverted), Whenever i change my array i update my Texture 2D using

 m_Texture->updateWithData(m_TextureData, 0, 0, w, h);

I think using array of color4B is quite memory intensive, but its working for my prototype and is quite flexible, i’ll profile it once i am done with my prototype. Until then if anyone has some suggestions about memory consumption pls do share.

@pandamicro @iQD @slackmoehrle I might be wrong but AFAIK we don’t have access to cocos::Image() internal array, do we??
if any one can suggest or point me towards a possible solution for optimizations pls do share.
Thanks

if your having performance issues then you should try memset() and see if it gives any better performance … i hear its faster But!! you have to try it yourself.
and note that for memset you have to pass the number of bytes by using sizeof(), not the number of elements.

sadly we cannot access a sprite/texture raw data other than using RenderTexture and img->getData() … it would’ve been helpful to access a sprite raw data directly from the sprite or its texture rather than using RenderTexture.

with this i am not facing any performance issue as of now, but yes loading image data in my array is a little slower. My main concern was to reduce memory consumption early on so that i can push my implementation to its limits. As per memset() it will only applicable to a block of pixels and only allow me to write same color data for that block, i am afraid for individual pixels it will yield same performance(correct me if i am wrong).

Take a read:

You are afraid of yielding the same performance?

It depends on how your code is aligned and if you get cache misses or not.

Isn’t it an option, do create a shader for your problem?

Thanks for the link but it doesn’t provide or guide to a feasible solution if i understand it correctly,
as Walzer Pointed
while maintaining the image data in CCImage will consume double size of memory, this is unacceptable.

I don’t see How VolatileTextureMgr(unable to locate it as well) can help in this regards. I am facing memory consumption issues rather then performance, for loading assets i can preload them but it will consume double memory. This could be avoided easily if we could get pixel data from shader itself.

[quote=“iQD, post:6, topic:25333”]
Isn’t it an option, do create a shader for your problem?
[/quote] My current implementation allows me to remove/ Add pixel color data pixel by pixel which is very convenient and flexible. I afraid if shader will allow to do the same, Also i am using raw data array for some other collision related stuff, Only alternate solution i see if i could write a Shader which can return raw data array and then i can manipulate that array and pass it to shade again using a Uniform. But then again as suggested by KawaiSky and perminovVs

is glGetTexImage supported in OpenGL/ES?

its not possible to return array data from shader also, :frowning: i am looking to find some other alternative approach which could allow me all the flexibility with less memory consumption.

[quote=“iQD, post:6, topic:25333”]
It depends on how your code is aligned and if you get cache misses or not.
[/quote] Right now my approach is SOA, so i think cache won’t be much of an issue.

What are you guys talking about …is it even possible to get pixel data from shader?? if it is then how … can anyone show us how or explain it or at least give us a link pls.

and i know we are going off topic here but it’s just for the sake of learning new stuff thats why we are all here aren’t we

[quote=“Joseph39, post:8, topic:25333”]
is it even possible to get pixel data from shader
[/quote]No its not seems to be possible, atleast i haven’t encountered a article till now which states a workaround for GLES2.0, Its only available in GLES 4.0. I am exploring some other optimization techniques, for performance i am trying to spatial hash my texture array so i can easily find my pixels to remove or add. @iQD and @Joseph39 do you guys know any thing better then spatial Hash with low-pass filters.

Hi,

I am trying to do this coloring book.

I have followed @Lazy_Gamer approach & implemented the flood fill algorithm but i am unable to understand why my code is not working.

// 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();

//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);

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;

// 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 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 = *pixel;
// tempValues.g = *(pixel + 1);
// tempValues.b = *(pixel + 2);
// tempValues.a = *(pixel + 3);

        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 + 2] = fillColor.a;
        
        vCheckedPixels[i + ((int)current.y * width)] = true;
        
        texture->updateWithData(data, current.x, current.y, 1, 1);
        
        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);
        
        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::GREEN, Color4B::BLACK);
}

return true;

}Archive.zip (533.0 KB)

I have also uploaded my classes & Resources.
Please tell me where i am doing wrong.

Can you share the nature of issue you are facing? what is the expected result and what are you getting. According to you what could be the issue? This will help us better understand your implementation.