RGB card 40 columns text with 16 colors video mode

This commit is contained in:
Ivan Izaguirre 2020-08-08 19:23:35 +02:00
parent aa198877ec
commit 0ef160a56e
6 changed files with 140 additions and 48 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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