CVB++ 15.0
Loading...
Searching...
No Matches
detail_ui.hpp
1#pragma once
2
3#include "../../global.hpp"
4#include "../../size_2d.hpp"
5#include "../../point_2d.hpp"
6#include "../../rect_lt.hpp"
7#include "../../image.hpp"
8
9#ifndef CVB_NO_QT5
10
11# ifdef _MSC_VER
12# pragma warning(push, 1)
13# pragma warning(disable : 4005)
14# endif
15# include <QtGlobal>
16# include <QObject>
17# ifdef _MSC_VER
18# pragma warning(pop)
19# endif
20
21#endif
22
23namespace Cvb
24{
25
26 CVB_BEGIN_INLINE_NS
27
28 namespace UI
29 {
30
31#ifndef CVB_NO_QT5
32 Q_NAMESPACE
33#endif
34
36 enum class UploadMode
37 {
54 };
55#ifndef CVB_NO_QT5
56 Q_ENUM_NS(UploadMode)
57#endif
58
108#ifndef CVB_NO_QT5
109 Q_ENUM_NS(ZoomID)
110#endif
111
113 enum class AutoRefresh
114 {
126 };
127
129 enum class LabelScale
130 {
131 /*
132 * The image view label will scale/zoom like the image (usefully to mark dimensions).
133 */
134 On,
135 /*
136 * The image view label will ignore image scale/zoom operations.
137 */
138 Off
139 };
140#ifndef CVB_NO_QT5
141 Q_ENUM_NS(LabelScale)
142#endif
143
145
147 struct ZoomDescriptor final
148 {
149
150#ifndef CVB_NO_QT5
151 Q_GADGET
152#endif
153 public:
158
159 ZoomDescriptor() = default;
160
162
167 bool operator==(const ZoomDescriptor &zoomDescriptor) const noexcept
168 {
169 return ID == zoomDescriptor.ID && Factor == zoomDescriptor.Factor;
170 }
171
173
178 bool operator!=(const ZoomDescriptor &zoomDescriptor) const noexcept
179 {
180 return !(*this == zoomDescriptor);
181 }
182
184
189 explicit ZoomDescriptor(ZoomID id) noexcept
190 : ID(id)
191 {
192 switch (id)
193 {
194 default:
195 case ZoomID::Invalid:
196 case ZoomID::Custom:
197 case ZoomID::Panorama:
198 break;
199
200 case ZoomID::Factor1:
201 Factor = 1.0;
202 break;
203
204 case ZoomID::Factor2:
205 Factor = 2.0;
206 break;
207
208 case ZoomID::Factor4:
209 Factor = 4.0;
210 break;
211
212 case ZoomID::Factor8:
213 Factor = 8.0;
214 break;
215
216 case ZoomID::Factor16:
217 Factor = 16.0;
218 break;
219
220 case ZoomID::Factor32:
221 Factor = 32.0;
222 break;
223
224 case ZoomID::Factor64:
225 Factor = 64.0;
226 break;
227
229 Factor = 128.0;
230 break;
231 }
232 }
233
235
240 explicit ZoomDescriptor(double customFactor) noexcept
242 , Factor(customFactor)
243 {
244 }
245 };
246
247 namespace Private
248 {
249
250 class IImageView
251 {
252 friend class ZoomHandler;
253
254 public:
255 IImageView(const IImageView &other) = delete;
256 IImageView &operator=(const IImageView &other) = delete;
257 IImageView(IImageView &&other) = delete;
258 IImageView &operator=(IImageView &&other) = delete;
259 virtual ~IImageView() = default;
260
261 protected:
262 IImageView() noexcept = default;
263
264 virtual Size2D<double> ViewSize() const noexcept = 0;
265 virtual Size2D<double> ImageSize() const noexcept = 0;
266
267 virtual void ApplyZoom(double factor) = 0;
268 };
269
270 class ZoomHandler
271 {
272 public:
273 explicit ZoomHandler(class IImageView &view) noexcept
274 : view_(view)
275 {
276 std::vector<UI::ZoomDescriptor> defaultDescriptorList(8);
277 defaultDescriptorList[0] = Cvb::UI::ZoomDescriptor(ZoomID::Factor1);
278 defaultDescriptorList[1] = Cvb::UI::ZoomDescriptor(ZoomID::Factor2);
279 defaultDescriptorList[2] = Cvb::UI::ZoomDescriptor(ZoomID::Factor4);
280 defaultDescriptorList[3] = Cvb::UI::ZoomDescriptor(ZoomID::Factor8);
281 defaultDescriptorList[4] = Cvb::UI::ZoomDescriptor(ZoomID::Factor16);
282 defaultDescriptorList[5] = Cvb::UI::ZoomDescriptor(ZoomID::Factor32);
283 defaultDescriptorList[6] = Cvb::UI::ZoomDescriptor(ZoomID::Factor64);
284 defaultDescriptorList[7] = Cvb::UI::ZoomDescriptor(ZoomID::Factor128);
285
286 defaultDescriptorList_.swap(defaultDescriptorList);
287
288 panoramaDescriptor_.ID = ZoomID::Panorama;
289 currentZoomDescriptor_ = panoramaDescriptor_; // NOLINT(cppcoreguidelines-prefer-member-initializer)
290 }
291
292 ZoomHandler(const ZoomHandler &other) = delete;
293 ZoomHandler &operator=(const ZoomHandler &other) = delete;
294 ZoomHandler(ZoomHandler &&other) = delete;
295 ZoomHandler &operator=(ZoomHandler &&other) = delete;
296 ~ZoomHandler() = default;
297
298 void UpdatePanoramaFactor() noexcept
299 {
300 auto viewportSize = view_.ViewSize();
301 auto sceneSize = view_.ImageSize();
302 if ((viewportSize_ == viewportSize && sceneSize_ == sceneSize)
303 || (sceneSize.Width() < 1 || sceneSize.Height() < 1))
304 return;
305
306 viewportSize_ = viewportSize;
307 sceneSize_ = sceneSize;
308#ifdef min
309# undef min
310#endif
311 double panoramaFactor =
312 std::min(viewportSize_.Width() / sceneSize_.Width(), viewportSize_.Height() / sceneSize_.Height());
313
314 panoramaDescriptor_.Factor = panoramaFactor;
315 if (currentZoomDescriptor_.ID == ZoomID::Panorama)
316 {
317 currentZoomDescriptor_ = panoramaDescriptor_;
318 Apply();
319 }
320 }
321
322 struct ZoomDescriptor ZoomDescriptor() const noexcept
323 {
324 return currentZoomDescriptor_;
325 }
326
327 bool TryZoomIn() noexcept
328 {
329 struct ZoomDescriptor result;
330
331 for (auto descriptor : defaultDescriptorList_)
332 {
333 if (descriptor.Factor > currentZoomDescriptor_.Factor
334 && (result.ID == ZoomID::Invalid || descriptor.Factor < result.Factor))
335 result = descriptor;
336 }
337
338 if (panoramaDescriptor_.Factor > currentZoomDescriptor_.Factor
339 && (result.ID == ZoomID::Invalid || panoramaDescriptor_.Factor < result.Factor))
340 result = panoramaDescriptor_;
341
342 if (result.ID == ZoomID::Invalid)
343 return false;
344
345 currentZoomDescriptor_ = result;
346 Apply();
347 return true;
348 }
349
350 bool TryZoomOut() noexcept
351 {
352 struct ZoomDescriptor result;
353
354 for (auto descriptor : defaultDescriptorList_)
355 {
356 if (descriptor.Factor < currentZoomDescriptor_.Factor
357 && (result.ID == ZoomID::Invalid || descriptor.Factor > result.Factor))
358 result = descriptor;
359 }
360
361 if (panoramaDescriptor_.Factor < currentZoomDescriptor_.Factor
362 && (result.ID == ZoomID::Invalid || panoramaDescriptor_.Factor > result.Factor))
363 result = panoramaDescriptor_;
364
365 if (result.ID == ZoomID::Invalid)
366 return false;
367
368 currentZoomDescriptor_ = result;
369 Apply();
370 return true;
371 }
372
373 void SetZoomDescriptor(struct ZoomDescriptor zoomDescriptor) noexcept
374 {
375
376 currentZoomDescriptor_ = zoomDescriptor;
377 Apply();
378 }
379
380 private:
381 enum class RenderDescriptor
382 {
383 AsyncImageUploadGl, // 1 0 no rendering, no upload
384 AsyncImageUploadRaster, // 2 1 no render, upload image in acquisitions thread
385 AsyncImageRepaintGl, // 3 1 waits for render, upload image in UI thread (image must be shared)
386 AsyncImageRepaintRaster, // 4 1 waits for render, upload image in acquisitions thread
387 AsyncImageScheduleGl, // 5 1 schedules rendering, upload image in UI thread (image must be shared)
388 AsyncImageScheduleRaster, // 6 1 schedules rendering, upload image in acquisitions thread
389 AsyncViewportUploadGl, // 7 0 equal to AsyncImageUploadGl
390 AsyncViewportUploadRaster, // 8 1 no rendering, upload viewport (image must be shared)
391 AsyncViewportRepaintGl, // 9 1 waits for render, upload viewport in UI thread (image must be shared)
392 AsyncViewportRepaintRaster, // 10 1 waits for render, upload viewport in Acquisition thread (image must be
393 // shared)
394 AsyncViewportScheduleGl, // 11 1 schedules rendering, upload viewport in UI thread (image must be shared)
395 AsyncViewportScheduleRaster, // 12 1 schedule for render, upload viewport in Acquisition thread (image must
396 // be shared)
397 SyncImageUploadGl, // 13 0 no rendering upload image into texture
398 SyncImageUploadRaster, // 14 0 no rendering upload image into QImage
399 SyncImageRepaintGl, // 15 -1 renders immediately, upload image into texture (measure render performance)
400 SyncImageRepaintRaster, // 16 -1 renders immediately, upload image into QImage (measure render performance)
401 SyncImageScheduleGl, // 17 1 schedules render, uploads image into texture
402 SyncImageScheduleRaster, // 18 1 schedules render, uploads image into QImage
403 SyncViewportUploadGl, // 19 0 no rendering, upload viewport into texture
404 SyncViewportUploadRaster, // 20 0 no rendering, upload viewport into QImage
405 SyncViewportRepaintGl, // 21 -1 renders immediately, upload viewport into texture (measure render
406 // performance) (image must be shared)
407 SyncViewportRepaintRaster, // 22 -1 renders immediately, upload viewport into QImage (measure render
408 // performance) (image must be shared)
409 SyncViewportScheduleGl, // 23 1 schedules render, uploads viewport into texture (image must be shared)
410 SyncViewportScheduleRaster, // 24 1 schedules render, uploads viewport into QImage (image must be shared)
411 };
412
413 void Apply() noexcept
414 {
415 if (currentZoomDescriptor_.ID == ZoomID::Invalid || std::isnan(currentZoomDescriptor_.Factor))
416 return;
417
418 view_.ApplyZoom(currentZoomDescriptor_.Factor);
419 }
420
421 IImageView &view_;
422
423 Size2D<double> sceneSize_;
424 Size2D<double> viewportSize_;
425 std::vector<struct ZoomDescriptor> defaultDescriptorList_;
426 struct ZoomDescriptor panoramaDescriptor_;
427 struct ZoomDescriptor currentZoomDescriptor_;
428 };
429
430 struct BGRAComponents
431 {
432 std::uint8_t Blue = 0;
433 std::uint8_t Green = 0;
434 std::uint8_t Red = 0;
435 std::uint8_t Alpha = 255;
436 };
437
438 struct RGBAComponents
439 {
440 std::uint8_t Red = 0;
441 std::uint8_t Green = 0;
442 std::uint8_t Blue = 0;
443 std::uint8_t Alpha = 255;
444 };
445
446 template <class T>
447 inline void CopyImageDataColorToColor(const Image &src, T *dst) noexcept
448 {
449 auto vpatR = src.Plane(0).Vpat();
450 auto vpatG = src.Plane(1).Vpat();
451 auto vpatB = src.Plane(2).Vpat();
452
453 auto entrysR = vpatR.VpatPtr();
454 auto entrysG = vpatG.VpatPtr();
455 auto entrysB = vpatB.VpatPtr();
456
457 auto baseR = vpatR.BasePtr();
458 auto baseG = vpatG.BasePtr();
459 auto baseB = vpatB.BasePtr();
460
461 auto widht = src.Width();
462 auto height = src.Height();
463
464 for (int h = 0; h < height; ++h)
465 {
466 auto lineOffsetR = baseR + entrysR[h].OffsetY;
467 auto lineOffsetG = baseG + entrysG[h].OffsetY;
468 auto lineOffsetB = baseB + entrysB[h].OffsetY;
469 for (int w = 0; w < widht; ++w)
470 {
471 (*dst).Alpha = 255;
472 (*dst).Red = *reinterpret_cast<std::uint8_t *>(lineOffsetR + entrysR[w].OffsetX);
473 (*dst).Green = *reinterpret_cast<std::uint8_t *>(lineOffsetG + entrysG[w].OffsetX);
474 (*dst).Blue = *reinterpret_cast<std::uint8_t *>(lineOffsetB + entrysB[w].OffsetX);
475 ++dst;
476 }
477 }
478 }
479
480 template <class T>
481 inline void CopyImageDataMonoToColor(const Image &src, T *dst) noexcept
482 {
483 auto vpat = src.Plane(0).Vpat();
484 auto entrys = vpat.VpatPtr();
485 auto base = vpat.BasePtr();
486
487 auto widht = src.Width();
488 auto height = src.Height();
489
490 for (int h = 0; h < height; ++h)
491 {
492 auto lineOffset = base + entrys[h].OffsetY;
493 for (int w = 0; w < widht; ++w)
494 {
495 (*dst).Alpha = 255;
496 auto value = *reinterpret_cast<std::uint8_t *>(lineOffset + entrys[w].OffsetX);
497 (*dst).Blue = (*dst).Green = (*dst).Red = value;
498 ++dst;
499 }
500 }
501 }
502
503 inline void CopyImageDataMonoToMono(const Image &src, void *dst, int linePad) noexcept
504 {
505 auto dstMono = reinterpret_cast<std::uint8_t *>(dst);
506 auto vpat = src.Plane(0).Vpat();
507 auto entrys = vpat.VpatPtr();
508 auto base = vpat.BasePtr();
509
510 auto widht = src.Width();
511 auto height = src.Height();
512
513 for (int h = 0; h < height; ++h)
514 {
515 auto lineOffset = base + entrys[h].OffsetY;
516 for (int w = 0; w < widht; ++w)
517 {
518 (*dstMono) = *reinterpret_cast<std::uint8_t *>(lineOffset + entrys[w].OffsetX);
519 ++dstMono;
520 }
521 dstMono += linePad;
522 }
523 }
524
525 inline void CopyImageDataMonoToBGRA(const Image &src, void *dst) noexcept
526 {
527 CopyImageDataMonoToColor(src, reinterpret_cast<BGRAComponents *>(dst));
528 }
529
530 inline void CopyImageDataMonoToRGBA(const Image &src, void *dst) noexcept
531 {
532 CopyImageDataMonoToColor(src, reinterpret_cast<RGBAComponents *>(dst));
533 }
534
535 inline void CopyImageDataRGBToBGRA(const Image &src, void *dst) noexcept
536 {
537 CopyImageDataColorToColor(src, reinterpret_cast<BGRAComponents *>(dst));
538 }
539
540 inline void CopyImageDataRGBToRGBA(const Image &src, void *dst) noexcept
541 {
542 CopyImageDataColorToColor(src, reinterpret_cast<RGBAComponents *>(dst));
543 }
544
545 class ImageViewDispatcher : public UI::Private::IImageView
546 {
547 friend class ImageViewDispatcherGuard;
548
549 public:
550 explicit ImageViewDispatcher(std::function<void()> notifyZoom)
551 : zoomHandler_(*this)
552 , notifyZoom_(notifyZoom)
553 {
554 }
555
556 UI::ZoomID ZoomID() const noexcept
557 {
558 return zoomHandler_.ZoomDescriptor().ID;
559 }
560
561 void UpdateZoomID(UI::ZoomID id, std::function<void()> notify)
562 {
563 if (id == ZoomID())
564 return;
565
566 zoomHandler_.SetZoomDescriptor(ZoomDescriptor(id));
567 notify();
568 }
569
570 void UpdateUploadMode(UI::UploadMode uploadMode, std::function<void()> notify)
571 {
572 if (uploadMode_ == uploadMode)
573 return;
574
575 {
576 std::unique_lock<std::mutex> gurad(
577 renderMutex_); // prevent mixing upload modes during render (innermost mutex)
578 uploadMode_ = uploadMode;
579 }
580 notify();
581 }
582
583 double ZoomFactor() const noexcept
584 {
585 return zoomHandler_.ZoomDescriptor().Factor;
586 }
587
588 void UpdateZoomFactor(double factor, std::function<void()> notify)
589 {
590 if (factor == ZoomFactor())
591 return;
592
593 zoomHandler_.SetZoomDescriptor(ZoomDescriptor(factor));
594 notify();
595 }
596
597 Point2D<double> ImageAnchor() const noexcept
598 {
599 return imageAnchor_;
600 }
601
602 void UpdateImageAnchor(Point2D<double> imageAnchor, std::function<void()> notify)
603 {
604 if (imageAnchor_ == imageAnchor)
605 return;
606
607 imageAnchor_ = imageAnchor;
608 notify();
609 }
610
611 Point2D<double> ViewAnchor() const noexcept
612 {
613 return viewAnchor_;
614 }
615
616 void UpdateViewAnchor(Point2D<double> viewAnchor, std::function<void()> notify)
617 {
618 if (viewAnchor_ == viewAnchor)
619 return;
620
621 viewAnchor_ = viewAnchor;
622 notify();
623 }
624
625 Point2D<double> LastMousePosition() const noexcept
626 {
627 return *lastPos_;
628 }
629
630 Point2D<double> HoverPosition() const noexcept
631 {
632 return *hoverPosition_;
633 }
634
635 bool TryTranslate(Vector2D<double> translation, std::function<void()> notifySourceRect)
636 {
637 auto zoom = zoomHandler_.ZoomDescriptor().Factor;
638
639 auto imageTranslation = translation / zoom;
640 auto xRange = imageRect_.Width() - sourceRect_.Width();
641 auto yRange = imageRect_.Height() - sourceRect_.Height();
642
643 auto sourceCandidate = sourceRect_;
644
645 auto translationCandidate = sourceCandidate.Location() - imageTranslation;
646
647#ifdef min
648# undef min
649#endif
650#ifdef max
651# undef max
652#endif
653
654 // ensure min range
655 translationCandidate.SetX(std::max(translationCandidate.X(), 0.0));
656 translationCandidate.SetY(std::max(translationCandidate.Y(), 0.0));
657
658 // ensure max range
659 Point2D<double> location(std::min(xRange, translationCandidate.X()),
660 std::min(yRange, translationCandidate.Y()));
661 sourceCandidate.SetLocation(location);
662
663 auto result = sourceRect_ != sourceCandidate;
664 UpdateSourceRect(sourceCandidate, notifySourceRect);
665 return result;
666 }
667
668 bool TryZoomIn(std::function<void()> notifyTargetRect, std::function<void()> notifySourceRect)
669 {
670 auto result = zoomHandler_.TryZoomIn();
671 UpdateTargetSource(notifyTargetRect, notifySourceRect);
672 return result;
673 }
674
675 bool TryZoomOut(std::function<void()> notifyTargetRect, std::function<void()> notifySourceRect)
676 {
677 auto result = zoomHandler_.TryZoomOut();
678 UpdateTargetSource(notifyTargetRect, notifySourceRect);
679 return result;
680 }
681
682 RectLT<double> ViewRect() const noexcept
683 {
684 return viewRect_;
685 }
686
687 RectLT<double> ImageRect() const noexcept
688 {
689 return imageRect_;
690 }
691
692 RectLT<double> SourceRect() const noexcept
693 {
694 return sourceRect_;
695 }
696
697 RectLT<double> TargetRect() const noexcept
698 {
699 return targetRect_;
700 }
701
702 void UpdateViewRect(RectLT<double> rect, std::function<void()> notify)
703 {
704 if (viewRect_ == rect)
705 return;
706 viewRect_ = rect;
707 notify();
708 }
709
710 UI::UploadMode UploadMode() const noexcept
711 {
712 return uploadMode_;
713 }
714
715 Point2D<double> MapViewToTarget(Point2D<double> viewPoint) const
716 {
717 return viewPoint - targetRect_.Location();
718 }
719
720 Point2D<double> MapTargetToSource(Point2D<double> targetPoint) const
721 {
722 return targetPoint / zoomHandler_.ZoomDescriptor().Factor;
723 }
724
725 Point2D<double> MapSourceToImage(Point2D<double> sourcePoint) const
726 {
727 return sourcePoint + sourceRect_.Location();
728 }
729
730 Point2D<double> MapTargetToView(Point2D<double> targetPoint) const
731 {
732 return targetPoint + targetRect_.Location();
733 }
734
735 Point2D<double> MapSourceToTarget(Point2D<double> sourcePoint) const
736 {
737 return sourcePoint * zoomHandler_.ZoomDescriptor().Factor;
738 }
739
740 Point2D<double> MapImageToSource(Point2D<double> imagePoint) const
741 {
742 return imagePoint - sourceRect_.Location();
743 }
744
745 Point2D<double> MapViewToImage(Point2D<double> viewPoint) const
746 {
747 return MapSourceToImage(MapTargetToSource(MapViewToTarget(viewPoint)));
748 }
749
750 Point2D<double> MapImageToView(Point2D<double> imagePoint) const
751 {
752 return MapTargetToView(MapSourceToTarget(MapImageToSource(imagePoint)));
753 }
754
755 Point2D<double> MapTargetToImage(Point2D<double> targetPoint) const
756 {
757 return MapSourceToImage(MapTargetToSource(targetPoint));
758 }
759
760 Point2D<double> MapImageToTarget(Point2D<double> imagePoint) const
761 {
762 return MapSourceToTarget(MapImageToSource(imagePoint));
763 }
764
765 void UploadImage(const Image &image, std::function<void()> notifyImageRect)
766 {
767 std::unique_lock<std::mutex> guard(renderMutex_);
768 UpdateImageRect(image, notifyImageRect);
769
770 auto mappedImage = UpdateRenderTarget(image);
771
772 if (uploadMode_ == UI::UploadMode::Image)
773 CopyImageData(image);
774 else if (mappedImage)
775 CopyImageData(*mappedImage);
776 }
777
778 void CopyImageData(const Image &image) noexcept
779 {
780 switch (image.PlanesCount())
781 {
782 default:
783 return;
784
785 case 1:
786 UI::Private::CopyImageDataMonoToBGRA(image, buffer_.data());
787 return;
788
789 case 3:
790 case 4: // RGBA8 support
791 UI::Private::CopyImageDataRGBToBGRA(image, buffer_.data());
792 return;
793 }
794 }
795
796 void UpdateImageRect(const Cvb::Image &image, std::function<void()> notify)
797 {
798 RectLT<double> requestedImageRect(
799 Point2D<double>(0.0, 0.0),
800 Size2D<double>(static_cast<double>(image.Width()), static_cast<double>(image.Height())));
801
802 if (imageRect_ == requestedImageRect)
803 return;
804
805 imageRect_ = requestedImageRect;
806 notify();
807 }
808
809 void UpdateTargetRect(RectLT<double> rect, std::function<void()> notify)
810 {
811 if (targetRect_ == rect)
812 return;
813 targetRect_ = rect;
814 notify();
815 }
816
817 void UpdateSourceRect(RectLT<double> rect, std::function<void()> notify)
818 {
819 if (sourceRect_ == rect)
820 return;
821 sourceRect_ = rect;
822 notify();
823 }
824
825 void UpdateBuffer(Size2D<int> size)
826 {
827 bufferSize_ = size;
828 std::vector<std::uint8_t>(static_cast<size_t>(bufferSize_.Width()) * static_cast<size_t>(bufferSize_.Height())
829 * 4)
830 .swap(buffer_);
831 }
832
833 std::unique_ptr<class Image> UpdateRenderTarget(const Cvb::Image &image)
834 {
835 if (uploadMode_ == UI::UploadMode::Image)
836 {
837 auto currentWidth = image.Width();
838 auto currentHeight = image.Height();
839 if (bufferSize_ == Size2D<int>(currentWidth, currentHeight))
840 return std::unique_ptr<class Image>();
841
842 UpdateBuffer(Size2D<int>(currentWidth, currentHeight));
843 return std::unique_ptr<class Image>();
844 }
845 else
846 {
847 // get the part of the image that is visible in as integer pixel coordinates (for some pixels only a tiny
848 // part might be visible)
849 auto sourceLeft = std::max(static_cast<int>(std::floor(sourceRect_.Left())), 0);
850 auto sourceTop = std::max(static_cast<int>(std::floor(sourceRect_.Top())), 0);
851 auto sourceRight = std::min(static_cast<int>(std::ceil(sourceRect_.Right())), image.Width() - 1);
852 auto sourceBottom = std::min(static_cast<int>(std::ceil(sourceRect_.Bottom())), image.Height() - 1);
853 Size2D<int> sourceSize(sourceRight - sourceLeft, sourceBottom - sourceTop);
854 Rect<int> sourceRect(Point2D<int>(sourceLeft, sourceTop), sourceSize);
855
856 // validate source rectangle is positive
857 if (sourceRect.Width() <= 0 || sourceRect.Height() <= 0)
858 return std::unique_ptr<class Image>();
859
860 // validate source rectangle is positive
861 if (!targetRect_.IsValid())
862 return std::unique_ptr<class Image>();
863
864 Size2D<int> targetSize;
865 if (sourceRect_.Width() <= targetRect_.Width()) // source pixel is bigger in target -> zoom
866 {
867 targetSize = sourceRect.Size();
868 sourceRectAdj_ = RectLT<double>(
869 Point2D<double>(sourceRect_.Left() - std::floor(sourceRect_.Left()),
870 sourceRect_.Top() - std::floor(sourceRect_.Top())),
871 Size2D<double>(std::min(sourceRect_.Width(), static_cast<double>(sourceRect.Width())),
872 std::min(sourceRect_.Height(), static_cast<double>(sourceRect.Height()))));
873 }
874 else
875 {
876 targetSize = Size2D<int>(static_cast<int>(targetRect_.Width()), static_cast<int>(targetRect_.Height()));
877 sourceRectAdj_ =
878 RectLT<double>(Point2D<double>(0.0, 0.0), Size2D<double>(targetSize.Width(), targetSize.Height()));
879 }
880
881 if (targetSize.Width() <= 0 || targetSize.Height() <= 0)
882 return std::unique_ptr<class Image>();
883
884 auto mappedImage = image.Map(sourceRect, targetSize);
885 if (bufferSize_ == Size2D<int>(targetSize.Width(), targetSize.Height()))
886 return mappedImage;
887
888 UpdateBuffer(targetSize);
889 return mappedImage;
890 }
891 }
892
893 Point2D<double> LimitPointToRect(Point2D<double> point, RectLT<double> rect)
894 {
895 auto result = point;
896 if (result.X() < 0.0)
897 result.SetX(0.0);
898 else if (result.X() > rect.Width())
899 result.SetX(rect.Width());
900 if (result.Y() < 0.0)
901 result.SetY(0.0);
902 else if (result.Y() > rect.Height())
903 result.SetY(rect.Height());
904 return result;
905 }
906
907 void UpdateTargetSource(std::function<void()> notifyTargetRect, std::function<void()> notifySourceRect)
908 {
909 zoomHandler_.UpdatePanoramaFactor();
910 auto zoom = zoomHandler_.ZoomDescriptor().Factor;
911
912 auto contentWidth = zoom * imageRect_.Width();
913 auto contentHeight = zoom * imageRect_.Height();
914
915 UpdateTargetRect(
916 RectLT<double>(
917 Point2D<double>(
918 (contentWidth <= viewRect_.Width()) ? (viewRect_.Width() - contentWidth) / 2.0
919 : 0.0, // if all the image content width fits into the view
920 // port center it otherwise fill the target
921 (contentHeight <= viewRect_.Height()) ? (viewRect_.Height() - contentHeight) / 2.0
922 : 0.0), // if all the image content height fits into the
923 // view port center it otherwise fill the target
924 Size2D<double>((contentWidth < viewRect_.Width())
925 ? contentWidth
926 : viewRect_.Width(), // if all the image content width fits into the view port
927 // draw all of it otherwise fill the viewport
928 (contentHeight < viewRect_.Height()) ? contentHeight : viewRect_.Height())),
929 notifyTargetRect); // if all the image content height fits into the view port draw all of it otherwise
930 // fill the viewport
931
932 // The view port rectangle adjusted by the current zoom factor is our maximum candidate source rectangle
933 auto sourceCandidate = viewRect_; // TODO verify
934 sourceCandidate.SetWidth(sourceCandidate.Width() / zoom);
935 sourceCandidate.SetHeight(sourceCandidate.Height() / zoom);
936
937 // However the source rectangle might be smaller as our candidate rectangle
938 sourceCandidate.SetWidth(std::min(sourceCandidate.Width(), imageRect_.Width()));
939 sourceCandidate.SetHeight(std::min(sourceCandidate.Height(), imageRect_.Height()));
940
941 auto targetPoint = MapViewToTarget(viewAnchor_);
942 targetPoint = LimitPointToRect(targetPoint, targetRect_);
943 auto sourcePoint = MapTargetToSource(targetPoint);
944
945 auto offsetCandidate = imageAnchor_ - sourcePoint;
946
947 // Valid range for offset
948 auto xRange = imageRect_.Width() - sourceCandidate.Width();
949 auto yRange = imageRect_.Height() - sourceCandidate.Height();
950
951 // ancor point for zoom in the center of your image if possible
952 auto xOffsetCandidate = offsetCandidate.X();
953 auto yOffsetCandidate = offsetCandidate.Y();
954
955 // ensure min range
956 xOffsetCandidate = std::max(xOffsetCandidate, 0.0);
957 yOffsetCandidate = std::max(yOffsetCandidate, 0.0);
958
959 // ensure max range
960 Point2D<double> location(std::min(xRange, xOffsetCandidate), std::min(yRange, yOffsetCandidate));
961 sourceCandidate.SetLocation(location);
962
963 UpdateSourceRect(sourceCandidate, notifySourceRect);
964 }
965
966 void UpdateHoverPosition(Point2D<double> point, std::function<void()> notify)
967 {
968 if (hoverPosition_ && *hoverPosition_ == point)
969 return;
970
971 hoverPosition_ = std::make_unique<Point2D<double>>(point);
972 notify();
973 }
974
975 void InvalidateLastMousePosition() noexcept
976 {
977 lastPos_.reset();
978 }
979
980 void InvalidateLastMousePosition(Point2D<double> point) noexcept
981 {
982 lastPos_ = std::make_unique<Point2D<double>>(point);
983 }
984
985 bool IsLastMousePositionValid() const noexcept
986 {
987 return (lastPos_) ? true : false;
988 }
989
990 bool IsHoverPositionValid() const noexcept
991 {
992 return (hoverPosition_) ? true : false;
993 }
994
995 Size2D<double> ViewSize() const noexcept override
996 {
997 return viewRect_.Size();
998 }
999
1000 Size2D<double> ImageSize() const noexcept override
1001 {
1002 return imageRect_.Size();
1003 }
1004
1005 void ApplyZoom(double) override
1006 {
1007 notifyZoom_();
1008 }
1009
1010 RectLT<double> SourceRectAdj() const noexcept
1011 {
1012 return sourceRectAdj_;
1013 }
1014
1015 bool IsBufferValid() const noexcept
1016 {
1017 return (buffer_.size()) ? true : false;
1018 }
1019
1020 std::uint8_t *Buffer() const noexcept
1021 {
1022 return buffer_.data();
1023 }
1024
1025 Size2D<int> BufferSize() const noexcept
1026 {
1027 return bufferSize_;
1028 }
1029
1030 private:
1031 RectLT<double> imageRect_;
1032 RectLT<double> viewRect_;
1033 RectLT<double> sourceRect_;
1034 RectLT<double> targetRect_;
1035
1036 RectLT<double> sourceRectAdj_;
1037
1038 Point2D<double> viewAnchor_;
1039 Point2D<double> imageAnchor_;
1040
1041 std::mutex renderMutex_;
1042
1043 UI::Private::ZoomHandler zoomHandler_;
1044
1045 std::unique_ptr<Point2D<double>> lastPos_;
1046 std::unique_ptr<Point2D<double>> hoverPosition_;
1047
1048 Cvb::UI::UploadMode uploadMode_ = UI::UploadMode::Image;
1049
1050 mutable std::vector<std::uint8_t> buffer_;
1051 Size2D<int> bufferSize_;
1052
1053 std::function<void()> notifyZoom_;
1054 };
1055
1056 class ImageViewDispatcherGuard
1057 {
1058 public:
1059 explicit ImageViewDispatcherGuard(ImageViewDispatcher &dispatcher)
1060 : dispatcher_(dispatcher)
1061 {
1062 dispatcher_.renderMutex_.lock();
1063 }
1064
1065 ImageViewDispatcherGuard(const ImageViewDispatcherGuard &other) = delete;
1066 ImageViewDispatcherGuard &operator=(const ImageViewDispatcherGuard &other) = delete;
1067 ImageViewDispatcherGuard(ImageViewDispatcherGuard &&other) = delete;
1068 ImageViewDispatcherGuard &operator=(ImageViewDispatcherGuard &&other) = delete;
1069 ~ImageViewDispatcherGuard()
1070 {
1071 dispatcher_.renderMutex_.unlock();
1072 }
1073
1074 private:
1075 ImageViewDispatcher &dispatcher_;
1076 };
1077
1078 } // namespace Private
1079
1080 } // namespace UI
1081
1082 CVB_END_INLINE_NS
1083
1084} // namespace Cvb
int Width() const noexcept
Width of the image in pixels.
Definition decl_image.hpp:309
int Height() const noexcept
Height of the image in pixels.
Definition decl_image.hpp:299
std::unique_ptr< Image > Map(Rect< int > rect) const
Creates a mapped image of the region of this image.
Definition decl_image.hpp:352
Stores a pair of numbers that represents the width and the height of a subject, typically a rectangle...
Definition size_2d.hpp:20
T min(T... args)
Namespace for user interface components.
Definition decl_image_scene.hpp:39
ZoomID
Identifier for a zoom factor.
Definition detail_ui.hpp:61
@ Factor4
Definition detail_ui.hpp:85
@ Factor2
Definition detail_ui.hpp:81
@ Factor64
Definition detail_ui.hpp:101
@ Invalid
Definition detail_ui.hpp:65
@ Factor32
Definition detail_ui.hpp:97
@ Factor8
Definition detail_ui.hpp:89
@ Factor128
Definition detail_ui.hpp:105
@ Factor1
Definition detail_ui.hpp:77
@ Panorama
Definition detail_ui.hpp:73
@ Custom
Definition detail_ui.hpp:69
@ Factor16
Definition detail_ui.hpp:93
AutoRefresh
Allows to automatically refresh, if image content changes.
Definition detail_ui.hpp:114
@ On
Definition detail_ui.hpp:121
@ Off
Definition detail_ui.hpp:125
LabelScale
Switch defining if image view labels are sensitive to zoom operations.
Definition detail_ui.hpp:130
UploadMode
Defines the upload behavior.
Definition detail_ui.hpp:37
@ Viewport
Definition detail_ui.hpp:53
@ Image
Definition detail_ui.hpp:45
Root namespace for the Image Manager interface.
Definition version.hpp:11
T quiet_NaN(T... args)
Describes an zoom setting for the display.
Definition detail_ui.hpp:148
bool operator==(const ZoomDescriptor &zoomDescriptor) const noexcept
Compares to an other zoom descriptor.
Definition detail_ui.hpp:167
bool operator!=(const ZoomDescriptor &zoomDescriptor) const noexcept
Compares to an other zoom descriptor.
Definition detail_ui.hpp:178
ZoomDescriptor(double customFactor) noexcept
Constructor for a custom zoom descriptor.
Definition detail_ui.hpp:240
double Factor
Numeric representation of the zoom factor.
Definition detail_ui.hpp:157
ZoomDescriptor(ZoomID id) noexcept
Constructor for a standard zoom descriptor.
Definition detail_ui.hpp:189
ZoomID ID
Zoom identifier for known factors.
Definition detail_ui.hpp:155