From 0460aa75445b39034bb28be873d8e2728f4e115d Mon Sep 17 00:00:00 2001 From: Miguel Camba Date: Tue, 29 Jul 2014 10:37:16 +0100 Subject: [PATCH 1/6] First implementation. Filling pixels one by one. TODO: - Must exist a better way of filling portion of an image an once instead of iteration over pixels. - Allow to export image as jpg/webp - Allow to customize with flags: output file name/path, format? - Different image generations? Random triangles? Barcodes? --- cibernox/src/avatarme.go | 97 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 cibernox/src/avatarme.go diff --git a/cibernox/src/avatarme.go b/cibernox/src/avatarme.go new file mode 100644 index 0000000..2fe46a8 --- /dev/null +++ b/cibernox/src/avatarme.go @@ -0,0 +1,97 @@ +package main + +import ( + "crypto/md5" + "fmt" + "image" + "image/color" + "image/png" + "os" +) + +const ( + enlarger string = "md5-enlarger" + avatarSide int = 8 + signatureLength int = avatarSide * avatarSide + scale int = 32 +) + +var palette map[byte]color.RGBA = map[byte]color.RGBA{ + 48: color.RGBA{255, 255, 255, 255}, // 0 => white, + 49: color.RGBA{213, 0, 0, 255}, // 1 => red, + 50: color.RGBA{255, 255, 255, 255}, // 2 => white, + 51: color.RGBA{255, 255, 255, 255}, // 3 => white, + 52: color.RGBA{255, 76, 0, 255}, // 4 => orange, + 53: color.RGBA{255, 255, 255, 255}, // 5 => white, + 54: color.RGBA{255, 255, 255, 255}, // 6 => white, + 55: color.RGBA{255, 255, 11, 255}, // 7 => yellow, + 56: color.RGBA{255, 76, 0, 255}, // 8 => orange, + 57: color.RGBA{213, 0, 0, 255}, // 9 => red, + 97: color.RGBA{239, 0, 113, 255}, // a => magenta, + 98: color.RGBA{54, 0, 151, 255}, // b => purple, + 99: color.RGBA{0, 0, 205, 255}, // c => blue, + 100: color.RGBA{0, 152, 232, 255}, // d => cyan, + 101: color.RGBA{26, 176, 0, 255}, // e => green, + 102: color.RGBA{0, 0, 0, 255}, // f => black, + } + +// Gets a MD5 hash from the given string limited to exactly 64 chars. +// +func getMD5(s string) string { + hasher := md5.New() + byteSignature := hasher.Sum([]byte(s + enlarger)) + chunkedSignature := byteSignature[0 : signatureLength/2] + return fmt.Sprintf("%x", chunkedSignature) +} + +// Generates a image.RGBA of colors given a hexadecimal string +// +func buildImage(hash string) *image.RGBA { + img := image.NewRGBA(image.Rect(0, 0, avatarSide*scale, avatarSide*scale)) + + for multiplier := 0; multiplier < scale; multiplier++ { + for x := 0; x < avatarSide; x++ { + for y := 0; y < avatarSide; y++ { + scaledX := x + x * multiplier + scaledY := y + y * multiplier + color := palette[hash[x*avatarSide+y]] + fillPixel(img, scaledX, scaledY, color) + } + } + } + + return img +} + +// Fill the virtual pixel with the given coordinates with the given color. +// +// Images always are a 8x8 virtual image, but the output might be bigger. +// Per example, if we want a 512x512 avatar, we set the `scale` constant to 64. +// That way each virtual pixel will be a square of 32x32, resulting in a 512x512 image. +// +func fillPixel(img *image.RGBA, x, y int, color color.RGBA) { + for i := 0; i < scale; i++ { + for j := 0; j < scale; j++ { + img.Set(x+i, y+j, color) + } + } +} + +// Exports the given image as a PNG file with the name `output.png` +// +func exportImage(img *image.RGBA) { + file, err := os.Create("output.png") + if err != nil { + fmt.Println("Error creating file") + } + defer file.Close() + + png.Encode(file, img) +} + +func main() { + text := os.Args[1] + hash := getMD5(text) + img := buildImage(hash) + exportImage(img) +} From 6bb044400e3835e3c86a59c31dda13fcc2ca0dfd Mon Sep 17 00:00:00 2001 From: Miguel Camba Date: Tue, 29 Jul 2014 10:49:06 +0100 Subject: [PATCH 2/6] gofmt code --- cibernox/src/avatarme.go | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/cibernox/src/avatarme.go b/cibernox/src/avatarme.go index 2fe46a8..4eaf326 100644 --- a/cibernox/src/avatarme.go +++ b/cibernox/src/avatarme.go @@ -17,23 +17,23 @@ const ( ) var palette map[byte]color.RGBA = map[byte]color.RGBA{ - 48: color.RGBA{255, 255, 255, 255}, // 0 => white, - 49: color.RGBA{213, 0, 0, 255}, // 1 => red, - 50: color.RGBA{255, 255, 255, 255}, // 2 => white, - 51: color.RGBA{255, 255, 255, 255}, // 3 => white, - 52: color.RGBA{255, 76, 0, 255}, // 4 => orange, - 53: color.RGBA{255, 255, 255, 255}, // 5 => white, - 54: color.RGBA{255, 255, 255, 255}, // 6 => white, - 55: color.RGBA{255, 255, 11, 255}, // 7 => yellow, - 56: color.RGBA{255, 76, 0, 255}, // 8 => orange, - 57: color.RGBA{213, 0, 0, 255}, // 9 => red, - 97: color.RGBA{239, 0, 113, 255}, // a => magenta, - 98: color.RGBA{54, 0, 151, 255}, // b => purple, - 99: color.RGBA{0, 0, 205, 255}, // c => blue, - 100: color.RGBA{0, 152, 232, 255}, // d => cyan, - 101: color.RGBA{26, 176, 0, 255}, // e => green, - 102: color.RGBA{0, 0, 0, 255}, // f => black, - } + 48: color.RGBA{255, 255, 255, 255}, // 0 => white, + 49: color.RGBA{213, 0, 0, 255}, // 1 => red, + 50: color.RGBA{255, 255, 255, 255}, // 2 => white, + 51: color.RGBA{255, 255, 255, 255}, // 3 => white, + 52: color.RGBA{255, 76, 0, 255}, // 4 => orange, + 53: color.RGBA{255, 255, 255, 255}, // 5 => white, + 54: color.RGBA{255, 255, 255, 255}, // 6 => white, + 55: color.RGBA{255, 255, 11, 255}, // 7 => yellow, + 56: color.RGBA{255, 76, 0, 255}, // 8 => orange, + 57: color.RGBA{213, 0, 0, 255}, // 9 => red, + 97: color.RGBA{239, 0, 113, 255}, // a => magenta, + 98: color.RGBA{54, 0, 151, 255}, // b => purple, + 99: color.RGBA{0, 0, 205, 255}, // c => blue, + 100: color.RGBA{0, 152, 232, 255}, // d => cyan, + 101: color.RGBA{26, 176, 0, 255}, // e => green, + 102: color.RGBA{0, 0, 0, 255}, // f => black, +} // Gets a MD5 hash from the given string limited to exactly 64 chars. // @@ -49,11 +49,11 @@ func getMD5(s string) string { func buildImage(hash string) *image.RGBA { img := image.NewRGBA(image.Rect(0, 0, avatarSide*scale, avatarSide*scale)) - for multiplier := 0; multiplier < scale; multiplier++ { + for multiplier := 0; multiplier < scale; multiplier++ { for x := 0; x < avatarSide; x++ { for y := 0; y < avatarSide; y++ { - scaledX := x + x * multiplier - scaledY := y + y * multiplier + scaledX := x + x*multiplier + scaledY := y + y*multiplier color := palette[hash[x*avatarSide+y]] fillPixel(img, scaledX, scaledY, color) } @@ -91,7 +91,7 @@ func exportImage(img *image.RGBA) { func main() { text := os.Args[1] - hash := getMD5(text) + hash := getMD5(text) img := buildImage(hash) exportImage(img) } From 08e3c1350e5c8ba4f2ecf82d7db07b28bab2af27 Mon Sep 17 00:00:00 2001 From: Miguel Camba Date: Thu, 31 Jul 2014 00:41:27 +0100 Subject: [PATCH 3/6] Optimized loop and new approach using image/draw --- cibernox/src/avatarme.go | 48 ++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/cibernox/src/avatarme.go b/cibernox/src/avatarme.go index 4eaf326..4d987bb 100644 --- a/cibernox/src/avatarme.go +++ b/cibernox/src/avatarme.go @@ -5,6 +5,7 @@ import ( "fmt" "image" "image/color" + "image/draw" "image/png" "os" ) @@ -13,7 +14,7 @@ const ( enlarger string = "md5-enlarger" avatarSide int = 8 signatureLength int = avatarSide * avatarSide - scale int = 32 + scale int = 512 ) var palette map[byte]color.RGBA = map[byte]color.RGBA{ @@ -44,19 +45,41 @@ func getMD5(s string) string { return fmt.Sprintf("%x", chunkedSignature) } -// Generates a image.RGBA of colors given a hexadecimal string +// Generates a image.RGBA of colors given a hexadecimal string. // -func buildImage(hash string) *image.RGBA { +// Time required for compile and generate an image 64x64: 0.175s +// Time required for compile and generate an image 4096x4096: 1.644s +// +func buildImagePixelByPixel(hash string) *image.RGBA { + img := image.NewRGBA(image.Rect(0, 0, avatarSide*scale, avatarSide*scale)) + + for x := 0; x < avatarSide; x++ { + for y := 0; y < avatarSide; y++ { + fillPixel(img, x*scale, y*scale, palette[hash[x*avatarSide+y]]) + } + } + + return img +} + +// Generates a image.RGBA of colors given a hexadecimal string. It draws solid rectangles +// instead of iterate over all the pixels of the image. +// Due to this, it always performs the same number of painting operations (64) and +// times are much more constant. +// +// Time required for compile and generate an image 64x64: 0.176s +// Time required for compile and generate an image 4096x4096: 0.784s +// +func buildImageDrawing(hash string) *image.RGBA { img := image.NewRGBA(image.Rect(0, 0, avatarSide*scale, avatarSide*scale)) - for multiplier := 0; multiplier < scale; multiplier++ { - for x := 0; x < avatarSide; x++ { - for y := 0; y < avatarSide; y++ { - scaledX := x + x*multiplier - scaledY := y + y*multiplier - color := palette[hash[x*avatarSide+y]] - fillPixel(img, scaledX, scaledY, color) - } + for x := 0; x < avatarSide; x++ { + for y := 0; y < avatarSide; y++ { + color := palette[hash[x*avatarSide+y]] + startPoint := image.Point{x*scale, y*scale} + endPoint := image.Point{x*scale + scale, y*scale + scale} + rectangle := image.Rectangle{startPoint, endPoint} + draw.Draw(img, rectangle, &image.Uniform{color}, image.ZP, draw.Src) } } @@ -92,6 +115,7 @@ func exportImage(img *image.RGBA) { func main() { text := os.Args[1] hash := getMD5(text) - img := buildImage(hash) + // img := buildImagePixelByPixel(hash) + img := buildImageDrawing(hash) exportImage(img) } From f151c35fa1828db21ffefcc1c0d1f14f31e4b94f Mon Sep 17 00:00:00 2001 From: Miguel Camba Date: Thu, 31 Jul 2014 09:51:45 +0100 Subject: [PATCH 4/6] Start using codegansta/cli for parsing arguemnts. --- cibernox/src/avatarme.go | 91 +++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 48 deletions(-) diff --git a/cibernox/src/avatarme.go b/cibernox/src/avatarme.go index 4d987bb..14c3d32 100644 --- a/cibernox/src/avatarme.go +++ b/cibernox/src/avatarme.go @@ -3,18 +3,24 @@ package main import ( "crypto/md5" "fmt" + "github.com/codegangsta/cli" "image" "image/color" "image/draw" + "image/jpeg" "image/png" "os" + "regexp" ) const ( enlarger string = "md5-enlarger" avatarSide int = 8 signatureLength int = avatarSide * avatarSide - scale int = 512 +) + +var ( + extensionRegexp *regexp.Regexp = regexp.MustCompile(`\.(png|jpe?g)$`) ) var palette map[byte]color.RGBA = map[byte]color.RGBA{ @@ -39,27 +45,12 @@ var palette map[byte]color.RGBA = map[byte]color.RGBA{ // Gets a MD5 hash from the given string limited to exactly 64 chars. // func getMD5(s string) string { - hasher := md5.New() - byteSignature := hasher.Sum([]byte(s + enlarger)) - chunkedSignature := byteSignature[0 : signatureLength/2] - return fmt.Sprintf("%x", chunkedSignature) -} - -// Generates a image.RGBA of colors given a hexadecimal string. -// -// Time required for compile and generate an image 64x64: 0.175s -// Time required for compile and generate an image 4096x4096: 1.644s -// -func buildImagePixelByPixel(hash string) *image.RGBA { - img := image.NewRGBA(image.Rect(0, 0, avatarSide*scale, avatarSide*scale)) - - for x := 0; x < avatarSide; x++ { - for y := 0; y < avatarSide; y++ { - fillPixel(img, x*scale, y*scale, palette[hash[x*avatarSide+y]]) - } + length := len(s) + if length < 16 { + s = s + enlarger[0:16-length] } - - return img + hasher := md5.New() + return fmt.Sprintf("%x", hasher.Sum([]byte(s))) } // Generates a image.RGBA of colors given a hexadecimal string. It draws solid rectangles @@ -67,16 +58,16 @@ func buildImagePixelByPixel(hash string) *image.RGBA { // Due to this, it always performs the same number of painting operations (64) and // times are much more constant. // -// Time required for compile and generate an image 64x64: 0.176s -// Time required for compile and generate an image 4096x4096: 0.784s +// Time required for generate an image 64x64: 0.005s +// Time required for generate an image 4096x4096: 0.613s // -func buildImageDrawing(hash string) *image.RGBA { +func buildImage(hash string, scale int) *image.RGBA { img := image.NewRGBA(image.Rect(0, 0, avatarSide*scale, avatarSide*scale)) for x := 0; x < avatarSide; x++ { for y := 0; y < avatarSide; y++ { color := palette[hash[x*avatarSide+y]] - startPoint := image.Point{x*scale, y*scale} + startPoint := image.Point{x * scale, y * scale} endPoint := image.Point{x*scale + scale, y*scale + scale} rectangle := image.Rectangle{startPoint, endPoint} draw.Draw(img, rectangle, &image.Uniform{color}, image.ZP, draw.Src) @@ -86,36 +77,40 @@ func buildImageDrawing(hash string) *image.RGBA { return img } -// Fill the virtual pixel with the given coordinates with the given color. +// Exports the given image in the given path. // -// Images always are a 8x8 virtual image, but the output might be bigger. -// Per example, if we want a 512x512 avatar, we set the `scale` constant to 64. -// That way each virtual pixel will be a square of 32x32, resulting in a 512x512 image. -// -func fillPixel(img *image.RGBA, x, y int, color color.RGBA) { - for i := 0; i < scale; i++ { - for j := 0; j < scale; j++ { - img.Set(x+i, y+j, color) - } - } -} - -// Exports the given image as a PNG file with the name `output.png` -// -func exportImage(img *image.RGBA) { - file, err := os.Create("output.png") +func exportImage(img *image.RGBA, filepath string) { + file, err := os.Create(filepath) if err != nil { fmt.Println("Error creating file") } defer file.Close() - png.Encode(file, img) + extension := extensionRegexp.FindString(filepath) + + if extension == ".png" { + png.Encode(file, img) + } else if extension == ".jpg" || extension == ".jpeg" { + jpeg.Encode(file, img, &jpeg.Options{95}) + } else { + fmt.Println("Invalid extension") + } } func main() { - text := os.Args[1] - hash := getMD5(text) - // img := buildImagePixelByPixel(hash) - img := buildImageDrawing(hash) - exportImage(img) + app := cli.NewApp() + app.Name = "Avatarme" + app.Usage = "Generates an unique avatar for the given string" + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "output, o", Value: "output.png", Usage: "path of the output file"}, + cli.IntFlag{Name: "size, s", Value: 256, Usage: "side length of the generated image (in px). Will be ronded to a multiple of 8"}, + } + app.Action = func(c *cli.Context) { + text := c.Args()[0] + hash := getMD5(text) + img := buildImage(hash, c.Int("size")/8) + exportImage(img, c.String("output")) + } + + app.Run(os.Args) } From 934a52405ebd8099b51185a05569f940fb8eb258 Mon Sep 17 00:00:00 2001 From: Miguel Camba Date: Sat, 2 Aug 2014 00:54:47 +0100 Subject: [PATCH 5/6] Reorganize code in modules --- cibernox/src/avatarme/encoder/encoder.go | 32 +++++++++ .../generators/pixelated/pixelated.go} | 68 +++---------------- cibernox/src/avatarme/main.go | 25 +++++++ 3 files changed, 67 insertions(+), 58 deletions(-) create mode 100644 cibernox/src/avatarme/encoder/encoder.go rename cibernox/src/{avatarme.go => avatarme/generators/pixelated/pixelated.go} (50%) create mode 100644 cibernox/src/avatarme/main.go diff --git a/cibernox/src/avatarme/encoder/encoder.go b/cibernox/src/avatarme/encoder/encoder.go new file mode 100644 index 0000000..e41d7e1 --- /dev/null +++ b/cibernox/src/avatarme/encoder/encoder.go @@ -0,0 +1,32 @@ +package encoder + +import ( + "fmt" + "image" + "image/jpeg" + "image/png" + "os" + "regexp" +) + +var extensionRegexp *regexp.Regexp = regexp.MustCompile(`\.(png|jpe?g)$`) + +// Exports the given image in the given path. +// +func ExportImage(img *image.RGBA, filepath string) { + file, err := os.Create(filepath) + if err != nil { + fmt.Println("Error creating file") + } + defer file.Close() + + extension := extensionRegexp.FindString(filepath) + + if extension == ".png" { + png.Encode(file, img) + } else if extension == ".jpg" || extension == ".jpeg" { + jpeg.Encode(file, img, &jpeg.Options{95}) + } else { + fmt.Println("Invalid extension") + } +} diff --git a/cibernox/src/avatarme.go b/cibernox/src/avatarme/generators/pixelated/pixelated.go similarity index 50% rename from cibernox/src/avatarme.go rename to cibernox/src/avatarme/generators/pixelated/pixelated.go index 14c3d32..1917edd 100644 --- a/cibernox/src/avatarme.go +++ b/cibernox/src/avatarme/generators/pixelated/pixelated.go @@ -1,26 +1,17 @@ -package main +package pixelated import ( "crypto/md5" "fmt" - "github.com/codegangsta/cli" "image" "image/color" "image/draw" - "image/jpeg" - "image/png" - "os" - "regexp" ) const ( - enlarger string = "md5-enlarger" avatarSide int = 8 signatureLength int = avatarSide * avatarSide -) - -var ( - extensionRegexp *regexp.Regexp = regexp.MustCompile(`\.(png|jpe?g)$`) + enlarger string = "md5-enlarger" ) var palette map[byte]color.RGBA = map[byte]color.RGBA{ @@ -42,7 +33,9 @@ var palette map[byte]color.RGBA = map[byte]color.RGBA{ 102: color.RGBA{0, 0, 0, 255}, // f => black, } -// Gets a MD5 hash from the given string limited to exactly 64 chars. +// Generates a MD5 hash from the given string limited to exactly 64 chars. +// If the length of the string is not enough to generate a valid hash, appends some content +// at the end. // func getMD5(s string) string { length := len(s) @@ -53,15 +46,12 @@ func getMD5(s string) string { return fmt.Sprintf("%x", hasher.Sum([]byte(s))) } -// Generates a image.RGBA of colors given a hexadecimal string. It draws solid rectangles -// instead of iterate over all the pixels of the image. -// Due to this, it always performs the same number of painting operations (64) and -// times are much more constant. -// -// Time required for generate an image 64x64: 0.005s -// Time required for generate an image 4096x4096: 0.613s +// Generates a image.RGBA of colors given a hexadecimal string. // -func buildImage(hash string, scale int) *image.RGBA { +func BuildImage(text string, size int) *image.RGBA { + hash := getMD5(text) + scale := size / avatarSide + img := image.NewRGBA(image.Rect(0, 0, avatarSide*scale, avatarSide*scale)) for x := 0; x < avatarSide; x++ { @@ -76,41 +66,3 @@ func buildImage(hash string, scale int) *image.RGBA { return img } - -// Exports the given image in the given path. -// -func exportImage(img *image.RGBA, filepath string) { - file, err := os.Create(filepath) - if err != nil { - fmt.Println("Error creating file") - } - defer file.Close() - - extension := extensionRegexp.FindString(filepath) - - if extension == ".png" { - png.Encode(file, img) - } else if extension == ".jpg" || extension == ".jpeg" { - jpeg.Encode(file, img, &jpeg.Options{95}) - } else { - fmt.Println("Invalid extension") - } -} - -func main() { - app := cli.NewApp() - app.Name = "Avatarme" - app.Usage = "Generates an unique avatar for the given string" - app.Flags = []cli.Flag{ - cli.StringFlag{Name: "output, o", Value: "output.png", Usage: "path of the output file"}, - cli.IntFlag{Name: "size, s", Value: 256, Usage: "side length of the generated image (in px). Will be ronded to a multiple of 8"}, - } - app.Action = func(c *cli.Context) { - text := c.Args()[0] - hash := getMD5(text) - img := buildImage(hash, c.Int("size")/8) - exportImage(img, c.String("output")) - } - - app.Run(os.Args) -} diff --git a/cibernox/src/avatarme/main.go b/cibernox/src/avatarme/main.go new file mode 100644 index 0000000..f4711fb --- /dev/null +++ b/cibernox/src/avatarme/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "./encoder" + "./generators/pixelated" + "github.com/codegangsta/cli" + "os" +) + +func main() { + app := cli.NewApp() + app.Name = "Avatarme" + app.Usage = "Generates an unique avatar for the given string" + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "output, o", Value: "output.png", Usage: "path of the output file"}, + cli.IntFlag{Name: "size, s", Value: 256, Usage: "side length of the generated image (in px). Will be ronded to a multiple of 8"}, + } + app.Action = func(c *cli.Context) { + text := c.Args()[0] + img := pixelated.BuildImage(text, c.Int("size")) + encoder.ExportImage(img, c.String("output")) + } + + app.Run(os.Args) +} From fdc92313bd039afc4c6c6184e819bba594aaeb0d Mon Sep 17 00:00:00 2001 From: Miguel Camba Date: Tue, 5 Aug 2014 00:35:49 +0100 Subject: [PATCH 6/6] Check presence of string to be encoded. V 0.0.1 --- cibernox/src/avatarme/main.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cibernox/src/avatarme/main.go b/cibernox/src/avatarme/main.go index f4711fb..bf20d5a 100644 --- a/cibernox/src/avatarme/main.go +++ b/cibernox/src/avatarme/main.go @@ -5,17 +5,23 @@ import ( "./generators/pixelated" "github.com/codegangsta/cli" "os" + "fmt" ) func main() { app := cli.NewApp() app.Name = "Avatarme" app.Usage = "Generates an unique avatar for the given string" + app.Version = "0.0.1" app.Flags = []cli.Flag{ cli.StringFlag{Name: "output, o", Value: "output.png", Usage: "path of the output file"}, cli.IntFlag{Name: "size, s", Value: 256, Usage: "side length of the generated image (in px). Will be ronded to a multiple of 8"}, } app.Action = func(c *cli.Context) { + if len(c.Args()) <= 0 { + fmt.Println("Error: You need to supply a string to encode") + return + } text := c.Args()[0] img := pixelated.BuildImage(text, c.Int("size")) encoder.ExportImage(img, c.String("output"))