Skip to main content
8 of 8
added 678 characters in body
maraca
  • 9.6k
  • 3
  • 28
  • 51

Answers

  1. "Three may keep a secret, if two of them are dead." (Benjamin Franklin)
  2. 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++;
            }
        }
    }