Details
-
Bug
-
Resolution: Fixed
-
P2: Important
-
6.x
-
None
Description
Clipping is inherently a discrete (aliased) operation. So fractional clip coordinates - e.g. the result of fractional scaling - must be rounded to integers. There are three problems in the current code causing glitches:
- The various clip commands - setClipRect(QRect), setClipRect(QRectF), setClipRegion(QRect) etc. - do not use consistent rounding. Some use QRectF::toRect() others QRectF::toAlignedRect().
- Clip rects are a way of subdividing an area into discrete subareas. So when two cliprects are given as edge-to-edge, they should keep that property also under scaling. There should be neither gaps or overlaps between the scaled versions of such cliprects. The right edge of the left rectangle should always map to the pixel next to the left edge of the right rectangle. But neither of the two roundings above guarantee that property.
- A second-order issue is that QTransform(QRectF) can hit a numerical accuracy limit around the .5 point. Since QRectF internally stores (x,y,w,h), not (x1,y1,x2,y2), the mapping of the left/top edges is a slighly different operation than the mapping of the right/bottom edges. So when both should be X.5 (to be rounded up), it can happen that one of them is slightly less, so being rounded down. The result is a gap.
Issue 2 above is particularly critical for QRegions with more than one rect, since QRegion is implemented as a collection of edge-to-edge rects. Ref. QTBUG-95957.
The attached clipdemo app demonstrates the various issues. The area is divided into 4 edge-to-edge cliprects, which can be scaled by resizing the window. Gaps then appear in lighter color, overlaps in darker.
About the toAlignedRect() rounding: This appeared relatively recently, as a result of the discussion in QTBUG-83229. toAlignedRect() gives a surrounding QRect, i.e. it always rounds "outwards". However, the argument for that seems to rest on a strange premise: namely that any pixel partially covered by the floating-point cliprect should be included in the actual cliprect. I.e. so that even just a 1% covered pixel should be included. That seems like unexpected behavior. Instead, the natural breaking point is at 50% coverage, like fillRect() does. The remaining question is then, what ahppens at exactly 50% coverage? (something which often happens, as it corresponds to an X.5 corner coordinate). Again the suggested answer is to do the same as fillRect(): we use Qt's default policy of snapping rightwards and downwards. So 50% pixels along top and left edges should be excluded, while those on the right and bottom edges should be included.
The obvious way to achieve the edge-to-edge property (#2 above) is to use a rounding that always rounds the corner coordinates, and ignores the height/width. (That corresponds to item 2 in the history of QRectF::toRect() here: QTBUG-96223). It is also what toNormalizedFillRect() does, which is what QPainter::fillRect() uses. However, to also solve issue #3 above, not only the rounding, but even the QTransform mapping should be done on the corner coordinates, not rect size.
Attachments
Issue Links
- is duplicated by
-
QTBUG-114672 Bad clip region when using 175% High DPI scaling
- Closed