OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "ash/touch/touch_transformer_controller.h" | |
6 | |
7 #include "ash/display/window_tree_host_manager.h" | |
8 #include "ash/host/ash_window_tree_host.h" | |
9 #include "ash/root_window_controller.h" | |
10 #include "ash/shell.h" | |
11 #include "third_party/skia/include/core/SkMatrix44.h" | |
12 #include "ui/aura/window_tree_host.h" | |
13 #include "ui/display/display_layout.h" | |
14 #include "ui/display/manager/chromeos/display_configurator.h" | |
15 #include "ui/display/manager/display_manager.h" | |
16 #include "ui/display/types/display_constants.h" | |
17 #include "ui/display/types/display_snapshot.h" | |
18 #include "ui/events/devices/device_data_manager.h" | |
19 | |
20 namespace ash { | |
21 | |
22 namespace { | |
23 | |
24 display::DisplayManager* GetDisplayManager() { | |
25 return Shell::GetInstance()->display_manager(); | |
26 } | |
27 | |
28 ui::TouchscreenDevice FindTouchscreenById(int id) { | |
29 const std::vector<ui::TouchscreenDevice>& touchscreens = | |
30 ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices(); | |
31 for (const auto& touchscreen : touchscreens) { | |
32 if (touchscreen.id == id) | |
33 return touchscreen; | |
34 } | |
35 | |
36 return ui::TouchscreenDevice(); | |
37 } | |
38 | |
39 // Given an array of touch point and display point pairs, this function computes | |
40 // and returns the constants(defined below) using a least fit algorithm. | |
41 // If (xt, yt) is a touch point then its corresponding (xd, yd) would be defined | |
42 // by the following 2 equations: | |
43 // xd = xt * A + yt * B + C | |
44 // yd = xt * D + yt * E + F | |
45 // This function computes A, B, C, D, E and F and sets |ctm| with the calibrated | |
46 // transform matrix. In case the computation fails, the function will return | |
47 // false. | |
48 // See http://crbug.com/672293 | |
49 bool GetCalibratedTransform( | |
50 std::array<std::pair<gfx::Point, gfx::Point>, 4> touch_point_pairs, | |
51 const gfx::Transform& pre_calibration_tm, | |
52 gfx::Transform* ctm) { | |
53 // Transform the display points before solving the equation. | |
54 // If the calibration was performed at a resolution that is 0.5 times the | |
55 // current resolution, then the display points (x, y) for a given touch point | |
56 // now represents a display point at (2 * x, 2 * y). This and other kinds of | |
57 // similar tranforms can be applied using |pre_calibration_tm|. | |
58 for (int row = 0; row < 4; row++) | |
59 pre_calibration_tm.TransformPoint(&touch_point_pairs[row].first); | |
60 | |
61 // Vector of the X-coordinate of display points corresponding to each of the | |
62 // touch points. | |
63 SkVector4 display_points_x( | |
64 touch_point_pairs[0].first.x(), touch_point_pairs[1].first.x(), | |
65 touch_point_pairs[2].first.x(), touch_point_pairs[3].first.x()); | |
66 // Vector of the Y-coordinate of display points corresponding to each of the | |
67 // touch points. | |
68 SkVector4 display_points_y( | |
69 touch_point_pairs[0].first.y(), touch_point_pairs[1].first.y(), | |
70 touch_point_pairs[2].first.y(), touch_point_pairs[3].first.y()); | |
71 | |
72 // Initialize |touch_point_matrix| | |
73 // If {(xt_1, yt_1), (xt_2, yt_2), (xt_3, yt_3)....} are a set of touch points | |
74 // received during calibration, then the |touch_point_matrix| would be defined | |
75 // as: | |
76 // |xt_1 yt_1 1 0| | |
77 // |xt_2 yt_2 1 0| | |
78 // |xt_3 yt_3 1 0| | |
79 // |xt_4 yt_4 1 0| | |
80 SkMatrix44 touch_point_matrix; | |
81 for (int row = 0; row < 4; row++) { | |
82 touch_point_matrix.set(row, 0, touch_point_pairs[row].second.x()); | |
83 touch_point_matrix.set(row, 1, touch_point_pairs[row].second.y()); | |
84 touch_point_matrix.set(row, 2, 1); | |
85 touch_point_matrix.set(row, 3, 0); | |
86 } | |
87 SkMatrix44 touch_point_matrix_transpose(touch_point_matrix); | |
88 touch_point_matrix_transpose.transpose(); | |
89 | |
90 SkMatrix44 product_matrix = touch_point_matrix_transpose * touch_point_matrix; | |
91 | |
92 // Set (3, 3) = 1 so that |determinent| of the matrix is != 0 and the inverse | |
93 // can be calculated. | |
94 product_matrix.set(3, 3, 1); | |
95 | |
96 SkMatrix44 product_matrix_inverse; | |
97 | |
98 // NOTE: If the determinent is zero then the inverse cannot be computed. The | |
99 // only solution is to restart touch calibration and get new points from user. | |
100 if (!product_matrix.invert(&product_matrix_inverse)) { | |
101 NOTREACHED() << "Touch Calibration failed. Determinent is zero."; | |
102 return false; | |
103 } | |
104 | |
105 product_matrix_inverse.set(3, 3, 0); | |
106 | |
107 product_matrix = product_matrix_inverse * touch_point_matrix_transpose; | |
108 | |
109 // Constants [A, B, C, 0] used to calibrate the x-coordinate of touch input. | |
110 // x_new = x_old * A + y_old * B + C; | |
111 SkVector4 x_constants = product_matrix * display_points_x; | |
112 // Constants [D, E, F, 0] used to calibrate the y-coordinate of touch input. | |
113 // y_new = x_old * D + y_old * E + F; | |
114 SkVector4 y_constants = product_matrix * display_points_y; | |
115 | |
116 // Create a transform matrix using the touch calibration data. | |
117 ctm->ConcatTransform(gfx::Transform( | |
118 x_constants.fData[0], x_constants.fData[1], 0, x_constants.fData[2], | |
119 y_constants.fData[0], y_constants.fData[1], 0, y_constants.fData[2], 0, 0, | |
120 1, 0, 0, 0, 0, 1)); | |
121 return true; | |
122 } | |
123 | |
124 // Returns an uncalibrated touch transform. | |
125 gfx::Transform GetUncalibratedTransform( | |
126 const gfx::Transform& tm, | |
127 const display::ManagedDisplayInfo& display, | |
128 const display::ManagedDisplayInfo& touch_display, | |
129 const gfx::SizeF& touch_area, | |
130 const gfx::SizeF& touch_native_size) { | |
131 gfx::SizeF current_size(display.bounds_in_native().size()); | |
132 gfx::Transform ctm(tm); | |
133 // Take care of panel fitting only if supported. Panel fitting is emulated | |
134 // in software mirroring mode (display != touch_display). | |
135 // If panel fitting is enabled then the aspect ratio is preserved and the | |
136 // display is scaled acordingly. In this case blank regions would be present | |
137 // in order to center the displayed area. | |
138 if (display.is_aspect_preserving_scaling() || | |
139 display.id() != touch_display.id()) { | |
140 float touch_calib_ar = | |
141 touch_native_size.width() / touch_native_size.height(); | |
142 float current_ar = current_size.width() / current_size.height(); | |
143 | |
144 if (current_ar > touch_calib_ar) { // Letterboxing | |
145 ctm.Translate( | |
146 0, (1 - current_ar / touch_calib_ar) * 0.5 * current_size.height()); | |
147 ctm.Scale(1, current_ar / touch_calib_ar); | |
148 } else if (touch_calib_ar > current_ar) { // Pillarboxing | |
149 ctm.Translate( | |
150 (1 - touch_calib_ar / current_ar) * 0.5 * current_size.width(), 0); | |
151 ctm.Scale(touch_calib_ar / current_ar, 1); | |
152 } | |
153 } | |
154 // Take care of scaling between touchscreen area and display resolution. | |
155 ctm.Scale(current_size.width() / touch_area.width(), | |
156 current_size.height() / touch_area.height()); | |
157 return ctm; | |
158 } | |
159 | |
160 } // namespace | |
161 | |
162 // This is to compute the scale ratio for the TouchEvent's radius. The | |
163 // configured resolution of the display is not always the same as the touch | |
164 // screen's reporting resolution, e.g. the display could be set as | |
165 // 1920x1080 while the touchscreen is reporting touch position range at | |
166 // 32767x32767. Touch radius is reported in the units the same as touch position | |
167 // so we need to scale the touch radius to be compatible with the display's | |
168 // resolution. We compute the scale as | |
169 // sqrt of (display_area / touchscreen_area) | |
170 double TouchTransformerController::GetTouchResolutionScale( | |
171 const display::ManagedDisplayInfo& touch_display, | |
172 const ui::TouchscreenDevice& touch_device) const { | |
173 if (touch_device.id == ui::InputDevice::kInvalidId || | |
174 touch_device.size.IsEmpty() || | |
175 touch_display.bounds_in_native().size().IsEmpty()) | |
176 return 1.0; | |
177 | |
178 double display_area = touch_display.bounds_in_native().size().GetArea(); | |
179 double touch_area = touch_device.size.GetArea(); | |
180 double ratio = std::sqrt(display_area / touch_area); | |
181 | |
182 VLOG(2) << "Display size: " | |
183 << touch_display.bounds_in_native().size().ToString() | |
184 << ", Touchscreen size: " << touch_device.size.ToString() | |
185 << ", Touch radius scale ratio: " << ratio; | |
186 return ratio; | |
187 } | |
188 | |
189 gfx::Transform TouchTransformerController::GetTouchTransform( | |
190 const display::ManagedDisplayInfo& display, | |
191 const display::ManagedDisplayInfo& touch_display, | |
192 const ui::TouchscreenDevice& touchscreen, | |
193 const gfx::Size& framebuffer_size) const { | |
194 auto current_size = gfx::SizeF(display.bounds_in_native().size()); | |
195 auto touch_native_size = gfx::SizeF(touch_display.GetNativeModeSize()); | |
196 #if defined(USE_OZONE) | |
197 auto touch_area = gfx::SizeF(touchscreen.size); | |
198 #elif defined(USE_X11) | |
199 // On X11 touches are reported in the framebuffer coordinate space. | |
200 auto touch_area = gfx::SizeF(framebuffer_size); | |
201 #endif | |
202 | |
203 gfx::Transform ctm; | |
204 | |
205 if (current_size.IsEmpty() || touch_native_size.IsEmpty() || | |
206 touch_area.IsEmpty() || touchscreen.id == ui::InputDevice::kInvalidId) | |
207 return ctm; | |
208 | |
209 #if defined(USE_OZONE) | |
210 // Translate the touch so that it falls within the display bounds. This | |
211 // should not be performed if the displays are mirrored. | |
212 if (display.id() == touch_display.id()) { | |
213 ctm.Translate(display.bounds_in_native().x(), | |
214 display.bounds_in_native().y()); | |
215 } | |
216 #endif | |
217 | |
218 // If touch calibration data is unavailable, use naive approach. | |
219 if (!touch_display.has_touch_calibration_data()) { | |
220 return GetUncalibratedTransform(ctm, display, touch_display, touch_area, | |
221 touch_native_size); | |
222 } | |
223 | |
224 // The resolution at which the touch calibration was performed. | |
225 gfx::SizeF touch_calib_size(touch_display.GetTouchCalibrationData().bounds); | |
226 | |
227 // Any additional transfomration that needs to be applied to the display | |
228 // points, before we solve for the final transform. | |
229 gfx::Transform pre_transform; | |
230 | |
231 if (display.id() != touch_display.id() || | |
232 display.is_aspect_preserving_scaling()) { | |
233 // Case of displays being mirrored or in panel fitting mode. | |
234 // Aspect ratio of the touch display's resolution during calibration. | |
235 float calib_ar = touch_calib_size.width() / touch_calib_size.height(); | |
236 // Aspect ratio of the display that is being mirrored. | |
237 float current_ar = current_size.width() / current_size.height(); | |
238 | |
239 if (current_ar < calib_ar) { | |
240 pre_transform.Scale(current_size.height() / touch_calib_size.height(), | |
241 current_size.height() / touch_calib_size.height()); | |
242 pre_transform.Translate( | |
243 (current_ar / calib_ar - 1.f) * touch_calib_size.width() * 0.5f, 0); | |
244 } else { | |
245 pre_transform.Scale(current_size.width() / touch_calib_size.width(), | |
246 current_size.width() / touch_calib_size.width()); | |
247 pre_transform.Translate( | |
248 0, (calib_ar / current_ar - 1.f) * touch_calib_size.height() * 0.5f); | |
249 } | |
250 } else { | |
251 // Case of current resolution being different from the resolution when the | |
252 // touch calibration was performed. | |
253 pre_transform.Scale(current_size.width() / touch_calib_size.width(), | |
254 current_size.height() / touch_calib_size.height()); | |
255 } | |
256 // Solve for coefficients and compute transform matrix. | |
257 gfx::Transform stored_ctm; | |
258 if (!GetCalibratedTransform( | |
259 touch_display.GetTouchCalibrationData().point_pairs, pre_transform, | |
260 &stored_ctm)) { | |
261 // TODO(malaykeshav): This can be checked at the calibration step before | |
262 // storing the calibration associated data. This will allow us to explicitly | |
263 // inform the user with proper UX. | |
264 | |
265 // Clear stored calibration data. | |
266 GetDisplayManager()->ClearTouchCalibrationData(touch_display.id()); | |
267 | |
268 // Return uncalibrated transform. | |
269 return GetUncalibratedTransform(ctm, display, touch_display, touch_area, | |
270 touch_native_size); | |
271 } | |
272 | |
273 stored_ctm.ConcatTransform(ctm); | |
274 return stored_ctm; | |
275 } | |
276 | |
277 TouchTransformerController::TouchTransformerController() { | |
278 Shell::GetInstance()->window_tree_host_manager()->AddObserver(this); | |
279 } | |
280 | |
281 TouchTransformerController::~TouchTransformerController() { | |
282 Shell::GetInstance()->window_tree_host_manager()->RemoveObserver(this); | |
283 } | |
284 | |
285 void TouchTransformerController::UpdateTouchRadius( | |
286 const display::ManagedDisplayInfo& display) const { | |
287 ui::DeviceDataManager* device_manager = ui::DeviceDataManager::GetInstance(); | |
288 for (const auto& device_id : display.input_devices()) { | |
289 device_manager->UpdateTouchRadiusScale( | |
290 device_id, | |
291 GetTouchResolutionScale(display, FindTouchscreenById(device_id))); | |
292 } | |
293 } | |
294 | |
295 void TouchTransformerController::UpdateTouchTransform( | |
296 int64_t target_display_id, | |
297 const display::ManagedDisplayInfo& touch_display, | |
298 const display::ManagedDisplayInfo& target_display) const { | |
299 ui::DeviceDataManager* device_manager = ui::DeviceDataManager::GetInstance(); | |
300 gfx::Size fb_size = | |
301 Shell::GetInstance()->display_configurator()->framebuffer_size(); | |
302 for (const auto& device_id : touch_display.input_devices()) { | |
303 device_manager->UpdateTouchInfoForDisplay( | |
304 target_display_id, device_id, | |
305 GetTouchTransform(target_display, touch_display, | |
306 FindTouchscreenById(device_id), fb_size)); | |
307 } | |
308 } | |
309 | |
310 void TouchTransformerController::UpdateTouchTransformer() const { | |
311 ui::DeviceDataManager* device_manager = ui::DeviceDataManager::GetInstance(); | |
312 device_manager->ClearTouchDeviceAssociations(); | |
313 | |
314 // Display IDs and display::ManagedDisplayInfo for mirror or extended mode. | |
315 int64_t display1_id = display::kInvalidDisplayId; | |
316 int64_t display2_id = display::kInvalidDisplayId; | |
317 display::ManagedDisplayInfo display1; | |
318 display::ManagedDisplayInfo display2; | |
319 // Display ID and display::ManagedDisplayInfo for single display mode. | |
320 int64_t single_display_id = display::kInvalidDisplayId; | |
321 display::ManagedDisplayInfo single_display; | |
322 | |
323 WindowTreeHostManager* window_tree_host_manager = | |
324 Shell::GetInstance()->window_tree_host_manager(); | |
325 display::DisplayManager* display_manager = GetDisplayManager(); | |
326 if (display_manager->num_connected_displays() == 0) { | |
327 return; | |
328 } else if (display_manager->num_connected_displays() == 1 || | |
329 display_manager->IsInUnifiedMode()) { | |
330 single_display_id = display_manager->first_display_id(); | |
331 DCHECK(single_display_id != display::kInvalidDisplayId); | |
332 single_display = display_manager->GetDisplayInfo(single_display_id); | |
333 UpdateTouchRadius(single_display); | |
334 } else { | |
335 display::DisplayIdList list = display_manager->GetCurrentDisplayIdList(); | |
336 display1_id = list[0]; | |
337 display2_id = list[1]; | |
338 DCHECK(display1_id != display::kInvalidDisplayId && | |
339 display2_id != display::kInvalidDisplayId); | |
340 display1 = display_manager->GetDisplayInfo(display1_id); | |
341 display2 = display_manager->GetDisplayInfo(display2_id); | |
342 UpdateTouchRadius(display1); | |
343 UpdateTouchRadius(display2); | |
344 } | |
345 | |
346 if (display_manager->IsInMirrorMode()) { | |
347 int64_t primary_display_id = | |
348 window_tree_host_manager->GetPrimaryDisplayId(); | |
349 if (GetDisplayManager()->SoftwareMirroringEnabled()) { | |
350 // In extended but software mirroring mode, there is a WindowTreeHost for | |
351 // each display, but all touches are forwarded to the primary root | |
352 // window's WindowTreeHost. | |
353 display::ManagedDisplayInfo target_display = | |
354 primary_display_id == display1_id ? display1 : display2; | |
355 UpdateTouchTransform(target_display.id(), display1, target_display); | |
356 UpdateTouchTransform(target_display.id(), display2, target_display); | |
357 } else { | |
358 // In mirror mode, there is just one WindowTreeHost and two displays. Make | |
359 // the WindowTreeHost accept touch events from both displays. | |
360 UpdateTouchTransform(primary_display_id, display1, display1); | |
361 UpdateTouchTransform(primary_display_id, display2, display2); | |
362 } | |
363 return; | |
364 } | |
365 | |
366 if (display_manager->num_connected_displays() > 1) { | |
367 // In actual extended mode, each display is associated with one | |
368 // WindowTreeHost. | |
369 UpdateTouchTransform(display1_id, display1, display1); | |
370 UpdateTouchTransform(display2_id, display2, display2); | |
371 return; | |
372 } | |
373 | |
374 // Single display mode. The WindowTreeHost has one associated display id. | |
375 UpdateTouchTransform(single_display_id, single_display, single_display); | |
376 } | |
377 | |
378 void TouchTransformerController::OnDisplaysInitialized() { | |
379 UpdateTouchTransformer(); | |
380 } | |
381 | |
382 void TouchTransformerController::OnDisplayConfigurationChanged() { | |
383 UpdateTouchTransformer(); | |
384 } | |
385 | |
386 } // namespace ash | |
OLD | NEW |