Spaces:
Running
Running
# Base configuration for scaling bucket options | |
_BASE_RESOLUTION = 640 | |
_BASE_BUCKET_OPTIONS = [ | |
(416, 960), (448, 864), (480, 832), (512, 768), (544, 704), | |
(576, 672), (608, 640), (640, 608), (672, 576), (704, 544), | |
(768, 512), (832, 480), (864, 448), (960, 416), | |
] | |
# Cache for generated bucket options to avoid redundant calculations | |
_generated_bucket_cache = {} | |
def _round_to_multiple(number, multiple): | |
"""Rounds a number to the nearest multiple of a given number.""" | |
if multiple == 0: | |
# Default behavior: round to nearest int. Could also raise an error. | |
return int(round(number)) | |
return int(multiple * round(float(number) / multiple)) | |
def _adjust_resolution(resolution, divisor=32): | |
""" | |
Adjusts a given resolution to the nearest multiple of 'divisor'. | |
If the input resolution is positive but rounds to 0 (e.g., resolution=10, divisor=32), | |
it's adjusted to 'divisor'. | |
If the input resolution is non-positive (<=0), it defaults to 'divisor'. | |
""" | |
if resolution <= 0: | |
return divisor # Default to minimum valid resolution for non-positive inputs | |
adjusted = _round_to_multiple(resolution, divisor) | |
# If resolution was positive but _round_to_multiple resulted in 0 | |
# (e.g. input 10 for divisor 32 rounds to 0), ensure it's at least the divisor. | |
if adjusted == 0: | |
return divisor | |
return adjusted | |
def generate_scaled_buckets(target_resolution_input, | |
base_resolution=_BASE_RESOLUTION, | |
base_options=_BASE_BUCKET_OPTIONS, | |
divisor=32): | |
""" | |
Generates scaled bucket options for a target resolution. | |
The target_resolution_input is first adjusted to the nearest multiple of 'divisor'. | |
Bucket dimensions are scaled from 'base_options' (which are for 'base_resolution') | |
to the adjusted target resolution. These scaled dimensions are then rounded to the | |
nearest multiple of 'divisor' and ensured to be at least 'divisor'. | |
Args: | |
target_resolution_input (int): The desired target resolution. | |
base_resolution (int): The resolution for which 'base_options' are defined. | |
base_options (list of tuples): A list of (height, width) tuples for 'base_resolution'. | |
divisor (int): The number that resolutions and bucket dimensions should be multiples of. | |
Returns: | |
list of tuples: Scaled and adjusted bucket options (height, width). | |
""" | |
# Adjust the target resolution for scaling | |
actual_target_resolution = _adjust_resolution(target_resolution_input, divisor) | |
if actual_target_resolution in _generated_bucket_cache: | |
return _generated_bucket_cache[actual_target_resolution] | |
# Optimization: If adjusted target resolution matches base resolution. | |
# This assumes base_options are already compliant with the divisor. | |
# (Our _BASE_BUCKET_OPTIONS are multiples of 32, so this is fine for divisor=32). | |
if actual_target_resolution == base_resolution: | |
options_to_return = list(base_options) # Return a copy | |
_generated_bucket_cache[actual_target_resolution] = options_to_return | |
return options_to_return | |
scaled_options = [] | |
seen_options = set() # To handle potential duplicates after rounding | |
# Prevent division by zero if base_resolution is 0 (though _BASE_RESOLUTION is 640). | |
if base_resolution == 0: | |
# Fallback: return a single square bucket of the target resolution. | |
# This case should not be hit with current constants. | |
default_bucket = (actual_target_resolution, actual_target_resolution) | |
_generated_bucket_cache[actual_target_resolution] = [default_bucket] | |
return [default_bucket] | |
scale_factor = float(actual_target_resolution) / base_resolution | |
for base_h, base_w in base_options: | |
scaled_h_float = base_h * scale_factor | |
scaled_w_float = base_w * scale_factor | |
scaled_h = _round_to_multiple(scaled_h_float, divisor) | |
scaled_w = _round_to_multiple(scaled_w_float, divisor) | |
# Ensure minimum dimension is at least the divisor | |
scaled_h = max(scaled_h, divisor) | |
scaled_w = max(scaled_w, divisor) | |
bucket_tuple = (scaled_h, scaled_w) | |
if bucket_tuple not in seen_options: | |
scaled_options.append(bucket_tuple) | |
seen_options.add(bucket_tuple) | |
# If base_options was empty (not the case for internal use but could be if called externally), | |
# scaled_options would be empty. Provide a default bucket in such a scenario. | |
# actual_target_resolution is guaranteed to be >= divisor by _adjust_resolution. | |
if not scaled_options: | |
default_bucket = (actual_target_resolution, actual_target_resolution) | |
scaled_options.append(default_bucket) | |
_generated_bucket_cache[actual_target_resolution] = scaled_options | |
return scaled_options | |
def find_nearest_bucket(h, w, resolution=640): | |
""" | |
Finds the nearest bucket for a given height (h) and width (w) | |
at a specified target resolution. | |
The 'resolution' parameter is the user's intended target resolution. | |
This function will: | |
1. Adjust this resolution to the nearest multiple of 32 (minimum 32). | |
2. Generate a list of bucket options (height, width pairs) by scaling | |
predefined base options (for 640px) to this adjusted resolution. | |
All generated bucket dimensions will also be multiples of 32 and at least 32. | |
3. Find the bucket from this generated list that is "nearest" to the | |
aspect ratio of the input h, w. The nearness metric is | |
abs(input_h * bucket_w - input_w * bucket_h). | |
Args: | |
h (int): The height of the image/item. | |
w (int): The width of the image/item. | |
resolution (int): The target resolution for which to find buckets. | |
Defaults to 640. | |
Returns: | |
tuple: A (bucket_h, bucket_w) tuple representing the best bucket found. | |
""" | |
# generate_scaled_buckets handles the adjustment of 'resolution' internally | |
# and uses a divisor of 32 by default for its calculations. | |
# The problem statement implies a fixed divisor of 32 for this tool. | |
current_bucket_options = generate_scaled_buckets(resolution, divisor=32) | |
# Failsafe: If generate_scaled_buckets somehow returned an empty list (e.g., if _BASE_BUCKET_OPTIONS was empty), | |
# provide a default bucket based on the adjusted resolution. | |
if not current_bucket_options: | |
adjusted_res_for_fallback = _adjust_resolution(resolution, 32) | |
return (adjusted_res_for_fallback, adjusted_res_for_fallback) | |
min_metric = float('inf') | |
best_bucket = None | |
# Since current_bucket_options is guaranteed to be non-empty by the check above (or by generate_scaled_buckets's own logic | |
# when _BASE_BUCKET_OPTIONS is populated), best_bucket will be assigned in the loop. | |
for (bucket_h, bucket_w) in current_bucket_options: | |
metric = abs(h * bucket_w - w * bucket_h) | |
if metric <= min_metric: # Using "<=" preserves original behavior (last encountered wins on ties) | |
min_metric = metric | |
best_bucket = (bucket_h, bucket_w) | |
return best_bucket |