/// <summary> /// General routine for adding a Primitive to DisplayList /// 1) Apply transformation to Geometry, Brush, Pen /// 2) Optimize Primitive /// 3) Add to DisplayList /// </summary> /// <param name="p"></param> public void AddPrimitive(Primitive p) { if (p.IsTransparent) { return; } p.ApplyTransform(); if (!p.Optimize()) { return; } GeometryPrimitive gp = p as GeometryPrimitive; if (gp != null) { if ((gp.Pen != null) && (gp.Pen.StrokeBrush.Brush is DrawingBrush)) { gp.Widen(); } } _dl.RecordPrimitive(p); }
// External API public void DrawGeometry(Geometry cur, string desp, GeometryPrimitive gp) { if (cur == null) { return; } int start = 0; PrimitiveInfo topPI; Geometry topBounds; Geometry inter; if (_pen != null) { Debug.Assert(_brush == null, "no brush"); if ((_overlapping != null) && FindIntersection(gp.WidenGeometry, ref start, out topPI, out topBounds, out inter)) { cur = gp.WidenGeometry; _brush = _pen.StrokeBrush; _pen = null; // Draw the stroking as filling widened path #if DEBUG FillGeometry(topPI, cur, desp + "_widen", null, null, start, inter, topBounds); #else FillGeometry(topPI, cur, null, null, null, start, inter, topBounds); #endif } else { // Render to dc if nothing on top _dc.Comment(desp); _dc.DrawGeometry(_brush, _pen, cur, _clip, Matrix.Identity, ProxyDrawingFlags.None); } } else { if (FindIntersection(cur, ref start, out topPI, out topBounds, out inter)) { FillGeometry(topPI, cur, desp, null, null, start, inter, topBounds); } else { // Render to dc if nothing on top _dc.Comment(desp); _dc.DrawGeometry(_brush, _pen, cur, _clip, Matrix.Identity, ProxyDrawingFlags.None); } } }
/// <summary> /// Bug 1687865 /// Special optimization for annotation type of visual: lots of glyph runs covered by a single transparency geometry /// t1 ... tn g => t1 ... tn-1 g tn' /// => g t1' ... tn' /// </summary> /// <param name="commands"></param> /// <param name="j"></param> private static void PushTransparencyDown(List <PrimitiveInfo> commands, int j) { PrimitiveInfo pj = commands[j]; GeometryPrimitive gj = pj.primitive as GeometryPrimitive; if ((gj == null) || (pj.underlay == null) || (pj.underlay.Count == 0)) { return; } for (int n = pj.underlay.Count - 1; n >= 0; n--) { int i = pj.underlay[n]; PrimitiveInfo pi = commands[i]; if (pi == null) { continue; } GeometryPrimitive gi = pi.primitive as GeometryPrimitive; if ((gi != null) && (gi.Pen == null) && (pi.overlap.Count == 1) && pj.FullyCovers(pi)) { // c[i] ... c[j] => ... c[j] c[i]' if (BlendCommands(pi, pj)) // pi.Brush = Blend(pi.Brush, pj.Brush) { pj.underlay.Remove(i); pi.overlap = null; pi.overlapHasTransparency = 0; while (i < j) { SwitchCommands(commands, i, commands[i], i + 1, commands[i + 1], false); i++; } j--; } } } }
/// <summary> /// Check if a primitive only contains white color /// </summary> /// <param name="p"></param> /// <returns></returns> internal static bool IsWhitePrimitive(Primitive p) { GeometryPrimitive gp = p as GeometryPrimitive; if (gp != null) { if ((gp.Brush != null) && (gp.Pen == null)) { return(gp.Brush.IsWhite()); } if ((gp.Pen != null) && (gp.Brush == null)) { return(gp.Pen.StrokeBrush.IsWhite()); } } return(false); }
private static bool BlendCommands(PrimitiveInfo pi, PrimitiveInfo pj) { GeometryPrimitive gi = pi.primitive as GeometryPrimitive; GeometryPrimitive gj = pj.primitive as GeometryPrimitive; if ((gi != null) && (gi.Brush != null) && (gj != null) && (gj.Brush != null)) { // get brushes in world space BrushProxy bi = gi.Brush.ApplyTransformCopy(gi.Transform); BrushProxy bj = gj.Brush.ApplyTransformCopy(gj.Transform); gi.Brush = bi.BlendBrush(bj); return(true); } return(false); }
void IProxyDrawingContext.DrawGeometry(BrushProxy brush, PenProxy pen, Geometry geometry, Geometry clip, Matrix brushTrans, ProxyDrawingFlags flags) { if ((brush != null) && (pen != null)) // Split fill & stroke into two { ((IProxyDrawingContext)(this)).DrawGeometry(brush, null, geometry, clip, brushTrans, flags); brush = null; } bool empty; clip = Utility.Intersect(_clip, Utility.TransformGeometry(clip, _transform), Matrix.Identity, out empty); if (empty) { return; } GeometryPrimitive geo = new GeometryPrimitive(); // apply drawing flags to primitive if ((flags & ProxyDrawingFlags.PixelSnapBounds) != 0) { geo.PixelSnapBounds = true; } if (pen != null) { if (!brushTrans.IsIdentity) { double scale; bool uniform = Utility.HasUniformScale(brushTrans, out scale); if (uniform) { geo.Pen = pen.Clone(); geo.Pen.Scale(scale); geo.Pen.StrokeBrush.ApplyTransform(brushTrans); } else { // relative may not be good enough geometry = Utility.InverseTransformGeometry(geometry, brushTrans); geometry = geometry.GetWidenedPathGeometry(pen.GetPen(true), 0.0001, ToleranceType.Relative); geometry = Utility.TransformGeometry(geometry, brushTrans); brush = pen.StrokeBrush; pen = null; } } else { geo.Pen = pen.Clone(); } } if (brush != null) { geo.Brush = brush.ApplyTransformCopy(brushTrans); } geo.Geometry = geometry; geo.Clip = clip; geo.Transform = _transform; geo.PushOpacity(_opacity, _opacityMask); _flattener.AddPrimitive(geo); }
public void DrawGeometry(Brush brush, Pen pen, Geometry geometry) { // Ignore total transparent primitive if (Utility.IsTransparent(_opacity) || ((brush == null) && (pen == null || pen.Brush == null)) || (geometry == null)) { return; } // Split if having both pen and brush if ((brush != null) && (pen != null)) // if (!Utility.IsOpaque(_opacity) || (_opacityMask != null)) { // Push a canvas to handle geometry with brush + pen properly Push(Matrix.Identity, null, 1.0, null, Rect.Empty, false); DrawGeometry(brush, null, geometry); DrawGeometry(null, pen, geometry); Pop(); return; } AssertState(DeviceState.PageStarted, DeviceState.NoChange); GeometryPrimitive g = new GeometryPrimitive(); g.Geometry = geometry; g.Clip = _clip; g.Opacity = _opacity; g.OpacityMask = _opacityMask; int needBounds = 0; // 1 for fill, 2 for stroke if (brush != null) { // Fix bug 1427695: Need bounds for non-SolidColorBrushes to enable rebuilding Brush from BrushProxy. if (!(brush is SolidColorBrush)) { needBounds |= 1; } } if ((pen != null) && (pen.Brush != null)) { if (!(pen.Brush is SolidColorBrush)) { needBounds |= 2; } } if (g.OpacityMask != null) { if (g.OpacityMask.BrushList == null && !(g.OpacityMask.Brush is SolidColorBrush)) { if (pen != null) { needBounds |= 2; } else { needBounds |= 1; } } } Rect bounds = g.GetRectBounds((needBounds & 1) != 0); if (brush != null) { g.Brush = BrushProxy.CreateBrush(brush, bounds); } if ((needBounds & 2) != 0) { bounds = geometry.GetRenderBounds(pen); } if ((pen != null) && (pen.Brush != null)) { g.Pen = PenProxy.CreatePen(pen, bounds); } if (g.OpacityMask != null) { if (!g.OpacityMask.MakeBrushAbsolute(bounds)) { // Fix bug 1463955: Brush has become empty; replace with transparent brush. g.OpacityMask = BrushProxy.CreateColorBrush(Colors.Transparent); } } // Optimization: Unfold primitive DrawingBrush when possible to avoid rasterizing it. Primitive primitive = g.UnfoldDrawingBrush(); if (primitive != null) { _root.Children.Add(primitive); } }
/// <summary> /// Check if the whole cluster is complex enough such that rasterizing the whole thing is better than flattening it /// </summary> /// <param name="commands"></param> /// <returns></returns> private bool BetterRasterize(List <PrimitiveInfo> commands) { double clustersize = m_bounds.Width * m_bounds.Height; double diff = -Configuration.RasterizationCost(m_bounds.Width, m_bounds.Height); // Estimate cost of geometry operations (intersecting) double pathComplexity = 1; foreach (int i in m_primitives) { PrimitiveInfo pi = commands[i]; Primitive p = pi.primitive; GeometryPrimitive gp = p as GeometryPrimitive; Rect bounds = pi.GetClippedBounds(); bool rasterize = true; if (gp != null) { double complexity = 1; Geometry geo = gp.Geometry; if (geo != null) { complexity = Utility.GetGeometryPointCount(geo); // weight down the complexity of small region complexity *= bounds.Width * bounds.Height / clustersize; } BrushProxy bp = gp.Brush; if (bp == null) { bp = gp.Pen.StrokeBrush; // Widen path would at least double the points complexity *= 3; } if (complexity > 1) { pathComplexity *= complexity; if (pathComplexity > 100000) // 333 x 333 { return(true); } } if (bp != null) { Brush b = bp.Brush; if ((b != null) && ((b is SolidColorBrush) || (b is LinearGradientBrush))) { // SolidColorBrush does not need full rasterization // Vertical/Horizontal linear gradient brush does not need full rasterization rasterize = false; } } } if (rasterize) { diff += Configuration.RasterizationCost(bounds.Width, bounds.Height); if (diff > 0) { break; } } } return(diff > 0); }
static internal void PrintPrimitive(PrimitiveInfo info, int index, bool verbose) { if (index < 0) { Console.WriteLine(); Console.WriteLine(" No Type Und Ovr TrO Bounding Box Clipping"); if (verbose) { Console.Write(" Transform"); } Console.WriteLine(); return; } Primitive p = info.primitive; string typ = p.GetType().ToString(); typ = typ.Substring(typ.LastIndexOf('.') + 1); Console.Write(LeftPad(index, 4) + LeftPad(typ, 18) + ":"); List <int> extra = null; if (p.IsOpaque) { Console.Write(' '); } else { Console.Write('@'); } if (info.underlay == null) { Console.Write(" "); } else { extra = info.underlay; Console.Write(LeftPad(info.underlay.Count, 3)); } if (info.overlap != null) { Console.Write(' ' + LeftPad(info.overlap.Count, 3)); if (info.overlapHasTransparency != 0) { Console.Write('$'); Console.Write(LeftPad(info.overlapHasTransparency, 3)); } else { Console.Write(" "); } Console.Write(' '); } else { Console.Write(" "); } Console.Write(LeftPad(info.bounds, 0)); Geometry clip = p.Clip; if (clip != null) { Console.Write(LeftPad(clip.Bounds, 0)); } if (verbose) { Matrix m = p.Transform; Console.Write(" {"); Console.Write(LeftPad(m.M11, 3) + ' '); Console.Write(LeftPad(m.M12, 3) + ' '); Console.Write(LeftPad(m.M21, 3) + ' '); Console.Write(LeftPad(m.M22, 3) + ' '); Console.Write(LeftPad(m.OffsetX, 6) + ' '); Console.Write(LeftPad(m.OffsetY, 6)); Console.Write("} "); } if (verbose) { GlyphPrimitive gp = p as GlyphPrimitive; if (gp != null) { IList <char> chars = gp.GlyphRun.Characters; Console.Write(" \""); for (int i = 0; i < chars.Count; i++) { Console.Write(chars[i]); } Console.Write('"'); } } if (verbose) { GeometryPrimitive gp = p as GeometryPrimitive; if ((gp != null) && (gp.Brush != null) && (gp.Pen == null)) { Brush b = gp.Brush.Brush; if (b != null) { SolidColorBrush sb = b as SolidColorBrush; if (sb != null) { Console.Write(" SolidColorBrush({0})", sb.Color); } } } } if (extra != null) { Console.Write(' '); Console.Write(LeftPad(extra, 0)); } Console.WriteLine(); }
/// <summary> /// Flatten the structure of a primitive tree by push clip/transform/opacity onto each leaf node. /// Build an index in a DisplayList. /// </summary> /// <param name="tree"></param> /// <param name="clip"></param> /// <param name="transform"></param> /// <param name="opacity"></param> /// <param name="opacityMask"></param> public void TreeFlatten(Primitive tree, Geometry clip, Matrix transform, double opacity, BrushProxy opacityMask) { More: if (tree == null) { return; } Debug.Assert(Utility.IsValid(opacity) && Utility.IsValid(tree.Opacity), "Invalid opacity encountered, should've been normalized in conversion to Primitive"); // Non-invertible transforms may arise from brush unfolding, where brush content is huge but // we need to scale down significantly to fill target region. Allow such transforms. CanvasPrimitive canvas = tree as CanvasPrimitive; if (canvas != null) { ArrayList children = canvas.Children; // No children, nothing to do if ((children == null) || (children.Count == 0)) { return; } opacity *= tree.Opacity; // transform opacity mask into current primitive space if (opacityMask != null) { Matrix worldToPrimitiveTransform = tree.Transform; worldToPrimitiveTransform.Invert(); opacityMask.ApplyTransform(worldToPrimitiveTransform); } opacityMask = BrushProxy.BlendBrush(opacityMask, tree.OpacityMask); // Skip the subtree if it's transparent enough if (Utility.IsTransparent(opacity)) { return; } transform = tree.Transform * transform; Geometry transclip = Utility.TransformGeometry(tree.Clip, transform); bool empty; clip = Utility.Intersect(clip, transclip, Matrix.Identity, out empty); if (empty) { return; } // For single child, just push transform/clip/opacity onto it. if (children.Count == 1) { tree = children[0] as Primitive; // Save a recursive call goto More; } if (Configuration.BlendAlphaWithWhite || Configuration.ForceAlphaOpaque || (Utility.IsOpaque(opacity) && (opacityMask == null))) // For opaque subtree, just push trasform/clip into it. { foreach (Primitive p in children) { TreeFlatten(p, clip, transform, opacity, opacityMask); } } else { // A semi-transparent sub-tree with more than one child Flattener fl = new Flattener(true, _dl.m_width, _dl.m_height); Primitive ntree = tree; ntree.Clip = null; ntree.Transform = Matrix.Identity; ntree.Opacity = 1.0; ntree.OpacityMask = null; #if DEBUG if (Configuration.Verbose >= 2) { Console.WriteLine("TreeFlatten for subtree"); } #endif if (opacityMask != null) { opacityMask.ApplyTransform(transform); } // Flatten sub-tree structure into a new DisplayList fl.TreeFlatten(ntree, clip, transform, 1.0, null); // Remove alpha in the sub-tree and add to the current display list #if DEBUG if (Configuration.Verbose >= 2) { Console.WriteLine("end TreeFlatten for subtree"); Console.WriteLine("AlphaFlatten for subtree"); } #endif fl.AlphaFlatten(new DisplayListDrawingContext(this, opacity, opacityMask, Matrix.Identity, null), true); #if DEBUG if (Configuration.Verbose >= 2) { Console.WriteLine("end AlphaFlatten for subtree"); } #endif } } else { GeometryPrimitive gp = tree as GeometryPrimitive; if (gp != null && gp.Brush != null && gp.Pen != null && (!gp.IsOpaque || !Utility.IsOpaque(opacity))) { // // As an optimization we split fill from stroke, however doing so requires // an intermediate canvas to handle translucent fill/stroke, otherwise // the translucent stroke and fill will overlap. // CanvasPrimitive splitCanvas = new CanvasPrimitive(); GeometryPrimitive fill = (GeometryPrimitive)gp; GeometryPrimitive stroke = (GeometryPrimitive)gp.Clone(); fill.Pen = null; stroke.Brush = null; splitCanvas.Children.Add(fill); splitCanvas.Children.Add(stroke); tree = splitCanvas; goto More; } // Push transform/clip/opacity to leaf node tree.Transform = tree.Transform * transform; if (tree.Clip == null) { tree.Clip = clip; } else { Geometry transclip = Utility.TransformGeometry(tree.Clip, transform); bool empty; tree.Clip = Utility.Intersect(transclip, clip, Matrix.Identity, out empty); if (!empty) { empty = Utility.IsEmpty(tree.Clip, Matrix.Identity); } if (empty) { return; } } tree.PushOpacity(opacity, opacityMask); if (gp != null) { // Split fill and stroke into separate primitives if no opacity involved. // Intermediate Canvas not needed due to opaqueness. if ((gp.Brush != null) && (gp.Pen != null)) { GeometryPrimitive fill = gp.Clone() as GeometryPrimitive; fill.Pen = null; AddPrimitive(fill); // Fill only first // Stroke is flattend to fill only when needed gp.Brush = null; AddPrimitive(gp); // Followed by stroke only } else if ((gp.Pen != null) || (gp.Brush != null)) { AddPrimitive(gp); } } else { // Record it AddPrimitive(tree); } } }
/// <summary> /// Optimization: If a transparent primitive is covered underneath immediately by an opaque primitive, /// or has nothing underneath, convert it to opaque primitive /// </summary> /// <param name="commands"></param> /// <param name="i"></param> private static bool ConvertTransparentOnOpaque(List <PrimitiveInfo> commands, int i) { PrimitiveInfo pi = commands[i]; GeometryPrimitive gp = pi.primitive as GeometryPrimitive; if (gp != null) { PrimitiveInfo qi = null; if ((pi.underlay != null) && (pi.underlay.Count != 0)) { qi = commands[pi.underlay[pi.underlay.Count - 1]]; } if ((qi == null) || (qi.primitive.IsOpaque && qi.FullyCovers(pi))) { BrushProxy under = BrushProxy.CreateColorBrush(Colors.White); if (qi != null) { GeometryPrimitive qp = qi.primitive as GeometryPrimitive; if (qp != null) { under = qp.Brush; } } if (under != null) { // Blend it with brush underneath BrushProxy blendedBrush = gp.Brush; BrushProxy blendedPenBrush = gp.Pen == null ? null : gp.Pen.StrokeBrush; if (blendedBrush != null) { blendedBrush = under.BlendBrush(blendedBrush); } else if (blendedPenBrush != null) { blendedPenBrush = under.BlendBrush(blendedPenBrush); } // // Fix bug 1293500: // Allow blending to proceed only if we did not generate pen stroke // brush that is a brush list. Reason: Such a case would have to be // handled during rendering by stroking the object with each brush // in the list. But we're already rendering brushes of underlying // objects, so the optimization is pointless. // bool proceedBlending = true; if (blendedPenBrush != null && blendedPenBrush.BrushList != null) { proceedBlending = false; } if (proceedBlending) { gp.Brush = blendedBrush; if (gp.Pen != null) { gp.Pen.StrokeBrush = blendedPenBrush; } } if (proceedBlending && pi.primitive.IsOpaque) { #if DEBUG Console.WriteLine("Make {0} opaque", i); #endif if (pi.underlay != null) { for (int k = 0; k < pi.underlay.Count; k++) { commands[pi.underlay[k]].overlapHasTransparency--; } } return(true); } } } } return(false); }
/// <summary> /// Resolve overlapping for a single primitive /// </summary> /// <param name="primitive"></param> /// <param name="overlapping"></param> /// <param name="overlapHasTransparency"></param> /// <param name="disjoint"></param> /// <param name="desp"></param> private void AlphaRender(Primitive primitive, List <int> overlapping, int overlapHasTransparency, bool disjoint, string desp) { if (primitive == null) { return; } // Skip alpha flattening when there are too many layer of transparency if (overlapHasTransparency > Configuration.MaximumTransparencyLayer) { overlapping = null; } PrimitiveRenderer ri = new PrimitiveRenderer(); ri.Clip = primitive.Clip; ri.Brush = null; ri.Pen = null; ri.Overlapping = overlapping; ri.Commands = _dl.Commands; ri.DC = _dc; ri.Disjoint = disjoint; GeometryPrimitive p = primitive as GeometryPrimitive; if (p != null) { ri.Brush = p.Brush; ri.Pen = p.Pen; bool done = false; Geometry g = p.Geometry; GlyphPrimitive gp = p as GlyphPrimitive; if (gp != null) { done = ri.DrawGlyphs(gp.GlyphRun, gp.GetRectBounds(true), p.Transform, desp); if (!done) { g = p.WidenGeometry; } } if (!done) { if (!p.Transform.IsIdentity) { // Should not occur; GeometryPrimitive.ApplyTransform will push transform // to geometry and brush. g = Utility.TransformGeometry(g, p.Transform); if (ri.Brush != null) { ri.Brush = ri.Brush.ApplyTransformCopy(p.Transform); } if (ri.Pen != null && ri.Pen.StrokeBrush != null) { ri.Pen = ri.Pen.Clone(); ri.Pen.StrokeBrush = ri.Pen.StrokeBrush.ApplyTransformCopy(p.Transform); } } ri.DrawGeometry(g, desp, p); } return; } ImagePrimitive ip = primitive as ImagePrimitive; if (ip != null) { ri.RenderImage(ip.Image, ip.DstRect, ip.Clip, ip.Transform, desp); } else { Debug.Assert(false, "Wrong Primitive type"); } }