Storing an Entire, Readable Book in One Image.

Will libraries soon start looking like art galleries?

Kyle Huang
7 min readSep 5, 2024
Essay to image

Since the start of recorded history, human literature has been kept in some form of writing. However, I have created a method of storing writing in an image, managing to put a 528-page book into a single 899 by 899-pixel image. Here’s how it works:

An image is merely a large collection of pixels; each pixel is a square of color. Color can be represented in RGB format: red, green, and blue. Each of these colors has an integer value from 0 to 255. This number dictates how much of that color is in the final color. With this information, we now know that we can create an image if we have a set of numbers. But how can we convert text to numbers? For that, I used ASCII.

A reference chart that shows characters and their corresponding numerical value

ASCII, an acronym for American Standard Code for Information Interchange, assigns numbers to characters. The above image shows that letters and punctuation marks have assigned numbers. For example, the letter “T” has a value of 84 and the exclamation mark has a value of 33. This isn’t the most expansive character encoding standard, but it was enough for this project.

When we get input text, we can split it into chunks of three characters. For example, “Hello, World!” becomes “Hel”, “lo,”, “ Wo”, “rld”, and the incomplete set “!”. Now, we can use ASCII to change these chunks of text into chunks of numbers. If a set has less than 3 characters, we fill it in with zeroes. Now, our chunks of text become (72, 101, 108), (108, 111, 44), (32, 87, 111), (114, 108, 100), and (33, 0, 0). As you can see, we have converted the text string “Hello, World!” to sets of numbers that can be used as RGB triples.

Implementing Encoder in Python

I have written a simple implementation in Python. If you’d like, you can follow along.

  1. Download the necessary Python package. We’ll use the Pillow Python library for this, as it will let us output images.
pip install pillow

2. Create a new Python file named “encoder.py” in your code editor of choice.

3. Start by importing libraries at the top of your Python script. The Image module will let us manipulate images and os is used for interacting with the operating system. We’ll use it to manipulate files.

from PIL import Image
import os

4. Make a function to read the text file. For this example, I have a .txt file of Adam Smith’s “Wealth of Nations”. It’s a 528-page book with roughly 2.4 million characters. This function will take in an input of a filename and will return the whole thing as a string.

def readFile(filename):
with open(filename, 'r', encoding='utf-8') as file:
return file.read() #This function returns the contents of the txt file

5. Convert the text to RGB triples.

def convertToRGB(text):
#Split into chunks of 3
chunks = [text[i:i+3] for i in range(0, len(text), 3)]
#Make an empty list for RGB values
rgbValues = []
#Convert to ASCII
for chunk in chunks:
#First fill incomplete chunks with null
chunk = chunk.ljust(3, '\0')
#Convert characters to ASCII with ord function
#Append into the rgbValues list
rgbValues.append([ord(char) for char in chunk])
#Function returns rgbValues list
return rgbValues

6. Make a function to create the image.

def createImage(rgbChunks, imageSize):
img = Image.new('RGB', imageSize)
pixels = img.load()
#Make the image square
width, height = imageSize

#This loop loops through every location and sets it to appropriate color
for y in range(height):
for x in range(width):
index = y * width + x
if index < len(rgbChunks):
pixels[x, y] = tuple(rgbChunks[index])
else:
pixels[x, y] = (0, 0, 0)

img.save('output.png')

7. Write the main function that uses all the previous functions we wrote.

def main():
#My input file is Wealth of Nations.txt.
inputFile = 'Wealth of Nations.txt'

text = readFile(inputFile)
rgbChunks = convertToRGB(text)

numPixels = len(rgbChunks)
#Side length of a square is square root of its area
sideLength = int(numPixels**0.5)
imageSize = (sideLength, sideLength)

createImage(rgbChunks, imageSize)
#Output a finish notification
print("The image was saved.")

8. Call the main function.

#This code allows the program to be imported into other ones
if __name__ == '__main__':
main()

That’s it! Here’s the entire encoder.py program put together:

from PIL import Image
import os

def readFile(filename):
with open(filename, 'r', encoding='utf-8') as file:
return file.read()

def convertToRGB(text):
#Split into chunks of 3
chunks = [text[i:i+3] for i in range(0, len(text), 3)]
#Make an empty list for RGB values
rgbValues = []
#Convert to ASCII
for chunk in chunks:
#First fill incomplete chunks with null
chunk = chunk.ljust(3, '\0')
#Convert characters to ASCII with ord function
#Append into the rgbValues list
rgbValues.append([ord(char) for char in chunk])
#Function returns rgbValues list
return rgbValues

def createImage(rgbChunks, imageSize):
img = Image.new('RGB', imageSize)
pixels = img.load()
#Make the image square
width, height = imageSize

#This loop loops through every location and sets it to appropriate color
for y in range(height):
for x in range(width):
index = y * width + x
if index < len(rgbChunks):
pixels[x, y] = tuple(rgbChunks[index])
else:
pixels[x, y] = (0, 0, 0)

img.save('output.png')

def main():
#My input file is Wealth of Nations.txt.
inputFile = 'Wealth of Nations.txt'

text = readFile(inputFile)
rgbChunks = convertToRGB(text)

numPixels = len(rgbChunks)
#Side length of a square is square root of its area
sideLength = int(numPixels**0.5)
imageSize = (sideLength, sideLength)

createImage(rgbChunks, imageSize)
#Output a finish notification
print("The image was saved.")

if __name__ == '__main__':
main()

If you run this program in the same folder as your target text file, an output image will be generated in that folder.

Programming the Decoder

Now, what use is an encoder if it can’t be decoded back to its original state? Here’s how to program decoder.py.

  1. We’ll use the Pillow library again to read the image.
from PIL import Image

def loadImage(filename):
img = Image.open(filename)
return img

2. Now, we extract the RGB values.

def extractRGB(img):
pixels = img.load()
width, height = img.size
rgbValues = []
#Loop through every pixel
for y in range(height):
for x in range(width):
rgbValues.append(pixels[x, y])

return rgbValues

3. Convert RGB back to text.

def convertToText(rgbValues):
text=""
for rgb in rgbValues:
chunk = ''.join(chr(value) for value in rgb)
text += chunk
return text

4. Put the string into a file.

def saveText(filename, text):
with open(filename, 'w', encoding='utf-8') as file:
file.write(text)

5. Finally, write and run the main function.

def main():
imageFile = 'output.png'
textFile = 'decoded.txt'
img = loadImage(imageFile)
rgbValues = extractRGB(img)
text = conertToText(rgbValues)
saveText(textFile, text)

#Notification of success
print("The text was decoded.")

if __name__ == '__main__':
main()

That’s all. Here’s the finished decoding program:

from PIL import Image

def loadImage(filename):
img = Image.open(filename)
return img

def extractRGB(img):
pixels = img.load()
width, height = img.size
rgbValues = []
#Loop through every pixel
for y in range(height):
for x in range(width):
rgbValues.append(pixels[x, y])

return rgbValues

def convertToText(rgbValues):
text=""
for rgb in rgbValues:
chunk = ''.join(chr(value) for value in rgb)
text += chunk
return text

def saveText(filename, text):
with open(filename, 'w', encoding='utf-8') as file:
file.write(text)

def main():
imageFile = 'output.png'
textFile = 'decoded.txt'

img = loadImage(imageFile)
rgbValues = extractRGB(img)
text = convertToText(rgbValues)
saveText(textFile, text)

#Notification of success
print("The text was decoded.")

if __name__ == '__main__':
main()

Image Gallery

“Wealth of Nations” by Adam Smith:

An entire 528-page book in a 899x899 image
This is 1,185 KB while the text file was 2,401 KB.

“Bee Movie Script” by Jerry Seinfeld, Spike Feresten, Barry Marder, and Andy Robin

The Bee Movie Script in one image
It’s only 42 KB and 128 x 128 pixels.

Use Case

This is a cool thing to play around with, but is there any actual use? While the encoded images can be smaller than the text file, the compression pales in comparison to algorithms like DEFLATE. This means that although it’s novel to see an entire book being stored in a .png file, it’s not a useful form of text compression. However, perhaps others out there can find use of this in some other ways, like communicating with their friends in a way others cannot understand.

Playground

If you don’t want to go through the trouble of using my code and would rather opt for an easier, user-friendly experience, you’re in luck. I created a website that does the same thing. Although it may not be as efficient in compression as the Python program mentioned in earlier sections of this article, it’s simple to use and has an aesthetically pleasing user interface. Note that the images in the image gallery will not be compatible with the website. If you want to decode those, you’ll need the decoder.py program.

Final Thoughts

This encoding project was quite an enjoyable experience. It was a valuable learning experience, and it gave me an opportunity to write my first article on Medium. If you found this article interesting, a clap or a comment would be greatly appreciated. Thanks for reading!

--

--