func interpolateColor(c1, c2 color.Color, where float64) color.Color { r1, g1, b1, a1 := c1.RGBA() r2, g2, b2, a2 := c2.RGBA() var c color.RGBA64 c.R = uint16(float64(r2-r1)*where + float64(r1) + 0.5) c.G = uint16(float64(g2-g1)*where + float64(g1) + 0.5) c.B = uint16(float64(b2-b1)*where + float64(b1) + 0.5) c.A = uint16(float64(a2-a1)*where + float64(a1) + 0.5) return c }
func (z *Rasterizer) rasterizeOpSrc(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) { z.accumulateMask() out := color.RGBA64{} outc := color.Color(&out) for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ { for x, x1 := 0, r.Max.X-r.Min.X; x < x1; x++ { sr, sg, sb, sa := src.At(sp.X+x, sp.Y+y).RGBA() ma := z.bufU32[y*z.size.X+x] // This algorithm comes from the standard library's image/draw // package. out.R = uint16(sr * ma / 0xffff) out.G = uint16(sg * ma / 0xffff) out.B = uint16(sb * ma / 0xffff) out.A = uint16(sa * ma / 0xffff) dst.Set(r.Min.X+x, r.Min.Y+y, outc) } } }
func bilinearGeneral(src image.Image, x, y float64) color.Color { p := findLinearSrc(src.Bounds(), x, y) var fr, fg, fb, fa float64 var r, g, b, a uint32 r, g, b, a = src.At(p.low.X, p.low.Y).RGBA() fr += float64(r) * p.frac00 fg += float64(g) * p.frac00 fb += float64(b) * p.frac00 fa += float64(a) * p.frac00 r, g, b, a = src.At(p.high.X, p.low.Y).RGBA() fr += float64(r) * p.frac01 fg += float64(g) * p.frac01 fb += float64(b) * p.frac01 fa += float64(a) * p.frac01 r, g, b, a = src.At(p.low.X, p.high.Y).RGBA() fr += float64(r) * p.frac10 fg += float64(g) * p.frac10 fb += float64(b) * p.frac10 fa += float64(a) * p.frac10 r, g, b, a = src.At(p.high.X, p.high.Y).RGBA() fr += float64(r) * p.frac11 fg += float64(g) * p.frac11 fb += float64(b) * p.frac11 fa += float64(a) * p.frac11 var c color.RGBA64 c.R = uint16(fr + 0.5) c.G = uint16(fg + 0.5) c.B = uint16(fb + 0.5) c.A = uint16(fa + 0.5) return c }
// 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. switch dst0 := dst.(type) { case *image.RGBA: if op == Over { if mask == nil { switch src0 := src.(type) { case *image.Uniform: drawFillOver(dst0, r, src0) return case *image.RGBA: drawCopyOver(dst0, r, src0, sp) return case *image.NRGBA: drawNRGBAOver(dst0, r, src0, sp) return case *image.YCbCr: if drawYCbCr(dst0, r, src0, sp) { return } } } else if mask0, ok := mask.(*image.Alpha); ok { switch src0 := src.(type) { case *image.Uniform: drawGlyphOver(dst0, r, src0, mask0, mp) return } } } else { if mask == nil { switch src0 := src.(type) { case *image.Uniform: drawFillSrc(dst0, r, src0) return case *image.RGBA: drawCopySrc(dst0, r, src0, sp) return case *image.NRGBA: drawNRGBASrc(dst0, r, src0, sp) return case *image.YCbCr: if drawYCbCr(dst0, r, src0, sp) { return } } } } drawRGBA(dst0, r, src, sp, mask, mp, op) return case *image.Paletted: if op == Src && mask == nil && !processBackward(dst, r, src, sp) { drawPaletted(dst0, r, src, sp, false) } } x0, x1, dx := r.Min.X, r.Max.X, 1 y0, y1, dy := r.Min.Y, r.Max.Y, 1 if processBackward(dst, r, src, sp) { x0, x1, dx = x1-1, x0-1, -1 y0, y1, dy = y1-1, y0-1, -1 } var out color.RGBA64 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, color.Transparent) } case ma == m && op == Src: dst.Set(x, y, src.At(sx, sy)) default: sr, sg, sb, sa := src.At(sx, sy).RGBA() 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) } // The third argument is &out instead of out (and out is // declared outside of the inner loop) to avoid the implicit // conversion to color.Color here allocating memory in the // inner loop if sizeof(color.RGBA64) > sizeof(uintptr). dst.Set(x, y, &out) } } } }
func drawPaletted(dst Image, r image.Rectangle, src image.Image, sp image.Point, floydSteinberg bool) { // TODO(nigeltao): handle the case where the dst and src overlap. // Does it even make sense to try and do Floyd-Steinberg whilst // walking the image backward (right-to-left bottom-to-top)? // If dst is an *image.Paletted, we have a fast path for dst.Set and // dst.At. The dst.Set equivalent is a batch version of the algorithm // used by color.Palette's Index method in image/color/color.go, plus // optional Floyd-Steinberg error diffusion. palette, pix, stride := [][4]int32(nil), []byte(nil), 0 if p, ok := dst.(*image.Paletted); ok { palette = make([][4]int32, len(p.Palette)) for i, col := range p.Palette { r, g, b, a := col.RGBA() palette[i][0] = int32(r) palette[i][1] = int32(g) palette[i][2] = int32(b) palette[i][3] = int32(a) } pix, stride = p.Pix[p.PixOffset(r.Min.X, r.Min.Y):], p.Stride } // quantErrorCurr and quantErrorNext are the Floyd-Steinberg quantization // errors that have been propagated to the pixels in the current and next // rows. The +2 simplifies calculation near the edges. var quantErrorCurr, quantErrorNext [][4]int32 if floydSteinberg { quantErrorCurr = make([][4]int32, r.Dx()+2) quantErrorNext = make([][4]int32, r.Dx()+2) } // Loop over each source pixel. out := color.RGBA64{A: 0xffff} for y := 0; y != r.Dy(); y++ { for x := 0; x != r.Dx(); x++ { // er, eg and eb are the pixel's R,G,B values plus the // optional Floyd-Steinberg error. sr, sg, sb, sa := src.At(sp.X+x, sp.Y+y).RGBA() er, eg, eb, ea := int32(sr), int32(sg), int32(sb), int32(sa) if floydSteinberg { er = clamp(er + quantErrorCurr[x+1][0]/16) eg = clamp(eg + quantErrorCurr[x+1][1]/16) eb = clamp(eb + quantErrorCurr[x+1][2]/16) ea = clamp(ea + quantErrorCurr[x+1][3]/16) } if palette != nil { // Find the closest palette color in Euclidean R,G,B,A space: // the one that minimizes sum-squared-difference. // TODO(nigeltao): consider smarter algorithms. bestIndex, bestSum := 0, uint32(1<<32-1) for index, p := range palette { sum := sqDiff(er, p[0]) + sqDiff(eg, p[1]) + sqDiff(eb, p[2]) + sqDiff(ea, p[3]) if sum < bestSum { bestIndex, bestSum = index, sum if sum == 0 { break } } } pix[y*stride+x] = byte(bestIndex) if !floydSteinberg { continue } er -= int32(palette[bestIndex][0]) eg -= int32(palette[bestIndex][1]) eb -= int32(palette[bestIndex][2]) ea -= int32(palette[bestIndex][3]) } else { out.R = uint16(er) out.G = uint16(eg) out.B = uint16(eb) out.A = uint16(ea) // The third argument is &out instead of out (and out is // declared outside of the inner loop) to avoid the implicit // conversion to color.Color here allocating memory in the // inner loop if sizeof(color.RGBA64) > sizeof(uintptr). dst.Set(r.Min.X+x, r.Min.Y+y, &out) if !floydSteinberg { continue } sr, sg, sb, sa = dst.At(r.Min.X+x, r.Min.Y+y).RGBA() er -= int32(sr) eg -= int32(sg) eb -= int32(sb) ea -= int32(sa) } // Propagate the Floyd-Steinberg quantization error. quantErrorNext[x+0][0] += er * 3 quantErrorNext[x+0][1] += eg * 3 quantErrorNext[x+0][2] += eb * 3 quantErrorNext[x+0][3] += ea * 3 quantErrorNext[x+1][0] += er * 5 quantErrorNext[x+1][1] += eg * 5 quantErrorNext[x+1][2] += eb * 5 quantErrorNext[x+1][3] += ea * 5 quantErrorNext[x+2][0] += er * 1 quantErrorNext[x+2][1] += eg * 1 quantErrorNext[x+2][2] += eb * 1 quantErrorNext[x+2][3] += ea * 1 quantErrorCurr[x+2][0] += er * 7 quantErrorCurr[x+2][1] += eg * 7 quantErrorCurr[x+2][2] += eb * 7 quantErrorCurr[x+2][3] += ea * 7 } // Recycle the quantization error buffers. if floydSteinberg { quantErrorCurr, quantErrorNext = quantErrorNext, quantErrorCurr for i := range quantErrorNext { quantErrorNext[i] = [4]int32{} } } } }
// 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.Uniform: 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.Uniform: drawGlyphOver(dst0, r, src0, mask0, mp) return } } } else { if mask == nil { switch src0 := src.(type) { case *image.Uniform: 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 *color.RGBA64 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, color.Transparent) } 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(color.RGBA64) } 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) } } } }
// readImagePass reads a single image pass, sized according to the pass number. func (d *decoder) readImagePass(r io.Reader, pass int, allocateOnly bool) (image.Image, error) { var bitsPerPixel int = 0 pixOffset := 0 var ( gray *image.Gray rgba *image.RGBA paletted *image.Paletted nrgba *image.NRGBA gray16 *image.Gray16 rgba64 *image.RGBA64 nrgba64 *image.NRGBA64 img image.Image ) width, height := d.width, d.height if d.interlace == itAdam7 && !allocateOnly { p := interlacing[pass] // Add the multiplication factor and subtract one, effectively rounding up. width = (width - p.xOffset + p.xFactor - 1) / p.xFactor height = (height - p.yOffset + p.yFactor - 1) / p.yFactor } switch d.cb { case cbG1, cbG2, cbG4, cbG8: bitsPerPixel = d.depth gray = image.NewGray(image.Rect(0, 0, width, height)) img = gray case cbGA8: bitsPerPixel = 16 nrgba = image.NewNRGBA(image.Rect(0, 0, width, height)) img = nrgba case cbTC8: bitsPerPixel = 24 rgba = image.NewRGBA(image.Rect(0, 0, width, height)) img = rgba case cbP1, cbP2, cbP4, cbP8: bitsPerPixel = d.depth paletted = image.NewPaletted(image.Rect(0, 0, width, height), d.palette) img = paletted case cbTCA8: bitsPerPixel = 32 nrgba = image.NewNRGBA(image.Rect(0, 0, width, height)) img = nrgba case cbG16: bitsPerPixel = 16 gray16 = image.NewGray16(image.Rect(0, 0, width, height)) img = gray16 case cbGA16: bitsPerPixel = 32 nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height)) img = nrgba64 case cbTC16: bitsPerPixel = 48 rgba64 = image.NewRGBA64(image.Rect(0, 0, width, height)) img = rgba64 case cbTCA16: bitsPerPixel = 64 nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height)) img = nrgba64 } if allocateOnly { return img, nil } bytesPerPixel := (bitsPerPixel + 7) / 8 // The +1 is for the per-row filter type, which is at cr[0]. rowSize := 1 + (bitsPerPixel*width+7)/8 // cr and pr are the bytes for the current and previous row. cr := make([]uint8, rowSize) pr := make([]uint8, rowSize) for y := 0; y < height; y++ { // Read the decompressed bytes. _, err := io.ReadFull(r, cr) if err != nil { if err == io.EOF || err == io.ErrUnexpectedEOF { return nil, FormatError("not enough pixel data") } return nil, err } // Apply the filter. cdat := cr[1:] pdat := pr[1:] switch cr[0] { case ftNone: // No-op. case ftSub: for i := bytesPerPixel; i < len(cdat); i++ { cdat[i] += cdat[i-bytesPerPixel] } case ftUp: for i, p := range pdat { cdat[i] += p } case ftAverage: for i := 0; i < bytesPerPixel; i++ { cdat[i] += pdat[i] / 2 } for i := bytesPerPixel; i < len(cdat); i++ { cdat[i] += uint8((int(cdat[i-bytesPerPixel]) + int(pdat[i])) / 2) } case ftPaeth: filterPaeth(cdat, pdat, bytesPerPixel) default: return nil, FormatError("bad filter type") } // Convert from bytes to colors. switch d.cb { case cbG1: for x := 0; x < width; x += 8 { b := cdat[x/8] for x2 := 0; x2 < 8 && x+x2 < width; x2++ { gray.SetGray(x+x2, y, color.Gray{(b >> 7) * 0xff}) b <<= 1 } } case cbG2: for x := 0; x < width; x += 4 { b := cdat[x/4] for x2 := 0; x2 < 4 && x+x2 < width; x2++ { gray.SetGray(x+x2, y, color.Gray{(b >> 6) * 0x55}) b <<= 2 } } case cbG4: for x := 0; x < width; x += 2 { b := cdat[x/2] for x2 := 0; x2 < 2 && x+x2 < width; x2++ { gray.SetGray(x+x2, y, color.Gray{(b >> 4) * 0x11}) b <<= 4 } } case cbG8: copy(gray.Pix[pixOffset:], cdat) pixOffset += gray.Stride case cbGA8: for x := 0; x < width; x++ { ycol := cdat[2*x+0] nrgba.SetNRGBA(x, y, color.NRGBA{ycol, ycol, ycol, cdat[2*x+1]}) } case cbTC8: pix, i, j := rgba.Pix, pixOffset, 0 for x := 0; x < width; x++ { pix[i+0] = cdat[j+0] pix[i+1] = cdat[j+1] pix[i+2] = cdat[j+2] pix[i+3] = 0xff if d.tc != nil { r, g, b, _ := d.tc.RGBA() if pix[i+0] == uint8(r) && pix[i+1] == uint8(g) && pix[i+2] == uint8(b) { pix[i+3] = 0 } } i += 4 j += 3 } pixOffset += rgba.Stride case cbP1: for x := 0; x < width; x += 8 { b := cdat[x/8] for x2 := 0; x2 < 8 && x+x2 < width; x2++ { idx := b >> 7 if len(paletted.Palette) <= int(idx) { paletted.Palette = paletted.Palette[:int(idx)+1] } paletted.SetColorIndex(x+x2, y, idx) b <<= 1 } } case cbP2: for x := 0; x < width; x += 4 { b := cdat[x/4] for x2 := 0; x2 < 4 && x+x2 < width; x2++ { idx := b >> 6 if len(paletted.Palette) <= int(idx) { paletted.Palette = paletted.Palette[:int(idx)+1] } paletted.SetColorIndex(x+x2, y, idx) b <<= 2 } } case cbP4: for x := 0; x < width; x += 2 { b := cdat[x/2] for x2 := 0; x2 < 2 && x+x2 < width; x2++ { idx := b >> 4 if len(paletted.Palette) <= int(idx) { paletted.Palette = paletted.Palette[:int(idx)+1] } paletted.SetColorIndex(x+x2, y, idx) b <<= 4 } } case cbP8: if len(paletted.Palette) != 255 { for x := 0; x < width; x++ { if len(paletted.Palette) <= int(cdat[x]) { paletted.Palette = paletted.Palette[:int(cdat[x])+1] } } } copy(paletted.Pix[pixOffset:], cdat) pixOffset += paletted.Stride case cbTCA8: copy(nrgba.Pix[pixOffset:], cdat) pixOffset += nrgba.Stride case cbG16: for x := 0; x < width; x++ { ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1]) gray16.SetGray16(x, y, color.Gray16{ycol}) } case cbGA16: for x := 0; x < width; x++ { ycol := uint16(cdat[4*x+0])<<8 | uint16(cdat[4*x+1]) acol := uint16(cdat[4*x+2])<<8 | uint16(cdat[4*x+3]) nrgba64.SetNRGBA64(x, y, color.NRGBA64{ycol, ycol, ycol, acol}) } case cbTC16: for x := 0; x < width; x++ { rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1]) gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3]) bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5]) col := color.RGBA64{rcol, gcol, bcol, 0xffff} if col == d.tc { col.A = 0 } rgba64.SetRGBA64(x, y, col) } case cbTCA16: for x := 0; x < width; x++ { rcol := uint16(cdat[8*x+0])<<8 | uint16(cdat[8*x+1]) gcol := uint16(cdat[8*x+2])<<8 | uint16(cdat[8*x+3]) bcol := uint16(cdat[8*x+4])<<8 | uint16(cdat[8*x+5]) acol := uint16(cdat[8*x+6])<<8 | uint16(cdat[8*x+7]) nrgba64.SetNRGBA64(x, y, color.NRGBA64{rcol, gcol, bcol, acol}) } } // The current row for y is the previous row for y+1. pr, cr = cr, pr } return img, nil }
// 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. // The implementation is simple and slow. // TODO(nigeltao): Optimize this. func DrawMask(dst Image, r Rectangle, src image.Image, sp Point, mask image.Image, mp Point, op Op) { dx, dy := src.Width()-sp.X, src.Height()-sp.Y if mask != nil { if dx > mask.Width()-mp.X { dx = mask.Width() - mp.X } if dy > mask.Height()-mp.Y { dy = mask.Height() - mp.Y } } if r.Dx() > dx { r.Max.X = r.Min.X + dx } if r.Dy() > dy { r.Max.Y = r.Min.Y + dy } // TODO(nigeltao): Clip r to dst's bounding box, and handle the case when sp or mp has negative X or Y. // TODO(nigeltao): Ensure that r is well formed, i.e. r.Max.X >= r.Min.X and likewise for Y. // Fast paths for special cases. If none of them apply, then we fall back to a general but slow implementation. switch dst0 := dst.(type) { case *image.RGBA: if op == Over { if mask == nil { if src0, ok := src.(image.Uniform); ok { drawFillOver(dst0, r, src0) return } if src0, ok := src.(*image.RGBA); ok { if dst0 == src0 && r.Overlaps(r.Add(sp.Sub(r.Min))) { // TODO(nigeltao): Implement a fast path for the overlapping case. } else { drawCopyOver(dst0, r, src0, sp) return } } } else if mask0, ok := mask.(*image.Alpha); ok { if src0, ok := src.(image.Uniform); ok { drawGlyphOver(dst0, r, src0, mask0, mp) return } } } else { if mask == nil { if src0, ok := src.(image.Uniform); ok { drawFillSrc(dst0, r, src0) return } if src0, ok := src.(*image.RGBA); ok { if dst0 == src0 && r.Overlaps(r.Add(sp.Sub(r.Min))) { // TODO(nigeltao): Implement a fast path for the overlapping case. } else { drawCopySrc(dst0, r, src0, sp) return } } } } drawRGBA(dst0, r, src, sp, mask, mp, op) return case DrawMasker: // Destination might wish to perform the draw operation itself if dst0.DrawMask(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 *color.RGBA64 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(color.RGBA64) } 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) } } } }