Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve BitmapUtils: Bitmap Handling and Image Processing for Modern APIs and Performance Enhancements #13700

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 81 additions & 69 deletions app/src/main/java/com/owncloud/android/utils/BitmapUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Canvas;
import android.graphics.ImageDecoder;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
Expand All @@ -24,6 +25,7 @@
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.widget.ImageView;

import com.owncloud.android.MainApp;
Expand All @@ -33,9 +35,9 @@
import com.owncloud.android.lib.resources.users.StatusType;
import com.owncloud.android.ui.StatusDrawable;

import org.apache.commons.codec.binary.Hex;

import java.nio.charset.Charset;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
Expand Down Expand Up @@ -67,13 +69,20 @@ private BitmapUtils() {
* @return decoded bitmap
*/
public static Bitmap decodeSampledBitmapFromFile(String srcPath, int reqWidth, int reqHeight) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// For API 28 and above, use ImageDecoder
try {
return ImageDecoder.decodeBitmap(ImageDecoder.createSource(new File(srcPath)),
(decoder, info, source) -> {
// Set the target size
decoder.setTargetSize(reqWidth, reqHeight);
});
} catch (Exception exception) {
Log_OC.e("BitmapUtil", "Error decoding the bitmap from file: " + srcPath + ", exception: " + exception.getMessage());
}
}
// set desired options that will affect the size of the bitmap
final Options options = new Options();
options.inScaled = true;
options.inPurgeable = true;
options.inPreferQualityOverSpeed = false;
options.inMutable = false;

// make a false load of the bitmap to get its dimensions
options.inJustDecodeBounds = true;
Expand Down Expand Up @@ -151,45 +160,53 @@ public static Bitmap rotateImage(Bitmap bitmap, String storagePath) {
ExifInterface exifInterface = new ExifInterface(storagePath);
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);

Matrix matrix = new Matrix();

// 1: nothing to do

// 2
if (orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL) {
matrix.postScale(-1.0f, 1.0f);
}
// 3
else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {
matrix.postRotate(180);
}
// 4
else if (orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL) {
matrix.postScale(1.0f, -1.0f);
}
// 5
else if (orientation == ExifInterface.ORIENTATION_TRANSPOSE) {
matrix.postRotate(-90);
matrix.postScale(1.0f, -1.0f);
}
// 6
else if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
matrix.postRotate(90);
}
// 7
else if (orientation == ExifInterface.ORIENTATION_TRANSVERSE) {
matrix.postRotate(90);
matrix.postScale(1.0f, -1.0f);
}
// 8
else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {
matrix.postRotate(270);
}
if (orientation != ExifInterface.ORIENTATION_NORMAL) {
Matrix matrix = new Matrix();
switch (orientation) {
// 2
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: {
matrix.postScale(-1.0f, 1.0f);
break;
}
// 3
case ExifInterface.ORIENTATION_ROTATE_180: {
matrix.postRotate(180);
break;
}
// 4
case ExifInterface.ORIENTATION_FLIP_VERTICAL: {
matrix.postScale(1.0f, -1.0f);
break;
}
// 5
case ExifInterface.ORIENTATION_TRANSPOSE: {
matrix.postRotate(-90);
matrix.postScale(1.0f, -1.0f);
break;
}
// 6
case ExifInterface.ORIENTATION_ROTATE_90: {
matrix.postRotate(90);
break;
}
// 7
case ExifInterface.ORIENTATION_TRANSVERSE: {
matrix.postRotate(90);
matrix.postScale(1.0f, -1.0f);
break;
}
// 8
case ExifInterface.ORIENTATION_ROTATE_270: {
matrix.postRotate(270);
break;
}
}

// Rotate the bitmap
resultBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
if (!resultBitmap.equals(bitmap)) {
bitmap.recycle();
// Rotate the bitmap
resultBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
if (!resultBitmap.equals(bitmap)) {
bitmap.recycle();
}
}
} catch (Exception exception) {
Log_OC.e("BitmapUtil", "Could not rotate the image: " + storagePath);
Expand All @@ -207,8 +224,8 @@ public static int[] getImageResolution(String srcPath) {
public static Color usernameToColor(String name) {
String hash = name.toLowerCase(Locale.ROOT);

// already a md5 hash?
if (!hash.matches("([0-9a-f]{4}-?){8}$")) {
// Check if the input is already a valid MD5 hash (32 hex characters)
if (hash.length() != 32 || !hash.matches("[0-9a-f]+")) {
try {
hash = md5(hash);
} catch (NoSuchAlgorithmException e) {
Expand All @@ -229,22 +246,15 @@ public static Color usernameToColor(String name) {

private static int hashToInt(String hash, int maximum) {
int finalInt = 0;
int[] result = new int[hash.length()];

// splitting evenly the string
// Sum the values of the hexadecimal digits
for (int i = 0; i < hash.length(); i++) {
// chars in md5 goes up to f, hex: 16
result[i] = Integer.parseInt(String.valueOf(hash.charAt(i)), 16) % 16;
}

// adds up all results
for (int value : result) {
finalInt += value;
// Efficient hex char-to-int conversion
finalInt += Character.digit(hash.charAt(i), 16);
}

// chars in md5 goes up to f, hex:16
// make sure we're always using int in our operation
return Integer.parseInt(String.valueOf(Integer.parseInt(String.valueOf(finalInt), 10) % maximum), 10);
// Return the sum modulo maximum
return finalInt % maximum;
}

private static Color[] generateColors(int steps) {
Expand All @@ -257,13 +267,9 @@ private static Color[] generateColors(int steps) {
Color[] palette3 = mixPalette(steps, blue, red);

Color[] resultPalette = new Color[palette1.length + palette2.length + palette3.length];
System.arraycopy(palette1, 0, resultPalette, 0, palette1.length);
System.arraycopy(palette2, 0, resultPalette, palette1.length, palette2.length);
System.arraycopy(palette3,
0,
resultPalette,
palette1.length + palette2.length,
palette1.length);
System.arraycopy(palette1, 0, resultPalette, 0, steps);
System.arraycopy(palette2, 0, resultPalette, steps, steps);
System.arraycopy(palette3, 0, resultPalette, steps * 2, steps);

return resultPalette;
}
Expand Down Expand Up @@ -326,15 +332,21 @@ public boolean equals(@Nullable Object obj) {

@Override
public int hashCode() {
return r * 10000 + g * 1000 + b;
return (r << 16) + (g << 8) + b;
}
}

public static String md5(String string) throws NoSuchAlgorithmException {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(string.getBytes(Charset.defaultCharset()));
// Use UTF-8 for consistency
byte[] hashBytes = md5.digest(string.getBytes(StandardCharsets.UTF_8));

return new String(Hex.encodeHex(md5.digest()));
StringBuilder hexString = new StringBuilder(32);
for (byte b : hashBytes) {
// Convert each byte to a 2-digit hex string
hexString.append(String.format("%02x", b));
}
return hexString.toString();
}

/**
Expand Down