VectSharp  2.2.1
A light library for C# vector graphics
ImageSharpContext.cs
1 /*
2  VectSharp - A light library for C# vector graphics.
3  Copyright (C) 2020-2022 Giorgio Bianchini
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, version 3.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU Lesser General Public License for more details.
13 
14  You should have received a copy of the GNU Lesser General Public License
15  along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17 
18 using System;
19 using SixLabors.ImageSharp;
20 using SixLabors.ImageSharp.Drawing;
21 using SixLabors.ImageSharp.Processing;
22 using SixLabors.ImageSharp.Drawing.Processing;
23 using System.Collections.Generic;
24 using SixLabors.ImageSharp.Advanced;
25 using System.IO;
26 using VectSharp.Filters;
27 
29 {
30  internal static class MatrixUtils
31  {
32  public static System.Numerics.Matrix3x2 ToMatrix(this double[,] matrix)
33  {
34  return new System.Numerics.Matrix3x2((float)matrix[0, 0], (float)matrix[1, 0], (float)matrix[0, 1], (float)matrix[1, 1], (float)matrix[0, 2], (float)matrix[1, 2]);
35  }
36 
37  public static double[] Multiply(double[,] matrix, double[] vector)
38  {
39  double[] tbr = new double[2];
40 
41  tbr[0] = matrix[0, 0] * vector[0] + matrix[0, 1] * vector[1] + matrix[0, 2];
42  tbr[1] = matrix[1, 0] * vector[0] + matrix[1, 1] * vector[1] + matrix[1, 2];
43 
44  return tbr;
45  }
46 
47  public static Point Multiply(this double[,] matrix, Point vector)
48  {
49  return new Point(matrix[0, 0] * vector.X + matrix[0, 1] * vector.Y + matrix[0, 2], matrix[1, 0] * vector.X + matrix[1, 1] * vector.Y + matrix[1, 2]);
50  }
51 
52  public static double[,] Multiply(double[,] matrix1, double[,] matrix2)
53  {
54  double[,] tbr = new double[3, 3];
55 
56  for (int i = 0; i < 3; i++)
57  {
58  for (int j = 0; j < 3; j++)
59  {
60  for (int k = 0; k < 3; k++)
61  {
62  tbr[i, j] += matrix1[i, k] * matrix2[k, j];
63  }
64  }
65  }
66 
67  return tbr;
68  }
69 
70  public static double[,] Rotate(double[,] matrix, double angle)
71  {
72  double[,] rotationMatrix = new double[3, 3];
73  rotationMatrix[0, 0] = Math.Cos(angle);
74  rotationMatrix[0, 1] = -Math.Sin(angle);
75  rotationMatrix[1, 0] = Math.Sin(angle);
76  rotationMatrix[1, 1] = Math.Cos(angle);
77  rotationMatrix[2, 2] = 1;
78 
79  return Multiply(matrix, rotationMatrix);
80  }
81 
82  public static double[,] Translate(double[,] matrix, double x, double y)
83  {
84  double[,] translationMatrix = new double[3, 3];
85  translationMatrix[0, 0] = 1;
86  translationMatrix[0, 2] = x;
87  translationMatrix[1, 1] = 1;
88  translationMatrix[1, 2] = y;
89  translationMatrix[2, 2] = 1;
90 
91  return Multiply(matrix, translationMatrix);
92  }
93 
94  public static double[,] Scale(double[,] matrix, double scaleX, double scaleY)
95  {
96  double[,] scaleMatrix = new double[3, 3];
97  scaleMatrix[0, 0] = scaleX;
98  scaleMatrix[1, 1] = scaleY;
99  scaleMatrix[2, 2] = 1;
100 
101  return Multiply(matrix, scaleMatrix);
102  }
103 
104  public static double[,] Identity = new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
105  public static double[,] Invert(double[,] m)
106  {
107  double[,] tbr = new double[3, 3];
108 
109  tbr[0, 0] = (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
110  tbr[0, 1] = -(m[0, 1] * m[2, 2] - m[0, 2] * m[2, 1]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
111  tbr[0, 2] = (m[0, 1] * m[1, 2] - m[0, 2] * m[1, 1]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
112  tbr[1, 0] = -(m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
113  tbr[1, 1] = (m[0, 0] * m[2, 2] - m[0, 2] * m[2, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
114  tbr[1, 2] = -(m[0, 0] * m[1, 2] - m[0, 2] * m[1, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
115  tbr[2, 0] = (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
116  tbr[2, 1] = -(m[0, 0] * m[2, 1] - m[0, 1] * m[2, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
117  tbr[2, 2] = (m[0, 0] * m[1, 1] - m[0, 1] * m[1, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
118 
119  return tbr;
120  }
121 
122  public static double Determinant(double[,] matrix)
123  {
124  return (matrix[0, 0] * matrix[1, 1] - matrix[1, 0] * matrix[0, 1]) * matrix[2, 2] - (matrix[0, 0] * matrix[1, 2] - matrix[1, 0] * matrix[0, 2]) * matrix[2, 1] + (matrix[0, 1] * matrix[1, 2] - matrix[1, 1] * matrix[0, 2]) * matrix[2, 0];
125  }
126  }
127 
128  internal class ImageSharpContext : IGraphicsContext
129  {
130  public Image<SixLabors.ImageSharp.PixelFormats.Rgba32> Image { get; }
131  private Image Buffer { get; set; }
132  private Image ClipBuffer { get; }
133 
134  public double Width { get; private set; }
135 
136  public double Height { get; private set; }
137 
138  public int IntWidth { get; private set; }
139  public int IntHeight { get; private set; }
140 
141  public Font Font { get; set; }
142  public TextBaselines TextBaseline { get; set; }
143 
144  public Brush FillStyle { get; set; }
145 
146  public Brush StrokeStyle { get; set; }
147 
148  public double LineWidth { get; set; }
149  public LineCaps LineCap { get; set; }
150  public LineJoins LineJoin { get; set; }
151 
152  public LineDash Dash { get; set; }
153  public string Tag { get; set; }
154 
155  private double[,] _transform;
156  private readonly Stack<double[,]> states;
157 
158  private readonly Stack<Image> clips;
159  private Image CurrentClip;
160 
161  private List<IDisposable> disposables;
162 
163  PathBuilder currentPath;
164  bool figureInitialised;
165  Point currentPoint;
166  double scaleFactor;
167 
168  public ImageSharpContext(double width, double height, double scaleFactor, Colour backgroundColour)
169  {
170  currentPath = new PathBuilder();
171  figureInitialised = false;
172  currentPoint = new Point();
173 
174  IntWidth = (int)(width * scaleFactor);
175  IntHeight = (int)(height * scaleFactor);
176 
177  this.scaleFactor = Math.Sqrt(IntWidth / width * IntHeight / height);
178 
179  _transform = new double[3, 3];
180 
181  _transform[0, 0] = scaleFactor;
182  _transform[1, 1] = scaleFactor;
183  _transform[2, 2] = 1;
184 
185  states = new Stack<double[,]>();
186 
187  Width = width;
188  Height = height;
189 
190  this.Image = new Image<SixLabors.ImageSharp.PixelFormats.Rgba32>(IntWidth, IntHeight);
191  this.Image.Mutate(x => x.Fill(backgroundColour.ToImageSharpColor()));
192 
193  this.Buffer = new Image<SixLabors.ImageSharp.PixelFormats.Rgba32>(IntWidth, IntHeight);
194  this.Buffer.Mutate(x => x.Clear(Color.FromRgba(0, 0, 0, 0)));
195 
196  this.ClipBuffer = new Image<SixLabors.ImageSharp.PixelFormats.Rgba32>(IntWidth, IntHeight);
197  this.ClipBuffer.Mutate(x => x.Clear(Color.FromRgba(0, 0, 0, 0)));
198 
199  this.clips = new Stack<Image>();
200  this.CurrentClip = null;
201 
202  disposables = new List<IDisposable>();
203  }
204 
205  public void DisposeAllExceptImage()
206  {
207  for (int i = 0; i < disposables.Count; i++)
208  {
209  disposables[i].Dispose();
210  this.CurrentClip?.Dispose();
211  this.Buffer.Dispose();
212  this.ClipBuffer.Dispose();
213 
214  }
215  }
216 
217  public void Close()
218  {
219  this.currentPath.CloseFigure();
220  this.figureInitialised = false;
221  }
222 
223  public void CubicBezierTo(double p1X, double p1Y, double p2X, double p2Y, double p3X, double p3Y)
224  {
225  Utils.CoerceNaNAndInfinityToZero(ref p1X, ref p1Y, ref p2X, ref p2Y, ref p3X, ref p3Y);
226 
227  if (figureInitialised)
228  {
229  Point p1 = new Point(p1X, p1Y);
230  Point p2 = new Point(p2X, p2Y);
231  Point p3 = new Point(p3X, p3Y);
232  currentPath.AddBezier(currentPoint.ToPointF(1), p1.ToPointF(1), p2.ToPointF(1), p3.ToPointF(1));
233  currentPoint = p3;
234  }
235  else
236  {
237  currentPath.StartFigure();
238  currentPoint = new Point(p3X, p3Y);
239  figureInitialised = true;
240  }
241  }
242 
243  public void DrawRasterImage(int sourceX, int sourceY, int sourceWidth, int sourceHeight, double destinationX, double destinationY, double destinationWidth, double destinationHeight, RasterImage image)
244  {
245  Image sourceImage;
246 
247  unsafe
248  {
249  if (image.HasAlpha)
250  {
251  ReadOnlySpan<byte> data = new ReadOnlySpan<byte>((void*)image.ImageDataAddress, image.Width * image.Height * 4);
252  sourceImage = SixLabors.ImageSharp.Image.LoadPixelData<SixLabors.ImageSharp.PixelFormats.Rgba32>(data, image.Width, image.Height);
253  }
254  else
255  {
256  ReadOnlySpan<byte> data = new ReadOnlySpan<byte>((void*)image.ImageDataAddress, image.Width * image.Height * 3);
257  sourceImage = SixLabors.ImageSharp.Image.LoadPixelData<SixLabors.ImageSharp.PixelFormats.Rgb24>(data, image.Width, image.Height);
258  }
259  }
260 
261  DrawImage(sourceX, sourceY, sourceWidth, sourceHeight, destinationX, destinationY, destinationWidth, destinationHeight, image.Interpolate, sourceImage);
262  }
263 
264  internal void DrawImage(int sourceX, int sourceY, int sourceWidth, int sourceHeight, double destinationX, double destinationY, double destinationWidth, double destinationHeight, bool interpolate, Image sourceImage)
265  {
266  sourceImage.Mutate(x => x.Crop(new SixLabors.ImageSharp.Rectangle(sourceX, sourceY, sourceWidth, sourceHeight)));
267 
268  Point targetPoint = _transform.Multiply(new Point(destinationX, destinationY));
269  Point targetPointX = _transform.Multiply(new Point(destinationX + destinationWidth, destinationY));
270  Point targetPointY = _transform.Multiply(new Point(destinationX, destinationY + destinationHeight));
271  Point targetPointXY = _transform.Multiply(new Point(destinationX + destinationWidth, destinationY + destinationHeight));
272 
273  double minX = Math.Min(Math.Min(targetPoint.X, targetPointX.X), Math.Min(targetPointY.X, targetPointXY.X));
274  double minY = Math.Min(Math.Min(targetPoint.Y, targetPointX.Y), Math.Min(targetPointY.Y, targetPointXY.Y));
275 
276  double maxX = Math.Max(Math.Max(targetPoint.X, targetPointX.X), Math.Max(targetPointY.X, targetPointXY.X));
277  double maxY = Math.Max(Math.Max(targetPoint.Y, targetPointX.Y), Math.Max(targetPointY.Y, targetPointXY.Y));
278 
279  double[,] currTransform = MatrixUtils.Multiply(MatrixUtils.Translate(_transform, destinationX, destinationY), MatrixUtils.Scale(MatrixUtils.Identity, destinationWidth / sourceImage.Width, destinationHeight / sourceImage.Height));
280 
281  Point origin = currTransform.Multiply(new Point(0, 0));
282 
283  double[,] translation = MatrixUtils.Translate(MatrixUtils.Identity, -minX, -minY);
284 
285  double[,] centeredTransform = MatrixUtils.Multiply(translation, currTransform);
286 
287  SixLabors.ImageSharp.Processing.Processors.Transforms.IResampler resampler;
288 
289  if (interpolate)
290  {
291  resampler = KnownResamplers.Bicubic;
292  }
293  else
294  {
295  resampler = KnownResamplers.NearestNeighbor;
296  }
297 
298  sourceImage.Mutate(x => x.Transform(new SixLabors.ImageSharp.Rectangle(sourceX, sourceY, sourceWidth, sourceHeight), centeredTransform.ToMatrix(), new SixLabors.ImageSharp.Size((int)Math.Round(maxX - minX), (int)Math.Round(maxY - minY)), resampler));
299 
300  if (this.CurrentClip == null)
301  {
302  this.Image.Mutate(x => x.DrawImage(sourceImage, new SixLabors.ImageSharp.Point((int)Math.Round(minX), (int)Math.Round(minY)), 1));
303  }
304  else
305  {
306  this.ClipBuffer.Mutate(x => x.Clear(Color.FromRgba(0, 0, 0, 0)));
307  this.ClipBuffer.Mutate(x => x.DrawImage(sourceImage, new SixLabors.ImageSharp.Point((int)Math.Round(minX), (int)Math.Round(minY)), 1));
308 
309  GraphicsOptions opt = new GraphicsOptions() { AlphaCompositionMode = SixLabors.ImageSharp.PixelFormats.PixelAlphaCompositionMode.SrcIn };
310 
311  this.Buffer.Dispose();
312 
313  this.Buffer = this.CurrentClip.Clone(x => x.DrawImage(this.ClipBuffer, opt));
314 
315  this.Image.Mutate(x => x.DrawImage(this.Buffer, 1));
316  }
317  }
318 
319  public void Fill()
320  {
321  IPath path = this.currentPath.Build();
322 
323  if (this.CurrentClip == null)
324  {
325  this.Image.Mutate(x => x.Fill(new DrawingOptions() { Transform = _transform.ToMatrix() }, this.FillStyle.ToImageSharpBrush(_transform), path));
326  }
327  else
328  {
329  this.ClipBuffer.Mutate(x => x.Clear(Color.FromRgba(0, 0, 0, 0)));
330  this.ClipBuffer.Mutate(x => x.Fill(new DrawingOptions() { Transform = _transform.ToMatrix() }, this.FillStyle.ToImageSharpBrush(_transform), path));
331 
332  GraphicsOptions opt = new GraphicsOptions() { AlphaCompositionMode = SixLabors.ImageSharp.PixelFormats.PixelAlphaCompositionMode.SrcIn };
333 
334  this.Buffer.Dispose();
335 
336  this.Buffer = this.CurrentClip.Clone(x => x.DrawImage(this.ClipBuffer, opt));
337 
338  this.Image.Mutate(x => x.DrawImage(this.Buffer, 1));
339  }
340 
341  this.currentPath = new PathBuilder();
342  this.figureInitialised = false;
343  }
344 
345  public void FillText(string text, double x, double y)
346  {
347  PathText(text, x, y);
348  Fill();
349  }
350 
351  private void PathText(string text, double x, double y)
352  {
353  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
354 
355  GraphicsPath textPath = new GraphicsPath().AddText(x, y, text, Font, TextBaseline);
356 
357  for (int j = 0; j < textPath.Segments.Count; j++)
358  {
359  switch (textPath.Segments[j].Type)
360  {
361  case VectSharp.SegmentType.Move:
362  this.MoveTo(textPath.Segments[j].Point.X, textPath.Segments[j].Point.Y);
363  break;
364  case VectSharp.SegmentType.Line:
365  this.LineTo(textPath.Segments[j].Point.X, textPath.Segments[j].Point.Y);
366  break;
367  case VectSharp.SegmentType.CubicBezier:
368  this.CubicBezierTo(textPath.Segments[j].Points[0].X, textPath.Segments[j].Points[0].Y, textPath.Segments[j].Points[1].X, textPath.Segments[j].Points[1].Y, textPath.Segments[j].Points[2].X, textPath.Segments[j].Points[2].Y);
369  break;
370  case VectSharp.SegmentType.Close:
371  this.Close();
372  break;
373  }
374  }
375  }
376 
377  public void LineTo(double x, double y)
378  {
379  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
380 
381  if (figureInitialised)
382  {
383  Point newPoint = new Point(x, y);
384  currentPath.AddLine(currentPoint.ToPointF(1), newPoint.ToPointF(1));
385  currentPoint = newPoint;
386  }
387  else
388  {
389  currentPath.StartFigure();
390  currentPoint = new Point(x, y);
391  figureInitialised = true;
392  }
393  }
394 
395  public void MoveTo(double x, double y)
396  {
397  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
398 
399  currentPath.StartFigure();
400  currentPoint = new Point(x, y);
401  figureInitialised = true;
402  }
403 
404  public void Rectangle(double x0, double y0, double width, double height)
405  {
406  MoveTo(x0, y0);
407  LineTo(x0 + width, y0);
408  LineTo(x0 + width, y0 + height);
409  LineTo(x0, y0 + height);
410  Close();
411  }
412 
413  public void Restore()
414  {
415  _transform = states.Pop();
416 
417  Image newClip = clips.Pop();
418  if (CurrentClip != newClip)
419  {
420  CurrentClip.Dispose();
421  }
422 
423  CurrentClip = newClip;
424  }
425 
426  public void Rotate(double angle)
427  {
428  Utils.CoerceNaNAndInfinityToZero(ref angle);
429 
430  _transform = MatrixUtils.Rotate(_transform, angle);
431 
432  currentPath = new PathBuilder();
433  figureInitialised = false;
434  }
435 
436  public void Save()
437  {
438  states.Push((double[,])_transform.Clone());
439  clips.Push(CurrentClip);
440  }
441 
442  public void Scale(double x, double y)
443  {
444  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
445 
446  _transform = MatrixUtils.Scale(_transform, x, y);
447 
448  currentPath = new PathBuilder();
449  figureInitialised = false;
450  }
451 
452  public void SetClippingPath()
453  {
454  IPath path = this.currentPath.Build();
455 
456  Image newClip;
457 
458  DrawingOptions opt = new DrawingOptions() { Transform = _transform.ToMatrix() };
459 
460  if (this.CurrentClip == null)
461  {
462  newClip = new Image<SixLabors.ImageSharp.PixelFormats.Rgba32>(IntWidth, IntHeight);
463  newClip.Mutate(x => x.Clear(Color.FromRgba(0, 0, 0, 0)));
464  newClip.Mutate(x => x.Fill(opt, Color.FromRgb(0, 0, 0), path));
465  }
466  else
467  {
468  this.Buffer.Mutate(x => x.Clear(Color.FromRgba(0, 0, 0, 0)));
469  this.Buffer.Mutate(x => x.Fill(opt, Color.FromRgb(0, 0, 0), path));
470 
471  newClip = this.CurrentClip.Clone(x => x.DrawImage(this.Buffer, new GraphicsOptions() { AlphaCompositionMode = SixLabors.ImageSharp.PixelFormats.PixelAlphaCompositionMode.SrcIn }));
472 
473  disposables.Add(this.CurrentClip);
474  }
475 
476  this.CurrentClip = newClip;
477 
478  this.currentPath = new PathBuilder();
479  this.figureInitialised = false;
480  }
481 
482  public void SetFillStyle((int r, int g, int b, double a) style)
483  {
484  this.FillStyle = Colour.FromRgba(style);
485  }
486 
487  public void SetFillStyle(Brush style)
488  {
489  this.FillStyle = style;
490  }
491 
492  public void SetLineDash(LineDash dash)
493  {
494  this.Dash = dash;
495  }
496 
497  public void SetStrokeStyle((int r, int g, int b, double a) style)
498  {
499  this.StrokeStyle = Colour.FromRgba(style);
500  }
501 
502  public void SetStrokeStyle(Brush style)
503  {
504  this.StrokeStyle = style;
505  }
506 
507  public void Stroke()
508  {
509  if (this.CurrentClip == null)
510  {
511  this.Image.Mutate(x => x.Fill(new DrawingOptions() { Transform = _transform.ToMatrix() }, this.StrokeStyle.ToImageSharpBrush(_transform), this.currentPath.Build().GenerateOutline((float)(this.LineWidth), this.Dash.ToImageSharpDash(this.LineWidth), false, this.LineJoin.ToImageSharpJoint(), this.LineCap.ToImageSharpCap())));
512  }
513  else
514  {
515  this.ClipBuffer.Mutate(x => x.Clear(Color.FromRgba(0, 0, 0, 0)));
516  this.ClipBuffer.Mutate(x => x.Fill(new DrawingOptions() { Transform = _transform.ToMatrix() }, this.StrokeStyle.ToImageSharpBrush(_transform), this.currentPath.Build().GenerateOutline((float)(this.LineWidth), this.Dash.ToImageSharpDash(this.LineWidth), false, this.LineJoin.ToImageSharpJoint(), this.LineCap.ToImageSharpCap())));
517 
518  GraphicsOptions opt = new GraphicsOptions() { AlphaCompositionMode = SixLabors.ImageSharp.PixelFormats.PixelAlphaCompositionMode.SrcIn };
519 
520  this.Buffer.Dispose();
521 
522  this.Buffer = this.CurrentClip.Clone(x => x.DrawImage(this.ClipBuffer, opt));
523 
524  this.Image.Mutate(x => x.DrawImage(this.Buffer, 1));
525  }
526 
527 
528  this.currentPath = new PathBuilder();
529  this.figureInitialised = false;
530  }
531 
532  public void StrokeText(string text, double x, double y)
533  {
534  PathText(text, x, y);
535  Stroke();
536  }
537 
538  public void Transform(double a, double b, double c, double d, double e, double f)
539  {
540  Utils.CoerceNaNAndInfinityToZero(ref a, ref b, ref c, ref d, ref e, ref f);
541 
542  double[,] transfMatrix = new double[3, 3] { { a, c, e }, { b, d, f }, { 0, 0, 1 } };
543  _transform = MatrixUtils.Multiply(_transform, transfMatrix);
544 
545  currentPath = new PathBuilder();
546  figureInitialised = false;
547  }
548 
549  public void Translate(double x, double y)
550  {
551  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
552 
553  _transform = MatrixUtils.Translate(_transform, x, y);
554 
555  currentPath = new PathBuilder();
556  figureInitialised = false;
557  }
558 
559  public void DrawFilteredGraphics(Graphics graphics, IFilter filter)
560  {
561  double scale = this.scaleFactor * Math.Sqrt(MatrixUtils.Determinant(_transform));
562 
563  Rectangle bounds = graphics.GetBounds();
564 
565  bounds = new Rectangle(bounds.Location.X - filter.TopLeftMargin.X, bounds.Location.Y - filter.TopLeftMargin.Y, bounds.Size.Width + filter.TopLeftMargin.X + filter.BottomRightMargin.X, bounds.Size.Height + filter.TopLeftMargin.Y + filter.BottomRightMargin.Y);
566 
567  if (bounds.Size.Width > 0 && bounds.Size.Height > 0)
568  {
569  bool rasterisationNeeded = true;
570 
571  if (filter is GaussianBlurFilter gauss)
572  {
573  rasterisationNeeded = false;
574 
575  Page pag = new Page(1, 1);
576  pag.Graphics.DrawGraphics(0, 0, graphics);
577  pag.Crop(bounds.Location, bounds.Size);
578 
579  Image<SixLabors.ImageSharp.PixelFormats.Rgba32> img = ImageSharpContextInterpreter.SaveAsImage(pag, scale);
580  img.Mutate(x => x.GaussianBlur((float)(gauss.StandardDeviation * scale)));
581 
582  DrawImage(0, 0, img.Width, img.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, true, img);
583 
584  img.Dispose();
585  }
586  else if (filter is ColourMatrixFilter cmf)
587  {
588  rasterisationNeeded = false;
589 
590  Page pag = new Page(1, 1);
591  pag.Graphics.DrawGraphics(0, 0, graphics);
592  pag.Crop(bounds.Location, bounds.Size);
593 
594  Image<SixLabors.ImageSharp.PixelFormats.Rgba32> img = ImageSharpContextInterpreter.SaveAsImage(pag, scale);
595 
596  img.Mutate(x => x.Filter(new ColorMatrix((float)cmf.ColourMatrix.R1, (float)cmf.ColourMatrix.G1, (float)cmf.ColourMatrix.B1, (float)cmf.ColourMatrix.A1, (float)cmf.ColourMatrix.R2, (float)cmf.ColourMatrix.G2, (float)cmf.ColourMatrix.B2, (float)cmf.ColourMatrix.A2, (float)cmf.ColourMatrix.R3, (float)cmf.ColourMatrix.G3, (float)cmf.ColourMatrix.B3, (float)cmf.ColourMatrix.A3, (float)cmf.ColourMatrix.R4, (float)cmf.ColourMatrix.G4, (float)cmf.ColourMatrix.B4, (float)cmf.ColourMatrix.A4, (float)cmf.ColourMatrix.R5, (float)cmf.ColourMatrix.G5, (float)cmf.ColourMatrix.B5, (float)cmf.ColourMatrix.A5)));
597 
598  DrawImage(0, 0, img.Width, img.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, true, img);
599 
600  img.Dispose();
601  }
602  else if (filter is BoxBlurFilter bbf)
603  {
604  rasterisationNeeded = false;
605 
606  Page pag = new Page(1, 1);
607  pag.Graphics.DrawGraphics(0, 0, graphics);
608  pag.Crop(bounds.Location, bounds.Size);
609 
610  Image<SixLabors.ImageSharp.PixelFormats.Rgba32> img = ImageSharpContextInterpreter.SaveAsImage(pag, scale);
611 
612  img.Mutate(x => x.BoxBlur((int)Math.Round(bbf.BoxRadius * scale)));
613 
614  DrawImage(0, 0, img.Width, img.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, true, img);
615 
616  img.Dispose();
617  }
618  else if (filter is CompositeLocationInvariantFilter comp)
619  {
620  bool allSupported = true;
621 
622  foreach (IFilter filter2 in comp.Filters)
623  {
624  if (!(filter2 is GaussianBlurFilter) && !(filter2 is ColourMatrixFilter) && !(filter2 is BoxBlurFilter))
625  {
626  allSupported = false;
627  break;
628  }
629  }
630 
631  if (allSupported)
632  {
633  rasterisationNeeded = false;
634 
635  Page pag = new Page(1, 1);
636  pag.Graphics.DrawGraphics(0, 0, graphics);
637  pag.Crop(bounds.Location, bounds.Size);
638 
639  Image<SixLabors.ImageSharp.PixelFormats.Rgba32> img = ImageSharpContextInterpreter.SaveAsImage(pag, scale);
640 
641  foreach (IFilter filter2 in comp.Filters)
642  {
643  if (filter2 is GaussianBlurFilter gauss2)
644  {
645  img.Mutate(x => x.GaussianBlur((float)(gauss2.StandardDeviation * scale)));
646  }
647  else if (filter2 is ColourMatrixFilter cmf2)
648  {
649  img.Mutate(x => x.Filter(new ColorMatrix((float)cmf2.ColourMatrix.R1, (float)cmf2.ColourMatrix.G1, (float)cmf2.ColourMatrix.B1, (float)cmf2.ColourMatrix.A1, (float)cmf2.ColourMatrix.R2, (float)cmf2.ColourMatrix.G2, (float)cmf2.ColourMatrix.B2, (float)cmf2.ColourMatrix.A2, (float)cmf2.ColourMatrix.R3, (float)cmf2.ColourMatrix.G3, (float)cmf2.ColourMatrix.B3, (float)cmf2.ColourMatrix.A3, (float)cmf2.ColourMatrix.R4, (float)cmf2.ColourMatrix.G4, (float)cmf2.ColourMatrix.B4, (float)cmf2.ColourMatrix.A4, (float)cmf2.ColourMatrix.R5, (float)cmf2.ColourMatrix.G5, (float)cmf2.ColourMatrix.B5, (float)cmf2.ColourMatrix.A5)));
650  }
651  else if (filter2 is BoxBlurFilter bbf2)
652  {
653  img.Mutate(x => x.BoxBlur((int)Math.Round(bbf2.BoxRadius * scale)));
654  }
655  }
656 
657 
658  DrawImage(0, 0, img.Width, img.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, true, img);
659 
660  img.Dispose();
661  }
662  }
663 
664  if (rasterisationNeeded)
665  {
666  RasterImage rasterised = ImageSharpContextInterpreter.Rasterise(graphics, bounds, scale, true);
667  RasterImage filtered = null;
668 
669  if (filter is IFilterWithRasterisableParameter filterWithRastParam)
670  {
671  filterWithRastParam.RasteriseParameter(ImageSharpContextInterpreter.Rasterise, scale);
672  }
673 
674  if (filter is ILocationInvariantFilter locInvFilter)
675  {
676  filtered = locInvFilter.Filter(rasterised, scale);
677  }
678  else if (filter is IFilterWithLocation filterWithLoc)
679  {
680  filtered = filterWithLoc.Filter(rasterised, bounds, scale);
681  }
682 
683  if (filtered != null)
684  {
685  rasterised.Dispose();
686 
687  DrawRasterImage(0, 0, filtered.Width, filtered.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, filtered);
688  }
689  }
690  }
691  }
692  }
693 
694  internal static class Utils
695  {
696  public static PointF ToPointF(this Point pt, double scaleFactor)
697  {
698  return new PointF((float)(pt.X * scaleFactor), (float)(pt.Y * scaleFactor));
699  }
700 
701  public static void CoerceNaNAndInfinityToZero(ref double val)
702  {
703  if (double.IsNaN(val) || double.IsInfinity(val) || val == double.MinValue || val == double.MaxValue)
704  {
705  val = 0;
706  }
707  }
708 
709  public static void CoerceNaNAndInfinityToZero(ref double val1, ref double val2)
710  {
711  if (double.IsNaN(val1) || double.IsInfinity(val1) || val1 == double.MinValue || val1 == double.MaxValue)
712  {
713  val1 = 0;
714  }
715 
716  if (double.IsNaN(val2) || double.IsInfinity(val2) || val2 == double.MinValue || val2 == double.MaxValue)
717  {
718  val2 = 0;
719  }
720  }
721 
722  public static void CoerceNaNAndInfinityToZero(ref double val1, ref double val2, ref double val3, ref double val4)
723  {
724  if (double.IsNaN(val1) || double.IsInfinity(val1) || val1 == double.MinValue || val1 == double.MaxValue)
725  {
726  val1 = 0;
727  }
728 
729  if (double.IsNaN(val2) || double.IsInfinity(val2) || val2 == double.MinValue || val2 == double.MaxValue)
730  {
731  val2 = 0;
732  }
733 
734  if (double.IsNaN(val3) || double.IsInfinity(val3) || val3 == double.MinValue || val3 == double.MaxValue)
735  {
736  val3 = 0;
737  }
738 
739  if (double.IsNaN(val4) || double.IsInfinity(val4) || val4 == double.MinValue || val4 == double.MaxValue)
740  {
741  val4 = 0;
742  }
743  }
744 
745  public static void CoerceNaNAndInfinityToZero(ref double val1, ref double val2, ref double val3, ref double val4, ref double val5, ref double val6)
746  {
747  if (double.IsNaN(val1) || double.IsInfinity(val1) || val1 == double.MinValue || val1 == double.MaxValue)
748  {
749  val1 = 0;
750  }
751 
752  if (double.IsNaN(val2) || double.IsInfinity(val2) || val2 == double.MinValue || val2 == double.MaxValue)
753  {
754  val2 = 0;
755  }
756 
757  if (double.IsNaN(val3) || double.IsInfinity(val3) || val3 == double.MinValue || val3 == double.MaxValue)
758  {
759  val3 = 0;
760  }
761 
762  if (double.IsNaN(val4) || double.IsInfinity(val4) || val4 == double.MinValue || val4 == double.MaxValue)
763  {
764  val4 = 0;
765  }
766 
767  if (double.IsNaN(val5) || double.IsInfinity(val5) || val5 == double.MinValue || val5 == double.MaxValue)
768  {
769  val5 = 0;
770  }
771 
772  if (double.IsNaN(val6) || double.IsInfinity(val6) || val6 == double.MinValue || val6 == double.MaxValue)
773  {
774  val6 = 0;
775  }
776  }
777 
778  public static Color ToImageSharpColor(this Colour colour)
779  {
780  return Color.FromRgba((byte)(colour.R * 255), (byte)(colour.G * 255), (byte)(colour.B * 255), (byte)(colour.A * 255));
781  }
782 
783  public static IBrush ToImageSharpBrush(this Brush brush, double[,] transform)
784  {
785  if (brush is SolidColourBrush solid)
786  {
787  return new SixLabors.ImageSharp.Drawing.Processing.SolidBrush(solid.Colour.ToImageSharpColor());
788  }
789  else if (brush is LinearGradientBrush linear)
790  {
791  ColorStop[] colorStops = new ColorStop[linear.GradientStops.Count];
792 
793  for (int i = 0; i < linear.GradientStops.Count; i++)
794  {
795  colorStops[i] = new ColorStop((float)linear.GradientStops[i].Offset, linear.GradientStops[i].Colour.ToImageSharpColor());
796  }
797 
798  return new SixLabors.ImageSharp.Drawing.Processing.LinearGradientBrush(transform.Multiply(linear.StartPoint).ToPointF(1), transform.Multiply(linear.EndPoint).ToPointF(1), GradientRepetitionMode.None, colorStops);
799  }
800  else if (brush is RadialGradientBrush radial)
801  {
802  ColorStop[] colorStops = new ColorStop[radial.GradientStops.Count];
803 
804  for (int i = 0; i < radial.GradientStops.Count; i++)
805  {
806  colorStops[i] = new ColorStop((float)radial.GradientStops[i].Offset, radial.GradientStops[i].Colour.ToImageSharpColor());
807  }
808 
809  return new RadialGradientBrushSVGStyle(radial.Centre, radial.FocalPoint, radial.Radius, transform, GradientRepetitionMode.None, colorStops);
810  }
811  else
812  {
813  throw new NotImplementedException();
814  }
815  }
816 
817  public static float[] ToImageSharpDash(this LineDash dash, double lineThickness)
818  {
819  if (dash.UnitsOn > 0 || dash.UnitsOff > 0)
820  {
821  return new float[]
822  {
823  (float)(dash.UnitsOn / lineThickness),
824  (float)(dash.UnitsOff / lineThickness)
825  };
826  }
827  else
828  {
829  return new float[] { 1, 0 };
830  }
831  }
832 
833  public static JointStyle ToImageSharpJoint(this LineJoins join)
834  {
835  switch (join)
836  {
837  case LineJoins.Round:
838  return JointStyle.Round;
839  case LineJoins.Miter:
840  return JointStyle.Miter;
841  case LineJoins.Bevel:
842  return JointStyle.Square;
843  default:
844  return JointStyle.Miter;
845  }
846  }
847 
848  public static EndCapStyle ToImageSharpCap(this LineCaps cap)
849  {
850  switch (cap)
851  {
852  case LineCaps.Square:
853  return EndCapStyle.Square;
854  case LineCaps.Round:
855  return EndCapStyle.Round;
856  case LineCaps.Butt:
857  return EndCapStyle.Butt;
858  default:
859  throw new NotImplementedException();
860  }
861  }
862 
863  internal class RadialGradientBrushSVGStyle : SixLabors.ImageSharp.Drawing.Processing.GradientBrush
864  {
865  private readonly Point Centre;
866  private readonly Point FocalPoint;
867  private readonly double Radius;
868  private readonly double[,] InverseTransform;
869 
870  public RadialGradientBrushSVGStyle(Point centre, Point focalPoint, double radius, double[,] transform, GradientRepetitionMode repetitionMode, params ColorStop[] colorStops) : base(repetitionMode, colorStops)
871  {
872  Centre = centre;
873  FocalPoint = focalPoint;
874  Radius = radius;
875  InverseTransform = MatrixUtils.Invert(transform);
876  }
877 
878  public override BrushApplicator<TPixel> CreateApplicator<TPixel>(Configuration configuration, GraphicsOptions options, ImageFrame<TPixel> source, RectangleF region)
879  {
880  return new RadialGradientBrushPDFStyleApplicator<TPixel>(configuration, options, source, Centre, FocalPoint, Radius, InverseTransform, ColorStops, RepetitionMode);
881  }
882 
883  private class RadialGradientBrushPDFStyleApplicator<TPixel> : GradientBrushApplicator<TPixel> where TPixel : unmanaged, SixLabors.ImageSharp.PixelFormats.IPixel<TPixel>
884  {
885  private readonly Point Centre;
886  private readonly Point FocalPoint;
887  private readonly double Radius;
888  private readonly double[,] InverseTransform;
889 
890  private readonly double a;
891 
892  public RadialGradientBrushPDFStyleApplicator(
893  Configuration configuration,
894  GraphicsOptions options,
895  ImageFrame<TPixel> target,
896  Point centre, Point focalPoint, double radius, double[,] inverseTransform,
897  ColorStop[] colorStops,
898  GradientRepetitionMode repetitionMode)
899  : base(configuration, options, target, colorStops, repetitionMode)
900  {
901  this.Centre = centre;
902  this.FocalPoint = focalPoint;
903  this.Radius = radius;
904  this.InverseTransform = inverseTransform;
905 
906  a = (centre.X - focalPoint.X) * (centre.X - focalPoint.X) + (centre.Y - focalPoint.Y) * (centre.Y - focalPoint.Y) - radius * radius;
907  }
908 
909  Random rnd = new Random();
910 
911  protected override float PositionOnGradient(float x, float y)
912  {
913  Point realPoint = InverseTransform.Multiply(new Point(x, y));
914 
915  double c = (realPoint.X - FocalPoint.X) * (realPoint.X - FocalPoint.X) + (realPoint.Y - FocalPoint.Y) * (realPoint.Y - FocalPoint.Y);
916 
917  double halfB = -((realPoint.X - FocalPoint.X) * (Centre.X - FocalPoint.X) + (realPoint.Y - FocalPoint.Y) * (Centre.Y - FocalPoint.Y));
918 
919  double sqrt = Math.Sqrt(halfB * halfB - a * c);
920 
921  double tbr1 = (-halfB + sqrt) / a;
922  double tbr2 = (-halfB - sqrt) / a;
923 
924  if (tbr1 >= 0 && tbr2 < 0)
925  {
926  return (float)tbr1;
927  }
928  else if (tbr1 < 0 && tbr2 >= 0)
929  {
930  return (float)tbr2;
931  }
932  else if (tbr1 < 0 && tbr2 < 0)
933  {
934  return 0;
935  }
936  else
937  {
938  return (float)Math.Min(tbr1, tbr2);
939  }
940  }
941 
942  /// <inheritdoc/>
943  public override void Apply(Span<float> scanline, int x, int y)
944  {
945  base.Apply(scanline, x, y);
946  }
947  }
948  }
949  }
950 
951  /// <summary>
952  /// Enumeration containing the supported output formats.
953  /// </summary>
954  public enum OutputFormats
955  {
956  /// <summary>
957  /// Windows bitmap format
958  /// </summary>
959  BMP,
960 
961  /// <summary>
962  /// Graphics interchange format
963  /// </summary>
964  GIF,
965 
966  /// <summary>
967  /// Joint photographic experts group format
968  /// </summary>
969  JPEG,
970 
971 
972  /// <summary>
973  /// Portable bitmap format
974  /// </summary>
975  PBM,
976 
977  /// <summary>
978  /// Portable network graphics format
979  /// </summary>
980  PNG,
981 
982  /// <summary>
983  /// Truevision graphics adapter format
984  /// </summary>
985  TGA,
986 
987  /// <summary>
988  /// Tag image file format
989  /// </summary>
990  TIFF,
991 
992  /// <summary>
993  /// WebP format
994  /// </summary>
995  WebP
996  }
997 
998  /// <summary>
999  /// Contains methods to render a <see cref="Page"/> to an <see cref="Image"/>.
1000  /// </summary>
1001  public static class ImageSharpContextInterpreter
1002  {
1003  /// <summary>
1004  /// Render the page to an <see cref="Image"/> object.
1005  /// </summary>
1006  /// <param name="page">The <see cref="Page"/> to render.</param>
1007  /// <param name="scale">The scale to be used when rasterising the page. This will determine the width and height of the <see cref="Image"/>.</param>
1008  /// <returns>An <see cref="Image"/> containing the rasterised page.</returns>
1009  public static Image<SixLabors.ImageSharp.PixelFormats.Rgba32> SaveAsImage(this Page page, double scale = 1)
1010  {
1011  ImageSharpContext ctx = new ImageSharpContext(page.Width, page.Height, scale, page.Background);
1012  page.Graphics.CopyToIGraphicsContext(ctx);
1013  ctx.DisposeAllExceptImage();
1014  return ctx.Image;
1015  }
1016 
1017 
1018  /// <summary>
1019  /// Render the page to an image stream.
1020  /// </summary>
1021  /// <param name="page">The <see cref="Page"/> to render.</param>
1022  /// <param name="imageStream">The <see cref="Stream"/> on which the image data will be written.</param>
1023  /// <param name="outputFormat">The format of the image that will be created.</param>
1024  /// <param name="scale">The scale to be used when rasterising the page. This will determine the width and height of the image.</param>
1025  public static void SaveAsImage(this Page page, Stream imageStream, OutputFormats outputFormat, double scale = 1)
1026  {
1027  Image image = SaveAsImage(page, scale);
1028 
1029  switch (outputFormat)
1030  {
1031  case OutputFormats.BMP:
1032  image.SaveAsBmp(imageStream);
1033  break;
1034  case OutputFormats.GIF:
1035  image.SaveAsGif(imageStream);
1036  break;
1037  case OutputFormats.JPEG:
1038  image.SaveAsJpeg(imageStream);
1039  break;
1040  case OutputFormats.PBM:
1041  image.SaveAsPbm(imageStream);
1042  break;
1043  case OutputFormats.PNG:
1044  image.SaveAsPng(imageStream);
1045  break;
1046  case OutputFormats.TGA:
1047  image.SaveAsTga(imageStream);
1048  break;
1049  case OutputFormats.TIFF:
1050  image.SaveAsTiff(imageStream);
1051  break;
1052  case OutputFormats.WebP:
1053  image.SaveAsWebp(imageStream);
1054  break;
1055  }
1056 
1057  image.Dispose();
1058  }
1059 
1060  /// <summary>
1061  /// The exception that is raised when the output file format is not specified and the file name does not have an extension corresponding to a known file format.
1062  /// </summary>
1063  public class UnknownFormatException : Exception
1064  {
1065  /// <summary>
1066  /// The extension of the file that does not correspond to any known file format.
1067  /// </summary>
1068  public string Format { get; }
1069 
1070  internal UnknownFormatException(string format) : base("The extension " + format + " does not correspond to any known file format!")
1071  {
1072  this.Format = format;
1073  }
1074  }
1075 
1076  /// <summary>
1077  /// Render the page to an image file.
1078  /// </summary>
1079  /// <param name="page">The <see cref="Page"/> to render.</param>
1080  /// <param name="fileName">The path of the file where the image will be saved.</param>
1081  /// <param name="outputFormat">The format of the image that will be created. If this is <see langword="null" /> (the default), the format is desumed from the extension of the file.</param>
1082  /// <param name="scale">The scale to be used when rasterising the page. This will determine the width and height of the image.</param>
1083  public static void SaveAsImage(this Page page, string fileName, OutputFormats? outputFormat = null, double scale = 1)
1084  {
1085  OutputFormats actualOutputFormat;
1086 
1087  if (outputFormat.HasValue)
1088  {
1089  actualOutputFormat = outputFormat.Value;
1090  }
1091  else
1092  {
1093  string extension = System.IO.Path.GetExtension(fileName).ToLower();
1094 
1095  switch (extension)
1096  {
1097  case ".bmp":
1098  case ".dib":
1099  actualOutputFormat = OutputFormats.BMP;
1100  break;
1101 
1102  case ".gif":
1103  actualOutputFormat = OutputFormats.GIF;
1104  break;
1105 
1106  case ".jpeg":
1107  case ".jpg":
1108  actualOutputFormat = OutputFormats.JPEG;
1109  break;
1110 
1111  case ".pbm":
1112  actualOutputFormat = OutputFormats.PBM;
1113  break;
1114 
1115  case ".png":
1116  actualOutputFormat = OutputFormats.PNG;
1117  break;
1118 
1119  case ".tga":
1120  case ".targa":
1121  actualOutputFormat = OutputFormats.TGA;
1122  break;
1123 
1124  case ".tif":
1125  case ".tiff":
1126  actualOutputFormat = OutputFormats.TIFF;
1127  break;
1128 
1129  case ".webp":
1130  actualOutputFormat = OutputFormats.WebP;
1131  break;
1132 
1133  default:
1134  throw new UnknownFormatException(extension);
1135  }
1136  }
1137 
1138  using (FileStream imageStream = new FileStream(fileName, FileMode.Create))
1139  {
1140  SaveAsImage(page, imageStream, actualOutputFormat, scale);
1141  }
1142  }
1143 
1144  /// <summary>
1145  /// Return the page to raw pixel data, in 32bpp RGBA format.
1146  /// </summary>
1147  /// <param name="pag">The <see cref="Page"/> to render.</param>
1148  /// <param name="scale">The scale to be used when rasterising the page. This will determine the width and height of the image.</param>
1149  /// <param name="width">The width of the rendered image.</param>
1150  /// <param name="height">The height of the rendered image.</param>
1151  /// <param name="totalSize">The size in bytes of the raw pixel data.</param>
1152  /// <returns>A <see cref="DisposableIntPtr"/> containing a pointer to the raw pixel data, stored in unmanaged memory. Dispose this object to release the unmanaged memory.</returns>
1153  public static DisposableIntPtr SaveAsRawBytes(this Page pag, out int width, out int height, out int totalSize, double scale = 1)
1154  {
1155  Image<SixLabors.ImageSharp.PixelFormats.Rgba32> img = SaveAsImage(pag, scale);
1156 
1157  int stride = img.Width * 4;
1158  int size = stride * img.Height;
1159 
1160  IntPtr tbr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size);
1161  GC.AddMemoryPressure(size);
1162 
1163  IntPtr pointer = tbr;
1164 
1165  unsafe
1166  {
1167  for (int y = 0; y < img.Height; y++)
1168  {
1169  Memory<SixLabors.ImageSharp.PixelFormats.Rgba32> row = img.DangerousGetPixelRowMemory(y);
1170 
1171  Span<SixLabors.ImageSharp.PixelFormats.Rgba32> newRow = new Span<SixLabors.ImageSharp.PixelFormats.Rgba32>(pointer.ToPointer(), row.Length);
1172  row.Span.CopyTo(newRow);
1173 
1174  pointer = IntPtr.Add(pointer, stride);
1175  }
1176  }
1177 
1178  width = img.Width;
1179  height = img.Height;
1180  totalSize = size;
1181 
1182  img.Dispose();
1183 
1184  return new DisposableIntPtr(tbr);
1185  }
1186 
1187 
1188  /// <summary>
1189  /// Return the page to raw pixel data, in 32bpp RGBA format.
1190  /// </summary>
1191  /// <param name="pag">The <see cref="Page"/> to render.</param>
1192  /// <param name="scale">The scale to be used when rasterising the page. This will determine the width and height of the image.</param>
1193  /// <param name="width">The width of the rendered image.</param>
1194  /// <param name="height">The height of the rendered image.</param>
1195  /// <returns>A byte array containing the raw pixel data.</returns>
1196  public static byte[] SaveAsRawBytes(this Page pag, out int width, out int height, double scale = 1)
1197  {
1198  Image<SixLabors.ImageSharp.PixelFormats.Rgba32> img = SaveAsImage(pag, scale);
1199 
1200  int stride = img.Width * 4;
1201  int size = stride * img.Height;
1202 
1203  byte[] tbr = new byte[size];
1204 
1205  unsafe
1206  {
1207  for (int y = 0; y < img.Height; y++)
1208  {
1209  Memory<SixLabors.ImageSharp.PixelFormats.Rgba32> row = img.DangerousGetPixelRowMemory(y);
1210 
1211  Span<byte> bytes = System.Runtime.InteropServices.MemoryMarshal.Cast<SixLabors.ImageSharp.PixelFormats.Rgba32, byte>(row.Span);
1212  Span<byte> newRow = new Span<byte>(tbr, y * stride, stride);
1213  bytes.CopyTo(newRow);
1214  }
1215  }
1216 
1217  width = img.Width;
1218  height = img.Height;
1219 
1220  img.Dispose();
1221 
1222  return tbr;
1223  }
1224 
1225  /// <summary>
1226  /// Rasterise a region of a <see cref="Graphics"/> object.
1227  /// </summary>
1228  /// <param name="graphics">The <see cref="Graphics"/> object that will be rasterised.</param>
1229  /// <param name="region">The region of the <paramref name="graphics"/> that will be rasterised.</param>
1230  /// <param name="scale">The scale at which the image will be rendered.</param>
1231  /// <param name="interpolate">Whether the resulting image should be interpolated or not when it is drawn on another <see cref="Graphics"/> surface.</param>
1232  /// <returns>A <see cref="RasterImage"/> containing the rasterised graphics.</returns>
1233  public static RasterImage Rasterise(this Graphics graphics, Rectangle region, double scale, bool interpolate)
1234  {
1235  Page pag = new Page(1, 1);
1236  pag.Graphics.DrawGraphics(0, 0, graphics);
1237  pag.Crop(region.Location, region.Size);
1238 
1239  Image<SixLabors.ImageSharp.PixelFormats.Rgba32> img = SaveAsImage(pag, scale);
1240 
1241  int stride = img.Width * 4;
1242  int size = stride * img.Height;
1243 
1244  IntPtr tbr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size);
1245  GC.AddMemoryPressure(size);
1246 
1247  IntPtr pointer = tbr;
1248 
1249  unsafe
1250  {
1251  for (int y = 0; y < img.Height; y++)
1252  {
1253  Memory<SixLabors.ImageSharp.PixelFormats.Rgba32> row = img.DangerousGetPixelRowMemory(y);
1254 
1255  Span<SixLabors.ImageSharp.PixelFormats.Rgba32> newRow = new Span<SixLabors.ImageSharp.PixelFormats.Rgba32>(pointer.ToPointer(), row.Length);
1256  row.Span.CopyTo(newRow);
1257 
1258  pointer = IntPtr.Add(pointer, stride);
1259  }
1260  }
1261 
1262  int width = img.Width;
1263  int height = img.Height;
1264 
1265  img.Dispose();
1266 
1267  DisposableIntPtr disp = new DisposableIntPtr(tbr);
1268 
1269  return new RasterImage(ref disp, width, height, true, interpolate);
1270  }
1271  }
1272 }
VectSharp.Rectangle
Represents a rectangle.
Definition: Point.cs:173
VectSharp.Filters.IFilter.TopLeftMargin
Point TopLeftMargin
Determines how much the area of the filter's subject should be expanded on the top-left to accommodat...
Definition: Filters.cs:30
VectSharp.Graphics.DrawGraphics
void DrawGraphics(Point origin, Graphics graphics)
Draws a Graphics object on the current Graphics object.
Definition: Graphics.cs:802
VectSharp.Raster.ImageSharp.OutputFormats.PBM
@ PBM
Portable bitmap format
VectSharp.Page.Height
double Height
Height of the page.
Definition: Document.cs:57
VectSharp.RasterImage
Represents a raster image, created from raw pixel data. Consider using the derived classes included i...
Definition: RasterImage.cs:99
VectSharp.Raster.ImageSharp.OutputFormats.JPEG
@ JPEG
Joint photographic experts group format
VectSharp.DisposableIntPtr
An IDisposable wrapper around an IntPtr that frees the allocated memory when it is disposed.
Definition: RasterImage.cs:54
VectSharp.Filters.BoxBlurFilter
Represents a filter applying a box blur.
Definition: BoxBlurFilter.cs:27
VectSharp.Page.Background
Colour Background
Background colour of the page.
Definition: Document.cs:67
VectSharp
Definition: Brush.cs:26
VectSharp.Filters.IFilter
Represents a filter. Do not implement this interface directly; instead, implement ILocationInvariantF...
Definition: Filters.cs:26
VectSharp.Graphics.CopyToIGraphicsContext
void CopyToIGraphicsContext(IGraphicsContext destinationContext)
Copy the current graphics to an instance of a class implementing IGraphicsContext.
Definition: Graphics.cs:599
VectSharp.Filters.ColourMatrixFilter
Represents a filter that applies a Filters.ColourMatrix to the colours of the image.
Definition: ColourMatrixFilter.cs:568
VectSharp.Page.Width
double Width
Width of the page.
Definition: Document.cs:52
VectSharp.Page
Represents a Graphics object with a width and height.
Definition: Document.cs:48
VectSharp.SegmentType.Close
@ Close
The segment represents the closing segment of a figure.
VectSharp.Raster.ImageSharp.ImageSharpContextInterpreter.SaveAsRawBytes
static byte[] SaveAsRawBytes(this Page pag, out int width, out int height, double scale=1)
Return the page to raw pixel data, in 32bpp RGBA format.
Definition: ImageSharpContext.cs:1196
VectSharp.LineCaps
LineCaps
Represents line caps.
Definition: Enums.cs:71
VectSharp.Raster.ImageSharp.OutputFormats.TGA
@ TGA
Truevision graphics adapter format
VectSharp.TextBaselines
TextBaselines
Represent text baselines.
Definition: Enums.cs:24
VectSharp.Graphics
Represents an abstract drawing surface.
Definition: Graphics.cs:262
VectSharp.Rectangle.Size
Size Size
The size of the rectangle.
Definition: Point.cs:187
VectSharp.Raster.ImageSharp.ImageSharpContextInterpreter.SaveAsImage
static void SaveAsImage(this Page page, string fileName, OutputFormats? outputFormat=null, double scale=1)
Render the page to an image file.
Definition: ImageSharpContext.cs:1083
VectSharp.LineJoins
LineJoins
Represents line joining options.
Definition: Enums.cs:92
VectSharp.Raster.ImageSharp.OutputFormats.TIFF
@ TIFF
Tag image file format
VectSharp.Raster.ImageSharp.OutputFormats.WebP
@ WebP
WebP format
VectSharp.Page.Graphics
Graphics Graphics
Graphics surface of the page.
Definition: Document.cs:62
VectSharp.Raster.ImageSharp.OutputFormats
OutputFormats
Enumeration containing the supported output formats.
Definition: ImageSharpContext.cs:955
VectSharp.Raster.ImageSharp.ImageSharpContextInterpreter.SaveAsRawBytes
static DisposableIntPtr SaveAsRawBytes(this Page pag, out int width, out int height, out int totalSize, double scale=1)
Return the page to raw pixel data, in 32bpp RGBA format.
Definition: ImageSharpContext.cs:1153
VectSharp.Filters.IFilterWithRasterisableParameter
Represents a filter with a parameter that needs to be rasterised at the same resolution as the subjec...
Definition: Filters.cs:72
VectSharp.Point.X
double X
Horizontal (x) coordinate, measured to the right of the origin.
Definition: Point.cs:32
VectSharp.Raster.ImageSharp.ImageSharpContextInterpreter.UnknownFormatException.Format
string Format
The extension of the file that does not correspond to any known file format.
Definition: ImageSharpContext.cs:1068
VectSharp.Raster.ImageSharp.ImageSharpContextInterpreter.SaveAsImage
static Image< SixLabors.ImageSharp.PixelFormats.Rgba32 > SaveAsImage(this Page page, double scale=1)
Render the page to an Image object.
Definition: ImageSharpContext.cs:1009
VectSharp.PixelFormats
PixelFormats
Represents the pixel format of a raster image.
Definition: RasterImage.cs:28
VectSharp.Filters
Definition: BoxBlurFilter.cs:22
VectSharp.Rectangle.Location
Point Location
The top-left corner of the rectangle.
Definition: Point.cs:182
VectSharp.Raster.ImageSharp.ImageSharpContextInterpreter.Rasterise
static RasterImage Rasterise(this Graphics graphics, Rectangle region, double scale, bool interpolate)
Rasterise a region of a Graphics object.
Definition: ImageSharpContext.cs:1233
VectSharp.Filters.IFilter.BottomRightMargin
Point BottomRightMargin
Determines how much the area of the filter's subject should be expanded on the bottom-right to accomm...
Definition: Filters.cs:35
VectSharp.Filters.IFilterWithLocation
Represents a filter whose results depend on the position of the subject image on the graphics surface...
Definition: Filters.cs:56
VectSharp.Filters.ILocationInvariantFilter
Represents a filter that can be applied to an image regardless of its location on the graphics surfac...
Definition: Filters.cs:42
VectSharp.Raster.ImageSharp.OutputFormats.BMP
@ BMP
Windows bitmap format
VectSharp.Filters.CompositeLocationInvariantFilter
Represents a filter that corresponds to applying multiple ILocationInvariantFilters one after the oth...
Definition: CompositeFilter.cs:27
VectSharp.Raster.ImageSharp.ImageSharpContextInterpreter.UnknownFormatException
The exception that is raised when the output file format is not specified and the file name does not ...
Definition: ImageSharpContext.cs:1064
VectSharp.Page.Crop
void Crop(Point topLeft, Size size)
Translate and resize the Page so that it displays the rectangle defined by topLeft and size .
Definition: Document.cs:88
VectSharp.Raster.ImageSharp.ImageSharpContextInterpreter.SaveAsImage
static void SaveAsImage(this Page page, Stream imageStream, OutputFormats outputFormat, double scale=1)
Render the page to an image stream.
Definition: ImageSharpContext.cs:1025
VectSharp.Raster.ImageSharp.OutputFormats.PNG
@ PNG
Portable network graphics format
VectSharp.Filters.GaussianBlurFilter
Represents a filter that applies a Gaussian blur effect.
Definition: GaussianBlurFilter.cs:27
VectSharp.Raster.ImageSharp.OutputFormats.GIF
@ GIF
Graphics interchange format
VectSharp.Raster.ImageSharp
Definition: GradientBrushApplicator.cs:28
VectSharp.Point.Y
double Y
Vertical (y) coordinate, measured to the bottom of the origin.
Definition: Point.cs:37
VectSharp.Raster.ImageSharp.ImageSharpContextInterpreter
Contains methods to render a Page to an Image.
Definition: ImageSharpContext.cs:1002