Apple II Double Hires Converter

A Swift playground demo to convert Apple Double Hires graphics files for display in an NSView
This commit is contained in:
markpmlim 2018-09-24 18:46:06 +08:00
parent c0213489d9
commit 1c9cf23863
10 changed files with 233 additions and 0 deletions

5
.gitconfig Normal file
View File

@ -0,0 +1,5 @@
[filter "lfs"]
clean = git-lfs clean -- %f
smudge = git-lfs smudge -- %f
process = git-lfs filter-process
required = true

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Exclude the build directory
build/*
# Exclude temp nibs and swap files
*~.nib
*.swp
# Exclude OS X folder attributes
.DS_Store
.svn
# Exclude user-specific XCode 3 and 4 files
*.mode1
*.mode1v3
*.mode2v3
*.perspective
*.perspectivev3
*.pbxuser
*.xcworkspace
xcuserdata
# Documentation
documentation/*

View File

@ -0,0 +1,26 @@
/*
A Swift Playground Demo to display an Apple II Double Hi-Res color image.
Written in Swift 3.x
XCode: 8.x
*/
import Cocoa
import PlaygroundSupport
let testURL =
Bundle.main.url(forResource: "COLORBARS",
withExtension: "A2FC")
var dataContents: Data?
do {
dataContents = try Data(contentsOf: testURL!)
let converter = DHGRConverter(data: dataContents!)
let cgImage = converter.cgImage
let size = NSSize(width: 280, height: 192)
let nsImage = NSImage(cgImage: cgImage!, size: size)
let frameRect = CGRect(x: 0, y: 0, width: 280, height: 192)
let view = NSImageView(frame: frameRect)
view.image = nsImage
PlaygroundPage.current.liveView = view
}
catch let error {
Swift.print("Error code:", error)
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,159 @@
/*
An instance of this class will convert the data passed into a 24-bit RGB raw bitmap.
The caller can access the instantiated CGImage object through its "cgImage" property.
*/
import Cocoa
import PlaygroundSupport
public class DHGRConverter {
public var cgImage: CGImage?
var baseOffsets = [Int](repeating:0, count:192)
func generateBaseOffsets() {
var groupOfEight, lineOfEight, groupOfSixtyFour: Int
// Both HGR and DHGR graphics have 192 vertical lines.
// The screen lines of both types of graphics also have the same starting base offsets.
for line in 0..<192 {
lineOfEight = line % 8 // 8 lines
groupOfEight = (line % 64) / 8 // 8 groups of 8 lines
groupOfSixtyFour = line / 64 // 3 groups of 64 lines
baseOffsets[line] = lineOfEight * 0x0400 + groupOfEight * 0x0080 + groupOfSixtyFour * 0x0028
}
/*
for line in 0..<192 {
let baseStr = String(format: "%04x", baseOffsets[line])
print("\(line) $:\(baseStr)")
}
*/
}
func reverseBits(_ bitPattern: UInt8) -> UInt8 {
var newValue:UInt8 = 0
for i:UInt8 in 0..<8 {
if bitPattern & (1 << i) != 0 {
newValue |= (1 << (7-i))
}
}
newValue >>= 4
return newValue
}
struct Pixel {
var r: UInt8
var g: UInt8
var b: UInt8
}
let colorValues: [UInt8] = [
0, 0, 0, // 0 - Black
206, 15, 49, // 1 - Magenta 0x72, 0x26, 0x06 Deep Red
156, 99, 1, // 2 - Brown 0x40, 0x4C, 0x04
255, 70, 0, // 3 - Orange 0xE4, 0x65, 0x01
0, 99, 49, // 4 - Dark Green 0x0E, 0x59, 0x40
82, 82, 82, // 5 - Gray 0x80, 0x80, 0x80
0, 221, 2, // 6 - Green 0x1B, 0xCB, 0x01
255, 253, 4, // 7 - Yellow 0xBF, 0xCC, 0x80
2, 19, 156, // 8 - Dark Blue 0x40, 0x33, 0x7F
206, 49, 206, // 9 - Violet 0xE4, 0x34, 0xFE Purple
173, 173, 173, // A - Grey 0x80, 0x80, 0x80
255, 156, 156, // B - Pink 0xF1, 0xA6, 0xBF
49, 49, 255, // C - Blue 0x1B, 0x9A, 0xFE
99, 156, 255, // D - Light Blue 0xBF, 0xB3, 0xFF
49, 253, 156, // E - Aqua 0x8D, 0xD9, 0xBF
255, 255, 255] // F - White 0xFF, 0xFF, 0xFF
var colorTable = [Pixel]()
public init(data srcData: Data) {
// Prepare the color table as an array of 16 RGB color entries for easier access.
for i in stride(from: 0, to: 48, by: 3) {
let color = Pixel(r: colorValues[i+0],
g: colorValues[i+1],
b: colorValues[i+2])
colorTable.append(color)
}
generateBaseOffsets()
// We assume the A2FC file is saved as 2 separate blobs of data
// The data from aux bank ($2000-$3FFFF) is saved first
// followed by data from the main bank ($2000-$3FFFF)
let bir = NSBitmapImageRep(bitmapDataPlanes: nil,
pixelsWide: 560,
pixelsHigh: 384,
bitsPerSample: 8,
samplesPerPixel: 3,
hasAlpha: false,
isPlanar: false,
colorSpaceName: NSDeviceRGBColorSpace,
bytesPerRow: 560 * 3, // 1680 bytes
bitsPerPixel: 24)
// Get the pointer to the memory allocated.
// The size of the memory block should be 560x3x384 bytes.
let bm = bir?.bitmapData
for row in 0..<192 {
let lineOffset = baseOffsets[row]
var srcPixels = [Pixel](repeating:Pixel(r: 0, g: 0, b: 0), count: 140)
var pixelIndex = 0
var destPixels = [Pixel](repeating:Pixel(r: 0, g: 0, b: 0), count: 560)
// Each time thru the loop below, 4 bytes are consumed to produce 7 RGB pixels.
// Since the loop executes 20 times, a total of 7x20=140 RGB pixels are produced
// for every screen line of data.
for col in stride(from: 0, to: 40, by: 2) {
// Extract 2 bytes from Auxiliary bank & 2 from the Main bank.
let aux0 = (srcData[lineOffset+col+0])
let aux1 = (srcData[lineOffset+col+1])
let main0 = (srcData[0x2000+lineOffset+col+0])
let main1 = (srcData[0x2000+lineOffset+col+1])
var bitPatterns = [UInt8](repeating: 0x00, count: 7)
// Compute seven (4-bit) patterns from the 4 bytes.
bitPatterns[0] = aux0 & 0x0f
bitPatterns[1] = ((main0 & 0x01) << 3) | ((aux0 & 0x70) >> 4)
bitPatterns[2] = ((main0 & 0x1E) >> 1)
bitPatterns[3] = ((aux1 & 0x03) << 2) | ((main0 & 0x60) >> 5)
bitPatterns[4] = ((aux1 & 0x3C) >> 2)
bitPatterns[5] = ((main1 & 0x07) << 1) | ((aux1 & 0x40) >> 6)
bitPatterns[6] = ((main1 & 0x78) >> 3)
// Use the bit patterns to index the color table and obtain 7 RGB pixels.
for i in 0..<7 {
let colorPixel = colorTable[Int(reverseBits(bitPatterns[i]))]
srcPixels[pixelIndex] = colorPixel
pixelIndex += 1
}
} // col
// When we reach here, the 560 bits have been converted into a row of 140 RGB pixels.
// Proceed convert the row into one with 560 RGB pixels.
// Each color pixel is quadruple in size.
for k in 0..<140 {
let pixel = srcPixels[k]
destPixels[4*k+0] = pixel
destPixels[4*k+1] = pixel
destPixels[4*k+2] = pixel
destPixels[4*k+3] = pixel
} // k
// Double the number of rows.
let evenIndex = 2 * row * 560 * 3
let oddIndex = (2 * row + 1) * 560 * 3
for k in 0..<560 {
let pixel = destPixels[k]
bm?[evenIndex+3*k+0] = pixel.r
bm?[evenIndex+3*k+1] = pixel.g
bm?[evenIndex+3*k+2] = pixel.b
bm?[ oddIndex+3*k+0] = pixel.r
bm?[ oddIndex+3*k+1] = pixel.g
bm?[ oddIndex+3*k+2] = pixel.b
} // k
} // row
cgImage = bir?.cgImage
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='macos'>
<timeline fileName='timeline.xctimeline'/>
</playground>

15
Readme.md Normal file
View File

@ -0,0 +1,15 @@
Apple2ColorDHGR.playround is a Swift playground for loading and viewing coloured Apple II Double Hires graphic files. There is a series of articles at the website www.battlestations.zone which gives a good explanation of how DHGR works on an Apple //e and Apple //c.
Because the data of a Double Hires graphic is highly interleaved, the demo must generate an array of base offsets before it can proceed to extract the information. Note: the "pixels" of the graphic are actually indices into a colour table.
The "pixels" of the graphic file is assumed to have been saved in two blobs of data. The first blob is saved with data from the Auxiliary bank of an Apple //e (or //c) from memory locations $2000-$3FFF. The second blob is in the same memory range but from the Main bank of the computer.
Each line of “pixels” consists of 80 bytes, 40 from the Auxiliary bank and 40 from the Main bank. Since bit 7 (or bit $80) of each byte is ignored so we are actually dealing with 80 x 7 bits = 560 bits per screen line. Because the “pixels” of a screen line are interleaved between Auxiliary and Main Memory, the demo extracts the data 4 bytes at a time, converts the 4x7 (= 28) bits into seven 4-bit patterns (7x4) which are indices to a colour table. The 560 bits will be converted into a row of 140 RGB pixels.
Once we have a row of 140 RGB pixels, we proceed to convert it to a row of 560 RGB pixels; in order to create a 560x384 raw bitmap, we just double the number of rows.
When the raw bitmap is filled with all the data, return a CGImage object via a publicly-declared property. This CoreGraphics image object will be used to create an instance of NSImage which could be displayed in an NSImageView.
References:
a) http://www.battlestations.zone/2017/04/apple-ii-double-hi-res-from-ground-up.html
b) Apple IIE Technical Note #3