mirror of
				https://github.com/pcvolkmer/idicon.git
				synced 2025-10-31 10:06:12 +00:00 
			
		
		
		
	Add support for SVG identicons
This commit is contained in:
		| @@ -34,6 +34,8 @@ The latter resembles the color scheme used by GitHub. | |||||||
|  |  | ||||||
| The request query parameter `d` can be used to request GitHub like patterns by setting the value to `github`. | The request query parameter `d` can be used to request GitHub like patterns by setting the value to `github`. | ||||||
|  |  | ||||||
|  | Using query param `ct` with value `svg` or using request header `Accept: image/svg+xml` will generate SVG identicon. | ||||||
|  |  | ||||||
| ==== Examples | ==== Examples | ||||||
|  |  | ||||||
| Some examples for `/avatar/example` and different params. | Some examples for `/avatar/example` and different params. | ||||||
|   | |||||||
| @@ -42,6 +42,29 @@ func (generator *GhIconGenerator) GenIcon(id string, size int) *image.NRGBA { | |||||||
| 	return drawImage(mirrorData(data, blocks), blocks, size, generator.colorGenerator(hash)) | 	return drawImage(mirrorData(data, blocks), blocks, size, generator.colorGenerator(hash)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (generator *GhIconGenerator) GenSvg(id string, size int) string { | ||||||
|  | 	if size > 512 { | ||||||
|  | 		size = 512 | ||||||
|  | 	} | ||||||
|  | 	blocks := 5 | ||||||
|  |  | ||||||
|  | 	hash := HashBytes(id) | ||||||
|  | 	nibbles := nibbles(hash) | ||||||
|  | 	data := make([]bool, blocks*blocks) | ||||||
|  |  | ||||||
|  | 	for x := 0; x < blocks; x++ { | ||||||
|  | 		for y := 0; y < blocks; y++ { | ||||||
|  | 			ni := x + blocks*(blocks-y-1) | ||||||
|  | 			if x+blocks*y >= 2*blocks { | ||||||
|  | 				di := (x + blocks*y) - 2*blocks | ||||||
|  | 				data[di] = nibbles[ni%32]%2 == 0 | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return drawSvg(mirrorData(data, blocks), blocks, size, generator.colorGenerator(hash)) | ||||||
|  | } | ||||||
|  |  | ||||||
| // https://processing.org/reference/map_.html | // https://processing.org/reference/map_.html | ||||||
| func remap(value uint32, vmin uint32, vmax uint32, dmin uint32, dmax uint32) float32 { | func remap(value uint32, vmin uint32, vmax uint32, dmin uint32, dmax uint32) float32 { | ||||||
| 	return float32((value-vmin)*(dmax-dmin)) / float32((vmax-vmin)+dmin) | 	return float32((value-vmin)*(dmax-dmin)) / float32((vmax-vmin)+dmin) | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package icons | |||||||
| import ( | import ( | ||||||
| 	"crypto/md5" | 	"crypto/md5" | ||||||
| 	"encoding/hex" | 	"encoding/hex" | ||||||
|  | 	"fmt" | ||||||
| 	"image" | 	"image" | ||||||
| 	"image/color" | 	"image/color" | ||||||
| 	"image/draw" | 	"image/draw" | ||||||
| @@ -12,6 +13,7 @@ import ( | |||||||
|  |  | ||||||
| type IconGenerator interface { | type IconGenerator interface { | ||||||
| 	GenIcon(id string, size int) *image.NRGBA | 	GenIcon(id string, size int) *image.NRGBA | ||||||
|  | 	GenSvg(id string, size int) string | ||||||
| } | } | ||||||
|  |  | ||||||
| func HashBytes(id string) [16]byte { | func HashBytes(id string) [16]byte { | ||||||
| @@ -62,5 +64,37 @@ func drawImage(data []bool, blocks int, size int, c color.Color) *image.NRGBA { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	drawSvg(data, blocks, size, c) | ||||||
|  |  | ||||||
| 	return img | 	return img | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func drawSvg(data []bool, blocks int, size int, c color.Color) string { | ||||||
|  |  | ||||||
|  | 	blockSize := size / (blocks + 1) | ||||||
|  | 	border := (size - (blocks * blockSize)) / 2 | ||||||
|  | 	r, g, b, _ := c.RGBA() | ||||||
|  | 	colorHtml := fmt.Sprintf("#%x%x%x", r>>8, g>>8, b>>8) | ||||||
|  |  | ||||||
|  | 	blockElems := fmt.Sprintf("<rect style=\"fill:#f0f0f0\" width=\"%d\" height=\"%d\" x=\"0\" y=\"0\" />", size, size) | ||||||
|  |  | ||||||
|  | 	for x := 0; x < blocks; x++ { | ||||||
|  | 		for y := 0; y < blocks; y++ { | ||||||
|  | 			idx := x*blocks + y | ||||||
|  | 			if data[idx] { | ||||||
|  | 				blockElems += fmt.Sprintf( | ||||||
|  | 					`<rect style="fill:%s" width="%d" height="%d" x="%d" y="%d" />`, | ||||||
|  | 					colorHtml, | ||||||
|  | 					blockSize, | ||||||
|  | 					blockSize, | ||||||
|  | 					border+(x*blockSize), | ||||||
|  | 					border+(y*blockSize)) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <svg width="%d" height="%d" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"><g>%s</g></svg>`, | ||||||
|  | 		size, size, | ||||||
|  | 		blockElems) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -34,6 +34,21 @@ func (generator *IdIconGenerator) GenIcon(id string, size int) *image.NRGBA { | |||||||
| 	return drawImage(mirrorData(data, blocks), blocks, size, generator.colorGenerator(hash)) | 	return drawImage(mirrorData(data, blocks), blocks, size, generator.colorGenerator(hash)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (generator *IdIconGenerator) GenSvg(id string, size int) string { | ||||||
|  | 	id = strings.ToLower(id) | ||||||
|  | 	blocks := 5 | ||||||
|  | 	if size > 512 { | ||||||
|  | 		size = 512 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	hash := HashBytes(id) | ||||||
|  | 	data := make([]bool, blocks*blocks) | ||||||
|  | 	for i := 0; i < len(hash)-1; i++ { | ||||||
|  | 		data[i] = hash[i]%2 != hash[i+1]%2 | ||||||
|  | 	} | ||||||
|  | 	return drawSvg(mirrorData(data, blocks), blocks, size, generator.colorGenerator(hash)) | ||||||
|  | } | ||||||
|  |  | ||||||
| func ColorV1(hash [16]byte) color.RGBA { | func ColorV1(hash [16]byte) color.RGBA { | ||||||
| 	r := 32 + (hash[0]%16)/2<<4 | 	r := 32 + (hash[0]%16)/2<<4 | ||||||
| 	g := 32 + (hash[2]%16)/2<<4 | 	g := 32 + (hash[2]%16)/2<<4 | ||||||
|   | |||||||
| @@ -62,7 +62,6 @@ func requestHandler(w http.ResponseWriter, r *http.Request) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	w.Header().Add("Content-Type", "image/png") |  | ||||||
| 	cFunc := icons.ColorV2 | 	cFunc := icons.ColorV2 | ||||||
| 	if colorScheme == "v1" { | 	if colorScheme == "v1" { | ||||||
| 		cFunc = icons.ColorV1 | 		cFunc = icons.ColorV1 | ||||||
| @@ -77,7 +76,15 @@ func requestHandler(w http.ResponseWriter, r *http.Request) { | |||||||
| 		iconGenerator = icons.NewIdIconGenerator().WithColorGenerator(cFunc) | 		iconGenerator = icons.NewIdIconGenerator().WithColorGenerator(cFunc) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	ct := r.URL.Query().Get("ct") | ||||||
|  | 	cth := r.Header.Get("Accept") | ||||||
|  | 	if ct == "svg" || cth == "image/svg+xml" { | ||||||
|  | 		w.Header().Add("Content-Type", "image/svg+xml") | ||||||
|  | 		_, err = w.Write([]byte(iconGenerator.GenSvg(id, size))) | ||||||
|  | 	} else { | ||||||
|  | 		w.Header().Add("Content-Type", "image/png") | ||||||
| 		err = png.Encode(w, iconGenerator.GenIcon(id, size)) | 		err = png.Encode(w, iconGenerator.GenIcon(id, size)) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
|   | |||||||
| @@ -108,18 +108,18 @@ | |||||||
|             width: 90%; |             width: 90%; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         #size-input, #color-input, #type-input { |         #size-input, #color-input, #type-input, #contenttype-input { | ||||||
|             display: flex; |             display: flex; | ||||||
|             flex-direction: column; |             flex-direction: column; | ||||||
|             margin: 0 auto; |             margin: 0 auto; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         #size-input > input, #color-input > select, #type-input > select { |         #size-input > input, #color-input > select, #type-input > select, #contenttype-input > select { | ||||||
|             width: 8em; |             width: 8em; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         #settings { |         #settings { | ||||||
|             grid-template-columns: repeat(3, 1fr); |             grid-template-columns: repeat(4, 1fr); | ||||||
|             display: grid; |             display: grid; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -185,6 +185,13 @@ | |||||||
|                     <option value="gh" selected>GH</option> |                     <option value="gh" selected>GH</option> | ||||||
|                 </select> |                 </select> | ||||||
|             </div> |             </div> | ||||||
|  |             <div id="contenttype-input" class="small"> | ||||||
|  |                 <label for="contenttype">Content-Type</label> | ||||||
|  |                 <select id="contenttype" onchange="newcontenttype(this.value)"> | ||||||
|  |                     <option value="png" selected>PNG</option> | ||||||
|  |                     <option value="svg">SVG</option> | ||||||
|  |                 </select> | ||||||
|  |             </div> | ||||||
|         </fieldset> |         </fieldset> | ||||||
|         <fieldset id="actions"> |         <fieldset id="actions"> | ||||||
|             <legend>Actions</legend> |             <legend>Actions</legend> | ||||||
| @@ -212,6 +219,7 @@ | |||||||
|     let currentsize = document.getElementById('size').value; |     let currentsize = document.getElementById('size').value; | ||||||
|     let currentcolor = document.getElementById('color').value; |     let currentcolor = document.getElementById('color').value; | ||||||
|     let currenttype = document.getElementById('type').value; |     let currenttype = document.getElementById('type').value; | ||||||
|  |     let currentcontenttype = document.getElementById('contenttype').value; | ||||||
|     idicon(document.getElementById('value').value); |     idicon(document.getElementById('value').value); | ||||||
|  |  | ||||||
|     function fetchGhId() { |     function fetchGhId() { | ||||||
| @@ -253,12 +261,17 @@ | |||||||
|         idicon(document.getElementById('value').value); |         idicon(document.getElementById('value').value); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     function newcontenttype(value) { | ||||||
|  |         currentcontenttype = value; | ||||||
|  |         idicon(document.getElementById('value').value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     function idicon(value) { |     function idicon(value) { | ||||||
|         if (value.trim() === '') { |         if (value.trim() === '') { | ||||||
|             document.getElementById('idicon').src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQI12NgAAIAAAUAAeImBZsAAAAASUVORK5CYII='; |             document.getElementById('idicon').src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQI12NgAAIAAAUAAeImBZsAAAAASUVORK5CYII='; | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         document.getElementById('idicon').src = `./avatar/${value}?s=${currentsize}&c=${currentcolor}&d=${currenttype}` |         document.getElementById('idicon').src = `./avatar/${value}?s=${currentsize}&c=${currentcolor}&d=${currenttype}&ct=${currentcontenttype}` | ||||||
|     } |     } | ||||||
| </script> | </script> | ||||||
| </html> | </html> | ||||||
		Reference in New Issue
	
	Block a user