Rewrite image loading to be cleaner, safer, and more efficient
This commit is contained in:
parent
39a0de2c75
commit
af24aeef3d
3 changed files with 147 additions and 180 deletions
|
@ -198,14 +198,14 @@ DQ
|
||||||
</button>
|
</button>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="2oe-to-Rcz" firstAttribute="top" secondItem="8HM-na-o1E" secondAttribute="bottom" constant="8" symbolic="YES" id="85g-Dp-ezi"/>
|
|
||||||
<constraint firstItem="8HM-na-o1E" firstAttribute="top" secondItem="u9u-el-oA4" secondAttribute="bottom" constant="8" symbolic="YES" id="CA1-bq-4Dc"/>
|
<constraint firstItem="8HM-na-o1E" firstAttribute="top" secondItem="u9u-el-oA4" secondAttribute="bottom" constant="8" symbolic="YES" id="CA1-bq-4Dc"/>
|
||||||
<constraint firstItem="u9u-el-oA4" firstAttribute="trailing" secondItem="8HM-na-o1E" secondAttribute="trailing" id="ILX-8S-pOI"/>
|
<constraint firstItem="u9u-el-oA4" firstAttribute="trailing" secondItem="8HM-na-o1E" secondAttribute="trailing" id="ILX-8S-pOI"/>
|
||||||
<constraint firstItem="u9u-el-oA4" firstAttribute="top" secondItem="3vu-Kd-l73" secondAttribute="top" constant="20" symbolic="YES" id="IQj-on-T5L"/>
|
<constraint firstItem="u9u-el-oA4" firstAttribute="top" secondItem="3vu-Kd-l73" secondAttribute="top" constant="20" symbolic="YES" id="IQj-on-T5L"/>
|
||||||
<constraint firstItem="u9u-el-oA4" firstAttribute="leading" secondItem="8HM-na-o1E" secondAttribute="leading" id="KJd-X5-kxh"/>
|
<constraint firstItem="u9u-el-oA4" firstAttribute="leading" secondItem="8HM-na-o1E" secondAttribute="leading" id="KJd-X5-kxh"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="u9u-el-oA4" secondAttribute="trailing" constant="20" symbolic="YES" id="PlA-zS-FpK"/>
|
<constraint firstAttribute="trailing" secondItem="u9u-el-oA4" secondAttribute="trailing" constant="20" symbolic="YES" id="PlA-zS-FpK"/>
|
||||||
<constraint firstItem="2oe-to-Rcz" firstAttribute="trailing" secondItem="8HM-na-o1E" secondAttribute="trailing" id="Whu-Lh-znF"/>
|
|
||||||
<constraint firstItem="u9u-el-oA4" firstAttribute="leading" secondItem="3vu-Kd-l73" secondAttribute="leading" constant="20" symbolic="YES" id="Xzy-ZR-1ha"/>
|
<constraint firstItem="u9u-el-oA4" firstAttribute="leading" secondItem="3vu-Kd-l73" secondAttribute="leading" constant="20" symbolic="YES" id="Xzy-ZR-1ha"/>
|
||||||
|
<constraint firstItem="2oe-to-Rcz" firstAttribute="top" secondItem="8HM-na-o1E" secondAttribute="bottom" constant="8" symbolic="YES" id="ZQM-5m-wzY"/>
|
||||||
|
<constraint firstItem="2oe-to-Rcz" firstAttribute="trailing" secondItem="8HM-na-o1E" secondAttribute="trailing" id="oGX-cy-Uhv"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<connections>
|
<connections>
|
||||||
|
@ -247,49 +247,14 @@ DQ
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ipM-NH-wUM">
|
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ipM-NH-wUM">
|
||||||
<rect key="frame" x="20" y="20" width="440" height="201"/>
|
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>
|
||||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="chN-IE-x52"/>
|
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="chN-IE-x52"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
<popUpButton verticalHuggingPriority="750" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ifc-N4-GCF">
|
|
||||||
<rect key="frame" x="18" y="226" width="101" height="25"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="width" constant="96" id="Csg-An-ug2"/>
|
|
||||||
</constraints>
|
|
||||||
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="P5l-BO-Pd1">
|
|
||||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
<menu key="menu" id="n0K-5D-di9"/>
|
|
||||||
</popUpButtonCell>
|
|
||||||
<connections>
|
|
||||||
<action selector="selectLayerAction:" target="5gI-5U-AMq" id="nzQ-6F-mUw"/>
|
|
||||||
</connections>
|
|
||||||
</popUpButton>
|
|
||||||
<popUpButton verticalHuggingPriority="750" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vY4-dQ-JOJ">
|
|
||||||
<rect key="frame" x="122" y="226" width="101" height="25"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="width" constant="96" id="FeE-01-aTL"/>
|
|
||||||
</constraints>
|
|
||||||
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="IVG-g2-g5C">
|
|
||||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
<menu key="menu" id="H7D-h4-TRv"/>
|
|
||||||
</popUpButtonCell>
|
|
||||||
<connections>
|
|
||||||
<action selector="selectChunkAction:" target="5gI-5U-AMq" id="JPQ-dG-2AN"/>
|
|
||||||
</connections>
|
|
||||||
</popUpButton>
|
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="Ifc-N4-GCF" firstAttribute="top" secondItem="ERx-hH-rdd" secondAttribute="top" constant="20" symbolic="YES" id="BmT-9r-weG"/>
|
|
||||||
<constraint firstItem="vY4-dQ-JOJ" firstAttribute="leading" secondItem="Ifc-N4-GCF" secondAttribute="trailing" constant="8" symbolic="YES" id="hgm-Np-cYB"/>
|
|
||||||
<constraint firstItem="vY4-dQ-JOJ" firstAttribute="baseline" secondItem="Ifc-N4-GCF" secondAttribute="baseline" id="uHT-fd-ze3"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
</view>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="chunkPopup" destination="vY4-dQ-JOJ" id="o1u-J8-bIL"/>
|
|
||||||
<outlet property="imageView" destination="ipM-NH-wUM" id="RT5-09-Hwj"/>
|
<outlet property="imageView" destination="ipM-NH-wUM" id="RT5-09-Hwj"/>
|
||||||
<outlet property="layerPopup" destination="Ifc-N4-GCF" id="7hY-Va-SMT"/>
|
|
||||||
<segue destination="wda-Mt-beD" kind="sheet" identifier="showInfo" id="obo-Yt-yny"/>
|
<segue destination="wda-Mt-beD" kind="sheet" identifier="showInfo" id="obo-Yt-yny"/>
|
||||||
</connections>
|
</connections>
|
||||||
</viewController>
|
</viewController>
|
||||||
|
|
|
@ -3,15 +3,9 @@ import ZIPFoundation
|
||||||
import CoreFoundation
|
import CoreFoundation
|
||||||
|
|
||||||
struct SilicaChunk {
|
struct SilicaChunk {
|
||||||
init() {
|
var x: Int = 0
|
||||||
x = 0
|
var y: Int = 0
|
||||||
y = 0
|
var image: NSImage = NSImage()
|
||||||
image = NSImage()
|
|
||||||
}
|
|
||||||
|
|
||||||
var x: Int
|
|
||||||
var y: Int
|
|
||||||
var image: NSImage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SilicaLayer {
|
struct SilicaLayer {
|
||||||
|
@ -38,6 +32,11 @@ class Document: NSDocument {
|
||||||
|
|
||||||
var dict: NSDictionary?
|
var dict: NSDictionary?
|
||||||
|
|
||||||
|
let NSKeyedArchiveVersion = 100000
|
||||||
|
|
||||||
|
let ThumbnailPath = "QuickLook/Thumbnail.png"
|
||||||
|
let DocumentArchivePath = "Document.archive"
|
||||||
|
|
||||||
let DocumentClassName = "SilicaDocument"
|
let DocumentClassName = "SilicaDocument"
|
||||||
let TrackedTimeKey = "SilicaDocumentTrackedTimeKey"
|
let TrackedTimeKey = "SilicaDocumentTrackedTimeKey"
|
||||||
let LayersKey = "layers"
|
let LayersKey = "layers"
|
||||||
|
@ -47,9 +46,13 @@ class Document: NSDocument {
|
||||||
let LayerClassName = "SilicaLayer"
|
let LayerClassName = "SilicaLayer"
|
||||||
|
|
||||||
var info = SilicaDocument()
|
var info = SilicaDocument()
|
||||||
|
|
||||||
|
var rows: Int = 0
|
||||||
|
var columns: Int = 0
|
||||||
|
|
||||||
|
var remainderWidth: Int = 0
|
||||||
|
var remainderHeight: Int = 0
|
||||||
|
|
||||||
var thumbnail: NSImage? = nil
|
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
@ -76,6 +79,64 @@ class Document: NSDocument {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns the correct tile size, taking into account the remainder between tile size and image size.
|
||||||
|
*/
|
||||||
|
func getTileSize(x: Int, y: Int) -> (Int, Int) {
|
||||||
|
var width: Int = info.tileSize
|
||||||
|
var height: Int = info.tileSize
|
||||||
|
|
||||||
|
if((x + 1) == columns) {
|
||||||
|
width = info.tileSize - remainderWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
if(y == rows) {
|
||||||
|
height = info.tileSize - remainderHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
return (width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Converts a CFKeyedArchiveUID from a NSKeyedArchive to a Int for indexing an array or dictionary.
|
||||||
|
*/
|
||||||
|
func getClassID(id: Any?) -> Int {
|
||||||
|
return Int(objectRefGetValue(id! as CFTypeRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parses the chunk filename, ex. 1~1 to integer coordinates.
|
||||||
|
*/
|
||||||
|
func parseChunkFilename(filename: String) -> (Int, Int)? {
|
||||||
|
let pathURL = URL(fileURLWithPath: filename)
|
||||||
|
let pathComponents = pathURL.lastPathComponent.replacingOccurrences(of: ".chunk", with: "").components(separatedBy: "~")
|
||||||
|
|
||||||
|
let x = Int(pathComponents[0])
|
||||||
|
let y = Int(pathComponents[1])
|
||||||
|
|
||||||
|
if x != nil && y != nil {
|
||||||
|
return (x!, y!)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readData(archive: Archive, entry: Entry) -> Data? {
|
||||||
|
var data = Data()
|
||||||
|
|
||||||
|
do {
|
||||||
|
let _ = try archive.extract(entry, consumer: { (d) in
|
||||||
|
data.append(d)
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
Swift.print("Extracting entry from archive failed with error:\(error)")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
func parseSilicaLayer(archive: Archive, dict: NSDictionary) {
|
func parseSilicaLayer(archive: Archive, dict: NSDictionary) {
|
||||||
let objectsArray = self.dict?["$objects"] as! NSArray
|
let objectsArray = self.dict?["$objects"] as! NSArray
|
||||||
|
|
||||||
|
@ -83,70 +144,76 @@ class Document: NSDocument {
|
||||||
var layer = SilicaLayer()
|
var layer = SilicaLayer()
|
||||||
|
|
||||||
let UUIDKey = dict["UUID"]
|
let UUIDKey = dict["UUID"]
|
||||||
let UUIDClassID = objectRefGetValue(UUIDKey as CFTypeRef)
|
let UUIDClassID = getClassID(id: UUIDKey)
|
||||||
let UUIDClass = objectsArray[Int(UUIDClassID)] as! NSString
|
let UUIDClass = objectsArray[UUIDClassID] as! NSString
|
||||||
|
|
||||||
var chunkPaths: [Entry] = []
|
var chunkPaths: [String] = []
|
||||||
|
|
||||||
archive.forEach { (entry: Entry) in
|
archive.forEach { (entry: Entry) in
|
||||||
if entry.path.contains(String(UUIDClass)) {
|
if entry.path.contains(String(UUIDClass)) {
|
||||||
chunkPaths.append(entry)
|
chunkPaths.append(entry.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.chunks = Array(repeating: SilicaChunk(), count: chunkPaths.count)
|
layer.chunks = Array(repeating: SilicaChunk(), count: chunkPaths.count)
|
||||||
|
|
||||||
|
let dispatchGroup = DispatchGroup()
|
||||||
|
let queue = DispatchQueue(label: "imageWork")
|
||||||
|
|
||||||
DispatchQueue.concurrentPerform(iterations: chunkPaths.count) { (i: Int) in
|
DispatchQueue.concurrentPerform(iterations: chunkPaths.count) { (i: Int) in
|
||||||
let entry = chunkPaths[i]
|
dispatchGroup.enter()
|
||||||
|
|
||||||
|
var threadArchive: Archive?
|
||||||
|
var threadEntry: Entry?
|
||||||
|
|
||||||
let pathURL = URL(fileURLWithPath: entry.path)
|
queue.sync {
|
||||||
let pathComponents = pathURL.lastPathComponent.replacingOccurrences(of: ".chunk", with: "").components(separatedBy: "~")
|
threadArchive = Archive(data: self.data!, accessMode: Archive.AccessMode.read)
|
||||||
|
threadEntry = threadArchive?[chunkPaths[i]]
|
||||||
let x = Int(pathComponents[0])
|
}
|
||||||
let y = Int(pathComponents[1])
|
|
||||||
|
guard let (x, y) = parseChunkFilename(filename: threadEntry!.path) else {
|
||||||
layer.chunks[i].x = x!
|
|
||||||
layer.chunks[i].y = y!
|
|
||||||
|
|
||||||
guard let archive = Archive(data: self.data!, accessMode: Archive.AccessMode.read) else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var lzo_data = Data()
|
let (width, height) = getTileSize(x: x, y: y)
|
||||||
|
let byteSize = width * height * 4
|
||||||
|
|
||||||
do {
|
let uncompressedMemory = UnsafeMutablePointer<UInt8>.allocate(capacity: byteSize)
|
||||||
try archive.extract(entry, consumer: { (d) in
|
|
||||||
lzo_data.append(d)
|
guard let lzoData = readData(archive: threadArchive!, entry: threadEntry!) else {
|
||||||
})
|
return
|
||||||
} catch {
|
|
||||||
Swift.print("Extracting entry from archive failed with error:\(error)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let uint8Pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: info.tileSize * info.tileSize * 4)
|
lzoData.withUnsafeBytes({ (bytes: UnsafeRawBufferPointer) -> Void in
|
||||||
|
var len = lzo_uint(byteSize)
|
||||||
lzo_data.withUnsafeBytes({ (bytes: UnsafeRawBufferPointer) -> Void in
|
|
||||||
var len = lzo_uint(info.tileSize * info.tileSize * 4)
|
|
||||||
|
|
||||||
lzo1x_decompress_safe(bytes.baseAddress!.assumingMemoryBound(to: uint8.self), lzo_uint(lzo_data.count), uint8Pointer, &len, nil)
|
lzo1x_decompress_safe(bytes.baseAddress!.assumingMemoryBound(to: uint8.self), lzo_uint(lzoData.count), uncompressedMemory, &len, nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
let image_data = Data(bytes: uint8Pointer, count: info.tileSize * info.tileSize * 4)
|
let imageData = Data(bytes: uncompressedMemory, count: byteSize)
|
||||||
|
|
||||||
let render: CGColorRenderingIntent = CGColorRenderingIntent.defaultIntent
|
let render: CGColorRenderingIntent = CGColorRenderingIntent.defaultIntent
|
||||||
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
|
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue)
|
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue).union(.byteOrder32Big)
|
||||||
.union(.byteOrder32Big)
|
let providerRef: CGDataProvider? = CGDataProvider(data: imageData as CFData)
|
||||||
let providerRef: CGDataProvider? = CGDataProvider(data: image_data as CFData)
|
|
||||||
|
|
||||||
let cgimage: CGImage? = CGImage(width: info.tileSize, height: info.tileSize, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: info.tileSize * 4, space: rgbColorSpace, bitmapInfo: bitmapInfo, provider: providerRef!, decode: nil, shouldInterpolate: false, intent: render)
|
guard let cgimage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: width * 4, space: rgbColorSpace, bitmapInfo: bitmapInfo, provider: providerRef!, decode: nil, shouldInterpolate: false, intent: render) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if cgimage != nil {
|
let image = NSImage(cgImage: cgimage, size: NSZeroSize)
|
||||||
let image = NSImage(cgImage: cgimage!, size: NSZeroSize)
|
|
||||||
|
queue.async(flags: .barrier) {
|
||||||
layer.chunks[i].image = image
|
layer.chunks[i].image = image
|
||||||
|
layer.chunks[i].x = x
|
||||||
|
layer.chunks[i].y = y
|
||||||
|
|
||||||
|
dispatchGroup.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatchGroup.wait()
|
||||||
|
|
||||||
info.layers.append(layer)
|
info.layers.append(layer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,8 +226,8 @@ class Document: NSDocument {
|
||||||
info.tileSize = (dict[TileSizeKey] as! NSNumber).intValue
|
info.tileSize = (dict[TileSizeKey] as! NSNumber).intValue
|
||||||
|
|
||||||
let sizeClassKey = dict[SizeKey]
|
let sizeClassKey = dict[SizeKey]
|
||||||
let sizeClassID = objectRefGetValue(sizeClassKey as CFTypeRef)
|
let sizeClassID = getClassID(id: sizeClassKey)
|
||||||
let sizeString = objectsArray[Int(sizeClassID)] as! String
|
let sizeString = objectsArray[sizeClassID] as! String
|
||||||
|
|
||||||
let sizeComponents = sizeString.replacingOccurrences(of: "{", with: "").replacingOccurrences(of: "}", with: "").components(separatedBy: ", ")
|
let sizeComponents = sizeString.replacingOccurrences(of: "{", with: "").replacingOccurrences(of: "}", with: "").components(separatedBy: ", ")
|
||||||
let width = Int(sizeComponents[0])
|
let width = Int(sizeComponents[0])
|
||||||
|
@ -169,15 +236,26 @@ class Document: NSDocument {
|
||||||
info.width = width!
|
info.width = width!
|
||||||
info.height = height!
|
info.height = height!
|
||||||
|
|
||||||
|
columns = Int(ceil(Float(info.width) / Float(info.tileSize)))
|
||||||
|
rows = Int(ceil(Float(info.height) / Float(info.tileSize)))
|
||||||
|
|
||||||
|
if info.width % info.tileSize != 0 {
|
||||||
|
remainderWidth = (columns * info.tileSize) - info.width
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.height % info.tileSize != 0 {
|
||||||
|
remainderHeight = (rows * info.tileSize) - info.height
|
||||||
|
}
|
||||||
|
|
||||||
let layersClassKey = dict[LayersKey]
|
let layersClassKey = dict[LayersKey]
|
||||||
let layersClassID = objectRefGetValue(layersClassKey as CFTypeRef)
|
let layersClassID = getClassID(id: layersClassKey)
|
||||||
let layersClass = objectsArray[Int(layersClassID)] as! NSDictionary
|
let layersClass = objectsArray[layersClassID] as! NSDictionary
|
||||||
|
|
||||||
let array = layersClass["NS.objects"] as! NSArray
|
let array = layersClass["NS.objects"] as! NSArray
|
||||||
|
|
||||||
for object in array {
|
for object in array {
|
||||||
let layerClassID = objectRefGetValue(object as CFTypeRef)
|
let layerClassID = getClassID(id: object)
|
||||||
let layerClass = objectsArray[Int(layerClassID)] as! NSDictionary
|
let layerClass = objectsArray[layerClassID] as! NSDictionary
|
||||||
|
|
||||||
parseSilicaLayer(archive: archive, dict: layerClass)
|
parseSilicaLayer(archive: archive, dict: layerClass)
|
||||||
}
|
}
|
||||||
|
@ -187,8 +265,9 @@ class Document: NSDocument {
|
||||||
func parseDocument(archive: Archive, dict: NSDictionary) {
|
func parseDocument(archive: Archive, dict: NSDictionary) {
|
||||||
// double check if this archive is really correct
|
// double check if this archive is really correct
|
||||||
if let value = dict["$version"] {
|
if let value = dict["$version"] {
|
||||||
if (value as! Int) != 100000 {
|
if (value as! Int) != NSKeyedArchiveVersion {
|
||||||
Swift.print("This is not a valid document!")
|
Swift.print("This is not a valid document!")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dict = dict
|
self.dict = dict
|
||||||
|
@ -210,68 +289,42 @@ class Document: NSDocument {
|
||||||
guard let archive = Archive(data: data, accessMode: Archive.AccessMode.read) else {
|
guard let archive = Archive(data: data, accessMode: Archive.AccessMode.read) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// load thumbnail
|
|
||||||
guard let entry = archive["QuickLook/Thumbnail.png"] else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var top_data = Data()
|
|
||||||
|
|
||||||
do {
|
|
||||||
try archive.extract(entry, consumer: { (d) in
|
|
||||||
top_data.append(d)
|
|
||||||
})
|
|
||||||
} catch {
|
|
||||||
Swift.print("Extracting entry from archive failed with error:\(error)")
|
|
||||||
}
|
|
||||||
|
|
||||||
thumbnail = NSImage(data: top_data)
|
|
||||||
|
|
||||||
// load doc info
|
guard let documentEntry = archive[DocumentArchivePath] else {
|
||||||
guard let document_entry = archive["Document.archive"] else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var doc_data = Data()
|
guard let documentData = readData(archive: archive, entry: documentEntry) else {
|
||||||
|
return
|
||||||
do {
|
|
||||||
try archive.extract(document_entry, consumer: { (d) in
|
|
||||||
doc_data.append(d)
|
|
||||||
})
|
|
||||||
} catch {
|
|
||||||
Swift.print("Extracting entry from archive failed with error:\(error)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Document.archive is a binary plist (specifically a NSKeyedArchive), luckily swift has a built-in solution to decode it
|
|
||||||
var plistFormat = PropertyListSerialization.PropertyListFormat.binary
|
var plistFormat = PropertyListSerialization.PropertyListFormat.binary
|
||||||
let plistBinary = doc_data
|
guard let propertyList = try? PropertyListSerialization.propertyList(from: documentData, options: [], format: &plistFormat) else {
|
||||||
|
return
|
||||||
guard let propertyList = try? PropertyListSerialization.propertyList(from: plistBinary, options: [], format: &plistFormat) else {
|
|
||||||
fatalError("failed to deserialize")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let dict = (propertyList as! NSDictionary);
|
parseDocument(archive: archive, dict: propertyList as! NSDictionary)
|
||||||
|
|
||||||
parseDocument(archive: archive, dict: dict)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeComposite() -> NSImage {
|
func makeComposite() -> NSImage {
|
||||||
let image = NSImage(size: NSSize(width: info.width, height: info.height))
|
let image = NSImage(size: NSSize(width: info.width, height: info.height))
|
||||||
image.lockFocus()
|
image.lockFocus()
|
||||||
|
|
||||||
let rows = Int(ceil(Float(info.height) / Float(info.tileSize)))
|
let color = NSColor.white
|
||||||
|
color.drawSwatch(in: NSRect(origin: .zero, size: image.size))
|
||||||
|
|
||||||
for layer in info.layers.reversed() {
|
for layer in info.layers.reversed() {
|
||||||
for chunk in layer.chunks {
|
for chunk in layer.chunks {
|
||||||
let x = chunk.x
|
let x = chunk.x
|
||||||
var y = chunk.y
|
var y = chunk.y
|
||||||
|
|
||||||
|
let (width, height) = getTileSize(x: x, y: y)
|
||||||
|
|
||||||
if y == rows {
|
if y == rows {
|
||||||
y = 0
|
y = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
let rect = NSRect(x: info.tileSize * x, y: info.height - (info.tileSize * y), width: info.tileSize, height: info.tileSize)
|
let rect = NSRect(x: info.tileSize * x, y: info.height - (info.tileSize * y), width: width, height: height)
|
||||||
|
|
||||||
chunk.image.draw(in: rect)
|
chunk.image.draw(in: rect)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,62 +3,11 @@ import Cocoa
|
||||||
|
|
||||||
class ViewController: NSViewController {
|
class ViewController: NSViewController {
|
||||||
@IBOutlet weak var imageView: NSImageView!
|
@IBOutlet weak var imageView: NSImageView!
|
||||||
|
|
||||||
@IBOutlet weak var layerPopup: NSPopUpButton!
|
|
||||||
@IBOutlet weak var chunkPopup: NSPopUpButton!
|
|
||||||
|
|
||||||
var selectedLayer: Int = 0
|
|
||||||
var selectedChunk: Int = 0
|
|
||||||
|
|
||||||
func parseName(str: String) -> Int? {
|
|
||||||
if let int = Int(str.components(separatedBy: " ")[1]) {
|
|
||||||
return int
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadCanvas() {
|
|
||||||
let document = self.view.window?.windowController?.document as? Document
|
|
||||||
|
|
||||||
imageView.image = document?.info.layers[selectedLayer].chunks[selectedChunk].image
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction func selectLayerAction(_ sender: Any) {
|
|
||||||
selectedLayer = parseName(str: (layerPopup.titleOfSelectedItem)!)!
|
|
||||||
|
|
||||||
selectedChunk = 0
|
|
||||||
|
|
||||||
loadLayerChunks()
|
|
||||||
loadCanvas()
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction func selectChunkAction(_ sender: Any) {
|
|
||||||
selectedChunk = parseName(str: (chunkPopup.titleOfSelectedItem)!)!
|
|
||||||
|
|
||||||
loadCanvas()
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadLayerChunks() {
|
|
||||||
let document = self.view.window?.windowController?.document as? Document
|
|
||||||
|
|
||||||
chunkPopup.removeAllItems()
|
|
||||||
|
|
||||||
for (i, chunk) in (document?.info.layers[selectedLayer].chunks.enumerated())! {
|
|
||||||
chunkPopup.addItem(withTitle: "Chunk " + String(chunk.x) + " " + String(chunk.y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillAppear() {
|
override func viewWillAppear() {
|
||||||
let document = self.view.window?.windowController?.document as? Document
|
let document = self.view.window?.windowController?.document as? Document
|
||||||
|
|
||||||
imageView.image = document?.makeComposite()
|
imageView.image = document?.makeComposite()
|
||||||
|
|
||||||
for (i, layer) in (document?.info.layers.enumerated())! {
|
|
||||||
layerPopup.addItem(withTitle: "Layer " + String(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
loadLayerChunks()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
|
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
|
||||||
|
|
Reference in a new issue