Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledTiledMapRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledTiledMapRenderer.java	(revision 19218)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledTiledMapRenderer.java	(revision 19220)
@@ -8,4 +8,5 @@
 import java.awt.Font;
 import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
 import java.awt.Image;
 import java.awt.Point;
@@ -13,5 +14,4 @@
 import java.awt.Transparency;
 import java.awt.event.MouseEvent;
-import java.awt.geom.AffineTransform;
 import java.awt.image.BufferedImage;
 import java.util.ArrayList;
@@ -22,7 +22,7 @@
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
@@ -52,4 +52,5 @@
     private int zoom;
     private Consumer<TileZXY> notifier;
+    private final ExecutorService worker;
 
     /**
@@ -65,4 +66,5 @@
     public StyledTiledMapRenderer(Graphics2D g, NavigatableComponent nc, boolean isInactiveMode) {
         super(g, nc, isInactiveMode);
+        this.worker = MainApplication.worker;
     }
 
@@ -74,9 +76,9 @@
             return;
         }
-        final Executor worker = MainApplication.worker;
+        final Executor worker = this.worker;
         final BufferedImage tempImage;
         final Graphics2D tempG2d;
         // I'd like to avoid two image copies, but there are some issues using the original g2d object
-        tempImage = nc.getGraphicsConfiguration().createCompatibleImage(this.nc.getWidth(), this.nc.getHeight(), Transparency.TRANSLUCENT);
+        tempImage = createCompatibleImage(nc, this.nc.getWidth(), this.nc.getHeight());
         tempG2d = tempImage.createGraphics();
         tempG2d.setComposite(AlphaComposite.DstAtop); // Avoid tile lines in large areas
@@ -94,5 +96,5 @@
             final Point min = this.nc.getPoint(box2.getMin());
             final Point max = this.nc.getPoint(box2.getMax());
-            tileSize = max.x - min.x + BUFFER_PIXELS;
+            tileSize = max.x - min.x;
         }
 
@@ -151,6 +153,7 @@
                     painted++;
                 }
-                // There seems to be an off-by-one error somewhere.
-                tempG2d.drawImage(tileImage, point.x + 1, point.y + 1, null, null);
+                // There seems to be an off-by-one error somewhere. Seems to be tied to sign of lat/lon
+                final int offset = (tile.lat() > 0 ? 1 : 0) + (tile.lon() >= 0 ? 1 : 0);
+                tempG2d.drawImage(tileImage, point.x + 1, point.y + offset, null, null);
             } else {
                 Logging.trace("StyledMapRenderer did not paint tile {1}", tile);
@@ -250,11 +253,8 @@
 
         temporaryView.zoomTo(bounds.getCenter().getEastNorth(ProjectionRegistry.getProjection()), mapState.getScale());
-        BufferedImage bufferedImage = Optional.ofNullable(nc.getGraphicsConfiguration())
-                .map(gc -> gc.createCompatibleImage(tileSize * xCount + xCount, tileSize * yCount + yCount, Transparency.TRANSLUCENT))
-                .orElseGet(() -> new BufferedImage(tileSize * xCount + xCount, tileSize * yCount + yCount, BufferedImage.TYPE_INT_ARGB));
+        BufferedImage bufferedImage = createCompatibleImage(nc, width, height);
         Graphics2D g2d = bufferedImage.createGraphics();
         try {
             g2d.setRenderingHints(Map.of(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
-            g2d.setTransform(AffineTransform.getTranslateInstance(-BUFFER_TILES * (double) tileSize, -BUFFER_TILES * (double) tileSize));
             final AbstractMapRenderer tilePainter = MapRendererFactory.getInstance().createActiveRenderer(g2d, temporaryView, false);
             tilePainter.render(data, true, bounds);
@@ -262,5 +262,15 @@
             g2d.dispose();
         }
-        return bufferedImage;
+        final int bufferPixels = BUFFER_TILES * tileSize;
+        return bufferedImage.getSubimage(bufferPixels, bufferPixels,
+                width - 2 * bufferPixels + BUFFER_PIXELS, height - 2 * bufferPixels + BUFFER_PIXELS);
+    }
+
+    private static BufferedImage createCompatibleImage(NavigatableComponent nc, int width, int height) {
+        final GraphicsConfiguration gc = nc.getGraphicsConfiguration();
+        if (gc == null) {
+            return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+        }
+        return gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
     }
 
@@ -319,9 +329,9 @@
                         for (TileLoader loader : tileCollection) {
                             final TileZXY txy = loader.tile;
-                            final int x = (txy.x() - minX) * (tileSize - BUFFER_PIXELS) + BUFFER_PIXELS / 2;
-                            final int y = (txy.y() - minY) * (tileSize - BUFFER_PIXELS) + BUFFER_PIXELS / 2;
-                            final int wh = tileSize - BUFFER_PIXELS / 2;
-
-                            final BufferedImage tileImage = tImage.getSubimage(x, y, wh, wh);
+                            final int x = (txy.x() - minX) * tileSize;
+                            final int y = (txy.y() - minY) * tileSize;
+                            final int wh = tileSize;
+
+                            final BufferedImage tileImage = tImage.getSubimage(x, y, wh + BUFFER_PIXELS, wh + BUFFER_PIXELS);
                             loader.cacheTile(tileImage);
                         }
Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/TileZXY.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/TileZXY.java	(revision 19218)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/TileZXY.java	(revision 19220)
@@ -9,5 +9,6 @@
 
 /**
- * A record used for storing tile information for painting
+ * A record used for storing tile information for painting.
+ * The origin is upper-left, not lower-left (so more like Google tile coordinates than TMS tile coordinates).
  * @since 19176
  */
@@ -144,7 +145,8 @@
      */
     public static TileZXY latLonToTile(double lat, double lon, int zoom) {
-        int xCoord = (int) Math.floor(Math.pow(2, zoom) * (180 + lon) / 360);
-        int yCoord = (int) Math.floor(Math.pow(2, zoom) *
-                (1 - Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI) / 2);
+        final double zoom2 = Math.pow(2, zoom);
+        final double latLog = Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat)));
+        final int xCoord = (int) Math.floor(zoom2 * (180 + lon) / 360);
+        final int yCoord = (int) Math.floor(zoom2 * (1 - latLog / Math.PI) / 2);
         return new TileZXY(zoom, xCoord, yCoord);
     }
