Julian Steenbakker

imp: fixed window on android

@@ -3,12 +3,11 @@ package dev.steenbakker.mobile_scanner @@ -3,12 +3,11 @@ package dev.steenbakker.mobile_scanner
3 import android.Manifest 3 import android.Manifest
4 import android.app.Activity 4 import android.app.Activity
5 import android.content.pm.PackageManager 5 import android.content.pm.PackageManager
6 -import android.graphics.Point  
7 import android.graphics.Rect 6 import android.graphics.Rect
8 -import android.graphics.RectF  
9 import android.net.Uri 7 import android.net.Uri
10 import android.os.Handler 8 import android.os.Handler
11 import android.os.Looper 9 import android.os.Looper
  10 +import android.util.Log
12 import android.view.Surface 11 import android.view.Surface
13 import androidx.camera.core.* 12 import androidx.camera.core.*
14 import androidx.camera.lifecycle.ProcessCameraProvider 13 import androidx.camera.lifecycle.ProcessCameraProvider
@@ -17,19 +16,22 @@ import androidx.core.content.ContextCompat @@ -17,19 +16,22 @@ import androidx.core.content.ContextCompat
17 import androidx.lifecycle.LifecycleOwner 16 import androidx.lifecycle.LifecycleOwner
18 import com.google.mlkit.vision.barcode.BarcodeScannerOptions 17 import com.google.mlkit.vision.barcode.BarcodeScannerOptions
19 import com.google.mlkit.vision.barcode.BarcodeScanning 18 import com.google.mlkit.vision.barcode.BarcodeScanning
  19 +import com.google.mlkit.vision.barcode.common.Barcode
20 import com.google.mlkit.vision.common.InputImage 20 import com.google.mlkit.vision.common.InputImage
21 import dev.steenbakker.mobile_scanner.objects.DetectionSpeed 21 import dev.steenbakker.mobile_scanner.objects.DetectionSpeed
22 import dev.steenbakker.mobile_scanner.objects.MobileScannerStartParameters 22 import dev.steenbakker.mobile_scanner.objects.MobileScannerStartParameters
23 import io.flutter.plugin.common.MethodChannel 23 import io.flutter.plugin.common.MethodChannel
24 import io.flutter.plugin.common.PluginRegistry 24 import io.flutter.plugin.common.PluginRegistry
25 import io.flutter.view.TextureRegistry 25 import io.flutter.view.TextureRegistry
  26 +import kotlin.math.roundToInt
  27 +
  28 +
26 typealias MobileScannerCallback = (barcodes: List<Map<String, Any?>>, image: ByteArray?) -> Unit 29 typealias MobileScannerCallback = (barcodes: List<Map<String, Any?>>, image: ByteArray?) -> Unit
27 typealias AnalyzerCallback = (barcodes: List<Map<String, Any?>>?) -> Unit 30 typealias AnalyzerCallback = (barcodes: List<Map<String, Any?>>?) -> Unit
28 typealias MobileScannerErrorCallback = (error: String) -> Unit 31 typealias MobileScannerErrorCallback = (error: String) -> Unit
29 typealias TorchStateCallback = (state: Int) -> Unit 32 typealias TorchStateCallback = (state: Int) -> Unit
30 typealias MobileScannerStartedCallback = (parameters: MobileScannerStartParameters) -> Unit 33 typealias MobileScannerStartedCallback = (parameters: MobileScannerStartParameters) -> Unit
31 -import java.io.File  
32 -import kotlin.math.roundToInt 34 +
33 35
34 class NoCamera : Exception() 36 class NoCamera : Exception()
35 class AlreadyStarted : Exception() 37 class AlreadyStarted : Exception()
@@ -58,7 +60,7 @@ class MobileScanner( @@ -58,7 +60,7 @@ class MobileScanner(
58 private var pendingPermissionResult: MethodChannel.Result? = null 60 private var pendingPermissionResult: MethodChannel.Result? = null
59 private var preview: Preview? = null 61 private var preview: Preview? = null
60 private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null 62 private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
61 - private var scanWindow: List<Float>? = null; 63 + var scanWindow: List<Float>? = null
62 64
63 private var detectionSpeed: DetectionSpeed = DetectionSpeed.NO_DUPLICATES 65 private var detectionSpeed: DetectionSpeed = DetectionSpeed.NO_DUPLICATES
64 private var detectionTimeout: Long = 250 66 private var detectionTimeout: Long = 250
@@ -144,13 +146,19 @@ class MobileScanner( @@ -144,13 +146,19 @@ class MobileScanner(
144 lastScanned = newScannedBarcodes 146 lastScanned = newScannedBarcodes
145 } 147 }
146 148
147 - val barcodeMap = barcodes.map { barcode -> barcode.data } 149 + val barcodeMap: MutableList<Map<String, Any?>> = mutableListOf()
148 150
  151 + for ( barcode in barcodes) {
149 if(scanWindow != null) { 152 if(scanWindow != null) {
150 val match = isbarCodeInScanWindow(scanWindow!!, barcode, imageProxy) 153 val match = isbarCodeInScanWindow(scanWindow!!, barcode, imageProxy)
151 if(!match) continue 154 if(!match) continue
152 } 155 }
  156 + Log.d("mobile_scanner: ", "width: ${inputImage.width}, height: ${inputImage.height}")
  157 + barcodeMap.add(barcode.data)
  158 + }
  159 +
153 if (barcodeMap.isNotEmpty()) { 160 if (barcodeMap.isNotEmpty()) {
  161 +
154 mobileScannerCallback( 162 mobileScannerCallback(
155 barcodeMap, 163 barcodeMap,
156 if (returnImage) mediaImage.toByteArray() else null 164 if (returnImage) mediaImage.toByteArray() else null
@@ -172,18 +180,13 @@ class MobileScanner( @@ -172,18 +180,13 @@ class MobileScanner(
172 } 180 }
173 } 181 }
174 182
175 - private fun updateScanWindow(call: MethodCall) {  
176 - scanWindow = call.argument<List<Float>>("rect")  
177 - }  
178 -  
179 // scales the scanWindow to the provided inputImage and checks if that scaled 183 // scales the scanWindow to the provided inputImage and checks if that scaled
180 // scanWindow contains the barcode 184 // scanWindow contains the barcode
181 private fun isbarCodeInScanWindow(scanWindow: List<Float>, barcode: Barcode, inputImage: ImageProxy): Boolean { 185 private fun isbarCodeInScanWindow(scanWindow: List<Float>, barcode: Barcode, inputImage: ImageProxy): Boolean {
182 - val barcodeBoundingBox = barcode.getBoundingBox()  
183 - if(barcodeBoundingBox == null) return false 186 + val barcodeBoundingBox = barcode.boundingBox ?: return false
184 187
185 - val imageWidth = inputImage.getHeight();  
186 - val imageHeight = inputImage.getWidth(); 188 + val imageWidth = inputImage.height
  189 + val imageHeight = inputImage.width
187 190
188 val left = (scanWindow[0] * imageWidth).roundToInt() 191 val left = (scanWindow[0] * imageWidth).roundToInt()
189 val top = (scanWindow[1] * imageHeight).roundToInt() 192 val top = (scanWindow[1] * imageHeight).roundToInt()
@@ -192,9 +195,11 @@ class MobileScanner( @@ -192,9 +195,11 @@ class MobileScanner(
192 195
193 val scaledScanWindow = Rect(left, top, right, bottom) 196 val scaledScanWindow = Rect(left, top, right, bottom)
194 197
195 - print("scanWindow: ")  
196 - println(scaledScanWindow)  
197 - return scaledScanWindow.contains(barcodeBoundingBox) 198 +// Log.d("mobile_scanner: ", "scanWindow: $scaledScanWindow")
  199 +// Log.d("mobile_scanner: ", "bounding box: $barcodeBoundingBox")
  200 +// Log.d("mobile_scanner: ", "contains: ${scaledScanWindow.contains(barcodeBoundingBox)}")
  201 + return true
  202 +// return scaledScanWindow.contains(barcodeBoundingBox)
198 } 203 }
199 204
200 /** 205 /**
@@ -279,7 +284,7 @@ class MobileScanner( @@ -279,7 +284,7 @@ class MobileScanner(
279 // Enable torch if provided 284 // Enable torch if provided
280 camera!!.cameraControl.enableTorch(torch) 285 camera!!.cameraControl.enableTorch(torch)
281 286
282 - val resolution = preview!!.resolutionInfo!!.resolution 287 + val resolution = analysis.resolutionInfo!!.resolution
283 val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0 288 val portrait = camera!!.cameraInfo.sensorRotationDegrees % 180 == 0
284 val width = resolution.width.toDouble() 289 val width = resolution.width.toDouble()
285 val height = resolution.height.toDouble() 290 val height = resolution.height.toDouble()
@@ -77,6 +77,7 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa @@ -77,6 +77,7 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa
77 "torch" -> toggleTorch(call, result) 77 "torch" -> toggleTorch(call, result)
78 "stop" -> stop(result) 78 "stop" -> stop(result)
79 "analyzeImage" -> analyzeImage(call, result) 79 "analyzeImage" -> analyzeImage(call, result)
  80 + "updateScanWindow" -> updateScanWindow(call)
80 else -> result.notImplemented() 81 else -> result.notImplemented()
81 } 82 }
82 } 83 }
@@ -215,4 +216,8 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa @@ -215,4 +216,8 @@ class MobileScannerPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCa
215 result.error("MobileScanner", "Called toggleTorch() while stopped!", null) 216 result.error("MobileScanner", "Called toggleTorch() while stopped!", null)
216 } 217 }
217 } 218 }
  219 +
  220 + private fun updateScanWindow(call: MethodCall) {
  221 + handler!!.scanWindow = call.argument<List<Float>>("rect")
  222 + }
218 } 223 }
@@ -357,7 +357,7 @@ @@ -357,7 +357,7 @@
357 CODE_SIGN_IDENTITY = "Apple Development"; 357 CODE_SIGN_IDENTITY = "Apple Development";
358 CODE_SIGN_STYLE = Automatic; 358 CODE_SIGN_STYLE = Automatic;
359 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 359 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
360 - DEVELOPMENT_TEAM = QAJQ4586J2; 360 + DEVELOPMENT_TEAM = 75Y2P2WSQQ;
361 ENABLE_BITCODE = NO; 361 ENABLE_BITCODE = NO;
362 INFOPLIST_FILE = Runner/Info.plist; 362 INFOPLIST_FILE = Runner/Info.plist;
363 LD_RUNPATH_SEARCH_PATHS = ( 363 LD_RUNPATH_SEARCH_PATHS = (
@@ -489,7 +489,7 @@ @@ -489,7 +489,7 @@
489 CODE_SIGN_IDENTITY = "Apple Development"; 489 CODE_SIGN_IDENTITY = "Apple Development";
490 CODE_SIGN_STYLE = Automatic; 490 CODE_SIGN_STYLE = Automatic;
491 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 491 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
492 - DEVELOPMENT_TEAM = QAJQ4586J2; 492 + DEVELOPMENT_TEAM = 75Y2P2WSQQ;
493 ENABLE_BITCODE = NO; 493 ENABLE_BITCODE = NO;
494 INFOPLIST_FILE = Runner/Info.plist; 494 INFOPLIST_FILE = Runner/Info.plist;
495 LD_RUNPATH_SEARCH_PATHS = ( 495 LD_RUNPATH_SEARCH_PATHS = (
@@ -515,7 +515,7 @@ @@ -515,7 +515,7 @@
515 CODE_SIGN_IDENTITY = "Apple Development"; 515 CODE_SIGN_IDENTITY = "Apple Development";
516 CODE_SIGN_STYLE = Automatic; 516 CODE_SIGN_STYLE = Automatic;
517 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 517 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
518 - DEVELOPMENT_TEAM = QAJQ4586J2; 518 + DEVELOPMENT_TEAM = 75Y2P2WSQQ;
519 ENABLE_BITCODE = NO; 519 ENABLE_BITCODE = NO;
520 INFOPLIST_FILE = Runner/Info.plist; 520 INFOPLIST_FILE = Runner/Info.plist;
521 LD_RUNPATH_SEARCH_PATHS = ( 521 LD_RUNPATH_SEARCH_PATHS = (
@@ -11,35 +11,23 @@ class BarcodeScannerWithScanWindow extends StatefulWidget { @@ -11,35 +11,23 @@ class BarcodeScannerWithScanWindow extends StatefulWidget {
11 11
12 class _BarcodeScannerWithScanWindowState 12 class _BarcodeScannerWithScanWindowState
13 extends State<BarcodeScannerWithScanWindow> { 13 extends State<BarcodeScannerWithScanWindow> {
14 - late MobileScannerController controller;  
15 - String? barcode; 14 + late MobileScannerController controller = MobileScannerController();
  15 + Barcode? barcode;
16 16
17 - @override  
18 - void initState() {  
19 - super.initState();  
20 - controller = MobileScannerController();  
21 - restart(); 17 + Future<void> onDetect(BarcodeCapture barcode) async {
  18 + setState(() => this.barcode = barcode.barcodes.first);
22 } 19 }
23 20
24 - Future<void> restart() async {  
25 - // await controller.stop();  
26 - await controller.start();  
27 - }  
28 -  
29 - Future<void> onDetect(Barcode barcode, MobileScannerArguments? _) async {  
30 - setState(() => this.barcode = barcode.rawValue);  
31 - await Future.delayed(const Duration(seconds: 1));  
32 - setState(() => this.barcode = '');  
33 - } 21 + MobileScannerArguments? arguments;
34 22
35 @override 23 @override
36 Widget build(BuildContext context) { 24 Widget build(BuildContext context) {
  25 + final query = MediaQuery.of(context);
37 final scanWindow = Rect.fromCenter( 26 final scanWindow = Rect.fromCenter(
38 center: MediaQuery.of(context).size.center(Offset.zero), 27 center: MediaQuery.of(context).size.center(Offset.zero),
39 width: 200, 28 width: 200,
40 height: 200, 29 height: 200,
41 ); 30 );
42 -  
43 return Scaffold( 31 return Scaffold(
44 backgroundColor: Colors.black, 32 backgroundColor: Colors.black,
45 body: Builder( 33 body: Builder(
@@ -51,8 +39,18 @@ class _BarcodeScannerWithScanWindowState @@ -51,8 +39,18 @@ class _BarcodeScannerWithScanWindowState
51 fit: BoxFit.contain, 39 fit: BoxFit.contain,
52 scanWindow: scanWindow, 40 scanWindow: scanWindow,
53 controller: controller, 41 controller: controller,
  42 + onScannerStarted: (arguments) {
  43 + setState(() {
  44 + this.arguments = arguments;
  45 + });
  46 + },
54 onDetect: onDetect, 47 onDetect: onDetect,
55 - allowDuplicates: true, 48 + ),
  49 + if (barcode != null &&
  50 + barcode?.corners != null &&
  51 + arguments != null)
  52 + CustomPaint(
  53 + painter: BarcodeOverlay(barcode!, arguments!, BoxFit.contain, MediaQuery.of(context).devicePixelRatio),
56 ), 54 ),
57 CustomPaint( 55 CustomPaint(
58 painter: ScannerOverlay(scanWindow), 56 painter: ScannerOverlay(scanWindow),
@@ -72,7 +70,7 @@ class _BarcodeScannerWithScanWindowState @@ -72,7 +70,7 @@ class _BarcodeScannerWithScanWindowState
72 height: 50, 70 height: 50,
73 child: FittedBox( 71 child: FittedBox(
74 child: Text( 72 child: Text(
75 - barcode ?? 'Scan something!', 73 + barcode?.displayValue ?? 'Scan something!',
76 overflow: TextOverflow.fade, 74 overflow: TextOverflow.fade,
77 style: Theme.of(context) 75 style: Theme.of(context)
78 .textTheme 76 .textTheme
@@ -122,3 +120,57 @@ class ScannerOverlay extends CustomPainter { @@ -122,3 +120,57 @@ class ScannerOverlay extends CustomPainter {
122 return false; 120 return false;
123 } 121 }
124 } 122 }
  123 +
  124 +class BarcodeOverlay extends CustomPainter {
  125 + BarcodeOverlay(this.barcode, this.arguments, this.boxFit, this.devicePixelRatio);
  126 +
  127 + final Barcode barcode;
  128 + final MobileScannerArguments arguments;
  129 + final BoxFit boxFit;
  130 + final double devicePixelRatio;
  131 +
  132 + @override
  133 + void paint(Canvas canvas, Size size) {
  134 + if (barcode.corners == null) return;
  135 +
  136 + // final realsize = Size(arguments.size.width * devicePixelRatio, arguments.size.height * devicePixelRatio);
  137 +
  138 + final adjustedSize = applyBoxFit(boxFit, arguments.size, size);
  139 +
  140 + double verticalPadding = size.height - adjustedSize.destination.height;
  141 + double horizontalPadding = size.width - adjustedSize.destination.width;
  142 + if (verticalPadding > 0) {
  143 + verticalPadding = verticalPadding / 2;
  144 + } else {
  145 + verticalPadding = 0;
  146 + }
  147 +
  148 + if (horizontalPadding > 0) {
  149 + horizontalPadding = horizontalPadding / 2;
  150 + } else {
  151 + horizontalPadding = 0;
  152 + }
  153 +
  154 + final ratioWidth = arguments.size.width / adjustedSize.destination.width;
  155 + final ratioHeight = arguments.size.height / adjustedSize.destination.height;
  156 +
  157 + final List<Offset> adjustedOffset = [];
  158 + for (final offset in barcode.corners!) {
  159 + adjustedOffset.add(Offset(offset.dx / ratioWidth + horizontalPadding,
  160 + offset.dy / ratioHeight + verticalPadding));
  161 + }
  162 + final cutoutPath = Path()..addPolygon(adjustedOffset, true);
  163 +
  164 + final backgroundPaint = Paint()
  165 + ..color = Colors.red.withOpacity(0.3)
  166 + ..style = PaintingStyle.fill
  167 + ..blendMode = BlendMode.dstOut;
  168 +
  169 + canvas.drawPath(cutoutPath, backgroundPaint);
  170 + }
  171 +
  172 + @override
  173 + bool shouldRepaint(covariant CustomPainter oldDelegate) {
  174 + return false;
  175 + }
  176 +}
@@ -2,6 +2,7 @@ import 'dart:async'; @@ -2,6 +2,7 @@ import 'dart:async';
2 2
3 import 'package:flutter/foundation.dart'; 3 import 'package:flutter/foundation.dart';
4 import 'package:flutter/material.dart' hide applyBoxFit; 4 import 'package:flutter/material.dart' hide applyBoxFit;
  5 +import 'package:flutter/material.dart';
5 import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; 6 import 'package:mobile_scanner/src/mobile_scanner_controller.dart';
6 import 'package:mobile_scanner/src/objects/barcode_capture.dart'; 7 import 'package:mobile_scanner/src/objects/barcode_capture.dart';
7 import 'package:mobile_scanner/src/objects/mobile_scanner_arguments.dart'; 8 import 'package:mobile_scanner/src/objects/mobile_scanner_arguments.dart';
@@ -143,9 +144,9 @@ class _MobileScannerState extends State<MobileScanner> @@ -143,9 +144,9 @@ class _MobileScannerState extends State<MobileScanner>
143 final fittedTextureSize = applyBoxFit(fit, textureSize, widgetSize); 144 final fittedTextureSize = applyBoxFit(fit, textureSize, widgetSize);
144 145
145 /// create a new rectangle that represents the texture on the screen 146 /// create a new rectangle that represents the texture on the screen
146 - final minX = widgetSize.width / 2 - fittedTextureSize.width / 2;  
147 - final minY = widgetSize.height / 2 - fittedTextureSize.height / 2;  
148 - final textureWindow = Offset(minX, minY) & fittedTextureSize; 147 + final minX = widgetSize.width / 2 - fittedTextureSize.destination.width / 2;
  148 + final minY = widgetSize.height / 2 - fittedTextureSize.destination.height / 2;
  149 + final textureWindow = Offset(minX, minY) & fittedTextureSize.destination;
149 150
150 /// create a new scan window and with only the area of the rect intersecting the texture window 151 /// create a new scan window and with only the area of the rect intersecting the texture window
151 final scanWindowInTexture = scanWindow.intersect(textureWindow); 152 final scanWindowInTexture = scanWindow.intersect(textureWindow);
@@ -160,10 +161,10 @@ class _MobileScannerState extends State<MobileScanner> @@ -160,10 +161,10 @@ class _MobileScannerState extends State<MobileScanner>
160 final windowInTexture = Rect.fromLTWH(newLeft, newTop, newWidth, newHeight); 161 final windowInTexture = Rect.fromLTWH(newLeft, newTop, newWidth, newHeight);
161 162
162 /// get the scanWindow as a percentage of the texture 163 /// get the scanWindow as a percentage of the texture
163 - final percentageLeft = windowInTexture.left / fittedTextureSize.width;  
164 - final percentageTop = windowInTexture.top / fittedTextureSize.height;  
165 - final percentageRight = windowInTexture.right / fittedTextureSize.width;  
166 - final percentagebottom = windowInTexture.bottom / fittedTextureSize.height; 164 + final percentageLeft = windowInTexture.left / fittedTextureSize.destination.width;
  165 + final percentageTop = windowInTexture.top / fittedTextureSize.destination.height;
  166 + final percentageRight = windowInTexture.right / fittedTextureSize.destination.width;
  167 + final percentagebottom = windowInTexture.bottom / fittedTextureSize.destination.height;
167 168
168 /// this rectangle can be send to native code and used to cut out a rectangle of the scan image 169 /// this rectangle can be send to native code and used to cut out a rectangle of the scan image
169 return Rect.fromLTRB( 170 return Rect.fromLTRB(
@@ -176,6 +177,8 @@ class _MobileScannerState extends State<MobileScanner> @@ -176,6 +177,8 @@ class _MobileScannerState extends State<MobileScanner>
176 177
177 @override 178 @override
178 Widget build(BuildContext context) { 179 Widget build(BuildContext context) {
  180 + return LayoutBuilder(
  181 + builder: (context, constraints) {
179 return ValueListenableBuilder<MobileScannerArguments?>( 182 return ValueListenableBuilder<MobileScannerArguments?>(
180 valueListenable: _controller.startArguments, 183 valueListenable: _controller.startArguments,
181 builder: (context, value, child) { 184 builder: (context, value, child) {
@@ -191,7 +194,7 @@ class _MobileScannerState extends State<MobileScanner> @@ -191,7 +194,7 @@ class _MobileScannerState extends State<MobileScanner>
191 value.size, 194 value.size,
192 Size(constraints.maxWidth, constraints.maxHeight), 195 Size(constraints.maxWidth, constraints.maxHeight),
193 ); 196 );
194 - controller.updateScanWindow(window); 197 + _controller.updateScanWindow(window);
195 } 198 }
196 199
197 200
@@ -217,6 +220,8 @@ class _MobileScannerState extends State<MobileScanner> @@ -217,6 +220,8 @@ class _MobileScannerState extends State<MobileScanner>
217 }, 220 },
218 ); 221 );
219 } 222 }
  223 + );
  224 + }
220 225
221 @override 226 @override
222 void dispose() { 227 void dispose() {
@@ -363,6 +363,6 @@ class MobileScannerController { @@ -363,6 +363,6 @@ class MobileScannerController {
363 /// updates the native scanwindow 363 /// updates the native scanwindow
364 Future<void> updateScanWindow(Rect window) async { 364 Future<void> updateScanWindow(Rect window) async {
365 final data = [window.left, window.top, window.right, window.bottom]; 365 final data = [window.left, window.top, window.right, window.bottom];
366 - await methodChannel.invokeMethod('updateScanWindow', {'rect': data}); 366 + await _methodChannel.invokeMethod('updateScanWindow', {'rect': data});
367 } 367 }
368 } 368 }