diff --git a/data.go b/data.go
index 774d6bc1cd9194d28e4d672282be44a8742c6a03..b27a8d13efa4333710950fb0f1c8cec060d676f6 100644
--- a/data.go
+++ b/data.go
@@ -134,8 +134,8 @@ type PictureData struct {
 
 // MakePictureData creates a zero-initialized PictureData covering the given rectangle.
 func MakePictureData(rect Rect) *PictureData {
-	w := int(math.Ceil(rect.Pos.X()+rect.Size.X())) - int(math.Floor(rect.Pos.X()))
-	h := int(math.Ceil(rect.Pos.Y()+rect.Size.Y())) - int(math.Floor(rect.Pos.Y()))
+	w := int(math.Ceil(rect.Max.X())) - int(math.Floor(rect.Min.X()))
+	h := int(math.Ceil(rect.Max.Y())) - int(math.Floor(rect.Min.Y()))
 	pd := &PictureData{
 		Stride: w,
 		Rect:   rect,
@@ -206,12 +206,12 @@ func PictureDataFromPicture(pic Picture) *PictureData {
 	pd := MakePictureData(bounds)
 
 	if pic, ok := pic.(PictureColor); ok {
-		for y := math.Floor(bounds.Pos.Y()); y < bounds.Pos.Y()+bounds.Size.Y(); y++ {
-			for x := math.Floor(bounds.Pos.X()); x < bounds.Pos.X()+bounds.Size.X(); x++ {
+		for y := math.Floor(bounds.Min.Y()); y < bounds.Max.Y(); y++ {
+			for x := math.Floor(bounds.Min.X()); x < bounds.Max.X(); x++ {
 				// this together with the Floor is a trick to get all of the pixels
 				at := V(
-					math.Max(x, bounds.Pos.X()),
-					math.Max(y, bounds.Pos.Y()),
+					math.Max(x, bounds.Min.X()),
+					math.Max(y, bounds.Min.Y()),
 				)
 				pd.SetColor(at, pic.Color(at))
 			}
@@ -226,10 +226,10 @@ func PictureDataFromPicture(pic Picture) *PictureData {
 // The resulting image.NRGBA's Bounds will be equivalent of the PictureData's Bounds.
 func (pd *PictureData) Image() *image.NRGBA {
 	bounds := image.Rect(
-		int(math.Floor(pd.Rect.Pos.X())),
-		int(math.Floor(pd.Rect.Pos.Y())),
-		int(math.Ceil(pd.Rect.Pos.X()+pd.Rect.Size.X())),
-		int(math.Ceil(pd.Rect.Pos.Y()+pd.Rect.Size.Y())),
+		int(math.Floor(pd.Rect.Min.X())),
+		int(math.Floor(pd.Rect.Min.Y())),
+		int(math.Ceil(pd.Rect.Max.X())),
+		int(math.Ceil(pd.Rect.Max.Y())),
 	)
 	nrgba := image.NewNRGBA(bounds)
 
@@ -251,7 +251,7 @@ func (pd *PictureData) Image() *image.NRGBA {
 }
 
 func (pd *PictureData) offset(at Vec) int {
-	at -= pd.Rect.Pos.Map(math.Floor)
+	at -= pd.Rect.Min.Map(math.Floor)
 	x, y := int(at.X()), int(at.Y())
 	return y*pd.Stride + x
 }
@@ -264,7 +264,7 @@ func (pd *PictureData) Bounds() Rect {
 // Slice returns a sub-Picture of this PictureData inside the supplied rectangle.
 func (pd *PictureData) Slice(r Rect) Picture {
 	return &PictureData{
-		Pix:    pd.Pix[pd.offset(r.Pos):],
+		Pix:    pd.Pix[pd.offset(r.Min):],
 		Stride: pd.Stride,
 		Rect:   r,
 		Orig:   pd.Orig,
diff --git a/geometry.go b/geometry.go
index b2dfdb7b10b59dbcfad80b813e0c6b924978bd23..99504c10e7e9292d0fd3716dff6d0bdfa87abd31 100644
--- a/geometry.go
+++ b/geometry.go
@@ -83,6 +83,11 @@ func (u Vec) Scaled(c float64) Vec {
 	return u * V(c, 0)
 }
 
+// ScaledXY returns the vector u multiplied by vector v component-wise.
+func (u Vec) ScaledXY(v Vec) Vec {
+	return V(u.X()*v.X(), u.Y()*v.Y())
+}
+
 // Rotated returns the vector u rotated by the given angle in radians.
 func (u Vec) Rotated(angle float64) Vec {
 	sin, cos := math.Sincos(angle)
@@ -117,19 +122,34 @@ func Lerp(a, b Vec, t float64) Vec {
 	return a.Scaled(1-t) + b.Scaled(t)
 }
 
-// Rect is a 2D rectangle aligned with the axes of the coordinate system. It has a position
-// and a size.
+// Rect is a 2D rectangle aligned with the axes of the coordinate system. It is defined by two
+// points, Min and Max.
 //
-// You can manipulate the position and the size using the usual vector operations.
+// The invariant should hold, that Max's components are greater or equal than Min's components
+// respectively.
 type Rect struct {
-	Pos, Size Vec
+	Min, Max Vec
 }
 
-// R returns a new Rect with given position (x, y) and size (w, h).
-func R(x, y, w, h float64) Rect {
+// R returns a new Rect with given the Min and Max coordinates.
+func R(minX, minY, maxX, maxY float64) Rect {
 	return Rect{
-		Pos:  V(x, y),
-		Size: V(w, h),
+		Min: V(minX, minY),
+		Max: V(maxX, maxY),
+	}.Norm()
+}
+
+// Norm returns the Rect in normal form, such that Max is component-wise greater or equal than Min.
+func (r Rect) Norm() Rect {
+	return Rect{
+		Min: V(
+			math.Min(r.Min.X(), r.Max.X()),
+			math.Min(r.Min.Y(), r.Max.Y()),
+		),
+		Max: V(
+			math.Max(r.Min.X(), r.Max.X()),
+			math.Max(r.Min.Y(), r.Max.Y()),
+		),
 	}
 }
 
@@ -139,43 +159,63 @@ func R(x, y, w, h float64) Rect {
 //   r.String()     // returns "Rect(100, 50, 200, 300)"
 //   fmt.Println(r) // Rect(100, 50, 200, 300)
 func (r Rect) String() string {
-	return fmt.Sprintf("Rect(%v, %v, %v, %v)", r.X(), r.Y(), r.W(), r.H())
-}
-
-// X returns the x coordinate of the position of the rectangle.
-func (r Rect) X() float64 {
-	return r.Pos.X()
-}
-
-// Y returns the y coordinate of the position of the rectangle
-func (r Rect) Y() float64 {
-	return r.Pos.Y()
+	return fmt.Sprintf("Rect(%v, %v, %v, %v)", r.Min.X(), r.Min.Y(), r.Max.X(), r.Max.Y())
 }
 
 // W returns the width of the rectangle.
 func (r Rect) W() float64 {
-	return r.Size.X()
+	return r.Max.X() - r.Min.X()
 }
 
 // H returns the height of the rectangle.
 func (r Rect) H() float64 {
-	return r.Size.Y()
-}
-
-// XYWH returns all of the four components of the rectangle in four return values.
-func (r Rect) XYWH() (x, y, w, h float64) {
-	return r.X(), r.Y(), r.W(), r.H()
+	return r.Max.Y() - r.Min.Y()
 }
 
 // Center returns the position of the center of the rectangle.
 func (r Rect) Center() Vec {
-	return r.Pos + r.Size.Scaled(0.5)
+	return (r.Min + r.Max) / 2
+}
+
+// Moved returns the Rect moved (both Min and Max) by the given vector delta.
+func (r Rect) Moved(delta Vec) Rect {
+	return Rect{
+		Min: r.Min + delta,
+		Max: r.Max + delta,
+	}
+}
+
+// Resized returns the Rect resized to the given size while keeping the position of the given anchor.
+//   r.Resized(r.Min, size)      // resizes while keeping the position of the lower-left corner
+//   r.Resized(r.Max, size)      // same with the top-right corner
+//   r.Resized(r.Center(), size) // resizes around the center
+// This function does not make sense for size of zero area and will panic. Use ResizeMin in the case
+// of zero area.
+func (r Rect) Resized(anchor, size Vec) Rect {
+	if r.W()*r.H() == 0 || size.X()*size.Y() == 0 {
+		panic(fmt.Errorf("(%T).Resize: zero area", r))
+	}
+	fraction := size.ScaledXY(V(1/r.W(), 1/r.H()))
+	return Rect{
+		Min: anchor + (r.Min - anchor).ScaledXY(fraction),
+		Max: anchor + (r.Max - anchor).ScaledXY(fraction),
+	}
+}
+
+// ResizedMin returns the Rect resized to the given size while keeping the position of the Rect's
+// Min.
+//
+// Sizes of zero area are safe here.
+func (r Rect) ResizedMin(size Vec) Rect {
+	return Rect{
+		Min: r.Min,
+		Max: r.Min + size,
+	}
 }
 
 // Contains checks whether a vector u is contained within this Rect (including it's borders).
 func (r Rect) Contains(u Vec) bool {
-	min, max := r.Pos, r.Pos+r.Size
-	return min.X() <= u.X() && u.X() <= max.X() && min.Y() <= u.Y() && u.Y() <= max.Y()
+	return r.Min.X() <= u.X() && u.X() <= r.Max.X() && r.Min.Y() <= u.Y() && u.Y() <= r.Max.Y()
 }
 
 // Matrix is a 3x3 transformation matrix that can be used for all kinds of spacial transforms, such
@@ -200,8 +240,8 @@ func (m Matrix) Move(delta Vec) Matrix {
 	return Matrix(m3)
 }
 
-// ScaleXY scales everything around a given point by the scale factor in each axis respectively.
-func (m Matrix) ScaleXY(around Vec, scale Vec) Matrix {
+// ScaledXY scales everything around a given point by the scale factor in each axis respectively.
+func (m Matrix) ScaledXY(around Vec, scale Vec) Matrix {
 	m3 := mgl64.Mat3(m)
 	m3 = mgl64.Translate2D((-around).XY()).Mul3(m3)
 	m3 = mgl64.Scale2D(scale.XY()).Mul3(m3)
@@ -209,13 +249,13 @@ func (m Matrix) ScaleXY(around Vec, scale Vec) Matrix {
 	return Matrix(m3)
 }
 
-// Scale scales everything around a given point by the scale factor.
-func (m Matrix) Scale(around Vec, scale float64) Matrix {
-	return m.ScaleXY(around, V(scale, scale))
+// Scaled scales everything around a given point by the scale factor.
+func (m Matrix) Scaled(around Vec, scale float64) Matrix {
+	return m.ScaledXY(around, V(scale, scale))
 }
 
-// Rotate rotates everything around a given point by the given angle in radians.
-func (m Matrix) Rotate(around Vec, angle float64) Matrix {
+// Rotated rotates everything around a given point by the given angle in radians.
+func (m Matrix) Rotated(around Vec, angle float64) Matrix {
 	m3 := mgl64.Mat3(m)
 	m3 = mgl64.Translate2D((-around).XY()).Mul3(m3)
 	m3 = mgl64.Rotate3DZ(angle).Mul3(m3)
diff --git a/graphics.go b/graphics.go
index 5702306ddcc07b19aa86e430a52c0c84ee15b37d..a9477a9bddeed345a9b4e5c76512ef60597cef16 100644
--- a/graphics.go
+++ b/graphics.go
@@ -79,10 +79,10 @@ type IM struct {
 }
 
 type point struct {
-	position  Vec
-	color     NRGBA
-	picture   Vec
-	intensity float64
+	pos       Vec
+	col       NRGBA
+	pic       Vec
+	in        float64
 	width     float64
 	precision int
 	endshape  EndShape
@@ -130,25 +130,25 @@ func (im *IM) Draw(t Target) {
 func (im *IM) Push(pts ...Vec) {
 	point := im.opts
 	for _, pt := range pts {
-		point.position = im.matrix.Project(pt)
-		point.color = im.mask.Mul(im.opts.color)
+		point.pos = im.matrix.Project(pt)
+		point.col = im.mask.Mul(im.opts.col)
 		im.points = append(im.points, point)
 	}
 }
 
 // Color sets the color of the next Pushed points.
 func (im *IM) Color(color color.Color) {
-	im.opts.color = NRGBAModel.Convert(color).(NRGBA)
+	im.opts.col = NRGBAModel.Convert(color).(NRGBA)
 }
 
 // Picture sets the Picture coordinates of the next Pushed points.
 func (im *IM) Picture(pic Vec) {
-	im.opts.picture = pic
+	im.opts.pic = pic
 }
 
 // Intensity sets the picture Intensity of the next Pushed points.
 func (im *IM) Intensity(in float64) {
-	im.opts.intensity = in
+	im.opts.in = in
 }
 
 // Width sets the with property of the next Pushed points.
@@ -200,20 +200,20 @@ func (im *IM) FillConvexPolygon() {
 	im.tri.SetLen(im.tri.Len() + 3*(len(points)-2))
 
 	for j := 1; j+1 < len(points); j++ {
-		(*im.tri)[i].Position = points[0].position
-		(*im.tri)[i].Color = points[0].color
-		(*im.tri)[i].Picture = points[0].picture
-		(*im.tri)[i].Intensity = points[0].intensity
+		(*im.tri)[i].Position = points[0].pos
+		(*im.tri)[i].Color = points[0].col
+		(*im.tri)[i].Picture = points[0].pic
+		(*im.tri)[i].Intensity = points[0].in
 
-		(*im.tri)[i+1].Position = points[j].position
-		(*im.tri)[i+1].Color = points[j].color
-		(*im.tri)[i+1].Picture = points[j].picture
-		(*im.tri)[i+1].Intensity = points[j].intensity
+		(*im.tri)[i+1].Position = points[j].pos
+		(*im.tri)[i+1].Color = points[j].col
+		(*im.tri)[i+1].Picture = points[j].pic
+		(*im.tri)[i+1].Intensity = points[j].in
 
-		(*im.tri)[i+2].Position = points[j+1].position
-		(*im.tri)[i+2].Color = points[j+1].color
-		(*im.tri)[i+2].Picture = points[j+1].picture
-		(*im.tri)[i+2].Intensity = points[j+1].intensity
+		(*im.tri)[i+2].Position = points[j+1].pos
+		(*im.tri)[i+2].Color = points[j+1].col
+		(*im.tri)[i+2].Picture = points[j+1].pic
+		(*im.tri)[i+2].Intensity = points[j+1].in
 
 		i += 3
 	}
@@ -248,14 +248,14 @@ func (im *IM) FillEllipseArc(radius Vec, low, high float64) {
 	}
 
 	for _, pt := range points {
-		im.Push(pt.position) // center
+		im.Push(pt.pos) // center
 
 		num := math.Ceil(math.Abs(high-low) / (2 * math.Pi) * float64(pt.precision))
 		delta := (high - low) / num
 		for i := range im.tmp[:int(num)+1] {
 			angle := low + float64(i)*delta
 			sin, cos := math.Sincos(angle)
-			im.tmp[i] = pt.position + V(
+			im.tmp[i] = pt.pos + V(
 				radius.X()*cos,
 				radius.Y()*sin,
 			)
@@ -265,7 +265,3 @@ func (im *IM) FillEllipseArc(radius Vec, low, high float64) {
 		im.FillConvexPolygon()
 	}
 }
-
-func (im *IM) Line() {
-
-}
diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go
index 94bd34dcfd8b66ed576a2da160108272ed9b35f9..debfc2b17d56acb2269601aea6af97a512c5e9b9 100644
--- a/pixelgl/canvas.go
+++ b/pixelgl/canvas.go
@@ -105,8 +105,8 @@ func (c *Canvas) MakePicture(p pixel.Picture) pixel.TargetPicture {
 		for y := 0; y < bh; y++ {
 			for x := 0; x < bw; x++ {
 				at := pixel.V(
-					math.Max(float64(bx+x), bounds.Pos.X()),
-					math.Max(float64(by+y), bounds.Pos.Y()),
+					math.Max(float64(bx+x), bounds.Min.X()),
+					math.Max(float64(by+y), bounds.Min.Y()),
 				)
 				color := p.Color(at)
 				pixels[(y*bw+x)*4+0] = uint8(color.R * 255)
@@ -179,7 +179,7 @@ func (c *Canvas) SetBounds(bounds pixel.Rect) {
 		// preserve old content
 		if oldF != nil {
 			relBounds := bounds
-			relBounds.Pos -= c.orig.borders.Pos
+			relBounds = relBounds.Moved(-c.orig.borders.Min)
 			ox, oy, ow, oh := intBounds(relBounds)
 			oldF.Blit(
 				c.f,
@@ -216,7 +216,7 @@ func (c *Canvas) Smooth() bool {
 // must be manually called inside mainthread
 func (c *Canvas) setGlhfBounds() {
 	bounds := c.bounds
-	bounds.Pos -= c.orig.borders.Pos
+	bounds.Moved(c.orig.borders.Min)
 	bx, by, bw, bh := intBounds(bounds)
 	glhf.Bounds(bx, by, bw, bh)
 }
@@ -311,8 +311,8 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, borders, bounds pixel.Rect) {
 		ct.dst.s.Begin()
 
 		ct.dst.s.SetUniformAttr(canvasBounds, mgl32.Vec4{
-			float32(ct.dst.bounds.X()),
-			float32(ct.dst.bounds.Y()),
+			float32(ct.dst.bounds.Min.X()),
+			float32(ct.dst.bounds.Min.Y()),
 			float32(ct.dst.bounds.W()),
 			float32(ct.dst.bounds.H()),
 		})
@@ -327,14 +327,14 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, borders, bounds pixel.Rect) {
 			tex.Begin()
 
 			ct.dst.s.SetUniformAttr(canvasTexBorders, mgl32.Vec4{
-				float32(borders.X()),
-				float32(borders.Y()),
+				float32(borders.Min.X()),
+				float32(borders.Min.Y()),
 				float32(borders.W()),
 				float32(borders.H()),
 			})
 			ct.dst.s.SetUniformAttr(canvasTexBounds, mgl32.Vec4{
-				float32(bounds.X()),
-				float32(bounds.Y()),
+				float32(bounds.Min.X()),
+				float32(bounds.Min.Y()),
 				float32(bounds.W()),
 				float32(bounds.H()),
 			})
diff --git a/pixelgl/input.go b/pixelgl/input.go
index 26c914e5b21ae0a8c1baa46bd522013c4854127e..8153534d57ede5443ab92d05a197532f11e282fd 100644
--- a/pixelgl/input.go
+++ b/pixelgl/input.go
@@ -342,8 +342,8 @@ func (w *Window) initInput() {
 
 		w.window.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) {
 			w.currInp.mouse = pixel.V(
-				x+w.bounds.X(),
-				(w.bounds.H()-y)+w.bounds.Y(),
+				x+w.bounds.Min.X(),
+				(w.bounds.H()-y)+w.bounds.Min.Y(),
 			)
 		})
 
diff --git a/pixelgl/util.go b/pixelgl/util.go
index 0d4195debff069308a068421ef4d21950287131b..a0ed593816d6f97321053e8ddd54610bfbac2cc4 100644
--- a/pixelgl/util.go
+++ b/pixelgl/util.go
@@ -7,9 +7,9 @@ import (
 )
 
 func intBounds(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()))
+	x0 := int(math.Floor(bounds.Min.X()))
+	y0 := int(math.Floor(bounds.Min.Y()))
+	x1 := int(math.Ceil(bounds.Max.X()))
+	y1 := int(math.Ceil(bounds.Max.Y()))
 	return x0, y0, x1 - x0, y1 - y0
 }
diff --git a/pixelgl/window.go b/pixelgl/window.go
index 70dc422a366b951dbbefd3949af689217dfc0608..998029517a213870d5aac0db725f1e1bfcb036a9 100644
--- a/pixelgl/window.go
+++ b/pixelgl/window.go
@@ -2,7 +2,6 @@ package pixelgl
 
 import (
 	"image/color"
-	"math"
 	"runtime"
 
 	"github.com/faiface/glhf"
@@ -147,14 +146,7 @@ func (w *Window) Destroy() {
 func (w *Window) Update() {
 	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.bounds = w.bounds.ResizedMin(pixel.V(float64(wi), float64(hi)))
 	})
 
 	w.canvas.SetBounds(w.Bounds())