Estrarre RGB values senza img sampler

Carissimi, qualcuno sa dirmi come fare a scriptare un nodo in C# per estrarre le componenti RGB analogamente a quello che fa il nodo IMG sampler?

Prova a partire da qui:
2023-07-19 09_49_07-Window
c#_img_path_to_mesh.gh (3,9 KB)
cat

 private void RunScript(string path, ref object M, ref object W, ref object H)
  {
    System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(path);
    int width = (int) bmp.HorizontalResolution;
    int height = (int) bmp.VerticalResolution;
    int X = bmp.Width;
    int Y = bmp.Height;
    System.Drawing.Color[] colors = new System.Drawing.Color[X * Y];
    int i = 0;
    for(int y = Y - 1;y > -1;y--){
      for(int x = 0;x < X;x++){
        colors[i] = bmp.GetPixel(x, y);
        i++;
      }
    }
    Mesh m = Mesh.CreateFromPlane(Rhino.Geometry.Plane.WorldXY, new Interval(0, X - 1), new Interval(0, Y - 1), X - 1, Y - 1);
    m.VertexColors.AppendColors(colors);
    M = m;
    W = X; // Outputting image X size
    H = Y; // Outputting image Y size
  }

Grazie mille è già un grande aiuto!
Sono riuscito ad estrapolare tra gli output anche la lista dei valori RGB per pixel, ma la mia conoscenza di C# è molto limitata, e non riesco ad aggiungere come input variable una lista di punti per estrarre i valori RGB solo dei pixel limitrofi ai punti inseriti.
Non vorrei chiedere troppo ma se riuscissi a darmi un aiuto anche su questo fronte ti sarei davvero molto grato!

Certo!
Quello che ti serve è solo ed esattamente quello che fa il image sampler?
Tieni conto che lo farei con intervallo fisso 0 - 1 … ( però dimmi se lo vuoi variabile)

…

L’esempio che ho postato era solo ciò che avevo già pronto a casa…

Si esatto!
Sostanzialmente mi servirebbe un nodo unico in cui settando il path dell’immagine mi dia la dimensione dell’immagine (base e altezza in pixel), e i valori rgb separati a partire da una lista di punti.
Gran parte del lavoro é giá dentro l’esempio che mi hai mostrato prima! Manca solo l’estrazione dei canali r g e b a partire dai punti

Riguardo alla seconda domanda, l’intervallo fisso va benone, poi mi posso arrangiare io con il “remap domain” dopo se mi servono intervalli diversi.

Grazie davvero per la disponibilitá!

c#_bmp_path_evaluate.gh (7,6 KB)

  private void RunScript(string path, Point3d pt, ref object W, ref object H, ref object C)
  {
    System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(path);
    int w = bmp.Width;
    int h = bmp.Height;

    int x = (int) Math.Round(pt.X * (w - 1), 0);
    int y = (int) Math.Round((1 - pt.Y) * (h - 1), 0);

    C = bmp.GetPixel(x, y);

    W = w;
    H = h;
  }

Sembra fare esattamente quello che mi serve, ora non mi resta che capire come unire i due nodi che mi hai mandato in uno solo :slight_smile: ma per quello posso arrangiarmi io, grazie di tutto!
Non è che avresti un video tutorial o documento da consigliarmi in cui spiegano come funziona la logia dello scripting in C#? così la prossima volta che mi servirà non dovrò disturbare qualcun altro.
Grazie davvero! Riccardo

Ciao.
Guarda, sinceramente non ho mai usato manuali o guide quindi non ne conosco e non so consigliartene alcuno.

Ho imparato a furia di provare, googlando e chiedendo nel forum internazionale…

Tu non farti problemi, chiedi pure qua e se serve taggami pure

ok, sei troppo gentile, mi sento di approfittarmene a continuare a chiederti chiarimenti, ma se insisti ne approfitto, ti chiederei se mi puoi aiutare a capire come inserire anche l’output che mostra la mesh colorata (che era presente nel primo esempio che mi hai mandato) anche nel secondo esempio. Se provo a copiare e incollare la parte di codice da uno all’altro aggiornando le variabili input e Output continua a darmi errore, forse é perché hai usato variabili diverse per fare le stesse operazioni in un esempio e nell’altro?

Allorai, si, in effetti tra uno script e l’altro ho utilizzato stessi nomi per cose diverse, e quindi il codice non è più inter-compatibile.

Se noti, nel primo script faccio dei cicli for dove uso variabili “x” e “y”. Nel secondo script uso gli stessi nomi ma per cose diverse.


Questo fa un po tutto:
c#_bmp_path_mesh_evaluate.gh (5,4 KB)

 private void RunScript(string path, Point3d pt, ref object M, ref object W, ref object H, ref object C)
  {
    if(!System.IO.File.Exists(path)){
      this.Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Invalid file path!");
      return;
    }
    System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(path);
    if(bmp == null){
      this.Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Bitmap file not valid!");
      return;
    }

    int w = bmp.Width;
    int h = bmp.Height;
    W = w; // Outputting image X size
    H = h; // Outputting image Y size


    System.Drawing.Color[] colors = new System.Drawing.Color[w * h];
    int i = 0;
    for(int y = h - 1;y > -1;y--){
      for(int x = 0;x < w;x++){
        colors[i] = bmp.GetPixel(x, y);
        i++;
      }
    }
    Mesh m = Mesh.CreateFromPlane(Rhino.Geometry.Plane.WorldXY, new Interval(0, w - 1), new Interval(0, h - 1), w - 1, h - 1);
    m.VertexColors.AppendColors(colors);
    M = m;


    int u = (int) Math.Round(pt.X * (w - 1), 0);
    int v = (int) Math.Round((1 - pt.Y) * (h - 1), 0);
    C = bmp.GetPixel(u, v);
  }

In un’altro thread avevo messo un paio di esempi semplici di script c#.
Vedi qua e i post successivi.
E questo è un esempio di come riproporre il “Game of Life” in c#.

Grazie mille! Ă© tutto materiale prezioso per capire la logica di rhino script e c # in grasshopper! mi studierĂł i tre file attentamente, grazie ancora!
R

Ciao, sto testando il codice e ho un altro dubbio. In termini di efficienza cosa si può fare? Intendo rispetto all’image sampler node il nodo scriptato è decisamente più lento con una decina di punti ci mette un tempo per computare il risultato che è equivalente al tempo che ci mette l’image sampler originale per computarne migliaia, da cosa può essere dovuto?
Infine volevo charirmi un dubbio riguardo alle liste.
nel caso:
C = bmp.GetPixel(x, y);
se inserisco in x e y molteplici valori (diciamo che inseriso 3 punti quindi avrò x0, x1, x2 e y0, y1, y2) per ottenere C come una lista, devo fare un loop per appendere i nuovi risultati alla lista, oppure si appendono in automatico?

Ciao.

Domanda lecita. Giusto. Ottimo!

Qui entra in gioco un po una logica di base comune a tutti i componenti di grasshopper:
un componente può richiedere dati in input strutturati come singolo input, lista, o datatree.

Nel nostro caso, il nostro script c# era programmato per usare un singolo punto, e quindi l’intero script veniva ri-eseguito per ogni punto.
Ad ogni esecuzione lo script: verificava il percorso dell’immagine, la caricava come oggetto bitmap, costruiva una mesh (con migliaia e migliaia di vertici e colori!) ed estraeva un singolo colore in un punto… tutto questo per ogni singolo punto!
Qui si vede per 50 punti, 356millisecondi:


Nota come l’output M ci restituisce 50 mesh identiche… un bello spreco di risorse!


Questa è una banale prova rimuovendo la parte di codice che costruisce la mesh:
2023-07-22 17_40_32-Window
Il tempo di esecuzione è immensamente ridotto (costruire una mesh da migliaia di punti è faticoso!), ma vediamo ancora come l’intero script viene ri-eseguito per ogni punto (nota il messaggio “This component ran 50 times.”)


La modifica da fare è quindi impostare lo script perché i nostri punti vengano caricati tutti assieme, come lista:


… sarà quindi necessario modificare il codice di conseguenza:

  private void RunScript(string path, List<Point3d> pt, ref object M, ref object W, ref object H, ref object C)
  {
    if(!System.IO.File.Exists(path)){
      this.Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Invalid file path!");
      return;
    }
    System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(path);
    if(bmp == null){
      this.Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Bitmap file not valid!");
      return;
    }

    int w = bmp.Width;
    int h = bmp.Height;
    W = w; // Outputting image X size
    H = h; // Outputting image Y size

    System.Drawing.Color[] meshcolors = new System.Drawing.Color[w * h];
    for(int y = 0;y < h;y++){
      for(int x = 0;x < w;x++){
        meshcolors[y * w + x] = bmp.GetPixel(x, h - 1 - y);
      }
    }
    Mesh m = Mesh.CreateFromPlane(Rhino.Geometry.Plane.WorldXY, new Interval(0, w - 1), new Interval(0, h - 1), w - 1, h - 1);
    m.VertexColors.AppendColors(meshcolors);
    M = m;

    System.Drawing.Color[] colors = new System.Drawing.Color[pt.Count];
    for(int i = 0;i < pt.Count;i++){
      int u = (int) Math.Round(pt[i].X * (w - 1), 0);
      int v = (int) Math.Round((1 - pt[i].Y) * (h - 1), 0);
      colors[i] = bmp.GetPixel(u, v);
    }
    C = colors;
  }

2023-07-22 18_00_51-Window

Nota come ora, con anche 1000 punti, lo script viene eseguito una sola volta, una sola mesh costruita.

c#_bmp_path_mesh_evaluate V2.gh (5,8 KB)


Per andare piĂą veloci ancora serve passare al multithread.
Il metodo .GetPixel della classe Bitmap non tollera un accesso multithread, servirebbe quindi lavorare con il metodo .LockBits …

PS nota come nel mio codice uso degli array (li vedi quando ci sono le parentesi quadre [] dopo un “new” …) invece che delle liste.
Con una lista puoi usare il metodo .Add e aggiungi un elemento alla volta alla tua lista. Utile per quando non sai quanti elementi avrà la lista finale, ma è un metodo non compatibile con un programma multithreaded (più thread potrebbero voler aggiungere un elemento alla stessa lista nello stesso momento, non va bene).
Il nostro script invece usa degli array, perché sappiamo in anticipo quanto capienti saranno prima ancora di iniziare: i vertici della mesh sono tanti quanti i pixel dell’immagine, e i colori da estrarre tanti quanti i punti in input. Questo tornerà utile qualora volessimo convertire il nostro codice a multithreaded…

1 Mi Piace

Fantastico! Mi stai chiarendo una marea di concetti!
grazie davvero! Riguardo al multithread hai qualche link utile da visionare?

Eh, di nuovo … no … lol :sweat_smile:

sempre questo ^

PiĂą o meno ti direi che per ora puoi ignorare la cosa del multithread (salvo se hai assoluta urgenza di avere codice molto veloce).
Però puoi già iniziare a scrivere in un modo che sarà compatibile in futuro con il multithread:
fai array pre-allocati (magari prova a googlare questo “pre allocated arrays multithread”) e prova appunto a lavorare con array anziché liste.
O almeno, usa le liste ma fai i cicli for e non abusare dei foreach.

… ad ogni modo direi che hai altri passi, prima del multithread…
Se proprio hai voglia di curiosare, qua mi hanno dato dei consigli molto interessanti:

(questo problema del cast di tipo penso che sia strettamente presente su gh, non una cosa generale in programmazione…)

Grazie davvero, c’é tanto da imparare!