4

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

dsp_user
  • 2,061
  • 2
  • 16
  • 23
  • I think that this question is similar to yours: https://stackoverflow.com/questions/23702868/how-do-you-do-both-blending-and-transparency-using-just-pure-vcl-on-bitmapped-im You may want to look at the comments bellow it - the Graphics32 library looks quite promising. In particular take a look at the "CombineMode": https://graphics32.github.io/Docs/Units/GR32/Classes/TCustomBitmap32/Properties/CombineMode.htm – Radek Jan 01 '18 at 22:48
  • Thanks, that library looks interesting and is rather comprehensive. I'll look into it even if I decide to write my own transparent overlay routine. – dsp_user Jan 02 '18 at 07:00

1 Answers1

0

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

dsp_user
  • 2,061
  • 2
  • 16
  • 23