好记性不如铅笔头

C && C++, cocos2dx, 编程

cocos2dx学习笔记:CCImage及获取特定点的颜色值

在进行cocos2dx游戏开发时,我们有时候需要知道特定点的颜色,这里笔记下如何使用CCImage读取png文件来实现。

CONTENTS

CCImage简单分析:

这里我们先简单的分析下CCImage的实现。
首先看下【 CCImage::initWithImageFile 】的实现,在文件【 \cocos2d-x-2.2.3\cocos2dx\platform\CCImageCommon_cpp.h 】中:

bool CCImage::initWithImageFile(const char * strPath, EImageFormat eImgFmt/* = eFmtPng*/)
{
    bool bRet = false;

#ifdef EMSCRIPTEN
。。。。。。
。。。。。。
#else
    unsigned long nSize = 0;
    std::string fullPath = CCFileUtils::sharedFileUtils()->fullPathForFilename(strPath);//获取了传入文件的绝对路径
   //读文件,pBuffer为文件内容,nSize为内容长度
    unsigned char* pBuffer = CCFileUtils::sharedFileUtils()->getFileData(fullPath.c_str(), "rb", &nSize);
    if (pBuffer != NULL && nSize > 0)
    {//进入函数进行处理,这里假设我们传入的eImgFmt为png类型的。
        bRet = initWithImageData(pBuffer, nSize, eImgFmt);
    }
    CC_SAFE_DELETE_ARRAY(pBuffer);
#endif // EMSCRIPTEN

    return bRet;
}


bool CCImage::initWithImageData(void * pData, 
                                int nDataLen, 
                                EImageFormat eFmt/* = eSrcFmtPng*/, 
                                int nWidth/* = 0*/,
                                int nHeight/* = 0*/,
                                int nBitsPerComponent/* = 8*/)
{
    bool bRet = false;
    do 
    {
        CC_BREAK_IF(! pData || nDataLen <= 0);

        if (kFmtPng == eFmt)
        {
            bRet = _initWithPngData(pData, nDataLen);
            break;
        }
        //这里我们仅仅分析png格式的读取,其他的先略掉
 				。。。。。。
 				。。。。。。
    } while (0);
    return bRet;
}

//cocos2dx使用libpng来读取png图片,具体的使用方法这里不再展开笔记,只笔记下整体的流程
bool CCImage::_initWithPngData(void * pData, int nDatalen)
{
// length of bytes to check if it is a valid png file
#define PNGSIGSIZE  8
    bool bRet = false;
    //先声明libpng需要的结构体
    png_byte        header[PNGSIGSIZE]   = {0}; 
    png_structp     png_ptr     =   0;
    png_infop       info_ptr    = 0;

    do 
    {
 	//各种校验
        // png header len is 8 bytes
        CC_BREAK_IF(nDatalen < PNGSIGSIZE);

        // check the data is png or not
        memcpy(header, pData, PNGSIGSIZE);
        CC_BREAK_IF(png_sig_cmp(header, 0, PNGSIGSIZE));

        // init png_struct
        //初始化结构体
        png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
        CC_BREAK_IF(! png_ptr);
 	//初始化结构体
        // init png_info
        info_ptr = png_create_info_struct(png_ptr);
        CC_BREAK_IF(!info_ptr);

#if (CC_TARGET_PLATFORM != CC_PLATFORM_BADA && CC_TARGET_PLATFORM != CC_PLATFORM_NACL)
        CC_BREAK_IF(setjmp(png_jmpbuf(png_ptr)));
#endif

        // set the read call back function
        tImageSource imageSource;
        imageSource.data    = (unsigned char*)pData;
        imageSource.size    = nDatalen;
        imageSource.offset  = 0;
        //设定了读取的回调函数
        png_set_read_fn(png_ptr, &imageSource, pngReadCallback);

        // read png header info
        
        //读取png文件信息
        // read png file info
        png_read_info(png_ptr, info_ptr);
        //获取了文件的宽,高等信息
        m_nWidth = png_get_image_width(png_ptr, info_ptr);
        m_nHeight = png_get_image_height(png_ptr, info_ptr);
        m_nBitsPerComponent = png_get_bit_depth(png_ptr, info_ptr);
        png_uint_32 color_type = png_get_color_type(png_ptr, info_ptr);

        //CCLOG("color type %u", color_type);
        
        //将不同的png设置为RGB8888的格式。
        // force palette images to be expanded to 24-bit RGB
        // it may include alpha channel
        if (color_type == PNG_COLOR_TYPE_PALETTE)
        {
            png_set_palette_to_rgb(png_ptr);
        }
        // low-bit-depth grayscale images are to be expanded to 8 bits
        if (color_type == PNG_COLOR_TYPE_GRAY && m_nBitsPerComponent < 8)
        {
            png_set_expand_gray_1_2_4_to_8(png_ptr);
        }
        // expand any tRNS chunk data into a full alpha channel
        if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
        {
            png_set_tRNS_to_alpha(png_ptr);
        }  
        // reduce images with 16-bit samples to 8 bits
        if (m_nBitsPerComponent == 16)
        {
            png_set_strip_16(png_ptr);            
        } 
        // expand grayscale images to RGB
        if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
        {
            png_set_gray_to_rgb(png_ptr);
        }

        // read png data
        // m_nBitsPerComponent will always be 8
        m_nBitsPerComponent = 8;
        png_uint_32 rowbytes;
        //根据高度申请行数组,每个元素代表一行,每个元素的类型为png_bytep,即unsigned char* 
        png_bytep* row_pointers = (png_bytep*)malloc( sizeof(png_bytep) * m_nHeight );
        
        png_read_update_info(png_ptr, info_ptr);
        //获取存储单行的像素的数据的大小(字节),也就是宽*4(RGBA,每个占一字节)
        rowbytes = png_get_rowbytes(png_ptr, info_ptr);
        
        //根据行数(高)和单行的数据大小申请存储空间,每个元素类型为unsigned char*
        //用来存储rgba中的一个。即每4个元素构成一个完整的像素
        m_pData = new unsigned char[rowbytes * m_nHeight];
        CC_BREAK_IF(!m_pData);
        
        //更新下行数组的节点,将每行的起始位置赋值到行数组的节点
        for (unsigned short i = 0; i < m_nHeight; ++i)
        {
            row_pointers[i] = m_pData + i*rowbytes;
        }
        //按行读取数据
        png_read_image(png_ptr, row_pointers);
        //读取结束
        png_read_end(png_ptr, NULL);
        
        //获取单个像素点占用的字节。
        png_uint_32 channel = rowbytes/m_nWidth;
        if (channel == 4)//这里必须为4,RGBA
        {
            m_bHasAlpha = true;
            //用unsigned int 类型来处理m_pData,这样一次++就可以转到下一个像素点。
            unsigned int *tmp = (unsigned int *)m_pData;
            for(unsigned short i = 0; i < m_nHeight; i++)
            {
                for(unsigned int j = 0; j < rowbytes; j += 4)
                {
                	//将RGBA转换下重新放到原位置,宏定义在下面
                	//这里需要注意的是tmp的位置和row_pointers[i][j]其实是一样的。
                	//只不过是类型不同,tmp类型为unsigned int,++一次移动4个字节,即一个像素。
                	//row_pointers[i][j]为png_bytep(unsigned char *)型,++一次移动1个字节。因此row_pointers[i][j + 1]
                	//为row_pointers[i][j]向后移动一个字节。这里每4个字节构成一个完整的像素点。
                    *tmp++ = CC_RGB_PREMULTIPLY_ALPHA( row_pointers[i][j], row_pointers[i][j + 1], 
                                                      row_pointers[i][j + 2], row_pointers[i][j + 3] );
                }
            }
            
            m_bPreMulti = true;
        }

        CC_SAFE_FREE(row_pointers);

        bRet = true;
    } while (0);

    if (png_ptr)
    {
        png_destroy_read_struct(&png_ptr, (info_ptr) ? &info_ptr : 0, 0);
    }
    return bRet;
}

//非常重要的一个宏,通过这个宏我们可以知道rgba在内存中的分布。
// premultiply alpha, or the effect will wrong when want to use other pixel format in CCTexture2D,
// such as RGB888, RGB5A1
#define CC_RGB_PREMULTIPLY_ALPHA(vr, vg, vb, va) \
    (unsigned)(((unsigned)((unsigned char)(vr) * ((unsigned char)(va) + 1)) >> 8) | \
    ((unsigned)((unsigned char)(vg) * ((unsigned char)(va) + 1) >> 8) << 8) | \
    ((unsigned)((unsigned char)(vb) * ((unsigned char)(va) + 1) >> 8) << 16) | \
    ((unsigned)(unsigned char)(va) << 24))

// on ios, we should use platform/ios/CCImage_ios.mm instead

因此,根据最后的这个宏CC_RGB_PREMULTIPLY_ALPHA,我们就可以得到如下的函数,用来获取指定点的颜色:

获取特定点颜色的方法:

cocos2d::ccColor4B getImageColorOfPos(cocos2d::CCImage *pImage, cocos2d::CCPoint &pt)
{
	ccColor4B c = {0, 0, 0, 0};
	//cocos2dx使用的是openGL坐标系,y坐标是从下到上递增的。
	//libpng读取png图片时是从上到下逐行读取的,因此这里的纵坐标需要处理下。
	unsigned int x = pt.x, y = pImage->getHeight() - pt.y;
	int width = pImage->getWidth();

	unsigned char *data_=pImage->getData();
	unsigned int *pixel = (unsigned int *)data_;//以unsigned int来处理

	pixel = pixel + (y * width) + x;//跳到指定的像素点。
	c.a = (*pixel >> 24) & 0xff;
	
	float scale =  (c.a + 1.0f) / 256 ;
	c.r = *pixel & 0xff;
	c.r = c.r / scale ;
	c.g = (*pixel >> 8) & 0xff;
	c.g = c.g / scale ;
	c.b = (*pixel >> 16) & 0xff;
	c.b = c.b / scale ;

	return c;
}

//代码示例:
CCImage *pImage = new CCImage();
pImage->initWithImageFile("images.png",CCImage::kFmtPng);
ccColor4B colorOfPos = getImageColorOfPos(pImage, ccp(0,0));
pImage->release();

备注:

由于获取颜色的代码中有很多数学运算,比较复杂,这里可以简化一下,如果png上所有像素的透明度全部为255,那么根据(255 + 1)>>8 = 1可以得出:

c.r = *pixel & 0xff;
c.g = (*pixel >> 8) & 0xff;
c.b = (*pixel >> 16) & 0xff;
c.a = (*pixel >> 24) & 0xff;

这样可以简化掉相当多的运算。

后续参考加深资料:

http://www.cnblogs.com/xiaoxiaoboke/archive/2012/02/13/2349765.html 】
http://blog.csdn.net/honghaier/article/details/8032434 】

 

 

发表评论

14 + 20 =

此站点使用 Akismet 来减少垃圾评论。了解我们如何处理您的评论数据