mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-02 20:18:20 -04:00
improved thumbnailing
This commit is contained in:
parent
5c7e122d3e
commit
745e977ef2
|
@ -468,11 +468,7 @@ func (scr *screen) NewFrame(frameInfo television.FrameInfo) error {
|
|||
// prevent ugly screen updates while the resizer is finding the correct
|
||||
// size. in the case of PAL60 ROMs in particular, the screen can resize
|
||||
// dramatically
|
||||
scr.crit.resize = scr.crit.frameInfo.Stable &&
|
||||
(scr.crit.resize ||
|
||||
scr.crit.frameInfo.Spec.ID != frameInfo.Spec.ID ||
|
||||
scr.crit.frameInfo.VisibleTop != frameInfo.VisibleTop ||
|
||||
scr.crit.frameInfo.VisibleBottom != frameInfo.VisibleBottom)
|
||||
scr.crit.resize = scr.crit.frameInfo.Stable && (scr.crit.resize || scr.crit.frameInfo.IsDifferent(frameInfo))
|
||||
|
||||
// record frame info
|
||||
scr.crit.frameInfo = frameInfo
|
||||
|
|
|
@ -17,10 +17,13 @@ package sdlimgui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/image/draw"
|
||||
|
||||
"github.com/inkyblackness/imgui-go/v4"
|
||||
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
||||
|
@ -52,6 +55,9 @@ type winSelectROM struct {
|
|||
|
||||
thmb *thumbnailer.Anim
|
||||
thmbTexture texture
|
||||
|
||||
thmbImage *image.RGBA
|
||||
thmbDimensions image.Point
|
||||
}
|
||||
|
||||
func newSelectROM(img *SdlImgui) (window, error) {
|
||||
|
@ -76,6 +82,8 @@ func newSelectROM(img *SdlImgui) (window, error) {
|
|||
}
|
||||
|
||||
win.thmbTexture = img.rnd.addTexture(textureColor, true, true)
|
||||
win.thmbImage = image.NewRGBA(image.Rect(0, 0, specification.ClksVisible, specification.AbsoluteMaxScanlines))
|
||||
win.thmbDimensions = win.thmbImage.Bounds().Size()
|
||||
|
||||
return win, nil
|
||||
}
|
||||
|
@ -136,7 +144,7 @@ func (win *winSelectROM) playmodeDraw() bool {
|
|||
}
|
||||
|
||||
posFlgs := imgui.ConditionAppearing
|
||||
winFlgs := imgui.WindowFlagsAlwaysAutoResize | imgui.WindowFlagsNoSavedSettings
|
||||
winFlgs := imgui.WindowFlagsNoSavedSettings | imgui.WindowFlagsAlwaysAutoResize
|
||||
|
||||
imgui.SetNextWindowPosV(imgui.Vec2{75, 75}, posFlgs, imgui.Vec2{0, 0})
|
||||
|
||||
|
@ -186,10 +194,25 @@ func (win *winSelectROM) debuggerDraw() bool {
|
|||
func (win *winSelectROM) render() {
|
||||
// receive new thumbnail data and copy to texture
|
||||
select {
|
||||
case image := <-win.thmb.Render:
|
||||
if image != nil {
|
||||
win.thmbTexture.markForCreation()
|
||||
win.thmbTexture.render(image)
|
||||
case newImage := <-win.thmb.Render:
|
||||
if newImage != nil {
|
||||
// clear image
|
||||
for i := 0; i < len(win.thmbImage.Pix); i += 4 {
|
||||
s := win.thmbImage.Pix[i : i+4 : i+4]
|
||||
s[0] = 10
|
||||
s[1] = 10
|
||||
s[2] = 10
|
||||
s[3] = 255
|
||||
}
|
||||
|
||||
// copy new image so that it is centred in the thumbnail image
|
||||
sz := newImage.Bounds().Size()
|
||||
y := ((win.thmbDimensions.Y - sz.Y) / 2)
|
||||
draw.Copy(win.thmbImage, image.Point{X: 0, Y: y},
|
||||
newImage, newImage.Bounds(), draw.Over, nil)
|
||||
|
||||
// render image
|
||||
win.thmbTexture.render(win.thmbImage)
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
@ -214,6 +237,9 @@ func (win *winSelectROM) draw() {
|
|||
imgui.Text(win.currPath)
|
||||
|
||||
if imgui.BeginTable("romSelector", 2) {
|
||||
imgui.TableSetupColumnV("filelist", imgui.TableColumnFlagsWidthStretch, -1, 0)
|
||||
imgui.TableSetupColumnV("thumbnail", imgui.TableColumnFlagsWidthStretch, -1, 1)
|
||||
|
||||
imgui.TableNextRow()
|
||||
imgui.TableNextColumn()
|
||||
|
||||
|
@ -303,23 +329,25 @@ func (win *winSelectROM) draw() {
|
|||
imgui.EndChild()
|
||||
|
||||
imgui.TableNextColumn()
|
||||
imgui.Image(imgui.TextureID(win.thmbTexture.getID()), imgui.Vec2{specification.ClksVisible * 3, specification.AbsoluteMaxScanlines})
|
||||
|
||||
imguiSeparator()
|
||||
if win.thmb.IsEmulating() {
|
||||
imgui.Text(win.thmb.String())
|
||||
} else {
|
||||
imgui.Text("")
|
||||
}
|
||||
imgui.Image(imgui.TextureID(win.thmbTexture.getID()),
|
||||
imgui.Vec2{float32(win.thmbDimensions.X) * 2, float32(win.thmbDimensions.Y)})
|
||||
|
||||
imgui.EndTable()
|
||||
}
|
||||
|
||||
// control buttons. start controlHeight measurement
|
||||
win.controlHeight = imguiMeasureHeight(func() {
|
||||
if win.thmb.IsEmulating() {
|
||||
imgui.Text(win.thmb.String())
|
||||
} else {
|
||||
imgui.Text("")
|
||||
}
|
||||
|
||||
imguiSeparator()
|
||||
|
||||
imgui.Checkbox("Show all files", &win.showAllFiles)
|
||||
imgui.SameLine()
|
||||
imgui.Checkbox("Show hidden entries", &win.showHidden)
|
||||
imgui.Checkbox("Show hidden files", &win.showHidden)
|
||||
|
||||
imgui.Spacing()
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ package sdlimgui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"os"
|
||||
|
||||
"github.com/inkyblackness/imgui-go/v4"
|
||||
|
@ -25,6 +26,7 @@ import (
|
|||
"github.com/jetsetilly/gopher2600/logger"
|
||||
"github.com/jetsetilly/gopher2600/resources/unique"
|
||||
"github.com/jetsetilly/gopher2600/thumbnailer"
|
||||
"golang.org/x/image/draw"
|
||||
)
|
||||
|
||||
const winTimelineID = "Timeline"
|
||||
|
@ -38,7 +40,10 @@ type winTimeline struct {
|
|||
// goroutine so we must thumbnail those states in the same goroutine.
|
||||
thmb *thumbnailer.Image
|
||||
thmbTexture texture
|
||||
thmbValid bool
|
||||
|
||||
// the backing image for the texture
|
||||
thmbImage *image.RGBA
|
||||
thmbDimensions image.Point
|
||||
|
||||
// whether the thumbnail is being shown on the left of the timeline rather
|
||||
// than the right
|
||||
|
@ -91,6 +96,8 @@ func newWinTimeline(img *SdlImgui) (window, error) {
|
|||
}
|
||||
|
||||
win.thmbTexture = img.rnd.addTexture(textureColor, true, true)
|
||||
win.thmbImage = image.NewRGBA(image.Rect(0, 0, specification.ClksVisible, specification.AbsoluteMaxScanlines))
|
||||
win.thmbDimensions = win.thmbImage.Bounds().Size()
|
||||
|
||||
return win, nil
|
||||
}
|
||||
|
@ -111,11 +118,23 @@ const timelinePopupID = "timelinePopupID"
|
|||
func (win *winTimeline) debuggerDraw() bool {
|
||||
// receive new thumbnail data and copy to texture
|
||||
select {
|
||||
case image := <-win.thmb.Render:
|
||||
if image != nil {
|
||||
win.thmbTexture.markForCreation()
|
||||
win.thmbTexture.render(image)
|
||||
win.thmbValid = true
|
||||
case newImage := <-win.thmb.Render:
|
||||
if newImage != nil {
|
||||
// clear image
|
||||
for i := 0; i < len(win.thmbImage.Pix); i += 4 {
|
||||
s := win.thmbImage.Pix[i : i+4 : i+4]
|
||||
s[0] = 10
|
||||
s[1] = 10
|
||||
s[2] = 10
|
||||
s[3] = 255
|
||||
}
|
||||
|
||||
// copy new image so that it is centred in the thumbnail image
|
||||
sz := newImage.Bounds().Size()
|
||||
y := ((win.thmbDimensions.Y - sz.Y) / 2)
|
||||
draw.Copy(win.thmbImage, image.Point{X: 0, Y: y},
|
||||
newImage, newImage.Bounds(), draw.Over, nil)
|
||||
win.thmbTexture.render(win.thmbImage)
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
@ -188,7 +207,6 @@ func (win *winTimeline) debuggerDraw() bool {
|
|||
// thumbnailer must be run in the same goroutine as the main emulation
|
||||
win.thmb.Create(win.img.dbg.Rewind.GetState(hoverFrame))
|
||||
<-win.thmbRunning
|
||||
win.thmbValid = false
|
||||
})
|
||||
default:
|
||||
// if a thumbnail is currently being generated then we need to
|
||||
|
@ -334,8 +352,9 @@ func (win *winTimeline) drawTrace() {
|
|||
imgui.Vec2{X: win.hoverX + cursorWidth/2, Y: rootPos.Y + traceSize.Y},
|
||||
win.img.cols.timelineHoverCursor)
|
||||
|
||||
if win.thmbValid && win.img.prefs.showTimelineThumbnail.Get().(bool) {
|
||||
sz := imgui.Vec2{X: specification.ClksVisible * 3, Y: specification.AbsoluteMaxScanlines}.Times(traceSize.Y / specification.AbsoluteMaxScanlines)
|
||||
if win.img.prefs.showTimelineThumbnail.Get().(bool) {
|
||||
sz := imgui.Vec2{float32(win.thmbDimensions.X) * 2, float32(win.thmbDimensions.Y)}
|
||||
sz = sz.Times(traceSize.Y / specification.AbsoluteMaxScanlines)
|
||||
|
||||
// show thumbnail on either the left or right of the timeline window
|
||||
var pos imgui.Vec2
|
||||
|
|
|
@ -118,6 +118,14 @@ func (info FrameInfo) Crop() image.Rectangle {
|
|||
)
|
||||
}
|
||||
|
||||
// IsDifferent returns true if any of the pertinent display information is
|
||||
// different between the two copies of FrameInfo
|
||||
func (info FrameInfo) IsDifferent(cmp FrameInfo) bool {
|
||||
return info.Spec.ID != cmp.Spec.ID ||
|
||||
info.VisibleTop != cmp.VisibleTop ||
|
||||
info.VisibleBottom != cmp.VisibleBottom
|
||||
}
|
||||
|
||||
func (info *FrameInfo) reset() {
|
||||
info.VisibleTop = info.Spec.AtariSafeVisibleTop
|
||||
info.VisibleBottom = info.Spec.AtariSafeVisibleBottom
|
||||
|
|
|
@ -29,11 +29,12 @@ import (
|
|||
// the number of synced frames where we can expect things to be in flux.
|
||||
const leadingFrames = 1
|
||||
|
||||
// the number of synced frames required before the tv is considered to be
|
||||
// "stable". once the tv is stable then specification switching cannot happen.
|
||||
// the number of synced frames required before the TV is considered to be
|
||||
// stable. once the tv is stable then specification switching cannot happen.
|
||||
//
|
||||
// resizing can still happen however. so this value is important if we don't
|
||||
// want to see the screen jump on ROM startup
|
||||
// resizing also works a little differently before the TV is stable. the size of
|
||||
// the screen can shrink as well as grow. once the stability threshold is
|
||||
// reached, the screen can only grow
|
||||
const stabilityThreshold = 6
|
||||
|
||||
// State encapsulates the television values that can change from moment to
|
||||
|
|
|
@ -83,11 +83,7 @@ func NewAnim(prefs *preferences.Preferences) (*Anim, error) {
|
|||
return nil, fmt.Errorf("thumbnailer: %w", err)
|
||||
}
|
||||
thmb.vcs.Env.Label = thumbnailerEnv
|
||||
|
||||
thmb.img = image.NewRGBA(image.Rect(0, 0, specification.ClksScanline, specification.AbsoluteMaxScanlines))
|
||||
|
||||
// start with a NTSC television as default
|
||||
thmb.Resize(television.NewFrameInfo(specification.SpecNTSC))
|
||||
thmb.Reset()
|
||||
|
||||
return thmb, nil
|
||||
|
@ -231,21 +227,16 @@ func (thmb *Anim) CartYield(yield coprocessor.CoProcYieldType) coprocessor.Yield
|
|||
return coprocessor.YieldHookEnd
|
||||
}
|
||||
|
||||
// Resize implements the television.PixelRenderer interface
|
||||
func (thmb *Anim) Resize(frameInfo television.FrameInfo) error {
|
||||
func (thmb *Anim) resize(frameInfo television.FrameInfo, force bool) {
|
||||
if thmb.frameInfo.IsDifferent(frameInfo) && (force || frameInfo.Stable) {
|
||||
thmb.cropImg = thmb.img.SubImage(frameInfo.Crop()).(*image.RGBA)
|
||||
}
|
||||
thmb.frameInfo = frameInfo
|
||||
crop := thmb.frameInfo.Crop()
|
||||
thmb.cropImg = thmb.img.SubImage(crop).(*image.RGBA)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewFrame implements the television.PixelRenderer interface
|
||||
func (thmb *Anim) NewFrame(frameInfo television.FrameInfo) error {
|
||||
if !frameInfo.Stable {
|
||||
return nil
|
||||
}
|
||||
|
||||
thmb.frameInfo = frameInfo
|
||||
thmb.resize(frameInfo, false)
|
||||
|
||||
img := *thmb.cropImg
|
||||
img.Pix = make([]uint8, len(thmb.cropImg.Pix))
|
||||
|
@ -286,11 +277,15 @@ func (thmb *Anim) SetPixels(sig []signal.SignalAttributes, last int) error {
|
|||
|
||||
offset += 4
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset implements the television.PixelRenderer interface
|
||||
func (thmb *Anim) Reset() {
|
||||
// start with a NTSC television as default
|
||||
thmb.resize(television.NewFrameInfo(specification.SpecNTSC), true)
|
||||
|
||||
// clear pixels. setting the alpha channel so we don't have to later (the
|
||||
// alpha channel never changes)
|
||||
for y := 0; y < thmb.img.Bounds().Size().Y; y++ {
|
||||
|
|
|
@ -71,7 +71,7 @@ func NewImage(prefs *preferences.Preferences) (*Image, error) {
|
|||
return nil, fmt.Errorf("thumbnailer: %w", err)
|
||||
}
|
||||
tv.AddPixelRenderer(thmb)
|
||||
tv.SetFPSCap(true)
|
||||
tv.SetFPSCap(false)
|
||||
|
||||
// create a new VCS emulation
|
||||
thmb.vcs, err = hardware.NewVCS(tv, prefs)
|
||||
|
@ -79,11 +79,7 @@ func NewImage(prefs *preferences.Preferences) (*Image, error) {
|
|||
return nil, fmt.Errorf("thumbnailer: %w", err)
|
||||
}
|
||||
thmb.vcs.Env.Label = thumbnailerEnv
|
||||
|
||||
thmb.img = image.NewRGBA(image.Rect(0, 0, specification.ClksScanline, specification.AbsoluteMaxScanlines))
|
||||
|
||||
// start with a NTSC television as default
|
||||
thmb.Resize(television.NewFrameInfo(specification.SpecNTSC))
|
||||
thmb.Reset()
|
||||
|
||||
return thmb, nil
|
||||
|
@ -177,21 +173,17 @@ func (thmb *Image) CartYield(yield coprocessor.CoProcYieldType) coprocessor.Yiel
|
|||
return coprocessor.YieldHookEnd
|
||||
}
|
||||
|
||||
// Resize implements the television.PixelRenderer interface
|
||||
func (thmb *Image) Resize(frameInfo television.FrameInfo) error {
|
||||
func (thmb *Image) resize(frameInfo television.FrameInfo, force bool) error {
|
||||
if thmb.frameInfo.IsDifferent(frameInfo) && (force || frameInfo.Stable) {
|
||||
thmb.cropImg = thmb.img.SubImage(frameInfo.Crop()).(*image.RGBA)
|
||||
}
|
||||
thmb.frameInfo = frameInfo
|
||||
crop := thmb.frameInfo.Crop()
|
||||
thmb.cropImg = thmb.img.SubImage(crop).(*image.RGBA)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewFrame implements the television.PixelRenderer interface
|
||||
func (thmb *Image) NewFrame(frameInfo television.FrameInfo) error {
|
||||
if !frameInfo.Stable {
|
||||
return nil
|
||||
}
|
||||
|
||||
thmb.frameInfo = frameInfo
|
||||
thmb.resize(frameInfo, false)
|
||||
|
||||
img := *thmb.cropImg
|
||||
img.Pix = make([]uint8, len(thmb.cropImg.Pix))
|
||||
|
@ -237,6 +229,9 @@ func (thmb *Image) SetPixels(sig []signal.SignalAttributes, last int) error {
|
|||
|
||||
// Reset implements the television.PixelRenderer interface
|
||||
func (thmb *Image) Reset() {
|
||||
// start with a NTSC television as default
|
||||
thmb.resize(television.NewFrameInfo(specification.SpecNTSC), true)
|
||||
|
||||
// clear pixels. setting the alpha channel so we don't have to later (the
|
||||
// alpha channel never changes)
|
||||
for y := 0; y < thmb.img.Bounds().Size().Y; y++ {
|
||||
|
|
Loading…
Reference in a new issue