좌표와 경계 상자
좌표와 경계 상자 (Coordinates and bounding boxes)
Claude는 이미지의 영역을 찾고 라벨을 붙일 수 있어요(예: 표, 폼 필드, 차트 요소, UI 컴포넌트에 대한 경계 상자 반환). 이 가이드는 Claude가 처리 전에 이미지를 크기 조정하는 방식과, 반환하는 픽셀 좌표로 작업하는 방법을 다뤄서, 상자와 점이 원본 이미지와 맞도록 해줘요.
출처: 문서
본문
Claude는 이미지의 영역을 찾고 라벨을 붙일 수 있어요(예: 표, 폼 필드, 차트 요소, UI 컴포넌트에 대한 경계 상자 반환). 이 가이드는 Claude가 처리 전에 이미지를 크기 조정하는 방식과, 반환하는 픽셀 좌표로 작업하는 방법을 다뤄서, 상자와 점이 원본 이미지와 맞도록 해줘요.
이것은 OCR 파이프라인, 폼 추출, 차트 파싱, UI 요소 위치, 그리고 이미지의 특정 영역에 대해 행동하는 모든 과제에 필요해요. 이미지 보내기, 지원 형식, 모델별 해상도 한계는 Vision을 보세요.
좌표는 표준 이미지 규칙을 따라요: 원점 (0, 0)은 이미지의 왼쪽 위 모서리이고, x는 오른쪽으로, y는 아래로 증가해요. Claude가 반환하는 좌표는 Claude가 보는 이미지의 픽셀 위치예요: Claude가 모델의 네이티브 해상도에 맞게 크기 조정한 뒤의 여러분 이미지예요(Claude가 이미지를 크기 조정하고 패딩하는 방식 참고). 직접 쓸 수 있는 좌표를 얻으려면, 좌표가 가진 이미지에 일대일로 매핑되도록 이미지를 미리 조정하거나(업로드 전에 이미지 크기 조정), Claude가 반환한 좌표를 다시 조정하세요(미리 조정할 수 없을 때 좌표 다시 조정).
Claude가 이미지를 크기 조정하고 패딩하는 방식
Claude는 모델의 두 이미지 한계를 모두 만족하는 가장 큰 종횡비 보존 크기를 찾아요:
- 변 한계: 어느 변도 최대 변 길이(표준 계층 1568 px, 고해상도 계층 2576 px)를 초과하지 않아요.
- 시각 토큰 한계: 이미지의 토큰 비용
⌈width / 28⌉ × ⌈height / 28⌉이 모델의 시각 토큰 예산(표준 계층 1568 토큰, 고해상도 계층 4784)을 초과하지 않아요.
어떤 모델이 어느 계층인지 해상도와 토큰 비용을 보세요.
거의 모든 사진과 스크린샷에서 시각 토큰 한계가 최종 크기를 결정해요. 변 한계는 파노라마나 긴 폰 스크린샷 같은 길쭉한 이미지에서만 작동해요. 크기를 손으로 변 길이로 조정하지 말고 참조 구현으로 계산하세요: 1920×1080 스크린샷은 1456×819로 조정되지, 1568×882로 조정되지 않아요. 변 한계를 가정하면 모든 좌표가 눈에 띄게 벗어나요.
토큰 한계는 어느 변도 변 한계를 초과하지 않을 때도 크기 조정을 촉발할 수 있어요. 이걸 간과하는 것이 좌표 불일치의 가장 흔한 원인이에요. 예를 들어 130 DPI로 스캔한 A4 페이지는 1075×1520 픽셀인데: 두 변 모두 1568 px 미만이지만 39 × 55 = 2145 시각 토큰이 들어서, Claude는 그것을 924×1307로 조정해요.
Claude는 그다음 조정 여부와 무관하게 모든 이미지를 아래쪽과 오른쪽 변에서 28픽셀의 다음 배수까지 패딩해요(예시에서 924×1307이 924×1316이 돼요). 패딩에는 콘텐츠가 없어요: Claude는 패딩된 이미지를 인식하지만, 페이지 콘텐츠는 항상 패딩되지 않은 조정된 영역만 차지해요. 항상 패딩된 크기가 아니라 조정된 크기로 정규화하거나 다시 조정하세요. 패딩된 크기로 나누면 모든 좌표가 작은 양만큼 축소돼요.
업로드 전에 이미지 크기 조정하기
가장 안정적인 접근은 업로드 전에 이미지 크기를 직접 조정해서, 가진 이미지가 정확히 Claude가 보는 이미지가 되고 반환 좌표가 변환을 필요로 하지 않게 하는 거예요.
먼저 모델이 어느 해상도 계층에 있는지 확인하고(해상도와 토큰 비용 참고), 일치하는 변·토큰 한계를 전달하세요. 다음 참조 구현은 Claude가 이미지를 조정하는 정확한 크기를 계산해요:
# This reference implementation is local math that makes no API request, so
# there's nothing to show for the CLI. See the SDK tabs.
import math
def count_image_tokens(width: int, height: int) -> int:
"""Visual tokens consumed by an image: one token per 28x28 pixel patch."""
return math.ceil(width / 28) * math.ceil(height / 28)
def resized_size(
width: int,
height: int,
max_edge: int = 1568,
max_tokens: int = 1568,
) -> tuple[int, int]:
"""The size Claude resizes an image to before padding.
Defaults are for the standard resolution tier. For high-resolution-tier
models, use max_edge=2576 and max_tokens=4784. Returns (width, height).
Images that already fit within the limits are returned unchanged.
"""
def fits(w: int, h: int) -> bool:
return (
math.ceil(w / 28) * 28 <= max_edge
and math.ceil(h / 28) * 28 <= max_edge
and count_image_tokens(w, h) <= max_tokens
)
if fits(width, height):
return (width, height)
if height > width:
resized_h, resized_w = resized_size(height, width, max_edge, max_tokens)
return (resized_w, resized_h)
# Binary search along the long edge for the largest aspect-preserving
# size that fits.
aspect_ratio = width / height
lo, hi = 1, width # lo always fits; hi never fits
while lo + 1 < hi:
mid = (lo + hi) // 2
if fits(mid, max(round(mid / aspect_ratio), 1)):
lo = mid
else:
hi = mid
return (lo, max(round(lo / aspect_ratio), 1))
# The A4 example from "How Claude resizes and pads images":
print(resized_size(1075, 1520)) # (924, 1307)
# To apply the resize, use your image library, for example Pillow:
# image.resize(resized_size(*image.size))
/** Visual tokens consumed by an image: one token per 28x28 pixel patch. */
function countImageTokens(width: number, height: number): number {
return Math.ceil(width / 28) * Math.ceil(height / 28);
}
/**
* Round half to even (banker's rounding), matching Python's round(). The
* live API resolves exact .5 ties toward the even neighbor, so Math.round
* (which rounds halves up) would compute a different size for some images.
*/
function roundTiesToEven(value: number): number {
const floor = Math.floor(value);
if (value - floor !== 0.5) return Math.round(value);
return floor % 2 === 0 ? floor : floor + 1;
}
/**
* The size Claude resizes an image to before padding.
*
* Defaults are for the standard resolution tier. For high-resolution-tier
* models, use maxEdge = 2576 and maxTokens = 4784. Returns [width, height].
* Images that already fit within the limits are returned unchanged.
*/
function resizedSize(
width: number,
height: number,
maxEdge = 1568,
maxTokens = 1568
): [number, number] {
const fits = (w: number, h: number): boolean =>
Math.ceil(w / 28) * 28 <= maxEdge &&
Math.ceil(h / 28) * 28 <= maxEdge &&
countImageTokens(w, h) <= maxTokens;
if (fits(width, height)) return [width, height];
if (height > width) {
const [resizedH, resizedW] = resizedSize(height, width, maxEdge, maxTokens);
return [resizedW, resizedH];
}
// Binary search along the long edge for the largest aspect-preserving
// size that fits.
const aspectRatio = width / height;
let lo = 1; // lo always fits
let hi = width; // hi never fits
while (lo + 1 < hi) {
const mid = Math.floor((lo + hi) / 2);
if (fits(mid, Math.max(roundTiesToEven(mid / aspectRatio), 1))) {
lo = mid;
} else {
hi = mid;
}
}
return [lo, Math.max(roundTiesToEven(lo / aspectRatio), 1)];
}
// The A4 example from "How Claude resizes and pads images":
console.log(resizedSize(1075, 1520)); // [ 924, 1307 ]
// To apply the resize, use your image library, for example sharp:
// await sharp(input).resize(width, height).toBuffer()
// Visual tokens consumed by an image: one token per 28x28 pixel patch.
static int CountImageTokens(int width, int height)
{
return (width + 27) / 28 * ((height + 27) / 28); // ceil(w/28) * ceil(h/28)
}
// The size Claude resizes an image to before padding. Defaults are for the
// standard resolution tier; for high-resolution-tier models, pass
// maxEdge: 2576, maxTokens: 4784. Images that already fit within the limits
// are returned unchanged.
static (int Width, int Height) ResizedSize(
int width, int height, int maxEdge = 1568, int maxTokens = 1568)
{
bool Fits(int w, int h) =>
(w + 27) / 28 * 28 <= maxEdge
&& (h + 27) / 28 * 28 <= maxEdge
&& CountImageTokens(w, h) <= maxTokens;
if (Fits(width, height))
{
return (width, height);
}
if (height > width)
{
(int resizedH, int resizedW) = ResizedSize(height, width, maxEdge, maxTokens);
return (resizedW, resizedH);
}
// Binary search along the long edge for the largest aspect-preserving
// size that fits. The short edge rounds half to even, matching the live
// API at exact .5 ties (MidpointRounding.ToEven, Math.Round's default).
double aspectRatio = (double)width / height;
int lo = 1; // lo always fits
int hi = width; // hi never fits
while (lo + 1 < hi)
{
int mid = (lo + hi) / 2;
if (Fits(mid, ShortEdge(mid)))
{
lo = mid;
}
else
{
hi = mid;
}
}
return (lo, ShortEdge(lo));
int ShortEdge(int longEdge) =>
Math.Max((int)Math.Round(longEdge / aspectRatio, MidpointRounding.ToEven), 1);
}
// The A4 example from "How Claude resizes and pads images":
Console.WriteLine(ResizedSize(1075, 1520)); // (924, 1307)
// countImageTokens is the visual tokens consumed by an image: one token per
// 28x28 pixel patch.
func countImageTokens(width, height int) int {
return ((width + 27) / 28) * ((height + 27) / 28) // ceil(w/28) * ceil(h/28)
}
// resizedSize is the size Claude resizes an image to before padding, as
// (width, height). Pass maxEdge 1568 and maxTokens 1568 for the standard
// resolution tier, or 2576 and 4784 for the high-resolution tier. Images
// that already fit within the limits are returned unchanged.
// The A4 example from "How Claude resizes and pads images":
// resizedSize(1075, 1520, 1568, 1568) returns (924, 1307).
func resizedSize(width, height, maxEdge, maxTokens int) (int, int) {
fits := func(w, h int) bool {
return ((w+27)/28)*28 <= maxEdge &&
((h+27)/28)*28 <= maxEdge &&
countImageTokens(w, h) <= maxTokens
}
if fits(width, height) {
return width, height
}
if height > width {
resizedH, resizedW := resizedSize(height, width, maxEdge, maxTokens)
return resizedW, resizedH
}
// Binary search along the long edge for the largest aspect-preserving
// size that fits. The short edge rounds half to even (math.RoundToEven),
// matching the live API at exact .5 ties; math.Round would round them up.
aspectRatio := float64(width) / float64(height)
lo, hi := 1, width // lo always fits; hi never fits
for lo+1 < hi {
mid := (lo + hi) / 2
short := max(int(math.RoundToEven(float64(mid)/aspectRatio)), 1)
if fits(mid, short) {
lo = mid
} else {
hi = mid
}
}
return lo, max(int(math.RoundToEven(float64(lo)/aspectRatio)), 1)
}
/** A resized image size, as returned by resizedSize. */
record Size(int width, int height) {}
/** Visual tokens consumed by an image: one token per 28x28 pixel patch. */
static int countImageTokens(int width, int height) {
return Math.ceilDiv(width, 28) * Math.ceilDiv(height, 28);
}
/**
* The size Claude resizes an image to before padding.
*
* <p>Pass maxEdge 1568 and maxTokens 1568 for the standard resolution tier,
* or 2576 and 4784 for the high-resolution tier. Images that already fit
* within the limits are returned unchanged.
*
* <p>The A4 example from "How Claude resizes and pads images":
* resizedSize(1075, 1520, 1568, 1568) returns new Size(924, 1307).
*/
static Size resizedSize(int width, int height, int maxEdge, int maxTokens) {
if (fits(width, height, maxEdge, maxTokens)) {
return new Size(width, height);
}
if (height > width) {
Size rotated = resizedSize(height, width, maxEdge, maxTokens);
return new Size(rotated.height(), rotated.width());
}
// Binary search along the long edge for the largest aspect-preserving
// size that fits. The short edge rounds half to even (Math.rint),
// matching the live API at exact .5 ties; Math.round would round them up.
double aspectRatio = (double) width / height;
int lo = 1; // lo always fits
int hi = width; // hi never fits
while (lo + 1 < hi) {
int mid = (lo + hi) / 2;
if (fits(mid, shortEdge(mid, aspectRatio), maxEdge, maxTokens)) {
lo = mid;
} else {
hi = mid;
}
}
return new Size(lo, shortEdge(lo, aspectRatio));
}
private static boolean fits(int width, int height, int maxEdge, int maxTokens) {
return Math.ceilDiv(width, 28) * 28 <= maxEdge
&& Math.ceilDiv(height, 28) * 28 <= maxEdge
&& countImageTokens(width, height) <= maxTokens;
}
private static int shortEdge(int longEdge, double aspectRatio) {
return Math.max((int) Math.rint(longEdge / aspectRatio), 1);
}
// Visual tokens consumed by an image: one token per 28x28 pixel patch.
function countImageTokens(int $width, int $height): int
{
return intdiv($width + 27, 28) * intdiv($height + 27, 28);
}
/**
* The size Claude resizes an image to before padding, as [width, height].
*
* Defaults are for the standard resolution tier. For high-resolution-tier
* models, pass maxEdge: 2576, maxTokens: 4784. Images that already fit
* within the limits are returned unchanged.
*/
function resizedSize(int $width, int $height, int $maxEdge = 1568, int $maxTokens = 1568): array
{
$fits = fn (int $w, int $h): bool =>
intdiv($w + 27, 28) * 28 <= $maxEdge
&& intdiv($h + 27, 28) * 28 <= $maxEdge
&& countImageTokens($w, $h) <= $maxTokens;
if ($fits($width, $height)) {
return [$width, $height];
}
if ($height > $width) {
[$resizedH, $resizedW] = resizedSize($height, $width, $maxEdge, $maxTokens);
return [$resizedW, $resizedH];
}
// Binary search along the long edge for the largest aspect-preserving
// size that fits. The short edge rounds half to even
// (PHP_ROUND_HALF_EVEN), matching the live API at exact .5 ties.
$aspectRatio = $width / $height;
$lo = 1; // lo always fits
$hi = $width; // hi never fits
while ($lo + 1 < $hi) {
$mid = intdiv($lo + $hi, 2);
$short = max((int) round($mid / $aspectRatio, 0, PHP_ROUND_HALF_EVEN), 1);
if ($fits($mid, $short)) {
$lo = $mid;
} else {
$hi = $mid;
}
}
return [$lo, max((int) round($lo / $aspectRatio, 0, PHP_ROUND_HALF_EVEN), 1)];
}
// The A4 example from "How Claude resizes and pads images":
[$resizedWidth, $resizedHeight] = resizedSize(1075, 1520);
echo "({$resizedWidth}, {$resizedHeight})\n"; // (924, 1307)
# Visual tokens consumed by an image: one token per 28x28 pixel patch.
def count_image_tokens(width, height)
width.ceildiv(28) * height.ceildiv(28)
end
# The size Claude resizes an image to before padding, as [width, height].
#
# Defaults are for the standard resolution tier. For high-resolution-tier
# models, pass max_edge: 2576, max_tokens: 4784. Images that already fit
# within the limits are returned unchanged.
def resized_size(width, height, max_edge = 1568, max_tokens = 1568)
fits = lambda do |w, h|
w.ceildiv(28) * 28 <= max_edge &&
h.ceildiv(28) * 28 <= max_edge &&
count_image_tokens(w, h) <= max_tokens
end
return [width, height] if fits.call(width, height)
if height > width
resized_h, resized_w = resized_size(height, width, max_edge, max_tokens)
return [resized_w, resized_h]
end
# Binary search along the long edge for the largest aspect-preserving
# size that fits. The short edge rounds half to even (round(half: :even)),
# matching the live API at exact .5 ties.
aspect_ratio = width.fdiv(height)
lo = 1 # lo always fits
hi = width # hi never fits
while lo + 1 < hi
mid = (lo + hi) / 2
short = [(mid / aspect_ratio).round(half: :even), 1].max
if fits.call(mid, short)
lo = mid
else
hi = mid
end
end
[lo, [(lo / aspect_ratio).round(half: :even), 1].max]
end
# The A4 example from "How Claude resizes and pads images":
p resized_size(1075, 1520) # => [924, 1307]
- 이미지를 크기 조정 헬퍼가 반환한 크기로 조정하세요. 이미지가 이미 모델의 한계 안에 있으면, 헬퍼가 크기를 그대로 반환하고 조정이 필요 없어요.
- 조정된 이미지를 API에 보내세요. 직접 패딩하지 마세요. Claude가 패딩을 처리하고, 패딩은 좌표 원점을 이동시키지 않아요.
- 프롬프트에서 픽셀 좌표를 명시적으로 요청하세요. 예: "Return the click point for the Submit button as
[x, y]in pixel coordinates." - 보낸 이미지에 대해 반환 좌표를 직접 쓰세요. 정규화된 좌표가 필요하면, 보낸 이미지의 크기로 나누세요. 원본 이미지의 크기로도, 패딩된 크기로도 나누지 마세요.
transformations로 크기 조정을 오류로 바꾸기
미리 조정은 파이프라인이 올바른 크기를 계속 만들어내는 동안만 좌표를 보호해요. 새 이미지 소스나 다른 해상도 계층의 모델 전환은 서버 측 크기 조정을 조용히 다시 유발할 수 있어요. 그 조용한 드리프트를 눈에 보이는 오류로 바꾸려면 Messages 요청의 이미지 콘텐츠 블록에 선택적 transformations 필드를 설정하세요:
{
"type": "image",
"source": { "type": "base64", "media_type": "image/png", "data": "..." },
"transformations": { "oversized_image": "error" }
}
표시된 이미지("oversized_image": "error"를 설정한 어떤 블록)가 조정될 요청은 이미지의 크기와 맞는 가장 큰 크기를 밝히는 400 invalid_request_error로 거부돼요. 이미지가 거부를 촉발하는지 여부는 요청이 명명하는 모든 모델의 한계에 달려 있어요: 아래 1920×1080 예시는 표준 계층 모델이 거부하지만 고해상도 계층 안에는 맞아요:
messages.0.content.0: image dimensions 1920x1080 exceed the maximum image size of a model named on this request and would be downsized to 1456x819; scale the image to at most 1456x819 or set the image's oversized_image setting to "downsize"
보고된 대상으로 다시 조정하고 재전송하세요: 대상은 요청이 명명하는 모든 모델이 받아들이는, 여러분 이미지 종횡비의 가장 큰 크기예요. 표시된 이미지가 서버 측 폴백 베타와 어떻게 상호작용하는지는 그 기능과 함께 설명돼요. 모든 모드에서 표시된 이미지는 절대 조정된 채로 서빙되지 않아요.
설정은 이미지별이에요. "oversized_image": "downsize"(필드 생략 시 기본)는 이 페이지에 설명된 대로 자동 크기 조정을 유지해요. 각 이미지 블록은 자신의 설정에만 대해 검사되므로, 한 요청이 크기가 중요한 이미지(클릭할 스크린샷)와 크기 조정이 무해한 이미지(로고)를 섞을 수 있어요. 설정이 바꾸는 것과 바꾸지 않는 것:
- 패딩(절대 콘텐츠를 버리지 않는 것), 형식 변환, 방향 보정은 평소처럼 진행돼요.
- 하드 한계(가장 긴 변 8000 px, many-image 요청의 더 엄격한 이미지당 한계)는 별개의 거부예요. 이 설정이 이미지를 그걸 넘게 통과시키지는 않아요.
- URL이나 파일 ID로 제공된 이미지는 바이트를 가져온 뒤 검사돼요. 그 거부는 앞의 위치 없이 같은 메시지를 담아 어떤 이미지가 실패했는지 식별하지 못해요. 임베디드 base64 이미지만 오류에서 위치로 이름이 붙어요.
- PDF 페이지는 여러분이 제어하지 않는 크기로 서버 측에서 래스터화돼요.
document블록은 필드를 받지 않아요(문서 콘텐츠 안에 중첩된 이미지 블록은 다른 것처럼 받아요). - 크기를 결정할 수 없는 표시 이미지는 통과시키는 대신 거부돼요: 그 거부는 위에 인용된 크기 조정 메시지가 아니라 이미지의 소스 크기를 결정할 수 없다고 보고해요.
"error"를 설정한 이미지는 조정된 채로 모델에 도달하지 않아요.
토큰 계산 엔드포인트 역시 transformations를 존중해서, 임베디드 이미지를 Messages API가 거부하는 것과 똑같이 거부해요. 따라서 추론을 실행하기 전에 임베디드 이미지가 조정되지 않고 맞는지 확인할 수 있어요. 계산은 URL이나 파일 ID로 제공된 이미지를 가져오지 않고 거부하므로, 그 소스의 표시 이미지는 Messages 시점에만 검사돼요.
미리 조정할 수 없을 때 좌표 다시 조정하기
미리 조정할 수 없다면(예: 수정할 수 없는 업스트림 시스템에서 이미지가 올 때), 업로드 전에 이미지 크기 조정의 크기 조정 헬퍼를 사용해 Claude가 본 크기를 복구하고, Claude가 반환한 좌표를 정규화된 좌표나 원본 이미지로 매핑하세요. 이미지가 대신 오류를 선택하지 않는 한, Claude는 거대한 이미지를 거부하지 않고 축소해요. API의 요청 한계까지요. 그 한계를 넘으면 요청이 검증 오류로 실패해요. 호출한 모델과 일치하는 계층 한계를 전달하세요: 틀린 계층의 한계는 틀린 조정 크기를 복구하고 모든 좌표를 조용히 이동시켜요. 이 접근은 업로드한 이미지의 픽셀 크기를 알아야 하므로 PDF 업로드에는 적용되지 않아요.
computer use와 browser use 도구셋에 반환하는 스크린샷과 줌 이미지는 자동 크기 조정의 예외예요. API는 모델 한계를 초과하는 tool_result 이미지를 조정하지 않고 검증 오류로 거부해요. 그런 이미지는 반환하기 전에 애플리케이션에서 크기를 조정한 뒤, Claude가 반환한 좌표를 화면 크기로 다시 조정하세요.
# This local coordinate conversion makes no API request, so there's nothing
# to show for the CLI. See the SDK tabs.
# This helper calls resized_size from the resize example on this page.
def to_relative_coordinates(
x: float,
y: float,
original_width: int,
original_height: int,
max_edge: int = 1568,
max_tokens: int = 1568,
) -> tuple[float, float]:
"""Map a pixel coordinate returned by Claude to relative coordinates in [0, 1].
Pass the dimensions of the image you uploaded. For high-resolution-tier
models, use max_edge=2576 and max_tokens=4784.
"""
resized_w, resized_h = resized_size(
original_width, original_height, max_edge, max_tokens
)
return (x / resized_w, y / resized_h)
# A table corner Claude returns at (462, 653.5) on the resized A4 page maps
# back onto the 1075x1520 original like this:
rel_x, rel_y = to_relative_coordinates(462, 653.5, 1075, 1520)
print((rel_x * 1075, rel_y * 1520)) # (537.5, 760.0)
// This helper calls resizedSize from the resize example on this page.
/**
* Map a pixel coordinate returned by Claude to relative coordinates in [0, 1].
*
* Pass the dimensions of the image you uploaded. For high-resolution-tier
* models, use maxEdge = 2576 and maxTokens = 4784.
*/
function toRelativeCoordinates(
x: number,
y: number,
originalWidth: number,
originalHeight: number,
maxEdge = 1568,
maxTokens = 1568
): [number, number] {
const [resizedW, resizedH] = resizedSize(
originalWidth,
originalHeight,
maxEdge,
maxTokens
);
return [x / resizedW, y / resizedH];
}
// A table corner Claude returns at (462, 653.5) on the resized A4 page maps
// back onto the 1075x1520 original like this:
const [relX, relY] = toRelativeCoordinates(462, 653.5, 1075, 1520);
console.log([relX * 1075, relY * 1520]); // [ 537.5, 760 ]
// This helper calls ResizedSize from the resize example on this page.
// Map a pixel coordinate returned by Claude to relative coordinates in
// [0, 1]. Pass the dimensions of the image you uploaded, and the same tier
// limits used for ResizedSize.
static (double X, double Y) ToRelativeCoordinates(
double x, double y, int originalWidth, int originalHeight,
int maxEdge = 1568, int maxTokens = 1568)
{
(int resizedW, int resizedH) =
ResizedSize(originalWidth, originalHeight, maxEdge, maxTokens);
return (x / resizedW, y / resizedH);
}
// A table corner Claude returns at (462, 653.5) on the resized A4 page maps
// back onto the 1075x1520 original like this:
(double relX, double relY) = ToRelativeCoordinates(462, 653.5, 1075, 1520);
Console.WriteLine((relX * 1075, relY * 1520)); // (537.5, 760)
// This helper calls resizedSize from the resize example on this page.
// toRelativeCoordinates maps a pixel coordinate returned by Claude to
// relative coordinates in [0, 1]. Pass the dimensions of the image you
// uploaded, and the same tier limits used for resizedSize.
func toRelativeCoordinates(
x, y float64,
originalWidth, originalHeight, maxEdge, maxTokens int,
) (float64, float64) {
resizedW, resizedH := resizedSize(originalWidth, originalHeight, maxEdge, maxTokens)
return x / float64(resizedW), y / float64(resizedH)
}
// To map back to your original image's pixel space, multiply by the original
// dimensions: a table corner returned at (462, 653.5) on the resized A4 page
// is (relX*1075, relY*1520) = (537.5, 760) on the 1075x1520 original.
// This helper calls resizedSize from the resize example on this page.
/** A coordinate scaled into the [0, 1] range on both axes. */
record RelativeCoordinate(double x, double y) {}
/**
* Map a pixel coordinate returned by Claude to relative coordinates in
* [0, 1]. Pass the dimensions of the image you uploaded, and the same tier
* limits used for resizedSize.
*/
static RelativeCoordinate toRelativeCoordinates(
double x, double y, int originalWidth, int originalHeight, int maxEdge, int maxTokens) {
Size resized = resizedSize(originalWidth, originalHeight, maxEdge, maxTokens);
return new RelativeCoordinate(x / resized.width(), y / resized.height());
}
// To map back to your original image's pixel space, multiply by the original
// dimensions: a table corner returned at (462, 653.5) on the resized A4 page
// is (relative.x() * 1075, relative.y() * 1520) = (537.5, 760) on the
// 1075x1520 original.
// This helper calls resizedSize() from the resize example on this page.
/**
* Map a pixel coordinate returned by Claude to relative coordinates in
* [0, 1], as [x, y]. Pass the dimensions of the image you uploaded, and the
* same tier limits used for resizedSize.
*/
function toRelativeCoordinates(
float $x,
float $y,
int $originalWidth,
int $originalHeight,
int $maxEdge = 1568,
int $maxTokens = 1568,
): array {
[$resizedW, $resizedH] = resizedSize($originalWidth, $originalHeight, $maxEdge, $maxTokens);
return [$x / $resizedW, $y / $resizedH];
}
// A table corner Claude returns at (462, 653.5) on the resized A4 page maps
// back onto the 1075x1520 original like this:
[$relX, $relY] = toRelativeCoordinates(462, 653.5, 1075, 1520);
echo '(' . $relX * 1075 . ', ' . $relY * 1520 . ")\n"; // (537.5, 760)
# This helper calls resized_size from the resize example on this page.
# Map a pixel coordinate returned by Claude to relative coordinates in
# [0, 1], as [x, y]. Pass the dimensions of the image you uploaded, and the
# same tier limits used for resized_size.
def to_relative_coordinates(
x, y, original_width, original_height, max_edge = 1568, max_tokens = 1568
)
resized_w, resized_h = resized_size(original_width, original_height, max_edge, max_tokens)
[x.fdiv(resized_w), y.fdiv(resized_h)]
end
# A table corner Claude returns at (462, 653.5) on the resized A4 page maps
# back onto the 1075x1520 original like this:
rel_x, rel_y = to_relative_coordinates(462, 653.5, 1075, 1520)
p [rel_x * 1075, rel_y * 1520] # => [537.5, 760.0]
패딩은 아래쪽과 오른쪽 변에만 적용되므로 원점이 이동하지 않고, 축별 선형 재조정으로 충분해요. 재조정 전에 반환된 좌표를 조정된 크기로 클램프해서, 이미지 밖으로 약간 벗어난 점이 원본 밖으로 매핑되지 않게 하세요.
상대 좌표는 행동하는 어떤 표면에든 곱해져요: 원본 이미지, 전체 해상도 스캔, 화면. 화면에서 행동하고 스크린샷 픽셀이 논리 좌표와 다를 때(HiDPI 디스플레이)는 디스플레이 배율 계수로도 나누세요. Computer use 도구의 배율 지침이 그 패턴을 다뤄요.