Gå til innhold

Fargekart eller høydekoter med interpolering?


Anbefalte innlegg

Skrevet

Hei!

 

Jeg holder på å lage en makro i Excel som beregner støynivå i rommet ut fra gitte kilder og absorpsjonsegenskaper til rommet. Veldig spennende! :)

 

Men jeg er lei av VBA, og vil gjerne ha det over i .NET. Men da må jeg ha en fornuftig måte å presentere det på.

 

Jeg får ut data i tabellform som vist under (klikk for større):

08114325457933bd8e989.gif

 

 

Og med Excel sin "Surface" grafe, kan man lage et fint oversiktsbilde (selv om Excel ikke akkurat velger logiske farger som gir glidende overganger) slik:

0811464945793489863ff.gif

 

 

Gitt matrisen over, er det mulig å lage noe slikt i .NET?

Noen som vet hvordan?

 

 

På forhånd takk for hjelpen! :)

Videoannonse
Annonse
Skrevet

Det er greit å bruke GDI+ på et Image/Bitmap, men hvordan...

 

Som du ser dekker hver celle (i det øverste bildet) mer enn en pixel i det nederste. Excel genererer på en måte høydekurver mellom de aktuelle data. Det er det store problemet. ;)

Skrevet

Denne er ikke rask. Men skal være ganske enkel å forså ihvertfall :)

 

class Program
{
   static void Main(string[] args)
   {
       int[,] Orginal = new int[10, 10];
       int[,] Bigger = new int[20, 20]; 

       // Lager random data i Original
       Random r = new Random();
       for (int y = 0; y <= Orginal.GetUpperBound(1); y++)
           for (int x = 0; x <= Orginal.GetUpperBound(0); x++)
               Orginal[x, y] = r.Next(0, 99);

       // Kopierer fra Origianl over til Bigger
       ResizeAndInterpolate(Orginal, Bigger);

       // Debug :)
       Print(Orginal);
       Print(Bigger);

       Console.ReadKey();
   }

   private static void ResizeAndInterpolate(int[,] org, int[,] big)
   {
       for (int y = 0; y <= big.GetUpperBound(1); y++)
       {
           // Original Y koordinat
           double OrgY = (double)org.GetUpperBound(1) * (double)y / (double)big.GetUpperBound(1);
           // Øverste og nederste Y verdi
           int Y1 = (int)Math.Floor(OrgY);
           int Y2 = (int)Math.Ceiling(OrgY);
           // DeltaY (alltid mellom 0..1)
           double DY = OrgY - Y1;
           // Eks:
           // OrgY = 5.1  ==>
           // Y1 = 5, Y2=6, DY = 0.1

           for (int x = 0; x <= big.GetUpperBound(0); x++)
           {
               // Samma stæsj for X
               double OrgX = (double)org.GetUpperBound(0) * (double)x / (double)big.GetUpperBound(0);
               int X1 = (int)Math.Floor(OrgX);
               int X2 = (int)Math.Ceiling(OrgX);
               double DX = OrgX - X1;

               // Nå har vi 4 koordinater (X1,Y1 - X1,Y2 - X2,Y1 - X2,Y2)
               // og forholdet mellom disse (DX,DY)

               // Tallverdiene for Y på høyre og venstre side.
               double ValueY1 = org[X1, Y1] + (org[X1, Y2] - org[X1, Y1]) * DY;
               double ValueY2 = org[X2, Y1] + (org[X2, Y2] - org[X2, Y1]) * DY;

               // Interpolerer igjen for å finne Tallverdien mellom høyre og venstre side.
               double Value = ValueY1 + (ValueY2 - ValueY1) * DX;

               //   :D
               big[x, y] = (int)Value;
           }
       }
   }

   private static void Print(int[,] box)
   {
       for (int y = 0; y <= box.GetUpperBound(1); y++)
       {
           for (int x = 0; x <= box.GetUpperBound(0); x++)
               Console.Write(box[x, y].ToString("00") + " ");
           Console.WriteLine();
       }
   }
}

Skrevet

Skulle ønske jeg var smart slik at jeg øyeblikkelig kunne forstå hva de rutinene der gjør... :blush:

Men jeg tror jeg ser hva du gjør.

 

Forstår jeg det riktig om jeg sier at den tar en matrise, lager en større matrise og interpolerer aritmetisk for å fylle "tomrommet"? Ganske lurt! Da er det bare å lage en ny matrise basert på den gamle skalere denne slik at alle tall er mellom 0 og 1. Så er det bare å gange med en ColorMatrix :)

 

Da burde det være en kjapp sak å bygge den om til interpolering med logaritmiske verdier! :)

Skrevet
Skulle ønske jeg var smart slik at jeg øyeblikkelig kunne forstå hva de rutinene der gjør...  :blush:

Men jeg tror jeg ser hva du gjør.

 

Forstår jeg det riktig om jeg sier at den tar en matrise, lager en større matrise og interpolerer aritmetisk for å fylle "tomrommet"? Ganske lurt! Da er det bare å lage en ny matrise basert på den gamle skalere denne slik at alle tall er mellom 0 og 1. Så er det bare å gange med en ColorMatrix :)

 

Da burde det være en kjapp sak å bygge den om til interpolering med logaritmiske verdier! :)

7474588[/snapback]

 

Jeg lagde den slik at den skulle bli enkel å forstå. Jeg ville gjordt ting litt anderledes hvis hastighet skulle vært viktig.

 

Når det gjelder å mappe verdiene til farge så hørtes det veldig tungvindt ut å bruke ColorMatrix saken...

 

 

...

ResizeAndInterpolate(Original, Bigger);

 

// Lager fargekart...

Color[] FargeVerdi = new Color[MaxVerdi];

for(int i = 0; i < MaxVerdi; i++)

FargeVerdi = Color.FromArgb( i * 255 / MaxVerdi,0,0);

// Kan tegne mer fancy farger, men her mappes alle fargene bare fra sort til rød.

 

 

// Tegner stæsj over på et bilde...

for(int y = 0; y < Bigger.GetBounds(0); y++)

for(int x = 0; x < Bigger.GetBounds(0); x++)

g.PutPixel(x,y, FargeVerdi[ Bigger[x,y] ] );

Skrevet

Da kan jeg spørre: Hva ville du gjort om hastighet var viktig? ;)

 

 

Hvis jeg nå bare hadde fått DataGridView til å akseptere data jeg la inn, så hadde jeg snart kommet noen vei. Hvorfor skal ting være så mye vanskeligere enn i VS 2003?!?

Skrevet
Da kan jeg spørre: Hva ville du gjort om hastighet var viktig? ;)

7476600[/snapback]

 

Ikke regnet ut noe i innerloop'en. Stigningstallet kan regnes ut på utsiden, og heller bare plusses på for hver X verdi. Dvs. at innerloop'en ville kun inneholdt + istedenfor all koden med mye / og * som det står nå.

 

Hvis du skal ha det enda raskere så kan også dette optimaliseres med unsafe og unchecked kode...

 

I tillegg ville jeg ikke brukt putpixel for å tegne den, men det forstod du vel :) Se linken til GeirGrusom sin post...

Skrevet
Ikke regnet ut noe i innerloop'en. Stigningstallet kan regnes ut på utsiden, og heller bare plusses på for hver X verdi. Dvs. at innerloop'en ville kun inneholdt + istedenfor all koden med mye / og * som det står nå.

7476663[/snapback]

Selvfølgelig! :)

Takker så meget!

Skrevet

Allright! :D

 

Applikasjonen regner ut verdier, og gir fornuftige svar (et par småbugs som gir lydnivå på utsiden av rommet, men det henger med til mine testdata, så jeg tar det ikke så alvorlig siden lydnivået stemmer inni rommet).

 

Og igjen takk til Jorn som gav meg den hendige rutinen for interpoleringen. :)

 

 

MEN (og det er jo forbanna alltid et men):

I første omgang vil jeg ha dette til å fungere:

g.PutPixel(x,y, FargeVerdi[ Bigger[x,y] ])

... finnes ikke i VB.

 

Jeg vil bare enkelt og greit tegne en pixel. Umulig kan det jo ikke være...

Skrevet

dim b as new System.Drawing.Bitmap(Bigger.GetBounds(1), Bigger.GetBounds(0));

dim g as System.Drawing.Graphics = b.CreateGraphics();

for y as integer = 0 to Bigger.GetBounds(0)

for x as integer = 0 to Bigger.GetBounds(1)

g.PutPixel(?)

next

next

 

sånn ca.... må løpe nå, men lykke til! :)

Skrevet

Takk for all hjelp til mine to favoritt .NET guruer! :D

 

Jeg poster et skjermskudd "just for fun". Fremdeles mye arbeid igjen, men det fungerer!

:)

 

151626264582b09233bdb.jpg

Skrevet

Takker og bukker! Når det kommer fra deg så betyr det en del! :)

 

SetPixel er vel kanskje ikke det kjappeste, nei.

"Mottakermatrisen" er 4 ganger større enn største rom-dimensjoni meter (dvs. oppløsning = 0.25 ;)). Videre bruker jeg Jørns algoritme (mer eller mindre ukritisk) for å lage et større bilde for å få frem "diffuseringen" bedre (mattrisens dimensjoner er 20 ganger større enn "mottakermatrisen").

 

Jeg kjørte en "ekstrem" beregning med med et rom på 72x51x7.5 meter. Da tar selve beregningen ca. 16 minutter (mot ca. 2:30) og tegningen av bildet tar ca. 1 minutt (mot maksimalt et par sekunder). Men da er jo grafen riktignok på 33.177.600 pixler (72*4*20) så det er jo forståelig at det tar litt tid...

 

Men kommer du på noe som kan gjøre det kjappere, så tar jeg veldig gjerne i mot!

 

Jeg har enda ikke sett på hvilken oppløsning som faktisk er nødvendig, eller prøvd å optimalisere Jørns funksjon. Foreløpig har fokuset ligget på å få det til å kjøre skikkelig.

 

Et lite screenshot av det "store" rommet (klikk for større). Jeg tror skjermen gir bittelitt "hardere" linjer enn man har i virkeligheten, så modellen min må nok justeres litt. Men for "små" rom kan det se ut som om det stemmer ganske bra! :)

151712454582bb6dbd2e6.jpg

Skrevet (endret)

Vel, jeg vet ikke om dette går raskere, men jeg refererer til koden som jeg og jorn fikset i C# delen, dette er i C#, og funker bare i C#, men du kan eventuelt lage en dll:

using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace SharpGLTest
{
public delegate Color QuickDrawProc(int x, int y);
class QuickDraw
{
 protected Bitmap m_bmp;
 protected bool m_locked;
 public event QuickDrawProc ProcessPixel;
 protected BitmapData m_data;
 public unsafe void Draw()
 {
 	int* ptr;
 	LockBuffer();
 	ptr = (int*)m_data.Scan0.ToPointer();

 	for (int y = 0; y < m_data.Height; y++)
 	{
   for (int x = 0; x < m_data.Width; x++, ptr++)
   {
   	*ptr = ProcessPixel(x, y).ToArgb();
   }
 	}
 	UnlockBuffer();
 }
 public Bitmap Bitmap { get { return m_bmp; } }

 public QuickDraw(Bitmap bmp)
 {
 	if (!((bmp.PixelFormat == PixelFormat.Format32bppArgb) || (bmp.PixelFormat == PixelFormat.Format32bppRgb)))
   throw new Exception("Only 32-bit pixelformats are supported.");
 	m_locked = false;
 	m_bmp = bmp;
 }

 public bool Locked { get { return m_locked; } }

 public void LockBuffer()
 {
 	if (!m_locked)
 	{
   m_data = m_bmp.LockBits(new Rectangle(0, 0, m_bmp.Size.Width, m_bmp.Size.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, m_bmp.PixelFormat);
   m_locked = true;
 	}
 }

 public void UnlockBuffer()
 {
 	if (m_locked)
 	{
   m_bmp.UnlockBits(m_data);
   m_locked = false;
 	}
 }

 public unsafe void SetPixel(int x, int y, Color c)
 {
 	if (m_locked)
 	{
   int* ptr = (int*)m_data.Scan0.ToPointer();
   ptr += y * m_data.Width + x;
   *ptr = c.ToArgb();
 	}
 }
}
}

 

Det som er spesielt, er at den bruker pekere for å sette data, pluss at den kun støtter 32-bit, og burde gå ganske raskt.

 

Den kan brukes på to måter:

Enten legg inn en event handler på ProcessPixel, eller bruk SetPixel funksjonen i klassen.

 

Du kan prøve, vet ikke om det går noe raskere, men inntrykket jeg har av dette, er at det går svinaktig mye raskere en tilsvarende med Bitmap.SetPixel

 

Edit:

Du må kalle QuickDraw.LockBuffer() før du kan bruke SetPixel funksjonen, og QuickDraw.UnlockBuffer() før du kan bruke Bitmap egenskapen, for så lenge bitmapen er låst, kan ikke GDI+ bruke den.

Endret av GeirGrusom
Skrevet

Hehe, laget et eksempel:

public partial class frmColorTopia : Form
{
 private volatile bool m_app_running;
 private volatile bool m_app_stopped;
 private PictureBox pctDisp;
 QuickDraw quick_draw;
 Bitmap bmp;
 int w2;
 int h2;
 double degrees_x;
 double degrees_y;
 public frmColorTopia()
 {
 	this.Update();
 	this.Show();

 	Application.DoEvents();

 	//InitializeComponent();

 	pctDisp = new PictureBox();

 	pctDisp.Dock = DockStyle.Fill;

 	pctDisp.Paint += new PaintEventHandler(Form1_Paint);

 	this.Text = "ColorTopia";

 	Controls.Add(pctDisp);



 	bmp = new Bitmap(256, 256);

 	w2 = bmp.Size.Width / 2;
 	
 	h2 = bmp.Size.Height / 2;

 	degrees_x = Math.PI / w2;
 	degrees_y = Math.PI / h2;

 	quick_draw = new QuickDraw(bmp);
 	quick_draw.ProcessPixel += new QuickDrawProc(quick_draw_ProcessPixel);

 	m_app_stopped = false;

 	RunApp();

 	quick_draw.UnlockBuffer();

 }
 int xx = 1;
 int yy = 256;
 Color quick_draw_ProcessPixel(int x, int y)
 {
 	/*double fx = (Math.Cos(x * degrees_x) * Math.Sin(xx * degrees_x))  * 127 + 127;
 	double fy = (Math.Cos(y * degrees_y) * Math.Cos(yy * degrees_y)) * 127 + 127;
 	double fz = (Math.Cos(x * degrees_x) * Math.Cos(xx * degrees_x))* 127 + 127;

 	return Color.FromArgb((int) fx, (int)fy, (int)fz);*/
 	return Color.FromArgb(((x + 1) & (y + 1)) % 256, ((xx + x) & (yy + y)) % 256, ((yy + x) & (yy + y)) % 256);
 	
 }

 public void RunApp()
 {
 	m_app_running = true;
 	while (m_app_running)
 	{
   Application.DoEvents();
   xx++;
   yy--;
   if (yy < 0)
   	yy = 256;
   if(xx > 255)
   	xx = 0;
   quick_draw.Draw();
   pctDisp.Invalidate();
 	}
 	m_app_stopped = true;
 }

 private void Form1_Load(object sender, EventArgs e)
 {

 }

 private void Form1_Paint(object sender, PaintEventArgs e)
 {
 	bool was_locked = false;
 	if (quick_draw.Locked)
 	{
   quick_draw.UnlockBuffer();
   was_locked = true;
 	}

 	e.Graphics.DrawImage(bmp, 0, 0);

 	if (was_locked)
   quick_draw.LockBuffer();


 }

 private void frmColorTopia_FormClosing(object sender, FormClosingEventArgs e)
 {

 }
}

Skrevet

Nydelig! :D

(Planen var å prøve dette i helga, men sykdom, julebord og "dagen-derpå" satte en effektiv stopper for det)

 

Av generell nysgjerrighet: Har er det som gjør at dette ikke fungerer i VB.NET?

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
  • Hvem er aktive   0 medlemmer

    • Ingen innloggede medlemmer aktive
×
×
  • Opprett ny...