1
1
mirror of https://github.com/exyte/Macaw.git synced 2024-11-13 13:14:20 +03:00

Apply mask's color, not only form

This commit is contained in:
Alisa Mylnikova 2018-06-07 17:42:57 +07:00
parent 9fdf3c72ee
commit 4aa568466c
9 changed files with 114 additions and 66 deletions

View File

@ -18,13 +18,14 @@ open class Group: Node {
}
}
public init(contents: [Node] = [], place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
public init(contents: [Node] = [], place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, mask: Shape? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
self.contentsVar = AnimatableVariable<[Node]>(contents)
super.init(
place: place,
opaque: opaque,
opacity: opacity,
clip: clip,
mask: mask,
effect: effect,
visible: visible,
tag: tag

View File

@ -26,6 +26,12 @@ open class Node: Drawable {
set(val) { clipVar.value = val }
}
open let maskVar: Variable<Shape?>
open var mask: Shape? {
get { return maskVar.value }
set(val) { maskVar.value = val }
}
open let effectVar: Variable<Effect?>
open var effect: Effect? {
get { return effectVar.value }
@ -294,11 +300,12 @@ open class Node: Drawable {
return !pinchHandlers.isEmpty
}
public init(place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
public init(place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, mask: Shape? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
self.placeVar = AnimatableVariable<Transform>(place)
self.opaqueVar = Variable<Bool>(opaque)
self.opacityVar = AnimatableVariable<Double>(opacity)
self.clipVar = Variable<Locus?>(clip)
self.maskVar = Variable<Shape?>(mask)
self.effectVar = Variable<Effect?>(effect)
self.id = NSUUID().uuidString

View File

@ -18,7 +18,7 @@ open class Shape: Node {
set(val) { strokeVar.value = val }
}
public init(form: Locus, fill: Fill? = nil, stroke: Stroke? = nil, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
public init(form: Locus, fill: Fill? = nil, stroke: Stroke? = nil, place: Transform = Transform.identity, opaque: Bool = true, opacity: Double = 1, clip: Locus? = nil, mask: Shape? = nil, effect: Effect? = nil, visible: Bool = true, tag: [String] = []) {
self.formVar = AnimatableVariable<Locus>(form)
self.fillVar = AnimatableVariable<Fill?>(fill)
self.strokeVar = AnimatableVariable<Stroke?>(stroke)
@ -27,6 +27,7 @@ open class Shape: Node {
opaque: opaque,
opacity: opacity,
clip: clip,
mask: mask,
effect: effect,
visible: visible,
tag: tag

View File

@ -35,9 +35,9 @@ class GroupRenderer: NodeRenderer {
return group
}
override func doRender(in context: CGContext, force: Bool, opacity: Double, useAlphaOnly: Bool = false) {
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
renderers.forEach { renderer in
renderer.render(in: context, force: force, opacity: opacity, useAlphaOnly: useAlphaOnly)
renderer.render(in: context, force: force, opacity: opacity, coloringMode: coloringMode)
}
}

View File

@ -37,7 +37,7 @@ class ImageRenderer: NodeRenderer {
observe(image.hVar)
}
override func doRender(in context: CGContext, force: Bool, opacity: Double, useAlphaOnly: Bool = false) {
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
guard let image = image else {
return
}

View File

@ -11,6 +11,10 @@ struct RenderingInterval {
let to: Int
}
enum ColoringMode {
case rgb, greyscale, alphaOnly
}
class NodeRenderer {
let view: MView?
@ -71,7 +75,7 @@ class NodeRenderer {
fatalError("Unsupported")
}
final public func render(in context: CGContext, force: Bool, opacity: Double, useAlphaOnly: Bool = false) {
final public func render(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
context.saveGState()
defer {
context.restoreGState()
@ -84,16 +88,23 @@ class NodeRenderer {
context.concatenate(node.place.toCG())
applyClip(in: context)
// draw masked image
if let mask = node.mask {
let bounds = mask.bounds()!
context.draw(getMaskedImage(bounds: bounds), in: bounds.toCG())
return
}
// no effects, just draw as usual
guard let effect = node.effect else {
directRender(in: context, force: force, opacity: newOpacity, useAlphaOnly: useAlphaOnly)
directRender(in: context, force: force, opacity: newOpacity, coloringMode: coloringMode)
return
}
let (offset, otherEffects) = separateEffects(effect)
let useAlphaOnly = otherEffects.contains { effect -> Bool in
let effectColoringMode = otherEffects.contains { effect -> Bool in
effect is AlphaEffect
}
} ? ColoringMode.alphaOnly : coloringMode
// move to offset
if let offset = offset {
@ -102,10 +113,10 @@ class NodeRenderer {
if otherEffects.isEmpty {
// just draw offset shape
directRender(in: context, force: force, opacity: newOpacity, useAlphaOnly: useAlphaOnly)
directRender(in: context, force: force, opacity: newOpacity, coloringMode: effectColoringMode)
} else {
// apply other effects to offset shape and draw it
applyEffects(otherEffects, context: context, opacity: opacity, useAlphaOnly: useAlphaOnly)
applyEffects(otherEffects, context: context, opacity: opacity, coloringMode: effectColoringMode)
}
if otherEffects.contains(where: { effect -> Bool in
@ -119,7 +130,7 @@ class NodeRenderer {
}
}
final func directRender(in context: CGContext, force: Bool = true, opacity: Double = 1.0, useAlphaOnly: Bool = false) {
final func directRender(in context: CGContext, force: Bool = true, opacity: Double = 1.0, coloringMode: ColoringMode = .rgb) {
guard let node = node() else {
return
}
@ -132,7 +143,7 @@ class NodeRenderer {
} else {
self.addObservers()
}
doRender(in: context, force: force, opacity: opacity, useAlphaOnly: useAlphaOnly)
doRender(in: context, force: force, opacity: opacity, coloringMode: coloringMode)
}
fileprivate func separateEffects(_ effect: Effect) -> (OffsetEffect?, [Effect]) {
@ -152,7 +163,7 @@ class NodeRenderer {
return (offset, otherEffects.reversed())
}
fileprivate func applyEffects(_ effects: [Effect], context: CGContext, opacity: Double, useAlphaOnly: Bool = false) {
fileprivate func applyEffects(_ effects: [Effect], context: CGContext, opacity: Double, coloringMode: ColoringMode = .rgb) {
guard let node = node(), let bounds = node.bounds() else {
return
}
@ -163,7 +174,7 @@ class NodeRenderer {
}
}
let shapeImage = CIImage(cgImage: renderToImage(bounds: bounds, inset: inset, useAlphaOnly: useAlphaOnly)!.cgImage!)
let shapeImage = CIImage(cgImage: renderToImage(bounds: bounds, inset: inset, coloringMode: coloringMode)!.cgImage!)
var filteredImage = shapeImage
for effect in effects {
@ -201,7 +212,7 @@ class NodeRenderer {
return filter.outputImage!
}
func renderToImage(bounds: Rect, inset: Double, useAlphaOnly: Bool = false) -> MImage? {
func renderToImage(bounds: Rect, inset: Double = 0, coloringMode: ColoringMode = .rgb) -> MImage? {
MGraphicsBeginImageContextWithOptions(CGSize(width: bounds.w + inset, height: bounds.h + inset), false, 1)
guard let tempContext = MGraphicsGetCurrentContext() else {
@ -211,14 +222,14 @@ class NodeRenderer {
// flip y-axis and leave space for the blur
tempContext.translateBy(x: CGFloat(inset / 2 - bounds.x), y: CGFloat(bounds.h + inset / 2 + bounds.y))
tempContext.scaleBy(x: 1, y: -1)
directRender(in: tempContext, force: false, opacity: 1.0, useAlphaOnly: useAlphaOnly)
directRender(in: tempContext, force: false, opacity: 1.0, coloringMode: coloringMode)
let img = MGraphicsGetImageFromCurrentImageContext()
MGraphicsEndImageContext()
return img
}
func doRender(in context: CGContext, force: Bool, opacity: Double, useAlphaOnly: Bool = false) {
func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
fatalError("Unsupported")
}
@ -271,6 +282,35 @@ class NodeRenderer {
RenderUtils.toBezierPath(clip).addClip()
}
private func getMaskedImage(bounds: Rect) -> CGImage {
let mask = node()!.mask!
let image = renderToImage(bounds: bounds)!
let nodeRenderer = ShapeRenderer(shape: mask, view: .none, animationCache: animationCache)
let maskImage = nodeRenderer.renderToImage(bounds: bounds, coloringMode: .greyscale)!
return apply(maskImage: maskImage, to: image)
}
func apply(maskImage: MImage, to image: MImage) -> CGImage {
let imageReference = image.cgImage!
let maskReference = maskImage.cgImage!
let decode = [CGFloat(1), CGFloat(0),
CGFloat(0), CGFloat(1),
CGFloat(0), CGFloat(1),
CGFloat(0), CGFloat(1)]
let invertedMask = CGImage(maskWidth: maskReference.width,
height: maskReference.height,
bitsPerComponent: maskReference.bitsPerComponent,
bitsPerPixel: maskReference.bitsPerPixel,
bytesPerRow: maskReference.bytesPerRow,
provider: maskReference.dataProvider!,
decode: decode,
shouldInterpolate: maskReference.shouldInterpolate)!
return imageReference.masking(invertedMask)!
}
private func addObservers() {
if !active {
active = true

View File

@ -31,7 +31,7 @@ class ShapeRenderer: NodeRenderer {
observe(shape.strokeVar)
}
override func doRender(in context: CGContext, force: Bool, opacity: Double, useAlphaOnly: Bool = false) {
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
guard let shape = shape else {
return
}
@ -46,9 +46,12 @@ class ShapeRenderer: NodeRenderer {
fillRule = path.fillRule
}
if !useAlphaOnly {
switch coloringMode {
case .rgb:
drawPath(fill: shape.fill, stroke: shape.stroke, ctx: context, opacity: opacity, fillRule: fillRule)
} else {
case .greyscale:
drawPath(fill: shape.fill?.fillUsingGrayscaleNoAlpha(), stroke: shape.stroke?.strokeUsingGrayscaleNoAlpha(), ctx: context, opacity: opacity, fillRule: fillRule)
case .alphaOnly:
drawPath(fill: shape.fill?.fillUsingAlphaOnly(), stroke: shape.stroke?.strokeUsingAlphaOnly(), ctx: context, opacity: opacity, fillRule: fillRule)
}
}
@ -251,6 +254,9 @@ extension Stroke {
func strokeUsingAlphaOnly() -> Stroke {
return Stroke(fill: fill.fillUsingAlphaOnly(), width: width, cap: cap, join: join, dashes: dashes, offset: offset)
}
func strokeUsingGrayscaleNoAlpha() -> Stroke {
return Stroke(fill: fill.fillUsingGrayscaleNoAlpha(), width: width, cap: cap, join: join, dashes: dashes, offset: offset)
}
}
extension Fill {
@ -266,10 +272,28 @@ extension Fill {
let linear = self as! LinearGradient
return LinearGradient(x1: linear.x1, y1: linear.y1, x2: linear.x2, y2: linear.y2, userSpace: linear.userSpace, stops: newStops)
}
func fillUsingGrayscaleNoAlpha() -> Fill {
if let color = self as? Color {
return color.toGrayscaleNoAlpha()
}
let gradient = self as! Gradient
let newStops = gradient.stops.map { Stop(offset: $0.offset, color: $0.color.toGrayscaleNoAlpha()) }
if let radial = self as? RadialGradient {
return RadialGradient(cx: radial.cx, cy: radial.cy, fx: radial.fx, fy: radial.fy, r: radial.r, userSpace: radial.userSpace, stops: newStops)
}
let linear = self as! LinearGradient
return LinearGradient(x1: linear.x1, y1: linear.y1, x2: linear.x2, y2: linear.y2, userSpace: linear.userSpace, stops: newStops)
}
}
extension Color {
func colorUsingAlphaOnly() -> Color {
return Color.black.with(a: Double(a()) / 255.0)
}
func toGrayscaleNoAlpha() -> Color {
let grey = Int(0.21 * Double(r()) + 0.72 * Double(g()) + 0.07 * Double(b()))
return Color.rgb(r: grey, g: grey, b: grey)
}
}

View File

@ -33,7 +33,7 @@ class TextRenderer: NodeRenderer {
observe(text.baselineVar)
}
override func doRender(in context: CGContext, force: Bool, opacity: Double, useAlphaOnly: Bool = false) {
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
guard let text = text else {
return
}

View File

@ -34,7 +34,7 @@ open class SVGParser {
}
let availableStyleAttributes = ["stroke", "stroke-width", "stroke-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit",
"fill", "fill-rule", "fill-opacity", "clip-path",
"fill", "fill-rule", "fill-opacity", "clip-path", "mask",
"opacity", "color", "stop-color", "stop-opacity",
"font-family", "font-size", "font-weight", "text-anchor",
"visibility"]
@ -262,31 +262,31 @@ open class SVGParser {
if let rule = getFillRule(styleAttributes) {
path = Path(segments: path.segments, fillRule: rule)
}
return Shape(form: path, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: path), tag: getTag(element))
return Shape(form: path, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: path), mask: getMask(styleAttributes: styleAttributes), tag: getTag(element))
}
case "line":
if let line = parseLine(node) {
return Shape(form: line, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: line), tag: getTag(element))
return Shape(form: line, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: line), mask: getMask(styleAttributes: styleAttributes), tag: getTag(element))
}
case "rect":
if let rect = parseRect(node) {
return Shape(form: rect, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: rect), tag: getTag(element))
return Shape(form: rect, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: rect), mask: getMask(styleAttributes: styleAttributes), tag: getTag(element))
}
case "circle":
if let circle = parseCircle(node) {
return Shape(form: circle, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: circle), tag: getTag(element))
return Shape(form: circle, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: circle), mask: getMask(styleAttributes: styleAttributes), tag: getTag(element))
}
case "ellipse":
if let ellipse = parseEllipse(node) {
return Shape(form: ellipse, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: ellipse), tag: getTag(element))
return Shape(form: ellipse, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: ellipse), mask: getMask(styleAttributes: styleAttributes), tag: getTag(element))
}
case "polygon":
if let polygon = parsePolygon(node) {
return Shape(form: polygon, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: polygon), tag: getTag(element))
return Shape(form: polygon, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: polygon), mask: getMask(styleAttributes: styleAttributes), tag: getTag(element))
}
case "polyline":
if let polyline = parsePolyline(node) {
return Shape(form: polyline, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: polyline), tag: getTag(element))
return Shape(form: polyline, fill: getFillColor(styleAttributes, groupStyle: styleAttributes), stroke: getStroke(styleAttributes, groupStyle: styleAttributes), place: position, opacity: getOpacity(styleAttributes), clip: getClipPath(styleAttributes, locus: polyline), mask: getMask(styleAttributes: styleAttributes), tag: getTag(element))
}
case "image":
return parseImage(node, opacity: getOpacity(styleAttributes), pos: position, clip: getClipPath(styleAttributes, locus: nil))
@ -339,29 +339,25 @@ open class SVGParser {
var groupNodes: [Node] = []
let style = getStyleAttributes(groupStyle, element: element)
let position = getPosition(element)
var mask: TransformedLocus?
var mask: Shape?
if let maskId = element.allAttributes["mask"]?.text
.replacingOccurrences(of: "url(#", with: "")
.replacingOccurrences(of: ")", with: "") {
let maskShape = defMasks[maskId]
mask = TransformedLocus(locus: maskShape!.form, transform: maskShape!.place)
mask = defMasks[maskId]
}
group.children.forEach { child in
if let node = parseNode(child, groupStyle: style) {
groupNodes.append(node)
}
}
return Group(contents: groupNodes, place: position, clip: mask, tag: getTag(element))
return Group(contents: groupNodes, place: position, mask: mask, tag: getTag(element))
}
fileprivate func getMask(mask: String) -> Locus? {
if let maskIdenitifierMatcher = SVGParserRegexHelper.getMaskIdenitifierMatcher() {
let fullRange = NSRange(location: 0, length: mask.count)
if let match = maskIdenitifierMatcher.firstMatch(in: mask, options: .reportCompletion, range: fullRange), let maskReferenceNode = self.defMasks[(mask as NSString).substring(with: match.range(at: 1))] {
return maskReferenceNode.form
}
fileprivate func getMask(styleAttributes: [String: String]) -> Shape? {
guard let mask = styleAttributes["mask"], let id = parseIdFromUrl(mask) else {
return .none
}
return .none
return defMasks[id]
}
fileprivate func getPosition(_ element: SWXMLHash.XMLElement) -> Transform {
@ -994,12 +990,8 @@ open class SVGParser {
if let line = stroke {
shape.stroke = line
}
if let maskIdenitifierMatcher = SVGParserRegexHelper.getMaskIdenitifierMatcher() {
let fullRange = NSRange(location: 0, length: mask.count)
if let match = maskIdenitifierMatcher.firstMatch(in: mask, options: .reportCompletion, range: fullRange), let maskReferenceNode = self.defMasks[(mask as NSString).substring(with: match.range(at: 1))] {
shape.clip = maskReferenceNode.form
shape.fill = .none
}
if let id = parseIdFromUrl(mask), let mask = defMasks[id] {
shape.mask = mask
}
return shape
}
@ -1112,9 +1104,6 @@ open class SVGParser {
}
fileprivate func parseMask(_ mask: XMLIndexer) -> Shape? {
guard let element = mask.element else {
return .none
}
var node: Node?
mask.children.forEach { indexer in
let position = getPosition(indexer.element!)
@ -1127,22 +1116,8 @@ open class SVGParser {
guard let shape = node as? Shape else {
return .none
}
let maskShape: Shape
if let circle = shape.form as? Circle {
maskShape = Shape(form: circle.arc(shift: 0, extent: degreesToRadians(360)), tag: getTag(element))
} else {
maskShape = Shape(form: shape.form, place: node!.place, tag: getTag(element))
}
let maskStyleAttributes = getStyleAttributes([:], element: element)
maskShape.fill = getFillColor(maskStyleAttributes)
if let id = mask.element?.allAttributes["id"]?.text {
maskShape.place = node!.place
defMasks[id] = maskShape
return .none
}
return maskShape
return shape
}
fileprivate func parseLinearGradient(_ gradient: XMLIndexer, groupStyle: [String: String] = [:]) -> Fill? {