Skip to content

Commit 015dd6c

Browse files
authored
enhancement: implement touch icons (#677)
1 parent d79f2b5 commit 015dd6c

27 files changed

+180
-51
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ RUN apk update && apk add ca-certificates
44
RUN mkdir /app
55
WORKDIR /app
66

7-
COPY favicon.ico /app
7+
COPY favicon.png /app
88
COPY migrations /app/migrations
99
COPY views /app/views
1010
COPY dist /app/dist

app/cmd/routes.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func routes(r *web.Engine) *web.Engine {
3131
{
3232
assets.Use(middlewares.CORS())
3333
assets.Use(middlewares.ClientCache(365 * 24 * time.Hour))
34-
assets.Static("/favicon.ico", "favicon.ico")
34+
assets.Get("/favicon/:size", handlers.Favicon())
3535
assets.Static("/assets/*filepath", "dist")
3636
}
3737

@@ -66,6 +66,7 @@ func routes(r *web.Engine) *web.Engine {
6666
tenantAssets.Get("/avatars/:size/:id/:name", handlers.Avatar())
6767

6868
tenantAssets.Use(middlewares.ClientCache(30 * 24 * time.Hour))
69+
tenantAssets.Get("/images/:size/:id/favicon", handlers.Favicon())
6970
tenantAssets.Get("/images/:size/:id", handlers.ViewUploadedImage())
7071
tenantAssets.Get("/custom/:md5.css", func(c web.Context) error {
7172
return c.Blob(http.StatusOK, "text/css", []byte(c.Tenant().CustomCSS))

app/handlers/images.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ package handlers
33
import (
44
"bytes"
55
"fmt"
6+
"image/color"
67
"image/png"
78
"io/ioutil"
89
"net/http"
910
"strings"
1011

12+
"github.com/getfider/fider/app/pkg/env"
13+
1114
"github.com/getfider/fider/app/pkg/crypto"
1215
"github.com/getfider/fider/app/pkg/img"
1316
"github.com/getfider/fider/app/pkg/log"
@@ -63,6 +66,51 @@ func Avatar() web.HandlerFunc {
6366
}
6467
}
6568

69+
//Favicon returns the Fider favicon by given size
70+
func Favicon() web.HandlerFunc {
71+
return func(c web.Context) error {
72+
var (
73+
bytes []byte
74+
contentType string
75+
)
76+
77+
id, err := c.ParamAsInt("id")
78+
if err == nil {
79+
logo, err := c.Services().Tenants.GetUpload(id)
80+
if err != nil {
81+
return c.Failure(err)
82+
}
83+
bytes = logo.Content
84+
contentType = logo.ContentType
85+
} else {
86+
bytes, err = ioutil.ReadFile(env.Path("favicon.png"))
87+
contentType = "image/png"
88+
if err != nil {
89+
return c.Failure(err)
90+
}
91+
}
92+
93+
size, err := c.ParamAsInt("size")
94+
if err != nil {
95+
return c.NotFound()
96+
}
97+
98+
bytes, err = img.Resize(bytes, size, 5)
99+
if err != nil {
100+
return c.Failure(err)
101+
}
102+
103+
if c.QueryParam("bg") != "" {
104+
bytes, err = img.ChangeBackground(bytes, color.White)
105+
if err != nil {
106+
return c.Failure(err)
107+
}
108+
}
109+
110+
return c.Blob(http.StatusOK, contentType, bytes)
111+
}
112+
}
113+
66114
//ViewUploadedImage returns any uploaded image by given ID and size
67115
func ViewUploadedImage() web.HandlerFunc {
68116
return func(c web.Context) error {
@@ -81,7 +129,7 @@ func ViewUploadedImage() web.HandlerFunc {
81129
return c.Failure(err)
82130
}
83131

84-
bytes, err := img.Resize(logo.Content, size)
132+
bytes, err := img.Resize(logo.Content, size, 0)
85133
if err != nil {
86134
return c.Failure(err)
87135
}

app/pkg/dbx/dbx_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ func TestByteArray(t *testing.T) {
291291
Content []byte `db:"file"`
292292
}
293293

294-
fileContent, err := ioutil.ReadFile(env.Path("/favicon.ico"))
294+
fileContent, err := ioutil.ReadFile(env.Path("/favicon.png"))
295295
Expect(err).IsNil()
296296

297297
_, err = trx.Execute(`

app/pkg/img/img.go

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"image"
66

7+
"image/color"
78
"image/gif"
89
"image/jpeg"
910
"image/png"
@@ -40,29 +41,64 @@ func Parse(file []byte) (*File, error) {
4041
}, nil
4142
}
4243

44+
//ChangeBackground will change given image transparent background to given color
45+
func ChangeBackground(file []byte, bgColor color.Color) ([]byte, error) {
46+
src, format, err := decode(file)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
if format != "png" {
52+
return file, nil
53+
}
54+
55+
dst := image.NewRGBA(src.Bounds())
56+
draw.Draw(dst, dst.Bounds(), &image.Uniform{bgColor}, image.Point{}, draw.Src)
57+
draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Over)
58+
return encode(dst, format)
59+
}
60+
4361
//Resize image based on given size
44-
func Resize(file []byte, size int) ([]byte, error) {
45-
src, format, err := image.Decode(bytes.NewReader(file))
62+
func Resize(file []byte, size int, padding int) ([]byte, error) {
63+
src, format, err := decode(file)
4664
if err != nil {
47-
return nil, errors.Wrap(err, "failed to decode image")
65+
return nil, err
4866
}
4967

68+
//TODO: Very slow to resize images with aspect ratio different than 1:1
69+
5070
srcBounds := src.Bounds()
5171
srcW, srcH := srcBounds.Dx(), srcBounds.Dy()
5272
if (srcW <= size && srcH <= size) || srcW != srcH {
53-
return file, nil
73+
size = srcW
5474
}
5575

76+
padding = size * padding / 100
5677
dst := image.NewRGBA(image.Rect(0, 0, size, size))
57-
draw.CatmullRom.Scale(dst, dst.Bounds(), src, srcBounds, draw.Src, nil)
78+
dstBounds := image.Rect(padding, padding, size-padding, size-padding)
79+
srcBounds = image.Rect(0, 0, srcBounds.Max.X, srcBounds.Max.Y)
80+
draw.CatmullRom.Scale(dst, dstBounds, src, srcBounds, draw.Src, nil)
81+
82+
return encode(dst, format)
83+
}
84+
85+
func decode(file []byte) (image.Image, string, error) {
86+
src, format, err := image.Decode(bytes.NewReader(file))
87+
if err != nil {
88+
return nil, "", errors.Wrap(err, "failed to decode image")
89+
}
90+
return src, format, err
91+
}
5892

93+
func encode(img image.Image, format string) ([]byte, error) {
94+
var err error
5995
writer := new(bytes.Buffer)
6096
if format == "png" {
61-
err = png.Encode(writer, dst)
97+
err = png.Encode(writer, img)
6298
} else if format == "jpeg" {
63-
err = jpeg.Encode(writer, dst, nil)
99+
err = jpeg.Encode(writer, img, nil)
64100
} else if format == "gif" {
65-
err = gif.Encode(writer, dst, nil)
101+
err = gif.Encode(writer, img, nil)
66102
}
67103

68104
if err != nil {

app/pkg/img/img_test.go

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package img_test
22

33
import (
4+
"image/color"
45
"io/ioutil"
56
"testing"
67

@@ -22,7 +23,7 @@ var parseTestCases = []struct {
2223
{"/app/pkg/img/testdata/logo5.png", 200, 200, true},
2324
{"/app/pkg/img/testdata/logo6.jpg", 400, 400, true},
2425
{"/app/pkg/img/testdata/logo7.gif", 400, 400, true},
25-
{"/favicon.ico", 0, 0, false},
26+
{"/app/pkg/img/testdata/favicon.ico", 0, 0, false},
2627
}
2728

2829
func TestImageParse(t *testing.T) {
@@ -49,15 +50,17 @@ var resizeTestCases = []struct {
4950
fileName string
5051
resizedFileName string
5152
size int
53+
padding int
5254
}{
53-
{"/app/pkg/img/testdata/logo1.png", "/app/pkg/img/testdata/logo1-200x200.png", 200},
54-
{"/app/pkg/img/testdata/logo2.jpg", "/app/pkg/img/testdata/logo2.jpg", 200},
55-
{"/app/pkg/img/testdata/logo3.gif", "/app/pkg/img/testdata/logo3.gif", 200},
56-
{"/app/pkg/img/testdata/logo4.png", "/app/pkg/img/testdata/logo4-100x100.png", 100},
57-
{"/app/pkg/img/testdata/logo5.png", "/app/pkg/img/testdata/logo5.png", 200},
58-
{"/app/pkg/img/testdata/logo6.jpg", "/app/pkg/img/testdata/logo6-200x200.jpg", 200},
59-
{"/app/pkg/img/testdata/logo7.gif", "/app/pkg/img/testdata/logo7-200x200.gif", 200},
60-
{"/app/pkg/img/testdata/logo7.gif", "/app/pkg/img/testdata/logo7.gif", 1000},
55+
{"/app/pkg/img/testdata/logo1.png", "/app/pkg/img/testdata/logo1-200x200.png", 200, 0},
56+
//TODO: Very slow to resize images with aspect ratio different than 1:1
57+
// {"/app/pkg/img/testdata/logo2.jpg", "/app/pkg/img/testdata/logo2-200x200.jpg", 200, 0},
58+
// {"/app/pkg/img/testdata/logo3.gif", "/app/pkg/img/testdata/logo3-200x200.gif", 200, 0},
59+
{"/app/pkg/img/testdata/logo4.png", "/app/pkg/img/testdata/logo4-100x100.png", 100, 0},
60+
{"/app/pkg/img/testdata/logo5.png", "/app/pkg/img/testdata/logo5-200x200.png", 200, 0},
61+
{"/app/pkg/img/testdata/logo6.jpg", "/app/pkg/img/testdata/logo6-200x200.jpg", 200, 0},
62+
{"/app/pkg/img/testdata/logo7.gif", "/app/pkg/img/testdata/logo7-200x200.gif", 200, 0},
63+
{"/app/pkg/img/testdata/logo7.gif", "/app/pkg/img/testdata/logo7-1000-1000.gif", 1000, 0},
6164
}
6265

6366
func TestImageResize(t *testing.T) {
@@ -67,7 +70,7 @@ func TestImageResize(t *testing.T) {
6770
bytes, err := ioutil.ReadFile(env.Path(testCase.fileName))
6871
Expect(err).IsNil()
6972

70-
resized, err := img.Resize(bytes, testCase.size)
73+
resized, err := img.Resize(bytes, testCase.size, testCase.padding)
7174
Expect(err).IsNil()
7275

7376
expected, err := ioutil.ReadFile(env.Path(testCase.resizedFileName))
@@ -76,3 +79,29 @@ func TestImageResize(t *testing.T) {
7679
Expect(resized).Equals(expected)
7780
}
7881
}
82+
83+
var bgColorTestCases = []struct {
84+
fileName string
85+
whiteColorFileName string
86+
bgColor color.Color
87+
}{
88+
{"/app/pkg/img/testdata/logo1.png", "/app/pkg/img/testdata/logo1-white.png", color.White},
89+
{"/app/pkg/img/testdata/logo1.png", "/app/pkg/img/testdata/logo1-black.png", color.Black},
90+
}
91+
92+
func TestImageChangeBackground(t *testing.T) {
93+
RegisterT(t)
94+
95+
for _, testCase := range bgColorTestCases {
96+
bytes, err := ioutil.ReadFile(env.Path(testCase.fileName))
97+
Expect(err).IsNil()
98+
99+
withColor, err := img.ChangeBackground(bytes, testCase.bgColor)
100+
Expect(err).IsNil()
101+
102+
expected, err := ioutil.ReadFile(env.Path(testCase.whiteColorFileName))
103+
Expect(err).IsNil()
104+
105+
Expect(withColor).Equals(expected)
106+
}
107+
}
File renamed without changes.

app/pkg/img/testdata/logo1-black.png

16.3 KB
Loading

app/pkg/img/testdata/logo1-white.png

16.3 KB
Loading
221 KB
Loading

0 commit comments

Comments
 (0)