Attempt at cubism

Which line did you omit? We might not be talking about the same one…

I have only tried segment_watershed. I have found slic but I’d need to extend it to 3 dimensions. segment_snake and segment_cells don’t produce the kinds of patches I want; they conform to image contours far too much and leave huge backgrounds.

This one, which you also did in your latest version.


Latest version

1 After display, if I do f[2] 0, the resultant image doesn’t look very different without [2], using your default parameters.

2 Typo: your image has a 0 at the top left corner in the plugin preview.

3 David’s and GIMP’s cubism are very different from yours. They maintain the basic structure of the original image.

Raw from @Carmelo_DrRaw.

image

David’s (default)

image

GIMP’s (50,2.5)

image

PS
The challenge is to do something in between their cubism and yours, and somehow make it more deliberate instead of a mere collection of random haphazard shapes. Your intent probably isn’t to replicate real cubism; consider this food for thought or a challenge.

Actually I do want to recreate real cubist styles. I didn’t think that this rotated gradient square approach represents any cubist style I’ve seen before. The way I thought of it was that I’d use the contours of the existing image to generate segments and then transform images within them using warping.

Here’s an example which to me has almost everything that fits cubist style:

https://en.wikipedia.org/wiki/File:Jean_Metzinger,1915,Soldat_jouant_aux%C3%A9checs(Soldier_at_a_Game_of_Chess),_oil_on_canvas,_81.3_x_61_cm,_Smart_Museum_of_Art.jpg

  1. Simplified lines, usually straight. I think we’ll need the Hough transform and its inverse, the second of which I don’t know how to implement.
  2. Multiple interwoven perspectives (transformations) of these lines (i.e. simple windows within which the transformations occur)
  3. Borders between each window.

If I didn’t have to rely on the stylize mega-script and I could choose how I wanted the image to be divided, the script would be really good. The whole 3D thing I was interested in can be part of that - again, my idea was to turn a stack of 2D images with the same dimensions into a single 3D image, screw it up and then take 2D slices of this image.

It’s really odd. When I changed it to i-0.25 there was some anti-aliasing, though quite low in quality. It turns out that the specification of the trilinearly-interpolated pixel is happening in the opposite direction from the currently-selected pixel so it’s really just blurring everything in a stupid way within 0.25 pixels, but if I turn it back to -i I’ve still got problems…

Weird. That’s the post-label segmented version of the original image which acts as a 3D map for all the label ids, and it’s used in the cubism() function within the final fill block. Without that, the function would return the result for label id 0 for a given input pixel. The entire image would be rotated as if it was within one big segment.

That was a vestigial text command which I was once using to check if one of the variables wasn’t being treated properly - the y and z offsets had gone mad because I assigned the x offset variable to them by mistake…

Anyway, the current code’s been modified again and this time I’ve bypassed cubism() altogether so that I’m only seeing what happens to the label ids.

The current code
#@gui Cubism: fx_jr_rep_cubism_preview
#@gui : sep = separator()
#@gui : 0. Recompute = button()
#@gui : 1. Threshold=float(2,0,15)
#@gui : 2. Smoothness=float(5,0,20)
#@gui : 3. X Offset Centre=float(0,-100,100)
#@gui : 4. X Offset Range=float(25,0,200)
#@gui : 5. Y Offset Centre=float(0,-100,100)
#@gui : 6. Y Offset Range=float(25,0,200)
#@gui : 7. Z Offset Centre=float(0,-100,100)
#@gui : 8. Z Offset Range=float(0,0,200)
#@gui : 9. Rot Angle Centre=float(0,-360,360)
#@gui : 10. Rot Angle Range=float(360,0,360)
#@gui : 11. Rot Axis X Centre=float(0,-1,1)
#@gui : 12. Rot Axis X Range=float(0,0,2)
#@gui : 13. Rot Axis Y Centre=float(0,-1,1)
#@gui : 14. Rot Axis Y Range=float(0,0,2)
#@gui : 15. Rot Axis Z Centre=float(1,-1,1)
#@gui : 16. Rot Axis Z Range=float(0,0,2)
#@gui : 17. AA Iterations=int(16,0,100)
#@gui : 18. Sharpness=float(0,0,2)
#@gui : 19. AA Anisotropy=float(1,0,1)
#@gui : 20. AA Gradient Smoothness=float(1,0,10)
#@gui : 21. Tensor Smoothness=float(5,0,10)
#@gui : 22. Time Step=float(15,5,50)


fx_jr_rep_cubism:
to_gray
# initial parameters for all images
rotac=$9
rotar=$10
rotxxc=$11
rotxxr=$12
rotxyc=$13
rotxyr=$14
rotxzc=$15
rotxzr=$16
aa=$17
repeat $! l[$>]
# initial parameters for each image
ww={w}
hh={h}
dd={d}
xoffc=$3*0.01*$ww
xoffr=$4*0.01*$hh
yoffc=$5*0.01*$dd
yoffr=$6*0.01*$ww
zoffc=$7*0.01*$hh
zoffr=$8*0.01*$dd
+b. $2% segment_watershed. $1	# embedded fx_segment_watershed
label.	# labelling the segments of the copied image
l[1]
# get the edges of the segment and find the distance to the nearest pixel on the other side of the nearest edge for each pixel in terms of x, y and z components
# this will be used for the anti-aliasing later on
+gradient_orientation. 3
a[^0] c
100%,100%,100%,1,"min(1,ceil(norm(I(#1))))"
rm[1]
distance. 1
+gradient_orientation. 3
f[1] "(i+0.5)"  # experimenting with this to find a good distance offset, should really be (i+0.5) for true distance to edge
a[1-4] c

endl
# uncomment this to blur this distance-to-edge image, was just another experiment but could be used to highlight the problem in a clearer way
# b. 2
# approximate the centre of each segment and store the x, y and z components in a new image which will store more parameters later
# this image's x coordinate is the segment id as specified in the labelled segment image (#1)
initsize={1+iM#1}
$initsize,10,1,1
vsize={$initsize*3}
eval ${-math_lib}"
coordinates_per_val=vector"$vsize"(0);
count_per_val=vector"$initsize"(0);
for(px=0,px<w#1,px++,
    for(py=0,py<h#1,py++,
		for(pz=0,pz<d#1,pz++,
			pos=i(#1,px,py,pz);
			coordinates_per_val[pos*3]+=px;
			coordinates_per_val[pos*3+1]+=py;
			coordinates_per_val[pos*3+2]+=pz;
			count_per_val[pos]++;
		);
    );
);
for(n=0,n<"$initsize",n++,
    coordinates_per_val[n*3]/=count_per_val[n];
    coordinates_per_val[n*3+1]/=count_per_val[n];
	coordinates_per_val[n*3+2]/=count_per_val[n];
	I(n,0)=coordinates_per_val[n*3];
	I(n,1)=coordinates_per_val[n*3+1];
	I(n,2)=coordinates_per_val[n*3+2];
);
"
# add pseudo-randomised parameters for each segment id to the parameters image
sh. 3,9,0,0 f. ">begin(randm=u(100,200);randa=u(1,3));srand((x+(randa)+(u))*(randm+y));u(-1,1)"
eval. "begin(multvec=["$xoffr","$yoffr","$zoffr","$rotar","$rotxxr","$rotxyr","$rotxzr"];
addvec=["$xoffc","$yoffc","$zoffc","$rotac","$rotxxc","$rotxyc","$rotxzc"]);
for(n=0,n<w,n++,
strip=crop(n,0,0,0,1,7,1,1);
draw(#4,(strip*multvec)+addvec,n,0,0,0,1,7,1,1,1))"
rm.
# get the edges, subtract a smoothed copy in order to create a mask for the anti-aliasing
+gradient_norm[1] f. "i>0?255:0"
+smooth. $aa,${18-22},0
-.. .
rm.
abs.
/. 255
# let's put all of this together now
f[0] "critical(begin(const spec=s;aa="$aa";
cubism(id,xx,yy,zz)=( # this function warps a given pixel using the parameters assigned to a specified id within the parameters image
params=crop(#3,id,0,0,0,1,10,1,1);
# get segment centre
xc=params[0];
yc=params[1];
zc=params[2];
# get offsets
xoff=params[3];
yoff=params[4];
zoff=params[5];
# get rotation angle and axis
ang=params[6];
xa=params[7];
ya=params[8];
za=params[9];
# rotate and offset
vx=xx-xc-xoff;
vy=yy-yc-yoff;
vz=zz-zc-zoff;
pix=[xa,ya,za];
select=(rot(asin(pix/norm(pix))*2/pi,ang)*[vx,vy,vz])+[xc,yc,zc];
# return the new pixel value
I(select,1,3)));
# anti-aliasing only if the setting's enabled and we're at a pixel where the mask (#4) has a non-zero value
(aa&&(i(#4)>0))?(
# get the displacements necessary to reach the nearest pixel on the other side of the nearest edge
dist=i(#2,x,y,z,0);
xdist=-i(#2,x,y,z,1)*dist;
ydist=-i(#2,x,y,z,2)*dist;
zdist=-i(#2,x,y,z,3)*dist;
# trilinear interpolation to determine the value of the nearest pixel behind the nearest edge
# we don't have to cubism() eight times for each pixel here since we can instead make a weighted histogram of ids
idlist=vector(#8,-1);
freqlist=vector(#8,0);
idnum=0;
xint=floor(xdist);yint=floor(ydist);zint=floor(zdist);
xo=xdist-xint;yo=ydist-yint;zo=zdist-zint;
gridmult=[(1-xo)*(1-yo)*(1-zo),xo*(1-yo)*(1-zo),(1-xo)*yo*(1-zo),xo*yo*(1-zo),(1-xo)*(1-yo)*zo,xo*(1-yo)*zo,(1-xo)*yo*zo,xo*yo*zo];
for(pz=0,pz<2,pz++,
	for(py=0,py<2,py++,
		for(px=0,px<2,px++,
			curr=i(#1,x+xint+px,y+yint+py,z+zint+pz,0,1,1);
			pos=find(idlist,curr);
			pos==-1?(idnum+=1;pos=find(idlist,-1);idlist[pos]=curr);
			freqlist[pos]+=gridmult[pz*4+py*2+px];
		);
	);
);
val=0; # val=vector(#spec,0);
for(n=0,n<idnum,n++,
# construct the final value for the interpolated pixel
val+=idlist[n]*freqlist[n]);
# interpolate between that pixel and the current one in accordance with the AA mask
lerp(val,i(#1,x,y,z,0,1,1),i(#4))):
# if we don't need any AA, we can just use the current pixel
(i(#1,x,y,z,0,1,1)))
"
# clean up now that we're done
display
rm[^0]
endl done
to_rgb
n 0,255
fx_jr_rep_cubism_preview:
fx_jr_rep_cubism ${2-23}

What I am saying is that yours doesn’t look like cubism either and that the truth lies somewhere between yours and theirs. Look: do any of these intermediaries of your cubism resemble the landscape image that I posted above? I am not convinced.

[2] kind of has the same flat area as the cloudless region of the sky but that is it. In fact, upon closer inspection, I now know the cause of the zippering and narrow pieces: the flat and low textured areas.

I completely ignored that. Yeah, it doesn’t really look like real cubism that much now that I think about it. If anything the Rodilius script is closer to that, though I don’t think that keeping the same structure is so necessary for cubism…

Yes, I can see that now! I’m not sure why segment_watershed does this…

Try the pattern → mosaic filter for g’mic-qt. See if you like the structure. That may be the solution.

1 Like

You are a genius, I have no idea why I didn’t think of that!

Here’s what I’ve come up with:

jr_mosaic 20,15,10%

jr_mosaic:
repeat $! l[$>]
+gradient_norm b. $3 n. 0,1 
100%,100%,100%,2,'u<0.25*(lerp($1%,i(#1),$2%))^4?[u,1]' s. c
distance. 1 *. -1  watershed.. . rm[1,3]
blend shapeaverage
endl
done

The second and third options allow the size of the segments to adjust to the level of detail in the original image.

1 Like

I like the simpler code. If you could combine the chunks to make more deliberate intersecting shapes, and chose more primary, complimentary and opponent colours; then you would be done. :stuck_out_tongue:

Hold on, the borders of those mosaic pieces in that image are smooth! How did you do that? Was that with a post-processing anti-aliasing thing?

Anyway, I now have two filters to sort out then: the cubism one and the previous jumbling thing. One of the ideas I have for the new cubism mosaic thing involves using gradients within each segment. I should also generate some edges…

So here’s something I made from that:

#@gui Multi-Mosaic: fx_jr_multi_mosaic_preview
#@gui : sep = separator()
#@gui : 0. Recompute = button()
#@gui : 1. Iterations=int(3,1,10)
#@gui : sep = separator(), note = note("<small><b>Mosaic</b></small>")
#@gui : 2. Density=float(15,0,100)
#@gui : 3. Details Influence=float(15,0,100)
#@gui : 4. Details Smoothness=float(0,0,100)
#@gui : sep = separator(), note = note("<small><b>Smooth [Diffusion]</b></small>")
#@gui : 5. Iterations=int(16,0,100)
#@gui : 6. Sharpness=float(0.5,0,2)
#@gui : 7. Anisotropy=float(1,0,1)
#@gui : 8. Gradient Smoothness=float(3,0,10)
#@gui : 9. Tensor Smoothness=float(5,0,10)
#@gui : 10. Time Step=float(15,5,50)
#@gui : sep = separator(), note = note("<small><b>Edges [Gradient Norm]</b></small>")
#@gui : 11. Opacity=float(1,0,1)
#@gui : 12. Smoothness=float(0,0,10)
#@gui : 13. Linearity=float(0.5,0,1.5)
#@gui : 14. Min Threshold=float(0,0,100)
#@gui : 15. Max Threshold=float(100,0,100)
#@gui : 16. Negative Colors=bool(0)
#@gui : sep = separator(), note = note("<small><b>Final Edge Blend</b></small>")
#@gui : 17. Edge Blend=bool(1)
#@gui : 18. Smoothness=float(15,0,100)

fx_jr_multi_mosaic:
repeat $! l[$>]
repeat $1 +l[0]
+gradient_norm n. 0,1 b. $4 
100%,100%,100%,2,'u<0.25*(lerp($2%,i(#1),$3%))^(4)?[u,1]' s. c
distance. 1 *. -1  watershed.. . rm[1,3]
blend shapeaverage
smooth. ${5-10},0
+fx_gradient_norm ${12-16} *. $11
split_opacity..
-[^1]
a c
endl done
rm[0]
if $17 blend_edges $18 fi
endl done

fx_jr_multi_mosaic_preview :
fx_jr_multi_mosaic ${2-19}

fx_jr_multi_mosaic 3,31.3,42.7,0,20,0.594,1,1.73,3.25,7.925,0,0,0.5,0,100,0,1,8 on this image:

…gives me this:

image

Edit: a few changes. Now it can do multiple densities at once.

New script
#@gui Multi-Mosaic: fx_jr_multi_mosaic_preview
#@gui : sep = separator()
#@gui : 0. Recompute = button()
#@gui : 1. Iterations=int(3,1,10)
#@gui : sep = separator(), note = note("<small><b>Mosaic</b></small>")
#@gui : 2. Lowest Density=float(15,0,100)
#@gui : 3. Highest Density=float(45,0,100)
#@gui : 4. Details Influence=float(50,0,100)
#@gui : 5. Details Smoothness=float(0,0,100)
#@gui : sep = separator(), note = note("<small><b>Smooth [Diffusion]</b></small>")
#@gui : 6. Iterations=int(16,0,100)
#@gui : 7. Sharpness=float(0.5,0,2)
#@gui : 8. Anisotropy=float(1,0,1)
#@gui : 10. Gradient Smoothness=float(3,0,10)
#@gui : 11. Tensor Smoothness=float(5,0,10)
#@gui : 12. Time Step=float(15,5,50)
#@gui : sep = separator(), note = note("<small><b>Edges [Gradient Norm]</b></small>")
#@gui : 13. Opacity=float(1,0,1)
#@gui : 14. Smoothness=float(0,0,10)
#@gui : 15. Linearity=float(0.5,0,1.5)
#@gui : 16. Min Threshold=float(0,0,100)
#@gui : 17. Max Threshold=float(100,0,100)
#@gui : 18. Negative Colors=bool(0)
#@gui : sep = separator(), note = note("<small><b>Final Edge Blend</b></small>")
#@gui : 19. Edge Blend=bool(1)
#@gui : 20. Smoothness=float(15,0,100)

fx_jr_multi_mosaic:
to_rgba
repeat $! l[$>]
repeat $1 +l[0]
+gradient_norm n. 0,1 b. $5
100%,100%,100%,2,'u<lerp(0.5,i(#1),$4%)*((lerp($2,$3,($>/max(1,($1-1))))*0.01))^4?[u,1]'  s. c
distance. 1 *. -1  watershed.. . rm[1,3]
blend shapeaverage
smooth. ${6-11},0
+fx_gradient_norm ${13-17} *. $12
split_opacity..
-[^1]
a c
endl done
rm[0]
if $18 blend_edges $19 fi
endl done

fx_jr_multi_mosaic_preview :
fx_jr_multi_mosaic ${2-20}

@Joan_Rake1 I should let you know that your .gmic file was edited in github as part of code cleaning effort.

Okay, that was easy to sort out because it was mostly about replacing tab stops with spaces.

So this more cubist command is done and it’s got loads more features, but now I’ve still got the problem of the original ‘cubist’ one’s anti-aliasing…

Multi-Mosaic
#@gui Multi-Mosaic: fx_jr_multi_mosaic_preview
#@gui : sep = separator()
#@gui : 0. Recompute = button()
#@gui : 1. Iterations=int(3,1,10)
#@gui : sep = separator(), note = note("<small><b>Mosaic</b></small>")
#@gui : 2. Lowest Density=float(15,0,100)
#@gui : 3. Highest Density=float(45,0,100)
#@gui : 4. Details Influence=float(50,0,100)
#@gui : 5. Details Smoothness=float(0,0,100)
#@gui : 6-8. Colour Balance=color(128,128,128)
#@gui : 9. Luma Range = float(0,0,100)
#@gui : 10. Chroma Range = float(0,0,100)
#@gui : 11. Hue Range = float(0,0,100)
#@gui : sep = separator(), note = note("<small><b>Edges [Gradient Norm]</b></small>")
#@gui : 12. Opacity=float(1,-1,1)
#@gui : 13. Smoothness=float(0,0,10)
#@gui : 14. Linearity=float(0.5,0,1.5)
#@gui : 15. Min Threshold=float(0,0,100)
#@gui : 16. Max Threshold=float(100,0,100)
#@gui : 17. Thickness=int(1,1,10)
#@gui : 18-20. Color = color(0,0,0)
#@gui : 21. Luma Range = float(0,0,100)
#@gui : 22. Chroma Range = float(0,0,100)
#@gui : 23. Hue Range = float(0,0,100)
#@gui : sep = separator(), note = note("<small><b>Final Edge Blend</b></small>")
#@gui : 24. Edge Blend=bool(1)
#@gui : 25. Smoothness=float(15,0,100)
#@gui : sep = separator(), note = note("<small><b>Smooth [Diffusion]</b></small>")
#@gui : 26. Iterations=int(16,0,100)
#@gui : 27. Sharpness=float(0.5,0,2)
#@gui : 28. Anisotropy=float(1,0,1)
#@gui : 29. Gradient Smoothness=float(3,0,10)
#@gui : 30. Tensor Smoothness=float(5,0,10)
#@gui : 31. Time Step=float(15,5,50)
#@gui : sep = separator(), note = note("<small><b>Local Normalization</b></small>")
#@gui : 32. Amplitude=float(2,0,60)
#@gui : 33. Radius=int(6,1,64)
#@gui : 34. Neighborhood Smoothness=float(5,0,40)
#@gui : 35. Average Smoothness=float(20,0,40)
#@gui : 36. Constrain Values=bool(1)
#@gui : 37 .Channel(s)=choice(2,"All","RGBA [All]","RGB [All]","RGB [Red]","RGB [Green]","RGB [Blue]","RGBA [Alpha]","Linear RGB [All]","Linear RGB [Red]","Linear RGB [Green]","Linear RGB [Blue]","YCbCr [Luminance]","YCbCr [Blue-Red Chrominances]","YCbCr [Blue Chrominance]","YCbCr [Red Chrominance]","YCbCr [Green Chrominance]","Lab [Lightness]","Lab [ab-Chrominances]","Lab [a-Chrominance]","Lab [b-Chrominance]","Lch [ch-Chrominances]","Lch [c-Chrominance]","Lch [h-Chrominance]","HSV [Hue]","HSV [Saturation]","HSV [Value]","HSI [Intensity]","HSL [Lightness]","CMYK [Cyan]","CMYK [Magenta]","CMYK [Yellow]","CMYK [Key]","YIQ [Luma]","YIQ [Chromas]","RYB [All]","RYB [Red]","RYB [Yellow]","RYB [Blue]")

fx_jr_multi_mosaic:
to_rgba
repeat $! l[$>]
repeat $1 +l[0]
+gradient_norm n. 0,1 b. $5
100%,100%,100%,2,'u<lerp(0.5,i(#1),$4%)*((lerp($2,$3,($>/max(1,($1-1))))*0.01))^4?[u,1]'  s. c
distance. 1 *. -1  watershed.. . rm[1,3]
blend shapeaverage
1,1,1,3,([$6,$7,$8]) rgb2lch8. f. "I+[u(-255,255)*$9%,u(-255,255)*$10%,u(-255,255)*$11%]" lch82rgb.  fx_balance_gamma.. {I(#-1,0,0,0)},0 rm. 
+to_rgb.
fx_gradient_norm. ${13-16},0 dilate_circ $17 to_rgba. 1,1,1,3,([$18,$19,$20]) rgb2lch8. f. "I+[u(-255,255)*$21%,u(-255,255)*$22%,u(-255,255)*$23%]" lch82rgb. f.. "[I(#-1,0,0,0),($12<0?255-i0:i0)*abs($12)]" rm.
blend alpha
endl done
rm[0]
if $24 blend_edges $25 fi
smooth ${26-31},0
endl done
fx_normalize_local ${32-37}
fx_jr_multi_mosaic_preview :
fx_jr_multi_mosaic ${2-38}

Found a nice glitch art setting there. Set Lowest Density to 0, and Highest Density to 100. I see a nice line streak.

Other than that, some great mosaic art filter!

That’s an artefact of local normalisation on an image where all pixels are the same value. Setting the density to 0 does that.

I have some ideas regarding the old ‘cubism’ command’s AA: I can go back to using a 2x2 window and use a blurred version of the gradient norm as a blending weight.

Edit: I’ll stick with the distance-to-nearest-edge approach but with a smoothed gradient norm weighting thing instead. I’ve swapped the segmentation for the mosaic thing instead, but still no progress with the AA.

The old 'cubism' command right now
#@gui Cubism: fx_jr_rep_cubism_preview
#@gui : sep = separator()
#@gui : 0. Recompute = button()
#@gui : 1. Density=float(15,0,100)
#@gui : 2. Details Influence=float(50,0,100)
#@gui : 3. X Offset Centre=float(0,-100,100)
#@gui : 4. X Offset Range=float(25,0,200)
#@gui : 5. Y Offset Centre=float(0,-100,100)
#@gui : 6. Y Offset Range=float(25,0,200)
#@gui : 7. Z Offset Centre=float(0,-100,100)
#@gui : 8. Z Offset Range=float(0,0,200)
#@gui : 9. Rot Angle Centre=float(0,-360,360)
#@gui : 10. Rot Angle Range=float(360,0,360)
#@gui : 11. Rot Axis X Centre=float(0,-1,1)
#@gui : 12. Rot Axis X Range=float(0,0,2)
#@gui : 13. Rot Axis Y Centre=float(0,-1,1)
#@gui : 14. Rot Axis Y Range=float(0,0,2)
#@gui : 15. Rot Axis Z Centre=float(1,-1,1)
#@gui : 16. Rot Axis Z Range=float(0,0,2)
#@gui : 17. AA Iterations=int(16,0,100)
#@gui : 18. Sharpness=float(0,0,2)
#@gui : 19. AA Anisotropy=float(1,0,1)
#@gui : 20. AA Gradient Smoothness=float(1,0,10)
#@gui : 21. Tensor Smoothness=float(5,0,10)
#@gui : 22. Time Step=float(15,5,50)


fx_jr_rep_cubism:
to_gray
# initial parameters for all images
rotac=$9
rotar=$10
rotxxc=$11
rotxxr=$12
rotxyc=$13
rotxyr=$14
rotxzc=$15
rotxzr=$16
aa=$17
repeat $! l[$>]
# initial parameters for each image
ww={w}
hh={h}
dd={d}
xoffc=$3*0.01*$ww
xoffr=$4*0.01*$hh
yoffc=$5*0.01*$dd
yoffr=$6*0.01*$ww
zoffc=$7*0.01*$hh
zoffr=$8*0.01*$dd
+gradient_norm
100%,100%,100%,2,'begin(n=-1);u<lerp(0.5,i(-1),$2%)*($1%)^4?(n+=1;[u,1])' s. c
distance. 1 *. -1  watershed.. . rm[1,3]
label.	# labelling the segments of the copied image
l[1]
# get the edges of the segment and find the distance to the nearest pixel on the other side of the nearest edge for each pixel in terms of x, y and z components
# this will be used for the anti-aliasing later on
+gradient_orientation. 3
a[^0] c
100%,100%,100%,1,"min(1,ceil(norm(I(#1))))"

rm[1]
distance. 1
+gradient_orientation. 3
f[1] "-(i+0.5)"  # experimenting with this to find a good distance offset, should really be (i+0.5) for true distance to edge
a[1-4] c

endl
# uncomment this to blur this distance-to-edge image, was just another experiment but could be used to highlight the problem in a clearer way
# b. 2
# approximate the centre of each segment and store the x, y and z components in a new image which will store more parameters later
# this image's x coordinate is the segment id as specified in the labelled segment image (#1)
initsize={1+iM#1}
$initsize,10,1,1
vsize={$initsize*3}
eval ${-math_lib}"
coordinates_per_val=vector"$vsize"(0);
count_per_val=vector"$initsize"(0);
for(px=0,px<w#1,px++,
 for(py=0,py<h#1,py++,
  for(pz=0,pz<d#1,pz++,
   pos=i(#1,px,py,pz);
   coordinates_per_val[pos*3]+=px;
   coordinates_per_val[pos*3+1]+=py;
   coordinates_per_val[pos*3+2]+=pz;
   count_per_val[pos]++;
  );
 );
);
for(n=0,n<"$initsize",n++,
 coordinates_per_val[n*3]/=count_per_val[n];
 coordinates_per_val[n*3+1]/=count_per_val[n];
 coordinates_per_val[n*3+2]/=count_per_val[n];
 I(n,0)=coordinates_per_val[n*3];
 I(n,1)=coordinates_per_val[n*3+1];
 I(n,2)=coordinates_per_val[n*3+2];
);
"
# add pseudo-randomised parameters for each segment id to the parameters image
sh. 3,9,0,0 f. ">begin(randm=u(100,200);randa=u(1,3));srand((x+(randa)+(u))*(randm+y));u(-1,1)"
eval. "begin(multvec=["$xoffr","$yoffr","$zoffr","$rotar","$rotxxr","$rotxyr","$rotxzr"];
addvec=["$xoffc","$yoffc","$zoffc","$rotac","$rotxxc","$rotxyc","$rotxzc"]);
for(n=0,n<w,n++,
strip=crop(n,0,0,0,1,7,1,1);
draw(#4,(strip*multvec)+addvec,n,0,0,0,1,7,1,1,1))"
rm.
# get the edges, subtract a smoothed copy in order to create a mask for the anti-aliasing
+gradient_norm[1] f. "i>0?255:0"
smooth. $aa,${18-22},0
/. 255
# let's put all of this together now
f[0] "critical(begin(const spec=s;aa="$aa";
cubism(id,xx,yy,zz)=( # this function warps a given pixel using the parameters assigned to a specified id within the parameters image
params=crop(#3,id,0,0,0,1,10,1,1);
# get segment centre
xc=params[0];
yc=params[1];
zc=params[2];
# get offsets
xoff=params[3];
yoff=params[4];
zoff=params[5];
# get rotation angle and axis
ang=params[6];
xa=params[7];
ya=params[8];
za=params[9];
# rotate and offset
vx=xx-xc-xoff;
vy=yy-yc-yoff;
vz=zz-zc-zoff;
pix=[xa,ya,za];
select=(rot(asin(pix/norm(pix))*2/pi,ang)*[vx,vy,vz])+[xc,yc,zc];
# return the new pixel value
I(select,1,3)));
# anti-aliasing only if the setting's enabled and we're at a pixel where the mask (#4) has a non-zero value
(aa&&(i(#4)>0))?(
# get the displacements necessary to reach the nearest pixel on the other side of the nearest edge
dist=i(#2,x,y,z,0);
xdist=-i(#2,x,y,z,1)*dist;
ydist=-i(#2,x,y,z,2)*dist;
zdist=-i(#2,x,y,z,3)*dist;
# trilinear interpolation to determine the value of the nearest pixel behind the nearest edge
# we don't have to cubism() eight times for each pixel here since we can instead make a weighted histogram of ids
idlist=vector(#8,-1);
freqlist=vector(#8,0);
idnum=0;
xint=floor(xdist);yint=floor(ydist);zint=floor(zdist);
xo=xdist-xint;yo=ydist-yint;zo=zdist-zint;
gridmult=[(1-xo)*(1-yo)*(1-zo),xo*(1-yo)*(1-zo),(1-xo)*yo*(1-zo),xo*yo*(1-zo),(1-xo)*(1-yo)*zo,xo*(1-yo)*zo,(1-xo)*yo*zo,xo*yo*zo];
for(pz=0,pz<2,pz++,
 for(py=0,py<2,py++,
  for(px=0,px<2,px++,
   curr=i(#1,x+xint+px,y+yint+py,z+zint+pz,0,1,1);
   pos=find(idlist,curr);
   pos==-1?(idnum+=1;pos=find(idlist,-1);idlist[pos]=curr);
   freqlist[pos]+=gridmult[pz*4+py*2+px];
  );
 );
);
val=0; # val=vector(#spec,0);
for(n=0,n<idnum,n++,
# construct the final value for the interpolated pixel
val+=idlist[n]*freqlist[n]);
# interpolate between that pixel and the current one in accordance with the AA mask
lerp(val,i(#1,x,y,z,0,1,1),i(#4))):
# if we don't need any AA, we can just use the current pixel
(i(#1,x,y,z,0,1,1)))
"
# clean up now that we're done
display
rm[^0]
endl done
to_rgb
n 0,255
fx_jr_rep_cubism_preview:
fx_jr_rep_cubism ${2-23}

This is based on the procedure I used to try and get the value of the pixel just beyond the other side of the boundary. It’ll operate on any input image. Why does it only work when I use -(i+n) when n is larger than 1.5?

mosaic ,
label
+l
+gradient_norm f. "i>0?1:0"
distance. 1
+gradient_orientation. 3
f[1] "-(i+1)"
a[1-4] c
f.. "dist=i(#1,x,y,z,0);
xdist=round(i(#1,x,y,z,1)*dist);
ydist=round(i(#1,x,y,z,2)*dist);
zdist=round(i(#1,x,y,z,3)*dist);
J(xdist,ydist,zdist)"
endl
display

Also, is there really any other method that I can use to anti-alias this? I have one more method but it means I have to sacrifice any blurring and focus only on AA. How do I use the output of structuretensors?

If it works when n>1.5, then is it possible to exploit that to solve it by using a conditional?

How would I use a conditional in that case?

I don’t know, really. Maybe based on radial angle? I have no idea how to work with your script.

There’s no reason to care about radial angles. f[1] "-(i+1)" (which should be f[1] "-(i+1.5)" but as usual I messed it up again) just adds 1 to the distance-to-edge calculation before negating it all, and this is used later on in the last f block with the x, y and z components having been given by gradient_orientation.

I still don’t understand this. What does increasing and negating the intensity have to do with modifying the edge?