计算旋转文本和边界边界的坐标

时间:2014-04-11 17:08:46

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

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

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

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

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

如何计算x和y坐标: 放置旋转的文本 AND一个边界矩形,用10px

填充文本的宽度和高度

下面的代码有效,直到我开始调整字体大小,然后一切都没有偏差。

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

enter image description here enter image description here

下一张图片显示,随着文字尺寸变大,我的像素四处移动并被切断。在更大的文本中,宽度/高度也会结束。

enter image description here enter image description here

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

        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是原始图像的副本,亮度调高。当我从下面更改代码时,我最终得到:

enter image description here注意双线。它们随着印章旋转,即使它们充当背景图像并且应该是未旋转的

根据建议,我将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

但是,我现在离开了enter image description here

注意它绘制了背景,然后进行了旋转并绘制了标记。它几乎可以工作。在这个例子中,直线显示了预期的行为......但是我希望用背景填充整个图章。两侧多余的白色将是旋转到邮票背景中的东西。我很困惑,因为灰色&#39;然后,我会怀疑是要剪掉图像的一部分,但它们不是(如果我把它移到其他区域,我很遗憾不能在这里张贴),除了事实之外,通知没有偏差矩形的边是这样画的。

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

希望能够更好地解释我想要做的事情。我使用第三方PDF查看器,我需要允许用户将图像添加到PDF。观众不允许我在其上提出点击事件,因此为了抓住用户点击鼠标,我会执行以下操作:

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

      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:enter image description here _curImg:enter image description here

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

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

    enter image description here

    然而,当对它应用旋转时,我似乎无法从原始图像复制。看下面的图像,背景与它一起旋转。我需要从ORIGINAL图像中抓取一个旋转的矩形。

    enter image description here

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

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

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

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

2 个答案:

答案 0 :(得分:1)

这只是三角学:

enter image description here

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

现在你需要运用数学的力量:首先在最后取小矩形。在左下角你可以看到,x的右角实际上是180°-90°-α,或90°-α。所以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

这样你就可以得到边界框的大小。至于坐标,它取决于打印旋转文本时实际定义的点。

答案 1 :(得分:1)

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

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

结果:

enter image description here

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

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

新结果:

enter image description here