|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Windows; // for Rect WindowsBase.dll
using System.Windows.Media; // for Geometry, Brush, ImageData. PresentationCore.dll
using System.Windows.Media.Imaging; // for BitmapSource
namespace Microsoft.Internal.AlphaFlattener
{
/// <summary>
/// Render a primitive to IProxyDrawingContext
/// </summary>
internal class PrimitiveRenderer
{
#region Public Methods
// External API
public void RenderImage(ImageProxy image, Rect dest, Geometry clip, Matrix trans, string desp)
{
if (image == null)
{
return;
}
Geometry bounds;
bool clipToBounds;
if (clip == null)
{
// no clipping needed, draw everything
bounds = Utility.TransformGeometry(new RectangleGeometry(dest), trans);
clipToBounds = false;
}
else
{
// clip to provided geometry. it's already in world space
bounds = clip;
clipToBounds = true;
}
RenderImage(image, dest, bounds, clipToBounds, 0, trans, desp);
}
// 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);
}
}
}
// External API
public bool DrawGlyphs(GlyphRun glyphrun, Rect bounds, Matrix trans, string desp)
{
if (glyphrun == null)
{
return true;
}
int start = 0;
PrimitiveInfo topPI;
Geometry topBounds;
Geometry inter;
// If glyph has intersection with something on the top, change to geometry fill
// Use bounding rectangle to test for overlapping first, avoinding expensive BuildGeometry call
if ((_overlapping != null) && FindIntersection(new RectangleGeometry(bounds), ref start, out topPI, out topBounds, out inter))
{
start = 0;
Geometry cur = glyphrun.BuildGeometry();
cur = Utility.TransformGeometry(cur, trans);
if (FindIntersection(cur, ref start, out topPI, out topBounds, out inter))
{
// FillGeometry expects brush in world space. Apply trans to brush.
if (_brush != null)
{
_brush = _brush.ApplyTransformCopy(trans);
}
FillGeometry(topPI, cur, desp, null, null, start, inter, topBounds);
return true;
}
}
_dc.Comment(desp);
return _dc.DrawGlyphs(glyphrun, _clip, trans, _brush);
}
#endregion
#region Public Properties
public Geometry Clip
{
set { _clip = value; }
}
public BrushProxy Brush
{
get
{
return _brush;
}
set
{
_brush = value;
}
}
public PenProxy Pen
{
get
{
return _pen;
}
set
{
_pen = value;
}
}
public List<int> Overlapping
{
set { _overlapping = value; }
}
public List<PrimitiveInfo> Commands
{
set { _commands = value; }
}
public IProxyDrawingContext DC
{
set { _dc = value; }
}
public bool Disjoint
{
set { _disjoint = value; }
}
#endregion
#region Private Static Methods
private static Matrix ReverseMap(Matrix trans, Rect dest, double width, double height)
{
// Render the intersection using blended image
Matrix mat = Matrix.Identity;
// Transformation from source rect to destination
mat.Scale(dest.Width / width, dest.Height / height);
mat.Translate(dest.Left, dest.Top);
// Transformation from source rect to canvas coordinate space
mat.Append(trans);
// from canvas to source rect
mat.Invert();
return mat;
}
#if DEBUG
private static string Oper(string t1, char op, string t2)
{
char[] opers = new char[2];
opers[0] = '-';
opers[1] = '*';
if ((op != '-') && t1.IndexOfAny(opers) >= 0)
{
t1 = "(" + t1 + ")";
}
if (t2.IndexOfAny(opers) >= 0)
{
t2 = "(" + t2 + ")";
}
return t1 + op + t2;
}
#endif
#endregion
#region Private Methods
private void RenderImage(
ImageProxy image,
Rect dest,
Geometry bounds,
bool clipToBounds,
int start,
Matrix trans,
string desp
)
{
PrimitiveInfo topPI;
Geometry topBounds;
Geometry inter;
if (FindIntersection(bounds, ref start, out topPI, out topBounds, out inter))
{
Primitive p = topPI.primitive;
Geometry diff = Utility.Exclude(bounds, topBounds, trans);
// DrawImage may modify image
ImageProxy imageBlend = new ImageProxy(image.GetImage());
if (diff != null)
{
// Render cur - top
#if DEBUG
RenderImage(image, dest, diff, true, start + 1, trans, Oper(desp, '-', topPI.id));
#else
RenderImage(image, dest, diff, true, start + 1, trans, null);
#endif
}
if (!p.IsTransparent)
{
#if DEBUG
topPI.id = Oper(topPI.id, '-', Oper(desp, '.', "bounds"));
#endif
// Render the intersection using blended image
p.BlendOverImage(imageBlend, ReverseMap(trans, dest, imageBlend.PixelWidth, imageBlend.PixelHeight));
#if DEBUG
RenderImage(imageBlend, dest, inter, true, start + 1, trans, Oper(desp, '*', topPI.id));
#else
RenderImage(imageBlend, dest, inter, true, start + 1, trans, null);
#endif
}
p.Exclude(bounds);
}
else
{
Geometry clip = _clip;
bool empty = false;
if (clipToBounds)
{
clip = Utility.Intersect(clip, bounds, Matrix.Identity, out empty);
}
if (!empty)
{
_dc.Comment(desp);
_dc.DrawImage(image, dest, clip, trans);
}
}
}
// Find the next Primitive having intersection with cur in overlapping list
private bool FindIntersection(Geometry cur, ref int start, out PrimitiveInfo topPI, out Geometry topBounds, out Geometry inter)
{
topPI = null;
topBounds = null;
inter = null;
if (_overlapping == null)
{
return false;
}
// If not in a subtree which needs composition, igore overlapping list if all are opaque
if (!_disjoint)
{
bool allopaque = true;
for (int s = start; s < _overlapping.Count; s++)
{
PrimitiveInfo pi = _commands[_overlapping[s]] as PrimitiveInfo;
if ((pi != null) && !pi.primitive.IsTransparent && !pi.primitive.IsOpaque)
{
allopaque = false;
break;
}
}
if (allopaque)
{
return false;
}
}
// Search for all possible intersections
while (start < _overlapping.Count)
{
topPI = _commands[_overlapping[start]] as PrimitiveInfo;
if (!topPI.primitive.IsTransparent) // Skip primitives with nothing to draw
{
topBounds = topPI.primitive.GetClippedShapeGeometry();
if (topBounds != null)
{
bool empty;
inter = Utility.Intersect(cur, topBounds, Matrix.Identity, out empty);
if (inter != null)
{
return true;
}
}
}
start++;
}
return false;
}
// Recursive
// _brush must be in world space
private void FillGeometry(Geometry cur, string desp, Geometry curAlt, string despAlt, int start)
{
PrimitiveInfo pi;
Geometry top;
Geometry inter;
if (FindIntersection(cur, ref start, out pi, out top, out inter))
{
FillGeometry(pi, cur, desp, curAlt, despAlt, start, inter, top);
}
else
{
if (curAlt != null)
{
cur = curAlt;
desp = despAlt;
}
// Render to dc if nothing on top
_dc.Comment(desp);
_dc.DrawGeometry(_brush, _pen, cur, _clip, Matrix.Identity, ProxyDrawingFlags.None);
}
}
// Recursive
// _brush must be in world space
private void FillGeometry(
PrimitiveInfo topPI,
Geometry cur,
string desp,
Geometry curAlt,
string despAlt,
int start,
Geometry inter,
Geometry topBounds
)
{
Primitive p = topPI.primitive;
Geometry diff = Utility.Exclude(cur, topBounds, Matrix.Identity);
if (diff != null)
{
// Render cur [- topBounds] using original brush
if (_disjoint)
{
#if DEBUG
FillGeometry(diff, Oper(desp, '-', topPI.id), null, null, start + 1);
#else
FillGeometry(diff, null, null, null, start + 1);
#endif
}
else
{
// Only diff = cur - topBounds need to be rendered. But it may generate more
// complicated path and gaps between objects
if (curAlt != null)
{
#if DEBUG
FillGeometry(diff, Oper(desp, '-', topPI.id), curAlt, despAlt, start + 1);
#else
FillGeometry(diff, null, curAlt, despAlt, start + 1);
#endif
}
else
{
#if DEBUG
FillGeometry(diff, Oper(desp, '-', topPI.id), cur, desp, start + 1);
#else
FillGeometry(diff, null, cur, desp, start + 1);
#endif
}
}
}
//if (_disjoint || ! p.IsOpaque)
{
if (topPI.primitive is ImagePrimitive)
{
// If primitve on the top is ImagePrimitive, change it to DrawImage with blended image.
// An alternative will be generating an image brush
ImagePrimitive ip = topPI.primitive as ImagePrimitive;
bool empty;
double imageWidth = ip.Image.Image.Width;
double imageHeight = ip.Image.Image.Height;
// Get clip in world space.
Geometry clip = Utility.Intersect(inter, Utility.TransformGeometry(new RectangleGeometry(ip.DstRect), ip.Transform), ip.Transform, out empty);
if (!empty)
{
// Get clip bounds in image space.
Geometry clipImageSpace = Utility.TransformGeometry(clip, ReverseMap(ip.Transform, ip.DstRect, imageWidth, imageHeight));
Rect drawBounds = clipImageSpace.Bounds;
// Clip image data to the intersection. Resulting draw bounds are in image space.
BitmapSource clippedImage = ip.Image.GetClippedImage(drawBounds, out drawBounds);
if (clippedImage != null)
{
// Transform draw bounds back to world space.
drawBounds.Scale(ip.DstRect.Width / imageWidth, ip.DstRect.Height / imageHeight);
drawBounds.Offset(ip.DstRect.Left, ip.DstRect.Top);
ImageProxy image = new ImageProxy(clippedImage);
// Blend image with other brush, then render composited image.
image.BlendOverBrush(false, _brush, ReverseMap(ip.Transform, drawBounds, image.PixelWidth, image.PixelHeight));
#if DEBUG
RenderImage(image, drawBounds, clip, true, start + 1, ip.Transform, Oper(desp, '*', topPI.id));
#else
RenderImage(image, drawBounds, clip, true, start + 1, ip.Transform, null);
#endif
}
}
}
else
{
// -- If top primitive opaque, skip the intersection
// -- If current primitive is completely covered by an opaque object, skip the intersection
if (p.IsOpaque) // && Utility.Covers(topBounds, cur))
{
cur = null;
}
else
{
// Render the intersection using blended brush
BrushProxy oldbrush = _brush;
_brush = p.BlendBrush(_brush);
#if DEBUG
FillGeometry(inter, Oper(desp, '*', topPI.id), null, null, start + 1);
#else
FillGeometry(inter, null, null, null, start + 1);
#endif
_brush = oldbrush;
}
}
if (cur != null)
{
bool empty;
Geometry geo = Utility.Intersect(cur, _clip, Matrix.Identity, out empty);
if (geo != null)
{
topPI.primitive.Exclude(geo); // exclude cur & _clip
#if DEBUG
topPI.id = Oper(topPI.id, '-', Oper(desp, '*', Oper(desp, '.', "c")));
#endif
}
}
}
}
#endregion
#region Private Fields
private Geometry _clip;
private BrushProxy _brush; // primitive brush, possibly in world space
private PenProxy _pen;
private List<int> _overlapping;
private List<PrimitiveInfo> _commands;
private IProxyDrawingContext _dc;
private bool _disjoint;
#endregion
} // end of class PrimitiveRenderer
} // end of namespace
|