Commit 1a00f391 authored by Eric Domke's avatar Eric Domke
Browse files

Bug fixes: Gradients and Filters

- Support of generic fonts (serif, sans-serif, monospace)
- Initial support of filters
- Fixes to gradient rendering
parent 2187be3e
......@@ -99,34 +99,66 @@ namespace Svg
this._requiresSmoothRendering = false;
}
protected virtual bool Renderable { get { return true; } }
/// <summary>
/// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> object to render to.</param>
protected override void Render(SvgRenderer renderer)
{
if ((this.Path(renderer) != null) && this.Visible && this.Displayable)
this.Render(renderer, true);
}
private void Render(SvgRenderer renderer, bool renderFilter)
{
if (this.Visible && this.Displayable && this.PushTransforms(renderer) &&
(!Renderable || this.Path(renderer) != null))
{
this.PushTransforms(renderer);
this.SetClip(renderer);
bool renderNormal = true;
// If this element needs smoothing enabled turn anti-aliasing on
if (this.RequiresSmoothRendering)
if (renderFilter && this.Filter != null)
{
renderer.SmoothingMode = SmoothingMode.AntiAlias;
var filter = this.OwnerDocument.IdManager.GetElementById(this.Filter) as FilterEffects.SvgFilter;
if (filter != null)
{
this.PopTransforms(renderer);
filter.ApplyFilter(this, renderer, (r) => this.Render(r, false));
renderNormal = false;
}
}
this.RenderFill(renderer);
this.RenderStroke(renderer);
// Reset the smoothing mode
if (this.RequiresSmoothRendering && renderer.SmoothingMode == SmoothingMode.AntiAlias)
if (renderNormal)
{
renderer.SmoothingMode = SmoothingMode.Default;
this.SetClip(renderer);
if (Renderable)
{
// If this element needs smoothing enabled turn anti-aliasing on
if (this.RequiresSmoothRendering)
{
renderer.SmoothingMode = SmoothingMode.AntiAlias;
}
this.RenderFill(renderer);
this.RenderStroke(renderer);
// Reset the smoothing mode
if (this.RequiresSmoothRendering && renderer.SmoothingMode == SmoothingMode.AntiAlias)
{
renderer.SmoothingMode = SmoothingMode.Default;
}
}
else
{
base.RenderChildren(renderer);
}
this.ResetClip(renderer);
this.PopTransforms(renderer);
}
this.ResetClip(renderer);
this.PopTransforms(renderer);
}
}
......
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Reflection;
using System.ComponentModel;
......@@ -53,6 +54,16 @@ namespace Svg
}
}
/// <summary>
/// Gets or sets the fill <see cref="SvgPaintServer"/> of this element.
/// </summary>
[SvgAttribute("enable-background")]
public virtual string EnableBackground
{
get { return this.Attributes["enable-background"] as string; }
set { this.Attributes["enable-background"] = value; }
}
/// <summary>
/// Gets or sets the fill <see cref="SvgPaintServer"/> of this element.
/// </summary>
......@@ -361,26 +372,43 @@ namespace Svg
break;
}
var family = ValidateFontFamily(this.FontFamily);
if (!family.IsStyleAvailable(fontStyle))
{
// Do Something
}
// Get the font-family
string family = ValidateFontFamily(this.FontFamily) ?? DefaultFontFamily;
return new System.Drawing.Font(family, fontSize, fontStyle, System.Drawing.GraphicsUnit.Pixel);
}
private static string ValidateFontFamily(string fontFamilyList)
private static FontFamily ValidateFontFamily(string fontFamilyList)
{
// Split font family list on "," and then trim start and end spaces and quotes.
var fontParts = (fontFamilyList ?? "").Split(new[] { ',' }).Select(fontName => fontName.Trim(new[] { '"', ' ', '\'' }));
var families = System.Drawing.FontFamily.Families;
FontFamily family;
// Find a the first font that exists in the list of installed font families.
//styles from IE get sent through as lowercase.
foreach (var f in fontParts.Where(f => families.Any(family => family.Name.ToLower() == f.ToLower())))
foreach (var f in fontParts)
{
return f;
family = families.FirstOrDefault(ff => ff.Name.ToLower() == f.ToLower());
if (family != null) return family;
switch (f)
{
case "serif":
return System.Drawing.FontFamily.GenericSerif;
case "sans-serif":
return System.Drawing.FontFamily.GenericSansSerif;
case "monospace":
return System.Drawing.FontFamily.GenericMonospace;
}
}
// No valid font family found from the list requested.
return null;
return System.Drawing.FontFamily.GenericSansSerif;
}
}
......
......@@ -23,6 +23,12 @@ namespace Svg
return ret;
}
public static bool IsNullOrEmpty(SvgUnitCollection collection)
{
return collection == null || collection.Count < 1 ||
(collection.Count == 1 && (collection[0] == SvgUnit.Empty || collection[0] == SvgUnit.None));
}
}
/// <summary>
......
......@@ -56,23 +56,25 @@ namespace Svg
}
}
/// <summary>
/// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
/// </summary>
/// <param name="graphics">The <see cref="Graphics"/> object to render to.</param>
protected override void Render(SvgRenderer renderer)
{
if (!Visible || !Displayable)
return;
protected override bool Renderable { get { return false; } }
if (this.PushTransforms(renderer))
{
this.SetClip(renderer);
base.RenderChildren(renderer);
this.ResetClip(renderer);
this.PopTransforms(renderer);
}
}
///// <summary>
///// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
///// </summary>
///// <param name="graphics">The <see cref="Graphics"/> object to render to.</param>
//protected override void Render(SvgRenderer renderer)
//{
// if (!Visible || !Displayable)
// return;
// if (this.PushTransforms(renderer))
// {
// this.SetClip(renderer);
// base.RenderChildren(renderer);
// this.ResetClip(renderer);
// this.PopTransforms(renderer);
// }
//}
public override SvgElement DeepCopy()
......
......@@ -77,6 +77,8 @@ namespace Svg
// }
// }
protected override bool Renderable { get { return false; } }
protected override void Render(SvgRenderer renderer)
{
if (!Visible || !Displayable)
......
......@@ -56,21 +56,23 @@ namespace Svg
}
}
/// <summary>
/// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
/// </summary>
/// <param name="renderer">The <see cref="Graphics"/> object to render to.</param>
protected override void Render(SvgRenderer renderer)
{
if (!Visible || !Displayable)
return;
protected override bool Renderable { get { return false; } }
this.PushTransforms(renderer);
this.SetClip(renderer);
base.RenderChildren(renderer);
this.ResetClip(renderer);
this.PopTransforms(renderer);
}
///// <summary>
///// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
///// </summary>
///// <param name="renderer">The <see cref="Graphics"/> object to render to.</param>
//protected override void Render(SvgRenderer renderer)
//{
// if (!Visible || !Displayable)
// return;
// this.PushTransforms(renderer);
// this.SetClip(renderer);
// base.RenderChildren(renderer);
// this.ResetClip(renderer);
// this.PopTransforms(renderer);
//}
public override SvgElement DeepCopy()
{
......
......@@ -7,6 +7,28 @@ using ExCSS.Model.TextBlocks;
// ReSharper disable once CheckNamespace
using System;
//The MIT License (MIT)
//Copyright (c) [year] [fullname]
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
namespace ExCSS
{
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
namespace Svg.FilterEffects
{
public class ImageBuffer : IDictionary<string, Bitmap>
{
private const string BufferKey = "__!!BUFFER";
private Dictionary<string, Bitmap> _images;
private RectangleF _bounds;
private SvgRenderer _renderer;
private Action<SvgRenderer> _renderMethod;
private float _inflate;
public Matrix Transform { get; set; }
public Bitmap Buffer
{
get { return _images[BufferKey]; }
}
public int Count
{
get { return _images.Count; }
}
public Bitmap this[string key]
{
get
{
return ProcessResult(key, _images[ProcessKey(key)]);
}
set
{
_images[ProcessKey(key)] = value;
if (key != null) _images[BufferKey] = value;
}
}
public ImageBuffer(RectangleF bounds, float inflate, SvgRenderer renderer, Action<SvgRenderer> renderMethod)
{
_bounds = bounds;
_inflate = inflate;
_renderer = renderer;
_renderMethod = renderMethod;
_images = new Dictionary<string, Bitmap>();
_images[SvgFilterPrimitive.BackgroundAlpha] = null;
_images[SvgFilterPrimitive.BackgroundImage] = null;
_images[SvgFilterPrimitive.FillPaint] = null;
_images[SvgFilterPrimitive.SourceAlpha] = null;
_images[SvgFilterPrimitive.SourceGraphic] = null;
_images[SvgFilterPrimitive.StrokePaint] = null;
}
public void Add(string key, Bitmap value)
{
_images.Add(ProcessKey(key), value);
}
public bool ContainsKey(string key)
{
return _images.ContainsKey(ProcessKey(key));
}
public void Clear()
{
_images.Clear();
}
public IEnumerator<KeyValuePair<string, Bitmap>> GetEnumerator()
{
return _images.GetEnumerator();
}
public bool Remove(string key)
{
switch (key)
{
case SvgFilterPrimitive.BackgroundAlpha:
case SvgFilterPrimitive.BackgroundImage:
case SvgFilterPrimitive.FillPaint:
case SvgFilterPrimitive.SourceAlpha:
case SvgFilterPrimitive.SourceGraphic:
case SvgFilterPrimitive.StrokePaint:
return false;
default:
return _images.Remove(ProcessKey(key));
}
}
public bool TryGetValue(string key, out Bitmap value)
{
if (_images.TryGetValue(ProcessKey(key), out value))
{
value = ProcessResult(key, value);
return true;
}
else
{
return false;
}
}
private Bitmap ProcessResult(string key, Bitmap curr)
{
if (curr == null)
{
switch (key)
{
case SvgFilterPrimitive.BackgroundAlpha:
case SvgFilterPrimitive.BackgroundImage:
case SvgFilterPrimitive.FillPaint:
case SvgFilterPrimitive.StrokePaint:
// Do nothing
return null;
case SvgFilterPrimitive.SourceAlpha:
_images[key] = CreateSourceAlpha();
return _images[key];
case SvgFilterPrimitive.SourceGraphic:
_images[key] = CreateSourceGraphic();
return _images[key];
}
}
return curr;
}
private string ProcessKey(string key)
{
if (string.IsNullOrEmpty(key)) return BufferKey;
return key;
}
private Bitmap CreateSourceGraphic()
{
var graphic = new Bitmap((int)(_bounds.Width + 2 * _inflate * _bounds.Width + _bounds.X),
(int)(_bounds.Height + 2 * _inflate * _bounds.Height + _bounds.Y));
using (var renderer = SvgRenderer.FromImage(graphic))
{
renderer.Boundable(_renderer.Boundable());
var transform = new Matrix();
transform.Translate(_bounds.Width * _inflate, _bounds.Height * _inflate);
renderer.Transform = transform;
//renderer.Transform = _renderer.Transform;
//renderer.Clip = _renderer.Clip;
_renderMethod.Invoke(renderer);
}
return graphic;
}
private Bitmap CreateSourceAlpha()
{
Bitmap source = this[SvgFilterPrimitive.SourceGraphic];
float[][] colorMatrixElements = {
new float[] {0, 0, 0, 0, 0}, // red
new float[] {0, 0, 0, 0, 0}, // green
new float[] {0, 0, 0, 0, 0}, // blue
new float[] {0, 0, 0, 1, 1}, // alpha
new float[] {0, 0, 0, 0, 0} }; // translations
var matrix = new ColorMatrix(colorMatrixElements);
ImageAttributes attributes = new ImageAttributes();
attributes.SetColorMatrix(matrix);
var sourceAlpha = new Bitmap(source.Width, source.Height);
using (var graphics = Graphics.FromImage(sourceAlpha))
{
graphics.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height), 0, 0,
source.Width, source.Height, GraphicsUnit.Pixel, attributes);
graphics.Save();
}
return sourceAlpha;
}
bool ICollection<KeyValuePair<string, Bitmap>>.IsReadOnly
{
get { return false; }
}
ICollection<string> IDictionary<string, Bitmap>.Keys
{
get { return _images.Keys; }
}
ICollection<Bitmap> IDictionary<string, Bitmap>.Values
{
get { return _images.Values; }
}
void ICollection<KeyValuePair<string, Bitmap>>.Add(KeyValuePair<string, Bitmap> item)
{
_images.Add(item.Key, item.Value);
}
bool ICollection<KeyValuePair<string, Bitmap>>.Contains(KeyValuePair<string, Bitmap> item)
{
return ((IDictionary<string, Bitmap>)_images).Contains(item);
}
void ICollection<KeyValuePair<string, Bitmap>>.CopyTo(KeyValuePair<string, Bitmap>[] array, int arrayIndex)
{
((IDictionary<string, Bitmap>)_images).CopyTo(array, arrayIndex);
}
bool ICollection<KeyValuePair<string, Bitmap>>.Remove(KeyValuePair<string, Bitmap> item)
{
return _images.Remove(item.Key);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _images.GetEnumerator();
}
}
}
......@@ -72,15 +72,11 @@ namespace Svg.FilterEffects
set { this.Attributes["color-interpolation-filters"] = value; }
}
internal Dictionary<string, Func<SvgVisualElement, SvgRenderer, Bitmap>> Buffer { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="SvgFilter"/> class.
/// </summary>
public SvgFilter()
{
this.Buffer = new Dictionary<string, Func<SvgVisualElement, SvgRenderer, Bitmap>>();
}
/// <summary>
......@@ -103,10 +99,32 @@ namespace Svg.FilterEffects
return (SvgFilter)this.MemberwiseClone();
}
public void ApplyFilter(SvgVisualElement element, SvgRenderer renderer)
private Matrix GetTransform(SvgVisualElement element)
{
var transformMatrix = new Matrix();
foreach (var transformation in element.Transforms)
{
transformMatrix.Multiply(transformation.Matrix);
}
return transformMatrix;
}
private RectangleF GetPathBounds(SvgVisualElement element, SvgRenderer renderer, Matrix transform)
{
var bounds = element.Path(renderer).GetBounds();
var pts = new PointF[] { bounds.Location, new PointF(bounds.Right, bounds.Bottom) };
transform.TransformPoints(pts);
return new RectangleF(Math.Min(pts[0].X, pts[1].X), Math.Min(pts[0].Y, pts[1].Y),
Math.Abs(pts[0].X - pts[1].X), Math.Abs(pts[0].Y - pts[1].Y));
}
public void ApplyFilter(SvgVisualElement element, SvgRenderer renderer, Action<SvgRenderer> renderMethod)
{
this.Buffer.Clear();
this.PopulateDefaults(element, renderer);
var inflate = 0.5f;
var transform = GetTransform(element);
var bounds = GetPathBounds(element, renderer, transform);
var buffer = new ImageBuffer(bounds, inflate, renderer, renderMethod) { Transform = transform };
IEnumerable<SvgFilterPrimitive> primitives = this.Children.OfType<SvgFilterPrimitive>();
......@@ -114,21 +132,21 @@ namespace Svg.FilterEffects
{
foreach (var primitive in primitives)
{
this.Buffer.Add(primitive.Result, (e, r) => primitive.Process());
primitive.Process(buffer);
}
// Render the final filtered image
renderer.DrawImageUnscaled(this.Buffer.Last().Value(element, renderer), new Point(0, 0));
var bufferImg = buffer.Buffer;
bufferImg.Save(@"C:\test.png");
var imgDraw = RectangleF.Inflate(bounds, inflate * bounds.Width, inflate * bounds.Height);
var prevClip = renderer.Clip;
renderer.Clip = new Region(imgDraw);
renderer.DrawImage(bufferImg, imgDraw, new RectangleF(bounds.X, bounds.Y, imgDraw.Width, imgDraw.Height), GraphicsUnit.Pixel);
renderer.Clip = prevClip;
//renderer.DrawImage(bufferImg, bounds, bounds, GraphicsUnit.Pixel);
}
}
private void PopulateDefaults(SvgVisualElement element, SvgRenderer renderer)
{
this.ResetDefaults();
this.Buffer.Add(SvgFilterPrimitive.SourceGraphic, this.CreateSourceGraphic);
this.Buffer.Add(SvgFilterPrimitive.SourceAlpha, this.CreateSourceAlpha);
}
#region Defaults
......@@ -147,58 +165,7 @@ namespace Svg.FilterEffects
}
}
private Bitmap CreateSourceGraphic(SvgVisualElement element, SvgRenderer renderer)
{
if (this.sourceGraphic == null)
{
RectangleF bounds = element.Path(renderer).GetBounds();
this.sourceGraphic = new Bitmap((int)bounds.Width, (int)bounds.Height);
using (var graphics = Graphics.FromImage(this.sourceGraphic))
{
graphics.Clip = renderer.Clip;
graphics.Transform = renderer.Transform;
element.RenderElement(SvgRenderer.FromGraphics(graphics));
graphics.Save();
}
}
return this.sourceGraphic;
}
private Bitmap CreateSourceAlpha(SvgVisualElement element, SvgRenderer renderer)
{
if (this.sourceAlpha == null)
{
Bitmap source = this.Buffer[SvgFilterPrimitive.SourceGraphic](element, renderer);
float[][] colorMatrixElements = {
new float[] {0, 0, 0, 0, 0}, // red
new float[] {0, 0, 0, 0, 0}, // green
new float[] {0, 0, 0, 0, 0}, // blue
new float[] {0, 0, 0, 1, 1}, // alpha
new float[] {0, 0, 0, 0, 0} }; // translations
var matrix = new ColorMatrix(colorMatrixElements);
ImageAttributes attributes = new ImageAttributes();
attributes.SetColorMatrix(matrix);
this.sourceAlpha = new Bitmap(source.Width, source.Height);
using (var graphics = Graphics.FromImage(this.sourceAlpha))
{
graphics.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height), 0, 0,
source.Width, source.Height, GraphicsUnit.Pixel, attributes);
graphics.Save();
}
}
return this.sourceAlpha;
}
#endregion
......
......@@ -8,12 +8,12 @@ namespace Svg.FilterEffects
{
public abstract class SvgFilterPrimitive : SvgElement
{
public static readonly string SourceGraphic = "SourceGraphic";
public static readonly string SourceAlpha = "SourceAlpha";
public static readonly string BackgroundImage = "BackgroundImage";
public static readonly string BackgroundAlpha = "BackgroundAlpha";
public static readonly string FillPaint = "FillPaint";
public static readonly string StrokePaint = "StrokePaint";
public const string SourceGraphic = "SourceGraphic";
public const string SourceAlpha = "SourceAlpha";
public const string BackgroundImage = "BackgroundImage";
public const string BackgroundAlpha = "BackgroundAlpha";
public const string FillPaint = "FillPaint";
public const string StrokePaint = "StrokePaint";
[SvgAttribute("in")]
public string Input
......@@ -34,6 +34,6 @@ namespace Svg.FilterEffects
get { return (SvgFilter)this.Parent; }
}
public abstract Bitmap Process();
public abstract void Process(ImageBuffer buffer);
}
}
\ No newline at end of file
using System;
using System.Drawing;
using System.Collections.Generic;
using Svg.Filter_Effects.feColourMatrix;
using System.Linq;
using System.Drawing.Imaging;
namespace Svg.FilterEffects
{
......@@ -19,8 +20,7 @@ namespace Svg.FilterEffects
/// </summary>
[SvgAttribute("type")]
public SvgColourMatrixType Type { get; set; }
/// <summary>
/// list of <number>s
......@@ -29,13 +29,74 @@ namespace Svg.FilterEffects
/// </summary>
[SvgAttribute("values")]
public string Values { get; set; }
public override void Process(ImageBuffer buffer)
{
var inputImage = buffer[this.Input];
float[][] colorMatrixElements;
float value;
switch (this.Type)
{
case SvgColourMatrixType.hueRotate:
value = (string.IsNullOrEmpty(this.Values) ? 0 : float.Parse(this.Values));
colorMatrixElements = new float[][] {
new float[] {(float)(0.213 + Math.Cos(value) * +0.787 + Math.Sin(value) * -0.213),
(float)(0.715 + Math.Cos(value) * -0.715 + Math.Sin(value) * -0.715),
(float)(0.072 + Math.Cos(value) * -0.072 + Math.Sin(value) * +0.928), 0, 0},
new float[] {(float)(0.213 + Math.Cos(value) * -0.213 + Math.Sin(value) * +0.143),
(float)(0.715 + Math.Cos(value) * +0.285 + Math.Sin(value) * +0.140),
(float)(0.072 + Math.Cos(value) * -0.072 + Math.Sin(value) * -0.283), 0, 0},
new float[] {(float)(0.213 + Math.Cos(value) * -0.213 + Math.Sin(value) * -0.787),
(float)(0.715 + Math.Cos(value) * -0.715 + Math.Sin(value) * +0.715),
(float)(0.072 + Math.Cos(value) * +0.928 + Math.Sin(value) * +0.072), 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
};
break;
case SvgColourMatrixType.luminanceToAlpha:
colorMatrixElements = new float[][] {
new float[] {0, 0, 0, 0, 0},
new float[] {0, 0, 0, 0, 0},
new float[] {0, 0, 0, 0, 0},
new float[] {0.2125f, 0.7154f, 0.0721f, 0, 0},
new float[] {0, 0, 0, 0, 1}
};
break;
case SvgColourMatrixType.saturate:
value = (string.IsNullOrEmpty(this.Values) ? 1 : float.Parse(this.Values));
colorMatrixElements = new float[][] {
new float[] {(float)(0.213+0.787*value), (float)(0.715-0.715*value), (float)(0.072-0.072*value), 0, 0},
new float[] {(float)(0.213-0.213*value), (float)(0.715+0.285*value), (float)(0.072-0.072*value), 0, 0},
new float[] {(float)(0.213-0.213*value), (float)(0.715-0.715*value), (float)(0.072+0.928*value), 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
};
break;
default: // Matrix
var parts = this.Values.Split(new char[] { ' ', '\t', '\n', '\r', ',' });
colorMatrixElements = new float[5][];
for (int i = 0; i < 4; i++)
{
colorMatrixElements[i] = parts.Skip(i * 5).Take(5).Select(v => float.Parse(v)).ToArray();
}
colorMatrixElements[4] = new float[] { 0, 0, 0, 0, 1 };
break;
}
var colorMatrix = new ColorMatrix(colorMatrixElements);
var imageAttrs = new ImageAttributes();
imageAttrs.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
public override Bitmap Process()
{
return null;
var result = new Bitmap(inputImage.Width, inputImage.Height);
using (var g = Graphics.FromImage(result))
{
g.DrawImage(inputImage, new Rectangle(0, 0, inputImage.Width, inputImage.Height),
0, 0, inputImage.Width, inputImage.Height, GraphicsUnit.Pixel, imageAttrs);
g.Flush();
}
buffer[this.Result] = result;
}
......
......@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Svg.Filter_Effects.feColourMatrix
namespace Svg.FilterEffects
{
public enum SvgColourMatrixType
{
......
......@@ -67,7 +67,10 @@ namespace Svg.FilterEffects
public Bitmap Apply(Image inputImage)
{
using (RawBitmap src = new RawBitmap(new Bitmap(inputImage)))
var bitmapSrc = inputImage as Bitmap;
if (bitmapSrc == null) bitmapSrc = new Bitmap(inputImage);
using (RawBitmap src = new RawBitmap(bitmapSrc))
{
using (RawBitmap dest = new RawBitmap(new Bitmap(inputImage.Width, inputImage.Height)))
{
......@@ -250,11 +253,11 @@ namespace Svg.FilterEffects
public override Bitmap Process()
public override void Process(ImageBuffer buffer)
{
//Todo
return null;
var inputImage = buffer[this.Input];
var result = Apply(inputImage);
buffer[this.Result] = result;
}
......
......@@ -5,44 +5,34 @@ using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
namespace Svg.FilterEffects
{
[SvgElement("feMerge")]
public class SvgMerge : SvgFilterPrimitive
{
public StringCollection MergeResults { get; private set; }
public SvgMerge()
public override void Process(ImageBuffer buffer)
{
MergeResults = new StringCollection();
var children = this.Children.OfType<SvgMergeNode>().ToList();
var inputImage = buffer[children.First().Input];
var result = new Bitmap(inputImage.Width, inputImage.Height);
using (var g = Graphics.FromImage(result))
{
foreach (var child in children)
{
g.DrawImage(buffer[child.Input], new Rectangle(0, 0, inputImage.Width, inputImage.Height),
0, 0, inputImage.Width, inputImage.Height, GraphicsUnit.Pixel);
}
g.Flush();
}
result.Save(@"C:\test.png");
buffer[this.Result] = result;
}
public override Bitmap Process()
{
//Todo
//Bitmap merged = new Bitmap((int)this.Owner.Width.Value, (int)this.Owner.Height.Value);
//Graphics mergedGraphics = Graphics.FromImage(merged);
//foreach (string resultId in this.MergeResults)
//{
// mergedGraphics.DrawImageUnscaled(this.Owner.Results[resultId](), new Point(0, 0));
//}
//mergedGraphics.Save();
//mergedGraphics.Dispose();
//results.Add(this.Result, () => merged);
return null;
}
public override SvgElement DeepCopy()
{
throw new NotImplementedException();
return DeepCopy<SvgMerge>();
}
}
......
......@@ -10,13 +10,13 @@ namespace Svg.FilterEffects
{
[SvgElement("feMergeNode")]
public class SvgMergeNode : SvgFilterPrimitive
public class SvgMergeNode : SvgElement
{
public override Bitmap Process()
[SvgAttribute("in")]
public string Input
{
//Todo
return null;
get { return this.Attributes.GetAttribute<string>("in"); }
set { this.Attributes["in"] = value; }
}
public override SvgElement DeepCopy()
......
using System;
using System.Drawing;
using System.Collections.Generic;
using Svg.Filter_Effects.feColourMatrix;
namespace Svg.FilterEffects
{
......@@ -28,13 +27,27 @@ namespace Svg.FilterEffects
/// Note: this is not used in calculations to bitmap - used only to allow for svg xml output
/// </summary>
[SvgAttribute("dy")]
public string Dy { get; set; }
public SvgUnit Dy { get; set; }
public override Bitmap Process()
public override void Process(ImageBuffer buffer)
{
return null;
var inputImage = buffer[this.Input];
var result = new Bitmap(inputImage.Width, inputImage.Height);
var pts = new PointF[] { new PointF(this.Dx.ToDeviceValue(null, UnitRenderingType.Horizontal, null),
this.Dy.ToDeviceValue(null, UnitRenderingType.Vertical, null)) };
buffer.Transform.TransformVectors(pts);
using (var g = Graphics.FromImage(result))
{
g.DrawImage(inputImage, new Rectangle((int)pts[0].X, (int)pts[0].Y,
inputImage.Width, inputImage.Height),
0, 0, inputImage.Width, inputImage.Height, GraphicsUnit.Pixel);
g.Flush();
}
buffer[this.Result] = result;
}
......
......@@ -111,7 +111,7 @@ namespace Svg
}
}
private Matrix EffectiveGradientTransform
protected Matrix EffectiveGradientTransform
{
get
{
......@@ -247,6 +247,15 @@ namespace Svg
return newVector[0];
}
protected float TransformDistance(float dist)
{
var newVector = new[] { new PointF(dist, 0) };
EffectiveGradientTransform.TransformVectors(newVector);
return (float)Math.Sqrt(Math.Pow(newVector[0].X, 2) + Math.Pow(newVector[0].Y, 2));
}
protected static double CalculateDistance(PointF first, PointF second)
{
return Math.Sqrt(Math.Pow(first.X - second.X, 2) + Math.Pow(first.Y - second.Y, 2));
......
......@@ -173,7 +173,7 @@ namespace Svg
if (boundable.Bounds.Contains(specifiedEnd))
{
effectiveEnd = CalculateClosestIntersectionPoint(effectiveEnd, intersectionPoints);
effectiveEnd = CalculateClosestIntersectionPoint(expandedEnd, intersectionPoints);
effectiveEnd = MovePointAlongVector(effectiveEnd, specifiedUnitVector, 1);
}
......@@ -296,48 +296,87 @@ namespace Svg
return result;
}
/// <remarks>http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2</remarks>
private PointF? Intersection(LineF other)
{
var a1 = Y2 - Y1;
var b1 = X1 - X2;
var c1 = X2 * Y1 - X1 * Y2;
const int precision = 8;
var r3 = a1 * other.X1 + b1 * other.Y1 + c1;
var r4 = a1 * other.X2 + b1 * other.Y2 + c1;
var a1 = (double)Y2 - Y1;
var b1 = (double)X1 - X2;
var c1 = a1 * X1 + b1 * Y1;
if (r3 != 0 && r4 != 0 && Math.Sign(r3) == Math.Sign(r4))
var a2 = (double)other.Y2 - other.Y1;
var b2 = (double)other.X1 - other.X2;
var c2 = a2 * other.X1 + b2 * other.Y1;
var det = a1 * b2 - a2 * b1;
if (det == 0)
{
return null;
}
else
{
var xi = (b2 * c1 - b1 * c2) / det;
var yi = (a1 * c2 - a2 * c1) / det;
if (Math.Round(Math.Min(X1, X2), precision) <= Math.Round(xi, precision) &&
Math.Round(xi, precision) <= Math.Round(Math.Max(X1, X2), precision) &&
Math.Round(Math.Min(Y1, Y2), precision) <= Math.Round(yi, precision) &&
Math.Round(yi, precision) <= Math.Round(Math.Max(Y1, Y2), precision) &&
Math.Round(Math.Min(other.X1, other.X2), precision) <= Math.Round(xi, precision) &&
Math.Round(xi, precision) <= Math.Round(Math.Max(other.X1, other.X2), precision) &&
Math.Round(Math.Min(other.Y1, other.Y2), precision) <= Math.Round(yi, precision) &&
Math.Round(yi, precision) <= Math.Round(Math.Max(other.Y1, other.Y2), precision))
{
return new PointF((float)xi, (float)yi);
}
else
{
return null;
}
}
var a2 = other.Y2 - other.Y1;
var b2 = other.X1 - other.X2;
var c2 = other.X2 * other.Y1 - other.X1 * other.Y2;
var r1 = a2 * X1 + b2 * Y1 + c2;
var r2 = a2 * X2 + b2 * Y2 + c2;
//var a1 = Y2 - Y1;
//var b1 = X1 - X2;
//var c1 = X2 * Y1 - X1 * Y2;
if (r1 != 0 && r2 != 0 && Math.Sign(r1) == Math.Sign(r2))
{
return (null);
}
//var r3 = a1 * other.X1 + b1 * other.Y1 + c1;
//var r4 = a1 * other.X2 + b1 * other.Y2 + c1;
var denom = a1 * b2 - a2 * b1;
//if (r3 != 0 && r4 != 0 && Math.Sign(r3) == Math.Sign(r4))
//{
// return null;
//}
if (denom == 0)
{
return null;
}
//var a2 = other.Y2 - other.Y1;
//var b2 = other.X1 - other.X2;
//var c2 = other.X2 * other.Y1 - other.X1 * other.Y2;
//var r1 = a2 * X1 + b2 * Y1 + c2;
//var r2 = a2 * X2 + b2 * Y2 + c2;
//if (r1 != 0 && r2 != 0 && Math.Sign(r1) == Math.Sign(r2))
//{
// return (null);
//}
//var denom = a1 * b2 - a2 * b1;
//if (denom == 0)
//{
// return null;
//}
var offset = denom < 0 ? -denom / 2 : denom / 2;
//var offset = denom < 0 ? -denom / 2 : denom / 2;
var num = b1 * c2 - b2 * c1;
var x = (num < 0 ? num - offset : num + offset) / denom;
//var num = b1 * c2 - b2 * c1;
//var x = (num < 0 ? num - offset : num + offset) / denom;
num = a2 * c1 - a1 * c2;
var y = (num < 0 ? num - offset : num + offset) / denom;
//num = a2 * c1 - a1 * c2;
//var y = (num < 0 ? num - offset : num + offset) / denom;
return new PointF(x, y);
//return new PointF(x, y);
}
private static void AddIfIntersect(LineF first, LineF second, ICollection<PointF> result)
......
using System;
using System.Diagnostics;
using System.Drawing;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Linq;
......@@ -95,6 +96,8 @@ namespace Svg
Radius = new SvgUnit(SvgUnitType.Percentage, 50F);
}
private object _lockObj = new Object();
public override Brush GetBrush(SvgVisualElement renderingElement, SvgRenderer renderer, float opacity)
{
LoadStops(renderingElement);
......@@ -102,20 +105,39 @@ namespace Svg
try
{
if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.Boundable(renderingElement);
// Calculate the path and transform it appropriately
var origin = renderer.Boundable().Location;
var centerPoint = CalculateCenterPoint(renderer, origin);
var focalPoint = CalculateFocalPoint(renderer, origin);
var specifiedRadius = CalculateRadius(renderer);
var effectiveRadius = CalculateEffectiveRadius(renderingElement, centerPoint, specifiedRadius);
var brush = new PathGradientBrush(CreateGraphicsPath(origin, centerPoint, effectiveRadius))
{
InterpolationColors = CalculateColorBlend(renderer, opacity, specifiedRadius, effectiveRadius),
CenterPoint = focalPoint
};
Debug.Assert(brush.Rectangle.Contains(renderingElement.Bounds), "Brush rectangle does not contain rendering element bounds!");
var center = new PointF(origin.X + CenterX.ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, this),
origin.Y + CenterY.ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, this));
var specifiedRadius = Radius.ToDeviceValue(renderer, UnitRenderingType.Other, this);
var path = new GraphicsPath();
path.AddEllipse(
origin.X + center.X - specifiedRadius, origin.Y + center.Y - specifiedRadius,
specifiedRadius * 2, specifiedRadius * 2
);
path.Transform(EffectiveGradientTransform);
// Calculate any required scaling
var scale = CalcScale(renderingElement.Bounds, path);
// Get the color blend and any tweak to the scaling
var blend = CalculateColorBlend(renderer, opacity, scale, out scale);
// Transform the path based on the scaling
var gradBounds = path.GetBounds();
var transCenter = new PointF(gradBounds.Left + gradBounds.Width / 2, gradBounds.Top + gradBounds.Height / 2);
var scaleMat = new Matrix();
scaleMat.Translate(-1 * transCenter.X, -1 * transCenter.Y, MatrixOrder.Append);
scaleMat.Scale(scale, scale, MatrixOrder.Append);
scaleMat.Translate(transCenter.X, transCenter.Y, MatrixOrder.Append);
path.Transform(scaleMat);
// calculate the brush
var brush = new PathGradientBrush(path);
brush.CenterPoint = CalculateFocalPoint(renderer, origin);
brush.InterpolationColors = blend;
return brush;
}
......@@ -125,12 +147,38 @@ namespace Svg
}
}
private PointF CalculateCenterPoint(SvgRenderer renderer, PointF origin)
/// <summary>
/// Determine how much (approximately) the path must be scaled to contain the rectangle
/// </summary>
/// <param name="bounds">Bounds that the path must contain</param>
/// <param name="path">Path of the gradient</param>
/// <returns>Scale factor</returns>
/// <remarks>
/// This method continually transforms the rectangle (fewer points) until it is contained by the path
/// and returns the result of the search. The scale factor is set to a constant 95%
/// </remarks>
private float CalcScale(RectangleF bounds, GraphicsPath path)
{
var deviceCenterX = origin.X + CenterX.ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, this);
var deviceCenterY = origin.Y + CenterY.ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, this);
var transformedCenterPoint = TransformPoint(new PointF(deviceCenterX, deviceCenterY));
return transformedCenterPoint;
var points = new PointF[] {
new PointF(bounds.Left, bounds.Top),
new PointF(bounds.Right, bounds.Top),
new PointF(bounds.Right, bounds.Bottom),
new PointF(bounds.Left, bounds.Bottom)
};
var pathBounds = path.GetBounds();
var pathCenter = new PointF(pathBounds.X + pathBounds.Width / 2, pathBounds.Y + pathBounds.Height / 2);
var transform = new Matrix();
transform.Translate(-1 * pathCenter.X, -1 * pathCenter.Y, MatrixOrder.Append);
transform.Scale(.95f, .95f, MatrixOrder.Append);
transform.Translate(pathCenter.X, pathCenter.Y, MatrixOrder.Append);
var boundsTest = RectangleF.Inflate(bounds, 0, 0);
while (!(path.IsVisible(points[0]) && path.IsVisible(points[1]) &&
path.IsVisible(points[2]) && path.IsVisible(points[3])))
{
transform.TransformPoints(points);
}
return bounds.Height / (points[2].Y - points[1].Y);
}
private PointF CalculateFocalPoint(SvgRenderer renderer, PointF origin)
......@@ -141,44 +189,6 @@ namespace Svg
return transformedFocalPoint;
}
private float CalculateRadius(SvgRenderer renderer)
{
var radius = Radius.ToDeviceValue(renderer, UnitRenderingType.Other, this);
var transformRadiusVector = TransformVector(new PointF(radius, 0));
var transformedRadius = CalculateLength(transformRadiusVector);
return transformedRadius;
}
private float CalculateEffectiveRadius(ISvgBoundable boundable, PointF centerPoint, float specifiedRadius)
{
if (SpreadMethod != SvgGradientSpreadMethod.Pad)
{
return specifiedRadius;
}
var topLeft = new PointF(boundable.Bounds.Left, boundable.Bounds.Top);
var topRight = new PointF(boundable.Bounds.Right, boundable.Bounds.Top);
var bottomRight = new PointF(boundable.Bounds.Right, boundable.Bounds.Bottom);
var bottomLeft = new PointF(boundable.Bounds.Left, boundable.Bounds.Bottom);
var effectiveRadius = (float)Math.Ceiling(
Math.Max(
Math.Max(
CalculateDistance(centerPoint, topLeft),
CalculateDistance(centerPoint, topRight)
),
Math.Max(
CalculateDistance(centerPoint, bottomRight),
CalculateDistance(centerPoint, bottomLeft)
)
)
);
effectiveRadius = Math.Max(effectiveRadius, specifiedRadius);
return effectiveRadius;
}
private static GraphicsPath CreateGraphicsPath(PointF origin, PointF centerPoint, float effectiveRadius)
{
var path = new GraphicsPath();
......@@ -193,23 +203,66 @@ namespace Svg
return path;
}
private ColorBlend CalculateColorBlend(SvgRenderer renderer, float opacity, float specifiedRadius, float effectiveRadius)
private ColorBlend CalculateColorBlend(SvgRenderer renderer, float opacity, float scale, out float outScale)
{
var colorBlend = GetColorBlend(renderer, opacity, true);
float newScale;
List<float> pos;
List<Color> colors;
if (specifiedRadius >= effectiveRadius)
outScale = scale;
if (scale > 1)
{
return colorBlend;
}
for (var i = 0; i < colorBlend.Positions.Length - 1; i++)
{
colorBlend.Positions[i] = 1 - (specifiedRadius / effectiveRadius) * (1 - colorBlend.Positions[i]);
switch (this.SpreadMethod)
{
case SvgGradientSpreadMethod.Reflect:
newScale = (float)Math.Ceiling(scale);
pos = (from p in colorBlend.Positions select p / newScale).ToList();
colors = colorBlend.Colors.ToList();
for (var i = 1; i < newScale; i++)
{
if (i % 2 == 1)
{
pos.AddRange(from p in colorBlend.Positions.Reverse().Skip(1) select (1 - p + i) / newScale);
colors.AddRange(colorBlend.Colors.Reverse().Skip(1));
}
else
{
pos.AddRange(from p in colorBlend.Positions.Skip(1) select (p + i) / newScale);
colors.AddRange(colorBlend.Colors.Skip(1));
}
}
colorBlend.Positions = pos.ToArray();
colorBlend.Colors = colors.ToArray();
outScale = newScale;
break;
case SvgGradientSpreadMethod.Repeat:
newScale = (float)Math.Ceiling(scale);
pos = (from p in colorBlend.Positions select p / newScale).ToList();
colors = colorBlend.Colors.ToList();
for (var i = 1; i < newScale; i++)
{
pos.AddRange(from p in colorBlend.Positions select (p <= 0 ? 0.001f : p) / newScale);
colors.AddRange(colorBlend.Colors);
}
break;
default:
for (var i = 0; i < colorBlend.Positions.Length - 1; i++)
{
colorBlend.Positions[i] = 1 - (1 - colorBlend.Positions[i]) / scale;
}
colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray();
colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray();
break;
}
}
colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray();
colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray();
return colorBlend;
}
......
......@@ -101,6 +101,7 @@
<Compile Include="Clipping and Masking\SvgMask.cs" />
<Compile Include="DataTypes\ISvgSupportsCoordinateUnits.cs" />
<Compile Include="DataTypes\SvgTextDecoration.cs" />
<Compile Include="Filter Effects\ImageBuffer.cs" />
<Compile Include="Painting\GenericBoundable.cs" />
<Compile Include="SvgNodeReader.cs" />
<Compile Include="Css\CssQuery.cs" />
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment