1
Fork 0

Rename to Silica Viewer

This commit is contained in:
Joshua Goins 2021-09-15 14:10:07 -04:00
parent 636145e265
commit e7b35fcfdf
43 changed files with 249 additions and 982 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -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
}
}

View file

@ -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);

View file

@ -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)
}
}

View file

@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSExtension</key>
@ -31,7 +31,7 @@
<string>com.procreate</string>
</array>
<key>QLSupportsSearchableItems</key>
<false/>
<true/>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.quicklook.preview</string>

View file

@ -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
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
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.

View file

@ -7,15 +7,10 @@
objects = {
/* 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 */; };
030F700E2415C6B500A43F01 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030F700D2415C6B500A43F01 /* PreviewViewController.swift */; };
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, ); }; };
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 */; };
035D1A0526F0927200B332BE /* cbridge.c in Sources */ = {isa = PBXBuildFile; fileRef = 035D19FA26F0927200B332BE /* cbridge.c */; };
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 */; };
035D1A0B26F0927200B332BE /* TimelapseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D1A0226F0927200B332BE /* TimelapseViewController.swift */; };
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 */; };
036AFC0D241800350075400A /* QuickLookThumbnailing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 036AFC0C241800350075400A /* QuickLookThumbnailing.framework */; };
036AFC0E241800350075400A /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 030F700A2415C6B500A43F01 /* Quartz.framework */; };
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, ); }; };
036AFC1B241800850075400A /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 036AFC1A241800850075400A /* ZIPFoundation */; };
0371996027BAC5D800EE1DFD /* Silica_ViewerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0371995F27BAC5D800EE1DFD /* Silica_ViewerTests.swift */; };
03C39DE726F90734005555AE /* lzodefs.h in Headers */ = {isa = PBXBuildFile; fileRef = 03C39DE226F90733005555AE /* lzodefs.h */; };
03C39DE826F90734005555AE /* Makefile in Sources */ = {isa = PBXBuildFile; fileRef = 03C39DE426F90734005555AE /* Makefile */; };
03C39DE926F90734005555AE /* testmini.c in Sources */ = {isa = PBXBuildFile; fileRef = 03C39DE526F90734005555AE /* testmini.c */; };
03C39DEA26F90734005555AE /* minilzo.h in Headers */ = {isa = PBXBuildFile; fileRef = 03C39DE626F90734005555AE /* minilzo.h */; };
==== BASE ====
037B4042241821D200392452 /* InfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037B4041241821D200392452 /* InfoViewController.swift */; };
03CB382424191F620078B3E5 /* cbridge.c in Sources */ = {isa = PBXBuildFile; fileRef = 03CB382324191F620078B3E5 /* cbridge.c */; };
==== BASE ====
03CB383B2419CA2D0078B3E5 /* libLZO.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 03CB382E2419C9DB0078B3E5 /* libLZO.a */; };
03CB3840241A5AED0078B3E5 /* 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;
remoteInfo = Thumbnail;
};
0371996127BAC5D800EE1DFD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 030F6FE62415C5E300A43F01 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 030F6FED2415C5E300A43F01;
remoteInfo = SilicaViewer;
};
03CB383C2419CA2D0078B3E5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 030F6FE62415C5E300A43F01 /* Project object */;
@ -102,10 +87,6 @@
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>"; };
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>"; };
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>"; };
@ -122,14 +103,11 @@
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>"; };
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>"; };
0371995D27BAC5D800EE1DFD /* Silica ViewerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Silica ViewerTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
0371995F27BAC5D800EE1DFD /* Silica_ViewerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Silica_ViewerTests.swift; sourceTree = "<group>"; };
03C39DE226F90733005555AE /* lzodefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lzodefs.h; sourceTree = "<group>"; };
03C39DE326F90733005555AE /* COPYING */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = COPYING; sourceTree = "<group>"; };
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>"; };
==== BASE ====
037B4041241821D200392452 /* InfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoViewController.swift; sourceTree = "<group>"; };
03CB382224191F610078B3E5 /* ProcreateViewer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ProcreateViewer-Bridging-Header.h"; sourceTree = "<group>"; };
03CB382324191F620078B3E5 /* cbridge.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cbridge.c; sourceTree = "<group>"; };
==== BASE ====
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>"; };
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;
};
0371995A27BAC5D800EE1DFD /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
03CB382C2419C9DB0078B3E5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -187,15 +158,11 @@
030F6FE52415C5E300A43F01 = {
isa = PBXGroup;
children = (
0371995827BAC52900EE1DFD /* SilicaViewer.xctestplan */,
0357A94826F9C71E0075D5BC /* LZO */,
035D19F726F0927200B332BE /* SilicaViewer */,
03CB383E241A5ACD0078B3E5 /* Shared */,
03CB38322419C9F80078B3E5 /* LZO */,
030F6FF02415C5E300A43F01 /* ProcreateViewer */,
030F700C2415C6B500A43F01 /* Quicklook */,
036AFC0F241800350075400A /* Thumbnail */,
0371995E27BAC5D800EE1DFD /* Silica ViewerTests */,
030F70092415C6B500A43F01 /* Frameworks */,
030F6FEF2415C5E300A43F01 /* Products */,
);
@ -208,29 +175,10 @@
030F70082415C6B500A43F01 /* QuickLook.appex */,
036AFC0B241800350075400A /* Thumbnail.appex */,
03CB382E2419C9DB0078B3E5 /* libLZO.a */,
0371995D27BAC5D800EE1DFD /* Silica ViewerTests.xctest */,
);
name = Products;
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 */ = {
isa = PBXGroup;
children = (
@ -251,22 +199,6 @@
path = Quicklook;
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 */ = {
isa = PBXGroup;
children = (
@ -295,12 +227,15 @@
path = Thumbnail;
sourceTree = "<group>";
};
0371995E27BAC5D800EE1DFD /* Silica ViewerTests */ = {
03CB38322419C9F80078B3E5 /* LZO */ = {
isa = PBXGroup;
children = (
0371995F27BAC5D800EE1DFD /* Silica_ViewerTests.swift */,
03CB3846241A5D600078B3E5 /* lzoconf.h */,
03CB3844241A5D600078B3E5 /* lzodefs.h */,
03CB3845241A5D600078B3E5 /* minilzo.c */,
03CB3843241A5D600078B3E5 /* minilzo.h */,
);
path = "Silica ViewerTests";
path = LZO;
sourceTree = "<group>";
};
03CB383E241A5ACD0078B3E5 /* Shared */ = {
@ -318,9 +253,9 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
03C39DE726F90734005555AE /* lzodefs.h in Headers */,
0357A94E26F9C7370075D5BC /* lzoconf.h in Headers */,
03C39DEA26F90734005555AE /* minilzo.h in Headers */,
03CB3848241A5D600078B3E5 /* lzodefs.h in Headers */,
03CB384A241A5D600078B3E5 /* lzoconf.h in Headers */,
03CB3847241A5D600078B3E5 /* minilzo.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -435,7 +370,6 @@
==== BASE ====
LastSwiftUpdateCheck = 1130;
LastUpgradeCheck = 1250;
==== BASE ====
ORGANIZATIONNAME = Josh;
TargetAttributes = {
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";
developmentRegion = en;
hasScannedForEncodings = 0;
@ -473,7 +407,7 @@
projectDirPath = "";
projectRoot = "";
targets = (
030F6FED2415C5E300A43F01 /* ProcreateViewer */,
030F6FED2415C5E300A43F01 /* SilicaViewer */,
030F70072415C6B500A43F01 /* QuickLook */,
036AFC0A241800350075400A /* Thumbnail */,
03CB382D2419C9DB0078B3E5 /* LZO */,
@ -487,8 +421,8 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
030F6FF92415C5E400A43F01 /* Assets.xcassets in Resources */,
036AFBE32417F0A00075400A /* Main.storyboard in Resources */,
035D1A0626F0927200B332BE /* Assets.xcassets in Resources */,
035D1A0726F0927200B332BE /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -521,13 +455,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
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 */,
033F94F92648B8E200099FB7 /* TimelapseViewController.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 */,
035D1A0926F0927200B332BE /* Document.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -599,10 +533,10 @@
name = PreviewViewController.xib;
sourceTree = "<group>";
};
036AFBE12417F0A00075400A /* Main.storyboard */ = {
035D19FD26F0927200B332BE /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
036AFBE22417F0A00075400A /* Base */,
035D19FE26F0927200B332BE /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
@ -636,6 +570,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -696,6 +631,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -729,18 +665,20 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = ProcreateViewer/ProcreateViewer.entitlements;
CODE_SIGN_ENTITLEMENTS = SilicaViewer/SilicaViewer.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = JM5LKVKH48;
INFOPLIST_FILE = ProcreateViewer/Info.plist;
INFOPLIST_FILE = SilicaViewer/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.ProcreateViewer;
PRODUCT_NAME = "Procreate Viewer";
SWIFT_OBJC_BRIDGING_HEADER = "ProcreateViewer/ProcreateViewer-Bridging-Header.h";
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.SilicaViewer;
PRODUCT_NAME = "Silica Viewer";
SWIFT_OBJC_BRIDGING_HEADER = "SilicaViewer/SilicaViewer-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
@ -752,18 +690,20 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = ProcreateViewer/ProcreateViewer.entitlements;
CODE_SIGN_ENTITLEMENTS = SilicaViewer/SilicaViewer.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = JM5LKVKH48;
INFOPLIST_FILE = ProcreateViewer/Info.plist;
INFOPLIST_FILE = SilicaViewer/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.ProcreateViewer;
PRODUCT_NAME = "Procreate Viewer";
SWIFT_OBJC_BRIDGING_HEADER = "ProcreateViewer/ProcreateViewer-Bridging-Header.h";
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.SilicaViewer;
PRODUCT_NAME = "Silica Viewer";
SWIFT_OBJC_BRIDGING_HEADER = "SilicaViewer/SilicaViewer-Bridging-Header.h";
SWIFT_VERSION = 5.0;
};
name = Release;
@ -772,7 +712,9 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = Quicklook/Quicklook.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = JM5LKVKH48;
INFOPLIST_FILE = Quicklook/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -780,7 +722,7 @@
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.ProcreateViewer.QuickLook;
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.SilicaViewer.QuickLook;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
@ -791,7 +733,9 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = Quicklook/Quicklook.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = JM5LKVKH48;
INFOPLIST_FILE = Quicklook/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -799,7 +743,7 @@
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.ProcreateViewer.QuickLook;
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.SilicaViewer.QuickLook;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
@ -810,7 +754,9 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = Thumbnail/Thumbnail.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = JM5LKVKH48;
INFOPLIST_FILE = Thumbnail/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -819,7 +765,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.ProcreateViewer.Thumbnail;
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.SilicaViewer.Thumbnail;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
@ -830,7 +776,9 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = Thumbnail/Thumbnail.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = JM5LKVKH48;
INFOPLIST_FILE = Thumbnail/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -839,7 +787,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.ProcreateViewer.Thumbnail;
PRODUCT_BUNDLE_IDENTIFIER = com.redstrate.SilicaViewer.Thumbnail;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
@ -909,7 +857,7 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
030F6FE92415C5E300A43F01 /* Build configuration list for PBXProject "ProcreateViewer" */ = {
030F6FE92415C5E300A43F01 /* Build configuration list for PBXProject "SilicaViewer" */ = {
isa = XCConfigurationList;
buildConfigurations = (
030F6FFF2415C5E400A43F01 /* Debug */,
@ -918,7 +866,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
030F70012415C5E400A43F01 /* Build configuration list for PBXNativeTarget "ProcreateViewer" */ = {
030F70012415C5E400A43F01 /* Build configuration list for PBXNativeTarget "SilicaViewer" */ = {
isa = XCConfigurationList;
buildConfigurations = (
030F70022415C5E400A43F01 /* Debug */,

View file

@ -3,61 +3,61 @@
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "untitled-16.png",
"filename" : "icon-16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "untitled-32.png",
"filename" : "icon-32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "untitled-32.png",
"filename" : "icon-32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "untitled-64.png",
"filename" : "icon-64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "untitled-128.png",
"filename" : "icon-128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "untitled-256.png",
"filename" : "icon-256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "untitled-256.png",
"filename" : "icon-256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "untitled-512.png",
"filename" : "icon-512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "untitled-512.png",
"filename" : "icon-512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "untitled-1024.png",
"filename" : "icon-1024.png",
"scale" : "2x"
}
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View file

@ -1,9 +1,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>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.AVKitIBPlugin" version="17156"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17156"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -13,11 +12,11 @@
<application id="hnw-xV-0zn" sceneMemberID="viewController">
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<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"/>
<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>
<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"/>
<connections>
<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"/>
</menuItem>
<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>
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
</connections>
@ -49,7 +48,7 @@
</connections>
</menuItem>
<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>
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
</connections>
@ -79,7 +78,13 @@
</items>
</menu>
</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">
<connections>
<action selector="performClose:" target="Ady-hI-5gd" id="HmO-Ls-i7Q"/>
@ -143,7 +148,7 @@
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="Procreate Viewer Help" keyEquivalent="?" id="FKE-Sm-Kum">
<menuItem title="Silica Viewer Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
</connections>
@ -157,7 +162,7 @@
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</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="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<userDefaultsController representsSharedInstance="YES" id="JdS-Pg-8N9"/>
@ -167,7 +172,7 @@
<!--Info View Controller-->
<scene sceneID="nJy-a4-E0d">
<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">
<rect key="frame" x="0.0" y="0.0" width="480" height="109"/>
<autoresizingMask key="autoresizingMask"/>
@ -253,25 +258,18 @@ DQ
<!--Timelapse View Controller-->
<scene sceneID="6lP-oO-4ie">
<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">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<avPlayerView controlsStyle="minimal" videoGravity="resizeAspectFill" translatesAutoresizingMaskIntoConstraints="NO" id="zOe-qf-LSc">
<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>
<gestureRecognizers>
<clickGestureRecognizer delaysPrimaryMouseButtonEvents="YES" numberOfClicksRequired="1" id="I6z-cw-DZX">
<connections>
<action selector="clickAction:" target="2g9-GV-hNg" id="322-pp-TuC"/>
</connections>
</clickGestureRecognizer>
</gestureRecognizers>
</view>
<connections>
<outlet property="playerView" destination="zOe-qf-LSc" id="Iy7-O4-v4P"/>
</connections>
</viewController>
<customObject id="LO0-g9-QLL" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
@ -301,7 +299,7 @@ DQ
<!--View Controller-->
<scene sceneID="hIz-AP-VOD">
<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">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>

View file

@ -1,28 +1,15 @@
import Cocoa
import ZIPFoundation
import CoreFoundation
import Accelerate
import CoreMedia
struct SilicaChunk {
var x: Int = 0
var y: Int = 0
var image: CGImage?
}
struct SilicaLayerData {
var blendMode: Int = 0
var extendedBlend: Int = 0
var chunks: [SilicaChunk] = []
var opacity: Double = 1.0
var hidden: Bool = false
var image: NSImage = NSImage()
}
struct SilicaLayer {
var name: String = ""
var data: SilicaLayerData = SilicaLayerData()
var mask: SilicaLayerData?
var clipped: Bool = false
var chunks: [SilicaChunk] = []
}
struct SilicaDocument {
@ -31,31 +18,11 @@ struct SilicaDocument {
var orientation: Int = 0
var flippedHorizontally: 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 height: Int = 0
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 {
@ -87,10 +54,6 @@ class Document: NSDocument {
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.
*/
@ -110,7 +73,7 @@ class Document: NSDocument {
/*
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 height: Int = info.tileSize
@ -149,138 +112,16 @@ class Document: NSDocument {
}
}
func getChunkRect(_ chunk: SilicaChunk) -> NSRect {
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? {
func parseSilicaLayer(archive: Archive, dict: NSDictionary) {
let objectsArray = self.dict?["$objects"] as! NSArray
if getDocumentClassName(dict: dict) == LayerClassName {
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 UUIDClassID = getClassID(id: UUIDKey)
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] = []
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 queue = DispatchQueue(label: "imageWork")
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
}
@ -305,10 +148,8 @@ class Document: NSDocument {
return
}
let (width, height) = getTileSize(x, y)
let numChannels = isMask ? 1 : 4
let byteSize = width * height * numChannels
let (width, height) = getTileSize(x: x, y: y)
let byteSize = width * height * 4
let uncompressedMemory = UnsafeMutablePointer<UInt8>.allocate(capacity: byteSize)
@ -325,38 +166,28 @@ class Document: NSDocument {
let imageData = Data(bytes: uncompressedMemory, count: byteSize)
let render: CGColorRenderingIntent = .defaultIntent
let rgbColorSpace = isMask ? CGColorSpaceCreateDeviceGray() : info.colorSpace
let bitmapInfo = CGBitmapInfo(rawValue: (isMask ? CGImageAlphaInfo.none : CGImageAlphaInfo.premultipliedLast).rawValue).union(.byteOrder32Big)
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: 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
}
queue.async(group: dispatchGroup) {
layer.data.chunks[i].image = cgimage
layer.data.chunks[i].x = x
layer.data.chunks[i].y = y
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()
return 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
info.layers.append(layer)
}
}
@ -370,70 +201,19 @@ class Document: NSDocument {
info.flippedHorizontally = (dict[FlippedHorizontallyKey] 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 sizeClassID = getClassID(id: sizeClassKey)
let sizeString = objectsArray[sizeClassID] as! String
let (width, height) = parsePairString(sizeString)!
info.width = width
info.height = height
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))) + 1 // TODO: lol why
rows = Int(ceil(Float(info.height) / Float(info.tileSize)))
if info.width % info.tileSize != 0 {
remainderWidth = (columns * info.tileSize) - info.width
@ -449,14 +229,11 @@ class Document: NSDocument {
let array = layersClass["NS.objects"] as! NSArray
//dump(dict, indent: 5)
for object in array {
let layerClassID = getClassID(id: object)
let layerClass = objectsArray[layerClassID] as! NSDictionary
guard let layer = parseSilicaLayer(archive: archive, dict: layerClass, isMask: false) else { return }
info.layers.append(layer)
parseSilicaLayer(archive: archive, dict: layerClass)
}
}
}
@ -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 {
self.data = data
guard let archive = Archive(data: data, accessMode: Archive.AccessMode.read) else {
throwError(.invalid)
guard let archive = Archive(data: data, accessMode: Archive.AccessMode.read) else {
return
}
guard let documentEntry = archive[DocumentArchivePath] else {
throwError(.invalid)
return
}
guard let documentData = readData(archive: archive, entry: documentEntry) else {
throwError(.invalid)
return
}
var plistFormat = PropertyListSerialization.PropertyListFormat.binary
guard let propertyList = try? PropertyListSerialization.propertyList(from: documentData, options: [], format: &plistFormat) else {
throwError(.invalid)
return
}
parseDocument(archive: archive, dict: propertyList as! NSDictionary)
}
func makeComposite() -> NSImage? {
// create the final composite output image
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).union(.byteOrder32Big)
func makeComposite() -> NSImage {
var image = NSImage(size: NSSize(width: info.width, height: info.height))
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)
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
let color = NSColor.white
color.drawSwatch(in: NSRect(origin: .zero, size: image.size))
for layer in info.layers.reversed() {
if !layer.data.hidden {
// start by creating a new layer composite image, needed for image masking
let layerContext = CGContext(data: nil, width: info.width, height: info.height, bitsPerComponent: 8, bytesPerRow: info.width * 4, space: info.colorSpace, bitmapInfo: bitmapInfo.rawValue)
for chunk in layer.chunks {
let x = chunk.x
var y = chunk.y
layerContext?.clear(info.cgRect)
let (width, height) = getTileSize(x: x, y: y)
var maskContext: CGContext?
let kernel = getBlendKernel(layer)
if layer.mask != nil {
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))
}
if y == rows {
y = 0
}
for chunk in layer.data.chunks {
layerContext?.setAlpha(CGFloat(layer.data.opacity))
layerContext?.setBlendMode(.normal)
let rect = NSRect(x: info.tileSize * x, y: info.height - (info.tileSize * y), width: width, height: height)
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)!
chunk.image.draw(in: rect)
}
}
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)
image.unlockFocus()
if info.orientation == 3 {
image = image.imageRotatedByDegreess(degrees: 90)
@ -711,35 +408,3 @@ public extension NSImage {
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()
}
}

View file

@ -14,9 +14,11 @@
<key>CFBundleTypeIconFile</key>
<string></string>
<key>CFBundleTypeName</key>
<string></string>
<string>com.proceate</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>com.procreate</string>
@ -42,13 +44,13 @@
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Joshua Goins. All rights reserved.</string>
<string>Copyright © 2021 Joshua Goins. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>

View 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;
}

View 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()
}
}

View file

@ -7,7 +7,7 @@ class ViewController: NSViewController {
override func viewWillAppear() {
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?) {

View file

@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSExtension</key>