Skip to content

Commit

Permalink
Allow previously missing bitmaps to be displayed
Browse files Browse the repository at this point in the history
  • Loading branch information
IsaacMarovitz committed Sep 10, 2023
1 parent a65c46f commit fcdca97
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 7 deletions.
4 changes: 4 additions & 0 deletions Whisky.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
6EDF9ABF2A9A6F3D006CAA7E /* GPTKInstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0822862A39D73800195094 /* GPTKInstaller.swift */; };
6EDF9AC32A9A6F46006CAA7E /* Tar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0822852A39D73800195094 /* Tar.swift */; };
6EF557982A410599001A4F09 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF557972A410599001A4F09 /* SetupView.swift */; };
6EFDF6442AAD65F500EF622F /* BitmapInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFDF6432AAD65F500EF622F /* BitmapInfo.swift */; };
AB66A8642A4195B10006D238 /* Rosetta2.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB66A8632A4195B10006D238 /* Rosetta2.swift */; };
EB58FB552A499896002DC184 /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = EB58FB542A499896002DC184 /* SemanticVersion */; };
EBFF512A2A43C6A300CF9B56 /* ConvertFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBFF51292A43C6A300CF9B56 /* ConvertFormat.swift */; };
Expand Down Expand Up @@ -166,6 +167,7 @@
6EDF9AB52A9A6D8C006CAA7E /* BottleData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottleData.swift; sourceTree = "<group>"; };
6EF557952A40B8D1001A4F09 /* GPTKDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPTKDownloader.swift; sourceTree = "<group>"; };
6EF557972A410599001A4F09 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
6EFDF6432AAD65F500EF622F /* BitmapInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitmapInfo.swift; sourceTree = "<group>"; };
AB66A8632A4195B10006D238 /* Rosetta2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rosetta2.swift; sourceTree = "<group>"; };
AB8570C22A34FA4000BFBBE0 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = "<group>"; };
B584FD842A3376E0009B00F4 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -310,6 +312,7 @@
6E26058529D9267B00DDD788 /* ShellLink.swift */,
6E8A3B3D2A3613F400D19632 /* PortableExecutable.swift */,
6E8A3B3F2A36140300D19632 /* ResourceSection.swift */,
6EFDF6432AAD65F500EF622F /* BitmapInfo.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -568,6 +571,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6EFDF6442AAD65F500EF622F /* BitmapInfo.swift in Sources */,
EEA5A2462A31DD65008274AE /* AppDelegate.swift in Sources */,
6E70A4A12A9A280C007799E9 /* WhiskyCmd.swift in Sources */,
6E40495829CCA19C006E3F1B /* ContentView.swift in Sources */,
Expand Down
218 changes: 218 additions & 0 deletions Whisky/Models/BitmapInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
//
// BitmapInfo.swift
// Whisky
//
// Created by Isaac Marovitz on 09/09/2023.
//

import Foundation
import AppKit

struct BitmapInfoHeader: Hashable {
var size: UInt32
var width: Int32
var height: Int32
var planes: UInt16
var bitCount: UInt16
var compression: BitmapCompression
var sizeImage: UInt32
var xPelsPerMeter: Int32
var yPelsPerMeter: Int32
var clrUsed: UInt32
var clrImportant: UInt32

var originDirection: BitmapOriginDirection
var colorFormat: ColorFormat

init(data: Data, offset: Int) {
var offset = offset
self.size = data.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.width = data.extract(Int32.self, offset: offset) ?? 0
offset += 4
self.height = data.extract(Int32.self, offset: offset) ?? 0
offset += 4
self.planes = data.extract(UInt16.self, offset: offset) ?? 0
offset += 2
self.bitCount = data.extract(UInt16.self, offset: offset) ?? 0
offset += 2
self.compression = BitmapCompression(rawValue: data.extract(UInt32.self, offset: offset) ?? 0) ?? .rgb
offset += 4
self.sizeImage = data.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.xPelsPerMeter = data.extract(Int32.self, offset: offset) ?? 0
offset += 4
self.yPelsPerMeter = data.extract(Int32.self, offset: offset) ?? 0
offset += 4
self.clrUsed = data.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.clrImportant = data.extract(UInt32.self, offset: offset) ?? 0
offset += 4

self.originDirection = self.height < 0 ? .upperLeft : .bottomLeft
self.colorFormat = ColorFormat(rawValue: bitCount) ?? .unknown
}

// swiftlint:disable:next cyclomatic_complexity function_body_length
func renderBitmap(data: Data, offset: Int) -> NSImage {
var offset = offset
let colorTable = buildColorTable(offset: &offset, data: data)

var pixels: [ColorQuad] = []

// Handle bitfields later if necessary

for _ in 0..<Int(height / 2) {
var pixelRow: [ColorQuad] = []

for _ in 0..<width {
switch colorFormat {
case .indexed1:
// Swift has no data type equivelent to a single bit
// This will take some bitwise magic
break
case .indexed2:
// Swift's smallest data type is 1 byte
// Ditto .indexed1
break
case .indexed4:
// Swift's smallest data type is 1 byte
// Ditto .indexed1
break
case .indexed8:
let index = data.extract(UInt8.self, offset: offset) ?? 0
pixelRow.append(colorTable[Int(index)])
offset += 1
case .sampled16:
let sample = data.extract(UInt16.self, offset: offset) ?? 0
let red = sample & 0x001F
let green = (sample & 0x03E0) >> 5
let blue = (sample & 0x7C00) >> 10
pixels.append(ColorQuad(red: UInt8(red),
green: UInt8(green),
blue: UInt8(blue),
alpha: 1))
offset += 2
case .sampled24:
let blue = data.extract(UInt8.self, offset: offset) ?? 0
offset += 1
let green = data.extract(UInt8.self, offset: offset) ?? 0
offset += 1
let red = data.extract(UInt8.self, offset: offset) ?? 0
offset += 1
pixelRow.append(ColorQuad(red: red,
green: green,
blue: blue,
alpha: 1))
case .sampled32:
let blue = data.extract(UInt8.self, offset: offset) ?? 0
offset += 1
let green = data.extract(UInt8.self, offset: offset) ?? 0
offset += 1
let red = data.extract(UInt8.self, offset: offset) ?? 0
offset += 1
let alpha = data.extract(UInt8.self, offset: offset) ?? 0
offset += 1
pixelRow.append(ColorQuad(red: red,
green: green,
blue: blue,
alpha: alpha))
case .unknown:
break
}
}

if originDirection == .upperLeft {
pixels.append(contentsOf: pixelRow)
} else {
pixels.insert(contentsOf: pixelRow, at: 0)
}
}

return constructImage(pixels: pixels)
}

func buildColorTable(offset: inout Int, data: Data) -> [ColorQuad] {
var colorTable: [ColorQuad] = []

for _ in 0..<clrUsed {
let blue = data.extract(UInt8.self, offset: offset) ?? 0
offset += 1
let green = data.extract(UInt8.self, offset: offset) ?? 0
offset += 1
let red = data.extract(UInt8.self, offset: offset) ?? 0
offset += 2

colorTable.append(ColorQuad(red: red,
green: green,
blue: blue,
alpha: Int(red) + Int(green) + Int(blue) == 0 ? 0 : 255))
}

return colorTable
}

func constructImage(pixels: [ColorQuad]) -> NSImage {
var pixels = pixels

if pixels.count > 0 {
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue)
let quadStride = MemoryLayout<ColorQuad>.stride

if let providerRef = CGDataProvider(data: Data(bytes: &pixels,
count: pixels.count * quadStride) as CFData) {
if let cgImg = CGImage(width: Int(width),
height: Int(height / 2),
bitsPerComponent: 8,
bitsPerPixel: 32,
bytesPerRow: Int(width) * quadStride,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: true,
intent: .defaultIntent) {
return NSImage(cgImage: cgImg, size: .zero)
}
}
}

return NSImage()
}
}

struct ColorQuad {
var red: UInt8
var green: UInt8
var blue: UInt8
var alpha: UInt8
}

enum BitmapCompression: UInt32 {
case rgb = 0x0000
case rle8 = 0x0001
case rle4 = 0x0002
case bitfields = 0x0003
case jpeg = 0x0004
case png = 0x0005
case alphaBitfields = 0x0006
case cmyk = 0x000B
case cmykRle8 = 0x000C
case cmykRle4 = 0x000D
}

enum BitmapOriginDirection {
case bottomLeft
case upperLeft
}

enum ColorFormat: UInt16 {
case unknown = 0
case indexed1 = 1
case indexed2 = 2
case indexed4 = 4
case indexed8 = 8
case sampled16 = 16
case sampled24 = 24
case sampled32 = 32
}
17 changes: 13 additions & 4 deletions Whisky/Models/ResourceSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,19 @@ struct ResourceDataEntry: Hashable {
offset += 4

if let offsetToData = resolveRVA(data: data, rva: dataRVA, sectionTable: sectionTable) {
let iconData = data.subdata(in: Int(offsetToData)..<Int(offsetToData + size))
if let rep = NSBitmapImageRep(data: iconData) {
icon = NSImage(size: rep.size)
icon.addRepresentation(rep)
let bitmapInfo = BitmapInfoHeader(data: data, offset: Int(offsetToData))
var infoSize = bitmapInfo.size
if bitmapInfo.size != 40 {
let iconData = data.subdata(in: Int(offsetToData)..<Int(offsetToData + size))
if let rep = NSBitmapImageRep(data: iconData) {
icon = NSImage(size: rep.size)
icon.addRepresentation(rep)
}
} else {
if bitmapInfo.colorFormat != .unknown {
icon = bitmapInfo.renderBitmap(data: data,
offset: Int(offsetToData + bitmapInfo.size))
}
}
} else {
print("Failed to resolve RVA")
Expand Down
4 changes: 2 additions & 2 deletions Whisky/Views/Bottle Views/BottleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ struct ShellLinkView: View {
}

if icons.count > 0 {
image = icons[0]
image = icons.max(by: { $0.size.height < $1.size.height })
}
} catch {
print(error)
Expand Down Expand Up @@ -345,7 +345,7 @@ struct ShortcutView: View {
}

if icons.count > 0 {
image = icons[0]
image = icons.max(by: { $0.size.height < $1.size.height })
}
} catch {
print(error)
Expand Down
2 changes: 1 addition & 1 deletion Whisky/Views/ProgramView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ struct ProgramView: View {
}

if icons.count > 0 {
image = icons[0]
image = icons.max(by: { $0.size.height < $1.size.height })
}
} catch {
print(error)
Expand Down

0 comments on commit fcdca97

Please sign in to comment.