#include <string.h>
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <limits>
#include <vector>
#include <set>

using namespace std;

/*
 * DEBUG ONLY: If compiled -DDEBUG, the program will print the computed water
 * layout to stderr for each of the four rotations. If compiled -DSTEP, it
 * will print an intermediate grid state showing the puddle id assignments
 * after scanning each row of the grid. The -DSTEP can be used to trace the
 * execution of the algorithm.
 */

/* SANITY CHECK: assertion macro for verifying input data and internal state */
#define ASSERT(e) { if(!(e)) { cerr << #e << endl; throw; } }

/* Maximum integer value used to represent closed walls in the grid */
#define MAXINT (numeric_limits<int>::max())

/* Max width/height of a Stick-Tite construction */
#define MAXSIZE 40

/*
 * This array type holds the initial input layout and the intermediate states
 * of the board as the computation proceeds. A 0 indicates an empty space (
 * either one that can't hold water or one that hasn't been analyzed yet). A
 * MAXINT indicates a space with a Stick-Tite bock. Any number in between
 * identifies a puddle. The structure is scanned bottom to top and independant
 * puddles are assigned numbers starting from 1.
 */
typedef int grid_t[MAXSIZE][MAXSIZE];

/*
 * This data structure maps each puddle id number to the maximum water height
 * the puddle can achieve. This is necessary to account for the effect of
 * static water pressure which causes all connected pools to drain down to the
 * same height as the lowest opening in any of the connected pools.
 */
typedef set<int> heightset_t;

#if defined(STEP) || defined(DEBUG)
/* 
 * DEBUG ONLY: Print out (to stderr) the current state of the grid. In "water"
 * mode all the puddles that have a defined height (i.e. that are not totally
 * enclosed) are marked with a ~ character. If "water" is false then  it prints
 * the  numbers that show how puddles have been identified and grouped together.
 * The first 9 puddles are printed as ASCII digits. Any higher numbers than that
 * are printed as capital letters A-Z. Any higher than that is printed as a ?
 * since we would need two characters to represent the space.
 */
void print_grid(grid_t grid, int height, int width, bool water,
    heightset_t &heightset)
{
    cerr << "   ";
    for(int col = 0; col < width; col++) {
        cerr << col % 10;
    }
    cerr << endl;

    for(int row = 0; row < height; row++) {
        cerr << setw(2) << row << " ";
        for(int col = 0; col < width; col++) {
            int id = grid[row][col];
            
            if(id == 0) {
                cerr << ".";
            } else if(id == MAXINT) {
                cerr << "X";
            } else if(water) {
                if(heightset.find(id) != heightset.end()) {
                    cerr << '~';
                } else {
                    cerr << '.';
                }
            } else {
                if(id >= 1 && id <= 9) {
                    cerr << (char) ('0' + id);
                } else if(id >= 10 && id <= 36) {
                    cerr << (char) ('A' + id - 10);
                } else {
                    cerr << '?';
                }
            }                
        }
        cerr << endl;
    }
}
#endif

/*
 * Count and return the total number of grid cells that will hold water. This
 * function counts all cells marked as belonging to a puddle but only if that
 * puddle id has already reached its maximum height (i.e. it's not completely
 * enclosed).
 */
int count_water(grid_t grid, int height, int width, heightset_t &heightset)
{
    int total = 0;

    for(int row = 0; row < height; row++) {
        for(int col = 0; col < width; col++) {
            int id = grid[row][col];
            
            if(id != 0 && id != MAXINT) {
                if(heightset.find(id) != heightset.end()) {
                    total++;
                }
            }
        }
    }

    return total;
}

/*
 * Since grid[row][col] contains an open space, this space sets the maximum
 * height of any pool below it (and to the lower left and right). Setting the
 * max height of the pool will prevent it from growing any further since it
 * would always drain out through this open space.
 */
void check_height(grid_t grid, int row, int col, int width,
    heightset_t &heightset)
{
    int id;
    
    /* Check directly below */
    id = grid[row + 1][col];        
    if(id != MAXINT && id != 0) {
        heightset.insert(id);
    }
    
    /* Check in the lower left */
    if(col >= 1) {
        id = grid[row + 1][col - 1];
        if(id != MAXINT && id != 0) {
            heightset.insert(id);
        }
    }

    /* Check in the lower right */
    if(col < width - 1) {
        id = grid[row + 1][col + 1];
        if(id != MAXINT && id != 0) {
            heightset.insert(id);
        }
    }
}

/*
 * When a puddle is being merged, this function helps with merging any other
 * previously separate puddles connected to us. Given a merge id and a location
 * in the grid to start merging from, this function uses a recursive flood
 * fill to merge any other connected puddles by assigning the merge id to them.
 * The recursion stops when a wall, empty space, or an already merged grid
 * cell is hit, and the recursion continues across any cells with puddle ids
 * that are not yet merged.
 */
void merge_recursive(grid_t grid, int row, int col, int width, int merge)
{
    /* The 8 possible adjacent locations encoded into row, col offsets */
    int rowoff[] = { 1, -1,  0,  0,  1,  1, -1, -1 };
    int coloff[] = { 0,  0,  1, -1,  1, -1,  1, -1 };

    /*
     * Stop recursion on out of bounds. There's no max height check here
     * because there will always be a floor beneath us; otherwise the puddle
     * above that initiated the merge in the first place would have never
     * formed.
     */
    if(row < 0 || col < 0 || col >= width) {
        return;
    }

    /* Merge current grid cell */
    grid[row][col] = merge;

    /* Merge current grid cell and recurse in every direction */
    for(int dir = 0; dir < 8; dir++) {
        int newrow = row + rowoff[dir];
        int newcol = col + coloff[dir];
        int id = grid[newrow][newcol];
        
        /* Stop recursion on wall, empty space, or already merged puddle */
        if(id == 0 || id == MAXINT || id == merge) {
            continue;
        }
        
        merge_recursive(grid, newrow, newcol, width, merge);
    }    
}

/* 
 * Return true if the grid location (row, col) really is a hole that will let
 * water drain out. Return false if the location is a wall since a wall will
 * always support water. If the location is another pool, then return true
 * (can't hold water) if the other pool has already reached its max height;
 * otherwise return false (will hold water) because the other pool is still
 * allowed to increase in height. If another pool is present, we also set
 * the "merge" variable to its id so that the pool above can be assigned the
 * same id as this one. This merging behavior is necessary to "transfer"
 * max height information (i.e. static water pressure acts on all connected
 * pools).
 */
bool check_hole(grid_t grid, int row, int col, int &merge,
    heightset_t &heightset)
{
    int id = grid[row][col];

    /* If hole drains into empty space, then this pool can't hold water */
    if(id == 0) {
        return true;
    }
    
    /* If there is a solid wall beneath us, then it'll hold water */
    if(id == MAXINT) {
        return false;
    }
    
    /* If pool beneath us reached it's max height, then it can't hold water */
    if(heightset.find(id) != heightset.end()) {
        return true;
    }
    
    /* Since the pool beneath can still grow; merge and use common id */     
    merge = id;
    return false;
}

/*
 * Rotate the entire grid 90 degrees to the right. While at it, all puddle
 * ids are reset back to 0 (i.e. empty spaces) to prepare for the next
 * analysis round. The width and height are also swapped to reflect the
 * new geometry. Finally, a local temporary grid copy is used while performing
 * the rotation.
 */
void rotate_grid(grid_t grid, int &width, int &height)
{
    grid_t temp;
    swap(width, height);
    memcpy(temp, grid, sizeof(grid_t));

    for(int row = 0; row < height; row++) {
        for(int col = 0; col < width; col++) {
            int oldid = temp[width - col - 1][row];                    
            grid[row][col] = oldid == MAXINT ? MAXINT : 0;
        }
    }
}

/*
 * Scan across (from left to right) a single "row" in the "grid" and mark any
 * puddles that can potentially hold water. To hold water, the puddle must have
 * walls and it must not have any holes that drain into empty space. The grid
 * locations of the puddle are marked with "count" and the the global "count"
 * is incremented so the next puddle can get a unique number. If the puddle
 * does have holes but they drain into another puddle beneath, then this
 * puddle may or may not merge with the one below (see check_hole).
 */
void scan_line(grid_t grid, int row, int width, int &count,
    heightset_t &heightset)
{
    int start, end = 0;

    /* Keep running until the entire row is analyzed */
    while(end < width) {
        int merge = MAXINT;

        /* Skip over blank spaces that can't hold any water */
        for(start = end; start < width; start++) {
            if(grid[row][start] == MAXINT) {
                break;
            }
            check_height(grid, row, start, width, heightset);
        }

        /* Skip over the solid wall that will hold the water in */
        for(; start < width; start++) {
            if(grid[row][start] == 0) {
                break;
            }
        }

        /*
         * Check for any holes beneath this row. Note that we don't have to
         * check the lower-right space because the next loop iteration will
         * take care of that. If no holes are found and we find a wall to the
         * right of the pool, then we can mark the pool with either a new
         * "count" id or we can merge it with the pool beneath us.
         */
        for(end = start; end < width; end++) {

            /* Check for holes diretly beneath and to the lower left */
            if(check_hole(grid, row + 1, end, merge, heightset)) {
                break;
            }
            if(check_hole(grid, row + 1, end - 1, merge, heightset)) {
                break;
            }

            /*
             * If new wall found to hold the right side of the puddle, then mark
             * the [start, end) interval on this as either a new or a merged
             * puddle. If merging this puddle with one below, there may also
             * be other unrelated puddles under this one, therefore we have to
             * descent and merge those to ourselves as well.
             */
            if(grid[row][end] == MAXINT) {
                int id = merge;
            
                /* If not merging, use the next higher "count" as puddle id */
                if(merge == MAXINT) {
                    count++;
                    id = count;
                }
                
                /* Mark this puddle as viable */
                for(; start < end; start++) {
                    grid[row][start] = id;
                    
                    /* If merging, recursively merge all puddles below */
                    if(merge == id) {
                        merge_recursive(grid, row, start, width, id);
                    }
                }
                
                break;
            }
        }
        
        /* If pool couldn't hold water, set max height for pools below */
        for(int i = start; i < end; i++) {
            check_height(grid, row, i, width, heightset);
        }
    }
    
    /* Correct any pools that may have grown too high too early */
    for(int i = 0; i < width; i++) {
        int id = grid[row][i];
        
        if(id != 0 && id != MAXINT) {
            if(heightset.find(id) != heightset.end()) {
                grid[row][i] = 0;
            }
        }
    }
}

/* Main body of program */
void process(void)
{
    int data_num, data_idx;

    /* Read how many data sets to process */
    cin >> data_num;
    
    /* Process each data set separately */
    for(data_idx = 0; data_idx < data_num; data_idx++) {
        int height, width, count = 0;
        vector<int> results;
        grid_t grid;
                
        cin >> height >> width;
        ASSERT(1 <= height && height <= MAXSIZE);
        ASSERT(1 <= width && width <= MAXSIZE);
        
        /*
         * Read in the input layout but convert "." spacezs to 0 and "X" to
         * MAXINT. During the analysis phase, as puddles get identified they
         * are assigned integers starting from 1.
         */
        for(int row = 0; row < height; row++) {
            for(int col = 0; col < width; col++) {
                char c;
                
                cin >> c;
                ASSERT(c == '.' || c == 'X');
                grid[row][col] = c == '.' ? 0 : MAXINT;                
            }
        }
                
        /* Analyze the grid from each of the four directions */
        for(int rot_idx = 0; rot_idx < 4; rot_idx++) {
            heightset_t heightset;
#ifdef STEP
            cerr << "Initial layout (rotation " << rot_idx << ")" << endl;
            print_grid(grid, height, width, false, heightset);
#endif        
            /* Scan from bottom to top identifying all puddles */
            for(int row = height - 2; row >= 0; row--) {
                scan_line(grid, row, width, count, heightset);
#ifdef STEP
                cerr << "Scanning row " << row << endl;
                print_grid(grid, height, width, false, heightset);
#endif        
            }

            /* Assign max heights to any puddles open at the top */
            for(int col = 0; col < width; col++) {
                check_height(grid, -1, col, width, heightset);
            }

#if defined(DEBUG) || defined(STEP)
            cerr << "Water layout (rotation " << rot_idx << ")" << endl;
            print_grid(grid, height, width, true, heightset);        
#endif
            /* Count retained water and add to result vector */
            results.push_back(count_water(grid, height, width, heightset));

            /* Rotate and reset grid for next analysis */
            rotate_grid(grid, width, height);
        }        


        /* Print out the results in descending sorted order */
        sort(results.begin(), results.end(), greater<int>());
        for(int i = 0; i < results.size(); i++) {
            cout << results[i];
            if (i < results.size() - 1) {
                cout << " ";
            }
#if defined(DEBUG) || defined(STEP)
            cerr << results[i] << " ";
#endif        
        }
            cout << endl;
#if defined(DEBUG) || defined(STEP)
            cerr << endl << endl;
#endif        
    }    
}

/* Run program and print out any exceptions that occur */
int main(void)
{
    /* Throw exceptions on EOF or failed data extraction in >> operator */
    cin.exceptions(ios::eofbit | ios::failbit);

    /* Run main body of code */
    try {
        process();
    }
    
    /* Catch unexpected EOF or bad input data */
    catch(ios::failure const &e) {
        cerr << "Unexpected EOF or data type mismatch on input" << endl;
    }

    return 0;
}