Answers
- "Three may keep a secret, if two of them are dead." (Benjamin Franklin)
- A key (and maybe the kite can also be viewed as on the line).
Code (Java)
The code is pretty much self-explanatory. Images are converted to an integer array (containing ones and zeroes) from the channel last bits which is then used for decoding. The helper function getAsInt() returns the bits in a range as int which is more or less all that's needed to decode the messages. There is no header verification: starts with [0, 0] for the first message and [1, 0] for the 2nd message.
package so;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class App {
public static void main(String[] args) {
System.out.println(decode1(toChannelLastBits(readImage("MBlXyTSp.png"))));
showImage(decode2(toChannelLastBits(readImage("mCeETXDs.png"))));
}
private static BufferedImage readImage(String path) {
try {
return ImageIO.read(new File(path));
} catch (IOException e) {
throw new RuntimeException(e); // not checked = needs no try/catch
}
}
private static int[] toChannelLastBits(BufferedImage img) {
int[] bits = new int[img.getWidth() * img.getHeight() * 3];
for (int y = 0, pos = 0; y < img.getHeight(); y++) {
for (int x = 0; x < img.getWidth(); x++) {
int pixel = img.getRGB(x, y);
bits[pos++] = (pixel >> 16) & 1; // red
bits[pos++] = (pixel >> 8) & 1; // green
bits[pos++] = pixel & 1; // blue
}
}
return bits;
}
private static String decode1(int[] bits) {
int len = getAsInt(bits, 2, 18) >> 3;
char[] message = new char[len];
for (int i = 0; i < len; i++)
message[i] = (char) getAsInt(bits, 18 + (i << 3), 26 + (i << 3));
return new String(message);
}
private static BufferedImage decode2(int[] bits) {
int w = getAsInt(bits, 2, 18);
int h = getAsInt(bits, 18, 34);
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
for (int y = 0, i = 34; y < h; y++)
for (int x = 0; x < w; x++)
img.setRGB(x, y, -bits[i++]); // -1 = 0xfff... => white
return img;
}
private static int getAsInt(int[] bits, int from, int to) {
int r = 0;
for (int i = from; i < to; i++)
r = (r << 1) | bits[i];
return r;
}
private static void showImage(BufferedImage img) {
ImageIcon icon = new ImageIcon(img);
JLabel label = new JLabel(icon);
JFrame frame = new JFrame("Image Display");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(label);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Bonus (Encoder)
public static void encode(BufferedImage img, int[] bits) {
if (bits.length > img.getWidth() * img.getHeight() * 3)
throw new RuntimeException("Image too small / message too long");
for (int i = 0, x = 0, y = 0; i < bits.length; i += 3) {
int pixel = img.getRGB(x, y);
if (bits[i] != ((pixel >> 16) & 1))
pixel ^= 0x10000;
if (i + 1 < bits.length && bits[i + 1] != ((pixel >> 8) & 1))
pixel ^= 0x100;
if (i + 2 < bits.length && bits[i + 2] != (pixel & 1))
pixel ^= 1;
img.setRGB(x, y, pixel);
if (++x >= img.getWidth()) {
x = 0;
y++;
}
}
}