diff --git a/CoreUtil/CoreUtil.xcodeproj/project.pbxproj b/CoreUtil/CoreUtil.xcodeproj/project.pbxproj index ce75eb7..48a92e1 100644 --- a/CoreUtil/CoreUtil.xcodeproj/project.pbxproj +++ b/CoreUtil/CoreUtil.xcodeproj/project.pbxproj @@ -49,6 +49,7 @@ B680A8A027A8DA78007CB707 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = B680A89F27A8DA78007CB707 /* OrderedCollections */; }; B680A8A227A8DA78007CB707 /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = B680A8A127A8DA78007CB707 /* DequeModule */; }; B680A8A427A8DA78007CB707 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = B680A8A327A8DA78007CB707 /* Collections */; }; + B6A93D2B27CBA2EB003A6D7F /* Zip3Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A93D2A27CBA2EB003A6D7F /* Zip3Sequence.swift */; }; B6AC27A327AA6F5C000FD713 /* Reachability+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AC27A127AA6F5B000FD713 /* Reachability+Publisher.swift */; }; B6AC27A427AA6F5C000FD713 /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AC27A227AA6F5C000FD713 /* Reachability.swift */; }; B6B5727A27AC223A0069DBA7 /* RestorableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5727927AC223A0069DBA7 /* RestorableState.swift */; }; @@ -99,6 +100,7 @@ B680A79027A681F8007CB707 /* NSTextField+Combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTextField+Combine.swift"; sourceTree = ""; }; B680A86427A8C58C007CB707 /* Combine+Peek.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Combine+Peek.swift"; sourceTree = ""; }; B680A89B27A8DA16007CB707 /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; + B6A93D2A27CBA2EB003A6D7F /* Zip3Sequence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Zip3Sequence.swift; sourceTree = ""; }; B6AC27A127AA6F5B000FD713 /* Reachability+Publisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Reachability+Publisher.swift"; sourceTree = ""; }; B6AC27A227AA6F5C000FD713 /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = ""; }; B6B5727927AC223A0069DBA7 /* RestorableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorableState.swift; sourceTree = ""; }; @@ -218,6 +220,7 @@ B64B1F6D27A4FC9100AC2601 /* Class */ = { isa = PBXGroup; children = ( + B6A93D2A27CBA2EB003A6D7F /* Zip3Sequence.swift */, B6AC27A227AA6F5C000FD713 /* Reachability.swift */, B6AC27A127AA6F5B000FD713 /* Reachability+Publisher.swift */, B64B201027A50E5200AC2601 /* NSColorView.swift */, @@ -382,6 +385,7 @@ B64B1F1827A4F80800AC2601 /* Ex+CGPoint.swift in Sources */, B680A86527A8C58C007CB707 /* Combine+Peek.swift in Sources */, B64B1ED027A4F71200AC2601 /* ViewPlaceholder.swift in Sources */, + B6A93D2B27CBA2EB003A6D7F /* Zip3Sequence.swift in Sources */, B64B1F3127A4F83500AC2601 /* Ex+Image.swift in Sources */, B64B1ED627A4F72D00AC2601 /* Query.swift in Sources */, B64B1F2B27A4F83500AC2601 /* Ex+UI.swift in Sources */, diff --git a/CoreUtil/CoreUtil/Class/Zip3Sequence.swift b/CoreUtil/CoreUtil/Class/Zip3Sequence.swift new file mode 100644 index 0000000..6920d5f --- /dev/null +++ b/CoreUtil/CoreUtil/Class/Zip3Sequence.swift @@ -0,0 +1,69 @@ +// +// Zip3Sequence.swift +// CoreUtil +// +// Created by yuki on 2022/01/22. +// Copyright © 2022 yuki. All rights reserved. +// + +public func zip(_ a: A, _ b: B, _ c: C) -> Zip3Sequence { + Zip3Sequence(a, b, c) +} +public func zip(_ a: A, _ b: B, _ c: C, _ d: D) -> Zip4Sequence { + Zip4Sequence(a, b, c, d) +} + +public struct Zip3Sequence: Sequence { + public typealias Element = (A.Element, B.Element, C.Element) + + public let a: A + public let b: B + public let c: C + + public struct Iterator: IteratorProtocol { + var a: A.Iterator + var b: B.Iterator + var c: C.Iterator + + mutating public func next() -> Element? { + if let a = a.next(), let b = b.next(), let c = c.next() { return (a, b, c) }; return nil + } + } + + public func makeIterator() -> Iterator { Iterator(a: a.makeIterator(), b: b.makeIterator(), c: c.makeIterator()) } + + init(_ a: A, _ b: B, _ c: C) { + self.a = a + self.b = b + self.c = c + } +} + +public struct Zip4Sequence: Sequence { + public typealias Element = (A.Element, B.Element, C.Element, D.Element) + + public let a: A + public let b: B + public let c: C + public let d: D + + public struct Iterator: IteratorProtocol { + var a: A.Iterator + var b: B.Iterator + var c: C.Iterator + var d: D.Iterator + + mutating public func next() -> Element? { + if let a = a.next(), let b = b.next(), let c = c.next(), let d = d.next() { return (a, b, c, d) }; return nil + } + } + + public func makeIterator() -> Iterator { Iterator(a: a.makeIterator(), b: b.makeIterator(), c: c.makeIterator(), d: d.makeIterator()) } + + init(_ a: A, _ b: B, _ c: C, _ d: D) { + self.a = a + self.b = b + self.c = c + self.d = d + } +} diff --git a/CoreUtil/CoreUtil/Extensions/UI/Ex+Image.swift b/CoreUtil/CoreUtil/Extensions/UI/Ex+Image.swift index ee00f9d..bbaba6f 100644 --- a/CoreUtil/CoreUtil/Extensions/UI/Ex+Image.swift +++ b/CoreUtil/CoreUtil/Extensions/UI/Ex+Image.swift @@ -14,3 +14,17 @@ extension NSImage { return cgImage(forProposedRect: &imageRect, context: nil, hints: nil) } } + + +extension CGImage { + public func convertToGrayscale() -> CGImage { + let imageRect = CGRect(size: CGSize(width: width, height: height)) + let context = CGContext( + data: nil, width: self.width, height: self.height, + bitsPerComponent: 8, bytesPerRow: 0, + space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue + )! + context.draw(self, in: imageRect) + return context.makeImage()! + } +} diff --git a/DevToys/DevToys.xcodeproj/project.pbxproj b/DevToys/DevToys.xcodeproj/project.pbxproj index 8846473..f080a53 100644 --- a/DevToys/DevToys.xcodeproj/project.pbxproj +++ b/DevToys/DevToys.xcodeproj/project.pbxproj @@ -105,6 +105,29 @@ B684EA2627C5EFDB0014802F /* DiffMatchPatch in Frameworks */ = {isa = PBXBuildFile; productRef = B684EA2527C5EFDB0014802F /* DiffMatchPatch */; }; B684EA2827C5EFE90014802F /* TextDiffView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B684EA2727C5EFE90014802F /* TextDiffView+.swift */; }; B684EA2A27C609620014802F /* QRCodeGeneratorView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B684EA2927C609620014802F /* QRCodeGeneratorView+.swift */; }; + B69980D427CCCF0A0063F63D /* android_mask.png in Resources */ = {isa = PBXBuildFile; fileRef = B69980D327CCCF0A0063F63D /* android_mask.png */; }; + B69F0E8327CBC2100032F96A /* folder_back_64_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E7C27CBC20F0032F96A /* folder_back_64_bs.png */; }; + B69F0E8427CBC2100032F96A /* folder_back_128_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E7D27CBC20F0032F96A /* folder_back_128_bs.png */; }; + B69F0E8527CBC2100032F96A /* folder_back_256_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E7E27CBC20F0032F96A /* folder_back_256_bs.png */; }; + B69F0E8627CBC2100032F96A /* folder_back_1024_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E7F27CBC20F0032F96A /* folder_back_1024_bs.png */; }; + B69F0E8727CBC2100032F96A /* folder_back_32_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8027CBC20F0032F96A /* folder_back_32_bs.png */; }; + B69F0E8827CBC2100032F96A /* folder_back_16_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8127CBC20F0032F96A /* folder_back_16_bs.png */; }; + B69F0E8927CBC2100032F96A /* folder_back_512_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8227CBC20F0032F96A /* folder_back_512_bs.png */; }; + B69F0E9127CBC2340032F96A /* folder_mask2_1024_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8B27CBC2340032F96A /* folder_mask2_1024_bs.png */; }; + B69F0E9227CBC2340032F96A /* folder_mask2_512_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8C27CBC2340032F96A /* folder_mask2_512_bs.png */; }; + B69F0E9327CBC2340032F96A /* folder_mask2_16_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8D27CBC2340032F96A /* folder_mask2_16_bs.png */; }; + B69F0E9427CBC2340032F96A /* folder_mask2_32_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8E27CBC2340032F96A /* folder_mask2_32_bs.png */; }; + B69F0E9527CBC2340032F96A /* folder_mask2_64_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E8F27CBC2340032F96A /* folder_mask2_64_bs.png */; }; + B69F0E9627CBC2340032F96A /* folder_mask2_128_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E9027CBC2340032F96A /* folder_mask2_128_bs.png */; }; + B69F0E9827CBC2800032F96A /* folder_mask2_256_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E9727CBC2800032F96A /* folder_mask2_256_bs.png */; }; + B69F0E9C27CBC7550032F96A /* folder_top_1024.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E9A27CBC7550032F96A /* folder_top_1024.png */; }; + B69F0E9D27CBC7550032F96A /* folder_top_512.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0E9B27CBC7550032F96A /* folder_top_512.png */; }; + B69F0E9F27CC76A50032F96A /* IconImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69F0E9E27CC76A50032F96A /* IconImageManager.swift */; }; + B69F0EA627CC7CF40032F96A /* watermark_mask_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0EA427CC7CF40032F96A /* watermark_mask_bs.png */; }; + B69F0EA727CC7CF40032F96A /* watermark_mask_dark_bs.png in Resources */ = {isa = PBXBuildFile; fileRef = B69F0EA527CC7CF40032F96A /* watermark_mask_dark_bs.png */; }; + B6A93D3227CBBC97003A6D7F /* IconTemplete+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A93D2F27CBBC97003A6D7F /* IconTemplete+.swift */; }; + B6A93D3327CBBC97003A6D7F /* IconSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A93D3027CBBC97003A6D7F /* IconSet.swift */; }; + B6A93D3427CBBC97003A6D7F /* IconTemplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A93D3127CBBC97003A6D7F /* IconTemplete.swift */; }; B6AC273027AA1373000FD713 /* optipng in Resources */ = {isa = PBXBuildFile; fileRef = B6AC272F27AA1373000FD713 /* optipng */; }; B6AC273227AA2BF6000FD713 /* ImageOptimizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AC273127AA2BF6000FD713 /* ImageOptimizer.swift */; }; B6AC276527AA3D61000FD713 /* jpegoptim in Resources */ = {isa = PBXBuildFile; fileRef = B6AC276427AA3D61000FD713 /* jpegoptim */; }; @@ -125,6 +148,30 @@ B6BD80F427B8AEE700152AB9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6BD80F627B8AEE700152AB9 /* Localizable.strings */; }; B6C5292427C20D520019BCB0 /* GifConverterView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C5292327C20D520019BCB0 /* GifConverterView+.swift */; }; B6C5292727C20DC60019BCB0 /* FFMpegExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C5292627C20DC60019BCB0 /* FFMpegExecutor.swift */; }; + B6CBFEF927CCA1B000902E56 /* squircle_bg_mask_256x256.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEE827CCA1B000902E56 /* squircle_bg_mask_256x256.png */; }; + B6CBFEFA27CCA1B000902E56 /* squircle_bg_mask_1024x1024.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEE927CCA1B000902E56 /* squircle_bg_mask_1024x1024.png */; }; + B6CBFEFB27CCA1B000902E56 /* squircle_back_128x128.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEEA27CCA1B000902E56 /* squircle_back_128x128.png */; }; + B6CBFEFC27CCA1B000902E56 /* squircle_back_1024x1024.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEEB27CCA1B000902E56 /* squircle_back_1024x1024.png */; }; + B6CBFEFD27CCA1B000902E56 /* squircle_back_16x16.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEEC27CCA1B000902E56 /* squircle_back_16x16.png */; }; + B6CBFEFE27CCA1B000902E56 /* squircle_mask_256x256.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEED27CCA1B000902E56 /* squircle_mask_256x256.png */; }; + B6CBFEFF27CCA1B000902E56 /* squircle_mask_16x16.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEEE27CCA1B000902E56 /* squircle_mask_16x16.png */; }; + B6CBFF0027CCA1B000902E56 /* squircle_back_512x512.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEEF27CCA1B000902E56 /* squircle_back_512x512.png */; }; + B6CBFF0127CCA1B000902E56 /* squircle_mask_64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF027CCA1B000902E56 /* squircle_mask_64x64.png */; }; + B6CBFF0227CCA1B000902E56 /* squircle_mask_128x128.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF127CCA1B000902E56 /* squircle_mask_128x128.png */; }; + B6CBFF0327CCA1B000902E56 /* squircle_mask_32x32.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF227CCA1B000902E56 /* squircle_mask_32x32.png */; }; + B6CBFF0427CCA1B000902E56 /* squircle_bg_mask_512x512.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF327CCA1B000902E56 /* squircle_bg_mask_512x512.png */; }; + B6CBFF0527CCA1B000902E56 /* squircle_mask_1024x1024.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF427CCA1B000902E56 /* squircle_mask_1024x1024.png */; }; + B6CBFF0627CCA1B000902E56 /* squircle_back_32x32.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF527CCA1B000902E56 /* squircle_back_32x32.png */; }; + B6CBFF0727CCA1B000902E56 /* squircle_back_256x256.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF627CCA1B000902E56 /* squircle_back_256x256.png */; }; + B6CBFF0827CCA1B000902E56 /* squircle_back_64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF727CCA1B000902E56 /* squircle_back_64x64.png */; }; + B6CBFF0927CCA1B000902E56 /* squircle_mask_512x512.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFEF827CCA1B000902E56 /* squircle_mask_512x512.png */; }; + B6CBFF1727CCA88F00902E56 /* external_256x256.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF0D27CCA88E00902E56 /* external_256x256.png */; }; + B6CBFF1827CCA88F00902E56 /* external_128x128.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF0E27CCA88E00902E56 /* external_128x128.png */; }; + B6CBFF1927CCA88F00902E56 /* external_64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF0F27CCA88E00902E56 /* external_64x64.png */; }; + B6CBFF1A27CCA88F00902E56 /* external_16x16.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF1027CCA88E00902E56 /* external_16x16.png */; }; + B6CBFF1B27CCA88F00902E56 /* external_32x32.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF1127CCA88E00902E56 /* external_32x32.png */; }; + B6CBFF1D27CCA88F00902E56 /* external_512x512.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF1327CCA88E00902E56 /* external_512x512.png */; }; + B6CBFF1F27CCA88F00902E56 /* external_1024x1024.png in Resources */ = {isa = PBXBuildFile; fileRef = B6CBFF1527CCA88F00902E56 /* external_1024x1024.png */; }; B6D1AEDF27A534960022FED2 /* BodyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AEDE27A534960022FED2 /* BodyViewController.swift */; }; B6D1AEE427A53A560022FED2 /* HomeView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AEE327A53A560022FED2 /* HomeView+.swift */; }; B6D1AEE927A545230022FED2 /* Area.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D1AEE827A545230022FED2 /* Area.swift */; }; @@ -279,6 +326,29 @@ B680A8A927A8E106007CB707 /* HashGeneratorView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashGeneratorView+.swift"; sourceTree = ""; }; B684EA2727C5EFE90014802F /* TextDiffView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextDiffView+.swift"; sourceTree = ""; }; B684EA2927C609620014802F /* QRCodeGeneratorView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QRCodeGeneratorView+.swift"; sourceTree = ""; }; + B69980D327CCCF0A0063F63D /* android_mask.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = android_mask.png; sourceTree = ""; }; + B69F0E7C27CBC20F0032F96A /* folder_back_64_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_64_bs.png; sourceTree = ""; }; + B69F0E7D27CBC20F0032F96A /* folder_back_128_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_128_bs.png; sourceTree = ""; }; + B69F0E7E27CBC20F0032F96A /* folder_back_256_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_256_bs.png; sourceTree = ""; }; + B69F0E7F27CBC20F0032F96A /* folder_back_1024_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_1024_bs.png; sourceTree = ""; }; + B69F0E8027CBC20F0032F96A /* folder_back_32_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_32_bs.png; sourceTree = ""; }; + B69F0E8127CBC20F0032F96A /* folder_back_16_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_16_bs.png; sourceTree = ""; }; + B69F0E8227CBC20F0032F96A /* folder_back_512_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_back_512_bs.png; sourceTree = ""; }; + B69F0E8B27CBC2340032F96A /* folder_mask2_1024_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_1024_bs.png; sourceTree = ""; }; + B69F0E8C27CBC2340032F96A /* folder_mask2_512_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_512_bs.png; sourceTree = ""; }; + B69F0E8D27CBC2340032F96A /* folder_mask2_16_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_16_bs.png; sourceTree = ""; }; + B69F0E8E27CBC2340032F96A /* folder_mask2_32_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_32_bs.png; sourceTree = ""; }; + B69F0E8F27CBC2340032F96A /* folder_mask2_64_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_64_bs.png; sourceTree = ""; }; + B69F0E9027CBC2340032F96A /* folder_mask2_128_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_128_bs.png; sourceTree = ""; }; + B69F0E9727CBC2800032F96A /* folder_mask2_256_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_mask2_256_bs.png; sourceTree = ""; }; + B69F0E9A27CBC7550032F96A /* folder_top_1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_top_1024.png; sourceTree = ""; }; + B69F0E9B27CBC7550032F96A /* folder_top_512.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_top_512.png; sourceTree = ""; }; + B69F0E9E27CC76A50032F96A /* IconImageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImageManager.swift; sourceTree = ""; }; + B69F0EA427CC7CF40032F96A /* watermark_mask_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = watermark_mask_bs.png; sourceTree = ""; }; + B69F0EA527CC7CF40032F96A /* watermark_mask_dark_bs.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = watermark_mask_dark_bs.png; sourceTree = ""; }; + B6A93D2F27CBBC97003A6D7F /* IconTemplete+.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IconTemplete+.swift"; sourceTree = ""; }; + B6A93D3027CBBC97003A6D7F /* IconSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IconSet.swift; sourceTree = ""; }; + B6A93D3127CBBC97003A6D7F /* IconTemplete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IconTemplete.swift; sourceTree = ""; }; B6AC272F27AA1373000FD713 /* optipng */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = optipng; sourceTree = ""; }; B6AC273127AA2BF6000FD713 /* ImageOptimizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageOptimizer.swift; sourceTree = ""; }; B6AC276427AA3D61000FD713 /* jpegoptim */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = jpegoptim; sourceTree = ""; }; @@ -299,6 +369,30 @@ B6BD80F827B8AF1800152AB9 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; B6C5292327C20D520019BCB0 /* GifConverterView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GifConverterView+.swift"; sourceTree = ""; }; B6C5292627C20DC60019BCB0 /* FFMpegExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FFMpegExecutor.swift; sourceTree = ""; }; + B6CBFEE827CCA1B000902E56 /* squircle_bg_mask_256x256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_bg_mask_256x256.png; sourceTree = ""; }; + B6CBFEE927CCA1B000902E56 /* squircle_bg_mask_1024x1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_bg_mask_1024x1024.png; sourceTree = ""; }; + B6CBFEEA27CCA1B000902E56 /* squircle_back_128x128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_128x128.png; sourceTree = ""; }; + B6CBFEEB27CCA1B000902E56 /* squircle_back_1024x1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_1024x1024.png; sourceTree = ""; }; + B6CBFEEC27CCA1B000902E56 /* squircle_back_16x16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_16x16.png; sourceTree = ""; }; + B6CBFEED27CCA1B000902E56 /* squircle_mask_256x256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_256x256.png; sourceTree = ""; }; + B6CBFEEE27CCA1B000902E56 /* squircle_mask_16x16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_16x16.png; sourceTree = ""; }; + B6CBFEEF27CCA1B000902E56 /* squircle_back_512x512.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_512x512.png; sourceTree = ""; }; + B6CBFEF027CCA1B000902E56 /* squircle_mask_64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_64x64.png; sourceTree = ""; }; + B6CBFEF127CCA1B000902E56 /* squircle_mask_128x128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_128x128.png; sourceTree = ""; }; + B6CBFEF227CCA1B000902E56 /* squircle_mask_32x32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_32x32.png; sourceTree = ""; }; + B6CBFEF327CCA1B000902E56 /* squircle_bg_mask_512x512.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_bg_mask_512x512.png; sourceTree = ""; }; + B6CBFEF427CCA1B000902E56 /* squircle_mask_1024x1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_1024x1024.png; sourceTree = ""; }; + B6CBFEF527CCA1B000902E56 /* squircle_back_32x32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_32x32.png; sourceTree = ""; }; + B6CBFEF627CCA1B000902E56 /* squircle_back_256x256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_256x256.png; sourceTree = ""; }; + B6CBFEF727CCA1B000902E56 /* squircle_back_64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_back_64x64.png; sourceTree = ""; }; + B6CBFEF827CCA1B000902E56 /* squircle_mask_512x512.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = squircle_mask_512x512.png; sourceTree = ""; }; + B6CBFF0D27CCA88E00902E56 /* external_256x256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_256x256.png; sourceTree = ""; }; + B6CBFF0E27CCA88E00902E56 /* external_128x128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_128x128.png; sourceTree = ""; }; + B6CBFF0F27CCA88E00902E56 /* external_64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_64x64.png; sourceTree = ""; }; + B6CBFF1027CCA88E00902E56 /* external_16x16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_16x16.png; sourceTree = ""; }; + B6CBFF1127CCA88E00902E56 /* external_32x32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_32x32.png; sourceTree = ""; }; + B6CBFF1327CCA88E00902E56 /* external_512x512.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_512x512.png; sourceTree = ""; }; + B6CBFF1527CCA88F00902E56 /* external_1024x1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = external_1024x1024.png; sourceTree = ""; }; B6D1AEDE27A534960022FED2 /* BodyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyViewController.swift; sourceTree = ""; }; B6D1AEE327A53A560022FED2 /* HomeView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeView+.swift"; sourceTree = ""; }; B6D1AEE827A545230022FED2 /* Area.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Area.swift; sourceTree = ""; }; @@ -524,6 +618,8 @@ B6789EDD27CA5F5400D8B58C /* Generators */ = { isa = PBXGroup; children = ( + B6A93D2E27CBBC97003A6D7F /* Icon Templete */, + B6789EE827CB3D6300D8B58C /* Resource */, B6789EBB27CA48A300D8B58C /* IOSIconGenerator.swift */, B6789EDE27CA5F5D00D8B58C /* IconsetGenerator.swift */, B6789EE027CA5F6900D8B58C /* IcnsGenerator.swift */, @@ -534,6 +630,14 @@ path = Generators; sourceTree = ""; }; + B6789EE827CB3D6300D8B58C /* Resource */ = { + isa = PBXGroup; + children = ( + B6A93CC527CB9C0D003A6D7F /* Folder */, + ); + path = Resource; + sourceTree = ""; + }; B67A4C0127C0B9FF009277FB /* Color Picker */ = { isa = PBXGroup; children = ( @@ -614,6 +718,69 @@ path = Generator; sourceTree = ""; }; + B69F0E7B27CBC20F0032F96A /* folder_back */ = { + isa = PBXGroup; + children = ( + B69F0E8127CBC20F0032F96A /* folder_back_16_bs.png */, + B69F0E8027CBC20F0032F96A /* folder_back_32_bs.png */, + B69F0E7C27CBC20F0032F96A /* folder_back_64_bs.png */, + B69F0E7D27CBC20F0032F96A /* folder_back_128_bs.png */, + B69F0E7E27CBC20F0032F96A /* folder_back_256_bs.png */, + B69F0E8227CBC20F0032F96A /* folder_back_512_bs.png */, + B69F0E7F27CBC20F0032F96A /* folder_back_1024_bs.png */, + ); + path = folder_back; + sourceTree = ""; + }; + B69F0E8A27CBC2340032F96A /* folder_mask */ = { + isa = PBXGroup; + children = ( + B69F0E8D27CBC2340032F96A /* folder_mask2_16_bs.png */, + B69F0E8E27CBC2340032F96A /* folder_mask2_32_bs.png */, + B69F0E8F27CBC2340032F96A /* folder_mask2_64_bs.png */, + B69F0E9027CBC2340032F96A /* folder_mask2_128_bs.png */, + B69F0E9727CBC2800032F96A /* folder_mask2_256_bs.png */, + B69F0E8C27CBC2340032F96A /* folder_mask2_512_bs.png */, + B69F0E8B27CBC2340032F96A /* folder_mask2_1024_bs.png */, + ); + path = folder_mask; + sourceTree = ""; + }; + B69F0E9927CBC7550032F96A /* folder_top */ = { + isa = PBXGroup; + children = ( + B69F0E9A27CBC7550032F96A /* folder_top_1024.png */, + B69F0E9B27CBC7550032F96A /* folder_top_512.png */, + ); + path = folder_top; + sourceTree = ""; + }; + B6A93CC527CB9C0D003A6D7F /* Folder */ = { + isa = PBXGroup; + children = ( + B69980D327CCCF0A0063F63D /* android_mask.png */, + B6CBFF0B27CCA88100902E56 /* external_drive */, + B69F0EA427CC7CF40032F96A /* watermark_mask_bs.png */, + B69F0EA527CC7CF40032F96A /* watermark_mask_dark_bs.png */, + B6CBFEE727CCA1B000902E56 /* big_sur_icon */, + B69F0E9927CBC7550032F96A /* folder_top */, + B69F0E8A27CBC2340032F96A /* folder_mask */, + B69F0E7B27CBC20F0032F96A /* folder_back */, + ); + path = Folder; + sourceTree = ""; + }; + B6A93D2E27CBBC97003A6D7F /* Icon Templete */ = { + isa = PBXGroup; + children = ( + B6A93D2F27CBBC97003A6D7F /* IconTemplete+.swift */, + B6A93D3027CBBC97003A6D7F /* IconSet.swift */, + B6A93D3127CBBC97003A6D7F /* IconTemplete.swift */, + B69F0E9E27CC76A50032F96A /* IconImageManager.swift */, + ); + path = "Icon Templete"; + sourceTree = ""; + }; B6AC276827AA532B000FD713 /* Image Optimizer */ = { isa = PBXGroup; children = ( @@ -667,6 +834,44 @@ path = ffmpeg; sourceTree = ""; }; + B6CBFEE727CCA1B000902E56 /* big_sur_icon */ = { + isa = PBXGroup; + children = ( + B6CBFEE827CCA1B000902E56 /* squircle_bg_mask_256x256.png */, + B6CBFEE927CCA1B000902E56 /* squircle_bg_mask_1024x1024.png */, + B6CBFEEA27CCA1B000902E56 /* squircle_back_128x128.png */, + B6CBFEEB27CCA1B000902E56 /* squircle_back_1024x1024.png */, + B6CBFEEC27CCA1B000902E56 /* squircle_back_16x16.png */, + B6CBFEED27CCA1B000902E56 /* squircle_mask_256x256.png */, + B6CBFEEE27CCA1B000902E56 /* squircle_mask_16x16.png */, + B6CBFEEF27CCA1B000902E56 /* squircle_back_512x512.png */, + B6CBFEF027CCA1B000902E56 /* squircle_mask_64x64.png */, + B6CBFEF127CCA1B000902E56 /* squircle_mask_128x128.png */, + B6CBFEF227CCA1B000902E56 /* squircle_mask_32x32.png */, + B6CBFEF327CCA1B000902E56 /* squircle_bg_mask_512x512.png */, + B6CBFEF427CCA1B000902E56 /* squircle_mask_1024x1024.png */, + B6CBFEF527CCA1B000902E56 /* squircle_back_32x32.png */, + B6CBFEF627CCA1B000902E56 /* squircle_back_256x256.png */, + B6CBFEF727CCA1B000902E56 /* squircle_back_64x64.png */, + B6CBFEF827CCA1B000902E56 /* squircle_mask_512x512.png */, + ); + path = big_sur_icon; + sourceTree = ""; + }; + B6CBFF0B27CCA88100902E56 /* external_drive */ = { + isa = PBXGroup; + children = ( + B6CBFF1027CCA88E00902E56 /* external_16x16.png */, + B6CBFF1127CCA88E00902E56 /* external_32x32.png */, + B6CBFF0F27CCA88E00902E56 /* external_64x64.png */, + B6CBFF0E27CCA88E00902E56 /* external_128x128.png */, + B6CBFF0D27CCA88E00902E56 /* external_256x256.png */, + B6CBFF1327CCA88E00902E56 /* external_512x512.png */, + B6CBFF1527CCA88F00902E56 /* external_1024x1024.png */, + ); + path = external_drive; + sourceTree = ""; + }; B6D1AEDD27A534850022FED2 /* Body */ = { isa = PBXGroup; children = ( @@ -824,15 +1029,58 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + B69F0E8927CBC2100032F96A /* folder_back_512_bs.png in Resources */, B6E15A9427B6745100DC4D6B /* libimageoptimjpeg.dylib in Resources */, + B69F0E9C27CBC7550032F96A /* folder_top_1024.png in Resources */, + B69F0E8527CBC2100032F96A /* folder_back_256_bs.png in Resources */, B63A034D27C21A99009FD2AD /* ffmpeg in Resources */, B6BD80F427B8AEE700152AB9 /* Localizable.strings in Resources */, + B6CBFF1927CCA88F00902E56 /* external_64x64.png in Resources */, + B6CBFF0827CCA1B000902E56 /* squircle_back_64x64.png in Resources */, + B69F0E8627CBC2100032F96A /* folder_back_1024_bs.png in Resources */, + B6CBFF1D27CCA88F00902E56 /* external_512x512.png in Resources */, + B6CBFF0727CCA1B000902E56 /* squircle_back_256x256.png in Resources */, + B6CBFEFD27CCA1B000902E56 /* squircle_back_16x16.png in Resources */, + B6CBFF1B27CCA88F00902E56 /* external_32x32.png in Resources */, + B69F0E9127CBC2340032F96A /* folder_mask2_1024_bs.png in Resources */, + B6CBFF0027CCA1B000902E56 /* squircle_back_512x512.png in Resources */, + B69F0E9D27CBC7550032F96A /* folder_top_512.png in Resources */, B627295227BFB8CB0034D70C /* cwebp in Resources */, + B69F0E8827CBC2100032F96A /* folder_back_16_bs.png in Resources */, + B6CBFEFE27CCA1B000902E56 /* squircle_mask_256x256.png in Resources */, + B69F0EA627CC7CF40032F96A /* watermark_mask_bs.png in Resources */, + B69F0E8727CBC2100032F96A /* folder_back_32_bs.png in Resources */, + B69F0EA727CC7CF40032F96A /* watermark_mask_dark_bs.png in Resources */, + B69F0E9427CBC2340032F96A /* folder_mask2_32_bs.png in Resources */, + B69980D427CCCF0A0063F63D /* android_mask.png in Resources */, + B6CBFEFA27CCA1B000902E56 /* squircle_bg_mask_1024x1024.png in Resources */, + B69F0E9227CBC2340032F96A /* folder_mask2_512_bs.png in Resources */, + B6CBFEFB27CCA1B000902E56 /* squircle_back_128x128.png in Resources */, + B6CBFF1727CCA88F00902E56 /* external_256x256.png in Resources */, + B6CBFF0627CCA1B000902E56 /* squircle_back_32x32.png in Resources */, + B6CBFEFF27CCA1B000902E56 /* squircle_mask_16x16.png in Resources */, + B6CBFF0527CCA1B000902E56 /* squircle_mask_1024x1024.png in Resources */, + B6CBFF0927CCA1B000902E56 /* squircle_mask_512x512.png in Resources */, B6AC276527AA3D61000FD713 /* jpegoptim in Resources */, + B6CBFF0427CCA1B000902E56 /* squircle_bg_mask_512x512.png in Resources */, + B69F0E9327CBC2340032F96A /* folder_mask2_16_bs.png in Resources */, + B6CBFF0327CCA1B000902E56 /* squircle_mask_32x32.png in Resources */, + B6CBFF0127CCA1B000902E56 /* squircle_mask_64x64.png in Resources */, B6AC273027AA1373000FD713 /* optipng in Resources */, + B6CBFF1827CCA88F00902E56 /* external_128x128.png in Resources */, + B6CBFF1F27CCA88F00902E56 /* external_1024x1024.png in Resources */, + B6CBFEFC27CCA1B000902E56 /* squircle_back_1024x1024.png in Resources */, + B69F0E9627CBC2340032F96A /* folder_mask2_128_bs.png in Resources */, + B6CBFF1A27CCA88F00902E56 /* external_16x16.png in Resources */, B64B1E7827A4F5E300AC2601 /* Assets.xcassets in Resources */, B67A4C5727C0EA84009277FB /* ACOverlayController.xib in Resources */, + B69F0E8427CBC2100032F96A /* folder_back_128_bs.png in Resources */, + B6CBFEF927CCA1B000902E56 /* squircle_bg_mask_256x256.png in Resources */, B64B1E7B27A4F5E300AC2601 /* Main.storyboard in Resources */, + B69F0E8327CBC2100032F96A /* folder_back_64_bs.png in Resources */, + B6CBFF0227CCA1B000902E56 /* squircle_mask_128x128.png in Resources */, + B69F0E9827CBC2800032F96A /* folder_mask2_256_bs.png in Resources */, + B69F0E9527CBC2340032F96A /* folder_mask2_64_bs.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -863,6 +1111,8 @@ B67A4C0C27C0BA00009277FB /* ColorBoxView.swift in Sources */, B6B5726427AC14D10069DBA7 /* RegexTextView.swift in Sources */, B67A4C5227C0EA84009277FB /* ShowAndHideCursor.m in Sources */, + B6A93D3227CBBC97003A6D7F /* IconTemplete+.swift in Sources */, + B69F0E9F27CC76A50032F96A /* IconImageManager.swift in Sources */, B6789EBA27CA43E200D8B58C /* IconGeneratorView+.swift in Sources */, B680A83327A8BF7D007CB707 /* TextViewSection.swift in Sources */, B6B5726227AC14B60069DBA7 /* TextView.swift in Sources */, @@ -894,6 +1144,7 @@ B684EA2827C5EFE90014802F /* TextDiffView+.swift in Sources */, B6789EE127CA5F6900D8B58C /* IcnsGenerator.swift in Sources */, B63A035027C24BA0009FD2AD /* FFProgressReport.swift in Sources */, + B6A93D3327CBBC97003A6D7F /* IconSet.swift in Sources */, B64B1E7627A4F5E200AC2601 /* AppViewController.swift in Sources */, B67A4C5327C0EA84009277FB /* Ex+NSColor.swift in Sources */, B6E294B327C3351F00314132 /* FFTask.swift in Sources */, @@ -943,6 +1194,7 @@ B6D1AEE427A53A560022FED2 /* HomeView+.swift in Sources */, B6AC276A27AA535A000FD713 /* PDFGeneratorView+.swift in Sources */, B608542027A67E4D003BF243 /* TextField.swift in Sources */, + B6A93D3427CBBC97003A6D7F /* IconTemplete.swift in Sources */, B67A4C5527C0EA84009277FB /* ACOverlayPanel.swift in Sources */, B680A79A27A69324007CB707 /* URLDecoderView+.swift in Sources */, B61157C327C8C78A004D77A5 /* JSONNormalSearchView.swift in Sources */, diff --git a/DevToys/DevToys/AppDelegate.swift b/DevToys/DevToys/AppDelegate.swift index 9b2c08f..11e4264 100644 --- a/DevToys/DevToys/AppDelegate.swift +++ b/DevToys/DevToys/AppDelegate.swift @@ -11,4 +11,3 @@ import Cocoa class AppDelegate: NSObject, NSApplicationDelegate { func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { true } } - diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IOSIconGenerator.swift b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IOSIconGenerator.swift index 6f2b5c5..50b92ba 100644 --- a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IOSIconGenerator.swift +++ b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IOSIconGenerator.swift @@ -24,38 +24,38 @@ enum IOSIconGenerator { let fillColor = NSColor.black guard - let s16 = image.resizedAspectFit(to: [16, 16], fillColor: fillColor)?.png, - let s20 = image.resizedAspectFit(to: [20, 20], fillColor: fillColor)?.png, - let s29 = image.resizedAspectFit(to: [29, 29], fillColor: fillColor)?.png, - let s32 = image.resizedAspectFit(to: [32, 32], fillColor: fillColor)?.png, - let s40 = image.resizedAspectFit(to: [40, 40], fillColor: fillColor)?.png, - let s48 = image.resizedAspectFit(to: [48, 48], fillColor: fillColor)?.png, - let s55 = image.resizedAspectFit(to: [55, 55], fillColor: fillColor)?.png, - let s57 = image.resizedAspectFit(to: [57, 57], fillColor: fillColor)?.png, - let s58 = image.resizedAspectFit(to: [58, 58], fillColor: fillColor)?.png, - let s60 = image.resizedAspectFit(to: [60, 60], fillColor: fillColor)?.png, - let s64 = image.resizedAspectFit(to: [64, 64], fillColor: fillColor)?.png, - let s66 = image.resizedAspectFit(to: [66, 66], fillColor: fillColor)?.png, - let s76 = image.resizedAspectFit(to: [76, 76], fillColor: fillColor)?.png, - let s80 = image.resizedAspectFit(to: [80, 80], fillColor: fillColor)?.png, - let s87 = image.resizedAspectFit(to: [87, 87], fillColor: fillColor)?.png, - let s88 = image.resizedAspectFit(to: [88, 88], fillColor: fillColor)?.png, - let s92 = image.resizedAspectFit(to: [92, 92], fillColor: fillColor)?.png, - let s100 = image.resizedAspectFit(to: [100, 100], fillColor: fillColor)?.png, - let s102 = image.resizedAspectFit(to: [102, 102], fillColor: fillColor)?.png, - let s114 = image.resizedAspectFit(to: [114, 114], fillColor: fillColor)?.png, - let s120 = image.resizedAspectFit(to: [120, 120], fillColor: fillColor)?.png, - let s128 = image.resizedAspectFit(to: [128, 128], fillColor: fillColor)?.png, - let s152 = image.resizedAspectFit(to: [152, 152], fillColor: fillColor)?.png, - let s167 = image.resizedAspectFit(to: [167, 167], fillColor: fillColor)?.png, - let s172 = image.resizedAspectFit(to: [172, 172], fillColor: fillColor)?.png, - let s180 = image.resizedAspectFit(to: [180, 180], fillColor: fillColor)?.png, - let s196 = image.resizedAspectFit(to: [196, 196], fillColor: fillColor)?.png, - let s216 = image.resizedAspectFit(to: [216, 216], fillColor: fillColor)?.png, - let s234 = image.resizedAspectFit(to: [234, 234], fillColor: fillColor)?.png, - let s256 = image.resizedAspectFit(to: [256, 256], fillColor: fillColor)?.png, - let s512 = image.resizedAspectFit(to: [512, 512], fillColor: fillColor)?.png, - let s1024 = image.resizedAspectFit(to: [1024, 1024], fillColor: fillColor)?.png + let s16 = image.resizedAspectFit(to: [16, 16], fillColor: fillColor).png, + let s20 = image.resizedAspectFit(to: [20, 20], fillColor: fillColor).png, + let s29 = image.resizedAspectFit(to: [29, 29], fillColor: fillColor).png, + let s32 = image.resizedAspectFit(to: [32, 32], fillColor: fillColor).png, + let s40 = image.resizedAspectFit(to: [40, 40], fillColor: fillColor).png, + let s48 = image.resizedAspectFit(to: [48, 48], fillColor: fillColor).png, + let s55 = image.resizedAspectFit(to: [55, 55], fillColor: fillColor).png, + let s57 = image.resizedAspectFit(to: [57, 57], fillColor: fillColor).png, + let s58 = image.resizedAspectFit(to: [58, 58], fillColor: fillColor).png, + let s60 = image.resizedAspectFit(to: [60, 60], fillColor: fillColor).png, + let s64 = image.resizedAspectFit(to: [64, 64], fillColor: fillColor).png, + let s66 = image.resizedAspectFit(to: [66, 66], fillColor: fillColor).png, + let s76 = image.resizedAspectFit(to: [76, 76], fillColor: fillColor).png, + let s80 = image.resizedAspectFit(to: [80, 80], fillColor: fillColor).png, + let s87 = image.resizedAspectFit(to: [87, 87], fillColor: fillColor).png, + let s88 = image.resizedAspectFit(to: [88, 88], fillColor: fillColor).png, + let s92 = image.resizedAspectFit(to: [92, 92], fillColor: fillColor).png, + let s100 = image.resizedAspectFit(to: [100, 100], fillColor: fillColor).png, + let s102 = image.resizedAspectFit(to: [102, 102], fillColor: fillColor).png, + let s114 = image.resizedAspectFit(to: [114, 114], fillColor: fillColor).png, + let s120 = image.resizedAspectFit(to: [120, 120], fillColor: fillColor).png, + let s128 = image.resizedAspectFit(to: [128, 128], fillColor: fillColor).png, + let s152 = image.resizedAspectFit(to: [152, 152], fillColor: fillColor).png, + let s167 = image.resizedAspectFit(to: [167, 167], fillColor: fillColor).png, + let s172 = image.resizedAspectFit(to: [172, 172], fillColor: fillColor).png, + let s180 = image.resizedAspectFit(to: [180, 180], fillColor: fillColor).png, + let s196 = image.resizedAspectFit(to: [196, 196], fillColor: fillColor).png, + let s216 = image.resizedAspectFit(to: [216, 216], fillColor: fillColor).png, + let s234 = image.resizedAspectFit(to: [234, 234], fillColor: fillColor).png, + let s256 = image.resizedAspectFit(to: [256, 256], fillColor: fillColor).png, + let s512 = image.resizedAspectFit(to: [512, 512], fillColor: fillColor).png, + let s1024 = image.resizedAspectFit(to: [1024, 1024], fillColor: fillColor).png else { throw IconGenerateError.convertError } do { diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IcnsGenerator.swift b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IcnsGenerator.swift index da1fd13..1b88c35 100644 --- a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IcnsGenerator.swift +++ b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IcnsGenerator.swift @@ -13,11 +13,11 @@ enum IcnsGenerator { try? FileManager.default.createDirectory(at: $0, withIntermediateDirectories: true, attributes: nil) } - static func make(item: ImageItem, to destinationURL: URL) -> IconGenerateTask { + static func make(item: ImageItem, templete: IconTemplete, to destinationURL: URL) -> IconGenerateTask { let complete = Promise .tryAsync{ let iconsetURL = temporaryDirectory.appendingPathComponent(UUID().uuidString).appendingPathExtension("iconset") - try IconsetGenerator.generateIconset(image: item.image, to: iconsetURL) + try IconsetGenerator.generateIconset(image: item.image, templete: templete, to: iconsetURL) return iconsetURL } .flatPeek{ Terminal.run(iconutilURL, arguments: ["-c", "icns", "--output", destinationURL.path, $0.path]) } diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Icon Templete/IconImageManager.swift b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Icon Templete/IconImageManager.swift new file mode 100644 index 0000000..e897e57 --- /dev/null +++ b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Icon Templete/IconImageManager.swift @@ -0,0 +1,35 @@ +// +// IconImageManager.swift +// DevToys +// +// Created by yuki on 2022/02/28. +// + +import CoreUtil + +final class IconImageManager { + var templetes = [IconTemplete]() + private var templeteMap = [String: IconTemplete]() + + func templete(for identifier: String) -> IconTemplete? { + self.templeteMap[identifier] + } + func register(_ templete: IconTemplete) { + self.templetes.append(templete) + self.templeteMap[templete.identifier] = templete + } +} + +extension IconImageManager { + static let shared = IconImageManager() => { + $0.register(OriginalIconTemplete()) + $0.register(FolderFillIconTemplete()) + $0.register(FolderCenterIconTemplete()) + $0.register(FolderCenterEngravedIconTemplete()) + $0.register(BigSurFillIconTemplete()) + $0.register(AndroidIconTemplete()) + $0.register(CircleIconTemplete()) + $0.register(RoundedRectIconTemplete()) + } +} + diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Icon Templete/IconSet.swift b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Icon Templete/IconSet.swift new file mode 100644 index 0000000..edc687f --- /dev/null +++ b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Icon Templete/IconSet.swift @@ -0,0 +1,86 @@ +// +// IconSet.swift +// DevToys +// +// Created by yuki on 2022/02/27. +// + +import CoreUtil + + + +struct IconSet { + enum Scale { + case x16, x32, x64, x128, x256, x512, x1024 + + var size: CGFloat { + switch self { + case .x16: return 16 + case .x32: return 32 + case .x64: return 64 + case .x128: return 128 + case .x256: return 256 + case .x512: return 512 + case .x1024: return 1024 + } + } + var point: Int { Int(size) } + } + + let icon16: NSImage + let icon32: NSImage + let icon64: NSImage + let icon128: NSImage + let icon256: NSImage + let icon512: NSImage + let icon1024: NSImage + + func image(for scale: Scale) -> NSImage { + switch scale { + case .x16: return icon16 + case .x32: return icon32 + case .x64: return icon64 + case .x128: return icon128 + case .x256: return icon256 + case .x512: return icon512 + case .x1024: return icon1024 + } + } + + init(icon16: NSImage, icon32: NSImage, icon64: NSImage, icon128: NSImage, icon256: NSImage, icon512: NSImage, icon1024: NSImage) { + self.icon16 = icon16 + self.icon32 = icon32 + self.icon64 = icon64 + self.icon128 = icon128 + self.icon256 = icon256 + self.icon512 = icon512 + self.icon1024 = icon1024 + } + + init?(make: (Scale) -> NSImage?) { + guard let icon16 = make(.x16), let icon32 = make(.x32), let icon64 = make(.x64), let icon128 = make(.x128), let icon256 = make(.x256), let icon512 = make(.x512), let icon1024 = make(.x1024) else { return nil } + self.init(icon16: icon16, icon32: icon32, icon64: icon64, icon128: icon128, icon256: icon256, icon512: icon512, icon1024: icon1024) + } + + init?(bundleResouces: (Scale) -> String) { + self.init(make: { Bundle.main.image(forResource: bundleResouces($0)) }) + } +} + +extension IconSet { + func write(to url: URL) throws { + try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) + try self.icon16.png?.write(to: url.appendingPathComponent("icon_16x16.png")) + try self.icon32.png?.write(to: url.appendingPathComponent("icon_16x16@2x.png")) + try self.icon32.png?.write(to: url.appendingPathComponent("icon_32x32.png")) + try self.icon64.png?.write(to: url.appendingPathComponent("icon_32x32@2x.png")) + try self.icon64.png?.write(to: url.appendingPathComponent("icon_64x64.png")) + try self.icon128.png?.write(to: url.appendingPathComponent("icon_64x64@2x.png")) + try self.icon128.png?.write(to: url.appendingPathComponent("icon_128x128.png")) + try self.icon256.png?.write(to: url.appendingPathComponent("icon_128x128@2x.png")) + try self.icon256.png?.write(to: url.appendingPathComponent("icon_256x256.png")) + try self.icon512.png?.write(to: url.appendingPathComponent("icon_256x256@2x.png")) + try self.icon512.png?.write(to: url.appendingPathComponent("icon_512x512.png")) + try self.icon1024.png?.write(to: url.appendingPathComponent("icon_512x512@2x.png")) + } +} diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Icon Templete/IconTemplete+.swift b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Icon Templete/IconTemplete+.swift new file mode 100644 index 0000000..0790309 --- /dev/null +++ b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Icon Templete/IconTemplete+.swift @@ -0,0 +1,226 @@ +// +// FolderFillIconImage.swift +// DevToys +// +// Created by yuki on 2022/02/27. +// + +import CoreUtil +import CoreGraphics + +struct OriginalIconTemplete: IconTemplete { + let title: String = "Original" + let identifier: String = "original" + + func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { + return image.resizedAspectFit(to: [scale.size, scale.size], fillColor: .clear) + } +} + +struct RoundedRectIconTemplete: IconTemplete { + let title: String = "Rounded" + let identifier: String = "rounded" + + func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { + let size: CGSize = [scale.size, scale.size] + + let scaleFactor = scale.size / 1024 + return NSImage(size: size, cgcanvas: { context in + let imageRect = CGRect(size: size).slimmed(by: 64 * scaleFactor) + let image = IconTempleteHelper.fillToRect(rect: imageRect, image: image, size: size).cgImage! + let cornerRadius = 64 * scaleFactor + let circlePath = CGPath(roundedRect: imageRect, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil) + context.addPath(circlePath) + context.setShadow(offset: [0, -7] * scaleFactor, blur: 20 * scaleFactor, color: NSColor.black.withAlphaComponent(0.5).cgColor) + context.fillPath() + + context.addPath(circlePath) + context.clip() + context.draw(image, in: context.bounds) + context.resetClip() + }) + } +} + +struct CircleIconTemplete: IconTemplete { + let title: String = "Circle" + let identifier: String = "circle" + + func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { + let size: CGSize = [scale.size, scale.size] + + let scaleFactor = scale.size / 1024 + return NSImage(size: size, cgcanvas: { context in + let imageRect = CGRect(size: size).slimmed(by: 64 * scaleFactor) + let image = IconTempleteHelper.fillToRect(rect: imageRect, image: image, size: size).cgImage! + let circlePath = CGPath(ellipseIn: imageRect, transform: nil) + context.addPath(circlePath) + context.setShadow(offset: [0, -7] * scaleFactor, blur: 20 * scaleFactor, color: NSColor.black.withAlphaComponent(0.5).cgColor) + context.fillPath() + + context.addPath(circlePath) + context.clip() + context.draw(image, in: context.bounds) + context.resetClip() + }) + } +} + +struct AndroidIconTemplete: IconTemplete { + let title: String = "Android" + let identifier: String = "android" + + private static let mask = IconSet(make: { Bundle.main.image(forResource: "android_mask.png")!.resized(to: [$0.size, $0.size]) })! + + func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { + let mask = Self.mask.image(for: scale) + let size: CGSize = [scale.size, scale.size] + return NSImage(size: size, cgcanvas: { context in + let image = IconTempleteHelper.fillToRect(rect: CGRect(size: size), image: image, size: size).cgImage! + + context.clip(to: context.bounds, mask: mask.cgImage!.convertToGrayscale()) + context.draw(image, in: context.bounds) + }) + } +} + +struct BigSurFillIconTemplete: IconTemplete { + let title: String = "Big Sur Icon" + let identifier: String = "bigsur_icon" + private static let background = IconSet(bundleResouces: { "squircle_back_\($0.point)x\($0.point).png" })! + private static let mask = IconSet(bundleResouces: { "squircle_mask_\($0.point)x\($0.point).png" })! + + func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { + let background = Self.background.image(for: scale) + let mask = Self.mask.image(for: scale) + + return NSImage(size: background.size, cgcanvas: { context in + let image = IconTempleteHelper.fillToRect(rect: folderRect(scale), image: image, size: background.size).cgImage! + + context.draw(background.cgImage!, in: context.bounds) + context.clip(to: context.bounds, mask: mask.cgImage!.convertToGrayscale()) + context.draw(image, in: context.bounds) + }) + } + + private func folderRect(_ scale: IconSet.Scale) -> CGRect { + CGRect(origin: [100, 100] * (scale.size / 1024), size: [824, 824] * (scale.size / 1024)) + } +} + +struct FolderCenterEngravedIconTemplete: IconTemplete { + let title: String = "Folder (Engraved)" + let identifier: String = "folder_engraved" + let embossColor = NSColor(patternImage: NSImage(named: "watermark_mask_bs.png")!) + + func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { + let background = backgroundIconSet.image(for: scale) + + return NSImage(size: background.size, cgcanvas: { context in + let sizeImage = IconTempleteHelper.fitToRect(rect: imageRect(scale), image: image, size: background.size) + context.draw(background.cgImage!, in: context.bounds) + let maskImage = maskImage(image: sizeImage, color: embossColor.cgColor, size: background.size).cgImage! + context.setShadow(offset: [0, -2] * (scale.size / 512), blur: 2 * (scale.size / 512), color: NSColor.white.withAlphaComponent(0.2).cgColor) + context.draw(maskImage, in: context.bounds) + }) + } + + private func maskImage(image: NSImage, color: CGColor, size: CGSize) -> NSImage { + NSImage(size: size, cgcanvas: { context in + context.setFillColor(.clear) + context.fill(context.bounds) + context.clip(to: context.bounds, mask: image.cgImage!) + context.setFillColor(color) + context.fill(context.bounds) + }) + } + + private func imageRect(_ scale: IconSet.Scale) -> CGRect { + switch scale { + case .x1024: return CGRect(origin: [248, 235], size: [530, 530]) + default: return CGRect(origin: [130, 115] * (scale.size / 512), size: [255, 255] * (scale.size / 512)) + } + } +} + +struct FolderCenterIconTemplete: IconTemplete { + let title: String = "Folder (Center)" + let identifier: String = "folder_center" + + func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { + let background = backgroundIconSet.image(for: scale) + + return NSImage(size: background.size, cgcanvas: { context in + let image = IconTempleteHelper.fitToRect(rect: imageRect(scale), image: image, size: background.size).cgImage! + context.draw(background.cgImage!, in: context.bounds) + context.draw(image, in: context.bounds) + }) + } + + private func imageRect(_ scale: IconSet.Scale) -> CGRect { + switch scale { + case .x1024: return CGRect(origin: [248, 235], size: [530, 530]) + default: return CGRect(origin: [130, 115] * (scale.size / 512), size: [255, 255] * (scale.size / 512)) + } + } +} + +struct FolderFillIconTemplete: IconTemplete { + let title: String = "Folder (Fill)" + let identifier: String = "folder_fill" + + func bake(image: NSImage, scale: IconSet.Scale) -> NSImage { + let background = backgroundIconSet.image(for: scale) + let mask = maskIconSet.image(for: scale) + let top = topIconSet.image(for: scale) + + return NSImage(size: background.size, cgcanvas: { context in + let image = IconTempleteHelper.fillToRect(rect: folderRect(scale), image: image, size: background.size).cgImage! + + context.draw(background.cgImage!, in: context.bounds) + context.clip(to: context.bounds, mask: mask.cgImage!.convertToGrayscale()) + context.draw(image, in: context.bounds) + context.resetClip() + context.clip(to: context.bounds, mask: image) + context.setAlpha(0.75) + context.draw(top.cgImage!, in: context.bounds) + }) + } + + private func folderRect(_ scale: IconSet.Scale) -> CGRect { + switch scale { + case .x1024: return CGRect(origin: [24, 113], size: [974, 820]) + default: return CGRect(origin: [20, 55] * (scale.size / 512), size: [471, 397] * (scale.size / 512)) + } + } +} + +enum IconTempleteHelper { + static func fillToRect(rect: CGRect, image: NSImage, size: CGSize) -> NSImage { + NSImage(size: size, cgcanvas: { context in + let imageSize = image.size * image.size.aspectFillRatio(fillInside: rect.size) + let imageRect = CGRect(center: rect.center, size: imageSize) + image.draw(in: imageRect, from: NSRect(size: image.size), operation: .sourceOver, fraction: 1) + }) + } + + static func fitToRect(rect: CGRect, image: NSImage, size: CGSize) -> NSImage { + NSImage(size: size, cgcanvas: { context in + let imageSize = image.size * image.size.aspectFitRatio(fitInside: rect.size) + let imageRect = CGRect(center: rect.center, size: imageSize) + image.draw(in: imageRect, from: NSRect(size: image.size), operation: .sourceOver, fraction: 1) + }) + } +} + +private let backgroundIconSet = IconSet(bundleResouces: { "folder_back_\($0.point)_bs.png" })! +private let maskIconSet = IconSet(bundleResouces: { "folder_mask2_\($0.point)_bs.png" })! +private let topIconSet = IconSet( + icon16: Bundle.main.image(forResource: "folder_top_512.png")!.resized(to: [16, 16]), + icon32: Bundle.main.image(forResource: "folder_top_512.png")!.resized(to: [32, 32]), + icon64: Bundle.main.image(forResource: "folder_top_512.png")!.resized(to: [64, 64]), + icon128: Bundle.main.image(forResource: "folder_top_512.png")!.resized(to: [128, 128]), + icon256: Bundle.main.image(forResource: "folder_top_512.png")!.resized(to: [256, 256]), + icon512: Bundle.main.image(forResource: "folder_top_512.png")!, + icon1024: Bundle.main.image(forResource: "folder_top_1024.png")! +) diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Icon Templete/IconTemplete.swift b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Icon Templete/IconTemplete.swift new file mode 100644 index 0000000..70e1f11 --- /dev/null +++ b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Icon Templete/IconTemplete.swift @@ -0,0 +1,21 @@ +// +// IconImageManager+.swift +// DevToys +// +// Created by yuki on 2022/02/27. +// + +import CoreUtil + +protocol IconTemplete { + var title: String { get } + var identifier: String { get } + + func bake(image: NSImage, scale: IconSet.Scale) -> NSImage +} + +extension IconTemplete { + func bakeIconSet(image: NSImage) -> IconSet { + IconSet(make: { bake(image: image, scale: $0) })! + } +} diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IconFolderGenerator.swift b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IconFolderGenerator.swift index 15322ce..4ef2513 100644 --- a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IconFolderGenerator.swift +++ b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IconFolderGenerator.swift @@ -6,12 +6,14 @@ // import CoreUtil +import CoreGraphics enum IconFolderGenerator { - static func make(item: ImageItem, to destinationURL: URL) -> IconGenerateTask { + static func make(item: ImageItem, templete: IconTemplete, to destinationURL: URL) -> IconGenerateTask { IconGenerateTask(imageItem: item, complete: .tryAsync{ try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) - NSWorkspace.shared.setIcon(item.image, forFile: destinationURL.path) + let image = templete.bake(image: item.image, scale: .x1024) + NSWorkspace.shared.setIcon(image, forFile: destinationURL.path, options: .excludeQuickDrawElementsIconCreationOption) }) } } diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IconsetGenerator.swift b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IconsetGenerator.swift index 423c45b..324800b 100644 --- a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IconsetGenerator.swift +++ b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/IconsetGenerator.swift @@ -8,34 +8,17 @@ import CoreUtil enum IconsetGenerator { - static func make(item: ImageItem, to destinationURL: URL) -> IconGenerateTask { + static func make(item: ImageItem, templete: IconTemplete, to destinationURL: URL) -> IconGenerateTask { IconGenerateTask(imageItem: item, complete: .tryAsync{ - try self.generateIconset(image: item.image, to: destinationURL) + try self.generateIconset(image: item.image, templete: templete, to: destinationURL) }) } - static func generateIconset(image: NSImage, to destinationURL: URL) throws { - guard let s16 = image.resizedAspectFit(to: [16, 16], fillColor: .clear)?.png, - let s32 = image.resizedAspectFit(to: [32, 32], fillColor: .clear)?.png, - let s64 = image.resizedAspectFit(to: [64, 64], fillColor: .clear)?.png, - let s128 = image.resizedAspectFit(to: [128, 128], fillColor: .clear)?.png, - let s256 = image.resizedAspectFit(to: [256, 256], fillColor: .clear)?.png, - let s512 = image.resizedAspectFit(to: [512, 512], fillColor: .clear)?.png, - let s1024 = image.resizedAspectFit(to: [1024, 1024], fillColor: .clear)?.png - else { throw IconGenerateError.convertError } + static func generateIconset(image: NSImage, templete: IconTemplete, to destinationURL: URL) throws { + let iconset = templete.bakeIconSet(image: image) do { - try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) - try s16.write(to: destinationURL.appendingPathComponent("icon_16x16.png")) - try s32.write(to: destinationURL.appendingPathComponent("icon_16x16@2x.png")) - try s32.write(to: destinationURL.appendingPathComponent("icon_32x32.png")) - try s64.write(to: destinationURL.appendingPathComponent("icon_32x32@2x.png")) - try s128.write(to: destinationURL.appendingPathComponent("icon_128x128.png")) - try s256.write(to: destinationURL.appendingPathComponent("icon_128x128@2x.png")) - try s256.write(to: destinationURL.appendingPathComponent("icon_256x256.png")) - try s512.write(to: destinationURL.appendingPathComponent("icon_256x256@2x.png")) - try s512.write(to: destinationURL.appendingPathComponent("icon_512x512.png")) - try s1024.write(to: destinationURL.appendingPathComponent("icon_512x512@2x.png")) + try iconset.write(to: destinationURL) } catch { throw IconGenerateError.exportError(error) } diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/android_mask.png b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/android_mask.png new file mode 100644 index 0000000..a20517d Binary files /dev/null and b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/android_mask.png differ diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_1024x1024.png b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_1024x1024.png new file mode 100644 index 0000000..480018f Binary files /dev/null and b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_1024x1024.png differ diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_128x128.png b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_128x128.png new file mode 100644 index 0000000..21e7446 Binary files /dev/null and b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_128x128.png differ diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_16x16.png b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_16x16.png new file mode 100644 index 0000000..77f1afe Binary files /dev/null and b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_16x16.png differ diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_256x256.png b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_256x256.png new file mode 100644 index 0000000..3fec590 Binary files /dev/null and b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_256x256.png differ diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_32x32.png b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_32x32.png new file mode 100644 index 0000000..8ea5f17 Binary files /dev/null and b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_32x32.png differ diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_512x512.png b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_512x512.png new file mode 100644 index 0000000..63f92d4 Binary files /dev/null and b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_512x512.png differ diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_64x64.png b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_64x64.png new file mode 100644 index 0000000..394a8c0 Binary files /dev/null and b/DevToys/DevToys/Body/Graphic/Icon Generator/Generators/Resource/Folder/external_drive/external_64x64.png differ diff --git a/DevToys/DevToys/Body/Graphic/Icon Generator/IconGeneratorView+.swift b/DevToys/DevToys/Body/Graphic/Icon Generator/IconGeneratorView+.swift index 78434dc..0ee1462 100644 --- a/DevToys/DevToys/Body/Graphic/Icon Generator/IconGeneratorView+.swift +++ b/DevToys/DevToys/Body/Graphic/Icon Generator/IconGeneratorView+.swift @@ -13,26 +13,32 @@ final class IconGeneratorViewController: NSViewController { @RestorableState("icongen.exporttype") var exportType: IconExportType = .icns @RestorableState("icongen.iosopt") var iosOptions: IOSIconGenerator.ExportOptions = [.iphone, .ipad] @RestorableData("icongen.image") var imageItem: ImageItem? = nil + @RestorableData("icongen.templete") var templeteName = "original" + @Observable var iconTemplete: IconTemplete? = nil { didSet { templeteName = iconTemplete?.identifier ?? "original" } } + @Observable var previewImage: NSImage? = nil + + let iconTempleteManager = IconImageManager.shared override func loadView() { self.view = cell } override func viewDidLoad() { - self.$imageItem - .sink{[unowned self] in self.cell.imageDropView.image = $0?.image }.store(in: &objectBag) + self.$previewImage + .sink{[unowned self] in self.cell.imageDropView.image = $0 }.store(in: &objectBag) self.$exportType .sink{[unowned self] in self.cell.exportTypePicker.selectedItem = $0 }.store(in: &objectBag) self.$iosOptions .sink{[unowned self] in self.bindiOSOptionsView($0) }.store(in: &objectBag) + self.$iconTemplete + .sink{[unowned self] in self.cell.iconTempletePicker.selectedMenuTitle = $0?.title }.store(in: &objectBag) self.cell.clearButton.actionPublisher - .sink{[unowned self] in self.imageItem = nil }.store(in: &objectBag) + .sink{[unowned self] in self.imageItem = nil; updatePreviewImage() }.store(in: &objectBag) self.cell.imageDropView.imagePublisher - .sink{[unowned self] in self.imageItem = $0 }.store(in: &objectBag) + .sink{[unowned self] in self.imageItem = $0; updatePreviewImage() }.store(in: &objectBag) self.cell.exportTypePicker.itemPublisher .sink{[unowned self] in self.exportType = $0; updateOptionView() }.store(in: &objectBag) self.cell.exportButton.actionPublisher .sink{[unowned self] in self.exportIcon() }.store(in: &objectBag) - self.cell.iosOptionsView.iPhoneSwitch.isOnPublisher .sink{[unowned self] in self.iosOptions = $0 ? iosOptions.union(.iphone) : iosOptions.subtracting(.iphone) }.store(in: &objectBag) self.cell.iosOptionsView.iPadSwitch.isOnPublisher @@ -43,6 +49,27 @@ final class IconGeneratorViewController: NSViewController { .sink{[unowned self] in self.iosOptions = $0 ? iosOptions.union(.carplay) : iosOptions.subtracting(.carplay) }.store(in: &objectBag) self.cell.iosOptionsView.macSwitch.isOnPublisher .sink{[unowned self] in self.iosOptions = $0 ? iosOptions.union(.mac) : iosOptions.subtracting(.mac) }.store(in: &objectBag) + + for templete in self.iconTempleteManager.templetes { + self.cell.iconTempletePicker.menuItems.append(NSMenuItem(title: templete.title) { + self.iconTemplete = templete + self.updatePreviewImage() + }) + } + + self.iconTemplete = self.iconTempleteManager.templete(for: self.templeteName) ?? self.iconTempleteManager.templetes.first + self.updatePreviewImage() + self.updateOptionView() + } + + private func updatePreviewImage() { + guard let imageItem = imageItem else { return previewImage = nil } + + if let iconTemplete = iconTemplete { + self.previewImage = iconTemplete.bake(image: imageItem.image, scale: .x512) + } else { + self.previewImage = imageItem.image + } } private func bindiOSOptionsView(_ options: IOSIconGenerator.ExportOptions) { @@ -62,16 +89,16 @@ final class IconGeneratorViewController: NSViewController { } private func exportIcon() { - guard let item = imageItem, let filename = exportFilename() else { return } + guard let item = imageItem, let filename = exportFilename(), let templete = iconTemplete else { return } let panel = NSSavePanel() panel.nameFieldStringValue = filename guard panel.runModal() == .OK, let url = panel.url else { return } - + switch self.exportType { - case .iconFolder: IconFolderGenerator.make(item: item, to: url) => registerTask(_:) + case .iconFolder: IconFolderGenerator.make(item: item, templete: templete, to: url) => registerTask(_:) case .iosAssets: IOSIconGenerator.make(item: item, options: iosOptions, to: url) => registerTask(_:) - case .icns: IcnsGenerator.make(item: item, to: url) => registerTask(_:) - case .iconset: IconsetGenerator.make(item: item, to: url) => registerTask(_:) + case .icns: IcnsGenerator.make(item: item, templete: templete, to: url) => registerTask(_:) + case .iconset: IconsetGenerator.make(item: item, templete: templete, to: url) => registerTask(_:) } } @@ -104,6 +131,7 @@ enum IconExportType: String, TextItem { final private class IconGeneratorView: Page { let exportTypePicker = EnumPopupButton() + let iconTempletePicker = PopupButton() let imageDropView = ImageDropView() let iosOptionsView = iOSOptionsView() let clearButton = SectionButton(title: "Clear".localized(), image: R.Image.clear) @@ -111,23 +139,24 @@ final private class IconGeneratorView: Page { override func onAwake() { self.addSection(Section(title: "Configuration".localized(), items: [ - Area(icon: R.Image.export, title: "Icon Export Type", control: exportTypePicker) + Area(icon: R.Image.export, title: "Icon Export Type", control: exportTypePicker), + Area(icon: R.Image.paramators, title: "Templetes", control: iconTempletePicker) ])) - let sourceView = Section(title: "Source".localized(), items: [ - imageDropView => { - $0.snp.makeConstraints{ make in - make.height.equalTo(320) + self.addSection2( + Section(title: "Source".localized(), items: [ + imageDropView => { + $0.snp.makeConstraints{ make in + make.height.equalTo(320) + } } - } - ], toolbarItems: [clearButton]) - - let optionsView = Section(title: "Options", items: [ - iosOptionsView, - exportButton - ]) - - self.addSection2(sourceView, optionsView) + ], toolbarItems: [clearButton]), + + Section(title: "Options", items: [ + iosOptionsView, + exportButton + ]) + ) } } diff --git a/DevToys/DevToys/Body/Graphic/Image Converter/ImageConverter.swift b/DevToys/DevToys/Body/Graphic/Image Converter/ImageConverter.swift index 51cfecf..0afcc64 100644 --- a/DevToys/DevToys/Body/Graphic/Image Converter/ImageConverter.swift +++ b/DevToys/DevToys/Body/Graphic/Image Converter/ImageConverter.swift @@ -25,8 +25,8 @@ enum ImageConverter { if resize { switch scale { - case .scaleToFill: if let rimage = image.resizedAspectFill(to: size) { image = rimage } - case .scaleToFit: if let rimage = image.resizedAspectFit(to: size) { image = rimage } + case .scaleToFill: image = image.resizedAspectFill(to: size) + case .scaleToFit: image = image.resizedAspectFit(to: size) } } @@ -81,65 +81,80 @@ enum ImageScaleMode: String, TextItem { } extension NSImage { - func resizedAspectFill(to newSize: CGSize) -> NSImage? { - guard let bitmapRep = NSBitmapImageRep( - bitmapDataPlanes: nil, pixelsWide: Int(newSize.width), pixelsHigh: Int(newSize.height), - bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, - colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0 - ) else { return nil } + func resizedAspectFill(to newSize: CGSize) -> NSImage { + let bitmapRep = NSBitmapImageRep(size: newSize) let scale = self.size.aspectFillRatio(fillInside: newSize) - bitmapRep.size = newSize - NSGraphicsContext.saveGraphicsState() - NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmapRep) - self.draw(in: CGRect(center: newSize.convertToPoint()/2, size: self.size * scale), from: .zero, operation: .copy, fraction: 1.0) - NSGraphicsContext.restoreGraphicsState() + NSGraphicsContext(bitmapImageRep: bitmapRep)?.perform { + self.draw(in: CGRect(center: newSize.convertToPoint()/2, size: self.size * scale), from: .zero, operation: .copy, fraction: 1.0) + } - let resizedImage = NSImage(size: newSize) - resizedImage.addRepresentation(bitmapRep) - return resizedImage + return NSImage(bitmapImageRep: bitmapRep) } - func resizedAspectFit(to newSize: CGSize, fillColor: NSColor = .black) -> NSImage? { - guard let bitmapRep = NSBitmapImageRep( - bitmapDataPlanes: nil, pixelsWide: Int(newSize.width), pixelsHigh: Int(newSize.height), - bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, - colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0 - ) else { return nil } - + func resizedAspectFit(to newSize: CGSize, fillColor: NSColor = .black) -> NSImage { + let bitmapRep = NSBitmapImageRep(size: newSize) let scale = self.size.aspectFitRatio(fitInside: newSize) - bitmapRep.size = newSize - NSGraphicsContext.saveGraphicsState() - NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmapRep) - fillColor.setFill() - NSRect(size: newSize).fill() - draw(in: CGRect(center: newSize.convertToPoint()/2, size: self.size * scale), from: .zero, operation: .sourceOver, fraction: 1.0) - NSGraphicsContext.restoreGraphicsState() + NSGraphicsContext(bitmapImageRep: bitmapRep)?.perform { + fillColor.setFill() + NSRect(size: newSize).fill() + draw(in: CGRect(center: newSize.convertToPoint()/2, size: self.size * scale), from: .zero, operation: .sourceOver, fraction: 1.0) + } - let resizedImage = NSImage(size: newSize) - resizedImage.addRepresentation(bitmapRep) - return resizedImage + return NSImage(bitmapImageRep: bitmapRep) } func resized(to newSize: NSSize) -> NSImage { - if let bitmapRep = NSBitmapImageRep( - bitmapDataPlanes: nil, pixelsWide: Int(newSize.width), pixelsHigh: Int(newSize.height), - bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, - colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0 - ) { - bitmapRep.size = newSize - NSGraphicsContext.saveGraphicsState() - NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmapRep) + let bitmapRep = NSBitmapImageRep(size: newSize) + + NSGraphicsContext(bitmapImageRep: bitmapRep)?.perform { draw(in: NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height), from: .zero, operation: .copy, fraction: 1.0) - NSGraphicsContext.restoreGraphicsState() - - let resizedImage = NSImage(size: newSize) - resizedImage.addRepresentation(bitmapRep) - return resizedImage } + + return NSImage(bitmapImageRep: bitmapRep) + } +} - fatalError() +extension NSGraphicsContext { + public func perform(_ block: () -> ()) { + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = self + block() + NSGraphicsContext.restoreGraphicsState() + } +} + +extension NSImage { + public convenience init(bitmapImageRep: NSBitmapImageRep) { + self.init(size: bitmapImageRep.size) + self.addRepresentation(bitmapImageRep) + } + public convenience init(size: CGSize, colorSpaceName: NSColorSpaceName = .calibratedRGB, canvas: () -> ()) { + let bitmapImageRep = NSBitmapImageRep(size: size, colorSpaceName: colorSpaceName) + NSGraphicsContext(bitmapImageRep: bitmapImageRep)?.perform(canvas) + self.init(bitmapImageRep: bitmapImageRep) + } + public convenience init(size: CGSize, colorSpaceName: NSColorSpaceName = .calibratedRGB, cgcanvas: (CGContext) -> ()) { + self.init(size: size, colorSpaceName: colorSpaceName, canvas: { + if let context = NSGraphicsContext.current?.cgContext { cgcanvas(context) } + }) } } + +extension NSBitmapImageRep { + public convenience init(size: CGSize, colorSpaceName: NSColorSpaceName = .calibratedRGB) { + self.init( + bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height), + bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, + colorSpaceName: colorSpaceName, bytesPerRow: 0, bitsPerPixel: 0 + )! + } +} + + +extension CGContext { + public var size: CGSize { CGSize(width: width, height: height) } + public var bounds: CGRect { CGRect(size: size) } +}