mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-05 16:17:45 -05:00
Screenshot and GIF recording capability (#310)
* Configuration cleanup * Cleanup * Gif animation and screenshot support
This commit is contained in:
parent
e4c84c4fb9
commit
423cef304d
@ -2,6 +2,7 @@ package ebiten
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
|
||||
@ -106,3 +107,17 @@ func (s *ebitenSurface) GetDepth() int {
|
||||
func (s *ebitenSurface) ReplacePixels(pixels []byte) error {
|
||||
return s.image.ReplacePixels(pixels)
|
||||
}
|
||||
|
||||
func (s *ebitenSurface) Screenshot() *image.RGBA {
|
||||
width, height := s.GetSize()
|
||||
bounds := image.Rectangle{image.Point{0, 0}, image.Point{width, height}}
|
||||
image := image.NewRGBA(bounds)
|
||||
|
||||
for y := 0; y < height; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
image.Set(x, y, s.image.At(x, y))
|
||||
}
|
||||
}
|
||||
|
||||
return image
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package d2render
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
@ -19,4 +20,5 @@ type Surface interface {
|
||||
PushTranslation(x, y int)
|
||||
Render(surface Surface) error
|
||||
ReplacePixels(pixels []byte) error
|
||||
Screenshot() *image.RGBA
|
||||
}
|
||||
|
124
main.go
124
main.go
@ -1,12 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/gif"
|
||||
"image/png"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
|
||||
@ -36,10 +41,22 @@ var GitBranch string
|
||||
// GitCommit is set by the CI build process to the commit hash
|
||||
var GitCommit string
|
||||
|
||||
type captureState int
|
||||
|
||||
const (
|
||||
captureStateNone captureState = iota
|
||||
captureStateFrame
|
||||
captureStateGif
|
||||
)
|
||||
|
||||
var singleton struct {
|
||||
lastTime float64
|
||||
showFPS bool
|
||||
timeScale float64
|
||||
|
||||
captureState captureState
|
||||
capturePath string
|
||||
captureFrames []*image.RGBA
|
||||
}
|
||||
|
||||
func main() {
|
||||
@ -103,6 +120,19 @@ func initialize() error {
|
||||
d2render.SetFullScreen(fullscreen)
|
||||
d2term.OutputInfo("fullscreen is now: %v", fullscreen)
|
||||
})
|
||||
d2term.BindAction("capframe", "captures a still frame", func(path string) {
|
||||
singleton.captureState = captureStateFrame
|
||||
singleton.capturePath = path
|
||||
singleton.captureFrames = nil
|
||||
})
|
||||
d2term.BindAction("capgifstart", "captures an animation (start)", func(path string) {
|
||||
singleton.captureState = captureStateGif
|
||||
singleton.capturePath = path
|
||||
singleton.captureFrames = nil
|
||||
})
|
||||
d2term.BindAction("capgifstop", "captures an animation (stop)", func() {
|
||||
singleton.captureState = captureStateNone
|
||||
})
|
||||
d2term.BindAction("vsync", "toggles vsync", func() {
|
||||
vsync := !d2render.GetVSyncEnabled()
|
||||
d2render.SetVSyncEnabled(vsync)
|
||||
@ -212,14 +242,100 @@ func render(target d2render.Surface) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d2term.Render(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := renderDebug(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := renderCapture(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d2term.Render(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderCapture(target d2render.Surface) error {
|
||||
cleanupCapture := func() {
|
||||
singleton.captureState = captureStateNone
|
||||
singleton.capturePath = ""
|
||||
singleton.captureFrames = nil
|
||||
}
|
||||
|
||||
switch singleton.captureState {
|
||||
case captureStateFrame:
|
||||
defer cleanupCapture()
|
||||
|
||||
fp, err := os.Create(singleton.capturePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer fp.Close()
|
||||
|
||||
screenshot := target.Screenshot()
|
||||
if err := png.Encode(fp, screenshot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("saved frame to %s", singleton.capturePath)
|
||||
break
|
||||
case captureStateGif:
|
||||
screenshot := target.Screenshot()
|
||||
singleton.captureFrames = append(singleton.captureFrames, screenshot)
|
||||
break
|
||||
case captureStateNone:
|
||||
if len(singleton.captureFrames) > 0 {
|
||||
defer cleanupCapture()
|
||||
|
||||
fp, err := os.Create(singleton.capturePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer fp.Close()
|
||||
|
||||
var (
|
||||
framesTotal = len(singleton.captureFrames)
|
||||
framesPal = make([]*image.Paletted, framesTotal)
|
||||
frameDelays = make([]int, framesTotal)
|
||||
framesPerCpu = framesTotal / runtime.NumCPU()
|
||||
)
|
||||
|
||||
var waitGroup sync.WaitGroup
|
||||
for i := 0; i < framesTotal; i += framesPerCpu {
|
||||
waitGroup.Add(1)
|
||||
go func(start, end int) {
|
||||
defer waitGroup.Done()
|
||||
|
||||
for j := start; j < end; j++ {
|
||||
var buffer bytes.Buffer
|
||||
if err := gif.Encode(&buffer, singleton.captureFrames[j], nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
framePal, err := gif.Decode(&buffer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
framesPal[j] = framePal.(*image.Paletted)
|
||||
frameDelays[j] = 5
|
||||
}
|
||||
}(i, d2common.MinInt(i+framesPerCpu, framesTotal))
|
||||
}
|
||||
|
||||
waitGroup.Wait()
|
||||
|
||||
if err := gif.EncodeAll(fp, &gif.GIF{Image: framesPal, Delay: frameDelays}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("saved animation to %s", singleton.capturePath)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user