diff --git a/Sources/SVGConverter/SVGConverter.swift b/Sources/SVGConverter/SVGConverter.swift index 1dbbf42..cb21d43 100644 --- a/Sources/SVGConverter/SVGConverter.swift +++ b/Sources/SVGConverter/SVGConverter.swift @@ -28,12 +28,18 @@ struct SVGConverter: AsyncParsableCommand { @Flag(name: .customLong("no-svg-fix"), help: "Set this to true if you do not want this tool to automatically add a viewBox attribute if it is possible. Without viewBox the svg cannot be resized, but it can still be converted at its natural size") var preventMissingViewBoxFix: Bool = false + @Flag(name: .customLong("no-alpha-channel"), help: "When present the generated image will not contain an alpha channel") + var withoutAlphaChannel: Bool = false + @Flag(help: "Setting this to true prevent any output in standard error output") var quiet: Bool = false func run() async throws { let svgData = try Data(contentsOf: inputPath) - let configuration = SVGRenderer.Configuration(allowFixingMissingViewBox: !preventMissingViewBoxFix) + let configuration = SVGRenderer.Configuration( + allowFixingMissingViewBox: !preventMissingViewBoxFix, + removePNGAlphaChannel: withoutAlphaChannel + ) let renderer = await SVGRenderer( configuration: configuration, warningHandler: quiet ? nil : logWarning diff --git a/Sources/SVGConverterCore/Internal/Utils/CGImage+Utils.swift b/Sources/SVGConverterCore/Internal/Utils/CGImage+Utils.swift new file mode 100644 index 0000000..239e1bf --- /dev/null +++ b/Sources/SVGConverterCore/Internal/Utils/CGImage+Utils.swift @@ -0,0 +1,27 @@ +// +// CGImage+Utils.swift +// +// +// Created by Alexandre Podlewski on 07/04/2022. +// + +import CoreGraphics + +extension CGImage { + func removingAlphaChannel() -> CGImage? { + let cgBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipLast.rawValue) + + let cgContext = CGContext( + data: nil, + width: width, + height: height, + bitsPerComponent: bitsPerComponent, + bytesPerRow: 0, + space: CGColorSpace(name: CGColorSpace.sRGB)!, + bitmapInfo: cgBitmapInfo.rawValue + ) + cgContext?.draw(self, in: CGRect(origin: .zero, size: CGSize(width: width, height: height))) + + return cgContext?.makeImage() + } +} diff --git a/Sources/SVGConverterCore/Internal/WebViewSVGRenderer.swift b/Sources/SVGConverterCore/Internal/WebViewSVGRenderer.swift index fde24ed..8aff667 100644 --- a/Sources/SVGConverterCore/Internal/WebViewSVGRenderer.swift +++ b/Sources/SVGConverterCore/Internal/WebViewSVGRenderer.swift @@ -20,6 +20,17 @@ private extension SVGRenderingWarnings { @available(macOS 10.15, *) final class WebViewSVGRenderer: WKWebView, WKNavigationDelegate { + // MARK: - Public structures + + struct Options: OptionSet { + let rawValue: Int + + /// This option stop the renderer to try adding a viewBox attribute to svg that are lacking of it. Without viewBox the renderer is unable to resize the svg image. + static let preventViewBoxFix = Options(rawValue: 1<<0) + /// Tells the renderer to remove the alpha channel from the PNG image data + static let removePNGAlphaChannel = Options(rawValue: 1<<1) + } + // MARK: - Public typealias typealias WarningHandler = (SVGRenderingWarnings) -> Void @@ -30,7 +41,7 @@ final class WebViewSVGRenderer: WKWebView, WKNavigationDelegate { // MARK: - Private Properties - private let allowFixingMissingViewBox: Bool + private let options: Options private var completion: ((Result) -> Void)? private var isRendering = false private var inPixelSize: CGSize = .zero @@ -41,8 +52,8 @@ final class WebViewSVGRenderer: WKWebView, WKNavigationDelegate { /// An SVG renderer using a WebView to render the SVG /// - Parameter allowFixingMissingViewBox: allow the renderer to try adding a viewBox attribute to svg that are lacking of it. /// Without viewBox the renderer is unable to resize the svg image. - init(allowFixingMissingViewBox: Bool = true) { - self.allowFixingMissingViewBox = allowFixingMissingViewBox + init(options: Options = []) { + self.options = options super.init(frame: .zero, configuration: WebViewSVGRenderer.rendererConfiguration) navigationDelegate = self setValue(false, forKey: "drawsBackground") @@ -50,7 +61,7 @@ final class WebViewSVGRenderer: WKWebView, WKNavigationDelegate { @available(*, unavailable) required init?(coder: NSCoder) { - self.allowFixingMissingViewBox = true + self.options = [] fatalError("init(coder:) has not been implemented") } @@ -130,7 +141,15 @@ final class WebViewSVGRenderer: WKWebView, WKNavigationDelegate { else { throw SVGRenderingError.cgImageConversionFailed } - let rep = NSBitmapImageRep(cgImage: resizedCGImage) + let rep: NSBitmapImageRep + if self.options.contains(.removePNGAlphaChannel) { + guard let transformedImage = resizedCGImage.removingAlphaChannel() else { + throw SVGRenderingError.alphaChannelRemovalFailed + } + rep = NSBitmapImageRep(cgImage: transformedImage) + } else { + rep = NSBitmapImageRep(cgImage: resizedCGImage) + } rep.size = self.inPixelSize guard let data = rep.representation(using: .png, properties: [:]) else { throw SVGRenderingError.pngImageConversionFailed @@ -155,7 +174,7 @@ final class WebViewSVGRenderer: WKWebView, WKNavigationDelegate { if svgElement.attribute(forName: "viewBox") == nil { if - allowFixingMissingViewBox, + !options.contains(.preventViewBoxFix), let width = svgElement.attribute(forName: "width")?.stringValue.flatMap(Double.init), let height = svgElement.attribute(forName: "height")?.stringValue.flatMap(Double.init) { diff --git a/Sources/SVGConverterCore/SVGRenderer.swift b/Sources/SVGConverterCore/SVGRenderer.swift index 89ad715..effe503 100644 --- a/Sources/SVGConverterCore/SVGRenderer.swift +++ b/Sources/SVGConverterCore/SVGRenderer.swift @@ -24,10 +24,17 @@ public final class SVGRenderer { /// When at true, if there is no viewBox attribute in the SVG and the svg has a width and an height, it will add a viewBox attribute with the value `"0 0 width height"` public var allowFixingMissingViewBox: Bool + /// When this is true the renderer will produce an image without alpha channel + public var removePNGAlphaChannel: Bool + /// Create a configuration for SVGRenderer /// - Parameter allowFixingMissingViewBox: Controls wether or not the renderer will try to add viewBox attribute to SVG without one - public init(allowFixingMissingViewBox: Bool = true) { + public init( + allowFixingMissingViewBox: Bool = true, + removePNGAlphaChannel: Bool = false + ) { self.allowFixingMissingViewBox = allowFixingMissingViewBox + self.removePNGAlphaChannel = removePNGAlphaChannel } } @@ -53,7 +60,15 @@ public final class SVGRenderer { configuration: Configuration = Configuration(), warningHandler: WarningHandler? = nil ) { - self.renderer = WebViewSVGRenderer(allowFixingMissingViewBox: configuration.allowFixingMissingViewBox) + + var options: WebViewSVGRenderer.Options = [] + if !configuration.allowFixingMissingViewBox { + options.insert(.preventViewBoxFix) + } + if configuration.removePNGAlphaChannel { + options.insert(.removePNGAlphaChannel) + } + self.renderer = WebViewSVGRenderer(options: options) self.warningHandler = warningHandler self.renderer.warningHandler = warningHandler diff --git a/Sources/SVGConverterCore/SVGRenderingError.swift b/Sources/SVGConverterCore/SVGRenderingError.swift index eecea94..e351b54 100644 --- a/Sources/SVGConverterCore/SVGRenderingError.swift +++ b/Sources/SVGConverterCore/SVGRenderingError.swift @@ -11,6 +11,7 @@ public enum SVGRenderingError: Error { case renderingAlreadyInProgress case invalidSVGData case cgImageConversionFailed + case alphaChannelRemovalFailed case pngImageConversionFailed case invalidState } @@ -27,6 +28,8 @@ public extension SVGRenderingError { return "The SVG data is malformed" case .cgImageConversionFailed: return "Internal error, conversion to cgImage failed" + case .alphaChannelRemovalFailed: + return "Internal error, failed to remove alpha channel from the generated image" case .pngImageConversionFailed: return "Internal error, getting png representation from cgImage failed" case .invalidState: