Changeset View
Changeset View
Standalone View
Standalone View
libs/ui/canvas/kis_coordinates_converter.cpp
Show All 36 Lines | 31 | struct KisCoordinatesConverter::Private { | |||
---|---|---|---|---|---|
37 | { | 37 | { | ||
38 | } | 38 | } | ||
39 | 39 | | |||
40 | KisImageWSP image; | 40 | KisImageWSP image; | ||
41 | 41 | | |||
42 | bool isXAxisMirrored; | 42 | bool isXAxisMirrored; | ||
43 | bool isYAxisMirrored; | 43 | bool isYAxisMirrored; | ||
44 | qreal rotationAngle; | 44 | qreal rotationAngle; | ||
45 | QSizeF canvasWidgetSize; | 45 | // QSizeF canvasWidgetSize; | ||
46 | QSize canvasWidgetDevicePixelSize; | ||||
46 | qreal devicePixelRatio; | 47 | qreal devicePixelRatio; | ||
47 | QPointF documentOffset; | 48 | QPoint documentOffsetDevicePixel; | ||
alvinhochun: I have a suspicion that the original author chose `QSizeF` and `QPointF` for performance - that… | |||||
No, it was not an optimization. It was a fix to avoid rounding errors when doing canvas rotation with Shift+Space+LMB. The matrices are updated incrementally, so rounding errors build-up easily. Your refactoring causes this regression again. dkazakov: No, it was not an optimization. It was a fix to avoid rounding errors when doing canvas… | |||||
48 | 49 | | |||
49 | QTransform flakeToWidget; | 50 | QTransform flakeToWidget; | ||
50 | QTransform imageToDocument; | 51 | QTransform imageToDocument; | ||
51 | QTransform documentToFlake; | 52 | QTransform documentToFlake; | ||
52 | QTransform widgetToViewport; | 53 | QTransform widgetToViewport; | ||
53 | }; | 54 | }; | ||
54 | 55 | | |||
55 | /** | 56 | /** | ||
Show All 11 Lines | |||||
67 | */ | 68 | */ | ||
68 | 69 | | |||
69 | QPointF KisCoordinatesConverter::centeringCorrection() const | 70 | QPointF KisCoordinatesConverter::centeringCorrection() const | ||
70 | { | 71 | { | ||
71 | KisConfig cfg(true); | 72 | KisConfig cfg(true); | ||
72 | 73 | | |||
73 | QSize documentSize = imageRectInWidgetPixels().toAlignedRect().size(); | 74 | QSize documentSize = imageRectInWidgetPixels().toAlignedRect().size(); | ||
74 | QPointF dPoint(documentSize.width(), documentSize.height()); | 75 | QPointF dPoint(documentSize.width(), documentSize.height()); | ||
75 | QPointF wPoint(m_d->canvasWidgetSize.width(), m_d->canvasWidgetSize.height()); | 76 | QSizeF wSize = canvasWidgetLogicalPixelSize(); | ||
77 | QPointF wPoint(wSize.width(), wSize.height()); | ||||
76 | 78 | | |||
77 | QPointF minOffset = -cfg.vastScrolling() * wPoint; | 79 | QPointF minOffset = -cfg.vastScrolling() * wPoint; | ||
78 | QPointF maxOffset = dPoint - wPoint + cfg.vastScrolling() * wPoint; | 80 | QPointF maxOffset = dPoint - wPoint + cfg.vastScrolling() * wPoint; | ||
79 | 81 | | |||
80 | QPointF range = maxOffset - minOffset; | 82 | QPointF range = maxOffset - minOffset; | ||
81 | 83 | | |||
82 | range.rx() = qMin(range.x(), (qreal)0.0); | 84 | range.rx() = qMin(range.x(), (qreal)0.0); | ||
83 | range.ry() = qMin(range.y(), (qreal)0.0); | 85 | range.ry() = qMin(range.y(), (qreal)0.0); | ||
Show All 16 Lines | |||||
100 | * But when we do our own transformations of the canvas, like rotation | 102 | * But when we do our own transformations of the canvas, like rotation | ||
101 | * and mirroring, we cannot rely on the centering of the canvas | 103 | * and mirroring, we cannot rely on the centering of the canvas | ||
102 | * controller and we do it ourselves. Then we just set new offset and | 104 | * controller and we do it ourselves. Then we just set new offset and | ||
103 | * return its value to be set in the canvas controller explicitly. | 105 | * return its value to be set in the canvas controller explicitly. | ||
104 | */ | 106 | */ | ||
105 | 107 | | |||
106 | void KisCoordinatesConverter::correctOffsetToTransformation() | 108 | void KisCoordinatesConverter::correctOffsetToTransformation() | ||
107 | { | 109 | { | ||
108 | m_d->documentOffset = -(imageRectInWidgetPixels().topLeft() - | 110 | m_d->documentOffsetDevicePixel = -((imageRectInWidgetPixels().topLeft() - | ||
109 | centeringCorrection()).toPoint(); | 111 | centeringCorrection()) * m_d->devicePixelRatio).toPoint(); | ||
110 | } | 112 | } | ||
111 | 113 | | |||
112 | void KisCoordinatesConverter::correctTransformationToOffset() | 114 | void KisCoordinatesConverter::correctTransformationToOffset() | ||
113 | { | 115 | { | ||
114 | QPointF topLeft = imageRectInWidgetPixels().topLeft(); | 116 | QPointF topLeft = imageRectInWidgetPixels().topLeft(); | ||
115 | QPointF diff = (-topLeft) - m_d->documentOffset; | 117 | QPointF diff = (-topLeft) - documentOffsetLogicalPixel(); | ||
116 | diff += centeringCorrection(); | 118 | diff += centeringCorrection(); | ||
117 | m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y()); | 119 | m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y()); | ||
118 | } | 120 | } | ||
119 | 121 | | |||
120 | void KisCoordinatesConverter::recalculateTransformations() | 122 | void KisCoordinatesConverter::recalculateTransformations() | ||
121 | { | 123 | { | ||
122 | if(!m_d->image) return; | 124 | if(!m_d->image) return; | ||
123 | 125 | | |||
124 | m_d->imageToDocument = QTransform::fromScale(1 / m_d->image->xRes(), | 126 | m_d->imageToDocument = QTransform::fromScale(1 / m_d->image->xRes(), | ||
125 | 1 / m_d->image->yRes()); | 127 | 1 / m_d->image->yRes()); | ||
126 | 128 | | |||
127 | qreal zoomX, zoomY; | 129 | qreal zoomX, zoomY; | ||
128 | KoZoomHandler::zoom(&zoomX, &zoomY); | 130 | KoZoomHandler::zoom(&zoomX, &zoomY); | ||
129 | m_d->documentToFlake = QTransform::fromScale(zoomX, zoomY); | 131 | m_d->documentToFlake = QTransform::fromScale(zoomX, zoomY); | ||
130 | 132 | | |||
131 | correctTransformationToOffset(); | 133 | correctTransformationToOffset(); | ||
132 | 134 | | |||
133 | QRectF irect = imageRectInWidgetPixels(); | 135 | QRectF irect = imageRectInWidgetPixels(); | ||
134 | QRectF wrect = QRectF(QPoint(0,0), m_d->canvasWidgetSize); | 136 | QRectF wrect = QRectF(QPoint(0,0), canvasWidgetLogicalPixelSize()); | ||
135 | QRectF rrect = irect & wrect; | 137 | QRectF rrect = irect & wrect; | ||
136 | 138 | | |||
137 | QTransform reversedTransform = flakeToWidgetTransform().inverted(); | 139 | QTransform reversedTransform = flakeToWidgetTransform().inverted(); | ||
138 | QRectF canvasBounds = reversedTransform.mapRect(rrect); | 140 | QRectF canvasBounds = reversedTransform.mapRect(rrect); | ||
139 | QPointF offset = canvasBounds.topLeft(); | 141 | QPointF offset = canvasBounds.topLeft(); | ||
140 | 142 | | |||
141 | m_d->widgetToViewport = reversedTransform * QTransform::fromTranslate(-offset.x(), -offset.y()); | 143 | m_d->widgetToViewport = reversedTransform * QTransform::fromTranslate(-offset.x(), -offset.y()); | ||
142 | } | 144 | } | ||
143 | 145 | | |||
146 | QSizeF KisCoordinatesConverter::canvasWidgetLogicalPixelSize() const | ||||
147 | { | ||||
148 | return QSizeF(m_d->canvasWidgetDevicePixelSize) / m_d->devicePixelRatio; | ||||
149 | } | ||||
150 | | ||||
151 | QPointF KisCoordinatesConverter::documentOffsetLogicalPixel() const | ||||
152 | { | ||||
153 | return QPointF(m_d->documentOffsetDevicePixel) / m_d->devicePixelRatio; | ||||
154 | } | ||||
155 | | ||||
144 | 156 | | |||
145 | KisCoordinatesConverter::KisCoordinatesConverter() | 157 | KisCoordinatesConverter::KisCoordinatesConverter() | ||
146 | : m_d(new Private) { } | 158 | : m_d(new Private) { } | ||
147 | 159 | | |||
148 | KisCoordinatesConverter::~KisCoordinatesConverter() | 160 | KisCoordinatesConverter::~KisCoordinatesConverter() | ||
149 | { | 161 | { | ||
150 | delete m_d; | 162 | delete m_d; | ||
151 | } | 163 | } | ||
152 | 164 | | |||
153 | void KisCoordinatesConverter::setCanvasWidgetSize(QSize size) | 165 | void KisCoordinatesConverter::setCanvasWidgetSize(QSize size, qreal devicePixelRatio) | ||
154 | { | 166 | { | ||
155 | m_d->canvasWidgetSize = size; | 167 | qDebug() << "KisCoordinatesConverter::setCanvasWidgetSize" << size << devicePixelRatio; | ||
168 | #pragma clang diagnostic push | ||||
169 | #pragma clang diagnostic ignored "-Wfloat-equal" | ||||
170 | // This comparison is fine. We expect devicePixelRatio to be exactly identical. | ||||
171 | if (devicePixelRatio != m_d->devicePixelRatio) { | ||||
172 | #pragma clang diagnostic pop | ||||
173 | warnUI << "WARNING: KisCoordinatesConverter::setCanvasWidgetSize called with dpr" << devicePixelRatio << "but internal dpr is" << m_d->devicePixelRatio; | ||||
174 | } | ||||
175 | // This is how QOpenGLCanvas sets the FBO and the viewport size. If | ||||
176 | // devicePixelRatio is non-integral, the result is truncated. | ||||
177 | int viewportWidth = static_cast<int>(size.width() * devicePixelRatio); | ||||
178 | int viewportHeight = static_cast<int>(size.height() * devicePixelRatio); | ||||
179 | m_d->canvasWidgetDevicePixelSize = QSize(viewportWidth, viewportHeight); | ||||
156 | recalculateTransformations(); | 180 | recalculateTransformations(); | ||
157 | } | 181 | } | ||
158 | 182 | | |||
159 | void KisCoordinatesConverter::setDevicePixelRatio(qreal value) | 183 | void KisCoordinatesConverter::setDevicePixelRatio(qreal value) | ||
160 | { | 184 | { | ||
dkazakov: Looks like atm this function duplicates functionality of setCanvasWidgetSize | |||||
185 | qDebug() << "KisCoordinatesConverter::setDevicePixelRatio" << m_d->devicePixelRatio << "to" << value; | ||||
dkazakov: Please remove this debugging line :) | |||||
161 | m_d->devicePixelRatio = value; | 186 | m_d->devicePixelRatio = value; | ||
162 | } | 187 | } | ||
163 | 188 | | |||
164 | void KisCoordinatesConverter::setImage(KisImageWSP image) | 189 | void KisCoordinatesConverter::setImage(KisImageWSP image) | ||
165 | { | 190 | { | ||
166 | m_d->image = image; | 191 | m_d->image = image; | ||
167 | recalculateTransformations(); | 192 | recalculateTransformations(); | ||
168 | } | 193 | } | ||
169 | 194 | | |||
170 | void KisCoordinatesConverter::setDocumentOffset(const QPoint& offset) | 195 | void KisCoordinatesConverter::setDocumentOffsetDevicePixel(const QPoint &offset) | ||
171 | { | 196 | { | ||
172 | QPointF diff = m_d->documentOffset - offset; | 197 | // int physicalOffsetX = offset.x() * m_d->devicePixelRatio; | ||
173 | 198 | // int physicalOffsetY = offset.y() * m_d->devicePixelRatio; | |||
174 | m_d->documentOffset = offset; | 199 | // // These adjusted positions are in logical pixel but is aligned in device | ||
175 | m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y()); | 200 | // // pixel space for pixel-perfect rendering. | ||
201 | // qreal pixelPerfectOffsetX = physicalOffsetX / m_d->devicePixelRatio; | ||||
202 | // qreal pixelPerfectOffsetY = physicalOffsetY / m_d->devicePixelRatio; | ||||
203 | // // FIXME: This is a temporary hack for fixing the canvas under fractional | ||||
204 | // // DPI scaling before a new coordinate system is introduced. | ||||
205 | // QPointF offsetAdjusted(pixelPerfectOffsetX, pixelPerfectOffsetY); | ||||
206 | // QPointF diff = m_d->documentOffset - offsetAdjusted; | ||||
207 | QPoint diff = m_d->documentOffsetDevicePixel - offset; | ||||
208 | | ||||
209 | m_d->documentOffsetDevicePixel = offset; | ||||
210 | m_d->flakeToWidget *= QTransform::fromTranslate(diff.x() / m_d->devicePixelRatio, diff.y() / m_d->devicePixelRatio); | ||||
211 | qDebug() << "offset:" << offset | ||||
212 | // << "offsetAdjusted:" << offsetAdjusted | ||||
213 | // << "documentOffset:" << m_d->documentOffset | ||||
214 | << "documentOffsetDevicePixel" << m_d->documentOffsetDevicePixel | ||||
215 | << "flakeToWidget:" << m_d->flakeToWidget; | ||||
176 | recalculateTransformations(); | 216 | recalculateTransformations(); | ||
177 | } | 217 | } | ||
178 | 218 | | |||
219 | //void KisCoordinatesConverter::setDocumentOffsetLogicalPixel(const QPoint &offset) | ||||
220 | //{ | ||||
221 | // int physicalOffsetX = static_cast<int>(offset.x() * m_d->devicePixelRatio); | ||||
222 | // int physicalOffsetY = static_cast<int>(offset.y() * m_d->devicePixelRatio); | ||||
223 | // setDocumentOffsetDevicePixel(QPoint(physicalOffsetX, physicalOffsetY)); | ||||
224 | //} | ||||
225 | | ||||
179 | QPoint KisCoordinatesConverter::documentOffset() const | 226 | QPoint KisCoordinatesConverter::documentOffset() const | ||
180 | { | 227 | { | ||
181 | return QPoint(int(m_d->documentOffset.x()), int(m_d->documentOffset.y())); | 228 | QPointF offset = documentOffsetLogicalPixel(); | ||
229 | return QPoint(int(offset.x()), int(offset.y())); | ||||
182 | } | 230 | } | ||
183 | 231 | | |||
184 | qreal KisCoordinatesConverter::rotationAngle() const | 232 | qreal KisCoordinatesConverter::rotationAngle() const | ||
185 | { | 233 | { | ||
186 | return m_d->rotationAngle; | 234 | return m_d->rotationAngle; | ||
187 | } | 235 | } | ||
188 | 236 | | |||
189 | void KisCoordinatesConverter::setZoom(qreal zoom) | 237 | void KisCoordinatesConverter::setZoom(qreal zoom) | ||
Show All 23 Lines | 257 | { | |||
213 | m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(),-center.y()); | 261 | m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(),-center.y()); | ||
214 | m_d->flakeToWidget *= rot; | 262 | m_d->flakeToWidget *= rot; | ||
215 | m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y()); | 263 | m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y()); | ||
216 | m_d->rotationAngle = std::fmod(m_d->rotationAngle + angle, 360.0); | 264 | m_d->rotationAngle = std::fmod(m_d->rotationAngle + angle, 360.0); | ||
217 | 265 | | |||
218 | correctOffsetToTransformation(); | 266 | correctOffsetToTransformation(); | ||
219 | recalculateTransformations(); | 267 | recalculateTransformations(); | ||
220 | 268 | | |||
221 | return m_d->documentOffset.toPoint(); | 269 | return documentOffsetLogicalPixel().toPoint(); | ||
222 | } | 270 | } | ||
223 | 271 | | |||
224 | QPoint KisCoordinatesConverter::mirror(QPointF center, bool mirrorXAxis, bool mirrorYAxis) | 272 | QPoint KisCoordinatesConverter::mirror(QPointF center, bool mirrorXAxis, bool mirrorYAxis) | ||
225 | { | 273 | { | ||
226 | bool keepOrientation = false; // XXX: Keep here for now, maybe some day we can restore the parameter again. | 274 | bool keepOrientation = false; // XXX: Keep here for now, maybe some day we can restore the parameter again. | ||
227 | 275 | | |||
228 | bool doXMirroring = m_d->isXAxisMirrored ^ mirrorXAxis; | 276 | bool doXMirroring = m_d->isXAxisMirrored ^ mirrorXAxis; | ||
229 | bool doYMirroring = m_d->isYAxisMirrored ^ mirrorYAxis; | 277 | bool doYMirroring = m_d->isYAxisMirrored ^ mirrorYAxis; | ||
Show All 24 Lines | |||||
254 | } | 302 | } | ||
255 | 303 | | |||
256 | m_d->isXAxisMirrored = mirrorXAxis; | 304 | m_d->isXAxisMirrored = mirrorXAxis; | ||
257 | m_d->isYAxisMirrored = mirrorYAxis; | 305 | m_d->isYAxisMirrored = mirrorYAxis; | ||
258 | 306 | | |||
259 | correctOffsetToTransformation(); | 307 | correctOffsetToTransformation(); | ||
260 | recalculateTransformations(); | 308 | recalculateTransformations(); | ||
261 | 309 | | |||
262 | return m_d->documentOffset.toPoint(); | 310 | return documentOffsetLogicalPixel().toPoint(); | ||
263 | } | 311 | } | ||
264 | 312 | | |||
265 | bool KisCoordinatesConverter::xAxisMirrored() const | 313 | bool KisCoordinatesConverter::xAxisMirrored() const | ||
266 | { | 314 | { | ||
267 | return m_d->isXAxisMirrored; | 315 | return m_d->isXAxisMirrored; | ||
268 | } | 316 | } | ||
269 | 317 | | |||
270 | bool KisCoordinatesConverter::yAxisMirrored() const | 318 | bool KisCoordinatesConverter::yAxisMirrored() const | ||
Show All 9 Lines | 324 | { | |||
280 | m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(), -center.y()); | 328 | m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(), -center.y()); | ||
281 | m_d->flakeToWidget *= rot; | 329 | m_d->flakeToWidget *= rot; | ||
282 | m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y()); | 330 | m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y()); | ||
283 | m_d->rotationAngle = 0.0; | 331 | m_d->rotationAngle = 0.0; | ||
284 | 332 | | |||
285 | correctOffsetToTransformation(); | 333 | correctOffsetToTransformation(); | ||
286 | recalculateTransformations(); | 334 | recalculateTransformations(); | ||
287 | 335 | | |||
288 | return m_d->documentOffset.toPoint(); | 336 | return documentOffsetLogicalPixel().toPoint(); | ||
289 | } | 337 | } | ||
290 | 338 | | |||
291 | QTransform KisCoordinatesConverter::imageToWidgetTransform() const{ | 339 | QTransform KisCoordinatesConverter::imageToWidgetTransform() const{ | ||
292 | return m_d->imageToDocument * m_d->documentToFlake * m_d->flakeToWidget; | 340 | return m_d->imageToDocument * m_d->documentToFlake * m_d->flakeToWidget; | ||
293 | } | 341 | } | ||
294 | 342 | | |||
295 | QTransform KisCoordinatesConverter::imageToDocumentTransform() const { | 343 | QTransform KisCoordinatesConverter::imageToDocumentTransform() const { | ||
296 | return m_d->imageToDocument; | 344 | return m_d->imageToDocument; | ||
▲ Show 20 Lines • Show All 113 Lines • ▼ Show 20 Line(s) | 454 | { | |||
410 | imageScale(&scaleX, &scaleY); | 458 | imageScale(&scaleX, &scaleY); | ||
411 | QSize imageSize = m_d->image->size(); | 459 | QSize imageSize = m_d->image->size(); | ||
412 | 460 | | |||
413 | return QSizeF(imageSize.width() * scaleX, imageSize.height() * scaleY); | 461 | return QSizeF(imageSize.width() * scaleX, imageSize.height() * scaleY); | ||
414 | } | 462 | } | ||
415 | 463 | | |||
416 | QRectF KisCoordinatesConverter::widgetRectInFlakePixels() const | 464 | QRectF KisCoordinatesConverter::widgetRectInFlakePixels() const | ||
417 | { | 465 | { | ||
418 | return widgetToFlake(QRectF(QPoint(0,0), m_d->canvasWidgetSize)); | 466 | return widgetToFlake(QRectF(QPoint(0,0), canvasWidgetLogicalPixelSize())); | ||
419 | } | 467 | } | ||
420 | 468 | | |||
421 | QRectF KisCoordinatesConverter::widgetRectInImagePixels() const | 469 | QRectF KisCoordinatesConverter::widgetRectInImagePixels() const | ||
422 | { | 470 | { | ||
423 | return widgetToImage(QRectF(QPoint(0,0), m_d->canvasWidgetSize)); | 471 | return widgetToImage(QRectF(QPoint(0,0), canvasWidgetLogicalPixelSize())); | ||
424 | } | 472 | } | ||
425 | 473 | | |||
426 | QPointF KisCoordinatesConverter::flakeCenterPoint() const | 474 | QPointF KisCoordinatesConverter::flakeCenterPoint() const | ||
427 | { | 475 | { | ||
428 | QRectF widgetRect = widgetRectInFlakePixels(); | 476 | QRectF widgetRect = widgetRectInFlakePixels(); | ||
429 | return QPointF(widgetRect.left() + widgetRect.width() / 2, | 477 | return QPointF(widgetRect.left() + widgetRect.width() / 2, | ||
430 | widgetRect.top() + widgetRect.height() / 2); | 478 | widgetRect.top() + widgetRect.height() / 2); | ||
431 | } | 479 | } | ||
432 | 480 | | |||
433 | QPointF KisCoordinatesConverter::widgetCenterPoint() const | 481 | QPointF KisCoordinatesConverter::widgetCenterPoint() const | ||
434 | { | 482 | { | ||
435 | return QPointF(m_d->canvasWidgetSize.width() / 2.0, m_d->canvasWidgetSize.height() / 2.0); | 483 | QSizeF size = canvasWidgetLogicalPixelSize(); | ||
484 | return QPointF(size.width() / 2.0, size.height() / 2.0); | ||||
436 | } | 485 | } | ||
437 | 486 | | |||
438 | void KisCoordinatesConverter::imageScale(qreal *scaleX, qreal *scaleY) const | 487 | void KisCoordinatesConverter::imageScale(qreal *scaleX, qreal *scaleY) const | ||
439 | { | 488 | { | ||
440 | if(!m_d->image) { | 489 | if(!m_d->image) { | ||
441 | *scaleX = 1.0; | 490 | *scaleX = 1.0; | ||
442 | *scaleY = 1.0; | 491 | *scaleY = 1.0; | ||
443 | return; | 492 | return; | ||
Show All 21 Lines |
I have a suspicion that the original author chose QSizeF and QPointF for performance - that is, converting between integers and floating point adds some overhead? It was changed on commit c93a6dcc61b96d0387ae0f60f0edb8339f5f18a0.
Looks like premature optimization to me...