Skip to content

Commit 1b4514e

Browse files
authored
Encode and Decode using the libwebp library via WASM with animation support
Fixes #10030 Fixes #8500 Fixes #12843 Fixes #8879 Fixes #12842
1 parent 429e572 commit 1b4514e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+4460
-349
lines changed

common/himage/image.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2025 The Hugo Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
// Package himage provides some high level image types and interfaces.
15+
package himage
16+
17+
import "image"
18+
19+
// AnimatedImage represents an animated image.
20+
// This is currently supported for GIF and WebP images.
21+
type AnimatedImage interface {
22+
image.Image // The first frame.
23+
GetRaw() any // *gif.GIF or *WEBP.
24+
GetLoopCount() int // Number of times to loop the animation. 0 means infinite.
25+
ImageFrames
26+
}
27+
28+
// ImageFrames provides access to the frames of an animated image.
29+
type ImageFrames interface {
30+
GetFrames() []image.Image
31+
32+
// Frame durations in milliseconds.
33+
// Note that Gif frame durations are in 100ths of a second,
34+
// so they need to be multiplied by 10 to get milliseconds and vice versa.
35+
GetFrameDurations() []int
36+
37+
SetFrames(frames []image.Image)
38+
SetWidthHeight(width, height int)
39+
}
40+
41+
// ImageConfigProvider provides access to the image.Config of an image.
42+
type ImageConfigProvider interface {
43+
GetImageConfig() image.Config
44+
}
45+
46+
// FrameDurationsToGifDelays converts frame durations in milliseconds to
47+
// GIF delays in 100ths of a second.
48+
func FrameDurationsToGifDelays(frameDurations []int) []int {
49+
delays := make([]int, len(frameDurations))
50+
for i, fd := range frameDurations {
51+
delays[i] = fd / 10
52+
if delays[i] == 0 && fd > 0 {
53+
delays[i] = 1
54+
}
55+
}
56+
return delays
57+
}
58+
59+
// GifDelaysToFrameDurations converts GIF delays in 100ths of a second to
60+
// frame durations in milliseconds.
61+
func GifDelaysToFrameDurations(delays []int) []int {
62+
frameDurations := make([]int, len(delays))
63+
for i, d := range delays {
64+
frameDurations[i] = d * 10
65+
}
66+
return frameDurations
67+
}

common/hugio/readers.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,30 @@ type ReadSeekCloser interface {
3232
io.Closer
3333
}
3434

35+
// Sizer provides the size of, typically, a io.Reader.
36+
// As implemented by e.g. os.File and io.SectionReader.
37+
type Sizer interface {
38+
Size() int64
39+
}
40+
41+
type SizeReader interface {
42+
io.Reader
43+
Sizer
44+
}
45+
46+
// ToSizeReader converts the given io.Reader to a SizeReader.
47+
// Note that if r is not a SizeReader, the entire content will be read into memory
48+
func ToSizeReader(r io.Reader) (SizeReader, error) {
49+
if sr, ok := r.(SizeReader); ok {
50+
return sr, nil
51+
}
52+
b, err := io.ReadAll(r)
53+
if err != nil {
54+
return nil, err
55+
}
56+
return bytes.NewReader(b), nil
57+
}
58+
3559
// CloserFunc is an adapter to allow the use of ordinary functions as io.Closers.
3660
type CloserFunc func() error
3761

common/hugo/hugo.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,13 +310,12 @@ func GetDependencyList() []string {
310310

311311
// GetDependencyListNonGo returns a list of non-Go dependencies.
312312
func GetDependencyListNonGo() []string {
313-
var deps []string
313+
deps := []string{formatDep("github.com/webmproject/libwebp", "v1.6.0")} // via WASM. TODO(bep) get versions from the plugin setup.
314314

315315
if IsExtended {
316316
deps = append(
317317
deps,
318318
formatDep("github.com/sass/libsass", "3.6.6"),
319-
formatDep("github.com/webmproject/libwebp", "v1.3.2"),
320319
)
321320
}
322321

common/maps/map.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ func (m *Map[K, T]) Set(key K, value T) {
7474
}
7575

7676
// WithWriteLock executes the given function with a write lock on the map.
77-
func (m *Map[K, T]) WithWriteLock(f func(m map[K]T)) {
77+
func (m *Map[K, T]) WithWriteLock(f func(m map[K]T) error) error {
7878
m.mu.Lock()
7979
defer m.mu.Unlock()
80-
f(m.m)
80+
return f(m.m)
8181
}
8282

8383
// SetIfAbsent sets the given key to the given value if the key does not already exist in the map.

common/maps/map_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,9 @@ func TestMap(t *testing.T) {
6262
c.Assert(found, qt.Equals, true)
6363
c.Assert(v, qt.Equals, 300)
6464

65-
m.WithWriteLock(func(m map[string]int) {
65+
m.WithWriteLock(func(m map[string]int) error {
6666
m["f"] = 500
67+
return nil
6768
})
6869
v, found = m.Lookup("f")
6970
c.Assert(found, qt.Equals, true)

config/testconfig/testconfig.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/gohugoio/hugo/config/allconfig"
2323
"github.com/gohugoio/hugo/deps"
2424
"github.com/gohugoio/hugo/hugofs"
25+
"github.com/gohugoio/hugo/internal/warpc"
2526
toml "github.com/pelletier/go-toml/v2"
2627
"github.com/spf13/afero"
2728
)
@@ -60,6 +61,14 @@ func GetTestDeps(fs afero.Fs, cfg config.Provider, beforeInit ...func(*deps.Deps
6061
d := &deps.Deps{
6162
Conf: conf,
6263
Fs: hugofs.NewFrom(fs, conf.BaseConfig()),
64+
WasmDispatchers: warpc.AllDispatchers(
65+
warpc.Options{
66+
PoolSize: 1,
67+
},
68+
warpc.Options{
69+
PoolSize: 1,
70+
},
71+
),
6372
}
6473
for _, f := range beforeInit {
6574
f(d)

deps/deps.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
// Copyright 2025 The Hugo Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
114
package deps
215

316
import (
@@ -252,7 +265,7 @@ func (d *Deps) Init() error {
252265
return fmt.Errorf("failed to create file caches from configuration: %w", err)
253266
}
254267

255-
resourceSpec, err := resources.NewSpec(d.PathSpec, common, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper, d.BuildClosers, d.BuildState)
268+
resourceSpec, err := resources.NewSpec(d.PathSpec, common, d.WasmDispatchers, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper, d.BuildClosers, d.BuildState)
256269
if err != nil {
257270
return fmt.Errorf("failed to create resource spec: %w", err)
258271
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ require (
1313
github.com/bep/godartsass/v2 v2.5.0
1414
github.com/bep/golibsass v1.2.0
1515
github.com/bep/goportabletext v0.1.0
16-
github.com/bep/gowebp v0.3.0
1716
github.com/bep/helpers v0.6.0
1817
github.com/bep/imagemeta v0.12.0
1918
github.com/bep/lazycache v0.8.0
2019
github.com/bep/logg v0.4.0
2120
github.com/bep/mclib v1.20400.20402
2221
github.com/bep/overlayfs v0.10.0
2322
github.com/bep/simplecobra v0.6.1
23+
github.com/bep/textandbinarywriter v0.0.0-20251212174530-cd9f0732f60f
2424
github.com/bep/tmc v0.5.1
2525
github.com/bits-and-blooms/bitset v1.24.4
2626
github.com/cespare/xxhash/v2 v2.3.0

go.sum

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,6 @@ github.com/bep/golibsass v1.2.0 h1:nyZUkKP/0psr8nT6GR2cnmt99xS93Ji82ZD9AgOK6VI=
158158
github.com/bep/golibsass v1.2.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
159159
github.com/bep/goportabletext v0.1.0 h1:8dqym2So1cEqVZiBa4ZnMM1R9l/DnC1h4ONg4J5kujw=
160160
github.com/bep/goportabletext v0.1.0/go.mod h1:6lzSTsSue75bbcyvVc0zqd1CdApuT+xkZQ6Re5DzZFg=
161-
github.com/bep/gowebp v0.3.0 h1:MhmMrcf88pUY7/PsEhMgEP0T6fDUnRTMpN8OclDrbrY=
162-
github.com/bep/gowebp v0.3.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
163161
github.com/bep/helpers v0.6.0 h1:qtqMCK8XPFNM9hp5Ztu9piPjxNNkk8PIyUVjg6v8Bsw=
164162
github.com/bep/helpers v0.6.0/go.mod h1:IOZlgx5PM/R/2wgyCatfsgg5qQ6rNZJNDpWGXqDR044=
165163
github.com/bep/imagemeta v0.12.0 h1:ARf+igs5B7pf079LrqRnwzQ/wEB8Q9v4NSDRZO1/F5k=
@@ -174,6 +172,8 @@ github.com/bep/overlayfs v0.10.0 h1:wS3eQ6bRsLX+4AAmwGjvoFSAQoeheamxofFiJ2SthSE=
174172
github.com/bep/overlayfs v0.10.0/go.mod h1:ouu4nu6fFJaL0sPzNICzxYsBeWwrjiTdFZdK4lI3tro=
175173
github.com/bep/simplecobra v0.6.1 h1:ORBAC5CSar99/NPZ5fCthCx/uvlm7ry58wwDsZ23a20=
176174
github.com/bep/simplecobra v0.6.1/go.mod h1:hmtjyHv6xwD637ScIRP++0NKkR5szrHuMw5BxMUH66s=
175+
github.com/bep/textandbinarywriter v0.0.0-20251212174530-cd9f0732f60f h1:NzhMpf5eis+w8bTbT1jqVz+gcMEBhcIPA/KRbYvX8+Y=
176+
github.com/bep/textandbinarywriter v0.0.0-20251212174530-cd9f0732f60f/go.mod h1:vTWM9sqhanOWdo2B2NHwDQPuPmD/nCdMKDFPYxd4VKU=
177177
github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=
178178
github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
179179
github.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg=
@@ -584,7 +584,6 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
584584
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
585585
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
586586
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
587-
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
588587
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
589588
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
590589
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

hugolib/content_map_page_assembler.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,9 @@ func (a *allPagesAssembler) doCreatePages(prefix string, depth int) error {
282282
default:
283283
// Skip this page.
284284
a.droppedPages.WithWriteLock(
285-
func(m map[*Site][]string) {
285+
func(m map[*Site][]string) error {
286286
m[site] = append(m[site], s)
287+
return nil
287288
},
288289
)
289290

@@ -635,15 +636,16 @@ func (a *allPagesAssembler) doCreatePages(prefix string, depth int) error {
635636
continue
636637
}
637638
t := term{view: viewName, term: v}
638-
a.seenTerms.WithWriteLock(func(m map[term]sitesmatrix.Vectors) {
639+
a.seenTerms.WithWriteLock(func(m map[term]sitesmatrix.Vectors) error {
639640
vectors, found := m[t]
640641
if !found {
641642
m[t] = sitesmatrix.Vectors{
642643
vec: struct{}{},
643644
}
644-
return
645+
return nil
645646
}
646647
vectors[vec] = struct{}{}
648+
return nil
647649
})
648650
}
649651
}

0 commit comments

Comments
 (0)