mirror of
https://github.com/tsolomko/SWCompression.git
synced 2026-06-23 14:56:41 +00:00
307 lines
11 KiB
Swift
307 lines
11 KiB
Swift
// Copyright (c) 2017 Timofey Solomko
|
|
// Licensed under MIT License
|
|
//
|
|
// See LICENSE for license information
|
|
|
|
import Foundation
|
|
|
|
public extension BZip2 {
|
|
|
|
public static func compress(data: Data) -> Data {
|
|
var out = [UInt8]()
|
|
|
|
// Run Length Encoding
|
|
var index = 0
|
|
while index < data.count {
|
|
var runLength = 1
|
|
while index + 1 < data.count && data[index] == data[index + 1] && runLength <= 255 {
|
|
runLength += 1
|
|
index += 1
|
|
}
|
|
if runLength >= 4 {
|
|
for _ in 0..<4 {
|
|
out.append(data[index])
|
|
}
|
|
out.append(runLength.toUInt8() - 4)
|
|
} else {
|
|
for _ in 0..<runLength {
|
|
out.append(data[index])
|
|
}
|
|
}
|
|
index += 1
|
|
}
|
|
|
|
// BWT
|
|
var pointer = 0
|
|
(out, pointer) = BurrowsWheeler.transform(bytes: out)
|
|
|
|
// Move to front
|
|
var usedBytes = Set(out).sorted()
|
|
for i in 0..<out.count {
|
|
let index = usedBytes.index(of: out[i])!
|
|
out[i] = index.toUInt8()
|
|
let oldByte = usedBytes.remove(at: index)
|
|
usedBytes.insert(oldByte, at: 0)
|
|
}
|
|
|
|
// RLE of MTF
|
|
var zeroRunLength = 0
|
|
var symbolOut = [Int]()
|
|
var maxSymbol = 1
|
|
for byte in out {
|
|
if byte == 0 {
|
|
zeroRunLength += 1
|
|
} else {
|
|
if zeroRunLength > 0 {
|
|
let digitsNumber = floor(log2(Double(zeroRunLength) + 1))
|
|
var remainder = zeroRunLength
|
|
for _ in 0..<Int(digitsNumber) {
|
|
let quotient = Int(ceil(Double(remainder) / 2) - 1)
|
|
let digit = remainder - quotient * 2
|
|
if digit == 1 {
|
|
symbolOut.append(0)
|
|
} else {
|
|
symbolOut.append(1)
|
|
}
|
|
remainder = quotient
|
|
}
|
|
zeroRunLength = 0
|
|
}
|
|
let newSymbol = byte.toInt() + 1
|
|
// We add one because, 1 is used as RUNB.
|
|
// We don't add two instead, because 0 is never encountered as separate symbol,
|
|
// without RUNA meaning.
|
|
symbolOut.append(newSymbol)
|
|
if newSymbol > maxSymbol {
|
|
maxSymbol = newSymbol
|
|
}
|
|
}
|
|
}
|
|
// In case last symbols were 0.
|
|
if zeroRunLength > 0 {
|
|
let digitsNumber = floor(log2(Double(zeroRunLength) + 1))
|
|
var remainder = zeroRunLength
|
|
for _ in 0..<Int(digitsNumber) {
|
|
let quotient = Int(ceil(Double(remainder) / 2) - 1)
|
|
let digit = remainder - quotient * 2
|
|
if digit == 1 {
|
|
symbolOut.append(0)
|
|
} else {
|
|
symbolOut.append(1)
|
|
}
|
|
remainder = quotient
|
|
}
|
|
}
|
|
// Add 'end of stream' symbol.
|
|
symbolOut.append(maxSymbol + 1)
|
|
|
|
// Huffman Coding
|
|
let bitWriter = BitWriter(bitOrder: .straight)
|
|
|
|
// First, we analyze data and create Huffman trees and selectors.
|
|
// Then we will perform encoding itself.
|
|
// These are separate stages because all information about trees is stored at the beginning of the block,
|
|
// and it is hard to modify it later.
|
|
var processed = 50
|
|
var tables = [EncodingHuffmanTree]()
|
|
var tablesLengths = [[Int]]()
|
|
var selectorsUsed = 0
|
|
var selectorList = [Int]()
|
|
|
|
// Algorithm for code lengths calculations skips any symbol with frequency equal to 0.
|
|
// Unfortunately, we need such unused symbols in tree creation, so we cannot skip them.
|
|
// To prevent skipping, we set default value of 1 for every symbol's frequency.
|
|
var stats = Array(repeating: 1, count: maxSymbol + 2)
|
|
|
|
for i in 0..<symbolOut.count {
|
|
let symbol = symbolOut[i]
|
|
stats[symbol] += 1
|
|
processed -= 1
|
|
if processed <= 0 || i == symbolOut.count - 1 {
|
|
processed = 50
|
|
// We need to calculate code lengths for our current stats.
|
|
let lengths = BZip2.lengths(from: stats)
|
|
// Using these code lengths we can create new Huffman tree, which we may use.
|
|
let table = EncodingHuffmanTree(lengths: lengths, bitWriter)
|
|
// Let's compute possible sizes for our stats using new tree and existing trees.
|
|
var minimumSize = Int.max
|
|
var minimumSelector = -1
|
|
for tableIndex in 0..<tables.count {
|
|
let bitSize = tables[tableIndex].bitSize(for: stats)
|
|
if bitSize < minimumSize {
|
|
minimumSize = bitSize
|
|
minimumSelector = tableIndex
|
|
}
|
|
}
|
|
if table.bitSize(for: stats) < minimumSize {
|
|
tables.append(table)
|
|
tablesLengths.append(lengths.sorted { $0.symbol < $1.symbol }.map { $0.codeLength })
|
|
selectorList.append(tables.count - 1)
|
|
selectorsUsed += 1
|
|
} else {
|
|
selectorList.append(minimumSelector)
|
|
selectorsUsed += 1
|
|
}
|
|
|
|
// Clear stats.
|
|
stats = Array(repeating: 1, count: maxSymbol + 2)
|
|
}
|
|
}
|
|
|
|
// Format requires at least two tables to be present.
|
|
// If we have only one, we add a duplicate of it.
|
|
if tables.count == 1 {
|
|
tables.append(tables[0])
|
|
tablesLengths.append(tablesLengths[0])
|
|
}
|
|
|
|
// Now, we perform encoding itself.
|
|
// But first, we need to create block header.
|
|
|
|
bitWriter.write(number: 0x425a, bitsCount: 16) // Magic number = 'BZ'.
|
|
bitWriter.write(number: 0x68, bitsCount: 8) // Version = 'h'.
|
|
bitWriter.write(number: 0x39, bitsCount: 8) // Block size. We use '9' = 900 KB for now.
|
|
bitWriter.write(number: 0x314159265359, bitsCount: 48) // Block magic number.
|
|
bitWriter.write(number: CheckSums.bzip2CRC32(data).toInt(), bitsCount: 32) // Block crc32.
|
|
bitWriter.write(number: 0, bitsCount: 1) // "Randomized".
|
|
bitWriter.write(number: pointer, bitsCount: 24) // Original pointer (from BWT).
|
|
|
|
var usedMap = Array(repeating: UInt8(0), count: 16)
|
|
for usedByte in usedBytes {
|
|
if 0 <= usedByte && usedByte < 16 {
|
|
usedMap[0] = 1
|
|
} else if 16 <= usedByte && usedByte < 32 {
|
|
usedMap[1] = 1
|
|
} else if 32 <= usedByte && usedByte < 48 {
|
|
usedMap[2] = 1
|
|
} else if 48 <= usedByte && usedByte < 64 {
|
|
usedMap[3] = 1
|
|
} else if 64 <= usedByte && usedByte < 80 {
|
|
usedMap[4] = 1
|
|
} else if 80 <= usedByte && usedByte < 96 {
|
|
usedMap[5] = 1
|
|
} else if 96 <= usedByte && usedByte < 112 {
|
|
usedMap[6] = 1
|
|
} else if 112 <= usedByte && usedByte < 128 {
|
|
usedMap[7] = 1
|
|
} else if 128 <= usedByte && usedByte < 144 {
|
|
usedMap[8] = 1
|
|
} else if 144 <= usedByte && usedByte < 160 {
|
|
usedMap[9] = 1
|
|
} else if 160 <= usedByte && usedByte < 176 {
|
|
usedMap[10] = 1
|
|
} else if 176 <= usedByte && usedByte < 192 {
|
|
usedMap[11] = 1
|
|
} else if 192 <= usedByte && usedByte < 208 {
|
|
usedMap[12] = 1
|
|
} else if 208 <= usedByte && usedByte < 224 {
|
|
usedMap[13] = 1
|
|
} else if 224 <= usedByte && usedByte < 240 {
|
|
usedMap[14] = 1
|
|
} else if 240 <= usedByte && usedByte <= 255 {
|
|
usedMap[15] = 1
|
|
} else {
|
|
fatalError("Incorrect used byte.")
|
|
}
|
|
|
|
}
|
|
bitWriter.write(bits: usedMap)
|
|
|
|
var usedBytesIndex = 0
|
|
usedBytes.sort()
|
|
for i in 0..<16 {
|
|
guard usedMap[i] == 1 else { continue }
|
|
for j in 0..<16 {
|
|
if usedBytesIndex < usedBytes.count && i * 16 + j == usedBytes[usedBytesIndex].toInt() {
|
|
bitWriter.write(bit: 1)
|
|
usedBytesIndex += 1
|
|
} else {
|
|
bitWriter.write(bit: 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
bitWriter.write(number: tables.count, bitsCount: 3)
|
|
bitWriter.write(number: selectorsUsed, bitsCount: 15)
|
|
|
|
let mtfSelectorList = mtf(selectorList)
|
|
for selector in mtfSelectorList {
|
|
if selector == 0 {
|
|
bitWriter.write(bit: 0)
|
|
} else if selector == 1 {
|
|
bitWriter.write(bits: [1, 0])
|
|
} else if selector == 2 {
|
|
bitWriter.write(bits: [1, 1, 0])
|
|
} else if selector == 3 {
|
|
bitWriter.write(bits: [1, 1, 1, 0])
|
|
} else if selector == 4 {
|
|
bitWriter.write(bits: [1, 1, 1, 1, 0])
|
|
} else if selector == 5 {
|
|
bitWriter.write(bits: [1, 1, 1, 1, 1, 0])
|
|
} else {
|
|
fatalError("Incorrect selector.")
|
|
}
|
|
}
|
|
|
|
// Delta bit lengths.
|
|
for lengths in tablesLengths {
|
|
// Starting length.
|
|
var currentLength = lengths[0]
|
|
bitWriter.write(number: currentLength, bitsCount: 5)
|
|
for length in lengths {
|
|
while currentLength != length {
|
|
bitWriter.write(bit: 1) // Alter length.
|
|
if currentLength > length {
|
|
bitWriter.write(bit: 1) // Decrement length.
|
|
currentLength -= 1
|
|
} else {
|
|
bitWriter.write(bit: 0) // Increment length.
|
|
currentLength += 1
|
|
}
|
|
}
|
|
bitWriter.write(bit: 0)
|
|
}
|
|
}
|
|
|
|
// Contents.
|
|
var encoded = 0
|
|
var selectorPointer = 0
|
|
var t: EncodingHuffmanTree?
|
|
for symbol in symbolOut {
|
|
encoded -= 1
|
|
if encoded <= 0 {
|
|
encoded = 50
|
|
if selectorPointer == selectorList.count {
|
|
fatalError("Incorrect selector.")
|
|
} else if selectorPointer < selectorList.count {
|
|
t = tables[selectorList[selectorPointer]]
|
|
selectorPointer += 1
|
|
}
|
|
}
|
|
t?.code(symbol: symbol)
|
|
}
|
|
|
|
// EOS magic number.
|
|
bitWriter.write(number: 0x177245385090, bitsCount: 48)
|
|
// Total crc32.
|
|
bitWriter.write(number: CheckSums.bzip2CRC32(data).toInt(), bitsCount: 32)
|
|
|
|
bitWriter.finish()
|
|
|
|
return Data(bytes: bitWriter.buffer)
|
|
}
|
|
|
|
static func mtf(_ array: [Int]) -> [Int] {
|
|
var result = [Int]()
|
|
var mtf = Array(0..<array.count)
|
|
for i in 0..<array.count {
|
|
let index = mtf.index(of: array[i])!
|
|
result.append(index)
|
|
let old = mtf.remove(at: index)
|
|
mtf.insert(old, at: 0)
|
|
}
|
|
return result
|
|
}
|
|
|
|
}
|