mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-02 20:18:20 -04:00
screenshotting improvements for OpenGL 3.1
OpenGL 2.1 renderer does not support screenshotting
This commit is contained in:
parent
08a7695d01
commit
821ff26a79
|
@ -176,7 +176,10 @@ func (rnd *gl21) render() {
|
|||
gl.Scissor(lastScissorBox[0], lastScissorBox[1], lastScissorBox[2], lastScissorBox[3])
|
||||
}
|
||||
|
||||
func (rnd *gl21) screenshot(mode screenshotMode, filenameSuffix string) {
|
||||
func (rnd *gl21) screenshot(mode screenshotMode, finish chan screenshotResult) {
|
||||
finish <- screenshotResult{
|
||||
err: fmt.Errorf("gl21: renderer does not support screenshotting"),
|
||||
}
|
||||
}
|
||||
|
||||
func (rnd *gl21) addTexture(_ textureType, linear bool, clamp bool) texture {
|
||||
|
|
|
@ -217,8 +217,8 @@ func (rnd *gl32) render() {
|
|||
gl.DeleteVertexArrays(1, &vaoHandle)
|
||||
}
|
||||
|
||||
func (rnd *gl32) screenshot(mode screenshotMode, filenameSuffix string) {
|
||||
rnd.shaders[texturePlayscr].(*playscrShader).screenshot.startProcess(modeSingle, filenameSuffix)
|
||||
func (rnd *gl32) screenshot(mode screenshotMode, finish chan screenshotResult) {
|
||||
rnd.shaders[texturePlayscr].(*playscrShader).screenshot.start(mode, finish)
|
||||
}
|
||||
|
||||
// glState stores GL state with the intention of restoration after a short period.
|
||||
|
|
|
@ -25,14 +25,14 @@ import (
|
|||
type playscrShader struct {
|
||||
img *SdlImgui
|
||||
crt *crtSequencer
|
||||
screenshot *screenshotSequencer
|
||||
screenshot *gl32Screenshot
|
||||
}
|
||||
|
||||
func newPlayscrShader(img *SdlImgui) shaderProgram {
|
||||
sh := &playscrShader{
|
||||
img: img,
|
||||
crt: newCRTSequencer(img),
|
||||
screenshot: newScreenshotSequencer(img),
|
||||
screenshot: newGl32Screenshot(img),
|
||||
}
|
||||
return sh
|
||||
}
|
||||
|
|
|
@ -21,37 +21,29 @@ import (
|
|||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/jpeg"
|
||||
"os"
|
||||
|
||||
"github.com/go-gl/gl/v3.2-core/gl"
|
||||
"github.com/jetsetilly/gopher2600/gui/sdlimgui/framebuffer"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
||||
"github.com/jetsetilly/gopher2600/logger"
|
||||
"github.com/jetsetilly/gopher2600/resources/unique"
|
||||
)
|
||||
|
||||
type screenshotFinalise func(shaderEnvironment)
|
||||
|
||||
type screenshotSequencer struct {
|
||||
type gl32Screenshot struct {
|
||||
img *SdlImgui
|
||||
crt *crtSequencer
|
||||
|
||||
// when the screenshot process is finished. the channel is sent to the
|
||||
// startProcess() function
|
||||
finish chan screenshotResult
|
||||
|
||||
// the description of the screenshot to be returned over the finish channel
|
||||
// as part of screenshotResult
|
||||
description string
|
||||
|
||||
// the screenshot mode we're working with
|
||||
mode screenshotMode
|
||||
|
||||
// we don't want to start a new process until the working channel is empty
|
||||
working chan bool
|
||||
|
||||
// the number of frames required for the screenshot processing. is reduced
|
||||
// by onw every frame down to zero
|
||||
workingCt int
|
||||
|
||||
// finalise function
|
||||
finalise chan screenshotFinalise
|
||||
|
||||
// name to use for saved file
|
||||
savePath string
|
||||
// the number of frames required for the screenshot processing
|
||||
frames int
|
||||
|
||||
// for composited screenshots we need to sharpen the shader manually
|
||||
compositeSharpen shaderProgram
|
||||
|
@ -61,19 +53,22 @@ type screenshotSequencer struct {
|
|||
|
||||
// list of exposures. used to create a composited image
|
||||
compositeExposures []*image.RGBA
|
||||
|
||||
// finalisation of sequence. the function will be called in the main
|
||||
// goroutine so this is used for the composite process
|
||||
finalise chan func(shaderEnvironment) *image.RGBA
|
||||
}
|
||||
|
||||
// returns texture ID and the width and height of the texture
|
||||
func (sh *screenshotSequencer) textureSpec() (uint32, float32, float32) {
|
||||
func (sh *gl32Screenshot) textureSpec() (uint32, float32, float32) {
|
||||
width, height := sh.compositeBuffer.Dimensions()
|
||||
return sh.compositeBuffer.Texture(0), float32(width), float32(height)
|
||||
}
|
||||
|
||||
func newScreenshotSequencer(img *SdlImgui) *screenshotSequencer {
|
||||
sh := &screenshotSequencer{
|
||||
func newGl32Screenshot(img *SdlImgui) *gl32Screenshot {
|
||||
sh := &gl32Screenshot{
|
||||
img: img,
|
||||
working: make(chan bool, 1),
|
||||
finalise: make(chan screenshotFinalise, 1),
|
||||
finalise: make(chan func(shaderEnvironment) *image.RGBA, 1),
|
||||
|
||||
compositeBuffer: framebuffer.NewSequence(1),
|
||||
compositeSharpen: newSharpenShader(true),
|
||||
|
@ -82,7 +77,7 @@ func newScreenshotSequencer(img *SdlImgui) *screenshotSequencer {
|
|||
return sh
|
||||
}
|
||||
|
||||
func (sh *screenshotSequencer) destroy() {
|
||||
func (sh *gl32Screenshot) destroy() {
|
||||
sh.compositeBuffer.Destroy()
|
||||
sh.compositeSharpen.destroy()
|
||||
sh.crt.destroy()
|
||||
|
@ -90,61 +85,63 @@ func (sh *screenshotSequencer) destroy() {
|
|||
|
||||
// filenameSuffix will be appended to the short filename of the cartridge. if
|
||||
// the string is empty then the default suffix is used
|
||||
func (sh *screenshotSequencer) startProcess(mode screenshotMode, filenameSuffix string) {
|
||||
func (sh *gl32Screenshot) start(mode screenshotMode, finish chan screenshotResult) {
|
||||
// begin screenshot process if possible
|
||||
select {
|
||||
case sh.working <- true:
|
||||
default:
|
||||
logger.Log("screenshot", "previous screenshotting still in progress")
|
||||
if sh.finish != nil {
|
||||
finish <- screenshotResult{
|
||||
err: fmt.Errorf("previous screenshotting still in progress"),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// note the channel to use on screenshot completion
|
||||
sh.finish = finish
|
||||
|
||||
// mode of screenshot
|
||||
sh.mode = mode
|
||||
switch mode {
|
||||
|
||||
// description of screenshot to be returned to caller over finish channel
|
||||
if sh.img.crtPrefs.Enabled.Get().(bool) {
|
||||
sh.description = fmt.Sprintf("crt_%s", sh.mode)
|
||||
} else {
|
||||
sh.description = fmt.Sprintf("pix_%s", sh.mode)
|
||||
}
|
||||
|
||||
switch sh.mode {
|
||||
case modeSingle:
|
||||
// single screenshot mode requires just one working frame
|
||||
sh.workingCt = 1
|
||||
sh.frames = 1
|
||||
case modeMotion:
|
||||
// a generous working frame count is required for motion screenshots so that
|
||||
// large phosphor values have time to accumulate
|
||||
sh.workingCt = 20
|
||||
sh.frames = 20
|
||||
case modeComposite:
|
||||
// a working count of six is good because it is divisible by both two
|
||||
// and three. this means that screens with flickering elements of both
|
||||
// two and three will work well
|
||||
sh.workingCt = 6
|
||||
sh.frames = 6
|
||||
}
|
||||
|
||||
sh.crt.flushPhosphor()
|
||||
sh.compositeExposures = sh.compositeExposures[:0]
|
||||
|
||||
// prepare file path for when the image needs to be saved
|
||||
if len(filenameSuffix) == 0 {
|
||||
if sh.img.crtPrefs.Enabled.Get().(bool) {
|
||||
sh.savePath = unique.Filename(fmt.Sprintf("crt_%s", mode), sh.img.cache.VCS.Mem.Cart.ShortName)
|
||||
} else {
|
||||
sh.savePath = unique.Filename(fmt.Sprintf("pix_%s", mode), sh.img.cache.VCS.Mem.Cart.ShortName)
|
||||
}
|
||||
} else {
|
||||
sh.savePath = fmt.Sprintf("%s_%s", sh.img.cache.VCS.Mem.Cart.ShortName, filenameSuffix)
|
||||
}
|
||||
sh.savePath = fmt.Sprintf("%s.jpg", sh.savePath)
|
||||
}
|
||||
|
||||
func (sh *screenshotSequencer) process(env shaderEnvironment, scalingImage textureSpec) {
|
||||
if sh.workingCt <= 0 {
|
||||
func (sh *gl32Screenshot) process(env shaderEnvironment, scalingImage textureSpec) {
|
||||
if sh.frames <= 0 {
|
||||
select {
|
||||
case f := <-sh.finalise:
|
||||
f(env)
|
||||
sh.finish <- screenshotResult{
|
||||
description: sh.description,
|
||||
image: f(env),
|
||||
}
|
||||
sh.finish = nil
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// if working channel is empty then make sure exposure is zero
|
||||
if len(sh.working) == 0 {
|
||||
logger.Log("screenshot", "recovering after a failed screenshot")
|
||||
sh.workingCt = 0
|
||||
// make sure workingCt is zero if the working channel is empty
|
||||
sh.frames = 0
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
switch sh.mode {
|
||||
|
@ -155,11 +152,11 @@ func (sh *screenshotSequencer) process(env shaderEnvironment, scalingImage textu
|
|||
}
|
||||
}
|
||||
|
||||
func (sh *screenshotSequencer) crtProcess(env shaderEnvironment, scalingImage textureSpec) {
|
||||
func (sh *gl32Screenshot) crtProcess(env shaderEnvironment, scalingImage textureSpec) {
|
||||
prefs := newCrtSeqPrefs(sh.img.crtPrefs)
|
||||
|
||||
if sh.mode == modeMotion {
|
||||
switch sh.workingCt {
|
||||
switch sh.frames {
|
||||
case 1:
|
||||
prefs.PixelPerfectFade = 1.0
|
||||
prefs.PhosphorLatency = 1.0
|
||||
|
@ -178,15 +175,17 @@ func (sh *screenshotSequencer) crtProcess(env shaderEnvironment, scalingImage te
|
|||
sh.img.playScr, prefs)
|
||||
|
||||
// reduce exposure count and return if there is still more to do
|
||||
sh.workingCt--
|
||||
if sh.workingCt > 0 {
|
||||
sh.frames--
|
||||
if sh.frames > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
final := image.NewRGBA(image.Rect(0, 0, int(env.width), int(env.height)))
|
||||
if final == nil {
|
||||
logger.Log("screenshot", "save failed: cannot allocate image data")
|
||||
<-sh.working
|
||||
sh.finish <- screenshotResult{
|
||||
err: fmt.Errorf("save failed: cannot allocate image data"),
|
||||
}
|
||||
sh.finish = nil
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -194,18 +193,21 @@ func (sh *screenshotSequencer) crtProcess(env shaderEnvironment, scalingImage te
|
|||
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureID, 0)
|
||||
gl.ReadPixels(0, 0, env.width, env.height, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(final.Pix))
|
||||
|
||||
sh.finalise <- func(_ shaderEnvironment) {
|
||||
saveJPEG(final, sh.savePath)
|
||||
<-sh.working
|
||||
sh.finish <- screenshotResult{
|
||||
description: sh.description,
|
||||
image: final,
|
||||
}
|
||||
sh.finish = nil
|
||||
}
|
||||
|
||||
func (sh *screenshotSequencer) compositeProcess(env shaderEnvironment, scalingImage textureSpec) {
|
||||
func (sh *gl32Screenshot) compositeProcess(env shaderEnvironment, scalingImage textureSpec) {
|
||||
// set up frame buffer. if dimensions have changed refuse to continue with
|
||||
// screenshot processing
|
||||
if sh.compositeBuffer.Setup(env.width, env.height) {
|
||||
logger.Log("screenshot", "save failed: emulation window has changed dimensions")
|
||||
<-sh.working
|
||||
sh.finish <- screenshotResult{
|
||||
err: fmt.Errorf("save failed: emulation window has changed dimensions"),
|
||||
}
|
||||
sh.finish = nil
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -218,8 +220,10 @@ func (sh *screenshotSequencer) compositeProcess(env shaderEnvironment, scalingIm
|
|||
// retrieve exposure
|
||||
newExposure := image.NewRGBA(image.Rect(0, 0, int(env.width), int(env.height)))
|
||||
if newExposure == nil {
|
||||
logger.Log("screenshot", "save failed: cannot allocate image data")
|
||||
<-sh.working
|
||||
sh.finish <- screenshotResult{
|
||||
err: fmt.Errorf("save failed: cannot allocate image data"),
|
||||
}
|
||||
sh.finish = nil
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -231,44 +235,43 @@ func (sh *screenshotSequencer) compositeProcess(env shaderEnvironment, scalingIm
|
|||
sh.compositeExposures = append(sh.compositeExposures, newExposure)
|
||||
|
||||
// reduce exposure count and return if there is still more to do
|
||||
sh.workingCt--
|
||||
if sh.workingCt > 0 {
|
||||
sh.frames--
|
||||
if sh.frames > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// composite exposures. we can do this in a separate goroutine. send result
|
||||
// over the composite channel
|
||||
// composite exposures. we can do this in a separate goroutine. doing it in
|
||||
// the main thread causes a noticeable pause in the emulation
|
||||
//
|
||||
// note however that the final composition step must be conducting in the
|
||||
// main goroutine so we make use of the finalise channel
|
||||
go func() {
|
||||
var composite *image.RGBA
|
||||
composite, err := sh.compositeAssemble()
|
||||
|
||||
switch len(sh.compositeExposures) {
|
||||
case 0:
|
||||
logger.Logf("screenshot", "composition: exposure list is empty")
|
||||
case 1:
|
||||
// if there is only one exposure then the composition is by
|
||||
// defintion already completed
|
||||
composite = sh.compositeExposures[0]
|
||||
default:
|
||||
var err error
|
||||
composite, err = sh.compositeAssemble()
|
||||
if err != nil {
|
||||
logger.Logf("screenshot", err.Error())
|
||||
composite = nil
|
||||
if err != nil {
|
||||
sh.finish <- screenshotResult{
|
||||
err: fmt.Errorf("save failed: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
if composite == nil {
|
||||
<-sh.working
|
||||
sh.finish = nil
|
||||
return
|
||||
}
|
||||
|
||||
sh.finalise <- func(env shaderEnvironment) {
|
||||
sh.compositeFinalise(env, composite)
|
||||
sh.finalise <- func(env shaderEnvironment) *image.RGBA {
|
||||
return sh.compositeFinalise(env, composite)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (sh *screenshotSequencer) compositeAssemble() (*image.RGBA, error) {
|
||||
func (sh *gl32Screenshot) compositeAssemble() (*image.RGBA, error) {
|
||||
switch len(sh.compositeExposures) {
|
||||
case 0:
|
||||
return nil, fmt.Errorf("composition: exposure list is empty")
|
||||
case 1:
|
||||
// if there is only one exposure then the composition is by
|
||||
// defintion already completed
|
||||
return sh.compositeExposures[0], nil
|
||||
}
|
||||
|
||||
rgba := image.NewRGBA(sh.compositeExposures[0].Rect)
|
||||
if rgba == nil {
|
||||
return nil, fmt.Errorf("composition: cannot allocate image data")
|
||||
|
@ -296,18 +299,6 @@ func (sh *screenshotSequencer) compositeAssemble() (*image.RGBA, error) {
|
|||
return uint8(A + B)
|
||||
}
|
||||
|
||||
// const brightnessAdjust = 1.20
|
||||
// ratio := brightnessAdjust / float64(len(sh.compositeExposures))
|
||||
// accumulate := func(a uint8, b uint8) uint8 {
|
||||
// // being careful not to overflow the uint8
|
||||
// B := int(float64(b) * ratio)
|
||||
// C := int(a) + B
|
||||
// if C > 255 {
|
||||
// return 255
|
||||
// }
|
||||
// return uint8(C)
|
||||
// }
|
||||
|
||||
for y := 0; y < int(height); y++ {
|
||||
for x := 0; x < int(width); x++ {
|
||||
ep := rgba.RGBAAt(x, y)
|
||||
|
@ -333,7 +324,7 @@ func (sh *screenshotSequencer) compositeAssemble() (*image.RGBA, error) {
|
|||
}
|
||||
|
||||
// finalise composite by passing it through the CRT shaders and then saving the image
|
||||
func (sh *screenshotSequencer) compositeFinalise(env shaderEnvironment, composite *image.RGBA) {
|
||||
func (sh *gl32Screenshot) compositeFinalise(env shaderEnvironment, composite *image.RGBA) *image.RGBA {
|
||||
// copy composite pixels to framebuffer texture
|
||||
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, int32(composite.Stride)/4)
|
||||
gl.BindTexture(gl.TEXTURE_2D, sh.compositeBuffer.Texture(0))
|
||||
|
@ -354,34 +345,5 @@ func (sh *screenshotSequencer) compositeFinalise(env shaderEnvironment, composit
|
|||
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureID, 0)
|
||||
gl.ReadPixels(0, 0, env.width, env.height, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(composite.Pix))
|
||||
|
||||
// save composite image to file
|
||||
go func() {
|
||||
saveJPEG(composite, sh.savePath)
|
||||
<-sh.working
|
||||
}()
|
||||
}
|
||||
|
||||
// saveJPEG writes the texture to the specified path.
|
||||
func saveJPEG(rgba *image.RGBA, path string) {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
logger.Logf("screenshot", "save failed: %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = jpeg.Encode(f, rgba, &jpeg.Options{Quality: 100})
|
||||
if err != nil {
|
||||
logger.Logf("screenshot", "save failed: %v", err.Error())
|
||||
_ = f.Close()
|
||||
return
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
logger.Logf("screenshot", "save failed: %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// indicate success
|
||||
logger.Logf("screenshot", "saved: %s", path)
|
||||
return composite
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ type renderer interface {
|
|||
destroy()
|
||||
preRender()
|
||||
render()
|
||||
screenshot(mode screenshotMode, filenameSuffix string)
|
||||
screenshot(mode screenshotMode, finish chan screenshotResult)
|
||||
addTexture(typ textureType, linear bool, clamp bool) texture
|
||||
addFontTexture(fnt imgui.FontAtlas) texture
|
||||
}
|
||||
|
@ -65,3 +65,14 @@ const (
|
|||
modeMotion screenshotMode = "motion"
|
||||
modeComposite screenshotMode = "composite"
|
||||
)
|
||||
|
||||
type screenshotResult struct {
|
||||
// a description of the screenshot as provided by the renderer
|
||||
description string
|
||||
|
||||
// the final image
|
||||
image *image.RGBA
|
||||
|
||||
// any errors that were encountered in the screenshotting preperation
|
||||
err error
|
||||
}
|
||||
|
|
|
@ -148,9 +148,9 @@ func (img *SdlImgui) serviceSetFeature(request featureRequest) {
|
|||
case gui.ReqScreenshot:
|
||||
switch len(request.args) {
|
||||
case 0:
|
||||
img.rnd.screenshot(modeSingle, "")
|
||||
img.screenshot(modeSingle, "")
|
||||
case 1:
|
||||
img.rnd.screenshot(modeSingle, request.args[0].(string))
|
||||
img.screenshot(modeSingle, request.args[0].(string))
|
||||
default:
|
||||
err = fmt.Errorf("wrong number of arguments (%d instead of 1 or zero)", len(request.args))
|
||||
}
|
||||
|
|
83
gui/sdlimgui/screenshot.go
Normal file
83
gui/sdlimgui/screenshot.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
// This file is part of Gopher2600.
|
||||
//
|
||||
// Gopher2600 is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Gopher2600 is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package sdlimgui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"os"
|
||||
|
||||
"github.com/jetsetilly/gopher2600/logger"
|
||||
"github.com/jetsetilly/gopher2600/resources/unique"
|
||||
)
|
||||
|
||||
func (img *SdlImgui) screenshot(mode screenshotMode, filenameSuffix string) {
|
||||
finish := make(chan screenshotResult, 1)
|
||||
img.rnd.screenshot(mode, finish)
|
||||
|
||||
// we'll be waiting on the screenshot completion in another goroutine and we
|
||||
// don't want to be accessing the cache from there. so we make a copy of the
|
||||
// cartridge name value
|
||||
cartName := img.cache.VCS.Mem.Cart.ShortName
|
||||
|
||||
go func() {
|
||||
// wait for result and log any errors
|
||||
res := <-finish
|
||||
if res.err != nil {
|
||||
logger.Log("screenshot", res.err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// prepare file path for when the image needs to be saved
|
||||
var path string
|
||||
|
||||
if len(filenameSuffix) == 0 {
|
||||
path = unique.Filename(res.description, cartName)
|
||||
} else {
|
||||
path = fmt.Sprintf("%s_%s", cartName, filenameSuffix)
|
||||
}
|
||||
path = fmt.Sprintf("%s.jpg", path)
|
||||
|
||||
// save image to file as a JPEG
|
||||
saveJPEG(res.image, path)
|
||||
}()
|
||||
}
|
||||
|
||||
// saveJPEG writes the texture to the specified path.
|
||||
func saveJPEG(rgba *image.RGBA, path string) {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
logger.Logf("screenshot", "save failed: %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = jpeg.Encode(f, rgba, &jpeg.Options{Quality: 100})
|
||||
if err != nil {
|
||||
logger.Logf("screenshot", "save failed: %v", err.Error())
|
||||
_ = f.Close()
|
||||
return
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
logger.Logf("screenshot", "save failed: %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// indicate success
|
||||
logger.Logf("screenshot", "saved: %s", path)
|
||||
}
|
|
@ -150,11 +150,11 @@ func (img *SdlImgui) serviceKeyboard(ev *sdl.KeyboardEvent) {
|
|||
|
||||
case sdl.SCANCODE_F12:
|
||||
if ctrl && !shift {
|
||||
img.rnd.screenshot(modeComposite, "")
|
||||
img.screenshot(modeComposite, "")
|
||||
} else if shift && !ctrl {
|
||||
img.rnd.screenshot(modeMotion, "")
|
||||
img.screenshot(modeMotion, "")
|
||||
} else {
|
||||
img.rnd.screenshot(modeSingle, "")
|
||||
img.screenshot(modeSingle, "")
|
||||
}
|
||||
|
||||
img.playScr.emulationNotice.set(notifications.NotifyScreenshot)
|
||||
|
|
Loading…
Reference in a new issue