SetDIBitsToDevice 真虐心

最近因为使用了Google的skia 2D图形库: https://code.google.com/p/skia/ 由于skia本身是只是一个图形库, 没有直接对设备绘制的能力 (除views库), 而在不同平台上这些API都是不同的. 在参考skia的views库中,其中包含了SkView的类,为跨平台的窗口类, 这里当然有在windows上的设备绘制能力. 但是由于各种原因,本人不想使用SkView作为基础窗口类, 只用SkBitmap做为内存画布, 故找到了在windows对设备绘制的API: 在SkOSWindow::doPaint方法中, 可以看到是SetDIBitsToDevice函数对设备进行绘制. 但是由于在doPaint中是对SkBitmap全部数据进行绘制,而我自然是希望可以绘制部分, 于是查阅了MSDN: http://msdn.microsoft.com/zh-cn/library/windows/desktop/dd162974(v=vs.85).aspx 但是其中的YSrc参数一直很让我很苦恼, MSDN对其的描述为: The y-coordinate, in logical units, of the lower-left corner of the image. 左下角 (lower-left corner), 这是什么坑爹的概念, 计算机里一般不都是左上角, 方向为向右和向下么! (擦, 写到这里浏览器哭脸了! 好在WordPress有保存草稿的功能) 但是暂时不管这些,很快在使用的过程中发现, 凡是指定了源高度(ySrc)绘制的图像都对不上位置, 图像源高度位置变成了 (bmpheight - ysrc - drawheight) 这个位置, 而且由于SkBitmap内图片数据储存方式和windows默认的规则(下到上)相反, 而是(上到下). 网上查的很多资料看起来都不怎么适用, 甚至没有讲明白到底是怎么绘制的. 搜了好久后, 发现这样的一篇文章, 很详细的描述了SetDIBitsToDevice的绘制机制: http://jixiang1119.blog.163.com/blog/static/2827097320108342559871/ 多亏了这篇文章, 本人明白了主要控制绘制源高度的参数其实是 lpvBits, cScanLines, 即在lpvBits上加上源高度乘以每行字节数(ysrc * rowBytes), cScanLines则设置为绘制高度, 如果cScanLines设置的扫描行数超出了源图片范围也会出问题, 本人虽然也尝试过这种方式, 但就是因为没管cScanLines参数, 导致失败. 顺便贴出skia指定区域绘图到HDC的代码吧 (大部分代码来自SkOSWindow::doPaint): [code lang="cpp"] void SkiaPaint(void* ctx, int x, int y, int cx, int cy, const SkBitmap& bitmap, int srcx, int srcy) { //this->update(NULL); //if (kNone_BackEndType == fAttached) { HDC hdc = (HDC)ctx; BITMAPINFO bmi; memset(&bmi, 0, sizeof(bmi)); bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = bitmap.width(); bmi.bmiHeader.biHeight = -bitmap.height(); // top-down image bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biSizeImage = 0; // // Do the SetDIBitsToDevice. // // TODO(wjmaclean): // Fix this call to handle SkBitmaps that have rowBytes != width, // i.e. may have padding at the end of lines. The SkASSERT below // may be ignored by builds, and the only obviously safe option // seems to be to copy the bitmap to a temporary (contiguous) // buffer before passing to SetDIBitsToDevice(). SkASSERT(bitmap.width() * bitmap.bytesPerPixel() == bitmap.rowBytes()); bitmap.lockPixels(); int iRet = SetDIBitsToDevice(hdc, x, y, cx, cy, srcx, 0, 0, cy, (byte *)bitmap.getPixels() + bitmap.rowBytes() * srcy, &bmi, DIB_RGB_COLORS); bitmap.unlockPixels(); } } [/code] 真心虐心啊!