thomasasfk / sd-webui-aspect-ratio-helper

Simple extension to easily maintain aspect ratio while changing dimensions. Install via the extensions tab on the AUTOMATIC1111 webui.
https://github.com/thomasasfk/sd-webui-aspect-ratio-helper.git
415 stars 66 forks source link

Feature Request: Randomize Aspect Ratio #81

Open ArchAngelAries opened 2 months ago

ArchAngelAries commented 2 months ago

I was wondering if it could be possible to add a feature where a user could select randomize Aspect Ratio to where a user could randomly generate images of varying dimensions without having to change the Width/Height/AR.

I'm kinda envisioning a tick box or two that allows:

  1. While selected, randomizes Aspect Ratio per generation while still locking in user defined Width/Height
  2. While selected, randomly flips the width/height per generation to alternate between portrait and landscape.

Not sure if it is very difficult to implement, but thought it'd be a cool addition to AR Helper. Could be really useful for people who like to utilize Dynamic Prompts and run Generate Forever for a while. Could net really unique outputs.

ArchAngelAries commented 2 months ago

I went ahead and created a custom script to be saved in A1111 script folder that can do this, so I guess never mind? Or you can implement this if you want somehow.

import modules.scripts as scripts
import gradio as gr
import random

from modules import processing
from modules.processing import Processed
from modules.shared import opts, cmd_opts, state

class Script(scripts.Script):
    def title(self):
        return "Randomize Aspect Ratio and Layout"

    def ui(self, is_img2img):
        with gr.Group():
            with gr.Row():
                is_enabled = gr.Checkbox(label="Enable Randomize Aspect Ratio", value=False)
            with gr.Row():
                min_dim = gr.Slider(minimum=256, maximum=2048, step=64, label="Minimum Dimension", value=512)
                max_dim = gr.Slider(minimum=256, maximum=2048, step=64, label="Maximum Dimension", value=1024)

        return [is_enabled, min_dim, max_dim]

    def run(self, p, is_enabled, min_dim, max_dim):
        if not is_enabled:
            return processing.process_images(p)

        original_batch_size = p.batch_size
        p.batch_size = 1  # Process one image at a time to allow different sizes

        all_processed = []
        for _ in range(original_batch_size):
            aspect_ratios = [(1, 1), (4, 3), (3, 4), (16, 9), (9, 16)]
            aspect_ratio = random.choice(aspect_ratios)

            # Randomly choose between portrait and landscape
            if random.choice([True, False]):
                width_ratio, height_ratio = aspect_ratio
            else:
                height_ratio, width_ratio = aspect_ratio

            # Calculate dimensions
            max_width = int(max_dim * width_ratio / max(width_ratio, height_ratio))
            max_height = int(max_dim * height_ratio / max(width_ratio, height_ratio))
            min_width = int(min_dim * width_ratio / max(width_ratio, height_ratio))
            min_height = int(min_dim * height_ratio / max(width_ratio, height_ratio))

            p.width = random.randint(min_width, max_width)
            p.height = random.randint(min_height, max_height)

            p.width = max(64, min(p.width, 2048))  # Ensure width is within global limits
            p.height = max(64, min(p.height, 2048))  # Ensure height is within global limits

            p.init_latent = None  # Reset latent to force recalculation with new dimensions

            processed = processing.process_images(p)

            # Add aspect ratio info to generation parameters
            processed.infotexts[0] = f"{processed.infotexts[0]}, Aspect Ratio: {width_ratio}:{height_ratio}, Dimensions: {p.width}x{p.height}"

            all_processed.append(processed)

        # Combine all processed results
        combined = Processed(p, [], p.seed, "")
        for processed in all_processed:
            combined.images.extend(processed.images)
            combined.all_prompts.extend(processed.all_prompts)
            combined.all_seeds.extend(processed.all_seeds)
            combined.infotexts.extend(processed.infotexts)

        return combined

    def process(self, p):
        # This method is called before processing begins; we can modify the processing object here
        # This is useful for setting up any initial parameters or flags
        p.extra_generation_params["Randomize Aspect Ratio"] = True
        return p
ArchAngelAries commented 2 months ago

Added prompt recognition and re-enforced User AR min/max values:

import modules.scripts as scripts
import gradio as gr
import random
import re

from modules import processing
from modules.processing import Processed
from modules.shared import opts, cmd_opts, state

class Script(scripts.Script):
    def __init__(self):
        self.keyword_orientations = {
            'landscape': [
                'aerial view', 'bird\'s-eye view', 'establishing shot', 'extreme long shot',
                'long shot', 'fisheye-shot', 'hdri', 'panorama', 'wide angle'
            ],
            'portrait': [
                'close-up', 'extreme close-up', 'full shot', 'full body shot',
                'medium close-up', 'medium shot', 'cowboy shot', 'from behind',
                'from the side', 'over-the-shoulder shot', 'worm\'s eye shot',
                'low-angle shot', 'macro shot'
            ],
            'square': ['dutch angle', 'point-of-view shot', 'two-shot']
        }

    def title(self):
        return "Randomize Aspect Ratio and Layout"

    def ui(self, is_img2img):
        with gr.Group():
            with gr.Row():
                is_enabled = gr.Checkbox(label="Enable Randomize Aspect Ratio", value=False)
            with gr.Row():
                use_prompt_recognition = gr.Checkbox(label="Use Prompt Recognition", value=False)
            with gr.Row():
                min_dim = gr.Slider(minimum=256, maximum=2048, step=64, label="Minimum Dimension", value=512)
                max_dim = gr.Slider(minimum=256, maximum=2048, step=64, label="Maximum Dimension", value=1024)

        return [is_enabled, use_prompt_recognition, min_dim, max_dim]

    def recognize_orientation(self, prompt):
        prompt = prompt.lower()
        for orientation, keywords in self.keyword_orientations.items():
            if any(keyword in prompt for keyword in keywords):
                return orientation
        return 'random'

    def calculate_dimensions(self, width_ratio, height_ratio, min_dim, max_dim):
        # Calculate the scaling factor to meet the minimum dimension
        scale_factor = max(min_dim / width_ratio, min_dim / height_ratio)

        # Calculate initial dimensions
        width = width_ratio * scale_factor
        height = height_ratio * scale_factor

        # If either dimension exceeds max_dim, scale down
        if width > max_dim or height > max_dim:
            scale_factor = min(max_dim / width, max_dim / height)
            width *= scale_factor
            height *= scale_factor

        # Ensure dimensions are integers
        width = int(width)
        height = int(height)

        # Ensure dimensions are within global limits
        width = max(64, min(width, 2048))
        height = max(64, min(height, 2048))

        return width, height

    def run(self, p, is_enabled, use_prompt_recognition, min_dim, max_dim):
        if not is_enabled:
            return processing.process_images(p)

        original_batch_size = p.batch_size
        p.batch_size = 1  # Process one image at a time to allow different sizes

        all_processed = []
        for _ in range(original_batch_size):
            aspect_ratios = {
                'landscape': [(16, 9), (3, 2), (4, 3)],
                'portrait': [(9, 16), (2, 3), (3, 4)],
                'square': [(1, 1)],
                'random': [(16, 9), (9, 16), (4, 3), (3, 4), (1, 1)]
            }

            if use_prompt_recognition:
                orientation = self.recognize_orientation(p.prompt)
            else:
                orientation = 'random'

            aspect_ratio = random.choice(aspect_ratios[orientation])

            # For landscape and portrait, we don't need to flip
            if orientation == 'random':
                if random.choice([True, False]):
                    aspect_ratio = aspect_ratio[::-1]  # Flip for random orientation

            width_ratio, height_ratio = aspect_ratio

            # Calculate dimensions using the new method
            p.width, p.height = self.calculate_dimensions(width_ratio, height_ratio, min_dim, max_dim)

            p.init_latent = None  # Reset latent to force recalculation with new dimensions

            processed = processing.process_images(p)

            # Add aspect ratio info to generation parameters
            processed.infotexts[0] = f"{processed.infotexts[0]}, Aspect Ratio: {width_ratio}:{height_ratio}, Dimensions: {p.width}x{p.height}, Orientation: {orientation}"

            all_processed.append(processed)

        # Combine all processed results
        combined = Processed(p, [], p.seed, "")
        for processed in all_processed:
            combined.images.extend(processed.images)
            combined.all_prompts.extend(processed.all_prompts)
            combined.all_seeds.extend(processed.all_seeds)
            combined.infotexts.extend(processed.infotexts)

        return combined

    def process(self, p):
        p.extra_generation_params["Randomize Aspect Ratio"] = True
        return p