Convert Apple IIGS graphics

Converts a file with the Apple IIGS Graphic Format $C0/$0000 to a macOS NSImage object.
This commit is contained in:
markpmlim 2018-09-17 08:51:42 +08:00
parent 9a9647198c
commit b14c2c0484
8 changed files with 252 additions and 0 deletions

View File

@ -0,0 +1,11 @@
/*
A simple Playground demo to load an unpacked Apple IIGS Super-Hires graphic file ($C1/$0000) and display it.
Written in Swift 3.x
Requires XCode 8.x or later.
*/
import Cocoa
import PlaygroundSupport // for the live preview
let viewFrame = NSMakeRect(0, 0, 640, 400)
let imageView = ImageView(frame: viewFrame)
PlaygroundPage.current.liveView = imageView

Binary file not shown.

View File

@ -0,0 +1,210 @@
import AppKit
import MetalKit
/*
Loads an unpacked Apple IIGS graphic ($C0/$0000) and make available
the generated macOS bit map.
*/
public class Converter {
public var bmData: [UInt8]
public init?(_ url: URL) {
var SCB = [UInt8](repeating: 0, count: 256)
// Each scanline is 160 bytes. Each byte consists of 2 "pixels".
// There are 200 scanlines in a 320x200 IIGS graphic
var iigsBitmap = [UInt8](repeating: 0, count: 160*200)
// First load the entire file
var rawData: Data? = nil
do {
try rawData = Data(contentsOf: url)
}
catch let error {
print("Error", error)
return nil
}
// 1. Extract 160x200 = 32 000 bytes - this is the IIGS bitmap
var range = Range(0..<32000)
rawData?.copyBytes(to: &iigsBitmap, from: range)
// 2. Extract 256 bytes - this is the SCB table (only 200 required).
range = Range(32000..<32256)
rawData?.copyBytes(to: &SCB, from: range)
// 3. Extract 16 color tables = 16 x 32 bytes = 512 bytes.
range = Range(32256..<32768)
var buffer512 = [UInt8](repeating:0, count: 512)
rawData?.copyBytes(to: &buffer512, from: range)
// Declare a 2D array for easier access and copy data from buffer.
var colorTables = [[UInt16]](repeating: [UInt16](repeating: 0, count: 16),
count: 16)
var k = 0;
for row in 0..<16 {
for col in 0..<16 {
colorTables[row][col] = UInt16(buffer512[k]) + (UInt16(buffer512[k+1]) << 8)
k = k + 2
// Checked - color table entries are correct.
//print(colorTables[row][col], terminator: " ")
}
//print()
}
bmData = Converter.convert(bitMap: iigsBitmap,
colorTables: colorTables,
scbs: SCB)
}
// Convert a IIGS bitmap to a macOS bitmap.
// Each IIGS byte which has 2 "pixels" becomes 8 bytes.
// The "pixels" are actually an index (0-15) to a color entry.
// The color table to be used for a particular scanline is given
// by its corresponding scanline control byte obtained from the SCBs table
static func convert(bitMap:[UInt8],
colorTables: [[UInt16]],
scbs: [UInt8]) -> [UInt8] {
// The width and height of the macOS bitmap.
let width = 320
let height = 200
let bytesPerPixel = 4
var macOSBitmap = [UInt8](repeating: 0,
count: width*height*bytesPerPixel)
for row in 0..<height {
let whichColorTable = Int(scbs[row] & 0x0f)
let baseIndex = row * width * bytesPerPixel // macOS bitmap
// Each Apple IIGS scanline is 160 bytes for the $C0/$0000 graphic format.
for col in 0..<160 {
let index = row * 160 + col // IIgs bitmap
let pixels = bitMap[index] // 2 IIGS "pixels"
var colorEntry = Int((pixels >> 4) & 0x0f) // bits 4-7 - pixel # 0
var whichColor = colorTables[whichColorTable][colorEntry]
var r = ((whichColor & 0x0f00) >> 8) * 17 // 0-15 --> 0, 17, ...238, 255
var g = ((whichColor & 0x00f0) >> 4) * 17
var b = (whichColor & 0x000f) * 17
// Convert the first "pixel" to a true pixel.
let bmIndex = baseIndex + 8*col
macOSBitmap[bmIndex+0] = UInt8(r)
macOSBitmap[bmIndex+1] = UInt8(g)
macOSBitmap[bmIndex+2] = UInt8(b)
macOSBitmap[bmIndex+3] = UInt8(255)
// Convert the second "pixel" to a true pixel.
colorEntry = Int(pixels & 0x0f) // bits 3-0 - pixel # 1
whichColor = colorTables[whichColorTable][colorEntry]
r = ((whichColor & 0x0f00) >> 8) * 17 // 0-15 --> 0, 17, ...238, 255
g = ((whichColor & 0x00f0) >> 4) * 17
b = (whichColor & 0x000f) * 17
macOSBitmap[bmIndex+4] = UInt8(r)
macOSBitmap[bmIndex+5] = UInt8(g)
macOSBitmap[bmIndex+6] = UInt8(b)
macOSBitmap[bmIndex+7] = UInt8(255)
}
}
// Return the raw bit map for further processing.
return macOSBitmap
}
}
/*
An object of this class is used to display the image.
*/
public class ImageView: NSView {
var nsImage: NSImage?
override public init(frame frameRect: NSRect) {
super.init(frame: frameRect)
let myBundle = Bundle.main
let assetURL = myBundle.url(forResource: "ANGELFISH",
withExtension:"SHR")
let graphicsConverter = Converter(assetURL!)!
let bmData = graphicsConverter.bmData
// Create a CGImage object from the raw bit map.
let cgImage = cgImageFromRawBitmap(bitMapData: bmData)
let size = NSSize(width: 320, height: 200)
nsImage = NSImage(cgImage: cgImage!, size: size)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func cgImageFromRawBitmap(bitMapData: UnsafeRawPointer) -> CGImage? {
let width = 320
let height = 200
let bitsPerComponent = 8
let bytesPerPixel = 4 // Four 8-bit components viz RGBA
let colorSpace = NSDeviceRGBColorSpace
// Create an instance of NSBitmapImageRep; pass nil as the first parameter
// to tell macOS to allocate enough memory hold the image.
let bir = NSBitmapImageRep(bitmapDataPlanes: nil,
pixelsWide: width,
pixelsHigh: height,
bitsPerSample: bitsPerComponent,
samplesPerPixel: bytesPerPixel,
hasAlpha: true,
isPlanar: false,
colorSpaceName: colorSpace,
bytesPerRow: width*bytesPerPixel,
bitsPerPixel: bitsPerComponent*bytesPerPixel)
memcpy(bir?.bitmapData, bitMapData, width*height*bytesPerPixel)
guard let bmImageRep = bir
else {
return nil
}
return bmImageRep.cgImage
}
func imageRect() -> NSRect {
let bounds = self.bounds
return self.nsImage!.proportionalRectForTargetRect(bounds)
}
override public func draw(_ dirtyRect: NSRect) {
let image = self.nsImage
NSGraphicsContext.saveGraphicsState()
image!.draw(in: imageRect(),
from: NSZeroRect,
operation: .sourceOver,
fraction: 1.0)
NSGraphicsContext.restoreGraphicsState()
}
}
extension NSImage
{
func proportionalRectForTargetRect(_ targetRect: NSRect) ->NSRect {
if NSEqualSizes(self.size, targetRect.size) {
return targetRect
}
let imageSize = self.size
let sourceWidth = imageSize.width
let sourceHeight = imageSize.height
let widthAdjust = targetRect.size.width / sourceWidth
let heightAdjust = targetRect.size.height / sourceHeight
var scaleFactor: CGFloat = 1.0
if (widthAdjust < heightAdjust) {
scaleFactor = widthAdjust
}
else {
scaleFactor = heightAdjust
}
let finalWidth = sourceWidth * scaleFactor
let finalHeight = sourceHeight * scaleFactor
let finalSize = NSMakeSize(finalWidth, finalHeight)
var finalRect = NSRect()
finalRect.size = finalSize
finalRect.origin = targetRect.origin
finalRect.origin.x += (targetRect.size.width - finalWidth) * 0.5
finalRect.origin.y += (targetRect.size.height - finalHeight) * 0.5
return NSIntegralRect(finalRect)
}
}

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>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>

14
Readme.md Normal file
View File

@ -0,0 +1,14 @@
A simple Playground demonstration on how to
a) load an Apple IIGS graphic file with a file type and auxiliary type of $C0 and $0000,
b) extract data from the loaded file and use the information to instantiate a raw bitmap which can be used by macOS to create a CGImage object,
c) create an instance of NSImage from the CGImage object, and,
d) display the NSImage object in a Playground view.
Requirements:
Written in Swift 3.x later
Xcode 8.x or later