Skip to content

Instantly share code, notes, and snippets.

@iDevelopThings
Created November 17, 2024 08:17
Show Gist options
  • Save iDevelopThings/acb68d32558ee5980832944c69737bfb to your computer and use it in GitHub Desktop.
Save iDevelopThings/acb68d32558ee5980832944c69737bfb to your computer and use it in GitHub Desktop.
package icons_service
import (
"context"
"fmt"
"image"
"image/png"
"io"
"net/http"
"os"
"path"
"time"
"github.com/fogleman/gg"
"github.com/schollz/progressbar/v3"
"github.com/wandb/parallel"
"golang.org/x/image/colornames"
"github.com/srwiley/oksvg"
"github.com/srwiley/rasterx"
"GoogleFontsPluginApi/logger"
"GoogleFontsPluginApi/util"
)
func downloadAllIcons(provider *IconProvider) error {
fmt.Println("Downloading icons\n\n")
startedAt := time.Now()
defer func() { logger.Debug("[downloadAllIcons]: %v", time.Since(startedAt)) }()
bar := progressbar.Default(int64(provider.Cache.Len()), "Downloading icons")
ctx := context.Background()
group := parallel.ErrGroup(parallel.Limited(ctx, 100))
for k, i := range provider.Cache.KVIterator() {
icon := i
key := k
group.Go(func(ctx context.Context) error {
defer bar.Add(1)
p := path.Join("data", provider.Id, "icons", key+".svg")
if util.FileExists(p) {
return nil
}
// download the file from icon.Url
res, err := http.Get(icon.Url)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return fmt.Errorf("failed to download icon %s: %s", icon.Name, res.Status)
}
if err := os.MkdirAll(path.Dir(p), os.ModePerm); err != nil {
return err
}
f, err := os.Create(p)
if err != nil {
return err
}
defer f.Close()
if _, err := io.Copy(f, res.Body); err != nil {
return err
}
return nil
})
}
return group.Wait()
}
func CreateSheet(provider *IconProvider) error {
// download all icons
if err := downloadAllIcons(provider); err != nil {
return err
}
const (
iconSize = 64 // Default size to scale each icon to in the sprite sheet
numColumns = 10 // Number of columns in the sprite sheet
)
iconPaths := []string{}
for k, _ := range provider.Cache.KVIterator() {
iconPaths = append(iconPaths, path.Join("data", provider.Id, "icons", k+".svg"))
}
// Calculate the size of the sprite sheet
numRows := (len(iconPaths) + numColumns - 1) / numColumns
sheetWidth := iconSize * numColumns
sheetHeight := iconSize * numRows
// Create a new sprite sheet
dc := gg.NewContext(sheetWidth, sheetHeight)
dc.SetRGBA(0, 0, 0, 0) // Transparent background
dc.Clear()
// Draw each icon onto the sprite sheet
for i, iconPath := range iconPaths {
file, err := os.Open(iconPath)
if err != nil {
panic(err)
}
defer file.Close()
dc.SetHexColor("#ffffff")
svgIcon, err := oksvg.ReadIconStream(file)
if err != nil {
panic(err)
}
// Get the original dimensions of the SVG
vbWidth := svgIcon.ViewBox.W
vbHeight := svgIcon.ViewBox.H
// Calculate the scale factor to fit the icon within the iconSize while maintaining aspect ratio
scaleX := float64(iconSize) / vbWidth
scaleY := float64(iconSize) / vbHeight
scale := min(scaleX, scaleY)
// Calculate the offset to center the icon within the iconSize
offsetX := (float64(iconSize) - (vbWidth * scale)) / 2
offsetY := (float64(iconSize) - (vbHeight * scale)) / 2
// Set the target dimensions for the SVG
svgIcon.SetTarget(offsetX, offsetY, vbWidth*scale, vbHeight*scale)
img := image.NewRGBA(image.Rect(0, 0, iconSize, iconSize))
// Render the SVG to a raster image
rgba := gg.NewContextForRGBA(img)
raster := rasterx.NewDasher(
iconSize,
iconSize,
rasterx.NewScannerGV(iconSize, iconSize, img, img.Bounds()),
)
raster.SetColor(colornames.White)
for idx, svgp := range svgIcon.SVGPaths {
// svgp.SetLineColor(colornames.White)
svgp.SetFillColor(colornames.White)
svgIcon.SVGPaths[idx] = svgp
}
svgIcon.Draw(raster, 1.0)
col := i % numColumns
row := i / numColumns
x := float64(col * iconSize)
y := float64(row * iconSize)
dc.DrawImage(rgba.Image(), int(x), int(y))
}
// Save the sprite sheet to a file
outFile, err := os.Create("sprite_sheet.png")
if err != nil {
panic(err)
}
defer outFile.Close()
err = png.Encode(outFile, dc.Image())
if err != nil {
panic(err)
}
// create sprite sheet
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment