mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-20 22:59:54 +01:00
Create ImageCompressor amd ImageCompressorImpl
* Methods from AttachmentCreationTask have been moved into them: * compressImage() * createBitmap() * ImageCompressor is availabe via AttachmentModule
This commit is contained in:
committed by
Torsten Grote
parent
aa00ba7220
commit
f819930570
@@ -1,41 +0,0 @@
|
|||||||
package org.briarproject.briar.android.attachment;
|
|
||||||
|
|
||||||
import android.content.res.AssetManager;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import static androidx.test.InstrumentationRegistry.getContext;
|
|
||||||
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
|
||||||
|
|
||||||
abstract class AbstractAttachmentCreationTaskTest {
|
|
||||||
|
|
||||||
private final ImageHelper imageHelper = new ImageHelperImpl();
|
|
||||||
private final ImageSizeCalculator imageSizeCalculator =
|
|
||||||
new ImageSizeCalculator(imageHelper);
|
|
||||||
|
|
||||||
private AttachmentCreationTask task;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
@SuppressWarnings("ConstantConditions") // add real objects when needed
|
|
||||||
public void setUp() {
|
|
||||||
task = new AttachmentCreationTask(null,
|
|
||||||
getApplicationContext().getContentResolver(), null,
|
|
||||||
imageSizeCalculator, null, null, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testCompress(String filename, String contentType)
|
|
||||||
throws IOException {
|
|
||||||
InputStream is = getAssetManager().open(filename);
|
|
||||||
task.compressImage(is, contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
static AssetManager getAssetManager() {
|
|
||||||
// pm.getResourcesForApplication(packageName).getAssets() did not work
|
|
||||||
//noinspection deprecation
|
|
||||||
return getContext().getAssets();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Component;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = {
|
||||||
|
AttachmentModule.class
|
||||||
|
})
|
||||||
|
interface AbstractImageCompressorComponent {
|
||||||
|
|
||||||
|
void inject(AbstractImageCompressorTest test);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static androidx.test.InstrumentationRegistry.getContext;
|
||||||
|
|
||||||
|
public abstract class AbstractImageCompressorTest {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ImageCompressor imageCompressor;
|
||||||
|
|
||||||
|
public AbstractImageCompressorTest() {
|
||||||
|
AbstractImageCompressorComponent component =
|
||||||
|
DaggerAbstractImageCompressorComponent.builder().build();
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void inject(
|
||||||
|
AbstractImageCompressorComponent component);
|
||||||
|
|
||||||
|
void testCompress(String filename, String contentType)
|
||||||
|
throws IOException {
|
||||||
|
InputStream is = getAssetManager().open(filename);
|
||||||
|
imageCompressor.compressImage(is, contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
static AssetManager getAssetManager() {
|
||||||
|
// pm.getResourcesForApplication(packageName).getAssets() did not work
|
||||||
|
//noinspection deprecation
|
||||||
|
return getContext().getAssets();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -9,8 +9,13 @@ import static android.os.Build.VERSION.SDK_INT;
|
|||||||
import static org.junit.Assume.assumeTrue;
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class AttachmentCreationTaskTest
|
public class ImageCompressorTest
|
||||||
extends AbstractAttachmentCreationTaskTest {
|
extends AbstractImageCompressorTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void inject(AbstractImageCompressorComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCompressSmallPng() throws Exception {
|
public void testCompressSmallPng() throws Exception {
|
||||||
@@ -17,11 +17,16 @@ import static org.junit.Assert.fail;
|
|||||||
import static org.junit.Assume.assumeTrue;
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
public class PngSuiteAttachmentCreationTaskTest
|
public class PngSuiteImageCompressorTest
|
||||||
extends AbstractAttachmentCreationTaskTest {
|
extends AbstractImageCompressorTest {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(PngSuiteAttachmentCreationTaskTest.class.getName());
|
getLogger(PngSuiteImageCompressorTest.class.getName());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void inject(AbstractImageCompressorComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Parameters
|
@Parameters
|
||||||
public static Iterable<String> data() throws IOException {
|
public static Iterable<String> data() throws IOException {
|
||||||
@@ -34,14 +39,14 @@ public class PngSuiteAttachmentCreationTaskTest
|
|||||||
|
|
||||||
private final String filename;
|
private final String filename;
|
||||||
|
|
||||||
public PngSuiteAttachmentCreationTaskTest(String filename) {
|
public PngSuiteImageCompressorTest(String filename) {
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPngSuiteCompress() throws Exception {
|
public void testPngSuiteCompress() throws Exception {
|
||||||
assumeTrue(isOptionalTestEnabled(
|
assumeTrue(isOptionalTestEnabled(
|
||||||
PngSuiteAttachmentCreationTaskTest.class));
|
PngSuiteImageCompressorTest.class));
|
||||||
LOG.info("Testing " + filename);
|
LOG.info("Testing " + filename);
|
||||||
if (filename.startsWith("x")) {
|
if (filename.startsWith("x")) {
|
||||||
try {
|
try {
|
||||||
@@ -14,17 +14,15 @@ import android.graphics.Color;
|
|||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
|
||||||
|
import org.briarproject.briar.android.attachment.ImageCompressor;
|
||||||
import org.briarproject.briar.api.test.TestAvatarCreator;
|
import org.briarproject.briar.api.test.TestAvatarCreator;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.IOException;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
import static android.graphics.Bitmap.CompressFormat.JPEG;
|
|
||||||
import static org.briarproject.briar.api.media.MediaConstants.MAX_IMAGE_SIZE;
|
|
||||||
|
|
||||||
public class TestAvatarCreatorImpl implements TestAvatarCreator {
|
public class TestAvatarCreatorImpl implements TestAvatarCreator {
|
||||||
private final int WIDTH = 800;
|
private final int WIDTH = 800;
|
||||||
@@ -34,20 +32,18 @@ public class TestAvatarCreatorImpl implements TestAvatarCreator {
|
|||||||
private final float[] hsv = new float[3];
|
private final float[] hsv = new float[3];
|
||||||
private final Random random = new Random();
|
private final Random random = new Random();
|
||||||
|
|
||||||
|
private final ImageCompressor imageCompressor;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TestAvatarCreatorImpl(ImageCompressor imageCompressor) {
|
||||||
|
this.imageCompressor = imageCompressor;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public InputStream getAvatarInputStream() {
|
public InputStream getAvatarInputStream() throws IOException {
|
||||||
Bitmap bitmap = generateBitmap();
|
Bitmap bitmap = generateBitmap();
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
return imageCompressor.compressImage(bitmap);
|
||||||
// TODO maybe use ImageCompressor once available standalone
|
|
||||||
for (int quality = 100; quality >= 0; quality -= 10) {
|
|
||||||
if (!bitmap.compress(JPEG, quality, out)) return null;
|
|
||||||
if (out.size() <= MAX_IMAGE_SIZE) {
|
|
||||||
return new ByteArrayInputStream(out.toByteArray());
|
|
||||||
}
|
|
||||||
out.reset();
|
|
||||||
}
|
|
||||||
return new ByteArrayInputStream(out.toByteArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap generateBitmap() {
|
private Bitmap generateBitmap() {
|
||||||
|
|||||||
@@ -187,8 +187,9 @@ public class AppModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
TestAvatarCreator provideTestAvatarCreator() {
|
TestAvatarCreator provideTestAvatarCreator(
|
||||||
return new TestAvatarCreatorImpl();
|
TestAvatarCreatorImpl testAvatarCreator) {
|
||||||
|
return testAvatarCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
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 org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
@@ -13,21 +11,14 @@ import org.briarproject.briar.api.media.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 androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
import static android.graphics.Bitmap.CompressFormat.JPEG;
|
|
||||||
import static android.graphics.BitmapFactory.decodeStream;
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
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.AndroidUtils.getSupportedImageContentTypes;
|
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
|
||||||
@@ -35,19 +26,16 @@ import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
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.media.MediaConstants.MAX_IMAGE_SIZE;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AttachmentCreationTask {
|
class AttachmentCreationTask {
|
||||||
|
|
||||||
private static Logger LOG =
|
private static final 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 ImageCompressor imageCompressor;
|
||||||
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;
|
||||||
@@ -59,11 +47,11 @@ class AttachmentCreationTask {
|
|||||||
AttachmentCreationTask(MessagingManager messagingManager,
|
AttachmentCreationTask(MessagingManager messagingManager,
|
||||||
ContentResolver contentResolver,
|
ContentResolver contentResolver,
|
||||||
AttachmentCreator attachmentCreator,
|
AttachmentCreator attachmentCreator,
|
||||||
ImageSizeCalculator imageSizeCalculator,
|
ImageCompressor imageCompressor,
|
||||||
GroupId groupId, Collection<Uri> uris, boolean needsSize) {
|
GroupId groupId, Collection<Uri> uris, boolean needsSize) {
|
||||||
this.messagingManager = messagingManager;
|
this.messagingManager = messagingManager;
|
||||||
this.contentResolver = contentResolver;
|
this.contentResolver = contentResolver;
|
||||||
this.imageSizeCalculator = imageSizeCalculator;
|
this.imageCompressor = imageCompressor;
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this.uris = uris;
|
this.uris = uris;
|
||||||
this.needsSize = needsSize;
|
this.needsSize = needsSize;
|
||||||
@@ -115,7 +103,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);
|
is = imageCompressor
|
||||||
|
.compressImage(is, contentType);
|
||||||
contentType = "image/jpeg";
|
contentType = "image/jpeg";
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
AttachmentHeader h = messagingManager
|
AttachmentHeader h = messagingManager
|
||||||
@@ -125,51 +114,4 @@ class AttachmentCreationTask {
|
|||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
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 inSampleSize = 1;
|
|
||||||
while (dimension > MAX_ATTACHMENT_DIMENSION) {
|
|
||||||
inSampleSize *= 2;
|
|
||||||
dimension /= 2;
|
|
||||||
}
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Scaling attachment by factor of " + inSampleSize);
|
|
||||||
Options options = new Options();
|
|
||||||
options.inSampleSize = inSampleSize;
|
|
||||||
if (contentType.equals("image/png"))
|
|
||||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
|
||||||
Bitmap bitmap = decodeStream(is, null, options);
|
|
||||||
if (bitmap == null) throw new IOException();
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import static org.briarproject.briar.api.media.MediaConstants.MAX_IMAGE_SIZE;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AttachmentCreatorImpl implements AttachmentCreator {
|
class AttachmentCreatorImpl implements AttachmentCreator {
|
||||||
|
|
||||||
private static Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(AttachmentCreatorImpl.class.getName());
|
getLogger(AttachmentCreatorImpl.class.getName());
|
||||||
|
|
||||||
private final Application app;
|
private final Application app;
|
||||||
@@ -49,7 +49,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 ImageCompressor imageCompressor;
|
||||||
|
|
||||||
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
|
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
|
||||||
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
|
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
|
||||||
@@ -64,12 +64,12 @@ 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) {
|
ImageCompressor imageCompressor) {
|
||||||
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;
|
this.imageCompressor = imageCompressor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -89,7 +89,7 @@ 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, imageSizeCalculator, id,
|
app.getContentResolver(), this, imageCompressor, id,
|
||||||
uris, needsSize);
|
uris, needsSize);
|
||||||
ioExecutor.execute(() -> task.storeAttachments());
|
ioExecutor.execute(() -> task.storeAttachments());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ public class AttachmentModule {
|
|||||||
return new ImageSizeCalculator(imageHelper);
|
return new ImageSizeCalculator(imageHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
ImageCompressor provideImageCompressor(
|
||||||
|
ImageCompressorImpl imageCompressor) {
|
||||||
|
return imageCompressor;
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
AttachmentDimensions provideAttachmentDimensions(Application app) {
|
AttachmentDimensions provideAttachmentDimensions(Application app) {
|
||||||
return getAttachmentDimensions(app.getResources());
|
return getAttachmentDimensions(app.getResources());
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public interface ImageCompressor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load an image from {@code is}, compress it and return an InputStream
|
||||||
|
* from which the resulting image can be read. The image will be compressed
|
||||||
|
* such that it fits into a message.
|
||||||
|
*
|
||||||
|
* @param is the stream to read the source image from
|
||||||
|
* @param contentType the mimetype of the source image such as "image/jpeg"
|
||||||
|
* as obtained by {@link android.content.ContentResolver#getType(Uri)}
|
||||||
|
* @return a stream from which the resulting image can be read
|
||||||
|
*/
|
||||||
|
InputStream compressImage(InputStream is, String contentType)
|
||||||
|
throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress an image and return an InputStream from which the resulting
|
||||||
|
* image can be read. The image will be compressed such that it fits into
|
||||||
|
* a message.
|
||||||
|
*
|
||||||
|
* @param bitmap the source image
|
||||||
|
* @return a stream from which the resulting image can be read
|
||||||
|
*/
|
||||||
|
InputStream compressImage(Bitmap bitmap) throws IOException;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
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.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
|
import static org.briarproject.briar.api.media.MediaConstants.MAX_IMAGE_SIZE;
|
||||||
|
|
||||||
|
class ImageCompressorImpl implements ImageCompressor {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(ImageCompressorImpl.class.getName());
|
||||||
|
|
||||||
|
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
|
||||||
|
|
||||||
|
private final ImageSizeCalculator imageSizeCalculator;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ImageCompressorImpl(ImageSizeCalculator imageSizeCalculator) {
|
||||||
|
this.imageSizeCalculator = imageSizeCalculator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream compressImage(InputStream is, String contentType)
|
||||||
|
throws IOException {
|
||||||
|
try {
|
||||||
|
Bitmap bitmap =
|
||||||
|
createBitmap(is, contentType, MAX_ATTACHMENT_DIMENSION);
|
||||||
|
return compressImage(bitmap);
|
||||||
|
} finally {
|
||||||
|
tryToClose(is, LOG, WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream compressImage(Bitmap bitmap) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap createBitmap(InputStream is, String contentType, int maxSize)
|
||||||
|
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 inSampleSize = 1;
|
||||||
|
while (dimension > maxSize) {
|
||||||
|
inSampleSize *= 2;
|
||||||
|
dimension /= 2;
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Scaling attachment by factor of " + inSampleSize);
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inSampleSize = inSampleSize;
|
||||||
|
if (contentType.equals("image/png"))
|
||||||
|
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||||
|
Bitmap bitmap = decodeStream(is, null, options);
|
||||||
|
if (bitmap == null) throw new IOException();
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
package org.briarproject.briar.android.test;
|
package org.briarproject.briar.android.test;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.briarproject.briar.api.test.TestAvatarCreator;
|
import org.briarproject.briar.api.test.TestAvatarCreator;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
public class TestAvatarCreatorImpl implements TestAvatarCreator {
|
public class TestAvatarCreatorImpl implements TestAvatarCreator {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TestAvatarCreatorImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public InputStream getAvatarInputStream() {
|
public InputStream getAvatarInputStream() {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package org.briarproject.briar.api.test;
|
package org.briarproject.briar.api.test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public interface TestAvatarCreator {
|
public interface TestAvatarCreator {
|
||||||
@Nullable
|
@Nullable
|
||||||
InputStream getAvatarInputStream();
|
InputStream getAvatarInputStream() throws IOException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,7 +302,13 @@ public class TestDataCreatorImpl implements TestDataCreator {
|
|||||||
AuthorId authorId = c.getAuthor().getId();
|
AuthorId authorId = c.getAuthor().getId();
|
||||||
GroupId groupId = groupFactory.createGroup(AvatarManager.CLIENT_ID,
|
GroupId groupId = groupFactory.createGroup(AvatarManager.CLIENT_ID,
|
||||||
AvatarManager.MAJOR_VERSION, authorId.getBytes()).getId();
|
AvatarManager.MAJOR_VERSION, authorId.getBytes()).getId();
|
||||||
InputStream is = testAvatarCreator.getAvatarInputStream();
|
InputStream is;
|
||||||
|
try {
|
||||||
|
is = testAvatarCreator.getAvatarInputStream();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (is == null) return;
|
if (is == null) return;
|
||||||
Message m;
|
Message m;
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user