lorgnette: add ToLorgnetteCaps

Add a method to convert from ScannerCapabilities to
LorgnetteCapabilities. This will be used by a future test to check that
the raw XML capabilities match the capabilities reported by lorgnette.

BUG=b:191933619
TEST=FEATURES=test emerge-nami lorgnette-wwcb-tests

Change-Id: I94368681317259a985c1ef6686c5c7a9c9620a2b
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/3202989
Reviewed-by: Benjamin Gordon <bmgordon@chromium.org>
Commit-Queue: Paul Moy <pmoy@chromium.org>
Tested-by: Paul Moy <pmoy@chromium.org>
diff --git a/lorgnette/hwtests/src/chromiumos/scanning/hwtests/resolution_tests.go b/lorgnette/hwtests/src/chromiumos/scanning/hwtests/resolution_tests.go
index 212fd42..bf40a98 100644
--- a/lorgnette/hwtests/src/chromiumos/scanning/hwtests/resolution_tests.go
+++ b/lorgnette/hwtests/src/chromiumos/scanning/hwtests/resolution_tests.go
@@ -10,39 +10,17 @@
 	"chromiumos/scanning/utils"
 )
 
-var supportedResolutions = []int{75, 100, 150, 200, 300, 600}
-
 // checkForSupportedResolution returns true if `sourceResolutions` advertises at
 // least one supported resolution, which must be advertised for both X and Y
 // resolutions.
 func checkForSupportedResolution(sourceResolutions utils.SupportedResolutions) bool {
-	for _, discreteResolution := range sourceResolutions.DiscreteResolutions {
-		if discreteResolution.XResolution != discreteResolution.YResolution {
-			continue
-		}
+	lorgnetteResolutions := sourceResolutions.ToLorgnetteResolutions()
 
-		for _, supportedResolution := range supportedResolutions {
-			if discreteResolution.XResolution == supportedResolution {
-				return true
-			}
-		}
+	if len(lorgnetteResolutions) == 0 {
+		return false
 	}
 
-	for _, supportedResolution := range supportedResolutions {
-		if supportedResolution < sourceResolutions.XResolutionRange.Min || supportedResolution > sourceResolutions.XResolutionRange.Max {
-			continue
-		}
-
-		if supportedResolution < sourceResolutions.YResolutionRange.Min || supportedResolution > sourceResolutions.YResolutionRange.Max {
-			continue
-		}
-
-		if (supportedResolution-sourceResolutions.XResolutionRange.Min)%sourceResolutions.XResolutionRange.Step == 0 && (supportedResolution-sourceResolutions.YResolutionRange.Min)%sourceResolutions.YResolutionRange.Step == 0 {
-			return true
-		}
-	}
-
-	return false
+	return true
 }
 
 // HasSupportedResolutionTest checks that each supported document source
diff --git a/lorgnette/hwtests/src/chromiumos/scanning/utils/scanner_capabilities_utils.go b/lorgnette/hwtests/src/chromiumos/scanning/utils/scanner_capabilities_utils.go
index 047511b..91d1a04 100644
--- a/lorgnette/hwtests/src/chromiumos/scanning/utils/scanner_capabilities_utils.go
+++ b/lorgnette/hwtests/src/chromiumos/scanning/utils/scanner_capabilities_utils.go
@@ -117,6 +117,31 @@
 	StoredJobRequestSupport StoredJobRequestSupport `xml:"StoredJobRequestSupport"`
 }
 
+// constructScannableAreaFromESCL constructs a ScannableArea object from eSCL
+// units.
+func constructScannableAreaFromESCL(maxHeight int, maxWidth int) (area ScannableArea) {
+	inchesToMM := 25.4
+	eSCLToInches := 300
+	area.Height = float32(maxHeight) * float32(inchesToMM) / float32(eSCLToInches)
+	area.Width = float32(maxWidth) * float32(inchesToMM) / float32(eSCLToInches)
+	return
+}
+
+// eSCLToLorgnetteColorMode converts `eSCLColorMode` to the corresponding
+// Lorgnette color mode.
+func eSCLToLorgnetteColorMode(eSCLColorMode string) string {
+	switch eSCLColorMode {
+	case "BlackAndWhite1":
+		return "MODE_LINEART"
+	case "Grayscale8":
+		return "MODE_GRAYSCALE"
+	case "RGB24":
+		return "MODE_COLOR"
+	default:
+		return "MODE_UNSPECIFIED"
+	}
+}
+
 // setReferencedProfileIfNecessary checks to see if `outProfile` references
 // another SettingProfile, and if so, finds that profile in `referencedProfiles`
 // and copies its information into `outProfile`.
@@ -209,3 +234,62 @@
 func (caps SourceCapabilities) IsPopulated() bool {
 	return !cmp.Equal(caps, SourceCapabilities{})
 }
+
+// ToLorgnetteResolutions converts `resolutions` to a format returned by
+// lorgnette. All resolutions unsupported by lorgnette are dropped.
+func (resolutions SupportedResolutions) ToLorgnetteResolutions() (lorgnetteResolutions []int) {
+	supportedResolutions := []int{75, 100, 150, 200, 300, 600}
+
+	for _, discreteResolution := range resolutions.DiscreteResolutions {
+		if discreteResolution.XResolution != discreteResolution.YResolution {
+			continue
+		}
+
+		for _, supportedResolution := range supportedResolutions {
+			if discreteResolution.XResolution == supportedResolution {
+				lorgnetteResolutions = append(lorgnetteResolutions, supportedResolution)
+			}
+		}
+	}
+
+	for _, supportedResolution := range supportedResolutions {
+		if supportedResolution < resolutions.XResolutionRange.Min || supportedResolution > resolutions.XResolutionRange.Max {
+			continue
+		}
+
+		if supportedResolution < resolutions.YResolutionRange.Min || supportedResolution > resolutions.YResolutionRange.Max {
+			continue
+		}
+
+		if (supportedResolution-resolutions.XResolutionRange.Min)%resolutions.XResolutionRange.Step == 0 && (supportedResolution-resolutions.YResolutionRange.Min)%resolutions.YResolutionRange.Step == 0 {
+			lorgnetteResolutions = append(lorgnetteResolutions, supportedResolution)
+		}
+	}
+
+	return
+}
+
+// ToLorgnetteSource converts `sourceCaps` to LorgnetteSource.
+func (caps SourceCapabilities) ToLorgnetteSource() (lorgnetteSource LorgnetteSource) {
+	for _, colorMode := range caps.SettingProfile.ColorModes {
+		lorgnetteColorMode := eSCLToLorgnetteColorMode(colorMode)
+		if lorgnetteColorMode == "MODE_LINEART" {
+			// Skip black and white because sane-airscan doesn't support it.
+			continue
+		}
+		lorgnetteSource.ColorModes = append(lorgnetteSource.ColorModes, lorgnetteColorMode)
+	}
+
+	lorgnetteSource.Resolutions = caps.SettingProfile.SupportedResolutions.ToLorgnetteResolutions()
+	lorgnetteSource.ScannableArea = constructScannableAreaFromESCL(caps.MaxHeight, caps.MaxWidth)
+
+	return
+}
+
+// ToLorgnetteCaps converts `scannerCaps` to LorgnetteCapabilities.
+func (scannerCaps ScannerCapabilities) ToLorgnetteCaps() (lorgnetteCaps LorgnetteCapabilities) {
+	lorgnetteCaps.PlatenCaps = scannerCaps.PlatenInputCaps.ToLorgnetteSource()
+	lorgnetteCaps.AdfSimplexCaps = scannerCaps.AdfCapabilities.AdfSimplexInputCaps.ToLorgnetteSource()
+	lorgnetteCaps.AdfDuplexCaps = scannerCaps.AdfCapabilities.AdfDuplexInputCaps.ToLorgnetteSource()
+	return
+}
diff --git a/lorgnette/hwtests/src/chromiumos/scanning/utils/scanner_capabilities_utils_test.go b/lorgnette/hwtests/src/chromiumos/scanning/utils/scanner_capabilities_utils_test.go
index 506741b..f03f102 100644
--- a/lorgnette/hwtests/src/chromiumos/scanning/utils/scanner_capabilities_utils_test.go
+++ b/lorgnette/hwtests/src/chromiumos/scanning/utils/scanner_capabilities_utils_test.go
@@ -568,3 +568,264 @@
 		}
 	}
 }
+
+// TestToLorgnetteResolutions tests that the ToLorgnetteResolutions function
+// works correctly.
+func TestToLorgnetteResolutions(t *testing.T) {
+	tests := []struct {
+		resolutions          SupportedResolutions
+		lorgnetteResolutions []int
+	}{
+		{
+			resolutions: SupportedResolutions{
+				XResolutionRange: ResolutionRange{
+					Min:    75,
+					Max:    600,
+					Normal: 300,
+					Step:   25},
+				YResolutionRange: ResolutionRange{
+					Min:    75,
+					Max:    600,
+					Normal: 300,
+					Step:   5}},
+			lorgnetteResolutions: []int{75, 100, 150, 200, 300, 600},
+		},
+		{
+			resolutions: SupportedResolutions{
+				DiscreteResolutions: []DiscreteResolution{
+					DiscreteResolution{
+						XResolution: 75,
+						YResolution: 75,
+					},
+					DiscreteResolution{
+						XResolution: 100,
+						YResolution: 100,
+					},
+					DiscreteResolution{
+						XResolution: 150,
+						YResolution: 150,
+					},
+					DiscreteResolution{
+						XResolution: 200,
+						YResolution: 200,
+					},
+					DiscreteResolution{
+						XResolution: 300,
+						YResolution: 300,
+					},
+					DiscreteResolution{
+						XResolution: 600,
+						YResolution: 600,
+					},
+				}},
+			lorgnetteResolutions: []int{75, 100, 150, 200, 300, 600},
+		},
+		{
+			resolutions: SupportedResolutions{
+				XResolutionRange: ResolutionRange{
+					Min:    0,
+					Max:    225,
+					Normal: 150,
+					Step:   75},
+				YResolutionRange: ResolutionRange{
+					Min:    0,
+					Max:    700,
+					Normal: 300,
+					Step:   100}},
+		},
+		{
+			resolutions: SupportedResolutions{
+				DiscreteResolutions: []DiscreteResolution{
+					DiscreteResolution{
+						XResolution: 50,
+						YResolution: 50,
+					},
+					DiscreteResolution{
+						XResolution: 125,
+						YResolution: 125,
+					},
+					DiscreteResolution{
+						XResolution: 1200,
+						YResolution: 1200,
+					},
+					DiscreteResolution{
+						XResolution: 200,
+						YResolution: 300,
+					},
+					DiscreteResolution{
+						XResolution: 300,
+						YResolution: 200,
+					},
+				}},
+		},
+		{
+			resolutions: SupportedResolutions{},
+		},
+	}
+
+	for _, tc := range tests {
+		got := tc.resolutions.ToLorgnetteResolutions()
+
+		if !cmp.Equal(got, tc.lorgnetteResolutions) {
+			t.Errorf("Expected %v, got %v for resolutions: %v", tc.lorgnetteResolutions, got, tc.resolutions)
+		}
+	}
+}
+
+// TestToLorgnetteSource tests that the ToLorgnetteSource function works
+// correctly.
+func TestToLorgnetteSource(t *testing.T) {
+	tests := []struct {
+		caps            SourceCapabilities
+		lorgnetteSource LorgnetteSource
+	}{
+		{
+			caps: SourceCapabilities{
+				MaxWidth:  2551,
+				MaxHeight: 4200,
+				SettingProfile: SettingProfile{
+					ColorModes: []string{"BlackAndWhite1", "Grayscale8", "Grayscale16", "RGB24", "RGB48"},
+					SupportedResolutions: SupportedResolutions{
+						XResolutionRange: ResolutionRange{
+							Min:    75,
+							Max:    600,
+							Normal: 75,
+							Step:   25},
+						YResolutionRange: ResolutionRange{
+							Min:    60,
+							Max:    700,
+							Normal: 90,
+							Step:   5}}}},
+			lorgnetteSource: LorgnetteSource{
+				ColorModes:  []string{"MODE_GRAYSCALE", "MODE_UNSPECIFIED", "MODE_COLOR", "MODE_UNSPECIFIED"},
+				Resolutions: []int{75, 100, 150, 200, 300, 600},
+				ScannableArea: ScannableArea{
+					Height: 355.6,
+					Width:  215.98466}},
+		},
+		{
+			caps:            SourceCapabilities{},
+			lorgnetteSource: LorgnetteSource{},
+		},
+	}
+
+	for _, tc := range tests {
+		got := tc.caps.ToLorgnetteSource()
+
+		if !cmp.Equal(got, tc.lorgnetteSource) {
+			t.Errorf("Expected %v, got %v for caps: %v", tc.lorgnetteSource, got, tc.caps)
+		}
+	}
+}
+
+// TestToLorgnetteCaps tests that the ToLorgnetteCaps function works correctly.
+func TestToLorgnetteCaps(t *testing.T) {
+	tests := []struct {
+		caps          ScannerCapabilities
+		lorgnetteCaps LorgnetteCapabilities
+	}{
+		{
+			caps: ScannerCapabilities{
+				PlatenInputCaps: SourceCapabilities{
+					MaxWidth:  1200,
+					MaxHeight: 2800,
+					SettingProfile: SettingProfile{
+						ColorModes: []string{"Grayscale16", "RGB24"},
+						SupportedResolutions: SupportedResolutions{
+							XResolutionRange: ResolutionRange{
+								Min:    75,
+								Max:    150,
+								Normal: 150,
+								Step:   75},
+							YResolutionRange: ResolutionRange{
+								Min:    75,
+								Max:    150,
+								Normal: 150,
+								Step:   75}}}},
+				AdfCapabilities: AdfCapabilities{
+					AdfSimplexInputCaps: SourceCapabilities{
+						MaxWidth:  2551,
+						MaxHeight: 4200,
+						SettingProfile: SettingProfile{
+							ColorModes: []string{"BlackAndWhite1", "Grayscale8"},
+							SupportedResolutions: SupportedResolutions{
+								DiscreteResolutions: []DiscreteResolution{
+									DiscreteResolution{
+										XResolution: 100,
+										YResolution: 200},
+									DiscreteResolution{
+										XResolution: 300,
+										YResolution: 300}}}}},
+					AdfDuplexInputCaps: SourceCapabilities{
+						MaxWidth:  1850,
+						MaxHeight: 3200,
+						SettingProfile: SettingProfile{
+							ColorModes: []string{"BlackAndWhite1", "RGB48"},
+							SupportedResolutions: SupportedResolutions{
+								DiscreteResolutions: []DiscreteResolution{
+									DiscreteResolution{
+										XResolution: 200,
+										YResolution: 200},
+									DiscreteResolution{
+										XResolution: 600,
+										YResolution: 600}}}}}}},
+			lorgnetteCaps: LorgnetteCapabilities{
+				PlatenCaps: LorgnetteSource{
+					ColorModes:  []string{"MODE_UNSPECIFIED", "MODE_COLOR"},
+					Resolutions: []int{75, 150},
+					ScannableArea: ScannableArea{
+						Height: 237.06667,
+						Width:  101.6}},
+				AdfSimplexCaps: LorgnetteSource{
+					ColorModes:  []string{"MODE_GRAYSCALE"},
+					Resolutions: []int{300},
+					ScannableArea: ScannableArea{
+						Height: 355.6,
+						Width:  215.98466}},
+				AdfDuplexCaps: LorgnetteSource{
+					ColorModes:  []string{"MODE_UNSPECIFIED"},
+					Resolutions: []int{200, 600},
+					ScannableArea: ScannableArea{
+						Height: 270.93333,
+						Width:  156.63333}}},
+		},
+		{
+			caps: ScannerCapabilities{
+				PlatenInputCaps: SourceCapabilities{
+					MaxWidth:  1200,
+					MaxHeight: 2800,
+					SettingProfile: SettingProfile{
+						ColorModes: []string{"Grayscale16", "RGB24"},
+						SupportedResolutions: SupportedResolutions{
+							XResolutionRange: ResolutionRange{
+								Min:    75,
+								Max:    150,
+								Normal: 150,
+								Step:   75},
+							YResolutionRange: ResolutionRange{
+								Min:    75,
+								Max:    150,
+								Normal: 150,
+								Step:   75}}}}},
+			lorgnetteCaps: LorgnetteCapabilities{
+				PlatenCaps: LorgnetteSource{
+					ColorModes:  []string{"MODE_UNSPECIFIED", "MODE_COLOR"},
+					Resolutions: []int{75, 150},
+					ScannableArea: ScannableArea{
+						Height: 237.06667,
+						Width:  101.6}}},
+		},
+		{
+			caps:          ScannerCapabilities{},
+			lorgnetteCaps: LorgnetteCapabilities{},
+		},
+	}
+
+	for _, tc := range tests {
+		got := tc.caps.ToLorgnetteCaps()
+
+		if !cmp.Equal(got, tc.lorgnetteCaps) {
+			t.Errorf("Expected %v, got %v for caps: %v", tc.lorgnetteCaps, got, tc.caps)
+		}
+	}
+}