OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chromoting; | 5 package org.chromium.chromoting; |
6 | 6 |
7 import android.graphics.Matrix; | 7 import android.graphics.Matrix; |
8 import android.graphics.PointF; | 8 import android.graphics.PointF; |
9 import android.graphics.Rect; | 9 import android.graphics.Rect; |
10 import android.graphics.RectF; | 10 import android.graphics.RectF; |
11 | 11 |
12 /** | 12 /** |
13 * This class is responsible for transforming the desktop image matrix. | 13 * This class is responsible for transforming the desktop image matrix. |
14 */ | 14 */ |
15 public class DesktopCanvas { | 15 public class DesktopCanvas { |
16 /** | 16 /** |
17 * Maximum allowed zoom level - see {@link #repositionImageWithZoom()}. | 17 * Maximum allowed zoom level - see {@link #repositionImageWithZoom()}. |
18 */ | 18 */ |
19 private static final float MAX_ZOOM_FACTOR = 100.0f; | 19 private static final float MAX_ZOOM_FACTOR = 100.0f; |
20 | 20 |
21 private final RenderStub mRenderStub; | 21 private final RenderStub mRenderStub; |
22 private final RenderData mRenderData; | 22 private final RenderData mRenderData; |
23 | 23 |
24 /** | 24 /** |
| 25 * Represents the actual center of the viewport. This value needs to be a p
air of floats so the |
| 26 * desktop image can be positioned with sub-pixel accuracy for smoother pann
ing animations at |
| 27 * high zoom levels. |
| 28 */ |
| 29 private PointF mViewportPosition = new PointF(); |
| 30 |
| 31 /** |
25 * Represents the desired center of the viewport. This value may not repres
ent the actual | 32 * Represents the desired center of the viewport. This value may not repres
ent the actual |
26 * center of the viewport as adjustments are made to ensure as much of the d
esktop is visible as | 33 * center of the viewport as adjustments are made to ensure as much of the d
esktop is visible as |
27 * possible. This value needs to be a pair of floats so the desktop image c
an be positioned | 34 * possible. This value needs to be a pair of floats so the desktop image c
an be positioned |
28 * with sub-pixel accuracy for smoother panning animations at high zoom leve
ls. | 35 * with sub-pixel accuracy for smoother panning animations at high zoom leve
ls. |
29 */ | 36 */ |
30 private PointF mViewportPosition = new PointF(); | 37 private PointF mCursorPosition = new PointF(); |
31 | 38 |
32 /** | 39 /** |
33 * Represents the amount of space, in pixels, used by System UI. | 40 * Represents the amount of space, in pixels, used by System UI. |
34 */ | 41 */ |
35 private Rect mSystemUiOffsetPixels = new Rect(); | 42 private Rect mSystemUiOffsetPixels = new Rect(); |
36 | 43 |
37 public DesktopCanvas(RenderStub renderStub, RenderData renderData) { | 44 public DesktopCanvas(RenderStub renderStub, RenderData renderData) { |
38 mRenderStub = renderStub; | 45 mRenderStub = renderStub; |
39 mRenderData = renderData; | 46 mRenderData = renderData; |
40 } | 47 } |
41 | 48 |
42 /** | 49 /** |
43 * Shifts the viewport by the passed in deltas (in image coordinates). | 50 * Sets the desired center position of the viewport (a.k.a. the cursor posit
ion) and ensures |
44 * | 51 * the viewport is updated to include the cursor within it. |
45 * @param useScreenCenter Determines whether to use the desired viewport pos
ition or the actual | |
46 * center of the screen for positioning. | |
47 * @param deltaX The distance (in image coordinates) to move the viewport al
ong the x-axis. | |
48 * @param deltaY The distance (in image coordinates) to move the viewport al
ong the y-axis. | |
49 * @return A point representing the new center position of the viewport. | |
50 */ | |
51 public PointF moveViewportCenter(boolean useScreenCenter, float deltaX, floa
t deltaY) { | |
52 PointF viewportCenter; | |
53 if (useScreenCenter) { | |
54 viewportCenter = getTrueViewportCenter(); | |
55 } else { | |
56 viewportCenter = new PointF(mViewportPosition.x, mViewportPosition.y
); | |
57 } | |
58 viewportCenter.offset(deltaX, deltaY); | |
59 | |
60 RectF bounds = new RectF(0, 0, mRenderData.imageWidth, mRenderData.image
Height); | |
61 | |
62 if (viewportCenter.x < bounds.left) { | |
63 viewportCenter.x = bounds.left; | |
64 } else if (viewportCenter.x > bounds.right) { | |
65 viewportCenter.x = bounds.right; | |
66 } | |
67 | |
68 if (viewportCenter.y < bounds.top) { | |
69 viewportCenter.y = bounds.top; | |
70 } else if (viewportCenter.y > bounds.bottom) { | |
71 viewportCenter.y = bounds.bottom; | |
72 } | |
73 | |
74 mViewportPosition.set(viewportCenter); | |
75 | |
76 return viewportCenter; | |
77 } | |
78 | |
79 /** | |
80 * Sets the desired center position of the viewport. | |
81 * | 52 * |
82 * @param newX The new x coordinate value for the desired center position. | 53 * @param newX The new x coordinate value for the desired center position. |
83 * @param newY The new y coordinate value for the desired center position. | 54 * @param newY The new y coordinate value for the desired center position. |
84 */ | 55 */ |
85 public void setViewportPosition(float newX, float newY) { | 56 public void setCursorPosition(float newX, float newY) { |
86 mViewportPosition.set(newX, newY); | 57 // First set the cursor position since its potential values are a supers
et of the viewport. |
| 58 mCursorPosition.set(newX, newY); |
| 59 constrainPointToBounds(mCursorPosition, getImageBounds()); |
| 60 |
| 61 // Now set the viewport position based on the cursor. |
| 62 mViewportPosition.set(mCursorPosition); |
| 63 constrainPointToBounds(mViewportPosition, getViewportBounds()); |
| 64 |
| 65 repositionImage(); |
87 } | 66 } |
88 | 67 |
89 /** | 68 /** |
90 * Returns the true center position of the viewport (in image coordinates). | 69 * Shifts the viewport by the passed in values (in image coordinates). |
91 * | 70 * |
92 * @return A point representing the true center position of the viewport. | 71 * @param deltaX The distance (in image coordinates) to move the viewport al
ong the x-axis. |
| 72 * @param deltaY The distance (in image coordinates) to move the viewport al
ong the y-axis. |
93 */ | 73 */ |
94 private PointF getTrueViewportCenter() { | 74 public void moveViewportCenter(float deltaX, float deltaY) { |
95 // Find the center point of the viewport on the screen. | 75 // Offset and adjust the viewport center position to fit the screen. |
96 float[] viewportPosition = { | 76 mViewportPosition.offset(deltaX, deltaY); |
97 ((float) mRenderData.screenWidth / 2), ((float) mRenderData.scre
enHeight / 2)}; | 77 constrainPointToBounds(mViewportPosition, getViewportBounds()); |
98 | 78 |
99 // Convert the screen position to an image position. | 79 // We don't need to constrain the cursor position as the viewport positi
on is always within |
100 Matrix screenToImage = new Matrix(); | 80 // the bounds of the potential cursor positions. |
101 mRenderData.transform.invert(screenToImage); | 81 mCursorPosition.set(mViewportPosition); |
102 screenToImage.mapPoints(viewportPosition); | 82 |
103 return new PointF(viewportPosition[0], viewportPosition[1]); | 83 repositionImage(); |
104 } | 84 } |
105 | 85 |
106 /** | 86 /** |
| 87 * Shifts the cursor by the passed in values (in image coordinates) and adju
sts the viewport. |
| 88 * |
| 89 * @param deltaX The distance (in image coordinates) to move the cursor alon
g the x-axis. |
| 90 * @param deltaY The distance (in image coordinates) to move the cursor alon
g the y-axis. |
| 91 * @return A point representing the new cursor position. |
| 92 */ |
| 93 public PointF moveCursorPosition(float deltaX, float deltaY) { |
| 94 setCursorPosition(mCursorPosition.x + deltaX, mCursorPosition.y + deltaY
); |
| 95 return new PointF(mCursorPosition.x, mCursorPosition.y); |
| 96 } |
| 97 |
| 98 /** |
107 * Sets the offset values used to calculate the space used by System UI. | 99 * Sets the offset values used to calculate the space used by System UI. |
108 * | 100 * |
109 * @param left The space used by System UI on the left edge of the screen. | 101 * @param left The space used by System UI on the left edge of the screen. |
110 * @param top The space used by System UI on the top edge of the screen. | 102 * @param top The space used by System UI on the top edge of the screen. |
111 * @param right The space used by System UI on the right edge of the screen. | 103 * @param right The space used by System UI on the right edge of the screen. |
112 * @param bottom The space used by System UI on the bottom edge of the scree
n. | 104 * @param bottom The space used by System UI on the bottom edge of the scree
n. |
113 */ | 105 */ |
114 public void setSystemUiOffsetValues(int left, int top, int right, int bottom
) { | 106 public void setSystemUiOffsetValues(int left, int top, int right, int bottom
) { |
115 mSystemUiOffsetPixels.set(left, top, right, bottom); | 107 mSystemUiOffsetPixels.set(left, top, right, bottom); |
116 } | 108 } |
117 | 109 |
118 /** Called to indicate that no System UI is visible. */ | 110 /** Called to indicate that no System UI is visible. */ |
119 public void clearSystemUiOffsets() { | 111 public void clearSystemUiOffsets() { |
120 mSystemUiOffsetPixels.setEmpty(); | 112 mSystemUiOffsetPixels.setEmpty(); |
121 } | 113 } |
122 | 114 |
123 /** Repositions the image by zooming it such that the image is displayed wit
hout borders. */ | 115 /** Resizes the image by zooming it such that the image is displayed without
borders. */ |
124 public void resizeImageToFitScreen() { | 116 public void resizeImageToFitScreen() { |
125 // Protect against being called before the image has been initialized. | 117 // Protect against being called before the image has been initialized. |
126 if (mRenderData.imageWidth == 0 || mRenderData.imageHeight == 0) { | 118 if (mRenderData.imageWidth == 0 || mRenderData.imageHeight == 0) { |
127 return; | 119 return; |
128 } | 120 } |
129 | 121 |
130 float widthRatio = (float) mRenderData.screenWidth / mRenderData.imageWi
dth; | 122 float widthRatio = (float) mRenderData.screenWidth / mRenderData.imageWi
dth; |
131 float heightRatio = (float) mRenderData.screenHeight / mRenderData.image
Height; | 123 float heightRatio = (float) mRenderData.screenHeight / mRenderData.image
Height; |
132 float screenToImageScale = Math.max(widthRatio, heightRatio); | 124 float screenToImageScale = Math.max(widthRatio, heightRatio); |
133 | 125 |
134 // If the image is smaller than the screen in either dimension, then we
want to scale it | 126 // If the image is smaller than the screen in either dimension, then we
want to scale it |
135 // up to fit both and fill the screen with the image of the remote deskt
op. | 127 // up to fit both and fill the screen with the image of the remote deskt
op. |
136 if (screenToImageScale > 1.0f) { | 128 if (screenToImageScale > 1.0f) { |
137 mRenderData.transform.setScale(screenToImageScale, screenToImageScal
e); | 129 mRenderData.transform.setScale(screenToImageScale, screenToImageScal
e); |
138 } | 130 } |
139 | |
140 repositionImage(false); | |
141 } | 131 } |
142 | 132 |
143 /** | 133 /** |
144 * Repositions the image by translating it (without affecting the zoom level
). | |
145 * | |
146 * @param centerViewport Determines whether the viewport will be translated
to the desired | |
147 * center position before being adjusted to fit the sc
reen boundaries. | |
148 */ | |
149 public void repositionImage(boolean centerViewport) { | |
150 // The goal of the code below is to position the viewport as close to th
e desired center | |
151 // position as possible whilst keeping as much of the desktop in view as
possible. | |
152 // To achieve these goals, we first position the desktop image at the de
sired center | |
153 // point and then re-position it to maximize the viewable area. | |
154 if (centerViewport) { | |
155 // Map the current viewport position to screen coordinates. | |
156 float[] viewportPosition = {mViewportPosition.x, mViewportPosition.y
}; | |
157 mRenderData.transform.mapPoints(viewportPosition); | |
158 | |
159 float viewportTransX = ((float) mRenderData.screenWidth / 2) - viewp
ortPosition[0]; | |
160 float viewportTransY = ((float) mRenderData.screenHeight / 2) - view
portPosition[1]; | |
161 | |
162 // Translate so the viewport is displayed in the middle of the scree
n. | |
163 mRenderData.transform.postTranslate(viewportTransX, viewportTransY); | |
164 } | |
165 | |
166 // Get the coordinates of the desktop rectangle (top-left/bottom-right c
orners) in | |
167 // screen coordinates. Order is: left, top, right, bottom. | |
168 RectF rectScreen = new RectF(0, 0, mRenderData.imageWidth, mRenderData.i
mageHeight); | |
169 mRenderData.transform.mapRect(rectScreen); | |
170 | |
171 float leftDelta = rectScreen.left; | |
172 float rightDelta = | |
173 rectScreen.right - mRenderData.screenWidth + mSystemUiOffsetPixe
ls.right; | |
174 float topDelta = rectScreen.top; | |
175 float bottomDelta = | |
176 rectScreen.bottom - mRenderData.screenHeight + mSystemUiOffsetPi
xels.bottom; | |
177 float xAdjust = 0; | |
178 float yAdjust = 0; | |
179 | |
180 if (rectScreen.right - rectScreen.left < mRenderData.screenWidth) { | |
181 // Image is narrower than the screen, so center it. | |
182 xAdjust = -(rightDelta + leftDelta) / 2; | |
183 } else if (leftDelta > 0 && rightDelta > 0) { | |
184 // Panning the image left will show more of it. | |
185 xAdjust = -Math.min(leftDelta, rightDelta); | |
186 } else if (leftDelta < 0 && rightDelta < 0) { | |
187 // Pan the image right. | |
188 xAdjust = Math.min(-leftDelta, -rightDelta); | |
189 } | |
190 | |
191 // Apply similar logic for yAdjust. | |
192 if (rectScreen.bottom - rectScreen.top < mRenderData.screenHeight) { | |
193 yAdjust = -(bottomDelta + topDelta) / 2; | |
194 } else if (topDelta > 0 && bottomDelta > 0) { | |
195 yAdjust = -Math.min(topDelta, bottomDelta); | |
196 } else if (topDelta < 0 && bottomDelta < 0) { | |
197 yAdjust = Math.min(-topDelta, -bottomDelta); | |
198 } | |
199 | |
200 mRenderData.transform.postTranslate(xAdjust, yAdjust); | |
201 | |
202 mRenderStub.setTransformation(mRenderData.transform); | |
203 } | |
204 | |
205 /** | |
206 * Repositions the image by translating and zooming it, to keep the zoom lev
el within sensible | 134 * Repositions the image by translating and zooming it, to keep the zoom lev
el within sensible |
207 * limits. The minimum zoom level is chosen to avoid black space around all
4 sides. The | 135 * limits. The minimum zoom level is chosen to avoid black space around all
4 sides. The |
208 * maximum zoom level is set arbitrarily, so that the user can zoom out agai
n in a reasonable | 136 * maximum zoom level is set arbitrarily, so that the user can zoom out agai
n in a reasonable |
209 * time, and to prevent arithmetic overflow problems from displaying the ima
ge. | 137 * time, and to prevent arithmetic overflow problems from displaying the ima
ge. |
210 * | 138 * |
211 * @param centerViewport Determines whether the viewport will be translated
to the desired | 139 * @param scaleFactor The factor used to zoom the canvas in or out. |
| 140 * @param px The center x coordinate for the scale action. |
| 141 * @param py The center y coordinate for the scale action. |
| 142 * @param centerOnCursor Determines whether the viewport will be translated
to the desired |
212 * center position before being adjusted to fit the sc
reen boundaries. | 143 * center position before being adjusted to fit the sc
reen boundaries. |
213 */ | 144 */ |
214 public void repositionImageWithZoom(boolean centerViewport) { | 145 public void scaleAndRepositionImage( |
| 146 float scaleFactor, float px, float py, boolean centerOnCursor) { |
215 // Avoid division by zero in case this gets called before the image size
is initialized. | 147 // Avoid division by zero in case this gets called before the image size
is initialized. |
216 if (mRenderData.imageWidth == 0 || mRenderData.imageHeight == 0) { | 148 if (mRenderData.imageWidth == 0 || mRenderData.imageHeight == 0) { |
217 return; | 149 return; |
218 } | 150 } |
219 | 151 |
| 152 mRenderData.transform.postScale(scaleFactor, scaleFactor, px, py); |
| 153 |
220 // Zoom out if the zoom level is too high. | 154 // Zoom out if the zoom level is too high. |
221 float currentZoomLevel = mRenderData.transform.mapRadius(1.0f); | 155 float currentZoomLevel = mRenderData.transform.mapRadius(1.0f); |
222 if (currentZoomLevel > MAX_ZOOM_FACTOR) { | 156 if (currentZoomLevel > MAX_ZOOM_FACTOR) { |
223 mRenderData.transform.setScale(MAX_ZOOM_FACTOR, MAX_ZOOM_FACTOR); | 157 mRenderData.transform.setScale(MAX_ZOOM_FACTOR, MAX_ZOOM_FACTOR); |
224 } | 158 } |
225 | 159 |
226 // Get image size scaled to screen coordinates. | 160 // Get image size scaled to screen coordinates. |
227 float[] imageSize = {mRenderData.imageWidth, mRenderData.imageHeight}; | 161 float[] imageSize = {mRenderData.imageWidth, mRenderData.imageHeight}; |
228 mRenderData.transform.mapVectors(imageSize); | 162 mRenderData.transform.mapVectors(imageSize); |
229 | 163 |
230 if (imageSize[0] < mRenderData.screenWidth && imageSize[1] < mRenderData
.screenHeight) { | 164 if (imageSize[0] < mRenderData.screenWidth && imageSize[1] < mRenderData
.screenHeight) { |
231 // Displayed image is too small in both directions, so apply the min
imum zoom | 165 // Displayed image is too small in both directions, so apply the min
imum zoom |
232 // level needed to fit either the width or height. | 166 // level needed to fit either the width or height. |
233 float scale = Math.min((float) mRenderData.screenWidth / mRenderData
.imageWidth, | 167 float scale = Math.min((float) mRenderData.screenWidth / mRenderData
.imageWidth, |
234 (float) mRenderData.screenHeight / mRenderDat
a.imageHeight); | 168 (float) mRenderData.screenHeight / mRenderDat
a.imageHeight); |
235 mRenderData.transform.setScale(scale, scale); | 169 mRenderData.transform.setScale(scale, scale); |
236 } | 170 } |
237 | 171 |
238 repositionImage(centerViewport); | 172 if (centerOnCursor) { |
| 173 setCursorPosition(mCursorPosition.x, mCursorPosition.y); |
| 174 } else { |
| 175 // Find the new screen center (it was probably changed during the zo
om operation) and |
| 176 // update the viewport and cursor. |
| 177 float[] mappedPoints = { |
| 178 ((float) mRenderData.screenWidth / 2), ((float) mRenderData.
screenHeight / 2)}; |
| 179 Matrix screenToImage = new Matrix(); |
| 180 mRenderData.transform.invert(screenToImage); |
| 181 screenToImage.mapPoints(mappedPoints); |
| 182 // The cursor is mapped to the center of the viewport in this case. |
| 183 setCursorPosition(mappedPoints[0], mappedPoints[1]); |
| 184 } |
| 185 } |
| 186 |
| 187 /** |
| 188 * Repositions the image by translating it (without affecting the zoom level
). |
| 189 */ |
| 190 private void repositionImage() { |
| 191 // Map the current viewport position to screen coordinates and adjust th
e image position. |
| 192 float[] viewportPosition = {mViewportPosition.x, mViewportPosition.y}; |
| 193 mRenderData.transform.mapPoints(viewportPosition); |
| 194 |
| 195 float viewportTransX = ((float) mRenderData.screenWidth / 2) - viewportP
osition[0]; |
| 196 float viewportTransY = ((float) mRenderData.screenHeight / 2) - viewport
Position[1]; |
| 197 |
| 198 // Translate the image so the viewport center is displayed in the middle
of the screen. |
| 199 mRenderData.transform.postTranslate(viewportTransX, viewportTransY); |
| 200 |
| 201 mRenderStub.setTransformation(mRenderData.transform); |
| 202 } |
| 203 |
| 204 /** |
| 205 * Updates the given point such that it refers to a coordinate within the bo
unds provided. |
| 206 * |
| 207 * @param point The point to adjust, the original object is modified. |
| 208 * @param bounds The bounds used to constrain the point. |
| 209 */ |
| 210 private void constrainPointToBounds(PointF point, RectF bounds) { |
| 211 if (point.x < bounds.left) { |
| 212 point.x = bounds.left; |
| 213 } else if (point.x > bounds.right) { |
| 214 point.x = bounds.right; |
| 215 } |
| 216 |
| 217 if (point.y < bounds.top) { |
| 218 point.y = bounds.top; |
| 219 } else if (point.y > bounds.bottom) { |
| 220 point.y = bounds.bottom; |
| 221 } |
| 222 } |
| 223 |
| 224 /** Returns a region which defines the set of valid cursor values in image s
pace. */ |
| 225 private RectF getImageBounds() { |
| 226 return new RectF(0, 0, mRenderData.imageWidth, mRenderData.imageHeight); |
| 227 } |
| 228 |
| 229 /** Returns a region which defines the set of valid viewport center values i
n image space. */ |
| 230 private RectF getViewportBounds() { |
| 231 float[] screenVectors = {(float) mRenderData.screenWidth, (float) mRende
rData.screenHeight}; |
| 232 Matrix screenToImage = new Matrix(); |
| 233 mRenderData.transform.invert(screenToImage); |
| 234 screenToImage.mapVectors(screenVectors); |
| 235 |
| 236 float xAdjust = 0.0f; |
| 237 if (mRenderData.imageWidth < screenVectors[0]) { |
| 238 // Image is narrower than the screen, so adjust the bounds to center
it. |
| 239 xAdjust = (screenVectors[0] - mRenderData.imageWidth) / 2.0f; |
| 240 } |
| 241 |
| 242 float yAdjust = 0.0f; |
| 243 if (mRenderData.imageHeight < screenVectors[1]) { |
| 244 // Image is shorter than the screen, so adjust the bounds to center
it. |
| 245 yAdjust = (screenVectors[1] - mRenderData.imageHeight) / 2.0f; |
| 246 } |
| 247 |
| 248 // screenCenter values are 1/2 of a particular screen dimension mapped t
o image space. |
| 249 float screenCenterX = screenVectors[0] / 2.0f; |
| 250 float screenCenterY = screenVectors[1] / 2.0f; |
| 251 return new RectF(screenCenterX - xAdjust, screenCenterY - yAdjust, |
| 252 mRenderData.imageWidth - screenCenterX + xAdjust, |
| 253 mRenderData.imageHeight - screenCenterY + yAdjust); |
239 } | 254 } |
240 } | 255 } |
OLD | NEW |