PerspectiveTransform port and passes

This commit is contained in:
Henry Schimke
2022-08-31 16:37:20 -05:00
parent 94d6754c46
commit 6063af0e93
4 changed files with 317 additions and 245 deletions

View File

@@ -1,156 +0,0 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
/**
* <p>This class implements a perspective transform in two dimensions. Given four source and four
* destination points, it will compute the transformation implied between them. The code is based
* directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.</p>
*
* @author Sean Owen
*/
public final class PerspectiveTransform {
private final float a11;
private final float a12;
private final float a13;
private final float a21;
private final float a22;
private final float a23;
private final float a31;
private final float a32;
private final float a33;
private PerspectiveTransform(float a11, float a21, float a31,
float a12, float a22, float a32,
float a13, float a23, float a33) {
this.a11 = a11;
this.a12 = a12;
this.a13 = a13;
this.a21 = a21;
this.a22 = a22;
this.a23 = a23;
this.a31 = a31;
this.a32 = a32;
this.a33 = a33;
}
public static PerspectiveTransform quadrilateralToQuadrilateral(float x0, float y0,
float x1, float y1,
float x2, float y2,
float x3, float y3,
float x0p, float y0p,
float x1p, float y1p,
float x2p, float y2p,
float x3p, float y3p) {
PerspectiveTransform qToS = quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);
PerspectiveTransform sToQ = squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);
return sToQ.times(qToS);
}
public void transformPoints(float[] points) {
float a11 = this.a11;
float a12 = this.a12;
float a13 = this.a13;
float a21 = this.a21;
float a22 = this.a22;
float a23 = this.a23;
float a31 = this.a31;
float a32 = this.a32;
float a33 = this.a33;
int maxI = points.length - 1; // points.length must be even
for (int i = 0; i < maxI; i += 2) {
float x = points[i];
float y = points[i + 1];
float denominator = a13 * x + a23 * y + a33;
points[i] = (a11 * x + a21 * y + a31) / denominator;
points[i + 1] = (a12 * x + a22 * y + a32) / denominator;
}
}
public void transformPoints(float[] xValues, float[] yValues) {
int n = xValues.length;
for (int i = 0; i < n; i++) {
float x = xValues[i];
float y = yValues[i];
float denominator = a13 * x + a23 * y + a33;
xValues[i] = (a11 * x + a21 * y + a31) / denominator;
yValues[i] = (a12 * x + a22 * y + a32) / denominator;
}
}
public static PerspectiveTransform squareToQuadrilateral(float x0, float y0,
float x1, float y1,
float x2, float y2,
float x3, float y3) {
float dx3 = x0 - x1 + x2 - x3;
float dy3 = y0 - y1 + y2 - y3;
if (dx3 == 0.0f && dy3 == 0.0f) {
// Affine
return new PerspectiveTransform(x1 - x0, x2 - x1, x0,
y1 - y0, y2 - y1, y0,
0.0f, 0.0f, 1.0f);
} else {
float dx1 = x1 - x2;
float dx2 = x3 - x2;
float dy1 = y1 - y2;
float dy2 = y3 - y2;
float denominator = dx1 * dy2 - dx2 * dy1;
float a13 = (dx3 * dy2 - dx2 * dy3) / denominator;
float a23 = (dx1 * dy3 - dx3 * dy1) / denominator;
return new PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0,
y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0,
a13, a23, 1.0f);
}
}
public static PerspectiveTransform quadrilateralToSquare(float x0, float y0,
float x1, float y1,
float x2, float y2,
float x3, float y3) {
// Here, the adjoint serves as the inverse:
return squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint();
}
PerspectiveTransform buildAdjoint() {
// Adjoint is the transpose of the cofactor matrix:
return new PerspectiveTransform(a22 * a33 - a23 * a32,
a23 * a31 - a21 * a33,
a21 * a32 - a22 * a31,
a13 * a32 - a12 * a33,
a11 * a33 - a13 * a31,
a12 * a31 - a11 * a32,
a12 * a23 - a13 * a22,
a13 * a21 - a11 * a23,
a11 * a22 - a12 * a21);
}
PerspectiveTransform times(PerspectiveTransform other) {
return new PerspectiveTransform(a11 * other.a11 + a21 * other.a12 + a31 * other.a13,
a11 * other.a21 + a21 * other.a22 + a31 * other.a23,
a11 * other.a31 + a21 * other.a32 + a31 * other.a33,
a12 * other.a11 + a22 * other.a12 + a32 * other.a13,
a12 * other.a21 + a22 * other.a22 + a32 * other.a23,
a12 * other.a31 + a22 * other.a32 + a32 * other.a33,
a13 * other.a11 + a23 * other.a12 + a33 * other.a13,
a13 * other.a21 + a23 * other.a22 + a33 * other.a23,
a13 * other.a31 + a23 * other.a32 + a33 * other.a33);
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common;
import org.junit.Assert;
import org.junit.Test;
/**
* @author Sean Owen
*/
public final class PerspectiveTransformTestCase extends Assert {
private static final float EPSILON = 1.0E-4f;
@Test
public void testSquareToQuadrilateral() {
PerspectiveTransform pt = PerspectiveTransform.squareToQuadrilateral(
2.0f, 3.0f, 10.0f, 4.0f, 16.0f, 15.0f, 4.0f, 9.0f);
assertPointEquals(2.0f, 3.0f, 0.0f, 0.0f, pt);
assertPointEquals(10.0f, 4.0f, 1.0f, 0.0f, pt);
assertPointEquals(4.0f, 9.0f, 0.0f, 1.0f, pt);
assertPointEquals(16.0f, 15.0f, 1.0f, 1.0f, pt);
assertPointEquals(6.535211f, 6.8873234f, 0.5f, 0.5f, pt);
assertPointEquals(48.0f, 42.42857f, 1.5f, 1.5f, pt);
}
@Test
public void testQuadrilateralToQuadrilateral() {
PerspectiveTransform pt = PerspectiveTransform.quadrilateralToQuadrilateral(
2.0f, 3.0f, 10.0f, 4.0f, 16.0f, 15.0f, 4.0f, 9.0f,
103.0f, 110.0f, 300.0f, 120.0f, 290.0f, 270.0f, 150.0f, 280.0f);
assertPointEquals(103.0f, 110.0f, 2.0f, 3.0f, pt);
assertPointEquals(300.0f, 120.0f, 10.0f, 4.0f, pt);
assertPointEquals(290.0f, 270.0f, 16.0f, 15.0f, pt);
assertPointEquals(150.0f, 280.0f, 4.0f, 9.0f, pt);
assertPointEquals(7.1516876f, -64.60185f, 0.5f, 0.5f, pt);
assertPointEquals(328.09116f, 334.16385f, 50.0f, 50.0f, pt);
}
private static void assertPointEquals(float expectedX,
float expectedY,
float sourceX,
float sourceY,
PerspectiveTransform pt) {
float[] points = {sourceX, sourceY};
pt.transformPoints(points);
assertEquals(expectedX, points[0], EPSILON);
assertEquals(expectedY, points[1], EPSILON);
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// package com.google.zxing.common;
// import org.junit.Assert;
// import org.junit.Test;
/**
* @author Sean Owen
*/
// public final class PerspectiveTransformTestCase extends Assert {
use super::PerspectiveTransform;
static EPSILON: f32 = 1.0E-4f32;
#[test]
fn test_square_to_quadrilateral() {
let pt = PerspectiveTransform::squareToQuadrilateral(
2.0f32, 3.0f32, 10.0, 4.0, 16.0, 15.0, 4.0, 9.0,
);
assert_point_equals(2.0, 3.0, 0.0, 0.0, &pt);
assert_point_equals(10.0, 4.0, 1.0, 0.0, &pt);
assert_point_equals(4.0, 9.0, 0.0, 1.0, &pt);
assert_point_equals(16.0, 15.0, 1.0, 1.0, &pt);
assert_point_equals(6.535211, 6.8873234, 0.5, 0.5, &pt);
assert_point_equals(48.0, 42.42857, 1.5, 1.5, &pt);
}
#[test]
fn test_quadrilateral_to_quadrilateral() {
let pt = PerspectiveTransform::quadrilateralToQuadrilateral(
2.0, 3.0, 10.0, 4.0, 16.0, 15.0, 4.0, 9.0, 103.0, 110.0, 300.0, 120.0, 290.0, 270.0, 150.0,
280.0,
);
assert_point_equals(103.0, 110.0, 2.0, 3.0, &pt);
assert_point_equals(300.0, 120.0, 10.0, 4.0, &pt);
assert_point_equals(290.0, 270.0, 16.0, 15.0, &pt);
assert_point_equals(150.0, 280.0, 4.0, 9.0, &pt);
assert_point_equals(7.1516876, -64.60185, 0.5, 0.5, &pt);
assert_point_equals(328.09116, 334.16385, 50.0, 50.0, &pt);
}
fn assert_point_equals(
expected_x: f32,
expected_y: f32,
source_x: f32,
source_y: f32,
pt: &PerspectiveTransform,
) {
let mut points = [source_x, source_y];
pt.transform_points_single(&mut points);
assert!(
(expected_x - points[0] < EPSILON || points[0] - expected_x < EPSILON),
"{} - {}",
expected_x,
points[0]
);
assert!(
(expected_y - points[1] < EPSILON || points[1] - expected_y < EPSILON),
"{} - {}",
expected_y,
points[1]
);
// assert_eq!( expectedX, points[0]);
// assert_eq!( expectedY, points[1]);
}

View File

@@ -6,8 +6,8 @@ use std::cmp;
use std::collections::HashMap;
use std::fmt;
use crate::Exceptions;
use crate::DecodeHintType;
use crate::Exceptions;
use crate::RXingResultPoint;
use encoding::Encoding;
@@ -22,6 +22,9 @@ mod BitMatrixTestCase;
#[cfg(test)]
mod BitSourceTestCase;
#[cfg(test)]
mod PerspectiveTransformTestCase;
/*
* Copyright (C) 2010 ZXing authors
*
@@ -131,10 +134,9 @@ impl StringUtils {
{
if bytes[0] == 0xFE && bytes[1] == 0xFF {
return encoding::all::UTF_16BE;
}else {
} else {
return encoding::all::UTF_16LE;
}
}
// For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS,
@@ -474,7 +476,7 @@ impl BitArray {
let firstBit = if i > firstInt { 0 } else { start & 0x1F };
let lastBit = if i < lastInt { 31 } else { end & 0x1F };
// Ones from firstBit to lastBit, inclusive
let mask:u64 = (2 << lastBit) - (1 << firstBit);
let mask: u64 = (2 << lastBit) - (1 << firstBit);
self.bits[i] |= mask as u32;
}
Ok(())
@@ -500,12 +502,7 @@ impl BitArray {
* @return true iff all bits are set or not set in range, according to value argument
* @throws IllegalArgumentException if end is less than start or the range is not contained in the array
*/
pub fn isRange(
&self,
start: usize,
end: usize,
value: bool,
) -> Result<bool, Exceptions> {
pub fn isRange(&self, start: usize, end: usize, value: bool) -> Result<bool, Exceptions> {
let mut end = end;
if end < start || start < 0 || end > self.size {
return Err(Exceptions::IllegalArgumentException(
@@ -523,7 +520,7 @@ impl BitArray {
let firstBit = if i > firstInt { 0 } else { start & 0x1F };
let lastBit = if i < lastInt { 31 } else { end & 0x1F };
// Ones from firstBit to lastBit, inclusive
let mask:u64 = (2 << lastBit) - (1 << firstBit);
let mask: u64 = (2 << lastBit) - (1 << firstBit);
// Return false if we're looking for 1s and the masked bits[i] isn't all 1s (that is,
// equals the mask, or we're looking for 0s and the masked portion is not all 0s
@@ -550,11 +547,7 @@ impl BitArray {
* @param value {@code int} containing bits to append
* @param numBits bits from value to append
*/
pub fn appendBits(
&mut self,
value: u32,
numBits: usize,
) -> Result<(), Exceptions> {
pub fn appendBits(&mut self, value: u32, numBits: usize) -> Result<(), Exceptions> {
if numBits < 0 || numBits > 32 {
return Err(Exceptions::IllegalArgumentException(
"Num bits must be between 0 and 32".to_owned(),
@@ -584,7 +577,9 @@ impl BitArray {
pub fn xor(&mut self, other: &BitArray) -> Result<(), Exceptions> {
if self.size != other.size {
return Err(Exceptions::IllegalArgumentException("Sizes don't match".to_owned()));
return Err(Exceptions::IllegalArgumentException(
"Sizes don't match".to_owned(),
));
}
for i in 0..self.bits.len() {
//for (int i = 0; i < bits.length; i++) {
@@ -631,7 +626,7 @@ impl BitArray {
* Reverses all bits in the array.
*/
pub fn reverse(&mut self) {
let mut newBits = vec![0;self.bits.len()];
let mut newBits = vec![0; self.bits.len()];
// reverse all int's first
let len = (self.size - 1) / 32;
let oldBitsLen = len + 1;
@@ -861,7 +856,7 @@ impl BitMatrix {
// throw new IllegalArgumentException();
// }
let mut bits = vec![false;stringRepresentation.len()];
let mut bits = vec![false; stringRepresentation.len()];
let mut bitsPos = 0;
let mut rowStartPos = 0;
let mut rowLength = 0; //-1;
@@ -878,7 +873,9 @@ impl BitMatrix {
first_run = false;
rowLength = bitsPos - rowStartPos;
} else if bitsPos - rowStartPos != rowLength {
return Err(Exceptions::IllegalArgumentException("row lengths do not match".to_owned()));
return Err(Exceptions::IllegalArgumentException(
"row lengths do not match".to_owned(),
));
}
rowStartPos = bitsPos;
nRows += 1;
@@ -907,7 +904,9 @@ impl BitMatrix {
first_run = false;
rowLength = bitsPos - rowStartPos;
} else if bitsPos - rowStartPos != rowLength {
return Err(Exceptions::IllegalArgumentException("row lengths do not match".to_owned()));
return Err(Exceptions::IllegalArgumentException(
"row lengths do not match".to_owned(),
));
}
nRows += 1;
}
@@ -1145,7 +1144,7 @@ impl BitMatrix {
let mut newWidth = self.height;
let mut newHeight = self.width;
let mut newRowSize = (newWidth + 31) / 32;
let mut newBits = vec![0;(newRowSize * newHeight).try_into().unwrap()];
let mut newBits = vec![0; (newRowSize * newHeight).try_into().unwrap()];
for y in 0..self.height {
//for (int y = 0; y < height; y++) {
@@ -1568,8 +1567,8 @@ impl BitSource {
if num_bits > 0 {
let bits_to_not_read = 8 - num_bits;
let mask = (0xFF >> bits_to_not_read) << bits_to_not_read;
result =
(result << num_bits) | ((self.bytes[self.byte_offset] & mask) >> bits_to_not_read);
result = (result << num_bits)
| ((self.bytes[self.byte_offset] & mask) >> bits_to_not_read);
self.bit_offset += num_bits;
}
}
@@ -1584,3 +1583,216 @@ impl BitSource {
return 8 * (self.bytes.len() - self.byte_offset) - self.bit_offset;
}
}
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// package com.google.zxing.common;
/**
* <p>This class implements a perspective transform in two dimensions. Given four source and four
* destination points, it will compute the transformation implied between them. The code is based
* directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.</p>
*
* @author Sean Owen
*/
pub struct PerspectiveTransform {
a11: f32,
a12: f32,
a13: f32,
a21: f32,
a22: f32,
a23: f32,
a31: f32,
a32: f32,
a33: f32,
}
impl PerspectiveTransform {
fn new(
a11: f32,
a21: f32,
a31: f32,
a12: f32,
a22: f32,
a32: f32,
a13: f32,
a23: f32,
a33: f32,
) -> Self {
Self {
a11,
a12,
a13,
a21,
a22,
a23,
a31,
a32,
a33,
}
}
pub fn quadrilateralToQuadrilateral(
x0: f32,
y0: f32,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
x0p: f32,
y0p: f32,
x1p: f32,
y1p: f32,
x2p: f32,
y2p: f32,
x3p: f32,
y3p: f32,
) -> Self {
let qToS = PerspectiveTransform::quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);
let sToQ =
PerspectiveTransform::squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);
return sToQ.times(&qToS);
}
pub fn transform_points_single(&self, points: &mut [f32]) {
let a11 = self.a11;
let a12 = self.a12;
let a13 = self.a13;
let a21 = self.a21;
let a22 = self.a22;
let a23 = self.a23;
let a31 = self.a31;
let a32 = self.a32;
let a33 = self.a33;
let maxI = points.len() - 1; // points.length must be even
let mut i = 0;
while i < maxI {
// for (int i = 0; i < maxI; i += 2) {
let x = points[i];
let y = points[i + 1];
let denominator = a13 * x + a23 * y + a33;
points[i] = (a11 * x + a21 * y + a31) / denominator;
points[i + 1] = (a12 * x + a22 * y + a32) / denominator;
i += 2;
}
}
pub fn transform_points_double(&self, x_values: &mut [f32], y_valuess: &mut [f32]) {
let n = x_values.len();
for i in 0..n {
// for (int i = 0; i < n; i++) {
let x = x_values[i];
let y = y_valuess[i];
let denominator = self.a13 * x + self.a23 * y + self.a33;
x_values[i] = (self.a11 * x + self.a21 * y + self.a31) / denominator;
y_valuess[i] = (self.a12 * x + self.a22 * y + self.a32) / denominator;
}
}
pub fn squareToQuadrilateral(
x0: f32,
y0: f32,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
) -> Self {
let dx3 = x0 - x1 + x2 - x3;
let dy3 = y0 - y1 + y2 - y3;
if dx3 == 0.0f32 && dy3 == 0.0f32 {
// Affine
return PerspectiveTransform::new(
x1 - x0,
x2 - x1,
x0,
y1 - y0,
y2 - y1,
y0,
0.0f32,
0.0f32,
1.0f32,
);
} else {
let dx1 = x1 - x2;
let dx2 = x3 - x2;
let dy1 = y1 - y2;
let dy2 = y3 - y2;
let denominator = dx1 * dy2 - dx2 * dy1;
let a13 = (dx3 * dy2 - dx2 * dy3) / denominator;
let a23 = (dx1 * dy3 - dx3 * dy1) / denominator;
return PerspectiveTransform::new(
x1 - x0 + a13 * x1,
x3 - x0 + a23 * x3,
x0,
y1 - y0 + a13 * y1,
y3 - y0 + a23 * y3,
y0,
a13,
a23,
1.0f32,
);
}
}
pub fn quadrilateralToSquare(
x0: f32,
y0: f32,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
) -> Self {
// Here, the adjoint serves as the inverse
return PerspectiveTransform::squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3)
.buildAdjoint();
}
fn buildAdjoint(&self) -> Self {
// Adjoint is the transpose of the cofactor matrix:
return PerspectiveTransform::new(
self.a22 * self.a33 - self.a23 * self.a32,
self.a23 * self.a31 - self.a21 * self.a33,
self.a21 * self.a32 - self.a22 * self.a31,
self.a13 * self.a32 - self.a12 * self.a33,
self.a11 * self.a33 - self.a13 * self.a31,
self.a12 * self.a31 - self.a11 * self.a32,
self.a12 * self.a23 - self.a13 * self.a22,
self.a13 * self.a21 - self.a11 * self.a23,
self.a11 * self.a22 - self.a12 * self.a21,
);
}
fn times(&self, other: &Self) -> Self {
return PerspectiveTransform::new(
self.a11 * other.a11 + self.a21 * other.a12 + self.a31 * other.a13,
self.a11 * other.a21 + self.a21 * other.a22 + self.a31 * other.a23,
self.a11 * other.a31 + self.a21 * other.a32 + self.a31 * other.a33,
self.a12 * other.a11 + self.a22 * other.a12 + self.a32 * other.a13,
self.a12 * other.a21 + self.a22 * other.a22 + self.a32 * other.a23,
self.a12 * other.a31 + self.a22 * other.a32 + self.a32 * other.a33,
self.a13 * other.a11 + self.a23 * other.a12 + self.a33 * other.a13,
self.a13 * other.a21 + self.a23 * other.a22 + self.a33 * other.a23,
self.a13 * other.a31 + self.a23 * other.a32 + self.a33 * other.a33,
);
}
}