diff --git a/internal/ui/noise.go b/internal/ui/noise.go new file mode 100644 index 0000000..db9df5e --- /dev/null +++ b/internal/ui/noise.go @@ -0,0 +1,83 @@ +package ui + +import ( + "image" + "image/color" + "math/rand" + "os" + "strings" + "sync" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" +) + +const ( + noiseTileSize = 128 + noiseAlpha = uint8(8) // ~3% opacity + noiseVariance = uint8(6) // low contrast + noiseMeanValue = uint8(128) +) + +var ( + noiseOnce sync.Once + noiseTile *image.NRGBA + uiTextureEnabled = true +) + +func init() { + switch strings.ToLower(strings.TrimSpace(os.Getenv("VT_UI_TEXTURE"))) { + case "0", "false", "off", "no": + uiTextureEnabled = false + } +} + +// NoisyBackgroundObjects returns background layers with an optional static noise overlay. +func NoisyBackgroundObjects(bg fyne.CanvasObject) []fyne.CanvasObject { + if !uiTextureEnabled { + return []fyne.CanvasObject{bg} + } + return []fyne.CanvasObject{bg, NewNoiseOverlay()} +} + +// NewNoiseOverlay creates a static, cached grayscale noise layer. +func NewNoiseOverlay() fyne.CanvasObject { + return canvas.NewRaster(func(w, h int) image.Image { + if w <= 0 || h <= 0 { + return image.NewNRGBA(image.Rect(0, 0, 1, 1)) + } + tile := getNoiseTile() + dst := image.NewNRGBA(image.Rect(0, 0, w, h)) + tw := tile.Bounds().Dx() + th := tile.Bounds().Dy() + for y := 0; y < h; y++ { + ty := y % th + for x := 0; x < w; x++ { + tx := x % tw + dst.SetNRGBA(x, y, tile.NRGBAAt(tx, ty)) + } + } + return dst + }) +} + +func getNoiseTile() *image.NRGBA { + noiseOnce.Do(func() { + tile := image.NewNRGBA(image.Rect(0, 0, noiseTileSize, noiseTileSize)) + rng := rand.New(rand.NewSource(1)) + for y := 0; y < noiseTileSize; y++ { + for x := 0; x < noiseTileSize; x++ { + delta := int(rng.Intn(int(noiseVariance)*2+1)) - int(noiseVariance) + v := int(noiseMeanValue) + delta + if v < 0 { + v = 0 + } else if v > 255 { + v = 255 + } + tile.SetNRGBA(x, y, color.NRGBA{R: uint8(v), G: uint8(v), B: uint8(v), A: noiseAlpha}) + } + } + noiseTile = tile + }) + return noiseTile +} diff --git a/main.go b/main.go index 536c88d..11ac065 100644 --- a/main.go +++ b/main.go @@ -6943,7 +6943,9 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject { widget.NewSeparator(), content, ) - return container.NewMax(bg, container.NewPadded(body)) + layers := ui.NoisyBackgroundObjects(bg) + layers = append(layers, container.NewPadded(body)) + return container.NewMax(layers...) } sectionGap := func() fyne.CanvasObject { @@ -10268,7 +10270,9 @@ func makeLabeledPanel(title, body string, min fyne.Size) *fyne.Container { desc.Wrapping = fyne.TextWrapWord box := container.NewVBox(header, desc, layout.NewSpacer()) - return container.NewMax(rect, container.NewPadded(box)) + layers := ui.NoisyBackgroundObjects(rect) + layers = append(layers, container.NewPadded(box)) + return container.NewMax(layers...) } func buildMetadataPanel(state *appState, src *videoSource, min fyne.Size) (fyne.CanvasObject, func()) { @@ -10289,7 +10293,9 @@ func buildMetadataPanel(state *appState, src *videoSource, min fyne.Size) (fyne. widget.NewLabel("Load a clip to inspect its technical details."), layout.NewSpacer(), ) - return container.NewMax(outer, container.NewPadded(body)), func() {} + layers := ui.NoisyBackgroundObjects(outer) + layers = append(layers, container.NewPadded(body)) + return container.NewMax(layers...), func() {} } bitrate := "--" @@ -10649,7 +10655,9 @@ Metadata: %s`, ) scroll := container.NewVScroll(body) scroll.SetMinSize(fyne.NewSize(0, 220)) - return container.NewMax(outer, container.NewPadded(scroll)), updateCoverDisplay + layers := ui.NoisyBackgroundObjects(outer) + layers = append(layers, container.NewPadded(scroll)) + return container.NewMax(layers...), updateCoverDisplay } func buildVideoPane(state *appState, min fyne.Size, src *videoSource, onCover func(string)) fyne.CanvasObject { @@ -15313,7 +15321,9 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { widget.NewSeparator(), content, ) - return container.NewMax(bg, container.NewPadded(body)) + layers := ui.NoisyBackgroundObjects(bg) + layers = append(layers, container.NewPadded(body)) + return container.NewMax(layers...) } // Scaling (method + blur) @@ -15936,8 +15946,7 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { mainContent := split content := container.NewMax( - canvas.NewRectangle(mediumBlue), - container.NewPadded(mainContent), + append(ui.NoisyBackgroundObjects(canvas.NewRectangle(mediumBlue)), container.NewPadded(mainContent))..., ) actionBar := container.NewHBox(layout.NewSpacer(), applyBtn, addQueueBtn)