Skip to content

Commit

Permalink
Add option to remove alpha channel from the generated image
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandre-pod committed Apr 7, 2022
1 parent 4857ebc commit 56d19df
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 9 deletions.
8 changes: 7 additions & 1 deletion Sources/SVGConverter/SVGConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions Sources/SVGConverterCore/Internal/Utils/CGImage+Utils.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}
31 changes: 25 additions & 6 deletions Sources/SVGConverterCore/Internal/WebViewSVGRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,7 +41,7 @@ final class WebViewSVGRenderer: WKWebView, WKNavigationDelegate {

// MARK: - Private Properties

private let allowFixingMissingViewBox: Bool
private let options: Options
private var completion: ((Result<Data, Error>) -> Void)?
private var isRendering = false
private var inPixelSize: CGSize = .zero
Expand All @@ -41,16 +52,16 @@ 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")
}

@available(*, unavailable)
required init?(coder: NSCoder) {
self.allowFixingMissingViewBox = true
self.options = []
fatalError("init(coder:) has not been implemented")
}

Expand Down Expand Up @@ -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
Expand All @@ -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)
{
Expand Down
19 changes: 17 additions & 2 deletions Sources/SVGConverterCore/SVGRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions Sources/SVGConverterCore/SVGRenderingError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum SVGRenderingError: Error {
case renderingAlreadyInProgress
case invalidSVGData
case cgImageConversionFailed
case alphaChannelRemovalFailed
case pngImageConversionFailed
case invalidState
}
Expand All @@ -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:
Expand Down

0 comments on commit 56d19df

Please sign in to comment.