RGB card 40 columns text with 16 colors video mode
This commit is contained in:
parent
aa198877ec
commit
0ef160a56e
|
@ -34,6 +34,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
|
|||
- Double-Width High-Resolution graphics (Apple //e only)
|
||||
- Super High Resolution (VidHD only)
|
||||
- Mixed mode
|
||||
- RGB card text 40 columns with 16 colors for foreground and background
|
||||
- RGB card mode 11, mono 560x192
|
||||
- RGB card mode 13, ntsc 140*192 (regular DHGR)
|
||||
- RGB card mode 14, mix of modes 11 and 13 on the fly
|
||||
|
|
|
@ -123,7 +123,7 @@ func (mmu *memoryManager) accessRead(address uint16) memoryHandler {
|
|||
return mmu.getPhysicalMainRAM(mmu.altZeroPage)
|
||||
}
|
||||
if mmu.store80Active && address <= addressLimitHgr {
|
||||
altPage := mmu.apple2.io.isSoftSwitchActive(ioFlagSecondPage)
|
||||
altPage := mmu.apple2.io.isSoftSwitchActive(ioFlagSecondPage) // TODO: move flag to mmu property like the store80
|
||||
if address >= addressStartText && address <= addressLimitText {
|
||||
return mmu.getPhysicalMainRAM(altPage)
|
||||
}
|
||||
|
|
32
screen.go
32
screen.go
|
@ -24,9 +24,10 @@ const (
|
|||
videoDGR uint8 = 0x09
|
||||
videoDHGR uint8 = 0x0a
|
||||
|
||||
videoMono560 uint8 = 0x10
|
||||
videoRGBMix uint8 = 0x11
|
||||
videoSHR uint8 = 0x12
|
||||
videoRGBText40 uint8 = 0x10
|
||||
videoMono560 uint8 = 0x11
|
||||
videoRGBMix uint8 = 0x12
|
||||
videoSHR uint8 = 0x13
|
||||
|
||||
// Modifiers
|
||||
videoBaseMask uint8 = 0x1f
|
||||
|
@ -56,7 +57,11 @@ func getCurrentVideoMode(a *Apple2) uint8 {
|
|||
if is80Columns {
|
||||
mode = videoText80
|
||||
} else {
|
||||
mode = videoText40
|
||||
if a.mmu.store80Active {
|
||||
mode = videoRGBText40
|
||||
} else {
|
||||
mode = videoText40
|
||||
}
|
||||
}
|
||||
isMixMode = false
|
||||
} else if isHiResMode {
|
||||
|
@ -111,15 +116,18 @@ func snapshotByMode(a *Apple2, videoMode uint8) *image.RGBA {
|
|||
var ntscMask *image.Alpha
|
||||
switch videoBase {
|
||||
case videoText40:
|
||||
snap = snapshotTextMode(a, false /*is80Columns*/, isSecondPage, lightColor)
|
||||
snap = snapshotText40Mode(a, isSecondPage, lightColor)
|
||||
applyNTSCFilter = false
|
||||
case videoText80:
|
||||
snap = snapshotTextMode(a, true /*is80Columns*/, isSecondPage, lightColor)
|
||||
snap = snapshotText80Mode(a, isSecondPage, lightColor)
|
||||
applyNTSCFilter = false
|
||||
case videoRGBText40:
|
||||
snap = snapshotText40RGBMode(a, isSecondPage)
|
||||
applyNTSCFilter = false
|
||||
case videoGR:
|
||||
snap = snapshotLoResModeMono(a, false /*isDoubleResMode*/, isSecondPage, lightColor)
|
||||
snap = snapshotLoResModeMono(a, isSecondPage, lightColor)
|
||||
case videoDGR:
|
||||
snap = snapshotLoResModeMono(a, true /*isDoubleResMode*/, isSecondPage, lightColor)
|
||||
snap = snapshotMeResModeMono(a, isSecondPage, lightColor)
|
||||
case videoHGR:
|
||||
snap = snapshotHiResModeMono(a, isSecondPage, lightColor)
|
||||
case videoDHGR:
|
||||
|
@ -135,8 +143,12 @@ func snapshotByMode(a *Apple2, videoMode uint8) *image.RGBA {
|
|||
}
|
||||
|
||||
if isMixMode {
|
||||
isMix80 := (videoMode & videoMixText80) != 0
|
||||
bottom := snapshotTextMode(a, isMix80, isSecondPage, lightColor)
|
||||
var bottom *image.RGBA
|
||||
if (videoMode & videoMixText40) != 0 {
|
||||
bottom = snapshotText40Mode(a, isSecondPage, lightColor)
|
||||
} else {
|
||||
bottom = snapshotText80Mode(a, isSecondPage, lightColor)
|
||||
}
|
||||
snap = mixSnapshots(snap, bottom)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,23 +7,25 @@ import (
|
|||
// SnapshotParts the currently visible screen
|
||||
func (a *Apple2) SnapshotParts() *image.RGBA {
|
||||
videoMode := getCurrentVideoMode(a)
|
||||
isSecondPage := (videoMode & videoSecondPage) != 0
|
||||
videoBase := videoMode & videoBaseMask
|
||||
|
||||
snapScreen := snapshotByMode(a, videoMode)
|
||||
snapPage1 := snapshotByMode(a, videoMode&^videoSecondPage)
|
||||
snapPage2 := snapshotByMode(a, videoMode|videoSecondPage)
|
||||
var snapAux *image.RGBA
|
||||
|
||||
/*videoBase := videoMode & videoBaseMask
|
||||
if videoBase == videoRGBMix {
|
||||
isSecondPage := (videoMode & videoSecondPage) != 0
|
||||
_, mask := snapshotDoubleHiResModeMono(a, isSecondPage, true /*isRGBMixMode*/ /*, color.White)
|
||||
/*
|
||||
if videoBase == videoRGBMix {
|
||||
_, mask := snapshotDoubleHiResModeMono(a, isSecondPage, true /*isRGBMixMode*/ /*, color.White)
|
||||
snapAux = filterMask(mask)
|
||||
}*/
|
||||
|
||||
if snapAux == nil && (videoMode&videoMixText80) != 0 {
|
||||
if videoBase == videoRGBText40 {
|
||||
snapAux = snapshotText40RGBModeColors(a, isSecondPage)
|
||||
} else if (videoMode & videoMixText80) != 0 {
|
||||
snapAux = snapshotByMode(a, videoText80)
|
||||
}
|
||||
|
||||
if snapAux == nil {
|
||||
} else {
|
||||
snapAux = snapshotByMode(a, videoText40)
|
||||
}
|
||||
|
||||
|
@ -45,6 +47,9 @@ func (a *Apple2) VideoModeName() string {
|
|||
case videoText80:
|
||||
name = "TEXT80COL"
|
||||
applyNTSCFilter = false
|
||||
case videoRGBText40:
|
||||
name = "TEXT40COLRGB"
|
||||
applyNTSCFilter = false
|
||||
case videoGR:
|
||||
name = "GR"
|
||||
case videoDGR:
|
||||
|
|
|
@ -38,9 +38,19 @@ func getColorPatterns(light color.Color) [16][16]color.Color {
|
|||
|
||||
}
|
||||
|
||||
func snapshotLoResModeMono(a *Apple2, isDoubleResMode bool, isSecondPage bool, light color.Color) *image.RGBA {
|
||||
text, columns, lines := getActiveText(a, isDoubleResMode, isSecondPage)
|
||||
grLines := lines * 2
|
||||
func snapshotLoResModeMono(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
|
||||
data := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
|
||||
return renderGrMode(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 renderGrMode(data []uint8, isDoubleResMode bool, light color.Color) *image.RGBA {
|
||||
grLines := textLines * 2
|
||||
columns := len(data) / textLines
|
||||
pixelWidth := loResPixelWidth
|
||||
if isDoubleResMode {
|
||||
pixelWidth = doubleLoResPixelWidth
|
||||
|
@ -52,7 +62,7 @@ func snapshotLoResModeMono(a *Apple2, isDoubleResMode bool, isSecondPage bool, l
|
|||
patterns := getColorPatterns(light)
|
||||
for l := 0; l < grLines; l++ {
|
||||
for c := 0; c < columns; c++ {
|
||||
char := text[(l/2)*columns+c]
|
||||
char := data[(l/2)*columns+c]
|
||||
grPixel := char >> 4
|
||||
if l%2 == 0 {
|
||||
grPixel = char & 0xf
|
||||
|
|
114
screenText.go
114
screenText.go
|
@ -19,8 +19,28 @@ const (
|
|||
textPageSize = uint16(0x0400)
|
||||
)
|
||||
|
||||
func getTextCharOffset(col int, line int) uint16 {
|
||||
func snapshotText40Mode(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
|
||||
text := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
|
||||
return renderTextMode(a, 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 snapshotText40RGBMode(a *Apple2, isSecondPage bool) *image.RGBA {
|
||||
text := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
|
||||
colorMap := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage)
|
||||
return renderTextMode(a, text, colorMap, nil)
|
||||
}
|
||||
|
||||
func snapshotText40RGBModeColors(a *Apple2, isSecondPage bool) *image.RGBA {
|
||||
colorMap := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage)
|
||||
return renderTextMode(a, 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
|
||||
|
@ -28,26 +48,18 @@ func getTextCharOffset(col int, line int) uint16 {
|
|||
return uint16(section*40 + eighth*0x80 + col)
|
||||
}
|
||||
|
||||
func snapshotTextMode(a *Apple2, is80Columns bool, isSecondPage bool, light color.Color) *image.RGBA {
|
||||
text, columns, lines := getActiveText(a, is80Columns, isSecondPage)
|
||||
return renderTextMode(a, text, columns, lines, light)
|
||||
}
|
||||
|
||||
func getActiveText(a *Apple2, is80Columns bool, isSecondPage bool) ([]uint8, int, int) {
|
||||
if !is80Columns {
|
||||
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
|
||||
return text40Columns, textColumns, textLines
|
||||
}
|
||||
|
||||
func getText80FromMemory(a *Apple2, isSecondPage bool) []uint8 {
|
||||
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
|
||||
text40ColumnsAlt := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage)
|
||||
|
||||
// Merge the two 40 cols to return 80 cols
|
||||
text80Columns := make([]uint8, 2*len(text40Columns))
|
||||
for i := 0; i < len(text40Columns); i++ {
|
||||
text80Columns[2*i] = text40ColumnsAlt[i]
|
||||
text80Columns[2*i+1] = text40Columns[i]
|
||||
}
|
||||
return text80Columns, textColumns * 2, textLines
|
||||
|
||||
return text80Columns
|
||||
}
|
||||
|
||||
func getTextFromMemory(mem *memoryRange, isSecondPage bool) []uint8 {
|
||||
|
@ -68,12 +80,50 @@ func getTextFromMemory(mem *memoryRange, isSecondPage bool) []uint8 {
|
|||
return text
|
||||
}
|
||||
|
||||
func renderTextMode(a *Apple2, text []uint8, columns int, lines int, light color.Color) *image.RGBA {
|
||||
/*
|
||||
See:
|
||||
https://mrob.com/pub/xapple2/colors.html
|
||||
https://archive.org/details/IIgs_2523063_Master_Color_Values
|
||||
*/
|
||||
var rgbColorMap = [16]color.Color{
|
||||
color.RGBA{0, 0, 0, 255}, // Black
|
||||
color.RGBA{221, 0, 51, 255}, // Magenta
|
||||
color.RGBA{0, 0, 153, 255}, // Dark Blue
|
||||
color.RGBA{221, 34, 221, 255}, // Purple
|
||||
color.RGBA{0, 119, 34, 255}, // Dark Green
|
||||
color.RGBA{85, 85, 85, 255}, // Grey 1
|
||||
color.RGBA{34, 34, 255, 255}, // Medium Blue
|
||||
color.RGBA{102, 170, 255, 255}, // Light Blue
|
||||
color.RGBA{136, 85, 0, 255}, // Brown
|
||||
color.RGBA{255, 102, 0, 255}, // Orange
|
||||
color.RGBA{170, 170, 170, 255}, // Grey 2
|
||||
color.RGBA{255, 153, 136, 255}, // Pink
|
||||
color.RGBA{17, 221, 0, 255}, // Green
|
||||
color.RGBA{255, 255, 0, 255}, // Yellow
|
||||
color.RGBA{68, 255, 153, 255}, // Aquamarine
|
||||
color.RGBA{255, 255, 255, 255}, // White
|
||||
}
|
||||
|
||||
func getRGBTextColor(pixel bool, colorKey uint8) color.Color {
|
||||
if pixel {
|
||||
colorKey >>= 4
|
||||
}
|
||||
colorKey &= 0x0f
|
||||
return rgbColorMap[colorKey]
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
columns := len(text) / textLines
|
||||
if text == nil {
|
||||
columns = textColumns
|
||||
}
|
||||
width := columns * charWidth
|
||||
height := lines * charHeight
|
||||
height := textLines * charHeight
|
||||
size := image.Rect(0, 0, width, height)
|
||||
img := image.NewRGBA(size)
|
||||
|
||||
|
@ -83,13 +133,18 @@ func renderTextMode(a *Apple2, text []uint8, columns int, lines int, light color
|
|||
col := x / charWidth
|
||||
rowInChar := y % charHeight
|
||||
colInChar := x % charWidth
|
||||
char := text[line*columns+col]
|
||||
charIndex := line*columns + col
|
||||
var char uint8
|
||||
if text != nil {
|
||||
char = text[charIndex]
|
||||
} else {
|
||||
char = 79 + 128 // Debug screen filed with O
|
||||
}
|
||||
|
||||
var pixel bool
|
||||
if a.isApple2e {
|
||||
isAltText := a.io.isSoftSwitchActive(ioFlagAltChar)
|
||||
vid6 := (char & 0x40) != 0
|
||||
vid7 := (char & 0x80) != 0
|
||||
|
||||
char := char & 0x3f
|
||||
if vid6 && (vid7 || isAltText) {
|
||||
char += 0x40
|
||||
|
@ -106,12 +161,16 @@ func renderTextMode(a *Apple2, text []uint8, columns int, lines int, light color
|
|||
|
||||
pixel = pixel != (isInverse || (isFlash && isFlashedFrame))
|
||||
}
|
||||
|
||||
var colour color.Color
|
||||
if pixel {
|
||||
if colorMap != nil {
|
||||
colour = getRGBTextColor(pixel, colorMap[charIndex])
|
||||
} else if pixel {
|
||||
colour = light
|
||||
} else {
|
||||
colour = color.Black
|
||||
}
|
||||
|
||||
img.Set(x, y, colour)
|
||||
}
|
||||
}
|
||||
|
@ -124,14 +183,19 @@ func renderTextMode(a *Apple2, text []uint8, columns int, lines int, light color
|
|||
func DumpTextModeAnsi(a *Apple2) string {
|
||||
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
||||
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
||||
|
||||
text, columns, lines := getActiveText(a, is80Columns, isSecondPage)
|
||||
content := "\n"
|
||||
content += fmt.Sprintln(strings.Repeat("#", columns+4))
|
||||
|
||||
isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
|
||||
|
||||
for l := 0; l < lines; l++ {
|
||||
var text []uint8
|
||||
if is80Columns {
|
||||
text = getText80FromMemory(a, isSecondPage)
|
||||
} else {
|
||||
text = getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
|
||||
}
|
||||
columns := len(text) / textLines
|
||||
|
||||
content := "\n"
|
||||
content += fmt.Sprintln(strings.Repeat("#", columns+4))
|
||||
for l := 0; l < textLines; l++ {
|
||||
line := ""
|
||||
for c := 0; c < columns; c++ {
|
||||
char := text[l*columns+c]
|
||||
|
|
Loading…
Reference in New Issue