Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Source/ExtensionMethods/PointFExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;

namespace Svg.ExtensionMethods
{
internal static class PointFExtensions
{
internal static RectangleF GetBounds(this IEnumerable<PointF> points)
{
var minX = points.Min(point => point.X);
var maxX = points.Max(point => point.X);
var minY = points.Min(point => point.Y);
var maxY = points.Max(point => point.Y);

return new RectangleF(minX, minY, maxX - minX, maxY - minY);
}
}
}
61 changes: 61 additions & 0 deletions Source/ExtensionMethods/RectangleFExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;

namespace Svg.ExtensionMethods
{
internal static class RectangleFExtensions
{
internal static Rectangle Round(this RectangleF rectangle)
{
return new Rectangle((int)Math.Round(rectangle.X), (int)Math.Round(rectangle.Y), (int)Math.Round(rectangle.Width), (int)Math.Round(rectangle.Height));
}

internal static RectangleF GetIntersection(this RectangleF rectangle, RectangleF anotherRectangle)
{
var x1 = Math.Max(rectangle.X, anotherRectangle.X);
var x2 = Math.Min(rectangle.X + rectangle.Width, anotherRectangle.X + anotherRectangle.Width);

var y1 = Math.Max(rectangle.Y, anotherRectangle.Y);
var y2 = Math.Min(rectangle.Y + rectangle.Height, anotherRectangle.Y + anotherRectangle.Height);

var width = x2 - x1;
var height = y2 - y1;

if (width < 0)
{
width = 0;
}

if (height < 0)
{
height = 0;
}

return new RectangleF(x1, y1, width, height);
}

internal static RectangleF GetIntersection(this RectangleF rectangle, SizeF size)
{
return GetIntersection(rectangle, new RectangleF(new PointF(0, 0), size));
}

internal static PointF[] GetPoints(this RectangleF rectangle)
{
return new[]
{
rectangle.Location,
rectangle.Location + rectangle.Size
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here should consider five points like this, If there are only two points, there will have issue with the bounds when the transform contains rotation

 return new[]
            {
                rectangle.Location,
                rectangle.Location + rectangle.Size,
                new PointF(rectangle.X,rectangle.Y+rectangle.Height),
                new PointF(rectangle.X+rectangle.Width,rectangle.Y)
            };

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can verify this by this transform : "matrix(0.51 0.51 -0.51 0.51 776.90 96.03)"

};
}

internal static RectangleF Transform(this RectangleF rectangle, Matrix matrix)
{
var points = GetPoints(rectangle);
matrix.TransformPoints(points);
return points.GetBounds();
}
}
}
71 changes: 40 additions & 31 deletions Source/Rendering/SvgRenderer.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System;
using Svg.ExtensionMethods;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Runtime.InteropServices;

namespace Svg
{
Expand Down Expand Up @@ -61,7 +61,8 @@ private SvgRenderer(Graphics graphics, Image image)

public void DrawImage(Image image, RectangleF destRect, RectangleF srcRect, GraphicsUnit graphicsUnit)
{
DrawMasked(graphics => graphics.DrawImage(image, destRect, srcRect, graphicsUnit));
var bounds = destRect.Transform(this.Transform);
DrawMasked(graphics => graphics.DrawImage(image, destRect, srcRect, graphicsUnit), bounds);
}
public void DrawImage(Image image, RectangleF destRect, RectangleF srcRect, GraphicsUnit graphicsUnit, float opacity)
{
Expand All @@ -86,12 +87,14 @@ public void DrawImageUnscaled(Image image, Point location)

public void DrawPath(Pen pen, GraphicsPath path)
{
DrawMasked(graphics => graphics.DrawPath(pen, path));
var bounds = path.GetBounds(this.Transform, pen);
DrawMasked(graphics => graphics.DrawPath(pen, path), bounds);
}

public void FillPath(Brush brush, GraphicsPath path)
{
DrawMasked(graphics => graphics.FillPath(brush, path));
var bounds = path.GetBounds(this.Transform);
DrawMasked(graphics => graphics.FillPath(brush, path), bounds);
}
public Region GetClip()
{
Expand Down Expand Up @@ -186,55 +189,61 @@ public Bitmap GetMask()
return _mask;
}

private void DrawMasked(Action<Graphics> drawAction)
private void DrawMasked(Action<Graphics> drawAction, RectangleF bounds)
{
if (_mask == null)
{
drawAction(_innerGraphics);
return;
}

var buffer = new Bitmap((int)Math.Round(this.RenderSize.Width), (int)Math.Round(this.RenderSize.Height), PixelFormat.Format32bppArgb);
var renderedBounds = bounds.GetIntersection(this.RenderSize).Round();

if (renderedBounds.Width == 0 || renderedBounds.Height == 0)
{
return;
}

var buffer = new Bitmap(renderedBounds.Width, renderedBounds.Height, PixelFormat.Format32bppArgb);

var localTransform = new Matrix();
localTransform.Translate(-renderedBounds.X, -renderedBounds.Y);
localTransform.Multiply(this.Transform);

var bufferGraphics = Graphics.FromImage(buffer);

bufferGraphics.Transform = this.Transform;
bufferGraphics.Transform = localTransform;
drawAction(bufferGraphics);

ApplyAlphaMask(buffer, _mask);
ApplyAlphaMask(buffer, _mask, renderedBounds);

var previousTransform = _innerGraphics.Transform;
_innerGraphics.Transform = new Matrix();
_innerGraphics.DrawImageUnscaled(buffer, new Point(0, 0));
_innerGraphics.DrawImageUnscaled(buffer, new Point(renderedBounds.X, renderedBounds.Y));
_innerGraphics.Transform = previousTransform;
}

private void ApplyAlphaMask(Bitmap buffer, Bitmap mask)
private void ApplyAlphaMask(Bitmap buffer, Bitmap mask, Rectangle renderedBounds)
{
var bufferData = buffer.LockBits(new Rectangle(0, 0, buffer.Width, buffer.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
var maskData = mask.LockBits(new Rectangle(0, 0, mask.Width, mask.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

var bufferStride = Math.Abs(bufferData.Stride);
var maskStride = Math.Abs(maskData.Stride);
var bufferData = buffer.LockBits(new Rectangle(0, 0, renderedBounds.Width, renderedBounds.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
var maskData = mask.LockBits(renderedBounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

var bufferBytes = new byte[bufferStride * buffer.Height];
var maskBytes = new byte[maskStride * mask.Height];

var bufferScan0 = bufferData.Scan0;
var maskScan0 = maskData.Scan0;

Marshal.Copy(bufferScan0, bufferBytes, 0, bufferBytes.Length);
Marshal.Copy(maskScan0, maskBytes, 0, maskBytes.Length);

for (var byteIndex = 0; byteIndex < bufferBytes.Length && byteIndex < maskBytes.Length; byteIndex += 4)
unsafe
{
var alpha = (maskBytes[byteIndex] + maskBytes[byteIndex + 1] + maskBytes[byteIndex + 2]) / 3;
var newAlpha = (byte)(bufferBytes[byteIndex + 3] * alpha / 255);
for (int y = 0; y < renderedBounds.Height; y++)
{
byte* bufferRow = (byte*)bufferData.Scan0 + (y * bufferData.Stride);
byte* maskRow = (byte*)maskData.Scan0 + (y * maskData.Stride);

bufferBytes[byteIndex + 3] = newAlpha;
}
for (int x = 0; x < renderedBounds.Width; x++)
{
var alpha = (maskRow[x * 4] + maskRow[x * 4 + 1] + maskRow[x * 4 + 2]) / 3;
var newAlpha = (byte)(bufferRow[x * 4 + 3] * alpha / 255);

Marshal.Copy(bufferBytes, 0, bufferScan0, bufferBytes.Length);
bufferRow[x * 4 + 3] = newAlpha;
}
}
}

buffer.UnlockBits(bufferData);
mask.UnlockBits(maskData);
Expand Down
56 changes: 56 additions & 0 deletions Source/Svg.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,62 @@ Many performance improvements by using SourceGenerators instad of reflection.
<PropertyGroup Condition="'$(TargetFramework)' != 'net452'">
<DefineConstants>$(DefineConstants);USE_SOURCE_GENERATORS</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this property required for each ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose so? That was auto-generated so I'm not 100% sure but please let me know what you think the better approach would be.

</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net5.0|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netcoreapp2.1|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netcoreapp2.1|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net452|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.1|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.1|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net461|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netcoreapp3.1|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netcoreapp3.1|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net5.0|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net461|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net452|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'net452'">
<ProjectReference Include="..\Generators\Svg.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
Expand Down
27 changes: 22 additions & 5 deletions Tests/Svg.UnitTests/MaskingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void RenderTestFileFromIssue482()

ImagesAreEqual(renderedDocument, expectedImage, 1, out equalPercentage, out difference);

Assert.Greater(equalPercentage, 98);
Assert.Greater(equalPercentage, 99);
}

[Test]
Expand All @@ -40,7 +40,7 @@ public void RenderVariousElementsDefaultSize()

ImagesAreEqual(renderedDocument, expectedImage, 1, out equalPercentage, out difference);

Assert.Greater(equalPercentage, 98);
Assert.Greater(equalPercentage, 99);
}

[Test]
Expand All @@ -54,9 +54,26 @@ public void RenderVariousElementsUpscaled()
float equalPercentage;
Bitmap difference;

ImagesAreEqual(renderedDocument, expectedImage, 1, out equalPercentage, out difference);
var areImagesEqual = ImagesAreEqual(renderedDocument, expectedImage, 1, out equalPercentage, out difference);

Assert.Greater(equalPercentage, 99);
}

[Test]
public void RenderPcb()
{
var document = OpenSvg(GetXMLDocFromResource(GetFullResourceString("Issue482_MasksNotRendered.PCB.svg")));

var renderedDocument = document.Draw(1440, 2560);

var expectedImage = GetBitmapFromResource("Issue482_MasksNotRendered.PCB.png");

float equalPercentage;
Bitmap difference;

var areImagesEqual = ImagesAreEqual(renderedDocument, expectedImage, 1, out equalPercentage, out difference);

Assert.Greater(equalPercentage, 98);
Assert.Greater(equalPercentage, 99);
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add CRLF.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading