Tłumaczenie kodu z c/c++ na Pascala. Zamiana kolorów z BGR na RGB.

0

mam taki kod:

for (imageIdx = 0; imageIdx < bitmapInfoHeader->biSizeImage; imageIdx+=3)
{
  tempRGB = bitmapImage[imageIdx];
  bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
  bitmapImage[imageIdx + 2] = tempRGB;
}

i przerabiam go na pascala. Napisałem coś takiego:

repeat
  tempRGB := ImageData[i];
  ImageData[i] := ImageData[i + 2];
  ImageData[i + 2] := tempRGB;
  i := i + 3
until i < BMPImageHeader.biImageSize; 

I wyskakują mi błędy:
Error: Array type required

ImageData deklaruję w ten sposób

var
  ImageData : Pointer

a bitmapImage jest zadeklarowane tak:

unsigned char*bitmapImage

z c++ mało umiem ale wydaje mi się ze jest to deklaracja wskaźnika na tablice charów i dlatego pierwszy kod działa.

Jak zadeklarować w moim kodzie ImageData bym mógł zamienić składowe R i B by uzyskać format RGB z BGR i mieć wskaźnik na ImageData który potem będę mógł użyć w glTexImage2D?

Coś mi się widzi że będę musiał się pobawić wskaźnikiem na tablicę Byte. coś w stylu:

type
  PByteArray = ^array of Byte;
 

Ale nie jestem pewny czy to będzie działać z glTexImage2D.

Poniżej cały mój kod.

function LoadDataFromBMP(FileName : string) : Pointer;

const
  BITMAP_ID = $4D42;

type
  TBMPFileHeader = packed record  // Nagłowek pliku BMP.
    bfType      : Word;
    bfSize      : LongWord;
    bfReserved1 : Word;
    bfReserved2 : Word;
    bfOffBits   : DWord;
  end;

type
  TBMPImageHeader = packed record // Nagłówek obrazu BMP.
    biSize           : LongWord;
    biWidth          : LongInt;
    biHeight         : LongInt;
    biPlanes         : Word;
    biBitCount       : Word;
    biCompression    : LongWord;
    biImageSize      : LongWord;
    biXPelsPerMetter : LongInt;
    biYPelsPerMetter : LongInt;
    biClrUsed        : LongWord;
    biClrImportant   : LongWord;
  end;

var
  BMPFile        : TFileStream;
  BMPFileHeader  : TBMPFIleHeader;
  BMPImageHEader : TBMPImageHeader;
  ImageData      : Pointer;
  i              : LongWord;
  tempRGB        : Byte;
begin
  BMPFile := TFIleStream.Create(Filename, fmOpenRead);
  BMPFIle.ReadBuffer(BMPFileHeader, SizeOf(BMPFileHeadeR));
  if BMPFileHeader.bfType <> BITMAP_ID then
  begin
    FreeAndNil(BMPFile);
    result := nil;
  end;
  BMPFile.ReadBuffer(BMPImageHeader, SizeOf(BMPImageHeader));
  BMPFile.Seek(BMPFileHeader.bfOffBits, soFromBeginning);
  GetMem(ImageData, BMPImageHeader.biImageSize);
  BMPFile.ReadBuffer(ImageData, BMPImageHeader.biImageSize);
  if ImageData = nil then
  begin
    FreeMem(ImageData, BMPImageHeader.biImageSize);
    FreeAndNil(BMPFile);
    result := nil;
  end;
  i := 0;
{  repeat
    tempRGB := ImageData[i];
    ImageData[i] := ImageData[i + 2];
    ImageData[i + 2] := tempRGB;                  <- W tej pętli jest błąd
    i := i + 3
  until i < BMPImageHeader.biImageSize;}
  result := ImageData;
end;
0

Też jestem noga w C++, ale z samego kodu widać, że zmienna, którą deklarujesz nie jest samym Pointerem. Wyraźnie widać, że jest to jakaś tablica, skoro po niej w nawiasach kwadratowych masz zmienną iteratora pętli. Ktoś lepiej znający C/C++ pewnie lepiej podpowie.

0

Dobra przejrzałem źródła ZenGL i to co myślałem ze wskaźnikiem na tablice Byte PByteArray = ^array of Byte powino działać. Zaraz idę potestować, ale mimo wszystko jak ktoś wpadnie na lepszy pomysł to niech się podzieli.

2

ImageData powinno być typu ^byte (PByte) - jeśli Delphi pozwala na indeksowanie wskaźników, (nie pamiętam, FPC pozwala na pewno).
W przeciwnym razie PByteArray albo własny typ tablicowy.

A tak w ogóle, co zresztą za każdym razem powtarzam ;-) polecałbym WIC (Windows Imaging Component) do ładowania bitmapy (w dowolnym formacie: bmp, jpg, png).
Kiedyś napisałem ładowanie tekstury dla OpenGL przez WIC, ale w C++. Nie wiem też czy Delphi ma gotowy unit do WIC.

0

@Azarien Masz rację PByteArray działa i funkcja zwraca Pointer więc wszystko działa tak jak chciałem. Paniki narobiłem a mogłem najpierw przejrzeć źródła ZenGL a potem jęczeć na forum. Tak czy inaczej dzięki za pomoc.

0

@babubabu - nie wiem czy tylko ja to widzę, ale ten Twój kod jest podejrzany, cztery rzeczy przykuły moją uwagę;

1. Co się stanie, jeśli poniższy warunek zostanie spełniony?

if BMPFileHeader.bfType <> BITMAP_ID then

Zwolnisz strumień, a w następnych liniach kodu spróbujesz z niego czytać dane:

begin
    FreeAndNil(BMPFile);
    result := nil;
  end;
  BMPFile.ReadBuffer(BMPImageHeader, SizeOf(BMPImageHeader));

Powinieneś tuż po ustawieniu wartości rezultatu wyjść z funkcji albo pominąć kolejne instrukcje; Piszesz pod Lazarusem, więc zamiast przypisywać nil to Result, równie dobrze możesz napisać Exit(nil) i pominiesz kolejne instrukcje w przypadku wczytania niepoprawnych danych;

2. Alokujesz pamięć dla wskaźnika ImageData, po czym pobierasz pod ten wskaźnik dane ze strumienia i dopiero sprawdzasz, czy wskaźnik jest nilem:

GetMem(ImageData, BMPImageHeader.biImageSize);
  BMPFile.ReadBuffer(ImageData, BMPImageHeader.biImageSize);
  if ImageData = nil then

Tutaj według mnie najpierw powinieneś sprawdzić czy pamięć poprawnie zaalokowano, a dopiero później próbować pod ten adres wczytywać dane ze strumienia; Co jest jeszcze dziwniejsze - jeśli wskaźnik jest nilem to zwalniasz po nim pamieć przez FreeMem:

if ImageData = nil then
  begin
    FreeMem(ImageData, BMPImageHeader.biImageSize);

Skoro jest tym nilem to co ma zostać zwolnione..?

3. Na koniec sama pętla - wykonać musisz zwykłego Swapa na odpowiednich kanałach (R i B); Według mnie przy rozmiarze obrazu większym niż jeden piksel Twoja pętla po prostu zawiedzie, bo zawsze wykona się tylko raz:

i := 0;

repeat
  tempRGB := ImageData[i];
  ImageData[i] := ImageData[i + 2];
  ImageData[i + 2] := tempRGB;
  i := i + 3
until i < BMPImageHeader.biImageSize;

Już po pierwszej iteracji pętli warunek jest poprawny, bo i jest mniejsze od rozmiaru danych i pętla zakończy działanie; Do tego celu powinieneś albo zmienić warunek zakończenia pętli repeat:

until i >= BMPImageHeader.biImageSize;

albo skorzystać z pętli while:

while i < BMPImageHeader.biImageSize do //tu trzeba by sprawdzić ile odjąć od rozmiaru

4. Heh, czwartą jeszcze dopiszę - zgadnij co się stanie ze strumieniem BMPFile jeśli kod wykona się do końca? Wyciek murowany;

Ten kod jest słabo zabezpieczony - brakuje bloków try .. finally, brak obsługi wyjątków itp.; Do tego brak znajomości procedury Inc - czas to poprawić :]

Poza tym tak jak poprzednicy podpowiedzieli - skorzystaj z PByteArray; Możesz też zadeklarować sobie wskaźnik-token, który będziesz przesuwał od pierwszego elementu macierzy aż do samego końca bufora; Zyskasz być może na szybkości, nie tracąc przy tym prostoty użycia i czytelności kodu.

0

@furious programming wszędzie przy obsłudze plików proponujesz TFileStream więc może Ty będziesz wiedział co jest nie tak.

Oto kolejna wersja mojej funkcji (tak wiem brakuje bloków try..finally):

type
  TBMPImageHeader = packed record // Nagłówek obrazu BMP.
    biSize           : LongWord;
    biWidth          : LongInt;
    biHeight         : LongInt;
    biPlanes         : Word;
    biBitCount       : Word;
    biCompression    : LongWord;
    biImageSize      : LongWord;
    biXPelsPerMetter : LongInt;
    biYPelsPerMetter : LongInt;
    biClrUsed        : LongWord;
    biClrImportant   : LongWord;
  end;

var
  BMPFile        : TFileStream;
  BMPFileHeader  : TBMPFileHeader;
  BMPImageHEader : TBMPImageHeader;
  ImageData      : PByteArray;
  i              : LongWord;
  tempRGB        : Byte;
begin
  BMPFile := TFIleStream.Create(Filename, fmOpenRead);
  BMPFIle.ReadBuffer(BMPFileHeader, SizeOf(BMPFileHeadeR));

  if BMPFileHeader.bfType <> BITMAP_ID then
  begin
    FreeAndNil(BMPFile);
    result.Height := 0;
    result.Width := 0;
    result.Data := nil;
    exit;
  end;

  BMPFile.ReadBuffer(BMPImageHeader, SizeOf(BMPImageHeader));
  BMPFile.Seek(BMPFileHeader.bfOffBits, soFromBeginning);
  GetMem(ImageData, BMPImageHeader.biImageSize);

  if ImageData = nil then
  begin
    FreeAndNil(BMPFile);
    result.Height := 0;
    result.Width := 0;
    result.Data := nil;
    exit;
  end;

  BMPFile.ReadBuffer(ImageData, BMPImageHeader.biImageSize); // <- O tutaj
  FreeAndNil(BMPFile);

  i := 0;
  while i < BMPImageHeader.biImageSize do
  begin
    tempRGB := ImageData^[i];
    ImageData^[i] := ImageData^[i + 2];
    ImageData^[i + 2] := tempRGB;
    Inc(i, 3);
  end;

  result.Width := BMPImageHeader.biWidth;
  result.Height := BMPImageHeader.biHeight;
  result.Data := ImageData;
end

;

Przy linijce zaznaczonej w kodzie wywala mi EReadError. Wygooglałem, że ten wyjątek wywala gdy brakuje danych do wczytania albo przy wczytywaniu błędnych danych komponentu.
Ja komponentu nie wczytuje, więc by wynikało, że brakuje danych czyli błąd może leżeć w

BMPFile.Seek(BMPFileHeader.bfOffBits, soFromBeginning);

przy czym nagłówki pliku są pobierane poprawnie bo sprawdzałem zrzucając dane zapisane w nagłówkach do pliku. Myślałem, że GetMem źle pamięć alokuje i chciałem zmienić na SetLength bo w końcu PByteArray to tablica, jednak wtedy przy SetLength sypało type mismatch. Co robię źle?

-----------------EDIT-----------------

Dobra ominąłem występowanie wyjątku w ten sposób:

var
  BMPFile        : TFileStream;
  BMPFileHeader  : TBMPFileHeader;
  BMPImageHEader : TBMPImageHeader;
  ImageData      : PByteArray;
  i              : LongWord;
  tempRGB        : Byte;
  Plik           : File of Byte;
begin
  BMPFile := TFileStream.Create(Filename, fmOpenRead);
  BMPFIle.ReadBuffer(BMPFileHeader, SizeOf(BMPFileHeadeR));

  if BMPFileHeader.bfType <> BITMAP_ID then
  begin
    FreeAndNil(BMPFile);
    result.Height := 0;
    result.Width := 0;
    result.Data := nil;
    exit;
  end;

  BMPFile.ReadBuffer(BMPImageHeader, SizeOf(BMPImageHeader));
//  BMPFile.Seek(BMPImageHeader.biImageSize, soFromEnd);
//  BMPFile.Seek(BMPFileHeader.bfOffBits, soFromBeginning);
  FreeAndNil(BMPFile);
  GetMem(ImageData, BMPImageHeader.biImageSize);

  if ImageData = nil then
  begin
    FreeAndNil(BMPFile);
    result.Height := 0;
    result.Width := 0;
    result.Data := nil;
    exit;
  end;

//  BMPFile.ReadBuffer(ImageData, BMPImageHeader.biImageSize);
//  FreeAndNil(BMPFile);
  AssignFile(Plik, Filename);
  Reset(Plik);
  seek(Plik, BMPFileHeader.bfOffBits);
  i := 0;
  for i := 0 to BMPImageHeader.biImageSize - 1 do
  begin
    Read(Plik, ImageData^[i]);
  end;
  CloseFile(Plik);

  i := 0;
  while i < BMPImageHeader.biImageSize do
  begin
    tempRGB := ImageData^[i];
    ImageData^[i] := ImageData^[i + 2];
    ImageData^[i + 2] := tempRGB;
    Inc(i, 3);
  end;

  result.Width := BMPImageHeader.biWidth;
  result.Height := BMPImageHeader.biHeight;
  result.Data := ImageData;
end;

Jednak miło by było gdyby ktoś zerknął czemu wyjątek występował.

1
BMPFile.ReadBuffer(ImageData, BMPImageHeader.biImageSize); // <- O tutaj

Szanowny kolego - jak się wczytuje dane do macierzy? Tym bardziej mając wskaźnik? Nie podajesz od którego elementu ma być pamięć wypełniana... Jest babol pieroński :]

Powinieneś zrobić to w ten sposób:

BMPFile.ReadBuffer(ImageData[0], BMPImageHeader.biImageSize);
//                           ^- zapis od pierwszego elementu macierzy
babubabu napisał(a)

Myślałem, że GetMem źle pamięć alokuje i chciałem zmienić na SetLength bo w końcu PByteArray to tablica, jednak wtedy przy SetLength sypało type mismatch. Co robię źle?

Nic - nie możesz użyć SetLength do ustalenia rozmiaru macierzy podając wskaźnik (ani sam wskaźnik, ani z operatorem odwołania do jego wartości, czyli ^); Po prostu musisz skorzystać z GetMem i FreeMem; Jednak rób to z głową, bo Twój kod nie jest zabezpieczony prawidłowo i może powodować wyjątki klasy EAccessViolation; Używaj odpowiednich bloków do tego celu lub wyłapuj wyjątki i na nie reaguj; Dodatkowo skorzystaj z debugera do sprawdzenia poprawności i działania kodu, i wczytanych danych, zamiast zapisywać wszystko do dodatkowego pliku.

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