image: replace Width and Height by Bounds, and introduce the Point and
Rect types.

The actual image representation is unchanged. A future change will
replace the {[][]color} with {[]color, stride int, r Rectangle} and
possibly a clip region.

The draw.Color, draw.Point and draw.Rect types will be removed in a
future change. Trying to do it in this one polluted the diff with
trivia.

R=r, rsc
CC=golang-dev
https://golang.org/cl/1918047
diff --git a/src/pkg/exp/4s/xs.go b/src/pkg/exp/4s/xs.go
index c5493e7..8f6d62f 100644
--- a/src/pkg/exp/4s/xs.go
+++ b/src/pkg/exp/4s/xs.go
@@ -669,7 +669,7 @@
 	//	if new && getwindow(display, Refmesg) < 0 {
 	//		sysfatal("can't reattach to window");
 	//	}
-	r := draw.Rect(0, 0, screen.Width(), screen.Height())
+	r := draw.Rect(screen.Bounds().MinX, screen.Bounds().Min.Y, screen, Bounds().Max.X, screen.Bounds().Max.Y)
 	pos.X = (pos.X - rboard.Min.X) / pcsz
 	pos.Y = (pos.Y - rboard.Min.Y) / pcsz
 	dx := r.Max.X - r.Min.X
@@ -722,7 +722,7 @@
 func Play(pp []Piece, ctxt draw.Context) {
 	display = ctxt
 	screen = ctxt.Screen()
-	screenr = draw.Rect(0, 0, screen.Width(), screen.Height())
+	screenr = draw.Rect(screen.Bounds().MinX, screen.Bounds().Min.Y, screen, Bounds().Max.X, screen.Bounds().Max.Y)
 	pieces = pp
 	N = len(pieces[0].d)
 	initPieces()
diff --git a/src/pkg/exp/draw/color.go b/src/pkg/exp/draw/color.go
index 3fe7b4a..5fb543a 100644
--- a/src/pkg/exp/draw/color.go
+++ b/src/pkg/exp/draw/color.go
@@ -88,9 +88,7 @@
 	return r<<24 | g<<16 | b<<8 | Color(a)
 }
 
-func (c Color) Width() int { return 1e9 }
-
-func (c Color) Height() int { return 1e9 }
+func (c Color) Bounds() image.Rectangle { return image.Rect(0, 0, 1e9, 1e9) }
 
 func (c Color) At(x, y int) image.Color { return c }
 
diff --git a/src/pkg/exp/draw/draw.go b/src/pkg/exp/draw/draw.go
index 415dd99..636501c 100644
--- a/src/pkg/exp/draw/draw.go
+++ b/src/pkg/exp/draw/draw.go
@@ -43,13 +43,15 @@
 // The implementation is simple and slow.
 // TODO(nigeltao): Optimize this.
 func DrawMask(dst Image, r Rectangle, src image.Image, sp Point, mask image.Image, mp Point, op Op) {
-	dx, dy := src.Width()-sp.X, src.Height()-sp.Y
+	sb := src.Bounds()
+	dx, dy := sb.Dx()-sp.X, sb.Dy()-sp.Y
 	if mask != nil {
-		if dx > mask.Width()-mp.X {
-			dx = mask.Width() - mp.X
+		mb := mask.Bounds()
+		if dx > mb.Dx()-mp.X {
+			dx = mb.Dx() - mp.X
 		}
-		if dy > mask.Height()-mp.Y {
-			dy = mask.Height() - mp.Y
+		if dy > mb.Dy()-mp.Y {
+			dy = mb.Dy() - mp.Y
 		}
 	}
 	if r.Dx() > dx {
diff --git a/src/pkg/exp/draw/draw_test.go b/src/pkg/exp/draw/draw_test.go
index e9fde25..5b503f8 100644
--- a/src/pkg/exp/draw/draw_test.go
+++ b/src/pkg/exp/draw/draw_test.go
@@ -97,10 +97,11 @@
 func makeGolden(dst image.Image, t drawTest) image.Image {
 	// Since golden is a newly allocated image, we don't have to check if the
 	// input source and mask images and the output golden image overlap.
-	golden := image.NewRGBA(dst.Width(), dst.Height())
-	for y := 0; y < golden.Height(); y++ {
+	b := dst.Bounds()
+	golden := image.NewRGBA(b.Dx(), b.Dy())
+	for y := b.Min.Y; y < b.Max.Y; y++ {
 		my, sy := y, y
-		for x := 0; x < golden.Width(); x++ {
+		for x := b.Min.X; x < b.Max.X; x++ {
 			mx, sx := x, x
 			const M = 1<<16 - 1
 			var dr, dg, db, da uint32
@@ -129,9 +130,14 @@
 	for _, test := range drawTests {
 		dst := hgradRed(255)
 		// Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
+		b := dst.Bounds()
 		golden := makeGolden(dst, test)
+		if !b.Eq(golden.Bounds()) {
+			t.Errorf("draw %s: bounds %v versus %v", test.desc, dst.Bounds(), golden.Bounds())
+			continue
+		}
 		// Draw the same combination onto the actual dst using the optimized DrawMask implementation.
-		DrawMask(dst, Rect(0, 0, dst.Width(), dst.Height()), test.src, ZP, test.mask, ZP, test.op)
+		DrawMask(dst, Rect(b.Min.X, b.Min.Y, b.Max.X, b.Max.Y), test.src, ZP, test.mask, ZP, test.op)
 		// Check that the resultant pixel at (8, 8) matches what we expect
 		// (the expected value can be verified by hand).
 		if !eq(dst.At(8, 8), test.expected) {
@@ -139,8 +145,8 @@
 			continue
 		}
 		// Check that the resultant dst image matches the golden output.
-		for y := 0; y < golden.Height(); y++ {
-			for x := 0; x < golden.Width(); x++ {
+		for y := b.Min.Y; y < b.Max.Y; y++ {
+			for x := b.Min.X; x < b.Max.X; x++ {
 				if !eq(dst.At(x, y), golden.At(x, y)) {
 					t.Errorf("draw %s: at (%d, %d), %v versus golden %v", test.desc, x, y, dst.At(x, y), golden.At(x, y))
 					continue loop
diff --git a/src/pkg/exp/draw/x11/conn.go b/src/pkg/exp/draw/x11/conn.go
index 979ce2b..eb498cf 100644
--- a/src/pkg/exp/draw/x11/conn.go
+++ b/src/pkg/exp/draw/x11/conn.go
@@ -67,14 +67,17 @@
 			return
 		}
 
+		b := c.img.Bounds()
+		if b.Empty() {
+			continue
+		}
 		// Each X request has a 16-bit length (in terms of 4-byte units). To avoid going over
 		// this limit, we send PutImage for each row of the image, rather than trying to paint
 		// the entire image in one X request. This approach could easily be optimized (or the
 		// X protocol may have an escape sequence to delimit very large requests).
 		// TODO(nigeltao): See what XCB's xcb_put_image does in this situation.
-		w, h := c.img.Width(), c.img.Height()
-		units := 6 + w
-		if units > 0xffff || h > 0xffff {
+		units := 6 + b.Dx()
+		if units > 0xffff || b.Dy() > 0xffff {
 			// This window is too large for X.
 			close(c.flush)
 			return
@@ -86,10 +89,10 @@
 		c.flushBuf0[3] = uint8(units >> 8)
 		setU32LE(c.flushBuf0[4:8], uint32(c.window))
 		setU32LE(c.flushBuf0[8:12], uint32(c.gc))
-		setU32LE(c.flushBuf0[12:16], 1<<16|uint32(w))
+		setU32LE(c.flushBuf0[12:16], 1<<16|uint32(b.Dx()))
 		c.flushBuf0[21] = 0x18 // depth = 24 bits.
 
-		for y := 0; y < h; y++ {
+		for y := b.Min.Y; y < b.Max.Y; y++ {
 			setU32LE(c.flushBuf0[16:20], uint32(y<<16))
 			_, err := c.w.Write(c.flushBuf0[0:24])
 			if err != nil {
@@ -97,8 +100,8 @@
 				return
 			}
 			p := c.img.Pixel[y]
-			for x := 0; x < w; {
-				nx := w - x
+			for x := b.Min.X; x < b.Max.X; {
+				nx := b.Max.X - x
 				if nx > len(c.flushBuf1)/4 {
 					nx = len(c.flushBuf1) / 4
 				}
diff --git a/src/pkg/exp/nacl/av/image.go b/src/pkg/exp/nacl/av/image.go
index 8de5311..4c4c558 100644
--- a/src/pkg/exp/nacl/av/image.go
+++ b/src/pkg/exp/nacl/av/image.go
@@ -24,15 +24,13 @@
 
 func (m *Image) ColorModel() image.ColorModel { return ColorModel }
 
-func (m *Image) Width() int {
+func (m *Image) Bounds() image.Rectangle {
 	if len(m.Pixel) == 0 {
-		return 0
+		return image.ZR
 	}
-	return len(m.Pixel[0])
+	return image.Rectangle{image.ZP, image.Point{len(m.Pixel[0]), len(m.Pixel)}}
 }
 
-func (m *Image) Height() int { return len(m.Pixel) }
-
 func (m *Image) At(x, y int) image.Color { return m.Pixel[y][x] }
 
 func (m *Image) Set(x, y int, color image.Color) {
diff --git a/src/pkg/exp/spacewar/spacewar.go b/src/pkg/exp/spacewar/spacewar.go
index 7333220e..e7a1560 100644
--- a/src/pkg/exp/spacewar/spacewar.go
+++ b/src/pkg/exp/spacewar/spacewar.go
@@ -99,8 +99,8 @@
 	m.ctxt = ctxt
 	m.kc = ctxt.KeyboardChan()
 	m.screen = ctxt.Screen()
-	m.dx = m.screen.Width()
-	m.dy = m.screen.Height()
+	m.dx = m.screen.Bounds().Dx()
+	m.dy = m.screen.Bounds().Dy()
 	m.colorModel = m.screen.ColorModel()
 	m.pix = make([][]uint8, m.dy)
 	for i := range m.pix {
diff --git a/src/pkg/image/Makefile b/src/pkg/image/Makefile
index e26deea..9015ed6 100644
--- a/src/pkg/image/Makefile
+++ b/src/pkg/image/Makefile
@@ -8,6 +8,7 @@
 GOFILES=\
 	color.go\
 	format.go\
+	geom.go\
 	image.go\
 	names.go\
 
diff --git a/src/pkg/image/geom.go b/src/pkg/image/geom.go
new file mode 100644
index 0000000..ecf0521
--- /dev/null
+++ b/src/pkg/image/geom.go
@@ -0,0 +1,125 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package image
+
+import (
+	"strconv"
+)
+
+// A Point is an X, Y coordinate pair. The axes increase right and down.
+type Point struct {
+	X, Y int
+}
+
+// String returns a string representation of p like "(3,4)".
+func (p Point) String() string {
+	return "(" + strconv.Itoa(p.X) + "," + strconv.Itoa(p.Y) + ")"
+}
+
+// Add returns the vector p+q.
+func (p Point) Add(q Point) Point {
+	return Point{p.X + q.X, p.Y + q.Y}
+}
+
+// Sub returns the vector p-q.
+func (p Point) Sub(q Point) Point {
+	return Point{p.X - q.X, p.Y - q.Y}
+}
+
+// ZP is the zero Point.
+var ZP Point
+
+// Pt is shorthand for Point{X, Y}.
+func Pt(X, Y int) Point {
+	return Point{X, Y}
+}
+
+// A Rectangle contains the points with Min.X <= X < Max.X, Min.Y <= Y < Max.Y.
+type Rectangle struct {
+	Min, Max Point
+}
+
+// String returns a string representation of r like "(3,4)-(6,5)".
+func (r Rectangle) String() string {
+	return r.Min.String() + "-" + r.Max.String()
+}
+
+// Dx returns r's width.
+func (r Rectangle) Dx() int {
+	return r.Max.X - r.Min.X
+}
+
+// Dy returns r's height.
+func (r Rectangle) Dy() int {
+	return r.Max.Y - r.Min.Y
+}
+
+// Add returns the rectangle r translated by p.
+func (r Rectangle) Add(p Point) Rectangle {
+	return Rectangle{
+		Point{r.Min.X + p.X, r.Min.Y + p.Y},
+		Point{r.Max.X + p.X, r.Max.Y + p.Y},
+	}
+}
+
+// Add returns the rectangle r translated by -p.
+func (r Rectangle) Sub(p Point) Rectangle {
+	return Rectangle{
+		Point{r.Min.X - p.X, r.Min.Y - p.Y},
+		Point{r.Max.X - p.X, r.Max.Y - p.Y},
+	}
+}
+
+// Inset returns the rectangle r inset by n, which may be negative.
+func (r Rectangle) Inset(n int) Rectangle {
+	return Rectangle{
+		Point{r.Min.X + n, r.Min.Y + n},
+		Point{r.Max.X - n, r.Max.Y - n},
+	}
+}
+
+// Empty returns whether the rectangle contains no points.
+func (r Rectangle) Empty() bool {
+	return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y
+}
+
+// Eq returns whether r and s are equal.
+func (r Rectangle) Eq(s Rectangle) bool {
+	return r.Min.X == s.Min.X && r.Min.Y == s.Min.Y &&
+		r.Max.X == s.Max.X && r.Max.Y == s.Max.Y
+}
+
+// Overlaps returns whether r and s have a non-empty intersection.
+func (r Rectangle) Overlaps(s Rectangle) bool {
+	return r.Min.X < s.Max.X && s.Min.X < r.Max.X &&
+		r.Min.Y < s.Max.Y && s.Min.Y < r.Max.Y
+}
+
+// Canon returns the canonical version of r. The returned rectangle has
+// minimum and maximum coordinates swapped if necessary so that Min.X <= Max.X
+// and Min.Y <= Max.Y.
+func (r Rectangle) Canon() Rectangle {
+	if r.Max.X < r.Min.X {
+		r.Min.X, r.Max.X = r.Max.X, r.Min.X
+	}
+	if r.Max.Y < r.Min.Y {
+		r.Min.Y, r.Max.Y = r.Max.Y, r.Min.Y
+	}
+	return r
+}
+
+// ZR is the zero Rectangle.
+var ZR Rectangle
+
+// Rect is shorthand for Rectangle{Pt(x0, y0), Pt(x1, y1)}.
+func Rect(x0, y0, x1, y1 int) Rectangle {
+	if x0 > x1 {
+		x0, x1 = x1, x0
+	}
+	if y0 > y1 {
+		y0, y1 = y1, y0
+	}
+	return Rectangle{Point{x0, y0}, Point{x1, y1}}
+}
diff --git a/src/pkg/image/image.go b/src/pkg/image/image.go
index cfe4427..c352da2 100644
--- a/src/pkg/image/image.go
+++ b/src/pkg/image/image.go
@@ -5,13 +5,16 @@
 // The image package implements a basic 2-D image library.
 package image
 
-// An Image is a rectangular grid of Colors drawn from a ColorModel.
+// An Image is a finite rectangular grid of Colors drawn from a ColorModel.
 type Image interface {
+	// ColorModel returns the Image's ColorModel.
 	ColorModel() ColorModel
-	Width() int
-	Height() int
-	// At(0, 0) returns the upper-left pixel of the grid.
-	// At(Width()-1, Height()-1) returns the lower-right pixel.
+	// Bounds returns the domain for which At can return non-zero color.
+	// The bounds do not necessarily contain the point (0, 0).
+	Bounds() Rectangle
+	// At returns the color of the pixel at (x, y).
+	// At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid.
+	// At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one.
 	At(x, y int) Color
 }
 
@@ -23,18 +26,24 @@
 
 func (p *RGBA) ColorModel() ColorModel { return RGBAColorModel }
 
-func (p *RGBA) Width() int {
+func (p *RGBA) Bounds() Rectangle {
 	if len(p.Pixel) == 0 {
-		return 0
+		return ZR
 	}
-	return len(p.Pixel[0])
+	return Rectangle{ZP, Point{len(p.Pixel[0]), len(p.Pixel)}}
 }
 
-func (p *RGBA) Height() int { return len(p.Pixel) }
+func (p *RGBA) At(x, y int) Color {
+	// TODO(nigeltao): Check if (x,y) is outside the bounds, and return zero.
+	// Similarly for the other concrete image types.
+	return p.Pixel[y][x]
+}
 
-func (p *RGBA) At(x, y int) Color { return p.Pixel[y][x] }
-
-func (p *RGBA) Set(x, y int, c Color) { p.Pixel[y][x] = toRGBAColor(c).(RGBAColor) }
+func (p *RGBA) Set(x, y int, c Color) {
+	// TODO(nigeltao): Check if (x,y) is outside the bounds, and return.
+	// Similarly for the other concrete image types.
+	p.Pixel[y][x] = toRGBAColor(c).(RGBAColor)
+}
 
 // Opaque scans the entire image and returns whether or not it is fully opaque.
 func (p *RGBA) Opaque() bool {
@@ -71,15 +80,13 @@
 
 func (p *RGBA64) ColorModel() ColorModel { return RGBA64ColorModel }
 
-func (p *RGBA64) Width() int {
+func (p *RGBA64) Bounds() Rectangle {
 	if len(p.Pixel) == 0 {
-		return 0
+		return ZR
 	}
-	return len(p.Pixel[0])
+	return Rectangle{ZP, Point{len(p.Pixel[0]), len(p.Pixel)}}
 }
 
-func (p *RGBA64) Height() int { return len(p.Pixel) }
-
 func (p *RGBA64) At(x, y int) Color { return p.Pixel[y][x] }
 
 func (p *RGBA64) Set(x, y int, c Color) { p.Pixel[y][x] = toRGBA64Color(c).(RGBA64Color) }
@@ -119,15 +126,13 @@
 
 func (p *NRGBA) ColorModel() ColorModel { return NRGBAColorModel }
 
-func (p *NRGBA) Width() int {
+func (p *NRGBA) Bounds() Rectangle {
 	if len(p.Pixel) == 0 {
-		return 0
+		return ZR
 	}
-	return len(p.Pixel[0])
+	return Rectangle{ZP, Point{len(p.Pixel[0]), len(p.Pixel)}}
 }
 
-func (p *NRGBA) Height() int { return len(p.Pixel) }
-
 func (p *NRGBA) At(x, y int) Color { return p.Pixel[y][x] }
 
 func (p *NRGBA) Set(x, y int, c Color) { p.Pixel[y][x] = toNRGBAColor(c).(NRGBAColor) }
@@ -167,15 +172,13 @@
 
 func (p *NRGBA64) ColorModel() ColorModel { return NRGBA64ColorModel }
 
-func (p *NRGBA64) Width() int {
+func (p *NRGBA64) Bounds() Rectangle {
 	if len(p.Pixel) == 0 {
-		return 0
+		return ZR
 	}
-	return len(p.Pixel[0])
+	return Rectangle{ZP, Point{len(p.Pixel[0]), len(p.Pixel)}}
 }
 
-func (p *NRGBA64) Height() int { return len(p.Pixel) }
-
 func (p *NRGBA64) At(x, y int) Color { return p.Pixel[y][x] }
 
 func (p *NRGBA64) Set(x, y int, c Color) { p.Pixel[y][x] = toNRGBA64Color(c).(NRGBA64Color) }
@@ -215,15 +218,13 @@
 
 func (p *Alpha) ColorModel() ColorModel { return AlphaColorModel }
 
-func (p *Alpha) Width() int {
+func (p *Alpha) Bounds() Rectangle {
 	if len(p.Pixel) == 0 {
-		return 0
+		return ZR
 	}
-	return len(p.Pixel[0])
+	return Rectangle{ZP, Point{len(p.Pixel[0]), len(p.Pixel)}}
 }
 
-func (p *Alpha) Height() int { return len(p.Pixel) }
-
 func (p *Alpha) At(x, y int) Color { return p.Pixel[y][x] }
 
 func (p *Alpha) Set(x, y int, c Color) { p.Pixel[y][x] = toAlphaColor(c).(AlphaColor) }
@@ -263,15 +264,13 @@
 
 func (p *Alpha16) ColorModel() ColorModel { return Alpha16ColorModel }
 
-func (p *Alpha16) Width() int {
+func (p *Alpha16) Bounds() Rectangle {
 	if len(p.Pixel) == 0 {
-		return 0
+		return ZR
 	}
-	return len(p.Pixel[0])
+	return Rectangle{ZP, Point{len(p.Pixel[0]), len(p.Pixel)}}
 }
 
-func (p *Alpha16) Height() int { return len(p.Pixel) }
-
 func (p *Alpha16) At(x, y int) Color { return p.Pixel[y][x] }
 
 func (p *Alpha16) Set(x, y int, c Color) { p.Pixel[y][x] = toAlpha16Color(c).(Alpha16Color) }
@@ -311,15 +310,13 @@
 
 func (p *Gray) ColorModel() ColorModel { return GrayColorModel }
 
-func (p *Gray) Width() int {
+func (p *Gray) Bounds() Rectangle {
 	if len(p.Pixel) == 0 {
-		return 0
+		return ZR
 	}
-	return len(p.Pixel[0])
+	return Rectangle{ZP, Point{len(p.Pixel[0]), len(p.Pixel)}}
 }
 
-func (p *Gray) Height() int { return len(p.Pixel) }
-
 func (p *Gray) At(x, y int) Color { return p.Pixel[y][x] }
 
 func (p *Gray) Set(x, y int, c Color) { p.Pixel[y][x] = toGrayColor(c).(GrayColor) }
@@ -347,15 +344,13 @@
 
 func (p *Gray16) ColorModel() ColorModel { return Gray16ColorModel }
 
-func (p *Gray16) Width() int {
+func (p *Gray16) Bounds() Rectangle {
 	if len(p.Pixel) == 0 {
-		return 0
+		return ZR
 	}
-	return len(p.Pixel[0])
+	return Rectangle{ZP, Point{len(p.Pixel[0]), len(p.Pixel)}}
 }
 
-func (p *Gray16) Height() int { return len(p.Pixel) }
-
 func (p *Gray16) At(x, y int) Color { return p.Pixel[y][x] }
 
 func (p *Gray16) Set(x, y int, c Color) { p.Pixel[y][x] = toGray16Color(c).(Gray16Color) }
@@ -421,15 +416,13 @@
 
 func (p *Paletted) ColorModel() ColorModel { return p.Palette }
 
-func (p *Paletted) Width() int {
+func (p *Paletted) Bounds() Rectangle {
 	if len(p.Pixel) == 0 {
-		return 0
+		return ZR
 	}
-	return len(p.Pixel[0])
+	return Rectangle{ZP, Point{len(p.Pixel[0]), len(p.Pixel)}}
 }
 
-func (p *Paletted) Height() int { return len(p.Pixel) }
-
 func (p *Paletted) At(x, y int) Color { return p.Palette[p.Pixel[y][x]] }
 
 func (p *Paletted) ColorIndexAt(x, y int) uint8 {
diff --git a/src/pkg/image/names.go b/src/pkg/image/names.go
index a5b4e48..198ac93 100644
--- a/src/pkg/image/names.go
+++ b/src/pkg/image/names.go
@@ -25,9 +25,7 @@
 	return ColorModelFunc(func(Color) Color { return c.C })
 }
 
-func (c ColorImage) Width() int { return 1e9 }
-
-func (c ColorImage) Height() int { return 1e9 }
+func (c ColorImage) Bounds() Rectangle { return Rectangle{ZP, Point{1e9, 1e9}} }
 
 func (c ColorImage) At(x, y int) Color { return c.C }
 
diff --git a/src/pkg/image/png/reader_test.go b/src/pkg/image/png/reader_test.go
index 1dc4599..f53d114 100644
--- a/src/pkg/image/png/reader_test.go
+++ b/src/pkg/image/png/reader_test.go
@@ -45,12 +45,13 @@
 // An approximation of the sng command-line tool.
 func sng(w io.WriteCloser, filename string, png image.Image) {
 	defer w.Close()
+	bounds := png.Bounds()
 	// For now, the go PNG parser only reads bitdepths of 8.
 	bitdepth := 8
 
 	// Write the filename and IHDR.
 	io.WriteString(w, "#SNG: from "+filename+".png\nIHDR {\n")
-	fmt.Fprintf(w, "    width: %d; height: %d; bitdepth: %d;\n", png.Width(), png.Height(), bitdepth)
+	fmt.Fprintf(w, "    width: %d; height: %d; bitdepth: %d;\n", bounds.Dx(), bounds.Dy(), bitdepth)
 	cm := png.ColorModel()
 	var paletted *image.Paletted
 	cpm, _ := cm.(image.PalettedColorModel)
@@ -86,20 +87,20 @@
 
 	// Write the IMAGE.
 	io.WriteString(w, "IMAGE {\n    pixels hex\n")
-	for y := 0; y < png.Height(); y++ {
+	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
 		switch {
 		case cm == image.RGBAColorModel:
-			for x := 0; x < png.Width(); x++ {
+			for x := bounds.Min.X; x < bounds.Max.X; x++ {
 				rgba := png.At(x, y).(image.RGBAColor)
 				fmt.Fprintf(w, "%02x%02x%02x ", rgba.R, rgba.G, rgba.B)
 			}
 		case cm == image.NRGBAColorModel:
-			for x := 0; x < png.Width(); x++ {
+			for x := bounds.Min.X; x < bounds.Max.X; x++ {
 				nrgba := png.At(x, y).(image.NRGBAColor)
 				fmt.Fprintf(w, "%02x%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B, nrgba.A)
 			}
 		case cpm != nil:
-			for x := 0; x < png.Width(); x++ {
+			for x := bounds.Min.X; x < bounds.Max.X; x++ {
 				fmt.Fprintf(w, "%02x", paletted.ColorIndexAt(x, y))
 			}
 		}
diff --git a/src/pkg/image/png/writer.go b/src/pkg/image/png/writer.go
index 323e66f..3ce30c8 100644
--- a/src/pkg/image/png/writer.go
+++ b/src/pkg/image/png/writer.go
@@ -41,8 +41,9 @@
 	if o, ok := m.(opaquer); ok {
 		return o.Opaque()
 	}
-	for y := 0; y < m.Height(); y++ {
-		for x := 0; x < m.Width(); x++ {
+	b := m.Bounds()
+	for y := b.Min.Y; y < b.Max.Y; y++ {
+		for x := b.Min.X; x < b.Max.X; x++ {
 			_, _, _, a := m.At(x, y).RGBA()
 			if a != 0xffff {
 				return false
@@ -91,8 +92,9 @@
 }
 
 func (e *encoder) writeIHDR() {
-	writeUint32(e.tmp[0:4], uint32(e.m.Width()))
-	writeUint32(e.tmp[4:8], uint32(e.m.Height()))
+	b := e.m.Bounds()
+	writeUint32(e.tmp[0:4], uint32(b.Dx()))
+	writeUint32(e.tmp[4:8], uint32(b.Dy()))
 	e.tmp[8] = 8 // bit depth
 	e.tmp[9] = e.colorType
 	e.tmp[10] = 0 // default compression method
@@ -254,18 +256,19 @@
 	// cr[ft], for non-zero filter types ft, are buffers for transforming cr[0] under the
 	// other PNG filter types. These buffers are allocated once and re-used for each row.
 	// The +1 is for the per-row filter type, which is at cr[*][0].
+	b := m.Bounds()
 	var cr [nFilter][]uint8
 	for i := 0; i < len(cr); i++ {
-		cr[i] = make([]uint8, 1+bpp*m.Width())
+		cr[i] = make([]uint8, 1+bpp*b.Dx())
 		cr[i][0] = uint8(i)
 	}
-	pr := make([]uint8, 1+bpp*m.Width())
+	pr := make([]uint8, 1+bpp*b.Dx())
 
-	for y := 0; y < m.Height(); y++ {
+	for y := b.Min.Y; y < b.Max.Y; y++ {
 		// Convert from colors to bytes.
 		switch ct {
 		case ctTrueColor:
-			for x := 0; x < m.Width(); x++ {
+			for x := b.Min.X; x < b.Max.X; x++ {
 				// We have previously verified that the alpha value is fully opaque.
 				r, g, b, _ := m.At(x, y).RGBA()
 				cr[0][3*x+1] = uint8(r >> 8)
@@ -273,12 +276,12 @@
 				cr[0][3*x+3] = uint8(b >> 8)
 			}
 		case ctPaletted:
-			for x := 0; x < m.Width(); x++ {
+			for x := b.Min.X; x < b.Max.X; x++ {
 				cr[0][x+1] = paletted.ColorIndexAt(x, y)
 			}
 		case ctTrueColorAlpha:
 			// Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.
-			for x := 0; x < m.Width(); x++ {
+			for x := b.Min.X; x < b.Max.X; x++ {
 				c := image.NRGBAColorModel.Convert(m.At(x, y)).(image.NRGBAColor)
 				cr[0][4*x+1] = c.R
 				cr[0][4*x+2] = c.G
@@ -327,7 +330,7 @@
 	// Obviously, negative widths and heights are invalid. Furthermore, the PNG
 	// spec section 11.2.2 says that zero is invalid. Excessively large images are
 	// also rejected.
-	mw, mh := int64(m.Width()), int64(m.Height())
+	mw, mh := int64(m.Bounds().Dx()), int64(m.Bounds().Dy())
 	if mw <= 0 || mh <= 0 || mw >= 1<<32 || mh >= 1<<32 {
 		return FormatError("invalid image size: " + strconv.Itoa64(mw) + "x" + strconv.Itoa64(mw))
 	}
diff --git a/src/pkg/image/png/writer_test.go b/src/pkg/image/png/writer_test.go
index a61e1c9..f25873e 100644
--- a/src/pkg/image/png/writer_test.go
+++ b/src/pkg/image/png/writer_test.go
@@ -13,11 +13,12 @@
 )
 
 func diff(m0, m1 image.Image) os.Error {
-	if m0.Width() != m1.Width() || m0.Height() != m1.Height() {
-		return os.NewError(fmt.Sprintf("dimensions differ: %dx%d vs %dx%d", m0.Width(), m0.Height(), m1.Width(), m1.Height()))
+	b0, b1 := m0.Bounds(), m1.Bounds()
+	if !b0.Eq(b1) {
+		return os.NewError(fmt.Sprintf("dimensions differ: %v vs %v", b0, b1))
 	}
-	for y := 0; y < m0.Height(); y++ {
-		for x := 0; x < m0.Width(); x++ {
+	for y := b0.Min.Y; y < b0.Max.Y; y++ {
+		for x := b0.Min.X; x < b0.Max.X; x++ {
 			r0, g0, b0, a0 := m0.At(x, y).RGBA()
 			r1, g1, b1, a1 := m1.At(x, y).RGBA()
 			if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {