2019-04-21 19:04:02 +00:00
|
|
|
package apple2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"image"
|
|
|
|
"image/color"
|
|
|
|
"image/png"
|
|
|
|
"os"
|
|
|
|
)
|
|
|
|
|
2019-05-02 10:21:07 +00:00
|
|
|
/*
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
2019-04-21 22:18:14 +00:00
|
|
|
// Snapshot the currently visible screen
|
|
|
|
func Snapshot(a *Apple2) *image.RGBA {
|
2019-05-05 11:25:45 +00:00
|
|
|
isColor := a.isColor
|
2019-04-26 16:08:30 +00:00
|
|
|
isTextMode := a.io.isSoftSwitchActive(ioFlagText)
|
|
|
|
isHiResMode := a.io.isSoftSwitchActive(ioFlagHiRes)
|
2019-05-03 19:45:29 +00:00
|
|
|
isMixMode := a.io.isSoftSwitchActive(ioFlagMixed)
|
2019-11-08 22:56:54 +00:00
|
|
|
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
2019-11-09 16:44:04 +00:00
|
|
|
isDoubleResMode := !isTextMode && is80Columns && !a.io.isSoftSwitchActive(ioFlagAnnunciator3)
|
2019-11-08 22:56:54 +00:00
|
|
|
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
2019-11-11 21:58:42 +00:00
|
|
|
isSuperHighResMode := a.io.isSoftSwitchActive(ioDataNewVideo)
|
2019-05-12 17:22:32 +00:00
|
|
|
|
2019-05-05 11:25:45 +00:00
|
|
|
var lightColor color.Color
|
|
|
|
if isColor {
|
|
|
|
lightColor = color.White
|
|
|
|
} else {
|
|
|
|
// Color for typical Apple ][ period green P1 phosphor monitors
|
2019-05-03 19:45:29 +00:00
|
|
|
// See: https://superuser.com/questions/361297/what-colour-is-the-dark-green-on-old-fashioned-green-screen-computer-displays
|
2019-05-05 11:25:45 +00:00
|
|
|
lightColor = color.RGBA{65, 255, 0, 255}
|
|
|
|
|
|
|
|
}
|
2019-05-03 19:45:29 +00:00
|
|
|
|
2019-05-05 11:25:45 +00:00
|
|
|
var snap *image.RGBA
|
2019-11-11 21:58:42 +00:00
|
|
|
if isSuperHighResMode { // Has to be first and disables the rest
|
|
|
|
snap = snapshotSuperHiResMode(a)
|
|
|
|
} else if isTextMode {
|
2019-11-08 22:56:54 +00:00
|
|
|
snap = snapshotTextMode(a, is80Columns, isSecondPage, false /*isMixMode*/, lightColor)
|
2019-04-26 16:08:30 +00:00
|
|
|
} else {
|
|
|
|
if isHiResMode {
|
2019-11-09 16:44:04 +00:00
|
|
|
if isDoubleResMode {
|
|
|
|
snap = snapshotDoubleHiResModeMono(a, isSecondPage, isMixMode, lightColor)
|
|
|
|
} else {
|
|
|
|
snap = snapshotHiResModeMono(a, isSecondPage, isMixMode, lightColor)
|
|
|
|
}
|
2019-04-26 16:08:30 +00:00
|
|
|
} else {
|
2019-11-08 23:44:13 +00:00
|
|
|
snap = snapshotLoResModeMono(a, isDoubleResMode, isSecondPage, isMixMode, lightColor)
|
2019-05-03 19:45:29 +00:00
|
|
|
}
|
2019-05-12 22:17:31 +00:00
|
|
|
|
2019-05-03 19:45:29 +00:00
|
|
|
if isMixMode {
|
2019-11-08 22:56:54 +00:00
|
|
|
snapText := snapshotTextMode(a, is80Columns, false /*isSecondPage*/, true /*isMixMode*/, lightColor)
|
2019-05-03 19:45:29 +00:00
|
|
|
snap = mixSnapshots(snap, snapText)
|
|
|
|
}
|
2019-05-05 11:25:45 +00:00
|
|
|
if isColor {
|
|
|
|
snap = filterNTSCColor(false /*blacker*/, snap)
|
|
|
|
}
|
2019-05-03 19:45:29 +00:00
|
|
|
}
|
2019-05-05 11:25:45 +00:00
|
|
|
|
2019-11-11 21:58:42 +00:00
|
|
|
if !isSuperHighResMode {
|
|
|
|
snap = linesSeparatedFilter(snap)
|
|
|
|
}
|
2019-05-03 19:45:29 +00:00
|
|
|
return snap
|
|
|
|
}
|
|
|
|
|
|
|
|
func mixSnapshots(top, bottom *image.RGBA) *image.RGBA {
|
|
|
|
topBounds := top.Bounds()
|
|
|
|
topWidth := topBounds.Dx()
|
|
|
|
topHeight := topBounds.Dy()
|
|
|
|
|
|
|
|
bottomBounds := bottom.Bounds()
|
|
|
|
bottomWidth := bottomBounds.Dx()
|
|
|
|
bottomHeight := bottomBounds.Dy()
|
|
|
|
|
|
|
|
factor := topWidth / bottomWidth
|
|
|
|
|
|
|
|
size := image.Rect(0, 0, topWidth, topHeight+bottomHeight)
|
|
|
|
out := image.NewRGBA(size)
|
|
|
|
|
|
|
|
// Copy top
|
|
|
|
for y := topBounds.Min.Y; y < topBounds.Max.Y; y++ {
|
|
|
|
for x := topBounds.Min.X; x < topBounds.Max.X; x++ {
|
|
|
|
c := top.At(x, y)
|
|
|
|
out.Set(x, y, c)
|
2019-04-26 16:08:30 +00:00
|
|
|
}
|
2019-04-21 19:04:02 +00:00
|
|
|
}
|
2019-04-26 16:08:30 +00:00
|
|
|
|
2019-05-03 19:45:29 +00:00
|
|
|
// Copy bottom, applyng the factor
|
|
|
|
for y := bottomBounds.Min.Y; y < bottomBounds.Max.Y; y++ {
|
|
|
|
for x := bottomBounds.Min.X; x < bottomBounds.Max.X; x++ {
|
|
|
|
c := bottom.At(x, y)
|
|
|
|
for f := 0; f < factor; f++ {
|
|
|
|
out.Set(x*factor+f, topHeight+y, c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
2019-04-21 19:04:02 +00:00
|
|
|
}
|
|
|
|
|
2019-06-02 20:59:51 +00:00
|
|
|
// SaveSnapshot saves a snapshot of the screen to a png file
|
2019-10-05 23:26:00 +00:00
|
|
|
func SaveSnapshot(a *Apple2, filename string) error {
|
2019-04-21 22:18:14 +00:00
|
|
|
img := Snapshot(a)
|
2019-06-02 20:59:51 +00:00
|
|
|
img = squarishPixelsFilter(img)
|
2019-04-21 19:04:02 +00:00
|
|
|
|
2019-06-02 20:59:51 +00:00
|
|
|
f, err := os.Create(filename)
|
2019-04-21 19:04:02 +00:00
|
|
|
if err != nil {
|
2019-10-05 23:26:00 +00:00
|
|
|
return err
|
2019-04-21 19:04:02 +00:00
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
png.Encode(f, img)
|
2019-10-05 23:26:00 +00:00
|
|
|
return nil
|
2019-04-21 19:04:02 +00:00
|
|
|
}
|
|
|
|
|
2019-06-02 20:59:51 +00:00
|
|
|
func squarishPixelsFilter(in *image.RGBA) *image.RGBA {
|
|
|
|
b := in.Bounds()
|
|
|
|
factor := 1200 / b.Dx()
|
|
|
|
fmt.Println(factor)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-05-01 14:54:53 +00:00
|
|
|
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
|
|
|
|
}
|