# Plotting Mutually-Occluding (Knit) Curves in 2D

조회 수: 61(최근 30일)
DGM 2022년 11월 27일
편집: DGM 2023년 1월 4일 8:39
I was browsing and doing cleanup earlier, and I came across this misplaced question-as-answer on a question about basic plotting of a sine function. As it's misplaced, I didn't want to answer it in-place, but the more I thought about it, it really did seem like a pretty good question. Considering the upvotes, I'm not the only one. I decided I would repost it as a proper question so that I could take a stab at answering it. It's normally slow on the weekend, so I figured I'd leave this as a bit of a challenge to anyone who wants to play along with what is otherwise a frivolous endeavor.
The requirements are loose.
• There is no strict definition of the interlocking curves. Any method which strikes at a similar shape is fine.
• The individual curves should have significant width -- that prevents some minor simplifications.
• The curves should have different colors for sake of clarity.
• Result can be done in-figure or as an image.
• Number of curves and loops should be adjustable.
The way I see it, there are a few inroads to an answer.
• Using plot3() to actually knit the curves together and then view in 2D
• Using nonflat patch objects in 3D to do similar
• Using polyshape or other 2D geometry tools that I'm unfamiliar with
• Directly compositing an image using masks like some kind of maniac
Using plot3() seems like the simple approach, but linewidth scaling is relative. Using patch() objects is appealing. That would allow the linewidth to be fixed, and it would also allow for the lines to have an additional border color. Off the top of my head, idk a neat way to calculate the vertex coordinates from the coordinates of a centerline curve (minkowski sum?). The idea of strictly operating with plane geometry sounds like the supreme challenge.
I'll add a couple of answers, but I know well enough that if anyone cares to contribute that someone else probably has a better way of approaching the problem.

댓글을 달려면 로그인하십시오.

### 답변(2개)

DGM 2022년 11월 27일
편집: DGM 2022년 11월 27일
I'm going to take this cursory shot at doing it with plot3(). My curve construction is pretty naive, but I just found it easier to indulge in basic precalc tedium instead of trying to make a parametric curve that looked similar to the original.
% parameters
aspectratio = 0.78; % loop width/height
rowoffset = 0.33;
strokew = 12; % stroke width
outlinew = 1.5; % relative to strokew
outlinev = 0.6; % relative brightness of outline
rowcolors = [0.07 0.33 0.52; 0.60 0.22 0.24; ...
0.71 0.70 0.63; 0.26 0.47 0.39]; % white adjusted to taste
nloops = 4; % number of loops per row
loopsontop = false; % controls layer order
% NOTE:
% apparent stroke width is a function of the view configuration (zoom level,etc)
% appearance and clipping aren't going to be consistent if the figure is adjusted
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% the basic curve construction
% i'm just going to make up something that looks like the picture
x = (y.^(1/3) - 0.5*y) .* (1 + 0.7*(1 - (y + (1-y).^4)));
x = [x fliplr(1-x)]/2; % half-curve expansion
y = [y y+1]/2;
x = [x fliplr(1-x)]; % single-curve expansion
y = [y fliplr(y)];
x = reshape((x+(0:nloops-1).').',1,[]); % replicate loops
y = repmat(y,[1 nloops]);
x = rescale(x,0,aspectratio*nloops); % scale to fit AR
% create z-contour to allow for mutual occlusion
if loopsontop
z = 0.5^(-4)*(y-0.5).^4;
else
z = 1 - 0.5^(-4)*(y-0.5).^4;
end
% plot the thing
hold on
for k = 1:nloops
thisy = y + (k-1)*(1-rowoffset);
thiscolor = rowcolors(nloops-k+1,:);
plot3(x,thisy,z,'color',thiscolor,'linewidth',strokew/outlinew) % the interior stroke
plot3(x,thisy,z-0.1,'color',thiscolor*outlinev,'linewidth',strokew) % the outline stroke
end
%view(-45,75)
axis equal If we rotate the view, we can see how this works. The direction of the weave can be swapped by just complementing the z data: I was honestly expecting this to be a lot more complicated, but then again, using plot3() probably is the easiest way. Still, the lack of control over the relative width of the strokes is really annoying.
##### 댓글 수: 0표시숨기기 이전 댓글 수: -1

댓글을 달려면 로그인하십시오.

DGM 2022년 11월 27일
편집: DGM 2023년 1월 4일 8:39
I'm going to assume that I'm the only person who would do it by image composition, but I guess that's the niche I've made for myself. I actually did it this way before I even thought of using plot3(). As bad as I thought it would be, it was worse. This answer uses a few tools from MIMT (on the File Exchange), namely the replacepixels() compositor.
The result is an antialiased raster image.
rowheight = 200; % nominal loop size
loopwidth = 155; % nominal loop size
strokew = 30; % nominal stroke width
% composition parameters
bgcolor = [1 0.9 0.8]*0.15; % pick a bg color
rowcolors = [0.07 0.33 0.52; 0.60 0.22 0.24; ...
0.71 0.70 0.63; 0.26 0.47 0.39]; % white adjusted to taste
nloops = 4; % number of loops per row
loopsontop = false; % controls layer order
% NOTES:
% maximum strokew is a function of rowheight, loopwidth and quarter-curve function
% there's nothing stopping you from causing clipping if you adjust the mask parameters
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
basemk = generatebasemk(rowheight,loopwidth,strokew);
% prepare masks and compose image
outpict = composeimg(basemk,nloops,strokew,bgcolor,rowcolors,loopsontop);
% display output
imshow(outpict)
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function outpict = composeimg(basemk,nloops,strokew,bgcolor,rowcolors,loopsontop)
% split single-loop mask into halves
halfh = size(basemk,1)/2;
tophalf = basemk(1:halfh,:);
bothalf = basemk(halfh+1:end,:);
padh = roundeven(halfh + ceil(strokew/2)) - halfh; % pad, ensuring even height
halfh = round(size(tophalfmid,1)/2);
midtu = tophalfmid(1:halfh,:); % lower half of loop tops
midtl = tophalfmid(halfh+1:end,:); % upper half of loop tops
midbu = bothalfmid(1:halfh,:); % lower half of loop bots
midbl = bothalfmid(halfh+1:end,:); % upper half of loop bots
% visualize the intersection of the halfmasks
%comprow = imfuse(tophalfmid,bothalfmid); imshow(iminv(comprow))
% create background templates
BGend = colorpict(imsize(tophalf,2),bgcolor);
BGmid = colorpict(imsize(midtu,2),bgcolor);
% construct the output image chunks
nsubimages = size(rowcolors,1)+1;
C = cell(nsubimages,1);
for k = 1:nsubimages
if k == 1
C{k} = replacepixels(rowcolors(1,:),BGend,tophalf);
elseif k == nsubimages
C{k} = replacepixels(rowcolors(end,:),BGend,bothalf);
else
if loopsontop
Au = replacepixels(rowcolors(k-1,:),BGmid,midbu);
Au = replacepixels(rowcolors(k,:),Au,midtu);
Al = replacepixels(rowcolors(k,:),BGmid,midtl);
Al = replacepixels(rowcolors(k-1,:),Al,midbl);
C{k} = [Au; Al];
else
Au = replacepixels(rowcolors(k,:),BGmid,midtu);
Au = replacepixels(rowcolors(k-1,:),Au,midbu);
Al = replacepixels(rowcolors(k-1,:),BGmid,midbl);
Al = replacepixels(rowcolors(k,:),Al,midtl);
C{k} = [Au; Al];
end
end
end
outpict = vertcat(C{:});
outpict = repmat(outpict,[1 nloops]);
end
% parameters
kaa = 3; % antialiasing upscale factor (i'm lazy)
% the basic half-loop curve construction
x = (y.^(1/3) - 0.5*y) .* (1 + 0.7*(1 - (y + (1-y).^4)));
x = [x fliplr(1-x)]; % half-curve expansion
y = [y y+1];
rowheight = roundeven(rowheight*kaa,'ceil');
x = rescale(x,1,blocksz(2));
% i'm going to be lazy and use roi tools to create the polyline
% it'd be cool to make masks without squatting on a figure
imshow(zeros(blocksz))
ROI = images.roi.Polyline(gca);
ROI.Position = [x(:) y(:)];
% assemble the base row mask
end It's a lot more verbose, but at least it provides better control over the stroke width. Since the background color is selectable and replacepixels() supports RGBA workflow, there's no reason that it can't be transparent. Since transparency is supported, post processing allows the background to be anything. Also, the tuples in rowcolors can be RGBA. Of course, the size is adjustable and the order can be flipped. EDIT:
I decided to turn this dumb example into a function and throw it in MIMT. See genknit()

댓글을 달려면 로그인하십시오.

### 범주

Find more on Create Block Masks in Help Center and File Exchange

### Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!