Yesterday I came across an interesting question on stackexchange. The idea was to store data in an avatar image, so you could easily send the avatar, which would display you and store your game data.
So for our method we will use the alpha channel and will take the loss of a possible transparency.
KEEP IN MIND: modifying the image, by applying filters or cropping, scaling ... might destroy or corrupt data!
User: bluewhile
Score: 99452
Date: 2014-04-01 12:04:33
We wrap it up in one string: bluewhile 999452 20140401 120433
When decoding, we can use String.split(" "); to get 4 values: name, score, date, time.
It should be straight forward what happens here. Initially we check if all chars are valid, then the array is populated. An additional blank is added to the String, but don't worry it will be deleted later by trim().
At the beginning we copy the image over to make sure it has an alpha channel, then we iterate through all pixels and write our encoded array from left to right, from top to bottom. Pixels that are off our data are set to 255. Also keep in mind that we can't use JPG because it has no alpha channel.
Calling methods should look like:
How can we achieve this?
There are many ways, but I would like to introduce a fairly easy to implement solution. We use the PNG image datatype, where every pixel contains of 4 values: alpha red green blue.ARGB from least significant order bit on |
So for our method we will use the alpha channel and will take the loss of a possible transparency.
Implementation details
- The stored data is limited by the number of pixels we have. We will use 1 pixel for one number, so we have space for width(image) * height(image) number of characters.
- The alpha value of each pixel that is not used will be set to 255 (non-transparent)
- We limit our data input to lowercase letters, numbers and blanks. However you might easily adapt the algorithm for other inputs.
- We will subtract a number between 0 .. 37 from the alpha channel
- The encoding is done like that:
A blank is represented by 0
A number is represented by it's successor, such as 0 will be 1, .. , 5 will be 6, ..., 9 will be 10
A letter is represented by 11 .. 37, so 'a' will be 11, ... , 'z' will be 37.
KEEP IN MIND: modifying the image, by applying filters or cropping, scaling ... might destroy or corrupt data!
Example
Let's say we want to store the given gamedata in the users avatar.User: bluewhile
Score: 99452
Date: 2014-04-01 12:04:33
We wrap it up in one string: bluewhile 999452 20140401 120433
When decoding, we can use String.split(" "); to get 4 values: name, score, date, time.
Original |
Encoded |
First let's look at turning a given String into a int array:
public int[] makeCode(String s) { if (s == null || !s.matches("[A-Za-z0-9 ]+")) // alphanumeric and blank return null; String work = s.toLowerCase().trim() + " "; int[] array = new int[work.length()]; for (int i = 0; i < work.length(); i++) { array[i] = work.charAt(i); if (array[i] == 32) { // blank -> 0 array[i] = 0; } else if (array[i] <= 57) { // number -> 1..9 array[i] -= 47; } else { // letter -> 10..36 array[i] -= 86; } } return array; }
It should be straight forward what happens here. Initially we check if all chars are valid, then the array is populated. An additional blank is added to the String, but don't worry it will be deleted later by trim().
Next up: Encoding
public void encodeAndSave(String s, File input, File output) { System.out.println("Encoding: "+s); try { BufferedImage cache = ImageIO.read(input); BufferedImage b = new BufferedImage(cache.getWidth(), cache.getHeight(), BufferedImage.TYPE_INT_ARGB); b.getGraphics().drawImage(cache, 0, 0, null); int i = 0; int[] data = makeCode(s); System.out.println("Code: " + Arrays.toString(data)); for (int y = 0; y < b.getHeight(); y++) for (int x = 0; x < b.getWidth(); x++) { Color c = new Color(b.getRGB(x, y)); int alpha = (255 - 37) + (i < data.length ? data[i] : 37); int col = (alpha << 24) | (c.getRed() << 16) | (c.getGreen() << 8) | c.getBlue(); b.setRGB(x, y, col); i++; } ImageIO.write(b, "png", output); } catch (Exception e) { e.printStackTrace(); } }
At the beginning we copy the image over to make sure it has an alpha channel, then we iterate through all pixels and write our encoded array from left to right, from top to bottom. Pixels that are off our data are set to 255. Also keep in mind that we can't use JPG because it has no alpha channel.
Decoding
public String decode(File input) { try { BufferedImage b = ImageIO.read(input); String s = ""; for (int y = 0; y < b.getHeight(); y++) { for (int x = 0; x < b.getWidth(); x++) { int alpha = (b.getRGB(x, y) >>> 24); if (alpha != 255 && alpha != 0) { int n = (37 - (255 - alpha)); if (n == 0) { s += " "; } else if (n <= 10) { s += n - 1; } else { s += ((char) (n + 86)); } } } } return s.trim(); } catch (Exception e) { e.printStackTrace(); } return null; }
Decoding is like Encoding, just reversed ;-) Calling methods should look like:
encodeAndSave("bluewhile 999452 20140401120433", new File( "/Users/paul/Downloads/avatar.png"), new File( "/Users/paul/Downloads/avatar2.png")); System.out.println(decode(new File( "/Users/paul/Downloads/avatar2.png")));
Kommentare
Kommentar veröffentlichen