# Storing an Entire, Readable Book in One Image.

## Will libraries soon start looking like art galleries?

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.

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 Imageimport 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 onesif __name__ == '__main__':  main()`

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

`from PIL import Imageimport osdef 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 rgbValuesdef 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 Imagedef 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 Imagedef loadImage(filename):    img = Image.open(filename)    return imgdef 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 rgbValuesdef convertToText(rgbValues):    text=""    for rgb in rgbValues:        chunk = ''.join(chr(value) for value in rgb)        text += chunk    return textdef 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()`

# 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!