diff --git a/area_windows.go b/area_windows.go index affdd9c..d83cc0c 100644 --- a/area_windows.go +++ b/area_windows.go @@ -7,6 +7,7 @@ import ( "syscall" "unsafe" "sync" + "image" ) const ( @@ -23,9 +24,126 @@ var ( areaWndClassNumLock sync.Mutex ) +var ( + _getUpdateRect = user32.NewProc("GetUpdateRect") + _beginPaint = user32.NewProc("BeginPaint") + _endPaint = user32.NewProc("EndPaint") + _gdipCreateBitmapFromScan0 = gdiplus.NewProc("GdipCreateBitmapFromScan0") + _gdipCreateFromHDC = gdiplus.NewProc("GdipCreateFromHDC") + _gdipDrawImageI = gdiplus.NewProc("GdipDrawImageI") + _gdipDeleteGraphics = gdiplus.NewProc("GdipDeleteGraphics") + _gdipDisposeImage = gdiplus.NewProc("GdipDisposeImage") +) + +const ( + // from winuser.h + _WM_PAINT = 0x000F +) + +func paintArea(s *sysData) { + const ( + // from gdipluspixelformats.h + _PixelFormatGDI = 0x00020000 + _PixelFormatAlpha = 0x00040000 + _PixelFormatCanonical = 0x00200000 + _PixelFormat32bppARGB = (10 | (32 << 8) | _PixelFormatAlpha | _PixelFormatGDI | _PixelFormatCanonical) + ) + + var xrect _RECT + var ps _PAINTSTRUCT + + // TODO send _TRUE if we want to erase the clip area + r1, _, _ := _getUpdateRect.Call( + uintptr(s.hwnd), + uintptr(unsafe.Pointer(&xrect)), + uintptr(_FALSE)) + if r1 == 0 { // no update rect; do nothing + return + } + + cliprect := image.Rect(int(xrect.Left), int(xrect.Top), int(xrect.Right), int(xrect.Bottom)) + // TODO offset cliprect by scroll position + // make sure the cliprect doesn't fall outside the size of the Area + cliprect = cliprect.Intersect(image.Rect(0, 0, 320, 240)) // TODO change when adding resizing + if cliprect.Empty() { // still no update rect + return + } + + r1, _, err := _beginPaint.Call( + uintptr(s.hwnd), + uintptr(unsafe.Pointer(&ps))) + if r1 == 0 { // failure + panic(fmt.Errorf("error beginning Area repaint: %v", err)) + } + hdc := _HANDLE(r1) + + 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 := 0 + 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 + } + + var bitmap, graphics uintptr + + r1, _, err = _gdipCreateBitmapFromScan0.Call( + 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)) + } + r1, _, err = _gdipCreateFromHDC.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)) + } + 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)) + } + 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 = _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)) + } + + // return value always nonzero according to MSDN + _endPaint.Call( + uintptr(s.hwnd), + uintptr(unsafe.Pointer(&ps))) +} + func areaWndProc(s *sysData) func(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT { return func(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT { switch uMsg { + case _WM_PAINT: + paintArea(s) + return _LRESULT(0) default: r1, _, _ := defWindowProc.Call( uintptr(hwnd), @@ -72,3 +190,12 @@ func registerAreaWndClass(s *sysData) (newClassName string, err error) { } return newClassName, nil } + +type _PAINTSTRUCT struct { + hdc _HANDLE + fErase int32 // originally BOOL + rcPaint _RECT + fRestore int32 // originally BOOL + fIncUpdate int32 // originally BOOL + rgbReserved [32]byte +}