fyne-widgets/pkg/waveform/waveform.go

184 lines
5.1 KiB
Go
Raw Normal View History

2022-02-11 19:07:21 +00:00
package waveform
2022-02-11 18:32:22 +00:00
import (
"image"
"image/color"
"math"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/disintegration/imaging"
)
//
// Widget code starts here
//
// A visualisation of an audio stream widget with themed or overridden background and foreground
//
2022-02-11 19:07:21 +00:00
type Waveform struct {
2022-02-11 18:32:22 +00:00
widget.BaseWidget // Inherit from BaseWidget
audioData []int32 // The data to display in the widget
StretchSamples bool
TransparentBackground bool
OverrideForeground bool
OverrideForegroundColor color.Color
OverrideBackground bool
OverrideBackgroundColor color.Color
minSize fyne.Size
}
//
// Create a Widget and Extend (initialiase) the BaseWidget
//
2022-02-11 19:07:21 +00:00
func NewWaveform(data []int32) *Waveform {
w := &Waveform{ // Create this widget with an initial text value
2022-02-11 18:32:22 +00:00
audioData: data,
StretchSamples: false,
TransparentBackground: false,
OverrideForeground: false,
OverrideForegroundColor: color.RGBA{0, 0, 0, 0xff},
OverrideBackground: false,
OverrideBackgroundColor: color.RGBA{0xff, 0xff, 0xff, 0xff},
minSize: fyne.NewSize(200, 64),
}
w.ExtendBaseWidget(w) // Initialiase the BaseWidget
return w
}
//
// Create the renderer. This is called by the fyne application
//
2022-02-11 19:07:21 +00:00
func (w *Waveform) CreateRenderer() fyne.WidgetRenderer {
2022-02-11 18:32:22 +00:00
// Pass this widget to the renderer so it can access the text field
2022-02-11 19:07:21 +00:00
return newWaveformRenderer(w)
2022-02-11 18:32:22 +00:00
}
//
// Set the minsize (default is 200x64)
//
2022-02-11 19:07:21 +00:00
func (w *Waveform) SetMinSize(newSize fyne.Size) {
2022-02-11 18:32:22 +00:00
// Pass this widget to the renderer so it can access the text field
w.minSize = newSize
}
//
// Widget Renderer code starts here
//
2022-02-11 19:07:21 +00:00
type waveformRenderer struct {
widget *Waveform // Reference to the widget holding the current state
2022-02-11 18:32:22 +00:00
background *canvas.Rectangle // A background rectangle
canvas *canvas.Raster // The waveform
}
//
// Create the renderer with a reference to the widget
// Note: The background and foreground colours are set from the current theme.
//
// Do not size or move canvas objects here.
//
2022-02-11 19:07:21 +00:00
func newWaveformRenderer(theWidget *Waveform) *waveformRenderer {
r := &waveformRenderer{
2022-02-11 18:32:22 +00:00
widget: theWidget,
background: canvas.NewRectangle(theme.BackgroundColor()),
}
r.canvas = canvas.NewRaster(r.audioDataToImage)
return r
}
// From arduino map() lol
func int32Map(x int32, in_min int32, in_max int32, out_min int32, out_max int32) int32 {
var _x = int64(x)
var _in_min = int64(in_min)
var _in_max = int64(in_max)
var _out_min = int64(out_min)
var _out_max = int64(out_max)
var r = int64((_x-_in_min)*(_out_max-_out_min)/(_in_max-_in_min) + _out_min)
return int32(r)
}
2022-02-11 19:07:21 +00:00
func (r *waveformRenderer) audioDataToImage(w, h int) image.Image {
2022-02-11 18:32:22 +00:00
foregroundColor := theme.ForegroundColor()
if r.widget.OverrideForeground {
foregroundColor = r.widget.OverrideForegroundColor
}
upLeft := image.Point{0, 0}
lowRight := image.Point{w, h}
if r.widget.StretchSamples {
lowRight = image.Point{len(r.widget.audioData), h}
}
img := image.NewRGBA(image.Rectangle{upLeft, lowRight})
i := int32(0)
for i = 0; i < int32(len(r.widget.audioData)); i++ {
sample := r.widget.audioData[i]
2022-02-11 19:07:21 +00:00
// /2 because we draw waveforms with 0 at the middle!
2022-02-11 18:32:22 +00:00
sampleheight := int32Map(sample, 0, math.MaxInt32, 0, int32(h/2))
y := int32(0)
//center line
img.Set(int(i), int(h/2), foregroundColor)
for y = int32(h); y > int32(h)-sampleheight; y-- {
img.Set(int(i), int(y)-(h/2), foregroundColor)
}
for y = int32(h / 2); y < sampleheight+(int32(h)/2); y++ {
img.Set(int(i), int(y), foregroundColor)
}
}
if r.widget.StretchSamples {
return imaging.Resize(img, w, h, imaging.NearestNeighbor)
}
return img
}
//
// The Refresh() method is called if the state of the widget changes or the
// theme is changed
//
// Note: The background and foreground colours are set from the current theme
//
2022-02-11 19:07:21 +00:00
func (r *waveformRenderer) Refresh() {
2022-02-11 18:32:22 +00:00
backgroundColor := theme.BackgroundColor()
if r.widget.OverrideBackground {
backgroundColor = color.RGBA{0, 0, 0xff, 0xff} //r.widget.OverrideBackgroundColor
}
r.background.FillColor = backgroundColor
r.background.Refresh() // Redraw the background first
r.canvas.Refresh() // Redraw the waveform on top
}
//
// Given the size required by the fyne application move and re-size the
// canvas objects.
//
2022-02-11 19:07:21 +00:00
func (r *waveformRenderer) Layout(s fyne.Size) {
2022-02-11 18:32:22 +00:00
// Make sure the background fills the widget
r.background.Resize(s)
r.canvas.Resize(s)
r.Refresh()
}
//
// Create a minimum size for the widget.
// The smallest size is the size of the text with a border defined by the theme padding
//
2022-02-11 19:07:21 +00:00
func (r *waveformRenderer) MinSize() fyne.Size {
2022-02-11 18:32:22 +00:00
// Default is set in newSpectrumRenderer, overridable by users
return r.widget.minSize
}
//
// Return a list of each canvas object.
//
2022-02-11 19:07:21 +00:00
func (r *waveformRenderer) Objects() []fyne.CanvasObject {
2022-02-11 18:32:22 +00:00
if r.widget.TransparentBackground {
return []fyne.CanvasObject{r.canvas}
}
return []fyne.CanvasObject{r.background, r.canvas}
}
//
// Cleanup if resources have been allocated
//
2022-02-11 19:07:21 +00:00
func (r *waveformRenderer) Destroy() {}