SkiaAntialiasing.vb
''
'' This code is part of Document Solutions for Imaging demos.
'' Copyright (c) MESCIUS inc. All rights reserved.
''
Imports System.Drawing
Imports System.IO
Imports System.Linq
Imports System.Numerics
Imports GrapeCity.Documents.Drawing
Imports GrapeCity.Documents.Text
Imports GrapeCity.Documents.Imaging
Imports GrapeCity.Documents.Imaging.Skia
Imports GCTEXT = GrapeCity.Documents.Text
Imports GCDRAW = GrapeCity.Documents.Drawing

'' Text is always rendered with anti-aliasing in Skia.
'' Graphics rendering on the other hand can be controlled by
'' setting the GcSkiaGraphics.Aliased property.
'' This example shows the difference between shapes drawn
'' with that property set to false (which is the default)
'' and true. Note the more jagged appearance of some lines
'' in the lower part of the image (drawn with Aliased set
'' to false).
Public Class SkiaAntialiasing
    Public Shared ReadOnly Property IsSkiaOnly As Boolean = True

    Private _font As GCTEXT.Font = Nothing

    '' Helper method to draw a polygon and a caption beneath it.
    '' Can also be used to just calculate the points without actual drawing.
    '' startAngle is for the first point, clockwise from (1,0).
    Private Function DrawPolygon(g As GcGraphics, center As PointF, r As Single, n As Integer, startAngle As Single, pen As GCDRAW.Pen, Optional caption As String = Nothing) As PointF()
        Dim pts As PointF() = New PointF(n - 1) {}
        For i = 0 To n - 1
            pts(i) = New PointF(center.X + CSng(r * Math.Cos(startAngle + 2 * Math.PI * i / n)), center.Y + CSng(r * Math.Sin(startAngle + 2 * Math.PI * i / n)))
        Next
        If pen IsNot Nothing Then
            g.DrawPolygon(pts, pen)
        End If
        If Not String.IsNullOrEmpty(caption) Then
            DrawCaption(g, center, r, caption)
        End If
        Return pts
    End Function

    '' Helper method to draw a caption beneath a shape:
    Private Sub DrawCaption(g As GcGraphics, center As PointF, r As Single, caption As String)
        g.DrawString(caption,
            New TextFormat() With
                {
                    .Font = _font,
                    .FontSize = 12
                },
            New RectangleF(center.X - r, center.Y - r, r * 2, r * 2),
            TextAlignment.Center, ParagraphAlignment.Center, False)
    End Sub

    '' Main entry point.
    Public Function GenerateImageStream(targetMime As String, pixelSize As Size, dpi As Single, opaque As Boolean, Optional sampleParams As String() = Nothing) As Stream
        Select Case targetMime
            Case Util.MimeTypes.JPEG, Util.MimeTypes.PNG, Util.MimeTypes.WEBP
                '' OK
            Case Else
                Throw New Exception("This sample uses Skia to create the image, and only supports JPEG, PNG and WEBP output formats.")
        End Select

        _font = GCTEXT.Font.FromFile(Path.Combine("Resources", "Fonts", "FreeSerif.ttf"))
        Dim Inch = dpi
        Dim bmp = New GcSkiaBitmap(pixelSize.Width, pixelSize.Height, opaque)
        Using g = bmp.CreateGraphics(Color.White)
            DrawShapes(g, bmp.PixelWidth, bmp.PixelHeight, False)
            DrawShapes(g, bmp.PixelWidth, bmp.PixelHeight, True)
        End Using

        '' Done:
        Dim ms = New MemoryStream()
        Select Case targetMime
            Case Util.MimeTypes.JPEG
                bmp.SaveAsJpeg(ms)
            Case Util.MimeTypes.PNG
                bmp.SaveAsPng(ms)
            Case Util.MimeTypes.WEBP
                bmp.SaveAsWebp(ms)
            Case Else
                System.Diagnostics.Debug.Assert(False)
        End Select
        Return ms
    End Function

    Private Sub DrawShapes(g As GcGraphics, pixelWidth As Integer, pixelHeight As Integer, aliased As Boolean)
        Dim gskia = TryCast(g, GcSkiaGraphics)
        If gskia IsNot Nothing Then
            gskia.Aliased = aliased
        End If

        '' Set up the helper layout grid:
        Dim Inch = g.Resolution
        Dim grid = New With
            {
                .Cols = 4,
                .Rows = 3,
                .MarginX = Inch / 6,
                .MarginY = Inch / 4,
                .Radius = Inch * 0.6F,
                .StepX = (pixelWidth - Inch / 2) / 4,
                .StepY = (pixelHeight - Inch / 4) / 7F
            }

        '' Insertion point of the next figure's center:
        Dim startIp As PointF = New PointF(grid.MarginX + grid.StepX / 2, grid.MarginY + grid.StepY / 2 + 10)
        If aliased Then
            startIp.Y += pixelHeight \ 2
        End If

        Dim ip As PointF = startIp

        '' Header:
        Dim pad = 8
        Dim bord = New RectangleF(pad, pad + If(aliased, pixelHeight \ 2, 0), pixelWidth - pad * 2, pixelHeight \ 2 - pad * 2)
        g.DrawString($"Shapes (Skia) - Aliased is {aliased}",
            New TextFormat() With
                {
                    .Font = _font,
                    .FontSize = 14
                },
            bord,
            TextAlignment.Center, ParagraphAlignment.Near)
        g.DrawRoundRect(bord, pad, Color.MediumPurple, 2, DashStyle.DashDot)

        '' Pen used to draw shapes:
        Dim pen = New GCDRAW.Pen(Color.Orange, 1)
        pen.LineJoin = PenLineJoin.Round
        Dim fill = 100 '' Surfaces fill alpha

        '' Circle:
        g.DrawEllipse(New RectangleF(ip.X - grid.Radius, ip.Y - grid.Radius, grid.Radius * 2, grid.Radius * 2), pen)
        DrawCaption(g, ip, grid.Radius, "Circle")
        ip.X += grid.StepX

        '' Ellipse:
        g.DrawEllipse(New RectangleF(ip.X - grid.Radius * 1.4F, ip.Y - grid.Radius / 2, grid.Radius * 2 * 1.4F, grid.Radius), pen)
        DrawCaption(g, ip, grid.Radius, "Ellipse")
        ip.X += grid.StepX

        '' Cylinder:
        Dim radX As Single = grid.Radius / 1.4F
        Dim radY As Single = grid.Radius / 6
        Dim height As Single = grid.Radius * 1.8F
        g.DrawEllipse(New RectangleF(ip.X - radX, ip.Y - height / 2, radX * 2, radY * 2), pen)
        g.FillEllipse(New RectangleF(ip.X - radX, ip.Y + height / 2 - radY * 2, radX * 2, radY * 2), Color.FromArgb(fill, pen.Color))
        g.DrawEllipse(New RectangleF(ip.X - radX, ip.Y + height / 2 - radY * 2, radX * 2, radY * 2), pen)
        g.DrawLine(New PointF(ip.X - radX, ip.Y - height / 2 + radY), New PointF(ip.X - radX, ip.Y + height / 2 - radY), pen)
        g.DrawLine(New PointF(ip.X + radX, ip.Y - height / 2 + radY), New PointF(ip.X + radX, ip.Y + height / 2 - radY), pen)
        DrawCaption(g, ip, grid.Radius, "Cylinder")
        pen.Color = Color.Indigo
        ip.X += grid.StepX

        '' Cube:
        Dim cubex As Single = 6
        Dim cubePtsFar = DrawPolygon(g, New PointF(ip.X - cubex, ip.Y - cubex), grid.Radius, 4, CSng(-Math.PI) / 4, pen)
        Dim cubePtsNear = DrawPolygon(g, New PointF(ip.X + cubex, ip.Y + cubex), grid.Radius, 4, CSng(-Math.PI) / 4, pen)
        g.DrawLine(cubePtsFar(0), cubePtsNear(0), pen)
        g.DrawLine(cubePtsFar(1), cubePtsNear(1), pen)
        g.DrawLine(cubePtsFar(2), cubePtsNear(2), pen)
        g.DrawLine(cubePtsFar(3), cubePtsNear(3), pen)
        g.FillPolygon(New PointF() {cubePtsFar(1), cubePtsFar(2), cubePtsNear(2), cubePtsNear(1)}, Color.FromArgb(fill, pen.Color))
        DrawCaption(g, ip, grid.Radius, "Cube")
        pen.Color = Color.DarkGreen
        ip.X = startIp.X
        ip.Y += grid.StepY

        '' Pentagon:
        DrawPolygon(g, ip, grid.Radius, 5, CSng(-Math.PI) / 2, pen, "Pentagon")
        ip.X += grid.StepX

        '' Hexagon:
        '' For sample sake, we apply a transform to make the hexagon wider and shorter:
        g.Transform = Matrix3x2.CreateScale(1.4F, 0.8F) * Matrix3x2.CreateTranslation(ip.X, ip.Y)
        DrawPolygon(g, PointF.Empty, grid.Radius, 6, 0, pen, Nothing)
        g.Transform = Matrix3x2.Identity
        DrawCaption(g, ip, grid.Radius, "Hexagon")
        ip.X += grid.StepX

        '' Octagon:
        DrawPolygon(g, ip, grid.Radius, 8, CSng(-Math.PI) / 8, pen, "Octagon")
        pen.Color = Color.DarkRed
        ip.X += grid.StepX

        '' Triangle:
        DrawPolygon(g, ip, grid.Radius, 3, CSng(-Math.PI) / 2, pen, "Triangle")
        ip.X = startIp.X
        ip.Y += grid.StepY

        '' Filled pentagram:
        Dim pts = DrawPolygon(g, ip, grid.Radius, 5, CSng(-Math.PI) / 2, pen, "Pentagram")
        pts = New PointF() {pts(0), pts(2), pts(4), pts(1), pts(3)}
        g.FillPolygon(pts, Color.FromArgb(fill, pen.Color))
        g.DrawPolygon(pts, pen)
        ip.X += grid.StepX

        '' Set up a simple kind of oblique projection to draw a pyramid:
        Dim angle = Math.PI / 6
        Dim s As Single = CSng(Math.Sin(angle))
        Dim c As Single = CSng(Math.Cos(angle))
        Dim project As Func(Of Single, Single, Single, PointF) = Function(x_, y_, z_) New PointF(x_ - c * y_ * 0.5F, -(z_ - s * y_ * 0.5F))
        Dim p3d As Func(Of Vector3, PointF) = Function(v_) project(v_.X, v_.Y, v_.Z)
        Dim hedge As Single = grid.Radius '' 1/2 edge
        '' Debug - draw the 3 axis:
        '' g.DrawLine(project(0, 0, 0), project(100, 0, 0), Color.Red);
        '' g.DrawLine(project(0, 0, 0), project(0, 100, 0), Color.Green);
        '' g.DrawLine(project(0, 0, 0), project(0, 0, 100), Color.Blue);
        '' 3d points forming a square pyramid:
        Dim pts3d = New Vector3() {
            New Vector3(-hedge, -hedge, 0),
            New Vector3(hedge, -hedge, 0),
            New Vector3(hedge, hedge, 0),
            New Vector3(-hedge, hedge, 0),
            New Vector3(0, 0, hedge * 2)
        }
        '' project the points to draw the pyramid:
        pts = pts3d.Select(Function(v_) p3d(v_)).ToArray()
        g.Transform = Matrix3x2.CreateTranslation(ip.X, ip.Y + hedge * 0.7F)
        '' Visible edges:
        g.DrawPolygon(New PointF() {pts(4), pts(1), pts(2), pts(3), pts(4), pts(2)}, pen)
        '' Invisible edges:
        pen.Width /= 2
        pen.Color = Color.FromArgb(fill, pen.Color)
        g.DrawLine(pts(0), pts(4), pen)
        g.DrawLine(pts(0), pts(1), pen)
        g.DrawLine(pts(0), pts(3), pen)
        g.FillPolygon(pts.Take(4).ToArray(), pen.Color)
        ''
        g.Transform = Matrix3x2.Identity
        DrawCaption(g, ip, grid.Radius, "Pyramid")
        ip.X += grid.StepX
        pen.Width *= 2
        pen.Color = Color.Green

        '' Cone:
        Dim baseh As Single = grid.Radius * 0.3F
        pts = DrawPolygon(g, ip, grid.Radius, 3, CSng(-Math.PI) / 2, Nothing, "Cone")
        g.DrawLines(New PointF() {pts(2), pts(0), pts(1)}, pen)
        Dim rect = New RectangleF(pts(2).X, pts(2).Y - baseh / 2, pts(1).X - pts(2).X, baseh)
        g.FillEllipse(rect, Color.FromArgb(fill, pen.Color))
        g.DrawEllipse(rect, pen)
        ip.X += grid.StepX

        '' Trapezoid (use DrawPolygon to just get the points of the square):
        Dim dx As Single = 10
        pts = DrawPolygon(g, ip, grid.Radius, 4, CSng(-Math.PI) / 4, Nothing, "Trapezoid")
        pts(0).X -= dx
        pts(1).X += dx
        pts(2).X -= dx
        pts(3).X += dx
        g.DrawPolygon(pts, pen)
    End Sub
End Class