GoLang绘制多分支图形仅显示单分支问题排查求助
GoLang绘图仅显示单个分支的问题排查
我正在用GoLang编写代码,遍历一组带根节点和茎节点坐标的“分支”列表,日志显示每个分支都执行了绘制逻辑,但最终生成的PNG图片里只显示一个最小的分支。以下是逐点绘制分支的函数代码,需要排查原因并修复以显示所有分支:
myImage := image.NewRGBA(image.Rect(0, 0, 800, 600)) white := color.RGBA{255, 255, 255, 255} //draw.Draw(myImage, image.Rect(0, 0, 800, 600), &image.Uniform{white}, image.ZP, draw.Src) //black := color.RGBA{0, 0, 0, 255} myFile, err := os.Create(imagePath) // ... now lets save output image if err != nil { panic(err) } for _, branch := range branchList { // might print in arbitrary order /*fmt.Println("basic values: ", branch.length, branch.angle, branch.slope, " ") fmt.Println("coords. root:", branch.root.x, branch.root.y, "stem: ", branch.stem.x, branch.stem.y, " ") fmt.Println("branch roots: L:", branch.lBranchCoord.x, branch.lBranchCoord.y, "R: ", branch.rBranchCoord.x, branch.rBranchCoord.y) fmt.Println("branch pointers ", branch.lBranch, branch.rBranch)*/ fmt.Println("branch values", branch) var x1, x2 int slope := (branch.stem.y - branch.root.y) / (branch.stem.x - branch.root.x) fmt.Println("slope: ", slope) if branch.root.x < branch.stem.x { x1 = int(branch.root.x) x2 = int(branch.stem.x) } else { x2 = int(branch.root.x) x1 = int(branch.stem.x) } if x1 < 0 { x1 = 0 } if x2 > 800 { x2 = 800 } y1 := branch.root.y // beware mixing up y coordiantes y2 := branch.stem.y if y1 < 0 { y1 = 0 } if y2 > 800 { y2 = 800 } // something is wrong with point drawing, likely having a rectangle with size 0 if branch.stem.x != branch.root.x { for h := x1; h <= x2; h++ { // make each dot drawn 2x2 instead of 1x1 // add a coefficient, either 1 or -1, to the values in the pixel // based on location // or, just add one to each and see what happens currentDot := image.Rect(h, int(float64(branch.root.y)+(float64(h-x1)*slope)), h+1, int(float64(branch.root.y)+(float64(h-x1)*slope))+1) //fmt.Println("point draw", currentDot) // negative coordiantes don't allow it to be drawn //fmt.Println("dot drawn") draw.Draw(myImage, currentDot, &image.Uniform{white}, image.ZP, draw.Src) } } else { for h := int(branch.root.y); h >= int(branch.stem.y); h-- { currentDot := image.Rect(int(branch.root.x), (int(branch.root.y) + h), int(branch.root.x), (int(branch.root.y) + h)) //fmt.Println("dot drawn for straight line") draw.Draw(myImage, currentDot, &image.Uniform{white}, image.ZP, draw.Src) } } defer myFile.Close() //fmt.Println(myImage) png.Encode(myFile, myImage) }
核心问题分析
1. 文件编码与关闭逻辑错误
png.Encode(myFile, myImage)和defer myFile.Close()被错误地放在了for循环内部:
- 每次循环调用
png.Encode都会将当前myImage的内容从头写入文件,覆盖之前的内容,最终文件只保留最后一次循环绘制的分支。 defer myFile.Close()在循环内会多次注册,函数退出时才执行关闭,多次Encode会导致文件写入异常,甚至内容截断。
2. 垂直分支绘制逻辑无效
在处理branch.stem.x == branch.root.x的垂直分支时:
currentDot使用image.Rect(int(branch.root.x), (int(branch.root.y) + h), int(branch.root.x), (int(branch.root.y) + h))生成了宽高为0的无效矩形,根本不会绘制任何像素。- 循环变量
h的计算逻辑错误,会导致y坐标严重越界,进一步加剧绘制失败。
3. 斜率计算精度丢失
slope := (branch.stem.y - branch.root.y) / (branch.stem.x - branch.root.x)使用整数除法,会丢失小数精度,导致非整数斜率的分支绘制出现阶梯状偏移,甚至部分像素无法正确渲染。
修复后的代码
import ( "image" "image/color" "image/draw" "image/png" "math" "os" ) func drawBranches(branchList []Branch, imagePath string) { myImage := image.NewRGBA(image.Rect(0, 0, 800, 600)) white := color.RGBA{255, 255, 255, 255} black := color.RGBA{0, 0, 0, 255} // 先填充黑色背景,让白色分支更清晰 draw.Draw(myImage, myImage.Bounds(), &image.Uniform{black}, image.ZP, draw.Src) myFile, err := os.Create(imagePath) if err != nil { panic(err) } // 将defer移到循环外,确保所有绘制完成后再关闭文件 defer myFile.Close() for _, branch := range branchList { fmt.Println("branch values", branch) dx := branch.stem.x - branch.root.x dy := branch.stem.y - branch.root.y // 用浮点数计算斜率,避免精度丢失 slope := dy / dx fmt.Println("slope: ", slope) // 对坐标四舍五入,确保像素位置准确 x1 := int(math.Round(branch.root.x)) x2 := int(math.Round(branch.stem.x)) if branch.root.x > branch.stem.x { x1, x2 = x2, x1 } // 限制x坐标在图像有效范围内(0~799) x1 = max(x1, 0) x2 = min(x2, 799) yRoot := int(math.Round(branch.root.y)) yStem := int(math.Round(branch.stem.y)) // 限制y坐标在图像有效范围内(0~599) yRoot = max(yRoot, 0) yStem = min(yStem, 599) if dx != 0 { for h := x1; h <= x2; h++ { y := int(math.Round(branch.root.y + float64(h-x1)*slope)) y = max(y, 0) y = min(y, 599) // 绘制2x2的点,确保矩形有效 currentDot := image.Rect(h, y, h+2, y+2) // 确保矩形不超出图像边界 currentDot = currentDot.Intersect(myImage.Bounds()) draw.Draw(myImage, currentDot, &image.Uniform{white}, image.ZP, draw.Src) } } else { // 修正垂直分支绘制逻辑:遍历正确的y范围 yStart := min(yRoot, yStem) yEnd := max(yRoot, yStem) for h := yStart; h <= yEnd; h++ { currentDot := image.Rect(int(branch.root.x), h, int(branch.root.x)+2, h+2) currentDot = currentDot.Intersect(myImage.Bounds()) draw.Draw(myImage, currentDot, &image.Uniform{white}, image.ZP, draw.Src) } } } // 所有分支绘制完成后,再统一编码写入文件 if err := png.Encode(myFile, myImage); err != nil { panic(err) } } // 辅助函数:取最大值 func max(a, b int) int { if a > b { return a } return b } // 辅助函数:取最小值 func min(a, b int) int { if a < b { return a } return b }
额外说明
- 新增
max/min辅助函数,严格限制坐标在图像的有效像素范围内,避免越界绘制。 - 对坐标值做四舍五入处理,让线条渲染更平滑自然。
- 完全重构垂直分支的绘制逻辑,确保垂直方向分支能正常显示。
- 将文件编码操作移到循环外部,确保所有分支绘制完成后再生成最终PNG文件。
内容的提问来源于stack exchange,提问作者T. J. Foster
相关产品推荐
相关产品推荐

