Save GIFs on headless mode
This commit is contained in:
parent
dcc1597cb2
commit
695eaa603b
|
@ -5,3 +5,6 @@ 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
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue