forked from juliansteenbakker/mobile_scanner
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request juliansteenbakker#778 from navaronbracke/cherry_pi…
…ck_scan_window_fix fix: scan window fix (cherry-pick)
- Loading branch information
Showing
3 changed files
with
282 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import 'dart:math'; | ||
|
||
import 'package:flutter/rendering.dart'; | ||
|
||
/// Calculate the scan window rectangle relative to the texture size. | ||
/// | ||
/// The [scanWindow] rectangle will be relative and scaled to [widgetSize], not [textureSize]. | ||
/// Depending on the given [fit], the [scanWindow] can partially overlap the [textureSize], | ||
/// or not at all. | ||
/// | ||
/// Due to using [BoxFit] the content will always be centered on its parent, | ||
/// which enables converting the rectangle to be relative to the texture. | ||
/// | ||
/// Because the size of the actual texture and the size of the texture in widget-space | ||
/// can be different, calculate the size of the scan window in percentages, | ||
/// rather than pixels. | ||
/// | ||
/// Returns a [Rect] that represents the position and size of the scan window in the texture. | ||
Rect calculateScanWindowRelativeToTextureInPercentage( | ||
BoxFit fit, | ||
Rect scanWindow, { | ||
required Size textureSize, | ||
required Size widgetSize, | ||
}) { | ||
// Convert the texture size to a size in widget-space, with the box fit applied. | ||
final fittedTextureSize = applyBoxFit(fit, textureSize, widgetSize); | ||
|
||
// Get the correct scaling values depending on the given BoxFit mode | ||
double sx = fittedTextureSize.destination.width / textureSize.width; | ||
double sy = fittedTextureSize.destination.height / textureSize.height; | ||
|
||
switch (fit) { | ||
case BoxFit.fill: | ||
// No-op, just use sx and sy. | ||
break; | ||
case BoxFit.contain: | ||
final s = min(sx, sy); | ||
sx = s; | ||
sy = s; | ||
break; | ||
case BoxFit.cover: | ||
final s = max(sx, sy); | ||
sx = s; | ||
sy = s; | ||
break; | ||
case BoxFit.fitWidth: | ||
sy = sx; | ||
break; | ||
case BoxFit.fitHeight: | ||
sx = sy; | ||
break; | ||
case BoxFit.none: | ||
sx = 1.0; | ||
sy = 1.0; | ||
break; | ||
case BoxFit.scaleDown: | ||
final s = min(sx, sy); | ||
sx = s; | ||
sy = s; | ||
break; | ||
} | ||
|
||
// Fit the texture size to the widget rectangle given by the scaling values above. | ||
final textureWindow = Alignment.center.inscribe( | ||
Size(textureSize.width * sx, textureSize.height * sy), | ||
Rect.fromLTWH(0, 0, widgetSize.width, widgetSize.height), | ||
); | ||
|
||
// Transform the scan window from widget coordinates to texture coordinates. | ||
final scanWindowInTexSpace = Rect.fromLTRB( | ||
(1 / sx) * (scanWindow.left - textureWindow.left), | ||
(1 / sy) * (scanWindow.top - textureWindow.top), | ||
(1 / sx) * (scanWindow.right - textureWindow.left), | ||
(1 / sy) * (scanWindow.bottom - textureWindow.top), | ||
); | ||
|
||
// Clip the scan window in texture coordinates with the texture bounds. | ||
// This prevents percentages outside the range [0; 1]. | ||
final clippedScanWndInTexSpace = scanWindowInTexSpace.intersect( | ||
Rect.fromLTWH(0, 0, textureSize.width, textureSize.height), | ||
); | ||
|
||
// Compute relative rectangle coordinates, | ||
// with respect to the texture size, i.e. scan image. | ||
final percentageLeft = clippedScanWndInTexSpace.left / textureSize.width; | ||
final percentageTop = clippedScanWndInTexSpace.top / textureSize.height; | ||
final percentageRight = clippedScanWndInTexSpace.right / textureSize.width; | ||
final percentageBottom = clippedScanWndInTexSpace.bottom / textureSize.height; | ||
|
||
// This rectangle can be used to cut out a rectangle of the scan image. | ||
return Rect.fromLTRB( | ||
percentageLeft, | ||
percentageTop, | ||
percentageRight, | ||
percentageBottom, | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
import 'package:flutter/painting.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:mobile_scanner/src/scan_window_calculation.dart'; | ||
|
||
void main() { | ||
group( | ||
'Scan window relative to texture', | ||
() { | ||
group('Widget (landscape) smaller than texture (portrait)', () { | ||
const textureSize = Size(480.0, 640.0); | ||
const widgetSize = Size(432.0, 256.0); | ||
final ctx = ScanWindowTestContext( | ||
textureSize: textureSize, | ||
widgetSize: widgetSize, | ||
scanWindow: Rect.fromLTWH( | ||
widgetSize.width / 4, | ||
widgetSize.height / 4, | ||
widgetSize.width / 2, | ||
widgetSize.height / 2, | ||
), | ||
); | ||
|
||
test('wl tp: BoxFit.none', () { | ||
ctx.testScanWindow( | ||
BoxFit.none, | ||
const Rect.fromLTRB(0.275, 0.4, 0.725, 0.6), | ||
); | ||
}); | ||
|
||
test('wl tp: BoxFit.fill', () { | ||
ctx.testScanWindow( | ||
BoxFit.fill, | ||
const Rect.fromLTRB(0.25, 0.25, 0.75, 0.75), | ||
); | ||
}); | ||
|
||
test('wl tp: BoxFit.fitHeight', () { | ||
ctx.testScanWindow( | ||
BoxFit.fitHeight, | ||
const Rect.fromLTRB(0.0, 0.25, 1.0, 0.75), | ||
); | ||
}); | ||
|
||
test('wl tp: BoxFit.fitWidth', () { | ||
ctx.testScanWindow( | ||
BoxFit.fitWidth, | ||
const Rect.fromLTRB( | ||
0.25, | ||
0.38888888888888895, | ||
0.75, | ||
0.6111111111111112, | ||
), | ||
); | ||
}); | ||
|
||
test('wl tp: BoxFit.cover', () { | ||
// equal to fitWidth | ||
ctx.testScanWindow( | ||
BoxFit.cover, | ||
const Rect.fromLTRB( | ||
0.25, | ||
0.38888888888888895, | ||
0.75, | ||
0.6111111111111112, | ||
), | ||
); | ||
}); | ||
|
||
test('wl tp: BoxFit.contain', () { | ||
// equal to fitHeigth | ||
ctx.testScanWindow( | ||
BoxFit.contain, | ||
const Rect.fromLTRB(0.0, 0.25, 1.0, 0.75), | ||
); | ||
}); | ||
|
||
test('wl tp: BoxFit.scaleDown', () { | ||
// equal to fitHeigth, contain | ||
ctx.testScanWindow( | ||
BoxFit.scaleDown, | ||
const Rect.fromLTRB(0.0, 0.25, 1.0, 0.75), | ||
); | ||
}); | ||
}); | ||
|
||
group('Widget (landscape) smaller than texture and texture (landscape)', | ||
() { | ||
const textureSize = Size(640.0, 480.0); | ||
const widgetSize = Size(320.0, 120.0); | ||
final ctx = ScanWindowTestContext( | ||
textureSize: textureSize, | ||
widgetSize: widgetSize, | ||
scanWindow: Rect.fromLTWH( | ||
widgetSize.width / 4, | ||
widgetSize.height / 4, | ||
widgetSize.width / 2, | ||
widgetSize.height / 2, | ||
), | ||
); | ||
|
||
test('wl tl: BoxFit.none', () { | ||
ctx.testScanWindow( | ||
BoxFit.none, | ||
const Rect.fromLTRB(0.375, 0.4375, 0.625, 0.5625), | ||
); | ||
}); | ||
|
||
test('wl tl: BoxFit.fill', () { | ||
ctx.testScanWindow( | ||
BoxFit.fill, | ||
const Rect.fromLTRB(0.25, 0.25, 0.75, 0.75), | ||
); | ||
}); | ||
|
||
test('wl tl: BoxFit.fitHeight', () { | ||
ctx.testScanWindow( | ||
BoxFit.fitHeight, | ||
const Rect.fromLTRB(0.0, 0.25, 1.0, 0.75), | ||
); | ||
}); | ||
|
||
test('wl tl: BoxFit.fitWidth', () { | ||
ctx.testScanWindow( | ||
BoxFit.fitWidth, | ||
const Rect.fromLTRB(0.25, 0.375, 0.75, 0.625), | ||
); | ||
}); | ||
|
||
test('wl tl: BoxFit.cover', () { | ||
// equal to fitWidth | ||
ctx.testScanWindow( | ||
BoxFit.cover, | ||
const Rect.fromLTRB(0.25, 0.375, 0.75, 0.625), | ||
); | ||
}); | ||
|
||
test('wl tl: BoxFit.contain', () { | ||
// equal to fitHeigth | ||
ctx.testScanWindow( | ||
BoxFit.contain, | ||
const Rect.fromLTRB(0.0, 0.25, 1.0, 0.75), | ||
); | ||
}); | ||
|
||
test('wl tl: BoxFit.scaleDown', () { | ||
// equal to fitHeigth, contain | ||
ctx.testScanWindow( | ||
BoxFit.scaleDown, | ||
const Rect.fromLTRB(0.0, 0.25, 1.0, 0.75), | ||
); | ||
}); | ||
}); | ||
}, | ||
); | ||
} | ||
|
||
class ScanWindowTestContext { | ||
ScanWindowTestContext({ | ||
required this.textureSize, | ||
required this.widgetSize, | ||
required this.scanWindow, | ||
}); | ||
|
||
final Size textureSize; | ||
final Size widgetSize; | ||
final Rect scanWindow; | ||
|
||
void testScanWindow(BoxFit fit, Rect expected) { | ||
final actual = calculateScanWindowRelativeToTextureInPercentage( | ||
fit, | ||
scanWindow, | ||
textureSize: textureSize, | ||
widgetSize: widgetSize, | ||
); | ||
|
||
// don't use expect(actual, expected) because Rect.toString() only shows one digit after the comma which can be confusing | ||
expect(actual.left, expected.left); | ||
expect(actual.top, expected.top); | ||
expect(actual.right, expected.right); | ||
expect(actual.bottom, expected.bottom); | ||
} | ||
} |