Aspect ratio buckets

종횡비 버킷

Grouping non-square images by aspect ratio into a handful of pre-set resolutions so none of them are stretched or cropped to fit.

Also known as: BucketingResolution buckets
···
html
<div class="row" id="row">
  <div class="frame"><canvas></canvas><span class="lbl">1:1</span></div>
  <div class="frame"><canvas></canvas><span class="lbl">3:4</span></div>
  <div class="frame"><canvas></canvas><span class="lbl">16:9</span></div>
  <div class="frame"><canvas></canvas><span class="lbl">9:16</span></div>
  <div class="frame bad"><canvas></canvas><span class="lbl">비율 무시</span></div>
</div>
css
.row{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;height:100%;padding:0 6px;flex-wrap:nowrap}
.frame{display:flex;flex-direction:column;align-items:center;gap:3px;flex-shrink:0}
.frame canvas{display:block;border-radius:5px;border:1.5px solid var(--line)}
.frame.bad canvas{border-color:var(--accent-2)}
.lbl{font-size:8.5px;color:var(--muted);font-weight:700;white-space:nowrap}
js
function mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }

function paintScene(ctx, w, h, seed) {
  const r = mulberry32(seed);
  const g = ctx.createLinearGradient(0, 0, 0, h);
  g.addColorStop(0, 'hsl(200 55% 68%)'); g.addColorStop(1, 'hsl(210 40% 35%)');
  ctx.fillStyle = g; ctx.fillRect(0, 0, w, h);
  ctx.beginPath(); ctx.arc(w * 0.68, h * 0.22, Math.min(w, h) * 0.1, 0, Math.PI * 2); ctx.fillStyle = 'hsl(45 90% 78%)'; ctx.fill();
  for (let l = 0; l < 2; l++) {
    ctx.beginPath(); ctx.moveTo(0, h);
    for (let i = 0; i <= 6; i++) { const x = w * i / 6; const y = h * (0.55 + l * 0.16) - r() * h * 0.12; ctx.lineTo(x, y); }
    ctx.lineTo(w, h); ctx.closePath();
    ctx.fillStyle = 'hsl(150 35% ' + (22 + l * 13) + '%)'; ctx.fill();
  }
}
// 박스 종횡비 목록. 마지막(비율 무시)은 정사각형 내용을 이 비율의 박스에 강제로 눌러 담아 왜곡을 보여준다.
const ratios = [1, 0.75, 1.78, 0.56, 1.35];
const BAD = ratios.length - 1;
const frameEls = [...document.querySelectorAll('.frame')];
function layout(seed) {
  const gap = 6, n = ratios.length;
  const totalRatio = ratios.reduce((s, r) => s + r, 0);
  const availW = innerWidth - 12;
  const maxH = innerHeight * 0.68;
  const H = Math.max(20, Math.min(maxH, (availW - gap * (n - 1)) / totalRatio));
  frameEls.forEach((f, i) => {
    const cv = f.querySelector('canvas');
    const boxW = Math.round(H * ratios[i]), boxH = Math.round(H);
    cv.style.width = boxW + 'px'; cv.style.height = boxH + 'px';
    if (i === BAD) {
      // 내용은 정사각형으로 그리고, 표시 박스만 다른 비율 — 캔버스가 자동으로 눌려 찌그러진다
      cv.width = Math.round(H); cv.height = Math.round(H);
    } else {
      cv.width = boxW; cv.height = boxH;
    }
    paintScene(cv.getContext('2d'), cv.width, cv.height, seed);
  });
}
const seeds = [3, 17];
let si = 0;
layout(seeds[si]);
addEventListener('resize', () => layout(seeds[si]));
setInterval(() => { si = (si + 1) % seeds.length; layout(seeds[si]); }, 3000);

Training or generating many images together in a batch needs tensors of the same shape. If every image has its own native ratio, the simplest fix is to force-crop all of them (losing edges) or force-stretch them (distorting proportions).

Bucketing instead pre-defines a handful of common ratios — square, portrait, landscape — and assigns each incoming image to whichever bucket is closest to its native ratio. Images in the same bucket share a size for batching, but because the bucket is close to the original ratio rather than forcing a stretch or crop, the content stays intact.

Buckets are still a discrete set of values, though, so not every ratio lands exactly on one. A ratio far from all of them still gets nudged, with a bit of resizing, into whichever bucket is nearest.

The demo below redraws the same scene into frames of different ratios — each fit naturally to its own frame size, not stretched — next to one red-bordered frame that ignores the ratio and squashes the same content to fit, for contrast.

When to use

Useful background when a pipeline has to handle images of several ratios. It is less something you tune directly and more how the tool or model handles things internally.