Separate package for screen rendering
This commit is contained in:
parent
9d4df5b643
commit
083c19d2c2
|
@ -6,6 +6,7 @@ import (
|
||||||
"fyne.io/fyne"
|
"fyne.io/fyne"
|
||||||
"fyne.io/fyne/driver/desktop"
|
"fyne.io/fyne/driver/desktop"
|
||||||
"github.com/ivanizag/izapple2"
|
"github.com/ivanizag/izapple2"
|
||||||
|
"github.com/ivanizag/izapple2/screen"
|
||||||
)
|
)
|
||||||
|
|
||||||
type keyboard struct {
|
type keyboard struct {
|
||||||
|
@ -103,10 +104,10 @@ func (k *keyboard) putKey(keyEvent *fyne.KeyEvent) {
|
||||||
k.s.a.SendCommand(izapple2.CommandToggleSpeed)
|
k.s.a.SendCommand(izapple2.CommandToggleSpeed)
|
||||||
}
|
}
|
||||||
case fyne.KeyF6:
|
case fyne.KeyF6:
|
||||||
if k.s.screenMode != izapple2.ScreenModeGreen {
|
if k.s.screenMode != screen.ScreenModeGreen {
|
||||||
k.s.screenMode = izapple2.ScreenModeGreen
|
k.s.screenMode = screen.ScreenModeGreen
|
||||||
} else {
|
} else {
|
||||||
k.s.screenMode = izapple2.ScreenModeNTSC
|
k.s.screenMode = screen.ScreenModeNTSC
|
||||||
}
|
}
|
||||||
case fyne.KeyF7:
|
case fyne.KeyF7:
|
||||||
k.s.showPages = !k.s.showPages
|
k.s.showPages = !k.s.showPages
|
||||||
|
@ -118,7 +119,7 @@ func (k *keyboard) putKey(keyEvent *fyne.KeyEvent) {
|
||||||
k.s.a.SendCommand(izapple2.CommandToggleCPUTrace)
|
k.s.a.SendCommand(izapple2.CommandToggleCPUTrace)
|
||||||
case fyne.KeyF12:
|
case fyne.KeyF12:
|
||||||
//case fyne.KeyPrintScreen:
|
//case fyne.KeyPrintScreen:
|
||||||
err := izapple2.SaveSnapshot(k.s.a, k.s.screenMode, "snapshot.png")
|
err := screen.SaveSnapshot(k.s.a, k.s.screenMode, "snapshot.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error saving snapshoot: %v.\n.", err)
|
fmt.Printf("Error saving snapshoot: %v.\n.", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ivanizag/izapple2"
|
"github.com/ivanizag/izapple2"
|
||||||
|
"github.com/ivanizag/izapple2/screen"
|
||||||
|
|
||||||
"github.com/pkg/profile"
|
"github.com/pkg/profile"
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func fyneRun(s *state) {
|
func fyneRun(s *state) {
|
||||||
s.screenMode = izapple2.ScreenModeNTSC
|
s.screenMode = screen.ScreenModeNTSC
|
||||||
|
|
||||||
s.app = app.New()
|
s.app = app.New()
|
||||||
s.app.SetIcon(resourceApple2Png)
|
s.app.SetIcon(resourceApple2Png)
|
||||||
|
@ -52,13 +53,13 @@ func fyneRun(s *state) {
|
||||||
s.devices = newPanelDevices(s)
|
s.devices = newPanelDevices(s)
|
||||||
s.devices.w.Hide()
|
s.devices.w.Hide()
|
||||||
toolbar := buildToolbar(s)
|
toolbar := buildToolbar(s)
|
||||||
screen := canvas.NewImageFromImage(nil)
|
display := canvas.NewImageFromImage(nil)
|
||||||
screen.ScaleMode = canvas.ImageScalePixels // Looks worst but loads less.
|
display.ScaleMode = canvas.ImageScalePixels // Looks worst but loads less.
|
||||||
screen.SetMinSize(fyne.NewSize(280*2, 192*2))
|
display.SetMinSize(fyne.NewSize(280*2, 192*2))
|
||||||
|
|
||||||
container := fyne.NewContainerWithLayout(
|
container := fyne.NewContainerWithLayout(
|
||||||
layout.NewBorderLayout(nil, toolbar, nil, s.devices.w),
|
layout.NewBorderLayout(nil, toolbar, nil, s.devices.w),
|
||||||
screen, toolbar, s.devices.w,
|
display, toolbar, s.devices.w,
|
||||||
)
|
)
|
||||||
s.win.SetContent(container)
|
s.win.SetContent(container)
|
||||||
s.win.SetPadded(false)
|
s.win.SetPadded(false)
|
||||||
|
@ -81,13 +82,13 @@ func fyneRun(s *state) {
|
||||||
if !s.a.IsPaused() {
|
if !s.a.IsPaused() {
|
||||||
var img *image.RGBA
|
var img *image.RGBA
|
||||||
if s.showPages {
|
if s.showPages {
|
||||||
img = s.a.SnapshotParts(s.screenMode)
|
img = screen.SnapshotParts(s.a, s.screenMode)
|
||||||
s.win.SetTitle(fmt.Sprintf("%v %v %vx%v", s.a.Name, s.a.VideoModeName(), img.Rect.Dx()/2, img.Rect.Dy()/2))
|
s.win.SetTitle(fmt.Sprintf("%v %v %vx%v", s.a.Name, screen.VideoModeName(s.a), img.Rect.Dx()/2, img.Rect.Dy()/2))
|
||||||
} else {
|
} else {
|
||||||
img = s.a.Snapshot(s.screenMode)
|
img = screen.Snapshot(s.a, s.screenMode)
|
||||||
}
|
}
|
||||||
screen.Image = img
|
display.Image = img
|
||||||
canvas.Refresh(screen)
|
canvas.Refresh(display)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ivanizag/izapple2"
|
"github.com/ivanizag/izapple2"
|
||||||
|
"github.com/ivanizag/izapple2/screen"
|
||||||
|
|
||||||
"fyne.io/fyne"
|
"fyne.io/fyne"
|
||||||
"fyne.io/fyne/theme"
|
"fyne.io/fyne/theme"
|
||||||
|
@ -34,7 +35,7 @@ func buildToolbar(s *state) *widget.Toolbar {
|
||||||
}))
|
}))
|
||||||
tb.Append(widget.NewToolbarAction(
|
tb.Append(widget.NewToolbarAction(
|
||||||
theme.NewThemedResource(resourceCameraSvg, nil), func() {
|
theme.NewThemedResource(resourceCameraSvg, nil), func() {
|
||||||
err := izapple2.SaveSnapshot(s.a, s.screenMode, "snapshot.png")
|
err := screen.SaveSnapshot(s.a, s.screenMode, "snapshot.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.app.SendNotification(fyne.NewNotification(
|
s.app.SendNotification(fyne.NewNotification(
|
||||||
s.win.Title(),
|
s.win.Title(),
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fyne.io/fyne"
|
"fyne.io/fyne"
|
||||||
"fyne.io/fyne/theme"
|
"fyne.io/fyne/theme"
|
||||||
"fyne.io/fyne/widget"
|
"fyne.io/fyne/widget"
|
||||||
"github.com/ivanizag/izapple2"
|
"github.com/ivanizag/izapple2/screen"
|
||||||
)
|
)
|
||||||
|
|
||||||
type toolbarScreen struct {
|
type toolbarScreen struct {
|
||||||
|
@ -25,7 +25,7 @@ func newToolbarScreen(s *state) *toolbarScreen {
|
||||||
tbs.ntsc = widget.NewButtonWithIcon("",
|
tbs.ntsc = widget.NewButtonWithIcon("",
|
||||||
theme.NewThemedResource(resourceTelevisionClassicSvg, nil),
|
theme.NewThemedResource(resourceTelevisionClassicSvg, nil),
|
||||||
func() {
|
func() {
|
||||||
tbs.setScreenMode(izapple2.ScreenModeNTSC)
|
tbs.setScreenMode(screen.ScreenModeNTSC)
|
||||||
})
|
})
|
||||||
tbs.ntscDisabled = widget.NewIcon(
|
tbs.ntscDisabled = widget.NewIcon(
|
||||||
theme.NewDisabledResource(resourceTelevisionClassicSvg))
|
theme.NewDisabledResource(resourceTelevisionClassicSvg))
|
||||||
|
@ -33,7 +33,7 @@ func newToolbarScreen(s *state) *toolbarScreen {
|
||||||
tbs.plain = widget.NewButtonWithIcon("",
|
tbs.plain = widget.NewButtonWithIcon("",
|
||||||
theme.NewThemedResource(resourceTelevisionSvg, nil),
|
theme.NewThemedResource(resourceTelevisionSvg, nil),
|
||||||
func() {
|
func() {
|
||||||
tbs.setScreenMode(izapple2.ScreenModePlain)
|
tbs.setScreenMode(screen.ScreenModePlain)
|
||||||
})
|
})
|
||||||
tbs.plainDisabled = widget.NewIcon(
|
tbs.plainDisabled = widget.NewIcon(
|
||||||
theme.NewDisabledResource(resourceTelevisionSvg))
|
theme.NewDisabledResource(resourceTelevisionSvg))
|
||||||
|
@ -41,7 +41,7 @@ func newToolbarScreen(s *state) *toolbarScreen {
|
||||||
tbs.green = widget.NewButtonWithIcon("",
|
tbs.green = widget.NewButtonWithIcon("",
|
||||||
theme.NewThemedResource(resourceMonitorSvg, nil),
|
theme.NewThemedResource(resourceMonitorSvg, nil),
|
||||||
func() {
|
func() {
|
||||||
tbs.setScreenMode(izapple2.ScreenModeGreen)
|
tbs.setScreenMode(screen.ScreenModeGreen)
|
||||||
})
|
})
|
||||||
tbs.greenDisabled = widget.NewIcon(
|
tbs.greenDisabled = widget.NewIcon(
|
||||||
theme.NewDisabledResource(resourceMonitorSvg))
|
theme.NewDisabledResource(resourceMonitorSvg))
|
||||||
|
@ -61,13 +61,13 @@ func newToolbarScreen(s *state) *toolbarScreen {
|
||||||
|
|
||||||
func (tbs *toolbarScreen) setScreenMode(screenMode int) {
|
func (tbs *toolbarScreen) setScreenMode(screenMode int) {
|
||||||
switch tbs.s.screenMode {
|
switch tbs.s.screenMode {
|
||||||
case izapple2.ScreenModeNTSC:
|
case screen.ScreenModeNTSC:
|
||||||
tbs.ntsc.Show()
|
tbs.ntsc.Show()
|
||||||
tbs.ntscDisabled.Hide()
|
tbs.ntscDisabled.Hide()
|
||||||
case izapple2.ScreenModePlain:
|
case screen.ScreenModePlain:
|
||||||
tbs.plain.Show()
|
tbs.plain.Show()
|
||||||
tbs.plainDisabled.Hide()
|
tbs.plainDisabled.Hide()
|
||||||
case izapple2.ScreenModeGreen:
|
case screen.ScreenModeGreen:
|
||||||
tbs.green.Show()
|
tbs.green.Show()
|
||||||
tbs.greenDisabled.Hide()
|
tbs.greenDisabled.Hide()
|
||||||
}
|
}
|
||||||
|
@ -75,13 +75,13 @@ func (tbs *toolbarScreen) setScreenMode(screenMode int) {
|
||||||
tbs.s.screenMode = screenMode
|
tbs.s.screenMode = screenMode
|
||||||
|
|
||||||
switch screenMode {
|
switch screenMode {
|
||||||
case izapple2.ScreenModeNTSC:
|
case screen.ScreenModeNTSC:
|
||||||
tbs.ntsc.Hide()
|
tbs.ntsc.Hide()
|
||||||
tbs.ntscDisabled.Show()
|
tbs.ntscDisabled.Show()
|
||||||
case izapple2.ScreenModePlain:
|
case screen.ScreenModePlain:
|
||||||
tbs.plain.Hide()
|
tbs.plain.Hide()
|
||||||
tbs.plainDisabled.Show()
|
tbs.plainDisabled.Show()
|
||||||
case izapple2.ScreenModeGreen:
|
case screen.ScreenModeGreen:
|
||||||
tbs.green.Hide()
|
tbs.green.Hide()
|
||||||
tbs.greenDisabled.Show()
|
tbs.greenDisabled.Show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/ivanizag/izapple2"
|
"github.com/ivanizag/izapple2"
|
||||||
|
"github.com/ivanizag/izapple2/screen"
|
||||||
|
|
||||||
"github.com/pkg/profile"
|
"github.com/pkg/profile"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
@ -85,10 +86,10 @@ func sdlRun(a *izapple2.Apple2) {
|
||||||
if !a.IsPaused() {
|
if !a.IsPaused() {
|
||||||
var img *image.RGBA
|
var img *image.RGBA
|
||||||
if kp.showPages {
|
if kp.showPages {
|
||||||
img = a.SnapshotParts(izapple2.ScreenModeNTSC)
|
img = screen.SnapshotParts(a, screen.ScreenModeNTSC)
|
||||||
window.SetTitle(fmt.Sprintf("%v %v %vx%v", a.Name, a.VideoModeName(), img.Rect.Dx()/2, img.Rect.Dy()/2))
|
window.SetTitle(fmt.Sprintf("%v %v %vx%v", a.Name, screen.VideoModeName(a), img.Rect.Dx()/2, img.Rect.Dy()/2))
|
||||||
} else {
|
} else {
|
||||||
img = a.Snapshot(izapple2.ScreenModeNTSC)
|
img = screen.Snapshot(a, screen.ScreenModeNTSC)
|
||||||
}
|
}
|
||||||
if img != nil {
|
if img != nil {
|
||||||
surface, err := sdl.CreateRGBSurfaceFrom(unsafe.Pointer(&img.Pix[0]),
|
surface, err := sdl.CreateRGBSurfaceFrom(unsafe.Pointer(&img.Pix[0]),
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ivanizag/izapple2"
|
"github.com/ivanizag/izapple2"
|
||||||
|
"github.com/ivanizag/izapple2/screen"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -104,7 +105,7 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
|
||||||
k.a.SendCommand(izapple2.CommandToggleCPUTrace)
|
k.a.SendCommand(izapple2.CommandToggleCPUTrace)
|
||||||
case sdl.K_F12:
|
case sdl.K_F12:
|
||||||
case sdl.K_PRINTSCREEN:
|
case sdl.K_PRINTSCREEN:
|
||||||
err := izapple2.SaveSnapshot(k.a, izapple2.ScreenModeNTSC, "snapshot.png")
|
err := screen.SaveSnapshot(k.a, screen.ScreenModeNTSC, "snapshot.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error saving snapshoot: %v.\n.", err)
|
fmt.Printf("Error saving snapshoot: %v.\n.", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
260
screen.go
260
screen.go
|
@ -1,260 +0,0 @@
|
||||||
package izapple2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/png"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
References:
|
|
||||||
- "Understanding the Apple II", http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
|
||||||
- "Apple II Reference Manual"
|
|
||||||
- "More Colors for your Apple", https://archive.org/details/byte-magazine-1979-06/page/n61
|
|
||||||
*/
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Base Video Mode
|
|
||||||
videoBaseMask uint16 = 0x1f
|
|
||||||
videoText40 uint16 = 0x01
|
|
||||||
videoGR uint16 = 0x02
|
|
||||||
videoHGR uint16 = 0x03
|
|
||||||
videoText80 uint16 = 0x08
|
|
||||||
videoDGR uint16 = 0x09
|
|
||||||
videoDHGR uint16 = 0x0a
|
|
||||||
videoText40RGB uint16 = 0x10
|
|
||||||
videoMono560 uint16 = 0x11
|
|
||||||
videoRGBMix uint16 = 0x12
|
|
||||||
videoRGB160 uint16 = 0x13
|
|
||||||
videoSHR uint16 = 0x14
|
|
||||||
|
|
||||||
// Mix text modifiers
|
|
||||||
videoMixTextMask uint16 = 0x0f00
|
|
||||||
videoMixText40 uint16 = 0x0100
|
|
||||||
videoMixText80 uint16 = 0x0200
|
|
||||||
videoMixText40RGB uint16 = 0x0300
|
|
||||||
|
|
||||||
// Other modifiers
|
|
||||||
videoModifiersMask uint16 = 0xf000
|
|
||||||
videoSecondPage uint16 = 0x1000
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ScreenModeGreen to render as a green phosphor monitor
|
|
||||||
ScreenModeGreen = iota
|
|
||||||
// ScreenModePlain to render in color with filled areas
|
|
||||||
ScreenModePlain
|
|
||||||
//ScreenModeNTSC shows spaces between pixels
|
|
||||||
ScreenModeNTSC
|
|
||||||
)
|
|
||||||
|
|
||||||
func getCurrentVideoMode(a *Apple2) uint16 {
|
|
||||||
isTextMode := a.io.isSoftSwitchActive(ioFlagText)
|
|
||||||
isHiResMode := a.io.isSoftSwitchActive(ioFlagHiRes)
|
|
||||||
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
|
||||||
isStore80Active := a.mmu.store80Active
|
|
||||||
isDoubleResMode := !isTextMode && is80Columns && !a.io.isSoftSwitchActive(ioFlagAnnunciator3)
|
|
||||||
isSuperHighResMode := a.io.isSoftSwitchActive(ioDataNewVideo)
|
|
||||||
|
|
||||||
isRGBCard := a.io.isSoftSwitchActive(ioFlagRGBCardActive)
|
|
||||||
rgbFlag1 := a.io.isSoftSwitchActive(ioFlag1RGBCard)
|
|
||||||
rgbFlag2 := a.io.isSoftSwitchActive(ioFlag2RGBCard)
|
|
||||||
isMono560 := isDoubleResMode && !rgbFlag1 && !rgbFlag2
|
|
||||||
isRGBMixMode := isDoubleResMode && !rgbFlag1 && rgbFlag2
|
|
||||||
isRGB160Mode := isDoubleResMode && rgbFlag1 && !rgbFlag2
|
|
||||||
|
|
||||||
isMixMode := a.io.isSoftSwitchActive(ioFlagMixed)
|
|
||||||
|
|
||||||
var mode uint16
|
|
||||||
if isSuperHighResMode {
|
|
||||||
mode = videoSHR
|
|
||||||
isMixMode = false
|
|
||||||
} else if isTextMode {
|
|
||||||
if is80Columns {
|
|
||||||
mode = videoText80
|
|
||||||
} else if isRGBCard && isStore80Active {
|
|
||||||
mode = videoText40RGB
|
|
||||||
} else {
|
|
||||||
mode = videoText40
|
|
||||||
}
|
|
||||||
isMixMode = false
|
|
||||||
} else if isHiResMode {
|
|
||||||
if !isDoubleResMode {
|
|
||||||
mode = videoHGR
|
|
||||||
} else if isMono560 {
|
|
||||||
mode = videoMono560
|
|
||||||
} else if isRGBMixMode {
|
|
||||||
mode = videoRGBMix
|
|
||||||
} else if isRGB160Mode {
|
|
||||||
mode = videoRGB160
|
|
||||||
} else {
|
|
||||||
mode = videoDHGR
|
|
||||||
}
|
|
||||||
} else if isDoubleResMode {
|
|
||||||
mode = videoDGR
|
|
||||||
} else {
|
|
||||||
mode = videoGR
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modifiers
|
|
||||||
if isMixMode {
|
|
||||||
if is80Columns {
|
|
||||||
mode |= videoMixText80
|
|
||||||
} else /* if isStore80Active {
|
|
||||||
mode |= videoMixText40RGB
|
|
||||||
} else */{
|
|
||||||
mode |= videoMixText40
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
|
||||||
if isSecondPage {
|
|
||||||
mode |= videoSecondPage
|
|
||||||
}
|
|
||||||
|
|
||||||
return mode
|
|
||||||
}
|
|
||||||
|
|
||||||
func snapshotByMode(a *Apple2, videoMode uint16, screenMode int) *image.RGBA {
|
|
||||||
videoBase := videoMode & videoBaseMask
|
|
||||||
mixMode := videoMode & videoMixTextMask
|
|
||||||
isSecondPage := (videoMode & videoSecondPage) != 0
|
|
||||||
|
|
||||||
var lightColor color.Color = color.White
|
|
||||||
if screenMode == ScreenModeGreen {
|
|
||||||
// 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
|
|
||||||
lightColor = color.RGBA{65, 255, 0, 255}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyNTSCFilter := screenMode != ScreenModeGreen
|
|
||||||
var snap *image.RGBA
|
|
||||||
var ntscMask *image.Alpha
|
|
||||||
switch videoBase {
|
|
||||||
case videoText40:
|
|
||||||
snap = snapshotText40Mode(a, isSecondPage, lightColor)
|
|
||||||
applyNTSCFilter = false
|
|
||||||
case videoText80:
|
|
||||||
snap = snapshotText80Mode(a, isSecondPage, lightColor)
|
|
||||||
applyNTSCFilter = false
|
|
||||||
case videoText40RGB:
|
|
||||||
snap = snapshotText40RGBMode(a, isSecondPage)
|
|
||||||
applyNTSCFilter = false
|
|
||||||
case videoGR:
|
|
||||||
snap = snapshotLoResModeMono(a, isSecondPage, lightColor)
|
|
||||||
case videoDGR:
|
|
||||||
snap = snapshotMeResModeMono(a, isSecondPage, lightColor)
|
|
||||||
case videoHGR:
|
|
||||||
snap = snapshotHiResModeMono(a, isSecondPage, lightColor)
|
|
||||||
case videoDHGR:
|
|
||||||
snap, _ = snapshotDoubleHiResModeMono(a, isSecondPage, false /*isRGBMixMode*/, lightColor)
|
|
||||||
case videoMono560:
|
|
||||||
snap, _ = snapshotDoubleHiResModeMono(a, isSecondPage, false /*isRGBMixMode*/, lightColor)
|
|
||||||
applyNTSCFilter = false
|
|
||||||
case videoRGBMix:
|
|
||||||
snap, ntscMask = snapshotDoubleHiResModeMono(a, isSecondPage, true /*isRGBMixMode*/, lightColor)
|
|
||||||
case videoRGB160:
|
|
||||||
snap = snapshotDoubleHiRes160ModeMono(a, isSecondPage, lightColor)
|
|
||||||
case videoSHR:
|
|
||||||
snap = snapshotSuperHiResMode(a)
|
|
||||||
applyNTSCFilter = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if applyNTSCFilter {
|
|
||||||
snap = filterNTSCColor(snap, ntscMask, screenMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
if mixMode != 0 {
|
|
||||||
var bottom *image.RGBA
|
|
||||||
applyNTSCFilter := screenMode != ScreenModeGreen
|
|
||||||
switch mixMode {
|
|
||||||
case videoMixText40:
|
|
||||||
bottom = snapshotText40Mode(a, isSecondPage, lightColor)
|
|
||||||
case videoMixText80:
|
|
||||||
bottom = snapshotText80Mode(a, isSecondPage, lightColor)
|
|
||||||
case videoMixText40RGB:
|
|
||||||
bottom = snapshotText40RGBMode(a, isSecondPage)
|
|
||||||
applyNTSCFilter = false
|
|
||||||
}
|
|
||||||
if applyNTSCFilter {
|
|
||||||
bottom = filterNTSCColor(bottom, ntscMask, screenMode)
|
|
||||||
}
|
|
||||||
snap = mixSnapshots(snap, bottom)
|
|
||||||
}
|
|
||||||
|
|
||||||
return snap
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snapshot the currently visible screen
|
|
||||||
func (a *Apple2) Snapshot(screenMode int) *image.RGBA {
|
|
||||||
videoMode := getCurrentVideoMode(a)
|
|
||||||
snap := snapshotByMode(a, videoMode, screenMode)
|
|
||||||
|
|
||||||
if screenMode != ScreenModePlain && snap.Bounds().Dy() == hiResHeight {
|
|
||||||
// Apply the filter to regular CRT snapshots with 192 lines. Not to SHR
|
|
||||||
snap = linesSeparatedFilter(snap)
|
|
||||||
}
|
|
||||||
|
|
||||||
return snap
|
|
||||||
}
|
|
||||||
|
|
||||||
func mixSnapshots(top, bottom *image.RGBA) *image.RGBA {
|
|
||||||
bottomWidth := bottom.Bounds().Dx()
|
|
||||||
|
|
||||||
// Copy bottom's bottom on top's bottom
|
|
||||||
for y := hiResHeightMixed; y < hiResHeight; y++ {
|
|
||||||
for x := 0; x < bottomWidth; x++ {
|
|
||||||
c := bottom.At(x, y)
|
|
||||||
top.Set(x, y, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return top
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveSnapshot saves a snapshot of the screen to a png file
|
|
||||||
func SaveSnapshot(a *Apple2, screenMode int, filename string) error {
|
|
||||||
img := a.Snapshot(screenMode)
|
|
||||||
img = squarishPixelsFilter(img)
|
|
||||||
|
|
||||||
f, err := os.Create(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
png.Encode(f, img)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func squarishPixelsFilter(in *image.RGBA) *image.RGBA {
|
|
||||||
b := in.Bounds()
|
|
||||||
factor := 1200 / b.Dx()
|
|
||||||
size := image.Rect(0, 0, factor*b.Dx(), b.Dy())
|
|
||||||
out := image.NewRGBA(size)
|
|
||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
||||||
c := in.At(x, y)
|
|
||||||
for i := 0; i < factor; i++ {
|
|
||||||
out.Set(factor*x+i, y, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func linesSeparatedFilter(in *image.RGBA) *image.RGBA {
|
|
||||||
b := in.Bounds()
|
|
||||||
size := image.Rect(0, 0, b.Dx(), 4*b.Dy())
|
|
||||||
out := image.NewRGBA(size)
|
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
|
||||||
c := in.At(x, y)
|
|
||||||
out.Set(x, 4*y, c)
|
|
||||||
out.Set(x, 4*y+1, c)
|
|
||||||
out.Set(x, 4*y+2, c)
|
|
||||||
out.Set(x, 4*y+3, color.Black)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
package screen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
References:
|
||||||
|
- "Understanding the Apple II", http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
||||||
|
- "Apple II Reference Manual"
|
||||||
|
- "More Colors for your Apple", https://archive.org/details/byte-magazine-1979-06/page/n61
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ScreenModeGreen to render as a green phosphor monitor
|
||||||
|
ScreenModeGreen = iota
|
||||||
|
// ScreenModePlain to render in color with filled areas
|
||||||
|
ScreenModePlain
|
||||||
|
//ScreenModeNTSC shows spaces between pixels
|
||||||
|
ScreenModeNTSC
|
||||||
|
)
|
||||||
|
|
||||||
|
// Snapshot the currently visible screen
|
||||||
|
func Snapshot(vs VideoSource, screenMode int) *image.RGBA {
|
||||||
|
videoMode := vs.GetCurrentVideoMode()
|
||||||
|
snap := snapshotByMode(vs, videoMode, screenMode)
|
||||||
|
|
||||||
|
if screenMode != ScreenModePlain && snap.Bounds().Dy() == hiResHeight {
|
||||||
|
// Apply the filter to regular CRT snapshots with 192 lines. Not to SHR
|
||||||
|
snap = linesSeparatedFilter(snap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return snap
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGBA {
|
||||||
|
videoBase := videoMode & VideoBaseMask
|
||||||
|
mixMode := videoMode & VideoMixTextMask
|
||||||
|
isSecondPage := (videoMode & VideoSecondPage) != 0
|
||||||
|
|
||||||
|
var lightColor color.Color = color.White
|
||||||
|
if screenMode == ScreenModeGreen {
|
||||||
|
// 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
|
||||||
|
lightColor = color.RGBA{65, 255, 0, 255}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyNTSCFilter := screenMode != ScreenModeGreen
|
||||||
|
var snap *image.RGBA
|
||||||
|
var ntscMask *image.Alpha
|
||||||
|
switch videoBase {
|
||||||
|
case VideoText40:
|
||||||
|
snap = snapshotText40(vs, isSecondPage, lightColor)
|
||||||
|
applyNTSCFilter = false
|
||||||
|
case VideoText80:
|
||||||
|
snap = snapshotText80(vs, isSecondPage, lightColor)
|
||||||
|
applyNTSCFilter = false
|
||||||
|
case VideoText40RGB:
|
||||||
|
snap = snapshotText40RGB(vs, isSecondPage)
|
||||||
|
applyNTSCFilter = false
|
||||||
|
case VideoGR:
|
||||||
|
snap = snapshotLoRes(vs, isSecondPage, lightColor)
|
||||||
|
case VideoDGR:
|
||||||
|
snap = snapshotMeRes(vs, isSecondPage, lightColor)
|
||||||
|
case VideoHGR:
|
||||||
|
snap = snapshotHiRes(vs, isSecondPage, lightColor)
|
||||||
|
case VideoDHGR:
|
||||||
|
snap, _ = snapshotDoubleHiRes(vs, isSecondPage, false /*isRGBMixMode*/, lightColor)
|
||||||
|
case VideoMono560:
|
||||||
|
snap, _ = snapshotDoubleHiRes(vs, isSecondPage, false /*isRGBMixMode*/, lightColor)
|
||||||
|
applyNTSCFilter = false
|
||||||
|
case VideoRGBMix:
|
||||||
|
snap, ntscMask = snapshotDoubleHiRes(vs, isSecondPage, true /*isRGBMixMode*/, lightColor)
|
||||||
|
case VideoRGB160:
|
||||||
|
snap = snapshotDoubleHiRes160(vs, isSecondPage, lightColor)
|
||||||
|
case VideoSHR:
|
||||||
|
snap = snapshotSuperHiRes(vs)
|
||||||
|
applyNTSCFilter = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if applyNTSCFilter {
|
||||||
|
snap = filterNTSCColor(snap, ntscMask, screenMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mixMode != 0 {
|
||||||
|
var bottom *image.RGBA
|
||||||
|
applyNTSCFilter := screenMode != ScreenModeGreen
|
||||||
|
switch mixMode {
|
||||||
|
case VideoMixText40:
|
||||||
|
bottom = snapshotText40(vs, isSecondPage, lightColor)
|
||||||
|
case VideoMixText80:
|
||||||
|
bottom = snapshotText80(vs, isSecondPage, lightColor)
|
||||||
|
case VideoMixText40RGB:
|
||||||
|
bottom = snapshotText40RGB(vs, isSecondPage)
|
||||||
|
applyNTSCFilter = false
|
||||||
|
}
|
||||||
|
if applyNTSCFilter {
|
||||||
|
bottom = filterNTSCColor(bottom, ntscMask, screenMode)
|
||||||
|
}
|
||||||
|
snap = mixSnapshots(snap, bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
return snap
|
||||||
|
}
|
||||||
|
|
||||||
|
func mixSnapshots(top, bottom *image.RGBA) *image.RGBA {
|
||||||
|
bottomWidth := bottom.Bounds().Dx()
|
||||||
|
|
||||||
|
// Copy bottom's bottom on top's bottom
|
||||||
|
for y := hiResHeightMixed; y < hiResHeight; y++ {
|
||||||
|
for x := 0; x < bottomWidth; x++ {
|
||||||
|
c := bottom.At(x, y)
|
||||||
|
top.Set(x, y, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return top
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveSnapshot saves a snapshot of the screen to a png file
|
||||||
|
func SaveSnapshot(vs VideoSource, screenMode int, filename string) error {
|
||||||
|
img := Snapshot(vs, screenMode)
|
||||||
|
img = squarishPixelsFilter(img)
|
||||||
|
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
png.Encode(f, img)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func squarishPixelsFilter(in *image.RGBA) *image.RGBA {
|
||||||
|
b := in.Bounds()
|
||||||
|
factor := 1200 / b.Dx()
|
||||||
|
size := image.Rect(0, 0, factor*b.Dx(), b.Dy())
|
||||||
|
out := image.NewRGBA(size)
|
||||||
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||||
|
c := in.At(x, y)
|
||||||
|
for i := 0; i < factor; i++ {
|
||||||
|
out.Set(factor*x+i, y, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func linesSeparatedFilter(in *image.RGBA) *image.RGBA {
|
||||||
|
b := in.Bounds()
|
||||||
|
size := image.Rect(0, 0, b.Dx(), 4*b.Dy())
|
||||||
|
out := image.NewRGBA(size)
|
||||||
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||||
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
|
c := in.At(x, y)
|
||||||
|
out.Set(x, 4*y, c)
|
||||||
|
out.Set(x, 4*y+1, c)
|
||||||
|
out.Set(x, 4*y+2, c)
|
||||||
|
out.Set(x, 4*y+3, color.Black)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
|
@ -1,20 +1,20 @@
|
||||||
package izapple2
|
package screen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SnapshotParts the currently visible screen
|
// SnapshotParts the currently visible screen
|
||||||
func (a *Apple2) SnapshotParts(screenMode int) *image.RGBA {
|
func SnapshotParts(vs VideoSource, screenMode int) *image.RGBA {
|
||||||
videoMode := getCurrentVideoMode(a)
|
videoMode := vs.GetCurrentVideoMode()
|
||||||
isSecondPage := (videoMode & videoSecondPage) != 0
|
isSecondPage := (videoMode & VideoSecondPage) != 0
|
||||||
videoBase := videoMode & videoBaseMask
|
videoBase := videoMode & VideoBaseMask
|
||||||
mixMode := videoMode & videoMixTextMask
|
mixMode := videoMode & VideoMixTextMask
|
||||||
modifiers := videoMode & videoModifiersMask
|
modifiers := videoMode & VideoModifiersMask
|
||||||
|
|
||||||
snapScreen := snapshotByMode(a, videoMode, screenMode)
|
snapScreen := snapshotByMode(vs, videoMode, screenMode)
|
||||||
snapPage1 := snapshotByMode(a, videoMode&^videoSecondPage, screenMode)
|
snapPage1 := snapshotByMode(vs, videoMode&^VideoSecondPage, screenMode)
|
||||||
snapPage2 := snapshotByMode(a, videoMode|videoSecondPage, screenMode)
|
snapPage2 := snapshotByMode(vs, videoMode|VideoSecondPage, screenMode)
|
||||||
var snapAux *image.RGBA
|
var snapAux *image.RGBA
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -23,16 +23,16 @@ func (a *Apple2) SnapshotParts(screenMode int) *image.RGBA {
|
||||||
snapAux = filterMask(mask)
|
snapAux = filterMask(mask)
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
if videoBase == videoText40RGB {
|
if videoBase == VideoText40RGB {
|
||||||
snapAux = snapshotText40RGBModeColors(a, isSecondPage)
|
snapAux = snapshotText40RGBColors(vs, isSecondPage)
|
||||||
} else {
|
} else {
|
||||||
switch mixMode {
|
switch mixMode {
|
||||||
case videoMixText80:
|
case VideoMixText80:
|
||||||
snapAux = snapshotByMode(a, videoText80|modifiers, screenMode)
|
snapAux = snapshotByMode(vs, VideoText80|modifiers, screenMode)
|
||||||
case videoMixText40RGB:
|
case VideoMixText40RGB:
|
||||||
snapAux = snapshotByMode(a, videoText40RGB|modifiers, screenMode)
|
snapAux = snapshotByMode(vs, VideoText40RGB|modifiers, screenMode)
|
||||||
default:
|
default:
|
||||||
snapAux = snapshotByMode(a, videoText40|modifiers, screenMode)
|
snapAux = snapshotByMode(vs, VideoText40|modifiers, screenMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,50 +40,50 @@ func (a *Apple2) SnapshotParts(screenMode int) *image.RGBA {
|
||||||
}
|
}
|
||||||
|
|
||||||
// VideoModeName returns the name of the current video mode
|
// VideoModeName returns the name of the current video mode
|
||||||
func (a *Apple2) VideoModeName() string {
|
func VideoModeName(vs VideoSource) string {
|
||||||
videoMode := getCurrentVideoMode(a)
|
videoMode := vs.GetCurrentVideoMode()
|
||||||
videoBase := videoMode & videoBaseMask
|
videoBase := videoMode & VideoBaseMask
|
||||||
mixMode := videoMode & videoMixTextMask
|
mixMode := videoMode & VideoMixTextMask
|
||||||
|
|
||||||
var name string
|
var name string
|
||||||
|
|
||||||
switch videoBase {
|
switch videoBase {
|
||||||
case videoText40:
|
case VideoText40:
|
||||||
name = "TEXT40COL"
|
name = "TEXT40COL"
|
||||||
case videoText80:
|
case VideoText80:
|
||||||
name = "TEXT80COL"
|
name = "TEXT80COL"
|
||||||
case videoText40RGB:
|
case VideoText40RGB:
|
||||||
name = "TEXT40COLRGB"
|
name = "TEXT40COLRGB"
|
||||||
case videoGR:
|
case VideoGR:
|
||||||
name = "GR"
|
name = "GR"
|
||||||
case videoDGR:
|
case VideoDGR:
|
||||||
name = "DGR"
|
name = "DGR"
|
||||||
case videoHGR:
|
case VideoHGR:
|
||||||
name = "HGR"
|
name = "HGR"
|
||||||
case videoDHGR:
|
case VideoDHGR:
|
||||||
name = "DHGR"
|
name = "DHGR"
|
||||||
case videoMono560:
|
case VideoMono560:
|
||||||
name = "Mono560"
|
name = "Mono560"
|
||||||
case videoRGBMix:
|
case VideoRGBMix:
|
||||||
name = "RGMMIX"
|
name = "RGMMIX"
|
||||||
case videoRGB160:
|
case VideoRGB160:
|
||||||
name = "RGB160"
|
name = "RGB160"
|
||||||
case videoSHR:
|
case VideoSHR:
|
||||||
name = "SHR"
|
name = "SHR"
|
||||||
default:
|
default:
|
||||||
name = "Unknown video mode"
|
name = "Unknown video mode"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoMode & videoSecondPage) != 0 {
|
if (videoMode & VideoSecondPage) != 0 {
|
||||||
name += "-PAGE2"
|
name += "-PAGE2"
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mixMode {
|
switch mixMode {
|
||||||
case videoMixText40:
|
case VideoMixText40:
|
||||||
name += "-MIX40"
|
name += "-MIX40"
|
||||||
case videoMixText80:
|
case VideoMixText80:
|
||||||
name += "-MIX80"
|
name += "-MIX80"
|
||||||
case videoMixText40RGB:
|
case VideoMixText40RGB:
|
||||||
name += "-MIX40RGB"
|
name += "-MIX40RGB"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package izapple2
|
package screen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
|
@ -10,7 +10,14 @@ const (
|
||||||
rgb160Width = 4 * 160
|
rgb160Width = 4 * 160
|
||||||
)
|
)
|
||||||
|
|
||||||
func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, getNTSCMask bool, light color.Color) (*image.RGBA, *image.Alpha) {
|
func snapshotDoubleHiRes(vs VideoSource, isSecondPage bool, getNTSCMask bool, light color.Color) (*image.RGBA, *image.Alpha) {
|
||||||
|
dataMain := vs.GetVideoMemory(isSecondPage, false)
|
||||||
|
dataAux := vs.GetVideoMemory(isSecondPage, true)
|
||||||
|
return renderDoubleHiRes(dataMain, dataAux, getNTSCMask, light)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderDoubleHiRes(dataMain []uint8, dataAux []uint8, getNTSCMask bool, light color.Color) (*image.RGBA, *image.Alpha) {
|
||||||
|
|
||||||
// As described in "Inside the Apple IIe"
|
// As described in "Inside the Apple IIe"
|
||||||
size := image.Rect(0, 0, doubleHiResWidth, hiResHeight)
|
size := image.Rect(0, 0, doubleHiResWidth, hiResHeight)
|
||||||
img := image.NewRGBA(size)
|
img := image.NewRGBA(size)
|
||||||
|
@ -23,9 +30,10 @@ func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, getNTSCMask bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
for y := 0; y < hiResHeight; y++ {
|
for y := 0; y < hiResHeight; y++ {
|
||||||
|
offset := getHiResLineOffset(y)
|
||||||
lineParts := [][]uint8{
|
lineParts := [][]uint8{
|
||||||
getHiResLine(a, y, isSecondPage, true /*auxmem*/),
|
dataAux[offset : offset+hiResLineBytes],
|
||||||
getHiResLine(a, y, isSecondPage, false /*auxmem*/),
|
dataMain[offset : offset+hiResLineBytes],
|
||||||
}
|
}
|
||||||
x := 0
|
x := 0
|
||||||
// For the NTSC filter to work we have to insert an initial black pixel and skip the last one ¿?
|
// For the NTSC filter to work we have to insert an initial black pixel and skip the last one ¿?
|
||||||
|
@ -64,14 +72,21 @@ func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, getNTSCMask bool,
|
||||||
return img, ntscMask
|
return img, ntscMask
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotDoubleHiRes160ModeMono(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
|
func snapshotDoubleHiRes160(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA {
|
||||||
|
dataMain := vs.GetVideoMemory(isSecondPage, false)
|
||||||
|
dataAux := vs.GetVideoMemory(isSecondPage, true)
|
||||||
|
return renderDoubleHiRes160(dataMain, dataAux, light)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderDoubleHiRes160(dataMain []uint8, dataAux []uint8, light color.Color) *image.RGBA {
|
||||||
size := image.Rect(0, 0, rgb160Width, hiResHeight)
|
size := image.Rect(0, 0, rgb160Width, hiResHeight)
|
||||||
img := image.NewRGBA(size)
|
img := image.NewRGBA(size)
|
||||||
|
|
||||||
for y := 0; y < hiResHeight; y++ {
|
for y := 0; y < hiResHeight; y++ {
|
||||||
|
offset := getHiResLineOffset(y)
|
||||||
lineParts := [][]uint8{
|
lineParts := [][]uint8{
|
||||||
getHiResLine(a, y, isSecondPage, true /*auxmem*/),
|
dataAux[offset : offset+hiResLineBytes],
|
||||||
getHiResLine(a, y, isSecondPage, false /*auxmem*/),
|
dataMain[offset : offset+hiResLineBytes],
|
||||||
}
|
}
|
||||||
x := 0
|
x := 0
|
||||||
for iByte := 0; iByte < hiResLineBytes; iByte++ {
|
for iByte := 0; iByte < hiResLineBytes; iByte++ {
|
|
@ -1,4 +1,4 @@
|
||||||
package izapple2
|
package screen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
|
@ -6,16 +6,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
hiResWidth = 280
|
hiResWidth = 280
|
||||||
hiResLineBytes = hiResWidth / 7
|
hiResLineBytes = hiResWidth / 7
|
||||||
hiResHeight = 192
|
hiResHeight = 192
|
||||||
hiResHeightMixed = 160
|
hiResHeightMixed = 160
|
||||||
hiResPage1Address = uint16(0x2000)
|
|
||||||
hiResPage2Address = uint16(0x4000)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getHiResLineOffset(line int) uint16 {
|
func snapshotHiRes(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA {
|
||||||
|
data := vs.GetVideoMemory(isSecondPage, false)
|
||||||
|
return renderHiRes(data, light)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHiResLineOffset(line int) uint16 {
|
||||||
// See "Understanding the Apple II", page 5-14
|
// See "Understanding the Apple II", page 5-14
|
||||||
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
||||||
section := line >> 6 // Top, middle and bottom
|
section := line >> 6 // Top, middle and bottom
|
||||||
|
@ -24,23 +26,14 @@ func getHiResLineOffset(line int) uint16 {
|
||||||
return uint16(section*40 + outerEighth*0x80 + innerEighth*0x400)
|
return uint16(section*40 + outerEighth*0x80 + innerEighth*0x400)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHiResLine(a *Apple2, line int, isSecondPage bool, auxMem bool) []uint8 {
|
func renderHiRes(data []uint8, light color.Color) *image.RGBA {
|
||||||
address := hiResPage1Address
|
|
||||||
if isSecondPage {
|
|
||||||
address = hiResPage2Address
|
|
||||||
}
|
|
||||||
|
|
||||||
address += getHiResLineOffset(line)
|
|
||||||
return a.mmu.getVideoRAM(auxMem).subRange(address, address+hiResLineBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func snapshotHiResModeMono(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
|
|
||||||
// As described in "Undertanding the Apple II", with half pixel shifts
|
// As described in "Undertanding the Apple II", with half pixel shifts
|
||||||
size := image.Rect(0, 0, 2*hiResWidth, hiResHeight)
|
size := image.Rect(0, 0, 2*hiResWidth, hiResHeight)
|
||||||
img := image.NewRGBA(size)
|
img := image.NewRGBA(size)
|
||||||
|
|
||||||
for y := 0; y < hiResHeight; y++ {
|
for y := 0; y < hiResHeight; y++ {
|
||||||
bytes := getHiResLine(a, y, isSecondPage, false /*auxMem*/)
|
offset := getHiResLineOffset(y)
|
||||||
|
bytes := data[offset : offset+hiResLineBytes]
|
||||||
x := 0
|
x := 0
|
||||||
var previousColour color.Color = color.Black
|
var previousColour color.Color = color.Black
|
||||||
for _, b := range bytes {
|
for _, b := range bytes {
|
|
@ -1,4 +1,4 @@
|
||||||
package izapple2
|
package screen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
|
@ -1,4 +1,4 @@
|
||||||
package izapple2
|
package screen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
|
@ -38,17 +38,17 @@ func getColorPatterns(light color.Color) [16][16]color.Color {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotLoResModeMono(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
|
func snapshotLoRes(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA {
|
||||||
data := getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage)
|
data := getTextFromMemory(vs, isSecondPage, false)
|
||||||
return renderGrMode(data, false /*isMeres*/, light)
|
return renderGr(data, false /*isMeres*/, light)
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotMeResModeMono(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
|
func snapshotMeRes(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA {
|
||||||
data := getText80FromMemory(a, isSecondPage)
|
data := getText80FromMemory(vs, isSecondPage)
|
||||||
return renderGrMode(data, true /*isMeres*/, light)
|
return renderGr(data, true /*isMeres*/, light)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderGrMode(data []uint8, isDoubleResMode bool, light color.Color) *image.RGBA {
|
func renderGr(data []uint8, isDoubleResMode bool, light color.Color) *image.RGBA {
|
||||||
grLines := textLines * 2
|
grLines := textLines * 2
|
||||||
columns := len(data) / textLines
|
columns := len(data) / textLines
|
||||||
pixelWidth := loResPixelWidth
|
pixelWidth := loResPixelWidth
|
|
@ -1,4 +1,4 @@
|
||||||
package izapple2
|
package screen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,4 +1,4 @@
|
||||||
package izapple2
|
package screen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
|
@ -11,29 +11,28 @@ const (
|
||||||
shrHeight = 200
|
shrHeight = 200
|
||||||
palettesCount = 256
|
palettesCount = 256
|
||||||
|
|
||||||
shrPixelDataAddress = uint16(0x2000)
|
shrScanLineControlOffset = uint16(0x7d00)
|
||||||
shrScanLineControlAddress = uint16(0x9d00)
|
shrColorPalettesOffset = uint16(0x7e00)
|
||||||
shrColorPalettesAddress = uint16(0x9e00)
|
|
||||||
shrColorPalettesAddressEnd = uint16(0xa000)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func snapshotSuperHiResMode(a *Apple2) *image.RGBA {
|
func snapshotSuperHiRes(vs VideoSource) *image.RGBA {
|
||||||
|
data := vs.GetSuperVideoMemory()
|
||||||
|
return renderSuperHiRes(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderSuperHiRes(data []uint8) *image.RGBA {
|
||||||
// See "Apple IIGS Hardware Reference", chapter 4, page 91
|
// See "Apple IIGS Hardware Reference", chapter 4, page 91
|
||||||
// http://www.applelogic.org/files/GSHARDWAREREF.pdf
|
// http://www.applelogic.org/files/GSHARDWAREREF.pdf
|
||||||
|
|
||||||
size := image.Rect(0, 0, shrWidth, shrHeight)
|
size := image.Rect(0, 0, shrWidth, shrHeight)
|
||||||
img := image.NewRGBA(size)
|
img := image.NewRGBA(size)
|
||||||
|
|
||||||
videoRAM := a.mmu.getVideoRAM(true)
|
|
||||||
|
|
||||||
// Load the palettes
|
// Load the palettes
|
||||||
paletteMem := videoRAM.subRange(shrColorPalettesAddress, shrColorPalettesAddressEnd)
|
|
||||||
colors := make([]color.Color, palettesCount)
|
colors := make([]color.Color, palettesCount)
|
||||||
iMem := 0
|
iMem := uint16(0)
|
||||||
for i := 0; i < palettesCount; i++ {
|
for i := 0; i < palettesCount; i++ {
|
||||||
b0 := paletteMem[iMem]
|
b0 := data[iMem+shrColorPalettesOffset]
|
||||||
iMem++
|
iMem++
|
||||||
b1 := paletteMem[iMem]
|
b1 := data[iMem+shrColorPalettesOffset]
|
||||||
iMem++
|
iMem++
|
||||||
|
|
||||||
red := (b1 & 0x0f) << 4
|
red := (b1 & 0x0f) << 4
|
||||||
|
@ -48,14 +47,13 @@ func snapshotSuperHiResMode(a *Apple2) *image.RGBA {
|
||||||
|
|
||||||
// Build the lines
|
// Build the lines
|
||||||
for y := 0; y < shrHeight; y++ {
|
for y := 0; y < shrHeight; y++ {
|
||||||
controlByte := videoRAM.peek(shrScanLineControlAddress + uint16(y))
|
controlByte := data[uint16(y)+shrScanLineControlOffset]
|
||||||
is640Wide := (controlByte & 0x80) != 0
|
is640Wide := (controlByte & 0x80) != 0
|
||||||
isColorFill := (controlByte & 0x20) != 0
|
isColorFill := (controlByte & 0x20) != 0
|
||||||
paletteIndex := (controlByte & 0x0f) << 4
|
paletteIndex := (controlByte & 0x0f) << 4
|
||||||
|
|
||||||
lineAddress := shrPixelDataAddress + uint16(shrWidthBytes*y)
|
lineAddress := uint16(shrWidthBytes * y)
|
||||||
lineBytes := videoRAM.subRange(lineAddress, uint16(lineAddress+shrWidthBytes))
|
lineBytes := data[lineAddress : lineAddress+shrWidthBytes]
|
||||||
|
|
||||||
if is640Wide {
|
if is640Wide {
|
||||||
// Line is 640 pixels, two bits per pixel
|
// Line is 640 pixels, two bits per pixel
|
||||||
x := 0
|
x := 0
|
|
@ -1,11 +1,10 @@
|
||||||
package izapple2
|
package screen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -13,44 +12,32 @@ const (
|
||||||
charHeight = 8
|
charHeight = 8
|
||||||
text40Columns = 40
|
text40Columns = 40
|
||||||
textLines = 24
|
textLines = 24
|
||||||
|
|
||||||
textPage1Address = uint16(0x0400)
|
|
||||||
textPage2Address = uint16(0x0800)
|
|
||||||
textPageSize = uint16(0x0400)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func snapshotText40Mode(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
|
func snapshotText40(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA {
|
||||||
text := getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage)
|
text := getTextFromMemory(vs, isSecondPage, false)
|
||||||
return renderTextMode(a, text, nil /*colorMap*/, light)
|
return renderText(vs, text, nil /*colorMap*/, light)
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotText80Mode(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
|
func snapshotText80(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA {
|
||||||
text := getText80FromMemory(a, isSecondPage)
|
text := getText80FromMemory(vs, isSecondPage)
|
||||||
return renderTextMode(a, text, nil /*colorMap*/, light)
|
return renderText(vs, text, nil /*colorMap*/, light)
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotText40RGBMode(a *Apple2, isSecondPage bool) *image.RGBA {
|
func snapshotText40RGB(vs VideoSource, isSecondPage bool) *image.RGBA {
|
||||||
text := getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage)
|
text := getTextFromMemory(vs, isSecondPage, false)
|
||||||
colorMap := getTextFromMemory(a.mmu.getVideoRAM(true), isSecondPage)
|
colorMap := getTextFromMemory(vs, isSecondPage, true)
|
||||||
return renderTextMode(a, text, colorMap, nil)
|
return renderText(vs, text, colorMap, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotText40RGBModeColors(a *Apple2, isSecondPage bool) *image.RGBA {
|
func snapshotText40RGBColors(vs VideoSource, isSecondPage bool) *image.RGBA {
|
||||||
colorMap := getTextFromMemory(a.mmu.getVideoRAM(true), isSecondPage)
|
colorMap := getTextFromMemory(vs, isSecondPage, true)
|
||||||
return renderTextMode(a, nil /*text*/, colorMap, nil)
|
return renderText(vs, nil /*text*/, colorMap, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTextCharOffset(col int, line int) uint16 {
|
func getText80FromMemory(vs VideoSource, isSecondPage bool) []uint8 {
|
||||||
// See "Understanding the Apple II", page 5-10
|
text40Columns := getTextFromMemory(vs, isSecondPage, false)
|
||||||
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
text40ColumnsAlt := getTextFromMemory(vs, isSecondPage, true)
|
||||||
section := line / 8 // Top, middle and bottom
|
|
||||||
eighth := line % 8
|
|
||||||
return uint16(section*40 + eighth*0x80 + col)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getText80FromMemory(a *Apple2, isSecondPage bool) []uint8 {
|
|
||||||
text40Columns := getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage)
|
|
||||||
text40ColumnsAlt := getTextFromMemory(a.mmu.getVideoRAM(true), isSecondPage)
|
|
||||||
|
|
||||||
// Merge the two 40 cols to return 80 cols
|
// Merge the two 40 cols to return 80 cols
|
||||||
text80Columns := make([]uint8, 2*len(text40Columns))
|
text80Columns := make([]uint8, 2*len(text40Columns))
|
||||||
|
@ -62,13 +49,8 @@ func getText80FromMemory(a *Apple2, isSecondPage bool) []uint8 {
|
||||||
return text80Columns
|
return text80Columns
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTextFromMemory(mem *memoryRange, isSecondPage bool) []uint8 {
|
func getTextFromMemory(vs VideoSource, isSecondPage bool, isExt bool) []uint8 {
|
||||||
addressStart := textPage1Address
|
data := vs.GetTextMemory(isSecondPage, isExt)
|
||||||
if isSecondPage {
|
|
||||||
addressStart = textPage2Address
|
|
||||||
}
|
|
||||||
addressEnd := addressStart + textPageSize
|
|
||||||
data := mem.subRange(addressStart, addressEnd)
|
|
||||||
|
|
||||||
text := make([]uint8, textLines*text40Columns)
|
text := make([]uint8, textLines*text40Columns)
|
||||||
for l := 0; l < textLines; l++ {
|
for l := 0; l < textLines; l++ {
|
||||||
|
@ -80,6 +62,14 @@ func getTextFromMemory(mem *memoryRange, isSecondPage bool) []uint8 {
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTextCharOffset(col int, line int) uint16 {
|
||||||
|
// See "Understanding the Apple II", page 5-10
|
||||||
|
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
||||||
|
section := line / 8 // Top, middle and bottom
|
||||||
|
eighth := line % 8
|
||||||
|
return uint16(section*40 + eighth*0x80 + col)
|
||||||
|
}
|
||||||
|
|
||||||
func getRGBTextColor(pixel bool, colorKey uint8) color.Color {
|
func getRGBTextColor(pixel bool, colorKey uint8) color.Color {
|
||||||
if pixel {
|
if pixel {
|
||||||
colorKey >>= 4
|
colorKey >>= 4
|
||||||
|
@ -89,11 +79,7 @@ func getRGBTextColor(pixel bool, colorKey uint8) color.Color {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTextMode(a *Apple2, text []uint8, colorMap []uint8, light color.Color) *image.RGBA {
|
func renderText(vs VideoSource, text []uint8, colorMap []uint8, light color.Color) *image.RGBA {
|
||||||
// Flash mode is 2Hz (host time)
|
|
||||||
isFlashedFrame := time.Now().Nanosecond() > (1 * 1000 * 1000 * 1000 / 2)
|
|
||||||
isAltText := a.io.isSoftSwitchActive(ioFlagAltChar)
|
|
||||||
|
|
||||||
columns := len(text) / textLines
|
columns := len(text) / textLines
|
||||||
if text == nil {
|
if text == nil {
|
||||||
columns = text40Columns
|
columns = text40Columns
|
||||||
|
@ -118,26 +104,7 @@ func renderTextMode(a *Apple2, text []uint8, colorMap []uint8, light color.Color
|
||||||
char = 79 + 128 // Debug screen filed with O
|
char = 79 + 128 // Debug screen filed with O
|
||||||
}
|
}
|
||||||
|
|
||||||
var pixel bool
|
pixel := vs.GetCharacterPixel(char, rowInChar, colInChar)
|
||||||
if a.isApple2e {
|
|
||||||
vid6 := (char & 0x40) != 0
|
|
||||||
vid7 := (char & 0x80) != 0
|
|
||||||
char := char & 0x3f
|
|
||||||
if vid6 && (vid7 || isAltText) {
|
|
||||||
char += 0x40
|
|
||||||
}
|
|
||||||
if vid7 || (vid6 && isFlashedFrame && !isAltText) {
|
|
||||||
char += 0x80
|
|
||||||
}
|
|
||||||
pixel = !a.cg.getPixel(char, rowInChar, colInChar)
|
|
||||||
} else {
|
|
||||||
pixel = a.cg.getPixel(char, rowInChar, colInChar)
|
|
||||||
topBits := char >> 6
|
|
||||||
isInverse := topBits == 0
|
|
||||||
isFlash := topBits == 1
|
|
||||||
|
|
||||||
pixel = pixel != (isInverse || (isFlash && isFlashedFrame))
|
|
||||||
}
|
|
||||||
|
|
||||||
var colour color.Color
|
var colour color.Color
|
||||||
if colorMap != nil {
|
if colorMap != nil {
|
||||||
|
@ -160,18 +127,18 @@ func renderTextMode(a *Apple2, text []uint8, colorMap []uint8, light color.Color
|
||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
// DumpTextModeAnsi returns the text mode contents using ANSI escape codes
|
// RenderTextModeAnsi returns the text mode contents using ANSI escape codes for reverse and flash
|
||||||
// for reverse and flash
|
func RenderTextModeAnsi(vs VideoSource, is80Columns bool, isSecondPage bool, isAltText bool) string {
|
||||||
func DumpTextModeAnsi(a *Apple2) string {
|
//func DumpTextModeAnsi(a *Apple2) string {
|
||||||
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
// is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
||||||
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
// isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
||||||
isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
|
// isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
|
||||||
|
|
||||||
var text []uint8
|
var text []uint8
|
||||||
if is80Columns {
|
if is80Columns {
|
||||||
text = getText80FromMemory(a, isSecondPage)
|
text = getText80FromMemory(vs, isSecondPage)
|
||||||
} else {
|
} else {
|
||||||
text = getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage)
|
text = getTextFromMemory(vs, isSecondPage, false)
|
||||||
}
|
}
|
||||||
columns := len(text) / textLines
|
columns := len(text) / textLines
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package izapple2
|
package screen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
|
@ -0,0 +1,41 @@
|
||||||
|
package screen
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Base Video Mode
|
||||||
|
VideoBaseMask uint16 = 0x1f
|
||||||
|
VideoText40 uint16 = 0x01
|
||||||
|
VideoGR uint16 = 0x02
|
||||||
|
VideoHGR uint16 = 0x03
|
||||||
|
VideoText80 uint16 = 0x08
|
||||||
|
VideoDGR uint16 = 0x09
|
||||||
|
VideoDHGR uint16 = 0x0a
|
||||||
|
VideoText40RGB uint16 = 0x10
|
||||||
|
VideoMono560 uint16 = 0x11
|
||||||
|
VideoRGBMix uint16 = 0x12
|
||||||
|
VideoRGB160 uint16 = 0x13
|
||||||
|
VideoSHR uint16 = 0x14
|
||||||
|
|
||||||
|
// Mix text modifiers
|
||||||
|
VideoMixTextMask uint16 = 0x0f00
|
||||||
|
VideoMixText40 uint16 = 0x0100
|
||||||
|
VideoMixText80 uint16 = 0x0200
|
||||||
|
VideoMixText40RGB uint16 = 0x0300
|
||||||
|
|
||||||
|
// Other modifiers
|
||||||
|
VideoModifiersMask uint16 = 0xf000
|
||||||
|
VideoSecondPage uint16 = 0x1000
|
||||||
|
)
|
||||||
|
|
||||||
|
// VideoSource provides the info to build the video output
|
||||||
|
type VideoSource interface {
|
||||||
|
// GetCurrentVideoMode returns the active video mode
|
||||||
|
GetCurrentVideoMode() uint16
|
||||||
|
// GetTextMemory returns a slice to the text memory pages
|
||||||
|
GetTextMemory(secondPage bool, ext bool) []uint8
|
||||||
|
// GetVideoMemory returns a slice to the video memory pages
|
||||||
|
GetVideoMemory(secondPage bool, ext bool) []uint8
|
||||||
|
// GetCharactePixel returns the pixel as output by the character generator
|
||||||
|
GetCharacterPixel(char uint8, rowInChar int, colInChar int) bool
|
||||||
|
// GetSuperVideoMemory returns a slice to the SHR video memory
|
||||||
|
GetSuperVideoMemory() []uint8
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
package izapple2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ivanizag/izapple2/screen"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
textPage1Address = uint16(0x0400)
|
||||||
|
textPage2Address = uint16(0x0800)
|
||||||
|
textPageSize = uint16(0x0400)
|
||||||
|
hiResPage1Address = uint16(0x2000)
|
||||||
|
hiResPage2Address = uint16(0x4000)
|
||||||
|
hiResPageSize = uint16(0x2000)
|
||||||
|
shResPageAddress = uint16(0x2000)
|
||||||
|
shResPageSize = uint16(0x8000)
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetCurrentVideoMode returns the active video mode
|
||||||
|
func (a *Apple2) GetCurrentVideoMode() uint16 {
|
||||||
|
isTextMode := a.io.isSoftSwitchActive(ioFlagText)
|
||||||
|
isHiResMode := a.io.isSoftSwitchActive(ioFlagHiRes)
|
||||||
|
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
||||||
|
isStore80Active := a.mmu.store80Active
|
||||||
|
isDoubleResMode := !isTextMode && is80Columns && !a.io.isSoftSwitchActive(ioFlagAnnunciator3)
|
||||||
|
isSuperHighResMode := a.io.isSoftSwitchActive(ioDataNewVideo)
|
||||||
|
|
||||||
|
isRGBCard := a.io.isSoftSwitchActive(ioFlagRGBCardActive)
|
||||||
|
rgbFlag1 := a.io.isSoftSwitchActive(ioFlag1RGBCard)
|
||||||
|
rgbFlag2 := a.io.isSoftSwitchActive(ioFlag2RGBCard)
|
||||||
|
isMono560 := isDoubleResMode && !rgbFlag1 && !rgbFlag2
|
||||||
|
isRGBMixMode := isDoubleResMode && !rgbFlag1 && rgbFlag2
|
||||||
|
isRGB160Mode := isDoubleResMode && rgbFlag1 && !rgbFlag2
|
||||||
|
|
||||||
|
isMixMode := a.io.isSoftSwitchActive(ioFlagMixed)
|
||||||
|
|
||||||
|
var mode uint16
|
||||||
|
if isSuperHighResMode {
|
||||||
|
mode = screen.VideoSHR
|
||||||
|
isMixMode = false
|
||||||
|
} else if isTextMode {
|
||||||
|
if is80Columns {
|
||||||
|
mode = screen.VideoText80
|
||||||
|
} else if isRGBCard && isStore80Active {
|
||||||
|
mode = screen.VideoText40RGB
|
||||||
|
} else {
|
||||||
|
mode = screen.VideoText40
|
||||||
|
}
|
||||||
|
isMixMode = false
|
||||||
|
} else if isHiResMode {
|
||||||
|
if !isDoubleResMode {
|
||||||
|
mode = screen.VideoHGR
|
||||||
|
} else if isMono560 {
|
||||||
|
mode = screen.VideoMono560
|
||||||
|
} else if isRGBMixMode {
|
||||||
|
mode = screen.VideoRGBMix
|
||||||
|
} else if isRGB160Mode {
|
||||||
|
mode = screen.VideoRGB160
|
||||||
|
} else {
|
||||||
|
mode = screen.VideoDHGR
|
||||||
|
}
|
||||||
|
} else if isDoubleResMode {
|
||||||
|
mode = screen.VideoDGR
|
||||||
|
} else {
|
||||||
|
mode = screen.VideoGR
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
if isMixMode {
|
||||||
|
if is80Columns {
|
||||||
|
mode |= screen.VideoMixText80
|
||||||
|
} else /* if isStore80Active {
|
||||||
|
mode |= screen.VideoMixText40RGB
|
||||||
|
} else */{
|
||||||
|
mode |= screen.VideoMixText40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
||||||
|
if isSecondPage {
|
||||||
|
mode |= screen.VideoSecondPage
|
||||||
|
}
|
||||||
|
|
||||||
|
return mode
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTextMemory returns a slice to the text memory pages
|
||||||
|
func (a *Apple2) GetTextMemory(secondPage bool, ext bool) []uint8 {
|
||||||
|
mem := a.mmu.getVideoRAM(ext)
|
||||||
|
addressStart := textPage1Address
|
||||||
|
if secondPage {
|
||||||
|
addressStart = textPage2Address
|
||||||
|
}
|
||||||
|
return mem.subRange(addressStart, addressStart+textPageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVideoMemory returns a slice to the video memory pages
|
||||||
|
func (a *Apple2) GetVideoMemory(secondPage bool, ext bool) []uint8 {
|
||||||
|
mem := a.mmu.getVideoRAM(ext)
|
||||||
|
addressStart := hiResPage1Address
|
||||||
|
if secondPage {
|
||||||
|
addressStart = hiResPage2Address
|
||||||
|
}
|
||||||
|
return mem.subRange(addressStart, addressStart+hiResPageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSuperVideoMemory returns a slice to the SHR video memory
|
||||||
|
func (a *Apple2) GetSuperVideoMemory() []uint8 {
|
||||||
|
mem := a.mmu.getVideoRAM(true)
|
||||||
|
return mem.subRange(shResPageAddress, shResPageAddress+shResPageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCharacterPixel returns the pixel as output by the character generator
|
||||||
|
func (a *Apple2) GetCharacterPixel(char uint8, rowInChar int, colInChar int) bool {
|
||||||
|
// Flash mode is 2Hz (host time)
|
||||||
|
isFlashedFrame := time.Now().Nanosecond() > (1 * 1000 * 1000 * 1000 / 2)
|
||||||
|
isAltText := a.io.isSoftSwitchActive(ioFlagAltChar)
|
||||||
|
|
||||||
|
var pixel bool
|
||||||
|
if a.isApple2e {
|
||||||
|
vid6 := (char & 0x40) != 0
|
||||||
|
vid7 := (char & 0x80) != 0
|
||||||
|
char := char & 0x3f
|
||||||
|
if vid6 && (vid7 || isAltText) {
|
||||||
|
char += 0x40
|
||||||
|
}
|
||||||
|
if vid7 || (vid6 && isFlashedFrame && !isAltText) {
|
||||||
|
char += 0x80
|
||||||
|
}
|
||||||
|
pixel = !a.cg.getPixel(char, rowInChar, colInChar)
|
||||||
|
} else {
|
||||||
|
pixel = a.cg.getPixel(char, rowInChar, colInChar)
|
||||||
|
topBits := char >> 6
|
||||||
|
isInverse := topBits == 0
|
||||||
|
isFlash := topBits == 1
|
||||||
|
|
||||||
|
pixel = pixel != (isInverse || (isFlash && isFlashedFrame))
|
||||||
|
}
|
||||||
|
return pixel
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpTextModeAnsi returns the text mode contents using ANSI escape codes for reverse and flash
|
||||||
|
func DumpTextModeAnsi(a *Apple2) string {
|
||||||
|
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
||||||
|
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
||||||
|
isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
|
||||||
|
return screen.RenderTextModeAnsi(a, is80Columns, isSecondPage, isAltText)
|
||||||
|
}
|
Loading…
Reference in New Issue