From e3fdc76c5bf6b77ce53c8573f05f2d0ae8e33778 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 11 Apr 2014 21:30:19 -0400 Subject: [PATCH] Rewrote Area code on Windows to use alpha-premultiplied RGB and only use GDI functions to do it... and it doesn't quite work right yet. --- area.go | 14 ++- area_windows.go | 256 ++++++++++++++++++++++++++++++++++------------ common_windows.go | 1 + 3 files changed, 201 insertions(+), 70 deletions(-) diff --git a/area.go b/area.go index 579d484..2314837 100644 --- a/area.go +++ b/area.go @@ -7,6 +7,7 @@ import ( "sync" "image" "unsafe" + "reflect" ) // Area represents a blank canvas upon which programs may draw anything and receive arbitrary events from the user. @@ -339,10 +340,14 @@ func pixelData(img *image.RGBA) *uint8 { // some platforms require pixels in ARGB order in their native endianness (because they treat the pixel array as an array of uint32s) // this does the conversion -// s stores the slice used to avoid frequent memory allocations -func toARGB(i *image.RGBA, s *sysData) *byte { - // TODO actually store realBits in s - realbits := make([]byte, 4 * i.Rect.Dx() * i.Rect.Dy()) +// you need to convert somewhere; this memory is assumed to have a stride equal to the pixels per scanline (Windows gives us memory to use; other platforms we'll see) +func toARGB(i *image.RGBA, memory uintptr) { + var realbits []byte + + rbs := (*reflect.SliceHeader)(unsafe.Pointer(&realbits)) + rbs.Data = memory + rbs.Len = 4 * i.Rect.Dx() * i.Rect.Dy() + rbs.Cap = rbs.Len p := pixelDataPos(i) q := 0 for y := i.Rect.Min.Y; y < i.Rect.Max.Y; y++ { @@ -363,5 +368,4 @@ func toARGB(i *image.RGBA, s *sysData) *byte { } p = nextp } - return &realbits[0] } diff --git a/area_windows.go b/area_windows.go index f29c3a7..7cf1fb6 100644 --- a/area_windows.go +++ b/area_windows.go @@ -51,15 +51,18 @@ func getScrollPos(hwnd _HWND) (xpos int32, ypos int32) { } var ( - _getUpdateRect = user32.NewProc("GetUpdateRect") + _alphaBlend = msimg32.NewProc("AlphaBlend") _beginPaint = user32.NewProc("BeginPaint") + _bitBlt = gdi32.NewProc("BitBlt") + _createCompatibleBitmap = gdi32.NewProc("CreateCompatibleBitmap") + _createCompatibleDC = gdi32.NewProc("CreateCompatibleDC") + _createDIBSection = gdi32.NewProc("CreateDIBSection") + _deleteDC = gdi32.NewProc("DeleteDC") + _deleteObject = gdi32.NewProc("DeleteObject") _endPaint = user32.NewProc("EndPaint") _fillRect = user32.NewProc("FillRect") - _gdipCreateBitmapFromScan0 = gdiplus.NewProc("GdipCreateBitmapFromScan0") - _gdipCreateFromHDC = gdiplus.NewProc("GdipCreateFromHDC") - _gdipDrawImageI = gdiplus.NewProc("GdipDrawImageI") - _gdipDeleteGraphics = gdiplus.NewProc("GdipDeleteGraphics") - _gdipDisposeImage = gdiplus.NewProc("GdipDisposeImage") + _getUpdateRect = user32.NewProc("GetUpdateRect") + // _selectObject in prefsize_windows.go ) const ( @@ -71,11 +74,12 @@ const ( func paintArea(s *sysData) { const ( - // from gdipluspixelformats.h - _PixelFormatGDI = 0x00020000 - _PixelFormatAlpha = 0x00040000 - _PixelFormatCanonical = 0x00200000 - _PixelFormat32bppARGB = (10 | (32 << 8) | _PixelFormatAlpha | _PixelFormatGDI | _PixelFormatCanonical) + // from wingdi.h + _BI_RGB = 0 + _DIB_RGB_COLORS = 0 + _SRCCOPY = 0x00CC0020 + _AC_SRC_OVER = 0x00 + _AC_SRC_ALPHA = 0x01 ) var xrect _RECT @@ -107,73 +111,159 @@ func paintArea(s *sysData) { } hdc := _HANDLE(r1) - // Windows won't necessarily erase the update rect for us; we need to do so ourselves - // thanks to the people at http://stackoverflow.com/questions/23001890/winapi-getupdaterect-with-brepaint-true-inside-wm-paint-doesnt-clear-the-pai - // TODO this whole thing is inefficient, as explained in the page; we probably don't need _getUpdateRect - if ps.fErase != 0 { // if Windows didn't - r1, _, err := _fillRect.Call( - uintptr(hdc), - uintptr(unsafe.Pointer(&xrect)), - uintptr(areaBackgroundBrush)) - if r1 == 0 { // failure - panic(fmt.Errorf("error manually clearing Area background: %v", err)) - } + // very big thanks to Ninjifox for suggesting this technique and helping me go through it + + // first let's create the destination image, which we fill with the windows background color + // this is how we fake drawing the background; see also http://msdn.microsoft.com/en-us/library/ms969905.aspx + r1, _, err = _createCompatibleDC.Call(uintptr(hdc)) + if r1 == 0 { // failure + panic(fmt.Errorf("error creating off-screen rendering DC: %v", err)) + } + rdc := _HANDLE(r1) + r1, _, err = _createCompatibleBitmap.Call( + uintptr(rdc), + uintptr(xrect.Right - xrect.Left), + uintptr(xrect.Bottom - xrect.Top)) + if r1 == 0 { // failure + panic(fmt.Errorf("error creating off-screen rendering bitmap: %v", err)) + } + rbitmap := _HANDLE(r1) + r1, _, err = _selectObject.Call( + uintptr(rdc), + uintptr(rbitmap)) + if r1 == 0 { // failure + panic(fmt.Errorf("error connecting off-screen rendering bitmap to off-screen rendering DC: %v", err)) + } + prevrbitmap := _HANDLE(r1) + rrect := _RECT{ + Left: 0, + Right: xrect.Right - xrect.Left, + Top: 0, + Bottom: xrect.Bottom - xrect.Top, + } + r1, _, err = _fillRect.Call( + uintptr(rdc), + uintptr(unsafe.Pointer(&rrect)), + uintptr(areaBackgroundBrush)) + if r1 == 0 { // failure + panic(fmt.Errorf("error filling off-screen rendering bitmap with the system background color: %v", err)) } i := s.handler.Paint(cliprect) - // the pixels are arranged in RGBA order, but GDI+ requires BGRA - // we don't have a choice but to convert it ourselves - // TODO make realbits a part of sysData to conserve memory - realbits := make([]byte, 4 * i.Rect.Dx() * i.Rect.Dy()) - p := pixelDataPos(i) - q := 0 - for y := i.Rect.Min.Y; y < i.Rect.Max.Y; y++ { - nextp := p + i.Stride - for x := i.Rect.Min.X; x < i.Rect.Max.X; x++ { - realbits[q + 0] = byte(i.Pix[p + 2]) // B - realbits[q + 1] = byte(i.Pix[p + 1]) // G - realbits[q + 2] = byte(i.Pix[p + 0]) // R - realbits[q + 3] = byte(i.Pix[p + 3]) // A - p += 4 - q += 4 - } - p = nextp + // don't convert to BRGA just yet; see below + + // now we need to shove realbits into a bitmap + // technically bitmaps don't know about alpha; they just ignore the alpha byte + // AlphaBlend(), however, sees it - see http://msdn.microsoft.com/en-us/library/windows/desktop/dd183352%28v=vs.85%29.aspx + bi := _BITMAPINFO{} + bi.bmiHeader.biSize = uint32(unsafe.Sizeof(bi.bmiHeader)) + bi.bmiHeader.biWidth = int32(i.Rect.Dx()) + bi.bmiHeader.biHeight = -int32(i.Rect.Dy()) // negative height to force top-down drawing + bi.bmiHeader.biPlanes = 1 + bi.bmiHeader.biBitCount = 32 + bi.bmiHeader.biCompression = _BI_RGB + bi.bmiHeader.biSizeImage = uint32(i.Rect.Dx() * i.Rect.Dy() * 4) + // this is all we need, but because this confused me at first, I will say the two pixels-per-meter fields are unused (see http://blogs.msdn.com/b/oldnewthing/archive/2013/05/15/10418646.aspx and page 581 of Charles Petzold's Programming Windows, Fifth Edition) + ppvBits := uintptr(0) // now for the trouble: CreateDIBSection() allocates the memory for us... + r1, _, err = _createDIBSection.Call( + uintptr(0), // TODO is this safe? Ninjifox does it + uintptr(unsafe.Pointer(&bi)), + uintptr(_DIB_RGB_COLORS), + uintptr(unsafe.Pointer(&ppvBits)), + uintptr(0), // we're not dealing with hSection or dwOffset + uintptr(0)) + if r1 == 0 { // failure + panic(fmt.Errorf("error creating HBITMAP for image returned by AreaHandler.Paint(): %v", err)) } + ibitmap := _HANDLE(r1) - var bitmap, graphics uintptr + // now we have to do TWO MORE things before we can finally do alpha blending + // first, we need to load the bitmap memory, because Windows makes it for us + // the pixels are arranged in RGBA order, but GDI+ requires BGRA + // this turns out to be just ARGB in little endian; let's convert into this memory + toARGB(i, ppvBits) - r1, _, err = _gdipCreateBitmapFromScan0.Call( + // the second thing is... make a device context for the bitmap :| + // Ninjifox just makes another compatible DC; we'll do the same + r1, _, err = _createCompatibleDC.Call(uintptr(hdc)) + if r1 == 0 { // failure + panic(fmt.Errorf("error creating HDC for image returned by AreaHandler.Paint(): %v", err)) + } + idc := _HANDLE(r1) + r1, _, err = _selectObject.Call( + uintptr(idc), + uintptr(ibitmap)) + if r1 == 0 { // failure + panic(fmt.Errorf("error connecting HBITMAP for image returned by AreaHandler.Paint() to its HDC: %v", err)) + } + previbitmap := _HANDLE(r1) + + // AND FINALLY WE CAN DO THE ALPHA BLENDING!!!!!!111 +/* blendfunc := _BLENDFUNCTION{ + BlendOp: _AC_SRC_OVER, + BlendFlags: 0, + SourceConstantAlpha: 255, // only use per-pixel alphas + AlphaFormat: _AC_SRC_ALPHA, // premultiplied + } + r1, _, err = _alphaBlend.Call( + uintptr(rdc), // destination + uintptr(0), // origin and size + uintptr(0), uintptr(i.Rect.Dx()), uintptr(i.Rect.Dy()), - uintptr(i.Rect.Dx() * 4), // got rid of extra stride - uintptr(_PixelFormat32bppARGB), - uintptr(unsafe.Pointer(&realbits[0])), - uintptr(unsafe.Pointer(&bitmap))) - if r1 != 0 { // failure - panic(fmt.Errorf("error creating GDI+ bitmap to blit (GDI+ error code %d; Windows last error %v)", r1, err)) + uintptr(idc), // source image + uintptr(0), + uintptr(0), + uintptr(i.Rect.Dx()), + uintptr(i.Rect.Dy()), + blendfunc.arg()) + if r1 == _FALSE { // failure + panic(fmt.Errorf("error alpha-blending image returned by AreaHandler.Paint() onto background: %v", err)) } - r1, _, err = _gdipCreateFromHDC.Call( +*/ + // and finally we can just blit that into the window + r1, _, err = _bitBlt.Call( uintptr(hdc), - uintptr(unsafe.Pointer(&graphics))) - if r1 != 0 { // failure - panic(fmt.Errorf("error creating GDI+ graphics context to blit to (GDI+ error code %d; Windows last error %v)", r1, err)) + uintptr(xrect.Left), + uintptr(xrect.Top), + uintptr(xrect.Right - xrect.Left), + uintptr(xrect.Bottom - xrect.Top), + uintptr(rdc), + uintptr(0), // from the rdc's origin + uintptr(0), + uintptr(_SRCCOPY)) + if r1 == 0 { // failure + panic(fmt.Errorf("error blitting Area image to Area: %v", err)) } - r1, _, err = _gdipDrawImageI.Call( - graphics, - bitmap, - uintptr(xrect.Left), // cliprect is adjusted; use original - uintptr(xrect.Top)) - if r1 != 0 { // failure - panic(fmt.Errorf("error blitting GDI+ bitmap (GDI+ error code %d; Windows last error %v)", r1, err)) + + // now to clean up + r1, _, err = _selectObject.Call( + uintptr(idc), + uintptr(previbitmap)) + if r1 == 0 { // failure + panic(fmt.Errorf("error reverting HDC for image returned by AreaHandler.Paint() to original HBITMAP: %v", err)) } - r1, _, err = _gdipDeleteGraphics.Call(graphics) - if r1 != 0 { // failure - panic(fmt.Errorf("error freeing GDI+ graphics context to blit to (GDI+ error code %d; Windows last error %v)", r1, err)) + r1, _, err = _selectObject.Call( + uintptr(rdc), + uintptr(prevrbitmap)) + if r1 == 0 { // failure + panic(fmt.Errorf("error reverting HDC for off-screen rendering to original HBITMAP: %v", err)) } - // TODO this is the destructor of Image (Bitmap's base class); I don't see a specific destructor for Bitmap itself so - r1, _, err = _gdipDisposeImage.Call(bitmap) - if r1 != 0 { // failure - panic(fmt.Errorf("error freeing GDI+ bitmap to blit (GDI+ error code %d; Windows last error %v)", r1, err)) + r1, _, err = _deleteObject.Call(uintptr(ibitmap)) + if r1 == 0 { // failure + panic(fmt.Errorf("error deleting HBITMAP for image returned by AreaHandler.Paint(): %v", err)) + } + r1, _, err = _deleteObject.Call(uintptr(rbitmap)) + if r1 == 0 { // failure + panic(fmt.Errorf("error deleting HBITMAP for off-screen rendering: %v", err)) + } + r1, _, err = _deleteDC.Call(uintptr(idc)) + if r1 == 0 { // failure + panic(fmt.Errorf("error deleting HDC for image returned by AreaHandler.Paint(): %v", err)) + } + r1, _, err = _deleteDC.Call(uintptr(rdc)) + if r1 == 0 { // failure + panic(fmt.Errorf("error deleting HDC for off-screen rendering: %v", err)) } // return value always nonzero according to MSDN @@ -631,6 +721,42 @@ func registerAreaWndClass(s *sysData) (newClassName string, err error) { return newClassName, nil } +type _BITMAPINFO struct { + bmiHeader _BITMAPINFOHEADER + bmiColors [32]uintptr // we don't use it; make it an arbitrary number that wouldn't cause issues +} + +type _BITMAPINFOHEADER struct { + biSize uint32 + biWidth int32 + biHeight int32 + biPlanes uint16 + biBitCount uint16 + biCompression uint32 + biSizeImage uint32 + biXPelsPerMeter int32 + biYPelsPerMeter int32 + biClrUsed uint32 + biClrImportant uint32 +} + +type _BLENDFUNCTION struct { + BlendOp byte + BlendFlags byte + SourceConstantAlpha byte + AlphaFormat byte +} + +// AlphaBlend() takes a BLENDFUNCTION value +func (b _BLENDFUNCTION) arg() (x uintptr) { + // little endian + x = uintptr(b.AlphaFormat) << 24 + x |= uintptr(b.SourceConstantAlpha) << 16 + x |= uintptr(b.BlendFlags) << 8 + x |= uintptr(b.BlendOp) + return x +} + type _PAINTSTRUCT struct { hdc _HANDLE fErase int32 // originally BOOL diff --git a/common_windows.go b/common_windows.go index 522b65a..0c9cf39 100644 --- a/common_windows.go +++ b/common_windows.go @@ -13,6 +13,7 @@ var ( gdi32 = syscall.NewLazyDLL("gdi32.dll") comctl32 *syscall.LazyDLL // comctl32 not defined here; see comctl_windows.go gdiplus = syscall.NewLazyDLL("gdiplus.dll") + msimg32 = syscall.NewLazyDLL("msimg32.dll") ) type _HANDLE uintptr