Rotating a 2D Matrix

Octave, 210 bytes

function M=F(M,R);f=@(z)[-z/2:-1 !eye(!!mod(z,2)) 1:z/2];t=angle(f([x y]=size(M))'+f(y)*i);B=!!M;B(2:x-1,2:y-1)=0;d=bwdist(B,'ch');[~,I]=sortrows([d(:) t(:)]);for k=0:max(d(:));M(h)=shift(M(h=I(d(I)==k)),R);end

Try it on Octave Online!

Ungolfed version:

function M=F(M,R)
    [x y]=size(M);
    f=@(z)[-z/2:-1 !eye(!!mod(z,2)) 1:z/2];
    t=angle(f(x)'+f(y)*i);
    B=!!M;
    B(2:x-1,2:y-1)=0;
    d=bwdist(B,'chessboard');
    [~,I]=sortrows([d(:) t(:)]);
    for k=0:max(d(:))
        M(h)=shift(M(h=I(d(I)==k)),R);
    end
end
R=2;
M=randi(10,4,7)
F(M,R)

Explanation:

f=@(z)[-z/2:-1 !eye(!!mod(z,2)) 1:z/2]; 

A function that gets a number and generates a range that is ordered and centered for input 4 (even) generates -2 -1 1 2
for input 5(odd) generates -2.5 -1.5 0 1 2
only it should be ordered and centered

f(x)'+f(y)*i    

a complex matrix generated from ranges

(-2,-2.5) (-2,-1.5) (-2,0) (-2,1) (-2,2)
(-1,-2.5) (-1,-1.5) (-1,0) (-1,1) (-1,2)
(1,-2.5)  (1,-1.5)  (1,0)  (1,1)  (1,2)
(2,-2.5)  (2,-1.5)  (2,0)  (2,1)  (2,2)

t=angle(f(x)'+f(y)*i);                    

Convert rectangular to polar coordinates and return angles so for each ring angles are sorted counteclockwise

-2.25  -2.50  3.14  2.68  2.36
-1.95  -2.16  3.14  2.36  2.03
-1.19  -0.98  0.00  0.79  1.11
-0.90  -0.64  0.00  0.46  0.79


B=!!M;
B(2:x-1,2:y-1)=0;

The following matrix generated

1   1   1   1   1
1   0   0   0   1
1   0   0   0   1
1   1   1   1   1

d=bwdist(B,'chessboard');

Computes the distance transform of B using chessboard distance to generate ring indices

0   0   0   0   0
0   1   1   1   0
0   1   1   1   0
0   0   0   0   0               

for a 6 * 7 matrix we will have the following matrix

0   0   0   0   0   0   0
0   1   1   1   1   1   0
0   1   2   2   2   1   0
0   1   2   2   2   1   0
0   1   1   1   1   1   0
0   0   0   0   0   0   0   

[~,I]=sortrows([d(:) t(:)]);

lexicographic sort first based on ring index and then by order of angle(indices of sorted elements returned)

    for k=0:max(d(:))
        M(h)=shift(M(h=I(d(I)==k)),R);
    end

and finally circular shift each ring.


Jelly, 39 38 36 35 bytes

ḢṚ;Ḣ€;Ṫ;Ṫ€Ṛ$,-ṙ\;"ß¹¡
FJ©ṁ¹Çy®ịFṁµ¡

Try it online!


Python 3, 292 288 bytes

_=eval
a=input().split()
b,a=int(a[0]),_("".join(a[1:]))[::-1]
c=len(a)
d=len(a[0])
e=range
f="int((i>=j and i+j<c-1)|2*(i>=c/2and i+d>j+c)|3*(i<c/2and i+j<d))"
l=[-1,1,0,0],[0,0,1,-1]
g=lambda x:[[x[i+l[0][_(f)]][j+l[1][_(f)]]for j in e(d)]for i in e(c)]
print(_("g("*b+"a"+")"*b)[::-1])

Takes input with newlines removed, but leaving a space after the number of increments to rotate it by.

Explanation:

Instead of modeling the matrix as a series of concentric rings per the OP's suggestion, one can instead divide it into four regions where the elements travel either up, down, right, or left during a single rotation. This is the purpose of the long eval-ed string f: to determine which region each i,j combination falls into. Then, the result of that is looked up twice in l, giving the element that must rotate into position i,j in the next step. The function g that does all of this and forms the new matrix after a single step is then called repeatedly by evaling a generated string containing the representation of a nested function call.

When I made this originally, I accidentally caused the matrix to rotate clockwise instead of counterclockwise. Rather than doing a proper fix, I added two strategically placed copies of [::-1] to reverse the matrix before and after the rotation. These could probably be golfed off to ~280 276 bytes, but I'm too lazy to do that.

Also, this is a quick untested port from a slightly longer Python 2 program, so forgive me if it doesn't work quite right. Here's the Python 2 code, anyways:

_=eval
a=raw_input().split()
b,a=int(a[0]),_("".join(a[1:]))[::-1]
c=len(a)
d=len(a[0])
e=xrange
f="int((i>=j and i+j<c-1)|2*(i>=c/2and i+d>j+c)|3*(i<c/2and i+j<d))"
l=[-1,1,0,0],[0,0,1,-1]
g=lambda x:[[x[i+l[0][_(f)]][j+l[1][_(f)]]for j in e(d)]for i in e(c)]
print _("g("*b+"a"+")"*b)[::-1]

EDIT: Golfed off 4 bytes by replacing or with | twice. and can't be helped, unfortunately.