해당 포스트는 "열혈강의 영상처리 프로그래밍" 책의 내용을 요약한 것이다.
※ 비트맵 파일 읽기와 쓰기
비트맵 파일의 읽기와 쓰기에서 주의할 점은 비트맵 파일 안에서 영상은 상하가 반전되어 저장된다. 위 그림에서와 같이 프로그램 내부에서는 왼쪽 상단부터 시작해서 행 우선 배열에 따라 픽셀의 배열이 이루어지나 BMP 파일 안에서는 영상의 왼쪽 하단부터 시작해서 행 우선 배열에 따라 저장되 상하 좌표가 바뀐다.
ex) 영상 데이터 클래스 구현하기 포스트 코드에서 나왔던 LoadImage() 와 SaveImage()를 구현할 것이다.
bool LoadImage(const char* filename) { assert(sizeof(T) == 1); // 일반적인 비트맵 파일은 1byte이기 때문에 BYTE형의 영상인 경우에만 동작할 수 있도록 한다. if (strcmp(".BMP", &filename[strlen(filename) - 4])) //확장자가 BMP인 비트맵 파일이 맞는 지 확인 { FILE* pFile = NULL; fopen_s(&pFile, filename, "rb"); // 바이너리 읽기 모드 if (!pFile) return false; BITMAPFILEHEADER fileHeader; if (!fread(&fileHeader, sizeof(BITMAPFILEHEADER), 1, pFile)) { fclose(pFile); return false; } if (fileHeader.bfType != 0x4D42) // 'BM' 문자 검사 { fclose(pFile); return false; } BITMAPINFOHEADER infoHeader; if (!fread(&infoHeader, sizeof(BITMAPINFOHEADER), 1, pFile)) { fclose(pFile); return false; } if (infoHeader.biBitCount != 8 && infoHeader.biBitCount != 24) //회색조 영상 또는 트루 컬러영상만을 읽을 수 있도록 한다. { fclose(pFile); return false; } if (m_nWidth != infoHeader.biWidth && m_nHeight != infoHeader.biHeight && m_nChannels != infoHeader.biBitCount / 8) { if (m_pImageData) delete[] m_pImageData; m_nChannels = infoHeader.biBitCount / 8; m_nHeight = infoHeader.biHeight; m_nWidth = infoHeader.biWidth; m_nWStep = (m_nWidth*m_nChannels * sizeof(T) + 3)&~3; m_pImageData = new T[m_nHeight*m_nWStep]; } fseek(pFile, fileHeader.bfOffBits, SEEK_SET); //실제 픽셀 데이터 위치를 가리킨다. int r; for (r = m_nHeight - 1; r >= 0; r--) //비트맵 파일은 상하가 바뀌므로 m_nHeight-1부터 접근 { if (!fread(&m_pImageData[r*m_nWStep], sizeof(BYTE), m_nWStep, pFile)) { fclose(pFile); return false; } } fclose(pFile); return true; } else { return false; } }
비트맵 파일의 헤더정보를 읽는 과정과 저장할 픽셀 데이터에 관한 영상 정보를 읽는 과정, 픽셀 데이터를 읽는 과정으로 구성된다.
bool SaveImage(const char* filename) { assert(sizeof(T) == 1); // 일반적인 비트맵 파일은 1byte이기 때문에 BYTE형의 영상인 경우에만 동작할 수 있도록 한다. if (strcmp(".BMP", &filename[strlen(filename) - 4])) { FILE* pFile = NULL; fopen_s(&pFile, filename, "wb"); if (!pFile) return false; BITMAPFILEHEADER fileHeader; fileHeader.bfType = 0x4D42; // 'BM' //(1) fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + m_nWStep*m_nHeight + (m_nChannels == 1) * 1024; fileHeader.bfReserved1 = 0; fileHeader.bfReserved2 = 0; //(2) fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + (m_nChannels == 1) * 256 * sizeof(RGBQUAD); fwrite(&fileHeader, sizeof(BITMAPFILEHEADER), 1, pFile); BITMAPINFOHEADER infoHeader; infoHeader.biSize = sizeof(BITMAPINFOHEADER); infoHeader.biWidth = m_nWidth; infoHeader.biHeight = m_nHeight; infoHeader.biPlanes = 1; infoHeader.biBitCount = m_nChannels * 8; infoHeader.biCompression = BI_RGB; infoHeader.biSizeImage = m_nWStep*m_nHeight; infoHeader.biClrImportant = 0; infoHeader.biClrUsed = 0; infoHeader.biXPelsPerMeter = 0; infoHeader.biYPelsPerMeter = 0; fwrite(&infoHeader, sizeof(BITMAPINFOHEADER), 1, pFile); if (m_nChannels == 1) //(3) { for (int l = 0; l<256; l++) { RGBQUAD GrayPalette = { l, l, l, 0 }; fwrite(&GrayPalette, sizeof(RGBQUAD), 1, pFile); } } int r; for (r = m_nHeight - 1; r >= 0; r--) { fwrite(&m_pImageData[r*m_nWStep], sizeof(BYTE), m_nWStep, pFile); } fclose(pFile); return true; } else { return false; } }
SaveImag(0 함수와 반대 과정으로 비트맵 파일 헤더와 영상 정보 헤더를 생성하고 파일에 쓰는 과정, 픽셀 데이터를 파일에 쓰는 과정으로 구성된다. 회색조 영상은 팔레트 정보를 기록하는 과정도 있다. 또한, BMP파일에 픽셀 데이터를 쓸 때 상하 반대로 해서 써야한다.
(1)번 코드 fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + m_nWStep*m_nHeight + (m_nChannels == 1) * 1024; 는 비트맵 파일의 크기를 구하는 것으로 m_nChannels가 1이면 즉, 회색조 영상일 경우 팔레트 정보의 크기를 추가해 주는 코드이다. 1024 대신 (2)번 코드와 같이 256*sizeof(RGBQUAD)를 곱해줘도 된다.
(3)번 코드는 회색조 영상의 경우 팔레트 정보를 추가해 줘야 하기 때문에 구현한 코드이다.
'영상처리 프로그래밍' 카테고리의 다른 글
영상 입출력 프로그램 만들기(4) - 대화상자기반 영상 출력 (0) | 2017.06.09 |
---|---|
영상 입출력 프로그램 만들기(3) - MDI에서 화면 출력 (0) | 2017.06.07 |
영상 입출력 프로그램 만들기(1) - 비트맵 구조 (0) | 2017.06.06 |
영상 데이터 클래스 구현(5) (0) | 2017.06.04 |
영상 데이터 클래스 구현(4) (0) | 2017.06.04 |
옵저버(Observer) 패턴(2) - 직접 구현
해당 포스트는 "자바 객체지향 디자인 패턴", "JAVA 언어로 배우는 디자인 패턴 입문" 책의 내용을 요약한 것이다.
2. Observer 패턴을 JDK API를 이용하지 않고 직접 구현하는 방법
ex)
<Observer>
public interface Display { public void display(); } interface Observer{ public void update(Observable o); } class KoreanDisplay implements Observer, Display{ private Observable observable; // 등록될 Observable private float temperature; private float humidity; private float pressure; public KoreanDisplay(Observable observable) { this.observable = observable; observable.addObserver(this); //this(KoreaDisplay)를 옵저버로 등록 } @Override public void display() { System.out.println("온도 : "+temperature); System.out.println("습도 : "+humidity); System.out.println("압력 : "+pressure); } /* * Observable에서 notifyObservers()를 호출할 시 해당 메소드가 호출되어 옵저버의 데이터가 갱신 */ @Override public void update(Observable o) { if(o instanceof WeatherData){ WeatherData weatherData = (WeatherData)o; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); this.pressure = weatherData.getPressure(); display(); } } } class EnglishDisplay implements Observer, Display{ private Observable observable; // 등록될 Observable private float temperature; private float humidity; private float pressure; public EnglishDisplay(Observable observable) { this.observable = observable; observable.addObserver(this); //this(EnglishDisplay)를 옵저버로 등록 } @Override public void display() { System.out.println("temperature : "+temperature); System.out.println("humidity : "+humidity); System.out.println("pressure : "+pressure); } /* * Observable에서 notifyObservers()를 호출할 시 해당 메소드가 호출되어 옵저버의 데이터가 갱신 */ @Override public void update(Observable o) { if(o instanceof WeatherData){ WeatherData weatherData = (WeatherData)o; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); this.pressure = weatherData.getPressure(); display(); } } }
Observer는 JDK API를 사용했던 것과 거의 달라지는 게 없고 Observer 인터페이스만 변경했다.
<직접 구현한 Observable>
public interface Observable { public void addObserver(Observer o); //옵저버 등록 public void deleteObserver(Observer o); //옵저버 제거 public void notifyObservers(); //옵저버에 데이터 전송 } class WeatherData implements Observable{ private ArrayListobservers; //등록할 옵저버 리스트 private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList (); } /* * 옵저버 등록 메소드 */ @Override public void addObserver(Observer o) { observers.add(o); } /* * 옵저버 제거 메소드 */ @Override public void deleteObserver(Observer o) { int i = observers.indexOf(o); if(i>=0){ observers.remove(i); } } /* * 옵저버에 데이터 전달 */ @Override public void notifyObservers() { for (int i = 0; i < observers.size(); i++) { Observer observer = (Observer)observers.get(i); observer.update(this); } } public void setData(float temperature, float humidity, float pressure){ this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; notifyObservers(); } public float getTemperature() { return temperature; } public float getHumidity() { return humidity; } public float getPressure() { return pressure; } }
'자바 > 디자인패턴' 카테고리의 다른 글
퍼사드 패턴 (0) | 2017.06.08 |
---|---|
프로토타입 패턴 (0) | 2017.06.08 |
컴퍼지트 패턴 (0) | 2017.06.07 |
데코레이터(Decorator) 패턴 (0) | 2017.06.06 |
옵저버(Observer) 패턴(1) - JDK API 활용 (0) | 2017.06.06 |