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.
This commit is contained in:
parent
57088b9787
commit
e3fdc76c5b
14
area.go
14
area.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"image"
|
"image"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Area represents a blank canvas upon which programs may draw anything and receive arbitrary events from the user.
|
// 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)
|
// 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
|
// this does the conversion
|
||||||
// s stores the slice used to avoid frequent memory allocations
|
// 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, s *sysData) *byte {
|
func toARGB(i *image.RGBA, memory uintptr) {
|
||||||
// TODO actually store realBits in s
|
var realbits []byte
|
||||||
realbits := make([]byte, 4 * i.Rect.Dx() * i.Rect.Dy())
|
|
||||||
|
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)
|
p := pixelDataPos(i)
|
||||||
q := 0
|
q := 0
|
||||||
for y := i.Rect.Min.Y; y < i.Rect.Max.Y; y++ {
|
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
|
p = nextp
|
||||||
}
|
}
|
||||||
return &realbits[0]
|
|
||||||
}
|
}
|
||||||
|
|
256
area_windows.go
256
area_windows.go
|
@ -51,15 +51,18 @@ func getScrollPos(hwnd _HWND) (xpos int32, ypos int32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_getUpdateRect = user32.NewProc("GetUpdateRect")
|
_alphaBlend = msimg32.NewProc("AlphaBlend")
|
||||||
_beginPaint = user32.NewProc("BeginPaint")
|
_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")
|
_endPaint = user32.NewProc("EndPaint")
|
||||||
_fillRect = user32.NewProc("FillRect")
|
_fillRect = user32.NewProc("FillRect")
|
||||||
_gdipCreateBitmapFromScan0 = gdiplus.NewProc("GdipCreateBitmapFromScan0")
|
_getUpdateRect = user32.NewProc("GetUpdateRect")
|
||||||
_gdipCreateFromHDC = gdiplus.NewProc("GdipCreateFromHDC")
|
// _selectObject in prefsize_windows.go
|
||||||
_gdipDrawImageI = gdiplus.NewProc("GdipDrawImageI")
|
|
||||||
_gdipDeleteGraphics = gdiplus.NewProc("GdipDeleteGraphics")
|
|
||||||
_gdipDisposeImage = gdiplus.NewProc("GdipDisposeImage")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -71,11 +74,12 @@ const (
|
||||||
|
|
||||||
func paintArea(s *sysData) {
|
func paintArea(s *sysData) {
|
||||||
const (
|
const (
|
||||||
// from gdipluspixelformats.h
|
// from wingdi.h
|
||||||
_PixelFormatGDI = 0x00020000
|
_BI_RGB = 0
|
||||||
_PixelFormatAlpha = 0x00040000
|
_DIB_RGB_COLORS = 0
|
||||||
_PixelFormatCanonical = 0x00200000
|
_SRCCOPY = 0x00CC0020
|
||||||
_PixelFormat32bppARGB = (10 | (32 << 8) | _PixelFormatAlpha | _PixelFormatGDI | _PixelFormatCanonical)
|
_AC_SRC_OVER = 0x00
|
||||||
|
_AC_SRC_ALPHA = 0x01
|
||||||
)
|
)
|
||||||
|
|
||||||
var xrect _RECT
|
var xrect _RECT
|
||||||
|
@ -107,73 +111,159 @@ func paintArea(s *sysData) {
|
||||||
}
|
}
|
||||||
hdc := _HANDLE(r1)
|
hdc := _HANDLE(r1)
|
||||||
|
|
||||||
// Windows won't necessarily erase the update rect for us; we need to do so ourselves
|
// very big thanks to Ninjifox for suggesting this technique and helping me go through it
|
||||||
// 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
|
// first let's create the destination image, which we fill with the windows background color
|
||||||
if ps.fErase != 0 { // if Windows didn't
|
// this is how we fake drawing the background; see also http://msdn.microsoft.com/en-us/library/ms969905.aspx
|
||||||
r1, _, err := _fillRect.Call(
|
r1, _, err = _createCompatibleDC.Call(uintptr(hdc))
|
||||||
uintptr(hdc),
|
if r1 == 0 { // failure
|
||||||
uintptr(unsafe.Pointer(&xrect)),
|
panic(fmt.Errorf("error creating off-screen rendering DC: %v", err))
|
||||||
uintptr(areaBackgroundBrush))
|
}
|
||||||
if r1 == 0 { // failure
|
rdc := _HANDLE(r1)
|
||||||
panic(fmt.Errorf("error manually clearing Area background: %v", err))
|
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)
|
i := s.handler.Paint(cliprect)
|
||||||
// the pixels are arranged in RGBA order, but GDI+ requires BGRA
|
// don't convert to BRGA just yet; see below
|
||||||
// we don't have a choice but to convert it ourselves
|
|
||||||
// TODO make realbits a part of sysData to conserve memory
|
// now we need to shove realbits into a bitmap
|
||||||
realbits := make([]byte, 4 * i.Rect.Dx() * i.Rect.Dy())
|
// technically bitmaps don't know about alpha; they just ignore the alpha byte
|
||||||
p := pixelDataPos(i)
|
// AlphaBlend(), however, sees it - see http://msdn.microsoft.com/en-us/library/windows/desktop/dd183352%28v=vs.85%29.aspx
|
||||||
q := 0
|
bi := _BITMAPINFO{}
|
||||||
for y := i.Rect.Min.Y; y < i.Rect.Max.Y; y++ {
|
bi.bmiHeader.biSize = uint32(unsafe.Sizeof(bi.bmiHeader))
|
||||||
nextp := p + i.Stride
|
bi.bmiHeader.biWidth = int32(i.Rect.Dx())
|
||||||
for x := i.Rect.Min.X; x < i.Rect.Max.X; x++ {
|
bi.bmiHeader.biHeight = -int32(i.Rect.Dy()) // negative height to force top-down drawing
|
||||||
realbits[q + 0] = byte(i.Pix[p + 2]) // B
|
bi.bmiHeader.biPlanes = 1
|
||||||
realbits[q + 1] = byte(i.Pix[p + 1]) // G
|
bi.bmiHeader.biBitCount = 32
|
||||||
realbits[q + 2] = byte(i.Pix[p + 0]) // R
|
bi.bmiHeader.biCompression = _BI_RGB
|
||||||
realbits[q + 3] = byte(i.Pix[p + 3]) // A
|
bi.bmiHeader.biSizeImage = uint32(i.Rect.Dx() * i.Rect.Dy() * 4)
|
||||||
p += 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)
|
||||||
q += 4
|
ppvBits := uintptr(0) // now for the trouble: CreateDIBSection() allocates the memory for us...
|
||||||
}
|
r1, _, err = _createDIBSection.Call(
|
||||||
p = nextp
|
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.Dx()),
|
||||||
uintptr(i.Rect.Dy()),
|
uintptr(i.Rect.Dy()),
|
||||||
uintptr(i.Rect.Dx() * 4), // got rid of extra stride
|
uintptr(idc), // source image
|
||||||
uintptr(_PixelFormat32bppARGB),
|
uintptr(0),
|
||||||
uintptr(unsafe.Pointer(&realbits[0])),
|
uintptr(0),
|
||||||
uintptr(unsafe.Pointer(&bitmap)))
|
uintptr(i.Rect.Dx()),
|
||||||
if r1 != 0 { // failure
|
uintptr(i.Rect.Dy()),
|
||||||
panic(fmt.Errorf("error creating GDI+ bitmap to blit (GDI+ error code %d; Windows last error %v)", r1, err))
|
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(hdc),
|
||||||
uintptr(unsafe.Pointer(&graphics)))
|
uintptr(xrect.Left),
|
||||||
if r1 != 0 { // failure
|
uintptr(xrect.Top),
|
||||||
panic(fmt.Errorf("error creating GDI+ graphics context to blit to (GDI+ error code %d; Windows last error %v)", r1, err))
|
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,
|
// now to clean up
|
||||||
bitmap,
|
r1, _, err = _selectObject.Call(
|
||||||
uintptr(xrect.Left), // cliprect is adjusted; use original
|
uintptr(idc),
|
||||||
uintptr(xrect.Top))
|
uintptr(previbitmap))
|
||||||
if r1 != 0 { // failure
|
if r1 == 0 { // failure
|
||||||
panic(fmt.Errorf("error blitting GDI+ bitmap (GDI+ error code %d; Windows last error %v)", r1, err))
|
panic(fmt.Errorf("error reverting HDC for image returned by AreaHandler.Paint() to original HBITMAP: %v", err))
|
||||||
}
|
}
|
||||||
r1, _, err = _gdipDeleteGraphics.Call(graphics)
|
r1, _, err = _selectObject.Call(
|
||||||
if r1 != 0 { // failure
|
uintptr(rdc),
|
||||||
panic(fmt.Errorf("error freeing GDI+ graphics context to blit to (GDI+ error code %d; Windows last error %v)", r1, err))
|
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 = _deleteObject.Call(uintptr(ibitmap))
|
||||||
r1, _, err = _gdipDisposeImage.Call(bitmap)
|
if r1 == 0 { // failure
|
||||||
if r1 != 0 { // failure
|
panic(fmt.Errorf("error deleting HBITMAP for image returned by AreaHandler.Paint(): %v", err))
|
||||||
panic(fmt.Errorf("error freeing GDI+ bitmap to blit (GDI+ error code %d; Windows last error %v)", r1, 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
|
// return value always nonzero according to MSDN
|
||||||
|
@ -631,6 +721,42 @@ func registerAreaWndClass(s *sysData) (newClassName string, err error) {
|
||||||
return newClassName, nil
|
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 {
|
type _PAINTSTRUCT struct {
|
||||||
hdc _HANDLE
|
hdc _HANDLE
|
||||||
fErase int32 // originally BOOL
|
fErase int32 // originally BOOL
|
||||||
|
|
|
@ -13,6 +13,7 @@ var (
|
||||||
gdi32 = syscall.NewLazyDLL("gdi32.dll")
|
gdi32 = syscall.NewLazyDLL("gdi32.dll")
|
||||||
comctl32 *syscall.LazyDLL // comctl32 not defined here; see comctl_windows.go
|
comctl32 *syscall.LazyDLL // comctl32 not defined here; see comctl_windows.go
|
||||||
gdiplus = syscall.NewLazyDLL("gdiplus.dll")
|
gdiplus = syscall.NewLazyDLL("gdiplus.dll")
|
||||||
|
msimg32 = syscall.NewLazyDLL("msimg32.dll")
|
||||||
)
|
)
|
||||||
|
|
||||||
type _HANDLE uintptr
|
type _HANDLE uintptr
|
||||||
|
|
Loading…
Reference in New Issue