You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Gloss中绘制Picture边界矩形?能否获取/实现其最小包围盒函数?

Great question! Let's break this down step by step—since Gloss doesn't have built-in support for this out of the box, but it's totally feasible to implement yourself.

Handling Bounding Boxes in Gloss

First: Does Gloss have a built-in function for picture bounding boxes?

Short answer: No, there's no standard function in Gloss that takes a Picture and returns its minimal enclosing rectangle's dimensions and coordinates. You'll need to implement this yourself by traversing Gloss's Picture algebraic data type.

Step 1: Define a Bounding Box Structure

First, let's create a simple data type to represent our bounding box—this makes it easy to store and manipulate the left/right/top/bottom bounds:

data BoundingBox = BoundingBox
  { bbLeft   :: Float  -- Leftmost x-coordinate
  , bbRight  :: Float  -- Rightmost x-coordinate
  , bbTop    :: Float  -- Topmost y-coordinate
  , bbBottom :: Float  -- Bottommost y-coordinate
  } deriving (Show, Eq)

Step 2: Implement a Bounding Box Calculator

We'll write a recursive function boundingBox :: Picture -> BoundingBox that handles every constructor in the Picture type. Here's a complete implementation with explanations:

import Graphics.Gloss
import Graphics.Gloss.Data.Text (textWidth, textHeight)
import qualified Graphics.Gloss.Geometry.Angle as Angle

-- Merge two bounding boxes into one that contains both
mergeBoundingBoxes :: BoundingBox -> BoundingBox -> BoundingBox
mergeBoundingBoxes b1 b2 = BoundingBox
  { bbLeft   = min (bbLeft b1) (bbLeft b2)
  , bbRight  = max (bbRight b1) (bbRight b2)
  , bbTop    = max (bbTop b1) (bbTop b2)
  , bbBottom = min (bbBottom b1) (bbBottom b2)
  }

-- Calculate the bounding box for any Gloss Picture
boundingBox :: Picture -> BoundingBox
boundingBox Blank = BoundingBox 0 0 0 0  -- Empty picture, trivial bounds
boundingBox (Polygon pts) =
  let xs = map fst pts
      ys = map snd pts
  in BoundingBox (minimum xs) (maximum xs) (maximum ys) (minimum ys)
boundingBox (Circle r) = BoundingBox (-r) r r (-r)  -- Centered at origin
boundingBox (ThickCircle r t) = let outerR = r + t in BoundingBox (-outerR) outerR outerR (-outerR)
boundingBox (Line pts) =
  let xs = map fst pts
      ys = map snd pts
  in BoundingBox (minimum xs) (maximum xs) (maximum ys) (minimum ys)
boundingBox (Text str) =
  -- Approximate bounds: text starts at (0,0), extends to (width, height)
  -- Note: This doesn't account for descenders (like y/g) perfectly, but works for most cases
  let w = textWidth str
      h = textHeight str
  in BoundingBox 0 w h 0
boundingBox (Color _ pic) = boundingBox pic  -- Color doesn't affect bounds
boundingBox (Translate dx dy pic) =
  let bb = boundingBox pic
  in BoundingBox
       (bbLeft bb + dx)
       (bbRight bb + dx)
       (bbTop bb + dy)
       (bbBottom bb + dy)
boundingBox (Rotate theta pic) =
  let bb = boundingBox pic
      rad = Angle.degToRad theta  -- Convert degrees to radians
      cosθ = cos rad
      sinθ = sin rad
      -- Get all four corners of the original bounding box
      corners = [ (bbLeft bb, bbTop bb)
                , (bbRight bb, bbTop bb)
                , (bbRight bb, bbBottom bb)
                , (bbLeft bb, bbBottom bb)
                ]
      -- Rotate each corner using standard 2D rotation formula
      rotatedCorners = map (\(x,y) -> (x*cosθ - y*sinθ, x*sinθ + y*cosθ)) corners
      xs = map fst rotatedCorners
      ys = map snd rotatedCorners
  in BoundingBox (minimum xs) (maximum xs) (maximum ys) (minimum ys)
boundingBox (Scale sx sy pic) =
  let bb = boundingBox pic
      scaledLeft = bbLeft bb * sx
      scaledRight = bbRight bb * sx
      scaledTop = bbTop bb * sy
      scaledBottom = bbBottom bb * sy
  -- Handle negative scaling (which flips axes) by taking min/max
  in BoundingBox
       (min scaledLeft scaledRight)
       (max scaledLeft scaledRight)
       (max scaledTop scaledBottom)
       (min scaledTop scaledBottom)
boundingBox (Pictures pics) =
  -- Merge bounds of all child pictures
  foldl mergeBoundingBoxes (BoundingBox 0 0 0 0) (map boundingBox pics)

Step 3: Draw the Bounding Rectangle

Once you have the bounding box, converting it to a Gloss Picture (to draw as a border) is straightforward. Here's a function that creates a red rectangle around the bounds:

drawBoundingBox :: BoundingBox -> Picture
drawBoundingBox bb =
  let width = bbRight bb - bbLeft bb
      height = bbTop bb - bbBottom bb
      centerX = (bbLeft bb + bbRight bb) / 2
      centerY = (bbTop bb + bbBottom bb) / 2
  -- Gloss's Rectangle uses center coordinates + half-width/half-height
  in Color red $ Translate centerX centerY $ Rectangle (width/2) (height/2)

Example Usage

Putting it all together, here's a main function that draws a couple of pictures and their combined bounding box:

main :: IO ()
main =
  let myArt = Pictures
        [ Translate 100 50 (Text "Hello Gloss!")
        , Rotate 30 (Translate (-50) (-20) (Circle 30))
        ]
      artBounds = boundingBox myArt
  in display
       (InWindow "Gloss Bounding Box" (400, 400) (10, 10))
       white
       (Pictures [myArt, drawBoundingBox artBounds])

Notes to Keep in Mind

  • Text Bounds: The text handling is approximate—Gloss's textWidth and textHeight don't account for descenders (like the bottom of a 'g' or 'y') perfectly. For precise text bounds, you'd need a lower-level text rendering library, but this works for most casual use cases.
  • Transformations: The function correctly handles Translate, Rotate, and Scale (even negative scaling, which flips the picture).
  • Edge Cases: Empty Blank pictures return a trivial bounding box at (0,0), but you can adjust this if needed for your use case.

内容的提问来源于stack exchange,提问作者user1023733

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:17:50