Save GIFs on headless mode

This commit is contained in:
Iván Izaguirre 2022-05-09 14:34:47 +02:00
parent dcc1597cb2
commit 695eaa603b
4 changed files with 92 additions and 13 deletions

5
.gitignore vendored
View File

@ -4,4 +4,7 @@ a2sdl.exe
frontend/a2sdl/a2sdl frontend/a2sdl/a2sdl
frontend/a2sdl/*.woz frontend/a2sdl/*.woz
frontend/a2sdl/*.dsk frontend/a2sdl/*.dsk
frontend/a2fyne/a2fyne frontend/a2fyne/a2fyne
frontend/headless/headless
frontend/*/snapshot.gif
frontend/*/snapshot.png

View File

@ -3,8 +3,10 @@ package main
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"image/gif"
"os" "os"
"strings" "strings"
"time"
"github.com/ivanizag/izapple2" "github.com/ivanizag/izapple2"
"github.com/ivanizag/izapple2/screen" "github.com/ivanizag/izapple2/screen"
@ -88,6 +90,9 @@ func main() {
case "return": case "return":
fe.keyChannel <- 13 fe.keyChannel <- 13
case "gif":
SaveGif(a, "snapshot.gif")
case "help": case "help":
fmt.Print(` fmt.Print(`
Available commands: Available commands:
@ -99,6 +104,7 @@ Available commands:
Key or k: Sends a key to the emulator Key or k: Sends a key to the emulator
Keys or ks: Sends a string to the emulator Keys or ks: Sends a string to the emulator
Return or r: Sends a return to the emulator Return or r: Sends a return to the emulator
GIF or gif: Captures a GIF animation
Help: Prints this help Help: Prints this help
`) `)
default: default:
@ -107,6 +113,40 @@ Available commands:
} }
} }
func SaveGif(a *izapple2.Apple2, filename string) error {
animation := gif.GIF{}
delay := 50 * time.Millisecond
delayHundredsS := 5
frames := 20 // 1 second
planned := time.Now()
for i := 0; i < frames; i++ {
lapse := planned.Sub(time.Now())
fmt.Printf("%v\n", lapse)
if lapse > 0 {
time.Sleep(lapse)
}
fmt.Printf("%v\n", time.Now())
img := screen.SnapshotPaletted(a, screen.ScreenModeNTSC)
animation.Image = append(animation.Image, img)
animation.Delay = append(animation.Delay, delayHundredsS)
planned = planned.Add(delay)
}
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
gif.EncodeAll(f, &animation)
return nil
}
/* /*
Uses the console to send commands and queries to an emulated machine. Uses the console to send commands and queries to an emulated machine.
*/ */

View File

@ -30,6 +30,21 @@ var ntscColorMap = [16]color.Color{
color.RGBA{255, 255, 255, 255}, // White color.RGBA{255, 255, 255, 255}, // White
} }
var attenuatedColorMap = buildAttenuatedColorMap(ntscColorMap)
func buildAttenuatedColorMap(colorMap [16]color.Color) [16]color.Color {
colors := [16]color.Color{}
for i := 0; i < len(colorMap); i++ {
r, g, b, _ := colorMap[i].RGBA()
colors[i] = color.RGBA64{
uint16(r / 2), uint16(g / 2), uint16(b / 2),
65535,
}
}
return colors
}
/*
var rgbColorMap = [16]color.Color{ var rgbColorMap = [16]color.Color{
color.RGBA{0, 0, 0, 255}, // Black color.RGBA{0, 0, 0, 255}, // Black
color.RGBA{221, 0, 51, 255}, // Magenta color.RGBA{221, 0, 51, 255}, // Magenta
@ -48,18 +63,13 @@ var rgbColorMap = [16]color.Color{
color.RGBA{68, 255, 153, 255}, // Aquamarine color.RGBA{68, 255, 153, 255}, // Aquamarine
color.RGBA{255, 255, 255, 255}, // White color.RGBA{255, 255, 255, 255}, // White
} }
*/
func filterNTSCColor(in *image.RGBA, mask *image.Alpha, screenMode int) *image.RGBA { func filterNTSCColor(in *image.RGBA, mask *image.Alpha, screenMode int) *image.RGBA {
colorMap := ntscColorMap // or rgbColorMap colorMap := ntscColorMap // or rgbColorMap
attenuatedColorMap := ntscColorMap colorMapLow := ntscColorMap
if screenMode == ScreenModeNTSC { if screenMode == ScreenModeNTSC {
for i := 0; i < len(colorMap); i++ { colorMapLow = attenuatedColorMap
r, g, b, _ := colorMap[i].RGBA()
attenuatedColorMap[i] = color.RGBA64{
uint16(r / 2), uint16(g / 2), uint16(b / 2),
65535,
}
}
} }
b := in.Bounds() b := in.Bounds()
@ -90,7 +100,7 @@ func filterNTSCColor(in *image.RGBA, mask *image.Alpha, screenMode int) *image.R
if r != 0 { if r != 0 {
cOut = colorMap[v] cOut = colorMap[v]
} else { } else {
cOut = attenuatedColorMap[v] cOut = colorMapLow[v]
} }
if mask != nil { if mask != nil {
// RGB mode7 // RGB mode7

View File

@ -36,6 +36,16 @@ func Snapshot(vs VideoSource, screenMode int) *image.RGBA {
return snap return snap
} }
// SnapshotPaletted, snapshot of the currently visible screen as a paletted image
func SnapshotPaletted(vs VideoSource, screenMode int) *image.Paletted {
img := Snapshot(vs, screenMode)
return palletedFilter(img)
}
// Color for typical Apple ][ period green P1 phosphor monitors
// See: https://superuser.com/questions/361297/what-colour-is-the-dark-green-on-old-fashioned-green-screen-computer-displays
var greenPhosphorColor = color.RGBA{65, 255, 0, 255}
func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGBA { func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGBA {
videoBase := videoMode & VideoBaseMask videoBase := videoMode & VideoBaseMask
mixMode := videoMode & VideoMixTextMask mixMode := videoMode & VideoMixTextMask
@ -44,9 +54,7 @@ func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGB
var lightColor color.Color = color.White var lightColor color.Color = color.White
if screenMode == ScreenModeGreen { if screenMode == ScreenModeGreen {
// Color for typical Apple ][ period green P1 phosphor monitors lightColor = greenPhosphorColor
// See: https://superuser.com/questions/361297/what-colour-is-the-dark-green-on-old-fashioned-green-screen-computer-displays
lightColor = color.RGBA{65, 255, 0, 255}
} }
applyNTSCFilter := screenMode != ScreenModeGreen applyNTSCFilter := screenMode != ScreenModeGreen
@ -170,3 +178,21 @@ func linesSeparatedFilter(in *image.RGBA) *image.RGBA {
} }
return out return out
} }
func palletedFilter(in *image.RGBA) *image.Paletted {
bounds := in.Bounds()
outBounds := image.Rect(0, 0, bounds.Dx()*2, bounds.Dy())
palette := []color.Color{color.Black, color.White, greenPhosphorColor}
palette = append(palette, ntscColorMap[:]...)
palette = append(palette, attenuatedColorMap[:]...)
paletted := image.NewPaletted(outBounds, palette)
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
c := in.At(x, y)
paletted.Set(x*2, y, c)
paletted.Set(x*2+1, y, c)
}
}
return paletted
}