Rename to Silica Viewer
Before Width: | Height: | Size: 541 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 138 KiB |
Before Width: | Height: | Size: 5.3 KiB |
|
@ -1,393 +0,0 @@
|
||||||
import Cocoa
|
|
||||||
import ZIPFoundation
|
|
||||||
import CoreFoundation
|
|
||||||
|
|
||||||
struct SilicaChunk {
|
|
||||||
var x: Int = 0
|
|
||||||
var y: Int = 0
|
|
||||||
var image: NSImage = NSImage()
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SilicaLayer {
|
|
||||||
var chunks: [SilicaChunk] = []
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SilicaDocument {
|
|
||||||
var trackedTime: Int = 0
|
|
||||||
var tileSize: Int = 0
|
|
||||||
var orientation: Int = 0
|
|
||||||
var flippedHorizontally: Bool = false
|
|
||||||
var flippedVertically: Bool = false
|
|
||||||
|
|
||||||
var width: Int = 0
|
|
||||||
var height: Int = 0
|
|
||||||
|
|
||||||
var layers: [SilicaLayer] = []
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since this is a C-function we have to unsafe cast...
|
|
||||||
func objectRefGetValue(_ objectRef : CFTypeRef) -> UInt32 {
|
|
||||||
return _CFKeyedArchiverUIDGetValue(unsafeBitCast(objectRef, to: CFKeyedArchiverUIDRef.self))
|
|
||||||
}
|
|
||||||
|
|
||||||
class Document: NSDocument {
|
|
||||||
var data: Data? // oh no...
|
|
||||||
|
|
||||||
var dict: NSDictionary?
|
|
||||||
|
|
||||||
var info = SilicaDocument()
|
|
||||||
|
|
||||||
var rows: Int = 0
|
|
||||||
var columns: Int = 0
|
|
||||||
|
|
||||||
var remainderWidth: Int = 0
|
|
||||||
var remainderHeight: Int = 0
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func makeWindowControllers() {
|
|
||||||
let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
|
|
||||||
let windowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as! NSWindowController
|
|
||||||
self.addWindowController(windowController)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Pass in an object from the $object array, which always contains a $class key.
|
|
||||||
*/
|
|
||||||
func getDocumentClassName(dict: NSDictionary) -> String? {
|
|
||||||
let objectsArray = self.dict?["$objects"] as! NSArray
|
|
||||||
|
|
||||||
if let value = dict["$class"] {
|
|
||||||
let classObjectId = objectRefGetValue(value as CFTypeRef)
|
|
||||||
let classObject = objectsArray[Int(classObjectId)] as! NSDictionary
|
|
||||||
|
|
||||||
return classObject["$classname"] as? String
|
|
||||||
}
|
|
||||||
|
|
||||||
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! + 1)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSilicaLayer(archive: Archive, dict: NSDictionary) {
|
|
||||||
let objectsArray = self.dict?["$objects"] as! NSArray
|
|
||||||
|
|
||||||
if getDocumentClassName(dict: dict) == LayerClassName {
|
|
||||||
var layer = SilicaLayer()
|
|
||||||
|
|
||||||
let UUIDKey = dict["UUID"]
|
|
||||||
let UUIDClassID = getClassID(id: UUIDKey)
|
|
||||||
let UUIDClass = objectsArray[UUIDClassID] as! NSString
|
|
||||||
|
|
||||||
var chunkPaths: [String] = []
|
|
||||||
|
|
||||||
archive.forEach { (entry: Entry) in
|
|
||||||
if entry.path.contains(String(UUIDClass)) {
|
|
||||||
chunkPaths.append(entry.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.chunks = Array(repeating: SilicaChunk(), count: chunkPaths.count)
|
|
||||||
|
|
||||||
let dispatchGroup = DispatchGroup()
|
|
||||||
let queue = DispatchQueue(label: "imageWork")
|
|
||||||
|
|
||||||
DispatchQueue.concurrentPerform(iterations: chunkPaths.count) { (i: Int) in
|
|
||||||
dispatchGroup.enter()
|
|
||||||
|
|
||||||
guard let threadArchive = Archive(data: self.data!, accessMode: Archive.AccessMode.read) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let threadEntry = threadArchive[chunkPaths[i]]
|
|
||||||
|
|
||||||
guard let (x, y) = parseChunkFilename(filename: threadEntry!.path) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let (width, height) = getTileSize(x: x, y: y)
|
|
||||||
let byteSize = width * height * 4
|
|
||||||
|
|
||||||
let uncompressedMemory = UnsafeMutablePointer<UInt8>.allocate(capacity: byteSize)
|
|
||||||
|
|
||||||
guard let lzoData = readData(archive: threadArchive, entry: threadEntry!) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
lzoData.withUnsafeBytes({ (bytes: UnsafeRawBufferPointer) -> Void in
|
|
||||||
var len = lzo_uint(byteSize)
|
|
||||||
|
|
||||||
lzo1x_decompress_safe(bytes.baseAddress!.assumingMemoryBound(to: uint8.self), lzo_uint(lzoData.count), uncompressedMemory, &len, nil)
|
|
||||||
})
|
|
||||||
|
|
||||||
let imageData = Data(bytes: uncompressedMemory, count: byteSize)
|
|
||||||
|
|
||||||
let render: CGColorRenderingIntent = .defaultIntent
|
|
||||||
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
|
|
||||||
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue).union(.byteOrder32Big)
|
|
||||||
let providerRef: CGDataProvider? = CGDataProvider(data: imageData as CFData)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
let image = NSImage(cgImage: cgimage, size: NSZeroSize)
|
|
||||||
|
|
||||||
queue.async(flags: .barrier) {
|
|
||||||
layer.chunks[i].image = image
|
|
||||||
layer.chunks[i].x = x
|
|
||||||
layer.chunks[i].y = y
|
|
||||||
|
|
||||||
dispatchGroup.leave()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchGroup.wait()
|
|
||||||
|
|
||||||
info.layers.append(layer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSilicaDocument(archive: Archive, dict: NSDictionary) {
|
|
||||||
let objectsArray = self.dict?["$objects"] as! NSArray
|
|
||||||
|
|
||||||
if getDocumentClassName(dict: dict) == DocumentClassName {
|
|
||||||
info.trackedTime = (dict[TrackedTimeKey] as! NSNumber).intValue
|
|
||||||
info.tileSize = (dict[TileSizeKey] as! NSNumber).intValue
|
|
||||||
info.orientation = (dict[OrientationKey] as! NSNumber).intValue
|
|
||||||
info.flippedHorizontally = (dict[FlippedHorizontallyKey] as! NSNumber).boolValue
|
|
||||||
info.flippedVertically = (dict[FlippedVerticallyKey] as! NSNumber).boolValue
|
|
||||||
|
|
||||||
let sizeClassKey = dict[SizeKey]
|
|
||||||
let sizeClassID = getClassID(id: sizeClassKey)
|
|
||||||
let sizeString = objectsArray[sizeClassID] as! String
|
|
||||||
|
|
||||||
let sizeComponents = sizeString.replacingOccurrences(of: "{", with: "").replacingOccurrences(of: "}", with: "").components(separatedBy: ", ")
|
|
||||||
let width = Int(sizeComponents[0])
|
|
||||||
let height = Int(sizeComponents[1])
|
|
||||||
|
|
||||||
info.width = width!
|
|
||||||
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 layersClassID = getClassID(id: layersClassKey)
|
|
||||||
let layersClass = objectsArray[layersClassID] as! NSDictionary
|
|
||||||
|
|
||||||
let array = layersClass["NS.objects"] as! NSArray
|
|
||||||
|
|
||||||
for object in array {
|
|
||||||
let layerClassID = getClassID(id: object)
|
|
||||||
let layerClass = objectsArray[layerClassID] as! NSDictionary
|
|
||||||
|
|
||||||
parseSilicaLayer(archive: archive, dict: layerClass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDocument(archive: Archive, dict: NSDictionary) {
|
|
||||||
// double check if this archive is really correct
|
|
||||||
if let value = dict["$version"] {
|
|
||||||
if (value as! Int) != NSKeyedArchiveVersion {
|
|
||||||
Swift.print("This is not a valid document!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.dict = dict
|
|
||||||
|
|
||||||
let objectsArray = dict["$objects"] as! NSArray
|
|
||||||
|
|
||||||
// let's read the $top class, which is always going to be SilicaDocument type.
|
|
||||||
let topObject = dict["$top"] as! NSDictionary
|
|
||||||
let topClassID = objectRefGetValue(topObject["root"] as CFTypeRef)
|
|
||||||
let topObjectClass = objectsArray[Int(topClassID)] as! NSDictionary
|
|
||||||
|
|
||||||
parseSilicaDocument(archive: archive, dict: topObjectClass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func read(from data: Data, ofType typeName: String) throws {
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
guard let archive = Archive(data: data, accessMode: Archive.AccessMode.read) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let documentEntry = archive[DocumentArchivePath] else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let documentData = readData(archive: archive, entry: documentEntry) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var plistFormat = PropertyListSerialization.PropertyListFormat.binary
|
|
||||||
guard let propertyList = try? PropertyListSerialization.propertyList(from: documentData, options: [], format: &plistFormat) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parseDocument(archive: archive, dict: propertyList as! NSDictionary)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeComposite() -> NSImage {
|
|
||||||
var image = NSImage(size: NSSize(width: info.width, height: info.height))
|
|
||||||
image.lockFocus()
|
|
||||||
|
|
||||||
let color = NSColor.white
|
|
||||||
color.drawSwatch(in: NSRect(origin: .zero, size: image.size))
|
|
||||||
|
|
||||||
for layer in info.layers.reversed() {
|
|
||||||
for chunk in layer.chunks {
|
|
||||||
let x = chunk.x
|
|
||||||
var y = chunk.y
|
|
||||||
|
|
||||||
let (width, height) = getTileSize(x: x, y: y)
|
|
||||||
|
|
||||||
if y == rows {
|
|
||||||
y = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
let rect = NSRect(x: info.tileSize * x, y: info.height - (info.tileSize * y), width: width, height: height)
|
|
||||||
|
|
||||||
chunk.image.draw(in: rect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
image.unlockFocus()
|
|
||||||
|
|
||||||
if info.orientation == 3 {
|
|
||||||
image = image.imageRotatedByDegreess(degrees: 90)
|
|
||||||
} else if info.orientation == 4 {
|
|
||||||
image = image.imageRotatedByDegreess(degrees: -90)
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.flippedHorizontally && (info.orientation == 1 || info.orientation == 2) {
|
|
||||||
image = image.flipHorizontally()
|
|
||||||
} else if info.flippedHorizontally && (info.orientation == 3 || info.orientation == 4) {
|
|
||||||
image = image.flipVertically()
|
|
||||||
} else if info.flippedVertically && (info.orientation == 1 || info.orientation == 2) {
|
|
||||||
image = image.flipVertically()
|
|
||||||
} else if !info.flippedVertically && (info.orientation == 3 || info.orientation == 4) {
|
|
||||||
image = image.flipHorizontally()
|
|
||||||
}
|
|
||||||
|
|
||||||
return image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public extension NSImage {
|
|
||||||
func imageRotatedByDegreess(degrees:CGFloat) -> NSImage {
|
|
||||||
var imageBounds = NSMakeRect(0.0, 0.0, size.width, size.height)
|
|
||||||
|
|
||||||
let pathBounds = NSBezierPath(rect: imageBounds)
|
|
||||||
var transform = NSAffineTransform()
|
|
||||||
transform.rotate(byDegrees: degrees)
|
|
||||||
pathBounds.transform(using: transform as AffineTransform)
|
|
||||||
|
|
||||||
let rotatedBounds:NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y, pathBounds.bounds.size.width, pathBounds.bounds.size.height )
|
|
||||||
let rotatedImage = NSImage(size: rotatedBounds.size)
|
|
||||||
|
|
||||||
imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2)
|
|
||||||
imageBounds.origin.y = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2)
|
|
||||||
|
|
||||||
transform = NSAffineTransform()
|
|
||||||
transform.translateX(by: +(NSWidth(rotatedBounds) / 2 ), yBy: +(NSHeight(rotatedBounds) / 2))
|
|
||||||
transform.rotate(byDegrees: degrees)
|
|
||||||
transform.translateX(by: -(NSWidth(rotatedBounds) / 2 ), yBy: -(NSHeight(rotatedBounds) / 2))
|
|
||||||
|
|
||||||
rotatedImage.lockFocus()
|
|
||||||
transform.concat()
|
|
||||||
|
|
||||||
self.draw(in: imageBounds, from: .zero, operation: .copy, fraction: 1.0)
|
|
||||||
|
|
||||||
rotatedImage.unlockFocus()
|
|
||||||
|
|
||||||
return rotatedImage
|
|
||||||
}
|
|
||||||
|
|
||||||
func flipHorizontally() -> NSImage {
|
|
||||||
let flipedImage = NSImage(size: size)
|
|
||||||
flipedImage.lockFocus()
|
|
||||||
|
|
||||||
let transform = NSAffineTransform()
|
|
||||||
transform.translateX(by: size.width, yBy: 0.0)
|
|
||||||
transform.scaleX(by: -1.0, yBy: 1.0)
|
|
||||||
transform.concat()
|
|
||||||
|
|
||||||
let rect = NSMakeRect(0, 0, size.width, size.height)
|
|
||||||
self.draw(at: .zero, from: rect, operation: .sourceOver, fraction: 1.0)
|
|
||||||
|
|
||||||
flipedImage.unlockFocus()
|
|
||||||
|
|
||||||
return flipedImage
|
|
||||||
}
|
|
||||||
|
|
||||||
func flipVertically() -> NSImage {
|
|
||||||
let flipedImage = NSImage(size: size)
|
|
||||||
flipedImage.lockFocus()
|
|
||||||
|
|
||||||
let transform = NSAffineTransform()
|
|
||||||
transform.translateX(by: 0.0, yBy: size.height)
|
|
||||||
transform.scaleX(by: 1.0, yBy: -1.0)
|
|
||||||
transform.concat()
|
|
||||||
|
|
||||||
let rect = NSMakeRect(0, 0, size.width, size.height)
|
|
||||||
self.draw(at: .zero, from: rect, operation: .sourceOver, fraction: 1.0)
|
|
||||||
|
|
||||||
flipedImage.unlockFocus()
|
|
||||||
|
|
||||||
return flipedImage
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
/*
|
|
||||||
Here we import Apple's functions to decode __CFKeyedArchiverUID types.
|
|
||||||
*/
|
|
||||||
#include <CoreFoundation/CFBase.h>
|
|
||||||
#include "minilzo.h"
|
|
||||||
|
|
||||||
typedef const struct __CFKeyedArchiverUID * CFKeyedArchiverUIDRef;
|
|
||||||
|
|
||||||
extern uint32_t _CFKeyedArchiverUIDGetValue(CFKeyedArchiverUIDRef uid);
|
|
|
@ -1,37 +0,0 @@
|
||||||
import Foundation
|
|
||||||
import Cocoa
|
|
||||||
import AVKit
|
|
||||||
import AVFoundation
|
|
||||||
import ZIPFoundation
|
|
||||||
|
|
||||||
class TimelapseViewController: NSViewController {
|
|
||||||
var document: Document?
|
|
||||||
|
|
||||||
@IBOutlet weak var playerView: AVPlayerView!
|
|
||||||
|
|
||||||
override func viewWillAppear() {
|
|
||||||
super.viewDidAppear()
|
|
||||||
|
|
||||||
guard let archive = Archive(data: (document?.data)!, accessMode: Archive.AccessMode.read) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let directory = NSTemporaryDirectory()
|
|
||||||
|
|
||||||
var entries: [AVPlayerItem] = []
|
|
||||||
for entry in archive.makeIterator() {
|
|
||||||
if entry.path.contains(VideoPath) {
|
|
||||||
let fileName = NSUUID().uuidString + ".mp4"
|
|
||||||
|
|
||||||
// This returns a URL? even though it is an NSURL class method
|
|
||||||
let fullURL = NSURL.fileURL(withPathComponents: [directory, fileName])!
|
|
||||||
|
|
||||||
try? archive.extract(entry, to: fullURL)
|
|
||||||
|
|
||||||
entries.append(AVPlayerItem(url: fullURL))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
playerView?.player = AVQueuePlayer(items: entries)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,7 +19,7 @@
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
<string>com.procreate</string>
|
<string>com.procreate</string>
|
||||||
</array>
|
</array>
|
||||||
<key>QLSupportsSearchableItems</key>
|
<key>QLSupportsSearchableItems</key>
|
||||||
<false/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSExtensionPointIdentifier</key>
|
<key>NSExtensionPointIdentifier</key>
|
||||||
<string>com.apple.quicklook.preview</string>
|
<string>com.apple.quicklook.preview</string>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Procreate Viewer
|
# Silica Viewer
|
||||||
This is a macOS app to view [Procreate](https://procreate.art) documents, and it also contains [QuickLook](https://support.apple.com/guide/mac-help/preview-files-with-quick-look-mh14119/mac) and thumbnail extensions to allow you to
|
This is a macOS app to view [Procreate](https://procreate.art) documents, and it also contains [QuickLook](https://support.apple.com/guide/mac-help/preview-files-with-quick-look-mh14119/mac) and thumbnail extensions to allow you to
|
||||||
quickly preview your files as well!
|
quickly preview your files as well!
|
||||||
|
|
||||||
|
@ -41,5 +41,3 @@ Procreate, just like with thumbnails and image data - continue to use standard f
|
||||||
|
|
||||||
### Document Data
|
### Document Data
|
||||||
Layer names, time spent and other data is located in `Document.archive`. This is the only hard-to-read file in Procreate documents, but it is a [NSKeyedArchive](https://developer.apple.com/documentation/foundation/nskeyedarchiver). Here, we just use the [PropertyListSerialization](https://developer.apple.com/documentation/foundation/propertylistserialization) object to decode this in Swift.
|
Layer names, time spent and other data is located in `Document.archive`. This is the only hard-to-read file in Procreate documents, but it is a [NSKeyedArchive](https://developer.apple.com/documentation/foundation/nskeyedarchiver). Here, we just use the [PropertyListSerialization](https://developer.apple.com/documentation/foundation/propertylistserialization) object to decode this in Swift.
|
||||||
|
|
||||||
If you want more information please read [#2](https://github.com/redstrate/procreate-viewer/issues/2) where I break down the format of this file in more detail.
|
|
||||||
|
|
|
@ -7,15 +7,10 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
030F6FF22415C5E300A43F01 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030F6FF12415C5E300A43F01 /* AppDelegate.swift */; };
|
|
||||||
030F6FF42415C5E300A43F01 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030F6FF32415C5E300A43F01 /* Document.swift */; };
|
|
||||||
030F6FF92415C5E400A43F01 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 030F6FF82415C5E400A43F01 /* Assets.xcassets */; };
|
|
||||||
030F700B2415C6B500A43F01 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 030F700A2415C6B500A43F01 /* Quartz.framework */; };
|
030F700B2415C6B500A43F01 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 030F700A2415C6B500A43F01 /* Quartz.framework */; };
|
||||||
030F700E2415C6B500A43F01 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030F700D2415C6B500A43F01 /* PreviewViewController.swift */; };
|
030F700E2415C6B500A43F01 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030F700D2415C6B500A43F01 /* PreviewViewController.swift */; };
|
||||||
030F70112415C6B500A43F01 /* PreviewViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 030F700F2415C6B500A43F01 /* PreviewViewController.xib */; };
|
030F70112415C6B500A43F01 /* PreviewViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 030F700F2415C6B500A43F01 /* PreviewViewController.xib */; };
|
||||||
030F70162415C6B500A43F01 /* QuickLook.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 030F70082415C6B500A43F01 /* QuickLook.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
030F70162415C6B500A43F01 /* QuickLook.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 030F70082415C6B500A43F01 /* QuickLook.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
0357A94D26F9C7370075D5BC /* minilzo.c in Sources */ = {isa = PBXBuildFile; fileRef = 0357A94A26F9C7370075D5BC /* minilzo.c */; };
|
|
||||||
0357A94E26F9C7370075D5BC /* lzoconf.h in Headers */ = {isa = PBXBuildFile; fileRef = 0357A94C26F9C7370075D5BC /* lzoconf.h */; };
|
|
||||||
035D1A0426F0927200B332BE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D19F826F0927200B332BE /* ViewController.swift */; };
|
035D1A0426F0927200B332BE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D19F826F0927200B332BE /* ViewController.swift */; };
|
||||||
035D1A0526F0927200B332BE /* cbridge.c in Sources */ = {isa = PBXBuildFile; fileRef = 035D19FA26F0927200B332BE /* cbridge.c */; };
|
035D1A0526F0927200B332BE /* cbridge.c in Sources */ = {isa = PBXBuildFile; fileRef = 035D19FA26F0927200B332BE /* cbridge.c */; };
|
||||||
035D1A0626F0927200B332BE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 035D19FB26F0927200B332BE /* Assets.xcassets */; };
|
035D1A0626F0927200B332BE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 035D19FB26F0927200B332BE /* Assets.xcassets */; };
|
||||||
|
@ -25,19 +20,16 @@
|
||||||
035D1A0A26F0927200B332BE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D1A0126F0927200B332BE /* AppDelegate.swift */; };
|
035D1A0A26F0927200B332BE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D1A0126F0927200B332BE /* AppDelegate.swift */; };
|
||||||
035D1A0B26F0927200B332BE /* TimelapseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D1A0226F0927200B332BE /* TimelapseViewController.swift */; };
|
035D1A0B26F0927200B332BE /* TimelapseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D1A0226F0927200B332BE /* TimelapseViewController.swift */; };
|
||||||
036AFBB8241687680075400A /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 036AFBB7241687680075400A /* ZIPFoundation */; };
|
036AFBB8241687680075400A /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 036AFBB7241687680075400A /* ZIPFoundation */; };
|
||||||
036AFBBA24168C030075400A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036AFBB924168C030075400A /* ViewController.swift */; };
|
|
||||||
036AFBE32417F0A00075400A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 036AFBE12417F0A00075400A /* Main.storyboard */; };
|
|
||||||
036AFC062417F2990075400A /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 036AFC052417F2990075400A /* ZIPFoundation */; };
|
036AFC062417F2990075400A /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 036AFC052417F2990075400A /* ZIPFoundation */; };
|
||||||
036AFC0D241800350075400A /* QuickLookThumbnailing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 036AFC0C241800350075400A /* QuickLookThumbnailing.framework */; };
|
036AFC0D241800350075400A /* QuickLookThumbnailing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 036AFC0C241800350075400A /* QuickLookThumbnailing.framework */; };
|
||||||
036AFC0E241800350075400A /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 030F700A2415C6B500A43F01 /* Quartz.framework */; };
|
036AFC0E241800350075400A /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 030F700A2415C6B500A43F01 /* Quartz.framework */; };
|
||||||
036AFC11241800350075400A /* ThumbnailProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036AFC10241800350075400A /* ThumbnailProvider.swift */; };
|
036AFC11241800350075400A /* ThumbnailProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036AFC10241800350075400A /* ThumbnailProvider.swift */; };
|
||||||
036AFC16241800350075400A /* Thumbnail.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 036AFC0B241800350075400A /* Thumbnail.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
036AFC16241800350075400A /* Thumbnail.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 036AFC0B241800350075400A /* Thumbnail.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
036AFC1B241800850075400A /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 036AFC1A241800850075400A /* ZIPFoundation */; };
|
036AFC1B241800850075400A /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 036AFC1A241800850075400A /* ZIPFoundation */; };
|
||||||
0371996027BAC5D800EE1DFD /* Silica_ViewerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0371995F27BAC5D800EE1DFD /* Silica_ViewerTests.swift */; };
|
==== BASE ====
|
||||||
03C39DE726F90734005555AE /* lzodefs.h in Headers */ = {isa = PBXBuildFile; fileRef = 03C39DE226F90733005555AE /* lzodefs.h */; };
|
037B4042241821D200392452 /* InfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037B4041241821D200392452 /* InfoViewController.swift */; };
|
||||||
03C39DE826F90734005555AE /* Makefile in Sources */ = {isa = PBXBuildFile; fileRef = 03C39DE426F90734005555AE /* Makefile */; };
|
03CB382424191F620078B3E5 /* cbridge.c in Sources */ = {isa = PBXBuildFile; fileRef = 03CB382324191F620078B3E5 /* cbridge.c */; };
|
||||||
03C39DE926F90734005555AE /* testmini.c in Sources */ = {isa = PBXBuildFile; fileRef = 03C39DE526F90734005555AE /* testmini.c */; };
|
==== BASE ====
|
||||||
03C39DEA26F90734005555AE /* minilzo.h in Headers */ = {isa = PBXBuildFile; fileRef = 03C39DE626F90734005555AE /* minilzo.h */; };
|
|
||||||
03CB383B2419CA2D0078B3E5 /* libLZO.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 03CB382E2419C9DB0078B3E5 /* libLZO.a */; };
|
03CB383B2419CA2D0078B3E5 /* libLZO.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 03CB382E2419C9DB0078B3E5 /* libLZO.a */; };
|
||||||
03CB3840241A5AED0078B3E5 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CB383F241A5AED0078B3E5 /* Shared.swift */; };
|
03CB3840241A5AED0078B3E5 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CB383F241A5AED0078B3E5 /* Shared.swift */; };
|
||||||
03CB3841241A5AED0078B3E5 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CB383F241A5AED0078B3E5 /* Shared.swift */; };
|
03CB3841241A5AED0078B3E5 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CB383F241A5AED0078B3E5 /* Shared.swift */; };
|
||||||
|
@ -63,13 +55,6 @@
|
||||||
remoteGlobalIDString = 036AFC0A241800350075400A;
|
remoteGlobalIDString = 036AFC0A241800350075400A;
|
||||||
remoteInfo = Thumbnail;
|
remoteInfo = Thumbnail;
|
||||||
};
|
};
|
||||||
0371996127BAC5D800EE1DFD /* PBXContainerItemProxy */ = {
|
|
||||||
isa = PBXContainerItemProxy;
|
|
||||||
containerPortal = 030F6FE62415C5E300A43F01 /* Project object */;
|
|
||||||
proxyType = 1;
|
|
||||||
remoteGlobalIDString = 030F6FED2415C5E300A43F01;
|
|
||||||
remoteInfo = SilicaViewer;
|
|
||||||
};
|
|
||||||
03CB383C2419CA2D0078B3E5 /* PBXContainerItemProxy */ = {
|
03CB383C2419CA2D0078B3E5 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 030F6FE62415C5E300A43F01 /* Project object */;
|
containerPortal = 030F6FE62415C5E300A43F01 /* Project object */;
|
||||||
|
@ -102,10 +87,6 @@
|
||||||
030F70102415C6B500A43F01 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PreviewViewController.xib; sourceTree = "<group>"; };
|
030F70102415C6B500A43F01 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PreviewViewController.xib; sourceTree = "<group>"; };
|
||||||
030F70122415C6B500A43F01 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
030F70122415C6B500A43F01 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
030F70132415C6B500A43F01 /* Quicklook.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Quicklook.entitlements; sourceTree = "<group>"; };
|
030F70132415C6B500A43F01 /* Quicklook.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Quicklook.entitlements; sourceTree = "<group>"; };
|
||||||
0357A94926F9C7370075D5BC /* README.LZO */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.LZO; sourceTree = "<group>"; };
|
|
||||||
0357A94A26F9C7370075D5BC /* minilzo.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = minilzo.c; sourceTree = "<group>"; };
|
|
||||||
0357A94B26F9C7370075D5BC /* testmini */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = testmini; sourceTree = "<group>"; };
|
|
||||||
0357A94C26F9C7370075D5BC /* lzoconf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lzoconf.h; sourceTree = "<group>"; };
|
|
||||||
035D19F826F0927200B332BE /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
035D19F826F0927200B332BE /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||||
035D19F926F0927200B332BE /* SilicaViewer.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = SilicaViewer.entitlements; sourceTree = "<group>"; };
|
035D19F926F0927200B332BE /* SilicaViewer.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = SilicaViewer.entitlements; sourceTree = "<group>"; };
|
||||||
035D19FA26F0927200B332BE /* cbridge.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cbridge.c; sourceTree = "<group>"; };
|
035D19FA26F0927200B332BE /* cbridge.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cbridge.c; sourceTree = "<group>"; };
|
||||||
|
@ -122,14 +103,11 @@
|
||||||
036AFC10241800350075400A /* ThumbnailProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailProvider.swift; sourceTree = "<group>"; };
|
036AFC10241800350075400A /* ThumbnailProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailProvider.swift; sourceTree = "<group>"; };
|
||||||
036AFC12241800350075400A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
036AFC12241800350075400A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
036AFC13241800350075400A /* Thumbnail.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Thumbnail.entitlements; sourceTree = "<group>"; };
|
036AFC13241800350075400A /* Thumbnail.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Thumbnail.entitlements; sourceTree = "<group>"; };
|
||||||
0371995827BAC52900EE1DFD /* SilicaViewer.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SilicaViewer.xctestplan; sourceTree = "<group>"; };
|
==== BASE ====
|
||||||
0371995D27BAC5D800EE1DFD /* Silica ViewerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Silica ViewerTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
037B4041241821D200392452 /* InfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoViewController.swift; sourceTree = "<group>"; };
|
||||||
0371995F27BAC5D800EE1DFD /* Silica_ViewerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Silica_ViewerTests.swift; sourceTree = "<group>"; };
|
03CB382224191F610078B3E5 /* ProcreateViewer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ProcreateViewer-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
03C39DE226F90733005555AE /* lzodefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lzodefs.h; sourceTree = "<group>"; };
|
03CB382324191F620078B3E5 /* cbridge.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cbridge.c; sourceTree = "<group>"; };
|
||||||
03C39DE326F90733005555AE /* COPYING */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = COPYING; sourceTree = "<group>"; };
|
==== BASE ====
|
||||||
03C39DE426F90734005555AE /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = "<group>"; };
|
|
||||||
03C39DE526F90734005555AE /* testmini.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testmini.c; sourceTree = "<group>"; };
|
|
||||||
03C39DE626F90734005555AE /* minilzo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = minilzo.h; sourceTree = "<group>"; };
|
|
||||||
03CB382E2419C9DB0078B3E5 /* libLZO.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libLZO.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
03CB382E2419C9DB0078B3E5 /* libLZO.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libLZO.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
03CB383F241A5AED0078B3E5 /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = "<group>"; };
|
03CB383F241A5AED0078B3E5 /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = "<group>"; };
|
||||||
03CB3843241A5D600078B3E5 /* minilzo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = minilzo.h; path = "Dependencies/minilzo-2.10/minilzo.h"; sourceTree = SOURCE_ROOT; };
|
03CB3843241A5D600078B3E5 /* minilzo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = minilzo.h; path = "Dependencies/minilzo-2.10/minilzo.h"; sourceTree = SOURCE_ROOT; };
|
||||||
|
@ -167,13 +145,6 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
0371995A27BAC5D800EE1DFD /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
03CB382C2419C9DB0078B3E5 /* Frameworks */ = {
|
03CB382C2419C9DB0078B3E5 /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -187,15 +158,11 @@
|
||||||
030F6FE52415C5E300A43F01 = {
|
030F6FE52415C5E300A43F01 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
0371995827BAC52900EE1DFD /* SilicaViewer.xctestplan */,
|
|
||||||
0357A94826F9C71E0075D5BC /* LZO */,
|
|
||||||
035D19F726F0927200B332BE /* SilicaViewer */,
|
035D19F726F0927200B332BE /* SilicaViewer */,
|
||||||
03CB383E241A5ACD0078B3E5 /* Shared */,
|
03CB383E241A5ACD0078B3E5 /* Shared */,
|
||||||
03CB38322419C9F80078B3E5 /* LZO */,
|
03CB38322419C9F80078B3E5 /* LZO */,
|
||||||
030F6FF02415C5E300A43F01 /* ProcreateViewer */,
|
|
||||||
030F700C2415C6B500A43F01 /* Quicklook */,
|
030F700C2415C6B500A43F01 /* Quicklook */,
|
||||||
036AFC0F241800350075400A /* Thumbnail */,
|
036AFC0F241800350075400A /* Thumbnail */,
|
||||||
0371995E27BAC5D800EE1DFD /* Silica ViewerTests */,
|
|
||||||
030F70092415C6B500A43F01 /* Frameworks */,
|
030F70092415C6B500A43F01 /* Frameworks */,
|
||||||
030F6FEF2415C5E300A43F01 /* Products */,
|
030F6FEF2415C5E300A43F01 /* Products */,
|
||||||
);
|
);
|
||||||
|
@ -208,29 +175,10 @@
|
||||||
030F70082415C6B500A43F01 /* QuickLook.appex */,
|
030F70082415C6B500A43F01 /* QuickLook.appex */,
|
||||||
036AFC0B241800350075400A /* Thumbnail.appex */,
|
036AFC0B241800350075400A /* Thumbnail.appex */,
|
||||||
03CB382E2419C9DB0078B3E5 /* libLZO.a */,
|
03CB382E2419C9DB0078B3E5 /* libLZO.a */,
|
||||||
0371995D27BAC5D800EE1DFD /* Silica ViewerTests.xctest */,
|
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
030F6FF02415C5E300A43F01 /* ProcreateViewer */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
036AFBE12417F0A00075400A /* Main.storyboard */,
|
|
||||||
030F6FF12415C5E300A43F01 /* AppDelegate.swift */,
|
|
||||||
030F6FF32415C5E300A43F01 /* Document.swift */,
|
|
||||||
030F6FF82415C5E400A43F01 /* Assets.xcassets */,
|
|
||||||
030F6FFD2415C5E400A43F01 /* Info.plist */,
|
|
||||||
030F6FFE2415C5E400A43F01 /* ProcreateViewer.entitlements */,
|
|
||||||
036AFBB924168C030075400A /* ViewController.swift */,
|
|
||||||
037B4041241821D200392452 /* InfoViewController.swift */,
|
|
||||||
03CB382324191F620078B3E5 /* cbridge.c */,
|
|
||||||
03CB382224191F610078B3E5 /* ProcreateViewer-Bridging-Header.h */,
|
|
||||||
033F94F82648B8E200099FB7 /* TimelapseViewController.swift */,
|
|
||||||
);
|
|
||||||
path = ProcreateViewer;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
030F70092415C6B500A43F01 /* Frameworks */ = {
|
030F70092415C6B500A43F01 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -251,22 +199,6 @@
|
||||||
path = Quicklook;
|
path = Quicklook;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
0357A94826F9C71E0075D5BC /* LZO */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
0357A94C26F9C7370075D5BC /* lzoconf.h */,
|
|
||||||
0357A94A26F9C7370075D5BC /* minilzo.c */,
|
|
||||||
0357A94926F9C7370075D5BC /* README.LZO */,
|
|
||||||
0357A94B26F9C7370075D5BC /* testmini */,
|
|
||||||
03C39DE326F90733005555AE /* COPYING */,
|
|
||||||
03C39DE226F90733005555AE /* lzodefs.h */,
|
|
||||||
03C39DE426F90734005555AE /* Makefile */,
|
|
||||||
03C39DE626F90734005555AE /* minilzo.h */,
|
|
||||||
03C39DE526F90734005555AE /* testmini.c */,
|
|
||||||
);
|
|
||||||
name = LZO;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
035D19F726F0927200B332BE /* SilicaViewer */ = {
|
035D19F726F0927200B332BE /* SilicaViewer */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -295,12 +227,15 @@
|
||||||
path = Thumbnail;
|
path = Thumbnail;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
0371995E27BAC5D800EE1DFD /* Silica ViewerTests */ = {
|
03CB38322419C9F80078B3E5 /* LZO */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
0371995F27BAC5D800EE1DFD /* Silica_ViewerTests.swift */,
|
03CB3846241A5D600078B3E5 /* lzoconf.h */,
|
||||||
|
03CB3844241A5D600078B3E5 /* lzodefs.h */,
|
||||||
|
03CB3845241A5D600078B3E5 /* minilzo.c */,
|
||||||
|
03CB3843241A5D600078B3E5 /* minilzo.h */,
|
||||||
);
|
);
|
||||||
path = "Silica ViewerTests";
|
path = LZO;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
03CB383E241A5ACD0078B3E5 /* Shared */ = {
|
03CB383E241A5ACD0078B3E5 /* Shared */ = {
|
||||||
|
@ -318,9 +253,9 @@
|
||||||
isa = PBXHeadersBuildPhase;
|
isa = PBXHeadersBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
03C39DE726F90734005555AE /* lzodefs.h in Headers */,
|
03CB3848241A5D600078B3E5 /* lzodefs.h in Headers */,
|
||||||
0357A94E26F9C7370075D5BC /* lzoconf.h in Headers */,
|
03CB384A241A5D600078B3E5 /* lzoconf.h in Headers */,
|
||||||
03C39DEA26F90734005555AE /* minilzo.h in Headers */,
|
03CB3847241A5D600078B3E5 /* minilzo.h in Headers */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -435,7 +370,6 @@
|
||||||
==== BASE ====
|
==== BASE ====
|
||||||
LastSwiftUpdateCheck = 1130;
|
LastSwiftUpdateCheck = 1130;
|
||||||
LastUpgradeCheck = 1250;
|
LastUpgradeCheck = 1250;
|
||||||
==== BASE ====
|
|
||||||
ORGANIZATIONNAME = Josh;
|
ORGANIZATIONNAME = Josh;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
030F6FED2415C5E300A43F01 = {
|
030F6FED2415C5E300A43F01 = {
|
||||||
|
@ -457,7 +391,7 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = 030F6FE92415C5E300A43F01 /* Build configuration list for PBXProject "ProcreateViewer" */;
|
buildConfigurationList = 030F6FE92415C5E300A43F01 /* Build configuration list for PBXProject "SilicaViewer" */;
|
||||||
compatibilityVersion = "Xcode 9.3";
|
compatibilityVersion = "Xcode 9.3";
|
||||||
developmentRegion = en;
|
developmentRegion = en;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
|
@ -473,7 +407,7 @@
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
030F6FED2415C5E300A43F01 /* ProcreateViewer */,
|
030F6FED2415C5E300A43F01 /* SilicaViewer */,
|
||||||
030F70072415C6B500A43F01 /* QuickLook */,
|
030F70072415C6B500A43F01 /* QuickLook */,
|
||||||
036AFC0A241800350075400A /* Thumbnail */,
|
036AFC0A241800350075400A /* Thumbnail */,
|
||||||
03CB382D2419C9DB0078B3E5 /* LZO */,
|
03CB382D2419C9DB0078B3E5 /* LZO */,
|
||||||
|
@ -487,8 +421,8 @@
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
030F6FF92415C5E400A43F01 /* Assets.xcassets in Resources */,
|
035D1A0626F0927200B332BE /* Assets.xcassets in Resources */,
|
||||||
036AFBE32417F0A00075400A /* Main.storyboard in Resources */,
|
035D1A0726F0927200B332BE /* Main.storyboard in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -521,13 +455,13 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
035D1A0A26F0927200B332BE /* AppDelegate.swift in Sources */,
|
||||||
|
035D1A0B26F0927200B332BE /* TimelapseViewController.swift in Sources */,
|
||||||
|
035D1A0426F0927200B332BE /* ViewController.swift in Sources */,
|
||||||
|
035D1A0526F0927200B332BE /* cbridge.c in Sources */,
|
||||||
|
035D1A0826F0927200B332BE /* InfoViewController.swift in Sources */,
|
||||||
03CB3840241A5AED0078B3E5 /* Shared.swift in Sources */,
|
03CB3840241A5AED0078B3E5 /* Shared.swift in Sources */,
|
||||||
033F94F92648B8E200099FB7 /* TimelapseViewController.swift in Sources */,
|
035D1A0926F0927200B332BE /* Document.swift in Sources */,
|
||||||
036AFBBA24168C030075400A /* ViewController.swift in Sources */,
|
|
||||||
03CB382424191F620078B3E5 /* cbridge.c in Sources */,
|
|
||||||
030F6FF42415C5E300A43F01 /* Document.swift in Sources */,
|
|
||||||
037B4042241821D200392452 /* InfoViewController.swift in Sources */,
|
|
||||||
030F6FF22415C5E300A43F01 /* AppDelegate.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -599,10 +533,10 @@
|
||||||
name = PreviewViewController.xib;
|
name = PreviewViewController.xib;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
036AFBE12417F0A00075400A /* Main.storyboard */ = {
|
035D19FD26F0927200B332BE /* Main.storyboard */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
036AFBE22417F0A00075400A /* Base */,
|
035D19FE26F0927200B332BE /* Base */,
|
||||||
);
|
);
|
||||||
name = Main.storyboard;
|
name = Main.storyboard;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -636,6 +570,7 @@
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
@ -696,6 +631,7 @@
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
@ -729,18 +665,20 @@
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = ProcreateViewer/ProcreateViewer.entitlements;
|
CODE_SIGN_ENTITLEMENTS = SilicaViewer/SilicaViewer.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
DEVELOPMENT_TEAM = JM5LKVKH48;
|
DEVELOPMENT_TEAM = JM5LKVKH48;
|
||||||
INFOPLIST_FILE = ProcreateViewer/Info.plist;
|
INFOPLIST_FILE = SilicaViewer/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.ProcreateViewer;
|
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.SilicaViewer;
|
||||||
PRODUCT_NAME = "Procreate Viewer";
|
PRODUCT_NAME = "Silica Viewer";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "ProcreateViewer/ProcreateViewer-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "SilicaViewer/SilicaViewer-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
|
@ -752,18 +690,20 @@
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = ProcreateViewer/ProcreateViewer.entitlements;
|
CODE_SIGN_ENTITLEMENTS = SilicaViewer/SilicaViewer.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
DEVELOPMENT_TEAM = JM5LKVKH48;
|
DEVELOPMENT_TEAM = JM5LKVKH48;
|
||||||
INFOPLIST_FILE = ProcreateViewer/Info.plist;
|
INFOPLIST_FILE = SilicaViewer/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.ProcreateViewer;
|
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.SilicaViewer;
|
||||||
PRODUCT_NAME = "Procreate Viewer";
|
PRODUCT_NAME = "Silica Viewer";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "ProcreateViewer/ProcreateViewer-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "SilicaViewer/SilicaViewer-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
@ -772,7 +712,9 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = Quicklook/Quicklook.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Quicklook/Quicklook.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
DEVELOPMENT_TEAM = JM5LKVKH48;
|
DEVELOPMENT_TEAM = JM5LKVKH48;
|
||||||
INFOPLIST_FILE = Quicklook/Info.plist;
|
INFOPLIST_FILE = Quicklook/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -780,7 +722,7 @@
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
"@executable_path/../../../../Frameworks",
|
"@executable_path/../../../../Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.ProcreateViewer.QuickLook;
|
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.SilicaViewer.QuickLook;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
@ -791,7 +733,9 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = Quicklook/Quicklook.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Quicklook/Quicklook.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
DEVELOPMENT_TEAM = JM5LKVKH48;
|
DEVELOPMENT_TEAM = JM5LKVKH48;
|
||||||
INFOPLIST_FILE = Quicklook/Info.plist;
|
INFOPLIST_FILE = Quicklook/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -799,7 +743,7 @@
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
"@executable_path/../../../../Frameworks",
|
"@executable_path/../../../../Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.ProcreateViewer.QuickLook;
|
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.SilicaViewer.QuickLook;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
@ -810,7 +754,9 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = Thumbnail/Thumbnail.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Thumbnail/Thumbnail.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
DEVELOPMENT_TEAM = JM5LKVKH48;
|
DEVELOPMENT_TEAM = JM5LKVKH48;
|
||||||
INFOPLIST_FILE = Thumbnail/Info.plist;
|
INFOPLIST_FILE = Thumbnail/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -819,7 +765,7 @@
|
||||||
"@executable_path/../../../../Frameworks",
|
"@executable_path/../../../../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.ProcreateViewer.Thumbnail;
|
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.SilicaViewer.Thumbnail;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
@ -830,7 +776,9 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = Thumbnail/Thumbnail.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Thumbnail/Thumbnail.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
DEVELOPMENT_TEAM = JM5LKVKH48;
|
DEVELOPMENT_TEAM = JM5LKVKH48;
|
||||||
INFOPLIST_FILE = Thumbnail/Info.plist;
|
INFOPLIST_FILE = Thumbnail/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -839,7 +787,7 @@
|
||||||
"@executable_path/../../../../Frameworks",
|
"@executable_path/../../../../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.ProcreateViewer.Thumbnail;
|
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.SilicaViewer.Thumbnail;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
@ -909,7 +857,7 @@
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
030F6FE92415C5E300A43F01 /* Build configuration list for PBXProject "ProcreateViewer" */ = {
|
030F6FE92415C5E300A43F01 /* Build configuration list for PBXProject "SilicaViewer" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
030F6FFF2415C5E400A43F01 /* Debug */,
|
030F6FFF2415C5E400A43F01 /* Debug */,
|
||||||
|
@ -918,7 +866,7 @@
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
030F70012415C5E400A43F01 /* Build configuration list for PBXNativeTarget "ProcreateViewer" */ = {
|
030F70012415C5E400A43F01 /* Build configuration list for PBXNativeTarget "SilicaViewer" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
030F70022415C5E400A43F01 /* Debug */,
|
030F70022415C5E400A43F01 /* Debug */,
|
|
@ -3,61 +3,61 @@
|
||||||
{
|
{
|
||||||
"size" : "16x16",
|
"size" : "16x16",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "untitled-16.png",
|
"filename" : "icon-16.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "16x16",
|
"size" : "16x16",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "untitled-32.png",
|
"filename" : "icon-32.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "32x32",
|
"size" : "32x32",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "untitled-32.png",
|
"filename" : "icon-32.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "32x32",
|
"size" : "32x32",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "untitled-64.png",
|
"filename" : "icon-64.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "128x128",
|
"size" : "128x128",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "untitled-128.png",
|
"filename" : "icon-128.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "128x128",
|
"size" : "128x128",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "untitled-256.png",
|
"filename" : "icon-256.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "256x256",
|
"size" : "256x256",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "untitled-256.png",
|
"filename" : "icon-256.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "256x256",
|
"size" : "256x256",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "untitled-512.png",
|
"filename" : "icon-512.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "512x512",
|
"size" : "512x512",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "untitled-512.png",
|
"filename" : "icon-512.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "512x512",
|
"size" : "512x512",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "untitled-1024.png",
|
"filename" : "icon-1024.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
}
|
}
|
||||||
],
|
],
|
After Width: | Height: | Size: 585 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/Untitled-128.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/Untitled-16.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/Untitled-256.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/Untitled-32.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/Untitled-512.png
Normal file
After Width: | Height: | Size: 194 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/Untitled-64.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/icon-1024.png
Normal file
After Width: | Height: | Size: 605 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/icon-128.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/icon-16.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/icon-256.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/icon-32.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/icon-512.png
Normal file
After Width: | Height: | Size: 199 KiB |
BIN
SilicaViewer/Assets.xcassets/AppIcon.appiconset/icon-64.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
|
@ -1,9 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.AVKitIBPlugin" version="17156"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17156"/>
|
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
|
@ -13,11 +12,11 @@
|
||||||
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
||||||
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Procreate Viewer" id="1Xt-HY-uBw" userLabel="Procreate Viewer">
|
<menuItem title="Silica Viewer" id="1Xt-HY-uBw" userLabel="Silica Viewer">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Procreate Viewer" systemMenu="apple" id="uQy-DD-JDr">
|
<menu key="submenu" title="Silica Viewer" systemMenu="apple" id="uQy-DD-JDr">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="About Procreate Viewer" id="5kV-Vb-QxS" userLabel="About ProcreateViewer">
|
<menuItem title="About Silica Viewer" id="5kV-Vb-QxS" userLabel="About Silica Viewer">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
|
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
|
||||||
|
@ -31,7 +30,7 @@
|
||||||
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||||
<menuItem title="Hide Procreate Viewer" keyEquivalent="h" id="Olw-nP-bQN">
|
<menuItem title="Hide Silica Viewer" keyEquivalent="h" id="Olw-nP-bQN">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
|
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
|
||||||
</connections>
|
</connections>
|
||||||
|
@ -49,7 +48,7 @@
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||||
<menuItem title="Quit Procreate Viewer" keyEquivalent="q" id="4sb-4s-VLi">
|
<menuItem title="Quit Silica Viewer" keyEquivalent="q" id="4sb-4s-VLi">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
|
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
|
||||||
</connections>
|
</connections>
|
||||||
|
@ -79,7 +78,13 @@
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
|
<menuItem isSeparatorItem="YES" id="MaD-eL-lbO"/>
|
||||||
|
<menuItem title="Save Thumbnail..." keyEquivalent="s" id="JR5-se-hvt" userLabel="Save Thumbnail..."/>
|
||||||
|
<menuItem isSeparatorItem="YES" id="m54-Is-iLE">
|
||||||
|
<connections>
|
||||||
|
<action selector="saveDocument:" target="Ady-hI-5gd" id="fGt-aM-WYN"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
|
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="performClose:" target="Ady-hI-5gd" id="HmO-Ls-i7Q"/>
|
<action selector="performClose:" target="Ady-hI-5gd" id="HmO-Ls-i7Q"/>
|
||||||
|
@ -143,7 +148,7 @@
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Procreate Viewer Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
<menuItem title="Silica Viewer Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
|
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
|
||||||
</connections>
|
</connections>
|
||||||
|
@ -157,7 +162,7 @@
|
||||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
||||||
</connections>
|
</connections>
|
||||||
</application>
|
</application>
|
||||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Procreate_Viewer" customModuleProvider="target"/>
|
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Silica_Viewer" customModuleProvider="target"/>
|
||||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
<userDefaultsController representsSharedInstance="YES" id="JdS-Pg-8N9"/>
|
<userDefaultsController representsSharedInstance="YES" id="JdS-Pg-8N9"/>
|
||||||
|
@ -167,7 +172,7 @@
|
||||||
<!--Info View Controller-->
|
<!--Info View Controller-->
|
||||||
<scene sceneID="nJy-a4-E0d">
|
<scene sceneID="nJy-a4-E0d">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController showSeguePresentationStyle="single" id="wda-Mt-beD" customClass="InfoViewController" customModule="Procreate_Viewer" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController showSeguePresentationStyle="single" id="wda-Mt-beD" customClass="InfoViewController" customModule="Silica_Viewer" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" id="3vu-Kd-l73">
|
<view key="view" id="3vu-Kd-l73">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="480" height="109"/>
|
<rect key="frame" x="0.0" y="0.0" width="480" height="109"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
@ -253,25 +258,18 @@ DQ
|
||||||
<!--Timelapse View Controller-->
|
<!--Timelapse View Controller-->
|
||||||
<scene sceneID="6lP-oO-4ie">
|
<scene sceneID="6lP-oO-4ie">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="2g9-GV-hNg" customClass="TimelapseViewController" customModule="Procreate_Viewer" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController id="2g9-GV-hNg" customClass="TimelapseViewController" customModule="Silica_Viewer" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" id="n7g-Sv-vdB">
|
<view key="view" id="n7g-Sv-vdB">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<gestureRecognizers>
|
||||||
<avPlayerView controlsStyle="minimal" videoGravity="resizeAspectFill" translatesAutoresizingMaskIntoConstraints="NO" id="zOe-qf-LSc">
|
<clickGestureRecognizer delaysPrimaryMouseButtonEvents="YES" numberOfClicksRequired="1" id="I6z-cw-DZX">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
|
||||||
</avPlayerView>
|
|
||||||
</subviews>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="zOe-qf-LSc" secondAttribute="trailing" id="5e2-Uh-miL"/>
|
|
||||||
<constraint firstAttribute="bottom" secondItem="zOe-qf-LSc" secondAttribute="bottom" id="Kfx-dj-9x8"/>
|
|
||||||
<constraint firstItem="zOe-qf-LSc" firstAttribute="top" secondItem="n7g-Sv-vdB" secondAttribute="top" id="rwV-Dr-EPg"/>
|
|
||||||
<constraint firstItem="zOe-qf-LSc" firstAttribute="leading" secondItem="n7g-Sv-vdB" secondAttribute="leading" id="uY5-WX-NhA"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="playerView" destination="zOe-qf-LSc" id="Iy7-O4-v4P"/>
|
<action selector="clickAction:" target="2g9-GV-hNg" id="322-pp-TuC"/>
|
||||||
</connections>
|
</connections>
|
||||||
|
</clickGestureRecognizer>
|
||||||
|
</gestureRecognizers>
|
||||||
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
<customObject id="LO0-g9-QLL" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
<customObject id="LO0-g9-QLL" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
|
@ -301,7 +299,7 @@ DQ
|
||||||
<!--View Controller-->
|
<!--View Controller-->
|
||||||
<scene sceneID="hIz-AP-VOD">
|
<scene sceneID="hIz-AP-VOD">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="5gI-5U-AMq" customClass="ViewController" customModule="Procreate_Viewer" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController id="5gI-5U-AMq" customClass="ViewController" customModule="Silica_Viewer" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" id="ERx-hH-rdd">
|
<view key="view" id="ERx-hH-rdd">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
|
@ -1,28 +1,15 @@
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import ZIPFoundation
|
import ZIPFoundation
|
||||||
import CoreFoundation
|
import CoreFoundation
|
||||||
import Accelerate
|
|
||||||
import CoreMedia
|
|
||||||
|
|
||||||
struct SilicaChunk {
|
struct SilicaChunk {
|
||||||
var x: Int = 0
|
var x: Int = 0
|
||||||
var y: Int = 0
|
var y: Int = 0
|
||||||
var image: CGImage?
|
var image: NSImage = NSImage()
|
||||||
}
|
|
||||||
|
|
||||||
struct SilicaLayerData {
|
|
||||||
var blendMode: Int = 0
|
|
||||||
var extendedBlend: Int = 0
|
|
||||||
var chunks: [SilicaChunk] = []
|
|
||||||
var opacity: Double = 1.0
|
|
||||||
var hidden: Bool = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SilicaLayer {
|
struct SilicaLayer {
|
||||||
var name: String = ""
|
var chunks: [SilicaChunk] = []
|
||||||
var data: SilicaLayerData = SilicaLayerData()
|
|
||||||
var mask: SilicaLayerData?
|
|
||||||
var clipped: Bool = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SilicaDocument {
|
struct SilicaDocument {
|
||||||
|
@ -31,31 +18,11 @@ struct SilicaDocument {
|
||||||
var orientation: Int = 0
|
var orientation: Int = 0
|
||||||
var flippedHorizontally: Bool = false
|
var flippedHorizontally: Bool = false
|
||||||
var flippedVertically: Bool = false
|
var flippedVertically: Bool = false
|
||||||
var name: String = ""
|
|
||||||
var authorName: String = ""
|
|
||||||
var strokeCount: Int = 0
|
|
||||||
|
|
||||||
var backgroundColor: CGColor = .white
|
|
||||||
var colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB()
|
|
||||||
|
|
||||||
var width: Int = 0
|
var width: Int = 0
|
||||||
var height: Int = 0
|
var height: Int = 0
|
||||||
|
|
||||||
var layers: [SilicaLayer] = []
|
var layers: [SilicaLayer] = []
|
||||||
|
|
||||||
var videoFrame: (Int, Int) = (0, 0)
|
|
||||||
|
|
||||||
lazy var nsSize = {
|
|
||||||
return NSSize(width: width, height: height)
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var cgSize = {
|
|
||||||
return CGSize(width: width, height: height)
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var cgRect = {
|
|
||||||
return CGRect(origin: .zero, size: cgSize)
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func objectRefGetValue2(_ objectRef: CFTypeRef) -> UInt32 {
|
func objectRefGetValue2(_ objectRef: CFTypeRef) -> UInt32 {
|
||||||
|
@ -87,10 +54,6 @@ class Document: NSDocument {
|
||||||
self.addWindowController(windowController)
|
self.addWindowController(windowController)
|
||||||
}
|
}
|
||||||
|
|
||||||
override class func canConcurrentlyReadDocuments(ofType: String) -> Bool {
|
|
||||||
return ofType == "com.procreate"
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Pass in an object from the $object array, which always contains a $class key.
|
Pass in an object from the $object array, which always contains a $class key.
|
||||||
*/
|
*/
|
||||||
|
@ -110,7 +73,7 @@ class Document: NSDocument {
|
||||||
/*
|
/*
|
||||||
Returns the correct tile size, taking into account the remainder between tile size and image size.
|
Returns the correct tile size, taking into account the remainder between tile size and image size.
|
||||||
*/
|
*/
|
||||||
func getTileSize(_ x: Int, _ y: Int) -> (Int, Int) {
|
func getTileSize(x: Int, y: Int) -> (Int, Int) {
|
||||||
var width: Int = info.tileSize
|
var width: Int = info.tileSize
|
||||||
var height: Int = info.tileSize
|
var height: Int = info.tileSize
|
||||||
|
|
||||||
|
@ -149,138 +112,16 @@ class Document: NSDocument {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getChunkRect(_ chunk: SilicaChunk) -> NSRect {
|
func parseSilicaLayer(archive: Archive, dict: NSDictionary) {
|
||||||
let x = chunk.x
|
|
||||||
var y = chunk.y
|
|
||||||
|
|
||||||
let (width, height) = getTileSize(x, y)
|
|
||||||
|
|
||||||
if y == rows {
|
|
||||||
y = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return NSRect(x: info.tileSize * x, y: info.height - (info.tileSize * y), width: width, height: height)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: convert to switch/case
|
|
||||||
func getBlendKernel(_ layer: SilicaLayer) -> CIBlendKernel? {
|
|
||||||
if layer.data.blendMode == 1 {
|
|
||||||
return .multiply
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 10 {
|
|
||||||
return .colorBurn
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 19 {
|
|
||||||
return .darken
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 8 {
|
|
||||||
return .linearBurn
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 4 {
|
|
||||||
return .lighten
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 2 {
|
|
||||||
return .screen
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 13 {
|
|
||||||
return .hardLight
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 9 {
|
|
||||||
return .colorDodge
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 3 {
|
|
||||||
return .subtract
|
|
||||||
}
|
|
||||||
|
|
||||||
if layer.data.blendMode == 0 && layer.data.blendMode != layer.data.extendedBlend {
|
|
||||||
if layer.data.extendedBlend == 25 {
|
|
||||||
return .darkerColor
|
|
||||||
}
|
|
||||||
if layer.data.extendedBlend == 24 {
|
|
||||||
return .lighterColor
|
|
||||||
}
|
|
||||||
if layer.data.extendedBlend == 21 {
|
|
||||||
return .vividLight
|
|
||||||
}
|
|
||||||
if layer.data.extendedBlend == 22 {
|
|
||||||
return .linearLight
|
|
||||||
}
|
|
||||||
if layer.data.extendedBlend == 23 {
|
|
||||||
return .pinLight
|
|
||||||
}
|
|
||||||
if layer.data.extendedBlend == 20 {
|
|
||||||
return .hardMix
|
|
||||||
}
|
|
||||||
if layer.data.extendedBlend == 26 {
|
|
||||||
return .divide
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if layer.data.blendMode == 11 {
|
|
||||||
return .overlay
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 17 {
|
|
||||||
return .softLight
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 12 {
|
|
||||||
return .hardLight
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 6 {
|
|
||||||
return .difference
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 5 {
|
|
||||||
return .exclusion
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 7 {
|
|
||||||
return .componentAdd
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 15 {
|
|
||||||
return .hue
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 16 {
|
|
||||||
return .saturation
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 13 {
|
|
||||||
return .color
|
|
||||||
}
|
|
||||||
if layer.data.blendMode == 14 {
|
|
||||||
return .luminosity
|
|
||||||
}
|
|
||||||
|
|
||||||
return .sourceOver
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSilicaLayer(archive: Archive, dict: NSDictionary, isMask: Bool) -> SilicaLayer? {
|
|
||||||
let objectsArray = self.dict?["$objects"] as! NSArray
|
let objectsArray = self.dict?["$objects"] as! NSArray
|
||||||
|
|
||||||
if getDocumentClassName(dict: dict) == LayerClassName {
|
if getDocumentClassName(dict: dict) == LayerClassName {
|
||||||
var layer = SilicaLayer()
|
var layer = SilicaLayer()
|
||||||
|
|
||||||
if let val = dict["name"] {
|
|
||||||
let NameClassID = getClassID(id: val)
|
|
||||||
let NameClass = objectsArray[NameClassID] as! NSString
|
|
||||||
|
|
||||||
layer.name = NameClass as String
|
|
||||||
}
|
|
||||||
|
|
||||||
let UUIDKey = dict["UUID"]
|
let UUIDKey = dict["UUID"]
|
||||||
let UUIDClassID = getClassID(id: UUIDKey)
|
let UUIDClassID = getClassID(id: UUIDKey)
|
||||||
let UUIDClass = objectsArray[UUIDClassID] as! NSString
|
let UUIDClass = objectsArray[UUIDClassID] as! NSString
|
||||||
|
|
||||||
let maskKey = dict["mask"]
|
|
||||||
let maskClassID = getClassID(id: maskKey)
|
|
||||||
let maskClass = objectsArray[maskClassID]
|
|
||||||
|
|
||||||
layer.data.blendMode = (dict["blend"] as? NSNumber)!.intValue
|
|
||||||
layer.data.extendedBlend = (dict["extendedBlend"] as? NSNumber)!.intValue
|
|
||||||
layer.data.opacity = (dict["opacity"] as? NSNumber)!.doubleValue
|
|
||||||
layer.data.hidden = (dict["hidden"] as? Bool)!
|
|
||||||
layer.clipped = (dict["clipped"] as? Bool)!
|
|
||||||
|
|
||||||
if maskClassID != 0 {
|
|
||||||
layer.mask = parseSilicaLayer(archive: archive, dict: maskClass as! NSDictionary, isMask: true)?.data
|
|
||||||
}
|
|
||||||
|
|
||||||
var chunkPaths: [String] = []
|
var chunkPaths: [String] = []
|
||||||
|
|
||||||
archive.forEach { (entry: Entry) in
|
archive.forEach { (entry: Entry) in
|
||||||
|
@ -289,13 +130,15 @@ class Document: NSDocument {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.data.chunks = Array(repeating: SilicaChunk(), count: chunkPaths.count)
|
layer.chunks = Array(repeating: SilicaChunk(), count: chunkPaths.count)
|
||||||
|
|
||||||
let dispatchGroup = DispatchGroup()
|
let dispatchGroup = DispatchGroup()
|
||||||
let queue = DispatchQueue(label: "imageWork")
|
let queue = DispatchQueue(label: "imageWork")
|
||||||
|
|
||||||
DispatchQueue.concurrentPerform(iterations: chunkPaths.count) { (i: Int) in
|
DispatchQueue.concurrentPerform(iterations: chunkPaths.count) { (i: Int) in
|
||||||
guard let threadArchive = Archive(data: self.data!, accessMode: .read) else {
|
dispatchGroup.enter()
|
||||||
|
|
||||||
|
guard let threadArchive = Archive(data: self.data!, accessMode: Archive.AccessMode.read) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,10 +148,8 @@ class Document: NSDocument {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let (width, height) = getTileSize(x, y)
|
let (width, height) = getTileSize(x: x, y: y)
|
||||||
|
let byteSize = width * height * 4
|
||||||
let numChannels = isMask ? 1 : 4
|
|
||||||
let byteSize = width * height * numChannels
|
|
||||||
|
|
||||||
let uncompressedMemory = UnsafeMutablePointer<UInt8>.allocate(capacity: byteSize)
|
let uncompressedMemory = UnsafeMutablePointer<UInt8>.allocate(capacity: byteSize)
|
||||||
|
|
||||||
|
@ -325,38 +166,28 @@ class Document: NSDocument {
|
||||||
let imageData = Data(bytes: uncompressedMemory, count: byteSize)
|
let imageData = Data(bytes: uncompressedMemory, count: byteSize)
|
||||||
|
|
||||||
let render: CGColorRenderingIntent = .defaultIntent
|
let render: CGColorRenderingIntent = .defaultIntent
|
||||||
let rgbColorSpace = isMask ? CGColorSpaceCreateDeviceGray() : info.colorSpace
|
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue).union(.byteOrder32Big)
|
||||||
let bitmapInfo = CGBitmapInfo(rawValue: (isMask ? CGImageAlphaInfo.none : CGImageAlphaInfo.premultipliedLast).rawValue).union(.byteOrder32Big)
|
|
||||||
let providerRef: CGDataProvider? = CGDataProvider(data: imageData as CFData)
|
let providerRef: CGDataProvider? = CGDataProvider(data: imageData as CFData)
|
||||||
|
|
||||||
guard let cgimage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 8 * numChannels, bytesPerRow: width * numChannels, space: rgbColorSpace, bitmapInfo: bitmapInfo, provider: providerRef!, decode: nil, shouldInterpolate: false, intent: render) else {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.async(group: dispatchGroup) {
|
let image = NSImage(cgImage: cgimage, size: NSZeroSize)
|
||||||
layer.data.chunks[i].image = cgimage
|
|
||||||
layer.data.chunks[i].x = x
|
queue.async(flags: .barrier) {
|
||||||
layer.data.chunks[i].y = y
|
layer.chunks[i].image = image
|
||||||
|
layer.chunks[i].x = x
|
||||||
|
layer.chunks[i].y = y
|
||||||
|
|
||||||
|
dispatchGroup.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchGroup.wait()
|
dispatchGroup.wait()
|
||||||
|
|
||||||
return layer
|
info.layers.append(layer)
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// this parses a string of form "{255, 255}"
|
|
||||||
func parsePairString(_ str: String) -> (Int, Int)? {
|
|
||||||
let sizeComponents = str.replacingOccurrences(of: "{", with: "").replacingOccurrences(of: "}", with: "").components(separatedBy: ", ")
|
|
||||||
|
|
||||||
if sizeComponents.count == 2, let width = Int(sizeComponents[0]), let height = Int(sizeComponents[1]) {
|
|
||||||
return (width, height)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,70 +201,19 @@ class Document: NSDocument {
|
||||||
info.flippedHorizontally = (dict[FlippedHorizontallyKey] as! NSNumber).boolValue
|
info.flippedHorizontally = (dict[FlippedHorizontallyKey] as! NSNumber).boolValue
|
||||||
info.flippedVertically = (dict[FlippedVerticallyKey] as! NSNumber).boolValue
|
info.flippedVertically = (dict[FlippedVerticallyKey] as! NSNumber).boolValue
|
||||||
|
|
||||||
let videoResolutionClassKey = dict["SilicaDocumentVideoSegmentInfoKey"]
|
|
||||||
let videoResolutionClassID = getClassID(id: videoResolutionClassKey)
|
|
||||||
let videoResolution = objectsArray[videoResolutionClassID] as! NSDictionary
|
|
||||||
|
|
||||||
let frameSizeClassKey = videoResolution["frameSize"]
|
|
||||||
let frameSizeClassID = getClassID(id: frameSizeClassKey)
|
|
||||||
let frameSize = objectsArray[frameSizeClassID] as! String
|
|
||||||
|
|
||||||
info.videoFrame = parsePairString(frameSize)!
|
|
||||||
|
|
||||||
let colorProfileClassKey = dict["colorProfile"]
|
|
||||||
let colorProfileClassID = getClassID(id: colorProfileClassKey)
|
|
||||||
let colorProfile = objectsArray[colorProfileClassID] as! NSDictionary
|
|
||||||
|
|
||||||
let colorProfileNameClassKey = colorProfile["SiColorProfileArchiveICCNameKey"]
|
|
||||||
let colorProfileNameClassID = getClassID(id: colorProfileNameClassKey)
|
|
||||||
let colorProfileName = objectsArray[colorProfileNameClassID] as! NSString
|
|
||||||
|
|
||||||
// we only support the basic "Display P3" color space... does Procreate actually store the ICC data??
|
|
||||||
if colorProfileName == "Display P3" {
|
|
||||||
info.colorSpace = CGColorSpace(name: CGColorSpace.displayP3)!
|
|
||||||
}
|
|
||||||
|
|
||||||
let backgroundClassKey = dict["backgroundColor"]
|
|
||||||
let backgroundClassID = getClassID(id: backgroundClassKey)
|
|
||||||
let background = objectsArray[backgroundClassID] as! NSData
|
|
||||||
|
|
||||||
var backgroundArray: [Float] = [0.0, 0.0, 0.0, 0.0]
|
|
||||||
|
|
||||||
background.getBytes(&backgroundArray, length: 16)
|
|
||||||
let backgroundCgArray: [CGFloat] = [CGFloat(backgroundArray[0]), CGFloat(backgroundArray[1]), CGFloat(backgroundArray[2]), CGFloat(backgroundArray[3])]
|
|
||||||
|
|
||||||
info.backgroundColor = CGColor(colorSpace: info.colorSpace, components: backgroundCgArray)!
|
|
||||||
|
|
||||||
let strokeClassKey = dict[StrokeCountKey]
|
|
||||||
let strokeClassID = getClassID(id: strokeClassKey)
|
|
||||||
let strokeCount = objectsArray[strokeClassID] as! NSNumber
|
|
||||||
|
|
||||||
info.strokeCount = Int(truncating: strokeCount)
|
|
||||||
|
|
||||||
let nameClassKey = dict[NameKey]
|
|
||||||
let nameClassID = getClassID(id: nameClassKey)
|
|
||||||
let nameString = objectsArray[nameClassID] as! String
|
|
||||||
|
|
||||||
info.name = nameString
|
|
||||||
|
|
||||||
let authorClassKey = dict[AuthorNameKey]
|
|
||||||
let authorClassID = getClassID(id: authorClassKey)
|
|
||||||
let authorString = objectsArray[authorClassID] as! String
|
|
||||||
|
|
||||||
if authorString != "$null" {
|
|
||||||
info.authorName = authorString
|
|
||||||
}
|
|
||||||
|
|
||||||
let sizeClassKey = dict[SizeKey]
|
let sizeClassKey = dict[SizeKey]
|
||||||
let sizeClassID = getClassID(id: sizeClassKey)
|
let sizeClassID = getClassID(id: sizeClassKey)
|
||||||
let sizeString = objectsArray[sizeClassID] as! String
|
let sizeString = objectsArray[sizeClassID] as! String
|
||||||
|
|
||||||
let (width, height) = parsePairString(sizeString)!
|
let sizeComponents = sizeString.replacingOccurrences(of: "{", with: "").replacingOccurrences(of: "}", with: "").components(separatedBy: ", ")
|
||||||
info.width = width
|
let width = Int(sizeComponents[0])
|
||||||
info.height = height
|
let height = Int(sizeComponents[1])
|
||||||
|
|
||||||
|
info.width = width!
|
||||||
|
info.height = height!
|
||||||
|
|
||||||
columns = Int(ceil(Float(info.width) / Float(info.tileSize)))
|
columns = Int(ceil(Float(info.width) / Float(info.tileSize)))
|
||||||
rows = Int(ceil(Float(info.height) / Float(info.tileSize))) + 1 // TODO: lol why
|
rows = Int(ceil(Float(info.height) / Float(info.tileSize)))
|
||||||
|
|
||||||
if info.width % info.tileSize != 0 {
|
if info.width % info.tileSize != 0 {
|
||||||
remainderWidth = (columns * info.tileSize) - info.width
|
remainderWidth = (columns * info.tileSize) - info.width
|
||||||
|
@ -449,14 +229,11 @@ class Document: NSDocument {
|
||||||
|
|
||||||
let array = layersClass["NS.objects"] as! NSArray
|
let array = layersClass["NS.objects"] as! NSArray
|
||||||
|
|
||||||
//dump(dict, indent: 5)
|
|
||||||
|
|
||||||
for object in array {
|
for object in array {
|
||||||
let layerClassID = getClassID(id: object)
|
let layerClassID = getClassID(id: object)
|
||||||
let layerClass = objectsArray[layerClassID] as! NSDictionary
|
let layerClass = objectsArray[layerClassID] as! NSDictionary
|
||||||
|
|
||||||
guard let layer = parseSilicaLayer(archive: archive, dict: layerClass, isMask: false) else { return }
|
parseSilicaLayer(archive: archive, dict: layerClass)
|
||||||
info.layers.append(layer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -482,134 +259,54 @@ class Document: NSDocument {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SilicaParsingError: Error, LocalizedError {
|
|
||||||
enum Kind {
|
|
||||||
case invalid
|
|
||||||
}
|
|
||||||
|
|
||||||
let kind: Kind
|
|
||||||
let filename: URL?
|
|
||||||
|
|
||||||
public var errorDescription: String? {
|
|
||||||
switch self.kind {
|
|
||||||
case .invalid:
|
|
||||||
return filename!.lastPathComponent + " is an invalid Silica Document."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func throwError(_ error: SilicaParsingError.Kind) {
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
let _ = presentError(SilicaParsingError(kind: error, filename: fileURL))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func read(from data: Data, ofType typeName: String) throws {
|
override func read(from data: Data, ofType typeName: String) throws {
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
guard let archive = Archive(data: data, accessMode: Archive.AccessMode.read) else {
|
guard let archive = Archive(data: data, accessMode: Archive.AccessMode.read) else {
|
||||||
throwError(.invalid)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let documentEntry = archive[DocumentArchivePath] else {
|
guard let documentEntry = archive[DocumentArchivePath] else {
|
||||||
throwError(.invalid)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let documentData = readData(archive: archive, entry: documentEntry) else {
|
guard let documentData = readData(archive: archive, entry: documentEntry) else {
|
||||||
throwError(.invalid)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var plistFormat = PropertyListSerialization.PropertyListFormat.binary
|
var plistFormat = PropertyListSerialization.PropertyListFormat.binary
|
||||||
guard let propertyList = try? PropertyListSerialization.propertyList(from: documentData, options: [], format: &plistFormat) else {
|
guard let propertyList = try? PropertyListSerialization.propertyList(from: documentData, options: [], format: &plistFormat) else {
|
||||||
throwError(.invalid)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parseDocument(archive: archive, dict: propertyList as! NSDictionary)
|
parseDocument(archive: archive, dict: propertyList as! NSDictionary)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeComposite() -> NSImage? {
|
func makeComposite() -> NSImage {
|
||||||
// create the final composite output image
|
var image = NSImage(size: NSSize(width: info.width, height: info.height))
|
||||||
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).union(.byteOrder32Big)
|
image.lockFocus()
|
||||||
|
|
||||||
let ccgContext = CGContext(data: nil, width: info.width, height: info.height, bitsPerComponent: 8, bytesPerRow: info.width * 4, space: info.colorSpace, bitmapInfo: bitmapInfo.rawValue)
|
let color = NSColor.white
|
||||||
|
color.drawSwatch(in: NSRect(origin: .zero, size: image.size))
|
||||||
ccgContext?.setFillColor(info.backgroundColor)
|
|
||||||
ccgContext?.fill(info.cgRect)
|
|
||||||
|
|
||||||
let context = CIContext()
|
|
||||||
|
|
||||||
guard let cgImage = ccgContext?.makeImage() else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var masterImage = CIImage(cgImage: cgImage)
|
|
||||||
|
|
||||||
var previousImage: CGImage? = nil
|
|
||||||
|
|
||||||
for layer in info.layers.reversed() {
|
for layer in info.layers.reversed() {
|
||||||
if !layer.data.hidden {
|
for chunk in layer.chunks {
|
||||||
// start by creating a new layer composite image, needed for image masking
|
let x = chunk.x
|
||||||
let layerContext = CGContext(data: nil, width: info.width, height: info.height, bitsPerComponent: 8, bytesPerRow: info.width * 4, space: info.colorSpace, bitmapInfo: bitmapInfo.rawValue)
|
var y = chunk.y
|
||||||
|
|
||||||
layerContext?.clear(info.cgRect)
|
let (width, height) = getTileSize(x: x, y: y)
|
||||||
|
|
||||||
var maskContext: CGContext?
|
if y == rows {
|
||||||
|
y = 0
|
||||||
|
}
|
||||||
|
|
||||||
let kernel = getBlendKernel(layer)
|
let rect = NSRect(x: info.tileSize * x, y: info.height - (info.tileSize * y), width: width, height: height)
|
||||||
|
|
||||||
if layer.mask != nil {
|
chunk.image.draw(in: rect)
|
||||||
let grayColorSpace = CGColorSpaceCreateDeviceGray()
|
|
||||||
let maskBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue).union(.byteOrder16Big)
|
|
||||||
|
|
||||||
maskContext = CGContext(data: nil, width: info.width, height: info.height, bitsPerComponent: 16, bytesPerRow: 0, space: grayColorSpace, bitmapInfo: maskBitmapInfo.rawValue)
|
|
||||||
|
|
||||||
maskContext?.setFillColor(.white)
|
|
||||||
maskContext?.fill(info.cgRect)
|
|
||||||
|
|
||||||
for chunk in layer.mask!.chunks {
|
|
||||||
maskContext?.draw(chunk.image!, in: getChunkRect(chunk))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for chunk in layer.data.chunks {
|
image.unlockFocus()
|
||||||
layerContext?.setAlpha(CGFloat(layer.data.opacity))
|
|
||||||
layerContext?.setBlendMode(.normal)
|
|
||||||
|
|
||||||
if !layer.data.hidden {
|
|
||||||
layerContext?.draw(chunk.image!, in: getChunkRect(chunk))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let layerImage = layerContext?.makeImage()
|
|
||||||
|
|
||||||
if layer.clipped && previousImage != nil {
|
|
||||||
let result = previousImage!.toGrayscale()
|
|
||||||
let newImage = layerImage!.masking(result!)
|
|
||||||
|
|
||||||
previousImage = newImage
|
|
||||||
} else if layer.mask != nil && maskContext != nil {
|
|
||||||
let maskImage = (maskContext?.makeImage())!
|
|
||||||
let newImage = layerImage!.masking(maskImage)!
|
|
||||||
|
|
||||||
previousImage = newImage
|
|
||||||
} else {
|
|
||||||
previousImage = layerImage
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply image
|
|
||||||
masterImage = kernel!.apply(foreground: CIImage(cgImage: previousImage!), background: masterImage, colorSpace: info.colorSpace)!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let finalCgImage = context.createCGImage(masterImage, from: info.cgRect, format: .RGBA8, colorSpace: info.colorSpace) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var image = NSImage(cgImage: finalCgImage, size: info.nsSize)
|
|
||||||
|
|
||||||
if info.orientation == 3 {
|
if info.orientation == 3 {
|
||||||
image = image.imageRotatedByDegreess(degrees: 90)
|
image = image.imageRotatedByDegreess(degrees: 90)
|
||||||
|
@ -711,35 +408,3 @@ public extension NSImage {
|
||||||
return flipedImage
|
return flipedImage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension CGImage {
|
|
||||||
func toGrayscale() -> CGImage? {
|
|
||||||
let ciImage = CIImage(cgImage: self)
|
|
||||||
|
|
||||||
let filter = CIFilter(name: "CIColorControls")
|
|
||||||
filter?.setValue(ciImage, forKey: kCIInputImageKey)
|
|
||||||
filter?.setValue(5.0, forKey: kCIInputBrightnessKey)
|
|
||||||
filter?.setValue(0.0, forKey: kCIInputSaturationKey)
|
|
||||||
filter?.setValue(1.1, forKey: kCIInputContrastKey)
|
|
||||||
|
|
||||||
guard let intermediateImage = filter?.outputImage else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let image = CIContext().createCGImage(intermediateImage, from: CGRect(origin: .zero, size: CGSize(width: self.width, height: self.height))) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let grayColorSpace = CGColorSpaceCreateDeviceGray()
|
|
||||||
let maskBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue).union(.byteOrder16Big)
|
|
||||||
|
|
||||||
let maskContext = CGContext(data: nil, width: self.width, height: self.height, bitsPerComponent: 16, bytesPerRow: 0, space: grayColorSpace, bitmapInfo: maskBitmapInfo.rawValue)
|
|
||||||
|
|
||||||
maskContext?.setFillColor(.black)
|
|
||||||
maskContext?.fill(CGRect(origin: .zero, size: CGSize(width: self.width, height: self.height)))
|
|
||||||
|
|
||||||
maskContext?.draw(image, in: CGRect(origin: .zero, size: CGSize(width: self.width, height: self.height)))
|
|
||||||
|
|
||||||
return maskContext?.makeImage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,9 +14,11 @@
|
||||||
<key>CFBundleTypeIconFile</key>
|
<key>CFBundleTypeIconFile</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string></string>
|
<string>com.proceate</string>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
<string>Viewer</string>
|
<string>Viewer</string>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Default</string>
|
||||||
<key>LSItemContentTypes</key>
|
<key>LSItemContentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>com.procreate</string>
|
<string>com.procreate</string>
|
||||||
|
@ -42,13 +44,13 @@
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string>public.app-category.utilities</string>
|
<string>public.app-category.utilities</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>Copyright © 2020 Joshua Goins. All rights reserved.</string>
|
<string>Copyright © 2021 Joshua Goins. All rights reserved.</string>
|
||||||
<key>NSMainStoryboardFile</key>
|
<key>NSMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
8
SilicaViewer/SilicaViewer-Bridging-Header.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "minilzo.h"
|
||||||
|
|
||||||
|
uint32_t valueForKeyedArchiverUID(uint64_t keyedArchiverUID) {
|
||||||
|
void *uid = (void*)keyedArchiverUID;
|
||||||
|
uint32_t *valuePtr = uid+16;
|
||||||
|
return *valuePtr;
|
||||||
|
}
|
87
SilicaViewer/TimelapseViewController.swift
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import Foundation
|
||||||
|
import Cocoa
|
||||||
|
import AVKit
|
||||||
|
import AVFoundation
|
||||||
|
import ZIPFoundation
|
||||||
|
|
||||||
|
func radians(_ x: CGFloat) -> CGFloat {
|
||||||
|
return .pi * x / 180.0
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimelapseViewController: NSViewController {
|
||||||
|
var document: Document?
|
||||||
|
|
||||||
|
private let rootLayer = CALayer()
|
||||||
|
let player: AVQueuePlayer =
|
||||||
|
AVQueuePlayer()
|
||||||
|
var isPlaying = true
|
||||||
|
|
||||||
|
@IBAction func clickAction(_ sender: Any) {
|
||||||
|
if isPlaying {
|
||||||
|
player.pause()
|
||||||
|
isPlaying = false
|
||||||
|
} else {
|
||||||
|
player.play()
|
||||||
|
isPlaying = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addVideo(entryPath: String, directory: String) {
|
||||||
|
guard let archive = Archive(data: (document?.data)!, accessMode: Archive.AccessMode.read) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileName = NSUUID().uuidString + ".mp4"
|
||||||
|
|
||||||
|
// This returns a URL? even though it is an NSURL class method
|
||||||
|
let fullURL = NSURL.fileURL(withPathComponents: [directory, fileName])!
|
||||||
|
|
||||||
|
try? archive.extract(archive[entryPath]!, to: fullURL)
|
||||||
|
|
||||||
|
OperationQueue.main.addOperation {
|
||||||
|
self.player.insert(AVPlayerItem(url: fullURL), after: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear() {
|
||||||
|
super.viewDidAppear()
|
||||||
|
|
||||||
|
guard let archive = Archive(data: (document?.data)!, accessMode: Archive.AccessMode.read) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let directory = NSTemporaryDirectory()
|
||||||
|
|
||||||
|
let queue = OperationQueue()
|
||||||
|
for entry in archive.makeIterator() {
|
||||||
|
if entry.path.contains(VideoPath) {
|
||||||
|
queue.addOperation() {
|
||||||
|
self.addVideo(entryPath: entry.path, directory: directory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let playerLayer = AVPlayerLayer.init(player: self.player)
|
||||||
|
playerLayer.videoGravity = .resizeAspect
|
||||||
|
playerLayer.autoresizingMask = [.layerHeightSizable, .layerWidthSizable]
|
||||||
|
playerLayer.frame = view.bounds
|
||||||
|
|
||||||
|
var rotationDegrees: CGFloat = 0
|
||||||
|
if document?.info.orientation == 3 {
|
||||||
|
rotationDegrees = 90
|
||||||
|
} else if document?.info.orientation == 4 {
|
||||||
|
rotationDegrees = -90
|
||||||
|
}
|
||||||
|
|
||||||
|
let affineTransform = CGAffineTransform(rotationAngle: radians(rotationDegrees))
|
||||||
|
playerLayer.setAffineTransform(affineTransform)
|
||||||
|
|
||||||
|
view.wantsLayer = true
|
||||||
|
view.layer = rootLayer
|
||||||
|
|
||||||
|
rootLayer.addSublayer(playerLayer)
|
||||||
|
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ class ViewController: NSViewController {
|
||||||
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?.makeThumbnail()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
|
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
|
|
@ -19,7 +19,7 @@
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
|
|