// highgui to XAML bridge for OpenCV // Copyright (c) Microsoft Open Technologies, Inc. // All rights reserved. // // (3 - clause BSD License) // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that // the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the // following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the // following disclaimer in the documentation and/or other materials provided with the distribution. // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or // promote products derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A // PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "precomp.hpp" #include "opencv2\highgui\highgui_winrt.hpp" #include "window_winrt_bridge.hpp" #include #include // Windows::Storage::Streams::IBufferByteAccess using namespace Microsoft::WRL; // ComPtr using namespace Windows::Storage::Streams; // IBuffer using namespace Windows::UI::Xaml; using namespace Windows::UI::Xaml::Controls; using namespace Windows::UI::Xaml::Media::Imaging; using namespace ::std; /***************************** Constants ****************************************/ // Default markup for the container content allowing for proper components placement const Platform::String^ CvWindow::markupContent = "\n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ ""; const double CvWindow::sliderDefaultWidth = 100; /***************************** HighguiBridge class ******************************/ HighguiBridge& HighguiBridge::getInstance() { static HighguiBridge instance; return instance; } void HighguiBridge::setContainer(Windows::UI::Xaml::Controls::Panel^ container) { this->container = container; } CvWindow* HighguiBridge::findWindowByName(cv::String name) { auto search = windowsMap->find(name); if (search != windowsMap->end()) { return search->second; } return nullptr; } CvTrackbar* HighguiBridge::findTrackbarByName(cv::String trackbar_name, cv::String window_name) { CvWindow* window = findWindowByName(window_name); if (window) return window->findTrackbarByName(trackbar_name); return nullptr; } Platform::String^ HighguiBridge::convertString(cv::String name) { auto data = name.c_str(); int bufferSize = MultiByteToWideChar(CP_UTF8, 0, data, -1, nullptr, 0); auto wide = std::make_unique(bufferSize); if (0 == MultiByteToWideChar(CP_UTF8, 0, data, -1, wide.get(), bufferSize)) return nullptr; std::wstring* stdStr = new std::wstring(wide.get()); return ref new Platform::String(stdStr->c_str()); } void HighguiBridge::cleanContainer() { container->Children->Clear(); } void HighguiBridge::showWindow(CvWindow* window) { currentWindow = window; cleanContainer(); HighguiBridge::getInstance().container->Children->Append(window->getPage()); } CvWindow* HighguiBridge::namedWindow(cv::String name) { CvWindow* window = HighguiBridge::getInstance().findWindowByName(name.c_str()); if (!window) { window = createWindow(name); } return window; } void HighguiBridge::destroyWindow(cv::String name) { auto window = windowsMap->find(name); if (window != windowsMap->end()) { // Check if deleted window is the one currently displayed // and clear container if this is the case if (window->second == currentWindow) { cleanContainer(); } windowsMap->erase(window); } } void HighguiBridge::destroyAllWindows() { cleanContainer(); windowsMap->clear(); } CvWindow* HighguiBridge::createWindow(cv::String name) { CvWindow* window = new CvWindow(name); windowsMap->insert(std::pair(name, window)); return window; } /***************************** CvTrackbar class *********************************/ CvTrackbar::CvTrackbar(cv::String name, Slider^ slider, CvWindow* parent) : name(name), slider(slider), parent(parent) {} CvTrackbar::~CvTrackbar() {} void CvTrackbar::setPosition(double pos) { if (pos < 0) pos = 0; if (pos > slider->Maximum) pos = slider->Maximum; slider->Value = pos; } void CvTrackbar::setMaxPosition(double pos) { //slider->Minimum is initialized with 0 if (pos < slider->Minimum) pos = slider->Minimum; slider->Maximum = pos; } void CvTrackbar::setMinPosition(double pos) { if (pos < 0) pos = 0; //Min is always less than Max. if (pos > slider->Maximum) pos = slider->Maximum; slider->Minimum = pos; } void CvTrackbar::setSlider(Slider^ slider) { if (slider) this->slider = slider; } double CvTrackbar::getPosition() { return slider->Value; } double CvTrackbar::getMaxPosition() { return slider->Maximum; } double CvTrackbar::getMinPosition() { return slider->Minimum; } Slider^ CvTrackbar::getSlider() { return slider; } /***************************** CvWindow class ***********************************/ CvWindow::CvWindow(cv::String name, int flags) : name(name) { this->page = (Page^)Windows::UI::Xaml::Markup::XamlReader::Load(const_cast(markupContent)); this->sliderMap = new std::map(); sliderPanel = (Panel^)page->FindName("cvTrackbar"); imageControl = (Image^)page->FindName("cvImage"); buttonPanel = (Panel^)page->FindName("cvButton"); // Required to adapt controls to the size of the image. // System calculates image control width first, after that we can // update other controls imageControl->Loaded += ref new Windows::UI::Xaml::RoutedEventHandler( [=](Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { // Need to update sliders with appropriate width for (auto iter = sliderMap->begin(); iter != sliderMap->end(); ++iter) { iter->second->getSlider()->Width = imageControl->ActualWidth; } // Need to update buttons with appropriate width // TODO: implement when adding buttons }); } CvWindow::~CvWindow() {} void CvWindow::createSlider(cv::String name, int* val, int count, CvTrackbarCallback2 on_notify, void* userdata) { CvTrackbar* trackbar = findTrackbarByName(name); // Creating slider if name is new or reusing the existing one Slider^ slider = !trackbar ? ref new Slider() : trackbar->getSlider(); slider->Header = HighguiBridge::getInstance().convertString(name); // Making slider the same size as the image control or setting minimal size. // This is added to cover potential edge cases because: // 1. Fist clause will not be true until the second call to any container-updating API // e.g. cv::createTrackbar, cv:imshow or cv::namedWindow // 2. Second clause will work but should be immediately overridden by Image->Loaded callback, // see CvWindow ctor. if (this->imageControl->ActualWidth > 0) { // One would use double.NaN for auto-stretching but there is no such constant in C++/CX // see https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.frameworkelement.width slider->Width = this->imageControl->ActualWidth; } else { // This value would never be used/seen on the screen unless there is something wrong with the image. // Although this code actually gets called, slider width will be overridden in the callback after // Image control is loaded. See callback implementation in CvWindow ctor. slider->Width = sliderDefaultWidth; } slider->Value = val ? *val : 0; slider->Maximum = count; slider->Visibility = Windows::UI::Xaml::Visibility::Visible; slider->Margin = Windows::UI::Xaml::ThicknessHelper::FromLengths(10, 10, 10, 0); slider->HorizontalAlignment = Windows::UI::Xaml::HorizontalAlignment::Left; if (!trackbar) { if (!sliderPanel) return; // Adding slider to the list for current window CvTrackbar* trackbar = new CvTrackbar(name, slider, this); trackbar->callback = on_notify; slider->ValueChanged += ref new Controls::Primitives::RangeBaseValueChangedEventHandler( [=](Platform::Object^ sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs^ e) { Slider^ slider = (Slider^)sender; trackbar->callback(slider->Value, nullptr); }); this->sliderMap->insert(std::pair(name, trackbar)); // Adding slider to the window sliderPanel->Children->Append(slider); } } CvTrackbar* CvWindow::findTrackbarByName(cv::String name) { auto search = sliderMap->find(name); if (search != sliderMap->end()) { return search->second; } return nullptr; } void CvWindow::updateImage(CvMat* src) { if (!imageControl) return; this->imageData = src; this->imageWidth = src->width; // Create the WriteableBitmap WriteableBitmap^ bitmap = ref new WriteableBitmap(src->cols, src->rows); // Get access to the pixels IBuffer^ buffer = bitmap->PixelBuffer; unsigned char* dstPixels; // Obtain IBufferByteAccess ComPtr pBufferByteAccess; ComPtr pBuffer((IInspectable*)buffer); pBuffer.As(&pBufferByteAccess); // Get pointer to pixel bytes pBufferByteAccess->Buffer(&dstPixels); memcpy(dstPixels, src->data.ptr, CV_ELEM_SIZE(src->type) * src->cols*src->rows); // Set the bitmap to the Image element imageControl->Source = bitmap; } Page^ CvWindow::getPage() { return page; } //TODO: prototype, not in use yet void CvWindow::createButton(cv::String name) { if (!buttonPanel) return; Button^ b = ref new Button(); b->Content = HighguiBridge::getInstance().convertString(name); b->Width = 260; b->Height = 80; b->Click += ref new Windows::UI::Xaml::RoutedEventHandler( [=](Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { Button^ button = (Button^)sender; // TODO: more logic here... }); buttonPanel->Children->Append(b); } // end