// imgHandle serves images of the player's view.
func imgHandle(w http.ResponseWriter, r *http.Request) {
	quality, err := strconv.Atoi(r.FormValue("quality"))
	if err != nil || quality < 0 || quality > 100 {
		quality = 70
	}

	// Center Gopher in view if possible
	gpos := model.Gopher.Pos
	rect := image.Rect(0, 0, ViewWidth, ViewHeight).Add(image.Pt(int(gpos.X)-ViewWidth/2, int(gpos.Y)-ViewHeight/2))

	// But needs correction at the edges of the view (it can't be centered)
	corr := image.Point{}
	if rect.Min.X < 0 {
		corr.X = -rect.Min.X
	}
	if rect.Min.Y < 0 {
		corr.Y = -rect.Min.Y
	}
	if rect.Max.X > model.LabWidth {
		corr.X = model.LabWidth - rect.Max.X
	}
	if rect.Max.Y > model.LabHeight {
		corr.Y = model.LabHeight - rect.Max.Y
	}
	rect = rect.Add(corr)

	model.Mutex.Lock()
	jpeg.Encode(w, model.LabImg.SubImage(rect), &jpeg.Options{quality})
	model.Mutex.Unlock()

	// Store the new view's position:
	Pos = rect.Min
}
Exemple #2
0
func (f *Frame) _draw(pt image.Point) image.Point {
	for nb := 0; nb < f.nbox; nb++ {
		b := f.box[nb]
		f.cklinewrap0(&pt, b)
		if pt.Y == f.Rect.Max.Y {
			f.nchars -= f.strlen(nb)
			f.delbox(nb, f.nbox-1)
			break
		}

		if b.Nrune > 0 {
			n, fits := f.canfit(pt, b)
			if !fits {
				break
			}
			if n != b.Nrune {
				f.splitbox(uint64(nb), uint64(n))
				b = f.box[nb]
			}
			pt.X += b.Wid
		} else {
			if b.Bc == '\n' {
				pt.X = f.Rect.Min.X
				pt.Y += f.Font.Height
			} else {
				pt.X += f.newwid(pt, b)
			}
		}
	}
	return pt
}
Exemple #3
0
func (self *Image) sourceRectTouchOriginalFromInside(width, height int, horAlign HorAlignment, verAlign VerAlignment) (r image.Rectangle) {
	var offset image.Point
	aspectRatio := float64(width) / float64(height)
	if aspectRatio > self.AspectRatio() {
		// Wider than original
		// so touchOriginalFromInside means
		// that the source rect is as wide as the original
		r.Max.X = self.Width()
		r.Max.Y = int(float64(self.Width()) / aspectRatio)
		switch verAlign {
		case VerCenter:
			offset.Y = (self.Height() - r.Max.Y) / 2
		case Bottom:
			offset.Y = self.Height() - r.Max.Y
		}
	} else {
		// Heigher than original,
		// so touchOriginalFromInside means
		// that the source rect is as high as the original
		r.Max.X = int(float64(self.Height()) * aspectRatio)
		r.Max.Y = self.Height()
		switch horAlign {
		case HorCenter:
			offset.X = (self.Width() - r.Max.X) / 2
		case Right:
			offset.X = self.Width() - r.Max.X
		}
	}
	return r.Add(offset)
}
Exemple #4
0
// originTrans translates the origin with respect to the current image and the
// current canvas size. This makes sure we never incorrect position the image.
// (i.e., panning never goes too far, and whenever the canvas is bigger than
// the image, the origin is *always* (0, 0).
func originTrans(pt image.Point, win *window, img *vimage) image.Point {
	// If there's no valid image, then always return (0, 0).
	if img == nil {
		return image.Point{0, 0}
	}

	// Quick aliases.
	ww, wh := win.Geom.Width(), win.Geom.Height()
	dw := img.Bounds().Dx() - ww
	dh := img.Bounds().Dy() - wh

	// Set the allowable range of the origin point of the image.
	// i.e., never less than (0, 0) and never greater than the width/height
	// of the image that isn't viewable at any given point (which is determined
	// by the canvas size).
	pt.X = min(img.Bounds().Min.X+dw, max(pt.X, 0))
	pt.Y = min(img.Bounds().Min.Y+dh, max(pt.Y, 0))

	// Validate origin point. If the width/height of an image is smaller than
	// the canvas width/height, then the image origin cannot change in x/y
	// direction.
	if img.Bounds().Dx() < ww {
		pt.X = 0
	}
	if img.Bounds().Dy() < wh {
		pt.Y = 0
	}

	return pt
}
Exemple #5
0
func (f *Frame) advance(p *image.Point, b *frbox) {
	if b.Nrune < 0 && b.Bc == '\n' {
		p.X = f.Rect.Min.X
		p.Y += f.Font.Height
	} else {
		p.X += b.Wid
	}
}
func main() {
	g = image.NewGray(image.Rectangle{image.Point{0, 0}, image.Point{w, h}})
	// off center seed position makes pleasingly asymetrical tree
	g.SetGray(w/3, h/3, color.Gray{frost})
generate:
	for a := 0; a < n; {
		// generate random position for new particle
		rp := image.Point{rand.Intn(w), rand.Intn(h)}
		if g.At(rp.X, rp.Y).(color.Gray).Y == frost {
			// position is already set.  find a nearby free position.
			for {
				rp.X += rand.Intn(3) - 1
				rp.Y += rand.Intn(3) - 1
				// execpt if we run out of bounds, consider the particle lost.
				if !rp.In(g.Rect) {
					continue generate
				}
				if g.At(rp.X, rp.Y).(color.Gray).Y != frost {
					break
				}
			}
		} else {
			// else particle is in free space.  let it wander
			// until it touches tree
			for !hasNeighbor(rp) {
				rp.X += rand.Intn(3) - 1
				rp.Y += rand.Intn(3) - 1
				// but again, if it wanders out of bounds consider it lost.
				if !rp.In(g.Rect) {
					continue generate
				}
			}
		}
		// x, y now specify a free position toucing the tree.
		g.SetGray(rp.X, rp.Y, color.Gray{frost})
		a++
		// progress indicator
		if a%100 == 0 {
			fmt.Println(a, "of", n)
		}
	}
	f, err := os.Create("tree.png")
	if err != nil {
		fmt.Println(err)
		return
	}
	err = png.Encode(f, g)
	if err != nil {
		fmt.Println(err)
	}
	f.Close()
}
Exemple #7
0
func (f *Frame) cklinewrap(p *image.Point, b *frbox) {
	if b.Nrune < 0 {
		if b.Minwid > byte(f.Rect.Max.X-p.X) {
			p.X = f.Rect.Min.X
			p.Y += f.Font.Height
		}
	} else {
		if b.Wid > f.Rect.Max.X-p.X {
			p.X = f.Rect.Min.X
			p.Y += f.Font.Height
		}
	}
}
Exemple #8
0
func (atlas *ManagedAtlas) LoadGroupSheetOffset(path string, pt image.Point, width, height, frames int) (err error, groupID ID) {
	fName := filepath.Base(path)
	extIndex := strings.LastIndex(fName, ".")
	if extIndex != -1 {
		fName = fName[:extIndex]
	}
	fName = atlas.nextGroupID(fName)

	file, e := os.Open(path)
	if e != nil {
		return e, nil
	}
	defer file.Close()
	ds, e := file.Stat()
	if e != nil {
		return e, nil
	}
	if ds.IsDir() {
		return errors.New("The path is not a file. " + path), nil
	}

	file.Close()

	img, e := LoadImage(path)
	if e != nil {
		return e, nil
	}

	group := make([]ID, 0)

	point := image.Point{pt.X, pt.Y}
	for i := 0; i < frames; i++ {
		is := strconv.FormatInt(int64(i), 10)
		if i == 0 {
			is = ""
		}
		sprite := image.NewRGBA(image.Rect(0, 0, width, height))
		draw.Draw(sprite, sprite.Rect, img, point, draw.Src)
		atlas.AddImage(sprite, fName+is)
		group = append(group, fName+is)

		point.X += width
		if !sprite.Rect.Add(point).In(img.Bounds()) {
			point.X = 0
			point.Y += height
		}
	}
	atlas.groups[fName] = group
	return nil, fName
}
Exemple #9
0
func collide(pt image.Point, p *Piece) bool {
	pt.X = (pt.X - rboard.Min.X) / pcsz
	pt.Y = (pt.Y - rboard.Min.Y) / pcsz
	for _, q := range p.d {
		pt.X += q.X
		pt.Y += q.Y
		if pt.X < 0 || pt.X >= NX || pt.Y < 0 || pt.Y >= NY {
			return true
		}
		if board[pt.Y][pt.X] != 0 {
			return true
		}
	}
	return false
}
Exemple #10
0
// Clamp a bound to inside the given bounds
func clamp(point image.Point, bounds image.Rectangle) image.Point {
	if point.X < bounds.Min.X {
		point.X = bounds.Min.X
	}
	if point.X >= bounds.Max.X {
		point.X -= bounds.Max.X - 1
	}
	if point.Y < bounds.Min.Y {
		point.Y = bounds.Min.Y
	}
	if point.Y >= bounds.Max.Y {
		point.Y = bounds.Max.Y - 1
	}

	return point
}
Exemple #11
0
func anchor(r image.Rectangle, flags Anchor, p image.Point) image.Rectangle {
	var dp image.Point
	switch flags & (E | W) {
	case E:
		dp.X = r.Dx()
	case E | W, 0:
		dp.X = r.Dx() / 2
	}
	switch flags & (N | S) {
	case S:
		dp.Y = r.Dy()
	case S | N, 0:
		dp.Y = r.Dy() / 2
	}
	return r.Add(p.Sub(r.Min).Sub(dp))
}
Exemple #12
0
func canfit(p *Piece) bool {
	var dx = [...]int{0, -1, 1, -2, 2, -3, 3, 4, -4}
	j := N + 1
	if j >= 4 {
		j = p.sz.X
		if j < p.sz.Y {
			j = p.sz.Y
		}
		j = 2*j - 1
	}
	for i := 0; i < j; i++ {
		var z image.Point
		z.X = pos.X + dx[i]*pcsz
		z.Y = pos.Y
		if !collide(z, p) {
			z.Y = pos.Y + pcsz - 1
			if !collide(z, p) {
				undrawpiece()
				pos.X = z.X
				return true
			}
		}
	}
	return false
}
func main() {
	bounds := image.Rect(0, 0, 100, 100)
	im := image.NewGray(bounds)
	gBlack := color.Gray{0}
	gWhite := color.Gray{255}
	draw.Draw(im, bounds, image.NewUniform(gWhite), image.ZP, draw.Src)
	pos := image.Point{50, 50}
	dir := up
	for pos.In(bounds) {
		switch im.At(pos.X, pos.Y).(color.Gray).Y {
		case gBlack.Y:
			im.SetGray(pos.X, pos.Y, gWhite)
			dir--
		case gWhite.Y:
			im.SetGray(pos.X, pos.Y, gBlack)
			dir++
		}
		if dir&1 == 1 {
			pos.X += 1 - dir&2
		} else {
			pos.Y -= 1 - dir&2
		}
	}
	f, err := os.Create("ant.png")
	if err != nil {
		fmt.Println(err)
		return
	}
	if err = png.Encode(f, im); err != nil {
		fmt.Println(err)
	}
	if err = f.Close(); err != nil {
		fmt.Println(err)
	}
}
Exemple #14
0
func (f *Frame) ptofcharptb(p uint64, pt image.Point, bn int) image.Point {

	var b *frbox
	var w int
	var r rune

	for ; bn < f.nbox; bn++ {
		b = f.box[bn]
		f.cklinewrap(&pt, b)
		l := nrune(b)
		if p < uint64(l) {
			if b.Nrune > 0 {
				for s := 0; s < len(b.Ptr) && p > 0; s += w {
					p--
					r, w = utf8.DecodeRune(b.Ptr[s:])
					pt.X += f.Font.StringWidth(string(b.Ptr[s : s+1]))
					if r == 0 || pt.X > f.Rect.Max.X {
						panic("frptofchar")
					}
				}
			}
			break
		}
		p -= uint64(l)
		f.advance(&pt, b)
	}

	return pt
}
Exemple #15
0
func (f *Frame) SelectPaint(p0, p1 image.Point, col *draw.Image) {
	q0 := p0
	q1 := p1

	q0.Y += f.Font.Height
	q1.Y += f.Font.Height

	n := (p1.Y - p0.Y) / f.Font.Height
	if f.Background == nil {
		panic("Frame.SelectPaint B == nil")
	}
	if p0.Y == f.Rect.Max.Y {
		return
	}
	if n == 0 {
		f.Background.Draw(Rpt(p0, q1), col, nil, image.ZP)
	} else {
		if p0.X >= f.Rect.Max.X {
			p0.X = f.Rect.Max.X - 1
		}
		f.Background.Draw(image.Rect(p0.X, p0.Y, f.Rect.Max.X, q0.Y), col, nil, image.ZP)
		if n > 1 {
			f.Background.Draw(image.Rect(f.Rect.Min.X, q0.Y, f.Rect.Max.X, p1.Y), col, nil, image.ZP)
		}
		f.Background.Draw(image.Rect(f.Rect.Min.X, p1.Y, q1.X, q1.Y), col, nil, image.ZP)
	}
}
Exemple #16
0
// Thumbnail scales and crops src so it fits in dst.
func Thumbnail(dst draw.Image, src image.Image) error {
	// Scale down src in the dimension that is closer to dst.
	sb := src.Bounds()
	db := dst.Bounds()
	rx := float64(sb.Dx()) / float64(db.Dx())
	ry := float64(sb.Dy()) / float64(db.Dy())
	var b image.Rectangle
	if rx < ry {
		b = image.Rect(0, 0, db.Dx(), int(float64(sb.Dy())/rx))
	} else {
		b = image.Rect(0, 0, int(float64(sb.Dx())/ry), db.Dy())
	}

	buf := image.NewRGBA(b)
	if err := Scale(buf, src); err != nil {
		return err
	}

	// Crop.
	// TODO(crawshaw): improve on center-alignment.
	var pt image.Point
	if rx < ry {
		pt.Y = (b.Dy() - db.Dy()) / 2
	} else {
		pt.X = (b.Dx() - db.Dx()) / 2
	}
	draw.Draw(dst, db, buf, pt, draw.Src)
	return nil
}
Exemple #17
0
func (f *Frame) grid(p image.Point) image.Point {
	p.Y -= f.Rect.Min.Y
	p.Y -= p.Y % f.Font.Height
	p.Y += f.Rect.Min.Y
	if p.X > f.Rect.Max.X {
		p.X = f.Rect.Max.X
	}
	return p
}
Exemple #18
0
Fichier : util.go Projet : oov/psd
// clip clips r against each image's bounds (after translating into the
// destination image's coordinate space) and shifts the points sp and mp by
// the same amount as the change in r.Min.
func clip(dst draw.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
	if mp != nil {
		mp.X += dx
		mp.Y += dy
	}
}
func GenerateImage(generatorSpecifications GeneratorSpecifications) *image.RGBA {
	minPoint := image.Point{0, 0}
	maxPoint := minPoint
	for i := 0; i < len(generatorSpecifications.lines); i++ {
		currentLen := len(generatorSpecifications.lines[i].points)
		for j := 1; j <= currentLen; j++ {
			currentPoint := Point(generatorSpecifications.lines[i].points[j%currentLen])
			max := Max(int(generatorSpecifications.lines[i].jagged[j%currentLen]), int(generatorSpecifications.lines[i].jagged[j-1])) + int(generatorSpecifications.lines[i].width)
			min := max / 2
			max -= min

			maxPoint.X = Max(maxPoint.X, currentPoint.X+max)
			maxPoint.Y = Max(maxPoint.Y, currentPoint.Y+max)
			minPoint.X = Min(maxPoint.X, currentPoint.X-min)
			minPoint.Y = Min(maxPoint.Y, currentPoint.Y-min)
		}
	}

	for i := 0; i < len(generatorSpecifications.circles); i++ {
		currentPoint := Point(generatorSpecifications.circles[i].point)
		max := int(generatorSpecifications.circles[i].width + generatorSpecifications.circles[i].jagged)
		min := max / 2
		max += int(generatorSpecifications.circles[i].radius) - min
		min += int(generatorSpecifications.circles[i].radius)

		maxPoint.X = Max(maxPoint.X, currentPoint.X+max)
		maxPoint.Y = Max(maxPoint.Y, currentPoint.Y+max)
		minPoint.X = Min(maxPoint.X, currentPoint.X-min)
		minPoint.Y = Min(maxPoint.Y, currentPoint.Y-min)
	}

	ret := image.NewRGBA(image.Rectangle{minPoint, maxPoint})

	for i := 0; i < len(generatorSpecifications.lines); i++ {
		DrawLine(ret, generatorSpecifications.lines[i])
	}
	for i := 0; i < len(generatorSpecifications.circles); i++ {
		DrawCircle(ret, generatorSpecifications.circles[i])
	}

	return ret
}
Exemple #20
0
func (f *Frame) DrawText(pt image.Point, text *draw.Image, back *draw.Image) {

	for nb := 0; nb < f.nbox; nb++ {
		b := f.box[nb]
		f.cklinewrap(&pt, b)
		if !f.noredraw && b.Nrune >= 0 {
			f.Background.String(pt, text, image.ZP, f.Font, string(b.Ptr))
		}
		pt.X += b.Wid
	}
}
Exemple #21
0
func (poly *jsonWedPolygon) BoundingBox() image.Rectangle {
	min := image.Point{math.MaxUint16, math.MaxUint16}
	max := image.Point{0, 0}

	for _, vert := range poly.Verts {
		if vert.X < min.X {
			min.X = int(vert.X)
		}
		if vert.Y < min.Y {
			min.Y = int(vert.Y)
		}
		if vert.X > max.X {
			max.X = int(vert.X)
		}
		if vert.Y > max.Y {
			max.Y = int(vert.Y)
		}
	}
	return image.Rectangle{min, max}.Canon()

}
Exemple #22
0
func fitted(into image.Rectangle, size image.Point) image.Rectangle {
	ratio := float64(size.X) / float64(size.Y)

	if size.X > into.Dx() {
		size.X = into.Dx()
		size.Y = int(float64(into.Dx()) / ratio)
	}

	if size.Y > into.Dy() {
		size.X = int(float64(into.Dy()) * ratio)
		size.Y = into.Dy()
	}

	r := image.Rectangle{
		Min: into.Min,
		Max: into.Min.Add(size),
	}
	r = r.Add(image.Point{into.Dx() / 2, into.Dy() / 2}).
		Sub(image.Point{size.X / 2, size.Y / 2})

	return r
}
Exemple #23
0
func getSpriteSize(imgs []*cssImage) image.Point {
	p := image.Point{}
	for _, img := range imgs {
		st := img.img
		if st.sp.X == -1 {
			st.sp = image.Pt(-p.X, 0)
			p.X += st.dx()
			if p.Y < st.dy() {
				p.Y = st.dy()
			}
		}
	}
	return p
}
Exemple #24
0
// Image Handler
// Reads the stored GIF & overlay for the authenticated user and returns a new GIF with the overlay copied onto each frame of the input GIF
func imageHandler(w http.ResponseWriter, r *http.Request) {
	token, _ := jwt.ParseFromRequest(r, hmacKeyFunc)
	issuer := token.Claims["iss"].(string)

	gfFile, err1 := os.Open(getFileName("gif:" + issuer))
	overlayFile, err2 := os.Open(getFileName("overlay:" + issuer))

	if err1 != nil || err2 != nil {
		sendResponse(w, 400, map[string]string{
			"error": "Error while reading images",
		})
		return
	}

	// Decode images
	dst, err1 := gif.DecodeAll(gfFile)
	src, _, err2 := image.Decode(overlayFile)

	if err1 != nil || err2 != nil {
		sendResponse(w, 400, map[string]string{
			"error": "Error while decoding images",
		})
		return
	}

	// Create location for the draw
	pt := new(image.Point)
	x, err := strconv.Atoi(r.URL.Query().Get("x"))
	if err != nil {
		x = 0
	}
	y, err := strconv.Atoi(r.URL.Query().Get("y"))
	if err != nil {
		y = 0
	}
	pt.X = x
	pt.Y = y
	// Draw the overlay over each frame of the GIF
	for _, frame := range dst.Image {
		draw.Draw(frame, frame.Bounds(), src, *pt, draw.Over)
	}

	// Encode image to base64
	buffer := new(bytes.Buffer)
	gif.EncodeAll(buffer, dst)
	fmt.Fprint(w, base64.StdEncoding.EncodeToString(buffer.Bytes()))
}
Exemple #25
0
func (f *Frame) newwid0(pt image.Point, b *frbox) int {
	c := f.Rect.Max.X
	x := pt.X
	if b.Nrune >= 0 || b.Bc != '\t' {
		return b.Wid
	}
	if x+int(b.Minwid) > c {
		pt.X = f.Rect.Min.X
		x = pt.X
	}
	x += f.maxtab
	x -= (x - f.Rect.Min.X) % f.maxtab
	if x-pt.X < int(b.Minwid) || x > c {
		x = pt.X + int(b.Minwid)
	}
	return x - pt.X
}
Exemple #26
0
func (poly *jsonWedPolygon) EffectedTiles(pt image.Point) []int {
	out := make([]int, 0)
	bb := poly.BoundingBox()
	bb.Min.X /= 64
	bb.Min.Y /= 64
	bb.Max.X /= 64
	bb.Max.Y /= 64
	pt.X /= 64
	pt.Y /= 64

	// All tiles in our bounding box should be considered effected
	for y := bb.Min.Y; y <= bb.Max.Y; y++ {
		for x := bb.Min.X; x <= bb.Max.X; x++ {
			out = append(out, y*pt.X+x)
		}
	}

	return out
}
Exemple #27
0
func (f *Frame) _tick(pt image.Point, ticked bool) {
	if f.ticked == ticked || f.tick == nil || !pt.In(f.Rect) {
		return
	}

	pt.X -= f.tickscale
	r := image.Rect(pt.X, pt.Y, pt.X+frtickw*f.tickscale, pt.Y+f.Font.Height)

	if r.Max.X > f.Rect.Max.X {
		r.Max.X = f.Rect.Max.X
	}
	if ticked {
		f.tickback.Draw(f.tickback.R, f.Background, nil, pt)
		f.Background.Draw(r, f.tick, nil, image.ZP)
	} else {
		f.Background.Draw(r, f.tickback, nil, image.ZP)
	}
	f.ticked = ticked
}
Exemple #28
0
// Uses half-sample symmetry.
func Symmetric(im image.Image, p image.Point) color.Color {
	b := im.Bounds()
	// Make twice as big.
	d := image.Rectangle{b.Min, b.Max.Add(b.Size())}
	p = p.Mod(d)

	// Move to origin.
	p = p.Sub(b.Min)
	w, h := b.Dx(), b.Dy()
	if p.X > w-1 {
		p.X = 2*w - 1 - p.X
	}
	if p.Y > h-1 {
		p.Y = 2*h - 1 - p.Y
	}
	p = p.Add(b.Min)

	return im.At(p.X, p.Y)
}
Exemple #29
0
// Warning: does modify contents of polys
func DrawPolygons(mul float64, polys []polyclip.Polygon) *image.NRGBA {
	img := image.NewNRGBA(image.Rect(0, 0, 0, 0))
	min := polyclip.Point{0, 0}

	for i, polygon := range polys {
		r0 := safebbox(polygon)
		translate := image.Point{}
		if r0.Min.X < min.X {
			translate.X = int(mul*min.X) - int(mul*r0.Min.X)
			min.X = r0.Min.X
		}
		if r0.Min.Y < min.Y {
			translate.Y = int(mul*min.Y) - int(mul*r0.Min.Y)
			min.Y = r0.Min.Y
		}

		for _, c := range polygon {
			for j, p := range c {
				c[j].X = (p.X - min.X) * mul
				c[j].Y = (p.Y - min.Y) * mul
			}
		}
		r := safebbox(polygon)

		img2 := image.NewNRGBA(img.Bounds().Add(translate).Union(image.Rect(int(r.Min.X), int(r.Min.Y), int(r.Max.X), int(r.Max.Y))))
		draw.Draw(img2, img2.Bounds(), image.Black, image.Pt(0, 0), draw.Src)
		draw.Draw(img2, img.Bounds().Add(translate), img, image.Pt(0, 0), draw.Src)
		img = img2

		for _, c := range polygon {
			polyutil.DrawPolyline(c, brush(img, i))
		}
	}

	return img
}
Exemple #30
0
// Text renders a text onto an image.
//
// TODO: support multiple lines
func (f *Font) Text(dst draw.Image, text string, opts ...Option) error {
	o := options{
		fg: color.White,
	}
	for _, opt := range opts {
		if err := opt(&o); err != nil {
			return err
		}
	}

	// X size 0 means preserve aspect ratio.
	//
	// Freetype docs say "You should not rely on the resulting glyphs
	// matching, or being constrained, to this pixel size." -- well,
	// we don't. We clip them to that bounding box, and don't crash or
	// corrupt memory. If it looks ugly, it's the font designer's
	// fault; switch to a better font.
	if err := f.font.SetSizePixels(0, dst.Bounds().Dy()); err != nil {
		return err
	}

	// Origin point for the baseline: left edge, lifted from the
	// bottom as much as the font descender says.
	//
	// TODO this results in a slightly ugly layout, with a lot of
	// negative space below the text, and less above; this is the
	// opposite of what one is supposed to do. The only fix, given
	// existing fonts with deep drops for "g" etc, is to just add
	// negative space above the text. Unfortunately, there seems to be
	// no good way to decide how much space to add; the state of the
	// art seems to be "expert human manually adjusts", which sucks.
	//
	// If we were to perform a similar calculation for the baseline,
	// starting from the drop and coming down by how much Ascender
	// tells us to, we'd end up in the same spot; that is of no use.
	pos := image.Point{
		X: dst.Bounds().Min.X,
		// Descender is in dimensionless units, of which there are
		// UnitsPerEm in the width & height of the em box, which is
		// the size we asked to fit in our image.
		//
		// Because of us truncating subpixel alignment to nearest
		// pixel, this might might end up being off by 1, but if we
		// add floor/ceil here, we'll just shuffle that lost pixel
		// between top and bottom of the image. Perhaps we should give
		// SetSizePixels a pre-shrunk height?
		Y: dst.Bounds().Max.Y + f.font.Descender*dst.Bounds().Dy()/f.font.UnitsPerEm,
	}

	if debugBaseline {
		draw.Draw(dst,
			image.Rectangle{
				Min: image.Point{X: dst.Bounds().Min.X, Y: pos.Y},
				Max: image.Point{X: dst.Bounds().Max.X, Y: pos.Y + 1},
			},
			image.NewUniform(color.RGBA{G: 0xFF, A: 0xFF}), image.ZP,
			draw.Over)
	}

	var prev rune
	for _, ch := range text {
		glyph, err := f.font.Load(f.font.Index(ch))
		if err != nil {
			return err
		}
		// All glyph measurements are in 26.6 format, and should be
		// pixel-aligned by now by the hinting process. Shift by 6 to
		// get pixels.
		i, err := glyph.Image()
		if err != nil {
			return err
		}
		if prev != 0 {
			x, _, err := f.font.Kerning(prev, ch)
			if err != nil {
				return err
			}
			pos.X += x >> 6
		}
		prev = ch
		to := image.Rectangle{pos, dst.Bounds().Max}
		to.Min.X += glyph.HMetrics.BearingX >> 6
		to.Min.Y -= glyph.HMetrics.BearingY >> 6

		if debugGlyphHeight {
			y := pos.Y - glyph.HMetrics.BearingY>>6
			draw.Draw(dst,
				image.Rectangle{
					Min: image.Point{X: to.Min.X, Y: y},
					Max: image.Point{X: to.Min.X + glyph.HMetrics.Advance>>6, Y: y + 1},
				},
				image.NewUniform(color.RGBA{R: 0xFF, B: 0xFF, A: 0xFF}), image.ZP,
				draw.Over)
		}

		draw.DrawMask(dst, to,
			image.NewUniform(o.fg), image.ZP,
			i, i.Bounds().Min,
			draw.Over)
		pos.X += glyph.HMetrics.Advance >> 6
	}
	return nil
}