/// <summary> /// Obtains a new image from this cache. /// </summary> /// <param name="data"></param> /// <returns></returns> public override INativeImage Obtain(byte[] data) { OsmSharp.Logging.Log.TraceEvent("NativeImageCache", Logging.TraceEventType.Information, "Bitmap obtain: {0} {1}", _unusedImages.Count, _usedImages.Count); lock (_usedImages) { // synchronize access to this cache. if (_unusedImages.Count > 0) { // there are unused images, first recover one from there. // get unused image and remove. var unusedImage = _unusedImages.First(); _unusedImages.Remove(unusedImage); var newNativeImage = global::Android.Graphics.BitmapFactory.DecodeByteArray(data, 0, data.Length, new global::Android.Graphics.BitmapFactory.Options() { InBitmap = (unusedImage as NativeImage).Image, InSampleSize = 1 }); // DecodeByteArray could return null: if we assign null to // NativeImage.Image, NativeImage.GetHashCode crashes on a // NullPointerException. So in this case, just roll back // and return null. User code should handle this. if (newNativeImage == null) { // Rollback _unusedImages.Add(unusedImage); return(null); } // change native image and add to used images. (unusedImage as NativeImage).Image = newNativeImage; _usedImages.Add(unusedImage); return(unusedImage); } else if (_unusedImages.Count + _usedImages.Count < _cacheSize) { // there is still space to add a used iamge. var newNativeImage = global::Android.Graphics.BitmapFactory.DecodeByteArray(data, 0, data.Length, new global::Android.Graphics.BitmapFactory.Options() { InSampleSize = 1, InMutable = true }); var usedImage = new NativeImage(newNativeImage); _usedImages.Add(usedImage); return(usedImage); } else { // there are no unused images left and there is no more room. throw new Exception("Cannot get a new image from cache, no image left."); } } }
/// <summary> /// Renders the current complete scene. /// </summary> private void Render() { try { if (_renderingSuspended) { // no rendering when rendering is suspended. return; } if (_cacheRenderer.IsRunning) { // cancel previous render. _cacheRenderer.CancelAndWait(); } // make sure only on thread at the same time is using the renderer. lock (_cacheRenderer) { this.Map.ViewChangedCancel(); // build the layers list. var layers = new List<Layer>(); for (int layerIdx = 0; layerIdx < this.Map.LayerCount; layerIdx++) { // get the layer. if (this.Map[layerIdx].IsVisible) { layers.Add(this.Map[layerIdx]); } } // add the internal layers. layers.Add(_makerLayer); if (this.SurfaceHeight == 0) { // the surface has no height yet. Impossible to render like this. return; } // get old image if available. NativeImage image = null; if (_offScreenBuffer != null) { // get the native image from the off-screen buffer. image = _offScreenBuffer.NativeImage as NativeImage; } // resize image if needed. float sizeX = this.SurfaceWidth; float sizeY = this.SurfaceHeight; //if(this.MapAllowTilt) //{ // when rotation is allowed make sure a square is rendered. // sizeX = System.Math.Max(this.SurfaceWidth, this.SurfaceHeight); // sizeY = System.Math.Max(this.SurfaceWidth, this.SurfaceHeight); //} // float size = System.Math.Max(this.SurfaceHeight, this.SurfaceWidth); if (image == null || image.Image.Width != (int)(sizeX * _extra) || image.Image.Height != (int)(sizeY * _extra)) { // create a bitmap and render there. if (image != null) { // make sure to dispose the old image. image.Dispose(); } image = new NativeImage(global::Android.Graphics.Bitmap.CreateBitmap((int)(sizeX * _extra), (int)(sizeY * _extra), global::Android.Graphics.Bitmap.Config.Argb8888)); } // create and reset the canvas. using (var canvas = new global::Android.Graphics.Canvas(image.Image)) { canvas.DrawColor(new global::Android.Graphics.Color( SimpleColor.FromKnownColor(KnownColor.White).Value)); // create the view. double[] sceneCenter = this.Map.Projection.ToPixel(this.MapCenter.Latitude, this.MapCenter.Longitude); float mapZoom = this.MapZoom; float sceneZoomFactor = (float)this.Map.Projection.ToZoomFactor(this.MapZoom); // create the view for this control. float scaledNormalWidth = image.Image.Width / _bufferFactor; float scaledNormalHeight = image.Image.Height / _bufferFactor; var view = View2D.CreateFrom((float)sceneCenter[0], (float)sceneCenter[1], scaledNormalWidth * _extra, scaledNormalHeight * _extra, sceneZoomFactor, _invertX, _invertY, this.MapTilt); long before = DateTime.Now.Ticks; OsmSharp.Logging.Log.TraceEvent("OsmSharp.Android.UI.MapView", TraceEventType.Information, "Rendering Start"); // notify the map that the view has changed. if (_previouslyChangedView == null || !_previouslyChangedView.Equals(view)) { // report change once! var normalView = View2D.CreateFrom((float)sceneCenter[0], (float)sceneCenter[1], scaledNormalWidth, scaledNormalHeight, sceneZoomFactor, _invertX, _invertY, this.MapTilt); this.Map.ViewChanged((float)this.Map.Projection.ToZoomFactor(this.MapZoom), this.MapCenter, normalView, view); _previouslyChangedView = view; long afterViewChanged = DateTime.Now.Ticks; OsmSharp.Logging.Log.TraceEvent("OsmSharp.Android.UI.MapView", TraceEventType.Information, "View change took: {0}ms @ zoom level {1}", (new TimeSpan(afterViewChanged - before).TotalMilliseconds), this.MapZoom); } // does the rendering. _cacheRenderer.Density = this.MapScaleFactor; bool complete = _cacheRenderer.Render(canvas, _map.Projection, layers, view, (float)this.Map.Projection.ToZoomFactor(this.MapZoom)); long afterRendering = DateTime.Now.Ticks; OsmSharp.Logging.Log.TraceEvent("OsmSharp.Android.UI.MapView", TraceEventType.Information, "Rendering took: {0}ms @ zoom level {1} and {2}", (new TimeSpan(afterRendering - before).TotalMilliseconds), this.MapZoom, this.MapCenter); if (complete) { // there was no cancellation, the rendering completely finished. // add the result to the scene cache. // add the newly rendered image again. if (_offScreenBuffer == null) { // create the offscreen buffer first. _offScreenBuffer = new ImageTilted2D(view.Rectangle, image, float.MinValue, float.MaxValue); } else { // augment the previous buffer. _offScreenBuffer.Bounds = view.Rectangle; _offScreenBuffer.NativeImage = image; } var temp = _onScreenBuffer; _onScreenBuffer = _offScreenBuffer; _offScreenBuffer = temp; } long after = DateTime.Now.Ticks; if (complete) { // report a successful render to listener. _listener.NotifyRenderSuccess(view, mapZoom, (int)new TimeSpan(after - before).TotalMilliseconds); } } } // notify the the current surface of the new rendering. this.PostInvalidate(); } catch (Exception ex) { // exceptions can be thrown when the mapview is disposed while rendering. // don't worry too much about these, the mapview is garbage anyway. OsmSharp.Logging.Log.TraceEvent("MapViewSurface", TraceEventType.Critical, string.Format("An unhandled exception occured:{0}", ex.ToString())); } }
/// <summary> /// Obtains a new image from this cache. /// </summary> /// <param name="data"></param> /// <returns></returns> public override INativeImage Obtain(byte[] data) { OsmSharp.Logging.Log.TraceEvent("NativeImageCache", Logging.TraceEventType.Information, "Bitmap obtain: {0} {1}", _unusedImages.Count, _usedImages.Count); lock (_usedImages) { // synchronize access to this cache. if (_unusedImages.Count > 0) { // there are unused images, first recover one from there. // get unused image and remove. var unusedImage = _unusedImages.First(); _unusedImages.Remove(unusedImage); var newNativeImage = global::Android.Graphics.BitmapFactory.DecodeByteArray(data, 0, data.Length, new global::Android.Graphics.BitmapFactory.Options() { InBitmap = (unusedImage as NativeImage).Image, InSampleSize = 1 }); // DecodeByteArray could return null: if we assign null to // NativeImage.Image, NativeImage.GetHashCode crashes on a // NullPointerException. So in this case, just roll back // and return null. User code should handle this. if (newNativeImage == null) { // Rollback _unusedImages.Add(unusedImage); return null; } // change native image and add to used images. (unusedImage as NativeImage).Image = newNativeImage; _usedImages.Add(unusedImage); return unusedImage; } else if (_unusedImages.Count + _usedImages.Count < _cacheSize) { // there is still space to add a used iamge. var newNativeImage = global::Android.Graphics.BitmapFactory.DecodeByteArray(data, 0, data.Length, new global::Android.Graphics.BitmapFactory.Options() { InSampleSize = 1, InMutable = true }); var usedImage = new NativeImage(newNativeImage); _usedImages.Add(usedImage); return usedImage; } else { // there are no unused images left and there is no more room. throw new Exception("Cannot get a new image from cache, no image left."); } } }