Skip to content

Commit d59436c

Browse files
committed
Decode
1 parent d21a9e4 commit d59436c

File tree

8 files changed

+126
-54
lines changed

8 files changed

+126
-54
lines changed

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.}
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

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ require (
2424
github.com/bep/simplecobra v0.6.1
2525
github.com/bep/textandbinaryreader v0.0.0-20251206192711-c7bc3fa7f114
2626
github.com/bep/tmc v0.5.1
27-
github.com/bep/webptemp v0.0.0-20251203095454-9ec7e7749338
2827
github.com/bits-and-blooms/bitset v1.24.4
2928
github.com/cespare/xxhash/v2 v2.3.0
3029
github.com/clbanning/mxj/v2 v2.7.0
@@ -127,6 +126,7 @@ require (
127126
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect
128127
github.com/aws/smithy-go v1.24.0 // indirect
129128
github.com/aymerick/douceur v0.2.0 // indirect
129+
github.com/bep/webptemp v0.0.0-20251203095454-9ec7e7749338 // indirect
130130
github.com/clipperhouse/displaywidth v0.6.0 // indirect
131131
github.com/clipperhouse/stringish v0.1.1 // indirect
132132
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
@@ -200,8 +200,6 @@ require (
200200

201201
go 1.24.0
202202

203-
replace github.com/bep/webptemp => /Users/bep/dev/go/bep/webptemp
204-
205203
replace github.com/bep/gowebpw => /Users/bep/dev/go/bep/gowebpw
206204

207205
replace github.com/bep/textandbinaryreader => /Users/bep/dev/go/bep/textandbinaryreader

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ github.com/bep/simplecobra v0.6.1 h1:ORBAC5CSar99/NPZ5fCthCx/uvlm7ry58wwDsZ23a20
178178
github.com/bep/simplecobra v0.6.1/go.mod h1:hmtjyHv6xwD637ScIRP++0NKkR5szrHuMw5BxMUH66s=
179179
github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=
180180
github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
181+
github.com/bep/webptemp v0.0.0-20251203095454-9ec7e7749338 h1:cFaJIvnMaH0LcwfrxMD96tFUdhVo6tqA/q27xmxVTSc=
182+
github.com/bep/webptemp v0.0.0-20251203095454-9ec7e7749338/go.mod h1:GYVQBekB+ttPTrAWlNum7pJwhLpTbOYl42n/9uFU+/s=
181183
github.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg=
182184
github.com/bep/workers v1.0.0/go.mod h1:7kIESOB86HfR2379pwoMWNy8B50D7r99fRLUyPSNyCs=
183185
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=

internal/warpc/warpc.go

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,11 @@ func (p *dispatcherPool[Q, R]) Execute(ctx context.Context, q Message[Q]) (Messa
198198
}
199199

200200
func (d *dispatcher[Q, R]) newCall(q Message[Q]) (*call[Q, R], error) {
201-
responseCountdown := 1 // Default is JSON response only.
201+
responseCountdown := &atomic.Int32{}
202+
responseCountdown.Add(1) // Default is JSON response only.
202203
if _, ok := any(d.zeroQ.Data).(DestinationProvider); ok {
203204
// Expecting JSON followed by binary blob.
204-
responseCountdown++
205+
responseCountdown.Add(1)
205206
}
206207
call := &call[Q, R]{
207208
donec: make(chan *call[Q, R], 1),
@@ -262,14 +263,15 @@ func (d *dispatcher[Q, R]) inputBlobs() {
262263
inputErr = err
263264
break
264265
}
265-
hdebug.Printf("=== === === Read blob header %d %d", id, length)
266+
266267
lr := &io.LimitedReader{
267268
R: d.inOut.stdoutBinary,
268269
N: int64(length),
269270
}
270271

271272
call := d.pendingCall(id)
272-
call.responseCountdown--
273+
274+
hdebug.Printf("START === === === Read blob header id: %d len: %d countdown: %d", id, length, call.responseCountdown.Load())
273275

274276
if err := call.handleBlob(lr); err != nil {
275277
inputErr = err
@@ -279,12 +281,14 @@ func (d *dispatcher[Q, R]) inputBlobs() {
279281
inputErr = fmt.Errorf("blob %d: expected to read %d more bytes", id, lr.N)
280282
break
281283
}
282-
if call.responseCountdown <= 0 {
284+
call.responseCountdown.Add(-1)
285+
if call.responseCountdown.Load() <= 0 {
283286
d.mu.Lock()
284287
delete(d.pending, id)
285288
d.mu.Unlock()
286289
call.done()
287290
}
291+
hdebug.Printf("END === === === Read blob header id: %d len: %d countdown: %d", id, length, call.responseCountdown.Load())
288292
}
289293

290294
if inputErr != nil && inputErr != io.EOF && inputErr != io.ErrClosedPipe {
@@ -304,17 +308,17 @@ func (d *dispatcher[Q, R]) inputJSON() {
304308

305309
call := d.pendingCall(r.GetID())
306310

307-
call.responseCountdown--
308-
if call.responseCountdown < 0 {
309-
call.err = fmt.Errorf("protocol error: JSON response must be sent first.")
310-
}
311+
hdebug.Printf("END === === === get JSON id: %d countdown: %d", r.GetID(), call.responseCountdown.Load())
312+
313+
call.responseCountdown.Add(-1)
311314
call.response = r
312-
if call.responseCountdown == 0 {
315+
if call.responseCountdown.Load() <= 0 || r.Header.Err != "" {
313316
d.mu.Lock()
314317
delete(d.pending, r.GetID())
315318
d.mu.Unlock()
316-
call.done()
319+
call.done() // TODO1 check that this can be called multiple times safely.
317320
}
321+
hdebug.Printf("END === === === get JSON id: %d countdown: %d", r.GetID(), call.responseCountdown.Load())
318322

319323
}
320324

@@ -352,13 +356,12 @@ func (d *dispatcher[Q, R]) pendingCall(id uint32) *call[Q, R] {
352356
type call[Q, R any] struct {
353357
request Message[Q]
354358
response Message[R]
355-
responseCountdown int
359+
responseCountdown *atomic.Int32
356360
err error
357361
donec chan *call[Q, R]
358362
}
359363

360364
func (c *call[Q, R]) handleBlob(r io.Reader) error {
361-
hdebug.Printf("call ====> handleBlob")
362365
dest := any(c.request.Data).(DestinationProvider).GetDestination()
363366
if dest == nil {
364367
panic("blob destination is not set")
@@ -522,29 +525,29 @@ func newDispatcher[Q, R any](opts Options) (*dispatcherPool[Q, R], error) {
522525

523526
inOuts := make([]*inOut, opts.PoolSize)
524527
for i := range opts.PoolSize {
525-
var stdin, stdoutBinary hugio.ReadWriteCloser
528+
var stdinPipe, stdoutBinary hugio.ReadWriteCloser
526529
var stdout io.WriteCloser
527530
var jsonr io.Reader
528531

529-
stdin = hugio.NewPipeReadWriteCloser()
530-
stdoutRwc := hugio.NewPipeReadWriteCloser()
531-
stdout = stdoutRwc
532+
stdinPipe = hugio.NewPipeReadWriteCloser()
533+
stdoutPipe := hugio.NewPipeReadWriteCloser()
534+
stdout = stdoutPipe
532535

533536
var zero Q
534537
if _, ok := any(zero).(DestinationProvider); ok {
535538
stdoutBinary = hugio.NewPipeReadWriteCloser()
536-
jsonr = stdoutRwc
539+
jsonr = stdoutPipe
537540
stdout = textandbinaryreader.NewWriter(stdout, stdoutBinary)
538541
} else {
539-
jsonr = stdoutRwc
542+
jsonr = stdoutPipe
540543
}
541544

542545
inOuts[i] = &inOut{
543-
stdin: stdin,
546+
stdin: stdinPipe,
544547
stdout: stdout,
545548
stdoutBinary: stdoutBinary,
546549
dec: json.NewDecoder(jsonr),
547-
enc: json.NewEncoder(stdin),
550+
enc: json.NewEncoder(stdinPipe),
548551
}
549552
}
550553

@@ -748,8 +751,13 @@ func AllDispatchers(opts Options) *Dispatchers {
748751
_, err = data.ReadFrom(r)
749752
webOpts.Main = Binary{Name: "webp", Data: data.Bytes()}
750753

751-
return &Dispatchers{
754+
dispatchers := &Dispatchers{
752755
katex: &lazyDispatcher[KatexInput, KatexOutput]{opts: katexOpts},
753756
webp: &lazyDispatcher[WebpInput, WebpOutput]{opts: webOpts},
754757
}
758+
759+
// TODO1 better way to do this?
760+
// image.RegisterFormat("webp", "RIFF????WEBPVP8", dispatchers.DecodeWebp, dispatchers.DecodeWebpConfig)
761+
762+
return dispatchers
755763
}

internal/warpc/webp.go

Lines changed: 88 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package warpc
1616
import (
1717
"bytes"
1818
"context"
19+
"fmt"
1920
"image"
2021
"io"
2122
"time"
@@ -30,13 +31,18 @@ var (
3031
_ DestinationProvider = WebpInput{}
3132
)
3233

34+
type CommonImageProcessingOptions struct {
35+
Width int `json:"width,omitempty"`
36+
Height int `json:"height,omitempty"`
37+
Stride int `json:"stride,omitempty"`
38+
}
39+
3340
type WebpInput struct {
34-
Source io.Reader `json:"-"` // Will be sent in a separate stream.
35-
SourceLength uint32 `json:"-"`
36-
Destination io.Writer `json:"-"` // Will be used to write the result to.
37-
Width int `json:"width"`
38-
Height int `json:"height"`
39-
Stride int `json:"stride"`
41+
Source io.Reader `json:"-"` // Will be sent in a separate stream.
42+
SourceLength uint32 `json:"-"`
43+
Destination io.Writer `json:"-"` // Will be used to write the result to.
44+
Options map[string]any `json:"options"`
45+
4046
// TODO1 config options.
4147
}
4248

@@ -52,23 +58,85 @@ func (w WebpInput) GetDestination() io.Writer {
5258
return w.Destination
5359
}
5460

55-
type WebpOutput struct{}
61+
type WebpOutput struct {
62+
Options CommonImageProcessingOptions
63+
}
5664

5765
func (d *Dispatchers) stopClock(what string, start time.Time) {
5866
hdebug.Printf("%s took %s", what, time.Since(start))
5967
}
6068

69+
// Decode reads a WEBP image from r and returns it as an image.Image.
70+
func (d *Dispatchers) DecodeWebp(r io.Reader) (image.Image, error) {
71+
var enabled bool
72+
if !enabled {
73+
return webp.Decode(r)
74+
}
75+
dd, err := d.Webp()
76+
if err != nil {
77+
return nil, err
78+
}
79+
b, err := io.ReadAll(r) // TODO1: stream
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
var imageBytesBuf bytes.Buffer
85+
86+
// Commands:
87+
// encodeNRGBA
88+
// encodeGray
89+
// decode
90+
// config
91+
message := Message[WebpInput]{
92+
Header: Header{
93+
Version: 1,
94+
ID: d.id.Add(1),
95+
Command: "decode",
96+
},
97+
98+
Data: WebpInput{
99+
Source: bytes.NewReader(b),
100+
SourceLength: uint32(len(b)),
101+
Destination: &imageBytesBuf,
102+
Options: map[string]any{
103+
// TODO1
104+
},
105+
},
106+
}
107+
108+
out, err := dd.Execute(context.Background(), message)
109+
if err != nil {
110+
return nil, err
111+
}
112+
hdebug.Printf("GOT %d/%d %d stride: %d", out.Data.Options.Width, out.Data.Options.Height, len(imageBytesBuf.Bytes()), out.Data.Options.Stride)
113+
114+
img := &image.RGBA{
115+
Pix: imageBytesBuf.Bytes(),
116+
Stride: out.Data.Options.Stride,
117+
Rect: image.Rect(0, 0, out.Data.Options.Width, out.Data.Options.Height),
118+
}
119+
120+
offset := img.PixOffset(100, 100)
121+
hdebug.Printf("Offset 100/100: %d", offset)
122+
if offset+4 > len(img.Pix) {
123+
return nil, fmt.Errorf("error")
124+
}
125+
126+
return img, nil
127+
}
128+
129+
func (d *Dispatchers) DecodeWebpConfig(r io.Reader) (image.Config, error) {
130+
panic("DecodeWebpConfig: not implemented")
131+
}
132+
61133
func (d *Dispatchers) EncodeWebp(w io.Writer, src image.Image) error {
134+
hdebug.Printf("EncodeWebp %T", src)
62135
d.Webp() // TODO1 remove. Just to get the timer below comparable.
63136
start := time.Now()
64137
defer d.stopClock("EncodeWebp", start)
65138
if false {
66-
return webp.Encode(w, src)
67-
}
68-
// 2.8 vs 3.4 ms... hmm...
69-
// TODO1 all of this is just temporary.
70-
if d == nil {
71-
panic("warpc Dispatchers is nil")
139+
// return webp.Encode(w, src)
72140
}
73141

74142
dd, err := d.Webp()
@@ -96,6 +164,8 @@ func (d *Dispatchers) EncodeWebp(w io.Writer, src image.Image) error {
96164
// Commands:
97165
// encodeNRGBA
98166
// encodeGray
167+
// decode
168+
// config
99169
message := Message[WebpInput]{
100170
Header: Header{
101171
Version: 1,
@@ -107,16 +177,17 @@ func (d *Dispatchers) EncodeWebp(w io.Writer, src image.Image) error {
107177
Source: bytes.NewReader(imageBytes),
108178
SourceLength: uint32(len(imageBytes)),
109179
Destination: ww,
110-
Width: bounds.Max.X,
111-
Height: bounds.Max.Y,
112-
Stride: stride,
180+
Options: map[string]any{
181+
"width": bounds.Max.X,
182+
"height": bounds.Max.Y,
183+
"stride": stride,
184+
},
113185
},
114186
}
115187

116188
_, err = dd.Execute(context.Background(), message)
117189
if err != nil {
118190
return err
119191
}
120-
hdebug.Printf("== == == ==DONE! == == == ==")
121192
return nil
122193
}

resources/image.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ import (
4040
"github.com/gohugoio/hugo/resources/resource"
4141

4242
"github.com/gohugoio/hugo/resources/images"
43-
44-
// Blind import for image.Decode
45-
_ "github.com/bep/webptemp"
4643
)
4744

4845
var (

resources/image_extended_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ func TestImageResizeWebP(t *testing.T) {
2525
sourcefilename: testdata/sunrise.webp
2626
-- layouts/home.html --
2727
{{ $image := resources.Get "sunrise.webp" }}
28-
{{ $resized := $image.Resize "123x" }}
28+
{{ $resized := $image.Resize "123x456" }}
2929
Original Width/Height: {{ $image.Width }}/{{ $image.Height }}|
30-
Resized Width: {{ $resized.Width }}|
30+
Resized Width:/Height {{ $resized.Width }}/{{ $resized.Height }}|
3131
Resized RelPermalink: {{ $resized.RelPermalink }}|
3232
`
3333

@@ -37,7 +37,7 @@ Resized RelPermalink: {{ $resized.RelPermalink }}|
3737

3838
b.AssertFileContent("public/index.html",
3939
"Original Width/Height: 1024/640|",
40-
"Resized Width: 123|",
41-
"Resized RelPermalink: /sunrise_hu_94448e81dce95acf.webp|",
40+
"Resized Width:/Height 123/456|",
41+
"Resized RelPermalink: /sunrise_hu_6095509b5348ba46.webp|",
4242
)
4343
}

tpl/images/images.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ import (
3434
_ "image/jpeg"
3535
_ "image/png"
3636

37-
// Import webp codec
38-
_ "github.com/bep/webptemp"
39-
4037
"github.com/gohugoio/hugo/deps"
4138
"github.com/spf13/afero"
4239
"github.com/spf13/cast"

0 commit comments

Comments
 (0)