diff options
author | B Stack <bgstack15@gmail.com> | 2020-07-22 16:56:03 +0000 |
---|---|---|
committer | B Stack <bgstack15@gmail.com> | 2020-07-22 16:56:03 +0000 |
commit | e5633fb1c0db91f01ab967330b76baf4ecdb0512 (patch) | |
tree | 10260e25ae905564f7978b83fc4e316670f987c6 /wx+/image_tools.cpp | |
parent | Merge branch '10.25' into 'master' (diff) | |
parent | add upstream 11.0 (diff) | |
download | FreeFileSync-11.0.tar.gz FreeFileSync-11.0.tar.bz2 FreeFileSync-11.0.zip |
Merge branch '11.0' into 'master'11.0
add upstream 11.0
See merge request opensource-tracking/FreeFileSync!24
Diffstat (limited to 'wx+/image_tools.cpp')
-rw-r--r-- | wx+/image_tools.cpp | 284 |
1 files changed, 189 insertions, 95 deletions
diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp index 9fcc5563..19ba6ba6 100644 --- a/wx+/image_tools.cpp +++ b/wx+/image_tools.cpp @@ -14,48 +14,116 @@ using namespace zen; namespace { -void writeToImage(wxImage& output, const wxImage& top, const wxPoint& pos) +template <int PixBytes> +void copyImageBlock(const unsigned char* src, int srcWidth, + /**/ unsigned char* trg, int trgWidth, int blockWidth, int blockHeight) { - const int topWidth = top.GetWidth (); - const int topHeight = top.GetHeight(); - const int outWidth = output.GetWidth(); + assert(srcWidth >= blockWidth && trgWidth >= blockWidth); + const int srcPitch = srcWidth * PixBytes; + const int trgPitch = trgWidth * PixBytes; + const int blockPitch = blockWidth * PixBytes; + for (int y = 0; y < blockHeight; ++y) + std::memcpy(trg + y * trgPitch, src + y * srcPitch, blockPitch); +} + - assert(0 <= pos.x && pos.x + topWidth <= outWidth ); //draw area must be a - assert(0 <= pos.y && pos.y + topHeight <= output.GetHeight()); //subset of output image! - assert(top.HasAlpha() && output.HasAlpha()); +//...what wxImage::Resize() wants to be when it grows up +void copySubImage(const wxImage& src, wxPoint srcPos, + /**/ wxImage& trg, wxPoint trgPos, wxSize blockSize) +{ + auto pointClamp = [](const wxPoint& pos, const wxImage& img) -> wxPoint + { + return { + std::clamp(pos.x, 0, img.GetWidth ()), + std::clamp(pos.y, 0, img.GetHeight())}; + }; + auto pointMinus = [](const wxPoint& lhs, const wxPoint& rhs) { return wxSize{lhs.x - rhs.x, lhs.y - rhs.y}; }; + //work around yet another wxWidgets screw up: WTF does "operator-(wxPoint, wxPoint)" return wxPoint instead of wxSize!?? + + const wxPoint trgPos2 = pointClamp(trgPos, trg); + const wxPoint trgPos2End = pointClamp(trgPos + blockSize, trg); + + blockSize = pointMinus(trgPos2End, trgPos2); + srcPos += pointMinus(trgPos2, trgPos); + trgPos = trgPos2; + if (blockSize.x <= 0 || blockSize.y <= 0) + return; + + const wxPoint srcPos2 = pointClamp(srcPos, src); + const wxPoint srcPos2End = pointClamp(srcPos + blockSize, src); + + blockSize = pointMinus(srcPos2End, srcPos2); + trgPos += pointMinus(srcPos2, srcPos); + srcPos = srcPos2; + if (blockSize.x <= 0 || blockSize.y <= 0) + return; + //what if target block size is bigger than source block size? should we clear the area that is not copied from source? + + copyImageBlock<3>(src.GetData() + 3 * (srcPos.x + srcPos.y * src.GetWidth()), src.GetWidth(), + trg.GetData() + 3 * (trgPos.x + trgPos.y * trg.GetWidth()), trg.GetWidth(), + blockSize.x, blockSize.y); + + copyImageBlock<1>(src.GetAlpha() + srcPos.x + srcPos.y * src.GetWidth(), src.GetWidth(), + trg.GetAlpha() + trgPos.x + trgPos.y * trg.GetWidth(), trg.GetWidth(), + blockSize.x, blockSize.y); +} + + +void copyImageLayover(const wxImage& src, + /**/ wxImage& trg, wxPoint trgPos) +{ + const int srcWidth = src.GetWidth (); + const int srcHeight = src.GetHeight(); + const int trgWidth = trg.GetWidth(); + + assert(0 <= trgPos.x && trgPos.x + srcWidth <= trgWidth ); //draw area must be a + assert(0 <= trgPos.y && trgPos.y + srcHeight <= trg.GetHeight()); //subset of target image! //https://en.wikipedia.org/wiki/Alpha_compositing - const unsigned char* topRgb = top.GetData(); - const unsigned char* topAlpha = top.GetAlpha(); + const unsigned char* srcRgb = src.GetData(); + const unsigned char* srcAlpha = src.GetAlpha(); - for (int y = 0; y < topHeight; ++y) + for (int y = 0; y < srcHeight; ++y) { - unsigned char* outRgb = output.GetData () + 3 * (pos.x + (pos.y + y) * outWidth); - unsigned char* outAlpha = output.GetAlpha() + pos.x + (pos.y + y) * outWidth; + unsigned char* trgRgb = trg.GetData () + 3 * (trgPos.x + (trgPos.y + y) * trgWidth); + unsigned char* trgAlpha = trg.GetAlpha() + trgPos.x + (trgPos.y + y) * trgWidth; - for (int x = 0; x < topWidth; ++x) + for (int x = 0; x < srcWidth; ++x) { - const int w1 = *topAlpha; //alpha-composition interpreted as weighted average - const int w2 = *outAlpha * (255 - w1) / 255; + const int w1 = *srcAlpha; //alpha-composition interpreted as weighted average + const int w2 = *trgAlpha * (255 - w1) / 255; const int wSum = w1 + w2; - auto calcColor = [w1, w2, wSum](unsigned char colTop, unsigned char colBot) + auto calcColor = [w1, w2, wSum](unsigned char colsrc, unsigned char colTrg) { - return static_cast<unsigned char>(wSum == 0 ? 0 : (colTop * w1 + colBot * w2) / wSum); + return static_cast<unsigned char>(wSum == 0 ? 0 : (colsrc * w1 + colTrg * w2) / wSum); }; - outRgb[0] = calcColor(topRgb[0], outRgb[0]); - outRgb[1] = calcColor(topRgb[1], outRgb[1]); - outRgb[2] = calcColor(topRgb[2], outRgb[2]); + trgRgb[0] = calcColor(srcRgb[0], trgRgb[0]); + trgRgb[1] = calcColor(srcRgb[1], trgRgb[1]); + trgRgb[2] = calcColor(srcRgb[2], trgRgb[2]); - *outAlpha = static_cast<unsigned char>(wSum); + *trgAlpha = static_cast<unsigned char>(wSum); - topRgb += 3; - outRgb += 3; - ++topAlpha; - ++outAlpha; + srcRgb += 3; + trgRgb += 3; + ++srcAlpha; + ++trgAlpha; } } } + + +std::vector<std::pair<wxString, wxSize>> getTextExtentInfo(const wxString& text, const wxFont& font) +{ + wxMemoryDC dc; //the context used for bitmaps + dc.SetFont(font); //the font parameter of GetMultiLineTextExtent() is not evaluated on OS X, wxWidgets 2.9.5, so apply it to the DC directly! + + std::vector<std::pair<wxString, wxSize>> lineInfo; //text + extent + for (const wxString& line : split(text, L'\n', SplitType::ALLOW_EMPTY)) + lineInfo.emplace_back(line, line.empty() ? wxSize() : dc.GetTextExtent(line)); + + return lineInfo; +} } @@ -69,27 +137,23 @@ wxImage zen::stackImages(const wxImage& img1, const wxImage& img2, ImageStackLay const int img2Width = img2.GetWidth (); const int img2Height = img2.GetHeight(); - int width = std::max(img1Width, img2Width); - int height = std::max(img1Height, img2Height); - - if (dir == ImageStackLayout::horizontal) - width = img1Width + gap + img2Width; - else - height = img1Height + gap + img2Height; + const wxSize newSize = dir == ImageStackLayout::horizontal ? + wxSize(img1Width + gap + img2Width, std::max(img1Height, img2Height)) : + wxSize(std::max(img1Width, img2Width), img1Height + gap + img2Height); - wxImage output(width, height); + wxImage output(newSize); output.SetAlpha(); - ::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, width * height); + std::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, newSize.x * newSize.y); auto calcPos = [&](int imageExtent, int totalExtent) { switch (align) { case ImageStackAlignment::center: - return static_cast<int>(std::floor((totalExtent - imageExtent) / 2.0)); //consistency: round down negative values, too! - case ImageStackAlignment::left: + return (totalExtent - imageExtent) / 2; + case ImageStackAlignment::left: //or top return 0; - case ImageStackAlignment::right: + case ImageStackAlignment::right: //or bottom return totalExtent - imageExtent; } assert(false); @@ -99,34 +163,19 @@ wxImage zen::stackImages(const wxImage& img1, const wxImage& img2, ImageStackLay switch (dir) { case ImageStackLayout::horizontal: - writeToImage(output, img1, wxPoint(0, calcPos(img1Height, height))); - writeToImage(output, img2, wxPoint(img1Width + gap, calcPos(img2Height, height))); + copySubImage(img1, wxPoint(), output, wxPoint(0, calcPos(img1Height, newSize.y)), img1.GetSize()); + copySubImage(img2, wxPoint(), output, wxPoint(img1Width + gap, calcPos(img2Height, newSize.y)), img2.GetSize()); break; case ImageStackLayout::vertical: - writeToImage(output, img1, wxPoint(calcPos(img1Width, width), 0)); - writeToImage(output, img2, wxPoint(calcPos(img2Width, width), img1Height + gap)); + copySubImage(img1, wxPoint(), output, wxPoint(calcPos(img1Width, newSize.x), 0), img1.GetSize()); + copySubImage(img2, wxPoint(), output, wxPoint(calcPos(img2Width, newSize.x), img1Height + gap), img2.GetSize()); break; } return output; } -namespace -{ -std::vector<std::pair<wxString, wxSize>> getTextExtentInfo(const wxString& text, const wxFont& font) -{ - wxMemoryDC dc; //the context used for bitmaps - dc.SetFont(font); //the font parameter of GetMultiLineTextExtent() is not evalated on OS X, wxWidgets 2.9.5, so apply it to the DC directly! - - std::vector<std::pair<wxString, wxSize>> lineInfo; //text + extent - for (const wxString& line : split(text, L'\n', SplitType::ALLOW_EMPTY)) - lineInfo.emplace_back(line, line.empty() ? wxSize() : dc.GetTextExtent(line)); - - return lineInfo; -} -} - wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const wxColor& col, ImageStackAlignment textAlign) { //assert(!contains(text, L"&")); //accelerator keys not supported here @@ -147,7 +196,7 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const lineHeight = std::max(lineHeight, lineSize.GetHeight()); //wxWidgets comment "GetTextExtent will return 0 for empty string" } if (maxWidth == 0 || lineHeight == 0) - return wxImage(); + return wxNullImage; wxBitmap newBitmap(maxWidth, lineHeight * lineInfo.size()); //seems we don't need to pass 24-bit depth here even for high-contrast color schemes { @@ -155,7 +204,7 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const dc.SetBackground(*wxWHITE_BRUSH); dc.Clear(); - dc.SetTextForeground(*wxBLACK); //for use in calcAlphaForBlackWhiteImage + dc.SetTextForeground(*wxBLACK); //for proper alpha-channel calculation dc.SetTextBackground(*wxWHITE); // dc.SetFont(font); @@ -175,7 +224,6 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const dc.DrawText(lineText, wxPoint((maxWidth - lineSize.GetWidth()) / 2, posY)); break; } - posY += lineHeight; } } @@ -193,11 +241,9 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const //black(0,0,0) becomes wxIMAGE_ALPHA_OPAQUE(255), while white(255,255,255) becomes wxIMAGE_ALPHA_TRANSPARENT(0) *alpha++ = static_cast<unsigned char>((255 - rgb[0] + 255 - rgb[1] + 255 - rgb[2]) / 3); //mixed-mode arithmetics! - rgb[0] = col.Red (); // - rgb[1] = col.Green(); //apply actual text color - rgb[2] = col.Blue (); // - - rgb += 3; + *rgb++ = col.Red (); // + *rgb++ = col.Green(); //apply actual text color + *rgb++ = col.Blue (); // } return output; } @@ -208,43 +254,91 @@ wxImage zen::layOver(const wxImage& back, const wxImage& front, int alignment) if (!front.IsOk()) return back; assert(front.HasAlpha() && back.HasAlpha()); - const int width = std::max(back.GetWidth(), front.GetWidth()); - const int height = std::max(back.GetHeight(), front.GetHeight()); + const wxSize newSize(std::max(back.GetWidth(), front.GetWidth()), + std::max(back.GetHeight(), front.GetHeight())); - const int offsetX = [&] + auto calcNewPos = [&](const wxImage& img) { - if (alignment & wxALIGN_RIGHT) - return back.GetWidth() - front.GetWidth(); - if (alignment & wxALIGN_CENTER_HORIZONTAL) - return (back.GetWidth() - front.GetWidth()) / 2; - - static_assert(wxALIGN_LEFT == 0); - return 0; - }(); + wxPoint newPos; + if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0! + newPos.x = newSize.GetWidth() - img.GetWidth(); + else if (alignment & wxALIGN_CENTER_HORIZONTAL) + newPos.x = (newSize.GetWidth() - img.GetWidth()) / 2; + + if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0! + newPos.y = newSize.GetHeight() - img.GetHeight(); + else if (alignment & wxALIGN_CENTER_VERTICAL) + newPos.y = (newSize.GetHeight() - img.GetHeight()) / 2; + + return newPos; + }; - const int offsetY = [&] - { - if (alignment & wxALIGN_BOTTOM) - return back.GetHeight() - front.GetHeight(); - if (alignment & wxALIGN_CENTER_VERTICAL) - return (back.GetHeight() - front.GetHeight()) / 2; + wxImage output(newSize); + output.SetAlpha(); + std::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, newSize.x * newSize.y); - static_assert(wxALIGN_TOP == 0); - return 0; - }(); + copySubImage(back, wxPoint(), output, calcNewPos(back), back.GetSize()); + //use resizeCanvas()? might return ref-counted copy! //can't use wxMemoryDC and wxDC::DrawBitmap(): no alpha channel support on wxGTK! - wxImage output(width, height); + copyImageLayover(front, output, calcNewPos(front)); + + return output; +} + + +wxImage zen::resizeCanvas(const wxImage& img, wxSize newSize, int alignment) +{ + if (newSize == img.GetSize()) + return img; //caveat: wxImage is ref-counted *without* copy on write + + wxPoint newPos; + if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0! + newPos.x = newSize.GetWidth() - img.GetWidth(); + else if (alignment & wxALIGN_CENTER_HORIZONTAL) + newPos.x = static_cast<int>(std::floor((newSize.GetWidth() - img.GetWidth()) / 2)); //consistency: round down negative values, too! + + if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0! + newPos.y = newSize.GetHeight() - img.GetHeight(); + else if (alignment & wxALIGN_CENTER_VERTICAL) + newPos.y = static_cast<int>(std::floor((newSize.GetHeight() - img.GetHeight()) / 2)); //consistency: round down negative values, too! + + wxImage output(newSize); output.SetAlpha(); - ::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, width * height); + std::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, newSize.x * newSize.y); - const wxPoint posBack(std::max(-offsetX, 0), std::max(-offsetY, 0)); - writeToImage(output, back, posBack); - writeToImage(output, front, posBack + wxPoint(offsetX, offsetY)); + copySubImage(img, wxPoint(), output, newPos, img.GetSize()); + //about 50x faster than e.g. wxImage::Resize!!! suprise :> return output; } +wxImage zen::shrinkImage(const wxImage& img, int maxWidth /*optional*/, int maxHeight /*optional*/) +{ + wxSize newSize = img.GetSize(); + + if (maxWidth >= 0) + if (maxWidth < newSize.x) + { + newSize.y = newSize.y * maxWidth / newSize.x; + newSize.x = maxWidth; + } + if (maxHeight >= 0) + if (maxHeight < newSize.y) + { + newSize = img.GetSize(); //avoid loss of precision + newSize.x = newSize.x * maxHeight / newSize.y; // + newSize.y = maxHeight; + } + + if (newSize == img.GetSize()) + return img; + + return img.Scale(newSize.x, newSize.y, wxIMAGE_QUALITY_BILINEAR); //looks sharper than wxIMAGE_QUALITY_HIGH! + //perf: use xbrz::bilinearScale instead? only about 10% shorter runtime +} + + void zen::convertToVanillaImage(wxImage& img) { if (!img.HasAlpha()) @@ -268,20 +362,20 @@ void zen::convertToVanillaImage(wxImage& img) if (haveMask) { img.SetMask(false); - unsigned char* alphaPtr = img.GetAlpha(); - const unsigned char* dataPtr = img.GetData(); + unsigned char* alpha = img.GetAlpha(); + const unsigned char* rgb = img.GetData(); const int pixelCount = width * height; for (int i = 0; i < pixelCount; ++ i) { - const unsigned char r = *dataPtr++; - const unsigned char g = *dataPtr++; - const unsigned char b = *dataPtr++; + const unsigned char r = *rgb++; + const unsigned char g = *rgb++; + const unsigned char b = *rgb++; if (r == mask_r && g == mask_g && b == mask_b) - alphaPtr[i] = wxIMAGE_ALPHA_TRANSPARENT; + alpha[i] = wxIMAGE_ALPHA_TRANSPARENT; } } } |