proposal: image: add Rectangle.TLBR() iter.Seq[Point]
Proposal Details
It is very common to process an image from top left to bottom right. I propose adding this method to Rectangle:
func (r Rectangle) TLBR() iter.Seq[Point] {
r = r.Canon()
return func(yield func(Point) bool) {
for y := r.Min.Y; y < r.Max.Y; y++ {
for x := r.Min.X; x < r.Max.X; x++ {
if !yield(Pt(x, y)) {
return
}
}
}
}
}
We may also want to add iterators for other directions, such as BRTL.
Related Issues
- proposal: image: add (Rectangle).PointsBy for iterating over points as iter.Seq[Point] #69254
- proposal: iter: Add functions for traversing several sequences. #70277 (closed)
- proposal: x/exp/xiter: Add transformers between iter.Seq and iter.Seq2 #69702 (closed)
- proposal: iter: add New #67930 (closed)
- reflect: Add Type.Fields() iter.Seq[StructField] and Type.Methods() iter.Seq[Method] #66631 (closed)
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
I honestly feel pretty torn about this one...
On the one hand, I do agree that I've written loops like this loads of times, and so it might be nice to reduce the boilerplate.
But on the other hand, I think uses of a function like this would hide the possibly-important detail of what order the points are being visited in. I like that in the longhand version it's clear whether we're going in row-major or column-major order just by how the two loops are nested.
And in fact, when I read the description I was personally expecting the implementation to iterate rows first and then columns because that would match the typical storage order for a bitmap, but the implementation you proposed iterates columns first and then rows. Both of those are valid interpretations of "top left to bottom right", so the name TLBR (assuming that's what you intended that to be short for) could apply to either of them.
However, I also recall that more than once I've made typos like the following that were hard to spot when debugging, and possibly even missed completely if the test cases happened to be square:
for y := r.Min.Y; y < r.Max.Y; y++ {
for x := r.Min.X; x < r.Max.Y; x++ {
...so having the loop part factored out in one place would make that mistake less likely.
This is a tricky one! 🤔
I think uses of a function like this would hide the possibly-important detail of what order the points are being visited in.
That's why it's called TLBR not All or Points. If there's a more clear name to indicate it's row major order top to bottom (RowMajor?) please bikeshed it.
LeftToRightTopToBottom?
How about RowMajor, RowMajorReversed, ColumnMajor, ColumnMajorReversed?
but the implementation you proposed iterates columns first and then rows
Oh, that was a bug. 😅 Updating the comment. Another argument for why it should be a method, it's easy to do wrong!
Since "row-major order" is a relatively standard term (notable enough to have a Wikipedia article), and it's arguably the most common processing order for bitmaps due to it matching the typical storage layout of a bitmap, I think a name like RowMajorPoints with the implementation in your now-updated proposal would lessen my concern enough for me.
I don't think that representing every possible scanning order is worth it, though. For me, row-major order is justified by my intuition that it's the common default order used when there isn't any reason to prefer a different order, but I feel like other possible orders are easy enough to write in your own codebase as a very similar helper function if you need it.
If we establish that iter.Seq[image.Point] is the convention for describing scanning orders then it becomes possible to write a function that takes a iter.Seq[image.Point] as an argument and so can do some per-point work in any arbitrary scanning order. The standard library doesn't need to be the one to provide all of them, but row-major order seems like a reasonable single common case that the standard library could provide as the first example of the pattern.
Third-party libraries could then implement other scanning orders, including more esoteric ones like space-filling curves, independently of whatever work needs to be done in the loop body.
If there's only one method, RowMajorPoints is a good name, but there are multiple, PointsRowMajor, PointsReversed etc. make more sense so the autocomplete groups together.