// // File.swift // // // Created by Thibaut Schmitt on 24/01/2022. // import Foundation import ToolCore class XcassetsGenerator { static let outputImageExtension = "png" let forceGeneration: Bool // MARK: - Init init(forceGeneration: Bool) { self.forceGeneration = forceGeneration } // MARK: - Assets generation func generateXcassets(inputPath: String, imagesToGenerate: [ImageToGen], xcassetsPath: String) { let fileManager = FileManager() let svgConverter = Imagium.getSvgConverterPath() let allSubFiles = fileManager.getAllRegularFileIn(directory: inputPath) var generatedAssetsPaths = [String]() // Generate new assets imagesToGenerate.forEach { imageToGen in // Get image path let imageData: (path: String, ext: String) = { for subfile in allSubFiles { if subfile.hasSuffix("/" + imageToGen.name + ".svg") { return (subfile, "svg") } if subfile.hasSuffix("/" + imageToGen.name + ".png") { return (subfile, "png") } if subfile.hasSuffix("/" + imageToGen.name + ".jpg") { return (subfile, "jpg") } if subfile.hasSuffix("/" + imageToGen.name + ".jepg") { return (subfile, "jepg") } } let error = ImagiumError.unknownImageExtension(imageToGen.name) print(error.localizedDescription) Imagium.exit(withError: error) }() // Create imageset folder let imagesetName = "\(imageToGen.name).imageset" let imagesetPath = "\(xcassetsPath)/\(imagesetName)" Shell.shell("mkdir", "-p", imagesetPath) // Store managed images path generatedAssetsPaths.append(imagesetName) // Generate output images path let output1x = "\(imagesetPath)/\(imageToGen.name).\(XcassetsGenerator.outputImageExtension)" let output2x = "\(imagesetPath)/\(imageToGen.name)@2x.\(XcassetsGenerator.outputImageExtension)" let output3x = "\(imagesetPath)/\(imageToGen.name)@3x.\(XcassetsGenerator.outputImageExtension)" // Check if we need to convert image if self.shouldBypassGeneration(for: imageToGen, xcassetImagePath: output1x) { print("\(imageToGen.name) -> Not regenerating") return } // Convert image let convertArguments = imageToGen.convertArguments if imageData.ext == "svg" { // /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png // /usr/local/bin/rsvg-convert path/to/image.png -w 200 -o path/to/output.png // /usr/local/bin/rsvg-convert path/to/image.png -h 300 -o path/to/output.png var command1x = ["\(svgConverter)", "\(imageData.path)"] var command2x = ["\(svgConverter)", "\(imageData.path)"] var command3x = ["\(svgConverter)", "\(imageData.path)"] self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1) self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2) self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3) command1x.append(contentsOf: ["-o", output1x]) command2x.append(contentsOf: ["-o", output2x]) command3x.append(contentsOf: ["-o", output3x]) Shell.shell(command1x) Shell.shell(command2x) Shell.shell(command3x) } else { // convert path/to/image.png -resize 200x300 path/to/output.png // convert path/to/image.png -resize 200x path/to/output.png // convert path/to/image.png -resize x300 path/to/output.png Shell.shell("convert", "\(imageData.path)", "-resize", "\(convertArguments.x1.width ?? "")x\(convertArguments.x1.height ?? "")", output1x) Shell.shell("convert", "\(imageData.path)", "-resize", "\(convertArguments.x2.width ?? "")x\(convertArguments.x2.height ?? "")", output2x) Shell.shell("convert", "\(imageData.path)", "-resize", "\(convertArguments.x3.width ?? "")x\(convertArguments.x3.height ?? "")", output3x) } // Write Content.json let imagesetContentJson = imageToGen.contentJson let contentJsonFilePath = "\(imagesetPath)/Contents.json" if fileManager.fileExists(atPath: contentJsonFilePath) == false { Shell.shell("touch", "\(contentJsonFilePath)") } let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath) try! imagesetContentJson.write(to: contentJsonFilePathURL, atomically: true, encoding: .utf8) print("\(imageToGen.name) -> Generated") } // Success info let generatedAssetsCount = generatedAssetsPaths.count print("Images generated: \(generatedAssetsCount)") // Delete old assets let allImagesetName = Set(fileManager.getAllImageSetFolderIn(directory: xcassetsPath)) let imagesetToRemove = allImagesetName.subtracting(Set(generatedAssetsPaths)) imagesetToRemove.forEach { print("Will remove: \($0)") } imagesetToRemove.forEach { itemToRemove in try! fileManager.removeItem(atPath: "\(xcassetsPath)/\(itemToRemove)") } print("Removed \(imagesetToRemove.count) images") } // MARK: - Helpers: SVG command private func addConvertArgument(command: inout [String], convertArgument: ConvertArgument) { if let width = convertArgument.width, width.isEmpty == false { command.append("-w") command.append("\(width)") } if let height = convertArgument.height, height.isEmpty == false { command.append("-h") command.append("\(height)") } } // MARK: - Helpers: bypass generation private func shouldBypassGeneration(for image: ImageToGen, xcassetImagePath: String) -> Bool { guard forceGeneration == false else { return false } let fileManager = FileManager() // File not exists -> do not bypass guard fileManager.fileExists(atPath: xcassetImagePath) else { return false } // Info unavailable -> do not bypass let taskWidth = Shell.shell("identify", "-format", "%w", xcassetImagePath) let taskHeight = Shell.shell("identify", "-format", "%h", xcassetImagePath) guard taskWidth.terminationStatus == 0, taskHeight.terminationStatus == 0 else { return false } let currentWidth = Int(taskWidth.output ?? "-1") ?? -1 let currentheight = Int(taskHeight.output ?? "-1") ?? -1 // Info unavailable -> do not bypass guard currentWidth > 0 && currentheight > 0 else { return false } // Check width and height if image.width != -1 && currentWidth == image.width { return true } if image.height != -1 && currentheight == image.height { return true } return false } }