Introduction
In our previous discussion, we explored creating custom QR codes. Building upon that foundation, this article demonstrates how to transform images into ASCII art representations where characters replace pixel colors, with each character's color matching the original pixel values.
Required Dependencies
For image manipulation, we'll utilize PIL, imageio, and image2gif libraries. OpenCV-Python handles pixel processing operations. NumPy provides support for array operations and mathematical functions. Install these packages:
pip install pillow
pip install imageio
pip install numpy
pip install opencv-python
pip install images2gif
During installation, you might encounter compatibility issues:
- When importing cv2 fails due to missing DLLs, it's typically caused by version mismatches between Python and OpenCV. Specify the compatible version during installation:
pip install opencv-python==3.4.1.15 - If images2gif fails to import writeGif interface, modify the source code by adding periods after the two 'from' statements in images2gif.py:
from .images2gif import readGif as readGif
from .images2gif import writeGif as writeGif
Understanding Animated GIF Processing
GIF files essentially combine multiple images into a single animated sequence. Creating ASCII art anmiations involves converting each frame into charatcer-based representation and reassembling them.
The process involves five key steps:
- Obtain a source GIF file
- Decompose the GIF into individual PNG frames
- Process each frame into ASCII art format
- Recombine processed frames into a new GIF
- Save the resulting animated ASCII art
Converting Between GIF and PNG Formats
Extracting Frames from GIF Files
Three approaches can decompose GIF animations:
Method 1 - Using Image Library:
def extract_frames_method1():
gif_image = Image.open(source_gif_path)
try:
frame_index = 0
while True:
current_frame = gif_image.tell()
gif_image.save(output_directory + "/frame_%d.png" % current_frame)
gif_image.seek(current_frame + 1)
frame_index += 1
except EOFError:
pass
Method 2 - Palette-based Extraction:
def extract_frames_method2():
try:
source_image = Image.open(source_gif_path)
except IOError:
print("Cannot load", source_gif_path)
return
frame_count = 0
palette_data = source_image.getpalette()
try:
while True:
source_image.putpalette(palette_data)
rgba_frame = source_image.convert('RGBA')
new_canvas = Image.new("RGBA", source_image.size)
new_canvas.paste(rgba_frame)
new_canvas.save(output_directory + "\\frame_%d.png" % frame_count)
frame_count += 1
source_image.seek(source_image.tell() + 1)
except EOFError:
pass
Method 3 - Using imageio:
def extract_frames_method3():
frame_list = imageio.mimread(source_gif_path)
for index, frame_data in enumerate(frame_list):
array_data = np.asarray(frame_data)
imageio.imwrite(output_directory + "\\frame_%d.png" % index, array_data)
Reconstructing GIF from Individual Frames
Two reconstruction methods:
Method 1:
def create_gif_method1():
frame_numbers = sorted([int(os.path.splitext(filename)[0]) for filename in os.listdir(frames_directory)])
frame_images = []
for number in frame_numbers:
file_path = frames_directory + "/" + str(number) + '.png'
frame_images.append(imageio.imread(file_path))
imageio.mimsave(output_gif_path, frame_images, 'GIF', duration=0.1)
Method 2:
def create_gif_method2():
frame_numbers = sorted([int(os.path.splitext(filename)[0]) for filename in os.listdir(frames_directory)])
image_paths = []
for number in frame_numbers:
path = frames_directory + "/" + str(number) + '.png'
image_paths.append(path)
processed_frames = []
for image_location in image_paths:
loaded_image = Image.open(image_location)
rgb_converted = loaded_image.convert("RGB")
array_format = np.array(rgb_converted)
processed_frames.append(array_format)
writeGif(output_gif_path, processed_frames, duration=0.1, subRectangles=False)
Direct Memory Processing Approach
To optimize performance, process frames directly in memory rather than saving intermediate files:
def convert_gif_to_ascii(input_gif_path):
ascii_frames = []
frame_data = imageio.mimread(input_gif_path)
for frame in frame_data:
height, width, channels = frame.shape
output_canvas = frame * 0 + 255
grayscale_version = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
for row in range(0, height, 6):
for col in range(0, width, 6):
pixel_value = grayscale_version[row, col]
blue, green, red, alpha = frame[row, col]
char_index = int(((len(character_set) - 1) * pixel_value) / 256)
selected_char = character_set[char_index]
if alpha != 0:
cv2.putText(output_canvas, selected_char, (col, row),
cv2.FONT_HERSHEY_COMPLEX, 0.3,
(int(blue), int(green), int(red), int(alpha)))
ascii_frames.append(output_canvas)
base_filename = os.path.splitext(os.path.basename(input_gif_path))[0]
result_path = os.getcwd() + '/output/' + base_filename + "_ascii.gif"
imageio.mimsave(result_path, ascii_frames, 'GIF', duration=0.1)
ASCII Art Animated QR Codes
Building upon standard GIF processing techniques, ASCII art animated QR codes can be generated following similar principles.