G'MIC exercises

Yeah, this is in the refs or stdlib, though David had to tell me personally too. :blush: It does make sense for it to be like that.

I did this, but it still doesn’t work. Here’s the version with no arguments needed. I replaced all instants of $1 with 3. A forever loop.

rep_test:
skip ${1=0}

sp cat

m "+_rep_tcr_create_restricted_palette: 
 {w},1,1,2,[0,x]
 
 eval[0] :\"
   v_col=I;
   color_sum=vector(#w#-1,0);
   repeat(s#-2,k,
    color_sum+=sqr(crop(#-2,0,0,0,k,w#-2,1,1,1)-v_col[k]);
   );
   p=argmin(sqrt(color_sum));
   i(#-1,p,0,0,0)++;
   I;
  \"
  
 s[-1] c
 pixelsort[-1] +,x,..
 crop[-1] {w#-1-3},{w#-1-1}
 map. ... rm..
 "
  
+pal 56

store. _palette

r[0] {ceil(w/4)*4},{ceil(h/8)*8},1,100%,0,3,.5,.5

if $1
 at[0] "$_palette
 +_rep_tcr_create_restricted_palette[-2,-1]
 index[0] [-1],.5,1
 rm[-2,-1]",4,8,1
else
 $_palette
 +_rep_tcr_create_restricted_palette[-2,-1]
 index[0] [-1],.5,1
 rm[-2,-1]
fi

EDIT: I can’t seem to replicate this with a smaller script.
EDIT: Same dimension including channel count. Only one layer output. Finally, it use only 0 CPU.

The goal is basically to find x number of colors that matches closest within the palette, then index it with the closest x number of colors within palette per m*n window.

EDIT: I added one input option. $1 == 1 means apply_tiles command is used, and $1==0 means no apply_tiles. Yes, the cat image has been resized before the problematic apply_tiles code, but that shouldn’t detract from the problem. The dimension doesn’t change after the resize command.

I’ve investigated and yes, there is a problem. Basically this is due to nested run() in mathematical expressions that makes G’MIC hang.
In your case, there is a first run() in command apply_tiles, and the second one in command pixelsort. As each run() is protected with the same mutex, a run() cannot be nested into another run().
This behavior is clearly not desirable.

Having a quick fix is another story though :slight_smile:
I think I’ll have to study this a bit before proposing a correct fix (and release 3.1.1 then).

Thanks for reporting!

1 Like

Found a fix. Compiling and testing more, then I think I’ll release new version 3.1.1.

1 Like

Version 3.1.1 is ready, and now accepts nested run().

Thank you so much, I can confirm it works:

image

I find that it’s terribly slow, but it is what it is.

Yes, interpreting an entire G’MIC pipeline containing an eval for each 4x8 patches of the image, using apply_tiles is slow.
It would be probably better to make it run as a single fill command, if possible.

I guess I can give a try, I don’t think it’s impossible.

This is the algorithm for Floyd-Steinberg dithering:

for each y from top to bottom do
    for each x from left to right do
        oldpixel := pixels[x][y]
        newpixel := find_closest_palette_color(oldpixel)
        pixels[x][y] := newpixel
        quant_error := oldpixel - newpixel
        pixels[x + 1][y    ] := pixels[x + 1][y    ] + quant_error × 7 / 16
        pixels[x - 1][y + 1] := pixels[x - 1][y + 1] + quant_error × 3 / 16
        pixels[x    ][y + 1] := pixels[x    ][y + 1] + quant_error × 5 / 16
        pixels[x + 1][y + 1] := pixels[x + 1][y + 1] + quant_error × 1 / 16

To my knowledge, index use Floyd-Steinberg dithering. I’m guessing quant_error would be used as '$2' in 'index'? If so, it doesn’t look impossible at all. Difficult, I guess, but not impossible.

Also, I realized I couldn’t sort an array based on another array in eval/fill easily, and that would be needed since I am restricting numbers of color per window. A new mathematical expression which is array_sort which does something similar to pixelsort in the math parser would be helpful here.

EDIT: Now I understand what Floyd-Steinberg does, and '$2' in 'index' seem to me is a multiplier next to '[7,3,5,1]/16'. That doesn’t solve sorting an array using another array as base for sorting within math parser though, but I will probably do that within math parser even if it isn’t easy.

Could you please link the source? Perhaps someone can help you suss out the algorithm and how to translate it to gmic script. Or is the code above the only thing you have?

It’s from Wikipedia: Floyd–Steinberg dithering - Wikipedia

Not much details, but I get it. It’s just sorting an array using values from another array is a problem, and something I don’t really want to try. That’s not part of the Floyd-Steinberg algorithm, but rather extracting the most common colors to replicate my above code. And then, the Floyd-Steinberg algorithm is next.

Thanks for clarifying.

I actually thought of a way to sort. I would need to use digit counter. Something like:

[0,.1,.2,.3,.4,.5,.6,.7,.8,.9,.11,.12,.13,.14,.15,.16,.17,....,.99,.101,.102]

Add frequency of match to this. Then sort array based on frequency of match, then subtract the int proportion, then convert the decimal proportion back to integer.

OEIS may be of help though. Here’s this - A052382 - OEIS

And here’s this too which may work - A067251 - OEIS

Second works beautifully. With division by (floor(log10(itself))+1), I get something like above.

Edit: On second thought, even if I extract the number after decimal point after sorting, I have to convert it back to integer from 0.n.

I’m going to try the vector_sort() idea by modifying cimg.h.

And unable to figure modifying Cimg.h out. :confused: I will try another way, and it might involve permute and pixelsort.

What are you having difficulty with in Cimg.h? I don’t know C++ but it looks like the functions simply build on each other incrementally and there is many versions of a general function to cover all the bases. When we script in G’MIC it is easier to organize the way we want it because things are mostly in one place under a command.

Basically reading how it works

I only get a few part of this:

            if (!std::strncmp(ss,"sort(",5)) { // Sort vector
              _cimg_mp_op("Function 'sort()'");
              s1 = ss5; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
              arg1 = compile(ss5,s1,depth1,0,bloc_flags);
              arg2 = arg4 = 1; arg3 = ~0U;
              if (s1<se1) {
                s0 = ++s1; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
                arg2 = compile(s1,s0,depth1,0,bloc_flags);
                if (s0<se1) {
                  s1 = ++s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
                  arg3 = compile(s0,s1,depth1,0,bloc_flags);
                  arg4 = s1<se1?compile(++s1,se1,depth1,0,bloc_flags):1;
                }
              }
              _cimg_mp_check_type(arg1,1,2,0);
              _cimg_mp_check_type(arg2,2,1,0);
              if (arg3!=~0U) _cimg_mp_check_type(arg3,3,1,0);
              _cimg_mp_check_type(arg4,4,1,0);
              p1 = _cimg_mp_size(arg1);
              pos = vector(p1);
              CImg<ulongT>::vector((ulongT)mp_sort,pos,arg1,p1,arg2,arg3,arg4).move_to(code);
              return_new_comp = true;
              _cimg_mp_return(pos);
            }

What I think I get:

  1. $n seem to be relative to arg_n.
  2. cimg_mp_check_type seem to define defaults. I guess that.
  3. CImg::vector((ulongT)mp_sort… This is where the sorting comes in.

It’s everything else I don’t get.

That being said, I resorted to looking at geeksforgeeks, and using iterative quicksort in hope of being able to sort vector using values from another vector. Kind of like [8,5,1,2] using [3,0,2,1] as sort reference would result in [5,2,1,8].

The ideal would be having something like sort as it is in current state now, but with an extra parameter being a vector as reference.

My thoughts (I may be wrong…): Where is $n? I can’t find it… Type defines argument type. Cimg defines the function and the input, arguments and output it takes.

!std::strncmp(ss,"sort(",5)

controls how many arguments it is allowed to take. This Cimg function translates to the math intepreter’s sort().

I’m saying $n is linked to argn.

That being said, I found my solution for sorting an array with another array, it’s not ideal:

rep_test:
eval "begin(
  partition(lo,ho)=(
  
   vi=vb[ho];
   ni=(lo-1);
   
   for(p=lo,p<=ho-1,++p,
    if(vb[p]<=vi,
     ++ni;
     swap(va[ni],va[p]);
     swap(vb[ni],vb[p]);
    );
   );
   
   swap(va[ni+1],va[ho]);
   swap(vb[ni+1],vb[ho]);
   ni+1;
  );
  
  quicksort_iterative(low,high)=(
  
   top=-1;
   
   stack[++top]=low;
   stack[++top]=high;
   
   while(top>=0,
    ho=stack[top--];
    lo=stack[top--];
    
    p=partition(lo,ho);
    
    if(p-1>1,
     stack[++top]=lo;
     stack[++top]=p-1;
    );
    
    if(p+1<ho,
     stack[++top]=p+1;
     stack[++top]=ho;
    );
    
   );
   
  );
  
 );
 stack=vector(#4,0);
 
 va=[8,5,1,2];
 vb=[3,0,1,2];
 
 print(va);
 print(vb);
 
 quicksort_iterative(0,3);
 
 print(va);
 print(vb);"

Yields:

C:\Windows\System32>gmic rep_test
[gmic]-0./ Start G'MIC interpreter.
[gmic_math_parser] va = (uninitialized) (compiled as 'vector4', memslot = 39)
[gmic_math_parser] vb = (uninitialized) (compiled as 'vector4', memslot = 44)
[gmic_math_parser] va = (uninitialized) (compiled as 'vector4', memslot = 39)
[gmic_math_parser] vb = (uninitialized) (compiled as 'vector4', memslot = 44)
[gmic_math_parser] va = [ 8,5,1,2 ] (size: 4)
[gmic_math_parser] vb = [ 3,0,1,2 ] (size: 4)
[gmic_math_parser] va = [ 5,1,2,8 ] (size: 4)
[gmic_math_parser] vb = [ 0,1,2,3 ] (size: 4)
[gmic]-0./ End G'MIC interpreter.

Nope… Something’s wrong, but closer to the answer. The second va should be [5,1,2,8].

EDIT: I had to add a 'swap(va[ni+1],va[ho]);' above the 'swap(vb[ni...]);'. Now it works.

It looks a lot cleaner than your previous attempt. I can actually read it. :stuck_out_tongue: :+1:

1 Like

I tested it more, it’s way too slow for much larger use. In comparison with current sort, that is. Wouldn’t matter much for 255 arrays or less though.

Yeah, you would have to understand CImg. Hope you do because you have lots of ideas that would benefit from compilation.

1 Like

Now, I have a new question. Can it be possible for sort to be modified to have option to return the index number? I think that may be the easiest fix. This would not require two vectors at all, and can be easily be utilized with fill to return a vector sorted by values of another vector instead of itself.