Thread: Editor problems

  1. #1
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607

    Editor problems

    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)
    Last edited by VirtualAce; 10-23-2005 at 02:21 PM.

  2. #2
    Registered User
    Join Date
    Apr 2002
    Posts
    1,571
    I would try something like:

    Code:
    MAKEROP4(0x00AA0029, SRCCOPY)
    for the raster operation. This is copy the source, but leave the destination alone.
    "...the results are undefined, and we all know what "undefined" means: it means it works during development, it works during testing, and it blows up in your most important customers' faces." --Scott Meyers

  3. #3
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Ok....and where do I put that. Essentially it will copy pixels from my bitmap that are not the transparent color to the destination. But if the source is transparent, it will leave the destination alone?

    Hmm..

    More info Mr. Wizard if you please.

    Why do you think my AND XOR blit thingy didn't work? According to truth tables...it should work.

    Ok, I'm an idiot. I didn't check the damn API just the MFC help file under CDC. Ok so I can create my own raster operations with MAKEROP4. However if I use MaskBlt it looks as if this will do what I want. I might have to call out to the API to perform MaskBlt as I don't think MFC class CDC encapsulated it.

    Thanks a bunch. I wasn't sure if my post was clear enough.
    Last edited by VirtualAce; 10-24-2005 at 10:57 PM.

Popular pages Recent additions subscribe to a feed