// ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #include "button.h" #include #include #include #include #include #include #include "image_tools.h" using namespace zen; void zen::setImage(wxBitmapButton& button, const wxBitmap& bmp) { if (!isEqual(button.GetBitmapLabel(), bmp)) button.SetBitmapLabel(bmp); } BitmapButton::BitmapButton(wxWindow* parent, wxWindowID id, const wxString& label, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name) : wxBitmapButton(parent, id, wxNullBitmap, pos, size, style | wxBU_AUTODRAW, validator, name), spaceAfter_(0), spaceBefore_(0), innerBorderSize(5) { SetLabel(label); } void BitmapButton::setBitmapFront(const wxBitmap& bitmap, int spaceAfter) { if (!isEqual(bitmap, bitmapFront) || spaceAfter_ != spaceAfter) //avoid flicker { bitmapFront = bitmap; spaceAfter_ = spaceAfter; refreshButtonLabel(); } } void BitmapButton::SetLabel(const wxString& label) { if (wxBitmapButton::GetLabel() != label) //avoid flicker { wxBitmapButton::SetLabel(label); refreshButtonLabel(); } } void BitmapButton::setBitmapBack(const wxBitmap& bitmap, int spaceBefore) { if (!isEqual(bitmap, bitmapBack) || spaceBefore_ != spaceBefore) //avoid flicker { bitmapBack = bitmap; spaceBefore_ = spaceBefore; refreshButtonLabel(); } } namespace { void makeWhiteTransparent(wxImage& image) //assume black text on white background { if (unsigned char* alphaFirst = image.GetAlpha()) { unsigned char* alphaLast = alphaFirst + image.GetWidth() * image.GetHeight(); //dist(black, white) const double distBlackWhite = 255 * std::sqrt(3.0); const unsigned char* bytePos = image.GetData(); for (unsigned char* j = alphaFirst; j != alphaLast; ++j) { unsigned char r = *bytePos++; // unsigned char g = *bytePos++; //each pixel consists of three bytes unsigned char b = *bytePos++; // //dist((r,g,b), white) double distColWhite = std::sqrt((255.0 - r) * (255.0 - r) + (255.0 - g) * (255.0 - g) + (255.0 - b) * (255.0 - b)); //black(0,0,0) becomes fully opaque(255), while white(255,255,255) becomes transparent(0) *j = distColWhite / distBlackWhite * wxIMAGE_ALPHA_OPAQUE; } } } wxSize getSizeNeeded(const wxString& text, wxFont& font) { wxCoord width = 0; wxCoord height = 0; //the context used for bitmaps... wxMemoryDC().GetMultiLineTextExtent(replaceCpy(text, L"&", L"", false), //remove accelerator &width, &height, nullptr, &font); return wxSize(width, height); } } wxBitmap BitmapButton::createBitmapFromText(const wxString& text) { //wxDC::DrawLabel() doesn't respect alpha channel at all => apply workaround to calculate alpha values manually: if (text.empty()) return wxBitmap(); wxFont currentFont = wxBitmapButton::GetFont(); wxColor textColor = wxBitmapButton::GetForegroundColour(); wxSize sizeNeeded = getSizeNeeded(text, currentFont); wxBitmap newBitmap(sizeNeeded.GetWidth(), sizeNeeded.GetHeight()); { wxMemoryDC dc(newBitmap); //set up white background dc.SetBackground(*wxWHITE_BRUSH); dc.Clear(); //find position of accelerator const size_t accelPos = text.find(L"&"); const int indexAccel = accelPos != wxString::npos ? static_cast(accelPos) : -1; dc.SetTextForeground(*wxBLACK); //for use in makeWhiteTransparent dc.SetTextBackground(*wxWHITE); // dc.SetFont(currentFont); dc.DrawLabel(replaceCpy(text, L"&", L"", false), //remove accelerator wxNullBitmap, wxRect(0, 0, newBitmap.GetWidth(), newBitmap.GetHeight()), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, indexAccel); } //add alpha channel to image wxImage finalImage(newBitmap.ConvertToImage()); finalImage.SetAlpha(); //set values for alpha channel makeWhiteTransparent(finalImage); //now apply real text color unsigned char* bytePos = finalImage.GetData(); const int pixelCount = finalImage.GetWidth() * finalImage.GetHeight(); for (int i = 0; i < pixelCount; ++ i) { *bytePos++ = textColor.Red(); *bytePos++ = textColor.Green(); *bytePos++ = textColor.Blue(); } return wxBitmap(finalImage); } //copy one image into another, allowing arbitrary overlapping! (pos may contain negative numbers) void writeToImage(const wxImage& source, const wxPoint& pos, wxImage& target) { //determine startpositions in source and target image, as well as width and height to be copied wxPoint posSrc, posTrg; int width, height; //X-axis if (pos.x < 0) { posSrc.x = -pos.x; posTrg.x = 0; width = std::min(pos.x + source.GetWidth(), target.GetWidth()); } else { posSrc.x = 0; posTrg.x = pos.x; width = std::min(target.GetWidth() - pos.x, source.GetWidth()); } //Y-axis if (pos.y < 0) { posSrc.y = -pos.y; posTrg.y = 0; height = std::min(pos.y + source.GetHeight(), target.GetHeight()); } else { posSrc.y = 0; posTrg.y = pos.y; height = std::min(target.GetHeight() - pos.y, source.GetHeight()); } if (width > 0 && height > 0) { { //copy source to target respecting overlapping parts const unsigned char* sourcePtr = source.GetData() + 3 * (posSrc.x + posSrc.y * source.GetWidth()); const unsigned char* const sourcePtrEnd = source.GetData() + 3 * (posSrc.x + (posSrc.y + height) * source.GetWidth()); unsigned char* targetPtr = target.GetData() + 3 * (posTrg.x + posTrg.y * target.GetWidth()); while (sourcePtr < sourcePtrEnd) { memcpy(targetPtr, sourcePtr, 3 * width); sourcePtr += 3 * source.GetWidth(); targetPtr += 3 * target.GetWidth(); } } //handle different cases concerning alpha channel if (source.HasAlpha()) { if (!target.HasAlpha()) { target.SetAlpha(); memset(target.GetAlpha(), wxIMAGE_ALPHA_OPAQUE, target.GetWidth() * target.GetHeight()); } //copy alpha channel const unsigned char* sourcePtr = source.GetAlpha() + (posSrc.x + posSrc.y * source.GetWidth()); const unsigned char* const sourcePtrEnd = source.GetAlpha() + (posSrc.x + (posSrc.y + height) * source.GetWidth()); unsigned char* targetPtr = target.GetAlpha() + (posTrg.x + posTrg.y * target.GetWidth()); while (sourcePtr < sourcePtrEnd) { memcpy(targetPtr, sourcePtr, width); sourcePtr += source.GetWidth(); targetPtr += target.GetWidth(); } } else if (target.HasAlpha()) { unsigned char* targetPtr = target.GetAlpha() + (posTrg.x + posTrg.y * target.GetWidth()); const unsigned char* const targetPtrEnd = target.GetAlpha() + (posTrg.x + (posTrg.y + height) * target.GetWidth()); while (targetPtr < targetPtrEnd) { memset(targetPtr, wxIMAGE_ALPHA_OPAQUE, width); targetPtr += target.GetWidth(); } } } } void BitmapButton::refreshButtonLabel() { wxBitmap bitmapText = createBitmapFromText(GetLabel()); auto getSize = [](const wxBitmap& bmp) { return bmp.IsOk() ? wxSize(bmp.GetWidth(), bmp.GetHeight()) : wxSize(0, 0); }; wxSize szFront = getSize(bitmapFront); // wxSize szText = getSize(bitmapText); //make sure to NOT access null-bitmaps! wxSize szBack = getSize(bitmapBack); // //calculate dimensions of new button const int height = std::max(std::max(szFront.GetHeight(), szText.GetHeight()), szBack.GetHeight()); const int width = szFront.GetWidth() + spaceAfter_ + szText.GetWidth() + spaceBefore_ + szBack.GetWidth(); //create a transparent image wxImage transparentImage(width, height, false); transparentImage.SetAlpha(); unsigned char* alpha = transparentImage.GetAlpha(); ::memset(alpha, wxIMAGE_ALPHA_TRANSPARENT, width * height); //wxDC::DrawLabel() unfortunately isn't working for transparent images on Linux, so we need to use custom image-concatenation if (bitmapFront.IsOk()) writeToImage(bitmapFront.ConvertToImage(), wxPoint(0, (transparentImage.GetHeight() - bitmapFront.GetHeight()) / 2), transparentImage); if (bitmapText.IsOk()) writeToImage(bitmapText.ConvertToImage(), wxPoint(szFront.GetWidth() + spaceAfter_, (transparentImage.GetHeight() - bitmapText.GetHeight()) / 2), transparentImage); if (bitmapBack.IsOk()) writeToImage(bitmapBack.ConvertToImage(), wxPoint(szFront.GetWidth() + spaceAfter_ + szText.GetWidth() + spaceBefore_, (transparentImage.GetHeight() - bitmapBack.GetHeight()) / 2), transparentImage); //adjust button size wxSize minSize = GetMinSize(); //SetMinSize() instead of SetSize() is needed here for wxWindows layout determination to work corretly wxBitmapButton::SetMinSize(wxSize(std::max(width + 2 * innerBorderSize, minSize.GetWidth()), std::max(height + 2 * innerBorderSize, minSize.GetHeight()))); //finally set bitmap wxBitmapButton::SetBitmapLabel(wxBitmap(transparentImage)); }