Last active
April 27, 2022 12:08
-
-
Save mike-ferenduros/f19d346bda656d347c7c1df3762bbfe9 to your computer and use it in GitHub Desktop.
Decode Cardboard calibration data retrieved from a Cardboard QR code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Cardboard.swift | |
// | |
// Created by Michael Ferenduros on 27/09/2016. | |
// Copyright © 2016 Mike Ferenduros. All rights reserved. | |
// | |
import Foundation | |
private class ProtoReader { | |
struct Err : Error { } | |
enum WireFormat: Int { case varint=0, f64=1, blob=2, f32=5 } | |
let data: Data | |
var cursor = 0 | |
var eof: Bool { return cursor >= data.count } | |
init(data: Data) { | |
self.data = data | |
} | |
func skip(count: UInt64) throws { | |
cursor += Int(count) | |
guard cursor <= data.count else { throw Err() } | |
} | |
func skip(wire: WireFormat) throws { | |
switch wire { | |
case .varint: _ = try getVarint() | |
case .f32: try skip(count: 4) | |
case .f64: try skip(count: 8) | |
case .blob: try skip(count: try getVarint()) | |
} | |
} | |
func getByte() throws -> UInt8 { | |
guard cursor < data.count else { throw Err() } | |
cursor += 1 | |
return data[cursor-1] | |
} | |
func getVarint() throws -> UInt64 { | |
var result: UInt64 = 0 | |
var shift = 0 | |
while true { | |
let b = try getByte() | |
result |= UInt64(b & 0x7F) << UInt64(shift) | |
if (b & 0x80) == 0 { break } | |
shift += 7 | |
} | |
return result | |
} | |
func getTag() throws -> (Int,WireFormat) { | |
let tag = try getVarint() | |
guard let wire = WireFormat(rawValue: Int(tag & 7)) else { throw Err() } | |
let field = Int(tag >> 3) | |
return (field, wire) | |
} | |
func getBlob() throws -> Data { | |
let count = Int(try getVarint()) | |
let result = data.subdata(in: cursor ..< cursor+count) | |
cursor += count | |
return result | |
} | |
func get32() throws -> UInt32 { | |
var result: UInt32 = 0 | |
for i in 0 ..< 4 { | |
result |= UInt32(try getByte()) << UInt32(i*8) | |
} | |
return result | |
} | |
func getFloat() throws -> Float { | |
let bits = try get32() | |
return Float(bitPattern: bits) | |
} | |
func getString() throws -> String { | |
let data = try getBlob() | |
guard let string = String(data: data, encoding: .utf8) else { throw Err() } | |
return string | |
} | |
} | |
public struct Cardboard { | |
public enum Alignment: Int { case bottom=0, center, top } | |
public enum Button: Int { case none=0, magnet, touch, indirectTouch } | |
public private(set) var vendor: String? | |
public private(set) var model: String? | |
public private(set) var screenToLens: Float? | |
public private(set) var interLens: Float? | |
public private(set) var trayToLens: Float? | |
public private(set) var alignment: Alignment? | |
public private(set) var radialDistortion: [Float]? | |
public private(set) var hasMagnet: Bool? | |
public private(set) var button: Button? | |
///The protocol-buffer data used to initialize this struct. | |
public let encoded: Data? | |
///Initialize using data encoded in redirection URL's `p` parameter | |
///NB: Does NOT handle the pre-redirection URL (the goo.gl/[whatever] encoded in the cardboard's QR code). | |
///You have to follow the QR code's URL, get the URL it redirects to, and pass *that* into this initializer. | |
public init?(url: URL) { | |
guard let query = URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems else { return nil } | |
guard let p = query.first(where: { $0.name == "p" })?.value else { return nil } | |
let base64 = p.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/") | |
switch base64.characters.count % 4 { | |
case 0: self.init(base64: base64) | |
case 2: self.init(base64: base64.appending("==")) | |
case 3: self.init(base64: base64.appending("=")) | |
default: return nil | |
} | |
} | |
///Initialize from base64-encoded protobuf data. | |
public init?(base64: String) { | |
guard let data = Data(base64Encoded: base64) else { return nil } | |
self.init(encoded: data) | |
} | |
///Initialize from protobuf data | |
public init?(encoded protobuf: Data) { | |
self.encoded = protobuf | |
let reader = ProtoReader(data: protobuf) | |
do { | |
while !reader.eof { | |
switch try reader.getTag() { | |
case (1, .blob): try vendor = reader.getString() | |
case (2, .blob): try model = reader.getString() | |
case (3, .f32): try screenToLens = reader.getFloat() | |
case (4, .f32): try interLens = reader.getFloat() | |
case (6, .f32): try trayToLens = reader.getFloat() | |
case (10, .varint): try hasMagnet = reader.getVarint() != 0 | |
case (11, .varint): try alignment = Alignment(rawValue: Int(reader.getVarint())) | |
case (12, .varint): try button = Button(rawValue: Int(reader.getVarint())) | |
case (7, .blob): | |
radialDistortion = radialDistortion ?? [] | |
let blob = try reader.getBlob() | |
let subreader = ProtoReader(data: blob) | |
while !subreader.eof { | |
let d = try subreader.getFloat() | |
radialDistortion!.append(d) | |
} | |
case (_, let format): | |
try reader.skip(wire: format) | |
} | |
} | |
} catch { return nil } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment