Code Monkey home page Code Monkey logo

Comments (8)

shawnhar avatar shawnhar commented on August 16, 2024

Thanks for the report Bill. We see the same thing. Actually a little different depending on whether antialiasing is enabled or not, but with AA turned off it looks exactly how you describe, which is definitely not right! Will investigate...

from win2d.

billhenn avatar billhenn commented on August 16, 2024

I had only tried it with AA turned off. I'm glad you can repro it.

from win2d.

clandrew avatar clandrew commented on August 16, 2024

I took a look at this. This isn't a behavior of Win2D, but of Direct2D. Direct2D uses a different strategy for anti-aliasing lines versus fills.

Below are images for anti-aliased and aliased, respectively. I don't repro DrawRectangle emiting a 7-pixels-wide border, this one is 8 pixels. Let me know if this is different from what you see.

drawrectzoomed

For lines such as DrawRectangle, drawing at integral pixel boundaries such as ltwh={1, 1, 6, 6}, with stroke width 1 will emit something that straddles the pixels. If you want them to be pixel-aligned exactly, use the rect ltwh={1.5, 1.5, 5, 5}. And perhaps this is the behavior you want. Image of this below.

drawrect_ltwh_1pt5_1p5_5_5_zoomed

Of course, another way to always have a sharp-looking DrawRectangle at integral pixel boundries is to have an even-numbered stroke width.

Filled rectangles use a different scheme where integral edges will look sharp, and non-pixel-aligned edges will have a visible fringe.

from win2d.

billhenn avatar billhenn commented on August 16, 2024

Ok that makes sense. Thanks for the explanation.

from win2d.

johndog avatar johndog commented on August 16, 2024

I'm having a hard time making sense of any of this. Why are we satisfied with an API that "straddles the pixels" we give it? I'm trying to perform pixel-perfect drawing. If I tell Win2D to use "pixel" as the unit, and turn off anti-aliasing, the idea of using sub-pixels itself makes no sense.

Furthermore, @clandrew posted three pictures: in the first one (anti-aliased), the box is 8 pixels wide. The second picture (aliased) is 7 pixels wide, as @BillHenning reported. Yet @clandrew wrote that he couldn't repro the 7 pixel-wide issue, when it seems to me that his own picture shows that he did.

If this behavior is by design, can we get the documentation on DrawRectangle updated so that it's possible for someone to reason about how it works (and why?)

Are they all off-by-one, or is it just DrawRectangle?

from win2d.

clandrew avatar clandrew commented on August 16, 2024

The 7-pixels comment was in regards to the anti-aliased rectangle- I think perhaps I hadn't seen the comment about AA having been turned off, so I had assumed they were using anti-aliased. (The page is supposed to refresh itself, but it doesn't always do it...) The general idea is that the behavior I reproed was all by design.

While I couldn't repro a bug in this area, I can explain the behavior more.

An API that "straddles the pixels" is how Direct2D (and therefore Win2D) works, and it's more intuitive when you remember that this API is built on top of Direct3D and is for drawing more than aliased, axis-aligned retangles.

When stroking geometry, the coordinate you give will represent the center of your line geometry. Coordinates represent the dividing lines between pixels, not the centers of pixels. Geometry is extruded outward from the coordinates that you pass in. And the amount by which it gets extruded depends on your stroke width.

It works this way for all geometry, including rectangles. I think you're asking for a different coordinate system- one where numbers represent the centers of pixels, not the boundaries between pixels. Problem is, where would this coordinate system apply? Just for aliased rectangles? How in the world are you supposed to make sense of having two coordinate systems? Or if we were to standardize on a coordinate system of centers-of-pixels, it'd be extra confusing since so many of the graphics technologies Win2D is designed to work well with- e.g., Direct3D- are not standardized this way.

One thing we could do, is have a dedicated API in Win2D for drawing aliased, pixel-aligned rectangles and Win2D would nudge the geometry (I guess, 0.5 right, and 0.5 down) for you, putting it effectively in a different coordinate system, while being careful in getting it to play nicely with transforms.

We could update the documentation on DrawRectangle, although this idea doesn't just pertain to rectangles- lines, circles, and general freeform geometry all uses this same coordinate system. If we do add some documentation for this, we will definitely want to make sure it's clear how Direct2D's coordinate system works irrespective of what's being drawn.

from win2d.

johndog avatar johndog commented on August 16, 2024

Okay, now it makes sense, in that I can understand it well enough to explain it myself. (Agreeing is a different matter)

In Direct2d:

  • A whole-number pixel coordinate addresses a null-space position at the upper-left corner of a space-occupying pixel, such that the position is neighboring 4 different pixels at that corner
  • A geometry draw centers a stroke on a position addressed by a coordinate
  • Therefore, a stroke that occupies an area of one square pixel that is addressed to a whole-number coordinate is effectively straddling the corners of 4 different pixels.
  • Furthermore, if Anti-Aliasing is turned off, the only one of these 4 pixels that gets lit up for Draw functions is the upper-left pixel; which ironically is not the same pixel that was addressed, presenting an apparent off-by-one situation.
  • Likewise, for Fill functions, the only one of these 4 pixels that gets lit up is the lower-right pixel. Depending on the coordinates specified, this can easily result in unintentionally skipping pixels between a Draw and a Fill.
  • If the goal is to center the stroke on a center of a pixel, an offset of .5 can be used, since that moves the address from the corner of the pixel to the center.
  • If the goal is to top-left justify the whole width of the stroke on a particular pixel, rather than centering it, the offset of strokewidth/2 can be used instead. This is actually just the general case of the special case of strokewidth=1 above.

Interestingly, if we had the coordinate system I expected, the formula for top-left justifying the edge of a stroke would be to use an offset of ((strokeWidth-1)/2), which is not quite as elegant as the simple strokeWidth/2 we have now. That said, I'm not sure how common a scenario it is, and it's still somewhat intuitive.

It may be possible to handle this at Win2D level with coordinate system changes. Perhaps if I set the unit to Pixels, that's when you can make it pixel-centric, instead of pixel-edgy. Or maybe there could just be another option in the draw session. I spent quite a bit of time trying to figure out how to make the system draw a one-pixel line; drawing on a half-pixel never occurred to me as a valid approach, it comes off as a hack to the uninformed. An explicit option to shift the coordinate system would have been fairly discoverable.

It may also be possible to address this with types. For example, I created a PixelPerfectPoint class, which implicitly converts to Point and/or Vector2, to help me consistently manage the conversion from pixel-centric to the system used by Direct2D. Perhaps there is opportunity for strongly-typed overloads natively in Win2D to make it easier to find the right behaviors, without hiding the behaviors that are native to Direct2D.

I agree that it doesn't make sense to document this just for DrawRectangle, it's fundamental to the coordinate system, so having some docs at the general level would be helpful. The docs should at least make it clear that setting the units to Pixels and turning off AA is not necessary to address pixels precisely, and it may actually make the result more confusing if the user doesn't already understand that coordinates aren't centered on pixels.

from win2d.

clandrew avatar clandrew commented on August 16, 2024

That all looks right to me!

If you need to ensure an aliased, 1-pixel-wide stroke is centered on a particular pixel, the best way to do that is to center the geometry on a half-pixel coordinate (like 0.5, 1.5, etc). Reason being, geometry which truly occupies 50% of a pixel- in aliased rendering- will be either shaded or not-shaded depending on your particular graphics hardware and what portion of the pixel it is (the bottom? or the right? etc). You can test things out and make some observations based on what you see, but there's a much better guarantee in behavior if you just ensure your geometry is right on top of the pixels you want shaded.

That's an interesting point about Pixels unit mode. The intent behind CanvasUnitMode is that it's meant to control scaling, not translation transforms in the coordinate space, but if we do something here I could see making it a separate option.

In the meanwhile, for your own convenience, I could definitely see putting a wrapper around this change in coordinate system so you don't even have to think about it. I.e., the PixelPerfectPoint class could just set a {0.5, 0.5} translation transform or do something equivalent, depending on the stroke width you're trying to use. For odd stroke widths, you'd want center your line on the half-pixel boundary. For even stroke widths, you'd center the line on the whole pixel boundary.

For now, I'll open an item to add some documentation. I couldn't find any Direct2D documentation that would exactly fit the bill here, so it makes sense for us to add something. In particular we can describe how the coordinate space works and how it interacts with AA.

from win2d.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.