14 октября 2008

Fading instead of cutting

Если текст не влезает в отведенное ему пространство на экране, можно просто его отрезать по краю области. Однако часто будет непонятно что текст обрезан - не очень очевидно что за "Пушкин" скрывается обрезанная "Пушкинская". Решение получше - добавить в конце троеточие: "Пушки...". Но тоже иногда забавно смотрится.

Красивый вариант - уходить в полупрозрачность:


Реализовать можно довольно просто:
  • перед отрисовкой текста запоминаем тот фрагмент фона, на котором текст будет уходить в полупрозрачность
  • после отрисовки текста выводим этот фрагмент с учетом полупрозрачности.

Для удобства использования этих двух процессов (который "перед" и который "после") вынесем первый в конструктор класса Fader, второй в деструктор. Использование будет такое:
{
Fader fader(dc, rect); // выполняется то, что "перед"
SomeDrawing(dc, rect); // непосредственно отрисовка текста
// выполняется то, что "после" - в деструкторе ~Fader()
}

Сама реализация предельно проста:
class Fader
{
public:
Fader::Fader(CDCHandle DC, const CRect& ClientRect) : DC_(DC), FaderRect_(ClientRect)
{
// опеределяем область, в которой будем уходить в полупрозрачность
const int FaderWidth = 32;
FaderRect_.left = max(FaderRect_.right - FaderWidth, 0);

// создаем bitmap
BYTE *Bits = static_cast(CreateBGRABitmap(Bitmap_, DC, FaderRect_.Size()));
if (!Bits)
return;

// копируем фон в bitmap
BitmapDC_.CreateCompatibleDC(DC);
BitmapDC_.SelectBitmap(Bitmap_);
BitBlt(BitmapDC_, 0, 0, FaderRect_.Width(), FaderRect_.Height(), DC, FaderRect_.left, FaderRect_.top, SRCCOPY);

// уводим в полупрозрачность
GdiFlush();
for (int Line = FaderRect_.Height(); Line; --Line)
for (int Pos = 0; Pos < FaderRect_.Width(); ++Pos, Bits += 4)
{
BYTE Alpha = Pos << 3; // как удачно, что FaderWidth является степенью двойки ;)
ApplyAlpha(Bits[0], Alpha);
ApplyAlpha(Bits[1], Alpha);
ApplyAlpha(Bits[2], Alpha);
Bits[3] = Alpha;
}
}

Fader::~Fader()
{
// накладываем полупрозрачный bitmap с фоном
if (BitmapDC_)
AlphaBlend(DC_, FaderRect_, BitmapDC_, CRect(CPoint(0, 0), FaderRect_.Size()));
}

private:
HDC DC_;
CRect FaderRect_;
CBitmap Bitmap_;
CDC BitmapDC_;
};

PS. Парочка helper-ов, которые используются выше:
void *CreateBGRABitmap(CBitmap& Bitmap, HDC DC, CSize Size, bool UpsideDown)
{
BITMAPV5HEADER bi;
memset(&bi, 0, sizeof(bi));
bi.bV5Size = sizeof (BITMAPV5HEADER);
bi.bV5Width = Size.cx;
bi.bV5Height = UpsideDown ? Size.cy : -Size.cy;
bi.bV5Planes = 1;
bi.bV5BitCount = 32;
bi.bV5Compression = BI_BITFIELDS;
bi.bV5RedMask = 0x00FF0000;
bi.bV5GreenMask = 0x0000FF00;
bi.bV5BlueMask = 0x000000FF;
bi.bV5AlphaMask = 0xFF000000;
void *Bits = NULL;
Bitmap.CreateDIBSection(DC, (BITMAPINFO*)&bi, DIB_RGB_COLORS, &Bits, NULL, 0);
return Bits;
}

void ApplyAlpha(BYTE& Value, BYTE Alpha)
{
Value = ((unsigned)Value * Alpha) >> 8;
}