func traverseTiles(Style style, Area area) (image.Image, error) { m := image.NewRGBA(image.Rect(-Margins, -Margins, Area.TileRange.Dx()*TileSize+Margins, Area.TileRange.Dy()*TileSize+Margins)) whiteColor := color.RGBA{255, 255, 255, 255} draw.Draw(m, m.Bounds(), &image.Uniform{whiteColor}, image.ZP, draw.Src) frameColor := color.RGBA{50, 50, 50, 255} draw.Draw(m, image.Rect(-Frame, -Frame, Area.TileRange.Dx()*TileSize+Frame, Area.TileRange.Dy()*TileSize+Frame), &image.Uniform{frameColor}, image.ZP, draw.Src) position := image.Rectangle{image.ZP, image.Point{X: TileSize, Y: TileSize}} for y := Area.TileRange.Min.Y; y < Area.TileRange.Max.Y; y++ { for x := Area.TileRange.Min.X; x < Area.TileRange.Max.X; x++ { img, err := readTile(Style.Mapid, Area.Z, uint(x), uint(y)) if err != nil { fmt.Printf("no luck reading this tile: z:%v, %vx %v \n", Area.Z, x, y) fmt.Println(err) break } fmt.Printf("...adding %vx%v \n", x, y) draw.Draw(m, position, img, image.ZP, draw.Src) position = position.Add(image.Point{X: TileSize, Y: 0}) } position = image.Rectangle{image.Point{X: 0, Y: (y - Area.TileRange.Min.Y + 1) * TileSize}, image.Point{X: TileSize, Y: (y - Area.TileRange.Min.Y + 2) * TileSize}} } fmt.Println("Great success!") return m, nil }
// SubImage returns an image representing the portion of the image p visible // through r. The returned value shares pixels with the original image. func (p *Image) SubImage(r image.Rectangle) image.Image { // TODO: share code with image.NewYCbCr when this type moves into the // standard image package. r = r.Intersect(p.Rect) // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside // either r1 or r2 if the intersection is empty. Without explicitly checking for // this, the Pix[i:] expression below can panic. if r.Empty() { return &Image{ YCbCr: image.YCbCr{ SubsampleRatio: p.SubsampleRatio, }, } } yi := p.YOffset(r.Min.X, r.Min.Y) ci := p.COffset(r.Min.X, r.Min.Y) ai := p.AOffset(r.Min.X, r.Min.Y) return &Image{ YCbCr: image.YCbCr{ Y: p.Y[yi:], Cb: p.Cb[ci:], Cr: p.Cr[ci:], SubsampleRatio: p.SubsampleRatio, YStride: p.YStride, CStride: p.CStride, Rect: r, }, A: p.A[ai:], AStride: p.AStride, } }
// New returns a new Image with the given bounds and subsample ratio. func New(r image.Rectangle, subsampleRatio image.YCbCrSubsampleRatio) *Image { // TODO: share code with image.NewYCbCr when this type moves into the // standard image package. w, h, cw, ch := r.Dx(), r.Dy(), 0, 0 switch subsampleRatio { case image.YCbCrSubsampleRatio422: cw = (r.Max.X+1)/2 - r.Min.X/2 ch = h case image.YCbCrSubsampleRatio420: cw = (r.Max.X+1)/2 - r.Min.X/2 ch = (r.Max.Y+1)/2 - r.Min.Y/2 case image.YCbCrSubsampleRatio440: cw = w ch = (r.Max.Y+1)/2 - r.Min.Y/2 default: // Default to 4:4:4 subsampling. cw = w ch = h } b := make([]byte, 2*w*h+2*cw*ch) // TODO: use s[i:j:k] notation to set the cap. return &Image{ YCbCr: image.YCbCr{ Y: b[:w*h], Cb: b[w*h+0*cw*ch : w*h+1*cw*ch], Cr: b[w*h+1*cw*ch : w*h+2*cw*ch], SubsampleRatio: subsampleRatio, YStride: w, CStride: cw, Rect: r, }, A: b[w*h+2*cw*ch:], AStride: w, } }
func (opts *DecodeOpts) wantRescale(b image.Rectangle, swapDimensions bool) bool { if opts == nil { return false } // In rescale Scale* trumps Max* so we assume the same relationship here. // Floating point compares probably only allow this to work if the values // were specified as the literal 1 or 1.0, computed values will likely be // off. If Scale{Width,Height} end up being 1.0-epsilon we'll rescale // when it probably wouldn't even be noticible but that's okay. if opts.ScaleWidth == 1.0 && opts.ScaleHeight == 1.0 { return false } if opts.ScaleWidth > 0 && opts.ScaleWidth < 1.0 || opts.ScaleHeight > 0 && opts.ScaleHeight < 1.0 { return true } w, h := b.Dx(), b.Dy() if swapDimensions { w, h = h, w } // Same size, don't rescale. if opts.MaxWidth == w && opts.MaxHeight == h { return false } return opts.MaxWidth > 0 && opts.MaxWidth < w || opts.MaxHeight > 0 && opts.MaxHeight < h }
// Trimmed ... func (t *Tree) Trimmed() *Tree { var leafs []image.Rectangle t.Visit(func(other *Tree) { if len(other.points) == 0 { return } leafs = append(leafs, image.Rectangle{ Min: other.Extents.Min, Max: other.Extents.Max, }) }) var r *image.Rectangle for _, b := range leafs { if r == nil { r = new(image.Rectangle) *r = b continue } *r = r.Union(b) } tr := New(*r, t.capacity) t.Visit(func(other *Tree) { for p := range other.points { tr.Insert(p) } }) return tr }
// 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 }
// PackHorzontal finds the Pos for a horizontally packed sprite func (l *Sprite) PackHorizontal(pos int) Pos { if pos == -1 || pos == 0 { return Pos{0, 0} } var x, y int var rect image.Rectangle l.optsMu.RLock() padding := l.opts.Padding l.optsMu.RUnlock() // there are n-1 paddings in an image list x = padding * pos // No padding on the outside of the image numimages := l.Len() if pos == numimages { x -= padding } for i := 1; i <= pos; i++ { l.goImagesMu.RLock() rect = l.imgs[i-1].Bounds() l.goImagesMu.RUnlock() x += rect.Dx() if pos == numimages { y = int(math.Max(float64(y), float64(rect.Dy()))) } } return Pos{ x, y, } }
func checkPsnrs(t *testing.T, ref, img image.Image, sub image.Rectangle, min []float64) { var a, b image.Image a, b = ref, img if !sub.Empty() { switch a.(type) { case *image.RGBA: a = a.(*image.RGBA).SubImage(sub) b = b.(*image.RGBA).SubImage(sub) case *image.NRGBA: a = a.(*image.NRGBA).SubImage(sub) b = b.(*image.NRGBA).SubImage(sub) case *image.YCbCr: a = a.(*image.YCbCr).SubImage(sub) b = b.(*image.YCbCr).SubImage(sub) case *image.Gray: a = a.(*image.Gray).SubImage(sub) b = b.(*image.Gray).SubImage(sub) } } psnrs, err := Psnr(a, b) expect(t, err, nil) for i, v := range psnrs { if v < min[i] { t.Fatalf("invalid psnr %v < %v\n", v, min[i]) } } }
func Make(r image.Rectangle) *Image { return &Image{ Pix: make([]byte, r.Dx()*r.Dy()*3), Stride: r.Dx() * 3, Rect: image.Rect(0, 0, r.Dx(), r.Dy()), } }
func (a *area) Repaint(r image.Rectangle) { r = image.Rect(0, 0, a.width, a.height).Intersect(r) if r.Empty() { return } C.gtk_widget_queue_draw_area(a.widget, C.gint(r.Min.X), C.gint(r.Min.Y), C.gint(r.Dx()), C.gint(r.Dy())) }
func (p *resizeToFitFilter) Bounds(srcBounds image.Rectangle) image.Rectangle { w, h := p.width, p.height srcw, srch := srcBounds.Dx(), srcBounds.Dy() if w <= 0 || h <= 0 || srcw <= 0 || srch <= 0 { return image.Rect(0, 0, 0, 0) } if srcw <= w && srch <= h { return image.Rect(0, 0, srcw, srch) } wratio := float64(srcw) / float64(w) hratio := float64(srch) / float64(h) var dstw, dsth int if wratio > hratio { dstw = w dsth = minint(int(float64(srch)/wratio+0.5), h) } else { dsth = h dstw = minint(int(float64(srcw)/hratio+0.5), w) } return image.Rect(0, 0, dstw, dsth) }
func imfilter(filter [][]float32, rect image.Rectangle, rgbFunc func(xpos, ypos int) (r0, g0, b0, a0 uint8), mergFunc func(a, b float32) float32) *image.RGBA { wg := sync.WaitGroup{} size := rect.Size() imgGrey := image.NewRGBA(rect) fX := len(filter) fY := len(filter[0]) xoffset := fX / 2 yoffset := fY / 2 threadCount := (fX + fY) / 2 sizeoffset := (size.X - fX) / threadCount timeStart := time.Now() wg.Add(threadCount) for i := 0; i < threadCount; i++ { go func(i int) { for x := xoffset + i*sizeoffset; x < xoffset+(i+1)*sizeoffset; x++ { for y := yoffset; y < size.Y-yoffset; y++ { newColor := imfilterMerg(filter, fX, fY, x, y, xoffset, yoffset, rgbFunc, mergFunc) imgGrey.SetRGBA(x, y, newColor) } } wg.Done() }(i) } wg.Wait() fmt.Println("Filt Time", time.Now().Sub(timeStart)) return imgGrey }
func NewPicture(bounds image.Rectangle) *Picture { return &Picture{ Data: make([]float64, bounds.Dx()*bounds.Dy()), Gamma: 1.0, //2.2, Rect: bounds, } }
func (v *platformView) drawScene(r image.Rectangle) { //log.Printf("platformView.drawScene %v\n", r) x, y, w, h := r.Min.X, r.Min.Y, r.Size().X, r.Size().Y v.area.QueueDrawArea(v.parent.ScaleCoord(x, false), v.parent.ScaleCoord(y, false), v.parent.ScaleCoord(w, true)+1, v.parent.ScaleCoord(h, true)+1) return }
// NewYCbCrAligned Allocates YCbCr image with padding. // Because LibJPEG needs extra padding to decoding buffer, This func add an // extra alignSize (16) padding to cover overflow from any such modes. func NewYCbCrAligned(r image.Rectangle, subsampleRatio image.YCbCrSubsampleRatio) *image.YCbCr { w, h, cw, ch := r.Dx(), r.Dy(), 0, 0 switch subsampleRatio { case image.YCbCrSubsampleRatio422: cw = (r.Max.X+1)/2 - r.Min.X/2 ch = h case image.YCbCrSubsampleRatio420: cw = (r.Max.X+1)/2 - r.Min.X/2 ch = (r.Max.Y+1)/2 - r.Min.Y/2 case image.YCbCrSubsampleRatio440: cw = w ch = (r.Max.Y+1)/2 - r.Min.Y/2 default: cw = w ch = h } // TODO: check the padding size to minimize memory allocation. yStride := pad(w, alignSize) + alignSize cStride := pad(cw, alignSize) + alignSize yHeight := pad(h, alignSize) + alignSize cHeight := pad(ch, alignSize) + alignSize b := make([]byte, yStride*yHeight+2*cStride*cHeight) return &image.YCbCr{ Y: b[:yStride*yHeight], Cb: b[yStride*yHeight+0*cStride*cHeight : yStride*yHeight+1*cStride*cHeight], Cr: b[yStride*yHeight+1*cStride*cHeight : yStride*yHeight+2*cStride*cHeight], SubsampleRatio: subsampleRatio, YStride: yStride, CStride: cStride, Rect: r, } }
// New allocates and initializes a new DockApp. NewDockApp does not initialize // the window contents and does not map the window to the display screen. The // window is mapped to the screen when the Main method is called on the // returned DockApp. func New(x *xgbutil.XUtil, rect image.Rectangle) (*DockApp, error) { win, err := xwindow.Generate(x) if err != nil { log.Fatalf("generate window: %v", err) } win.Create(x.RootWin(), 0, 0, rect.Size().X, rect.Size().Y, 0) // Set WM hints so that Openbox puts the window into the dock. hints := &icccm.Hints{ Flags: icccm.HintState | icccm.HintIconWindow, InitialState: icccm.StateWithdrawn, IconWindow: win.Id, WindowGroup: win.Id, } err = icccm.WmHintsSet(x, win.Id, hints) if err != nil { win.Destroy() return nil, fmt.Errorf("wm hints: %v", err) } img := xgraphics.New(x, rect) err = img.XSurfaceSet(win.Id) if err != nil { img.Destroy() win.Destroy() return nil, fmt.Errorf("xsurface set: %v", err) } app := &DockApp{ x: x, img: img, win: win, } return app, nil }
// Takes a bounding box in an image r. // Coerces it to the aspect ratio of target.Int according to mode. // Returns the rectangle which, when resized to target.Size, // will have the bounding box in target.Int. // Panics if the target has non-positive interior. func FitRect(orig image.Rectangle, target PadRect, mode string) (scale float64, fit image.Rectangle) { if target.Int.Dx() <= 0 || target.Int.Dy() <= 0 { panic("empty interior") } if mode == "stretch" { panic("stretch mode not supported by FitRect") } aspect := float64(target.Int.Dx()) / float64(target.Int.Dy()) // Width and height of box in image. w, h := float64(orig.Dx()), float64(orig.Dy()) // Co-erce size to match aspect ratio. w, h = SetAspect(w, h, aspect, mode) // If source is smaller than target, then scale is > 1 (i.e. need to magnify). scale = float64(target.Int.Dx()) / w // == float64(target.Int.Dy()) / h // Get position of interior centroid in target rectangle. left, top := centroid(target.Int) right, bottom := float64(target.Size.X)-left, float64(target.Size.Y)-top // Get position of centroid of original bounding box in image. x, y := centroid(orig) // Scale offsets on all sides and add to centroid for final rectangle. // If scale is greater than 1 then source is smaller than target. // Then the rectangle in the source image is shrunk (i.e. divide by scale). x0, x1 := x-left/scale, x+right/scale y0, y1 := y-top/scale, y+bottom/scale fit = image.Rect(round(x0), round(y0), round(x1), round(y1)) return }
// PackHorzontal finds the Pos for a horizontally packed sprite func (l *ImageList) PackHorizontal(pos int) Pos { if pos == -1 || pos == 0 { return Pos{0, 0} } var x, y int var rect image.Rectangle // there are n-1 paddings in an image list x = l.Padding * pos // No padding on the outside of the image if pos == len(l.GoImages) { x -= l.Padding } for i := 1; i <= pos; i++ { rect = l.GoImages[i-1].Bounds() x += rect.Dx() if pos == len(l.GoImages) { y = int(math.Max(float64(y), float64(rect.Dy()))) } } return Pos{ x, y, } }
func (floydSteinberg) Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point) { clip(dst, &r, src, &sp, nil, nil) if r.Empty() { return } drawPaletted(dst, r, src, sp, true) }
func drawGlyphOver(dst *image.RGBA, r image.Rectangle, src *image.Uniform, mask *image.Alpha, mp image.Point) { i0 := dst.PixOffset(r.Min.X, r.Min.Y) i1 := i0 + r.Dx()*4 mi0 := mask.PixOffset(mp.X, mp.Y) sr, sg, sb, sa := src.RGBA() for y, my := r.Min.Y, mp.Y; y != r.Max.Y; y, my = y+1, my+1 { for i, mi := i0, mi0; i < i1; i, mi = i+4, mi+1 { ma := uint32(mask.Pix[mi]) if ma == 0 { continue } ma |= ma << 8 dr := uint32(dst.Pix[i+0]) dg := uint32(dst.Pix[i+1]) db := uint32(dst.Pix[i+2]) da := uint32(dst.Pix[i+3]) // The 0x101 is here for the same reason as in drawRGBA. a := (m - (sa * ma / m)) * 0x101 dst.Pix[i+0] = uint8((dr*a + sr*ma) / m >> 8) dst.Pix[i+1] = uint8((dg*a + sg*ma) / m >> 8) dst.Pix[i+2] = uint8((db*a + sb*ma) / m >> 8) dst.Pix[i+3] = uint8((da*a + sa*ma) / m >> 8) } i0 += dst.Stride i1 += dst.Stride mi0 += mask.Stride } }
func (c *NativeCanvas) BlitToContext(nc NativeContext, x int, y int, srcRc *image.Rectangle) { // log.Printf("NativeCanvas.BlitToContext(...)") var srcDC = c.BeginPaint() if srcRc == nil { var tempRc = image.Rect(0, 0, c.W(), c.H()) srcRc = &tempRc } var copyWidth = srcRc.Dx() var copyHeight = srcRc.Dy() if c.Opaque() { // log.Printf("BitBlt(%v %v %v)", x, y, *srcRc) var err = BitBlt(Handle(nc), int32(x), int32(y), int32(copyWidth), int32(copyHeight), Handle(srcDC), int32(srcRc.Min.X), int32(srcRc.Min.Y), SRCCOPY) if err != nil { log.Printf("BitBlt(...) %v", err) } } else { var bf = BLENDFUNCTION{AC_SRC_OVER, 0, 255, AC_SRC_ALPHA} GdiAlphaBlend(Handle(nc), int32(x), int32(y), int32(copyWidth), int32(copyHeight), Handle(srcDC), int32(srcRc.Min.X), int32(srcRc.Min.Y), int32(copyWidth), int32(copyHeight), bf) } c.EndPaint() }
func drawGlyphOver(dst *image.RGBA, r image.Rectangle, src *image.ColorImage, mask *image.Alpha, mp image.Point) { i0 := (r.Min.Y-dst.Rect.Min.Y)*dst.Stride + r.Min.X - dst.Rect.Min.X i1 := i0 + r.Dx() j0 := (mp.Y-mask.Rect.Min.Y)*mask.Stride + mp.X - mask.Rect.Min.X cr, cg, cb, ca := src.RGBA() for y, my := r.Min.Y, mp.Y; y != r.Max.Y; y, my = y+1, my+1 { dpix := dst.Pix[i0:i1] mpix := mask.Pix[j0:] 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)} } i0 += dst.Stride i1 += dst.Stride j0 += mask.Stride } }
func (v *mappingView) drawArch(context *cairo.Context, r image.Rectangle) { for _, a := range v.arch { if r.Overlaps(a.BBox()) { a.Draw(context) } } }
func (buffer Image) CopyRGBA(src *image.RGBA, r image.Rectangle) { // clip r against each image's bounds and move sp accordingly (see draw.clip()) sp := image.ZP orig := r.Min r = r.Intersect(buffer.Bounds()) r = r.Intersect(src.Bounds().Add(orig.Sub(sp))) dx := r.Min.X - orig.X dy := r.Min.Y - orig.Y (sp).X += dx (sp).Y += dy i0 := (r.Min.X - buffer.Rect.Min.X) * 4 i1 := (r.Max.X - buffer.Rect.Min.X) * 4 si0 := (sp.X - src.Rect.Min.X) * 4 yMax := r.Max.Y - buffer.Rect.Min.Y y := r.Min.Y - buffer.Rect.Min.Y sy := sp.Y - src.Rect.Min.Y for ; y != yMax; y, sy = y+1, sy+1 { dpix := buffer.Pix[y*buffer.Stride:] spix := src.Pix[sy*src.Stride:] for i, si := i0, si0; i < i1; i, si = i+4, si+4 { dpix[i+0] = spix[si+2] dpix[i+1] = spix[si+1] dpix[i+2] = spix[si+0] dpix[i+3] = spix[si+3] } } }
func (b *bufferImpl) upload(u screen.Uploader, xd xproto.Drawable, xg xproto.Gcontext, depth uint8, dp image.Point, sr image.Rectangle, sender screen.Sender) { b.preUpload(sender != nil) // TODO: adjust if dp is outside dst bounds, or sr is outside src bounds. dr := sr.Sub(sr.Min).Add(dp) b.s.mu.Lock() b.s.nPendingUploads++ b.s.mu.Unlock() cookie := shm.PutImage( b.s.xc, xd, xg, uint16(b.size.X), uint16(b.size.Y), // TotalWidth, TotalHeight, uint16(sr.Min.X), uint16(sr.Min.Y), // SrcX, SrcY, uint16(dr.Dx()), uint16(dr.Dy()), // SrcWidth, SrcHeight, int16(dr.Min.X), int16(dr.Min.Y), // DstX, DstY, depth, xproto.ImageFormatZPixmap, 1, b.xs, 0, // 1 means send a completion event, 0 means a zero offset. ) b.s.mu.Lock() b.s.uploads[cookie.Sequence] = completion{ sender: sender, event: screen.UploadedEvent{ Buffer: b, Uploader: u, }, } b.s.nPendingUploads-- b.s.handleCompletions() b.s.mu.Unlock() }
func drawCopySrc(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.Point) { n, dy := 4*r.Dx(), r.Dy() d0 := dst.PixOffset(r.Min.X, r.Min.Y) s0 := src.PixOffset(sp.X, sp.Y) 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 coordinates because the built-in copy function can handle // overlapping slices. d0 += (dy - 1) * dst.Stride s0 += (dy - 1) * src.Stride ddelta = -dst.Stride sdelta = -src.Stride } for ; dy > 0; dy-- { copy(dst.Pix[d0:d0+n], src.Pix[s0:s0+n]) d0 += ddelta s0 += sdelta } }
func Crop(m image.Image, r image.Rectangle, w, h int) image.Image { if w < 0 || h < 0 { return nil } if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 { return image.NewRGBA64(image.Rect(0, 0, w, h)) } curw, curh := r.Min.X, r.Min.Y img := image.NewRGBA(image.Rect(0, 0, w, h)) for y := 0; y < h; y++ { for x := 0; x < w; x++ { // Get a source pixel. subx := curw + x suby := curh + y r32, g32, b32, a32 := m.At(subx, suby).RGBA() r := uint8(r32 >> 8) g := uint8(g32 >> 8) b := uint8(b32 >> 8) a := uint8(a32 >> 8) img.SetRGBA(x, y, color.RGBA{r, g, b, a}) } } return img }
func (v *mappingView) drawConnections(context *cairo.Context, r image.Rectangle) { for _, c := range v.connections { box := c.BBox() if r.Overlaps(box) { c.Draw(context) } } }
func drawsq(b *draw.Image, p image.Point, ptx int) { var r image.Rectangle r.Min = p r.Max.X = r.Min.X + pcsz r.Max.Y = r.Min.Y + pcsz b.Draw(r, display.Black, nil, image.ZP) b.Draw(r.Inset(1), tx[ptx], nil, image.ZP) }
// ToImageRect converts a point in the feature pyramid to a rectangle in the image. func (pyr *Generator) ToImageRect(level int, pt image.Point, interior image.Rectangle) image.Rectangle { // Translate interior by position (scaled by rate) and subtract margin offset. rate := pyr.Transform.Rate() offset := pyr.Pad.Margin.TopLeft() scale := pyr.Image.Scales[level] rect := interior.Add(pt.Mul(rate)).Sub(offset) return scaleRect(1/scale, rect) }