【问题标题】:Calculate coordinates for rotated text plus bounding border计算旋转文本和边界的坐标
【发布时间】:2014-05-25 23:10:16
【问题描述】:

我有一个表单,允许用户创建自定义“图章”以放置在 PDF 上。表单显示 pdf 第一页的图像,我希望用户基本上点击他们想要印章的屏幕,并能够预览它的外观。不用担心任何 PDF 的东西,我已经处理好了。

为了让事情变得时髦,我有两个图像副本,一个是正常的,一个是亮度降低的。我显示低亮度图像,当用户将鼠标移到上方时,会显示或突出显示原始图像的一大块。然后我在该区域显示用户将要放在 PDF 上的文本。

我允许用户使用鼠标滚轮滚动和更改他们放置的文本的角度(从 -45 度到 +45 度)。

这是我的问题:我无法计算正确的矩形/坐标。有时一切看起来都很棒,有时(随着字体大小的变化)它们不太合适。

如何计算 x 和 y 坐标: 旋转文本的位置 和一个边界矩形,以 10px 的宽度和高度填充文本

下面的代码有效,直到我开始增加字体大小,然后一切都变得不倾斜了。

前两张图片以较小的字体显示文本 + 边框。看起来不错:

下一张图片显示,随着文本大小变大,我的像素四处移动并被切掉。在更大的文本中,宽度/高度也会偏离。

抱歉,示例图片没有显示太多细节。我有无法分享的实际数据。

        Private Sub PanelMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) '// handles the mouse move (handler added elsehwere)

        With CType(sender, PictureBox)

            .Image.Dispose() '// get rid of old image

            Dim b As Bitmap = _curGray.Clone '// the low brightness image as the base image

            '// stamp font and text values are initiated from another form
            Using g As Graphics = Graphics.FromImage(b),
                f As New Font(DefaultFont.FontFamily, CSng(_stmpTools.StampTextSize), If(_stmpTools.StampBold, FontStyle.Bold, FontStyle.Regular))

                Const borderWidth As Integer = 10
                Const borderPadding As Integer = 5

                '// measure the string
                Dim szx As SizeF = g.MeasureString(_stmpTools.StampText, f, Integer.MaxValue, StringFormat.GenericDefault)
                Dim strLength As Single = szx.Width
                Dim strHeight As Single = szx.Height

                Dim x As Single = e.X - borderWidth - borderPadding,
                    y As Single = e.Y

                Dim w As Double, h As Double

                If Math.Abs(_angle) > Double.Epsilon Then
                    h = CDbl(strLength) * Math.Sin(CDbl(Math.Abs(_angle)) * Math.PI / 180.0F)
                    w = Math.Sqrt(CDbl(strLength) * CDbl(strLength) - h * h)
                Else
                    '// its zero. so use calculated values
                    h = strHeight
                    w = strLength
                End If

                '// add space for the 10px border plus 5px of padding
                Dim r As New Rectangle(0, 0, w, h)
                r.Inflate(borderWidth + borderPadding, borderWidth + borderPadding)

                h = r.Height
                w = r.Width

                '// keep box from moving off the left
                If x < .Location.X Then
                    x = .Location.X
                End If

                '// keep box from moving off the right
                If x > .Location.X + .Width - w Then
                    x = .Location.X + .Width - w
                End If

                '// I don't know, but these values work for most smaller fonts, but
                '// it has got to be a fluke
                If _angle > 0 Then
                    y = y - h + borderWidth + borderWidth
                Else
                    y = y - borderWidth
                End If

                '// can't go off the top
                If y < .Location.Y Then
                    y = .Location.Y
                End If

                '// can't go off the bottom
                If y > .Location.Y + .Height - h Then
                    y = .Location.Y + .Height - h
                End If

                Dim rect As New Rectangle(x, y, w, h)
                g.DrawImage(_curImg, rect, rect, GraphicsUnit.Pixel)

                Using br As New SolidBrush(_stmpTools.StampTextColor)
                    RotateString(_stmpTools.StampText, _angle, e.X, e.Y, f, g, br)
                End Using

                '//draw bounding rectangle
                Using p As New Pen(Color.Black, borderWidth)
                    g.DrawRectangle(p, rect)
                End Using

            End Using
            '// set the picture box to show the new image
            .Image = b
        End With

    End Sub

    Private Sub RotateString(ByVal Text As String, ByVal angle As Integer, _
                      ByVal x As Integer, ByVal y As Integer, myfont As Font, mydrawing As Graphics, myColor As Brush)
        Dim myMatrix As New Matrix
        myMatrix.RotateAt(angle * -1, New Point(x, y)) 'Rotate drawing 
        mydrawing.Transform = myMatrix
        mydrawing.DrawString(Text, myFont, myColor, x, y) 'Draw the text string 
        myMatrix.RotateAt(angle, New Point(x, y)) 'Rotate back 
        mydrawing.Transform = myMatrix
    End Sub

在绘画方面我不是最棒的。所以任何帮助都会很棒

使用@LarsTech 提供的以下解决方案。我将 g.FillRectangle 替换为:

g.DrawImage(_curImg, r, r, GraphicsUnit.Pixel)

_curImg 是调整了亮度的原始图像的副本。当我从下面更改代码时,我最终得到:

注意双线。它们与图章一起旋转,即使它们充当背景图像并且应该不旋转

根据建议,我将 DrawStamp 从 @LarsTech 更改为以下内容:

        Private Sub DrawStamp(g As Graphics, text As String,
                  f As Font, center As Point, angle As Integer, backImg As Image)

        Dim s As Size = g.MeasureString(text, f).ToSize
        Dim r As New Rectangle(center.X - (s.Width / 2) - 16,
                                 center.Y - (s.Height / 2) - 16,
                                 s.Width + 32,
                                 s.Height + 32)

        g.DrawImage(backImg, r, r, GraphicsUnit.Pixel)

        Using m As New Matrix
            m.RotateAt(angle, center)
            g.Transform = m

            Using p As New Pen(Color.Black, 6)
                g.DrawRectangle(p, r)
            End Using
            Using sf As New StringFormat
                sf.LineAlignment = StringAlignment.Center
                sf.Alignment = StringAlignment.Center
                g.DrawString(text, f, Brushes.Black, r, sf)
            End Using
            g.ResetTransform()
        End Using
    End Sub

但是,我现在只剩下

注意它绘制了背景,然后进行了旋转并绘制了图章。它几乎可以工作。在这个例子中,直线显示了预期的行为......但是我希望用背景填充整个图章。两侧多余的白色将被旋转到邮票的背景中。我很困惑,因为我会怀疑“灰色”部分会剪掉图像的一部分,但它们不是(如果我将它移到我不幸无法在此处发布的其他区域)通知已超出倾斜,除了矩形的边是这样绘制的。

希望有更多信息的另一个编辑

希望这里能更好地解释我正在尝试做的事情。我使用第三方 PDF 查看器,我需要允许用户将图像添加到 PDF。查看器不允许我在其上引发点击事件,因此为了获取用户鼠标点击,我执行以下操作:

  1. 获取表单的屏幕截图
  2. 隐藏 PDF 查看器
  3. 向我的表单添加一个 PictureBox 控件,替换 PDF 查看器所在的区域
  4. 通过屏幕抓取,我制作了降低亮度的图像副本
  5. 显示图像的灰度副本并使用图片框上的鼠标悬停事件直接在图像上绘图
  6. 我在图片框上画了一个图章,但希望它的背景是原始的(未调整亮度的图像)。但是,由于该区域可能会使用旋转进行转换,因此我无法抓取背景图像。如果未提供角度,则源矩形匹配。但是,如果它旋转,我无法从源图像中抓取相同的旋转矩形。

按钮点击事件:

  Dim bds As Rectangle = AxDPVActiveX1.Bounds
  Dim pt As Point = AxDPVActiveX1.PointToScreen(bds.Location)

    Using bit As Bitmap = New Bitmap(bds.Width, bds.Height)
        Using g As Graphics = Graphics.FromImage(bit)
            g.CopyFromScreen(New Point(pt.X - AxDPVActiveX1.Location.X, pt.Y - AxDPVActiveX1.Location.Y), Point.Empty, bds.Size)
        End Using

        _angle = 0

        _curImg = bit.Clone
        _curGray = Utils.CopyImageAndAdjustBrightness(bit, -100)

 End Using

   Dim p As New PictureBox

        Utils.SetControlDoubleBuffered(p)

        p.Dock = DockStyle.Fill
        p.BackColor = Color.Transparent
        AxDPVActiveX1.Visible = False
        p.Image = _curImg.Clone

        AddHandler p.MouseClick, AddressOf PanelDownMouse
        AddHandler p.MouseMove, AddressOf PanelMouseMove
        AddHandler p.MouseWheel, Sub(s As Object, ee As MouseEventArgs)
                                     _angle = Math.Max(Math.Min(_angle + (ee.Delta / 30), 45), -45)
                                     PanelMouseMove(s, ee)
                                 End Sub
        AddHandler p.MouseEnter, Sub(s As Object, ee As EventArgs)
                                     CType(s, Control).Focus()
                                 End Sub


        AxDPVActiveX1.Parent.Controls.Add(p)

在该代码之后,我最终得到了两个图像。 _curgray 是调整了亮度的图像,_curImg 是我的原始屏幕抓取。

_curGray: _curImg:

mouseMove 移动事件应用于我的新图片框。这就是问题前面的所有代码发挥作用的地方。

使用上面的代码,我的 mouseMove 事件不断创建一个新图像以显示在我的图片框中。如果不涉及轮换,我会得到我想要的东西。请注意下图中邮票的背景是如何比一切都亮的。蓝色方块上方的部分稍浅。我正在使用这种方式来吸引观众注意这个区域......这对我正在做的事情很重要。

但是,当对其应用旋转时,我似乎无法从原始图像中复制。看下图,背景没有随它旋转。我需要从原始图像中抓取一个旋转的矩形。

http://msdn.microsoft.com/en-us/library/ms142040(v=vs.110).aspxGraphics.DrawImage() 接受

Public Sub DrawImage ( _
    image As Image, _
    destRect As Rectangle, _
    srcRect As Rectangle, _
    srcUnit As GraphicsUnit _
)

我可以指定从我的源图像(在本例中为 _curImg)复制此源矩形并放置到我的新绘图上。它不允许我对源矩形应用转换。基本上我想从我的源图像中复制一个相当于旋转矩形的区域(基于@larstech 的转换)

我不知道如何更清楚地表达这个概念。如果仍然没有意义,我将接受 LarsTech 的答案作为最佳答案并放弃我的想法。

【问题讨论】:

  • 您可以变换矩形的点。新矩形(围绕旋转的文本)具有所有 X 的 x=min,所有 Y 的 y=min,width=maxX-minX 和 height=maxY-minY

标签: vb.net winforms .net-4.5 system.drawing


【解决方案1】:

这只是三角函数:

您知道c,因为您知道原始文本的宽度,您知道h,因为您知道文本的高度。您还需要知道 alpha,它是您旋转文本的角度。

现在你需要发挥数学的力量:首先取最后的小矩形。在左下角,您可以看到 x 的右侧角度实际上是 180°-90°-alpha 或 90°-alpha。所以 alpha 也可以在对面的站点上找到。所以你可以找到x:

x = h * sin(alpha)

y 也是如此,但它要么是 sin(90°-alpha),要么是 cos(alpha)

y = h * cos(alpha)

接下来你需要找到 a 和 b 来完成矩形。大三角给你

a = w * cos(alpha)

b = w * sin(alpha)

然后将这些部分加在一起:

NewWidth = a + x
NewHeight = b + y

这样你就可以得到边界框的大小。至于坐标,要看你打印旋转后的文字时实际定义的是哪个点。

【讨论】:

    【解决方案2】:

    我会尝试将矩形和文本一起绘制:

    Private Sub DrawStamp(g As Graphics, text As String,
                          f As Font, center As Point, angle As Integer)
      Using m As New Matrix
        g.SmoothingMode = SmoothingMode.AntiAlias
        g.TextRenderingHint = TextRenderingHint.AntiAlias
        m.RotateAt(angle, center)
        g.Transform = m
        Dim s As Size = g.MeasureString(text, f).ToSize
        Dim r As New Rectangle(center.X - (s.Width / 2) - 16,
                               center.Y - (s.Height / 2) - 16,
                               s.Width + 32,
                               s.Height + 32)
        g.FillRectangle(Brushes.White, r)
        Using p As New Pen(Color.Black, 6)
          g.DrawRectangle(p, r)
        End Using
        Using sf As New StringFormat
          sf.LineAlignment = StringAlignment.Center
          sf.Alignment = StringAlignment.Center
          g.DrawString(text, f, Brushes.Black, r, sf)
        End Using
        g.ResetTransform()
      End Using
    End Sub
    

    油漆示例:

    Protected Overrides Sub OnPaint(e As PaintEventArgs)
      MyBase.OnPaint(e)
      e.Graphics.Clear(Color.LightGray)
      Using f As New Font("Calibri", 16, FontStyle.Bold)
        DrawStamp(e.Graphics,
                  "Reviewed By Doctor Papa",
                  f,
                  New Point(Me.ClientSize.Width / 2, Me.ClientSize.Height / 2),
                  -25)
      End Using
    End Sub
    

    结果:

    在这里我更新了代码以“剪切”旋转的矩形,以便在应用文本和边框之前从原始图像复制相同的区域:

    Private Sub DrawStamp(g As Graphics, text As String,
                          f As Font, center As Point, angle As Integer)
    
      Dim s As Size = g.MeasureString(text, f).ToSize
      Dim r As New Rectangle(center.X - (s.Width / 2) - 16,
                             center.Y - (s.Height / 2) - 16,
                             s.Width + 32,
                             s.Height + 32)
    
      Using bmp As New Bitmap(_curImg.Width, _curImg.Height)
        Using gx As Graphics = Graphics.FromImage(bmp)
          Using m As New Matrix
            m.RotateAt(angle, center)
            gx.Transform = m
            gx.SetClip(r)
            gx.ResetTransform()
          End Using
          gx.DrawImage(_curImg, Point.Empty)
        End Using
        g.DrawImage(bmp, Point.Empty)
      End Using
    
      Using m As New Matrix
        g.SmoothingMode = SmoothingMode.AntiAlias
        g.TextRenderingHint = TextRenderingHint.AntiAlias
        m.RotateAt(angle, center)
        g.Transform = m      
        Using p As New Pen(Color.Black, 6)
          g.DrawRectangle(p, r)
        End Using
        Using sf As New StringFormat
          sf.LineAlignment = StringAlignment.Center
          sf.Alignment = StringAlignment.Center
          g.DrawString(text, f, Brushes.Black, r, sf)
        End Using
        g.ResetTransform()
      End Using
    End Sub
    

    新结果:

    【讨论】:

    • 我喜欢这样。不知道为什么我没有想到这一点。会试试看效果如何
    • 矩形被旋转...我想调用 g.DrawImage(...) 使用另一个图像作为源,但旋转的矩形作为源矩形。我该怎么做?
    • @pinkfloydx33 不确定我是否遵循这一点。我的示例是使用表单绘制事件中的图形。该图形对象可以替换为图像中的图形对象。
    • 在我的原始帖子中,您会在图片中看到它们在邮票内有背景。基本上它从另一个图像中复制一个源矩形并将其绘制到图章图形中。 g.DrawImage(_curImg, rect, rect, GraphicsUnit.Pixel) 其中 _curImg 是我在其他地方保留的不同图像。我正在将该图像的源区域绘制到当前图形对象中。但是,由于它现在从变换中旋转,源矩形不会平移。我需要 _curImg 的未旋转部分
    • 我在上面的帖子中更新了它现在正在做什么的图片,希望你能明白我的意思。
    猜你喜欢
    • 2010-10-11
    • 1970-01-01
    • 2019-01-05
    • 2020-05-24
    • 2012-04-15
    • 1970-01-01
    • 2021-02-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多