from PIL import Image from io import BytesIO def tile_image_row(image): width, height = image.size new_width = width * 2 new_height = height result = Image.new(image.mode, (new_width, new_height)) result.paste(image, (0, 0)) result.paste(image, (width, 0)) return result def tile_image_column(image): width, height = image.size new_width = width new_height = height * 2 result = Image.new(image.mode, (new_width, new_height)) result.paste(image, (0, 0)) result.paste(image, (0, height)) return result def tile_image_grid(image): temp_image = tile_image_column(image) result = tile_image_row(temp_image) return result def tile_image_vert_brick(image): width, height = image.size new_width = width * 2 new_height = height *2 result = Image.new(image.mode, (new_width, new_height)) # First column: Doubled image result.paste(image, (0, 0)) result.paste(image, (0, height)) # Second column: Shifted doubled image top_half = image.crop((0, 0, width, height // 2)) bottom_half = image.crop((0, height // 2, width, height)) shifted_image = Image.new(image.mode, (width, height*2)) shifted_image.paste(bottom_half, (0, 0)) shifted_image.paste(top_half, (0, height+height // 2)) shifted_image.paste(image, (0, height // 2)) result.paste(shifted_image, (width, 0)) return result def tile_image_horiz_brick(image): width, height = image.size new_width = width * 2 new_height = height *2 result = Image.new(image.mode, (new_width, new_height)) # First column: Doubled image result.paste(image, (0, 0)) result.paste(image, (width, 0)) # Second column: Shifted doubled image left_half = image.crop((0, 0, width//2, height)) right_half = image.crop((width//2, 0, width, height)) shifted_image = Image.new(image.mode, (width*2, height)) shifted_image.paste(right_half, (0, 0)) shifted_image.paste(left_half, (width+width // 2, 0)) shifted_image.paste(image, (width//2, 0)) result.paste(shifted_image, (0, height)) return result def crop_along_line_to_gif(img, width, height, start_x, start_y, end_x, end_y, num_crops=200, duration=80, loop=0, jpeg_quality=85, scale_factor=0.5): # Convert to JPEG in-memory if needed if jpeg_quality is not None: img = img.convert("RGB") # Ensure RGB mode with BytesIO() as buffer: img.save(buffer, format="JPEG", quality=jpeg_quality) buffer.seek(0) img = Image.open(buffer) img.load() # Apply scaling factor to image dimensions and coordinates if scale_factor != 1.0: img = img.resize((int(img.width * scale_factor), int(img.height * scale_factor)), Image.Resampling.LANCZOS) width = int(width * scale_factor) height = int(height * scale_factor) start_x = int(start_x * scale_factor) start_y = int(start_y * scale_factor) end_x = int(end_x * scale_factor) end_y = int(end_y * scale_factor) img_width, img_height = img.size x_step = (end_x - start_x) / (num_crops - 1) y_step = (end_y - start_y) / (num_crops - 1) cropped_images = [] for i in range(num_crops): x = start_x + i * x_step y = start_y + i * y_step if 0 <= x < img_width and 0 <= y < img_height: # Calculate crop area coordinates left = x top = y right = x + width bottom = y + height cropped_img = img.crop((left, top, right, bottom)) # Apply scaling if scale_factor is not 1.0 cropped_images.append(cropped_img) # Create a BytesIO buffer to store the GIF gif_buffer = BytesIO() # Save the GIF to the buffer first_image = cropped_images[0] first_image.info["duration"] = duration first_image.save( gif_buffer, save_all=True, append_images=cropped_images[1:], loop=loop, format="GIF" # Explicitly specify GIF format ) # Rewind the buffer to the beginning gif_buffer.seek(0) # Return the buffer containing the GIF return gif_buffer def tile_image(image, tile_type="grid"): """Tiles an image based on the specified type using Pillow. Args: image: The image to tile (PIL Image object). tile_type: The type of tiling ("row", "column", "grid", "vertical_brick"). Returns: The tiled image (PIL Image object). """ if tile_type == "row": return tile_image_row(image) elif tile_type == "column": return tile_image_column(image) elif tile_type == 'grid': # Grid return tile_image_grid(image) elif tile_type == "vertical_brick": return tile_image_vert_brick(image) elif tile_type == "horizontal_brick": return tile_image_horiz_brick(image) def tile_and_convert(image, tile_type='grid'): img_width, img_height = image.size assert img_width < 2600, "Please only upload 1x1, 2x1, 1x2 or 2x2 tiles downloaded from ideogram.ai" if img_width > 1280: scale_factor = 0.23 else: scale_factor = 0.46 tiled_image = tile_image(image, tile_type) start_x, start_y, end_x, end_y = 0,0,0,0 # default values if tile_type == 'row': end_x = img_width elif tile_type == 'column': end_y = img_height else: end_x = img_width end_y = img_height return crop_along_line_to_gif(tiled_image, img_width, img_height, start_x, start_y, end_x, end_y, jpeg_quality=80, scale_factor=scale_factor)