Determine the Dimensions of a Rotated Rectangle

Matlab, 226 bytes

The idea is simple: First I try to find out how much the rectangle was turned, then turn the image accordingly such that the rectangle is upright. Then I just 'sum' up all the pixels in the row columns sperately and try count how many of the sums are above than average (simple thresholding) for determining the width and height. This simple method works surprisingly reliably.

How can I detect the angle?

I just try each step (one degree each) and sum along the columns and get a vector of sums. When the rectangle is upright, I should ideally get only two sudden changes in this vector of sums. If the square is on the tip, the changes are very gradual. So I just use the first derivative, and try to minimize the number of 'jumps'. Here you can see a plot of the criterion we are trying to minimize. Notice that you can see the four minima which corresponds to the fours possible upright orientations.

minimization criterion

Further thoughts: I am not sure how much it could be golfed as the exhausive angle search takes a lot of characters and I doubt you could achieve that so well with built in optimization methods, because as you can see there are a lot of local minima that we are not looking for. You can easily improve the accuracy (for big pictures) by choosing a smaller step size for the angle and only search 90° instead of 360° so you could replace 0:360 with 0:.1:90 or somehting like that. But anyways, for me the challenge was more finding a robust algorithm rather than golfing and I am sure the entries of the golfing languages will leave my submission far behind=)

PS: Someone should really derive a golfing language from Matlab/Octave.

Outputs

Example 1:

 25    39

Example 2:

 25    24

Code

Golfed:

s=input('');r=sum(s=='n');S=reshape(s',nnz(s)/r,r)';S=S(:,1:2:end-2)=='.';m=Inf;a=0;for d=0:360;v=sum(1-~diff(sum(imrotate(S,d))));if v<m;m=v;a=d;end;end;S=imrotate(S,a);x=sum(S);y=sum(S');disp([sum(x>mean(x)),sum(y>mean(y))])

Ungolfed:

s=input('');
r=sum(s=='n');              
S=reshape(s',nnz(s)/r,r)'; 
S=S(:,1:2:end-2)=='.';    
m=Inf;a=0;
for d=0:360;                 
    v=sum(1-~diff(sum(imrotate(S,d))));
    if v<m;
        m=v;a=d;
    end;
end;
S=imrotate(S,a);
x=sum(S);y=sum(S');
disp([sum(x>mean(x)),sum(y>mean(y))])

CJam, 68 65 64 bytes

This can be golfed a little more..

qN/2f%{{:QN*'.#Qz,)mdQ2$>2<".X"f#_~>@@0=?Qz}2*;@@-@@-mhSQWf%}2*;

How it works

The logic is pretty simple, if you think about it.

All we need from the input X. combinations is 3 coordinates of two adjacent sides. Here is how we get them:

First

In any orientation of the rectangle, the first . in the whole of the input is going to be one of the corners. For example..

XXXXXXXXXXXXXX
XXXXXXX...XXXX
XXXX.......XXX
X............X
XX.........XXX
XXXX...XXXXXXX
XXXXXXXXXXXXXX

Here, the first . is in the 2nd line, 8th column.

But that's not it, we have to do some adjustment and add the width of the . run on that line to the coordinates to get the coordinate of the right end.

Second

If we transpose the above rectangle (pivoted on newlines), then the bottom left corner takes the above step's place. But here, we do not compensate for the . run length as we would have wished to get the bottom left coordinate of the edge anyways (which in transposed form will still be the first encountered .)

Rest two

For rest two coordinates, we simply flip horizontally, the rectangle and perform the above two steps. One of the corners here will be common from the first two.

After getting all 4, we simply do some simple math to get the distances.

Now this is not the most accurate method, but it works well within the error margin and well for all possible orientations of the rectangle.

Code expansion (bit outdated)

qN/2f%{{:QN*'.#Q0=,)md}:A~1$Q='.e=+QzA@@-@@-mhSQWf%}2*;
qN/2f%                               e# Read the input, split on newlines and squish it
      {   ...   }2*                  e# Run the code block two times, one for each side  
{:QN*'.#Q0=,)md}:A~                  e# Store the code block in variable A and execute it
 :QN*                                e# Store the rows in Q variable and join by newlines
     '.#                             e# Get the location of the first '.'
        Q0=,)                        e# Get length + 1 of the first row
             md                      e# Take in X and Y and leave out X/Y and X%Y on stack
1$Q=                                 e# Get the row in which the first '.' appeared
    '.e=+                            e# Get number of '.' in that row and add it to X%Y
         QzA                         e# Transpose the rows and apply function A to get
                                     e# the second coordinate
            @@-@@-                   e# Subtract resp. x and y coordinates of the two corners
                  mh                 e# Calculate (diff_x**2 + diff_y**2)**0.5 to get 1 side
                    SQWF%            e# Put a space on stack and put the horizontally flipped
                                     e# version of the rows/rectangle all ready for next two
                                     e# coordinates and thus, the second side

Try it online here


Python 3, 347 337 bytes

This turned out harder than I expected. Work in progress...

def f(s):
 l=s.split('\n');r=range;v=sorted;w=len(l[0]);h=len(l);p=[[x,y]for x in r(w)for y in r(h)if'X'>l[y][x]];x,y=[sum(k)/w/h for k in zip(*p)];g=[[x/2,y]];d=lambda a:((a[0]/2-a[2]/2)**2+(a[1]-a[3])**2)**.5
 for i in r(3):g+=v(p,key=lambda c:~-(c in g)*sum(d(j+c)for j in g))[:1]
 print(v(map(d,[g[1]+g[2],g[2]+g[3],g[1]+g[3]]))[:2])

Try it online! Comes with a test harness that tries 100 random valid rectangles. Note: The test harness seems to produce some errors and prints the corresponding grids. It seems like these cases all have rather round corners - an issue which seems to be caused by the grid generation code, which I translated from the original snippet.

Defines a function f taking the string as an argument and printing the result to STDOUT.

Pyth, 87 84 82 81 75 72 71 bytes (broken)

Km%2d.zJf<@@KeThTG*UhKUKPSm.adfqlT2ytu+G]ho*t}NGsm.a,kNGJ3]mccsklhKlKCJ

This seems to have been broken in 2015. Since then, something in Pyth has changed so that the old versions now crash and the new ones return [0, 0]. Right now I have neither the slightest idea what this monstrosity does nor the time to figure it out. I'm leaving it here as crossed-out for historical reasons.

Way Still too long. Basically a port of the previous. Loving Pyth's .a Euclidean distance. Takes input via STDIN and gives output via STDOUT. Expects the non-rectangle character to be lower-case x (well, anything with ASCII value 98 or more).

Algorithm

Both of these use the same algorithm. I basically start with an array containing the center of mass of the rectangle area. I then add three points to the array of all points in the rectangle, always choosing the one with maximum sum of distances to the points already in the array. The result is always three points in different corners of the rectangle. I then just calculate all the three distances between those three points and take the two shortest ones.