Skip to content

Instantly share code, notes, and snippets.

@mike-ferenduros
Last active April 27, 2022 12:08
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mike-ferenduros/f19d346bda656d347c7c1df3762bbfe9 to your computer and use it in GitHub Desktop.
Save mike-ferenduros/f19d346bda656d347c7c1df3762bbfe9 to your computer and use it in GitHub Desktop.
Decode Cardboard calibration data retrieved from a Cardboard QR code.
//
// 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