/* * Copyright 2010, 2011, 2012 mapsforge.org * * This program is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation, either version 3 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package org.mapsforge.android.maps; import java.io.OutputStream; import org.mapsforge.core.model.GeoPoint; import org.mapsforge.core.model.MapPosition; import org.mapsforge.core.model.Tile; import org.mapsforge.core.util.MercatorProjection; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; /** * A FrameBuffer uses two separate memory buffers to display the current and build up the next frame. */ public class FrameBuffer { static final int MAP_VIEW_BACKGROUND = Color.rgb(238, 238, 238); private int height; private final MapView mapView; private Bitmap mapViewBitmap1; private Bitmap mapViewBitmap2; private Canvas mapViewCanvas; private final Matrix matrix; private int width; FrameBuffer(MapView mapView) { this.mapView = mapView; this.mapViewCanvas = new Canvas(); this.matrix = new Matrix(); } /** * Draws a tile bitmap at the right position on the MapView bitmap. * * @param tile * the corresponding tile for the bitmap. * @param bitmap * the bitmap to be drawn. * @return true if the tile is visible and the bitmap was drawn, false otherwise. */ public boolean drawBitmap(Tile tile, Bitmap bitmap) { MapPosition mapPosition = this.mapView.getMapViewPosition().getMapPosition(); synchronized (this) { if (tile.zoomLevel != mapPosition.zoomLevel) { // the tile doesn't fit to the current zoom level return false; } else if (this.mapView.isZoomAnimatorRunning()) { // do not disturb the ongoing animation return false; } GeoPoint geoPoint = mapPosition.geoPoint; double pixelLeft = MercatorProjection.longitudeToPixelX(geoPoint.longitude, mapPosition.zoomLevel); double pixelTop = MercatorProjection.latitudeToPixelY(geoPoint.latitude, mapPosition.zoomLevel); pixelLeft -= this.width >> 1; pixelTop -= this.height >> 1; if (pixelLeft - tile.getPixelX() > Tile.TILE_SIZE || pixelLeft + this.width < tile.getPixelX()) { // no horizontal intersection return false; } else if (pixelTop - tile.getPixelY() > Tile.TILE_SIZE || pixelTop + this.height < tile.getPixelY()) { // no vertical intersection return false; } if (!this.matrix.isIdentity()) { // change the current MapView bitmap this.mapViewBitmap2.eraseColor(MAP_VIEW_BACKGROUND); this.mapViewCanvas.setBitmap(this.mapViewBitmap2); // draw the previous MapView bitmap on the current MapView bitmap this.mapViewCanvas.drawBitmap(this.mapViewBitmap1, this.matrix, null); this.matrix.reset(); // swap the two MapView bitmaps Bitmap mapViewBitmapSwap = this.mapViewBitmap1; this.mapViewBitmap1 = this.mapViewBitmap2; this.mapViewBitmap2 = mapViewBitmapSwap; } // draw the tile bitmap at the correct position float left = (float) (tile.getPixelX() - pixelLeft); float top = (float) (tile.getPixelY() - pixelTop); this.mapViewCanvas.drawBitmap(bitmap, left, top, null); return true; } } /** * Scales the matrix of the MapView and all its overlays. * * @param scaleX * the horizontal scale. * @param scaleY * the vertical scale. * @param pivotX * the horizontal pivot point. * @param pivotY * the vertical pivot point. */ public synchronized void matrixPostScale(float scaleX, float scaleY, float pivotX, float pivotY) { this.matrix.postScale(scaleX, scaleY, pivotX, pivotY); this.mapView.getOverlayController().postScale(scaleX, scaleY, pivotX, pivotY); } /** * Translates the matrix of the MapView and all its overlays. * * @param translateX * the horizontal translation. * @param translateY * the vertical translation. */ public synchronized void matrixPostTranslate(float translateX, float translateY) { this.matrix.postTranslate(translateX, translateY); this.mapView.getOverlayController().postTranslate(translateX, translateY); } synchronized void clear() { if (this.mapViewBitmap1 != null) { this.mapViewBitmap1.eraseColor(MAP_VIEW_BACKGROUND); } if (this.mapViewBitmap2 != null) { this.mapViewBitmap2.eraseColor(MAP_VIEW_BACKGROUND); } } synchronized boolean compress(CompressFormat compressFormat, int quality, OutputStream outputStream) { if (this.mapViewBitmap1 == null) { return false; } return this.mapViewBitmap1.compress(compressFormat, quality, outputStream); } synchronized void destroy() { if (this.mapViewBitmap1 != null) { this.mapViewBitmap1.recycle(); } if (this.mapViewBitmap2 != null) { this.mapViewBitmap2.recycle(); } // nulling out the canvas is essential to getting the bitmaps // recycled this.mapViewCanvas = null; } synchronized void draw(Canvas canvas) { if (this.mapViewBitmap1 != null) { canvas.drawBitmap(this.mapViewBitmap1, this.matrix, null); } } synchronized void onSizeChanged() { this.destroy(); this.mapViewCanvas = new Canvas(); this.width = this.mapView.getWidth(); this.height = this.mapView.getHeight(); this.mapViewBitmap1 = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.RGB_565); this.mapViewBitmap2 = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.RGB_565); clear(); this.mapViewCanvas.setBitmap(this.mapViewBitmap1); } }