mirror of
https://github.com/markpmlim/GraphicConverterIIGS.git
synced 2025-02-20 01:29:01 +00:00
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:
parent
9a9647198c
commit
b14c2c0484
11
ConvertBitMap.playground/Contents.swift
Normal file
11
ConvertBitMap.playground/Contents.swift
Normal 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
|
BIN
ConvertBitMap.playground/Resources/ANGELFISH.SHR
Normal file
BIN
ConvertBitMap.playground/Resources/ANGELFISH.SHR
Normal file
Binary file not shown.
210
ConvertBitMap.playground/Sources/Converter.swift
Normal file
210
ConvertBitMap.playground/Sources/Converter.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
4
ConvertBitMap.playground/contents.xcplayground
Normal file
4
ConvertBitMap.playground/contents.xcplayground
Normal 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>
|
7
ConvertBitMap.playground/playground.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
ConvertBitMap.playground/playground.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
Binary file not shown.
6
ConvertBitMap.playground/timeline.xctimeline
Normal file
6
ConvertBitMap.playground/timeline.xctimeline
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
14
Readme.md
Normal file
14
Readme.md
Normal 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
|
Loading…
x
Reference in New Issue
Block a user