[Developer Corner] Improvement for creating image from a set of values

This morning, I’ve done this:

A bit of explanation :
In G’MIC, you have the possibility to insert a new image in the image list by specifying directly its pixel values, like:

$ gmic (1,2,3;4,5,6;7,8,9)

image

Here, it creates a 3x3 scalar image with values from 1 to 9.
That’s actually quite useful in a number of situations.
But there was a limitation: In such expressions, the order of axis delimiters where considered to be first the x-axis, then the y-axis, then the z-axis and finally the c-axis (channels).

It means that if I wanted to create a 3x1 image of RGB colors (e.g. Red, Green and Blue), I could not just write:

$ gmic (255^0^0,0^255^0,0^0^255)

Writing this would generate a 2x1 image with 7 channels.

The change I’ve made this morning modify this behavior, by first analyzing in the given expression what is the “natural” order of specified values for the axes (not always ‘xyzc’ then).
Then, the parsed values are placed in the image, following the “correct” order.

And now, you get the expected result:

$ gmic (255^0^0,0^255^0,0^0^255)

With this change, this also means you can create a matrix by specifying its coefficient column by column :

$ gmic (1;2;3,4;5;6,7;8;9)

Well that’s it. Nothing spectacular, but still a small improvement to the language !

Cheers.

3 Likes

Seems ambiguous.

What if some one wants to write using the unexpected behavior instead?

And coming to think of it, this would make me change the +pal command I did.

My proposed solution is to add an argument to enforce the new order or to tell G’MIC to check and pick the order.

Indeed, It is.

Do you have an example ?

Probably any of these might be broken:

"Black & White","(0,255^0,255^0,255)",\
"Red-Green-Blue","(255,0,0^0,255,0^0,0,255)",\
"Black with Red-Green-Blue","(0,255,0,0^0,0,255,0^0,0,0,255)",\
"Black & White with Red-Green-Blue","(0,255,255,0,0^0,255,0,255,0^0,255,0,0,255)",\
"Cyan-Magenta-Yellow","(0,255,255^255,0,255^255,255,0)",\
"Cyan-Magenta-Yellow-Black","(0,255,255,0^255,0,255,0^255,255,0,0)",\
"White-Cyan-Magenta-Yellow-Black","(255,0,255,255,0^255,255,0,255,0^255,255,255,0,0)",\
"Red-Green-Blue with Cyan-Magenta-Yellow","(255,0,0,0,255,255^0,255,0,255,0,255^0,0,255,255,255,0)",\
"1-Bit RGB","(255,0,255,0,0,0,255,255^255,0,0,255,0,255,0,255^255,0,0,0,255,255,255,0)",\

Those are directly from +pal command.

Not at all,
if, like here, you specify the separator first as , then ^ , then the natural order of the specified values will be x-axis first, then c-axis, which is exactly what you want here.

2 Likes

That’s been slightly annoying for many years, but I just accepted it. Good things come to those who wait :smiley:

I’m hoping there’s minimal performance impact (e.g. in a tight loop which creates a small image of given values), but I suppose it can be worked around in other ways by using a pre-existing image if there is.

Have to revisit the input tutorial… (maybe by next year?)

1 Like

Yeah, I see that.

Also, I made some test using this code as initial base, and then extracting input into another command, and doing echo ${img2base64\ 0,0} rm, and then comparing the output base64 code, and found that they are the same, which means it’s all good :

foo_create_imgs:
4,1,4,4 {whds#-1},1,1,1,x 
resize_as_image[-1] [-2],-1 
rm.. 
+rep_permutations 4 ('xyzc') map.. . rm. crop. 1,100% 
repeat w#-1 { 
 text={`I[#1,$">"]`} 
 +permute[0] $text 
} 
rm[1] 
echo ${img2base64\ 0,0} 
rm # Remove this to be able to extract input code within CLI

The above code generates different permutation of xyzc…

Maybe I should have used different versions of G’MIC as a real test, but I think as all permutations are generated, and they all pass, I think this change works.

Sorry.

Broke the toy.

Did it with slices.

Here is a colorful, 4 channel image. Depth = 1.

gosgood@lydia ~/gmic/src $ gmic '(255^0^0^255;0^255^0^255;0^0^255^255,255^255^0^255;255^0^255^255;0^255^255^255)' o. /dev/shm/slice_0.png

gmic_000000

Here is a monochrome, 4 channel image. Depth = 1

gosgood@lydia ~/gmic/src $ gmic '(255^255^255^255;127^127^127^255;63^63^63^255,31^31^31^255;15^15^15^255;7^7^7^255)' o. /dev/shm/slice_1.png

gmic_000001

Here are both together, 4 channel images, each to their own slice. Depth = 2

gosgood@lydia ~/gmic/src $ gmic '(255^0^0^255;0^255^0^255;0^0^255^255,255^255^0^255;255^0^255^255;0^255^255^255/255^255^255^255;127^127^127^255;63^63^63^255,31^31^31^255;15^15^15^255;7^7^7^255)' o. /dev/shm/slice_2.png
[gmic]./ Start G'MIC interpreter (v.3.3.6).=================================================================
==1796==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe69041b20 at pc 0x560bdece8471 bp 0x7ffe6902ade0 sp 0x7ffe6902add0
READ of size 4 at 0x7ffe69041b20 thread T0
    #0 0x560bdece8470 in gmic& gmic::_run<float>(gmic_library::gmic_list<char> const&, unsigned int&, gmic_library::gmic_list<float>&, gmic_library::gmic_list<char>&, gmic_library::gmic_list<float>&, gmic_library::gmic_list<char>&, unsigned int const*, bool*, char const*, gmic_library::gmic_image<unsigned int> const*, bool) /home/gosgood/gmic/src/gmic.cpp:14034
    #1 0x560bdec1e21c in gmic& gmic::_run<float>(gmic_library::gmic_list<char> const&, gmic_library::gmic_list<float>&, gmic_library::gmic_list<char>&, bool) /home/gosgood/gmic/src/gmic.cpp:4946
    #2 0x560bdec02de6 in gmic& gmic::run<float>(char const*, gmic_library::gmic_list<float>&, gmic_library::gmic_list<char>&) /home/gosgood/gmic/src/gmic.cpp:4920
    #3 0x560bdeac7c3a in main /home/gosgood/gmic/src/gmic_cli.cpp:237
    #4 0x7f66163f408f  (/lib64/libc.so.6+0x2408f)
    #5 0x7f66163f4148 in __libc_start_main (/lib64/libc.so.6+0x24148)
    #6 0x560bdeac5854 in _start (/home/gosgood/.local/bin/gmic+0x4a854)

Address 0x7ffe69041b20 is located in stack of thread T0 at offset 86384 in frame
    #0 0x560bdec27b29 in gmic& gmic::_run<float>(gmic_library::gmic_list<char> const&, unsigned int&, gmic_library::gmic_list<float>&, gmic_library::gmic_list<char>&, gmic_library::gmic_list<float>&, gmic_library::gmic_list<char>&, unsigned int const*, bool*, char const*, gmic_library::gmic_image<unsigned int> const*, bool) /home/gosgood/gmic/src/gmic.cpp:4954

  This frame has 1726 object(s):

gdetails.txt (59.9 KB)

SUMMARY: AddressSanitizer: stack-buffer-overflow /home/gosgood/gmic/src/gmic.cpp:14034 in gmic& gmic::_run<float>(gmic_library::gmic_list<char> const&, unsigned int&, gmic_library::gmic_list<float>&, gmic_library::gmic_list<char>&, gmic_library::gmic_list<float>&, gmic_library::gmic_list<char>&, unsigned int const*, bool*, char const*, gmic_library::gmic_image<unsigned int> const*, bool)
Shadow bytes around the buggy address:
  0x7ffe69041880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7ffe69041900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7ffe69041980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7ffe69041a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f2 f2
  0x7ffe69041a80: f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 00 04
=>0x7ffe69041b00: f2 f2 00 00[f2]f2 05 f2 f2 f2 00 04 f2 f2 00 04
  0x7ffe69041b80: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7ffe69041c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7ffe69041c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7ffe69041d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7ffe69041d80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==1796==ABORTING
gosgood@lydia ~/gmic/src $ 

Oooops.

This is a debug build:

commit 099e39e6f318ab96bcac096f1fbd1285073260c3 (HEAD -> develop, origin/develop)
Author: David Tschumperle <David.Tschumperle@unicaen.fr>
Date:   Tue May 14 10:39:58 2024 +0200
~~~
1 Like

That’s the famous “bug-after-release” day.
I actually thought it odd that nothing had happened yet.

I’ll fix that ASAP. Thanks!

Ouch, OK, this one hurts!

It’s basically a one-character fix.
I’m reuploading source package for 3.3.6 with this tiny fix included.

2 Likes

So I guess I have to rebuild gmic? Built it on 3.3.6 release.

OK. Commit cd17d7a looks good…

commit cd17d7aea30893dde6069fd252be7efd4f85246e (HEAD -> develop, origin/develop)
Author: David Tschumperle <David.Tschumperle@unicaen.fr>
Date:   Thu May 16 08:16:26 2024 +0200

    Auto-commit for release 3.3.6
gosgood@lydia ~/gmic/src $ gmic '(255^0^0^255;0^255^0^255;0^0^255^255,255^255^0^255;255^0^255^255;0^255^255^255/255^255^255^255;127^127^127^255;63^63^63^255,31^31^31^255;15^15^15^255;7^7^7^255)' d , 
[gmic]./ Start G'MIC interpreter (v.3.3.6).
[gmic]./ Input image at position 0, with values (255^0^0^255;0^255^0^255;0^0^255^255,2(...),31^31^31^255;15^15^15^255;7^7^7^255) (1 image 2x3x2x4).
[gmic]./ Display image [0] = '255^255^255^255;127^127^127^2...'.
[0] = '(255^0^0^255;0^255^0^255;0^0^255^255,255^255^0^255;255^0^255^...':
  size = (2,3,2,4) [192 b of float32].
  data = (255,255;0,255;0,0/255,31;127,15;63,7^0,255;255,0;0,255/255,31;127,15;63,7^0,0;0,255;255,255/255,31;127,15;63,7^255,255;255,255;255,255/255,255;255,255;255,255).

gmic_000000

I’m good.

Yes.

Thanks :slight_smile:

BTW, is it possible to see the release date on official releases (i think pre-releases do)?
Like, gmic version could show 3.3.6 (stable) 2024-05-16?
But this would only be useful if the release date is shown on the website though.

Maybe in less than a year. Since the input tutorial conflates channels and slices in the order of axial traversals, it needs to be fixed sooner rather than later.

I was just preparing examples when the AddressSanitizer Monster visited.

Amended input documentation, methinks, features two rules of thumb — a good thing, that. I only have two thumbs.

First: (<pel><image boundary separator>…<pel>)

Second: Pels —

  1. Written with character class [0-9.eEinfa+-]

Third: Image boundary separators —

  1. column (label = x, dimension = w sep = , )
  2. row (label = y, dimension = h sep = ; )
  3. slice (label = z, dimension = d sep = / )
  4. channel (label = c, dimension = s sep = ^ ).

Fourth: Characters other than <pels> or <image boundary separators> are not allowed.

Fifth: Empty streams () are not allowed.

Sixth: Rules of Thumbs —

  1. Rule of Axial Order:
    a. When filling pels, traverse image axes in an order commensurate with the appearance of image boundary separators.
    b. Axial order increases with the appearance of each separator.
    c. Unseen separators are material to ordering only in that they default to a higher order than those seen. For the unseen, commensurate dimensional lengths are necessarily one (NB: it is not tenable to traverse axial dimensions of length=1).

  2. Rule of Dimensions: Image dimensions w×h×d×s are each equal to one, plus the longest sequence of like image boundary separators unbroken by any of a higher order.

Rule 1 sets axial traversal order. Rule 2 determines image size.


Example: (255^0^0;0^255^0^255;0^0^255)

  1. Axial order: cy?? A complete axial order is indeterminate but immateral. However x and z may be ordered in the abstract, they cannot be traversed. The pel data are necessarily in the first slice (because there are no other slices) and first column (because there are no other columns).

  2. Dimensions:
    a. w = 1 — longest sequence of , is zero. There can only be one column.
    b. h = 3 — longest sequence of ;, unbroken by , or /, is two.
    c. d = 1 — longest sequence of / is zero. There can only be one slice.
    d. s = 4 — longest sequence of ^, unbroken by ,, /, or ;, is three.

w×h×d×s = 1,3,1,4

This first example insists on a third Rule, and — darn! I’ve just run out of thumbs!

  1. Rule of Default Data: Pels not explicitly set are zero.

In the first example, the second row pixel has pels R=0, G=255, B=0 and A=255. This is so because these four pels are delimited by three channel boundary separators (^). It is a complete pixel specification and is opaque green.

Not so with the first and third row pixels. Only three pels, separated by two channel boundary separators, specify these pixels. Alpha channel specifications are missing and default to zero. These pixels are transparent.


Example: (255,0,0,0;0;0;0^0;0,255^0;0;0,0,255;0^0;0,0,0,255)

  1. Axial order: xycz
  2. Dimensions:
    w×h×d×s = 4,4,1,4

The four channel image cube has a through-the-center diagonal of four i=255 pels; the remaining 60 pels are i=0 by way of either explicit settings or various invocations of the Rule of Default Data.

Question: The second through fourth pels in the second example are all zero. For a shorter pel stream, why not delete them as well (by the Rule of Default Data)?

Answer: Sure! Go ahead! So long as you are fine with a w×h×d×s = 7,4,1,2 image (Exercise for the student: Review the so-modified stream against Rules 1 and 2).

There’s probably a fourth rule (I am now doubly deficient in thumbs).

  1. Rule of Separators:
    a. Image boundary separators must themselves be separated by at least one pel value.
    b. Data streams begin and end with pel values.

Maybe that’s it. Can’t afford any more thumbs. Posting here for now. It will help me on that fine future day when I actually revise the input tutorial. Have fun until then!

1 Like