Ticket #14787: fix14787.patch

File fix14787.patch, 6.9 KB (added by bastiK, 7 years ago)
  • src/org/openstreetmap/josm/gui/NavigatableComponent.java

     
    169169     * The current state (scale, center, ...) of this map view.
    170170     */
    171171    private transient MapViewState state;
     172    /**
     173     * A second center value that is carried along in addition to <code>state.getCenter()</code>.
     174     * It is the center before alignment to pixel grid. The alignment is normally
     175     * very small (0.5 pixel), but in certain workflows it can become noticeable (see #14787).
     176     * To amend this, zoom changes, like zooming in or out are based on the unaligned <code>centerCarry</code>.
     177     */
     178    private EastNorth centerCarry;
    172179
    173180    /**
    174181     * Main uses weak link to store this, so we need to keep a reference.
     
    181188    public NavigatableComponent() {
    182189        setLayout(null);
    183190        state = MapViewState.createDefaultState(getWidth(), getHeight());
     191        centerCarry = state.getCenter().getEastNorth();
    184192        Main.addProjectionChangeListener(projectionChangeListener);
    185193    }
    186194
     
    301309     * Zoom in current view. Use configured zoom step and scaling settings.
    302310     */
    303311    public void zoomIn() {
    304         zoomTo(state.getCenter().getEastNorth(), scaleZoomIn());
     312        zoomTo(centerCarry, scaleZoomIn());
    305313    }
    306314
    307315    /**
     
    308316     * Zoom out current view. Use configured zoom step and scaling settings.
    309317     */
    310318    public void zoomOut() {
    311         zoomTo(state.getCenter().getEastNorth(), scaleZoomOut());
     319        zoomTo(centerCarry, scaleZoomOut());
    312320    }
    313321
    314322    protected void updateLocationState() {
     
    623631        // snap scale to imagery if needed
    624632        newScale = scaleRound(newScale);
    625633
    626         // Align to the pixel grid:
    627         // This is a sub-pixel correction to ensure consistent drawing at a certain scale.
    628         // For example take 2 nodes, that have a distance of exactly 2.6 pixels.
    629         // Depending on the offset, the distance in rounded or truncated integer
    630         // pixels will be 2 or 3. It is preferable to have a consistent distance
    631         // and not switch back and forth as the viewport moves. This can be achieved by
    632         // locking an arbitrary point to integer pixel coordinates. (Here the EastNorth
    633         // origin is used as reference point.)
    634         // Note that the normal right mouse button drag moves the map by integer pixel
    635         // values, so it is not an issue in this case. It only shows when zooming
    636         // in & back out, etc.
    637         MapViewState mvs = getState().usingScale(newScale);
    638         mvs = mvs.movedTo(mvs.getCenter(), newCenter);
     634        EastNorth centerAligned = alignToPixelGrid(newCenter, newScale);
     635
     636        if (!centerAligned.equals(getCenter()) || !Utils.equalsEpsilon(getScale(), newScale)) {
     637            if (!initial) {
     638                pushZoomUndo(centerCarry, getScale());
     639            }
     640            zoomNoUndoTo(newCenter, centerAligned, newScale, initial);
     641        }
     642    }
     643
     644    // Align to the pixel grid:
     645    // This is a sub-pixel correction to ensure consistent drawing at a certain scale.
     646    // For example take 2 nodes, that have a distance of exactly 2.6 pixels.
     647    // Depending on the offset, the distance in rounded or truncated integer
     648    // pixels will be 2 or 3. It is preferable to have a consistent distance
     649    // and not switch back and forth as the viewport moves. This can be achieved by
     650    // locking an arbitrary point to integer pixel coordinates. (Here the EastNorth
     651    // origin is used as reference point.)
     652    // Note that the normal right mouse button drag moves the map by integer pixel
     653    // values, so it is not an issue in this case. It only shows when zooming
     654    // in & back out, etc.
     655    private EastNorth alignToPixelGrid(EastNorth center, double scale) {
     656        MapViewState mvs = getState().usingScale(scale);
     657        mvs = mvs.movedTo(mvs.getCenter(), center);
    639658        Point2D enOrigin = mvs.getPointFor(new EastNorth(0, 0)).getInView();
    640659        // as a result of the alignment, it is common to round "half integer" values
    641660        // like 1.49999, which is numerically unstable; add small epsilon to resolve this
     
    644663                Math.round(enOrigin.getX()) + epsilon,
    645664                Math.round(enOrigin.getY()) + epsilon);
    646665        EastNorth enShift = mvs.getForView(enOriginAligned.getX(), enOriginAligned.getY()).getEastNorth();
    647         newCenter = newCenter.subtract(enShift);
    648 
    649         if (!newCenter.equals(getCenter()) || !Utils.equalsEpsilon(getScale(), newScale)) {
    650             if (!initial) {
    651                 pushZoomUndo(getCenter(), getScale());
    652             }
    653             zoomNoUndoTo(newCenter, newScale, initial);
    654         }
     666        return center.subtract(enShift);
    655667    }
    656668
    657669    /**
     
    661673     * @param newScale The scale to use.
    662674     * @param initial true if this call initializes the viewport.
    663675     */
    664     private void zoomNoUndoTo(EastNorth newCenter, double newScale, boolean initial) {
     676    private void zoomNoUndoTo(EastNorth newCenterCarry, EastNorth newCenterAligned, double newScale, boolean initial) {
    665677        if (!Utils.equalsEpsilon(getScale(), newScale)) {
    666678            state = state.usingScale(newScale);
    667679        }
    668         if (!newCenter.equals(getCenter())) {
    669             state = state.movedTo(state.getCenter(), newCenter);
     680        if (!newCenterAligned.equals(getCenter())) {
     681            state = state.movedTo(state.getCenter(), newCenterAligned);
    670682        }
     683        this.centerCarry = newCenterCarry;
    671684        if (!initial) {
    672685            repaint();
    673686            fireZoomChanged();
     
    850863    public void zoomPrevious() {
    851864        if (!zoomUndoBuffer.isEmpty()) {
    852865            ZoomData zoom = zoomUndoBuffer.pop();
    853             zoomRedoBuffer.push(new ZoomData(getCenter(), getScale()));
    854             zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale(), false);
     866            zoomRedoBuffer.push(new ZoomData(centerCarry, getScale()));
     867            EastNorth centerAligned = alignToPixelGrid(zoom.getCenterEastNorth(), zoom.getScale());
     868            zoomNoUndoTo(zoom.getCenterEastNorth(), centerAligned, zoom.getScale(), false);
    855869        }
    856870    }
    857871
     
    861875    public void zoomNext() {
    862876        if (!zoomRedoBuffer.isEmpty()) {
    863877            ZoomData zoom = zoomRedoBuffer.pop();
    864             zoomUndoBuffer.push(new ZoomData(getCenter(), getScale()));
    865             zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale(), false);
     878            zoomUndoBuffer.push(new ZoomData(centerCarry, getScale()));
     879            EastNorth centerAligned = alignToPixelGrid(zoom.getCenterEastNorth(), zoom.getScale());
     880            zoomNoUndoTo(zoom.getCenterEastNorth(), centerAligned, zoom.getScale(), false);
    866881        }
    867882    }
    868883