At 600×600, the DS-160 compression limit implies a minimum of 54,000 JPEG bytes. Your file must also stay at or below 240 KB. To test both limits in your browser, measure decoded dimensions and encoded bytes, then review composition separately.
You can test your own photo's size, bytes and head height with the free DS-160 photo checker in your browser (disclosure: I built it).
I built pixtidy, which uses MediaPipe face detection in your browser without uploading your photo. The distinction I care about is what each result checks: file measurements cover the digital limits, while landmark-based measurements cover composition. Some rules need your own review.
What are the DS-160 photo requirements in numbers?
- Check the file against each limit independently.
The State Department's digital image requirements specify:
| Property | Requirement |
|---|---|
| Shape | Square |
| Dimensions | 600×600 through 1200×1200 pixels |
| Format | Color JPEG |
| File size | 240 KB or less |
| Compression ratio | No higher than 20:1 |
The photo composition template adds two measurements:
| Measurement | Requirement |
|---|---|
| Chin to top of hair | 50–69% of image height |
| Eyes, measured up from the bottom | 56–69% of image height |
You need both sets of checks.
How do you check file size and compression together?
- Read dimensions from the decoded image and bytes from the JPEG you intend to submit.
This JavaScript illustrates the measurements. It assumes blob contains the JPEG being tested. For the ceiling check, I use a conservative decimal interpretation of 240 KB: 240,000 bytes.
const image = await createImageBitmap(blob);
const { width, height } = image;
const encodedBytes = blob.size;
const rawRgbBytes = width * height * 3;
const compressionRatio = rawRgbBytes / encodedBytes;
const checks = {
square: width === height,
dimensions:
width >= 600 && width <= 1200 &&
height >= 600 && height <= 1200,
fileSize: encodedBytes <= 240 * 1000,
compression: compressionRatio <= 20
};
console.table({
width,
height,
encodedBytes,
rawRgbBytes,
compressionRatio
});
console.table(checks);
image.close();
The file-size ceiling and compression limit now appear alongside each other.
At 600×600, the calculation is:
Raw RGB bytes = 600 × 600 × 3 = 1,080,000
Minimum encoded bytes = 1,080,000 ÷ 20 = 54,000
At exactly 54,000 bytes, the ratio is 20:1. Any smaller encoded file exceeds that ratio under this calculation.
If you export another JPEG, rerun the measurements on that output. Use its file size and dimensions to calculate its compression ratio.
How do you turn face coordinates into composition percentages?
- Make the coordinate conversion explicit.
There are two separate jobs here: identifying anatomical positions and converting those positions into percentages.
For the head-height calculation, the endpoints are the chin and the top of the hair. For the eye-line calculation, you need the eyes' vertical position. Each measurement is expressed as a fraction of image height.
Here is the measurement layer. Its inputs are normalized vertical coordinates measured down from the top, rather than an assumed set of MediaPipe landmark indices:
function measureComposition({
imageHeight,
chinYNormalized,
eyeYNormalized,
topOfHairYNormalized
}) {
const chinY = chinYNormalized * imageHeight;
const eyeY = eyeYNormalized * imageHeight;
const topOfHairY =
topOfHairYNormalized * imageHeight;
const headFraction =
(chinY - topOfHairY) / imageHeight;
const eyesFromBottom =
(imageHeight - eyeY) / imageHeight;
return {
chinY,
eyeY,
topOfHairY,
headPercent: headFraction * 100,
eyesFromBottomPercent: eyesFromBottom * 100,
headInRange:
headFraction >= 0.50 && headFraction <= 0.69,
eyesInRange:
eyesFromBottom >= 0.56 && eyesFromBottom <= 0.69
};
}
What do the composition limits look like at 600×600?
- Convert the official percentages into pixel boundaries you can inspect.
For a 600-pixel-high image, the permitted chin-to-hair distance is:
Minimum head height = 600 × 0.50 = 300 pixels
Maximum head height = 600 × 0.69 = 414 pixels
The eye-line rule starts at the bottom. Converted to top-origin coordinates, its boundaries are:
At 56% from bottom: eyeY = 600 × (1 - 0.56) = 264
At 69% from bottom: eyeY = 600 × (1 - 0.69) = 186
Substituting the boundary coordinate 264 gives (600 - 264) / 600 * 100 = 56%.
For the head measurement, keep the chin coordinate explicit. A top-of-hair coordinate of chinY - 300 gives:
Head percentage
= (chinY - (chinY - 300)) / 600 × 100
= 50%
That is a boundary calculation. It illustrates the percentage conversion rather than how the coordinates are obtained.
Which mistakes still need manual review?
- Check these cases before relying on a numerical result.
Glasses pass unnoticed. Glasses are not allowed except for a medical reason with a signed statement. Pixtidy does not check glasses.
Filters and AI edits. Photos must not be edited with filters or AI.
Where can you test your photo?
You can use the free pixtidy checker in your browser, without uploading your photo. There is also a full DS-160 photo rejection guide.
Use the measurements to find specific issues. Review the rules outside the checker's scope yourself.
Related: Hitting a file-size window with canvas.toBlob: fitting passport photos into 54–240 KB · Checking passport photos against government specs in the browser with MediaPipe
Disclosure: I built pixtidy.
Top comments (0)