func warm(out, in draw.Image, opacity uint8, colors []color.Color) { draw.Draw(out, out.Bounds(), image.Transparent, image.ZP, draw.Src) bounds := in.Bounds() collen := float64(len(colors)) wg := &sync.WaitGroup{} for x := bounds.Min.X; x < bounds.Max.X; x++ { wg.Add(1) go func(x int) { defer wg.Done() for y := bounds.Min.Y; y < bounds.Max.Y; y++ { col := in.At(x, y) _, _, _, alpha := col.RGBA() if alpha > 0 { percent := float64(alpha) / float64(0xffff) template := colors[int((collen-1)*(1.0-percent))] tr, tg, tb, ta := template.RGBA() ta /= 256 outalpha := uint8(float64(ta) * (float64(opacity) / 256.0)) outcol := color.NRGBA{ uint8(tr / 256), uint8(tg / 256), uint8(tb / 256), uint8(outalpha)} out.Set(x, y, outcol) } } }(x) } wg.Wait() }
func warm(out, in draw.Image, opacity uint8, colors []color.Color) { bounds := in.Bounds() collen := float64(len(colors)) wg := sync.WaitGroup{} wg.Add(bounds.Dx()) for x := bounds.Min.X; x < bounds.Max.X; x++ { go func(x int) { defer wg.Done() for y := bounds.Min.Y; y < bounds.Max.Y; y++ { col := in.At(x, y) _, _, _, alpha := col.RGBA() percent := float64(alpha) / float64(0xffff) var outcol color.Color if percent == 0 { outcol = color.Transparent } else { template := colors[int((collen-1)*(1.0-percent))] tr, tg, tb, ta := template.RGBA() ta /= 256 outalpha := uint8(float64(ta) * (float64(opacity) / 256.0)) outcol = color.NRGBA{ uint8(tr / 256), uint8(tg / 256), uint8(tb / 256), uint8(outalpha)} } out.Set(x, y, outcol) } }(x) } wg.Wait() }
// Apply the given color mapping to the specified image buffers. func Apply(from, to *Rule, src, dst draw.Image) { var x, y int var r, g, b, a uint32 var sc color.Color var dc color.RGBA var pix pixel rect := src.Bounds() for y = 0; y < rect.Dy(); y++ { for x = 0; x < rect.Dx(); x++ { sc = src.At(x, y) r, g, b, a = sc.RGBA() pix.r = uint8(r >> 8) pix.g = uint8(g >> 8) pix.b = uint8(b >> 8) pix.a = uint8(a >> 8) // Check if the pixel matches the filter rule. if !(match(pix.r, from.R) && match(pix.g, from.G) && match(pix.b, from.B) && match(pix.a, from.A)) { dst.Set(x, y, sc) continue } // Compute three different types of grayscale conversion. // These can be applied by named references. pix.average = uint8(((r + g + b) / 3) >> 8) pix.lightness = uint8(((min(min(r, g), b) + max(max(r, g), b)) / 2) >> 8) // For luminosity it is necessary to apply an inverse of the gamma // function for the color space before calculating the inner product. // Then you apply the gamma function to the reduced value. Failure to // incorporate the gamma function can result in errors of up to 20%. // // For typical computer stuff, the color space is sRGB. The right // numbers for sRGB are approx. 0.21, 0.72, 0.07. Gamma for sRGB // is a composite function that approximates exponentiation by 1/2.2 // // This is a rather expensive operation, but gives a much more accurate // and satisfactory result than the average and lightness versions. pix.luminosity = gammaSRGB( 0.212655*invGammaSRGB(pix.r) + 0.715158*invGammaSRGB(pix.g) + 0.072187*invGammaSRGB(pix.b)) // Transform color. dc.R = transform(&pix, pix.r, to.R) dc.G = transform(&pix, pix.g, to.G) dc.B = transform(&pix, pix.b, to.B) dc.A = transform(&pix, pix.a, to.A) // Set new pixel. dst.Set(x, y, dc) } } }
// Blends src image (top layer) into dst image (bottom layer) using // the BlendFunc provided by mode. BlendFunc is applied to each pixel // where the src image overlaps the dst image and the result is stored // in the original dst image, src image is unmutable. func BlendImage(dst draw.Image, src image.Image, mode BlendFunc) { // Obtain the intersection of both images. inter := dst.Bounds().Intersect(src.Bounds()) // Apply BlendFuc to each pixel in the intersection. for y := inter.Min.Y; y < inter.Max.Y; y++ { for x := inter.Min.X; x < inter.Max.X; x++ { dst.Set(x, y, mode(dst.At(x, y), src.At(x, y))) } } }
func (burkes) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) { quantError0 := make([][3]int32, r.Dx()+4) quantError1 := make([][3]int32, r.Dx()+4) out := color.RGBA64{A: 0xffff} for y := 0; y != r.Dy(); y++ { for x := 0; x != r.Dx(); x++ { sr, sg, sb, _ := src.At(sp.X+x, sp.Y+y).RGBA() er, eg, eb := int32(sr), int32(sg), int32(sb) er = clamp(er + quantError0[x+2][0]/32) eg = clamp(eg + quantError0[x+2][1]/32) eb = clamp(eb + quantError0[x+2][2]/32) out.R = uint16(er) out.G = uint16(eg) out.B = uint16(eb) dst.Set(r.Min.X+x, r.Min.Y+y, &out) sr, sg, sb, _ = dst.At(r.Min.X+x, r.Min.Y+y).RGBA() er -= int32(sr) eg -= int32(sg) eb -= int32(sb) quantError0[x+3][0] += er * 8 quantError0[x+3][1] += eg * 8 quantError0[x+3][2] += eb * 8 quantError0[x+4][0] += er * 4 quantError0[x+4][1] += eg * 4 quantError0[x+4][2] += eb * 4 quantError1[x+0][0] += er * 2 quantError1[x+0][1] += eg * 2 quantError1[x+0][2] += eb * 2 quantError1[x+1][0] += er * 4 quantError1[x+1][1] += eg * 4 quantError1[x+1][2] += eb * 4 quantError1[x+2][0] += er * 8 quantError1[x+2][1] += eg * 8 quantError1[x+2][2] += eb * 8 quantError1[x+3][0] += er * 4 quantError1[x+3][1] += eg * 4 quantError1[x+3][2] += eb * 4 quantError1[x+4][0] += er * 2 quantError1[x+4][1] += eg * 2 quantError1[x+4][2] += eb * 2 } // Recycle the quantization error buffers. quantError0, quantError1 = quantError1, quantError0 for i := range quantError1 { quantError1[i] = [3]int32{} } } }
func noise(src draw.Image) { orig := src.Bounds() numToMod := (orig.Max.X * orig.Max.Y) / 2 for i := 0; i < numToMod; i++ { x := rand.Intn(orig.Max.X) y := rand.Intn(orig.Max.Y) origColor := src.At(x, y).(color.RGBA) origColor.R += 30 origColor.B += 30 origColor.G += 30 src.Set(x, y, origColor) } }
func DrawImage(src image.Image, dest draw.Image, tr MatrixTransform, op draw.Op, filter ImageFilter) { bounds := src.Bounds() x0, y0, x1, y1 := float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y) tr.TransformRectangle(&x0, &y0, &x1, &y1) var x, y, u, v float64 var c1, c2, cr color.Color var r, g, b, a, ia, r1, g1, b1, a1, r2, g2, b2, a2 uint32 var color color.RGBA for x = x0; x < x1; x++ { for y = y0; y < y1; y++ { u = x v = y tr.InverseTransform(&u, &v) if bounds.Min.X <= int(u) && bounds.Max.X > int(u) && bounds.Min.Y <= int(v) && bounds.Max.Y > int(v) { c1 = dest.At(int(x), int(y)) switch filter { case LinearFilter: c2 = src.At(int(u), int(v)) case BilinearFilter: c2 = getColorBilinear(src, u, v) case BicubicFilter: c2 = getColorBicubic(src, u, v) } switch op { case draw.Over: r1, g1, b1, a1 = c1.RGBA() r2, g2, b2, a2 = c2.RGBA() ia = M - a2 r = ((r1 * ia) / M) + r2 g = ((g1 * ia) / M) + g2 b = ((b1 * ia) / M) + b2 a = ((a1 * ia) / M) + a2 color.R = uint8(r >> 8) color.G = uint8(g >> 8) color.B = uint8(b >> 8) color.A = uint8(a >> 8) cr = color default: cr = c2 } dest.Set(int(x), int(y), cr) } } } }
func (z *Rasterizer) rasterizeOpOver(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. dr, dg, db, da := dst.At(r.Min.X+x, r.Min.Y+y).RGBA() a := 0xffff - (sa * ma / 0xffff) out.R = uint16((dr*a + sr*ma) / 0xffff) out.G = uint16((dg*a + sg*ma) / 0xffff) out.B = uint16((db*a + sb*ma) / 0xffff) out.A = uint16((da*a + sa*ma) / 0xffff) dst.Set(r.Min.X+x, r.Min.Y+y, outc) } } }
func (falseFloydSteinberg) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) { quantErrorNext := make([][3]int32, r.Dx()+1) out := color.RGBA64{A: 0xffff} for y := 0; y != r.Dy(); y++ { quantError := [3]int32{} quantErrorNext[0] = [3]int32{} for x := 0; x != r.Dx(); x++ { sr, sg, sb, _ := src.At(sp.X+x, sp.Y+y).RGBA() er, eg, eb := int32(sr), int32(sg), int32(sb) er = clamp(er + (quantErrorNext[x][0]+quantError[0])/16) eg = clamp(eg + (quantErrorNext[x][1]+quantError[1])/16) eb = clamp(eb + (quantErrorNext[x][2]+quantError[2])/16) out.R = uint16(er) out.G = uint16(eg) out.B = uint16(eb) dst.Set(r.Min.X+x, r.Min.Y+y, &out) sr, sg, sb, _ = dst.At(r.Min.X+x, r.Min.Y+y).RGBA() er -= int32(sr) eg -= int32(sg) eb -= int32(sb) quantError[0] = er * 3 quantError[1] = eg * 3 quantError[2] = eb * 3 quantErrorNext[x+0][0] += er * 3 quantErrorNext[x+0][1] += eg * 3 quantErrorNext[x+0][2] += eb * 3 quantErrorNext[x+1][0] = er * 2 quantErrorNext[x+1][1] = eg * 2 quantErrorNext[x+1][2] = eb * 2 } } }
// Copy an images outer edge of pixels and "bleeds" them to the edge of the image func bleed(im draw.Image, amount int) { // TODO refactor outer := im.Bounds() inner := image.Rect(outer.Min.X+amount, outer.Min.Y+amount, outer.Max.X-amount, outer.Max.Y-amount) // Top left col := im.At(inner.Min.X, inner.Min.Y) for x := 0; x < amount; x++ { for y := 0; y < amount; y++ { im.Set(x, y, col) } } // Top edge for x := inner.Min.X; x < inner.Max.X; x++ { col := im.At(x, amount) for y := 0; y < amount; y++ { im.Set(x, y, col) } } // Top right col = im.At(inner.Max.X-1, inner.Min.Y) for x := 1; x <= amount; x++ { for y := 0; y < amount; y++ { im.Set(outer.Max.X-x, y, col) } } // Right edge for y := amount; y < inner.Max.X; y++ { col := im.At(inner.Max.X-1, y) for x := 1; x <= amount; x++ { im.Set(outer.Max.X-x, y, col) } } // Bottom right col = im.At(inner.Max.X-1, inner.Max.Y-1) for x := 1; x <= amount; x++ { for y := 1; y <= amount; y++ { im.Set(outer.Max.X-x, outer.Max.Y-y, col) } } // Bottom edge for x := amount; x < inner.Max.X; x++ { col := im.At(x, inner.Max.Y-1) for y := 1; y <= amount; y++ { im.Set(x, outer.Max.Y-y, col) } } // Bottom left col = im.At(inner.Min.X, inner.Max.Y-1) for x := 0; x < amount; x++ { for y := 1; y <= amount; y++ { im.Set(x, outer.Max.Y-y, col) } } // Left edge for y := amount; y < inner.Max.Y; y++ { col := im.At(amount, y) for x := 0; x < amount; x++ { im.Set(x, y, col) } } }