해당 포스트는 "열혈강의 영상처리 프로그래밍" 책의 내용을 요약한 것이다.
※ 비트맵 파일 읽기와 쓰기
비트맵 파일의 읽기와 쓰기에서 주의할 점은 비트맵 파일 안에서 영상은 상하가 반전되어 저장된다. 위 그림에서와 같이 프로그램 내부에서는 왼쪽 상단부터 시작해서 행 우선 배열에 따라 픽셀의 배열이 이루어지나 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 ArrayList observers; //등록할 옵저버 리스트
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 |