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
Here's the selection overlay in the spectrogram view (the selection looks really bad and obscures parts of the spectrogram)
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
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