go-libwebp

Experimental translation from libwebp to Go source.
Log | Files | Refs | README | LICENSE

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 }