From 1c9cf2386376fd1dba4569eaf805af98b039383d Mon Sep 17 00:00:00 2001 From: markpmlim Date: Mon, 24 Sep 2018 18:46:06 +0800 Subject: [PATCH] Apple II Double Hires Converter A Swift playground demo to convert Apple Double Hires graphics files for display in an NSView --- .gitconfig | 5 + .gitignore | 24 +++ Apple2ColorDHGR.playground/Contents.swift | 26 +++ .../Resources/CC65140.A2FC | Bin 0 -> 16384 bytes .../Resources/COLORBARS.A2FC | Bin 0 -> 16384 bytes .../Resources/HAM140.A2FC | Bin 0 -> 16384 bytes .../Resources/TOWER140.A2FC | Bin 0 -> 16384 bytes .../Sources/DHGRImage.swift | 159 ++++++++++++++++++ .../contents.xcplayground | 4 + Readme.md | 15 ++ 10 files changed, 233 insertions(+) create mode 100644 .gitconfig create mode 100644 .gitignore create mode 100644 Apple2ColorDHGR.playground/Contents.swift create mode 100644 Apple2ColorDHGR.playground/Resources/CC65140.A2FC create mode 100644 Apple2ColorDHGR.playground/Resources/COLORBARS.A2FC create mode 100644 Apple2ColorDHGR.playground/Resources/HAM140.A2FC create mode 100644 Apple2ColorDHGR.playground/Resources/TOWER140.A2FC create mode 100644 Apple2ColorDHGR.playground/Sources/DHGRImage.swift create mode 100644 Apple2ColorDHGR.playground/contents.xcplayground create mode 100644 Readme.md diff --git a/.gitconfig b/.gitconfig new file mode 100644 index 0000000..222b71b --- /dev/null +++ b/.gitconfig @@ -0,0 +1,5 @@ +[filter "lfs"] + clean = git-lfs clean -- %f + smudge = git-lfs smudge -- %f + process = git-lfs filter-process + required = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3002497 --- /dev/null +++ b/.gitignore @@ -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/* + diff --git a/Apple2ColorDHGR.playground/Contents.swift b/Apple2ColorDHGR.playground/Contents.swift new file mode 100644 index 0000000..a2c6056 --- /dev/null +++ b/Apple2ColorDHGR.playground/Contents.swift @@ -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) +} diff --git a/Apple2ColorDHGR.playground/Resources/CC65140.A2FC b/Apple2ColorDHGR.playground/Resources/CC65140.A2FC new file mode 100644 index 0000000000000000000000000000000000000000..1f3d4e2c95b6147e41461d5861ad803e53f959c0 GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/Apple2ColorDHGR.playground/Resources/COLORBARS.A2FC b/Apple2ColorDHGR.playground/Resources/COLORBARS.A2FC new file mode 100644 index 0000000000000000000000000000000000000000..58041d90a45210749ff25e99febe6831d2c76724 GIT binary patch literal 16384 zcmeI%p_0Qu5ChRb(?HX}&_Gi|Q$y3h(7^lk15>&>_`odNB_ds|Dbc;%?)zhT9sB(} z-|O~S9_#!0{9ecF`hK>bpWEN-<|6L)s&Dq!0=k!S*;P}$%4u&n^(q&yrh1jr-f-&e z=4O8_(7mhrM(rQ3rrxWZ_J&iha`9@aS2^tsr`~RE_SXX4yQ**0{_$$+y~=5CIQ1$Q zucmsH)826E?dE2GEzrHI`bO;^ucqFsoc4xOuX6Efs#iJf4X562ZuZv#-Mgx9)c)~m z>b=ToZ#eZT7q6yzmDApE>h0!ce=X3xtNKRmAFrm~tDN?RQ?GLIYN}T`?G2~iZf^G1 z0^Pf+Z`A(rYU;hpX>U07Di^P&dX>}OaO&;mW`8Zvy{r00?H{kE-m9GUhEuO{@oK79 zIqePiSoZsST^`Tp@%mo({dJt@XItOf=lcHt|L^8ze=X3xtNKRmAFrm~tDN?RQ?GLI zYN}T`?G2~iZf^G10^Pf+Z`A(rYU;hpX>U07Di^P&dX>}OaO&;mW`8Zvy{r00?H{kE z-m9GUhEuO{@oK79IqeOn-fnL8*8<(Us&CZ(@oMV5%4u&n^(q&yrh1jr-f-&e=4O8_ z(7mhrM(rQ3rrxWZ_J&iha`9@aS2^tsr`~RE_SXX4yQ**0{_$$+y~=5CIQ1$QucmsH z)826E?dE2GEzrHI`bO;^ucqFsoc4xOuX6Efs#iJf4X562ZuZv#-Mgx9)c)~m>b=To RZ#eZT7q6yzmDAqhegT2Q?1%sW literal 0 HcmV?d00001 diff --git a/Apple2ColorDHGR.playground/Resources/HAM140.A2FC b/Apple2ColorDHGR.playground/Resources/HAM140.A2FC new file mode 100644 index 0000000000000000000000000000000000000000..6774cc12d9391f383e366cb0498eca7e758e3dda GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/Apple2ColorDHGR.playground/Resources/TOWER140.A2FC b/Apple2ColorDHGR.playground/Resources/TOWER140.A2FC new file mode 100644 index 0000000000000000000000000000000000000000..38c6ee84a8d215721417cd8aa1997cee198d3f2b GIT binary patch literal 16384 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 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 + } +} diff --git a/Apple2ColorDHGR.playground/contents.xcplayground b/Apple2ColorDHGR.playground/contents.xcplayground new file mode 100644 index 0000000..63b6dd8 --- /dev/null +++ b/Apple2ColorDHGR.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..a3e7244 --- /dev/null +++ b/Readme.md @@ -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