G'MIC exercises

Regarding the gradient norm stuff, here’s what I’ve got so far:

#@gui Gradient norm [JR's mod] : fx_jr_gradient_norm, fx_jr_gradient_norm_preview(1)
#@gui : Smoothness = float(0,0,10)
#@gui : Contrast = float(0.45,0,1.5)
#@gui : Min threshold = float(40,0,100)
#@gui : Max threshold = float(60,0,100)
#@gui : Negative = bool(0)
#@gui : Scale X = float(1,0,5)
#@gui : Scale Y = float(1,0,5)
#@gui : Interpolation = choice(2,"Nearest","Average","Bilinear","Bicubic")

fx_jr_gradient_norm :

if {$8==3}
inter=5
else 
inter={1+$8}
fi
if {$6!=1||$7!=1}
ww={w}
hh={h}
shift. -0.5,-0.5,0,0,1
r. {max(1,$6*w)},{max(1,$7*h)},100%,100%,$inter
fi
fx_gradient_norm. $1,$2,$3,$4,$5
if {$6!=1||$7!=1}
r. $ww,$hh,100%,100%,$inter
fi
fx_jr_gradient_norm_preview : 
fx_jr_gradient_norm $*

image

It took me much longer than it should’ve to figure out that I had to shift it a little.

And of course, I thought I’d make it into something even bigger and include my fix for the gradient norm filter and a new fix for the direction2rgb filter. Turns out there was yet another missing cut.

image

Edit: made it even better.

image

@Joan_Rake1

1. How did you arrive at the grid-like image?

2. The “embossed” lines radiating from the earth is neat. Would be even better if you could make the lines perpendicular to the tangent.

1. How did you arrive at the grid-like image?

This was just a simple and quite shallow gradient which I scaled up with nearest neighbour interpolation using GIMP - after which I applied the filter.

2. The “embossed” lines radiating from the earth is neat. Would be even better if you could make the lines perpendicular to the tangent.

It’s not intended to be an artistic filter, it’s more of a technical one. When the gradient norm is negative and the dir2rgb option is checked, it just computes the dir2rgb of what’s already there and anything which doesn’t seem to have a direction as such (which would normally appear black if one ran dir2rgb by itself) gets masked out as alpha and the alpha channel is removed using fx_solidify_td. Those ‘embossed lines’ are artefacts and nothing more, they can be removed by using the thresholds or the smoothness options.

I’ve got a newer version with some functional fixes which I’ll put up soon.

#@gui Vibrato Filter : fx_vibrato, fx_vibrato_preview(0)
#@gui : note = note("This filter is inspired by the Paint.NET plugin named Vibrato authored by MadJik for Paint.NET.")
#@gui : sep = separator()
#@gui : 1. X-Coordinate Pixel Based = float(1,0,2048)
#@gui : 2. Y-Coordinate Pixel Based = float(1,0,2048)
#@gui : 3. X-Coordinate Modulus Operation = float(1,0,32)
#@gui : 4. Y-Coordinate Modulus Operation = float(1,0,32)
#@gui : 5. Z-Modulus Operation = float(1,0,32)
#@gui : sep = separator(), Preview type = choice("Full","Forward horizontal","Forward vertical","Backward horizontal","Backward vertical","Duplicate top","Duplicate left","Duplicate bottom","Duplicate right","Duplicate horizontal","Duplicate vertical","Checkered","Checkered inverse")
#@gui : sep = separator(), note = note("<small>Author : <i>Reptorian</i>.      Latest update : <i>2018/09/22</i>.</small>")
fx_vibrato:
-normalize r.{{a = x+$1<0 ? w - abs(x+$1) : x + $1},{b = y+$2<0 ? w - abs(y+$2) : y + $2}, {c = a*$3 mod (w+$ 1)}, {d = b*$4 mod (h+$2)}, {X = (c/w-.5)*2}, {Y = (d/h-.5)*2},{ Z = (X - Y) * (X - 1) * (X + 1) * (Y - 1) * (Y + 1)},{1 - ( Z>.0001 ? Z : 1- Z*-1)}} n 0 255 mul $5 mod 256
endl done
fx_vibrato_preview :
gui_split_preview "fx_vibrato ${1--2}",$-1

My first work into Vibrato filter. Not working at the moment. How to do the equivalent of what I did for user-defined filter?

Also, I think that a working base of it could be the main base for generating many different patterns found in paint . NET forum as all I need is to plug in X and Y for different filters.

I’ve tried to get your filter at least working, but not working as it should:

#@gui Vibrato Filter : fx_vibrato, fx_vibrato_preview(0)
#@gui : note = note("This filter is inspired by the Paint.NET plugin named Vibrato authored by MadJik for Paint.NET.")
#@gui : sep = separator()
#@gui : 1. X-Coordinate Pixel Based = float(1,0,2048)
#@gui : 2. Y-Coordinate Pixel Based = float(1,0,2048)
#@gui : 3. X-Coordinate Modulus Operation = float(1,0,32)
#@gui : 4. Y-Coordinate Modulus Operation = float(1,0,32)
#@gui : 5. Z-Modulus Operation = float(1,0,32)
#@gui : sep = separator(), Preview type = choice("Full","Forward horizontal","Forward vertical","Backward horizontal","Backward vertical","Duplicate top","Duplicate left","Duplicate bottom","Duplicate right","Duplicate horizontal","Duplicate vertical","Checkered","Checkered inverse")
#@gui : sep = separator(), note = note("<small>Author : <i>Reptorian</i>.      Latest update : <i>2018/09/22</i>.</small>")
fx_vibrato:
repeat $! l[$>]
if {x+$1}
n 0,255 f "x+$1<0?a=w-abs(x+$1):a=x+$1;y+$2<0?b=w-abs(y+$2):b=y+$2;c=(a*$3)%(w+$1);d=(b*$4)%(h+$2);X=(c/w-.5)*2;Y=(d/h-.5)*2;Z=(X-Y)*(X-1)*(X+1)*(Y-1)*(Y+1);Z>.0001?1-Z:1-(-1*Z)" n 0,255 mul $5 mod 256
fi endl done
fx_vibrato_preview :
gui_split_preview "fx_vibrato ${1--2}",$-1

From here on out all that we need to do is change the equation in the fill command.

Yeah, I noticed some few issues here and there. I’ll have to experiment if I want this filter to happen. I did left some transparency to see if I can get some of the channel mixing effect from the pdn filter. Also, preview doesn’t match the actual result.

EDIT:

Now I figured out the shift, and the scale factor.

f "l=0;
m=0;
n=1;
o=1;
a=(x+l<0?w-abs(x+l):x+l);
b=(y+m<0?h-abs(y+m):y+m);
c=(a * (1/n))(w+l); d=(b * (1/o))(h+l);
X=(c/w-.5) * 2;
Y=(d/h-.5) * 2;
Z=(X-Y) * (X-1) * (X+1) * (Y-1) * (Y+1);
1-(Z>.0001?Z:1-Z * -1);
"

The modulo factor seem to be very difficult.

Aw man, there is no frac function in g’mic?! That would solve my issue.

You could use the wikipedia definition or use “abs(N)%1” depending on your needs. If there is a gmic equivalent I’m afraid I forgot it!

That’s not the same thing. frac (-.222) is 1-.222. Basically wanting a expression where it’s ignorant of negative number. Mod don’t work with negative numbers.

That’s why I linked wiki - you can define it how you wish, e.g. with “N-floor(N)”, “N%1” or even “N-int(N)” depending on what you want to happen with negatives.

@Reptorian I’m really trying to avoid writing the filter for you and spoil your fun :slight_smile:

But here’s a (hopefully) helpful hint… it might be easier to define a function “F(a,b)” first, then you can fill an [r,g,b] vector using the function:

f "
F(a,b) = ((a-b)*(a-1)*(a+1)*(b-1)*(b+1));

X = (x/w-.5)*2;
Y = (y/h-.5)*2;

red = F(2*X,3*Y);  # notice the multipliers, which could be parameters
green = F(X,Y);
blue = F(8*X,13*Y);

[red,green,blue]%256 # this is the vector written to the current pixel
"

Well, I did just have access to the source code. Instead of 1, it uses vY, and vX. If I’m not mistaken, it seem to involve vectors. I’m not good at understanding C# as opposed to C++, self-taught and partly trained in C++.

Going back to the code before the formula, I found:

vC1C2 = newToken.GetProperty&lt;DoubleProperty&gt;(PropertyNames.C1ZoomC2).Value

where C1 is Channel, and C2 is X or Y coordinate. This defines the x.


EDIT: Well, I am now closer to the answer.

For proof:

image

And the local code test :

f "n=2.5;
o=1;

X=(x/w-.5) * 2;
Y=(y/h-.5) * 2;

U=2^(n-1);
V=2^(o-1);

Z=(X-Y) * (X-U) * (X+U) * (Y-V) * (Y+V);
C=1-(Z>.0001?Z:1-Z * -1);

abs(C)>1?C-int(C):C;
"

@garagecoder I’ll keep in mind of that, but for now, I’m just going my way as I find going my way seem more natural way to learn.

Well, it looks like I’ll be taking a while looking at quirks and seeing how I can make the vibrato filter better.

Wow, the contrast is difficult. EDIT: Contrast issue is fixed.

EDIT: Updated the code a little bit. While I can’t fix the little difference, I added more features than the original PDN filter.

f "n=2;
o=1;
e=0;
t=1;
f=1;
a=0;
b=1;

X=((a>0?w-x:x)/w-.5) * 2;
Y=((b>0?h-y:y)/h-.5) * 2;

g=1*10^(-f);

U=n;
V=o;

Z=((X-Y) * (X-U) * (X+U) * (Y-V) * (Y+V))+e;
D=t>0?(1-(Z>g?Z:1-Z * -1)):Z;
C=D*D;

F=abs(C)>1?C-int(C):C;1-F;
"

My guess is that there would be a need to use piece-wise function to flip the area to fix that bothersome area. But, I don’t think that would fix it.

2 Likes

Up-sampling

Explored resize a bit more. This time how interpolation affects up-sampling. Say, e.g., I enlarge rose: 4 times its size. With ImageMagick, I do

magick rose: rose.png
magick rose: -filter Lanczos -resize 400%x 7-im_lresize.png
magick rose: -filter Lanczos -distort Resize 400%x 8-im_ldistort.png

rose

With G’MIC, I use r2dx 400% for { 1-nearest | 2-average | 3-linear | 5-bicubic | 6-lanczos }.


Results

Using psnr as a measure, relative to nearest:
nearest and average are the same but not if I use an odd up-scale like 401%.
linear performs better than bicubic and lanczos, unless I don’t cut or normalize.
bicubic performs better than lanczos.

I used 2 simple IM lanczos methods for comparison.
– both do better than linear, bicubic and lanczos of G’MIC on psnr terms.
-resize is better than -distort Resize.

Relative to 8-im_ldistort, to save space, I will just output the psnr values instead of describing them again:

psnr: 24.571659088134766 (1-nearest)
psnr: 24.271333694458008 (2-average)
psnr: 25.755859375 (3-linear)
psnr: 25.46391487121582 (5-bicubic)
psnr: 25.19731330871582 (6-lanczos)
psnr: 46.529079437255859 (7-im_lresize)

Since we are interested in G’MIC here, it looks like the default interpolation method for r2dx, linear, is the right (i.e., closest) choice; but I do like IM’s results more (i.e., #7 and #8).


Images

It is important to make conclusions visually rather than solely relying on psnr. What observations can you make? Do they match with the points above? Note that the GIF does reduces the image quality and colour count. I have included PNGs for better examination.

One thing I would point out is that #3, #5 and #6 looks like we are zooming in, which I don’t like.

results

1-nearest 2-average 3-linear 5-bicubic 6-lanczos 7-im_lresize 8-im_ldistort

I think the zoom effect is due to the way resizing is handled at the border of images.

Consider an image with 4 pixels:

A B
C D

To double it is size you can move the original pixels to these positions and interpolate from there

A x B x
x x x x
C x D x
x x x x 

or move them to these positions and interpolate from there

A x x B
x x x x 
x x x x 
C x x D

I don’t think there is a right answer. it’s just a different compromise

That’s exactly the point. G’MIC uses first or second pixel organizations depending on the selected interpolation type. Linear, cubic and lanczos use the latter.
IMHO, it’s the best way to do, because it ensures for instance that

$ gmic "(255,0)" r 16,1,1,1,3

indeed generates an homogeneous linear ramp of values from 255 to 0, without doubling pixels or other weird things.

Maybe some additional testing. I’m just wondering why IM produces this output.

$ gmic "(255,0)" o test.png  # Produce a 2x1 image with white-black pixels
$ convert test.png -filter Lanczos -resize 16x test_im.png

I get something like :
IM_resize

(image[0] is the original filetest.png and image[1] is the im-resized image test_resized.png).

I don’t really get the logic of the upscaled values here. Image test_im.png starts with 4 completely white pixels, and end with 4 completely black pixels, where i would expect what G’MIC actually produces,
e.g

$ gmic test.png r 16,8,1,1,6 o test_gmic.png

output_comp

(here test_gmic.png is the image[2]).

I notice that people are testing IM with the Lanczos filter for enlargement. If no filter is specified, IM will use the Mitchell filter for enlargement (perhaps for a good reason).

To understand IM’s behaviour when resizing a pair of white-black pixels, I suggest you use HDRI and observe the pixel values. For example (Windows CMD syntax):

f:\web\im>%IMG7%magick xc:White xc:Black +append -resize "16x1^!" txt:

# ImageMagick pixel enumeration: 16,1,65535,srgb
0,0: (69721.6,69721.6,69721.6)  #FFFFFFFFFFFF  srgb(106.388%,106.388%,106.388%)
1,0: (68462.7,68462.7,68462.7)  #FFFFFFFFFFFF  srgb(104.467%,104.467%,104.467%)
2,0: (66456.8,66456.8,66456.8)  #FFFFFFFFFFFF  srgb(101.407%,101.407%,101.407%)
3,0: (63554.5,63554.5,63554.5)  #F842F842F842  srgb(96.9779%,96.9779%,96.9779%)
4,0: (59317.6,59317.6,59317.6)  #E7B6E7B6E7B6  srgb(90.5129%,90.5129%,90.5129%)
5,0: (53055.6,53055.6,53055.6)  #CF40CF40CF40  srgb(80.9576%,80.9576%,80.9576%)
6,0: (45430.5,45430.5,45430.5)  #B177B177B177  srgb(69.3225%,69.3225%,69.3225%)
7,0: (37066.6,37066.6,37066.6)  #90CB90CB90CB  srgb(56.56%,56.56%,56.56%)
8,0: (28468.4,28468.4,28468.4)  #6F346F346F34  srgb(43.44%,43.44%,43.44%)
9,0: (20104.5,20104.5,20104.5)  #4E884E884E88  srgb(30.6775%,30.6775%,30.6775%)
10,0: (12479.4,12479.4,12479.4)  #30BF30BF30BF  srgb(19.0424%,19.0424%,19.0424%)
11,0: (6217.39,6217.39,6217.39)  #184918491849  srgb(9.48713%,9.48713%,9.48713%)
12,0: (1980.54,1980.54,1980.54)  #07BD07BD07BD  srgb(3.02212%,3.02212%,3.02212%)
13,0: (-921.814,-921.814,-921.814)  #000000000000  srgb(-1.4066%,-1.4066%,-1.4066%)
14,0: (-2927.69,-2927.69,-2927.69)  #000000000000  srgb(-4.46737%,-4.46737%,-4.46737%)
15,0: (-4186.58,-4186.58,-4186.58)  #000000000000  srgb(-6.38832%,-6.38832%,-6.38832%)

Notice that the percentage values range from -6% to +106%. We have ringing. “-filter Lanczos” gives worse ringing. “-filter cubic” gives no ringing.

In anycase, even with the ‘default’ filter, IM produces an upscaled image that looks like the one I showed above : 4 filters white, 8 pixels interpolated, then 4 pixels black.
I now understand that it’s because the first and last values are not really interpolated due to the boundary conditions, but this could be a problem for some use cases.

Regarding @Iain’s comments, suppose we have an image of two pixels, and we resize to 10x1 pixels. Possible approaches are:

A . . . . B . . . .
A . . . . . . . . B
. . A . . . . B . .

… where each “.” will be calculated. IM takes the third approach. That is, the centre of each input pixel will move to some position in the output. For Lanczos this is simple:

f:\web\im>%IMG7%magick xc:White xc:Black +append -filter lanczos -resize "10x1^!" txt:

# ImageMagick pixel enumeration: 10,1,65535,srgb
0,0: (81878,81878,81878)  #FFFFFFFFFFFF  srgb(124.938%,124.938%,124.938%)
1,0: (75074.3,75074.3,75074.3)  #FFFFFFFFFFFF  srgb(114.556%,114.556%,114.556%)
2,0: (65535,65535,65535)  #FFFFFFFFFFFF  white
3,0: (53568.1,53568.1,53568.1)  #D140D140D140  srgb(81.7397%,81.7397%,81.7397%)
4,0: (39904.9,39904.9,39904.9)  #9BE19BE19BE1  srgb(60.891%,60.891%,60.891%)
5,0: (25630.1,25630.1,25630.1)  #641E641E641E  srgb(39.109%,39.109%,39.109%)
6,0: (11966.9,11966.9,11966.9)  #2EBF2EBF2EBF  srgb(18.2603%,18.2603%,18.2603%)
7,0: (-6.01708e-011,-6.01708e-011,-6.01708e-011)  #000000000000  srgb(-9.18147e-014%,-9.18147e-014%,-9.18147e-014%)
8,0: (-9539.29,-9539.29,-9539.29)  #000000000000  srgb(-14.556%,-14.556%,-14.556%)
9,0: (-16343,-16343,-16343)  #000000000000  srgb(-24.9378%,-24.9378%,-24.9378%)

Note that output at (2,0) and (7,0) are white and black (within rounding error).

If we want a linear ramp, “-filter Triangle” gives us that (with white and black in the same positions). “-filter Gaussian” is also useful, with no ringing so the white and black positions have moved.

Which is the best method for enlarging photo? I almost never do, so I have no experience.

A x x x x x x x x B
x x x x x x x x x x
x x x x x x x x x x 
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
C x x x x x x x x D

and solidify + blur (10x10) + downscale

75726f73b46b9f22a203e79a02326aac42ac99ed

Thanks your insight on up-sampling. I have been rereading the IM docs on resampling, etc.; in particular, https://www.imagemagick.org/Usage/filter/nicolas/.

My recent interest comes from my use of guided, which I like to use because of its simplicity. However, it can over-smooth even when radius is 1; so my solution is to enlarge, filter and then shrink. (Unfortunately, my laptop is unable to handle 2x enlarge for slightly higher resolution photos.) Have been figuring out which up-sampling and down-sampling techniques to use.

PS I also used this 2x, filter and 0.5x strategy in my experiments with gradient_norm above. Maybe it isn’t the best way but it is a way.