func NewRGB48FromImage(m image.Image) *RGB48 { if m, ok := m.(*RGB48); ok { return m } // try `Image` interface if x, ok := m.(Image); ok { // try original type if m, ok := x.BaseType().(*RGB48); ok { return m } // create new image with `x.Pix()` if x.Channels() == 3 && x.Depth() == reflect.Uint16 { return new(RGB48).Init(x.Pix(), x.Stride(), x.Rect()) } } // convert to RGB48 b := m.Bounds() rgb48 := NewRGB48(b) for y := b.Min.Y; y < b.Max.Y; y++ { for x := b.Min.X; x < b.Max.X; x++ { pr, pg, pb, _ := m.At(x, y).RGBA() rgb48.SetRGB48(x, y, RGB48Color{ uint16(pr), uint16(pg), uint16(pb), }) } } return rgb48 }
func histogram(img image.Image, bins int) []int { mi, _, _ := getMin(img) ma, _, _ := getMax(img) //fmt.Printf("max and min are : %v %v\n", mi, ma) bounds := img.Bounds() var h []int h = make([]int, bins, bins) for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { r, g, b, _ := img.At(x, y).RGBA() a := 0.2125*float64(r) + 0.7154*float64(g) + 0.0721*float64(b) idx := int(math.Floor((a - mi) / (ma - mi) * (float64(bins) - 1.0))) if idx < 0 { idx = 0 } if idx >= bins { idx = bins - 1 } h[idx]++ } } return h }
// encodeRGB64Data writes image data as 16-bit samples. func encodeRGB64Data(w io.Writer, img image.Image, opts *EncodeOptions) error { // In the background, write each 16-bit color sample into a channel. rect := img.Bounds() width := rect.Max.X - rect.Min.X samples := make(chan uint16, width*3) go func() { cm := npcolor.RGBM64Model{M: opts.MaxValue} for y := rect.Min.Y; y < rect.Max.Y; y++ { for x := rect.Min.X; x < rect.Max.X; x++ { c := cm.Convert(img.At(x, y)).(npcolor.RGBM64) samples <- c.R samples <- c.G samples <- c.B } } close(samples) }() // In the foreground, consume color samples and write them to the image // file. if opts.Plain { return writePlainData(w, samples) } else { return writeRawData(w, samples, 2) } }
// convert an image into ASCII! // watch out, this might be painfully slow... func Convert(m image.Image, p []*TextColor) *Image { c := NewPalette(p) // create image of correct size bounds := m.Bounds() s := bounds.Size() img := NewImage(uint(s.X), uint(s.Y)) // dereference for slice manipulation grid := *img var wg sync.WaitGroup for y := range grid { wg.Add(1) go func(r []*TextColor, y int) { for x := range r { r[x] = c.Convert(m.At(x+bounds.Min.X, y+bounds.Min.Y)).(*TextColor) } wg.Done() }(grid[y], y) } wg.Wait() return img }
// DifferenceHash computes the difference hash of an image. func DifferenceHash(source image.Image) uint64 { const sw, sh, hw, hh = 9, 8, 8, 8 // Convert the image to the grayscale colourspace. bounds := source.Bounds() width, height := bounds.Max.X, bounds.Max.Y gray := image.NewGray(source.Bounds()) for x := 0; x < width; x++ { for y := 0; y < height; y++ { gray.Set(x, y, source.At(x, y)) } } // Resize the image. shrunk := resize.Resize(sw, sh, gray, resize.NearestNeighbor).(*image.Gray) // Compute the difference hash. var hash uint64 for y := 0; y < hh; y++ { for x := 0; x < hw; x++ { if shrunk.GrayAt(x, y).Y < shrunk.GrayAt(x+1, y).Y { hash |= 1 << uint64((y*hw)+x) } } } return hash }
func ReadShadowData(im image.Image) (b []byte, err error) { head, err := ReadShadowHeader(im) if err != nil { return nil, err } length := int(ReadShadowLength(head)) var bk []byte = make([]byte, length*8) b = make([]byte, length) _, err = SetImage(im, func(index, x, y int, in, out image.Image) { if index >= 64 && index < length*8+64 { R := readRGBAColor(im.At(x, y)).R bk[index-64] = uint8(R & 1) } }) var bb [8]byte var bs []byte for i := 0; i < length; i++ { bs = bk[8*i : 8*(i+1)] for j := 0; j < 8; j++ { bb[j] = bs[j] } b[i] = Bits2Byte(bb) } return }
func (w *walker) IsVertex(cell image.Point, img image.Image) bool { // a point is a vertex iff it has 4 'different' neightbors, this includes diagonal count := 0 x := cell.X y := cell.Y myColor := color.White for i := -1; i < 2; i++ { for j := -1; j < 2; j++ { Maxsize := img.Bounds().Max Minsize := img.Bounds().Min if x+i > Maxsize.X || x+i < Minsize.X { count++ } if y+i > Maxsize.Y || y+i < Minsize.Y { count++ } if sameColor(myColor, img.At(x+i, y+j)) { count++ } } } if count >= 4 { return true } return false }
func GlRgba(img image.Image, rot matrix23) []gl.GLubyte { bounds := img.Bounds() sizeX := bounds.Size().X sizeY := bounds.Size().Y data := make([]gl.GLubyte, sizeX*sizeY*4) // x and y are in the source frame. // X and Y are in the destination frame. // x and y are not garanteed to start at 0 because that is how images work // in Go. However, X and Y are because my output buffer is built the way // I want it. Y := -1 for y := bounds.Max.Y - 1; y >= bounds.Min.Y; y-- { Y++ X := -1 for x := bounds.Min.X; x < bounds.Max.X; x++ { X++ color := img.At(x, y) r, g, b, a := color.RGBA() rX, rY := rot.mul(X, Y) i := (rY*sizeX + rX) * 4 // Assumes square texture. //fmt.Println(X, Y, rX, rY, i) data[i+0] = gl.GLubyte(r >> 8) data[i+1] = gl.GLubyte(g >> 8) data[i+2] = gl.GLubyte(b >> 8) data[i+3] = gl.GLubyte(a >> 8) } } return data }
// EachColorInRectangle is a helper function for working on a part of an image. func EachColorInRectangle(img image.Image, b image.Rectangle, f func(c color.Color)) { for y := b.Min.Y; y < b.Max.Y; y++ { for x := b.Min.X; x < b.Max.X; x++ { f(img.At(x, y)) } } }
// thanks to IonicaBizau/image-to-ascii for algorithm func Convert(img image.Image) (str string) { var buffer bytes.Buffer size := img.Bounds().Max pixels := ".,:;i1tfLCG08@" precision := (255 * 4) / (len(pixels) - 1) // ansi color end constant reset := ansi.ColorCode("reset") for y := 0; y < size.Y; y += 1 { for x := 0; x < size.X; x += 1 { r, g, b, a := img.At(x, y).RGBA() sum := r>>8 + g>>8 + b>>8 + a>>8 pixel := pixels[int(sum)/precision] // find the closest ansi color code code := x256.ClosestCode(uint8(r>>8), uint8(g>>8), uint8(b>>8)) /// get the ansi color code color := ansi.ColorCode(strconv.Itoa(code)) // write the pixel buffer.WriteString(color) buffer.WriteString(string(pixel)) buffer.WriteString(reset) } buffer.WriteString("\n") } return buffer.String() }
func Crop(img image.Image, width int, height int, padding int) (fimg draw.Image, err error) { // For now assume top left is bg color // future maybe avg outer rows of pixels bgcolor := img.At(img.Bounds().Min.X, img.Bounds().Min.Y) logoh, logow := height-2*padding, width-2*padding logorat := float32(logow) / float32(logoh) interior := findLogo(img, bgcolor) interior.Max = interior.Max.Add(image.Pt(1, 1)) center := func(rect image.Rectangle) image.Point { return image.Point{(rect.Max.X - rect.Min.X) / 2, (rect.Max.Y - rect.Min.Y) / 2} } fimg = image.NewRGBA(image.Rect(0, 0, width, height)) rc := center(fimg.Bounds()) origrat := float32(interior.Dx()) / float32(interior.Dy()) if logorat > origrat { logow = int(origrat * float32(logoh)) } else { logoh = int(float32(logow) / origrat) } logoimg := Resize(img, interior, logow, logoh) logorect := image.Rect(0, 0, logoimg.Bounds().Dx(), logoimg.Bounds().Dy()) logorect = logorect.Add(rc.Sub(image.Pt(logorect.Dx()/2, logorect.Dy()/2))) draw.Draw(fimg, fimg.Bounds(), &image.Uniform{bgcolor}, image.ZP, draw.Src) draw.Draw(fimg, logorect, logoimg, image.ZP, draw.Src) return }
func encodePPM(w io.Writer, m image.Image, maxvalue int) error { b := m.Bounds() // write header _, err := fmt.Fprintf(w, "P6\n%d %d\n%d\n", b.Dx(), b.Dy(), maxvalue) if err != nil { return err } // write raster cm := color.RGBAModel row := make([]uint8, b.Dx()*3) for y := b.Min.Y; y < b.Max.Y; y++ { i := 0 for x := b.Min.X; x < b.Max.X; x++ { c := cm.Convert(m.At(x, y)).(color.RGBA) row[i] = c.R row[i+1] = c.G row[i+2] = c.B i += 3 } if _, err := w.Write(row); err != nil { return err } } return nil }
/* * NewWorld creates a brand new world. * * It loads the map from the provided Surviveler Package and initializes the * world representation from it. */ func NewWorld(img image.Image, gridScale float32) (*World, error) { bounds := img.Bounds() w := World{ GridWidth: bounds.Max.X, GridHeight: bounds.Max.Y, Width: float32(bounds.Max.X) / gridScale, Height: float32(bounds.Max.Y) / gridScale, GridScale: gridScale, Entities: make(map[uint32]TileList), } log.WithField("world", w).Info("Building world") // allocate tiles var kind TileKind w.Grid = make([]Tile, bounds.Max.X*bounds.Max.Y) for x := bounds.Min.X; x < bounds.Max.X; x++ { for y := bounds.Min.Y; y < bounds.Max.Y; y++ { r, _, _, _ := img.At(x, y).RGBA() if r == 0 { kind = KindNotWalkable } else { kind = KindWalkable } w.Grid[x+y*w.GridWidth] = NewTile(kind, &w, x, y) } } return &w, nil }
func nearestNeighbour(im image.Image, scalex float32, scaley float32) (imageng, os.Error) { if scalex > 1 && scaley > 1 { ws := im.Bounds().Dx() hs := im.Bounds().Dy() wd := int(scalex * float32(ws)) hd := int(scaley * float32(hs)) scaled := imgType(im, wd, hd) var xs, ys, dx, dy, neighbour, row int ys = 0 row = 0 dy = int(float32(ys+1)*scaley) / 2 for y := 0; y < hd; y++ { if abs(y-row) > dy { ys++ row = int(float32(ys) * scaley) dy = (int(float32(ys+1)*scaley) - row) / 2 } xs = 0 neighbour = 0 dx = int(float32(xs+1)*scalex) / 2 for x := 0; x < wd; x++ { if abs(x-neighbour) > dx { xs++ neighbour = int(float32(xs) * scalex) dx = (int(float32(xs+1)*scalex) - neighbour) / 2 } scaled.Set(x, y, im.At(xs, ys)) } } return scaled, nil } return nil, os.NewError("no scaling down") }
// cut out the image and return individual channels with image.Image // no encoding of JPEG func cut(original image.Image, db *map[string][3]float64, tileSize, x1, y1, x2, y2 int) <-chan image.Image { c := make(chan image.Image) sp := image.Point{0, 0} go func() { newimage := image.NewNRGBA(image.Rect(x1, y1, x2, y2)) for y := y1; y < y2; y = y + tileSize { for x := x1; x < x2; x = x + tileSize { r, g, b, _ := original.At(x, y).RGBA() color := [3]float64{float64(r), float64(g), float64(b)} nearest := nearest(color, db) file, err := os.Open(nearest) if err == nil { img, _, err := image.Decode(file) if err == nil { t := resize(img, tileSize) tile := t.SubImage(t.Bounds()) tileBounds := image.Rect(x, y, x+tileSize, y+tileSize) draw.Draw(newimage, tileBounds, tile, sp, draw.Src) } else { fmt.Println("error in decoding nearest", err, nearest) } } else { fmt.Println("error opening file when creating mosaic:", nearest) } file.Close() } } c <- newimage.SubImage(newimage.Rect) }() return c }
func Benchmark_BigResizeLanczos3(b *testing.B) { var m image.Image for i := 0; i < b.N; i++ { m = Resize(1000, 1000, img, Lanczos3) } m.At(0, 0) }
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 evalPixels(t *testing.T, i image.Image, p image.Point, colors []int) { for y := 0; y < p.Y; y++ { for x := 0; x < p.X; x++ { var er, eg, eb, ea uint32 e := colors[p.X*y+x] if e == 1 { er = 0xffff eg = 0xffff eb = 0xffff } else { er = 0 eg = 0 eb = 0 } ea = 0xffff a := i.At(u/2>>0+u*x, u/2>>0+u*y) ar, ag, ab, aa := a.RGBA() if !(isNear(er, ar) && isNear(eg, ag) && isNear(eb, ab) && isNear(ea, aa)) { t.Errorf( "wrong color at (%d, %d) expected {%d, %d, %d, %d}, but actual {%d, %d, %d, %d}", x, y, er, eg, eb, ea, ar, ag, ab, aa, ) } } } }
func generateHistogramForImage(m image.Image) [][]float64 { bounds := m.Bounds() widthPixels := int(bounds.Max.X - bounds.Min.X) heightPixels := int(bounds.Max.Y - bounds.Min.Y) histogram := make([][]float64, 3) for i := range histogram { histogram[i] = make([]float64, widthPixels) } for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { r, g, b, _ := m.At(x, y).RGBA() // values should be between 0 and 1 relR := float64(r) / 65535.0 relG := float64(g) / 65535.0 relB := float64(b) / 65535.0 histogram[0][x] += relR histogram[1][x] += relG histogram[2][x] += relB } } // create rounded mean value for each x value for i, values := range histogram { for j := range values { histogram[i][j] = roundedMean(histogram[i][j], float64(heightPixels)) } } return histogram }
// Image returns a thumbnail-size version of src. func Image(src image.Image) image.Image { // Compute thumbnail size, preserving aspect ratio. xs := src.Bounds().Size().X ys := src.Bounds().Size().Y width, height := 128, 128 if aspect := float64(xs) / float64(ys); aspect < 1.0 { width = int(128 * aspect) // portrait } else { height = int(128 / aspect) // landscape } xscale := float64(xs) / float64(width) yscale := float64(ys) / float64(height) dst := image.NewRGBA(image.Rect(0, 0, width, height)) // a very crude scaling algorithm for x := 0; x < width; x++ { for y := 0; y < height; y++ { srcx := int(float64(x) * xscale) srcy := int(float64(y) * yscale) dst.Set(x, y, src.At(srcx, srcy)) } } return dst }
func (w *walker) ExploreObject(o *object, img image.Image, loc image.Point) { frontier := []image.Point{} start := loc myColor := o.c Maxsize := img.Bounds().Max //Minsize := img.Bounds().Min x := start.X y := start.Y for sameColor(myColor, img.At(x, y)) && x < Maxsize.X { w.explored[image.Pt(x, y)] = true x++ } x-- if w.IsVertex(image.Pt(x, y), img) { //add the vertex to the objects list frontier = append(frontier, image.Pt(x, y)) } x = start.X for sameColor(myColor, img.At(x, y)) && y < Maxsize.Y { w.explored[image.Pt(x, y)] = true y++ } y-- if w.IsVertex(image.Pt(x, y), img) { //add the vertex to the objects list frontier = append(frontier, image.Pt(x, y)) } }
// pushGenericLocked is the slow path generic implementation that works on // any image.Image concrete type and any client-requested pixel format. // If you're lucky, you never end in this path. func (c *Conn) pushGenericLocked(im image.Image) { b := im.Bounds() width, height := b.Dx(), b.Dy() for y := 0; y < height; y++ { for x := 0; x < width; x++ { col := im.At(x, y) r16, g16, b16, _ := col.RGBA() r16 = inRange(r16, c.format.RedMax) g16 = inRange(g16, c.format.GreenMax) b16 = inRange(b16, c.format.BlueMax) var u32 uint32 = (r16 << c.format.RedShift) | (g16 << c.format.GreenShift) | (b16 << c.format.BlueShift) var v interface{} switch c.format.BPP { case 32: v = u32 case 16: v = uint16(u32) case 8: v = uint8(u32) default: c.failf("TODO: BPP of %d", c.format.BPP) } if c.format.BigEndian != 0 { binary.Write(c.bw, binary.BigEndian, v) } else { binary.Write(c.bw, binary.LittleEndian, v) } } } }
// TODO use multiple channels to store edge intersections func (sdf *SDF) calc(m image.Image) { max := dist(0, 0, sdf.pad, sdf.pad) - 1 b := m.Bounds() // TODO this space could probably be traversed better for y := b.Min.Y; y < b.Max.Y; y++ { for x := b.Min.X; x < b.Max.X; x++ { _, _, _, ma := m.At(x, y).RGBA() c := nearest(x, y, m.(*image.NRGBA).SubImage(image.Rect(x-sdf.pad, y-sdf.pad, x+sdf.pad, y+sdf.pad))) if c == 0xFF { // check if pixel is inside as a center of opposing edges if ma != 0 { sdf.dst.Set(x, y, color.RGBA{A: 0xFF}) } continue } // return from nearest is always >= 1 // decrement so that c/max returns a unit value inclusive of zero c-- n := 0xFF * (1 - (float64(c) / float64(max))) if ma != 0 { // inside edge sdf.dst.Set(x, y, color.RGBA{A: 0xFF - uint8(n/2)}) } else { // outside edge step := float64(0xFF) / float64(max) if n = n - step; n < 0 { n = 0 } sdf.dst.Set(x, y, color.RGBA{A: uint8(n / 2)}) } } } }
func parseImgBoundary(img image.Image) { minX, maxX := img.Bounds().Min.X, img.Bounds().Max.X minY, maxY := img.Bounds().Min.Y, img.Bounds().Max.Y for i := minX; i < maxX; i++ { for j := minY; j < maxY; j++ { _, _, _, a := img.At(i, j).RGBA() if a != 0 { if boundary.Empty() && boundary.Min.X == -1 { boundary = image.Rect(i, j, i, j) } else { _p := image.Point{i, j} if !_p.In(boundary) { boundary = boundary.Union(image.Rect(i, j, i, j)) } } } } } // Should Make the midline of boundary and the img l := boundary.Min.X r := imageW - boundary.Max.X if l > r { boundary.Min.X = r } else if l < r { boundary.Max.X = imageW - l } }
/** * Returns a new graph that represents the image img. The graph will be either * a King's grph or a Grid graph. It will compute the edge weights using the * provided function weight. */ func FromImage(img image.Image, weight WeightFn, graphType GraphType) *Graph { g := new(Graph) g.height = img.Bounds().Max.Y g.width = img.Bounds().Max.X g.graphType = graphType g.edges = make(EdgeList, 0, g.TotalEdges()) size := 4 if graphType == KINGSGRAPH { size = 8 } g.weights = make([][]float64, g.TotalVertices(), g.TotalVertices()) for y := 0; y < g.height; y++ { for x := 0; x < g.width; x++ { p := x + y*g.width pixel := Pixel{X: x, Y: y, Color: img.At(x, y)} g.weights[p] = make([]float64, size/2, size/2) for n := range g.Neighbors(p) { x2, y2 := n%g.width, n/g.width pixel2 := Pixel{X: x2, Y: x2, Color: img.At(x2, y2)} w := weight(pixel, pixel2) g.edges = append(g.edges, Edge{u: p, v: n, weight: w}) g.weights[p][g.weightIndex(p, n)] = w } } } return g }
// Return a VImageBuffer of the given image. The memory may or may not // be shared depending on the format. Also return the format of the returned // image. (e.g. argb8888, rgba8888, 8, ...) func VImageBufferFromImage(img image.Image) (*VImageBuffer, string) { switch m := img.(type) { case *image.Gray: return &VImageBuffer{Width: m.Bounds().Dx(), Height: m.Bounds().Dy(), RowBytes: m.Stride, Data: m.Pix}, "8" case *image.RGBA: return &VImageBuffer{Width: m.Bounds().Dx(), Height: m.Bounds().Dy(), RowBytes: m.Stride, Data: m.Pix}, "rgba8888" } b := img.Bounds() w := b.Dx() h := b.Dy() data := make([]byte, w*h*4) dataOffset := 0 for y := 0; y < h; y++ { for x := 0; x < w; x++ { c := img.At(x+b.Min.X, y+b.Min.Y) r, g, b, a := c.RGBA() data[dataOffset] = uint8(a >> 8) data[dataOffset+1] = uint8(r >> 8) data[dataOffset+2] = uint8(g >> 8) data[dataOffset+3] = uint8(b >> 8) dataOffset += 4 } } return &VImageBuffer{Width: w, Height: h, RowBytes: w * 4, Data: data}, "argb8888" }
// toRGBA translates the given image to RGBA format if necessary. // Optionally scales it by the given amount. func toRGBA(src image.Image, scale int) *image.RGBA { if scale < 1 { scale = 1 } dst, ok := src.(*image.RGBA) if ok && scale == 1 { return dst } // Scale image to match new size. ib := src.Bounds() rect := image.Rect(0, 0, ib.Dx()*scale, ib.Dy()*scale) if !ok { // Image is not RGBA, so we create it. dst = image.NewRGBA(rect) } for sy := 0; sy < ib.Dy(); sy++ { for sx := 0; sx < ib.Dx(); sx++ { dx := sx * scale dy := sy * scale pixel := src.At(sx, sy) for scy := 0; scy < scale; scy++ { for scx := 0; scx < scale; scx++ { dst.Set(dx+scx, dy+scy, pixel) } } } } return dst }
func find_projection_in_line(scan image.Image, y_pix int, est_location int) int { enter_white, exit_white := 0, 0 var min_x, max_x int if est_location == -1 || est_location < expected_x_deviation || est_location > (image_size_x-expected_x_deviation) { min_x, max_x = 0, image_size_x } else { min_x, max_x = est_location-expected_x_deviation, est_location+expected_x_deviation } for x := min_x; x < max_x; x++ { color := scan.At(x, y_pix) r, g, b, _ := color.RGBA() if is_white(r, g, b) { enter_white = x break } } for x := enter_white; x < max_x; x++ { color := scan.At(x, y_pix) r, g, b, _ := color.RGBA() if !is_white(r, g, b) { exit_white = x - 1 break } } if enter_white != 0 && exit_white != 0 && (exit_white-enter_white) < maximum_line_size { return int((enter_white + exit_white) / 2) } return -99999 }
func hough(im image.Image, ntx, mry int) draw.Image { nimx := im.Bounds().Max.X mimy := im.Bounds().Max.Y mry = int(mry/2) * 2 him := image.NewGray(image.Rect(0, 0, ntx, mry)) draw.Draw(him, him.Bounds(), image.NewUniform(color.White), image.ZP, draw.Src) rmax := math.Hypot(float64(nimx), float64(mimy)) dr := rmax / float64(mry/2) dth := math.Pi / float64(ntx) for jx := 0; jx < nimx; jx++ { for iy := 0; iy < mimy; iy++ { col := color.GrayModel.Convert(im.At(jx, iy)).(color.Gray) if col.Y == 255 { continue } for jtx := 0; jtx < ntx; jtx++ { th := dth * float64(jtx) r := float64(jx)*math.Cos(th) + float64(iy)*math.Sin(th) iry := mry/2 - int(math.Floor(r/dr+.5)) col = him.At(jtx, iry).(color.Gray) if col.Y > 0 { col.Y-- him.SetGray(jtx, iry, col) } } } } return him }
// Resample returns a resampled copy of the image slice r of m. // The returned image has width w and height h. func Resample(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)) } img := image.NewRGBA(image.Rect(0, 0, w, h)) xStep := float64(r.Dx()) / float64(w) yStep := float64(r.Dy()) / float64(h) for y := 0; y < h; y++ { for x := 0; x < w; x++ { xSrc := int(float64(r.Min.X) + float64(x)*xStep) ySrc := int(float64(r.Min.Y) + float64(y)*yStep) r, g, b, a := m.At(xSrc, ySrc).RGBA() img.SetRGBA(x, y, color.RGBA{ R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8), }) } } return img }