private void AddBitmapRequestToList(BasicRectangle boundsBitmap, BasicRectangle boundsWaveForm, float zoom, WaveFormDisplayType displayType) { // Make sure we don't slow down GetTile() by creating a task and running LINQ queries on another thread Task.Factory.StartNew(() => { var request = new WaveFormBitmapRequest() { DisplayType = displayType, BoundsBitmap = boundsBitmap, BoundsWaveForm = boundsWaveForm, Zoom = zoom }; // Check if a tile already exists WaveFormTile existingTile = null; lock (_lockerTiles) { existingTile = _tiles.FirstOrDefault(obj => obj.ContentOffset.X == boundsBitmap.X && obj.Zoom == zoom); } lock (_lockerRequests) { // Check if bitmap has already been requested in queue var existingRequest = _requests.FirstOrDefault(obj => obj.BoundsBitmap.Equals(request.BoundsBitmap) && obj.BoundsWaveForm.Equals(request.BoundsWaveForm) && obj.Zoom == request.Zoom); // Request a new bitmap only if necessary if (existingRequest == null && existingTile == null) { //Console.WriteLine("WaveFormCacheService - Adding bitmap request to queue - zoom: {0} boundsBitmap: {1} boundsWaveForm: {2}", zoom, boundsBitmap, boundsWaveForm); _requests.Add(request); // Remove the oldest request from the list if we hit the maximum if(_requests.Count > MaxNumberOfRequests) _requests.RemoveAt(0); //Console.WriteLine("............. PULSING"); Monitor.Pulse(_lockerRequests); } } }); }
public List<WaveFormTile> GetTiles(WaveFormBitmapRequest request) { if (request.Zoom != _lastZoom && request.Zoom > 1) { lock (_lockerCache) { // Bug: when requesting the smaller tiles, the zoom changes //Console.WriteLine("WaveFormCacheService - Zoom has changed - lastZoom: {0} zoom: {1}", _lastZoom, request.Zoom); _lastZoom = request.Zoom; _tileCacheForZoom.Clear(); } } //float coveredAreaX = 0; float zoomThreshold = (float)Math.Floor(request.Zoom); var boundsWaveFormAdjusted = new BasicRectangle(0, 0, request.BoundsWaveForm.Width * zoomThreshold, request.BoundsWaveForm.Height); var tiles = new List<WaveFormTile>(); //List<WaveFormTile> previouslyAvailableTiles = new List<WaveFormTile>(); for (int a = request.StartTile; a < request.EndTile; a++) { WaveFormTile tile = null; float tileX = a * request.TileSize; // Add lock for cache WaveFormTile cachedTile = null; lock (_lockerCache) { if(request.IsScrollBar) cachedTile = _tileCacheForScrollBar.FirstOrDefault(x => x.ContentOffset.X == tileX); else cachedTile = _tileCacheForZoom.FirstOrDefault(x => x.ContentOffset.X == tileX); } if (cachedTile != null) { //Console.WriteLine(">>>>>>>>>> Taking cached tile!"); tile = cachedTile; } else { // This is a hot line, and needs to be avoided as much as possible. // the problem is that tiles vary in time in quality. // maybe as a first, when a tile at the right zoom is available, cache it locally so it isn't necessary to call the algo again. var availableTiles = GetAvailableTilesForPosition(tileX, request.Zoom); var boundsBitmap = new BasicRectangle(tileX, 0, TileSize, request.BoundsWaveForm.Height); if (availableTiles != null && availableTiles.Count > 0) { // TEMP: Add every tile for zoom == 100% (TESTING) -- This fixes the empty areas and proves the coveredAreaX technique doesn't work. var tileLowRes = availableTiles.FirstOrDefault(x => x.Zoom == 1); if (tileLowRes != null && !tiles.Contains(tileLowRes)) tiles.Add(tileLowRes); // Get the tile with the zoom that is the closest to the current zoom threshold tile = GetOptimalTileAtZoom(availableTiles, zoomThreshold); // If we could not find a tile at this zoom level, we need to generate one if (tile.Zoom != zoomThreshold) { //Console.WriteLine("WaveFormCacheService - Requesting a new bitmap (zoom doesn't match) - zoomThreshold: {0} tile.Zoom: {1} boundsBitmap: {2} boundsWaveForm: {3}", zoomThreshold, tile.Zoom, boundsBitmap, request.BoundsWaveForm); AddBitmapRequestToList(boundsBitmap, boundsWaveFormAdjusted, zoomThreshold, request.DisplayType); } else { lock (_lockerCache) { _tileCacheForZoom.Add(tile); } } } else { // We need to request a new bitmap at this zoom threshold because there are no bitmaps available (usually zoom @ 100%) //Console.WriteLine("WaveFormCacheService - Requesting a new bitmap - zoom: {0} zoomThreshold: {1} boundsWaveForm: {2}", zoomThreshold, boundsBitmap, request.BoundsWaveForm); AddBitmapRequestToList(boundsBitmap, boundsWaveFormAdjusted, zoomThreshold, request.DisplayType); } } //Console.WriteLine("WaveFormCacheService - GetTiles - tile {0} x: {1} Zoom: {2} // tileFound: {3} tile.X: {4} tile.Zoom: {5}", a, tileX, zoom, tile == null, tile != null ? tile.ContentOffset.X : -1, tile != null ? tile.Zoom : -1); if (tile != null) { // Calculate the new covered area (adjusted with the zoom delta) //float currentTileDeltaZoom = request.Zoom/tile.Zoom; //float currentTileX = tile.ContentOffset.X*currentTileDeltaZoom; //float currentTileWidth = request.TileSize*currentTileDeltaZoom; //// Check if the new tile leaves an empty area behind //if (coveredAreaX < tile.ContentOffset.X) //{ // //Console.WriteLine("[...] WaveFormCacheService - GetTiles - An empty area has been found - coveredAreaX: {0} tile.ContentOffset.X: {1}", coveredAreaX, currentTileX); // //var tilesToFillEmptyArea = GetAvailableTilesToFillBounds(tileX, zoom, new BasicRectangle(coveredAreaX, 0, tile.ContentOffset.X - coveredAreaX, boundsWaveForm.Height)); // var tilesToFillEmptyArea = GetAvailableTilesToFillBounds(zoom, new BasicRectangle(coveredAreaX, 0, tile.ContentOffset.X - coveredAreaX, boundsWaveForm.Height)); // //Console.WriteLine("[...] $$$$$$$$$$$$ WaveFormCacheService - GetTiles - tilesToFillEmptyArea.Count: {0}", tilesToFillEmptyArea.Count); // //foreach (var daTile in tilesToFillEmptyArea) // // Console.WriteLine(" .....>>>>>> tile.ContentOffset.X: {0} tile.Zoom: {1}", daTile.ContentOffset.X, daTile.Zoom); // // Go through previously available tiles to find a tile to cover the empty area. // // This should return at least one tile (there should always be one around at zoom=100% except for the initial loading) // WaveFormTile tileToCoverEmptyArea = null; // //foreach (var previouslyAvailableTile in previouslyAvailableTiles) // //{ // // float previousTileDeltaZoom = zoom/previouslyAvailableTile.Zoom; // // float previousTileX = previouslyAvailableTile.ContentOffset.X * previousTileDeltaZoom; // // float previousTileWidth = tileSize * previousTileDeltaZoom; // // if (previousTileX < coveredAreaX && // // previousTileX + previousTileWidth >= currentTileX) // // { // // tileToCoverEmptyArea = previouslyAvailableTile; // // Console.WriteLine("[...] !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WaveFormCacheService - GetTiles - ==> A tile has been found to cover the empty area - tileToCover.ContentOffset.X: {0} tileWidth: {1} tile.Zoom: {2}", previousTileX, previousTileWidth, tileToCoverEmptyArea.Zoom); // // break; // // } // //} // //tileToCoverEmptyArea = tilesToFillEmptyArea.OrderByDescending(x => x.Zoom).FirstOrDefault(); // tileToCoverEmptyArea = tilesToFillEmptyArea.FirstOrDefault(x => x.Zoom == 1); // // We found a tile to cover the area. If not, this should be only when refreshing the wave form for the first time @ 100% // if (tileToCoverEmptyArea != null) // { // Console.WriteLine("---> Adding tile to fill empty - tile.X: {0} tile.Zoom: {1} -- {2}", tileToCoverEmptyArea.ContentOffset.X, tileToCoverEmptyArea.Zoom, DateTime.Now); // tiles.Add(tileToCoverEmptyArea); // } // else // { // // The problem is that there are sometimes a tile could not be found the previous list... is that really a good source of info? // // maybe try the previous list and if not, fall back to the 100% zoom. // // or actually make a linq query similar to the one with adjusted content offset x. // Console.WriteLine("[!!!] WARNING: WaveFormCacheService - GetTiles - An empty area could not be filled by a tile!"); // } //} // Update the covered area position after trying to fill any empty areas left behind // Note: There are still empty areas, this might not be the best way to make sure areas are all filled... //coveredAreaX = currentTileX + currentTileWidth; // Keep the available tiles from the last index so we can search through this list to cover an empty area if needed //previouslyAvailableTiles = availableTiles; // Add tile to list of tiles to draw (TO DO: Check for existing tiles with the same zoom + offset if(!tiles.Contains(tile)) tiles.Add(tile); } } // Order tiles by zoom and then by content offset x; this makes sure that the tiles with the nearest zoom level get drawn on top of farther zoom levels // maybe replace this linq query by inserting the tiles in the list in the right order (at tiles.Add(tile) just up from here) // Also use Distinct to prevent drawing the same tile multiple times // B U G: This might crash if a tile is removed from the list.... var tilesOrdered = tiles.OrderBy(obj => obj.Zoom).ThenBy(obj => obj.ContentOffset.X).ToList(); return tilesOrdered; }
private void DrawTiles(IGraphicsContext context, int tileSize, float realScrollBarHeight) { float deltaZoom = (float) (Zoom/Math.Floor(Zoom)); int startDirtyTile = (int)Math.Floor((ContentOffset.X + context.DirtyRect.X) / ((float)tileSize * deltaZoom)); int numberOfDirtyTilesToDraw = (int)Math.Ceiling(context.DirtyRect.Width / tileSize) + 1; var request = new WaveFormBitmapRequest() { StartTile = startDirtyTile, EndTile = startDirtyTile + numberOfDirtyTilesToDraw, TileSize = tileSize, BoundsWaveForm = Frame, Zoom = _zoom, DisplayType = _displayType }; var tiles = _waveFormCacheService.GetTiles(request); //Console.WriteLine("WaveFormControl - #### startTile: {0} startTileX: {1} contentOffset.X: {2} contentOffset.X/tileSize: {3} numberOfTilesToFillWidth: {4} firstTileX: {5}", startTile, startTile * tileSize, ContentOffset.X, ContentOffset.X / tileSize, numberOfTilesToFillWidth, (startTile * tileSize) - ContentOffset.X); foreach (var tile in tiles) { float tileDeltaZoom = Zoom / tile.Zoom; float x = tile.ContentOffset.X * tileDeltaZoom; float tileWidth = tileSize * tileDeltaZoom; float tileHeight = (ShowScrollBar && Zoom > 1) ? Frame.Height - realScrollBarHeight : Frame.Height; //Console.WriteLine("WaveFormControl - Draw - tile - x: {0} tileWidth: {1} deltaZoom: {2}", x, tileWidth, deltaZoom); //Console.WriteLine("WaveFormControl - Draw - tile - tile.ContentOffset.X: {0} x: {1} tileWidth: {2} tile.Zoom: {3}", tile.ContentOffset.X, x, tileWidth, tile.Zoom); //context.DrawImage(new BasicRectangle(x - ContentOffset.X, 0, tileWidth, Frame.Height), tile.Image.ImageSize, tile.Image.Image); context.DrawImage(new BasicRectangle(x - ContentOffset.X, 0, tileWidth, tileHeight), tile.Image.ImageSize, tile.Image.Image); // Debug overlay //context.DrawRectangle(new BasicRectangle(x - ContentOffset.X, 0, tileWidth, Frame.Height), new BasicBrush(new BasicColor(0, 0, 255, 50)), _penCursorLine); //context.DrawText(string.Format("{0:0.0}", tile.Zoom), new BasicPoint(x - ContentOffset.X + 2, 4), _textColor, "Roboto", 11); } }
private void DrawScrollBar(IGraphicsContext context, int tileSize, float realScrollBarHeight) { if (ShowScrollBar && Zoom > 1) { int startTile = 0; int numberOfTilesToFillWidth = (int)Math.Ceiling(Frame.Width / tileSize);// + 1; // maybe a bug here? when one of the tile is partially drawn, you need another one? var requestScrollBar = new WaveFormBitmapRequest() { StartTile = startTile, EndTile = numberOfTilesToFillWidth, TileSize = tileSize, BoundsWaveForm = new BasicRectangle(0, 0, Frame.Width, ScrollBarHeight), // Frame Zoom = 1, IsScrollBar = true, DisplayType = _displayType }; // TODO: Cache those tiles, we do not need to request them continually since these tiles are always at 100% var tilesScrollBar = _waveFormCacheService.GetTiles(requestScrollBar); foreach (var tile in tilesScrollBar) { context.DrawImage(new BasicRectangle(tile.ContentOffset.X, Frame.Height - realScrollBarHeight, tileSize, realScrollBarHeight), tile.Image.ImageSize, tile.Image.Image); } // Draw a veil over the area that's not visible. The veil alpha gets stronger as the zoom progresses. byte startAlpha = 170; byte maxAlpha = 210; byte alpha = (byte)Math.Min(maxAlpha, (startAlpha + (60 * (Zoom / 10)))); var colorVisibleArea = new BasicColor(32, 40, 46, alpha); float visibleAreaWidth = (1 / Zoom) * Frame.Width; float visibleAreaX = (1 / Zoom) * ContentOffset.X; var rectLeftArea = new BasicRectangle(0, Frame.Height - realScrollBarHeight, visibleAreaX, realScrollBarHeight); var rectRightArea = new BasicRectangle(visibleAreaX + visibleAreaWidth, Frame.Height - realScrollBarHeight, Frame.Width - visibleAreaX - visibleAreaWidth, realScrollBarHeight); //context.DrawRectangle(new BasicRectangle(visibleAreaX, Frame.Height - scrollBarHeight, visibleAreaWidth, scrollBarHeight), new BasicBrush(colorVisibleArea), new BasicPen()); context.DrawRectangle(rectLeftArea, new BasicBrush(colorVisibleArea), new BasicPen()); context.DrawRectangle(rectRightArea, new BasicBrush(colorVisibleArea), new BasicPen()); } }
/// <summary> /// Requests a wave form bitmap to be generated. Once the bitmap is generated, the OnGenerateWaveFormBitmapEnded event is fired. /// </summary> public void RequestBitmap(WaveFormBitmapRequest request) { ThreadPool.QueueUserWorkItem(new WaitCallback(RequestBitmapInternal), request); }