Sign in to follow this  

Reading pixel data from a texture

This topic is 3491 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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

Share this post


Link to post
Share on other sites
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];
}

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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).

    Share this post


    Link to post
    Share on other sites
    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];
    }
    }

    Share this post


    Link to post
    Share on other sites
    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.

    Share this post


    Link to post
    Share on other sites
    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.

    Share this post


    Link to post
    Share on other sites
    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?

    Share this post


    Link to post
    Share on other sites
    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.

    Share this post


    Link to post
    Share on other sites
    Thank you for all the help. I think there may be one major problem with the way I am reading the colors. Shouldn't I be reading it with the alpha being index + 3? So it should really look like

    float alpha = (float)pColor[index + 3] / 255.0f;
    float red = (float)pColor[index + 2] / 255.0f;
    float green = (float)pColor[index + 1] / 255.0f;
    float blue = (float)pColor[index] / 255.0f;

    Share this post


    Link to post
    Share on other sites
    This is how am creating texture.

    HRESULT hr = D3DXCreateTextureFromFileEx( GetDevice(), fileName, D3DX_DEFAULT, D3DX_DEFAULT, 1, 0,D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_FILTER_NONE, D3DX_FILTER_NONE, D3DCOLOR_COLORVALUE( 0.0f, 0.0f, 0.0f, 1.0f ),
    NULL, NULL, &m_texture );


    This is my code for reading pixel data,
    DWORD GetPixelColour( DWORD x, DWORD y )
    {
    D3DLOCKED_RECT rect;
    HRESULT result = m_texture->LockRect(0, &rect, NULL, D3DLOCK_READONLY );
    if( FAILED( result ))
    OutputDebugString( "\nFailed to lock the texture...." );

    const DWORD* pBits = (const DWORD*)rect.pBits;

    DWORD pixData = pBits[y*rect.Pitch + x];
    m_texture->UnlockRect(0);
    return pixData;
    }

    This is the code to read pixel data ( as given above )

    int iAlpha = (hex >> 24) & 0xFF;
    int iRed = (hex >> 16) & 0xFF;
    int iGreen = (hex >> 8) & 0xFF;
    int iBlue = (hex >> 0) & 0xFF;

    Even now am not getting proper values. Is there anything wrong? I have 3 textures. One Blue, one Red & one Green with 100% transparency in the center. I never get expected values.

    Share this post


    Link to post
    Share on other sites
    Usually you want to make sure you shift your pitch by >>2 if you are dealing with D3DFMTA8R8G8B8.

    Example:
    DWORD pbits = (DWORD*)rect.pbits;
    int shift = rect.pitch>>2;

    Then you can just do this for the pixel color.
    DWORD getPixel(int x,int y)
    {
    return pbits[y*shift+x];
    }

    //Remember that will return to you the RGBA pack into the DWORD you will need to do some shifting to extract it into component.

    Example:
    DWORD color = getPixel(0,0);
    int alpha = ((color&0xff000000)>>24)
    int red = ((color&0x00ff0000)>>16);
    int green = ((color&0x0000ff00)>>8);
    int blue = (color&0x000000ff);


    //That is exactly how i do it in my engine, and it works like an charm.

    Share this post


    Link to post
    Share on other sites
    Quote:
    Original post by LeonPost
    Thank you for all the help. I think there may be one major problem with the way I am reading the colors. Shouldn't I be reading it with the alpha being index + 3? So it should really look like

    float alpha = (float)pColor[index + 3] / 255.0f;
    float red = (float)pColor[index + 2] / 255.0f;
    float green = (float)pColor[index + 1] / 255.0f;
    float blue = (float)pColor[index] / 255.0f;
    Nope, your texture data is D3DFMT_A8R8G8B8. So the pixels are laid out in ARGB order. The first byte you come to (at pColor[index + 0]) will be the alpha, the next will be red, the next green, and the next blue.

    Quote:
    Original post by craziecoder
    Even now am not getting proper values. Is there anything wrong? I have 3 textures. One Blue, one Red & one Green with 100% transparency in the center. I never get expected values.
    What size is your source image? Is it a power of two? And what values are you getting? Also, I'd be inclined to add a return 0; if LockRect() fails. Spewing a debug message is fine, but it won't help you if it hangs on a machine where you're not monitoring debug output (Any end user).

    Quote:
    Original post by BornToCode
    Usually you want to make sure you shift your pitch by >>2 if you are dealing with D3DFMTA8R8G8B8.
    That's incorrect. The pitch is the distance in bytes between scanlines, if you divide it by two, then in the best case you're only skipping half a scanline, and in the worst case you're going to be reading or writing into driver-reserved memory and crash the app.

    Share this post


    Link to post
    Share on other sites
    Evil Steeve you are incorrect. I have being doing it like that for a long time and never ran into any issues. That is how it was done in DirectDraw and that is how it is done here.Also i use the same technique to create mask for my images and i never ran into any issues.

    Share this post


    Link to post
    Share on other sites
    Quote:
    Original post by BornToCode
    Evil Steeve you are incorrect. I have being doing it like that for a long time and never ran into any issues. That is how it was done in DirectDraw and that is how it is done here.Also i use the same technique to create mask for my images and i never ran into any issues.
    Ah, I see - the pBits pointer is being used as a DWORD* which is 4 bytes wide. Technically, it's preferable to use a BYTE* to offset by y*pitch, then cast to a DWORD, since Pitch might not be a multiple of 4 (Although it's extremely unlikely).

    Share this post


    Link to post
    Share on other sites
    My textures are non power of 2 textures. I get proper ARGB values as I set them in photoshop. Till now my application has never crashed. But I will surely add code for error detection as per Evil Steve`s suggestion.

    Share this post


    Link to post
    Share on other sites

    This topic is 3491 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

    If you intended to correct an error in the post then please contact us.

    Create an account or sign in to comment

    You need to be a member in order to leave a comment

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

    Sign in to follow this