diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go
index a75d6f7802c0ec9d22efe02cfdf17a37c8498a64..7d0bd816e624db865b175911f91813322fa9a8a3 100644
--- a/pixelgl/canvas.go
+++ b/pixelgl/canvas.go
@@ -1,6 +1,7 @@
 package pixelgl
 
 import (
+	"fmt"
 	"image/color"
 
 	"github.com/faiface/glhf"
@@ -10,30 +11,33 @@ import (
 	"github.com/pkg/errors"
 )
 
-// Canvas is basically a Picture that you can draw onto.
+//TODO: make Canvas a Picture
+
+// Canvas is an off-screen rectangular BasicTarget that you can draw onto.
 //
-// Canvas supports TrianglesPosition, TrianglesColor and TrianglesTexture.
+// It supports TrianglesPosition, TrianglesColor, TrianglesPicture and PictureColor.
 type Canvas struct {
 	f *glhf.Frame
 	s *glhf.Shader
 
-	copyVs *glhf.VertexSlice
+	bounds pixel.Rect
 	smooth bool
 
-	drawTd pixel.TrianglesDrawer
-
-	pic *pixel.GLPicture
 	mat mgl32.Mat3
 	col mgl32.Vec4
-	bnd mgl32.Vec4
 }
 
-// NewCanvas creates a new fully transparent Canvas with specified dimensions in pixels.
-func NewCanvas(width, height float64, smooth bool) *Canvas {
-	c := &Canvas{smooth: smooth}
+// NewCanvas creates a new empty, fully transparent Canvas with given bounds. If the smooth flag
+// set, then stretched Pictures will be smoothed and will not be drawn pixely onto this Canvas.
+func NewCanvas(bounds pixel.Rect, smooth bool) *Canvas {
+	c := &Canvas{
+		smooth: smooth,
+		mat:    mgl32.Ident3(),
+		col:    mgl32.Vec4{1, 1, 1, 1},
+	}
+
 	mainthread.Call(func() {
 		var err error
-		c.f = glhf.NewFrame(int(width), int(height), smooth)
 		c.s, err = glhf.NewShader(
 			canvasVertexFormat,
 			canvasUniformFormat,
@@ -41,162 +45,179 @@ func NewCanvas(width, height float64, smooth bool) *Canvas {
 			canvasFragmentShader,
 		)
 		if err != nil {
-			panic(errors.Wrap(err, "failed to create canvas"))
+			panic(errors.Wrap(err, "failed to create Canvas, there's a bug in the shader"))
 		}
-
-		c.copyVs = glhf.MakeVertexSlice(c.s, 6, 6)
-		c.copyVs.Begin()
-		c.copyVs.SetVertexData([]float32{
-			-1, -1, 1, 1, 1, 1, 0, 0,
-			1, -1, 1, 1, 1, 1, 1, 0,
-			1, 1, 1, 1, 1, 1, 1, 1,
-			-1, -1, 1, 1, 1, 1, 0, 0,
-			1, 1, 1, 1, 1, 1, 1, 1,
-			-1, 1, 1, 1, 1, 1, 0, 1,
-		})
-		c.copyVs.End()
 	})
 
-	white := pixel.NRGBA{R: 1, G: 1, B: 1, A: 1}
-	c.drawTd = pixel.TrianglesDrawer{Triangles: &pixel.TrianglesData{
-		{Position: pixel.V(-1, -1), Color: white, Picture: pixel.V(0, 0)},
-		{Position: pixel.V(1, -1), Color: white, Picture: pixel.V(1, 0)},
-		{Position: pixel.V(1, 1), Color: white, Picture: pixel.V(1, 1)},
-		{Position: pixel.V(-1, -1), Color: white, Picture: pixel.V(0, 0)},
-		{Position: pixel.V(1, 1), Color: white, Picture: pixel.V(1, 1)},
-		{Position: pixel.V(-1, 1), Color: white, Picture: pixel.V(0, 1)},
-	}}
-
-	c.pic = nil
-	c.mat = mgl32.Ident3()
-	c.col = mgl32.Vec4{1, 1, 1, 1}
-	c.bnd = mgl32.Vec4{0, 0, 1, 1}
+	c.SetBounds(bounds)
+
 	return c
 }
 
-// SetSize resizes the Canvas. The original content will be stretched to fit the new size.
-func (c *Canvas) SetSize(width, height float64) {
-	if pixel.V(width, height) == pixel.V(c.Size()) {
-		return
+// MakeTriangles creates a specialized copy of the supplied Triangles that draws onto this Canvas.
+//
+// TrianglesPosition, TrianglesColor and TrianglesPicture are supported.
+func (c *Canvas) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles {
+	return &canvasTriangles{
+		GLTriangles: NewGLTriangles(c.s, t),
+		c:           c,
 	}
-	mainthread.Call(func() {
-		oldF := c.f
-		c.f = glhf.NewFrame(int(width), int(height), c.smooth)
-
-		c.f.Begin()
-		c.s.Begin()
-
-		c.s.SetUniformAttr(canvasTransformMat3, mgl32.Ident3())
-		c.s.SetUniformAttr(canvasMaskColorVec4, mgl32.Vec4{1, 1, 1, 1})
-		c.s.SetUniformAttr(canvasBoundsVec4, mgl32.Vec4{0, 0, 1, 1})
+}
 
-		oldF.Texture().Begin()
-		c.copyVs.Begin()
-		c.copyVs.Draw()
-		c.copyVs.End()
-		oldF.Texture().End()
+// MakePicture create a specialized copy of the supplied Picture that draws onto this Canvas.
+//
+// PictureColor is supported.
+func (c *Canvas) MakePicture(p pixel.Picture) pixel.TargetPicture {
+	pd := pixel.PictureDataFromPicture(p)
+	pixels := make([]uint8, 4*len(pd.Pix))
+	for i := range pd.Pix {
+		pixels[i*4+0] = uint8(pd.Pix[i].R * 255)
+		pixels[i*4+1] = uint8(pd.Pix[i].G * 255)
+		pixels[i*4+2] = uint8(pd.Pix[i].B * 255)
+		pixels[i*4+3] = uint8(pd.Pix[i].A * 255)
+	}
 
-		c.s.End()
-		c.f.End()
+	var tex *glhf.Texture
+	mainthread.Call(func() {
+		tex = glhf.NewTexture(pd.Stride, len(pd.Pix)/pd.Stride, c.smooth, pixels)
 	})
-}
 
-// Size returns the width and the height of the Canvas in pixels.
-func (c *Canvas) Size() (width, height float64) {
-	return float64(c.f.Width()), float64(c.f.Height())
+	return &canvasPicture{
+		tex:    tex,
+		bounds: pd.Rect,
+		c:      c,
+	}
 }
 
-// Content returns a Picture that contains the content of this Canvas. The returned Picture changes
-// as you draw onto the Canvas, so there is no real need to call this method more than once (but it
-// might be beneficial to your code to do so).
-func (c *Canvas) Content() *pixel.GLPicture {
-	return pixel.PictureFromTexture(c.f.Texture())
+// SetTransform sets a set of Transforms that every position in triangles will be put through.
+func (c *Canvas) SetTransform(t ...pixel.Transform) {
+	c.mat = transformToMat(t...)
 }
 
-// Clear fills the whole Canvas with one specified color.
-func (c *Canvas) Clear(col color.Color) {
-	mainthread.CallNonBlock(func() {
-		c.f.Begin()
-		col := pixel.NRGBAModel.Convert(col).(pixel.NRGBA)
-		glhf.Clear(float32(col.R), float32(col.G), float32(col.B), float32(col.A))
-		c.f.End()
-	})
+// SetColorMask sets a color that every color in triangles or a picture will be multiplied by.
+func (c *Canvas) SetColorMask(col color.Color) {
+	nrgba := pixel.NRGBA{R: 1, G: 1, B: 1, A: 1}
+	if col != nil {
+		nrgba = pixel.NRGBAModel.Convert(col).(pixel.NRGBA)
+	}
+	c.col = mgl32.Vec4{
+		float32(nrgba.R),
+		float32(nrgba.G),
+		float32(nrgba.B),
+		float32(nrgba.A),
+	}
 }
 
-// Draw draws the content of the Canvas onto another Target. If no transform is applied, the content
-// is fully stretched to fit the Target.
-func (c *Canvas) Draw(t pixel.Target) {
-	c.drawTd.Draw(t)
+// SetBounds resizes the Canvas to the new bounds. Old content will be preserved.
+func (c *Canvas) SetBounds(bounds pixel.Rect) {
+	if c.Bounds() == bounds {
+		return
+	}
+
+	mainthread.Call(func() {
+		oldF := c.f
+
+		_, _, w, h := discreteBounds(bounds)
+		c.f = glhf.NewFrame(w, h, c.smooth)
+
+		// preserve old content
+		if oldF != nil {
+			relBounds := c.bounds
+			relBounds.Pos -= bounds.Pos
+			ox, oy, ow, oh := discreteBounds(relBounds)
+			oldF.Blit(
+				c.f,
+				ox, oy, ox+ow, oy+oh,
+				ox, oy, ox+ow, oy+oh,
+			)
+		}
+
+		c.s.Begin()
+		orig := bounds.Center()
+		c.s.SetUniformAttr(canvasOrig, mgl32.Vec2{
+			float32(orig.X()),
+			float32(orig.Y()),
+		})
+		c.s.SetUniformAttr(canvasSize, mgl32.Vec2{
+			float32(w),
+			float32(h),
+		})
+		c.s.End()
+	})
+	c.bounds = bounds
 }
 
-// MakeTriangles returns Triangles that draw onto this Canvas.
-func (c *Canvas) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles {
-	gt := NewGLTriangles(c.s, t).(*glTriangles)
-	return &canvasTriangles{
-		c:           c,
-		glTriangles: gt,
-	}
+// Bounds returns the rectangular bounds of the Canvas.
+func (c *Canvas) Bounds() pixel.Rect {
+	return c.bounds
 }
 
-// SetPicture sets a Picture that will be used in further draw operations.
-//
-// This does not set the Picture that this Canvas draws onto, don't confuse it.
-func (c *Canvas) SetPicture(p *pixel.GLPicture) {
-	if p != nil {
-		min := pictureBounds(p, pixel.V(0, 0))
-		max := pictureBounds(p, pixel.V(1, 1))
-		c.bnd = mgl32.Vec4{
-			float32(min.X()), float32(min.Y()),
-			float32(max.X()), float32(max.Y()),
-		}
-	}
-	c.pic = p
+// SetSmooth sets whether the stretched Pictures drawn onto this Canvas should be drawn smooth or
+// pixely.
+func (c *Canvas) SetSmooth(smooth bool) {
+	c.smooth = smooth
 }
 
-// SetTransform sets the transformations used in further draw operations.
-func (c *Canvas) SetTransform(t ...pixel.Transform) {
-	c.mat = transformToMat(t...)
+// Smooth returns whether the stretched Pictures drawn onto this Canvas are set to be drawn smooth
+// or pixely.
+func (c *Canvas) Smooth() bool {
+	return c.smooth
 }
 
-// SetMaskColor sets the mask color used in further draw operations.
-func (c *Canvas) SetMaskColor(col color.Color) {
-	if col == nil {
-		col = pixel.NRGBA{R: 1, G: 1, B: 1, A: 1}
-	}
-	nrgba := pixel.NRGBAModel.Convert(col).(pixel.NRGBA)
-	r := float32(nrgba.R)
-	g := float32(nrgba.G)
-	b := float32(nrgba.B)
-	a := float32(nrgba.A)
-	c.col = mgl32.Vec4{r, g, b, a}
+// Clear fill the whole Canvas with a single color.
+func (c *Canvas) Clear(color color.Color) {
+	nrgba := pixel.NRGBAModel.Convert(color).(pixel.NRGBA)
+	mainthread.CallNonBlock(func() {
+		c.f.Begin()
+		glhf.Clear(
+			float32(nrgba.R),
+			float32(nrgba.G),
+			float32(nrgba.B),
+			float32(nrgba.A),
+		)
+		c.f.End()
+	})
 }
 
 type canvasTriangles struct {
+	*GLTriangles
+
 	c *Canvas
-	*glTriangles
 }
 
-func (ct *canvasTriangles) Draw() {
-	// avoid possible race condition
-	pic := ct.c.pic
+func (ct *canvasTriangles) draw(cp *canvasPicture) {
+	// save the current state vars to avoid race condition
 	mat := ct.c.mat
 	col := ct.c.col
-	bnd := ct.c.bnd
 
 	mainthread.CallNonBlock(func() {
 		ct.c.f.Begin()
 		ct.c.s.Begin()
 
-		ct.c.s.SetUniformAttr(canvasTransformMat3, mat)
-		ct.c.s.SetUniformAttr(canvasMaskColorVec4, col)
-		ct.c.s.SetUniformAttr(canvasBoundsVec4, bnd)
+		ct.c.s.SetUniformAttr(canvasTransform, mat)
+		ct.c.s.SetUniformAttr(canvasColorMask, col)
 
-		if pic != nil {
-			pic.Texture().Begin()
-			ct.glTriangles.Draw()
-			pic.Texture().End()
+		if cp == nil {
+			ct.vs.Begin()
+			ct.vs.Draw()
+			ct.vs.End()
 		} else {
-			ct.glTriangles.Draw()
+			cp.tex.Begin()
+
+			ct.c.s.SetUniformAttr(canvasTexSize, mgl32.Vec2{
+				float32(cp.tex.Width()),
+				float32(cp.tex.Height()),
+			})
+
+			if cp.tex.Smooth() != ct.c.smooth {
+				cp.tex.SetSmooth(ct.c.smooth)
+			}
+
+			ct.vs.Begin()
+			ct.vs.Draw()
+			ct.vs.End()
+
+			cp.tex.End()
 		}
 
 		ct.c.s.End()
@@ -204,28 +225,64 @@ func (ct *canvasTriangles) Draw() {
 	})
 }
 
+func (ct *canvasTriangles) Draw() {
+	ct.draw(nil)
+}
+
+type canvasPicture struct {
+	tex    *glhf.Texture
+	bounds pixel.Rect
+
+	c *Canvas
+}
+
+func (cp *canvasPicture) Bounds() pixel.Rect {
+	return cp.bounds
+}
+
+func (cp *canvasPicture) Slice(r pixel.Rect) pixel.Picture {
+	return &canvasPicture{
+		bounds: r,
+		c:      cp.c,
+	}
+}
+
+func (cp *canvasPicture) Draw(t pixel.TargetTriangles) {
+	ct := t.(*canvasTriangles)
+	if cp.c != ct.c {
+		panic(fmt.Errorf("%T.Draw: TargetTriangles generated by different Canvas", cp))
+	}
+	ct.draw(cp)
+}
+
 const (
-	canvasPositionVec2 int = iota
-	canvasColorVec4
-	canvasTextureVec2
+	canvasPosition int = iota
+	canvasColor
+	canvasTexture
+	canvasIntensity
 )
 
 var canvasVertexFormat = glhf.AttrFormat{
-	canvasPositionVec2: {Name: "position", Type: glhf.Vec2},
-	canvasColorVec4:    {Name: "color", Type: glhf.Vec4},
-	canvasTextureVec2:  {Name: "texture", Type: glhf.Vec2},
+	canvasPosition:  {Name: "position", Type: glhf.Vec2},
+	canvasColor:     {Name: "color", Type: glhf.Vec4},
+	canvasTexture:   {Name: "texture", Type: glhf.Vec2},
+	canvasIntensity: {Name: "intensity", Type: glhf.Float},
 }
 
 const (
-	canvasMaskColorVec4 int = iota
-	canvasTransformMat3
-	canvasBoundsVec4
+	canvasTransform int = iota
+	canvasColorMask
+	canvasTexSize
+	canvasOrig
+	canvasSize
 )
 
 var canvasUniformFormat = glhf.AttrFormat{
-	{Name: "maskColor", Type: glhf.Vec4},
-	{Name: "transform", Type: glhf.Mat3},
-	{Name: "bounds", Type: glhf.Vec4},
+	canvasTransform: {Name: "transform", Type: glhf.Mat3},
+	canvasColorMask: {Name: "colorMask", Type: glhf.Vec4},
+	canvasTexSize:   {Name: "texSize", Type: glhf.Vec2},
+	canvasOrig:      {Name: "orig", Type: glhf.Vec2},
+	canvasSize:      {Name: "size", Type: glhf.Vec2},
 }
 
 var canvasVertexShader = `
@@ -234,16 +291,23 @@ var canvasVertexShader = `
 in vec2 position;
 in vec4 color;
 in vec2 texture;
+in float intensity;
 
 out vec4 Color;
 out vec2 Texture;
+out float Intensity;
 
 uniform mat3 transform;
+uniform vec2 orig;
+uniform vec2 size;
 
 void main() {
-	gl_Position = vec4((transform * vec3(position.x, position.y, 1.0)).xy, 0.0, 1.0);
+	vec2 transPos = (transform * vec3(position, 1.0)).xy;
+	vec2 normPos = (transPos - orig) / (size/2);
+	gl_Position = vec4(normPos, 0.0, 1.0);
 	Color = color;
 	Texture = texture;
+	Intensity = intensity;
 }
 `
 
@@ -252,23 +316,22 @@ var canvasFragmentShader = `
 
 in vec4 Color;
 in vec2 Texture;
+in float Intensity;
 
 out vec4 color;
 
-uniform vec4 maskColor;
-uniform vec4 bounds;
+uniform vec4 colorMask;
+uniform vec2 texSize;
 uniform sampler2D tex;
 
 void main() {
-	vec2 boundsMin = bounds.xy;
-	vec2 boundsMax = bounds.zw;
-
-	if (Texture == vec2(-1, -1)) {
-		color = maskColor * Color;
+	if (Intensity == 0) {
+		color = colorMask * Color;
 	} else {
-		float tx = boundsMin.x * (1 - Texture.x) + boundsMax.x * Texture.x;
-		float ty = boundsMin.y * (1 - Texture.y) + boundsMax.y * Texture.y;
-		color = maskColor * Color * texture(tex, vec2(tx, ty));
+		vec2 t = Texture / texSize;
+		color = vec4(0, 0, 0, 0);
+		color += (1 - Intensity) * colorMask * Color;
+		color += Intensity * colorMask * Color * texture(tex, t);
 	}
 }
 `
diff --git a/pixelgl/gltriangles.go b/pixelgl/gltriangles.go
index 7957a4aa68f25df785ca2b3e1eb6788f41bbdb8c..de3c1115b89e1976eca0b0f63737e795802d5bc8 100644
--- a/pixelgl/gltriangles.go
+++ b/pixelgl/gltriangles.go
@@ -2,7 +2,6 @@ package pixelgl
 
 import (
 	"fmt"
-	"math"
 
 	"github.com/faiface/glhf"
 	"github.com/faiface/mainthread"
@@ -29,9 +28,9 @@ var (
 //
 // Only draw the Triangles using the provided Shader.
 func NewGLTriangles(shader *glhf.Shader, t pixel.Triangles) *GLTriangles {
-	var gt *glTriangles
+	var gt *GLTriangles
 	mainthread.Call(func() {
-		gt = &glTriangles{
+		gt = &GLTriangles{
 			vs:     glhf.MakeVertexSlice(shader, 0, t.Len()),
 			shader: shader,
 		}
@@ -66,7 +65,8 @@ func (gt *GLTriangles) SetLen(len int) {
 			gt.data = append(gt.data,
 				0, 0,
 				1, 1, 1, 1,
-				float32(math.Inf(+1)), float32(math.Inf(+1)),
+				0, 0,
+				0,
 			)
 		}
 	}
@@ -77,7 +77,7 @@ func (gt *GLTriangles) SetLen(len int) {
 
 // Slice returns a sub-Triangles of this GLTriangles in range [i, j).
 func (gt *GLTriangles) Slice(i, j int) pixel.Triangles {
-	return &glTriangles{
+	return &GLTriangles{
 		vs:     gt.vs.Slice(i, j),
 		data:   gt.data[i*gt.vs.Stride() : j*gt.vs.Stride()],
 		shader: gt.shader,
@@ -86,7 +86,7 @@ func (gt *GLTriangles) Slice(i, j int) pixel.Triangles {
 
 func (gt *GLTriangles) updateData(t pixel.Triangles) {
 	// glTriangles short path
-	if t, ok := t.(*glTriangles); ok {
+	if t, ok := t.(*GLTriangles); ok {
 		copy(gt.data, t.data)
 		return
 	}
@@ -98,6 +98,7 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) {
 				px, py = (*t)[i].Position.XY()
 				col    = (*t)[i].Color
 				tx, ty = (*t)[i].Picture.XY()
+				in     = (*t)[i].Intensity
 			)
 			gt.data[i*gt.vs.Stride()+0] = float32(px)
 			gt.data[i*gt.vs.Stride()+1] = float32(py)
@@ -107,6 +108,7 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) {
 			gt.data[i*gt.vs.Stride()+5] = float32(col.A)
 			gt.data[i*gt.vs.Stride()+6] = float32(tx)
 			gt.data[i*gt.vs.Stride()+7] = float32(ty)
+			gt.data[i*gt.vs.Stride()+8] = float32(in)
 		}
 		return
 	}
@@ -129,15 +131,16 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) {
 	}
 	if t, ok := t.(pixel.TrianglesPicture); ok {
 		for i := 0; i < gt.Len(); i++ {
-			tx, ty := t.Picture(i).XY()
-			gt.data[i*gt.vs.Stride()+6] = float32(tx)
-			gt.data[i*gt.vs.Stride()+7] = float32(ty)
+			pic, intensity := t.Picture(i)
+			gt.data[i*gt.vs.Stride()+6] = float32(pic.X())
+			gt.data[i*gt.vs.Stride()+7] = float32(pic.Y())
+			gt.data[i*gt.vs.Stride()+8] = float32(intensity)
 		}
 	}
 }
 
 func (gt *GLTriangles) submitData() {
-	data := gt.data // avoid race condition
+	data := append([]float32{}, gt.data...) // avoid race condition
 	mainthread.CallNonBlock(func() {
 		gt.vs.Begin()
 		dataLen := len(data) / gt.vs.Stride()
@@ -187,8 +190,9 @@ func (gt *GLTriangles) Color(i int) pixel.NRGBA {
 }
 
 // Picture returns the Picture property of the i-th vertex.
-func (gt *GLTriangles) Picture(i int) pixel.Vec {
+func (gt *GLTriangles) Picture(i int) (pic pixel.Vec, intensity float64) {
 	tx := gt.data[i*gt.vs.Stride()+6]
 	ty := gt.data[i*gt.vs.Stride()+7]
-	return pixel.V(float64(tx), float64(ty))
+	intensity = float64(gt.data[i*gt.vs.Stride()+8])
+	return pixel.V(float64(tx), float64(ty)), intensity
 }
diff --git a/pixelgl/util.go b/pixelgl/util.go
index 1c760df64403060f473979dc9ab355d9bd6f62c7..94138eecde70de0db3ded9349b409a64df06d25b 100644
--- a/pixelgl/util.go
+++ b/pixelgl/util.go
@@ -1,31 +1,12 @@
 package pixelgl
 
 import (
+	"math"
+
 	"github.com/faiface/pixel"
 	"github.com/go-gl/mathgl/mgl32"
 )
 
-func clamp(x, low, high float64) float64 {
-	if x < low {
-		return low
-	}
-	if x > high {
-		return high
-	}
-	return x
-}
-
-func lerp(x float64, a, b pixel.Vec) pixel.Vec {
-	return a.Scaled(1-x) + b.Scaled(x)
-}
-
-func lerp2d(x, a, b pixel.Vec) pixel.Vec {
-	return pixel.V(
-		lerp(x.X(), a, b).X(),
-		lerp(x.Y(), a, b).Y(),
-	)
-}
-
 func transformToMat(t ...pixel.Transform) mgl32.Mat3 {
 	mat := mgl32.Ident3()
 	for i := range t {
@@ -34,10 +15,10 @@ func transformToMat(t ...pixel.Transform) mgl32.Mat3 {
 	return mat
 }
 
-func pictureBounds(p *pixel.GLPicture, v pixel.Vec) pixel.Vec {
-	w, h := float64(p.Texture().Width()), float64(p.Texture().Height())
-	a := p.Bounds().Pos
-	b := p.Bounds().Pos + p.Bounds().Size
-	u := lerp2d(v, a, b)
-	return pixel.V(u.X()/w, u.Y()/h)
+func discreteBounds(bounds pixel.Rect) (x, y, w, h int) {
+	x0 := int(math.Floor(bounds.Pos.X()))
+	y0 := int(math.Floor(bounds.Pos.Y()))
+	x1 := int(math.Ceil(bounds.Pos.X() + bounds.Size.X()))
+	y1 := int(math.Ceil(bounds.Pos.Y() + bounds.Size.Y()))
+	return x0, y0, x1 - x0, y1 - y0
 }
diff --git a/pixelgl/window.go b/pixelgl/window.go
index 82d010ad6622ff100c062875f9862b2ea3f1da6b..5cd63ec2e1ef78502b0aa834c400c47c0287a30d 100644
--- a/pixelgl/window.go
+++ b/pixelgl/window.go
@@ -2,7 +2,7 @@ package pixelgl
 
 import (
 	"image/color"
-
+	"math"
 	"runtime"
 
 	"github.com/faiface/glhf"
@@ -16,16 +16,13 @@ import (
 // chosen in such a way, that you usually only need to set a few of them - defaults (zeros) should
 // usually be sensible.
 //
-// Note that you always need to set the width and the height of a window.
+// Note that you always need to set the Bounds of the Window.
 type WindowConfig struct {
-	// Title at the top of a window.
+	// Title at the top of the Window
 	Title string
 
-	// Width of a window in pixels.
-	Width float64
-
-	// Height of a window in pixels.
-	Height float64
+	// Bounds specify the bounds of the Window in pixels.
+	Bounds pixel.Rect
 
 	// If set to nil, a window will be windowed. Otherwise it will be fullscreen on the
 	// specified monitor.
@@ -46,23 +43,22 @@ type WindowConfig struct {
 	// Whether a window is maximized.
 	Maximized bool
 
-	// VSync (vertical synchronization) synchronizes window's framerate with the framerate
-	// of the monitor.
+	// VSync (vertical synchronization) synchronizes window's framerate with the framerate of
+	// the monitor.
 	VSync bool
 
-	// Number of samples for multi-sample anti-aliasing (edge-smoothing).  Usual values
-	// are 0, 2, 4, 8 (powers of 2 and not much more than this).
+	// Number of samples for multi-sample anti-aliasing (edge-smoothing). Usual values are 0, 2,
+	// 4, 8 (powers of 2 and not much more than this).
 	MSAASamples int
 }
 
 // Window is a window handler. Use this type to manipulate a window (input, drawing, ...).
 type Window struct {
 	window *glfw.Window
-	config WindowConfig
 
-	canvas   *Canvas
-	canvasVs *glhf.VertexSlice
-	shader   *glhf.Shader
+	bounds pixel.Rect
+	canvas *Canvas
+	vsync  bool
 
 	// need to save these to correctly restore a fullscreen window
 	restore struct {
@@ -80,13 +76,15 @@ var currentWindow *Window
 // NewWindow creates a new Window with it's properties specified in the provided config.
 //
 // If Window creation fails, an error is returned (e.g. due to unavailable graphics device).
-func NewWindow(config WindowConfig) (*Window, error) {
+func NewWindow(cfg WindowConfig) (*Window, error) {
 	bool2int := map[bool]int{
 		true:  glfw.True,
 		false: glfw.False,
 	}
 
-	w := &Window{config: config}
+	w := &Window{
+		bounds: cfg.Bounds,
+	}
 
 	err := mainthread.CallErr(func() error {
 		var err error
@@ -96,21 +94,22 @@ func NewWindow(config WindowConfig) (*Window, error) {
 		glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
 		glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
 
-		glfw.WindowHint(glfw.Resizable, bool2int[config.Resizable])
-		glfw.WindowHint(glfw.Visible, bool2int[!config.Hidden])
-		glfw.WindowHint(glfw.Decorated, bool2int[!config.Undecorated])
-		glfw.WindowHint(glfw.Focused, bool2int[!config.Unfocused])
-		glfw.WindowHint(glfw.Maximized, bool2int[config.Maximized])
-		glfw.WindowHint(glfw.Samples, config.MSAASamples)
+		glfw.WindowHint(glfw.Resizable, bool2int[cfg.Resizable])
+		glfw.WindowHint(glfw.Visible, bool2int[!cfg.Hidden])
+		glfw.WindowHint(glfw.Decorated, bool2int[!cfg.Undecorated])
+		glfw.WindowHint(glfw.Focused, bool2int[!cfg.Unfocused])
+		glfw.WindowHint(glfw.Maximized, bool2int[cfg.Maximized])
+		glfw.WindowHint(glfw.Samples, cfg.MSAASamples)
 
 		var share *glfw.Window
 		if currentWindow != nil {
 			share = currentWindow.window
 		}
+		_, _, width, height := discreteBounds(cfg.Bounds)
 		w.window, err = glfw.CreateWindow(
-			int(config.Width),
-			int(config.Height),
-			config.Title,
+			width,
+			height,
+			cfg.Title,
 			nil,
 			share,
 		)
@@ -122,38 +121,18 @@ func NewWindow(config WindowConfig) (*Window, error) {
 		w.begin()
 		w.end()
 
-		w.shader, err = glhf.NewShader(
-			windowVertexFormat,
-			windowUniformFormat,
-			windowVertexShader,
-			windowFragmentShader,
-		)
-		if err != nil {
-			return err
-		}
-
-		w.canvasVs = glhf.MakeVertexSlice(w.shader, 6, 6)
-		w.canvasVs.Begin()
-		w.canvasVs.SetVertexData([]float32{
-			-1, -1, 0, 0,
-			1, -1, 1, 0,
-			1, 1, 1, 1,
-			-1, -1, 0, 0,
-			1, 1, 1, 1,
-			-1, 1, 0, 1,
-		})
-		w.canvasVs.End()
-
 		return nil
 	})
 	if err != nil {
 		return nil, errors.Wrap(err, "creating window failed")
 	}
 
+	w.SetVSync(cfg.VSync)
+
 	w.initInput()
-	w.SetMonitor(config.Fullscreen)
+	w.SetMonitor(cfg.Fullscreen)
 
-	w.canvas = NewCanvas(config.Width, config.Height, false)
+	w.canvas = NewCanvas(cfg.Bounds, false)
 	w.Update()
 
 	runtime.SetFinalizer(w, (*Window).Destroy)
@@ -168,29 +147,40 @@ func (w *Window) Destroy() {
 	})
 }
 
-// Clear clears the Window with a color.
-func (w *Window) Clear(c color.Color) {
-	w.canvas.Clear(c)
-}
-
 // Update swaps buffers and polls events.
 func (w *Window) Update() {
-	w.canvas.SetSize(w.Size())
+	mainthread.Call(func() {
+		wi, hi := w.window.GetSize()
+		w.bounds.Size = pixel.V(float64(wi), float64(hi))
+		// fractional positions end up covering more pixels with less size
+		if w.bounds.X() != math.Floor(w.bounds.X()) {
+			w.bounds.Size -= pixel.V(1, 0)
+		}
+		if w.bounds.Y() != math.Floor(w.bounds.Y()) {
+			w.bounds.Size -= pixel.V(0, 1)
+		}
+	})
+
+	w.canvas.SetBounds(w.Bounds())
 
 	mainthread.Call(func() {
 		w.begin()
 
+		glhf.Bounds(0, 0, w.canvas.f.Width(), w.canvas.f.Height())
+
 		glhf.Clear(0, 0, 0, 0)
-		w.shader.Begin()
-		w.canvas.f.Texture().Begin()
-		w.canvasVs.Begin()
-		w.canvasVs.Draw()
-		w.canvasVs.End()
-		w.canvas.f.Texture().End()
-		w.shader.End()
-
-		if w.config.VSync {
+		w.canvas.f.Begin()
+		w.canvas.f.Blit(
+			nil,
+			0, 0, w.canvas.f.Width(), w.canvas.f.Height(),
+			0, 0, w.canvas.f.Width(), w.canvas.f.Height(),
+		)
+		w.canvas.f.End()
+
+		if w.vsync {
 			glfw.SwapInterval(1)
+		} else {
+			glfw.SwapInterval(0)
 		}
 		w.window.SwapBuffers()
 		w.end()
@@ -225,22 +215,19 @@ func (w *Window) SetTitle(title string) {
 	})
 }
 
-// SetSize resizes the client area of the Window to the specified size in pixels. In case of a
-// fullscreen Window, it changes the resolution of that Window.
-func (w *Window) SetSize(width, height float64) {
+// SetBounds sets the bounds of the Window in pixels. Bounds can be fractional, but the size will be
+// changed in the next Update to a real possible size of the Window.
+func (w *Window) SetBounds(bounds pixel.Rect) {
+	w.bounds = bounds
 	mainthread.Call(func() {
-		w.window.SetSize(int(width), int(height))
+		_, _, width, height := discreteBounds(bounds)
+		w.window.SetSize(width, height)
 	})
 }
 
-// Size returns the size of the client area of the Window (the part you can draw on).
-func (w *Window) Size() (width, height float64) {
-	mainthread.Call(func() {
-		wi, hi := w.window.GetSize()
-		width = float64(wi)
-		height = float64(hi)
-	})
-	return width, height
+// Bounds returns the current bounds of the Window.
+func (w *Window) Bounds() pixel.Rect {
+	return w.bounds
 }
 
 // Show makes the Window visible if it was hidden.
@@ -351,6 +338,16 @@ func (w *Window) Restore() {
 	})
 }
 
+// SetVSync sets whether the Window should synchronize with the monitor refresh rate.
+func (w *Window) SetVSync(vsync bool) {
+	w.vsync = vsync
+}
+
+// VSync returns whether the Window is set to synchronize with the monitor refresh rate.
+func (w *Window) VSync() bool {
+	return w.vsync
+}
+
 // Note: must be called inside the main thread.
 func (w *Window) begin() {
 	if currentWindow != w {
@@ -365,7 +362,7 @@ func (w *Window) end() {
 	// nothing, really
 }
 
-// MakeTriangles generates a specialized copy of the supplied triangles that will draw onto this
+// MakeTriangles generates a specialized copy of the supplied Triangles that will draw onto this
 // Window.
 //
 // Window supports TrianglesPosition, TrianglesColor and TrianglesTexture.
@@ -373,9 +370,11 @@ func (w *Window) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles {
 	return w.canvas.MakeTriangles(t)
 }
 
-// SetPicture sets a Picture that will be used in subsequent drawings onto the Window.
-func (w *Window) SetPicture(p *pixel.GLPicture) {
-	w.canvas.SetPicture(p)
+// MakePicture generates a specialized copy of the supplied Picture that will draw onto this Window.
+//
+// Window support PictureColor.
+func (w *Window) MakePicture(p pixel.Picture) pixel.TargetPicture {
+	return w.canvas.MakePicture(p)
 }
 
 // SetTransform sets a global transformation matrix for the Window.
@@ -385,47 +384,24 @@ func (w *Window) SetTransform(t ...pixel.Transform) {
 	w.canvas.SetTransform(t...)
 }
 
-// SetMaskColor sets a global mask color for the Window.
-func (w *Window) SetMaskColor(c color.Color) {
-	w.canvas.SetMaskColor(c)
+// SetColorMask sets a global color mask for the Window.
+func (w *Window) SetColorMask(c color.Color) {
+	w.canvas.SetColorMask(c)
 }
 
-const (
-	windowPositionVec2 = iota
-	windowTextureVec2
-)
-
-var windowVertexFormat = glhf.AttrFormat{
-	windowPositionVec2: {Name: "position", Type: glhf.Vec2},
-	windowTextureVec2:  {Name: "texture", Type: glhf.Vec2},
+// SetSmooth sets whether the stretched Pictures drawn onto this Window should be drawn smooth or
+// pixely.
+func (w *Window) SetSmooth(smooth bool) {
+	w.canvas.SetSmooth(smooth)
 }
 
-var windowUniformFormat = glhf.AttrFormat{}
-
-var windowVertexShader = `
-#version 330 core
-
-in vec2 position;
-in vec2 texture;
-
-out vec2 Texture;
-
-void main() {
-	gl_Position = vec4(position, 0.0, 1.0);
-	Texture = texture;
+// Smooth returns whether the stretched Pictures drawn onto this Window are set to be drawn smooth
+// or pixely.
+func (w *Window) Smooth() bool {
+	return w.canvas.Smooth()
 }
-`
-
-var windowFragmentShader = `
-#version 330 core
-
-in vec2 Texture;
 
-out vec4 color;
-
-uniform sampler2D tex;
-
-void main() {
-	color = texture(tex, Texture);
+// Clear clears the Window with a color.
+func (w *Window) Clear(c color.Color) {
+	w.canvas.Clear(c)
 }
-`