Separate package for screen rendering

This commit is contained in:
Ivan Izaguirre 2020-10-16 20:41:34 +02:00 committed by Iván Izaguirre
parent 9d4df5b643
commit 083c19d2c2
19 changed files with 525 additions and 450 deletions

View File

@ -6,6 +6,7 @@ import (
"fyne.io/fyne"
"fyne.io/fyne/driver/desktop"
"github.com/ivanizag/izapple2"
"github.com/ivanizag/izapple2/screen"
)
type keyboard struct {
@ -103,10 +104,10 @@ func (k *keyboard) putKey(keyEvent *fyne.KeyEvent) {
k.s.a.SendCommand(izapple2.CommandToggleSpeed)
}
case fyne.KeyF6:
if k.s.screenMode != izapple2.ScreenModeGreen {
k.s.screenMode = izapple2.ScreenModeGreen
if k.s.screenMode != screen.ScreenModeGreen {
k.s.screenMode = screen.ScreenModeGreen
} else {
k.s.screenMode = izapple2.ScreenModeNTSC
k.s.screenMode = screen.ScreenModeNTSC
}
case fyne.KeyF7:
k.s.showPages = !k.s.showPages
@ -118,7 +119,7 @@ func (k *keyboard) putKey(keyEvent *fyne.KeyEvent) {
k.s.a.SendCommand(izapple2.CommandToggleCPUTrace)
case fyne.KeyF12:
//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 {
fmt.Printf("Error saving snapshoot: %v.\n.", err)
} else {

View File

@ -6,6 +6,7 @@ import (
"time"
"github.com/ivanizag/izapple2"
"github.com/ivanizag/izapple2/screen"
"github.com/pkg/profile"
@ -42,7 +43,7 @@ func main() {
}
func fyneRun(s *state) {
s.screenMode = izapple2.ScreenModeNTSC
s.screenMode = screen.ScreenModeNTSC
s.app = app.New()
s.app.SetIcon(resourceApple2Png)
@ -52,13 +53,13 @@ func fyneRun(s *state) {
s.devices = newPanelDevices(s)
s.devices.w.Hide()
toolbar := buildToolbar(s)
screen := canvas.NewImageFromImage(nil)
screen.ScaleMode = canvas.ImageScalePixels // Looks worst but loads less.
screen.SetMinSize(fyne.NewSize(280*2, 192*2))
display := canvas.NewImageFromImage(nil)
display.ScaleMode = canvas.ImageScalePixels // Looks worst but loads less.
display.SetMinSize(fyne.NewSize(280*2, 192*2))
container := fyne.NewContainerWithLayout(
layout.NewBorderLayout(nil, toolbar, nil, s.devices.w),
screen, toolbar, s.devices.w,
display, toolbar, s.devices.w,
)
s.win.SetContent(container)
s.win.SetPadded(false)
@ -81,13 +82,13 @@ func fyneRun(s *state) {
if !s.a.IsPaused() {
var img *image.RGBA
if s.showPages {
img = s.a.SnapshotParts(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))
img = screen.SnapshotParts(s.a, s.screenMode)
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 {
img = s.a.Snapshot(s.screenMode)
img = screen.Snapshot(s.a, s.screenMode)
}
screen.Image = img
canvas.Refresh(screen)
display.Image = img
canvas.Refresh(display)
}
}
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/ivanizag/izapple2"
"github.com/ivanizag/izapple2/screen"
"fyne.io/fyne"
"fyne.io/fyne/theme"
@ -34,7 +35,7 @@ func buildToolbar(s *state) *widget.Toolbar {
}))
tb.Append(widget.NewToolbarAction(
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 {
s.app.SendNotification(fyne.NewNotification(
s.win.Title(),

View File

@ -4,7 +4,7 @@ import (
"fyne.io/fyne"
"fyne.io/fyne/theme"
"fyne.io/fyne/widget"
"github.com/ivanizag/izapple2"
"github.com/ivanizag/izapple2/screen"
)
type toolbarScreen struct {
@ -25,7 +25,7 @@ func newToolbarScreen(s *state) *toolbarScreen {
tbs.ntsc = widget.NewButtonWithIcon("",
theme.NewThemedResource(resourceTelevisionClassicSvg, nil),
func() {
tbs.setScreenMode(izapple2.ScreenModeNTSC)
tbs.setScreenMode(screen.ScreenModeNTSC)
})
tbs.ntscDisabled = widget.NewIcon(
theme.NewDisabledResource(resourceTelevisionClassicSvg))
@ -33,7 +33,7 @@ func newToolbarScreen(s *state) *toolbarScreen {
tbs.plain = widget.NewButtonWithIcon("",
theme.NewThemedResource(resourceTelevisionSvg, nil),
func() {
tbs.setScreenMode(izapple2.ScreenModePlain)
tbs.setScreenMode(screen.ScreenModePlain)
})
tbs.plainDisabled = widget.NewIcon(
theme.NewDisabledResource(resourceTelevisionSvg))
@ -41,7 +41,7 @@ func newToolbarScreen(s *state) *toolbarScreen {
tbs.green = widget.NewButtonWithIcon("",
theme.NewThemedResource(resourceMonitorSvg, nil),
func() {
tbs.setScreenMode(izapple2.ScreenModeGreen)
tbs.setScreenMode(screen.ScreenModeGreen)
})
tbs.greenDisabled = widget.NewIcon(
theme.NewDisabledResource(resourceMonitorSvg))
@ -61,13 +61,13 @@ func newToolbarScreen(s *state) *toolbarScreen {
func (tbs *toolbarScreen) setScreenMode(screenMode int) {
switch tbs.s.screenMode {
case izapple2.ScreenModeNTSC:
case screen.ScreenModeNTSC:
tbs.ntsc.Show()
tbs.ntscDisabled.Hide()
case izapple2.ScreenModePlain:
case screen.ScreenModePlain:
tbs.plain.Show()
tbs.plainDisabled.Hide()
case izapple2.ScreenModeGreen:
case screen.ScreenModeGreen:
tbs.green.Show()
tbs.greenDisabled.Hide()
}
@ -75,13 +75,13 @@ func (tbs *toolbarScreen) setScreenMode(screenMode int) {
tbs.s.screenMode = screenMode
switch screenMode {
case izapple2.ScreenModeNTSC:
case screen.ScreenModeNTSC:
tbs.ntsc.Hide()
tbs.ntscDisabled.Show()
case izapple2.ScreenModePlain:
case screen.ScreenModePlain:
tbs.plain.Hide()
tbs.plainDisabled.Show()
case izapple2.ScreenModeGreen:
case screen.ScreenModeGreen:
tbs.green.Hide()
tbs.greenDisabled.Show()
}

View File

@ -6,6 +6,7 @@ import (
"unsafe"
"github.com/ivanizag/izapple2"
"github.com/ivanizag/izapple2/screen"
"github.com/pkg/profile"
"github.com/veandco/go-sdl2/sdl"
@ -85,10 +86,10 @@ func sdlRun(a *izapple2.Apple2) {
if !a.IsPaused() {
var img *image.RGBA
if kp.showPages {
img = a.SnapshotParts(izapple2.ScreenModeNTSC)
window.SetTitle(fmt.Sprintf("%v %v %vx%v", a.Name, a.VideoModeName(), img.Rect.Dx()/2, img.Rect.Dy()/2))
img = screen.SnapshotParts(a, screen.ScreenModeNTSC)
window.SetTitle(fmt.Sprintf("%v %v %vx%v", a.Name, screen.VideoModeName(a), img.Rect.Dx()/2, img.Rect.Dy()/2))
} else {
img = a.Snapshot(izapple2.ScreenModeNTSC)
img = screen.Snapshot(a, screen.ScreenModeNTSC)
}
if img != nil {
surface, err := sdl.CreateRGBSurfaceFrom(unsafe.Pointer(&img.Pix[0]),

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/ivanizag/izapple2"
"github.com/ivanizag/izapple2/screen"
"github.com/veandco/go-sdl2/sdl"
)
@ -104,7 +105,7 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
k.a.SendCommand(izapple2.CommandToggleCPUTrace)
case sdl.K_F12:
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 {
fmt.Printf("Error saving snapshoot: %v.\n.", err)
} else {

260
screen.go
View File

@ -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
}

168
screen/screen.go Normal file
View File

@ -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
}

View File

@ -1,20 +1,20 @@
package izapple2
package screen
import (
"image"
)
// SnapshotParts the currently visible screen
func (a *Apple2) SnapshotParts(screenMode int) *image.RGBA {
videoMode := getCurrentVideoMode(a)
isSecondPage := (videoMode & videoSecondPage) != 0
videoBase := videoMode & videoBaseMask
mixMode := videoMode & videoMixTextMask
modifiers := videoMode & videoModifiersMask
func SnapshotParts(vs VideoSource, screenMode int) *image.RGBA {
videoMode := vs.GetCurrentVideoMode()
isSecondPage := (videoMode & VideoSecondPage) != 0
videoBase := videoMode & VideoBaseMask
mixMode := videoMode & VideoMixTextMask
modifiers := videoMode & VideoModifiersMask
snapScreen := snapshotByMode(a, videoMode, screenMode)
snapPage1 := snapshotByMode(a, videoMode&^videoSecondPage, screenMode)
snapPage2 := snapshotByMode(a, videoMode|videoSecondPage, screenMode)
snapScreen := snapshotByMode(vs, videoMode, screenMode)
snapPage1 := snapshotByMode(vs, videoMode&^VideoSecondPage, screenMode)
snapPage2 := snapshotByMode(vs, videoMode|VideoSecondPage, screenMode)
var snapAux *image.RGBA
/*
@ -23,16 +23,16 @@ func (a *Apple2) SnapshotParts(screenMode int) *image.RGBA {
snapAux = filterMask(mask)
}*/
if videoBase == videoText40RGB {
snapAux = snapshotText40RGBModeColors(a, isSecondPage)
if videoBase == VideoText40RGB {
snapAux = snapshotText40RGBColors(vs, isSecondPage)
} else {
switch mixMode {
case videoMixText80:
snapAux = snapshotByMode(a, videoText80|modifiers, screenMode)
case videoMixText40RGB:
snapAux = snapshotByMode(a, videoText40RGB|modifiers, screenMode)
case VideoMixText80:
snapAux = snapshotByMode(vs, VideoText80|modifiers, screenMode)
case VideoMixText40RGB:
snapAux = snapshotByMode(vs, VideoText40RGB|modifiers, screenMode)
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
func (a *Apple2) VideoModeName() string {
videoMode := getCurrentVideoMode(a)
videoBase := videoMode & videoBaseMask
mixMode := videoMode & videoMixTextMask
func VideoModeName(vs VideoSource) string {
videoMode := vs.GetCurrentVideoMode()
videoBase := videoMode & VideoBaseMask
mixMode := videoMode & VideoMixTextMask
var name string
switch videoBase {
case videoText40:
case VideoText40:
name = "TEXT40COL"
case videoText80:
case VideoText80:
name = "TEXT80COL"
case videoText40RGB:
case VideoText40RGB:
name = "TEXT40COLRGB"
case videoGR:
case VideoGR:
name = "GR"
case videoDGR:
case VideoDGR:
name = "DGR"
case videoHGR:
case VideoHGR:
name = "HGR"
case videoDHGR:
case VideoDHGR:
name = "DHGR"
case videoMono560:
case VideoMono560:
name = "Mono560"
case videoRGBMix:
case VideoRGBMix:
name = "RGMMIX"
case videoRGB160:
case VideoRGB160:
name = "RGB160"
case videoSHR:
case VideoSHR:
name = "SHR"
default:
name = "Unknown video mode"
}
if (videoMode & videoSecondPage) != 0 {
if (videoMode & VideoSecondPage) != 0 {
name += "-PAGE2"
}
switch mixMode {
case videoMixText40:
case VideoMixText40:
name += "-MIX40"
case videoMixText80:
case VideoMixText80:
name += "-MIX80"
case videoMixText40RGB:
case VideoMixText40RGB:
name += "-MIX40RGB"
}

View File

@ -1,4 +1,4 @@
package izapple2
package screen
import (
"image"
@ -10,7 +10,14 @@ const (
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"
size := image.Rect(0, 0, doubleHiResWidth, hiResHeight)
img := image.NewRGBA(size)
@ -23,9 +30,10 @@ func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, getNTSCMask bool,
}
for y := 0; y < hiResHeight; y++ {
offset := getHiResLineOffset(y)
lineParts := [][]uint8{
getHiResLine(a, y, isSecondPage, true /*auxmem*/),
getHiResLine(a, y, isSecondPage, false /*auxmem*/),
dataAux[offset : offset+hiResLineBytes],
dataMain[offset : offset+hiResLineBytes],
}
x := 0
// 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
}
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)
img := image.NewRGBA(size)
for y := 0; y < hiResHeight; y++ {
offset := getHiResLineOffset(y)
lineParts := [][]uint8{
getHiResLine(a, y, isSecondPage, true /*auxmem*/),
getHiResLine(a, y, isSecondPage, false /*auxmem*/),
dataAux[offset : offset+hiResLineBytes],
dataMain[offset : offset+hiResLineBytes],
}
x := 0
for iByte := 0; iByte < hiResLineBytes; iByte++ {

View File

@ -1,4 +1,4 @@
package izapple2
package screen
import (
"image"
@ -6,16 +6,18 @@ import (
)
const (
hiResWidth = 280
hiResLineBytes = hiResWidth / 7
hiResHeight = 192
hiResHeightMixed = 160
hiResPage1Address = uint16(0x2000)
hiResPage2Address = uint16(0x4000)
hiResWidth = 280
hiResLineBytes = hiResWidth / 7
hiResHeight = 192
hiResHeightMixed = 160
)
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
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
section := line >> 6 // Top, middle and bottom
@ -24,23 +26,14 @@ func getHiResLineOffset(line int) uint16 {
return uint16(section*40 + outerEighth*0x80 + innerEighth*0x400)
}
func getHiResLine(a *Apple2, line int, isSecondPage bool, auxMem bool) []uint8 {
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 {
func renderHiRes(data []uint8, light color.Color) *image.RGBA {
// As described in "Undertanding the Apple II", with half pixel shifts
size := image.Rect(0, 0, 2*hiResWidth, hiResHeight)
img := image.NewRGBA(size)
for y := 0; y < hiResHeight; y++ {
bytes := getHiResLine(a, y, isSecondPage, false /*auxMem*/)
offset := getHiResLineOffset(y)
bytes := data[offset : offset+hiResLineBytes]
x := 0
var previousColour color.Color = color.Black
for _, b := range bytes {

View File

@ -1,4 +1,4 @@
package izapple2
package screen
import (
"testing"

View File

@ -1,4 +1,4 @@
package izapple2
package screen
import (
"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 {
data := getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage)
return renderGrMode(data, false /*isMeres*/, light)
func snapshotLoRes(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA {
data := getTextFromMemory(vs, isSecondPage, false)
return renderGr(data, false /*isMeres*/, light)
}
func snapshotMeResModeMono(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
data := getText80FromMemory(a, isSecondPage)
return renderGrMode(data, true /*isMeres*/, light)
func snapshotMeRes(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA {
data := getText80FromMemory(vs, isSecondPage)
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
columns := len(data) / textLines
pixelWidth := loResPixelWidth

View File

@ -1,4 +1,4 @@
package izapple2
package screen
import (
"fmt"

View File

@ -1,4 +1,4 @@
package izapple2
package screen
import (
"image"
@ -11,29 +11,28 @@ const (
shrHeight = 200
palettesCount = 256
shrPixelDataAddress = uint16(0x2000)
shrScanLineControlAddress = uint16(0x9d00)
shrColorPalettesAddress = uint16(0x9e00)
shrColorPalettesAddressEnd = uint16(0xa000)
shrScanLineControlOffset = uint16(0x7d00)
shrColorPalettesOffset = uint16(0x7e00)
)
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
// http://www.applelogic.org/files/GSHARDWAREREF.pdf
size := image.Rect(0, 0, shrWidth, shrHeight)
img := image.NewRGBA(size)
videoRAM := a.mmu.getVideoRAM(true)
// Load the palettes
paletteMem := videoRAM.subRange(shrColorPalettesAddress, shrColorPalettesAddressEnd)
colors := make([]color.Color, palettesCount)
iMem := 0
iMem := uint16(0)
for i := 0; i < palettesCount; i++ {
b0 := paletteMem[iMem]
b0 := data[iMem+shrColorPalettesOffset]
iMem++
b1 := paletteMem[iMem]
b1 := data[iMem+shrColorPalettesOffset]
iMem++
red := (b1 & 0x0f) << 4
@ -48,14 +47,13 @@ func snapshotSuperHiResMode(a *Apple2) *image.RGBA {
// Build the lines
for y := 0; y < shrHeight; y++ {
controlByte := videoRAM.peek(shrScanLineControlAddress + uint16(y))
controlByte := data[uint16(y)+shrScanLineControlOffset]
is640Wide := (controlByte & 0x80) != 0
isColorFill := (controlByte & 0x20) != 0
paletteIndex := (controlByte & 0x0f) << 4
lineAddress := shrPixelDataAddress + uint16(shrWidthBytes*y)
lineBytes := videoRAM.subRange(lineAddress, uint16(lineAddress+shrWidthBytes))
lineAddress := uint16(shrWidthBytes * y)
lineBytes := data[lineAddress : lineAddress+shrWidthBytes]
if is640Wide {
// Line is 640 pixels, two bits per pixel
x := 0

View File

@ -1,11 +1,10 @@
package izapple2
package screen
import (
"fmt"
"image"
"image/color"
"strings"
"time"
)
const (
@ -13,44 +12,32 @@ const (
charHeight = 8
text40Columns = 40
textLines = 24
textPage1Address = uint16(0x0400)
textPage2Address = uint16(0x0800)
textPageSize = uint16(0x0400)
)
func snapshotText40Mode(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
text := getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage)
return renderTextMode(a, text, nil /*colorMap*/, light)
func snapshotText40(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA {
text := getTextFromMemory(vs, isSecondPage, false)
return renderText(vs, text, nil /*colorMap*/, light)
}
func snapshotText80Mode(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
text := getText80FromMemory(a, isSecondPage)
return renderTextMode(a, text, nil /*colorMap*/, light)
func snapshotText80(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA {
text := getText80FromMemory(vs, isSecondPage)
return renderText(vs, text, nil /*colorMap*/, light)
}
func snapshotText40RGBMode(a *Apple2, isSecondPage bool) *image.RGBA {
text := getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage)
colorMap := getTextFromMemory(a.mmu.getVideoRAM(true), isSecondPage)
return renderTextMode(a, text, colorMap, nil)
func snapshotText40RGB(vs VideoSource, isSecondPage bool) *image.RGBA {
text := getTextFromMemory(vs, isSecondPage, false)
colorMap := getTextFromMemory(vs, isSecondPage, true)
return renderText(vs, text, colorMap, nil)
}
func snapshotText40RGBModeColors(a *Apple2, isSecondPage bool) *image.RGBA {
colorMap := getTextFromMemory(a.mmu.getVideoRAM(true), isSecondPage)
return renderTextMode(a, nil /*text*/, colorMap, nil)
func snapshotText40RGBColors(vs VideoSource, isSecondPage bool) *image.RGBA {
colorMap := getTextFromMemory(vs, isSecondPage, true)
return renderText(vs, nil /*text*/, colorMap, nil)
}
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 getText80FromMemory(a *Apple2, isSecondPage bool) []uint8 {
text40Columns := getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage)
text40ColumnsAlt := getTextFromMemory(a.mmu.getVideoRAM(true), isSecondPage)
func getText80FromMemory(vs VideoSource, isSecondPage bool) []uint8 {
text40Columns := getTextFromMemory(vs, isSecondPage, false)
text40ColumnsAlt := getTextFromMemory(vs, isSecondPage, true)
// Merge the two 40 cols to return 80 cols
text80Columns := make([]uint8, 2*len(text40Columns))
@ -62,13 +49,8 @@ func getText80FromMemory(a *Apple2, isSecondPage bool) []uint8 {
return text80Columns
}
func getTextFromMemory(mem *memoryRange, isSecondPage bool) []uint8 {
addressStart := textPage1Address
if isSecondPage {
addressStart = textPage2Address
}
addressEnd := addressStart + textPageSize
data := mem.subRange(addressStart, addressEnd)
func getTextFromMemory(vs VideoSource, isSecondPage bool, isExt bool) []uint8 {
data := vs.GetTextMemory(isSecondPage, isExt)
text := make([]uint8, textLines*text40Columns)
for l := 0; l < textLines; l++ {
@ -80,6 +62,14 @@ func getTextFromMemory(mem *memoryRange, isSecondPage bool) []uint8 {
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 {
if pixel {
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 {
// Flash mode is 2Hz (host time)
isFlashedFrame := time.Now().Nanosecond() > (1 * 1000 * 1000 * 1000 / 2)
isAltText := a.io.isSoftSwitchActive(ioFlagAltChar)
func renderText(vs VideoSource, text []uint8, colorMap []uint8, light color.Color) *image.RGBA {
columns := len(text) / textLines
if text == nil {
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
}
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))
}
pixel := vs.GetCharacterPixel(char, rowInChar, colInChar)
var colour color.Color
if colorMap != nil {
@ -160,18 +127,18 @@ func renderTextMode(a *Apple2, text []uint8, colorMap []uint8, light color.Color
return img
}
// 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)
// RenderTextModeAnsi returns the text mode contents using ANSI escape codes for reverse and flash
func RenderTextModeAnsi(vs VideoSource, is80Columns bool, isSecondPage bool, isAltText bool) string {
//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)
var text []uint8
if is80Columns {
text = getText80FromMemory(a, isSecondPage)
text = getText80FromMemory(vs, isSecondPage)
} else {
text = getTextFromMemory(a.mmu.getVideoRAM(false), isSecondPage)
text = getTextFromMemory(vs, isSecondPage, false)
}
columns := len(text) / textLines

View File

@ -1,4 +1,4 @@
package izapple2
package screen
import (
"testing"

41
screen/videoSource.go Normal file
View File

@ -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
}

148
videoSourceImpl.go Normal file
View File

@ -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)
}