android: [sample-render-val] add difference/output viewer (#9781)

- Add viewer for closer examination
 - Add slider to enhance difference
 - Fixed ImageDiff jni bug to account for stride and
   premultiplication by alpha
This commit is contained in:
Powei Feng
2026-03-13 11:39:22 -07:00
committed by GitHub
parent f18afe1d3e
commit b2531fff15
5 changed files with 460 additions and 32 deletions

View File

@@ -20,8 +20,6 @@
#include <imagediff/ImageDiff.h>
#include <utils/Log.h>
#include <vector>
using namespace imagediff;
using namespace utils;
@@ -102,30 +100,48 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
if (generateDiff && result.diffImage.getWidth() > 0) {
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
jmethodID createBitmap = env->GetStaticMethodID(bitmapClass, "createBitmap",
jmethodID createBitmap = env->GetStaticMethodID(bitmapClass, "createBitmap",
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
jclass configClass = env->FindClass("android/graphics/Bitmap$Config");
jfieldID argb8888 = env->GetStaticFieldID(configClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
jobject configObj = env->GetStaticObjectField(configClass, argb8888);
uint32_t width = result.diffImage.getWidth();
uint32_t height = result.diffImage.getHeight();
jobject diffBitmap = env->CallStaticObjectMethod(bitmapClass, createBitmap, (jint)width, (jint)height, configObj);
jobject diffBitmap = env->CallStaticObjectMethod(bitmapClass, createBitmap, (jint) width,
(jint) height, configObj);
if (diffBitmap) {
// We need to transport the bit differences accurately to the java side, so set
// premultiplied to false. From the java-side, if the bitmap is used to draw to a
// canvas, then client needs to set premultiplied to true again.
jmethodID setPremultiplied = env->GetMethodID(bitmapClass, "setPremultiplied", "(Z)V");
if (setPremultiplied) {
env->CallVoidMethod(diffBitmap, setPremultiplied, JNI_FALSE);
}
void* diffPixels;
if (AndroidBitmap_lockPixels(env, diffBitmap, &diffPixels) == 0) {
AndroidBitmapInfo info;
AndroidBitmap_getInfo(env, diffBitmap, &info);
float const* src = result.diffImage.getPixelRef();
uint8_t* dst = (uint8_t*) diffPixels;
uint32_t channels = result.diffImage.getChannels(); // usually 4
for (size_t i = 0; i < width * height; ++i) {
for (int c = 0; c < 4; ++c) {
float v = 0.0f;
if (c < channels) v = src[i * channels + c];
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
dst[i * 4 + c] = (uint8_t) std::min(255.0f, std::max(0.0f, v * 255.0f));
uint32_t const channels = result.diffImage.getChannels(); // usually 4
for (size_t y = 0; y < height; ++y) {
uint8_t* row = dst + y * info.stride;
for (size_t x = 0; x < width; ++x) {
size_t srcIdx = (y * width + x) * channels;
for (int c = 0; c < 4; ++c) {
float v = 0.0f;
if (c < channels) v = src[srcIdx + c];
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
row[x * 4 + c] = uint8_t(
std::min(255.0f, std::max(0.0f, std::round(v * 255.0f))));
}
}
}
AndroidBitmap_unlockPixels(env, diffBitmap);
@@ -133,7 +149,7 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
}
}
}
return resultObj;
}
@@ -147,7 +163,7 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jcla
BitmapLock maskArg(env, maskBitmap);
if (!refArg.isValid() || !candArg.isValid()) {
ImageDiffResult emptyResult;
ImageDiffResult emptyResult;
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
return createResult(env, emptyResult, false);
}
@@ -175,13 +191,13 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jcla
extern "C" JNIEXPORT jobject JNICALL
Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclass,
jobject refBitmap, jobject candBitmap, jstring jsonConfig, jobject maskBitmap) {
BitmapLock refArg(env, refBitmap);
BitmapLock candArg(env, candBitmap);
BitmapLock maskArg(env, maskBitmap);
if (!refArg.isValid() || !candArg.isValid()) {
ImageDiffResult emptyResult;
ImageDiffResult emptyResult;
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
return createResult(env, emptyResult, false);
}
@@ -189,7 +205,7 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclas
ImageDiffConfig config;
const char* nativeJson = env->GetStringUTFChars(jsonConfig, 0);
size_t length = env->GetStringUTFLength(jsonConfig);
bool parsed = parseConfig(nativeJson, length, &config);
env->ReleaseStringUTFChars(jsonConfig, nativeJson);
@@ -214,4 +230,3 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclas
return createResult(env, result, generateDiff);
}

View File

@@ -66,10 +66,26 @@ class MainActivity : Activity(), ValidationRunner.Callback {
private lateinit var inputManager: ValidationInputManager
private var currentInput: ValidationInputManager.ValidationInput? = null
private var currentAlphaDiffBitmap: Bitmap? = null
private var globalEnhancementFactor: Float = 1.0f
private data class TestImages(
val testName: String,
val golden: Bitmap?,
val rendered: Bitmap?,
val diff: Bitmap?,
val alphaDiff: Bitmap?
)
private val diffImageViews = mutableListOf<ImageView>()
// UI Elements
private lateinit var runButton: Button
private lateinit var loadButton: Button
private lateinit var optionsButton: Button
private lateinit var enhancementContainer: LinearLayout
private lateinit var enhancementLabel: TextView
private lateinit var enhancementSlider: android.widget.SeekBar
private var resultManager: ValidationResultManager? = null
private var validationRunner: ValidationRunner? = null
@@ -98,6 +114,19 @@ class MainActivity : Activity(), ValidationRunner.Callback {
runButton = findViewById(R.id.run_button)
loadButton = findViewById(R.id.load_button)
optionsButton = findViewById(R.id.options_button)
enhancementContainer = findViewById(R.id.enhancement_container)
enhancementLabel = findViewById(R.id.enhancement_label)
enhancementSlider = findViewById(R.id.enhancement_slider)
enhancementSlider.setOnSeekBarChangeListener(object : android.widget.SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: android.widget.SeekBar?, progress: Int, fromUser: Boolean) {
globalEnhancementFactor = 1.0f + (progress / 100f) * 49.0f
enhancementLabel.text = String.format(Locale.US, "Enhancement: %.1fx", globalEnhancementFactor)
applyGlobalEnhancement()
}
override fun onStartTrackingTouch(seekBar: android.widget.SeekBar?) {}
override fun onStopTrackingTouch(seekBar: android.widget.SeekBar?) {}
})
// Setup Run Button
runButton.setOnClickListener {
@@ -120,6 +149,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
popup.menu.add(0, 3, 0, "Export Result")
popup.menu.add(0, 4, 0, "Test ADB Info")
popup.menu.add(0, 5, 0, "Result ADB Info")
popup.menu.add(0, 6, 0, "Toggle Enhancement Slider")
popup.setOnMenuItemClickListener { item ->
when (item.itemId) {
@@ -133,6 +163,9 @@ class MainActivity : Activity(), ValidationRunner.Callback {
3 -> exportTestResultsAction()
4 -> showTestAdbInfo()
5 -> showResultAdbInfo()
6 -> {
enhancementContainer.visibility = if (enhancementContainer.visibility == View.VISIBLE) View.GONE else View.VISIBLE
}
}
true
}
@@ -256,6 +289,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
// Clear existing results UI and state
resultsContainer.removeAllViews()
diffImageViews.clear()
resultManager = null
val newInput = ValidationInputManager.ValidationInput(
@@ -337,6 +371,8 @@ class MainActivity : Activity(), ValidationRunner.Callback {
private fun startValidation(input: ValidationInputManager.ValidationInput) {
try {
resultsContainer.removeAllViews()
diffImageViews.clear()
enhancementSlider.isEnabled = false
Log.i(TAG, "Starting validation with config: ${input.config.name}")
Log.i(TAG, "Output dir: ${input.outputDir.absolutePath}")
@@ -420,7 +456,15 @@ class MainActivity : Activity(), ValidationRunner.Callback {
val imagesRow = LinearLayout(this)
imagesRow.orientation = LinearLayout.HORIZONTAL
fun addImage(label: String, bitmap: Bitmap?) {
val testImages = TestImages(
testName = result.testName,
golden = currentGoldenBitmap,
rendered = currentRenderedBitmap,
diff = currentDiffBitmap,
alphaDiff = currentAlphaDiffBitmap
)
fun addImage(label: String, bitmap: Bitmap?, isDiff: Boolean) {
if (bitmap != null) {
val container = LinearLayout(this)
container.orientation = LinearLayout.VERTICAL
@@ -436,16 +480,29 @@ class MainActivity : Activity(), ValidationRunner.Callback {
iv.layoutParams = LinearLayout.LayoutParams(250, 250) // Smaller thumbnails
iv.scaleType = ImageView.ScaleType.FIT_CENTER
iv.setBackgroundColor(0xFF404040.toInt())
if (isDiff) {
diffImageViews.add(iv)
applyEnhancementToView(iv, globalEnhancementFactor)
}
iv.setOnClickListener {
showImageDialog(testImages, label)
}
container.addView(iv)
imagesRow.addView(container)
}
}
addImage("Rendered", currentRenderedBitmap)
addImage("Golden", currentGoldenBitmap)
addImage("Rendered", currentRenderedBitmap, false)
addImage("Golden", currentGoldenBitmap, false)
if (!result.passed) {
addImage("Diff", currentDiffBitmap)
addImage("Diff", currentDiffBitmap, true)
}
if (currentAlphaDiffBitmap != null) {
addImage("Alpha Diff", currentAlphaDiffBitmap, true)
}
resultContainer.addView(imagesRow)
@@ -455,12 +512,14 @@ class MainActivity : Activity(), ValidationRunner.Callback {
currentRenderedBitmap = null
currentGoldenBitmap = null
currentDiffBitmap = null
currentAlphaDiffBitmap = null
}
}
override fun onAllTestsFinished() {
runOnUiThread {
statusTextView.text = "All tests finished!"
enhancementSlider.isEnabled = true
Log.i(TAG, "All tests finished " + if (currentInput?.autoExport == true) "Exporting bundle" else "x")
if (currentInput?.autoExport == true) {
@@ -523,7 +582,165 @@ class MainActivity : Activity(), ValidationRunner.Callback {
"Diff" -> {
currentDiffBitmap = bitmap
}
"Alpha Diff" -> {
currentAlphaDiffBitmap = bitmap
}
}
}
}
private fun applyEnhancementToView(iv: ImageView, factor: Float) {
val cm = android.graphics.ColorMatrix()
cm.setScale(factor, factor, factor, 1.0f)
iv.colorFilter = android.graphics.ColorMatrixColorFilter(cm)
}
private fun applyGlobalEnhancement() {
for (iv in diffImageViews) {
applyEnhancementToView(iv, globalEnhancementFactor)
}
}
private fun showImageDialog(images: TestImages, initialLabel: String) {
val dialogView = layoutInflater.inflate(R.layout.dialog_image_viewer, null)
val dialog = AlertDialog.Builder(this)
.setView(dialogView)
.create()
val titleView = dialogView.findViewById<TextView>(R.id.dialog_title)
val typeView = dialogView.findViewById<TextView>(R.id.dialog_image_type)
val imageView = dialogView.findViewById<ImageView>(R.id.dialog_image)
val btnClose = dialogView.findViewById<View>(R.id.btn_close)
val btnReset = dialogView.findViewById<View>(R.id.btn_reset)
val btnPrev = dialogView.findViewById<View>(R.id.btn_prev)
val btnNext = dialogView.findViewById<View>(R.id.btn_next)
val enhancementContainer = dialogView.findViewById<View>(R.id.dialog_enhancement_container)
val enhancementLabel = dialogView.findViewById<TextView>(R.id.dialog_enhancement_label)
val enhancementSlider = dialogView.findViewById<android.widget.SeekBar>(R.id.dialog_enhancement_slider)
titleView.text = images.testName
val availableImages = mutableListOf<Pair<String, Bitmap>>()
images.rendered?.let { availableImages.add(Pair("Rendered", it)) }
images.golden?.let { availableImages.add(Pair("Golden", it)) }
images.diff?.let { availableImages.add(Pair("Diff", it)) }
images.alphaDiff?.let { availableImages.add(Pair("Alpha Diff", it)) }
if (availableImages.isEmpty()) return
var currentIndex = availableImages.indexOfFirst { it.first == initialLabel }
if (currentIndex == -1) currentIndex = 0
var currentDialogEnhancement = globalEnhancementFactor
val matrix = android.graphics.Matrix()
// Save initial values for translation tracking
var lastTouchX = 0f
var lastTouchY = 0f
var isDragging = false
val scaleDetector = android.view.ScaleGestureDetector(this, object : android.view.ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: android.view.ScaleGestureDetector): Boolean {
matrix.postScale(detector.scaleFactor, detector.scaleFactor, detector.focusX, detector.focusY)
imageView.imageMatrix = matrix
return true
}
})
imageView.setOnTouchListener { _, event ->
scaleDetector.onTouchEvent(event)
when (event.actionMasked) {
android.view.MotionEvent.ACTION_DOWN -> {
lastTouchX = event.x
lastTouchY = event.y
isDragging = true
}
android.view.MotionEvent.ACTION_MOVE -> {
if (isDragging && !scaleDetector.isInProgress) {
val dx = event.x - lastTouchX
val dy = event.y - lastTouchY
matrix.postTranslate(dx, dy)
imageView.imageMatrix = matrix
}
lastTouchX = event.x
lastTouchY = event.y
}
android.view.MotionEvent.ACTION_UP, android.view.MotionEvent.ACTION_CANCEL -> {
isDragging = false
}
}
true
}
fun updateView() {
val (label, bitmap) = availableImages[currentIndex]
typeView.text = label
imageView.setImageBitmap(bitmap)
(imageView.drawable as? android.graphics.drawable.BitmapDrawable)?.setAntiAlias(false)
(imageView.drawable as? android.graphics.drawable.BitmapDrawable)?.setFilterBitmap(false)
imageView.imageMatrix = matrix
if (label == "Diff" || label == "Alpha Diff") {
enhancementContainer.visibility = View.VISIBLE
applyEnhancementToView(imageView, currentDialogEnhancement)
} else {
enhancementContainer.visibility = View.GONE
imageView.colorFilter = null
}
}
fun resetMatrix() {
val drawable = imageView.drawable ?: return
val width = imageView.width.toFloat()
val height = imageView.height.toFloat()
val dw = drawable.intrinsicWidth.toFloat()
val dh = drawable.intrinsicHeight.toFloat()
val scaleX = width / dw
val scaleY = height / dh
val scale = Math.min(scaleX, scaleY)
val dx = (width - dw * scale) / 2f
val dy = (height - dh * scale) / 2f
matrix.reset()
matrix.postScale(scale, scale)
matrix.postTranslate(dx, dy)
imageView.imageMatrix = matrix
}
btnClose.setOnClickListener { dialog.dismiss() }
btnReset.setOnClickListener { resetMatrix() }
btnPrev.setOnClickListener {
currentIndex = (currentIndex - 1 + availableImages.size) % availableImages.size
updateView()
}
btnNext.setOnClickListener {
currentIndex = (currentIndex + 1) % availableImages.size
updateView()
}
val defaultProgress = ((currentDialogEnhancement - 1.0f) / 49.0f * 100).toInt()
val safeProgress = Math.max(0, Math.min(100, defaultProgress))
enhancementSlider.progress = safeProgress
enhancementLabel.text = String.format(Locale.US, "Enhance: %.1fx", currentDialogEnhancement)
enhancementSlider.setOnSeekBarChangeListener(object : android.widget.SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: android.widget.SeekBar?, progress: Int, fromUser: Boolean) {
currentDialogEnhancement = 1.0f + (progress / 100f) * 49.0f
enhancementLabel.text = String.format(Locale.US, "Enhance: %.1fx", currentDialogEnhancement)
updateView()
}
override fun onStartTrackingTouch(seekBar: android.widget.SeekBar?) {}
override fun onStopTrackingTouch(seekBar: android.widget.SeekBar?) {}
})
imageView.post {
resetMatrix()
}
updateView()
dialog.show()
}
}

View File

@@ -115,10 +115,6 @@ class ValidationRunner(
}
fun onFrame(frameTimeNanos: Long) {
if (frameCounter % 60 == 0) {
Log.i("ValidationRunner", "onFrame: $currentState (frame: $frameCounter)")
}
when (currentState) {
State.IDLE -> {}
State.WAITING_FOR_RESOURCES -> {
@@ -136,7 +132,6 @@ class ValidationRunner(
}
}
State.RUNNING_TEST -> {
// Log.i("ValidationRunner", "Running test...")
currentEngine?.let { engine ->
val content = AutomationEngine.ViewerContent()
content.view = modelViewer.view
@@ -257,10 +252,54 @@ class ValidationRunner(
passed = (result.status == ImageDiff.Result.Status.PASSED)
diffMetric = result.failingPixelCount.toFloat()
if (!passed) {
if (!passed) {
if (result.diffImage != null) {
callback?.onImageResult("Diff", result.diffImage!!)
resultManager.saveImage("${testFullName}_diff", result.diffImage!!)
val diffImg = result.diffImage!!
val width = diffImg.width
val height = diffImg.height
val pixels = IntArray(width * height)
diffImg.getPixels(pixels, 0, width, 0, 0, width, height)
var hasAlphaDiff = false
val alphaPixels = IntArray(width * height)
for (i in pixels.indices) {
val color = pixels[i]
val a = android.graphics.Color.alpha(color)
val r = android.graphics.Color.red(color)
val g = android.graphics.Color.green(color)
val b = android.graphics.Color.blue(color)
if (a > 0) {
hasAlphaDiff = true
}
// Map alpha diff to grayscale RGB
alphaPixels[i] = android.graphics.Color.argb(255, a, a, a)
// Force main diff image alpha to 255
pixels[i] = android.graphics.Color.argb(255, r, g, b)
}
// Apply updated pixels to diff image
diffImg.setPixels(pixels, 0, width, 0, 0, width, height)
// The C++ ImageDiff code sets isPremultiplied to false so Android
// doesn't erase RGB diff values when Alpha diff is 0. However, Android's
// Canvas will crash if we try to draw a non-premultiplied bitmap.
// Since we just forced all alpha values to 255 (fully opaque) in the
// loop above, we can safely mark it as premultiplied again here.
diffImg.isPremultiplied = true
callback?.onImageResult("Diff", diffImg)
resultManager.saveImage("${testFullName}_diff", diffImg)
if (hasAlphaDiff) {
val alphaDiffImg = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
alphaDiffImg.setPixels(alphaPixels, 0, width, 0, 0, width, height)
callback?.onImageResult("Alpha Diff", alphaDiffImg)
resultManager.saveImage("${testFullName}_alpha_diff", alphaDiffImg)
}
}
}
} else {

View File

@@ -81,6 +81,33 @@
android:contentDescription="More Options"
android:layout_marginStart="8dp"/>
</LinearLayout>
<!-- Global Enhancement Controls (Hidden by default) -->
<LinearLayout
android:id="@+id/enhancement_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:visibility="gone"
android:paddingBottom="8dp">
<TextView
android:id="@+id/enhancement_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enhancement: 1.0x"
android:textSize="12sp"
android:minWidth="120dp" />
<SeekBar
android:id="@+id/enhancement_slider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="100"
android:progress="0" />
</LinearLayout>
</LinearLayout>
<TextView

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FF202020"
android:padding="8dp">
<!-- Header with title and close button -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/dialog_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Test Result"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold" />
<ImageButton
android:id="@+id/btn_close"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_close_clear_cancel" />
</LinearLayout>
<!-- Subtitle for Image Type -->
<TextView
android:id="@+id/dialog_image_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:text="Rendered"
android:textColor="#BBBBBB"
android:textSize="14sp"
android:paddingBottom="8dp" />
<!-- Image Area with Arrows -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintDimensionRatio="1:1">
<ImageView
android:id="@+id/dialog_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="matrix" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Navigation Arrows -->
<LinearLayout
android:id="@+id/dialog_arrow_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<ImageButton
android:id="@+id/btn_prev"
android:layout_width="64dp"
android:layout_height="64dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_media_previous"
app:tint="#FFFFFF" />
<ImageButton
android:id="@+id/btn_reset"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginHorizontal="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_revert"
app:tint="#FFFFFF" />
<ImageButton
android:id="@+id/btn_next"
android:layout_width="64dp"
android:layout_height="64dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_media_next"
app:tint="#FFFFFF" />
</LinearLayout>
<!-- Enhancement Controls (only for diff images) -->
<LinearLayout
android:id="@+id/dialog_enhancement_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingTop="12dp"
android:paddingBottom="8dp"
android:visibility="gone">
<TextView
android:id="@+id/dialog_enhancement_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enhance: 1.0x"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:minWidth="100dp" />
<SeekBar
android:id="@+id/dialog_enhancement_slider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="100"
android:progress="0" />
</LinearLayout>
</LinearLayout>