From 925e552deacba1293905c3ad60199fa987804b60 Mon Sep 17 00:00:00 2001 From: "C.W. Betts" Date: Sat, 13 Jan 2024 03:18:20 -0700 Subject: [PATCH] Fix Bootleg cover generator's text being in the wrong place. Draw to an sRGB color space. --- Boxer.xcodeproj/project.pbxproj | 10 +++--- Boxer/BXGamebox.m | 2 +- Boxer/BootlegCoverArt.swift | 5 +-- Boxer/CoverArt.swift | 57 +++++++++++++++++++++++---------- 4 files changed, 49 insertions(+), 25 deletions(-) diff --git a/Boxer.xcodeproj/project.pbxproj b/Boxer.xcodeproj/project.pbxproj index 5eed1cc95..ca38386a1 100644 --- a/Boxer.xcodeproj/project.pbxproj +++ b/Boxer.xcodeproj/project.pbxproj @@ -1091,12 +1091,12 @@ 5514503824BE8DE50002CE28 /* envelope.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = envelope.cpp; sourceTree = ""; }; 5514503B24BE8FB90002CE28 /* program_autotype.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = program_autotype.cpp; sourceTree = ""; }; 5514503C24BE8FB90002CE28 /* program_autotype.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = program_autotype.h; sourceTree = ""; }; - 551B793625593573006C57CE /* URL+ADBFilesystemHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+ADBFilesystemHelpers.swift"; sourceTree = ""; }; + 551B793625593573006C57CE /* URL+ADBFilesystemHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+ADBFilesystemHelpers.swift"; sourceTree = ""; }; 55215F442AD22FD8007C7C68 /* MT32LCDDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MT32LCDDisplay.swift; sourceTree = ""; }; 5529311E252AF0DA0069EB35 /* soft_limiter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = soft_limiter.h; sourceTree = ""; }; 55296E982AD4B86400845EB5 /* DummyMIDIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyMIDIDevice.swift; sourceTree = ""; }; - 55419B67255A2F1400A779B2 /* URL+ADBQuickLookHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+ADBQuickLookHelpers.swift"; sourceTree = ""; }; - 55419B73255A7FFA00A779B2 /* URL+ADBAliasHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+ADBAliasHelpers.swift"; sourceTree = ""; }; + 55419B67255A2F1400A779B2 /* URL+ADBQuickLookHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+ADBQuickLookHelpers.swift"; sourceTree = ""; }; + 55419B73255A7FFA00A779B2 /* URL+ADBAliasHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+ADBAliasHelpers.swift"; sourceTree = ""; }; 55488508226A501000A879B3 /* drive_overlay.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = drive_overlay.cpp; sourceTree = ""; }; 554E2B6C1BFAF6720038C506 /* Shared.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Shared.xcassets; sourceTree = ""; }; 554E2B6F1BFAF6D90038C506 /* Boxer only.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Boxer only.xcassets"; sourceTree = ""; }; @@ -1113,7 +1113,7 @@ 555747F9208FA8030045E635 /* sn76496.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sn76496.cpp; sourceTree = ""; }; 555747FA208FA8030045E635 /* saa1099.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = saa1099.h; sourceTree = ""; }; 555747FB208FA8030045E635 /* ymf262.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ymf262.h; sourceTree = ""; }; - 5563E57E239EF7CD007A1600 /* ADBSwiftHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ADBSwiftHelpers.swift; sourceTree = ""; }; + 5563E57E239EF7CD007A1600 /* ADBSwiftHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ADBSwiftHelpers.swift; sourceTree = ""; }; 5565757423568DED003489A6 /* risc_x64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = risc_x64.h; sourceTree = ""; }; 55844D0626C70E4E00AA5924 /* pacer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = pacer.h; sourceTree = ""; }; 55844D0F26C70E7700AA5924 /* pacer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pacer.cpp; sourceTree = ""; }; @@ -1183,7 +1183,7 @@ 55BA63F124B051C300A53F4C /* risc_armv8le.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = risc_armv8le.h; sourceTree = ""; }; 55BBA4E6235EE141007AE319 /* Boxer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Boxer-Bridging-Header.h"; sourceTree = ""; }; 55BBA4E7235EE141007AE319 /* Boxer Standalone-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Boxer Standalone-Bridging-Header.h"; sourceTree = ""; }; - 55BBA4E8235EE141007AE319 /* NSError+ADBErrorHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSError+ADBErrorHelpers.swift"; sourceTree = ""; }; + 55BBA4E8235EE141007AE319 /* NSError+ADBErrorHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+ADBErrorHelpers.swift"; sourceTree = ""; }; 55BF6F0320210F6000F3FE59 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 55C3B28D25A808D8001EE655 /* midi_handler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = midi_handler.h; sourceTree = ""; }; 55D7B1942ADB821600B83750 /* BootlegCoverArt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BootlegCoverArt.swift; sourceTree = ""; }; diff --git a/Boxer/BXGamebox.m b/Boxer/BXGamebox.m index f197a9eb0..96a058ebd 100644 --- a/Boxer/BXGamebox.m +++ b/Boxer/BXGamebox.m @@ -379,7 +379,7 @@ - (NSImage *) coverArt - (void) setCoverArt: (NSImage *)image { - [[NSWorkspace sharedWorkspace] setIcon: image forFile: self.bundlePath options: 0]; + [[NSWorkspace sharedWorkspace] setIcon: image forFile: self.bundlePath options: NSExcludeQuickDrawElementsIconCreationOption]; } - (NSDictionary *) gameInfo diff --git a/Boxer/BootlegCoverArt.swift b/Boxer/BootlegCoverArt.swift index 53c51e314..b34d1aee7 100644 --- a/Boxer/BootlegCoverArt.swift +++ b/Boxer/BootlegCoverArt.swift @@ -38,7 +38,8 @@ class JewelCase : NSObject, BXBootlegCoverArt { if !textRegion.isEmpty { let textAttributes = type(of: self).textAttributes(for: iconSize) - title.draw(with: textRegion, attributes: textAttributes) + (title as NSString).draw(in: textRegion, withAttributes: textAttributes) + //TODO: use title.draw(with: textRegion, attributes: textAttributes) } if let topLayer { @@ -50,7 +51,7 @@ class JewelCase : NSObject, BXBootlegCoverArt { let frame = NSRect(origin: .zero, size: iconSize) //Create a new empty canvas to draw into - let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(iconSize.width*scale), pixelsHigh: Int(iconSize.height*scale), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 32)! + let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(iconSize.width*scale), pixelsHigh: Int(iconSize.height*scale), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 32)!.retagging(with: .sRGB)! rep.size = iconSize NSGraphicsContext.saveGraphicsState() diff --git a/Boxer/CoverArt.swift b/Boxer/CoverArt.swift index 483198715..35f1acfae 100644 --- a/Boxer/CoverArt.swift +++ b/Boxer/CoverArt.swift @@ -62,7 +62,7 @@ class CoverArt: NSObject { // @objc(drawInRect:) /// Draws the source image as cover art into the specified frame in the current graphics context. - func draw(in frame: NSRect) { + private func draw(in frame: NSRect) { //Switch to high-quality interpolation before we begin, and restore it once we're done //(this is not stored by saveGraphicsState/restoreGraphicsState unfortunately) let oldInterpolation = NSGraphicsContext.current?.imageInterpolation ?? .`default` @@ -130,10 +130,10 @@ class CoverArt: NSObject { // @objc(representationForSize:scale:) /// Returns a cover art image representation from the source image rendered at the specified size and scale. private func representation(for iconSize: NSSize, scale: CGFloat = 1) -> NSImageRep! { - var frame = NSRect(origin: .zero, size: iconSize) + let frame = NSRect(origin: .zero, size: iconSize) //Create a new empty canvas to draw into - let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(iconSize.width * scale), pixelsHigh: Int(iconSize.height * scale), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 32)! + let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(iconSize.width * scale), pixelsHigh: Int(iconSize.height * scale), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 32)!.retagging(with: .sRGB)! rep.size = iconSize NSGraphicsContext.saveGraphicsState() @@ -152,7 +152,7 @@ class CoverArt: NSObject { } /// Returns a cover art image rendered from the source image to 512, 256, 128 and 32x32 sizes, - /// suitable for use as an OS X icon. + /// suitable for use as a macOS icon. func coverArt() -> NSImage? { //If our source image could not be read, then bail out. guard let image = sourceImage, image.isValid else { @@ -161,7 +161,7 @@ class CoverArt: NSObject { //If our source image already has transparency data, //then assume that it already has effects of its own applied and don't process it. - if type(of: self).imageHasTransparency(image) { + if imageHasTransparency(image) { return image } @@ -178,7 +178,7 @@ class CoverArt: NSObject { } /// Returns a cover art image rendered from the specified image to 512, 256, 128 and 32x32 sizes, - /// suitable for use as an OS X icon. + /// suitable for use as a macOS icon. /// /// Note that this returns an NSImage directly, not a `CoverArt` instance. @objc(coverArtWithImage:) @@ -186,15 +186,38 @@ class CoverArt: NSObject { let generator = self.init(sourceImage: image) return generator.coverArt() } - - /// Returns whether the specified image appears to contain actual transparent/translucent pixels. - /// This is distinct from whether it has an alpha channel, as the alpha channel may go unused - /// (e.g. in an opaque image saved as 32-bit PNG.) - private class func imageHasTransparency(_ image: NSImage) -> Bool { - var hasTranslucentPixels = false +} - //Only bother testing transparency if the image has an alpha channel - if image.representations.last?.hasAlpha ?? false { +/// Returns whether the specified image appears to contain actual transparent/translucent pixels. +/// This is distinct from whether it has an alpha channel, as the alpha channel may go unused +/// (e.g. in an opaque image saved as 32-bit PNG.) +private func imageHasTransparency(_ image: NSImage) -> Bool { + var hasTranslucentPixels = false + + //Only bother testing transparency if the image has an alpha channel + if image.representations.last?.hasAlpha ?? false { + if let bir = image.representations.last as? NSBitmapImageRep { + let imageSize = bir.size + let imageWidth = bir.pixelsWide + let imageHigh = bir.pixelsHigh + + //Test 5 pixels in an X pattern: each corner and right in the center of the image. + let testPoints: [(x: Int, y: Int)] = [ + (0, 0), + (imageWidth - 1, 0), + (0, imageHigh - 1), + (imageWidth - 1, imageHigh - 1), + (imageWidth / 2, imageHigh / 2) + ] + + for (x, y) in testPoints { + //If any of the pixels appears to be translucent, then stop looking further. + if let pixel = bir.colorAt(x: x, y: y), pixel.alphaComponent < 0.9 { + hasTranslucentPixels = true + break + } + } + } else { let imageSize = image.size //Test 5 pixels in an X pattern: each corner and right in the center of the image. @@ -205,7 +228,7 @@ class CoverArt: NSObject { NSMakePoint(imageSize.width - 1.0, imageSize.height - 1.0), NSMakePoint(imageSize.width * 0.5, imageSize.height * 0.5) ] - + image.lockFocus() for point in testPoints { //If any of the pixels appears to be translucent, then stop looking further. @@ -216,7 +239,7 @@ class CoverArt: NSObject { } image.unlockFocus() } - - return hasTranslucentPixels } + + return hasTranslucentPixels }