Procedural Tree Placement using perlin noise

I have made a procedural terrain generation in unity using some tutorials but i cant figure out how i can randomly place trees on my terrain and i couldnt find anything on the internet. So how can i randomly place trees using perlin noise on a procedural terrain?

Usually what I do is i loop over my map that I have in memory and if i’m on a tile that can inhabit a tree i put a random chance to put a tree there,
After that algorithm is done the map has random trees but its kinda ugly,
So I use something called Cellular Automata to remove all tree’s that aren’t grouped (all tiles that don’t have more than 3 neighbors of same tree).
After that you end up with dense forests of tree’s, depending on your chances.


Here is an example (without rendering):

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Assets.Scripts
{
    public class MapGeneration : MonoBehaviour
    {
        private const int mapSize = 25;
        private int[,] map;

        private void Start()
        {
            // Setup the array
            map = new int[mapSize, mapSize];

            // Populate map with some random types between 1 - 4
            PopulateMap();

            // will replace all type 2 with type 3
            // if the type 2 tile does not have more than 2 neighbors of the same type.
            // This will give you a grouped effect of the given type
            // parameters: tile, replacement, minAlive
            ApplyCellularAutomationMin(2, 3, 2); 

            // rendering of the map
            // .....
        }

        private void PopulateMap()
        {
            System.Random rand = new System.Random();
            for (int x=0; x < mapSize; x++)
            {
                for (int y = 0; y < mapSize; y++)
                {
                    map[x, y] = rand.Next(5); // Set a random type between 1 - 4
                }
            }
        }

        private int GetTile(int x, int y)
        {
            // Get the tile type or -1 if outside of bounds
            return (x > map.GetUpperBound(0) || y > map.GetUpperBound(1) || x < 0 || y < 0) ? -1 : map[x, y];
        }

        private IEnumerable<int> GetNeighbors(int x, int y)
        {
            // Each tile has 8 neighbors
            var neighbors = new int[8];

            // Get each neighbor based on the tile position x, y
            neighbors[0] = GetTile(x - 1, y);
            neighbors[1] = GetTile(x, y - 1);
            neighbors[2] = GetTile(x - 1, y - 1);
            neighbors[3] = GetTile(x + 1, y);
            neighbors[4] = GetTile(x, y + 1);
            neighbors[5] = GetTile(x + 1, y + 1);
            neighbors[6] = GetTile(x - 1, y + 1);
            neighbors[7] = GetTile(x + 1, y - 1);

            // Exclude -1 tiles because those are unexisting ones returned by GetTile
            return neighbors.Where(f => f != -1);
        }

        private void ApplyCellularAutomationMin(int tile, int replacementTile, int minAlive)
        {
            // Perform twice to cleanup residue of automation
            for (int i = 0; i < 2; i++)
            {
                for (int x = 0; x < map.GetLength(0); x++)
                {
                    for (int y = 0; y < map.GetLength(1); y++)
                    {
                        // Get only the neighbors where the type is the given tile.
                        var neighbors = GetNeighbors(x,y).Where(f => f == tile);
                        // If there are not enough alive, then replace the tile.
                        if (neighbors.Count() < minAlive)
                        {
                            map[x, y] = replacementTile;
                        }
                    }
                }
            }
        }
    }
}