Building InstaResizerBot: Scalable Image Resizing for Instagram on Telegram Infrastructure
When sharing non-square photos on Instagram, the platform frequently crops critical parts of portrait or landscape compositions. While dedicated mobile apps exist to pad images with borders, they often introduce unwanted compression artifacts, force user registration, or lock simple features behind paywalls.
To solve this problem, I built @InstaResizerBot, a lightweight Python-based Telegram utility designed to process images instantly in memory without persistent file storage. In this article, I will detail the underlying architecture, Pillow image processing pipeline, and Telegram state management logic I engineered for this standalone utility.
Architectural Overview and Request Lifecycle
I engineered InstaResizerBot as a stateless, event-driven service using python-telegram-bot. The primary objective was absolute data privacy and high processing speed. To achieve this, raw image payloads are streamed into memory, processed via Pillow (PIL), and returned directly as byte streams without touching disk.
[ User Action ]
│
├──> 1. Select Mode via Callback ('/white', '/black', '/blur', '/transparent')
│ └── Store in context.user_data['action']
│
└──> 2. Send Image (Compressed Photo or Document File)
│
▼
[ Telegram Dispatcher ] ──> Handled by instaresizerbot_handle_message
│
├──> Download as Bytearray in Memory (io.BytesIO)
├──> Call core.process_image(file_bytes, command)
│ ├── Calculate 1.01 Aspect Ratio Bounds & Dynamic Canvas
│ ├── Apply Background Transformation (Solid Color, Gaussian Blur, RGBA)
│ └── Center-paste Original Media Matrix
│
└──> Return BytesIO stream direct to Telegram API (reply_photo)
By keeping raw binary buffers isolated within RAM via io.BytesIO, the system minimizes disk I/O latency and guarantees that user images are discarded immediately after response dispatch.
Image Processing & Canvas Engineering
The core transformation logic resides in instaresizerbot/core.py. Instagram's automated cropping algorithms rely on standard aspect ratio boundaries. To prevent aggressive automatic cropping while keeping the original image intact, I applied a 1.01 dynamic offset logic to calculate bounding box dimensions.
Dynamic Aspect Ratio Bounds & Scaling Math
from io import BytesIO
from PIL import Image, ImageFilter
def process_image(image_bytes: bytes, background_command: str) -> BytesIO:
original_image = Image.open(BytesIO(image_bytes))
# Calculate target bounding box dimensions
width, height = original_image.size
new_size = max(width, height)
if int(width) > int(height):
new_width = int(new_size * 1.01)
new_height = new_size
else:
new_height = int(new_size * 1.01)
new_width = new_size
# Resize image matrix to expanded canvas bounds
resized_image = original_image.resize((new_width, new_height))
This math dynamically expands the primary axis by 1% (1.01), ensuring that landscape and portrait images fit inside a optimal square canvas without distortion.
Background Generation Algorithms
Depending on user choice, the canvas background is synthesized using one of three strategies:
-
Solid RGB Color Canvas (
/white,/black) Creates a standard RGB canvas pre-filled with white ((255, 255, 255)) or black ((0, 0, 0)). -
Transparent RGBA Canvas (
/transparent) Creates an alpha channel canvas (RGBA,(0, 0, 0, 0)) for users requiring PNG output with transparent margins. -
Gaussian Blur Backdrop (
/blur) Applies a 10-pixel radius Gaussian blur (ImageFilter.GaussianBlur(radius=10)) across the scaled base image to serve as a matching contextual background.
if background_command == '/white':
final_image = Image.new('RGB', resized_image.size, (255, 255, 255))
elif background_command == '/black':
final_image = Image.new('RGB', resized_image.size, (0, 0, 0))
elif background_command == '/blur':
blurred_image = resized_image.filter(ImageFilter.GaussianBlur(radius=10))
final_image = Image.new('RGB', blurred_image.size)
if blurred_image.mode == 'RGBA':
final_image.paste(blurred_image, (0, 0), blurred_image)
else:
final_image.paste(blurred_image, (0, 0))
elif background_command == '/transparent':
final_image = Image.new('RGBA', resized_image.size, (0, 0, 0, 0))
Centering and Alpha Channel Handling
To complete the transformation, the original image is centered on the generated canvas using mid-point calculations:
$$\text{paste\_x} = \lfloor \frac{\text{new\_width} - \text{width}}{2} \rfloor, \quad \text{paste\_y} = \lfloor \frac{\text{new\_height} - \text{height}}{2} \rfloor$$
If the incoming image contains transparency (mode RGBA), it is pasted using itself as an alpha mask to prevent blocky black artifact rendering around transparent edges.
paste_x = int((new_width - width) / 2)
paste_y = int((new_height - height) / 2)
if original_image.mode == 'RGBA':
final_image.paste(original_image, (paste_x, paste_y), original_image)
else:
final_image.paste(original_image, (paste_x, paste_y))
output_buffer = BytesIO()
if background_command == '/transparent':
final_image.save(output_buffer, format="PNG")
else:
final_image.save(output_buffer, format="JPEG")
output_buffer.seek(0)
return output_buffer
Telegram Handler Implementation
In instaresizerbot/handlers.py, I structured command dispatching to streamline the interactive user session using inline keyboards and contextual state persistence.
def register_instaresizerbot_handlers(dispatcher):
dispatcher.add_handler(CommandHandler('start', instaresizerbot_handle_start_message))
dispatcher.add_handler(CallbackQueryHandler(instaresizerbot_transform_callback, pass_user_data=True))
dispatcher.add_handler(MessageHandler(Filters.text, instaresizerbot_dontunderstand, pass_user_data=True, pass_chat_data=True))
dispatcher.add_handler(MessageHandler(Filters.photo | Filters.document, instaresizerbot_handle_message, pass_user_data=True, pass_chat_data=True))
dispatcher.add_handler(ChatMemberHandler(user_left, ChatMemberHandler.MY_CHAT_MEMBER))
Handling Uncompressed & Compressed Uploads
Users often send images as compressed Telegram photos or uncompressed raw documents. The handler inspects both message attributes to extract the highest-resolution available file:
if update.message.photo:
photo = update.message.photo[-1]
file = update.message.bot.get_file(photo.file_id)
elif update.message.document:
document = update.message.document
if document.file_name.endswith(('.jpg', '.jpeg', '.png', '.bmp', '.heic', '.heif', '.tiff', '.webp')):
file = update.message.bot.get_file(document.file_id)
else:
update.message.reply_text("File format not supported. Please upload a standard image file.")
return
Memory Management and Security Considerations
- Zero Disk Persistence: All byte array streams operate within transient RAM allocations (
io.BytesIO). Explicit call tooutput_buffer.close()insidefinallyblocks ensures garbage collection reclaims image buffers immediately. - Strict MIME & Extension Whitelisting: File document uploads pass through strict extension validation (
.jpg,.jpeg,.png,.webp,.bmp,.tiff), guarding against arbitrary payload processing. - Session State Isolation: User selection choices are scopes to
context.user_data, preventing state leakage across parallel active user sessions.