mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-05 14:18:53 +02:00
This #ifdeffed out TEST_CARD is useful for javascript code that shows how a theme will look, when applied to Audacity. With the code enabled, each pixel of audacity is painted with a colour that encodes the location in the theme image map that was used. Net result - in javascript you can change any colour in the theme cache and instantly see how Audacity will look.
435 lines
13 KiB
C++
435 lines
13 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
ImageManipulation.cpp
|
|
|
|
Dominic Mazzoni
|
|
James Crook
|
|
|
|
wxWidgets license (Dominic to confirm).
|
|
|
|
********************************************************************//*!
|
|
|
|
\file ImageManipulation.cpp
|
|
|
|
Provides Image Manipulation functions.
|
|
|
|
wxWidgets misses some important functions involving cutting and
|
|
pasting bitmaps, and (in version 2.6.1) is patchy in support of alpha
|
|
channel. This collection of functions fills that gap.
|
|
|
|
*//*********************************************************************/
|
|
|
|
|
|
#include <wx/image.h>
|
|
|
|
#include "Audacity.h"
|
|
#include "ImageManipulation.h"
|
|
#include "AllThemeResources.h"
|
|
#include "Theme.h"
|
|
|
|
/// This looks at the first pixel in the image, and shifts
|
|
/// the entire image by the vector difference between that
|
|
/// pixel and the dstColour. For better control, use
|
|
/// ChangeImageColour(wxImage, wxColour*, wxColour*) below
|
|
std::unique_ptr<wxImage> ChangeImageColour(wxImage * srcImage, wxColour & dstColour)
|
|
{
|
|
unsigned char *src = srcImage->GetData();
|
|
wxColour c;
|
|
c.Set(src[0], src[1], src[2]);
|
|
return ChangeImageColour(srcImage, c, dstColour);
|
|
}
|
|
|
|
///This will explicitly shift the image color from
|
|
///srcColour to dstColour.
|
|
std::unique_ptr<wxImage> ChangeImageColour(wxImage * srcImage,
|
|
wxColour & srcColour,
|
|
wxColour & dstColour)
|
|
{
|
|
// This function takes a source image, which it assumes to
|
|
// be grayscale, and smoothly changes the overall color
|
|
// to the specified color, and returns the result as a
|
|
// NEW image. This works well for grayscale 3D images.
|
|
// Audacity uses this routines to make the buttons
|
|
// (skip-start, play, stop, record, skip-end) adapt to
|
|
// the color scheme of the user.
|
|
|
|
unsigned char *src = srcImage->GetData();
|
|
int width = srcImage->GetWidth();
|
|
int height = srcImage->GetHeight();
|
|
|
|
auto dstImage = std::make_unique<wxImage>(width, height);
|
|
unsigned char *dst = dstImage->GetData();
|
|
|
|
|
|
//Get the source color
|
|
int srcVal[3], srcOpp[3];
|
|
srcVal[0] = srcColour.Red();
|
|
srcVal[1] = srcColour.Green();
|
|
srcVal[2] = srcColour.Blue();
|
|
|
|
int dstVal[3], dstOpp[3];
|
|
dstVal[0] = dstColour.Red();
|
|
dstVal[1] = dstColour.Green();
|
|
dstVal[2] = dstColour.Blue();
|
|
|
|
int i;
|
|
for (i = 0; i < 3; i++) {
|
|
srcOpp[i] = 256 - srcVal[i]; // avoid zero!
|
|
dstOpp[i] = 255 - dstVal[i];
|
|
}
|
|
|
|
int c = 0;
|
|
for (i = 0; i < width * height * 3; i++) {
|
|
int s = (int) *src;
|
|
|
|
if (s >= srcVal[c])
|
|
*dst++ = dstVal[c] + dstOpp[c] * (s - srcVal[c]) / srcOpp[c];
|
|
|
|
else
|
|
*dst++ = dstVal[c] * s / srcVal[c];
|
|
src++;
|
|
c = (c + 1) % 3;
|
|
}
|
|
|
|
return dstImage;
|
|
}
|
|
|
|
/// Takes a background image, foreground image, and mask
|
|
/// (i.e. the alpha channel for the foreground), and
|
|
/// returns an NEW image where the foreground has been
|
|
/// overlaid onto the background using alpha-blending,
|
|
/// at location (xoff, yoff).
|
|
std::unique_ptr<wxImage> OverlayImage(wxImage * background, wxImage * foreground,
|
|
wxImage * mask, int xoff, int yoff)
|
|
{
|
|
unsigned char *bg = background->GetData();
|
|
unsigned char *fg = foreground->GetData();
|
|
unsigned char *mk = mask->GetData();
|
|
|
|
int bgWidth = background->GetWidth();
|
|
int bgHeight = background->GetHeight();
|
|
int fgWidth = foreground->GetWidth();
|
|
int fgHeight = foreground->GetHeight();
|
|
int mkWidth = mask->GetWidth();
|
|
int mkHeight = mask->GetHeight();
|
|
|
|
|
|
//Now, determine the dimensions of the images to be masked together
|
|
//on top of the background. This should be equal to the area of the
|
|
//smaller of the foreground and the mask, as long as it is
|
|
//within the area of the background, given the offset.
|
|
|
|
//Make sure the foreground size is no bigger than the mask
|
|
int wCutoff = (fgWidth < mkWidth) ? fgWidth : mkWidth;
|
|
int hCutoff = (fgHeight < mkHeight) ? fgHeight : mkHeight;
|
|
|
|
|
|
// If the masked foreground + offset is bigger than the background, masking
|
|
// should only occur within these bounds of the foreground image
|
|
wCutoff = (bgWidth - xoff > wCutoff) ? wCutoff : bgWidth - xoff;
|
|
hCutoff = (bgHeight - yoff > hCutoff) ? hCutoff : bgHeight - yoff;
|
|
|
|
|
|
//Make a NEW image the size of the background
|
|
auto dstImage = std::make_unique<wxImage>(bgWidth, bgHeight);
|
|
unsigned char *dst = dstImage->GetData();
|
|
memcpy(dst, bg, bgWidth * bgHeight * 3);
|
|
|
|
|
|
// Go through the foreground image bit by bit and mask it on to the
|
|
// background, at an offset of xoff,yoff.
|
|
// BUT...Don't go beyond the size of the background image,
|
|
// the foreground image, or the mask
|
|
int x, y;
|
|
for (y = 0; y < hCutoff; y++) {
|
|
|
|
unsigned char *bkp = bg + 3 * ((y + yoff) * bgWidth + xoff);
|
|
unsigned char *dstp = dst + 3 * ((y + yoff) * bgWidth + xoff);
|
|
|
|
for (x = 0; x < wCutoff; x++) {
|
|
|
|
int value = mk[3 * (y * mkWidth + x)];
|
|
int opp = 255 - value;
|
|
|
|
for (int c = 0; c < 3; c++)
|
|
dstp[x * 3 + c] =
|
|
((bkp[x * 3 + c] * opp) +
|
|
(fg[3 * (y * fgWidth + x) + c] * value)) / 255;
|
|
}
|
|
}
|
|
return dstImage;
|
|
}
|
|
|
|
/// Takes a background image, foreground image, and mask
|
|
/// (i.e. the alpha channel for the foreground), and
|
|
/// returns an NEW image where the foreground has been
|
|
/// overlaid onto the background using alpha-blending,
|
|
/// at location (xoff, yoff).
|
|
std::unique_ptr<wxImage> OverlayImage(teBmps eBack, teBmps eForeground,
|
|
int xoff, int yoff)
|
|
{
|
|
wxImage imgBack(theTheme.Image( eBack ));
|
|
wxImage imgFore(theTheme.Image( eForeground ));
|
|
|
|
|
|
// TMP: dmazzoni - just so the code runs even though not all of
|
|
// our images have transparency...
|
|
if (!imgFore.HasAlpha())
|
|
return std::make_unique<wxImage>(imgBack);
|
|
|
|
|
|
wxASSERT( imgFore.HasAlpha() );
|
|
|
|
unsigned char *bg = imgBack.GetData();
|
|
unsigned char *fg = imgFore.GetData();
|
|
unsigned char *mk = imgFore.GetAlpha();
|
|
|
|
int bgWidth = imgBack.GetWidth();
|
|
int bgHeight = imgBack.GetHeight();
|
|
int fgWidth = imgFore.GetWidth();
|
|
int fgHeight = imgFore.GetHeight();
|
|
|
|
|
|
//Now, determine the dimensions of the images to be masked together
|
|
//on top of the background. This should be equal to the area of the
|
|
//smaller of the foreground and the mask, as long as it is
|
|
//within the area of the background, given the offset.
|
|
|
|
//Make sure the foreground size is no bigger than the mask
|
|
int wCutoff = fgWidth;
|
|
int hCutoff = fgHeight;
|
|
|
|
|
|
// If the masked foreground + offset is bigger than the background, masking
|
|
// should only occur within these bounds of the foreground image
|
|
wCutoff = (bgWidth - xoff > wCutoff) ? wCutoff : bgWidth - xoff;
|
|
hCutoff = (bgHeight - yoff > hCutoff) ? hCutoff : bgHeight - yoff;
|
|
|
|
//Make a NEW image the size of the background
|
|
auto dstImage = std::make_unique<wxImage>(bgWidth, bgHeight);
|
|
unsigned char *dst = dstImage->GetData();
|
|
memcpy(dst, bg, bgWidth * bgHeight * 3);
|
|
|
|
// If background image has tranparency, then we want to blend with the
|
|
// current backgorund colour.
|
|
if( imgBack.HasAlpha() ){
|
|
unsigned char *pAlpha = imgBack.GetAlpha();
|
|
wxColour c = theTheme.Colour( clrMedium );
|
|
unsigned char onePixImage[3];
|
|
// GetData() guarantees RGB order [wxWidgets does the ocnversion]
|
|
onePixImage[ 0 ] = c.Red();
|
|
onePixImage[ 1 ] = c.Green();
|
|
onePixImage[ 2 ] = c.Blue();
|
|
for( int i=0;i< bgWidth*bgHeight;i++){
|
|
unsigned char * pPix = &dst[ 3*i];
|
|
float alpha = 1.0 - (pAlpha[i]/255.0);
|
|
pPix[0] = pPix[0] + alpha *( (int)onePixImage[0]-(int)pPix[0]);
|
|
pPix[1] = pPix[1] + alpha *( (int)onePixImage[1]-(int)pPix[1]);
|
|
pPix[2] = pPix[2] + alpha *( (int)onePixImage[2]-(int)pPix[2]);
|
|
}
|
|
}
|
|
|
|
// Go through the foreground image bit by bit and mask it on to the
|
|
// background, at an offset of xoff,yoff.
|
|
// BUT...Don't go beyond the size of the background image,
|
|
// the foreground image, or the mask
|
|
int x, y;
|
|
for (y = 0; y < hCutoff; y++) {
|
|
|
|
unsigned char *bkp = bg + 3 * ((y + yoff) * bgWidth + xoff);
|
|
unsigned char *dstp = dst + 3 * ((y + yoff) * bgWidth + xoff);
|
|
|
|
for (x = 0; x < wCutoff; x++) {
|
|
|
|
int value = mk[(y * fgWidth + x)];// Don't multiply by 3...
|
|
int opp = 255 - value;
|
|
|
|
for (int c = 0; c < 3; c++)
|
|
dstp[x * 3 + c] =
|
|
((bkp[x * 3 + c] * opp) +
|
|
(fg[3 * (y * fgWidth + x) + c] * value)) / 255;
|
|
}
|
|
}
|
|
return dstImage;
|
|
}
|
|
|
|
// Creates an image with a solid background color
|
|
std::unique_ptr<wxImage> CreateBackground(int width, int height, wxColour colour)
|
|
{
|
|
auto i = std::make_unique<wxImage>(width, height);
|
|
unsigned char *ip;
|
|
int srcVal[3];
|
|
int x;
|
|
|
|
srcVal[0] = colour.Red();
|
|
srcVal[1] = colour.Green();
|
|
srcVal[2] = colour.Blue();
|
|
|
|
ip = i->GetData();
|
|
for(x=0; x<width*height; x++) {
|
|
*ip++ = srcVal[0];
|
|
*ip++ = srcVal[1];
|
|
*ip++ = srcVal[2];
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
// Creates an image with the Mac OS X Aqua stripes, to be used
|
|
// as a background
|
|
std::unique_ptr<wxImage> CreateAquaBackground(int width, int height, int offset)
|
|
{
|
|
auto image = std::make_unique<wxImage>(width, height);
|
|
unsigned char *ip = image->GetData();
|
|
unsigned char val[4] = {231, 239, 255, 239};
|
|
unsigned char v;
|
|
int x, y;
|
|
|
|
for(y=0; y<height; y++) {
|
|
v = val[(y+offset)%4];
|
|
for(x=0; x<width*3; x++)
|
|
*ip++ = v;
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
std::unique_ptr<wxImage> CreateSysBackground
|
|
#ifdef USE_AQUA_THEME
|
|
(int width, int height, int offset, wxColour colour)
|
|
#else
|
|
(int width, int height, int WXUNUSED(offset), wxColour colour)
|
|
#endif
|
|
{
|
|
#ifdef USE_AQUA_THEME
|
|
return CreateAquaBackground(width, height, offset);
|
|
#else
|
|
return CreateBackground(width, height, colour);
|
|
#endif
|
|
}
|
|
|
|
/// Pastes one image into another including the alpha channel.
|
|
/// Differs from OverlayImage in that:
|
|
/// Happens in place to existing background image.
|
|
/// Pastes image on top; no blending with existing background is done.
|
|
void PasteSubImage( wxImage * background, wxImage * foreground, int xoff, int yoff )
|
|
{
|
|
|
|
unsigned char *bg = background->GetData();
|
|
unsigned char *fg = foreground->GetData();
|
|
unsigned char *bgAlpha = background->HasAlpha() ? background->GetAlpha() : NULL;
|
|
unsigned char *fgAlpha = foreground->HasAlpha() ? foreground->GetAlpha() : NULL;
|
|
// For testing... Set as if no alpha in foreground....
|
|
// fgAlpha = NULL;
|
|
|
|
int bgWidth = background->GetWidth();
|
|
int bgHeight = background->GetHeight();
|
|
int fgWidth = foreground->GetWidth();
|
|
int fgHeight = foreground->GetHeight();
|
|
|
|
int wCutoff = fgWidth;
|
|
int hCutoff = fgHeight;
|
|
|
|
// If the masked foreground + offset is bigger than the background, masking
|
|
// should only occur within these bounds of the foreground image
|
|
wCutoff = (bgWidth - xoff > wCutoff) ? wCutoff : bgWidth - xoff;
|
|
hCutoff = (bgHeight - yoff > hCutoff) ? hCutoff : bgHeight - yoff;
|
|
|
|
// Go through the foreground image bit by bit and place it on to the
|
|
// background, at an offset of xoff,yoff.
|
|
// Don't go beyond the size of the background image,
|
|
// or the foreground image.
|
|
int y;
|
|
unsigned char *bkp;
|
|
unsigned char *fgp;
|
|
unsigned char *bgAlphap;
|
|
unsigned char *fgAlphap;
|
|
for (y = 0; y < hCutoff; y++) {
|
|
// RGB bytes
|
|
bkp = bg + 3 * ((y + yoff) * bgWidth + xoff);
|
|
fgp = fg + 3 * ( y * fgWidth);
|
|
memcpy( bkp, fgp, 3 * wCutoff );
|
|
|
|
// Alpha bytes.
|
|
if( bgAlpha )
|
|
{
|
|
bgAlphap = bgAlpha + ((y+yoff) * bgWidth + xoff );
|
|
if( fgAlpha )
|
|
{
|
|
fgAlphap = fgAlpha + (y * fgWidth );
|
|
memcpy( bgAlphap, fgAlphap, wCutoff );
|
|
}
|
|
else
|
|
{
|
|
memset( bgAlphap, 255, wCutoff );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Gets a rectangle from within another image, INCLUDING the alpha channel
|
|
/// \bug in wxWidgets, wxImage::GetSubImage should do this itself.
|
|
wxImage GetSubImageWithAlpha( const wxImage & Src, const wxRect &rect )
|
|
{
|
|
//First part of this code is lifted from wxImage::GetSubImage() source code.
|
|
wxImage image;
|
|
|
|
wxCHECK_MSG( Src.Ok(), image, wxT("invalid image") );
|
|
|
|
wxCHECK_MSG( (rect.GetLeft()>=0) && (rect.GetTop()>=0) && (
|
|
rect.GetRight()<=Src.GetWidth()) && (rect.GetBottom()<=Src.GetHeight()),
|
|
image, wxT("invalid subimage size") );
|
|
|
|
int subwidth=rect.GetWidth();
|
|
const int subheight=rect.GetHeight();
|
|
|
|
image.Create( subwidth, subheight, false );
|
|
|
|
unsigned char *subdata = image.GetData(), *data=Src.GetData();
|
|
|
|
wxCHECK_MSG( subdata, image, wxT("unable to create image") );
|
|
|
|
// JKC: Quick hack - don't deal with masks - need to understand macro M_IMGDATA first.
|
|
// if (Src.M_IMGDATA->m_hasMask)
|
|
// image.SetMaskColour( Src.M_IMGDATA->m_maskRed, Src.M_IMGDATA->m_maskGreen, Src.M_IMGDATA->m_maskBlue );
|
|
|
|
int subleft=3*rect.GetLeft();
|
|
int width=3*Src.GetWidth();
|
|
subwidth*=3;
|
|
|
|
data+=rect.GetTop()*width+subleft;
|
|
|
|
for (long j = 0; j < subheight; ++j)
|
|
{
|
|
memcpy( subdata, data, subwidth);
|
|
subdata+=subwidth;
|
|
data+=width;
|
|
}
|
|
|
|
image.InitAlpha();
|
|
if( !Src.HasAlpha() )
|
|
return image;
|
|
// OK, so we've copied the RGB data.
|
|
// Now do the Alpha channel.
|
|
//wxASSERT( Src.HasAlpha() );
|
|
|
|
subleft/=3;
|
|
width/=3;
|
|
subwidth/=3;
|
|
|
|
data =Src.GetAlpha();
|
|
subdata =image.GetAlpha();
|
|
|
|
data+=rect.GetTop()*width+subleft;
|
|
|
|
for (long j = 0; j < subheight; ++j)
|
|
{
|
|
memcpy( subdata, data, subwidth);
|
|
subdata+=subwidth;
|
|
data+=width;
|
|
}
|
|
return image;
|
|
}
|