/******************************************************************************* * Copyright (c) MOBAC developers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package mobac.program.atlascreators; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Insets; import java.awt.Point; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JComponent; import javax.swing.JPanel; import mobac.exceptions.MapCreationException; import mobac.gui.mapview.ScaleBar; import mobac.gui.mapview.WgsGrid; import mobac.mapsources.mapspace.MercatorPower2MapSpace; import mobac.program.interfaces.LayerInterface; import mobac.program.interfaces.MapSource; import mobac.program.interfaces.MapSpace; import mobac.program.interfaces.MapSpace.ProjectionCategory; import mobac.program.model.Settings; import mobac.program.model.SettingsPaperAtlas; import mobac.program.model.SettingsWgsGrid; import mobac.program.model.UnitSystem; import mobac.utilities.Utilities; public abstract class PaperAtlas extends AtlasCreator { private static final Font PAGE_NUMBER_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 24), LABEL_FONT = new Font( Font.SANS_SERIF, Font.BOLD, 12); private static final Color LABEL_BACKGROUND = new Color(0, 127, 0), LABEL_FOREGROUND = Color.WHITE, IMAGE_BACKGROUND = new Color(0, 0, 0, 0); private static final String UP = " \u2191", DOWN = " \u2193"; protected static class Page { protected final BufferedImage image; protected final int number, width, height; protected Page(int number, BufferedImage image) { this.number = number; this.image = image; this.width = image.getWidth(); this.height = image.getHeight(); } } private final JComponent dummy = new JPanel(); private final WgsGrid wgsGrid; private final int overlap; private final Insets insets = new Insets(0, 0, 0, 0); protected final SettingsPaperAtlas s; private int tileImageScale = 1; private File layerFolder; // base: page Size整数倍区域, // bottom:最底下的不能被page Height整除的区域 // right:最右侧的不能被page Width整除的区域 // corner:右下角区域,不能被page Size整除的部分 private Dimension base, bottom, right, corner; protected PaperAtlas(final boolean usePadding) { s = Settings.getInstance().paperAtlas.clone(); overlap = (int) UnitSystem.pointsToPixels(s.overlap, s.dpi); if (s.wgsEnabled) { SettingsWgsGrid sWgsGrid = Settings.getInstance().wgsGrid.clone(); sWgsGrid.enabled = s.wgsEnabled; sWgsGrid.density = s.wgsDensity; wgsGrid = new WgsGrid(sWgsGrid, dummy); } else { wgsGrid = null; } if (usePadding) { insets.top = (int) UnitSystem.pointsToPixels(s.marginTop, s.dpi); insets.left = (int) UnitSystem.pointsToPixels(s.marginLeft, s.dpi); insets.bottom = (int) UnitSystem.pointsToPixels(s.marginBottom, s.dpi); insets.right = (int) UnitSystem.pointsToPixels(s.marginTop, s.dpi); } if (s.paperSize != null) { double width = s.paperSize.width - s.marginLeft - s.marginRight; double height = s.paperSize.height - s.marginTop - s.marginBottom; width = UnitSystem.pointsToPixels(width, s.dpi); height = UnitSystem.pointsToPixels(height, s.dpi); base = new Dimension((int) width, (int) height); } } @Override public void createMap() throws MapCreationException, InterruptedException { corner = right = bottom = null; tileImageScale = getTileImageScale(); int mapWidth = (xMax - xMin + 1) * tileSize * tileImageScale; int mapHeight = (yMax - yMin + 1) * tileSize * tileImageScale; if (base == null) { base = new Dimension(mapWidth, mapHeight); processPages(1, 1); base = null; } else { // 最后一页少于页宽度的crop百分百的,会裁剪掉,即抛弃。 int sWidth = (mapWidth) % (base.width - overlap); if ((double) sWidth / base.getWidth() * 100d < s.crop) { sWidth = 0; } int sHeight = (mapHeight) % (base.height - overlap); if ((double) sHeight / base.getHeight() * 100d < s.crop) { sHeight = 0; } if (sHeight > 0 && sWidth > 0) { corner = new Dimension(sWidth, sHeight); } if (sWidth > 0) { right = new Dimension(sWidth, base.height); } if (sHeight > 0) { bottom = new Dimension(base.width, sHeight); } int rows = (mapHeight) / (base.height - overlap) + (bottom != null ? 1 : 0); int cols = (mapWidth) / (base.width - overlap) + (right != null ? 1 : 0); processPages(rows, cols); } } protected File getLayerFolder() { return layerFolder; } public void initLayerCreation(LayerInterface layer) throws IOException { super.initLayerCreation(layer); layerFolder = new File(atlasDir, layer.getName()); Utilities.mkDirs(layerFolder); } /** * check first existing image and return its scale. 2 means for retina( 512x512), 1 means normal * * @return */ protected int getTileImageScale() { int tileImageScale = 1; if (mapDlTileProvider == null) { return tileImageScale; } // init tileScale for check if there has retina tile try { // use loop because xMin, xMax may has no data for (int x = xMin; x <= xMax; x++) { for (int y = yMin; y <= yMax; y++) { BufferedImage tile = mapDlTileProvider.getTileImage(x, y); if (tile != null) { tileImageScale = tile.getWidth() / tileSize; break; // skip when get } } } // for invalid tileScale if (tileImageScale != 2) { log.debug("invalid tile Scale for Paper Atlas " + tileImageScale); tileImageScale = 1; } } catch (IOException e) { tileImageScale = 1; } return tileImageScale; } protected abstract void processPage(BufferedImage image, int pageNumber) throws MapCreationException; private void processPages(final int ROWS, final int COLS) throws MapCreationException, InterruptedException { try { atlasProgress.initMapCreation(ROWS * COLS * 2); for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { log.trace(String.format("cal=%d row=%d", col, row)); // Choose image Dimension size; boolean firstRow = row == 0; boolean lastRow = row + 1 == ROWS; boolean lastCol = col + 1 == COLS; if (corner != null && lastRow && lastCol) { size = new Dimension(corner); } else if (bottom != null && lastRow) { size = new Dimension(bottom); } else if (right != null && lastCol) { size = new Dimension(right); } else { size = new Dimension(base); } // Compute values int pageXMin = col * base.width - col * overlap; int pageYMin = row * base.height - row * overlap; int firstTileX = pageXMin / (tileSize * tileImageScale) + xMin; int firstTileY = pageYMin / (tileSize * tileImageScale) + yMin; int firstTileXOffset = pageXMin % (tileSize * tileImageScale); int firstTileYOffset = pageYMin % (tileSize * tileImageScale); int tilesInCol = (size.height + firstTileYOffset - 1) / (tileSize * tileImageScale) + 1; int tilesInRow = (size.width + firstTileXOffset - 1) / (tileSize * tileImageScale) + 1; // Create image and graphics int imageWidth = size.width + insets.left + insets.right; int imageHeight = size.height + insets.top + insets.bottom; BufferedImage image = Utilities.safeCreateBufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_4BYTE_ABGR); Graphics2D g = image.createGraphics(); g.translate(insets.left, insets.top); g.clipRect(0, 0, size.width, size.height); // Paint image g.setBackground(IMAGE_BACKGROUND); g.clearRect(0, 0, size.width, size.height); g.translate(-firstTileXOffset, -firstTileYOffset); for (int tileRow = 0; tileRow < tilesInCol; tileRow++) { for (int tileCol = 0; tileCol < tilesInRow; tileCol++) { int tileX = firstTileX + tileCol; int tileY = firstTileY + tileRow; int x = tileCol * tileSize * tileImageScale; int y = tileRow * tileSize * tileImageScale; BufferedImage tile = mapDlTileProvider.getTileImage(tileX, tileY); if (tile != null) g.drawImage(tile, x, y, tileSize * tileImageScale, tileSize * tileImageScale, null); } } g.translate(firstTileXOffset, firstTileYOffset); // Paint additions dummy.setSize(size); Point tlc = new Point(firstTileX * tileSize * tileImageScale + firstTileXOffset, firstTileY * tileSize * tileImageScale + firstTileYOffset); if (s.wgsEnabled) wgsGrid.paintWgsGrid(g, mapSource.getMapSpace(), tlc, zoom); if (s.scaleBar) ScaleBar.paintScaleBar(dummy, g, mapSource.getMapSpace(), tlc, zoom); if (s.compass) { Image compassRaw = ImageIO.read(Utilities.loadResourceAsStream("images/compass.png")); Image compass = compassRaw.getScaledInstance(150, 150, Image.SCALE_SMOOTH); g.drawImage(compass, 0, 0, null); } if (s.pageNumbers) { g.setBackground(LABEL_BACKGROUND); g.setColor(LABEL_FOREGROUND); String pageNumber = " " + getPageNumber(row, col, ROWS, COLS) + " "; g.setFont(PAGE_NUMBER_FONT); FontMetrics fontMetrics = g.getFontMetrics(); int fontHeight = fontMetrics.getHeight(); int pageNumberStringWidth = fontMetrics.stringWidth(pageNumber); g.clearRect(0, 0, pageNumberStringWidth, fontHeight); g.drawString(pageNumber, 0, fontHeight - fontMetrics.getDescent()); int centerX = size.width / 2; g.setFont(LABEL_FONT); fontMetrics = g.getFontMetrics(); fontHeight = fontMetrics.getHeight(); if (!firstRow) { String text = UP + getPageNumber(row - 1, col, ROWS, COLS) + " "; int stringWidth = fontMetrics.stringWidth(text); g.clearRect(centerX - stringWidth / 2, 8, stringWidth, fontHeight); g.drawString(text, centerX - stringWidth / 2, 8 + fontHeight - fontMetrics.getDescent()); } if (!lastRow) { String text = DOWN + getPageNumber(row + 1, col, ROWS, COLS) + " "; int stringWidth = fontMetrics.stringWidth(text); g.clearRect(centerX - stringWidth / 2, size.height - 32 - fontHeight, stringWidth, fontHeight); g.drawString(text, centerX - stringWidth / 2, size.height - 32 - fontMetrics.getDescent()); } } g.dispose(); // Process image atlasProgress.incMapCreationProgress(); checkUserAbort(); processPage(image, getPageNumber(row, col, ROWS, COLS)); atlasProgress.incMapCreationProgress(); checkUserAbort(); } } } catch (IOException e) { throw new MapCreationException(map, e); } } private int getPageNumber(int row, int col, int ROWS, int COLS) { return row * COLS + col + 1; } @Override public boolean testMapSource(MapSource mapSource) { MapSpace mapSpace = mapSource.getMapSpace(); ProjectionCategory projection = mapSpace.getProjectionCategory(); return (mapSpace instanceof MercatorPower2MapSpace) && (ProjectionCategory.SPHERE.equals(projection) || ProjectionCategory.ELLIPSOID.equals(projection)); } }