private void CheckForTileDuplication(ISuperTile superTile) { if (this.superTileHistory.ContainsKey(superTile.Scale)) { var offsets = Tuple.Create(superTile.OffsetX, superTile.OffsetY); if (this.superTileHistory[superTile.Scale].Contains(offsets)) { var tileDetails = "Scale: {0}, OffsetX: {1}, OffsetY: {2}".Format( superTile.Scale, superTile.OffsetX, superTile.OffsetY); throw new ArgumentException( "A duplicate set of supertiles (" + tileDetails + ") has been passed into the tiler - this exception is thrown because it will most likely result int tiles being written over."); } else { this.superTileHistory[superTile.Scale].Add(offsets); } } else { this.superTileHistory.Add(superTile.Scale, new HashSet <Tuple <int, int> >()); } }
/// <summary> /// Split one large image (a super tile) into smaller tiles. /// The super tile needs to be aligned within the layer first /// NOTE: If a tile spans multiple supertiles, /// it will paint forward/backward by using either the end of the current segment and the start /// of the next segment or the end of the previous segment and the start of the current segment. /// </summary> /// <param name="previous"> /// The previous super tile. Null if nothing beforehand. /// </param> /// <param name="current"> /// The super tile currently being operated on. /// </param> /// <param name="next"> /// The next super tile that will be processed (positive x-dimension) /// </param> public virtual void Tile(ISuperTile previous, ISuperTile current, ISuperTile next) { if (current == null) { return; } this.CheckForTileDuplication(current); if (!this.WriteImages) { Log.Debug("Tile method skipped"); return; } if (current.Image == null) { throw new ArgumentException("Image cannot be null"); } Layer layer = this.CalculatedLayers.First(x => Math.Abs(x.XScale - current.Scale) < Epsilon); int width = current.Image.Width, height = current.Image.Height, xOffset = current.OffsetX, yOffset = current.OffsetY; // determine padding needed int superTileOffsetInLayerX = this.AlignSuperTileInLayer( layer.Width, layer.XTiles, this.profile.TileWidth, current.OffsetX, current.Image.Width, out var paddingX, out var startTileEdgeX); int superTileOffsetInLayerY = this.AlignSuperTileInLayer( layer.Height, layer.YTiles, this.profile.TileHeight, current.OffsetY, current.Image.Height, out var paddingY, out var startTileEdgeY); var deltaTileEdgeSuperTileX = superTileOffsetInLayerX - startTileEdgeX; var deltaTileEdgeSuperTileY = superTileOffsetInLayerY - startTileEdgeY; var superTileRectangle = new Rectangle(xOffset, yOffset, width, height); if (previous == null && startTileEdgeX % this.profile.TileWidth != 0) { throw new InvalidOperationException("A non-aligned super tile, with no previous tile has been requested to be drawn, this means a fragment of the supertile will not been drawn."); } // drawable tiles in the current super tile // as a rule only draw the sections that are available in the current tile // and as much as we need from the next tile double tilesInSuperTileX = CalculateTilesInSuperTile(current.Image.Width, this.profile.TileWidth, paddingX, deltaTileEdgeSuperTileX); double tilesInSuperTileY = CalculateTilesInSuperTile(current.Image.Height, this.profile.TileHeight, paddingY, deltaTileEdgeSuperTileY); // draw tiles for (int i = 0; i < tilesInSuperTileX; i++) { for (int j = 0; j < tilesInSuperTileY; j++) { // clone a segment of the super tile // two cases are catered for bounds that exceed the current super tile // a) Negative X Bias - Paint transparency // b) Positive X Bias - Pull subsection from next image, or paint transparency // Note: best case: Neutral X Bias // Note: no support for anything other than Neutral y Bias // determine how to paint it // supertile relative int layerLeft = (i * this.profile.TileWidth) + startTileEdgeX, superTileLeft = layerLeft - paddingX; int layerTop = (j * this.profile.TileHeight) + startTileEdgeY, superTileTop = layerTop - paddingY; // construct the resulting name of the tile to produced string name = this.profile.GetFileBaseName( this.CalculatedLayers, layer, new Point(layerLeft, layerTop)); // make destination image var tileImage = new Image <Rgba32>( this.profile.TileWidth, this.profile.TileHeight); var subsection = new Rectangle { X = superTileLeft, Y = superTileTop, Width = this.profile.TileWidth, Height = this.profile.TileHeight, }; ImageComponent[] fragments = GetImageParts(superTileRectangle, subsection); // check if this tiler has already written this tile var renderedBefore = this.tileNameHistory.ContainsKey(name); if (renderedBefore) { // if the exact whole image is being drawn again, throw exception // otherwise continue, do not draw image again if (fragments.Length == 1) { if (fragments[0].XBias != TileBias.Neutral) { throw new InvalidOperationException( "This program is really not working at all - this should never happen"); } throw new DuplicateTileException(name, current); } var holes = this.tileNameHistory[name]; if ((holes.Item1 && previous == null) || (holes.Item2 && next == null)) { // if the tile was previously rendered with missing fragments // then this is a duplicate throw new DuplicateTileException(name, current); } else { // otherwise, tile should have been fully rendered // skip continue; } } else { // true if it is possible that an adjacent supertile is missing this.tileNameHistory.Add(name, Tuple.Create(previous == null, next == null)); } // now paint on destination image // 4 possible sources: nothing (transparent), current, next image (along X-axis), previous image (along x-axis) foreach (ImageComponent imageComponent in fragments) { if (imageComponent.YBias != TileBias.Neutral) { throw new NotImplementedException( "Currently no support has been implemented for drawing from supertiles that are not aligned with the current tile on the y-axis"); } var destinationRect = new Rectangle( new Point( imageComponent.Fragment.X - superTileLeft, imageComponent.Fragment.Y - superTileTop), imageComponent.Fragment.Size); var sourceRect = new Rectangle( new Point( imageComponent.Fragment.Location.X - (superTileOffsetInLayerX - paddingX), imageComponent.Fragment.Location.Y - (superTileOffsetInLayerY - paddingY)), imageComponent.Fragment.Size); if (imageComponent.XBias == TileBias.Negative) { // two cases here: edge of layer (paint transparent padding) // or grab previous section from image if (previous == null) { // start of stream, paint transparency // default background for the tile is transparent, // no need to paint that again } else { // paint a fraction from the previous image // here, we shift the co-ordinate system one-super-tile's width right sourceRect.X = sourceRect.X + width; tileImage.DrawImage(previous.Image, destinationRect, sourceRect); } } else if (imageComponent.XBias == TileBias.Positive) { // two cases here: edge of layer reached (paint transparent padding) // or grab next section from image if (next == null) { // end of stream, paint transparency // default background for the tile is transparent, // no need to paint that again } else { // paint a fraction from the next image // here, we shift the co-ordinate system one-super-tile's width left sourceRect.X = sourceRect.X - width; tileImage.DrawImage(next.Image, destinationRect, sourceRect); } } else { // neutral tileImage.DrawImage(current.Image, destinationRect, sourceRect); } } // write tile to disk UPath outputTilePath = this.output.Path / (name + "." + MediaTypes.ExtPng); Log.Debug("Saving tile: " + outputTilePath); tileImage.Save(this.output.FileSystem, outputTilePath); } } }
public void TestTileManyGroupsTilesByScaleAndSortsByOffset() { ISuperTile[] testCases = { MakeTile(60, 0), MakeTile(60, 1), MakeTile(30, 16), MakeTile(30, 15.5), MakeTile(30, 15), MakeTile(120, 0), MakeTile(1, 0), }; List <ISuperTile> moqCurrent = new List <ISuperTile>(testCases.Length), moqNext = new List <ISuperTile>(testCases.Length); var tilerMock = new Mock <Tiler>( this.outputDirectory, this.tilingProfile, new SortedSet <double>() { 60.0, 24, 12, 6, 2, 1 }, 60.0, 1440, new SortedSet <double>() { 1, 1, 1, 1, 1, 1 }, 1.0, 300); tilerMock.Setup(t => t.Tile(It.IsAny <ISuperTile>(), It.IsAny <ISuperTile>(), It.IsAny <ISuperTile>())) .Callback <ISuperTile, ISuperTile, ISuperTile>( (previous, current, next) => { moqCurrent.Add(current); moqNext.Add(next); }); tilerMock.Object.TileMany(testCases); const ISuperTile empty = null; var expected = new[] { Tuple.Create(empty, testCases[5]), Tuple.Create(testCases[5], empty), Tuple.Create(empty, empty), Tuple.Create(empty, testCases[0]), Tuple.Create(testCases[0], testCases[1]), Tuple.Create(testCases[1], empty), Tuple.Create(empty, empty), Tuple.Create(empty, testCases[4]), Tuple.Create(testCases[4], testCases[3]), Tuple.Create(testCases[3], testCases[2]), Tuple.Create(testCases[2], empty), Tuple.Create(empty, empty), Tuple.Create(empty, testCases[6]), Tuple.Create(testCases[6], empty), Tuple.Create(empty, empty), }; Assert.AreEqual(expected.Length, moqCurrent.Count); for (var i = 0; i < expected.Length; i++) { var expectedArgs = expected[i]; Assert.AreEqual(expectedArgs.Item1, moqCurrent[i]); Assert.AreEqual(expectedArgs.Item2, moqNext[i]); } }
/// <summary> /// Split one large image (a super tile) into smaller tiles /// </summary> /// <param name="superTile">The super tile to be split</param> public virtual void Tile(ISuperTile superTile) { this.Tile(null, superTile, null); }
public DuplicateTileException(string name, ISuperTile current) { this.Name = name; this.Current = current; }