Reading pixel data from a texture

Started by
16 comments, last by craziecoder 15 years, 11 months ago
Hi All, I am using Directx 9. I want to read RGBA value from a given texture. Am using D3DXCreateTextureFromFileEx to load a texture. The texture is stored in LPDIRECT3DTEXTURE9. What I want is, If I pass X & Y values, I should get RGBA value at that point. How do I go about it. Thanks In advance. Altaf
Advertisement
Use IDirect3DTexture9::LockRect to lock the surface. If you're reading pixels very infrequently (Less often than once or twice per frame), you could just lock a 1x1 rect, otherwise lock the entire surface and use the pitch to work out the X,Y coordinate.

Assuming your texture is 32-bit (If it's not, you'll have to change the code to cope with that):
DWORD GetPixelColour(const D3DLOCKED_RECT& rect, DWORD x, DWORD y){   const DWORD* pBits = (const DWORD*)rect.pBits;   return pBits[y*rect.Pitch + x];}
Hi Steve,

This is how I implemented it...
DWORD GetPixelColour( DWORD x, DWORD y )
{
D3DLOCKED_RECT rect;
m_texture->LockRect(0, &rect, NULL, D3DLOCK_READONLY );
const DWORD* pBits = (const DWORD*)rect.pBits;
m_texture->UnlockRect(0);

return pBits[y*rect.Pitch + x];
}

returned value is stored in a DWORD.
To convert this value into ARGB format I use the following code,

int iRed = hex & 0xFF;
int iGreen = ( hex / 0x100 ) & 0xFF;
int iBlue = ( hex / 0x10000 ) & 0xFF;
int iAlpha = ( hex / 0x1000000 ) & 0xFF;

Am I doing any thing wrong here. because am not getting proper values.


But am not getting proper values.
Don't unlock it until you're done reading from it.
As soon as you unlock the texture, the locked data is no longer valid (It might "just work", but might not). So you should grab the pixel, unlock the surface, then return the pixel.

The pixel format depends on the D3DFMT_ type you passed to D3DXCreateTextureFromFileEx)(. It needs to be D3DFMT_A8B8G8R8 to work with your code. I believe D3DFMT_A8R8G8B8 is more common, in which case your code would be:
DWORD GetPixelColour( DWORD x, DWORD y ){   D3DLOCKED_RECT rect;   HRESULT hResult = m_texture->LockRect(0, &rect, NULL, D3DLOCK_READONLY );   if(FAILED(hResult))      return 0; // Failed to lock the texture for some reason, return error   const DWORD* pBits = (const DWORD*)rect.pBits;   DWORD dwPixel = pBits[y*rect.Pitch + x];   m_texture->UnlockRect(0);   return dwPixel;}// And then:int iAlpha = (hex >> 24) & 0xFF;int iRed = (hex >> 16) & 0xFF;int iGreen = (hex >> 8) & 0xFF;int iBlue = (hex >> 0) & 0xFF;


A few points:
  • I changed divisions to bit shifts to make things a little easier to read (IMO).
  • I added error checking (Which is extremely important here, if LockRect() fails and you don't notice, you'll probably crash your app).
  • Locking a texture can be potentially expensive, so it'd be preferable to lock the texture, read several pixels, then unlock it if you're going to be processing more than one or two pixels per frame.
  • If you're just loading the texture to read pixels, and not actually rendering it, loading it into D3DPOOL_SCRATCH or D3DPOOL_SYSTEMMEM would probably be faster.
  • Unrelated - you should be using the Debug Runtimes if you're not already, which will give you useful error messages if anything goes wrong (Like Lockrect() failing).
  • I have been having a similar problem and I do not see what is wrong with what I am doing. Here is how I load the texture and how I try and grab the data. The probably is that I do not recieve the correct alpha/red/green/blue values that I should.

    D3DXCreateTextureFromFileEx(lpD3Device, "Test.bmp", 0, 0, D3DX_DEFAULT, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,	D3DX_DEFAULT, 0, 0, 0, &m_tTestToRead);	for(unsigned int x = 0; x < width; ++x)	{		for(unsigned int y = 0; y < height; ++y)		{			m_tTestToRead->LockRect(0, &tempLocked, NULL, D3DLOCK_READONLY);			int bytesPerPixel = tempLocked.Pitch / width;			int index = (x*tempLocked.Pitch) + y;			m_tTestToRead->UnlockRect(0);			BYTE* pColor = (BYTE*)tempLocked.pBits;						float alpha = pColor[index];			float red = pColor[index + 1];			float green = pColor[index + 2];			float blue = pColor[index + 3];		}	}
    Quote:Original post by LeonPost
    I have been having a similar problem and I do not see what is wrong with what I am doing. Here is how I load the texture and how I try and grab the data. The probably is that I do not recieve the correct alpha/red/green/blue values that I should.

    D3DXCreateTextureFromFileEx(lpD3Device, "Test.bmp", 0, 0, D3DX_DEFAULT, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,	D3DX_DEFAULT, 0, 0, 0, &m_tTestToRead);	for(unsigned int x = 0; x < width; ++x)	{		for(unsigned int y = 0; y < height; ++y)		{			m_tTestToRead->LockRect(0, &tempLocked, NULL, D3DLOCK_READONLY);			int bytesPerPixel = tempLocked.Pitch / width;			int index = (x*tempLocked.Pitch) + y;			m_tTestToRead->UnlockRect(0);			BYTE* pColor = (BYTE*)tempLocked.pBits;						float alpha = pColor[index];			float red = pColor[index + 1];			float green = pColor[index + 2];			float blue = pColor[index + 3];		}	}


    It's the exact same problem as others have already discussed above. You're trying to access the data (tempLocked.pBits) AFTER you unlock the surface. All data access should occur inside a lock/unlock pair.
    And X and Y are the wrong way around here:
    int index = (x*tempLocked.Pitch) + y;
    It should be:
    int index = (y*tempLocked.Pitch) + x*4;
    (*4 because you have 4 bytes per pixel)

    EDIT: And you realise that'll give you values of 0..255 for alpha, red, green and blue? You'll want to do something like:
    float alpha = (float)pColor[index] / 255.0f;
    to give you values of 0..1.

    EDIT#2: And you'll definitely want to do the Lock/Unlock call outside the loop, putting it inside the loop like that could have severe performance problems.
    I am still having the same exact problem, even with those fixes.

    	for(unsigned int x = 0; x < width; ++x)	{		for(unsigned int y = 0; y < height; ++y)		{			m_tTestToRead->LockRect(0, &tempLocked, NULL, D3DLOCK_READONLY);			int bytesPerPixel = tempLocked.Pitch / width;			int index = (y*tempLocked.Pitch) + x*4;			BYTE* pColor = (BYTE*)tempLocked.pBits;			m_tTestToRead->UnlockRect(0);						float alpha = (float)pColor[index] / 255.0f;			float red = (float)pColor[index + 1] / 255.0f;			float green = (float)pColor[index + 2] / 255.0f;			float blue = (float)pColor[index + 3] / 255.0f;		}	}


    Is what I am currently using. Was there anything wrong with the way I was loading my texture?
    You're still using the locked data after you've unlocked the texture. You need to copy the data out and then unlock the texture:
    m_tTestToRead->LockRect(0, &tempLocked, NULL, D3DLOCK_READONLY);for(unsigned int x = 0; x < width; ++x){   for(unsigned int y = 0; y < height; ++y)   {      int index = (y*tempLocked.Pitch) + x*4;      BYTE* pColor = (BYTE*)tempLocked.pBits;      float alpha = (float)pColor[index] / 255.0f;      float red = (float)pColor[index + 1] / 255.0f;      float green = (float)pColor[index + 2] / 255.0f;      float blue = (float)pColor[index + 3] / 255.0f;   }}m_tTestToRead->UnlockRect(0);
    I've also moved the LockRect() out of the loop. Also, this line doesn't do what you think it does:
    int bytesPerPixel = tempLocked.Pitch / width;
    A 32-bit texture that's 129 pixels wide could well have a pitch of 1024 bytes, which would give you a bytesperpixel value of 7. The pitch is the distance in bytes between scanlines. Some drivers keep information at the end of a scanline of pixels, and trampling over it will in the best case not work, and in the worst case cause graphical corruption or crash the application.

    This topic is closed to new replies.

    Advertisement