Skip to content

Commit

Permalink
Optimize encoding. Only required number of pixels is now grabbed from…
Browse files Browse the repository at this point in the history
… bitmap.

Set 0x00FFFFFF to be max length to attempt to decode. Failsafe for unencoded images with a decodable length.
  • Loading branch information
aksel committed Oct 3, 2016
1 parent 541541e commit 561ead1
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,20 @@ public EncodeTask(AsyncResponse<SteganographyParams> delegate) {
protected SteganographyParams execute(SteganographyParams steganographyParams) {

Bitmap bitmap = BitmapUtils.decodeFile(steganographyParams.getFilePath());
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int numberOfPixels = w * h;

byte[] data = steganographyParams.getMessage().getBytes();

int requiredLength = data.length * 8 + 32;

if (requiredLength > numberOfPixels) {
throw new IllegalArgumentException("Message is too long to fit into pixels.");
}

int[] encodedPixels = SteganographyUtils.encode(
BitmapUtils.getPixels(bitmap),
BitmapUtils.getPixels(bitmap, requiredLength),
steganographyParams.getMessage()
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@

public class BitmapUtils {

/**'
/**
* '
* Decodes a bitmap from file. If the bitmap is immutable, it is converted.
*
* @param filePath File
* @return Mutable bitmap.
*/
Expand All @@ -30,7 +32,7 @@ public static Bitmap decodeFile(String filePath) {
/**
* Bitmaps must be mutable in order for setPixels to works.
* http://stackoverflow.com/a/9194259
*
* <p>
* Converts a immutable bitmap to a mutable bitmap. This operation doesn't allocates
* more memory that there is already allocated.
*
Expand All @@ -56,7 +58,7 @@ private static Bitmap convertToMutable(Bitmap imgIn) {
//Copy the byte to the file
//Assume source bitmap loaded using options.inPreferredConfig = Config.ARGB_8888;
FileChannel channel = randomAccessFile.getChannel();
MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, imgIn.getRowBytes()*height);
MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, imgIn.getRowBytes() * height);
imgIn.copyPixelsToBuffer(map);
//recycle the source bitmap, this will be no longer used.
imgIn.recycle();
Expand Down Expand Up @@ -91,10 +93,31 @@ public static int[] getPixels(Bitmap bitmap) {
return pixels;
}

public static int[] getPixels(Bitmap bitmap, int requiredLength) {
int[] bounds = getMinimumAreaBounds(requiredLength, bitmap.getWidth());

int[] pixels = new int[bounds[0] * bounds[1]];
bitmap.getPixels(pixels, 0, bounds[0], 0, 0, bounds[0], bounds[1]);

return pixels;
}

public static void setPixels(Bitmap bitmap, int[] pixels) {
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] bounds = getMinimumAreaBounds(pixels.length, bitmap.getWidth());
bitmap.setPixels(pixels, 0, bounds[0], 0, 0, bounds[0], bounds[1]);
}

bitmap.setPixels(pixels, 0, w, 0, 0, w, h);
/**
* Gets the area required for numberOfBytes to fit into an image width width of imageWidth.
* @param requiredLength Number of pixels required for message to fit in image.
* @param imageWidth Width of image.
* @return [width, height]
*/
private static int[] getMinimumAreaBounds(int requiredLength, int imageWidth) {
if (requiredLength < imageWidth) {
return new int[] {requiredLength, 1};
} else {
return new int[] {imageWidth, (int) Math.ceil(requiredLength / imageWidth) };
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package com.akseltorgard.steganography.utils;

import android.util.Log;

public class SteganographyUtils {

/**
* Max length to attempt to decode.
*/
private static final int MAX_DECODABLE_LENGTH = 0x00FFFFFF;

private static final int LSB = 1;

/**
Expand All @@ -13,14 +20,9 @@ public class SteganographyUtils {
* @return Pixels encoded with message.
*/
public static int[] encode(int[] pixels, String message) {
Log.d("Steganography.Encode", "Encode Begin");
byte[] data = message.getBytes();

int requiredLength = data.length * 8 + 32;

if (requiredLength > pixels.length) {
throw new IllegalArgumentException("Message is too long to fit into pixels.");
}

//Insert length into data
{
byte[] dataWithLength = new byte[4 + data.length];
Expand Down Expand Up @@ -49,6 +51,8 @@ public static int[] encode(int[] pixels, String message) {
}
}

Log.d("Steganography.Encode", "Encode End");

return pixels;
}
/**
Expand All @@ -58,19 +62,26 @@ public static int[] encode(int[] pixels, String message) {
* @return Decoded data.
*/
public static String decode(int[] pixels) {
Log.d("Steganography.Decode", "Decode Begin");

int pixelIndex = 0;

//Decode length;
int length = decodeBitsFromPixels(pixels, 32, pixelIndex);
pixelIndex += 32;

if (length < 0 || length > MAX_DECODABLE_LENGTH) {
throw new IllegalArgumentException("Failed to decode. Are you sure the image is encoded?");
}

byte[] data = new byte[length];

for (int byteIndex = 0; byteIndex < length; byteIndex++, pixelIndex+=8) {
data[byteIndex] = (byte) decodeBitsFromPixels(pixels, 8, pixelIndex);
}

Log.d("Steganography.Decode", "Decode End");

return new String(data);
}

Expand Down

0 comments on commit 561ead1

Please sign in to comment.