diff --git a/Doggie.xcodeproj/project.pbxproj b/Doggie.xcodeproj/project.pbxproj index 27e18f59f..6d7410eb8 100644 --- a/Doggie.xcodeproj/project.pbxproj +++ b/Doggie.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ 0A2107161E5BEEBB00F1E00E /* Shape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21070C1E5BEEBB00F1E00E /* Shape.swift */; }; 0A21F0251F061AE2009C4490 /* PixelBlender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21F0231F061AE2009C4490 /* PixelBlender.swift */; }; 0A2406741F38109900B0EC28 /* iccNamedColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2406731F38109900B0EC28 /* iccNamedColor.swift */; }; + 0A26A0921FA98A1B008BEADB /* CompressionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A26A0901FA98994008BEADB /* CompressionTest.swift */; }; 0A2A643F1E5C45EE000E21BB /* PDFParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2A643D1E5C45EE000E21BB /* PDFParser.swift */; }; 0A32D9991F28825F0027F384 /* Tensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A32D9971F28825F0027F384 /* Tensor.swift */; }; 0A36612C1EEA54AB00216A26 /* CGPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A36612A1EEA54AB00216A26 /* CGPath.swift */; }; @@ -263,6 +264,7 @@ 0A21070C1E5BEEBB00F1E00E /* Shape.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shape.swift; sourceTree = ""; }; 0A21F0231F061AE2009C4490 /* PixelBlender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelBlender.swift; sourceTree = ""; }; 0A2406731F38109900B0EC28 /* iccNamedColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iccNamedColor.swift; sourceTree = ""; }; + 0A26A0901FA98994008BEADB /* CompressionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompressionTest.swift; sourceTree = ""; }; 0A2A643D1E5C45EE000E21BB /* PDFParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PDFParser.swift; sourceTree = ""; }; 0A32D9971F28825F0027F384 /* Tensor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tensor.swift; sourceTree = ""; }; 0A36612A1EEA54AB00216A26 /* CGPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGPath.swift; sourceTree = ""; }; @@ -598,10 +600,11 @@ 08AF77AE1A3A10510042491F /* Tests */ = { isa = PBXGroup; children = ( - 0A7A4BFF1F738D18005F44CD /* XMLTest.swift */, 0AFCA14F1D9E1F130034AB10 /* AtomicTest.swift */, + 0A26A0901FA98994008BEADB /* CompressionTest.swift */, 08BFF9B91B0715ED00EC88CB /* FourierTest.swift */, 0A8D98A21E53ED0F0050E112 /* ImageTest.swift */, + 0A7A4BFF1F738D18005F44CD /* XMLTest.swift */, ); name = Tests; path = Tests/DoggieTests; @@ -1240,6 +1243,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0A26A0921FA98A1B008BEADB /* CompressionTest.swift in Sources */, 0A92062D1F14A29E00AF5C49 /* ImageTest.swift in Sources */, 0A7A4C001F738D18005F44CD /* XMLTest.swift in Sources */, 0ACD21331EEBA55000C5811F /* AtomicTest.swift in Sources */, diff --git a/Sources/Doggie/Compression/CompressionCodec.swift b/Sources/Doggie/Compression/CompressionCodec.swift index 1c1a75002..56d8128d9 100644 --- a/Sources/Doggie/Compression/CompressionCodec.swift +++ b/Sources/Doggie/Compression/CompressionCodec.swift @@ -27,8 +27,21 @@ import Foundation public protocol CompressionCodec { - func process(data: Data) throws -> Data + func process(_ source: Data, _ output: inout C) throws where C.Element == UInt8 - func final() throws -> Data + func final(_ output: inout C) throws where C.Element == UInt8 } +extension CompressionCodec { + + @_inlineable + public func process(_ source: Data) throws -> Data { + + var result = Data(capacity: source.count) + + try self.process(source, &result) + try self.final(&result) + + return result + } +} diff --git a/Sources/Doggie/Compression/zlib.swift b/Sources/Doggie/Compression/zlib.swift index 89b20c859..5f824fc89 100644 --- a/Sources/Doggie/Compression/zlib.swift +++ b/Sources/Doggie/Compression/zlib.swift @@ -109,54 +109,44 @@ extension Deflate { extension Deflate { - private func _process(_ capacity: Int, _ flag: Int32) throws -> Data { + private func _process(_ flag: Int32, _ output: inout C) throws where C.Element == UInt8 { - var result = Data(capacity: capacity) + var buffer = [UInt8](repeating: 0, count: 4096) - stream.avail_out = 0 - - var written = 0 - - while stream.avail_in != 0 || stream.avail_out == 0 { + try buffer.withUnsafeMutableBufferPointer { buf in - result.count = written + 32 - - try result.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) in + repeat { - stream.next_out = bytes.advanced(by: written) - stream.avail_out = 32 + stream.next_out = buf.baseAddress + stream.avail_out = 4096 let status = deflate(&stream, flag) guard status == Z_OK || status == Z_BUF_ERROR || status == Z_STREAM_END else { throw Error(code: status, msg: stream.msg) } - written = result.count - Int(stream.avail_out) - } + output.append(contentsOf: buf.prefix(4096 - Int(stream.avail_out))) + + } while stream.avail_in != 0 || stream.avail_out == 0 } - - result.count -= Int(stream.avail_out) - stream.avail_out = 0 - - return result } - public func process(data: Data) throws -> Data { + public func process(_ source: Data, _ output: inout C) throws where C.Element == UInt8 { - return try data.withUnsafeBytes { (bytes: UnsafePointer) in + try source.withUnsafeBytes { (bytes: UnsafePointer) in stream.next_in = UnsafeMutablePointer(mutating: bytes) - stream.avail_in = uInt(data.count) + stream.avail_in = uInt(source.count) - return try _process(data.count, Z_NO_FLUSH) + try _process(Z_NO_FLUSH, &output) } } - public func final() throws -> Data { + public func final(_ output: inout C) throws where C.Element == UInt8 { stream.next_in = nil stream.avail_in = 0 - return try _process(0, Z_FINISH) + try _process(Z_FINISH, &output) } } @@ -204,53 +194,44 @@ extension Inflate { extension Inflate { - private func _process(_ capacity: Int, _ flag: Int32) throws -> Data { - - var result = Data(capacity: capacity) + private func _process(_ flag: Int32, _ output: inout C) throws where C.Element == UInt8 { - stream.avail_out = 0 + var buffer = [UInt8](repeating: 0, count: 4096) - var written = 0 - - while stream.avail_in != 0 || stream.avail_out == 0 { - - result.count = written + 32 + try buffer.withUnsafeMutableBufferPointer { buf in - try result.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) in + repeat { - stream.next_out = bytes.advanced(by: written) - stream.avail_out = 32 + stream.next_out = buf.baseAddress + stream.avail_out = 4096 let status = inflate(&stream, flag) guard status == Z_OK || status == Z_BUF_ERROR || status == Z_STREAM_END else { throw Error(code: status, msg: stream.msg) } - written = result.count - Int(stream.avail_out) - } + output.append(contentsOf: buf.prefix(4096 - Int(stream.avail_out))) + + } while stream.avail_in != 0 || stream.avail_out == 0 } - - result.count -= Int(stream.avail_out) - stream.avail_out = 0 - - return result } - public func process(data: Data) throws -> Data { + public func process(_ source: Data, _ output: inout C) throws where C.Element == UInt8 { - return try data.withUnsafeBytes { (bytes: UnsafePointer) in + try source.withUnsafeBytes { (bytes: UnsafePointer) in stream.next_in = UnsafeMutablePointer(mutating: bytes) - stream.avail_in = uInt(data.count) + stream.avail_in = uInt(source.count) - return try _process(data.count, Z_NO_FLUSH) + try _process(Z_NO_FLUSH, &output) } } - public func final() throws -> Data { + public func final(_ output: inout C) throws where C.Element == UInt8 { stream.next_in = nil stream.avail_in = 0 - return try _process(0, Z_FINISH) + try _process(Z_FINISH, &output) } } + diff --git a/Sources/Doggie/Font/Decoder/WOFFDecoder.swift b/Sources/Doggie/Font/Decoder/WOFFDecoder.swift index 6e8eafc62..4402d672c 100644 --- a/Sources/Doggie/Font/Decoder/WOFFDecoder.swift +++ b/Sources/Doggie/Font/Decoder/WOFFDecoder.swift @@ -42,8 +42,7 @@ struct WOFFDecoder : FontDecoder { if record.compLength == record.origLength { table[record.tag] = data.dropFirst(Int(record.offset)).prefix(Int(record.origLength)) } else { - let inflate = try Inflate() - table[record.tag] = try inflate.process(data: data.dropFirst(Int(record.offset)).prefix(Int(record.compLength))) + inflate.final() + table[record.tag] = try Inflate().process(data.dropFirst(Int(record.offset)).prefix(Int(record.compLength))) } } self.faces = [try SFNTFontFace(table: table)] diff --git a/Sources/Doggie/ImageCodec/Decoder/PNGDecoder.swift b/Sources/Doggie/ImageCodec/Decoder/PNGDecoder.swift index 36496d781..d2f8a5852 100644 --- a/Sources/Doggie/ImageCodec/Decoder/PNGDecoder.swift +++ b/Sources/Doggie/ImageCodec/Decoder/PNGDecoder.swift @@ -233,14 +233,7 @@ struct PNGDecoder : ImageRepDecoder { func decompress(data: Data, compression: UInt8) -> Data? { switch compression { - case 0: - do { - let inflate = try Inflate() - return try? inflate.process(data: data) + inflate.final() - } catch let error { - print(error) - return nil - } + case 0: return try? Inflate().process(data) default: return nil } } diff --git a/Sources/Doggie/ImageCodec/Encoder/PNGEncoder.swift b/Sources/Doggie/ImageCodec/Encoder/PNGEncoder.swift index ca77ed4cc..17f96f76e 100644 --- a/Sources/Doggie/ImageCodec/Encoder/PNGEncoder.swift +++ b/Sources/Doggie/ImageCodec/Encoder/PNGEncoder.swift @@ -75,7 +75,7 @@ struct PNGEncoder : ImageRepEncoder { private static func iCCP(_ colorSpace: ColorSpace) -> PNGChunk? { - if let iccData = colorSpace.iccData, let deflate = try? Deflate(), let data = try? deflate.process(data: iccData) + deflate.final() { + if let iccData = colorSpace.iccData, let data = try? Deflate(windowBits: 15).process(iccData) { var iccp = Data() @@ -179,7 +179,7 @@ struct PNGEncoder : ImageRepEncoder { body(&scanline, destination.pointee) } - compressed.append(try deflate.process(data: filter0(scanline, previous, bitsPerPixel))) + try deflate.process(filter0(scanline, previous, bitsPerPixel), &compressed) previous = scanline } @@ -206,7 +206,7 @@ struct PNGEncoder : ImageRepEncoder { buffer += 1 } - compressed.append(try deflate.process(data: filter0(scanline, previous, bitsPerPixel))) + try deflate.process(filter0(scanline, previous, bitsPerPixel), &compressed) previous = scanline } @@ -214,7 +214,7 @@ struct PNGEncoder : ImageRepEncoder { } } - compressed.append(try deflate.final()) + try deflate.final(&compressed) } catch { return nil diff --git a/Sources/Doggie/PDF/PDFEncoding/PDFFilter.swift b/Sources/Doggie/PDF/PDFEncoding/PDFFilter.swift index 7ec13a6ce..e54dfbfa3 100644 --- a/Sources/Doggie/PDF/PDFEncoding/PDFFilter.swift +++ b/Sources/Doggie/PDF/PDFEncoding/PDFFilter.swift @@ -36,9 +36,7 @@ fileprivate func _PDFFilterDecode(_ name: PDFDocument.Name, _ data: Data) throws case "ASCIIHexDecode": return try PDFASCIIHexDecode(data) case "ASCII85Decode": return try PDFASCII85Decode(data) case "LZWDecode": return try PDFLZWDecode(data) - case "FlateDecode": - let inflate = try Inflate() - return try inflate.process(data: data) + inflate.final() + case "FlateDecode": return try Inflate().process(data) case "RunLengthDecode": return try PDFRunLengthDecode(data) default: return data } diff --git a/Tests/DoggieTests/CompressionTest.swift b/Tests/DoggieTests/CompressionTest.swift new file mode 100644 index 000000000..c0f10e9ae --- /dev/null +++ b/Tests/DoggieTests/CompressionTest.swift @@ -0,0 +1,145 @@ +// +// CompressionTest.swift +// +// The MIT License +// Copyright (c) 2015 - 2017 Susan Cheng. All rights reserved. +// +// 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 +import Doggie +import XCTest + +class CompressionTest: XCTestCase { + + static let allTests = [ + ("testZlib", testZlib), + ("testGzip", testGzip), + ("testDeflatePerformance", testDeflatePerformance), + ("testInflatePerformance", testInflatePerformance), + ] + + let sample = ColorSpace.adobeRGB.iccData! + + override func setUp() { + super.setUp() + + } + + override func tearDown() { + + super.tearDown() + } + + func testZlib() { + + do { + + let deflate = try Deflate(windowBits: 15) + let inflate = try Inflate() + + let sample = self.sample + + let result = try inflate.process(deflate.process(self.sample)) + + XCTAssertEqual(result, sample) + + } catch let error { + + XCTFail("\(error)") + + } + + } + + func testGzip() { + + do { + + let deflate = try Deflate(windowBits: 15 + 16) + let inflate = try Inflate() + + let sample = self.sample + + let result = try inflate.process(deflate.process(self.sample)) + + XCTAssertEqual(result, sample) + + } catch let error { + + XCTFail("\(error)") + + } + + } + + func testDeflatePerformance() { + + let sample = self.sample + + self.measure() { + + do { + + let deflate = try Deflate(windowBits: 15) + + XCTAssert(try deflate.process(sample).count > 0) + + } catch let error { + + XCTFail("\(error)") + + } + } + } + + func testInflatePerformance() { + + do { + + let deflate = try Deflate(windowBits: 15) + + let sample = try deflate.process(self.sample) + + self.measure() { + + do { + + let inflate = try Inflate() + + XCTAssert(try inflate.process(sample).count > 0) + + } catch let error { + + XCTFail("\(error)") + + } + + } + + } catch let error { + + XCTFail("\(error)") + + } + + } + +} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 9e5e4e51e..f2a8f5c8c 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -29,6 +29,7 @@ @testable import DoggieTests XCTMain([ + testCase(CompressionTest.allTests), testCase(AtomicTest.allTests), testCase(FourierTest.allTests), testCase(ImageTest.allTests),