backend.go (3027B)
1 package webp 2 3 import ( 4 "errors" 5 "image" 6 "io" 7 "sync" 8 ) 9 10 // backend is one way of reaching libwebp. 11 // 12 // Which backends are compiled into a binary is a build-time choice made with 13 // tags, so that a consumer pays only for the ones they want: 14 // 15 // (default) dynamic, then wasm 16 // -tags transpiled dynamic, then transpiled 17 // -tags nowasm dynamic only 18 // -tags nodynamic wasm only 19 // -tags nodynamic,transpiled transpiled only 20 // 21 // The transpiled tag displaces the wasm backend rather than adding to it, so 22 // no build carries both pure-Go fallbacks. 23 type backend struct { 24 // name is what Backend reports. 25 name string 26 // ready reports whether this backend can be used in this process. A nil 27 // ready means it always can. 28 // 29 // It runs during selection, before any image is touched, so it must be 30 // cheap. A backend whose real initialisation is expensive — the wasm 31 // backend spends ~120ms compiling its module — leaves this nil and 32 // initialises lazily on first use, so a binary that ends up on the 33 // dynamic backend never pays for it. 34 ready func() error 35 encode func(io.Writer, *image.NRGBA, float32) error 36 decode func([]byte) (image.Image, error) 37 } 38 39 // Preference is declared here rather than emerging from init order: the 40 // tag-guarded files fill these slots and selection walks them in sequence. 41 // Only one file ever assigns fallbackBackend, so the mutual exclusivity of 42 // wasm and transpiled is enforced by the build tags, not at run time. 43 var ( 44 dynamicBackend *backend 45 fallbackBackend *backend 46 ) 47 48 // ErrNoBackend reports that no compiled-in backend could be used. In a 49 // default build that means no usable libwebp shared object was found and the 50 // embedded wasm module failed to initialise. 51 var ErrNoBackend = errors.New("webp: no usable backend") 52 53 var ( 54 selectOnce sync.Once 55 selected *backend 56 ) 57 58 // active returns the backend this process will use, selecting it on first 59 // call. Selection is cached: probing for a shared object on every encode is 60 // pure overhead on the machines that fall through to a pure-Go backend. 61 func active() (*backend, error) { 62 selectOnce.Do(func() { 63 for _, b := range []*backend{dynamicBackend, fallbackBackend} { 64 if b == nil { 65 continue 66 } 67 if b.ready != nil { 68 if err := b.ready(); err != nil { 69 continue 70 } 71 } 72 selected = b 73 return 74 } 75 }) 76 if selected == nil { 77 return nil, ErrNoBackend 78 } 79 return selected, nil 80 } 81 82 // Backend reports which backend this binary uses: "dynamic", "wasm" or 83 // "transpiled", or "" when none is usable. It performs selection if that has 84 // not happened yet. 85 // 86 // Worth checking whenever the exact output bytes matter. The dynamic backend 87 // binds whatever libwebp the host provides, which may be a different version 88 // from the one this package was built against and may legitimately encode to 89 // different bytes than the other backends. 90 func Backend() string { 91 b, err := active() 92 if err != nil { 93 return "" 94 } 95 return b.name 96 }