Search code examples
c++transparencyvcl

Transparent spectrogram selection overlays


I'm trying to create transparent selection overlays on top of a spectrogram but it doesn't quite work. I mean the result is not really satisfactory. In contrast, the overlays painted on top of a waveform work well but I need to support both the waveform as well as the spectrogram view (and maybe other views in the future)

The selection overlay works fine in the waveform view

enter image description here

Here's the selection overlay in the spectrogram view (the selection looks really bad and obscures parts of the spectrogram)

enter image description here

The code (VCL) is the same for both views

void TWaveDisplayContainer::DrawSelectedRegion(){


        if(selRange.selStart.x == selRange.selEnd.x){
            DrawCursorPosition( selRange.selStart.x);
            return;
        }
        Graphics::TBitmap *pWaveBmp = eContainerView == WAVEFORM ? pWaveBmpLeft : pSfftBmpLeft;
        TRect selRect(selRange.selStart.x, 0, selRange.selEnd.x, pWaveLeft->Height);
        TCanvas *pCanvas = pWaveLeft->Canvas;
        int copyMode = pCanvas->CopyMode;

        pCanvas->Draw(0,0, pWaveBmp);
        pCanvas->Brush->Color = clActiveBorder;
        pCanvas->CopyMode = cmSrcAnd;
        pCanvas->Rectangle(selRect);
        pCanvas->CopyRect(selRect, pWaveBmp->Canvas, selRect);
        pCanvas->CopyMode = copyMode;

        if(numChannels == 2){

            TCanvas* pOtherCanvas = pWaveRight->Canvas;
            pWaveBmp = eContainerView == WAVEFORM ? pWaveBmpRight : 
 pSfftBmpRight;
            pOtherCanvas->Draw(0,0, pWaveBmp);
            pOtherCanvas->Brush->Color = clActiveBorder;
            pOtherCanvas->CopyMode = cmSrcAnd;
            pOtherCanvas->Rectangle(selRect);
            pOtherCanvas->CopyRect(selRect, pWaveBmp->Canvas, selRect);
            pOtherCanvas->CopyMode = copyMode;

        }
}

So, I'm using cmSrcAnd copy mode and the CopyRect method to do the actual painting/drawing (TCanvas corresponds to a device context (HDC on Windows). I think, since a spectrogram, unlike a waveform, doesn't really have a single background colour using simple mixing copy modes isn't going to work well in most cases.

Note that I can still accomplish what I want but that would require messing with the individual pixels, which is something I'd like to avoid if possible)

I'm basically looking for an API (VCL wraps GDI so even WINAPI is fine) able to do this.

Any help is much appreciated


Solution

  • I'm going to answer my own question and hopefully this will prove to be useful to some people. Since there's apparently no way this can be achieved in either plain VCL or using WINAPI (except in some situations), I've written a simple function that blends a bitmap (32bpp / 24bpp) with an overlay colour (any colour). The actual result will also depend on the weights (w0,w1) given to the red, green and blue components of an individual pixel. Changing these will produce an overlay that leans more toward the spectrogram colour or the overlay colour respectively.

    The code

    Graphics::TBitmap *TSelectionOverlay::GetSelectionOverlay(Graphics::TBitmap *pBmp, TColor selColour,
    TRect &rect, EChannel eChannel){
    
     Graphics::TBitmap *pSelOverlay = eChannel==LEFT ? pSelOverlayLeft : pSelOverlayRight;
    
     const unsigned cGreenShift = 8;
     const unsigned cBlueShift = 16;
    
     const unsigned overlayWidth = abs(rect.right-rect.left);
     const unsigned overlayHeight = abs(rect.bottom-rect.top);
    
     pSelOverlay->Width = pBmp->Width;
     pSelOverlay->Height = pBmp->Height;
    
     const unsigned startOffset = rect.right>rect.left ? rect.left : rect.right;
    
     pSelOverlay->Assign(pBmp);
    
     unsigned char cRed0, cGreen0, cBlue0,cRed1, cGreen1, cBlue1, bRedColor0, bGreenColor0, bBlueColor0;
    
     cBlue0 =   selColour >> cBlueShift;
     cGreen0 =  selColour >> cGreenShift & 0xFF;
     cRed0 =    selColour & 0xFF;
    
     unsigned *pPixel;
    
     for(int i=0;i<overlayHeight;i++){
    
        pPixel = (unsigned*)pSelOverlay->ScanLine[i];//provides access to the pixel array
        for(int j=0;j<overlayWidth;j++){
    
        unsigned pixel =  pPixel[startOffset+j];
        cBlue1 =    pixel >> cBlueShift;
        cGreen1 =   pixel >> cGreenShift & 0xFF;
        cRed1 =     pixel & 0xFF;
    
        //blend the current bitmap pixel with the overlay colour
    
        const float w0 = 0.5f; //these weights influence the appearance of the overlay (here we use 50%)
        const float w1 = 0.5f;
    
        bRedColor0 = cRed0*w0+cRed1*w1;
        bGreenColor0 = cGreen0*w0+cGreen1*w1);
        bBlueColor0 = cBlue0*w0+cBlue1*w1;
    
        pPixel[startOffset+j] =  ((bBlueColor0 << cBlueShift) | (bGreenColor0 << cGreenShift)) | bRedColor0;
        }
     }
    
      return pSelOverlay;
    }
    

    Note that for some reason, CopyRect used with a CopyMode value of cmSrcCopy didn't work well so I used Draw instead.

    pCanvas->CopyMode = cmSrcCopy;
    pCanvas->CopyRect(dstRect, pSelOverlay->Canvas, srcRec);//this still didn't work well--possibly a bug
    

    so I used

    pCanvas->Draw(0,0, pSelOverlay);
    

    The result

    enter image description here