public static Stream GenerateMosaicFromTiles(
        Stream sourceImage,
        string tileBucket, string tileDirectory,
        int tilePixels,
        ILogger logger)
    {
        using (var tileProvider = new QuadrantMatchingTileProvider()) {
            // override default tile width and height if specified
            if (tilePixels != 0)
            {
                MosaicBuilder.TileWidth = MosaicBuilder.TileHeight = tilePixels;
            }

            MosaicBuilder.DitheringRadius = -1;
            MosaicBuilder.ScaleMultiplier = 1;
            List <byte[]> tileImages = GetTileImages(tileBucket, tileDirectory, logger);

            tileProvider.SetSourceStream(sourceImage);
            tileProvider.ProcessInputImageColors(MosaicBuilder.TileWidth, MosaicBuilder.TileHeight);
            tileProvider.ProcessTileColors(tileImages);

            logger.LogInformation("Generating mosaic...");
            var start = DateTime.Now;

            return(GenerateMosaic(tileProvider, sourceImage, tileImages));
        }
    }
    private static Stream GenerateMosaic(
        QuadrantMatchingTileProvider tileProvider, Stream inputStream,
        List <byte[]> tileImages)
    {
        SKBitmap[,] mosaicTileGrid;

        inputStream.Seek(0, SeekOrigin.Begin);

        using (var skStream = new SKManagedStream(inputStream))
            using (var bitmap = SKBitmap.Decode(skStream)) {
                // use transparency for the source image overlay
                var srcImagePaint = new SKPaint()
                {
                    Color = SKColors.White.WithAlpha(200)
                };

                int xTileCount = bitmap.Width / MosaicBuilder.TileWidth;
                int yTileCount = bitmap.Height / MosaicBuilder.TileHeight;

                int tileCount = xTileCount * yTileCount;

                mosaicTileGrid = new SKBitmap[xTileCount, yTileCount];

                int finalTileWidth  = MosaicBuilder.TileWidth * MosaicBuilder.ScaleMultiplier;
                int finalTileHeight = MosaicBuilder.TileHeight * MosaicBuilder.ScaleMultiplier;
                int targetWidth     = xTileCount * finalTileWidth;
                int targetHeight    = yTileCount * finalTileHeight;

                var tileList = new List <(int, int)>();

                // add coordinates for the left corner of each tile
                for (int x = 0; x < xTileCount; x++)
                {
                    for (int y = 0; y < yTileCount; y++)
                    {
                        tileList.Add((x, y));
                    }
                }

                // create output surface
                var surface = SKSurface.Create(targetWidth, targetHeight, SKImageInfo.PlatformColorType, SKAlphaType.Premul);
                surface.Canvas.DrawColor(SKColors.White); // clear the canvas / fill with white
                surface.Canvas.DrawBitmap(bitmap, 0, 0, srcImagePaint);

                // using the Darken blend mode causes colors from the source image to come through
                var tilePaint = new SKPaint()
                {
                    BlendMode = SKBlendMode.Darken
                };
                surface.Canvas.SaveLayer(tilePaint); // save layer so blend mode is applied

                var random = new Random();

                while (tileList.Count > 0)
                {
                    // choose a new tile at random
                    int nextIndex = random.Next(tileList.Count);
                    var tileInfo  = tileList[nextIndex];
                    tileList.RemoveAt(nextIndex);

                    // get the tile image for this point
                    var tileBitmap = tileProvider.GetImageForTile(tileInfo.Item1, tileInfo.Item2);
                    mosaicTileGrid[tileInfo.Item1, tileInfo.Item2] = tileBitmap;

                    // draw the tile on the surface at the coordinates
                    SKRect tileRect = SKRect.Create(tileInfo.Item1 * TileWidth, tileInfo.Item2 * TileHeight, finalTileWidth, finalTileHeight);
                    surface.Canvas.DrawBitmap(tileBitmap, tileRect);
                }

                surface.Canvas.Restore(); // merge layers
                surface.Canvas.Flush();

                var imageBytes = surface.Snapshot().Encode(SKEncodedImageFormat.Jpeg, 80);
                return(new MemoryStream(imageBytes.ToArray()));
            }
    }