Skip to content

Commit

Permalink
feat: add background, color, font sizes
Browse files Browse the repository at this point in the history
use gg to ease the rendering and calculations
  • Loading branch information
barelyhuman committed Sep 4, 2021
1 parent 181482b commit e2ad079
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 177 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module github.com/barelyhuman/og-image
go 1.16

require (
github.com/disintegration/imaging v1.6.2 // indirect
github.com/fogleman/gg v1.3.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
217 changes: 43 additions & 174 deletions lib/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ package lib
import (
"embed"
"image"
"image/color"
"image/draw"
"image/png"
"io"
"log"
"net/http"

"github.com/golang/freetype"
"github.com/disintegration/imaging"
"github.com/fogleman/gg"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)

type Point struct {
Expand All @@ -33,66 +32,47 @@ const dev = false
var embedFS embed.FS
var fontFile = "fonts/Inter-Regular.ttf"

func (ogImage *OGImage) drawText(text string, img *image.RGBA, fontFace font.Face, point fixed.Point26_6, startPoint int) {
func DrawImage(title string, subTitle string, fontSize int, subFontSize int, color string, backgroundImageURL string) image.Image {
titleFontFace := loadFont(fontSize)
subtitleFontFace := loadFont(subFontSize)

fittingText := getTruncatedText(fontFace, text, startPoint, ogImage.MaxEndPtX)
const height = 627
const width = 1200

if len(fittingText) < len(text) {
fittingText = fittingText[0:len(fittingText)-3] + "..."
}

fontDrawer := &font.Drawer{
Dst: img,
Src: image.NewUniform(color.Black),
Face: fontFace,
Dot: point,
}

fontDrawer.DrawString(fittingText)
}
const centerV = height / 2
const centerH = width / 2

func (ogImage *OGImage) drawTitle(text string, img *image.RGBA, fontSize int) {
fontBytes, err := embedFS.ReadFile(fontFile)
const offset = 2

if err != nil {
log.Fatal("failed to find font")
}
dc := gg.NewContext(width, height)

ttf, err := truetype.Parse(fontBytes)
if err != nil {
log.Fatal("failed to parse font")
if len(backgroundImageURL) > 0 {
img := loadImageFromURL(backgroundImageURL)
backgroundImage := imaging.Fill(img, dc.Width(), dc.Height(), imaging.Center, imaging.NearestNeighbor)
dc.DrawImage(backgroundImage, 0, 0)
} else {
dc.DrawRectangle(0, 0, width, height)
dc.SetHexColor("#fff")
dc.Fill()
dc.Clear()
}
fontFace := truetype.NewFace(ttf, &truetype.Options{
Size: float64(fontSize),
DPI: 300,
Hinting: font.HintingNone,
})

xToNegate, err := findStartPointX(text, fontFace)

if err != nil {
log.Fatal("couldn't find start point")
}

middleLine := Point{
x: ogImage.Width / 2,
y: ogImage.Height / 2,
}
dc.SetFontFace(titleFontFace)
dc.SetHexColor(color)
_, titleMHeight := dc.MeasureString(title)
dc.DrawStringAnchored(title, centerH, centerV, 0.5, 0.5)
dc.SetFontFace(subtitleFontFace)
dc.DrawStringAnchored(subTitle, centerH, centerV+titleMHeight+20, 0.5, 0.5)
return dc.Image()
}

dotStart := (middleLine.x - xToNegate)
if dotStart < ogImage.MaxStartPtX {
dotStart = ogImage.MaxStartPtX
func WriteImage(w io.Writer, img image.Image) error {
if err := png.Encode(w, img); err != nil {
return err
}

titlePositionY := int(ogImage.Height - int(float32(ogImage.Height)/offset))
titlePoint := freetype.Pt(dotStart, titlePositionY)
ogImage.drawText(text, img, fontFace, titlePoint, dotStart)
return nil
}

func (ogImage *OGImage) drawSubTitle(text string, img *image.RGBA, fontSize int) {
func loadFont(fontSize int) font.Face {
fontBytes, err := embedFS.ReadFile(fontFile)
const offset = 2.5

if err != nil {
log.Fatal("failed to find font")
Expand All @@ -107,129 +87,18 @@ func (ogImage *OGImage) drawSubTitle(text string, img *image.RGBA, fontSize int)
DPI: 300,
Hinting: font.HintingNone,
})

xToNegate, err := findStartPointX(text, fontFace)

if err != nil {
log.Fatal("couldn't find start point")
}

middleLine := Point{
x: ogImage.Width / 2,
y: ogImage.Height / 2,
}

dotStart := (middleLine.x - xToNegate)
if dotStart < ogImage.MaxStartPtX {
dotStart = ogImage.MaxStartPtX
}

titlePositionY := int(ogImage.Height - int(float32(ogImage.Height)/offset))
titlePoint := freetype.Pt(dotStart, titlePositionY)
ogImage.drawText(text, img, fontFace, titlePoint, dotStart)
}

func (ogImage *OGImage) drawCircle(img draw.Image, x0, y0, r float64, c color.Color) {
x, y, dx, dy := float64(r-1), float64(0), float64(1), float64(1)
err := dx - (r * float64(2))

for x > y {
img.Set(int(x0+x), int(y0+y), c)
img.Set(int(x0+y), int(y0+x), c)
img.Set(int(x0-y), int(y0+x), c)
img.Set(int(x0-x), int(y0+y), c)
img.Set(int(x0-x), int(y0-y), c)
img.Set(int(x0-y), int(y0-x), c)
img.Set(int(x0+y), int(y0-x), c)
img.Set(int(x0+x), int(y0-y), c)

if err <= 0 {
y++
err += dy
dy += 2
}
if err > 0 {
x--
dx += 2
err += dx - (r * 2)
}
}
}

func (ogImage *OGImage) drawBoundaries(img *image.RGBA) {

xMid := ogImage.Width / 2
yMid := ogImage.Height / 2

for y := 0; y <= ogImage.Height; y++ {
img.Set(xMid, y, color.Black)
}

for x := 0; x <= ogImage.Width; x++ {
img.Set(x, yMid, color.Black)
}

return fontFace
}

// DrawImage - draw the og image and return a image.Image to be used with
// an io.Writer to write it to the needed destination
func DrawImage(title string, subtitle string, fontSize int) image.Image {
ogImage := OGImage{}

ogImage.Width = 1200
ogImage.Height = 627

ogImage.MaxStartPtX = ogImage.Width / 8
ogImage.MaxEndPtX = ogImage.Width - ogImage.Width/8

imgRGBA := image.NewRGBA(image.Rect(0, 0, ogImage.Width, ogImage.Height))
white := color.RGBA{255, 255, 255, 255}
var img image.Image = imgRGBA

draw.Draw(imgRGBA, imgRGBA.Bounds(), &image.Uniform{white}, image.Point{X: 0, Y: 0}, draw.Src)

for y := 0; y <= ogImage.Height; y++ {
for x := 0; x <= ogImage.Width; x++ {
if x%40 == 0 && y%40 == 0 {
ogImage.drawCircle(imgRGBA, float64(x), float64(y), 1.1, color.RGBA{153, 153, 153, 255})
}
}
func loadImageFromURL(url string) image.Image {
res, err := http.Get(url)
if err != nil || res.StatusCode != 200 {
// TODO: handle errors
}

if dev {
ogImage.drawBoundaries(imgRGBA)
}

ogImage.drawTitle(title, imgRGBA, fontSize)
ogImage.drawSubTitle(subtitle, imgRGBA, int(float64(fontSize)/2))

return img
}

// WriteImage - a simple writer that encodes the generated img into a png
// you can use other writers for advanced use cases
func WriteImage(w io.Writer, img *image.Image) error {
if err := png.Encode(w, *img); err != nil {
return err
}
return nil
}

func findStartPointX(text string, face font.Face) (int, error) {

if len(text) <= 0 {
return 0, nil
}

aWidth := font.MeasureString(face, text)
return int(float64(aWidth.Round()) * float64(0.5)), nil
}

func getTruncatedText(face font.Face, text string, startPoint int, maxPoint int) string {
dotAdvanceEnd := font.MeasureString(face, text).Round()

if startPoint+dotAdvanceEnd <= maxPoint {
return text
defer res.Body.Close()
imageRef, _, err := image.Decode(res.Body)
if err != nil {
//TODO: handle error
}
return getTruncatedText(face, text[0:len(text)-1], startPoint, maxPoint)
return imageRef
}
9 changes: 6 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import (
func main() {
title := flag.String("title", "", "Title")
subTitle := flag.String("desc", "", "Description or Sub Title")
fontSize := flag.Int("size", 45, "Font Size")
fontSize := flag.Int("size-one", 16, "Font Size for title")
subFontSize := flag.Int("size-two", 12, "Font Size For description")
color := flag.String("color", "#000", "Font Color")
backgroundImageURL := flag.String("background-url", "", "URL for the background")
outFile := flag.String("out", "./og-image.png", "File to output to, will export a png")

flag.Parse()
Expand All @@ -20,6 +23,6 @@ func main() {
if err != nil {
log.Fatal(err)
}
img := lib.DrawImage(*title, *subTitle, *fontSize)
lib.WriteImage(file, &img)
img := lib.DrawImage(*title, *subTitle, *fontSize, *subFontSize, *color, *backgroundImageURL)
lib.WriteImage(file, img)
}

0 comments on commit e2ad079

Please sign in to comment.