mirror of
https://github.com/ivanizag/izapple2.git
synced 2025-01-01 13:29:19 +00:00
169 lines
4.4 KiB
Go
169 lines
4.4 KiB
Go
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
|
|
}
|