From e989a2f6adf57a80ed821611c1f66b2e548078ed Mon Sep 17 00:00:00 2001 From: Michael Hanselmann Date: Wed, 8 Dec 2021 21:04:48 +0100 Subject: [PATCH] videosink: Add options for JPEG and PNG encoders Expose the quality settings for the JPEG encoder and the compression level of the PNG encoder via the Options structure. Signed-off-by: Michael Hanselmann --- videosink/display.go | 29 ++++++++++++++++++++++++++--- videosink/encoder.go | 33 ++++++++++++++++++++------------- videosink/handler.go | 4 ++-- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/videosink/display.go b/videosink/display.go index 05d67d8..cf10b6b 100644 --- a/videosink/display.go +++ b/videosink/display.go @@ -21,12 +21,16 @@ import ( "image" "image/color" "image/draw" + "image/jpeg" + "image/png" "net/http" "sync" "periph.io/x/conn/v3/display" ) +const defaultJPEGQuality = 95 + // Options for videosink devices. type Options struct { // Width and height of the image buffer. @@ -35,11 +39,21 @@ type Options struct { // Format specifies the image format to send to clients. Format ImageFormat - // TODO: Add options for JPEG and PNG encoder settings + // JPEG controls options for the JPEG encoder. + JPEG jpeg.Options + + // PNG controls options for the PNG encoder. + PNG struct { + // CompressionLevel is the amount of compression applied by the PNG + // encoder. Defaults to png.DefaultCompression. + CompressionLevel png.CompressionLevel + } } type Display struct { - defaultFormat ImageFormat + defaultFormat ImageFormat + jpegOptions jpeg.Options + pngCompressionLevel png.CompressionLevel mu sync.Mutex buffer *image.RGBA @@ -58,12 +72,21 @@ func New(opt *Options) *Display { // draw operation makes it opaque. draw.Draw(buffer, buffer.Bounds(), image.Black, image.Point{}, draw.Src) - return &Display{ + d := &Display{ + jpegOptions: opt.JPEG, + pngCompressionLevel: opt.PNG.CompressionLevel, + buffer: buffer, clients: map[*client]struct{}{}, snapshot: map[imageConfig][]byte{}, defaultFormat: opt.Format, } + + if d.jpegOptions.Quality == 0 { + d.jpegOptions.Quality = defaultJPEGQuality + } + + return d } // String returns the name of the device. diff --git a/videosink/encoder.go b/videosink/encoder.go index ec6c66c..189ebec 100644 --- a/videosink/encoder.go +++ b/videosink/encoder.go @@ -5,15 +5,10 @@ package videosink import ( - "image/jpeg" "image/png" "sync" ) -var jpegOptions = jpeg.Options{ - Quality: 95, -} - type pngEncoderBufferPool sync.Pool func (p *pngEncoderBufferPool) Get() *png.EncoderBuffer { @@ -26,21 +21,33 @@ func (p *pngEncoderBufferPool) Put(buf *png.EncoderBuffer) { } type pngEncoderManager struct { - once sync.Once + mu sync.Mutex pool pngEncoderBufferPool - enc *png.Encoder + enc map[png.CompressionLevel]*png.Encoder } var pngEncoder pngEncoderManager // get returns a PNG encoder with a globally shared buffer pool. -func (m *pngEncoderManager) get() *png.Encoder { - m.once.Do(func() { - m.enc = &png.Encoder{ - CompressionLevel: png.BestSpeed, +func (m *pngEncoderManager) get(level png.CompressionLevel) *png.Encoder { + m.mu.Lock() + defer m.mu.Unlock() + + enc := m.enc[level] + if enc == nil { + if m.enc == nil { + // The vast majority of use cases will involve exactly one + // compression level. + m.enc = make(map[png.CompressionLevel]*png.Encoder, 1) + } + + enc = &png.Encoder{ + CompressionLevel: level, BufferPool: &m.pool, } - }) - return m.enc + m.enc[level] = enc + } + + return enc } diff --git a/videosink/handler.go b/videosink/handler.go index 9189b64..d24f476 100644 --- a/videosink/handler.go +++ b/videosink/handler.go @@ -80,12 +80,12 @@ func (d *Display) encodeBufferLocked(format ImageFormat) ([]byte, error) { switch format { case PNG: - if err := pngEncoder.get().Encode(buf, d.buffer); err != nil { + if err := pngEncoder.get(d.pngCompressionLevel).Encode(buf, d.buffer); err != nil { return nil, err } case JPEG: - if err := jpeg.Encode(buf, d.buffer, &jpegOptions); err != nil { + if err := jpeg.Encode(buf, d.buffer, &d.jpegOptions); err != nil { return nil, err }