added RCData, refactored bit coding

This commit is contained in:
Haik Aslanyan
2020-04-08 13:40:58 +04:00
parent 34acd11324
commit 866dbaabeb
9 changed files with 162 additions and 106 deletions
@@ -22,22 +22,10 @@
import Foundation
final class RCBitGroup {
var bit: RCBit
var count: Int
var offset: Int
init(bit: RCBit, count: Int, offset: Int) {
self.bit = bit
self.count = count
self.offset = offset
extension Array {
func chunked(into size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map {
Array(self[$0 ..< Swift.min($0 + size, count)])
}
}
}
extension RCBitGroup: CustomDebugStringConvertible {
var debugDescription: String {
"bit: \(bit), count: \(count), offset: \(offset) \n"
}
}
-10
View File
@@ -27,10 +27,6 @@ enum RCBit: Int {
case zero
case one
var boolValue: Bool {
return self == .zero ? false : true
}
init?(_ character: Character) {
switch character {
case "0": self = .zero
@@ -39,9 +35,3 @@ enum RCBit: Int {
}
}
}
extension RCBit: CustomDebugStringConvertible {
var debugDescription: String {
String(rawValue)
}
}
+35 -34
View File
@@ -29,61 +29,62 @@ final class RCBitCoder {
init(configuration: RCCoderConfiguration) {
self.configuration = configuration
}
func encode(message: String) throws -> [[[RCBitGroup]]] {
guard message.trimmingCharacters(in: configuration.characterSet).isEmpty else {
}
extension RCBitCoder {
func encode(message: String) throws -> RCData {
var specialCharacters = RCConstants.emptyCharacters
specialCharacters.append(RCConstants.startingCharacter)
guard message.map({$0}).allSatisfy({configuration.characters.contains($0) && !specialCharacters.contains($0)}) else {
throw RCError.invalidCharacter
}
guard message.count <= configuration.maxMessageCount else {
throw RCError.longText
}
let dataBytes = message.map({configuration.symbols.firstIndex(of: $0)!})
let emptyIndexes = configuration.emptySymbolsIndex()
var randomBytes = (0..<configuration.maxMessageCount).map({_ in emptyIndexes[Int.random(in: 0..<emptyIndexes.count)]})
randomBytes.insert(contentsOf: dataBytes, at: 0)
randomBytes = Array(randomBytes.prefix(configuration.maxMessageCount))
let encodedBits = randomBytes.map { byte -> [RCBit] in
var dataBytes = message.map({configuration.characters.firstIndex(of: $0)!})
dataBytes.insert(configuration.characters.firstIndex(of: RCConstants.startingCharacter)!, at: 0)
let emptyIndexes = RCConstants.emptyCharacters.map({configuration.characters.firstIndex(of: $0)!})
let randomBytes = (0..<configuration.maxMessageCount).map({_ in emptyIndexes[Int.random(in: 0..<emptyIndexes.count)]})
dataBytes.append(contentsOf: randomBytes)
dataBytes = Array(dataBytes.prefix(configuration.maxMessageCount))
let encodedBits = dataBytes.map { byte -> [RCBit] in
var stringValue = String(repeating: "0", count: configuration.bitesPerSymbol)
stringValue += String(byte, radix: 2)
return stringValue.suffix(configuration.bitesPerSymbol).compactMap({RCBit($0)})
}.flatMap({$0})
var totalBits = [RCBit](repeating: .zero, count: RCConstants.maxBites)
var totalBits = [RCBit](repeating: .zero, count: RCConstants.maxBitesPerSection * 3)
totalBits.insert(contentsOf: encodedBits, at: 0)
totalBits = Array(totalBits.prefix(RCConstants.maxBites))
let bitChunks = [RCConstants.level1BitesCount, RCConstants.level2BitesCount, RCConstants.level3BitesCount].map { count in
return (0...3).map { _ -> [RCBit] in // 4 groups
let bitChunk = Array(totalBits.prefix(count))
totalBits = Array(totalBits.dropFirst(count))
return bitChunk
}
}
let bitGroups = zip(bitChunks[0], zip(bitChunks[1], bitChunks[2])).map {[$0.0, $0.1.0, $0.1.1]}.map { group in
group.map { row -> [RCBitGroup] in
var bitGroupRow = [RCBitGroup(bit: row[0], count: 0, offset: 0)]
row.enumerated().forEach { value in
bitGroupRow.last!.bit == value.element ? bitGroupRow.last!.count += 1 : bitGroupRow.append(RCBitGroup(bit: value.element, count: 1, offset: value.offset))
}
return bitGroupRow
}
}
return bitGroups
totalBits = Array(totalBits.prefix(RCConstants.maxBitesPerSection * 3))
//reed solomon
return RCData(totalBits)
}
func decode(_ bits: [RCBit]) throws -> String {
guard bits.contains(.one) else { return "" }
let emptyIndexes = configuration.emptySymbolsIndex()
let bitChunks = stride(from: 0, to: bits.count, by: configuration.bitesPerSymbol).map {
//decode bits to indexes
let bytes = stride(from: 0, to: bits.count, by: configuration.bitesPerSymbol).map {
Array(bits[$0 ..< min($0 + configuration.bitesPerSymbol, bits.count)])
}
var indexes = bitChunks.map({ bits in
var indexes = bytes.map({ bits in
return bits.reduce(0) { accumulated, current in
accumulated << 1 | current.rawValue
}
})
indexes = Array(indexes.prefix(configuration.maxMessageCount)).filter({!emptyIndexes.contains($0)})
guard (indexes.max() ?? 0) <= configuration.symbols.count else {
var sectionIndexes = indexes.chunked(into: 4)
let startingIndex = configuration.characters.firstIndex(of: RCConstants.startingCharacter)!
guard let firstSectionIndex = sectionIndexes.firstIndex(where: {$0.first == startingIndex}) else {
throw RCError.decoding
}
let previousSections = Array(sectionIndexes.prefix(firstSectionIndex))
sectionIndexes = Array(sectionIndexes.dropFirst(firstSectionIndex))
sectionIndexes.append(contentsOf: previousSections)
//reed solomon check
let emptyIndexes = RCConstants.emptyCharacters.map({configuration.characters.firstIndex(of: $0)!})
indexes = Array(sectionIndexes.flatMap({$0}).prefix(configuration.maxMessageCount)).filter({!emptyIndexes.contains($0)})
guard (indexes.max() ?? 0) <= configuration.characters.count else {
throw RCError.wrongConfiguration
}
return String(indexes.compactMap({configuration.symbols[$0]}))
return String(indexes.compactMap({configuration.characters[$0]}))
}
}
+14 -5
View File
@@ -23,14 +23,23 @@
import CoreGraphics
struct RCConstants {
static let maxBites = 276
static let level1BitesCount = 23
static let level2BitesCount = 23
static let level3BitesCount = 23
static let maxBitesPerSection = 276
static let topLevelBitesCount = 23
static let middleLevelBitesCount = 23
static let bottomLevelBitesCount = 23
static let imageScale: CGFloat = 0.8
static let dotSizeScale: CGFloat = 0.08
static let dotPatterns: [CGFloat] = [6, 4, 2]
static let dotPointRange = (Float(1.6)...Float(2.2))
static let pixelThreshold = 180
static let emptySymbols: [Character] = ["\u{0540}", "\u{0531}"]
static let emptyCharacters: [Character] = ["\u{0540}", "\u{0531}"]
static let startingCharacter: Character = "\u{058D}"
static let level1BitesCount = 23
static let level2BitesCount = 23
static let level3BitesCount = 23
}
+76
View File
@@ -0,0 +1,76 @@
// MIT License
// Copyright (c) 2020 Haik Aslanyan
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
struct RCBitSection {
var topLevel = [RCBit]()
var middleLevel = [RCBit]()
var bottomLevel = [RCBit]()
}
struct RCData {
var firstShard = RCBitSection()
var secondShard = RCBitSection()
var thirdShard = RCBitSection()
var parity = RCBitSection()
init(_ bits: [RCBit]) {
let sections = bits.chunked(into: 4).map { sectionBits -> RCBitSection in
var data = sectionBits
var section = RCBitSection()
section.topLevel = Array(data.prefix(RCConstants.topLevelBitesCount))
data = Array(data.dropFirst(RCConstants.topLevelBitesCount))
section.middleLevel = Array(data.prefix(RCConstants.middleLevelBitesCount))
data = Array(data.dropFirst(RCConstants.middleLevelBitesCount))
section.bottomLevel = data
return section
}
firstShard = sections[0]
secondShard = sections[1]
thirdShard = sections[2]
parity = sections[3]
}
}
final class RCBitGroup1 {
var bit: RCBit
var count: Int
var offset: Int
init(bit: RCBit, count: Int, offset: Int) {
self.bit = bit
self.count = count
self.offset = offset
}
}
extension RCBitGroup1: CustomDebugStringConvertible {
var debugDescription: String {
"bit: \(bit), count: \(count), offset: \(offset) \n"
}
}
+1 -6
View File
@@ -230,16 +230,11 @@ extension RCImageDecoder {
extension RCImageDecoder {
struct RCPixelPattern: CustomStringConvertible {
struct RCPixelPattern {
let bit: RCBit
let x: Int
let y: Int
var count: Float
var description: String {
"\(bit.debugDescription), start: \(y) \(x), count: \(count)"
}
}
enum Side: CaseIterable {
+3 -3
View File
@@ -24,7 +24,7 @@ import UIKit
final class RCImageEncoder {
func encode(_ image: RCImage, bits: [[[RCBitGroup]]]) -> UIImage {
func encode(_ image: RCImage, bits: [[[RCBitGroup1]]]) -> UIImage {
let rect = CGRect(origin: .zero, size: CGSize(width: image.size + image.contentInsets.left + image.contentInsets.right, height: image.size + image.contentInsets.top + image.contentInsets.bottom))
let renderer = UIGraphicsImageRenderer(bounds: rect, format: .default())
let renderedImage = renderer.image { context in
@@ -97,7 +97,7 @@ private extension RCImageEncoder {
attachmentImage.draw(in: scaledImageRect, blendMode: .normal, alpha: 1)
}
func drawMessage(image: RCImage, group: [[RCBitGroup]], angle: CGFloat, path: UIBezierPath) {
func drawMessage(image: RCImage, group: [[RCBitGroup1]], angle: CGFloat, path: UIBezierPath) {
guard !image.message.isEmpty else { return }
let lineWidth = image.size * RCConstants.dotSizeScale / 11 * 2 //number of lines including spaces
let mainRadius = (image.size - lineWidth) / 2
@@ -110,7 +110,7 @@ private extension RCImageEncoder {
let rowBitsGroup = $0.1
let radius = $0.0
rowBitsGroup.forEach { bit in
guard bit.bit.boolValue else { return }
//guard bit.bit.boolValue else { return }
let startPosition = startAngle + distancePerBit * CGFloat(bit.offset)
let endPosition = startPosition + distancePerBit * CGFloat(bit.count)
let linePath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startPosition, endAngle: endPosition, clockwise: true)
+3 -3
View File
@@ -37,8 +37,8 @@ public final class RCCoder {
public extension RCCoder {
func encode(_ image: RCImage) throws -> UIImage {
let bits = try bitCoder.encode(message: image.message)
let image = imageEncoder.encode(image, bits: bits)
return image
// let image = imageEncoder.encode(image, bits: bits)
return UIImage()
}
func decode(_ image: UIImage) throws -> String {
@@ -49,7 +49,7 @@ public extension RCCoder {
}
func validate(_ text: String) -> Bool {
return text.trimmingCharacters(in: configuration.characterSet).isEmpty && text.count <= configuration.maxMessageCount
configuration.validate(text)
}
}
+25 -28
View File
@@ -22,51 +22,48 @@
import Foundation
public final class RCCoderConfiguration {
public struct RCCoderConfiguration {
public let version: Version
public let maxMessageCount: Int
public let symbols: [Character]
public let bitesPerSymbol: Int
internal let emptySymbols: [Character]
internal let characterSet: CharacterSet
public let characters: [Character]
public init(symbols: String, shouldFillEmptySpace: Bool = true) {
self.characterSet = CharacterSet(charactersIn: symbols.map({String($0)}).reduce("", +))
var symbolsArray = symbols.map({$0})
if shouldFillEmptySpace {
self.emptySymbols = RCConstants.emptySymbols
symbolsArray.insert(emptySymbols[0], at: symbolsArray.count / 3)
symbolsArray.insert(emptySymbols[1], at: symbolsArray.count / 3 * 2)
} else {
self.emptySymbols = [RCConstants.emptySymbols[0]]
symbolsArray.insert(emptySymbols[0], at: 0)
}
self.symbols = symbolsArray
self.bitesPerSymbol = String(symbols.count, radix: 2).count
self.maxMessageCount = RCConstants.maxBites / bitesPerSymbol
public init(version: Version = .v1, characters: String) {
self.version = version
var charactersArray = characters.map({$0})
charactersArray.append(RCConstants.startingCharacter)
charactersArray.append(contentsOf: RCConstants.emptyCharacters)
self.characters = charactersArray
self.bitesPerSymbol = String(charactersArray.count - 1, radix: 2).count
self.maxMessageCount = RCConstants.maxBitesPerSection * 3 / bitesPerSymbol
}
func indexOf(symbol: Character) -> Int {
return symbols.firstIndex(of: symbol)!
}
func emptySymbolsIndex() -> [Int] {
return emptySymbols.map({symbols.firstIndex(of: $0)!})
func validate(_ text: String) -> Bool {
var specialCharacters = RCConstants.emptyCharacters
specialCharacters.append(RCConstants.startingCharacter)
return text.map({$0}).allSatisfy({characters.contains($0) && !specialCharacters.contains($0)}) && text.count <= maxMessageCount
}
public static var uuidConfiguration: RCCoderConfiguration {
return RCCoderConfiguration(symbols: "-ABCDEF0123456789")
return RCCoderConfiguration(characters: "-ABCDEF0123456789")
}
public static var numericConfiguration: RCCoderConfiguration {
return RCCoderConfiguration(symbols: ".,_0123456789")
return RCCoderConfiguration(characters: ".,_0123456789")
}
public static var shortConfiguration: RCCoderConfiguration {
return RCCoderConfiguration(symbols: " -abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
return RCCoderConfiguration(characters: " -abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
}
public static var defaultConfiguration: RCCoderConfiguration {
return RCCoderConfiguration(symbols: ##"! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"##)
return RCCoderConfiguration(characters: ##"! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"##)
}
}
public extension RCCoderConfiguration {
enum Version {
case v1
}
}