Konwersja kodu c++ na c# heightmap

0

Cześć,

mam dwie takie same biblioteki jedna w c++, druga w c#. Mam napisaną funkcje w c++ i próbuję stworzyć taką samą w c#, ale nie wiem jak ugryźć memcpy i jak dokładnie ta funkcja działa.

void loadHeightmapFromImage(TerrainInfo& info, const Image& image)
  {
    uint bpp = 0;
    bool flip = false;

    switch (image.getFormat())
    {
    case PF_BYTE_A: case PF_BYTE_L:
      bpp = 1; break;
    case PF_BYTE_LA: case PF_L16:
      bpp = 2; break;
    case PF_BYTE_RGB:
      bpp = 3; break;
    case PF_BYTE_BGR:
      bpp = 3; flip = true; break;
    case PF_BYTE_RGBA:
      bpp = 4; break;
    case PF_BYTE_BGRA:
      bpp = 4; flip = true; break;
    default:
      OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Can't use the given image's format.", "loadHeightmapFromImage");
    }

    size_t size = image.getWidth() * image.getHeight();
    unsigned int maxVal = (1 << (bpp*8)) - 1;
    std::vector<float> data (size);
    const uchar* imageData = image.getData(); // definicja - const uchar * getData() const; 

    for (size_t i = 0; i < size; ++i)
    {
      uchar read[4] = {0, 0, 0, 0}; // nie rozumiem tego? po co taka tablica?
      // TODO: Make this big endian aware/compatible
      memcpy(read, imageData, bpp); // czytamy dane 
      imageData += bpp; // po co tutaj dodawana jest ta wartość do danych?
      if (flip) // zamieniamy w zależności od formatu pliku 
        swap(read[0], read[2]);
      unsigned int val = * ((unsigned int*)read); // tablica do int? co to ma na celu?
      data[i] = float(val) / maxVal;
    }

    info.setHeightmap(image.getWidth(), image.getHeight(), data);
  } 

Moja funkcja w c#

unsafe static public void loadHeightmapFromImage(TerrainInfo info, Image image)
        {
            int bpp = 0;
            bool flip = false;

            switch (image.Format)
            {   
                case PixelFormat.PF_BYTE_A: case PixelFormat.PF_BYTE_L:
                    bpp = 1; 
                break;

                case PixelFormat.PF_BYTE_LA: case PixelFormat.PF_L16:
                    bpp = 2; 
                break;

                case PixelFormat.PF_BYTE_RGB:
                    bpp = 3; 
                break;

                case PixelFormat.PF_BYTE_BGR:
                    bpp = 3; flip = true; 
                break;

                case PixelFormat.PF_BYTE_RGBA:
                    bpp = 4;
                break;

                case PixelFormat.PF_BYTE_BGRA:
                    bpp = 4; flip = true; 
                break;

                default:
                   LogManager.Singleton.LogMessage(OgreException.LastException.FullDescription);
                break;
            }

            int size = Convert.ToInt32(image.Width * image.Height);
            int maxVal = (1 << (bpp*8)) - 1;
            float[] data = new float[size];
           
            // tutaj nie mam pomysły jak to zrobić 
            // w c# mamy image.Data, a typ to byte*

            info.setHeightmap(Convert.ToInt32(image.Width), Convert.ToInt32(image.Height), data);
        }
2

Lepiej będzie, jeżeli pozbędziesz się unsafe i będziesz operować na IntPtr. Także jeżeli możesz zmodyfikować klasę Image, by dawała ci IntPtr zamiast byte* to lepiej to zrobić.

IntPtr pData = new IntPtr(image.Data); // albo IntPtr pData = image.Data; jak możesz dokonać modyfikacji

for (int i = 0, dataRead = 0; i < size; i++, dataRead += depth)
{
    byte[] buffer = new byte[4];
    Marshal.Copy(pData, buffer, 0, depth);

    if (flip)
    {
        byte temp = buffer[0];
        buffer[0] = buffer[2];
        buffer[2] = temp;
    }

    uint val = BitConverter.ToUInt32(buffer, 0);

    // ...
}
1

Można z image.Data zrobić tablicę bajtów byte[] zamiast bawić się w unsafe'y. Wczytywać można standardowym FileStream.

1

unsafe static public void loadHeightmapFromImage(TerrainInfo info, Image image)

czym jest TerrainInfo (twoja klasa?) i czym jest tutaj Image? czy to też jakiś twój typ, czy standardowy System.Drawing.Image?
chyba jednak nie standardowy, bo później odwołujesz się do pola Format, którego w System.Drawing.Image nie ma...

tak w miarę dosłownie, to końcówka twojego kodu będzie wyglądać na przykład tak:

   int size = Convert.ToInt32(image.Width * image.Height); // po co ten Convert? jakiego typu jest Width i Height?
   uint maxVal = (1 << (bpp*8)) - 1;
   float[] data = new float[size];
   byte* imageData = image.getData();
   byte* read = stackalloc byte[4];  // przed pętlę, bo szkoda tracić czas na alokację za każdym przebiegiem
   for (int i = 0; i < size; ++i)
   {
      for (int n=0; n<4; n++)      // memcpy()
         read[n] = imageData[n];
      imageData += bpp;
      if (flip)
      {
         byte tmp = read[0];    // swap()
         read[0] = read[2];
         read[2] = tmp;
      }
      uint val = *(uint*)read;
      data[i] = float(val) / maxVal;
    }
   info.setHeightmap(Convert.ToInt32(image.Width), Convert.ToInt32(image.Height), data); // znowu: po co ten convert?

Mogą być błędy.
Ale to nie znaczy, że to jest dobry kod C#. Przede wszystkim, należałoby więcej używać wbudowanych klas, chociażby tego Image...

0

Rev - nie ma szans jest to skompilowana biblioteka.
adf88 - dzięki zobaczę co da się z tym zrobić tylko nie mam rozmiaru tego pola Data.
Azarien - TerrainInfo jest to moja klasa którą właśnie przepisuje bo jest w Ogre3D, a nie ma w Mogre. Image jest to jakiś twór Ogre który pozwala na pobranie tylko w ten sposób danych bądź na podstawie kolorów z kanałów.

Dzięki za wskazów, będę kombinował.

0

Wszystko jest już prawie gotowe po testach wygląda na to, że zwracane wartości są takie same, wielkie dzięki! Nie rozumiem tylko jednej rzeczy:

// read = {255, 255, 255, 0}
int val = *(uint*)read;

Po tym 'rzutowaniu'? zmienna val = 16777215. Nie rozumiem tego jak to się dzieje? Możliwe, że mam jakieś braki w podstawach. Proszę kogoś o wyjaśnienie jak otrzymujemy taki wynik?

1

Wiesz w jaki sposób przedstawiona jest pamięć operacyjna komputera? Jako komórki bajtów. Zwykły int składa się z czterech takich bajtów następujących po sobie i dzięki temu rzutowaniu możemy te cztery komórki bajtów przedstawić jako liczbę (wartość takiej liczby jest ustala tak, jakby te cztery bajty były czterema cyframi liczby o podstawie 256).

0

Teraz rozumiem, chodzi o system binarny, a int jest 32 bitowy... dzięki :)

0

nie wiem co to za Ogry, ale czy nie dałoby się tej biblioteki w C++ skompilować jako C++/CLI i zostawić jak jest bez większego przepisywania?
bo ten kod przetłumaczony tak dosłownie na C# to jest straszny...

PS. jak się zastanowić, to ani stackalloc ani tablica nie są potrzebne. wystarczy tak:

   uint val;
   byte* read = (byte*)&val;
   for (int i = 0; i < size; ++i)
   {
      //  pętla bez zmian
      ...
      data[i] = float(val) / maxVal;
   }

to powinno działać szybciej.
w C++ można analogiczną zmianę zrobić…
…albo i nie można, ze względu na tzw. "strict aliasing".

0

Ogry to Ogre3D i jego wrapper Mogre :)
Próbowałem z C++/CLI, ale po skompilowaniu i wywołaniu funkcji z tej biblioteki dostaje:

System.IO.FileLoadEx​ception was unhandled
Message=Nie można załadować procedury zaimportowanej przez 'MET, Version=1.0.4556.242​06, Culture=neutral, PublicKeyToken=null'​.
Source=Phys
FileName=MET, Version=1.0.4556.242​06, Culture=neutral, PublicKeyToken=null
FusionLog=""
StackTrace:
w Phys.Engine.CreateTe​rrainCollison()
w Phys.Engine.createSc​ene() w C:\Users\Sannin\docume​nts\visual studio 2010\Projects\Phys\E​ngine.cs:wiersz 81
w Phys.Program.Main(St​ring[] args) w C:\Users\Sannin\docume​nts\visual studio 2010\Projects\Phys\P​rogram.cs:wiersz 15
w System.AppDomain._nE​xecuteAssembly(Assem​bly assembly, String[] args)
w System.AppDomain.Exe​cuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
w Microsoft.VisualStud​io.HostingProcess.Ho​stProc.RunUsersAssem​bly()
w System.Threading.Thr​eadHelper.ThreadStar​t_Context(Object state)
w System.Threading.Exe​cutionContext.Run(Ex​ecutionContext executionContext, ContextCallback callback, Object state)
w System.Threading.Thr​eadHelper.ThreadStar​t()
InnerException: System.Runtime.Inter​opServices.COMExcept​ion
Message=Nie można odnaleźć określonej procedury. (Wyjątek od HRESULT: 0x8007007F)
ErrorCode=-214702476​9
InnerException:

Nie mam pojęcia dlaczego (szczerze mówiąc nie znam się na C++/CLI, a nie mam teraz czasu na naukę tego). W akcie desperacji przepisuję klasę.

0

Przegrzebałem trochę bibliotekę i po skończeniu wygląda to tak:

            int maxVal = (1 << (bpp * 8)) - 1;
            uint size = image.Width * image.Height;
            float[] data = new float[size];
            byte b, g, r, a, tmp;
            int i = 0;

            for (int row = 0; row < image.Width; row++)
            {
                for (int column = 0; column < image.Height; column++)
                {
                    b = (byte)(image.GetColourAt(column, row, 0).b * 255);
                    g = (byte)(image.GetColourAt(column, row, 0).g * 255);
                    r = (byte)(image.GetColourAt(column, row, 0).r * 255);
                    a = (byte)(image.GetColourAt(column, row, 0).a * 255);

                    if (!image.HasAlpha) 
                        a = 0;

                    if (flip)
                    {
                        tmp = b;
                        b = r;
                        r = tmp;
                    }
                    
                    byte[] onePixel = {b, g, r, a};

                    if (!BitConverter.IsLittleEndian) 
                        Array.Reverse(onePixel);

                    int val = BitConverter.ToInt32(onePixel, 0); 
                    data[i] = (float)val / maxVal;
 
                    i++;
                }
            }

Z tym, że jest jeden mały problem. W pewnym momencie jest coś takiego:
data[i] = (float)13689824 / 16777215;

W debugrze c++ wynik wynosi 0,81597716 natomiast w c# 0,815977156. Niby niewielkie przekłamanie, ale dla mnie istotne. Próbowałem już na milion sposobów to zaokrąglić do cyfry z c++, ale jak ustawiam precyzje na 8 to wychodzi 0,81597720. Nie mam pomysłu jak sobie z tym poradzić.

1 użytkowników online, w tym zalogowanych: 0, gości: 1