I'm still working on my tile editor and I'm having some problems with layers. We all know it is a pain in the arse to blit non-rectangular images to a device context in Windows.
But I remember back in the days of BASIC programming that if you created a mask of the image - blitted it using the bitwise AND operator and then blitted the color image using the bitwise OR operator, it was akin to color keying.
So I thought I'd try it in Windows but it doesn't quite work. It does blit the transparency correctly, but when it is blitted over an already existing image it whitens those pixels that show through.
Here is my code: You might have to be fairly adept at C++ and Win32 API to understand what I'm doing here.
Code:
/////////////////////////////////////////////////////////////////////////////
// CZeldaEditorDoc commands
void CZeldaEditorDoc::AddToTileList(CString strFilename,CBitmap *Image,CDC *ImageDC,
DWORD mRed,DWORD mGreen,DWORD mBlue)
{
if (!Image)
{
::MessageBox(0,"CBitmap *Image not valid","CZeldaEditorDoc::SetTileList",0);
return;
}
if (!ImageDC)
{
::MessageBox(0,"CDC *ImageDC not valid","CZeldaEditorDoc::SetTileList",0);
return;
}
m_strFilename=strFilename;
//Get bitmap info
BITMAP info;
//::MessageBox(0,"Getting bitmap object","CZeldaEditorDoc::SetTileList()",0);
Image->GetObject(sizeof(BITMAP),&info);
DWORD ImageWidth=info.bmWidth;
DWORD ImageHeight=info.bmHeight;
//Tile size
DWORD TileSize=m_iTileSize;
//Allocate buffer for tile and mask
//::MessageBox(0,"Allocating temp tile buffer","CZeldaEditorDoc::SetTileList",0);
DWORD *TileBuffer=new DWORD[TileSize*TileSize];
DWORD *InvTileBuffer=new DWORD[TileSize*TileSize];
//Check for valid pointers
if (!TileBuffer)
{
::MessageBox(0,"Unable to create tile buffer","CZeldaEditorDoc::SetTileList()",0);
return;
}
if (!InvTileBuffer)
{
::MessageBox(0,"Unable to create tile mask buffer","CZeldaEditorDoc::SetTileList()",0);
return;
}
//Find out how many tiles horiz and vert
int NumHoriz=ImageWidth/TileSize;
int NumVert=ImageHeight/TileSize;
//Setup some variables we need
DWORD WidthCounter=0;
DWORD HeightCounter=0;
DWORD StartX=0,StartY=0;
DWORD CurrentX=0,CurrentY=0;
DWORD TileOffset=0;
DWORD PixelColor,Red,Green,Blue;
DWORD NumTilesCreated=0;
//Debugging text string if we need it
CString DebugText;
//Huge loop
do
{
do
{
do
{
//Get the pixel color from the image
PixelColor=ImageDC->GetPixel(CurrentX,CurrentY);
//Extract the red green and blue components
Red=(PixelColor & 0xFF0000) >> 16;
Green=(PixelColor & 0xFF00) >>8;
Blue=(PixelColor & 0xFF);
//Stick RGBs into tile buffer
TileBuffer[TileOffset]=RGB(Red,Green,Blue);
//If pixel matches transparent, set to white, else black
if ((Red==mRed) && (Green==mGreen) && (Blue==mBlue))
{
//::MessageBox(0,"Transparent found",0,0);
InvTileBuffer[TileOffset]=RGB(255,255,255);
} else InvTileBuffer[TileOffset]=0;
CurrentX++;
TileOffset++;
WidthCounter++;
} while (WidthCounter<TileSize);
WidthCounter=0;
CurrentX=StartX;
CurrentY++;
HeightCounter++;
} while (HeightCounter<TileSize);
//Reset TileOffset
TileOffset=0;
//Add tile to tile manager
m_pTileManager->Add(*ImageDC,TileSize,TileSize,1,32,TileBuffer,InvTileBuffer);
//Increment counter
NumTilesCreated++;
StartX+=TileSize;
//Check for wrap on X
if (StartX>=ImageWidth)
{
StartX=0;
StartY+=TileSize;
}
CurrentX=StartX;
CurrentY=StartY;
HeightCounter=0;
} while (StartY<ImageHeight);
//Clean up
delete [] TileBuffer;
delete [] InvTileBuffer;
}
That code creates the mask and the image. I'm extracting tiles from a much larger bitmap and sticking them into a TileManager list which manages the global list of all available tiles that you can put down on the maps.
Here is the tile manager add function. It is inlined, hence the lack of a class type before the function name:
Code:
bool Add(CDC &pDC,DWORD iHoriz,DWORD iVert,int iPlanes,int iColorDepth,
DWORD *pBuffer,DWORD *pMaskBuffer)
{
CTile *tile=new CTile();
tile->Create(pDC,iHoriz,iVert,iPlanes,iColorDepth,pBuffer,pMaskBuffer);
tile->m_dwID=TileVector.size();
TileVector.push_back(tile);
return true;
}
And here is the CTile::Create() function, again inlined in the code:
Code:
void Create(CDC &dc,DWORD iHoriz,DWORD iVert,int iPlanes,int iColorDepth,
DWORD *pBuffer,DWORD *pMaskBuffer)
{
//Setup class vars
m_pTileImage=new CBitmap;
m_pInvTileImage=new CBitmap;
//Create bitmap and check if valid
BOOL ok=m_pTileImage->CreateBitmap(iHoriz,iVert,iPlanes,iColorDepth,pBuffer);
if (!ok)
{
::MessageBox(0,"TileManager::Create() - Failed to create bitmap",0,0);
}
//Create mask bitmap and check if valid
ok=m_pInvTileImage->CreateBitmap(iHoriz,iVert,iPlanes,iColorDepth,pMaskBuffer);
if (!ok)
{
::MessageBox(0,"TileManager::Create() - Failed to create mask bitmap",0,0);
}
//Get bitmap info for image - same for mask
m_pTileImage->GetObject(sizeof(BITMAP),&m_pInfo);
//Create compatible DC for image
ImageDC.CreateCompatibleDC(&dc);
//Select bitmap into DC
ImageDC.SelectObject((CBitmap *)m_pTileImage);
//Create compatible DC for mask
AndImageDC.CreateCompatibleDC(&dc);
//Select bitmap into DC
AndImageDC.SelectObject((CBitmap *)m_pInvTileImage);
}
Ok, now that sets everything up for rendering. This is the CZeldaEditorView::OnDraw() function.
Code:
void CZeldaEditorView::OnDraw(CDC* pDC)
{
//Get pointer to document class
CZeldaEditorDoc* pDoc = (CZeldaEditorDoc *)GetDocument();
// TODO: add draw code here
//Get pointer to main frame class
CMainFrame *ptrFrame=(CMainFrame *)AfxGetMainWnd();
//Get number of maps in manager (number of layers)
DWORD numMaps=pDoc->m_pMapManager->GetNumberOfMaps();
//If tile manager is valid and this is not the move map (layer 0)
//then draw tile map
if (pDoc->m_pTileManager && numMaps>1)
{
//Get tile size from document
int TileSize=pDoc->m_iTileSize;
//Get our client rect
CRect rect;
GetClientRect(&rect);
//Create black solid brush
CBrush brush;
brush.CreateSolidBrush(RGB(0,0,0));
//Rect for use later
CRect boxrect;
//Do some math to set up the render based on scrollx
//scrolly, mapsizex, mapsizey, etc
int iOffsetHoriz=abs(m_iScrollX/m_iGridSize);
int iOffsetVert=abs(m_iScrollY/m_iGridSize);
int iStartTile=iOffsetVert*m_iMapSizeX+iOffsetVert;
int iCurTile=iStartTile;
//Iterate through all layers (all maps)
for (DWORD map=m_dwStartMapNum;map<m_dwEndMapNum;map++)
{
//Get pointer to Map class
CMap *ptrMap=pDoc->m_pMapManager->GetMapClass(map);
//Get pointer to movement map
//CMap *ptrMove=pDoc->m_pMapManager->GetMapClass(0);
//Debug string object
CString debug;
//Setup variables for render
int CurrentY=(-m_iScrollY/(m_iGridSize+2));
int CurrentX=(-m_iScrollX/(m_iGridSize+2));
int OriginX=CurrentX;
DWORD offset=CurrentY*pDoc->m_iMapSizeX+CurrentX;
int offsetx=m_iScrollX % (m_iGridSize+2);
int offsety=m_iScrollY % (m_iGridSize+2);
DWORD startoffset=offset;
//Another rect structure
RECT grect;
for (int i=offsety;i<rect.bottom;i+=(m_iGridSize+2))
{
for (int j=offsetx;j<rect.right;j+=(m_iGridSize+2))
{
//Get ID in map at current rendering offset
DWORD ID=ptrMap->GetValueAtOffset(offset);
//Get movement state of tile
//DWORD MoveState=ptrMove->GetValueAtOffset(offset);
//Setup rect
grect.left=j;
grect.top=i;
grect.right=j+m_iGridSize+2;
grect.bottom=i+m_iGridSize+2;
//Draw frame rect with black brush
pDC->FrameRect(&grect,&brush);
//Tint cell red if movement == false
//if (m_dwEndMapNum-m_dwStartMapNum==1)
//{
// if (MoveState!=0) pDC->FillRect(&grect,new CBrush(RGB(255,0,0)));
//}
//If cell is not empty
if (ID!=0xFFFFFFFF)
{
//Get DC from tile manager for tile ID
CDC *tempDC=pDoc->m_pTileManager->GetTileDC(ID);
//Get mask DC from tile manager for tile ID
CDC *tempAndDC=pDoc->m_pTileManager->GetMaskDC(ID);
//Stretch/shrink mask to fit grid size - AND blit
pDC->StretchBlt(j+1,i+1,m_iGridSize,m_iGridSize,tempAndDC,0,0,TileSize,TileSize,SRCAND);
//Stretch shrink bitmap to fit grid size - OR blit
pDC->StretchBlt(j+1,i+1,m_iGridSize,m_iGridSize,tempDC,0,0,TileSize,TileSize,SRCPAINT);
}
//Increment vars
offset++;
CurrentX++;
//Are we off the map on horiz?
//Possible to be off the map, but not out of client rect
if (CurrentX>=pDoc->m_iMapSizeX-1) break;
}
//Move down one row
CurrentX=OriginX;
startoffset+=pDoc->m_iMapSizeX;
offset=startoffset;
CurrentY++;
//Are we off the map on vert
if (CurrentY>pDoc->m_iMapSizeY-1) break;
}
}
}
//Layer print out
CString layer;
//To view one layer set m_dwEndMapMum to desired layer+1
//and set m_dwStartMapNum to desired layer
//If difference is only 1 we are only viewing one layer
if (m_dwEndMapNum-m_dwStartMapNum>1)
{
layer="Viewing all layers";
}
else
{
layer.Format("Viewing layer %u",m_dwCurrentMapNum);
}
//Write info to client DC
pDC->TextOut(0,0,layer);
}
I know it's a lot of code but you can see I've done nearly all the work here. I just need to know why the AND mask blit coupled with the OR mask blit is not working.
Don't worry too much about the structure.
Here it is:
CTile - tile class
CTileManager - vector of tiles
CMap - map class
CMapManager - vector of maps (each map is one layer)