From 8baed5d942ff5605b8f6673b271805562970bf62 Mon Sep 17 00:00:00 2001 From: Matthias Wiesmann Date: Wed, 17 Apr 2024 22:39:36 +0200 Subject: [PATCH] Code cleanups. * Moved more types into their own files. * Simplified polygon by using only one Polygon option type. * Moved bitmap reading code into a reader extension. --- QuickDrawViewer.xcodeproj/project.pbxproj | 18 ++ QuickDrawViewer/Animation.swift | 4 +- QuickDrawViewer/Blitting.swift | 6 +- QuickDrawViewer/FixedPoint.swift | 8 + QuickDrawViewer/MacPaint.swift | 3 - QuickDrawViewer/QuickDrawBitMap.swift | 143 +++++++++++++ QuickDrawViewer/QuickDrawColor.swift | 8 + QuickDrawViewer/QuickDrawComment.swift | 20 +- QuickDrawViewer/QuickDrawOpcodes.swift | 45 +--- QuickDrawViewer/QuickDrawParser.swift | 1 + QuickDrawViewer/QuickDrawPolygon.swift | 24 ++- QuickDrawViewer/QuickDrawReader.swift | 11 - QuickDrawViewer/QuickDrawRender.swift | 16 +- QuickDrawViewer/QuickDrawResolution.swift | 52 +++++ QuickDrawViewer/QuickDrawText.swift | 104 +++++++++ QuickDrawViewer/QuickDrawTypes.swift | 249 ---------------------- QuickDrawViewer/UI/ImageWrapper.swift | 24 +-- QuickDrawViewer/Yuv2.swift | 2 +- 18 files changed, 392 insertions(+), 346 deletions(-) create mode 100644 QuickDrawViewer/QuickDrawBitMap.swift create mode 100644 QuickDrawViewer/QuickDrawResolution.swift create mode 100644 QuickDrawViewer/QuickDrawText.swift diff --git a/QuickDrawViewer.xcodeproj/project.pbxproj b/QuickDrawViewer.xcodeproj/project.pbxproj index 3b73fc4..9a52d2c 100644 --- a/QuickDrawViewer.xcodeproj/project.pbxproj +++ b/QuickDrawViewer.xcodeproj/project.pbxproj @@ -40,6 +40,12 @@ E53BBF542BCFD39D00D9350A /* QuickDrawRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BBF522BCFD39D00D9350A /* QuickDrawRect.swift */; }; E53BBF562BCFD41600D9350A /* QuickDrawPolygon.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BBF552BCFD41600D9350A /* QuickDrawPolygon.swift */; }; E53BBF572BCFD41600D9350A /* QuickDrawPolygon.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BBF552BCFD41600D9350A /* QuickDrawPolygon.swift */; }; + E53BBF592BD05EFB00D9350A /* QuickDrawText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BBF582BD05EFB00D9350A /* QuickDrawText.swift */; }; + E53BBF5A2BD05EFB00D9350A /* QuickDrawText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BBF582BD05EFB00D9350A /* QuickDrawText.swift */; }; + E53BBF5C2BD060CE00D9350A /* QuickDrawResolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BBF5B2BD060CE00D9350A /* QuickDrawResolution.swift */; }; + E53BBF5D2BD060CE00D9350A /* QuickDrawResolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BBF5B2BD060CE00D9350A /* QuickDrawResolution.swift */; }; + E53BBF5F2BD0639700D9350A /* QuickDrawBitMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BBF5E2BD0639700D9350A /* QuickDrawBitMap.swift */; }; + E53BBF602BD0639700D9350A /* QuickDrawBitMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BBF5E2BD0639700D9350A /* QuickDrawBitMap.swift */; }; E553E4DC2B6AED62009B1EC2 /* Yuv2.swift in Sources */ = {isa = PBXBuildFile; fileRef = E553E4DB2B6AED62009B1EC2 /* Yuv2.swift */; }; E553E4DD2B6AED62009B1EC2 /* Yuv2.swift in Sources */ = {isa = PBXBuildFile; fileRef = E553E4DB2B6AED62009B1EC2 /* Yuv2.swift */; }; E553E4DF2B6FD9F5009B1EC2 /* QuickTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = E553E4DE2B6FD9F5009B1EC2 /* QuickTime.swift */; }; @@ -112,6 +118,9 @@ E53BBF4F2BCFD30F00D9350A /* QuickDrawPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDrawPoint.swift; sourceTree = ""; }; E53BBF522BCFD39D00D9350A /* QuickDrawRect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDrawRect.swift; sourceTree = ""; }; E53BBF552BCFD41600D9350A /* QuickDrawPolygon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDrawPolygon.swift; sourceTree = ""; }; + E53BBF582BD05EFB00D9350A /* QuickDrawText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDrawText.swift; sourceTree = ""; }; + E53BBF5B2BD060CE00D9350A /* QuickDrawResolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDrawResolution.swift; sourceTree = ""; }; + E53BBF5E2BD0639700D9350A /* QuickDrawBitMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDrawBitMap.swift; sourceTree = ""; }; E553E4DB2B6AED62009B1EC2 /* Yuv2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Yuv2.swift; sourceTree = ""; }; E553E4DE2B6FD9F5009B1EC2 /* QuickTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTime.swift; sourceTree = ""; }; E553E4E12B6FFA53009B1EC2 /* TypeCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeCode.swift; sourceTree = ""; }; @@ -205,9 +214,12 @@ E553E4E12B6FFA53009B1EC2 /* TypeCode.swift */, E59CDFA82B42EE0A00D7BB7B /* PackBit.swift */, E53BBF4F2BCFD30F00D9350A /* QuickDrawPoint.swift */, + E53BBF582BD05EFB00D9350A /* QuickDrawText.swift */, E53BBF522BCFD39D00D9350A /* QuickDrawRect.swift */, E53BBF552BCFD41600D9350A /* QuickDrawPolygon.swift */, E59CDFAA2B42EE0A00D7BB7B /* QuickDrawTypes.swift */, + E53BBF5E2BD0639700D9350A /* QuickDrawBitMap.swift */, + E53BBF5B2BD060CE00D9350A /* QuickDrawResolution.swift */, E554EE5D2B9DF68800D56DF7 /* QuickDrawPattern.swift */, E508C4F62B8E9575007686AE /* QuickDrawColor.swift */, E508C4FF2B936919007686AE /* QuickDrawRegions.swift */, @@ -363,9 +375,12 @@ E53BBF482BC3254200D9350A /* Cinepak.swift in Sources */, E5CDA0272B44155E008DC6D6 /* QuickDrawParser.swift in Sources */, E59CDFAF2B42EE0A00D7BB7B /* PackBit.swift in Sources */, + E53BBF5F2BD0639700D9350A /* QuickDrawBitMap.swift in Sources */, E55E05F72B61A74A00EA1AD8 /* RoadPizza.swift in Sources */, E52FEB052BACC269004E8BB4 /* QuickDrawPort.swift in Sources */, + E53BBF592BD05EFB00D9350A /* QuickDrawText.swift in Sources */, E553E4DC2B6AED62009B1EC2 /* Yuv2.swift in Sources */, + E53BBF5C2BD060CE00D9350A /* QuickDrawResolution.swift in Sources */, E53BBF532BCFD39D00D9350A /* QuickDrawRect.swift in Sources */, E5CDA02D2B4450FB008DC6D6 /* ImageWrapper.swift in Sources */, E59CDF6E2B42C0E500D7BB7B /* QuickDrawViewerDocument.swift in Sources */, @@ -406,6 +421,7 @@ E53BBF462BBB4E5800D9350A /* IntelRaw.swift in Sources */, E53BBF512BCFD30F00D9350A /* QuickDrawPoint.swift in Sources */, E5CDA0252B441536008DC6D6 /* QuickDrawErrors.swift in Sources */, + E53BBF5D2BD060CE00D9350A /* QuickDrawResolution.swift in Sources */, E508C4F52B764CB2007686AE /* Planar.swift in Sources */, E50ADB972B599CDE009EA7AF /* ContentView.swift in Sources */, E59CDF812B42C0E600D7BB7B /* QuickDrawViewerTests.swift in Sources */, @@ -419,8 +435,10 @@ E50ADB982B599CE1009EA7AF /* QuickDrawViewerApp.swift in Sources */, E5CDA02E2B4450FB008DC6D6 /* ImageWrapper.swift in Sources */, E59CDFB62B42EE0A00D7BB7B /* QuickDrawRender.swift in Sources */, + E53BBF5A2BD05EFB00D9350A /* QuickDrawText.swift in Sources */, E554EE562B9BB4B100D56DF7 /* QuickTimeGraphics.swift in Sources */, E508C5012B936919007686AE /* QuickDrawRegions.swift in Sources */, + E53BBF602BD0639700D9350A /* QuickDrawBitMap.swift in Sources */, E53BBF492BC3254200D9350A /* Cinepak.swift in Sources */, E59CDFB22B42EE0A00D7BB7B /* QuickDrawReader.swift in Sources */, E52FEB032BAB8C53004E8BB4 /* QuickDrawComment.swift in Sources */, diff --git a/QuickDrawViewer/Animation.swift b/QuickDrawViewer/Animation.swift index 870e0a1..4274b1d 100644 --- a/QuickDrawViewer/Animation.swift +++ b/QuickDrawViewer/Animation.swift @@ -108,8 +108,8 @@ class AnimationImage : PixMapMetadata { y = Int(try reader.readInt16()); reader.skip(bytes: 6); } - let encoded = try reader.readUInt8(bytes: reader.remaining); - var index = 0; + let encoded = try reader.readSlice(bytes: reader.remaining); + var index = encoded.startIndex; while (index < encoded.count - 1) { let skip = Int(encoded[index]); diff --git a/QuickDrawViewer/Blitting.swift b/QuickDrawViewer/Blitting.swift index ac979a6..791b9e4 100644 --- a/QuickDrawViewer/Blitting.swift +++ b/QuickDrawViewer/Blitting.swift @@ -25,14 +25,14 @@ func roundTo(_ value: FixedPoint, multipleOf: Int) -> Int { /// - u: u chrominance in the -127 - 128 range /// - v: v chrominance in the -127 - 128 range /// - Returns: rgb bytes -func yuv2Rgb(y : Double, u: Double, v: Double) -> [UInt8] { +func yuv2Rgb(y : Double, u: Double, v: Double) -> RGB8 { let r = Int(y + (1.370705 * v)); let g = Int(y - (0.698001 * v) - 0.337633 * u); let b = Int(y + (1.732446 * u)); return [UInt8(clamping: r), UInt8(clamping: g), UInt8(clamping: b)]; } -func yuv2Rgb(y: UInt8, u: UInt8, v: UInt8) -> [UInt8] { +func yuv2Rgb(y: UInt8, u: UInt8, v: UInt8) -> RGB8 { let nu = Double(u) - 128; let nv = Double(v) - 128; let ny = Double(y); @@ -51,6 +51,8 @@ func expandDepth(_ depth : Int) throws -> (Int, Int) { return (3, 5); case 24: return (3, 8); + case 32: + return (3, 8); default: throw BlittingError.unsupportedDepth(depth: depth); } diff --git a/QuickDrawViewer/FixedPoint.swift b/QuickDrawViewer/FixedPoint.swift index 254ca9b..78205e6 100644 --- a/QuickDrawViewer/FixedPoint.swift +++ b/QuickDrawViewer/FixedPoint.swift @@ -152,3 +152,11 @@ public struct FixedPoint : CustomStringConvertible, Comparable, AdditiveArithmet return FixedPoint(a.value / b.value); } } + +extension QuickDrawDataReader { + + func readFixed() throws -> FixedPoint { + let v = try readInt32(); + return FixedPoint(rawValue: Int(v)); + } +} diff --git a/QuickDrawViewer/MacPaint.swift b/QuickDrawViewer/MacPaint.swift index a3a629d..3be3d28 100644 --- a/QuickDrawViewer/MacPaint.swift +++ b/QuickDrawViewer/MacPaint.swift @@ -9,9 +9,6 @@ import Foundation - - - /// MacPaint images are fixed size (720 × 576) PackBit compressed bitmaps. class MacPaintImage : PixMapMetadata { diff --git a/QuickDrawViewer/QuickDrawBitMap.swift b/QuickDrawViewer/QuickDrawBitMap.swift new file mode 100644 index 0000000..42da605 --- /dev/null +++ b/QuickDrawViewer/QuickDrawBitMap.swift @@ -0,0 +1,143 @@ +// +// QuickDrawBitMap.swift +// QuickDrawViewer +// +// Created by Matthias Wiesmann on 17.04.2024. +// + +import Foundation + +/// Packing format used in QuickDraw PixMaps +enum QDPackType : UInt16 { + case defaultPack = 0; + case noPack = 1; + case removePadByte = 2; + case pixelRunLength = 3; + case componentRunLength = 4; +} + +/// Add-on information for a BitMap that is actually a Pixmap. +/// Confusingly, this is called `PixMap` record in Inside Quickdraw,. +/// even though there is no actual pixel data. +class QDPixMapInfo : CustomStringConvertible { + public var description: String { + var result = "PixMapInfo version: \(version) pack-size: \(packSize) "; + result += "pack-type: \(packType) "; + if resolution != nil { + result += "resolution: \(resolution!) "; + } + result += "pixel type: \(pixelType) "; + result += "pixel size: \(pixelSize) "; + result += "composant count: \(cmpCount) "; + result += "composant size: \(cmpSize) "; + result += "plane byte: \(planeByte) "; + result += "clut Id: \(clutId) "; + if clut != nil { + result += "clut: \(clut!)"; + } + return result; + } + + var version : Int = 0; + var packType : QDPackType = QDPackType.defaultPack; + var packSize : Int = 0; + var resolution : QDResolution?; + var pixelType : Int = 0; + var pixelSize : Int = 0; + var cmpCount : Int = 0; + var cmpSize : Int = 0; + var planeByte : Int64 = 0; + var clutId : Int32 = 0; + var clutSeed : MacTypeCode = MacTypeCode.zero; + var clut : QDColorTable?; +} + + +class QDBitMapInfo : CustomStringConvertible, PixMapMetadata { + + init(isPacked: Bool) { + self.isPacked = isPacked; + } + + var hasShortRows : Bool { + return rowBytes < 250; + } + + let isPacked : Bool; + var rowBytes : Int = 0; + var bounds : QDRect = QDRect.empty; + var srcRect : QDRect?; + var dstRect : QDRect?; + var mode : QuickDrawMode = QuickDrawMode.defaultMode; + var data : [UInt8] = [UInt8](); + var pixMapInfo : QDPixMapInfo?; + + var destinationRect : QDRect { + return dstRect!; + } + + var dimensions: QDDelta { + return bounds.dimensions; + } + + var height : Int { + return bounds.dimensions.dv.rounded; + } + + var cmpSize : Int { + if let pix_info = pixMapInfo { + return pix_info.cmpSize; + } + return 1; + } + + var pixelSize : Int { + if let pix_info = pixMapInfo { + return pix_info.pixelSize; + } + return 1; + } + + var clut : QDColorTable? { + if let pix_info = pixMapInfo { + return pix_info.clut!; + } + return QDColorTable.palette1; + } + + public var description : String { + let pm = describePixMap(self); + var result = "Bitmap info [\(pm) packed: \(isPacked) "; + result += "Bounds \(bounds) " + if srcRect != nil { + result += "src: \(srcRect!) "; + } + if dstRect != nil { + result += "dst: \(dstRect!) "; + } + result += "Mode: \(mode)]"; + if let pixmap = pixMapInfo { + result += "Pixmap: \(pixmap)]"; + } + return result; + } +} + +extension QuickDrawDataReader { + func readPixMapInfo() throws -> QDPixMapInfo { + let pixMapInfo = QDPixMapInfo(); + pixMapInfo.version = Int(try readUInt16()); + pixMapInfo.packType = QDPackType(rawValue:try readUInt16())!; + pixMapInfo.packSize = Int(try readUInt32()); + pixMapInfo.resolution = try readResolution(); + pixMapInfo.pixelType = Int(try readUInt16()); + pixMapInfo.pixelSize = Int(try readUInt16()); + pixMapInfo.cmpCount = Int(try readUInt16()); + pixMapInfo.cmpSize = Int(try readUInt16()); + pixMapInfo.planeByte = Int64(try readUInt32()); + pixMapInfo.clutId = try readInt32(); + pixMapInfo.clutSeed = try readType(); + return pixMapInfo; + } +} + diff --git a/QuickDrawViewer/QuickDrawColor.swift b/QuickDrawViewer/QuickDrawColor.swift index 70d654c..2f8ebd0 100644 --- a/QuickDrawViewer/QuickDrawColor.swift +++ b/QuickDrawViewer/QuickDrawColor.swift @@ -342,6 +342,14 @@ extension QuickDrawDataReader { return ARGB555(rawValue: raw); } + func readQD1Color() throws -> QD1Color { + let code = try readUInt32(); + guard let qd1 = QD1Color(rawValue: code) else { + throw QuickDrawError.unsupportedQD1Color(colorCode: code); + } + return qd1; + } + func readClut() throws -> QDColorTable { skip(bytes: 4); let clutFlags = try readUInt16(); diff --git a/QuickDrawViewer/QuickDrawComment.swift b/QuickDrawViewer/QuickDrawComment.swift index aa75be8..ad9c244 100644 --- a/QuickDrawViewer/QuickDrawComment.swift +++ b/QuickDrawViewer/QuickDrawComment.swift @@ -88,21 +88,6 @@ struct TextPictPayload : FontStateOperation { let textPictRecord : QDTextPictRecord; } -struct PolySmoothVerb : OptionSet, CustomStringConvertible { - var description: String { - var result : String = "PolySmoothVerb " - if contains(.polyFrame) {result += " frame"} - if contains(.polyFill) {result += " fill"} - if contains(.polyClose) {result += " close"} - return result; - } - - let rawValue: UInt8; - static let polyFrame = PolySmoothVerb(rawValue: 1 << 0); - static let polyFill = PolySmoothVerb(rawValue: 1 << 1) ; - static let polyClose = PolySmoothVerb(rawValue: 1 << 2); -} - struct PostScript : CustomStringConvertible { var description: String { return ""; @@ -116,7 +101,7 @@ enum CommentPayload { case postScriptPayLoad(postscript: PostScript); case fontStatePayload(fontOperation: FontStateOperation); case penStatePayload(penOperation: PenStateOperation); - case polySmoothPayload(verb: PolySmoothVerb); + case polySmoothPayload(verb: PolygonOptions); case canvasPayload(canvas: CanvasPayload); case colorPayload(creator: MacTypeCode, color: QDColor); case unknownPayload(rawType: Int, data: Data); @@ -212,7 +197,8 @@ struct CommentOp : OpCode, CustomStringConvertible, CullableOpcode { let fontOp = TextCenterPayload(center: QDDelta(dv: v, dh: h)); payload = .fontStatePayload(fontOperation: fontOp); case (.polySmooth, 1): - let verb = PolySmoothVerb(rawValue: try reader.readUInt8()); + var verb = PolygonOptions(rawValue: try reader.readUInt8()); + verb.insert(.smooth); payload = .polySmoothPayload(verb: verb); case (_, 0): payload = .noPayload; diff --git a/QuickDrawViewer/QuickDrawOpcodes.swift b/QuickDrawViewer/QuickDrawOpcodes.swift index e64eb6d..0b6070d 100644 --- a/QuickDrawViewer/QuickDrawOpcodes.swift +++ b/QuickDrawViewer/QuickDrawOpcodes.swift @@ -23,7 +23,7 @@ protocol PictureOperation { /// opcodes that affect the pen-state can be execute independently of the target graphic system. protocol PenStateOperation { - func execute(penState : inout PenState) -> Void; + func execute(penState : inout PenState) throws -> Void; } /// opcodes that affect the font state (size, font-name, etc). @@ -130,12 +130,10 @@ struct Version2HeaderOp : OpCode, PictureOperation { } struct OriginOp : OpCode { - mutating func load(reader: QuickDrawDataReader) throws -> Void { - let dh = try reader.readInt16(); - let dv = try reader.readInt16(); - delta = QDDelta(dv: dv, dh: dh); + delta = try reader.readDelta(); } + var delta : QDDelta = QDDelta.zero; } @@ -349,11 +347,7 @@ struct ColorOp : OpCode, PenStateOperation, CustomStringConvertible { if rgb { color = try .rgb(rgb: reader.readRGB()); } else { - let code = try reader.readUInt32(); - guard let qd1 = QD1Color(rawValue: code) else { - throw QuickDrawError.unsupportedQD1Color(colorCode: code); - } - color = .qd1(qd1: qd1); + color = try .qd1(qd1: reader.readQD1Color()); } } @@ -377,16 +371,16 @@ struct ColorOp : OpCode, PenStateOperation, CustomStringConvertible { // Pen operations - +// -------------- struct PatternOp : OpCode, PenStateOperation { - func execute(penState: inout PenState) { + func execute(penState: inout PenState) throws { switch verb { case .fill, .paint: penState.fillPattern = pattern; case .frame: penState.drawPattern = pattern; default: - print("Unsupported pattern verb in \(self)"); + throw QuickDrawError.unsupportedVerb(verb: verb); } } @@ -542,7 +536,7 @@ struct SpaceExtraOp : OpCode, FontStateOperation { struct PnLocHFracOp : OpCode, PenStateOperation { mutating func load(reader: QuickDrawDataReader) throws { let f = try reader.readUInt16(); - penFraction = FixedPoint(rawValue: Int(f )); + penFraction = FixedPoint(rawValue: Int(f)); } func execute(penState: inout PenState) { @@ -565,7 +559,7 @@ struct FontStyleOp : OpCode, FontStateOperation { fontStyle = QDFontStyle(rawValue: try reader.readUInt8()); } - var fontStyle = QDFontStyle(rawValue:0); + var fontStyle = QDFontStyle.defaultStyle; } struct DHDVTextOp : OpCode, PortOperation { @@ -617,22 +611,6 @@ struct LongTextOp : OpCode, PortOperation { /// Bitmap op-codes /// --------------- -func readPixMapInfo(reader: QuickDrawDataReader) throws -> QDPixMapInfo { - let pixMapInfo = QDPixMapInfo(); - pixMapInfo.version = Int(try reader.readUInt16()); - pixMapInfo.packType = QDPackType(rawValue:try reader.readUInt16())!; - pixMapInfo.packSize = Int(try reader.readUInt32()); - pixMapInfo.resolution = try reader.readResolution(); - pixMapInfo.pixelType = Int(try reader.readUInt16()); - pixMapInfo.pixelSize = Int(try reader.readUInt16()); - pixMapInfo.cmpCount = Int(try reader.readUInt16()); - pixMapInfo.cmpSize = Int(try reader.readUInt16()); - pixMapInfo.planeByte = Int64(try reader.readUInt32()); - pixMapInfo.clutId = try reader.readInt32(); - pixMapInfo.clutSeed = try reader.readType(); - return pixMapInfo; -} - struct BitRectOpcode : OpCode { init(isPacked : Bool) { self.bitmapInfo = QDBitMapInfo(isPacked: isPacked); @@ -647,7 +625,7 @@ struct BitRectOpcode : OpCode { bitmapInfo.rowBytes = Int(masked); bitmapInfo.bounds = try reader.readRect(); if isPixMap { - let pixMapInfo = try readPixMapInfo(reader:reader); + let pixMapInfo = try reader.readPixMapInfo(); pixMapInfo.clut = try reader.readClut(); bitmapInfo.pixMapInfo = pixMapInfo; } @@ -688,7 +666,7 @@ struct DirectBitOpcode : OpCode { } bitmapInfo.rowBytes = Int(masked); bitmapInfo.bounds = try reader.readRect(); - bitmapInfo.pixMapInfo = try readPixMapInfo(reader:reader); + bitmapInfo.pixMapInfo = try reader.readPixMapInfo(); bitmapInfo.srcRect = try reader.readRect(); bitmapInfo.dstRect = try reader.readRect(); bitmapInfo.mode = try QuickDrawMode(rawValue: reader.readUInt16()); @@ -767,7 +745,6 @@ struct DirectBitOpcode : OpCode { bitmapInfo.data.append(decompressed[i + w]); bitmapInfo.data.append(decompressed[i + (2 * w)]); } - } /// Update the pixel information to reflect reality. There is no alpha. bitmapInfo.rowBytes = rowBytes ; diff --git a/QuickDrawViewer/QuickDrawParser.swift b/QuickDrawViewer/QuickDrawParser.swift index c2be473..1c516ed 100644 --- a/QuickDrawViewer/QuickDrawParser.swift +++ b/QuickDrawViewer/QuickDrawParser.swift @@ -186,3 +186,4 @@ class QDParser { } } } + diff --git a/QuickDrawViewer/QuickDrawPolygon.swift b/QuickDrawViewer/QuickDrawPolygon.swift index 972db07..47953ce 100644 --- a/QuickDrawViewer/QuickDrawPolygon.swift +++ b/QuickDrawViewer/QuickDrawPolygon.swift @@ -7,13 +7,30 @@ import Foundation +struct PolygonOptions : OptionSet, CustomStringConvertible { + var rawValue: UInt8; + static let frame = PolygonOptions(rawValue: 1 << 0); + static let fill = PolygonOptions(rawValue: 1 << 1); + static let close = PolygonOptions(rawValue: 1 << 2); + static let smooth = PolygonOptions(rawValue: 1 << 3); + static let empty = PolygonOptions([]); + + var description: String { + var result : String = "PolySmoothVerb " + if contains(.frame) {result += " frame"} + if contains(.fill) {result += " fill"} + if contains(.close) {result += " close"} + if contains(.smooth) {result += " smooth"} + return result; + } +} + class QDPolygon { init(boundingBox: QDRect?, points: [QDPoint]) { self.boundingBox = boundingBox; self.points = points; - self.closed = false; - self.smooth = false; + self.options = PolygonOptions.empty; } convenience init() { @@ -22,8 +39,7 @@ class QDPolygon { var boundingBox : QDRect?; var points : [QDPoint]; - var closed : Bool; - var smooth : Bool; + var options : PolygonOptions; func AddLine(line : [QDPoint]) { if points.isEmpty { diff --git a/QuickDrawViewer/QuickDrawReader.swift b/QuickDrawViewer/QuickDrawReader.swift index 6a34691..fa75262 100644 --- a/QuickDrawViewer/QuickDrawReader.swift +++ b/QuickDrawViewer/QuickDrawReader.swift @@ -165,17 +165,6 @@ class QuickDrawDataReader { return toScalar(bytes: bytes); } - func readFixed() throws -> FixedPoint { - let v = try readInt32(); - return FixedPoint(rawValue: Int(v)); - } - - func readResolution() throws -> QDResolution { - let hRes = try readFixed(); - let vRes = try readFixed(); - return QDResolution(hRes: hRes, vRes: vRes); - } - func readOpcode(version: Int) throws -> UInt16 { switch version { case 1: diff --git a/QuickDrawViewer/QuickDrawRender.swift b/QuickDrawViewer/QuickDrawRender.swift index 61a1539..0fbf1bc 100644 --- a/QuickDrawViewer/QuickDrawRender.swift +++ b/QuickDrawViewer/QuickDrawRender.swift @@ -379,7 +379,7 @@ class QuickdrawCGRenderer : QuickDrawRenderer, QuickDrawPort { let cg_points = polygon.points.map({ CGPoint(qd_point:$0)}); context!.beginPath(); context!.addLines(between: cg_points); - if polygon.closed { + if polygon.options.contains(.close) { context!.closePath(); } try applyVerbToPath(verb: verb); @@ -572,16 +572,12 @@ class QuickdrawCGRenderer : QuickDrawRenderer, QuickDrawPort { throw CoreGraphicRenderError.inconsistentPoly( message: String(localized: "Closing non existing polygon.")); } - poly.closed = true; + poly.options.insert(.close); case (.polySmooth, .polySmoothPayload(let polyVerb)): guard let poly = polyAccumulator else { throw CoreGraphicRenderError.inconsistentPoly( message: String(localized: "Ending non existing polygon.")); } - poly.smooth = true; - if polyVerb.contains(.polyClose) { - poly.closed = true; - } self.polyVerb = polyVerb; case (.polyIgnore, _): // Disabling the poly operations and executing it later (with polyClose) @@ -596,9 +592,9 @@ class QuickdrawCGRenderer : QuickDrawRenderer, QuickDrawPort { // Do something with polyverb? try stdPoly(polygon: poly, verb: QDVerb.frame); polyAccumulator = nil; - polyVerb = nil; + polyVerb = PolygonOptions.empty; case (_, .penStatePayload(let penOp)): - penOp.execute(penState: &penState); + try penOp.execute(penState: &penState); case (_, .fontStatePayload(let fontOp)): fontOp.execute(fontState: &fontState); case (_, .colorPayload(_, let color)): @@ -766,7 +762,7 @@ class QuickdrawCGRenderer : QuickDrawRenderer, QuickDrawPort { func execute(opcode: OpCode) throws { switch opcode { case let penOp as PenStateOperation: - penOp.execute(penState: &penState); + try penOp.execute(penState: &penState); case let fontOp as FontStateOperation: fontOp.execute(fontState: &fontState); case let portOp as PortOperation: @@ -831,7 +827,7 @@ class QuickdrawCGRenderer : QuickDrawRenderer, QuickDrawPort { var lastRegion :QDRegion = QDRegion.empty; // Polygon for reconstruction. var polyAccumulator : QDPolygon?; - var polyVerb : PolySmoothVerb?; + var polyVerb = PolygonOptions.empty; // Logger let logger : Logger = Logger(subsystem: "net.codiferes.wiesmann.QuickDraw", category: "render"); diff --git a/QuickDrawViewer/QuickDrawResolution.swift b/QuickDrawViewer/QuickDrawResolution.swift new file mode 100644 index 0000000..93dffc5 --- /dev/null +++ b/QuickDrawViewer/QuickDrawResolution.swift @@ -0,0 +1,52 @@ +// +// QuickDrawResolution.swift +// QuickDrawViewer +// +// Created by Matthias Wiesmann on 17.04.2024. +// + +import Foundation + +/// Quickdraw picture resolution, in DPI. +struct QDResolution : Equatable, CustomStringConvertible { + let hRes : FixedPoint; + let vRes : FixedPoint; + + public var description: String { + return "\(hRes)×\(vRes)"; + } + + /// Scale a delta as a function of the resolution, relative to the standard (72 DPI). + /// - Parameters: + /// - dim: dimension to scale + /// - resolution: resolution description + /// - Returns: scales dimension + public static func ⨴ (dim : QDDelta, resolution: QDResolution) -> QDDelta { + let h = dim.dh.value * defaultScalarResolution.value / resolution.hRes.value; + let v = dim.dv.value * defaultScalarResolution.value / resolution.vRes.value; + return QDDelta(dv: FixedPoint(v), dh: FixedPoint(h)); + } + + /// Return a rectangle scaled for a given resolution + /// - Parameters: + /// - rect: rectangle to scale + /// - resolution: resolution to use for scaling + /// - Returns: a scaled rectangle. + public static func ⨴ (rect: QDRect, resolution: QDResolution) -> QDRect { + let d = rect.dimensions ⨴ resolution; + return QDRect(topLeft: rect.topLeft, dimension: d); + } + + static let defaultScalarResolution = FixedPoint(72); + static let defaultResolution = QDResolution( + hRes: defaultScalarResolution, vRes: defaultScalarResolution); + static let zeroResolution = QDResolution(hRes: FixedPoint.zero, vRes: FixedPoint.zero); +} + +extension QuickDrawDataReader { + func readResolution() throws -> QDResolution { + let hRes = try readFixed(); + let vRes = try readFixed(); + return QDResolution(hRes: hRes, vRes: vRes); + } +} diff --git a/QuickDrawViewer/QuickDrawText.swift b/QuickDrawViewer/QuickDrawText.swift new file mode 100644 index 0000000..219a21c --- /dev/null +++ b/QuickDrawViewer/QuickDrawText.swift @@ -0,0 +1,104 @@ +// +// QuickDrawText.swift +// QuickDrawViewer +// +// Created by Matthias Wiesmann on 17.04.2024. +// + +import Foundation + +/// Text rendering options +struct QDGlyphState : OptionSet { + let rawValue: UInt8; + static let outlinePreferred = QDGlyphState(rawValue: 1 << 0); + static let preserveGlyphs = QDGlyphState(rawValue: 1 << 1); + static let fractionalWidths = QDGlyphState(rawValue: 1 << 2); + static let scalingDisabled = QDGlyphState(rawValue: 1 << 3); + static let defaultState = QDGlyphState([]); +} + +/// Various text related properties. +class QDFontState { + func getFontName() -> String? { + if let name = self.fontName { + return name; + } + /// List of classic fonts with their canonical IDs. + switch fontId { + case 2: return "New York"; + case 3: return "Geneva"; + case 4: return "Monaco"; + case 5: return "Venice"; + case 6: return "Venice"; + case 7: return "Athens"; + case 8: return "San Francisco"; + case 9: return "Toronto"; + case 11: return "Cairo"; + case 12: return "Los Angeles"; + case 20: return "Times"; + case 21: return "Helvetica"; + case 22: return "Courrier"; + case 23: return "Symbol"; + case 24: return "Mobile"; + default: + return nil; + } // Switch + } + var fontId : Int = 0; + var fontName : String?; + var fontSize = FixedPoint(12); + var fontMode : QuickDrawMode = QuickDrawMode.defaultMode; + var location : QDPoint = QDPoint.zero; + var fontStyle : QDFontStyle = QDFontStyle.defaultStyle; + var glyphState : QDGlyphState = QDGlyphState.defaultState; + var xRatio : FixedPoint = FixedPoint.one; + var yRatio : FixedPoint = FixedPoint.one; + var textCenter: QDDelta?; + var textPictRecord : QDTextPictRecord?; + var extraSpace : FixedPoint = FixedPoint.zero; +} + +enum QDTextJustification : UInt8 { + case justificationNone = 0; + case justificationLeft = 1; + case justificationCenter = 2; + case justificationRight = 3; + case justificationFull = 4; + case justification5 = 5; // Found in MacDraw 1 + case justification6 = 6; // Found in MacDraw 1 +} + +enum QDTextFlip : UInt8 { + case textFlipNone = 0; + case textFlipHorizontal = 1; + case textFlipVertical = 2; +} + +enum QDTextLineHeight : UInt8 { + case unknown = 0; + case single = 1; + case oneAndHalf = 2; + case double = 3; + case double2 = 4; +} + +// Text annotation for text comments +struct QDTextPictRecord { + let justification : QDTextJustification; + let flip : QDTextFlip; + let angle : FixedPoint; + let lineHeight : QDTextLineHeight; +} + +struct QDFontStyle : OptionSet { + let rawValue: UInt8; + static let boldBit = QDFontStyle(rawValue: 1 << 0); + static let italicBit = QDFontStyle(rawValue: 1 << 1); + static let ulineBit = QDFontStyle(rawValue: 1 << 2); + static let outlineBit = QDFontStyle(rawValue: 1 << 3); + static let shadowBit = QDFontStyle(rawValue: 1 << 4); + static let condenseBit = QDFontStyle(rawValue: 1 << 5); + static let extendBit = QDFontStyle(rawValue: 1 << 6); + + static let defaultStyle = QDFontStyle([]); +} diff --git a/QuickDrawViewer/QuickDrawTypes.swift b/QuickDrawViewer/QuickDrawTypes.swift index 0b97e65..7bce2d3 100644 --- a/QuickDrawViewer/QuickDrawTypes.swift +++ b/QuickDrawViewer/QuickDrawTypes.swift @@ -7,11 +7,6 @@ import Foundation - - - - - enum QDVerb : UInt16, CustomStringConvertible { var description: String { switch self { @@ -41,18 +36,6 @@ enum QDColorSelection : UInt8 { case highlight = 3; } -struct QDFontStyle : OptionSet { - let rawValue: UInt8; - static let boldBit = QDFontStyle(rawValue: 1 << 0); - static let italicBit = QDFontStyle(rawValue: 1 << 1); - static let ulineBit = QDFontStyle(rawValue: 1 << 2); - static let outlineBit = QDFontStyle(rawValue: 1 << 3); - static let shadowBit = QDFontStyle(rawValue: 1 << 4); - static let condenseBit = QDFontStyle(rawValue: 1 << 5); - static let extendBit = QDFontStyle(rawValue: 1 << 6); - - static let defaultStyle = QDFontStyle([]); -} // the 8 first transfer modes from QuickDraw.p // Patterns operation is bit 5. @@ -127,42 +110,6 @@ precedencegroup ComparisonPrecedence { infix operator ⨴ : MultiplicationPrecedence -/// Quickdraw picture resolution, in DPI. -struct QDResolution : Equatable, CustomStringConvertible { - let hRes : FixedPoint; - let vRes : FixedPoint; - - public var description: String { - return "\(hRes)×\(vRes)"; - } - - /// Scale a delta as a function of the resolution, relative to the standard (72 DPI). - /// - Parameters: - /// - dim: dimension to scale - /// - resolution: resolution description - /// - Returns: scales dimension - public static func ⨴ (dim : QDDelta, resolution: QDResolution) -> QDDelta { - let h = dim.dh.value * defaultScalarResolution.value / resolution.hRes.value; - let v = dim.dv.value * defaultScalarResolution.value / resolution.vRes.value; - return QDDelta(dv: FixedPoint(v), dh: FixedPoint(h)); - } - - /// Return a rectangle scaled for a given resolution - /// - Parameters: - /// - rect: rectangle to scale - /// - resolution: resolution to use for scaling - /// - Returns: a scaled rectangle. - public static func ⨴ (rect: QDRect, resolution: QDResolution) -> QDRect { - let d = rect.dimensions ⨴ resolution; - return QDRect(topLeft: rect.topLeft, dimension: d); - } - - static let defaultScalarResolution = FixedPoint(72); - static let defaultResolution = QDResolution( - hRes: defaultScalarResolution, vRes: defaultScalarResolution); - static let zeroResolution = QDResolution(hRes: FixedPoint.zero, vRes: FixedPoint.zero); -} - /// All the state associated with drawing class PenState { var location : QDPoint = QDPoint.zero; @@ -202,204 +149,8 @@ class PenState { static let defaultPen = QDPoint(vertical: defautPenWidth, horizontal: defautPenWidth); } -/// Text rendering options -struct QDGlyphState : OptionSet { - let rawValue: UInt8; - static let outlinePreferred = QDGlyphState(rawValue: 1 << 0); - static let preserveGlyphs = QDGlyphState(rawValue: 1 << 1); - static let fractionalWidths = QDGlyphState(rawValue: 1 << 2); - static let scalingDisabled = QDGlyphState(rawValue: 1 << 3); - static let defaultState = QDGlyphState([]); -} - -/// Various text related properties. -class QDFontState { - func getFontName() -> String? { - if let name = self.fontName { - return name; - } - /// List of classic fonts with their canonical IDs. - switch fontId { - case 2: return "New York"; - case 3: return "Geneva"; - case 4: return "Monaco"; - case 5: return "Venice"; - case 6: return "Venice"; - case 7: return "Athens"; - case 8: return "San Francisco"; - case 9: return "Toronto"; - case 11: return "Cairo"; - case 12: return "Los Angeles"; - case 20: return "Times"; - case 21: return "Helvetica"; - case 22: return "Courrier"; - case 23: return "Symbol"; - case 24: return "Mobile"; - default: - return nil; - } // Switch - } - var fontId : Int = 0; - var fontName : String?; - var fontSize = FixedPoint(12); - var fontMode : QuickDrawMode = QuickDrawMode.defaultMode; - var location : QDPoint = QDPoint.zero; - var fontStyle : QDFontStyle = QDFontStyle.defaultStyle; - var glyphState : QDGlyphState = QDGlyphState.defaultState; - var xRatio : FixedPoint = FixedPoint.one; - var yRatio : FixedPoint = FixedPoint.one; - var textCenter: QDDelta?; - var textPictRecord : QDTextPictRecord?; - var extraSpace : FixedPoint = FixedPoint.zero; -} - -enum QDTextJustification : UInt8 { - case justificationNone = 0; - case justificationLeft = 1; - case justificationCenter = 2; - case justificationRight = 3; - case justificationFull = 4; - case justification5 = 5; // Found in MacDraw 1 - case justification6 = 6; // Found in MacDraw 1 -} - -enum QDTextFlip : UInt8 { - case textFlipNone = 0; - case textFlipHorizontal = 1; - case textFlipVertical = 2; -} - -enum QDTextLineHeight : UInt8 { - case unknown = 0; - case single = 1; - case oneAndHalf = 2; - case double = 3; - case double2 = 4; -} - -// Text annotation for text comments -struct QDTextPictRecord { - let justification : QDTextJustification; - let flip : QDTextFlip; - let angle : FixedPoint; - let lineHeight : QDTextLineHeight; -} - -enum QDPackType : UInt16 { - case defaultPack = 0; - case noPack = 1; - case removePadByte = 2; - case pixelRunLength = 3; - case componentRunLength = 4; -} - -// Confusingly, this is called `PixMap` record in Inside Quickdraw,. -// even though there is no actual pixel data. -class QDPixMapInfo : CustomStringConvertible { - init() {} - - public var description: String { - var result = "PixMapInfo version: \(version) pack-size: \(packSize) "; - result += "pack-type: \(packType) "; - if resolution != nil { - result += "resolution: \(resolution!) "; - } - result += "pixel type: \(pixelType) "; - result += "pixel size: \(pixelSize) "; - result += "composant count: \(cmpCount) "; - result += "composant size: \(cmpSize) "; - result += "plane byte: \(planeByte) "; - result += "clut Id: \(clutId) "; - if clut != nil { - result += "clut: \(clut!)"; - } - return result; - } - - var version : Int = 0; - var packType : QDPackType = QDPackType.defaultPack; - var packSize : Int = 0; - var resolution : QDResolution?; - var pixelType : Int = 0; - var pixelSize : Int = 0; - var cmpCount : Int = 0; - var cmpSize : Int = 0; - var planeByte : Int64 = 0; - var clutId : Int32 = 0; - var clutSeed : MacTypeCode = MacTypeCode.zero; - var clut : QDColorTable?; -} -class QDBitMapInfo : CustomStringConvertible, PixMapMetadata { - - init(isPacked: Bool) { - self.isPacked = isPacked; - } - - var hasShortRows : Bool { - return rowBytes < 250; - } - - let isPacked : Bool; - var rowBytes : Int = 0; - var bounds : QDRect = QDRect.empty; - var srcRect : QDRect?; - var dstRect : QDRect?; - var mode : QuickDrawMode = QuickDrawMode.defaultMode; - var data : [UInt8] = [UInt8](); - var pixMapInfo : QDPixMapInfo?; - - var destinationRect : QDRect { - return dstRect!; - } - - var dimensions: QDDelta { - return bounds.dimensions; - } - - var height : Int { - return bounds.dimensions.dv.rounded; - } - - var cmpSize : Int { - if let pix_info = pixMapInfo { - return pix_info.cmpSize; - } - return 1; - } - - var pixelSize : Int { - if let pix_info = pixMapInfo { - return pix_info.pixelSize; - } - return 1; - } - - var clut : QDColorTable? { - if let pix_info = pixMapInfo { - return pix_info.clut!; - } - return QDColorTable.palette1; - } - - public var description : String { - let pm = describePixMap(self); - var result = "Bitmap info [\(pm) packed: \(isPacked) "; - result += "Bounds \(bounds) " - if srcRect != nil { - result += "src: \(srcRect!) "; - } - if dstRect != nil { - result += "dst: \(dstRect!) "; - } - result += "Mode: \(mode)]"; - if let pixmap = pixMapInfo { - result += "Pixmap: \(pixmap)]"; - } - return result; - } -} public class QDPicture : CustomStringConvertible { init(size: Int, frame:QDRect, filename: String?) { diff --git a/QuickDrawViewer/UI/ImageWrapper.swift b/QuickDrawViewer/UI/ImageWrapper.swift index 9cf5612..3ce8c0e 100644 --- a/QuickDrawViewer/UI/ImageWrapper.swift +++ b/QuickDrawViewer/UI/ImageWrapper.swift @@ -14,7 +14,7 @@ import SwiftUI /// Add UI related features. extension QDPicture { - + func pdfData() -> NSData { let data = NSMutableData(); let renderer = PDFRenderer(data: data); @@ -49,18 +49,16 @@ func ProvidePicture(picture: QDPicture) -> [NSItemProvider] { return [pdfProvider]; } - - func renderPicture(picture: QDPicture, context : CGContext, zoom: Double, logger: Logger) throws -> Void { let startTime = CFAbsoluteTimeGetCurrent(); - let renderer = QuickdrawCGRenderer(context: context); - try renderer.execute(picture: picture, zoom: zoom); - let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime; - let filename = picture.filename ?? ""; - let frame = picture.frame; - for (pos, opcode) in picture.opcodes.enumerated() { - let entry = "\(pos): \(opcode)"; - logger.log(level: .debug, "\(entry)"); - } - logger.log(level: .info, "\(filename) \(frame)\n rendered in : \(timeElapsed) seconds"); + let renderer = QuickdrawCGRenderer(context: context); + try renderer.execute(picture: picture, zoom: zoom); + let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime; + let filename = picture.filename ?? ""; + let frame = picture.frame; + for (pos, opcode) in picture.opcodes.enumerated() { + let entry = "\(pos): \(opcode)"; + logger.log(level: .debug, "\(entry)"); + } + logger.log(level: .info, "\(filename) \(frame)\n rendered in : \(timeElapsed) seconds"); } diff --git a/QuickDrawViewer/Yuv2.swift b/QuickDrawViewer/Yuv2.swift index f3fa869..d79f511 100644 --- a/QuickDrawViewer/Yuv2.swift +++ b/QuickDrawViewer/Yuv2.swift @@ -12,7 +12,7 @@ import CoreGraphics /// It would be nice to use something like the accelerate framework to decode this. /// Sadly this particular version uses _signed_ int8 for u and v, not a cut-off of 128. /// So we code it explicitely. -func signedYuv2Rgb(y: UInt8, u: UInt8, v: UInt8) -> [UInt8] { +func signedYuv2Rgb(y: UInt8, u: UInt8, v: UInt8) -> RGB8 { let nu = Double(Int8(bitPattern: u)); let nv = Double(Int8(bitPattern: v)); let ny = Double(y);