Compress image attachments.

parent 92eb06a9
Pipeline #3621 passed with stage
in 9 minutes and 50 seconds
package org.briarproject.briar.android.attachment; package org.briarproject.briar.android.attachment;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory.Options;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
...@@ -12,11 +14,17 @@ import org.briarproject.briar.api.messaging.AttachmentHeader; ...@@ -12,11 +14,17 @@ import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException; import org.jsoup.UnsupportedMimeTypeException;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Logger; import java.util.logging.Logger;
import static android.graphics.Bitmap.CompressFormat.JPEG;
import static android.graphics.BitmapFactory.decodeStream;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
...@@ -24,6 +32,7 @@ import static org.briarproject.bramble.util.LogUtils.logDuration; ...@@ -24,6 +32,7 @@ import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES; import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
@NotNullByDefault @NotNullByDefault
class AttachmentCreationTask { class AttachmentCreationTask {
...@@ -31,8 +40,11 @@ class AttachmentCreationTask { ...@@ -31,8 +40,11 @@ class AttachmentCreationTask {
private static Logger LOG = private static Logger LOG =
getLogger(AttachmentCreationTask.class.getName()); getLogger(AttachmentCreationTask.class.getName());
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final ContentResolver contentResolver; private final ContentResolver contentResolver;
private final ImageSizeCalculator imageSizeCalculator;
private final GroupId groupId; private final GroupId groupId;
private final Collection<Uri> uris; private final Collection<Uri> uris;
private final boolean needsSize; private final boolean needsSize;
...@@ -43,24 +55,26 @@ class AttachmentCreationTask { ...@@ -43,24 +55,26 @@ class AttachmentCreationTask {
AttachmentCreationTask(MessagingManager messagingManager, AttachmentCreationTask(MessagingManager messagingManager,
ContentResolver contentResolver, ContentResolver contentResolver,
AttachmentCreator attachmentCreator, GroupId groupId, AttachmentCreator attachmentCreator,
Collection<Uri> uris, boolean needsSize) { ImageSizeCalculator imageSizeCalculator,
GroupId groupId, Collection<Uri> uris, boolean needsSize) {
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.contentResolver = contentResolver; this.contentResolver = contentResolver;
this.imageSizeCalculator = imageSizeCalculator;
this.groupId = groupId; this.groupId = groupId;
this.uris = uris; this.uris = uris;
this.needsSize = needsSize; this.needsSize = needsSize;
this.attachmentCreator = attachmentCreator; this.attachmentCreator = attachmentCreator;
} }
public void cancel() { void cancel() {
canceled = true; canceled = true;
attachmentCreator = null; attachmentCreator = null;
} }
@IoExecutor @IoExecutor
public void storeAttachments() { void storeAttachments() {
for (Uri uri: uris) processUri(uri); for (Uri uri : uris) processUri(uri);
AttachmentCreator attachmentCreator = this.attachmentCreator; AttachmentCreator attachmentCreator = this.attachmentCreator;
if (!canceled && attachmentCreator != null) if (!canceled && attachmentCreator != null)
attachmentCreator.onAttachmentCreationFinished(); attachmentCreator.onAttachmentCreationFinished();
...@@ -98,6 +112,8 @@ class AttachmentCreationTask { ...@@ -98,6 +112,8 @@ class AttachmentCreationTask {
} }
InputStream is = contentResolver.openInputStream(uri); InputStream is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException(); if (is == null) throw new IOException();
is = compressImage(is, contentType);
contentType = "image/jpeg";
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
AttachmentHeader h = messagingManager AttachmentHeader h = messagingManager
.addLocalAttachment(groupId, timestamp, contentType, is); .addLocalAttachment(groupId, timestamp, contentType, is);
...@@ -113,4 +129,48 @@ class AttachmentCreationTask { ...@@ -113,4 +129,48 @@ class AttachmentCreationTask {
return false; return false;
} }
private InputStream compressImage(InputStream is, String contentType)
throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
Bitmap bitmap = createBitmap(is, contentType);
for (int quality = 100; quality >= 0; quality -= 10) {
if (!bitmap.compress(JPEG, quality, out))
throw new IOException();
if (out.size() <= MAX_IMAGE_SIZE) {
if (LOG.isLoggable(INFO)) {
LOG.info("Compressed image to "
+ out.size() + " bytes, quality " + quality);
}
return new ByteArrayInputStream(out.toByteArray());
}
out.reset();
}
throw new IOException();
} finally {
tryToClose(is, LOG, WARNING);
}
}
private Bitmap createBitmap(InputStream is, String contentType)
throws IOException {
is = new BufferedInputStream(is);
Size size = imageSizeCalculator.getSize(is, contentType);
if (size.error) throw new IOException();
if (LOG.isLoggable(INFO))
LOG.info("Original image size: " + size.width + "x" + size.height);
int dimension = Math.max(size.width, size.height);
int scale = 1;
while (dimension > MAX_ATTACHMENT_DIMENSION) {
scale *= 2;
dimension /= 2;
}
if (LOG.isLoggable(INFO))
LOG.info("Scaling attachment by factor of " + scale);
Options options = new Options();
options.inSampleSize = scale;
Bitmap bitmap = decodeStream(is, null, options);
if (bitmap == null) throw new IOException();
return bitmap;
}
} }
...@@ -47,6 +47,7 @@ class AttachmentCreatorImpl implements AttachmentCreator { ...@@ -47,6 +47,7 @@ class AttachmentCreatorImpl implements AttachmentCreator {
private final Executor ioExecutor; private final Executor ioExecutor;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final AttachmentRetriever retriever; private final AttachmentRetriever retriever;
private final ImageSizeCalculator imageSizeCalculator;
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults = private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
...@@ -60,11 +61,13 @@ class AttachmentCreatorImpl implements AttachmentCreator { ...@@ -60,11 +61,13 @@ class AttachmentCreatorImpl implements AttachmentCreator {
@Inject @Inject
AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor, AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor,
MessagingManager messagingManager, AttachmentRetriever retriever) { MessagingManager messagingManager, AttachmentRetriever retriever,
ImageSizeCalculator imageSizeCalculator) {
this.app = app; this.app = app;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.retriever = retriever; this.retriever = retriever;
this.imageSizeCalculator = imageSizeCalculator;
} }
@Override @Override
...@@ -78,7 +81,8 @@ class AttachmentCreatorImpl implements AttachmentCreator { ...@@ -78,7 +81,8 @@ class AttachmentCreatorImpl implements AttachmentCreator {
if (id == null) throw new IllegalStateException(); if (id == null) throw new IllegalStateException();
boolean needsSize = uris.size() == 1; boolean needsSize = uris.size() == 1;
task = new AttachmentCreationTask(messagingManager, task = new AttachmentCreationTask(messagingManager,
app.getContentResolver(), this, id, uris, needsSize); app.getContentResolver(), this, imageSizeCalculator, id,
uris, needsSize);
ioExecutor.execute(() -> task.storeAttachments()); ioExecutor.execute(() -> task.storeAttachments());
}); });
MutableLiveData<AttachmentResult> result = new MutableLiveData<>(); MutableLiveData<AttachmentResult> result = new MutableLiveData<>();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment