blob: aa2f77a1b6489a859680f208bdcda3efddaadb24 [file] [log] [blame]
Russ Coxaf5018f2020-03-09 23:54:35 -04001# The Go image/draw package
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100229 Sep 2011
Andrew Gerrandb316fcd2013-06-05 09:59:16 +10003Tags: draw, image, libraries, technical
Russ Coxfaf1e2d2020-03-14 09:44:01 -04004Summary: An introduction to image compositing in Go using the image/draw package.
Russ Cox972d42d2020-03-15 15:50:36 -04005OldURL: /go-imagedraw-package
Andrew Gerranddb9a09f2013-03-08 11:17:09 +11006
7Nigel Tao
8
Russ Coxaf5018f2020-03-09 23:54:35 -04009## Introduction
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110010
Russ Coxaf5018f2020-03-09 23:54:35 -040011[Package image/draw](https://golang.org/pkg/image/draw/) defines only one operation:
Russ Cox482079d2020-03-09 22:11:04 -040012drawing a source image onto a destination image,
13through an optional mask image.
14This one operation is surprisingly versatile and can perform a number of
15common image manipulation tasks elegantly and efficiently.
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110016
Russ Cox482079d2020-03-09 22:11:04 -040017Composition is performed pixel by pixel in the style of the Plan 9 graphics
18library and the X Render extension.
19The model is based on the classic "Compositing Digital Images" paper by Porter and Duff,
20with an additional mask parameter:
Russ Coxaf5018f2020-03-09 23:54:35 -040021`dst = (src IN mask) OP dst`.
22For a fully opaque mask, this reduces to the original Porter-Duff formula: `dst = src OP dst`.
Russ Cox482079d2020-03-09 22:11:04 -040023In Go, a nil mask image is equivalent to an infinitely sized,
24fully opaque mask image.
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110025
Russ Coxaf5018f2020-03-09 23:54:35 -040026The Porter-Duff paper presented [12 different composition operators](http://www.w3.org/TR/SVGCompositing/examples/compop-porterduff-examples.png),
Russ Cox482079d2020-03-09 22:11:04 -040027but with an explicit mask, only 2 of these are needed in practice:
28source-over-destination and source.
29In Go, these operators are represented by the `Over` and `Src` constants.
30The `Over` operator performs the natural layering of a source image over
31a destination image:
32the change to the destination image is smaller where the source (after masking)
33is more transparent (that is, has lower alpha).
34The `Src` operator merely copies the source (after masking) with no regard
35for the destination image's original content.
36For fully opaque source and mask images, the two operators produce the same output,
37but the `Src` operator is usually faster.
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110038
Russ Coxaf5018f2020-03-09 23:54:35 -040039## Geometric Alignment
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110040
Russ Cox482079d2020-03-09 22:11:04 -040041Composition requires associating destination pixels with source and mask pixels.
42Obviously, this requires destination, source and mask images,
43and a composition operator, but it also requires specifying what rectangle
44of each image to use.
45Not every drawing should write to the entire destination:
46when updating an animating image, it is more efficient to only draw the
47parts of the image that have changed.
48Not every drawing should read from the entire source:
49when using a sprite that combines many small images into one large one,
50only a part of the image is needed.
51Not every drawing should read from the entire mask:
52a mask image that collects a font's glyphs is similar to a sprite.
53Thus, drawing also needs to know three rectangles, one for each image.
54Since each rectangle has the same width and height,
55it suffices to pass a destination rectangle `r` and two points `sp` and `mp`:
56the source rectangle is equal to `r` translated so that `r.Min` in the destination
57image aligns with `sp` in the source image,
58and similarly for `mp`.
59The effective rectangle is also clipped to each image's bounds in their
60respective co-ordinate space.
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110061
Russ Cox972d42d2020-03-15 15:50:36 -040062.image image-draw/20.png
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110063
Russ Coxaf5018f2020-03-09 23:54:35 -040064The [`DrawMask`](https://golang.org/pkg/image/draw/#DrawMask) function
Russ Cox482079d2020-03-09 22:11:04 -040065takes seven arguments,
66but an explicit mask and mask-point are usually unnecessary,
Russ Coxaf5018f2020-03-09 23:54:35 -040067so the [`Draw`](https://golang.org/pkg/image/draw/#Draw) function takes five:
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110068
69 // Draw calls DrawMask with a nil mask.
70 func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op)
71 func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point,
72 mask image.Image, mp image.Point, op Op)
73
Russ Cox482079d2020-03-09 22:11:04 -040074The destination image must be mutable, so the image/draw package defines
Russ Coxaf5018f2020-03-09 23:54:35 -040075a [`draw.Image`](https://golang.org/pkg/image/draw/#Image) interface which has a `Set` method.
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110076
77 type Image interface {
78 image.Image
79 Set(x, y int, c color.Color)
80 }
81
Russ Coxaf5018f2020-03-09 23:54:35 -040082## Filling a Rectangle
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110083
Russ Cox482079d2020-03-09 22:11:04 -040084To fill a rectangle with a solid color, use an `image.Uniform` source.
85The `ColorImage` type re-interprets a `Color` as a practically infinite-sized
86`Image` of that color.
87For those familiar with the design of Plan 9's draw library,
88there is no need for an explicit "repeat bit" in Go's slice-based image types;
89the concept is subsumed by `Uniform`.
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110090
Russ Coxaf5018f2020-03-09 23:54:35 -040091 // image.ZP is the zero point -- the origin.
92 draw.Draw(dst, r, &image.Uniform{c}, image.ZP, draw.Src)
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110093
94To initialize a new image to all-blue:
95
Russ Coxaf5018f2020-03-09 23:54:35 -040096 m := image.NewRGBA(image.Rect(0, 0, 640, 480))
97 blue := color.RGBA{0, 0, 255, 255}
98 draw.Draw(m, m.Bounds(), &image.Uniform{blue}, image.ZP, draw.Src)
Andrew Gerranddb9a09f2013-03-08 11:17:09 +110099
Russ Cox482079d2020-03-09 22:11:04 -0400100To reset an image to transparent (or black,
101if the destination image's color model cannot represent transparency),
102use `image.Transparent`, which is an `image.Uniform`:
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100103
Russ Coxaf5018f2020-03-09 23:54:35 -0400104 draw.Draw(m, m.Bounds(), image.Transparent, image.ZP, draw.Src)
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100105
Russ Cox972d42d2020-03-15 15:50:36 -0400106.image image-draw/2a.png
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100107
Russ Coxaf5018f2020-03-09 23:54:35 -0400108## Copying an Image
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100109
Russ Cox482079d2020-03-09 22:11:04 -0400110To copy from a rectangle `sr` in the source image to a rectangle starting
111at a point `dp` in the destination,
112convert the source rectangle into the destination image's co-ordinate space:
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100113
Russ Coxaf5018f2020-03-09 23:54:35 -0400114 r := image.Rectangle{dp, dp.Add(sr.Size())}
115 draw.Draw(dst, r, src, sr.Min, draw.Src)
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100116
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100117Alternatively:
118
Russ Coxaf5018f2020-03-09 23:54:35 -0400119 r := sr.Sub(sr.Min).Add(dp)
120 draw.Draw(dst, r, src, sr.Min, draw.Src)
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100121
Russ Coxaf5018f2020-03-09 23:54:35 -0400122To copy the entire source image, use `sr = src.Bounds()`.
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100123
Russ Cox972d42d2020-03-15 15:50:36 -0400124.image image-draw/2b.png
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100125
Russ Coxaf5018f2020-03-09 23:54:35 -0400126## Scrolling an Image
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100127
Russ Cox482079d2020-03-09 22:11:04 -0400128Scrolling an image is just copying an image to itself,
129with different destination and source rectangles.
130Overlapping destination and source images are perfectly valid,
131just as Go's built-in copy function can handle overlapping destination and source slices.
132To scroll an image m by 20 pixels:
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100133
Russ Coxaf5018f2020-03-09 23:54:35 -0400134 b := m.Bounds()
135 p := image.Pt(0, 20)
136 // Note that even though the second argument is b,
137 // the effective rectangle is smaller due to clipping.
138 draw.Draw(m, b, m, b.Min.Add(p), draw.Src)
139 dirtyRect := b.Intersect(image.Rect(b.Min.X, b.Max.Y-20, b.Max.X, b.Max.Y))
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100140
Russ Cox972d42d2020-03-15 15:50:36 -0400141.image image-draw/2c.png
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100142
Russ Coxaf5018f2020-03-09 23:54:35 -0400143## Converting an Image to RGBA
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100144
Russ Cox482079d2020-03-09 22:11:04 -0400145The result of decoding an image format might not be an `image.RGBA`:
146decoding a GIF results in an `image.Paletted`,
147decoding a JPEG results in a `ycbcr.YCbCr`,
148and the result of decoding a PNG depends on the image data.
149To convert any image to an `image.RGBA`:
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100150
Russ Coxaf5018f2020-03-09 23:54:35 -0400151 b := src.Bounds()
152 m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
153 draw.Draw(m, m.Bounds(), src, b.Min, draw.Src)
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100154
Russ Cox972d42d2020-03-15 15:50:36 -0400155.image image-draw/2d.png
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100156
Russ Coxaf5018f2020-03-09 23:54:35 -0400157## Drawing Through a Mask
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100158
159To draw an image through a circular mask with center `p` and radius `r`:
160
161 type circle struct {
162 p image.Point
163 r int
164 }
165
166 func (c *circle) ColorModel() color.Model {
167 return color.AlphaModel
168 }
169
170 func (c *circle) Bounds() image.Rectangle {
171 return image.Rect(c.p.X-c.r, c.p.Y-c.r, c.p.X+c.r, c.p.Y+c.r)
172 }
173
174 func (c *circle) At(x, y int) color.Color {
175 xx, yy, rr := float64(x-c.p.X)+0.5, float64(y-c.p.Y)+0.5, float64(c.r)
176 if xx*xx+yy*yy < rr*rr {
177 return color.Alpha{255}
178 }
179 return color.Alpha{0}
180 }
181
182 draw.DrawMask(dst, dst.Bounds(), src, image.ZP, &circle{p, r}, image.ZP, draw.Over)
183
Russ Cox972d42d2020-03-15 15:50:36 -0400184.image image-draw/2e.png
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100185
Russ Coxaf5018f2020-03-09 23:54:35 -0400186## Drawing Font Glyphs
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100187
Russ Cox482079d2020-03-09 22:11:04 -0400188To draw a font glyph in blue starting from a point `p`,
Russ Coxaf5018f2020-03-09 23:54:35 -0400189draw with an `image.ColorImage` source and an `image.Alpha mask`.
Russ Cox482079d2020-03-09 22:11:04 -0400190For simplicity, we aren't performing any sub-pixel positioning or rendering,
191or correcting for a font's height above a baseline.
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100192
Russ Coxaf5018f2020-03-09 23:54:35 -0400193 src := &image.Uniform{color.RGBA{0, 0, 255, 255}}
194 mask := theGlyphImageForAFont()
195 mr := theBoundsFor(glyphIndex)
196 draw.DrawMask(dst, mr.Sub(mr.Min).Add(p), src, image.ZP, mask, mr.Min, draw.Over)
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100197
Russ Cox972d42d2020-03-15 15:50:36 -0400198.image image-draw/2f.png
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100199
Russ Coxaf5018f2020-03-09 23:54:35 -0400200## Performance
Andrew Gerranddb9a09f2013-03-08 11:17:09 +1100201
Russ Cox482079d2020-03-09 22:11:04 -0400202The image/draw package implementation demonstrates how to provide an image
203manipulation function that is both general purpose,
204yet efficient for common cases.
205The `DrawMask` function takes arguments of interface types,
206but immediately makes type assertions that its arguments are of specific struct types,
207corresponding to common operations like drawing one `image.RGBA` image onto another,
208or drawing an `image.Alpha` mask (such as a font glyph) onto an `image.RGBA` image.
209If a type assertion succeeds, that type information is used to run a specialized
210implementation of the general algorithm.
211If the assertions fail, the fallback code path uses the generic `At` and `Set` methods.
212The fast-paths are purely a performance optimization;
213the resultant destination image is the same either way.
214In practice, only a small number of special cases are necessary to support
215typical applications.