Графическое автомасштабирование - PullRequest
1 голос
/ 04 июля 2010

У меня есть следующие коды, чтобы нарисовать единичный круг

open System
open Microsoft.FSharp.Collections
open Microsoft.FSharp.Math
open System.Drawing
open System.Windows.Forms

let make_point (x:float) (y:float) = (fun bit -> if bit = 0.0 then x else y)
let x_of (point:float->float) = point 0.0
let y_of (point:float->float) = point 1.0

let unit_circle (t:float) = 
    make_point (sin <| 2.0 * Math.PI * t)
               (cos <| 2.0 * Math.PI * t)
let draw_connected (curve:float->float->float) (values: float list)=
    let form = new Form(Text = "Curve")
    let drawCurve (g:Graphics) = 
        for t in values do
            let p = curve t        
            g.DrawEllipse(Pens.Red, 
                          float32 (x_of p * 50.0 + (float)form.ClientSize.Width / 2.0), 
                          float32 (y_of p * 50.0 + (float)form.ClientSize.Height / 2.0), 
                          float32 1, 
                          float32 1)
    form.Paint.Add(fun e -> drawCurve e.Graphics)    
    form.Show()

draw_connected unit_circle ([0.0 .. 0.01 .. 1.0])

Я не совсем удовлетворен, потому что мне нужно вручную «масштабировать» координаты x и y на 50, чтобы сделать круг видимым.Есть ли способ заставить F # автоматически масштабировать?

Спасибо.

Ответы [ 3 ]

1 голос
/ 04 июля 2010

Я думаю, что код представляет 2D-точку как функцию, принимающую 3 аргумента - флаг, x & y. Флаг указывает, какой из х и у вернуть. Было бы (немного) больше смысла для начала, если бы флаг был скорее bool, чем float. Я предполагаю, что код был преобразован из другого языка, который имеет только плавающие числа?

Вот немного более понятная версия:

open System
open Microsoft.FSharp.Collections
open Microsoft.FSharp.Math
open System.Drawing
open System.Windows.Forms
open System.Threading

type Point = {x : float; y : float}

let unit_circle (angle : float) = 
    {
        x = (sin <| 2.0 * Math.PI * angle)
        y = (cos <| 2.0 * Math.PI * angle)
    }

let draw_connected (curve : float -> Point) (radius : float) (angles : float list) =
    let form = new Form(Text = "Curve")
    let drawCurve (gfx : Graphics) =
        for angle in angles do
            let p = curve angle        
            gfx.DrawEllipse(Pens.Red, 
                          float32 (p.x * radius + (float)form.ClientSize.Width / 2.0), 
                          float32 (p.y * radius + (float)form.ClientSize.Height / 2.0), 
                          float32 1,
                          float32 1)
    form.Paint.Add (fun pntEvntArgs -> drawCurve pntEvntArgs.Graphics)    
    form.Show ()
    form

let form = draw_connected unit_circle 50.0 ([0.0 .. 0.01 .. 1.0])

while form.Created do
    Thread.Sleep (1)
    Application.DoEvents ()
done

Не уверен, почему круг отображается как набор из 1 пиксельных эллипсов.

В любом случае, как говорит Томас, нужно либо масштабировать окружность, либо систему координат. В противном случае вы получите 1 пиксельный круг.

1 голос
/ 04 июля 2010

Я не пытался полностью понять ваш код, но, возможно, вы могли бы использовать масштабное преобразование , которое можно указать для объекта Graphics.Это меняет систему координат на Graphics, поэтому все выполняемые вами чертежи (например, с использованием DrawEllipse) автоматически масштабируются - вы можете настроить масштабирование таким образом, чтобы единичный круг отображался как круг с радиусом 50.

  • Чтобы установить преобразование, используйте метод ScaleTransfrom (см. документация MSDN для получения дополнительной информации) экземпляра Graphics (значение g в вашем коде).
0 голосов
/ 05 июля 2010

Как сказал Томас, вы можете использовать масштабные преобразования.Если вы хотите нарисовать круг, используя маленькие кривые, вы можете использовать несколько вызовов DrawCurve:)

Для этого я немного изменил код Джона:

  • использовал System.Drawing.Point введите вместо своей записи
  • , изменив unit_circle, чтобы он возвращал кортеж, представляющий координаты x, а y
  • превратили ваш список углов в последовательность последовательности углов.Это будет полезно, поскольку мы можем иметь переменное число узлов для нашей кривой (кардинальный сплайн), представленное константой N
  • , реализованной методом splitRepeatEvery, например:

    Seq.splitRepeatEvery 3 { 1 .. 10 } возвращает seq [seq [1; 2; 3]; seq [3; 4; 5]; seq [5; 6; 7]; seq [7; 8; 9]; seq [9; 10]]

Вот код:

module Seq =
    /// Split a sequence into pieces, each n items long
    /// repeating elements between start and end of subsequences.
    let splitRepeatEvery (count : int) (source : seq<'T>) = 
        if not (count > 1) then failwith "count must be superior to 1"
        seq { use e = source.GetEnumerator()
              let hasNext = ref (e.MoveNext())
              while !hasNext do
                 let (block:option<'T>[]) = Array.create count None

                 for i in 0 .. count - 1 do
                     do block.[i] <- if !hasNext then Some(e.Current) else None
                     if (i <> count - 1) then do hasNext := e.MoveNext()

                 yield seq { yield! block }
                 |> Seq.filter (fun x -> x.IsSome)
                 |> Seq.map (function Some(e) -> e | _ -> failwith "" ) }

let unit_circle (angle : float) = 
    (sin <| 2.0 * Math.PI * angle), (cos <| 2.0 * Math.PI * angle)

let draw_connected curve radius (seqOfAngles : float seq seq) knotsCount =
    let form = new Form(Text = "Curve")

    let computeBoundingBox points =
        let search f acc array =
            Array.fold (fun (x,y) (p:Point) -> f p.X x, f p.Y y) acc array
        let minX, minY = search min (form.ClientSize.Width, form.ClientSize.Height) points
        let maxX, maxY = search max (0,0) points
        new Rectangle(minX, minY, abs(minX-maxX), abs(minY-maxY))

    let drawCurves (gfx : Graphics) =
        // Create a buffer for storing our knots
        let buffer = Array.create knotsCount (new Point())
        let mutable i = 0

        for angles in seqOfAngles do
            for angle in angles do
                let x, y = curve angle
                let X = int(x * radius + (float)form.ClientSize.Width  / 2.0)
                let Y = int(y * radius + (float)form.ClientSize.Height / 2.0)
                let P = new Point(X, Y)
                buffer.[i] <- P
                i <- i + 1

            let knots = buffer.[0..(i-1)]
            // Draw spline only if we have one or more knots
            if knots.Length <> 1 then
                gfx.DrawCurve(Pens.Red, knots)
                // For debug: compute BBox of an array of points and draw it
                let debugRect = computeBoundingBox knots
                gfx.DrawRectangle(Pens.Black, debugRect)

            // Don't forget to reset position in buffer between each spline draw call
            i <- 0

    form.Paint.Add (fun pntEvntArgs -> drawCurves pntEvntArgs.Graphics)    
    form.Show ()
    form

// Define constants    
let STEP = 0.050
let N = 4
// Define a new sequence of sequence of angles
let s = {0.0 .. STEP .. 1.0} |> Seq.splitRepeatEvery N
let form = draw_connected unit_circle 120.0 s N

// For debug: print sequence of sequence of angles
s |> Seq.iter (fun s -> Seq.iter (fun x -> printf "%f " x) s; printfn "")

while form.Created do
    Thread.Sleep (1)
    Application.DoEvents ()
done

Вы можете играть с различными значениями N (количество узлов длясплайны) и STEP (но будьте осторожны, STEP следует выбирать так, чтобы 1.0f было кратным STEP или числом с плавающей запятой, чтобы последний элемент последней последовательности был достаточно близок к 1.0f, в противном случае последний выигранный сплайнне подключайтесь к первому!).И вуаля!

альтернативный текст http://img818.imageshack.us/img818/9765/circles3.png

...