I want to preface this by saying that what is written in this article is purely an exercise of curiosity and simply a result of the question: “what if?”. All of this stems from a project I recently found called wave-share [1], which converts files to audio, which can be picked up by a microphone. It’s a pretty clever idea, and it got me thinking if file data can be converted to images. I’m certainly not the first to try and do funny things with file data, as I’ve seen people putting file data into YouTube videos [2], using Google Spreadsheets to store files [3], and so on.
My setup for this idea was simple: read a file in chunks, and turn every 3 bytes into an RGB value, where #000000
is the lowest value and #FFFFFF
is the highest value. The image would be a square, and the pixels would be written from top-left to bottom-right. I opened up my editor, wrote some Rust, and with the image crate [4] to help me out, I got something working fairly quickly [5].
In my first attempt, it would turn any file into a JPG. Now, some people will already spot the problem here, but I wasn’t this clever yet, so I won’t spoil anything. I used an image of a cat [6], and then to confirm that it was working as intended, I tried to convert the JPG filled with pixels back to the original cat. This didn’t work because - spoiler - JPG compresses the image quite heavily which results in data-loss when converting back to the original cat.
I then switched over from JPG to PNG, and found out that PNG’s don’t suffer from compression problems. On top of that I get an extra byte per pixel as PNG’s also have an alpha channel (so the range extends from #00000000
to #FFFFFFFF
). PNG’s were the way to get a “cat to PNG back to cat”-cycle. This is what that looks like (for this example, I did use a compressed version of the original image):
After running cargo run img compressed-cat.jpg
:
After running cargo run unimg compressed-cat.jpg.png
:
Now the first question - of course - is: is the cat that comes out in the end the same as the cat that I started with? And the answer is: no. The original cat’s byte size is slightly smaller. The reason for this I have yet to explain, but I think it has something to do with the image library and the way it generates images. I could naturally store the original file size in the metadata of the PNG and make sure that when it converts back, that it has the exact same size, but I didn’t feel like making this too complicated considering its intentions.
From the cat image I moved on to simple text files. I converted a simple line of text to a PNG, and here it is:
These 4 pixels spell out “hello world”, and here is where I got curious what would happen to the text when I rotated these 4 pixels, or what would happen if you would invert the colors, grayscale the image, and so on. These are the text changes:
Text | Change | Image |
---|---|---|
hello world | - | ![]() |
rld\nHello wo | 90° | ![]() |
o woHellrld | -90° | ![]() |
rld\n o woHell | 180° | ![]() |
o woHellrld | flip-v | ![]() |
rld\nHello wo | flip-h | ![]() |
```lCCCommm | gray | ![]() |
l߈o | invert | ![]() |
Naturally, this got me thinking: with this Rust code, I can turn painting tools like Gimp or Paint into very poor text editors. It also brought up the question: out of all the PNG images that are out there on the internet, which ones will accidentally spell Shakespeare or any other famous piece of text? Just to show what you have to be looking for, here are some famous and not so famous pieces of text converted to PNG’s:
Of course, I was thinking to myself, the product of the conversion is also a file, so why not cycle over it a couple of times and see what would happen to an image or to text:
# | Image | Text |
---|---|---|
0 | ![]() |
hello world |
1 | ![]() |
![]() |
2 | ![]() |
![]() |
3 | ![]() |
![]() |
4 | ![]() |
![]() |
5 | ![]() |
![]() |
6 | ![]() |
![]() |
7 | ![]() |
![]() |
8 | ![]() |
![]() |
What is interesting to notice is that the file size gradually keeps on increasing and increasing. I’m pretty sure this happens because of the PNG format and its headers taking up space.
Obviously, this is a bad way of storing a file, but it was a fun exercise to obfuscate a file. The full code is available on GitHub [7]. It isn’t at all performance optimized, and for larger files it will likely take a long while for the code to render a PNG. There is an upper bound for PNG’s of 231-1 px, meaning this script can be used to store a file of up to 4 x 231-1 bytes, or roughly ~1.9 GB. I’m curious to see what happens if I go over, and I will definitely try to experiment more and come up with other “what if?”-projects in the future.