Jak wczytaj plik JPG i odczytać piksele?

0

Jak to zrobić w C++ i WinApi (ostatecznie w MFC):

  1. Wczytać z pliku obrazek w formacie JPG (lub PNG, GIF), np. przez OleLoadPicture() i odczytać kolejne piksele z tego obrazka, tzn. skopiować sobie wartości kolorów do swojej tablicy.
    Czy muszę tworzyć bitmapę i kopiować do niej wczytany obraz przez Render() albo BitBlt() i jak to zrobić?

  2. Zapisać do pliku bitmapę utworzoną na podstawie mojej tablicy pikseli.
    Jak się tworzy bitmapę, jak kopiować do niej i odczytać z niej kolejne piksele?

0

użyć WIC (Windows Imaging Component).

przykład mogę podać za jakąś godzinę.

0

No no poproszę!

0

No dzięki ale trochę to kosmos dla mnie ten WIC. Chodzi mi o zrobienie tego w samym WinAPI GDI - bez żadnych dodatkowych komponentów (niskopoziomowo - żeby nawet w WinXP to poszło).
Chodzi tylko o utworzenie bitmapy na podstawie JPG i dostęp do jej pikseli.

3
// jeśli nie zdefiniujemy _WIN32_WINNT to program skompilowany
// pod VS2012+ będzie wymagać Win8 i może nie odpalić nawet na Win7.
#define _WIN32_WINNT 0x501
#include <Windows.h>

// WIC jest tutaj:
#include <wincodec.h>
#pragma comment (lib, "windowscodecs.lib")

// wciągam ATL dla smartpointerów CComPtr.
// można zamiast tego użyć _com_ptr_t co nie wymaga ATL albo ręcznie bawić się w Release() na obiektach.
#include <atlbase.h>

// klasa automatyzująca wywołanie CoInitialize/CoUninitialize. pomysł: Raymond Chen.
struct CCoInitialize
{
	HRESULT hr;
	CCoInitialize() { hr = CoInitialize(NULL); }
	~CCoInitialize() { if (SUCCEEDED(hr)) CoUninitialize(); }
};

int main()
{
	// uwaga: kod przykładowy bez kontroli błędów. większość funkcji zwraca HRESULT.
	// sprawdzamy za pomocą if (SUCCEEDED(hr)) albo if (FAILED(hr))
	HRESULT hr;

	CCoInitialize coinit;

	// główny obiekt WIC
	CComPtr<IWICImagingFactory> factory;
	hr = factory.CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER);

	// ładujemy obrazek
	CComPtr<IWICBitmapDecoder> decoder;
	hr = factory->CreateDecoderFromFilename(L"obrazek.jpg", NULL, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder);

	// obrazek teoretycznie może się składać z wielu "frame", ale zazwyczaj jest tylko jedna klatka, o indeksie 0.
	CComPtr<IWICBitmapFrameDecode> frame;
	hr = decoder->GetFrame(0, &frame);

	// obrazek załadowany. co dalej, to już rozejrzyj się po dostępnych metodach...
	
	// rozmiar
	UINT w, h;
	hr = frame->GetSize(&w, &h);

	// format bitmapy
	WICPixelFormatGUID format;
	hr = frame->GetPixelFormat(&format); // np. GUID_WICPixelFormat24bppRGB

	// WICConvertBitmapSource() jeśli chcemy przekonwertować bitmapę na inny format

	// piksele w bieżącym formacie można odczytać za pomocą frame->CopyPixels().

	// aby zapisać bitmapę, tworzysz enkoder: factory->CreateEncoder i dalej analogicznie do odczytu
	// - poszukaj jak się metody nazywają.


	// ponieważ używaliśmy smartpointerów wszystko się ładnie samo zwolni.
}
0

Skoro może być PNG, to zobacz sobie picopng:

http://lodev.org/lodepng/picopng.cpp

Chodzi w czystym C++ z STL, obojętnie, czy pod Windowsem, czy Linuksem.

Tam jest sam odczyt. Zapis najłatwiej będzie Ci zrealizować w BMP, chociaż... jak znajdziesz kod do kodowania w innym formacie, to również będzie łatwo. Ty będziesz musiał tylko podać piksele w odpowiedni sposób :)

0

No zrobiłem sam:
Odczyt pikseli z dowolnego skmopresowanego obrazka w C++ WinAPI GDI
Oto dwie procedury:

  1. int LoadPictureFile(LPCTSTR szFile) -ładuje obraz** jpg/png/gif** itp. do LPPICTURE gpPicture; //IPicture*
  2. int ZapiszObrazDoPlikuRAWB(LPPICTURE pPicture, TCHAR *NazwaPliku) //zapisuje do pliku piksele z obrazu Picture w postaci RGB RGB RGB... w odpowiedniej kolejności
// Wczytuje plik do IStream. Ładuje obraz do gpPicture
int LoadPictureFile(LPCTSTR szFile)
{
	//---open file
	HANDLE hFile = CreateFile(szFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); //If the function fails, the return value is INVALID_HANDLE_VALUE
	if (INVALID_HANDLE_VALUE == hFile) {MSGBlad(ghWnd, L"Nie można otworzyć pliku do czytania.");  return -1;} //_ASSERTE(INVALID_HANDLE_VALUE != hFile);
	//---get file size
	DWORD dwFileSize = GetFileSize(hFile, NULL); //If the function fails and lpFileSizeHigh is NULL, the return value is INVALID_FILE_SIZE
	if (INVALID_FILE_SIZE == dwFileSize) {MSGBlad(ghWnd, L"Nie można pobrać rozmiaru pliku.");  return -2;} //_ASSERTE(-1 != dwFileSize);
	//---alloc memory based on file size
	HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);
	if (NULL == hGlobal) {MSGBlad(ghWnd, L"Nie można zaalokować pamięci na plik.");  return -3;} //_ASSERTE(NULL != hGlobal);
	//[...] a co ze zwolnieniem pamięci!???

	LPVOID pvData = GlobalLock(hGlobal);
	if (NULL == pvData) {MSGBlad(ghWnd, L"Problem: GlobalLock().");  return -4;} //_ASSERTE(NULL != pvData); GlobalFree(hGlobal)

	//---read file and store in global memory
	DWORD dwBytesRead = 0;
	BOOL bRead = ReadFile(hFile, pvData, dwFileSize, &dwBytesRead, NULL);
	if (FALSE == bRead) {MSGBlad(ghWnd, L"Problem z odczytem pliku.");  return -5;} //_ASSERTE(FALSE != bRead); GlobalFree(hGlobal)

	GlobalUnlock(hGlobal);
	CloseHandle(hFile);

	//---create IStream* from global memory
	LPSTREAM pstm = NULL;
	HRESULT hr = CreateStreamOnHGlobal(hGlobal, TRUE, &pstm); //SUCCEEDED(Status) == ((HRESULT)(Status) >= 0)
	if (!SUCCEEDED(hr) || !pstm) {MSGBlad(ghWnd, L"Problem z CreateStreamOnHGlobal().");  return -5;} //_ASSERTE(SUCCEEDED(hr) && pstm); GlobalFree(hGlobal)

	//---Create IPicture from image file
	if (gpPicture) gpPicture->Release();

	hr = ::OleLoadPicture(pstm, dwFileSize, FALSE, IID_IPicture, (LPVOID *)&gpPicture);
	if (!SUCCEEDED(hr) || !gpPicture) {MSGBlad(ghWnd, L"Problem z odczytem pliku.");  return -6;} //_ASSERTE(SUCCEEDED(hr) && gpPicture); GlobalFree(hGlobal);
	pstm->Release();

	//GlobalFree(hGlobal); // 16Bit Windows Needs This (32Bit - Automatic Release)
	//InvalidateRect(ghWnd, NULL, TRUE); //odśwież okno
	return 0; //wsz. ok
}

//---------------------------------------------------------------------------
int ZapiszObrazDoPlikuRAWB(LPPICTURE pPicture, TCHAR *NazwaPliku) //zapisuje do pliku RAWB piksele z obrazu Picture
{//używa ghWnd = CreateWindow() - uchwyt do głównego okna ale można użyć NULL
	HDC hdc = GetDC(ghWnd); //hwnd; Działa z ghWnd i NULL.
	HDC hdcMem = CreateCompatibleDC(hdc); //hdc; Tworzy HDC kompatybilny z podanym
	//..w większości przypadków nie musimy podawać tego hdc jako argumentu; wtedy funkcja utworzy HDC kompatybilne z "bieżącym ekranem aplikacji" 
	
    HBITMAP hBitmap;
    if (S_OK != pPicture->get_Handle((OLE_HANDLE FAR *)&hBitmap)) //pobranie uchwytu bitmapy z Picture
    {  MSGBlad(ghWnd, L"Nie mogę dostać bitmap handle z picture.");
	   return -1;
    }

	if (S_OK != gpPicture->SelectPicture(hdcMem, NULL, NULL)) //Selects a bitmap picture into a given device context, and returns the device context in which the picture was previously selected
	{  MSGBlad(ghWnd, L"Problem z SelectPicture().");
	   return -2;
	}

	//Pobierz width i height picture
	long hmWidth, hmHeight; //wielkie rozmiary: Dla obrazu 211x312 => 5583x8255
	pPicture->get_Width(&hmWidth);
	pPicture->get_Height(&hmHeight);
	//convert himetric to pixels
	int nWidth = MulDiv(hmWidth, GetDeviceCaps(hdc, LOGPIXELSX), HIMETRIC_INCH); //MulDiv(a,b,c)=a*b/c ;hmWidth; daje wielkie powiększenie
	int nHeight	= MulDiv(hmHeight, GetDeviceCaps(hdc, LOGPIXELSY), HIMETRIC_INCH);
	//---
	BITMAPINFO bi;	BOOL bRes;
	int RozmiarBuf = nWidth * 4 * nHeight;
	//Bitmap header
	bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
	bi.bmiHeader.biWidth = nWidth;
	bi.bmiHeader.biHeight = nHeight;
	bi.bmiHeader.biPlanes = 1;
	bi.bmiHeader.biBitCount = 32;
	bi.bmiHeader.biCompression = BI_RGB;
	bi.bmiHeader.biSizeImage = RozmiarBuf;
	bi.bmiHeader.biClrUsed = 0;
	bi.bmiHeader.biClrImportant = 0;

	//Alokacja Bufora na piksele
	unsigned char *Bufor = new unsigned char[RozmiarBuf]; //bo 4 bajty na piksel
	
	//Get the all scanline. Don't use getPixel and SetPixel.It's very slow.
	bRes = GetDIBits(hdcMem, //HDC hdc
			         hBitmap, //HBITMAP hbmp
					 0, //UINT uStartScan
					 nHeight, //UINT cScanLines
					 Bufor, //out: PVOID lpvBits
					 &bi, //LPBITMAPINFO lpbi,
					 DIB_RGB_COLORS); //UINT uUsage

    ReleaseDC(ghWnd, hdc); //zwalniam HDC okna, po którym rysowałem, bo było GetDC(). Tak!
	//DeleteDC(hdcMem); //kasowanie hdc, tylko Zawsze po SelectObject(hMemDC,hOldBitmap). Tu NIE, bo nie narysuje obrazu!! 
    //===Zapis do pliku uzyskanych pikseli
	FILE *plik; //= _wfopen_s(NazwaPliku, _T("wb")); //zapis, binary; errno_t err;
	_wfopen_s(&plik, NazwaPliku, _T("wb")); //zapis, binary; if( != 0) błąd
    if (!plik) 
	{  MessageBoxEx(ghWnd, L"Nie można otworzyć do zapisu pliku.", L"Błąd", MB_OK | MB_ICONINFORMATION, LANG_POLISH);  return -4;
	}
    int x,y,z,w;
    //for (i=0; i < nWidth*4*nHeight; i += 4) fwrite(buf+i, 3, 1, plik); //od końca; ale tak będzie obraz odwrócony lewo-prawo
    for (y=0; y < nHeight; y++)
	{w = (nHeight-1-y) * nWidth; //linie od dołu do góry
      for (x=0; x < nWidth; x++)
	  { z = (w + x)*4;  //długość linii == nWidth*4
        putc(Bufor[z+2], plik); //R
        putc(Bufor[z+1], plik); //G
        putc(Bufor[z], plik); //B
	  }
	}
    //---
    fclose(plik);
    delete[] Bufor;
    return 0; //wsz. ok
}
 
0

Niejednoznaczność/niekonsekwencja dekompresji JPG.
Jeszcze jedno. Bardzo ciekawa rzecz.
Zrobiłem 3 programy do odczytu pikseli z obrazka jpg.

  1. W Embarcadero C++ Builder przy użyciu: TPicture->Graphic, Graphics::TBitmap
  2. Przez WinAPI GDI
  3. Za pomocą biblioteki IJG(The Independent JPEG Group's JPEG software) - jpeglib 9a

Wynik dekompresji wszystkich trzech programów różnił się nieco.
Wartości niektórych pikseli RGB różniły się o wartość 1 lub 2. Około 10% pikseli różniło się między 1. i 2.
NAJBARDZIEJ różnił się wynik 3.WinAPI, około 40% pikseli różniło się od 1. i od 2.

Ciekawe dlaczego? Różne implementacje algorytmu dekompresji JPG?
Tu podobny wątek:
https://stackoverflow.com/questions/11872850/jpeg-decompression-inconsistent-across-windows-architectures

0

No oczywistym jest, że formaty bezstratne są niezmienne. Chodzi o to, że różne programy przy dekompresji programu dają różne wyniki!!
To tak jakby dany np. plik MP3, WinAMP ogrywał nieco inaczej od Foobara a jeszcze inaczej Windows MediaPlayer.
Jest tu gdzieś jakiś błąd implementacji dekompresji, jakaś nieścisłość lub niedokładność. Np. użycie mniejszej precyzji liczb rzeczywistych kosztem szybkości działania.
Pokazuje to, że są różne jakości dekompresji.
Idealnie powinno być tak, że każda dekompresja w każdych warunkach i każdym czasie powinna dawać TEN SAM WYNIK!

Teraz zrobiłem test (względem oryginalnej BMP), że najgorszy wynik dekompresji daje jpglib (lpsze jest WinAPI i Builder)

0

Znalazłem lepszy i prostszy sposób na uzyskanie pikseli z pliku JPG, bez użycia obiektów GDI:
LPPICTURE gpPicture;
Wczytanie pliku przez ww. LoadPictureFile(); a potem:

int ZapiszObrazDoPlikuRAWB(LPPICTURE pPicture, TCHAR *NazwaPliku) //zapisuje do pliku RAWB piksele z obrazu Picture
{	//---Pobranie uchwytu bitmapy z Picture, którą utrzymuję instancja IPicture
	HBITMAP hBitmap;
    if (S_OK != pPicture->get_Handle((OLE_HANDLE FAR *)&hBitmap)) 
    {  MSGBlad(L"Nie mogę dostać bitmap handle z picture.\n");  return -1;
    }
	
	//---Pobierz width i height picture
	BITMAP bm = {0}; //info o bitmapie
    GetObject(hBitmap, sizeof(BITMAP), &bm); 
	
	wprintf(L"Piksele bm: %d x %d\n", bm.bmWidth, bm.bmHeight);
	wprintf(L"Type: %ld\nWidthBytes: %ld\nPlanes: %d\nBitsPixel: %d\n\n",
	  bm.bmType, //LONG; =0 const.
	  bm.bmWidthBytes, //LONG; Number of bytes in each scan line; musi być podzielne przez 4, więc +1..3
      bm.bmPlanes, //WORD; =1 const.
	  bm.bmBitsPixel); //WORD; =24
	if (bm.bmBitsPixel != 24)   {MSGBlad(L"bmBitsPixel <> 24.\n");  return -2;}

	//---Pobranie pikseli z bitmapy
	long nWidth = bm.bmWidth,  nHeight = bm.bmHeight;
	int LBajtNaPiksel = 3;	
	unsigned char *Bufor = (unsigned char *)bm.bmBits; 	//LPVOID; tu są PIKSELE bitmapy w kolejnych liniach od dołu!

	//===Zapis do pliku uzyskanych pikseli===
	FILE *plik; //= _wfopen_s(NazwaPliku, _T("wb")); //zapis, binary; errno_t err;
	_wfopen_s(&plik, NazwaPliku, L"wb"); //zapis, binary; if( != 0) błąd
    if (!plik) {MSGBlad(L"Nie można otworzyć do zapisu pliku.\n");  return -3;}
	//---
	int x,y,w,z;
	//for (y=0; y < nHeight; y++) fwrite(Bufor + (nHeight-1-y) * bm.bmWidthBytes, nWidth, 3, plik); //ok ale odwrotnie są bajty kolorów: BGR zamiast RGB
    for (y=0; y < nHeight; y++)
	{  w = (nHeight-1-y) * bm.bmWidthBytes; //linie od dołu do góry
       for (x=0; x < nWidth; x++)
	   {  z = w + x * LBajtNaPiksel; //długość linii == nWidth*3
          putc(Bufor[z+2], plik); //R
          putc(Bufor[z+1], plik); //G
          putc(Bufor[z], plik); //B
	   }	  
	}
    //---
	fclose(plik);
    return 0; //wsz. ok
}

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