Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add customizable margin between avatars #5

Merged
merged 3 commits into from
Nov 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Package cmd contains the command line interface for the application.
package cmd

import (
"fmt"
"os"
)

func Execute() {
onError(rootCmd.Execute())
}

func onError(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
57 changes: 57 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cmd

import (
"image"
"image/png"
"os"

"github.com/cli/go-gh/v2/pkg/api"
"github.com/cli/go-gh/v2/pkg/repository"
"github.com/spenserblack/gh-collab-montage/pkg/avatar"
"github.com/spenserblack/gh-collab-montage/pkg/avatar/grid"
"github.com/spenserblack/gh-collab-montage/pkg/usersource"
"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "gh-collab-montage",
Short: "Combine your contributors avatars into a single image",
Run: func(cmd *cobra.Command, args []string) {
f, err := os.Create("montage.png")
defer f.Close()
onError(err)
client, err := api.DefaultRESTClient()
onError(err)
repository, err := repository.Current()
onError(err)
source := usersource.NewContributors(client, repository.Owner, repository.Name)
avatars := []image.Image{}
for {
user, stop, err := source.Next()
onError(err)
if stop {
break
}
if user.Type != "User" {
continue
}
a, err := avatar.Decode(user.AvatarURL)
onError(err)
avatars = append(avatars, a)
}
g := grid.NewWithSize(len(avatars), margin)
for _, a := range avatars {
g.AddAvatar(a)
}

m := g.Image()
err = png.Encode(f, m)
onError(err)
},
}

var margin int

func init() {
rootCmd.PersistentFlags().IntVarP(&margin, "margin", "m", 100, "Margin between avatars")
}
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@ module github.com/spenserblack/gh-collab-montage

go 1.18

require github.com/cli/go-gh/v2 v2.4.0
require (
github.com/cli/go-gh/v2 v2.4.0
github.com/spf13/cobra v1.8.0
)

require (
github.com/aymanbagabas/go-osc52 v1.0.3 // indirect
github.com/cli/safeexec v1.0.0 // indirect
github.com/cli/shurcooL-graphql v0.0.4 // indirect
github.com/henvic/httpretty v0.0.6 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/muesli/termenv v0.13.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY=
github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/henvic/httpretty v0.0.6 h1:JdzGzKZBajBfnvlMALXXMVQWxWMF/ofTy8C3/OSUTxs=
github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
Expand All @@ -28,6 +31,11 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e h1:BuzhfgfWQbX0dWzYzT1zsORLnHRv3bcRcsaUk0VmXA8=
github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI=
Expand Down
51 changes: 2 additions & 49 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,54 +1,7 @@
package main

import (
"fmt"
"image"
"image/png"
"os"

"github.com/cli/go-gh/v2/pkg/api"
"github.com/cli/go-gh/v2/pkg/repository"
"github.com/spenserblack/gh-collab-montage/pkg/avatar"
"github.com/spenserblack/gh-collab-montage/pkg/avatar/grid"
"github.com/spenserblack/gh-collab-montage/pkg/usersource"
)
import "github.com/spenserblack/gh-collab-montage/cmd"

func main() {
f, err := os.Create("montage.png")
defer f.Close()
onError(err)
client, err := api.DefaultRESTClient()
onError(err)
repository, err := repository.Current()
onError(err)
source := usersource.NewContributors(client, repository.Owner, repository.Name)
avatars := []image.Image{}
for {
user, stop, err := source.Next()
onError(err)
if stop {
break
}
if user.Type != "User" {
continue
}
a, err := avatar.Decode(user.AvatarURL)
onError(err)
avatars = append(avatars, a)
}
g := grid.NewWithSize(len(avatars))
for _, a := range avatars {
g.AddAvatar(a)
}

m := g.Image()
err = png.Encode(f, m)
onError(err)
}

func onError(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
cmd.Execute()
}
7 changes: 4 additions & 3 deletions pkg/avatar/grid/draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package grid
import (
"image"
"image/draw"

av "github.com/spenserblack/gh-collab-montage/pkg/avatar"
)

Expand All @@ -17,11 +18,11 @@ func (g *AvatarGrid) AddAvatar(avatar image.Image) {
} else if g.col == 0 && g.Rows() <= g.row {
g.setBounds(g.Rows()+1, g.Cols())
}
x := g.col * av.Width
y := g.row * av.Height
x := (g.col * av.Width) + (g.col * g.margin)
y := (g.row * av.Height) + (g.row * g.margin)
draw.Draw(
g.image,
image.Rect(x, y, x + av.Width, y+av.Height),
image.Rect(x, y, x+av.Width, y+av.Height),
avatar,
image.Point{},
draw.Src,
Expand Down
44 changes: 32 additions & 12 deletions pkg/avatar/grid/grid.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,25 @@ const perRow = 10
// It expands and adds new rows when needed.
type AvatarGrid struct {
image draw.Image
row int
col int
// Row is the current row (0-indexed).
row int
// Col is the current column (0-indexed).
col int
// Margin is the number of pixels between avatars.
margin int
// Cols is the number of columns in the grid.
cols int
// Rows is the number of rows in the grid.
rows int
}

// New returns a new AvatarGrid.
func New() *AvatarGrid {
return NewWithSize(0)
func New(margin int) *AvatarGrid {
return NewWithSize(0, margin)
}

// NewWithSize returns a new AvatarGrid with the given size.
func NewWithSize(avatars int) *AvatarGrid {
func NewWithSize(avatars int, margin int) *AvatarGrid {
var cols, rows int
if avatars == 0 {
rows = 1
Expand All @@ -43,9 +51,13 @@ func NewWithSize(avatars int) *AvatarGrid {
} else {
cols = avatars
}
return &AvatarGrid{
image: image.NewRGBA(image.Rect(0, 0, cols*avatar.Width, rows*avatar.Height)),
g := &AvatarGrid{
margin: margin,
cols: cols,
rows: rows,
}
g.image = g.newDst()
return g
}

// Image returns the image of the grid.
Expand All @@ -55,18 +67,26 @@ func (g AvatarGrid) Image() image.Image {

// Cols returns the number of columns in the grid.
func (g AvatarGrid) Cols() int {
return g.image.Bounds().Dx() / avatar.Width
return g.cols
}

// Rows returns the number of rows in the grid.
func (g AvatarGrid) Rows() int {
return g.image.Bounds().Dy() / avatar.Height
return g.rows
}

// SetBounds changes the bounds of the underlying image.
func (g *AvatarGrid) setBounds(rows, cols int) {
b := image.Rect(0, 0, cols*avatar.Width, rows*avatar.Height)
newImage := image.NewRGBA(b)
draw.Draw(newImage, b, g.image, image.Point{}, draw.Src)
g.cols = cols
g.rows = rows
newImage := g.newDst()
draw.Draw(newImage, newImage.Bounds(), g.image, image.Point{}, draw.Src)
g.image = newImage
}

// NewDst creates a new destination image based on the grid's dimensions.
func (g AvatarGrid) newDst() draw.Image {
width := g.cols*avatar.Width + (g.cols-1)*g.margin
height := g.rows*avatar.Height + (g.rows-1)*g.margin
return image.NewRGBA(image.Rect(0, 0, width, height))
}
6 changes: 3 additions & 3 deletions pkg/avatar/grid/grid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func TestNew(t *testing.T) {
g := New()
g := New(100)
if g.Cols() != 0 {
t.Errorf("g.Cols() = %d, want 0", g.Cols())
}
Expand Down Expand Up @@ -57,7 +57,7 @@ func TestNewWithSize(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithSize(tt.avatars)
g := NewWithSize(tt.avatars, 100)
if g.Cols() != tt.cols {
t.Errorf("g.Cols() = %d, want %d", g.Cols(), tt.cols)
}
Expand Down Expand Up @@ -172,7 +172,7 @@ func TestGrid_AddAvatar(t *testing.T) {

for _, tt := range tests {
t.Run(fmt.Sprintf("%d avatars added to %d-avatar grid", tt.n, tt.size), func(t *testing.T) {
g := NewWithSize(tt.size)
g := NewWithSize(tt.size, 100)
for i := 0; i < tt.n; i++ {
g.AddAvatar(avatar)
}
Expand Down