Exemple #1
0
//downsamples a source image as close as possible to a desired size, while also
//maintaining a linear downsampling ratio.
func downsample(img *image.RGBA, size image.Rectangle) *image.RGBA {

	xratio := int(img.Bounds().Max.X / size.Max.X)
	yratio := int(img.Bounds().Max.Y / size.Max.Y)

	minratio := xratio

	if yratio < xratio {
		minratio = yratio
	}

	xoffset := int((img.Bounds().Max.X - size.Max.X*minratio) / 2)
	yoffset := int((img.Bounds().Max.Y - size.Max.Y*minratio) / 2)

	out := image.NewRGBA(size)
	pixels := out.Pix

	for i := 0; i < size.Max.X; i++ {
		for j := 0; j < size.Max.Y; j++ {
			offset := 4 * (j*size.Max.X + i)
			r := image.Rect(i*minratio+xoffset, j*minratio+yoffset, (i+1)*minratio+xoffset, (j+1)*minratio+yoffset)

			c := averageColor(img, r)

			pixels[offset] = c.R
			pixels[offset+1] = c.G
			pixels[offset+2] = c.B
			pixels[offset+3] = 255
		}
	}
	return out
}
Exemple #2
0
func (sgImage *SgImage) writeTransparentImage(img *image.RGBA, buffer []byte, length int) {
	width := img.Bounds().Dx()

	var i, x, y int

	for i < length {
		c := int(buffer[i])
		i++
		if c == 255 {
			// The next byte is the number of pixels to skip
			x += int(buffer[i])
			i++
			for x >= width {
				y++
				x -= width
			}
		} else {
			// 'c' is the number of image data bytes
			for j := 0; j < c; j++ {
				sgImage.set555Pixel(img, x, y, uint16(buffer[i+1]<<8)|uint16(buffer[i]))
				x++
				if x >= width {
					y++
					x = 0
				}
				i += 2
			}
		}
	}
}
Exemple #3
0
func tileProcessor(
	subsampleF int,
	img *image.RGBA,
	in <-chan [2]int,
	wg *sync.WaitGroup, threadNum int,
	algo algos.AlgoFunc,
) {

	fmt.Fprintf(os.Stderr, "goroutine %d starting\n", threadNum)

	imgBounds := img.Bounds()
	yDelta := (constants.YMax - constants.YMin) / float64(imgBounds.Max.Y)
	xDelta := (constants.XMax - constants.XMin) / float64(imgBounds.Max.X)

	for d := range in {
		//fmt.Fprintf(os.Stderr, "Processing tile ((`%d`,`%d`),(`%d`,`%d`))\n",
		//py, px, py+constants.TileSize, px+constants.TileSize)

		//py, px := d[0], d[1]
		calculateTile(yDelta, xDelta, d[0], d[1], subsampleF, img, algo)
	}

	fmt.Fprintf(os.Stderr, "goroutine %d terminating\n", threadNum)
	wg.Done()
}
Exemple #4
0
func gaussianBlur(dst, src *image.RGBA, radius int) {
	boxes := determineBoxes(float64(radius), 3)
	tmp := image.NewRGBA(dst.Bounds())
	boxBlur3(dst, tmp, src, (boxes[0]-1)/2)
	boxBlur3(dst, tmp, dst, (boxes[1]-1)/2)
	boxBlur3(dst, tmp, dst, (boxes[2]-1)/2)
}
Exemple #5
0
func (sgImage *SgImage) loadAlphaMask(img *image.RGBA, buffer []byte) {
	width := img.Bounds().Dx()
	length := int(sgImage.workRecord.AlphaLength)
	var i, x, y int

	for i < length {
		c := int(buffer[i])
		i++
		if c == 255 {
			// The next byte is the number of pixels to skip
			x += int(buffer[i])
			i++
			for x >= width {
				y++
				x -= width
			}
		} else {
			// 'c' is the number of image data bytes
			for j := 0; j < c; j++ {
				sgImage.setAlphaPixel(img, x, y, buffer[i])
				x++
				if x >= width {
					y++
					x = 0
				}
				i += 2
			}
		}
	}
}
Exemple #6
0
// 将图片绘制到图片
func ImageDrawRGBA(img *image.RGBA, imgcode image.Image, x, y int) {
	// 绘制图像
	// image.Point A点的X,Y坐标,轴向右和向下增加{0,0}
	// image.ZP ZP is the zero Point
	// image.Pt Pt is shorthand for Point{X, Y}
	draw.Draw(img, img.Bounds(), imgcode, image.Pt(x, y), draw.Over)
}
Exemple #7
0
func (sgImage *SgImage) writeIsometricBase(img *image.RGBA, buffer []byte) error {
	width := img.Bounds().Dx()
	height := (width + 2) / 2 /* 58 -> 30, 118 -> 60, etc */
	heightOffset := img.Bounds().Dy() - height
	var size int
	size = int(sgImage.workRecord.Flags[3])
	yOffset := heightOffset
	var xOffset, tileBytes, tileHeight, tileWidth int

	if size == 0 {
		/* Derive the tile size from the height (more regular than width)
		 * Note that this causes a problem with 4x4 regular vs 3x3 large:
		 * 4 * 30 = 120; 3 * 40 = 120 -- give precedence to regular */
		if height%ISOMETRIC_TILE_HEIGHT == 0 {
			size = height / ISOMETRIC_TILE_HEIGHT
		} else if height%ISOMETRIC_LARGE_TILE_HEIGHT == 0 {
			size = height / ISOMETRIC_LARGE_TILE_HEIGHT
		}
	}

	// Determine whether we should use the regular or large (emperor) tiles
	if ISOMETRIC_TILE_HEIGHT*size == height {
		// Regular tile
		tileBytes = ISOMETRIC_TILE_BYTES
		tileHeight = ISOMETRIC_TILE_HEIGHT
		tileWidth = ISOMETRIC_TILE_WIDTH
	} else if ISOMETRIC_LARGE_TILE_HEIGHT*size == height {
		// Large (emperor) tile
		tileBytes = ISOMETRIC_LARGE_TILE_BYTES
		tileHeight = ISOMETRIC_LARGE_TILE_HEIGHT
		tileWidth = ISOMETRIC_LARGE_TILE_WIDTH
	} else {
		return fmt.Errorf("Unknown tile size: %d (height %d, width %d, size %d)", 2*height/size, height, width, size)
	}

	// Check if buffer length is enough: (width + 2) * height / 2 * 2bpp
	if (width+2)*height != int(sgImage.workRecord.UncompressedLength) {
		return fmt.Errorf("Data length doesn't match footprint size: %d vs %d (%d) %d", (width+2)*height, sgImage.workRecord.UncompressedLength, sgImage.workRecord.Length, sgImage.workRecord.InvertOffset)
	}

	i := 0
	for y := 0; y < (size + (size - 1)); y++ {
		var xRange int
		if y < size {
			xOffset = size - y - 1
			xRange = y + 1
		} else {
			xOffset = y - size + 1
			xRange = 2*size - y - 1
		}
		xOffset *= tileHeight
		for x := 0; x < xRange; x++ {
			sgImage.writeIsometricTile(img, buffer[i*tileBytes:], xOffset, yOffset, tileWidth, tileHeight)
			xOffset += tileWidth + 2
			i++
		}
		yOffset += tileHeight / 2
	}
	return nil
}
Exemple #8
0
// loadFont loads the given font data. This does not deal with font scaling.
// Scaling should be handled by the independent Bitmap/Truetype loaders.
// We therefore expect the supplied image and charset to already be adjusted
// to the correct font scale.
//
// The image should hold a sprite sheet, defining the graphical layout for
// every glyph. The config describes font metadata.
func loadFont(img *image.RGBA, config *FontConfig) (f *Font, err error) {
	f = new(Font)
	f.Config = config

	// Resize image to next power-of-two.
	img = glh.Pow2Image(img).(*image.RGBA)
	ib := img.Bounds()

	f.Width = ib.Dx()
	f.Height = ib.Dy()

	// Create the texture itself. It will contain all glyphs.
	// Individual glyph-quads display a subset of this texture.
	f.Texture = gl.GenTexture()
	f.Texture.Bind(gl.TEXTURE_2D)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, ib.Dx(), ib.Dy(), 0,
		gl.RGBA, gl.UNSIGNED_BYTE, img.Pix)

	// file, err := os.Create("font.png")
	// if err != nil {
	// 	log.Fatal(err)
	// }

	// err = png.Encode(file, img)
	// if err != nil {
	// 	log.Fatal(err)
	// }

	return
}
Exemple #9
0
func drawGradient(m *image.RGBA) {
	b := m.Bounds()
	for y := b.Min.Y; y < b.Max.Y; y++ {
		for x := b.Min.X; x < b.Max.X; x++ {
			if x%64 == 0 || y%64 == 0 {
				m.SetRGBA(x, y, color.RGBA{0xff, 0xff, 0xff, 0xff})
			} else if x%64 == 63 || y%64 == 63 {
				m.SetRGBA(x, y, color.RGBA{0x00, 0x00, 0xff, 0xff})
			} else {
				m.SetRGBA(x, y, color.RGBA{uint8(x), uint8(y), 0x00, 0xff})
			}
		}
	}

	// Round off the corners.
	const radius = 64
	lox := b.Min.X + radius - 1
	loy := b.Min.Y + radius - 1
	hix := b.Max.X - radius
	hiy := b.Max.Y - radius
	for y := 0; y < radius; y++ {
		for x := 0; x < radius; x++ {
			if x*x+y*y <= radius*radius {
				continue
			}
			m.SetRGBA(lox-x, loy-y, color.RGBA{})
			m.SetRGBA(hix+x, loy-y, color.RGBA{})
			m.SetRGBA(lox-x, hiy+y, color.RGBA{})
			m.SetRGBA(hix+x, hiy+y, color.RGBA{})
		}
	}
}
Exemple #10
0
//func loadSize(ctxt *fs.Context, name string, max int) *image.RGBA
func loadSize(name string, max int) *image.RGBA {
	//data, _, err := ctxt.Read("qr/upload/" + name + ".png")
	f1, err := os.Open(name + ".png")
	fmt.Println(name + ".png")
	if err != nil {
		panic(err)
	}
	i, err := png.Decode(f1)
	if err != nil {
		panic(err)
	}
	b := i.Bounds()
	fmt.Printf("%v, %v,max%v", b.Dx(), b.Dy(), max)
	dx, dy := max, max
	if b.Dx() > b.Dy() {
		dy = b.Dy() * dx / b.Dx()
	} else {
		dx = b.Dx() * dy / b.Dy()
	}
	fmt.Printf("%v, %v,", dx, dy)
	var irgba *image.RGBA
	switch i := i.(type) {
	case *image.RGBA:
		irgba = resize.ResizeRGBA(i, i.Bounds(), dx, dy)
	case *image.NRGBA:
		irgba = resize.ResizeNRGBA(i, i.Bounds(), dx, dy)
	default:
		fmt.Println("default")

	}
	fmt.Println("prereturnload")
	fmt.Printf("%v, %v,", irgba.Bounds().Dx(), irgba.Bounds().Dy())
	return irgba
}
Exemple #11
0
func (t *Texture) FromImageRGBA(rgba *image.RGBA, level int) {
	With(t, func() {
		gl.TexImage2D(gl.TEXTURE_2D, level, gl.RGBA,
			rgba.Bounds().Dx(), rgba.Bounds().Dy(),
			0, gl.RGBA, gl.UNSIGNED_BYTE, rgba.Pix)
	})
}
Exemple #12
0
//accepts a source image and a sub-rectangle, and returns the average of
//the rgb colors within this rectangle
func averageColor(img *image.RGBA, rect image.Rectangle) color.RGBA {

	var rSum float64
	var gSum float64
	var bSum float64
	var count float64

	pixels := img.Pix

	stride := img.Bounds().Max.X

	for i := rect.Min.X; i < rect.Max.X; i++ {
		for j := rect.Min.Y; j < rect.Max.Y; j++ {
			offset := 4 * (j*stride + i)

			rSum += sRGBtoLinear(pixels[offset])
			gSum += sRGBtoLinear(pixels[offset+1])
			bSum += sRGBtoLinear(pixels[offset+2])

			count++
		}
	}

	return color.RGBA{lineartosRGB(rSum / count), lineartosRGB(gSum / count), lineartosRGB(bSum / count), 255}
}
Exemple #13
0
// Image builds an image.RGBA type with 6 by 6 quadrants of alternate colors.
func Image(m *image.RGBA, key string, colors []color.RGBA) {
	size := m.Bounds().Size()
	squares := 6
	quad := size.X / squares
	middle := math.Ceil(float64(squares) / float64(2))
	colorMap := make(map[int]color.RGBA)
	var currentYQuadrand = 0
	for y := 0; y < size.Y; y++ {
		yQuadrant := y / quad
		if yQuadrant != currentYQuadrand {
			// when y quadrant changes, clear map
			colorMap = make(map[int]color.RGBA)
			currentYQuadrand = yQuadrant
		}
		for x := 0; x < size.X; x++ {
			xQuadrant := x / quad
			if _, ok := colorMap[xQuadrant]; !ok {
				if float64(xQuadrant) < middle {
					colorMap[xQuadrant] = draw.PickColor(key, colors, xQuadrant+3*yQuadrant)
				} else if xQuadrant < squares {
					colorMap[xQuadrant] = colorMap[squares-xQuadrant-1]
				} else {
					colorMap[xQuadrant] = colorMap[0]
				}
			}
			m.Set(x, y, colorMap[xQuadrant])
		}
	}
}
Exemple #14
0
func NewTiledImage(img *image.RGBA, tileSize image.Point) *TiledImage {
	b := img.Bounds()
	nx := b.Dx() / tileSize.X
	ny := b.Dy() / tileSize.Y
	tiles := make([]TileInfo, nx*ny)

	for j := 0; j < ny; j++ {
		y := b.Min.Y + j*tileSize.Y
		for i := 0; i < nx; i++ {
			x := b.Min.X + i*tileSize.X
			rect := image.Rect(x, y, x+tileSize.X, y+tileSize.Y)
			k := i + j*nx
			tiles[k].subImage = img.SubImage(rect).(*image.RGBA)
			tiles[k].dist2 = math.MaxFloat32
		}
	}

	return &TiledImage{
		data: tiles,
		nx:   nx,
		ny:   ny,
		sx:   tileSize.X,
		sy:   tileSize.Y,
	}
}
Exemple #15
0
func getRGBA(src *image.RGBA, x, y, borderMethod int) (r, g, b, a uint8) {
	bound := src.Bounds()
	if x < 0 {
		switch borderMethod {
		case BORDER_COPY:
			x = 0
		default:
			return 0, 0, 0, 0
		}
	} else if x >= bound.Max.X {
		switch borderMethod {
		case BORDER_COPY:
			x = bound.Max.X - 1
		default:
			return 0, 0, 0, 0
		}
	}
	if y < 0 {
		switch borderMethod {
		case BORDER_COPY:
			y = 0
		default:
			return 0, 0, 0, 0
		}
	} else if y >= bound.Max.Y {
		switch borderMethod {
		case BORDER_COPY:
			y = bound.Max.Y - 1
		default:
			return 0, 0, 0, 0
		}
	}
	i := (y-bound.Min.Y)*src.Stride + (x-bound.Min.X)*4
	return src.Pix[i], src.Pix[i+1], src.Pix[i+2], src.Pix[i+3]
}
Exemple #16
0
func constructPixelArray(img *image.RGBA) []Rgb {
	rgbaImg := image.NewRGBA(img.Bounds())
	draw.Draw(rgbaImg, rgbaImg.Bounds(), img, image.ZP, draw.Src)

	pixelArray := make([]Rgb, 0, 50)
	var rgbVal = Rgb{}

	for i, pix := range rgbaImg.Pix {
		switch i % 4 {
		case 0:
			rgbVal.R = pix
		case 1:
			rgbVal.G = pix
		case 2:
			rgbVal.B = pix
		case 3:
			if pix >= minTransparency && isOpaque(rgbVal) {
				pixelArray = append(pixelArray, rgbVal)
				rgbVal = Rgb{}
			}
		}
	}

	return pixelArray
}
Exemple #17
0
func (buffer Image) CopyRGBA(src *image.RGBA, r image.Rectangle) {
	// clip r against each image's bounds and move sp accordingly (see draw.clip())
	sp := image.ZP
	orig := r.Min
	r = r.Intersect(buffer.Bounds())
	r = r.Intersect(src.Bounds().Add(orig.Sub(sp)))
	dx := r.Min.X - orig.X
	dy := r.Min.Y - orig.Y
	(sp).X += dx
	(sp).Y += dy

	i0 := (r.Min.X - buffer.Rect.Min.X) * 4
	i1 := (r.Max.X - buffer.Rect.Min.X) * 4
	si0 := (sp.X - src.Rect.Min.X) * 4
	yMax := r.Max.Y - buffer.Rect.Min.Y

	y := r.Min.Y - buffer.Rect.Min.Y
	sy := sp.Y - src.Rect.Min.Y
	for ; y != yMax; y, sy = y+1, sy+1 {
		dpix := buffer.Pix[y*buffer.Stride:]
		spix := src.Pix[sy*src.Stride:]

		for i, si := i0, si0; i < i1; i, si = i+4, si+4 {
			dpix[i+0] = spix[si+2]
			dpix[i+1] = spix[si+1]
			dpix[i+2] = spix[si+0]
			dpix[i+3] = spix[si+3]
		}
	}
}
Exemple #18
0
func drawGradient(m *image.RGBA) {
	b := m.Bounds()
	for y := b.Min.Y; y < b.Max.Y; y++ {
		for x := b.Min.X; x < b.Max.X; x++ {
			m.SetRGBA(x, y, color.RGBA{uint8(x), uint8(y), 0x00, 0xff})
		}
	}
}
Exemple #19
0
// draw src centered within dst
func drawCentered(dst *image.RGBA, src image.Image) {
	srcDx := src.Bounds().Dx()
	srcDy := src.Bounds().Dy()
	x := (dst.Bounds().Dx() - srcDx) / 2
	y := (dst.Bounds().Dy() - srcDy) / 2
	dstRect := image.Rect(x, y, x+srcDx, y+srcDy)
	draw.Draw(dst, dstRect, src, src.Bounds().Min, draw.Src)
}
Exemple #20
0
func RGBAToMatrix(img *image.RGBA) mat64.Matrix {
	bounds := img.Bounds()
	imgSize := bounds.Size()
	rows := imgSize.X * imgSize.Y

	imgMatrix := mat64.NewDense(rows, 4, convertPixToFloat64(img.Pix))

	return imgMatrix
}
Exemple #21
0
func drawPoint(m *image.RGBA, x float32, y float32) {
	b := m.Bounds()
	height := float32(b.Max.Y)
	width := float32(b.Max.X)
	scale := float32(height / 11)
	y = (height - 25) - (scale * y)
	x = (width / 2) + (scale * x)
	m.Set(int(x), int(y), color.RGBA{0, 255, 0, 255})
}
Exemple #22
0
// Make a new canvas of size w x h.
func NewCanvas(img *image.RGBA) *Canvas {
	c := new(Canvas)
	c.RGBA = img
	c.RGBAPainter = raster.NewRGBAPainter(c.RGBA)
	c.rasterizer = raster.NewRasterizer(img.Bounds().Max.X, img.Bounds().Max.Y)
	c.rasterizer.UseNonZeroWinding = true
	c.SetColor(color.RGBA{0, 0, 0, 100})
	return c
}
Exemple #23
0
func (t *Tiler) newTypeContext(im *image.RGBA, color color.RGBA) *freetype.Context {
	c := freetype.NewContext()
	c.SetDPI(72)
	c.SetFont(t.font)
	c.SetFontSize(t.FontSize)
	c.SetClip(im.Bounds())
	c.SetDst(im)
	c.SetSrc(image.NewUniform(color))
	return c
}
Exemple #24
0
func resizeRGBA(in *image.RGBA, out *image.NRGBA, scale float64, coeffs []int16, offset []int, filterLength int) {
	newBounds := out.Bounds()
	maxX := in.Bounds().Dx() - 1

	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
		row := in.Pix[x*in.Stride:]
		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
			var rgba [4]int32
			var sum int32
			start := offset[y]
			ci := y * filterLength
			for i := 0; i < filterLength; i++ {
				coeff := coeffs[ci+i]
				if coeff != 0 {
					xi := start + i
					switch {
					case uint(xi) < uint(maxX):
						xi *= 4
					case xi >= maxX:
						xi = 4 * maxX
					default:
						xi = 0
					}

					r := uint32(row[xi+0])
					g := uint32(row[xi+1])
					b := uint32(row[xi+2])
					a := uint32(row[xi+3])

					// reverse alpha-premultiplication.
					if a != 0 {
						r *= 0xffff
						r /= a
						g *= 0xffff
						g /= a
						b *= 0xffff
						b /= a
					}

					rgba[0] += int32(coeff) * int32(r)
					rgba[1] += int32(coeff) * int32(g)
					rgba[2] += int32(coeff) * int32(b)
					rgba[3] += int32(coeff) * int32(a)
					sum += int32(coeff)
				}
			}

			xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4
			out.Pix[xo+0] = clampUint8(rgba[0] / sum)
			out.Pix[xo+1] = clampUint8(rgba[1] / sum)
			out.Pix[xo+2] = clampUint8(rgba[2] / sum)
			out.Pix[xo+3] = clampUint8(rgba[3] / sum)
		}
	}
}
Exemple #25
0
func NewPictureFromRGBA(img *image.RGBA) *Picture {
	p := NewPicture(img.Bounds())

	p.Each(func(x, y int) {
		c := img.RGBAAt(x, y)
		l := p.Luma(c)
		p.Set(x, y, l)
	})

	return p
}
Exemple #26
0
func (w *World) UploadRGBAImage(img *image.RGBA) gltext.Texture {
	t := new(texture)
	ib := img.Bounds()
	t.bounds = ib
	gl.GenTextures(1, &t.id)
	gl.BindTexture(gl.TEXTURE_2D, t.id)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.Sizei(ib.Dx()), gl.Sizei(ib.Dy()), 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Void(&img.Pix[0]))
	return t
}
Exemple #27
0
func uploadTexture(img *image.RGBA) gl.Texture {
	gl.Enable(gl.TEXTURE_2D)
	tex := gl.GenTexture()
	tex.Bind(gl.TEXTURE_2D)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
	gl.TexImage2D(gl.TEXTURE_2D, 0, 4, img.Bounds().Max.X, img.Bounds().Max.Y,
		0, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix)
	gl.Disable(gl.TEXTURE_2D)
	return tex
}
Exemple #28
0
// Palette builds an JPEG image with all the colors present in the theme color array.
func Palette(m *image.RGBA, theme []color.RGBA) {
	size := m.Bounds().Size()
	numColors := len(theme)
	quadrant := size.X / numColors
	for x := 0; x < size.X; x++ {
		currentQuadrant := (x / quadrant) % numColors
		for y := 0; y < size.Y; y++ {
			m.Set(x, y, theme[currentQuadrant])
		}
	}
}
Exemple #29
0
func Raytracer(scene *Scene, img *image.RGBA) {
	var c color.RGBA

	b := img.Bounds()
	for y := b.Min.Y; y < b.Max.Y; y++ {
		for x := b.Min.X; x < b.Max.X; x++ {
			c = calc(x, y)
			img.Set(x, y, c)
		}
	}
}
Exemple #30
0
func (wf *WindowFoundation) handleWindowDrawing() {

	waitingForRepaint := false
	newStuff := false

	var scrBuf *image.RGBA

	var invalidRects RectSet

	for {
		select {
		case pane := <-wf.paneCh:
			wf.setPane(pane)
		case inv := <-wf.Invalidations:
			invalidRects = append(invalidRects, inv.Bounds...)
			if waitingForRepaint {
				newStuff = true
			} else {
				waitingForRepaint = true
				newStuff = true
				time.AfterFunc(FrameDelay, func() {
					wf.doRepaintWindow <- true
				})
			}

		case <-wf.doRepaintWindow:
			waitingForRepaint = false
			if !newStuff {
				break
			}
			scr := wf.W.Screen()
			if scrBuf == nil || scr.Bounds() != scrBuf.Bounds() {
				scrBuf = image.NewRGBA(scr.Bounds())
				invalidRects = RectSet{wf.Bounds()}
			}
			// Report("window drawing starting")
			wf.Drawer.Draw(scrBuf, invalidRects)
			// Report("window drawing done")
			var srs []image.Rectangle
			for _, ir := range invalidRects {
				sr := RectangleForRect(ir)
				si := scrBuf.SubImage(sr)
				srs = append(srs, sr)
				draw.Draw(scr, scr.Bounds(), si, image.Point{}, draw.Src)
			}
			invalidRects = invalidRects[:0]

			wf.W.FlushImage(srs...)
			newStuff = false
		}
	}
}