CVB++ 15.0
detail_image_scene.hpp
1#pragma once
2
3#include "../../global.hpp"
4
5#include "../_decl/decl_image_scene.hpp"
6#include "../_decl/decl_image_view.hpp"
7#include "../_decl/decl_opengl_image_renderer_factory.hpp"
8
9#include "../../_decl/decl_image.hpp"
10#include "../../_decl/decl_image_plane.hpp"
11#include "../../_decl/decl_vpat.hpp"
12
13#include "../../driver/_decl/decl_device_image.hpp"
14
15namespace Cvb
16{
17
18 CVB_BEGIN_INLINE_NS
19
20 namespace UI
21 {
22
23 inline void ImageScene::SetRenderEngine(Cvb::UI::RenderEngine renderEngine)
24 {
25 std::unique_lock<std::mutex> guard(renderMutex_);
26
27 if (renderEngine == RenderEngine())
28 return;
29
30 std::unique_ptr<Engine> engine;
31 switch (renderEngine)
32 {
33 default:
34 throw std::runtime_error("failed to set unknown render engine.");
35 return;
36
38 engine = std::make_unique<RasterEngine>(*this);
39 break;
40
43 engine = std::make_unique<GLEngine>(*this, renderEngine);
44 break;
45 }
46
47 auto imageView = ImageView();
48 if (!imageView)
49 throw std::runtime_error("cannot change the render engine for a scene without a view.");
50
51 imageView->setViewport(engine->CreateViewport());
52 std::swap(engine_, engine);
53 }
54
55 inline void ImageScene::UpdateSceneRect(const Image &image) noexcept
56 {
57 auto currentSceneRect = sceneRect().toRect();
58 QRect requredSceneRect(0, 0, image.Width(), image.Height());
59 if (currentSceneRect == requredSceneRect)
60 return;
61
62 setSceneRect(requredSceneRect);
63 }
64
65 inline void ImageScene::UploadImage(const Image &image) noexcept
66 {
67 std::unique_lock<std::mutex> guard(renderMutex_);
68 UpdateSceneRect(image);
69 auto mappedImage = UpdateRenderTarget(image);
70
71 auto imageView = this->ImageView();
72 if (imageView->UploadMode() == UploadMode::Image)
73 CopyImageData(image);
74 else if (mappedImage)
75 CopyImageData(*mappedImage);
76 }
77
79 {
80 auto viewList = views();
81 if (!viewList.size())
82 return nullptr;
83
84 return dynamic_cast<class ImageView *>(viewList.first());
85 }
86
87 inline void ImageScene::RasterEngine::Render(QPainter *painter, const QRectF &rect) noexcept
88 {
89 auto imageView = ImageScene().ImageView();
90 auto uploadMode = imageView->UploadMode();
91
92 if (uploadMode == Cvb::UI::UploadMode::Image)
93 {
94 if (screenImage_.isNull())
95 return;
96
97 painter->drawImage(rect, screenImage_, rect);
98 }
99 else
100 {
101 auto image = imageView->Image();
102 if (!image)
103 return;
104
105 // limit the target rectangle to the scene rectangle
106 auto imageRect = ImageScene().sceneRect();
107 auto targetRect = rect;
108 targetRect.setLeft(qMax(0.0, targetRect.left()));
109 targetRect.setWidth(qMin(imageRect.width(), targetRect.width()));
110 targetRect.setTop(qMax(0.0, targetRect.top()));
111 targetRect.setHeight(qMin(imageRect.height(), targetRect.height()));
112
113 painter->drawImage(targetRect, screenImage_, screenImageRect_);
114 }
115 }
116
117 inline std::unique_ptr<Image> ImageScene::RasterEngine::UpdateRenderTarget(const Image &image) noexcept
118 {
119 auto imageView = ImageScene().ImageView();
120 auto uploadMode = imageView->UploadMode();
121 if (uploadMode == Cvb::UI::UploadMode::Image)
122 {
123
124 auto currentWidth = image.Width();
125 auto currentHeight = image.Height();
126 if (screenImage_.size() == QSize(currentWidth, currentHeight))
127 return std::unique_ptr<class Image>();
128
129 screenImage_ = QImage(currentWidth, currentHeight, QImage::Format_ARGB32_Premultiplied);
130 return std::unique_ptr<class Image>();
131 }
132 else
133 {
134 auto viewportRect = imageView->viewport()->rect();
135 auto mappedViewportRect = imageView->mapToScene(viewportRect).boundingRect();
136 auto imageRectF = ImageScene().sceneRect();
137
138 auto sourceRectF =
139 QRectF(qMax(imageRectF.left(), mappedViewportRect.left()), qMax(imageRectF.top(), mappedViewportRect.top()),
140 qMin(imageRectF.width(), mappedViewportRect.width()),
141 qMin(imageRectF.height(), mappedViewportRect.height()));
142
143 // get the part of the image that is visible in as integer pixel coordinates (for some pixels only a tiny part
144 // might be visible)
145 auto sourceLeft = qMax(qFloor(mappedViewportRect.left()), 0);
146 auto sourceTop = qMax(qFloor(mappedViewportRect.top()), 0);
147 auto sourceRight = qMin(qCeil(mappedViewportRect.right()), image.Width() - 1);
148 auto sourceBottom = qMin(qCeil(mappedViewportRect.bottom()), image.Height() - 1);
149 Size2D<int> sourceSize(sourceRight - sourceLeft, sourceBottom - sourceTop);
150 Rect<int> sourceRect(Point2D<int>(sourceLeft, sourceTop), sourceSize);
151
152 // validate source rectangle is positive
153 if (sourceRect.Width() <= 0 || sourceRect.Height() <= 0)
154 return std::unique_ptr<class Image>();
155
156 auto targetRectF = QRectF(imageView->mapFromScene(sourceRectF).boundingRect());
157 Size2D<int> targetSize;
158 if (mappedViewportRect.width() <= targetRectF.width()) // source pixel is bigger in target -> zoom
159 {
160 targetSize = sourceRect.Size();
161 screenImageRect_ = QRectF(mappedViewportRect.left() - qFloor(mappedViewportRect.left()),
162 mappedViewportRect.top() - qFloor(mappedViewportRect.top()),
163 qMin(mappedViewportRect.width(), static_cast<qreal>(sourceRect.Width())),
164 qMin(mappedViewportRect.height(), static_cast<qreal>(sourceRect.Height())));
165 }
166 else
167 {
168 targetSize = Size2D<int>(static_cast<int>(targetRectF.width()), static_cast<int>(targetRectF.height()));
169 screenImageRect_ = QRectF(0.0, 0.0, targetSize.Width(), targetSize.Height());
170 }
171
172 auto mappedImage = image.Map(sourceRect, targetSize);
173 if (screenImage_.size() == QSize(targetSize.Width(), targetSize.Height()))
174 return mappedImage;
175
176 screenImage_ = QImage(targetSize.Width(), targetSize.Height(), QImage::Format_ARGB32_Premultiplied);
177 return mappedImage;
178 }
179 }
180
181 inline void ImageScene::RasterEngine::CopyImageData(const Image &image) noexcept
182 {
183 switch (image.PlanesCount())
184 {
185 default:
186 return;
187
188 case 1:
189 Private::CopyImageDataMonoToBGRA(image, screenImage_.bits());
190 return;
191
192 case 3:
193 case 4: // RGBA8 support
194 Private::CopyImageDataRGBToBGRA(image, screenImage_.bits());
195 return;
196 }
197 }
198
199 inline void ImageScene::GLEngine::Render(QPainter *painter, const QRectF &rect) noexcept
200 {
201 if (!renderer_)
202 return;
203
204 MakeCurrent();
205 painter->beginNativePainting();
206
207 glClearColor(static_cast<GLclampf>(clearColor_.redF()), static_cast<GLclampf>(clearColor_.greenF()),
208 static_cast<GLclampf>(clearColor_.blueF()), 1.0);
209 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
210
211 renderer_->Render(rect);
212
213 painter->endNativePainting();
214 }
215
216 inline std::unique_ptr<Image> ImageScene::GLEngine::UpdateRenderTarget(const Image &image) noexcept
217 {
218 if (!renderer_)
219 return std::unique_ptr<class Image>();
220
221 if (QThread::currentThread() != QApplication::instance()->thread())
222 {
223 ImageScene().ScheduleSyncUpload();
224 return std::unique_ptr<class Image>();
225 }
226
227 auto imageView = ImageScene().ImageView();
228
229 MakeCurrent();
230
231 auto uploadMode = imageView->UploadMode();
232 if (uploadMode == Cvb::UI::UploadMode::Image)
233 {
234 OpenGLBufferFormat bufferFormat;
235 bufferFormat.TextureFormat = (image.PlanesCount() == 1) ? OpenGLTextureFormat::Mono : OpenGLTextureFormat::RGB;
236 bufferFormat.Width = image.Width();
237 bufferFormat.Height = image.Height();
238
239 if (bufferFormat != renderer_->BufferFormat())
240 {
241 renderer_->UpdateBufferFormat(bufferFormat);
242 renderer_->UpdateMaping(ImageScene().sceneRect(), Size2D<int>());
243 }
244
245 return std::unique_ptr<class Image>();
246 }
247 else
248 {
249 // get the part of the scene that is shown by the view, some parts might be outside the scene rect
250 auto viewportRect = imageView->viewport()->rect();
251 auto mappedViewportRect = imageView->mapToScene(viewportRect).boundingRect();
252
253 // get the part of the image that is visible as floating point
254 auto imageRectF = ImageScene().sceneRect();
255 auto sourceRectF =
256 QRectF(qMax(imageRectF.left(), mappedViewportRect.left()), qMax(imageRectF.top(), mappedViewportRect.top()),
257 qMin(imageRectF.width(), mappedViewportRect.width()),
258 qMin(imageRectF.height(), mappedViewportRect.height()));
259
260 // get the part of the image that is visible in as integer pixel coordinates (for some pixels only a tiny part
261 // might be visible)
262 auto sourceLeft = qMax(qFloor(mappedViewportRect.left()), 0);
263 auto sourceTop = qMax(qFloor(mappedViewportRect.top()), 0);
264 auto sourceRight = qMin(qCeil(mappedViewportRect.right()), image.Width() - 1);
265 auto sourceBottom = qMin(qCeil(mappedViewportRect.bottom()), image.Height() - 1);
266 Size2D<int> sourceSize(sourceRight - sourceLeft, sourceBottom - sourceTop);
267 Rect<int> sourceRect(Point2D<int>(sourceLeft, sourceTop), sourceSize);
268
269 // validate source rectangle is positive, might happen if some in puts are invalid during startup
270 if (sourceRect.Width() <= 0 || sourceRect.Height() <= 0)
271 return std::unique_ptr<class Image>();
272
273 auto targetRectF = QRectF(imageView->mapFromScene(sourceRectF).boundingRect());
274 Size2D<int> targetSize;
275 // check if an image pixel requires more than one screen pixel (zoom)
276 if (mappedViewportRect.width() <= targetRectF.width())
277 {
278 // zoomed in (or 1:1) we will see every single pixel form the visible image part on the screen
279 targetSize = sourceRect.Size();
280 }
281 else
282 {
283 // zoomed out we will not see every pixel of the visible image part on the screen -> subsampling
284 // due to accuracy problems when mapping it might happen that the target size is slightly bigger than our
285 // image. Although that is no problem and barely visible it seems odd to take
286 targetSize = Size2D<int>(static_cast<int>(qMin(targetRectF.width(), imageRectF.width())),
287 static_cast<int>(qMin(targetRectF.height(), imageRectF.height())));
288 }
289
290 auto mappedImage = image.Map(sourceRect, targetSize);
291
292 OpenGLBufferFormat bufferFormat;
293 bufferFormat.TextureFormat = (image.PlanesCount() == 1) ? OpenGLTextureFormat::Mono : OpenGLTextureFormat::RGB;
294 auto modWidth = (bufferFormat.TextureFormat == OpenGLTextureFormat::Mono)
295 ? targetSize.Width() % 4
296 : 0; // we need four byte scan line padding for mono
297 bufferFormat.LinePad = (modWidth) ? 4 - modWidth : 0;
298 bufferFormat.Width = targetSize.Width();
299 bufferFormat.Height = targetSize.Height();
300
301 if (bufferFormat != renderer_->BufferFormat())
302 renderer_->UpdateBufferFormat(bufferFormat);
303
304 renderer_->UpdateMaping(sourceRectF, targetSize);
305
306 return mappedImage;
307 }
308 }
309
310 inline void ImageScene::GLEngine::CopyImageData(const Image &image) noexcept
311 {
312 if (!renderer_)
313 return;
314
315 if (QThread::currentThread() != QApplication::instance()->thread())
316 {
317 ImageScene().ScheduleSyncUpload();
318 return;
319 }
320
321 MakeCurrent();
322 int linePad = 0;
323 auto dst = renderer_->BeginBufferAccess(linePad);
324 if (!dst)
325 return;
326
327 switch (image.PlanesCount())
328 {
329 default:
330 break;
331
332 case 1:
333 if (renderer_->MonoUploadSupported())
334 Private::CopyImageDataMonoToMono(image, dst, linePad);
335 else
336 Private::CopyImageDataMonoToRGBA(image, dst);
337 break;
338
339 case 3:
340 case 4: // RGBA8 support
341 Private::CopyImageDataRGBToRGBA(image, dst);
342 break;
343 }
344
345 renderer_->EndBufferAccess();
346 }
347
348 inline QWidget *ImageScene::GLEngine::CreateViewport()
349 {
350 auto glViewport = new ImageScene::GLViewport(*this); // NOLINT(cppcoreguidelines-owning-memory)
351 auto format = OpenGLImageRendererFactory::CreateFormat((renderEngine_ == Cvb::UI::RenderEngine::OpenGL2) ? 2 : 3);
352
353 glViewport->setFormat(format);
354 return glViewport;
355 }
356
357 inline void ImageScene::GLEngine::ViewPortSetup()
358 {
359 // a GL context is guaranteed to be current here
360 initializeOpenGLFunctions();
361
362 try
363 {
364 renderer_ =
366 }
367 catch (...)
368 {
369 // we cannot do something about any error here
370 }
371 }
372
373 inline void ImageScene::GLEngine::MakeCurrent() noexcept
374 {
375 if (auto imageView = ImageScene().ImageView())
376 if (auto glViewport = dynamic_cast<QOpenGLWidget *>(imageView->viewport()))
377 glViewport->makeCurrent();
378 }
379
380 inline void ImageScene::drawBackground(QPainter *painter, const QRectF &rect)
381 {
382 if (syncUploadRequired_)
383 {
384 if (auto imageView = this->ImageView())
385 if (auto image = imageView->Image())
386 UploadImage(*image);
387 syncUploadRequired_ = false;
388 }
389
390 std::unique_lock<std::mutex> guard(renderMutex_);
391 Render(painter, rect);
392 }
393
394 } // namespace UI
395
396 CVB_END_INLINE_NS
397
398} // namespace Cvb
Scene to provide a convenient display for an image.
Definition decl_image_scene.hpp:49
class ImageView * ImageView() noexcept
Return the image view associated with this scene.
Definition detail_image_scene.hpp:78
ImageScene(QObject *parent=nullptr)
Definition decl_image_scene.hpp:60
virtual void UploadImage(const Image &image) noexcept
Upload an image.
Definition detail_image_scene.hpp:65
Cvb::UI::UploadMode UploadMode() const noexcept
Get the current upload mode.
Definition decl_image_view.hpp:321
static QSurfaceFormat CreateFormat(int version)
Create a OpenGL format for the given version.
Definition detail_opengl_image_renderer_factory.hpp:35
static std::unique_ptr< OpenGLImageRenderer > CreateRenderer(int version)
Create a renderer.
Definition detail_opengl_image_renderer_factory.hpp:18
Namespace for user interface components.
Definition decl_image_scene.hpp:39
@ Mono
Definition ui.hpp:144
@ RGB
Definition ui.hpp:140
@ Image
Definition detail_ui.hpp:45
RenderEngine
Defines the render engine used for drawing.
Definition ui.hpp:108
@ Raster
Definition ui.hpp:114
@ OpenGL3
Definition ui.hpp:127
@ OpenGL2
Definition ui.hpp:121
Root namespace for the Image Manager interface.
Definition c_bayer_to_rgb.h:17
Buffer format description for a texture to hold the pixel data.
Definition ui.hpp:151
OpenGLTextureFormat TextureFormat
Definition ui.hpp:170