S-JIS[1998-08-16/2002-08-03]

パレットの使い方

256色モードでは、パレットを使わないと なかなか自分で表示したい色になってくれません。
ここでは、パレットの例を書きます。


  1. まずは、パレットデータを作成します。
    これには、CPaletteクラスを利用します。

    この例では、パレットを作成する関数をCMyView::CreatePalette()としておきます。
    実際には、CMyDocumentの中で作ってViewから読み込んでも構いませんし、 OnInitialUpdate()ハンドラの中で作っても構いません。

    MyView.h:

    class CMyView : public CView
    {
    〜
    protected:
    	CPalette m_Palette;
    	int CreatePalette();
    〜
    };
    

    MyView.cpp:

    CMyView::CMyView()
    {
    	// TODO: この場所に構築用のコードを追加してください。
    	CreatePalette();	//とりあえずコンストラクタで呼んでおく
    }
    〜
    int CMyView::CreatePalette()
    {
    	if(m_Palette.m_hObject==0){	//未作成かどうか
    		LOGPALETTE LogPal;
    		LogPal.palVersion=0x300;
    		LogPal.palNumEntries=1;
    		m_Palette.CreatePalette(&LogPal);
    		//パレットのエントリ数を256に
    		BOOL b=m_Palette.ResizePalette(256);
    		if(!b) return -1;
    	}
    
    	PALETTEENTRY PalEnt[256];
    	for(int i=0;i<256;i++){
    		PalEnt[i].peRed  =((i>>5)&7)*255/7;
    		PalEnt[i].peGreen=((i>>2)&7)*255/7;
    		PalEnt[i].peBlue =((i>>0)&3)*255/3;
    		PalEnt[i].peFlags=0;
    	}
    	UINT u=m_Palette.SetPaletteEntries(0,256,PalEnt);
    
    	return u;
    }
    

    この例ではパレットの個数を256個としていますが、必要に応じて16個にしたり、減らす事は可能です。
    peRed,peGreen,peBlueには、自分の好きなように入れて下さい。


  2. そして、パレットを実際のシステムのパレットに写します。
    これは ウィンドウがアクティブになった時に行えばいいので、 OnActivateViewをオーバーライドします。

    MyView.h:

    class CMyView : public CView
    {
    〜
    // オーバーライド
    	// ClassWizard は仮想関数を生成しオーバーライドします。
    	//{{AFX_VIRTUAL(CMyView)
    	protected:
    	virtual void OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView);
    	//}}AFX_VIRTUAL
    〜
    };
    

    MyView.cpp:

    void CMyView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView) 
    {
    	// TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください
    
    	CView::OnActivateView(bActivate, pActivateView, pDeactiveView);
    
    	if(bActivate) OnDoRealizePalette((WPARAM)m_hWnd,0);	//ここに追加
    }
    
    OnDoRealizePalette関数は、後で作ります。
  3. パレットをシステムパレットに写すOnDoRealizePaletteですが、 後で独自のメッセージを処理できるようにする為、このような名前にしています。

    MyView.h:

    class CMyView : public CView
    {
    〜
    // 生成されたメッセージ マップ関数
    protected:
    	//{{AFX_MSG(CMyView)
    		// メモ -  ClassWizard はこの位置にメンバ関数を追加または削除します。
    		//         この位置に生成されるコードを編集しないでください。
    	//}}AFX_MSG
    	afx_msg LRESULT OnDoRealizePalette(WPARAM wPrm,LPARAM lPrm);
    	DECLARE_MESSAGE_MAP()
    〜
    };
    

    MyView.cpp:

    afx_msg LRESULT CMyView::OnDoRealizePalette(WPARAM wPrm,LPARAM lPrm)
    {
    	CClientDC appDC(AfxGetApp()->m_pMainWnd);
    
    	CPalette *pOld=appDC.SelectPalette(&m_Palette,(HWND)wPrm!=m_hWnd);
    	UINT uiCount=appDC.RealizePalette();
    	appDC.SelectPalette(pOld,TRUE);
    
    	if(uiCount>0) GetDocument()->UpdateAllViews(NULL);
    	return uiCount;
    }
    

  4. では、試しに実際に描画してみましょう。

    MyView.cpp:

    void CMyView::OnDraw(CDC* pDC)
    {
    	CMyDoc* pDoc = GetDocument();
    	ASSERT_VALID(pDoc);
    
    	// TODO: この場所にネイティブ データ用の描画コードを追加します。
    
    	CPalette *pOldPal=pDC->SelectPalette(&m_Palette,FALSE);
    	for(int i=0;i<256;i++){
    		int x=i%16;
    		int y=i/16;
    		int r=((i>>5)&7)*255/7;
    		int g=((i>>2)&7)*255/7;
    		int b=((i>>0)&3)*255/3;
    //		pDC->FillSolidRect(x*32,y*32,32,32,RGB(r,g,b));
    		pDC->FillSolidRect(x*32,y*32,32,32,PALETTERGB(r,g,b));
    //		pDC->FillSolidRect(x*32,y*32,32,32,PALETTEINDEX(i));
    //		pDC->SetPixel(x,y,RGB(r,g,b));
    //		pDC->SetPixel(x,y,PALETTERGB(r,g,b));
    //		pDC->SetPixel(x,y,PALETTEINDEX(i));
    	}
    	pDC->SelectPalette(pOldPal,TRUE);
    }
    
    描画前にpDCに対してパレットを割り当てる必要があります。
    pDCはOnDrawが呼ばれる度に(少なくともパレットに関して)初期化されているらしいので、 いちいち割り当てなければならないようです。

    そして、RGBマクロではなくPALETTERGBマクロを使うというのがポイントです。
    RGBマクロではシステムが用意したパレットから指定された色に近い色を探します。
    それに対し、PALETTERGBマクロでは、割り当てられているパレット全体から近い色を探します。
    PALETTEINDEXマクロは、パレット番号の色をそのまま使います。近い色を探すという手間が無い分 早い(と思う)ですが、どの番号にどの色が入っているかは CPalette内の順番通りとは限らないらしいので注意が必要です。


    ちょっと想像が大部分ですが、パレットの注意点について。[2002-08-03]
    CPaletteで用意した自分のパレットは256色使っているわけですが、 これをSelectPaletteでDCに写した時に そのまま全部コピーされるわけではないようです。 DCの中では、コードが0〜15くらいの16色分くらい(具体的にいくつか、ということは忘れた)は システムが使っているのです。CPaletteで256色フルに使っていても、DCに反映される時には システムの個数分は別の似た色で置き換えられてしまうと思います。 この辺りが、「CPalette内の順番通りにならないらしい」の原因だと考えています。


  5. さて、パレットが他のアプリケーションによって変えられた時などの為に、 WM_PALETTECHANGEDとWM_QUERYNEWPALETTEメッセージのハンドラを作っておきます。
    これは、CMainFrameクラスに対して行います。
    しかし この2つのハンドラって、本当に効果があるのか いまいち分からないんだよなー。

    MainFrm.h:

    class CMainFrame : public CFrameWnd
    {
    〜
    // 生成されたメッセージ マップ関数
    protected:
    	//{{AFX_MSG(CMainFrame)
    	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    	afx_msg void OnPaletteChanged(CWnd* pFocusWnd);
    	afx_msg BOOL OnQueryNewPalette();
    	//}}AFX_MSG
    	DECLARE_MESSAGE_MAP()
    〜
    };
    

    MainFrm.cpp:

    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    	//{{AFX_MSG_MAP(CMainFrame)
    	ON_WM_CREATE()
    	ON_WM_PALETTECHANGED()
    	ON_WM_QUERYNEWPALETTE()
    	//}}AFX_MSG_MAP
    END_MESSAGE_MAP()
    〜
    void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd) 
    {
    //	CFrameWnd::OnPaletteChanged(pFocusWnd);
    
    	// TODO: この位置にメッセージ ハンドラ用のコードを追加してください
    	CView *pView=GetActiveView();
    	if(pView!=NULL){
    		SendMessageToDescendants(WM_DOREALIZEPALETTE,(WPARAM)pView->m_hWnd);
    	}
    }
    
    BOOL CMainFrame::OnQueryNewPalette() 
    {
    	// TODO: この位置にメッセージ ハンドラ用のコードを追加するかまたはデフォルトの処理を呼び出してください
    	CView *pView=GetActiveView();
    	if(pView!=NULL){
    		pView->SendMessage(WM_DOREALIZEPALETTE,(WPARAM)pView->m_hWnd);
    	}
    	return TRUE;
    
    //	return CFrameWnd::OnQueryNewPalette();
    }
    

    My.h

    〜
    #include "resource.h"       // メイン シンボル
    
    //独自のメッセージ:パレットを実体化する
    #define WM_DOREALIZEPALETTE (WM_USER+0)

    MyView.h

    BEGIN_MESSAGE_MAP(CMyView, CView)
    	//{{AFX_MSG_MAP(CMyView)
    		// メモ - ClassWizard はこの位置にマッピング用のマクロを追加または削除します。
    		//        この位置に生成されるコードを編集しないでください。
    	//}}AFX_MSG_MAP
    	ON_MESSAGE(WM_DOREALIZEPALETTE, OnDoRealizePalette)	//これを追加
    END_MESSAGE_MAP()
    

    WM_DOREALIZEPALETTEメッセージは、独自のものです。このメッセージを処理する関数は、 前に作ったOnDoRealizePalette関数です。

    このあたりの詳しい事は、DeveloperStudioヘルプ内のMFCサンプル「DIBLOOK」を見て下さい。


  6. もうひとつ、メモリDCを利用してビットマップに絵を描いておく場合のパレットについて書いておきます。

    この例では、ビットマップはメンバー変数にし、OnInitialUpdateメンバー関数の中で初期化します。

    MyView.h

    class CMyView : public CView
    {
    〜
    // オーバーライド
    	// ClassWizard は仮想関数を生成しオーバーライドします。
    	//{{AFX_VIRTUAL(CMyView)
    	public:
    	virtual void OnDraw(CDC* pDC);  // このビューを描画する際にオーバーライドされます。
    	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    	virtual void OnInitialUpdate();
    	//}}AFX_VIRTUAL
    〜
    protected:
    	CBitmap m_Bitmap;
    〜
    };
    

    MyView.cpp

    
    void CMyView::OnInitialUpdate() 
    {
    	CView::OnInitialUpdate();
    
    	// TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください
    	CDC *pDC=GetDC();
    	CDC tmpDC;
    	tmpDC.CreateCompatibleDC(pDC);
    	m_Bitmap.CreateCompatibleBitmap(pDC,32*16,32*16);
    	ReleaseDC(pDC);
    
    	CBitmap  *pOldTmpBmp=tmpDC.SelectObject(&m_Bitmap);
    
    	CPalette *pOldTmpPal=tmpDC.SelectPalette(&m_Palette,FALSE);
    	tmpDC.RealizePalette();
    
    	for(int i=0;i<256;i++){
    		int x=i%16;
    		int y=i/16;
    		int r=((i>>5)&7)*255/7;
    		int g=((i>>2)&7)*255/7;
    		int b=((i>>0)&3)*255/3;
    		CBrush bsh(PALETTERGB(r,g,b));
    		tmpDC.FillRect(CRect(CPoint(x*32,y*32),CSize(32,32)),&bsh);
    //		tmpDC.FillSolidRect(x*32,y*32,32,32,PALETTERGB(r,g,b));
    //		tmpDC.SetPixel(x,y,PALETTERGB(r,g,b));
    	}
    	tmpDC.SelectPalette(pOldTmpPal,TRUE);
    
    	tmpDC.SelectObject(pOldTmpBmp);
    }
    
    void CMyView::OnDraw(CDC* pDC)
    {
    	CMyDoc* pDoc = GetDocument();
    	ASSERT_VALID(pDoc);
    
    	// TODO: この場所にネイティブ データ用の描画コードを追加します。
    	CDC tmpDC;
    	tmpDC.CreateCompatibleDC(pDC);
    	CBitmap *pOldBmp=tmpDC.SelectObject(&m_Bitmap);
    
    	CPalette *pOldPal=pDC->SelectPalette(&m_Palette,FALSE);
    	pDC->BitBlt(0,0,32*16,32*16,&tmpDC,0,0,SRCCOPY);
    	pDC->SelectPalette(pOldPal,TRUE);
    
    	tmpDC.SelectObject(pOldBmp);
    }
    
    ビットマップに描画する為のメモリDCに対してSelectPalette,RealizePaletteを行うのがポイントです。

    ひとつ注意として、何故かFillSolidRectで描くとPALETTERGBマクロを使っていてもうまく表示されません。 FillRectとCBrushを使えば描けますが…。


VC++ページへ戻る / 技術メモへ戻る
メールの送信先:ひしだま