I'm using a GAN tflite model to process a bitmap. T model only accepts images of 512 x 512 pixels, so I first crop the image into tiles and then process all tiles at once. This resulted in an image full of seams and lines, so I added a code to blend the edges together.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = findViewById(R.id.imageView); picture = findViewById(R.id.button); MIRNetConverter.initialize(MainActivity.this); uploadButton = findViewById(R.id.upload);
uploadButton.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
@Override
public void onClick(View view) {
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_MEDIA_IMAGES}, 1);
} else {
uploadImage();
}
}
});
processingDialog = new Dialog(this);
processingDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
processingDialog.setCancelable(false);
processingDialog.setContentView(R.layout.processing_screen);
executorService = Executors.newFixedThreadPool(4);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (executorService != null) {
executorService.shutdown();
}
}
private void showProcessingDialog() {
if (processingDialog != null && !processingDialog.isShowing()) {
ProgressBar progressBar = processingDialog.findViewById(R.id.progressBar);
TextView textView = processingDialog.findViewById(R.id.textView);
if (progressBar != null && textView != null) {
progressBar.setIndeterminate(true);
textView.setText("Processing...");
}
processingDialog.show();
}
}
private void uploadImage() {
Intent galleryIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(galleryIntent, 2);
}
private void hideProcessingDialog() {
if (processingDialog != null && processingDialog.isShowing()) {
processingDialog.dismiss();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == 1 && data != null) {
Bitmap image = (Bitmap) data.getExtras().get("data");
imageView.setImageBitmap(image);
showProcessingDialog();
processImage(image);
} else if (requestCode == 2 && data != null) {
try {
Bitmap image = MediaStore.Images.Media.getBitmap(this.getContentResolver(), data.getData());
imageView.setImageBitmap(image);
showProcessingDialog();
processImage(image);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void processImage(Bitmap image) {
executorService.execute(new Runnable() {
@Override
public void run() {
int width = image.getWidth();
int height = image.getHeight();
Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
int numTilesX = (int) Math.ceil((float) width / (TILE_SIZE - OVERLAP));
int numTilesY = (int) Math.ceil((float) height / (TILE_SIZE - OVERLAP));
for (int y = 0; y < numTilesY; y++) {
for (int x = 0; x < numTilesX; x++) {
int startX = x * (TILE_SIZE - OVERLAP);
int startY = y * (TILE_SIZE - OVERLAP);
int endX = Math.min(startX + TILE_SIZE, width);
int endY = Math.min(startY + TILE_SIZE, height);
startX = Math.max(startX, 0);
startY = Math.max(startY, 0);
Bitmap tileBitmap = Bitmap.createBitmap(image, startX, startY, endX - startX, endY - startY);
Bitmap processedTile = processTile(tileBitmap);
int drawX = x * (TILE_SIZE - OVERLAP);
int drawY = y * (TILE_SIZE - OVERLAP);
blendTiles(resultBitmap, processedTile, drawX, drawY, startX, startY, endX, endY);
}
}
runOnUiThread(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(resultBitmap);
hideProcessingDialog();
}
});
}
});
}
private void blendTiles(Bitmap result, Bitmap tile, int drawX, int drawY, int startX, int startY, int endX, int endY) {
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setAntiAlias(true);
int tileWidth = tile.getWidth();
int tileHeight = tile.getHeight();
for (int i = 0; i < tileWidth; i++) {
for (int j = 0; j < tileHeight; j++) {
int tilePixel = tile.getPixel(i, j);
int resultPixelX = drawX + i;
int resultPixelY = drawY + j;
if (resultPixelX < endX && resultPixelY < endY) {
int resultPixel = result.getPixel(resultPixelX, resultPixelY);
float alpha = calculateAlpha(i, j, tileWidth, tileHeight);
int blendedPixel = blendPixels(resultPixel, tilePixel, alpha);
result.setPixel(resultPixelX, resultPixelY, blendedPixel);
}
}
}
}
private float calculateAlpha(int x, int y, int width, int height) {
float edgeX = (x < OVERLAP) ? (x / (float) OVERLAP) : ((width - x - 1) < OVERLAP) ? ((width - x - 1) / (float) OVERLAP) : 1.0f;
float edgeY = (y < OVERLAP) ? (y / (float) OVERLAP) : ((height - y - 1) < OVERLAP) ? ((height - y - 1) / (float) OVERLAP) : 1.0f;
return Math.min(edgeX, edgeY);
}
private int blendPixels(int basePixel, int topPixel, float alpha) {
int baseAlpha = Color.alpha(basePixel);
int baseRed = Color.red(basePixel);
int baseGreen = Color.green(basePixel);
int baseBlue = Color.blue(basePixel);
int topAlpha = Color.alpha(topPixel);
int topRed = Color.red(topPixel);
int topGreen = Color.green(topPixel);
int topBlue = Color.blue(topPixel);
int blendedAlpha = (int) (baseAlpha * (1 - alpha) + topAlpha * alpha);
int blendedRed = (int) (baseRed * (1 - alpha) + topRed * alpha);
int blendedGreen = (int) (baseGreen * (1 - alpha) + topGreen * alpha);
int blendedBlue = (int) (baseBlue * (1 - alpha) + topBlue * alpha);
return Color.argb(blendedAlpha, blendedRed, blendedGreen, blendedBlue);
}
private Bitmap processTile(Bitmap tile) {
Bitmap resizedTile = Bitmap.createScaledBitmap(tile, TILE_SIZE, TILE_SIZE, true);
Bitmap normalTile = MIRNetConverter.convertToNormal(MainActivity.this, resizedTile);
return Bitmap.createScaledBitmap(normalTile, tile.getWidth(), tile.getHeight(), true);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 100) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startCamera();
}
} else if (requestCode == 1) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
uploadImage();
} else {
Toast.makeText(this, "Permission denied to read images from media storage", Toast.LENGTH_SHORT).show();
}
}
}
My code kind of works, but the edges are now extremely dark. Any opinion on why it's the case?
The problem was that during blending, sometimes a tile doesn't have a tile next to itself, so you have to first check that topPixel
and basePixel
aren't black in order to blend them correctly.
private boolean isBlackPixel(int pixel) {
return Color.red(pixel) == 0 && Color.green(pixel) == 0 && Color.blue(pixel) == 0;
}
private void blendTiles(Bitmap result, Bitmap tile, int drawX, int drawY, int startX, int startY, int endX, int endY) {
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setAntiAlias(true);
int tileWidth = tile.getWidth();
int tileHeight = tile.getHeight();
for (int i = 0; i < tileWidth; i++) {
for (int j = 0; j < tileHeight; j++) {
int tilePixel = tile.getPixel(i, j);
int resultPixelX = drawX + i;
int resultPixelY = drawY + j;
if (resultPixelX < endX && resultPixelY < endY) {
int resultPixel = result.getPixel(resultPixelX, resultPixelY);
if (!isBlackPixel(tilePixel) && !isBlackPixel(resultPixel)) {
float alpha = calculateAlpha(i, j, tileWidth, tileHeight);
int blendedPixel = blendPixels(resultPixel, tilePixel, alpha);
result.setPixel(resultPixelX, resultPixelY, blendedPixel);
} else {
result.setPixel(resultPixelX, resultPixelY, isBlackPixel(resultPixel) ? tilePixel : resultPixel);
}
}
}
}
}
private float calculateAlpha(int x, int y, int width, int height) {
float edgeX = (x < OVERLAP) ? (x / (float) OVERLAP) : ((width - x - 1) < OVERLAP) ? ((width - x - 1) / (float) OVERLAP) : 1.0f;
float edgeY = (y < OVERLAP) ? (y / (float) OVERLAP) : ((height - y - 1) < OVERLAP) ? ((height - y - 1) / (float) OVERLAP) : 1.0f;
return Math.min(edgeX, edgeY);
}
private int blendPixels(int basePixel, int topPixel, float alpha) {
int baseAlpha = Color.alpha(basePixel);
int baseRed = Color.red(basePixel);
int baseGreen = Color.green(basePixel);
int baseBlue = Color.blue(basePixel);
int topAlpha = Color.alpha(topPixel);
int topRed = Color.red(topPixel);
int topGreen = Color.green(topPixel);
int topBlue = Color.blue(topPixel);
int blendedAlpha = (int) (baseAlpha * (1 - alpha) + topAlpha * alpha);
int blendedRed = (int) (baseRed * (1 - alpha) + topRed * alpha);
int blendedGreen = (int) (baseGreen * (1 - alpha) + topGreen * alpha);
int blendedBlue = (int) (baseBlue * (1 - alpha) + topBlue * alpha);
return Color.argb(blendedAlpha, blendedRed, blendedGreen, blendedBlue);
}