简体   繁体   中英

Draw images with 'Floating Point Numbers'

Currently, I'm trying to transform an existing C# project to GoLang. The project takes an XML file which contains a bunch of coordinates and draws them on an image.

In C# the code to draw a rectangle on an image is the following:

public void DrawRectangle(Graphics graphics, RectangleShape rectangle)
{
    using (var drawingPen = new Pen(Color.Black))
    {
        graphics.DrawRectangle(
            drawingPen,
            rectangle.StartX,
            rectangle.StartY,
            rectangle.Width,
            rectangle.Height);
    }
}

A rectangle is defined by the following class:

internal sealed class RectangleShape
{
    internal RectangleShape(float startX, float startY, float width, float height)
    {
        this.StartX = startX;
        this.StartY = startY;
        this.Width = width;
        this.Height = height;
    }

    internal float StartX { get; }

    internal float StartY { get; }

    internal float Width { get; }

    internal float Height { get; }
}

It means that C# is able to draw a rectangle on an image using coordinates that are defined as float .

Now, I'm trying to transform the code to GoLang where I'm drawing a rectangle with the following code:

// DrawRect draws a rectangle with the given dimensions on the given image.
func DrawRect(img *image.RGBA, rect Rectangle) {
    endX := rect.X + rect.Width
    endY := rect.Y + rect.Height

    drawHLine(img, rect.X, rect.Y, endX)
    drawHLine(img, rect.Y, endY, endX)
    drawVLine(img, rect.Y, rect.X, endY)
    drawVLine(img, rect.Y, endX, endY)
}

// PRIVATE: drawHLine draws a horizontal line with the given coordinates on the given image.
func drawHLine(img *image.RGBA, startX, y, endX float32) {
    col := color.RGBA{0x00, 0x00, 0x00, 0xff}

    for ; startX <= endX; startX++ {
        img.Set(startX, y, col)
    }
}

// PRIVATE: drawVLine draws a vertical line with the given coordinates on the given image.
func drawVLine(img *image.RGBA, startY, x, endY float32) {
    col := color.RGBA{0x00, 0x00, 0x00, 0xff}

    for ; startY <= endY; startY++ {
        img.Set(x, startY, col)
    }
}

The rectangle is defined with the following struct:

// Rectangle represents a rectangular shape.
type Rectangle struct {
    X      float32
    Y      float32
    Width  float32
    Height float32
}

The sample in Go does not work because the Set function on an image has the following structure:

func (p *RGBA) Set(x, y int, c color.Color) {

Is there any way how Go can work with float parameters to draw a rectangle on an image?

The image.Image type is an interface which defines an image to have pixels with integer coordinates, accessible via the Image.At() method:

At(x, y int) color.Color

The concrete implementation you're using image.RGBA allows changing the pixels, again, using integer coordinates with the RGBA.Set() method:

func (p *RGBA) Set(x, y int, c color.Color)

The simplest solution is to convert the float coordinates to integer coordinates. Simply converting floating point to integer is truncation, so rather you should use rounding. A simple rounding is adding 0.5 before the conversion. For details, see Golang Round to Nearest 0.05 .

And best is to do it on the "start" coordinates, so the loop can work with integer values, eg:

func drawHLine(img *image.RGBA, startX, y, endX float32) {
    col := color.RGBA{0x00, 0x00, 0x00, 0xff}

    x1, x2 := int(startX + 0.5), int(endX + 0.5)
    y1 := int(y + 0.5)
    for x := x1; x <= x2; x++ {
        img.Set(x, y1, col)
    }
}

Do note that however this "simplest" solution to convert float coordinates to integer might not be the "best". For example if you need to draw a horizontal line at the x = 0.1 coordinate, the above solution will draw a line at x = 0 . A different solution may be to draw a "stronger" line at x = 0 , and a "weeker" line at x = 1 , giving the effect that the line is in fact at x = 0.1 if viewed from a distance. This aliasing technique gives certainly better results if lines are not horizontal and vertical, but requires more computation (and thus are slower).

If you do need floating precision at drawing, you may use 3rd party libs like github.com/fogleman/gg which allows you to pass float64 coordiates, and it uses antialiasing to achieve nice results.

For example drawing a rectangle with github.com/fogleman/gg onto an image.RGBA image is like this:

var img *image.RGBA = // create your RGBA image

ctx := gg.NewContextForRGBA(img)

// Set drawing color:
ctx.SetColor(color.RGBA{0x00, 0x00, 0x00, 0xff})
// Or simply: ctx.SetRGB(0, 0, 0)

// Draw rectangle: x, y, w, h are all float64 parameters
ctx.DrawRectangle(x, y, w, h)

Also see related: Draw a rectangle in Golang?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM