diff --git a/ConvertBitMap.playground/Contents.swift b/ConvertBitMap.playground/Contents.swift new file mode 100644 index 0000000..3747177 --- /dev/null +++ b/ConvertBitMap.playground/Contents.swift @@ -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 diff --git a/ConvertBitMap.playground/Resources/ANGELFISH.SHR b/ConvertBitMap.playground/Resources/ANGELFISH.SHR new file mode 100644 index 0000000..413f0f1 Binary files /dev/null and b/ConvertBitMap.playground/Resources/ANGELFISH.SHR differ diff --git a/ConvertBitMap.playground/Sources/Converter.swift b/ConvertBitMap.playground/Sources/Converter.swift new file mode 100644 index 0000000..8c33bcb --- /dev/null +++ b/ConvertBitMap.playground/Sources/Converter.swift @@ -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..> 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) + } +} + diff --git a/ConvertBitMap.playground/contents.xcplayground b/ConvertBitMap.playground/contents.xcplayground new file mode 100644 index 0000000..63b6dd8 --- /dev/null +++ b/ConvertBitMap.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ConvertBitMap.playground/playground.xcworkspace/contents.xcworkspacedata b/ConvertBitMap.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ConvertBitMap.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ConvertBitMap.playground/playground.xcworkspace/xcuserdata/marklim.xcuserdatad/UserInterfaceState.xcuserstate b/ConvertBitMap.playground/playground.xcworkspace/xcuserdata/marklim.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..1ae67bb Binary files /dev/null and b/ConvertBitMap.playground/playground.xcworkspace/xcuserdata/marklim.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ConvertBitMap.playground/timeline.xctimeline b/ConvertBitMap.playground/timeline.xctimeline new file mode 100644 index 0000000..bf468af --- /dev/null +++ b/ConvertBitMap.playground/timeline.xctimeline @@ -0,0 +1,6 @@ + + + + + diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..47d6811 --- /dev/null +++ b/Readme.md @@ -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