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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user