diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-06-30 15:34:22 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-06-30 15:34:22 +0200 |
commit | d39f5aa373a4422f7a5f3ee764fb0f6b0b719d61 (patch) | |
tree | 1833f8b72a4b3a8f00d0d143b079a8fcad01c6ae /src/pkg/image/draw | |
parent | 8652e6c371b8905498d3d314491d36c58d5f68d5 (diff) | |
download | golang-upstream/58.tar.gz |
Imported Upstream version 58upstream/58
Diffstat (limited to 'src/pkg/image/draw')
-rw-r--r-- | src/pkg/image/draw/Makefile | 11 | ||||
-rw-r--r-- | src/pkg/image/draw/clip_test.go | 193 | ||||
-rw-r--r-- | src/pkg/image/draw/draw.go | 480 | ||||
-rw-r--r-- | src/pkg/image/draw/draw_test.go | 278 |
4 files changed, 962 insertions, 0 deletions
diff --git a/src/pkg/image/draw/Makefile b/src/pkg/image/draw/Makefile new file mode 100644 index 000000000..2ba6e7b51 --- /dev/null +++ b/src/pkg/image/draw/Makefile @@ -0,0 +1,11 @@ +# Copyright 2009 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. + +include ../../../Make.inc + +TARG=image/draw +GOFILES=\ + draw.go\ + +include ../../../Make.pkg diff --git a/src/pkg/image/draw/clip_test.go b/src/pkg/image/draw/clip_test.go new file mode 100644 index 000000000..db40d82f5 --- /dev/null +++ b/src/pkg/image/draw/clip_test.go @@ -0,0 +1,193 @@ +// Copyright 2011 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 draw + +import ( + "image" + "testing" +) + +type clipTest struct { + desc string + r, dr, sr, mr image.Rectangle + sp, mp image.Point + nilMask bool + r0 image.Rectangle + sp0, mp0 image.Point +} + +var clipTests = []clipTest{ + // The following tests all have a nil mask. + { + "basic", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 100, 100), + image.ZR, + image.ZP, + image.ZP, + true, + image.Rect(0, 0, 100, 100), + image.ZP, + image.ZP, + }, + { + "clip dr", + image.Rect(0, 0, 100, 100), + image.Rect(40, 40, 60, 60), + image.Rect(0, 0, 100, 100), + image.ZR, + image.ZP, + image.ZP, + true, + image.Rect(40, 40, 60, 60), + image.Pt(40, 40), + image.ZP, + }, + { + "clip sr", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 100, 100), + image.Rect(20, 20, 80, 80), + image.ZR, + image.ZP, + image.ZP, + true, + image.Rect(20, 20, 80, 80), + image.Pt(20, 20), + image.ZP, + }, + { + "clip dr and sr", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 50, 100), + image.Rect(20, 20, 80, 80), + image.ZR, + image.ZP, + image.ZP, + true, + image.Rect(20, 20, 50, 80), + image.Pt(20, 20), + image.ZP, + }, + { + "clip dr and sr, sp outside sr (top-left)", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 50, 100), + image.Rect(20, 20, 80, 80), + image.ZR, + image.Pt(15, 8), + image.ZP, + true, + image.Rect(5, 12, 50, 72), + image.Pt(20, 20), + image.ZP, + }, + { + "clip dr and sr, sp outside sr (middle-left)", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 50, 100), + image.Rect(20, 20, 80, 80), + image.ZR, + image.Pt(15, 66), + image.ZP, + true, + image.Rect(5, 0, 50, 14), + image.Pt(20, 66), + image.ZP, + }, + { + "clip dr and sr, sp outside sr (bottom-left)", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 50, 100), + image.Rect(20, 20, 80, 80), + image.ZR, + image.Pt(15, 91), + image.ZP, + true, + image.ZR, + image.Pt(15, 91), + image.ZP, + }, + { + "clip dr and sr, sp inside sr", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 50, 100), + image.Rect(20, 20, 80, 80), + image.ZR, + image.Pt(44, 33), + image.ZP, + true, + image.Rect(0, 0, 36, 47), + image.Pt(44, 33), + image.ZP, + }, + + // The following tests all have a non-nil mask. + { + "basic mask", + image.Rect(0, 0, 80, 80), + image.Rect(20, 0, 100, 80), + image.Rect(0, 0, 50, 49), + image.Rect(0, 0, 46, 47), + image.ZP, + image.ZP, + false, + image.Rect(20, 0, 46, 47), + image.Pt(20, 0), + image.Pt(20, 0), + }, + // TODO(nigeltao): write more tests. +} + +func TestClip(t *testing.T) { + dst0 := image.NewRGBA(100, 100) + src0 := image.NewRGBA(100, 100) + mask0 := image.NewRGBA(100, 100) + for _, c := range clipTests { + dst := dst0.SubImage(c.dr).(*image.RGBA) + src := src0.SubImage(c.sr).(*image.RGBA) + var mask image.Image + if !c.nilMask { + mask = mask0.SubImage(c.mr) + } + r, sp, mp := c.r, c.sp, c.mp + clip(dst, &r, src, &sp, mask, &mp) + + // Check that the actual results equal the expected results. + if !c.r0.Eq(r) { + t.Errorf("%s: clip rectangle want %v got %v", c.desc, c.r0, r) + continue + } + if !c.sp0.Eq(sp) { + t.Errorf("%s: sp want %v got %v", c.desc, c.sp0, sp) + continue + } + if !c.nilMask { + if !c.mp0.Eq(mp) { + t.Errorf("%s: mp want %v got %v", c.desc, c.mp0, mp) + continue + } + } + + // Check that the clipped rectangle is contained by the dst / src / mask + // rectangles, in their respective co-ordinate spaces. + if !r.In(c.dr) { + t.Errorf("%s: c.dr %v does not contain r %v", c.desc, c.dr, r) + } + // sr is r translated into src's co-ordinate space. + sr := r.Add(c.sp.Sub(c.dr.Min)) + if !sr.In(c.sr) { + t.Errorf("%s: c.sr %v does not contain sr %v", c.desc, c.sr, sr) + } + if !c.nilMask { + // mr is r translated into mask's co-ordinate space. + mr := r.Add(c.mp.Sub(c.dr.Min)) + if !mr.In(c.mr) { + t.Errorf("%s: c.mr %v does not contain mr %v", c.desc, c.mr, mr) + } + } + } +} diff --git a/src/pkg/image/draw/draw.go b/src/pkg/image/draw/draw.go new file mode 100644 index 000000000..618fb4aa6 --- /dev/null +++ b/src/pkg/image/draw/draw.go @@ -0,0 +1,480 @@ +// Copyright 2009 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 draw provides image composition functions +// in the style of the Plan 9 graphics library +// (see http://plan9.bell-labs.com/magic/man2html/2/draw) +// and the X Render extension. +package draw + +import ( + "image" + "image/ycbcr" +) + +// m is the maximum color value returned by image.Color.RGBA. +const m = 1<<16 - 1 + +// Op is a Porter-Duff compositing operator. +type Op int + +const ( + // Over specifies ``(src in mask) over dst''. + Over Op = iota + // Src specifies ``src in mask''. + Src +) + +var zeroColor image.Color = image.AlphaColor{0} + +// A draw.Image is an image.Image with a Set method to change a single pixel. +type Image interface { + image.Image + Set(x, y int, c image.Color) +} + +// Draw calls DrawMask with a nil mask and an Over op. +func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point) { + DrawMask(dst, r, src, sp, nil, image.ZP, Over) +} + +// clip clips r against each image's bounds (after translating into the +// destination image's co-ordinate space) and shifts the points sp and mp by +// the same amount as the change in r.Min. +func clip(dst Image, r *image.Rectangle, src image.Image, sp *image.Point, mask image.Image, mp *image.Point) { + orig := r.Min + *r = r.Intersect(dst.Bounds()) + *r = r.Intersect(src.Bounds().Add(orig.Sub(*sp))) + if mask != nil { + *r = r.Intersect(mask.Bounds().Add(orig.Sub(*mp))) + } + dx := r.Min.X - orig.X + dy := r.Min.Y - orig.Y + if dx == 0 && dy == 0 { + return + } + (*sp).X += dx + (*sp).Y += dy + (*mp).X += dx + (*mp).Y += dy +} + +// DrawMask aligns r.Min in dst with sp in src and mp in mask and then replaces the rectangle r +// in dst with the result of a Porter-Duff composition. A nil mask is treated as opaque. +func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) { + clip(dst, &r, src, &sp, mask, &mp) + if r.Empty() { + return + } + + // Fast paths for special cases. If none of them apply, then we fall back to a general but slow implementation. + if dst0, ok := dst.(*image.RGBA); ok { + if op == Over { + if mask == nil { + switch src0 := src.(type) { + case *image.ColorImage: + drawFillOver(dst0, r, src0) + return + case *image.RGBA: + drawCopyOver(dst0, r, src0, sp) + return + case *image.NRGBA: + drawNRGBAOver(dst0, r, src0, sp) + return + case *ycbcr.YCbCr: + drawYCbCr(dst0, r, src0, sp) + return + } + } else if mask0, ok := mask.(*image.Alpha); ok { + switch src0 := src.(type) { + case *image.ColorImage: + drawGlyphOver(dst0, r, src0, mask0, mp) + return + } + } + } else { + if mask == nil { + switch src0 := src.(type) { + case *image.ColorImage: + drawFillSrc(dst0, r, src0) + return + case *image.RGBA: + drawCopySrc(dst0, r, src0, sp) + return + case *image.NRGBA: + drawNRGBASrc(dst0, r, src0, sp) + return + case *ycbcr.YCbCr: + drawYCbCr(dst0, r, src0, sp) + return + } + } + } + drawRGBA(dst0, r, src, sp, mask, mp, op) + return + } + + x0, x1, dx := r.Min.X, r.Max.X, 1 + y0, y1, dy := r.Min.Y, r.Max.Y, 1 + if image.Image(dst) == src && r.Overlaps(r.Add(sp.Sub(r.Min))) { + // Rectangles overlap: process backward? + if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X { + x0, x1, dx = x1-1, x0-1, -1 + y0, y1, dy = y1-1, y0-1, -1 + } + } + + var out *image.RGBA64Color + sy := sp.Y + y0 - r.Min.Y + my := mp.Y + y0 - r.Min.Y + for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy { + sx := sp.X + x0 - r.Min.X + mx := mp.X + x0 - r.Min.X + for x := x0; x != x1; x, sx, mx = x+dx, sx+dx, mx+dx { + ma := uint32(m) + if mask != nil { + _, _, _, ma = mask.At(mx, my).RGBA() + } + switch { + case ma == 0: + if op == Over { + // No-op. + } else { + dst.Set(x, y, zeroColor) + } + case ma == m && op == Src: + dst.Set(x, y, src.At(sx, sy)) + default: + sr, sg, sb, sa := src.At(sx, sy).RGBA() + if out == nil { + out = new(image.RGBA64Color) + } + if op == Over { + dr, dg, db, da := dst.At(x, y).RGBA() + a := m - (sa * ma / m) + out.R = uint16((dr*a + sr*ma) / m) + out.G = uint16((dg*a + sg*ma) / m) + out.B = uint16((db*a + sb*ma) / m) + out.A = uint16((da*a + sa*ma) / m) + } else { + out.R = uint16(sr * ma / m) + out.G = uint16(sg * ma / m) + out.B = uint16(sb * ma / m) + out.A = uint16(sa * ma / m) + } + dst.Set(x, y, out) + } + } + } +} + +func drawFillOver(dst *image.RGBA, r image.Rectangle, src *image.ColorImage) { + cr, cg, cb, ca := src.RGBA() + // The 0x101 is here for the same reason as in drawRGBA. + a := (m - ca) * 0x101 + x0, x1 := r.Min.X, r.Max.X + y0, y1 := r.Min.Y, r.Max.Y + for y := y0; y != y1; y++ { + dbase := y * dst.Stride + dpix := dst.Pix[dbase+x0 : dbase+x1] + for i, rgba := range dpix { + dr := (uint32(rgba.R)*a)/m + cr + dg := (uint32(rgba.G)*a)/m + cg + db := (uint32(rgba.B)*a)/m + cb + da := (uint32(rgba.A)*a)/m + ca + dpix[i] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)} + } + } +} + +func drawCopyOver(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.Point) { + dx0, dx1 := r.Min.X, r.Max.X + dy0, dy1 := r.Min.Y, r.Max.Y + nrows := dy1 - dy0 + sx0, sx1 := sp.X, sp.X+dx1-dx0 + d0 := dy0*dst.Stride + dx0 + d1 := dy0*dst.Stride + dx1 + s0 := sp.Y*src.Stride + sx0 + s1 := sp.Y*src.Stride + sx1 + var ( + ddelta, sdelta int + i0, i1, idelta int + ) + if r.Min.Y < sp.Y || r.Min.Y == sp.Y && r.Min.X <= sp.X { + ddelta = dst.Stride + sdelta = src.Stride + i0, i1, idelta = 0, d1-d0, +1 + } else { + // If the source start point is higher than the destination start point, or equal height but to the left, + // then we compose the rows in right-to-left, bottom-up order instead of left-to-right, top-down. + d0 += (nrows - 1) * dst.Stride + d1 += (nrows - 1) * dst.Stride + s0 += (nrows - 1) * src.Stride + s1 += (nrows - 1) * src.Stride + ddelta = -dst.Stride + sdelta = -src.Stride + i0, i1, idelta = d1-d0-1, -1, -1 + } + for ; nrows > 0; nrows-- { + dpix := dst.Pix[d0:d1] + spix := src.Pix[s0:s1] + for i := i0; i != i1; i += idelta { + // For unknown reasons, even though both dpix[i] and spix[i] are + // image.RGBAColors, on an x86 CPU it seems fastest to call RGBA + // for the source but to do it manually for the destination. + sr, sg, sb, sa := spix[i].RGBA() + rgba := dpix[i] + dr := uint32(rgba.R) + dg := uint32(rgba.G) + db := uint32(rgba.B) + da := uint32(rgba.A) + // The 0x101 is here for the same reason as in drawRGBA. + a := (m - sa) * 0x101 + dr = (dr*a)/m + sr + dg = (dg*a)/m + sg + db = (db*a)/m + sb + da = (da*a)/m + sa + dpix[i] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)} + } + d0 += ddelta + d1 += ddelta + s0 += sdelta + s1 += sdelta + } +} + +func drawNRGBAOver(dst *image.RGBA, r image.Rectangle, src *image.NRGBA, sp image.Point) { + for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 { + dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride] + spix := src.Pix[sy*src.Stride : (sy+1)*src.Stride] + for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 { + // Convert from non-premultiplied color to pre-multiplied color. + // The order of operations here is to match the NRGBAColor.RGBA + // method in image/color.go. + snrgba := spix[sx] + sa := uint32(snrgba.A) + sr := uint32(snrgba.R) * 0x101 * sa / 0xff + sg := uint32(snrgba.G) * 0x101 * sa / 0xff + sb := uint32(snrgba.B) * 0x101 * sa / 0xff + sa *= 0x101 + + rgba := dpix[x] + dr := uint32(rgba.R) + dg := uint32(rgba.G) + db := uint32(rgba.B) + da := uint32(rgba.A) + a := (m - sa) * 0x101 + dr = (dr*a + sr*m) / m + dg = (dg*a + sg*m) / m + db = (db*a + sb*m) / m + da = (da*a + sa*m) / m + dpix[x] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)} + } + } +} + +func drawGlyphOver(dst *image.RGBA, r image.Rectangle, src *image.ColorImage, mask *image.Alpha, mp image.Point) { + x0, x1 := r.Min.X, r.Max.X + y0, y1 := r.Min.Y, r.Max.Y + cr, cg, cb, ca := src.RGBA() + for y, my := y0, mp.Y; y != y1; y, my = y+1, my+1 { + dbase := y * dst.Stride + dpix := dst.Pix[dbase+x0 : dbase+x1] + mbase := my * mask.Stride + mpix := mask.Pix[mbase+mp.X:] + for i, rgba := range dpix { + ma := uint32(mpix[i].A) + if ma == 0 { + continue + } + ma |= ma << 8 + dr := uint32(rgba.R) + dg := uint32(rgba.G) + db := uint32(rgba.B) + da := uint32(rgba.A) + // The 0x101 is here for the same reason as in drawRGBA. + a := (m - (ca * ma / m)) * 0x101 + dr = (dr*a + cr*ma) / m + dg = (dg*a + cg*ma) / m + db = (db*a + cb*ma) / m + da = (da*a + ca*ma) / m + dpix[i] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)} + } + } +} + +func drawFillSrc(dst *image.RGBA, r image.Rectangle, src *image.ColorImage) { + if r.Dy() < 1 { + return + } + cr, cg, cb, ca := src.RGBA() + color := image.RGBAColor{uint8(cr >> 8), uint8(cg >> 8), uint8(cb >> 8), uint8(ca >> 8)} + // The built-in copy function is faster than a straightforward for loop to fill the destination with + // the color, but copy requires a slice source. We therefore use a for loop to fill the first row, and + // then use the first row as the slice source for the remaining rows. + dx0, dx1 := r.Min.X, r.Max.X + dy0, dy1 := r.Min.Y, r.Max.Y + dbase := dy0 * dst.Stride + i0, i1 := dbase+dx0, dbase+dx1 + firstRow := dst.Pix[i0:i1] + for i := range firstRow { + firstRow[i] = color + } + for y := dy0 + 1; y < dy1; y++ { + i0 += dst.Stride + i1 += dst.Stride + copy(dst.Pix[i0:i1], firstRow) + } +} + +func drawCopySrc(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.Point) { + dx0, dx1 := r.Min.X, r.Max.X + dy0, dy1 := r.Min.Y, r.Max.Y + nrows := dy1 - dy0 + sx0, sx1 := sp.X, sp.X+dx1-dx0 + d0 := dy0*dst.Stride + dx0 + d1 := dy0*dst.Stride + dx1 + s0 := sp.Y*src.Stride + sx0 + s1 := sp.Y*src.Stride + sx1 + var ddelta, sdelta int + if r.Min.Y <= sp.Y { + ddelta = dst.Stride + sdelta = src.Stride + } else { + // If the source start point is higher than the destination start point, then we compose the rows + // in bottom-up order instead of top-down. Unlike the drawCopyOver function, we don't have to + // check the x co-ordinates because the built-in copy function can handle overlapping slices. + d0 += (nrows - 1) * dst.Stride + d1 += (nrows - 1) * dst.Stride + s0 += (nrows - 1) * src.Stride + s1 += (nrows - 1) * src.Stride + ddelta = -dst.Stride + sdelta = -src.Stride + } + for ; nrows > 0; nrows-- { + copy(dst.Pix[d0:d1], src.Pix[s0:s1]) + d0 += ddelta + d1 += ddelta + s0 += sdelta + s1 += sdelta + } +} + +func drawNRGBASrc(dst *image.RGBA, r image.Rectangle, src *image.NRGBA, sp image.Point) { + for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 { + dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride] + spix := src.Pix[sy*src.Stride : (sy+1)*src.Stride] + for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 { + // Convert from non-premultiplied color to pre-multiplied color. + // The order of operations here is to match the NRGBAColor.RGBA + // method in image/color.go. + snrgba := spix[sx] + sa := uint32(snrgba.A) + sr := uint32(snrgba.R) * 0x101 * sa / 0xff + sg := uint32(snrgba.G) * 0x101 * sa / 0xff + sb := uint32(snrgba.B) * 0x101 * sa / 0xff + sa *= 0x101 + + dpix[x] = image.RGBAColor{uint8(sr >> 8), uint8(sg >> 8), uint8(sb >> 8), uint8(sa >> 8)} + } + } +} + +func drawYCbCr(dst *image.RGBA, r image.Rectangle, src *ycbcr.YCbCr, sp image.Point) { + // A YCbCr image is always fully opaque, and so if the mask is implicitly nil + // (i.e. fully opaque) then the op is effectively always Src. + var ( + yy, cb, cr uint8 + rr, gg, bb uint8 + ) + switch src.SubsampleRatio { + case ycbcr.SubsampleRatio422: + for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 { + dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride] + for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 { + i := sx / 2 + yy = src.Y[sy*src.YStride+sx] + cb = src.Cb[sy*src.CStride+i] + cr = src.Cr[sy*src.CStride+i] + rr, gg, bb = ycbcr.YCbCrToRGB(yy, cb, cr) + dpix[x] = image.RGBAColor{rr, gg, bb, 255} + } + } + case ycbcr.SubsampleRatio420: + for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 { + dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride] + for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 { + i, j := sx/2, sy/2 + yy = src.Y[sy*src.YStride+sx] + cb = src.Cb[j*src.CStride+i] + cr = src.Cr[j*src.CStride+i] + rr, gg, bb = ycbcr.YCbCrToRGB(yy, cb, cr) + dpix[x] = image.RGBAColor{rr, gg, bb, 255} + } + } + default: + // Default to 4:4:4 subsampling. + for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 { + dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride] + for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 { + yy = src.Y[sy*src.YStride+sx] + cb = src.Cb[sy*src.CStride+sx] + cr = src.Cr[sy*src.CStride+sx] + rr, gg, bb = ycbcr.YCbCrToRGB(yy, cb, cr) + dpix[x] = image.RGBAColor{rr, gg, bb, 255} + } + } + } +} + +func drawRGBA(dst *image.RGBA, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) { + x0, x1, dx := r.Min.X, r.Max.X, 1 + y0, y1, dy := r.Min.Y, r.Max.Y, 1 + if image.Image(dst) == src && r.Overlaps(r.Add(sp.Sub(r.Min))) { + if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X { + x0, x1, dx = x1-1, x0-1, -1 + y0, y1, dy = y1-1, y0-1, -1 + } + } + + sy := sp.Y + y0 - r.Min.Y + my := mp.Y + y0 - r.Min.Y + for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy { + sx := sp.X + x0 - r.Min.X + mx := mp.X + x0 - r.Min.X + dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride] + for x := x0; x != x1; x, sx, mx = x+dx, sx+dx, mx+dx { + ma := uint32(m) + if mask != nil { + _, _, _, ma = mask.At(mx, my).RGBA() + } + sr, sg, sb, sa := src.At(sx, sy).RGBA() + var dr, dg, db, da uint32 + if op == Over { + rgba := dpix[x] + dr = uint32(rgba.R) + dg = uint32(rgba.G) + db = uint32(rgba.B) + da = uint32(rgba.A) + // dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255]. + // We work in 16-bit color, and so would normally do: + // dr |= dr << 8 + // and similarly for dg, db and da, but instead we multiply a + // (which is a 16-bit color, ranging in [0,65535]) by 0x101. + // This yields the same result, but is fewer arithmetic operations. + a := (m - (sa * ma / m)) * 0x101 + dr = (dr*a + sr*ma) / m + dg = (dg*a + sg*ma) / m + db = (db*a + sb*ma) / m + da = (da*a + sa*ma) / m + } else { + dr = sr * ma / m + dg = sg * ma / m + db = sb * ma / m + da = sa * ma / m + } + dpix[x] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)} + } + } +} diff --git a/src/pkg/image/draw/draw_test.go b/src/pkg/image/draw/draw_test.go new file mode 100644 index 000000000..37d630353 --- /dev/null +++ b/src/pkg/image/draw/draw_test.go @@ -0,0 +1,278 @@ +// 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 draw + +import ( + "image" + "image/ycbcr" + "testing" +) + +func eq(c0, c1 image.Color) bool { + r0, g0, b0, a0 := c0.RGBA() + r1, g1, b1, a1 := c1.RGBA() + return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1 +} + +func fillBlue(alpha int) image.Image { + return image.NewColorImage(image.RGBAColor{0, 0, uint8(alpha), uint8(alpha)}) +} + +func fillAlpha(alpha int) image.Image { + return image.NewColorImage(image.AlphaColor{uint8(alpha)}) +} + +func vgradGreen(alpha int) image.Image { + m := image.NewRGBA(16, 16) + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + m.Set(x, y, image.RGBAColor{0, uint8(y * alpha / 15), 0, uint8(alpha)}) + } + } + return m +} + +func vgradAlpha(alpha int) image.Image { + m := image.NewAlpha(16, 16) + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + m.Set(x, y, image.AlphaColor{uint8(y * alpha / 15)}) + } + } + return m +} + +func vgradGreenNRGBA(alpha int) image.Image { + m := image.NewNRGBA(16, 16) + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + m.Set(x, y, image.RGBAColor{0, uint8(y * 0x11), 0, uint8(alpha)}) + } + } + return m +} + +func vgradCr() image.Image { + m := &ycbcr.YCbCr{ + Y: make([]byte, 16*16), + Cb: make([]byte, 16*16), + Cr: make([]byte, 16*16), + YStride: 16, + CStride: 16, + SubsampleRatio: ycbcr.SubsampleRatio444, + Rect: image.Rect(0, 0, 16, 16), + } + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + m.Cr[y*m.CStride+x] = uint8(y * 0x11) + } + } + return m +} + +func hgradRed(alpha int) Image { + m := image.NewRGBA(16, 16) + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + m.Set(x, y, image.RGBAColor{uint8(x * alpha / 15), 0, 0, uint8(alpha)}) + } + } + return m +} + +func gradYellow(alpha int) Image { + m := image.NewRGBA(16, 16) + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + m.Set(x, y, image.RGBAColor{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)}) + } + } + return m +} + +type drawTest struct { + desc string + src image.Image + mask image.Image + op Op + expected image.Color +} + +var drawTests = []drawTest{ + // Uniform mask (0% opaque). + {"nop", vgradGreen(255), fillAlpha(0), Over, image.RGBAColor{136, 0, 0, 255}}, + {"clear", vgradGreen(255), fillAlpha(0), Src, image.RGBAColor{0, 0, 0, 0}}, + // Uniform mask (100%, 75%, nil) and uniform source. + // At (x, y) == (8, 8): + // The destination pixel is {136, 0, 0, 255}. + // The source pixel is {0, 0, 90, 90}. + {"fill", fillBlue(90), fillAlpha(255), Over, image.RGBAColor{88, 0, 90, 255}}, + {"fillSrc", fillBlue(90), fillAlpha(255), Src, image.RGBAColor{0, 0, 90, 90}}, + {"fillAlpha", fillBlue(90), fillAlpha(192), Over, image.RGBAColor{100, 0, 68, 255}}, + {"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, image.RGBAColor{0, 0, 68, 68}}, + {"fillNil", fillBlue(90), nil, Over, image.RGBAColor{88, 0, 90, 255}}, + {"fillNilSrc", fillBlue(90), nil, Src, image.RGBAColor{0, 0, 90, 90}}, + // Uniform mask (100%, 75%, nil) and variable source. + // At (x, y) == (8, 8): + // The destination pixel is {136, 0, 0, 255}. + // The source pixel is {0, 48, 0, 90}. + {"copy", vgradGreen(90), fillAlpha(255), Over, image.RGBAColor{88, 48, 0, 255}}, + {"copySrc", vgradGreen(90), fillAlpha(255), Src, image.RGBAColor{0, 48, 0, 90}}, + {"copyAlpha", vgradGreen(90), fillAlpha(192), Over, image.RGBAColor{100, 36, 0, 255}}, + {"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, image.RGBAColor{0, 36, 0, 68}}, + {"copyNil", vgradGreen(90), nil, Over, image.RGBAColor{88, 48, 0, 255}}, + {"copyNilSrc", vgradGreen(90), nil, Src, image.RGBAColor{0, 48, 0, 90}}, + // Uniform mask (100%, 75%, nil) and variable NRGBA source. + // At (x, y) == (8, 8): + // The destination pixel is {136, 0, 0, 255}. + // The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space. + // The result pixel is different than in the "copy*" test cases because of rounding errors. + {"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, image.RGBAColor{88, 46, 0, 255}}, + {"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, image.RGBAColor{0, 46, 0, 90}}, + {"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, image.RGBAColor{100, 34, 0, 255}}, + {"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, image.RGBAColor{0, 34, 0, 68}}, + {"nrgbaNil", vgradGreenNRGBA(90), nil, Over, image.RGBAColor{88, 46, 0, 255}}, + {"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, image.RGBAColor{0, 46, 0, 90}}, + // Uniform mask (100%, 75%, nil) and variable YCbCr source. + // At (x, y) == (8, 8): + // The destination pixel is {136, 0, 0, 255}. + // The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space. + {"ycbcr", vgradCr(), fillAlpha(255), Over, image.RGBAColor{11, 38, 0, 255}}, + {"ycbcrSrc", vgradCr(), fillAlpha(255), Src, image.RGBAColor{11, 38, 0, 255}}, + {"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, image.RGBAColor{42, 28, 0, 255}}, + {"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, image.RGBAColor{8, 28, 0, 192}}, + {"ycbcrNil", vgradCr(), nil, Over, image.RGBAColor{11, 38, 0, 255}}, + {"ycbcrNilSrc", vgradCr(), nil, Src, image.RGBAColor{11, 38, 0, 255}}, + // Variable mask and variable source. + // At (x, y) == (8, 8): + // The destination pixel is {136, 0, 0, 255}. + // The source pixel is {0, 0, 255, 255}. + // The mask pixel's alpha is 102, or 40%. + {"generic", fillBlue(255), vgradAlpha(192), Over, image.RGBAColor{81, 0, 102, 255}}, + {"genericSrc", fillBlue(255), vgradAlpha(192), Src, image.RGBAColor{0, 0, 102, 102}}, +} + +func makeGolden(dst, src, mask image.Image, op Op) 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. + b := dst.Bounds() + sx0 := src.Bounds().Min.X - b.Min.X + sy0 := src.Bounds().Min.Y - b.Min.Y + var mx0, my0 int + if mask != nil { + mx0 = mask.Bounds().Min.X - b.Min.X + my0 = mask.Bounds().Min.Y - b.Min.Y + } + golden := image.NewRGBA(b.Max.X, b.Max.Y) + for y := b.Min.Y; y < b.Max.Y; y++ { + my, sy := my0+y, sy0+y + for x := b.Min.X; x < b.Max.X; x++ { + mx, sx := mx0+x, sx0+x + const M = 1<<16 - 1 + var dr, dg, db, da uint32 + if op == Over { + dr, dg, db, da = dst.At(x, y).RGBA() + } + sr, sg, sb, sa := src.At(sx, sy).RGBA() + ma := uint32(M) + if mask != nil { + _, _, _, ma = mask.At(mx, my).RGBA() + } + a := M - (sa * ma / M) + golden.Set(x, y, image.RGBA64Color{ + uint16((dr*a + sr*ma) / M), + uint16((dg*a + sg*ma) / M), + uint16((db*a + sb*ma) / M), + uint16((da*a + sa*ma) / M), + }) + } + } + golden.Rect = b + return golden +} + +func TestDraw(t *testing.T) { +loop: + for _, test := range drawTests { + dst := hgradRed(255) + // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation. + golden := makeGolden(dst, test.src, test.mask, test.op) + b := dst.Bounds() + 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, b, test.src, image.ZP, test.mask, image.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) { + t.Errorf("draw %s: at (8, 8) %v versus %v", test.desc, dst.At(8, 8), test.expected) + continue + } + // Check that the resultant dst image matches the golden output. + 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 + } + } + } + } +} + +func TestDrawOverlap(t *testing.T) { + for _, op := range []Op{Over, Src} { + for yoff := -2; yoff <= 2; yoff++ { + loop: + for xoff := -2; xoff <= 2; xoff++ { + m := gradYellow(127).(*image.RGBA) + dst := &image.RGBA{ + Pix: m.Pix, + Stride: m.Stride, + Rect: image.Rect(5, 5, 10, 10), + } + src := &image.RGBA{ + Pix: m.Pix, + Stride: m.Stride, + Rect: image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff), + } + // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation. + golden := makeGolden(dst, src, nil, op) + b := dst.Bounds() + if !b.Eq(golden.Bounds()) { + t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds()) + continue + } + // Draw the same combination onto the actual dst using the optimized DrawMask implementation. + DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op) + // Check that the resultant dst image matches the golden output. + 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("drawOverlap xoff=%d,yoff=%d: at (%d, %d), %v versus golden %v", xoff, yoff, x, y, dst.At(x, y), golden.At(x, y)) + continue loop + } + } + } + } + } + } +} + +// TestNonZeroSrcPt checks drawing with a non-zero src point parameter. +func TestNonZeroSrcPt(t *testing.T) { + a := image.NewRGBA(1, 1) + b := image.NewRGBA(2, 2) + b.Set(0, 0, image.RGBAColor{0, 0, 0, 5}) + b.Set(1, 0, image.RGBAColor{0, 0, 5, 5}) + b.Set(0, 1, image.RGBAColor{0, 5, 0, 5}) + b.Set(1, 1, image.RGBAColor{5, 0, 0, 5}) + Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1)) + if !eq(image.RGBAColor{5, 0, 0, 5}, a.At(0, 0)) { + t.Errorf("non-zero src pt: want %v got %v", image.RGBAColor{5, 0, 0, 5}, a.At(0, 0)) + } +} |