From 1cee1ffcfd48e66446b7ac863fb1048c0d57b4d9 Mon Sep 17 00:00:00 2001 From: Christian Schneppe Date: Sat, 23 Dec 2017 23:21:49 +0100 Subject: use JPEG as file format for avatar and compress to <9400 chars and create avatar in background thread --- src/main/java/de/pixart/messenger/Config.java | 1 + .../pixart/messenger/persistance/FileBackend.java | 80 ++++++++++++++++------ .../messenger/services/XmppConnectionService.java | 37 ++++------ .../ui/PublishProfilePictureActivity.java | 28 ++------ 4 files changed, 82 insertions(+), 64 deletions(-) (limited to 'src/main/java/de') diff --git a/src/main/java/de/pixart/messenger/Config.java b/src/main/java/de/pixart/messenger/Config.java index 7715ed665..0604b0dda 100644 --- a/src/main/java/de/pixart/messenger/Config.java +++ b/src/main/java/de/pixart/messenger/Config.java @@ -72,6 +72,7 @@ public final class Config { public static final int AVATAR_SIZE = 480; public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.JPEG; + public static final int AVATAR_CHAR_LIMIT = 9400; public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG; public static final int IMAGE_QUALITY = 75; diff --git a/src/main/java/de/pixart/messenger/persistance/FileBackend.java b/src/main/java/de/pixart/messenger/persistance/FileBackend.java index 1d143cc53..b71c10cf5 100644 --- a/src/main/java/de/pixart/messenger/persistance/FileBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/FileBackend.java @@ -10,6 +10,7 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RectF; @@ -512,16 +513,20 @@ public class FileBackend { private void drawOverlay(Bitmap bitmap, int resource, float factor) { Bitmap overlay = BitmapFactory.decodeResource(mXmppConnectionService.getResources(), resource); Canvas canvas = new Canvas(bitmap); - Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setFilterBitmap(true); - paint.setDither(true); float targetSize = Math.min(canvas.getWidth(), canvas.getHeight()) * factor; Log.d(Config.LOGTAG, "target size overlay: " + targetSize + " overlay bitmap size was " + overlay.getHeight()); float left = (canvas.getWidth() - targetSize) / 2.0f; float top = (canvas.getHeight() - targetSize) / 2.0f; RectF dst = new RectF(left, top, left + targetSize - 1, top + targetSize - 1); - canvas.drawBitmap(overlay, null, dst, paint); + canvas.drawBitmap(overlay, null, dst, createAntiAliasingPaint()); + } + + private static Paint createAntiAliasingPaint() { + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setFilterBitmap(true); + paint.setDither(true); + return paint; } private Bitmap getVideoPreview(File file, int size) { @@ -583,30 +588,63 @@ public class FileBackend { } + private static boolean hasAlpha(final Bitmap bitmap) { + for (int x = 0; x < bitmap.getWidth(); ++x) { + for (int y = 0; y < bitmap.getWidth(); ++y) { + if (Color.alpha(bitmap.getPixel(x, y)) < 255) { + return true; + } + } + } + return false; + } + public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) { + Bitmap bm = cropCenterSquare(image, size); + if (bm == null) { + return null; + } + if (hasAlpha(bm)) { + Log.d(Config.LOGTAG, "alpha in avatar detected; uploading as PNG"); + bm.recycle(); + bm = cropCenterSquare(image, 96); + return getPepAvatar(bm, Bitmap.CompressFormat.PNG, 100); + } + return getPepAvatar(bm, format, 100); + } + + private Avatar getPepAvatar(Bitmap bitmap, Bitmap.CompressFormat format, int quality) { try { - Avatar avatar = new Avatar(); - Bitmap bm = cropCenterSquare(image, size); - if (bm == null) { - return null; - } ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream(); - Base64OutputStream mBase64OutputStream = new Base64OutputStream( - mByteArrayOutputStream, Base64.DEFAULT); + Base64OutputStream mBase64OutputStream = new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT); MessageDigest digest = MessageDigest.getInstance("SHA-1"); - DigestOutputStream mDigestOutputStream = new DigestOutputStream( - mBase64OutputStream, digest); - if (!bm.compress(format, 75, mDigestOutputStream)) { + DigestOutputStream mDigestOutputStream = new DigestOutputStream(mBase64OutputStream, digest); + if (!bitmap.compress(format, quality, mDigestOutputStream)) { return null; } mDigestOutputStream.flush(); mDigestOutputStream.close(); + long chars = mByteArrayOutputStream.size(); + if (format != Bitmap.CompressFormat.PNG && quality >= 50 && chars >= Config.AVATAR_CHAR_LIMIT) { + int q = quality - 2; + Log.d(Config.LOGTAG, "avatar char length was " + chars + " reducing quality to " + q); + return getPepAvatar(bitmap, format, q); + } + Log.d(Config.LOGTAG, "settled on char length " + chars + " with quality=" + quality); + final Avatar avatar = new Avatar(); avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest()); avatar.image = new String(mByteArrayOutputStream.toByteArray()); + if (format.equals(Bitmap.CompressFormat.WEBP)) { + avatar.type = "image/webp"; + } else if (format.equals(Bitmap.CompressFormat.JPEG)) { + avatar.type = "image/jpeg"; + } else if (format.equals(Bitmap.CompressFormat.PNG)) { + avatar.type = "image/png"; + } + avatar.width = bitmap.getWidth(); + avatar.height = bitmap.getHeight(); return avatar; - } catch (NoSuchAlgorithmException e) { - return null; - } catch (IOException e) { + } catch (Exception e) { return null; } } @@ -758,7 +796,7 @@ public class FileBackend { RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight); Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(dest); - canvas.drawBitmap(source, null, targetRect, null); + canvas.drawBitmap(source, null, targetRect, createAntiAliasingPaint()); if (source.isRecycled()) { source.recycle(); } @@ -786,8 +824,8 @@ public class FileBackend { Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); - canvas.drawBitmap(input, null, target, null); - if (input != null && !input.isRecycled()) { + canvas.drawBitmap(input, null, target, createAntiAliasingPaint()); + if (!input.isRecycled()) { input.recycle(); } return output; diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index 7b2231cc3..9adaad692 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -2997,28 +2997,21 @@ public class XmppConnectionService extends Service { } } - public void publishAvatar(Account account, Uri image, UiCallback callback) { - final Bitmap.CompressFormat format = Config.AVATAR_FORMAT; - final int size = Config.AVATAR_SIZE; - final Avatar avatar = getFileBackend().getPepAvatar(image, size, format); - if (avatar != null) { - avatar.height = size; - avatar.width = size; - if (format.equals(Bitmap.CompressFormat.WEBP)) { - avatar.type = "image/webp"; - } else if (format.equals(Bitmap.CompressFormat.JPEG)) { - avatar.type = "image/jpeg"; - } else if (format.equals(Bitmap.CompressFormat.PNG)) { - avatar.type = "image/png"; - } - if (!getFileBackend().save(avatar)) { - callback.error(R.string.error_saving_avatar, avatar); - return; - } - publishAvatar(account, avatar, callback); - } else { - callback.error(R.string.error_publish_avatar_converting, null); - } + public void publishAvatar(final Account account, final Uri image, final UiCallback callback) { + new Thread(() -> { + final Bitmap.CompressFormat format = Config.AVATAR_FORMAT; + final int size = Config.AVATAR_SIZE; + final Avatar avatar = getFileBackend().getPepAvatar(image, size, format); + if (avatar != null) { + if (!getFileBackend().save(avatar)) { + callback.error(R.string.error_saving_avatar, avatar); + return; + } + publishAvatar(account, avatar, callback); + } else { + callback.error(R.string.error_publish_avatar_converting, null); + } + }).start(); } public void publishAvatar(Account account, final Avatar avatar, final UiCallback callback) { diff --git a/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java b/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java index e79a5026d..707d7cfc8 100644 --- a/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java +++ b/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java @@ -6,12 +6,12 @@ import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.annotation.StringRes; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.widget.Button; import android.widget.ImageView; @@ -98,18 +98,13 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC this.avatar = findViewById(R.id.account_image); this.cancelButton = findViewById(R.id.cancel_button); this.publishButton = findViewById(R.id.publish_button); - this.accountTextView = findViewById(R.id.account); this.hintOrWarning = findViewById(R.id.hint_or_warning); this.secondaryHint = findViewById(R.id.secondary_hint); - this.publishButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (avatarUri != null) { - publishing = true; - togglePublishButton(false, R.string.publishing); - xmppConnectionService.publishAvatar(account, avatarUri, avatarPublication); - } + this.publishButton.setOnClickListener(v -> { + if (avatarUri != null) { + publishing = true; + togglePublishButton(false,R.string.publishing); + xmppConnectionService.publishAvatar(account, avatarUri, avatarPublication); } }); this.cancelButton.setOnClickListener(v -> { @@ -127,7 +122,6 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC if (hasStoragePermission(REQUEST_CHOOSE_FILE)) { chooseAvatar(false); } - }); this.defaultUri = PhoneHelper.getProfilePictureUri(getApplicationContext()); } @@ -141,7 +135,7 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC } @Override - public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { if (grantResults.length > 0) if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (requestCode == REQUEST_CHOOSE_FILE_AND_CROP) { @@ -238,14 +232,6 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC } else { loadImageIntoPreview(avatarUri); } - String account; - if (Config.DOMAIN_LOCK != null) { - account = this.account.getJid().getLocalpart(); - } else { - account = this.account.getJid().toBareJid().toString(); - } - this.accountTextView.setText(account); - } @Override -- cgit v1.2.3