// Halves the width of the double-width image created by hxl to produce nice // smooth edges. func halveWidth(img image.Image) image.Image { b := img.Bounds() o := image.NewRGBA(image.Rect(0, 0, b.Dx()/2, b.Dy())) for y := 0; y < b.Dy(); y++ { for x := 0; x < b.Dx()/2; x++ { l := img.At(x*2, y) r := img.At(x*2+1, y) o.Set(x, y, utils.Average(l, r)) } } return o }
func paintAverage(img image.Image, bounds image.Rectangle, dest draw.Image, c chan int) { values := make([]color.Color, bounds.Dx()*bounds.Dy()) count := 0 utils.EachColorInRectangle(img, bounds, func(c color.Color) { values[count] = c count++ }) avg := utils.Average(values...) utils.MapColorInRectangle(img, bounds, dest, func(c color.Color) color.Color { return avg }) c <- 1 }
// Hxl pixelates the Image into equilateral triangles with the width // given. These are arranged into hexagonal shapes. func Hxl(img image.Image, width int) image.Image { b := img.Bounds() pixelHeight := width * 2 pixelWidth := width cols := b.Dx() / pixelWidth rows := b.Dy() / pixelHeight o := image.NewRGBA(image.Rect(0, 0, pixelWidth*cols*2, pixelHeight*rows)) // Note: "Top" doesn't mean above the x-axis, it means in the triangle // pointing towards the x-axis. inTop := func(x, y float64) bool { return (x >= 0 && y >= x) || (x <= 0 && y >= -x) } // Same for "Bottom" this is the triangle below and pointing towards the // x-axis. inBottom := func(x, y float64) bool { return (x >= 0 && y <= -x) || (x <= 0 && y <= x) } for col := 0; col < cols; col++ { for row := 0; row < rows; row++ { north := []color.Color{} south := []color.Color{} for y := 0; y < pixelHeight; y++ { for x := 0; x < pixelWidth; x++ { realY := row*pixelHeight + y realX := col*pixelWidth + x pixel := img.At(realX, realY) yOrigin := float64(y - pixelHeight/2) xOrigin := float64(x - pixelWidth/2) if inTop(xOrigin, yOrigin) { north = append(north, pixel) } else if inBottom(xOrigin, yOrigin) { south = append(south, pixel) } } } top := utils.Average(north...) bot := utils.Average(south...) for y := 0; y < pixelHeight; y++ { for x := 0; x < pixelWidth*2; x++ { realY := row*pixelHeight + y realX := col*pixelWidth*2 + x yOrigin := float64(y - pixelHeight/2) xOrigin := float64(x - pixelWidth*2/2) if inTop(xOrigin, yOrigin) { o.Set(realX, realY, top) } else if inBottom(xOrigin, yOrigin) { o.Set(realX, realY, bot) } } } } } // Now for the shifted version offsetY := pixelHeight / 2 offsetX := pixelWidth / 2 for col := -1; col < cols; col++ { for row := -1; row < rows; row++ { north := []color.Color{} south := []color.Color{} for y := 0; y < pixelHeight; y++ { for x := 0; x < pixelWidth; x++ { realY := row*pixelHeight + y + offsetY realX := col*pixelWidth + x + offsetX if realX >= 0 && realX < b.Dx() { pixel := img.At(realX, realY) yOrigin := float64(y - pixelHeight/2) xOrigin := float64(x - pixelWidth/2) if inTop(xOrigin, yOrigin) { north = append(north, pixel) } else if inBottom(xOrigin, yOrigin) { south = append(south, pixel) } } } } top := utils.Average(north...) bot := utils.Average(south...) for y := 0; y < pixelHeight; y++ { for x := 0; x < pixelWidth*2; x++ { realY := row*pixelHeight + y + offsetY realX := col*pixelWidth*2 + x + offsetX*2 yOrigin := float64(y - pixelHeight/2) xOrigin := float64(x - pixelWidth*2/2) if inTop(xOrigin, yOrigin) { o.Set(realX, realY, top) } else if inBottom(xOrigin, yOrigin) { o.Set(realX, realY, bot) } } } } } return halveWidth(o) }
func pxlWorker(img image.Image, bounds image.Rectangle, dest draw.Image, size utils.Dimension, triangle Triangle, aliased bool, c chan int) { ratio := float64(size.H) / float64(size.W) inTop := func(x, y float64) bool { return (y > ratio*x) && (y > ratio*-x) } inRight := func(x, y float64) bool { return (y < ratio*x) && (y > ratio*-x) } inBottom := func(x, y float64) bool { return (y < ratio*x) && (y < ratio*-x) } inLeft := func(x, y float64) bool { return (y > ratio*x) && (y < ratio*-x) } to := []color.Color{} ri := []color.Color{} bo := []color.Color{} le := []color.Color{} for y := 0; y < bounds.Dy(); y++ { for x := 0; x < bounds.Dx(); x++ { realY := bounds.Min.Y + y realX := bounds.Min.X + x yOrigin := float64(y - size.H/2) xOrigin := float64(x - size.W/2) if inTop(xOrigin, yOrigin) { to = append(to, img.At(realX, realY)) } else if inRight(xOrigin, yOrigin) { ri = append(ri, img.At(realX, realY)) } else if inBottom(xOrigin, yOrigin) { bo = append(bo, img.At(realX, realY)) } else if inLeft(xOrigin, yOrigin) { le = append(le, img.At(realX, realY)) } } } ato := utils.Average(to...) ari := utils.Average(ri...) abo := utils.Average(bo...) ale := utils.Average(le...) if (triangle != LEFT) && (triangle == RIGHT || utils.Closeness(ato, ari) > utils.Closeness(ato, ale)) { topRight := utils.Average(ato, ari) bottomLeft := utils.Average(abo, ale) middle := utils.Average(topRight, bottomLeft) for y := 0; y < bounds.Dy(); y++ { for x := 0; x < bounds.Dx(); x++ { realY := bounds.Min.Y + y realX := bounds.Min.X + x yOrigin := float64(y - size.H/2) xOrigin := float64(x - size.W/2) if yOrigin > ratio*xOrigin { dest.Set(realX, realY, topRight) } else if yOrigin == ratio*xOrigin && !aliased { dest.Set(realX, realY, middle) } else { dest.Set(realX, realY, bottomLeft) } } } } else { topLeft := utils.Average(ato, ale) bottomRight := utils.Average(abo, ari) middle := utils.Average(topLeft, bottomRight) for y := 0; y < bounds.Dy(); y++ { for x := 0; x < bounds.Dx(); x++ { realY := bounds.Min.Y + y realX := bounds.Min.X + x yOrigin := float64(y - size.H/2) xOrigin := float64(x - size.W/2) // Do this one opposite to above so that the diagonals line up when // aliased. if yOrigin < ratio*-xOrigin { dest.Set(realX, realY, bottomRight) } else if yOrigin == ratio*-xOrigin && !aliased { dest.Set(realX, realY, middle) } else { dest.Set(realX, realY, topLeft) } } } } c <- 1 }
// Vxl pixelates the Image into isometric cubes. It averages the colours and // naïvely darkens and lightens the colours to mimic highlight and shade. func Vxl(img image.Image, height int, flip bool, top, left, right float64) image.Image { b := img.Bounds() pixelHeight := height pixelWidth := int(math.Sqrt(3.0) * float64(pixelHeight) / 2.0) cols := b.Dx() / pixelWidth rows := b.Dy() / pixelHeight // intersection of lines c := float64(pixelHeight) / 2 // gradient of lines k := math.Sqrt(3.0) / 3.0 o := image.NewRGBA(image.Rect(0, 0, pixelWidth*cols, pixelHeight*rows)) // See: http://www.flickr.com/photos/hawx-/8466236036/ inTopSquare := func(x, y float64) bool { if !flip { y *= -1 } return y <= -k*x+c && y >= k*x && y >= -k*x && y <= k*x+c } inBottomRight := func(x, y float64) bool { if !flip { y *= -1 } return x >= 0 && y <= k*x && y >= k*x-c } inBottomLeft := func(x, y float64) bool { if !flip { y *= -1 } return x <= 0 && y <= -k*x && y >= -k*x-c } inHexagon := func(x, y float64) bool { return inTopSquare(x, y) || inBottomRight(x, y) || inBottomLeft(x, y) } topL := channel.AdjustC(utils.Multiplier(top), channel.Lightness) rightL := channel.AdjustC(utils.Multiplier(right), channel.Lightness) leftL := channel.AdjustC(utils.Multiplier(left), channel.Lightness) for col := 0; col < cols; col++ { for row := 0; row < rows; row++ { seen := []color.Color{} for y := 0; y < pixelHeight; y++ { for x := 0; x < pixelWidth; x++ { realY := row*(pixelHeight+int(c)) + y realX := col*pixelWidth + x pixel := img.At(realX, realY) yOrigin := float64(y - pixelHeight/2) xOrigin := float64(x - pixelWidth/2) if inHexagon(xOrigin, yOrigin) { seen = append(seen, pixel) } } } average := utils.Average(seen...) for y := 0; y < pixelHeight; y++ { for x := 0; x < pixelWidth; x++ { realY := row*(pixelHeight+int(c)) + y realX := col*pixelWidth + x yOrigin := float64(y - pixelHeight/2) xOrigin := float64(x - pixelWidth/2) // This stops white bits showing above the top squares. It does mean // the dimensions aren't perfect, but what did you expect with pixels // and trig. It is inefficient though, maybe fix that later? if (!flip && yOrigin < 0) || (flip && yOrigin > 0) { o.Set(realX, realY, topL(average)) } else { if xOrigin > 0 { o.Set(realX, realY, rightL(average)) } else { o.Set(realX, realY, leftL(average)) } } if inTopSquare(xOrigin, yOrigin) { o.Set(realX, realY, topL(average)) } if inBottomRight(xOrigin, yOrigin) { o.Set(realX, realY, rightL(average)) } if inBottomLeft(xOrigin, yOrigin) { o.Set(realX, realY, leftL(average)) } } } } } offsetY := (pixelHeight + int(c)) / 2.0 offsetX := pixelWidth / 2 for col := -1; col < cols; col++ { for row := -1; row < rows; row++ { seen := []color.Color{} for y := 0; y < pixelHeight; y++ { for x := 0; x < pixelWidth; x++ { realY := row*(pixelHeight+int(c)) + y + offsetY realX := col*pixelWidth + x + offsetX if image.Pt(realX, realY).In(b) { pixel := img.At(realX, realY) yOrigin := float64(y - pixelHeight/2) xOrigin := float64(x - pixelWidth/2) if inHexagon(xOrigin, yOrigin) { seen = append(seen, pixel) } } } } if len(seen) <= 0 { continue } average := utils.Average(seen...) for y := 0; y < pixelHeight; y++ { for x := 0; x < pixelWidth; x++ { realY := row*(pixelHeight+int(c)) + y + offsetY realX := col*pixelWidth + x + offsetX yOrigin := float64(y - pixelHeight/2) xOrigin := float64(x - pixelWidth/2) if inTopSquare(xOrigin, yOrigin) { o.Set(realX, realY, topL(average)) } if inBottomRight(xOrigin, yOrigin) { o.Set(realX, realY, rightL(average)) } if inBottomLeft(xOrigin, yOrigin) { o.Set(realX, realY, leftL(average)) } } } } } return o }