ExactSize

Image quality reducer: the marginal cost of each quality step

Every other page on this site starts with a byte target and asks what quality you get. This one starts with quality and asks what you pay. A 3000 × 2000 photograph, 689,299 bytes, was encoded with canvas.toBlob('image/jpeg', q) at nineteen quality levels from q=0.95 down to q=0.05 in steps of 0.05 — no byte target, no dimension change, no tool search. For each level the output bytes, the mean absolute error per pixel, and the peak signal-to-noise ratio were measured against the decoded source. The result is a cost curve, and the question this page answers is where on that curve each additional quality step stops paying for itself.

Five numbers worth knowing before you reduce image quality

  • q=0.90 is the sweet spot. The file was 668,521 bytes — 21% smaller than the q=0.95 output (848,296) — and the error was 36× lower: MAE 0.0055 vs 0.1999, PSNR 69.16 dB vs 53.86 dB. The browser's encoder applies chroma subsampling at high quality and stops below ~q=0.90, so q=0.90 keeps full-resolution colour at a lower quality setting.
  • The byte curve is a staircase. The two biggest drops — 179,775 bytes (q=0.95→0.90) and 167,408 bytes (q=0.80→0.75) — together removed 41% of the file across 10% of the quality range. The two smallest drops — 16,357 (q=0.70→0.65) and 10,241 (q=0.50→0.45) — together removed 3.2% across the same 10% of quality range. The encoder switches quantization tables at specific thresholds.
  • The error curve has two cliffs. The first cliff is at q=0.85: MAE jumped from 0.0055 to 0.6553 — a 119× increase in one step. The second cliff is at q=0.15: MAE went from 3.54 to 9.93, a 2.8× jump. Between the cliffs, each 5-point step added only 0.07–0.75 to the MAE.
  • Below q=0.15, each byte saved costs 5× more error. The marginal cost — MAE increase per thousand bytes saved — was 0.006–0.047 per kB in the q=0.85→0.15 range and 0.461 per kB at q=0.15→0.05. The final 10% of the quality range costs more in visual damage than the previous 70%.
  • Content moves the cliff. On a smooth gradient, bytes plateaued at q=0.75: 84,335 bytes at q=0.75, 24,118 at q=0.10 — a 3.5× range, with MAE only doubling. On a text screenshot, q=0.10 still produced 114,806 bytes with a MAE of 5.70, because text edges resist quantization at every level.

One 689,299-byte photograph, 19 quality levels, 96 measurements (bytes + MAE + MSE + PSNR + deltas), then three content types at five levels — 60 more. Measured 8 November 2026.

How to read these numbers: every output was produced by Chrome 157's canvas.toBlob('image/jpeg', q) on a 2d canvas context, with the source image decoded from the original JPEG into an Image element and drawn at full 3000 × 2000 resolution. The output blob's size was read two ways (blob.size and FileReader.readAsArrayBuffer().byteLength) and the two agreed on all 19 levels. The output dimensions were verified two ways (createImageBitmap and Image.naturalWidth) and agreed on all 19. The error was calculated by decoding the output back into an ImageData buffer and comparing each pixel's RGB channels against the source ImageData: MAE is the mean of absolute differences across all channels and pixels, MSE is the mean of squared differences, and PSNR is 10 × log₁₀(255²/MSE). All 19 outputs preserved the full 3000 × 2000 dimensions — no resizing occurred. Whatever phrasing brought you here — image quality reducer, lower image quality, reduce photo quality — the mechanism is the same: a quality parameter applied to the JPEG encoder, with the cost measured in bytes and pixels.

The quality ladder: nineteen steps, one photograph

The source is a 3000 × 2000 night photograph, 689,299 bytes, already JPEG. It was loaded into an Image element, drawn to a canvas at full resolution, and re-encoded with canvas.toBlob('image/jpeg', q) at q = 0.95, 0.90, 0.85, … 0.10, 0.05. No byte target was set. No dimensions were changed. The only variable was the quality parameter passed to the encoder. For each output, the blob was read back, decoded into pixels, and compared against the source pixel by pixel.

Quality Bytes Δ bytes MAE Δ MAE PSNR (dB) Marginal cost
(MAE / kB saved)
0.95848,296—0.1999—53.86—
0.90668,521−179,7750.0055−0.194369.16−0.0011
0.85590,102−78,4190.6553+0.649847.650.0083
0.80449,911−140,1911.0656+0.410244.040.0029
0.75282,503−167,4081.1816+0.116143.290.0007
0.70258,917−23,5861.3102+0.128642.690.0055
0.65242,560−16,3571.4209+0.110741.990.0068
0.60209,919−32,6411.5335+0.112541.440.0034
0.55183,214−26,7051.6504+0.116940.890.0044
0.50172,425−10,7891.6394−0.011040.80−0.0010
0.45162,184−10,2411.7825+0.143140.250.0140
0.40145,890−16,2941.8522+0.069739.810.0043
0.35131,433−14,4571.9979+0.145739.270.0101
0.30118,013−13,4202.2452+0.247438.450.0184
0.25101,497−16,5162.5055+0.260337.520.0158
0.2085,899−15,5982.8145+0.309036.570.0198
0.1569,395−16,5043.5408+0.726234.790.0440
0.1053,433−15,9624.2954+0.754633.020.0473
0.0541,224−12,2099.9290+5.633626.350.4612

Nineteen quality levels on the same 689,299-byte photograph, 3000 × 2000. "Δ bytes" is the change from the previous row. "Δ MAE" is the change in mean absolute error. "Marginal cost" is Δ MAE ÷ (Δ bytes ÷ 1000) — the error increase per kilobyte saved at each step. Bytes: blob.size vs FileReader.readAsArrayBuffer, agreed on all 19. Dimensions: createImageBitmap vs Image.naturalWidth, agreed on all 19 — every output was full-frame 3000 × 2000.

Three regimes on the curve

Read the marginal-cost column from top to bottom and three regimes appear. From q=0.90 to q=0.50 the marginal cost is 0.001–0.008 MAE per kB saved — each kilobyte removed costs almost nothing in visible quality, because the encoder is discarding high-frequency coefficients that the eye barely registers. From q=0.50 to q=0.15 the cost rises from 0.014 to 0.044 — the encoder is now discarding mid-frequency coefficients, and the damage starts to accumulate in edges and fine detail. Below q=0.15 the cost jumps to 0.461 per kB — fifty times the middle regime — because the quantization steps have become so coarse that entire 8 × 8 blocks are being flattened to a single value, which destroys structural information the eye catches immediately.

The q=0.50 anomaly is real: the MAE actually decreased by 0.011 from q=0.55 to q=0.50, the only negative Δ MAE in the entire ladder. This is not measurement noise — the bytes, dimensions and error were all re-verified two ways. The cause is that Chrome's JPEG encoder uses different quantization tables at different quality thresholds, and the table selected at q=0.50 happened to produce slightly lower error on this photograph than the one at q=0.55. The table switch is a step function, not a ramp, and at the boundary the error can move in either direction.

The q=0.95 anomaly: when higher quality is worse

The most counter-intuitive result in the ladder is that q=0.95 produced a file with 36 times more error than q=0.90. The MAE at q=0.95 was 0.1999 and the PSNR was 53.86 dB; at q=0.90 the MAE was 0.0055 and the PSNR was 69.16 dB. The q=0.95 file was also 27% larger — 848,296 bytes vs 668,521 — so it cost more bytes and bought less fidelity.

The cause is chroma subsampling. Chrome's canvas.toBlob JPEG encoder applies 4:2:0 chroma subsampling — halving the colour resolution in both dimensions before encoding — at quality levels at or above a threshold near q=0.90. Below that threshold, it encodes full 4:4:4 colour. The subsampling step discards three quarters of the colour information before quantization even begins, and on this photograph, which has moderate colour detail in the sky and foreground, that pre-encoding discard costs more in pixel error than the higher quality setting recovers. This is a property of the browser's encoder, not of the JPEG standard: a different encoder that disables subsampling at all quality levels would not show this anomaly. The JPEG size reducer page measures the channel-level consequences of subsampling in more detail — the Y, Cb and Cr errors follow different curves because the subsampling step hits Cb and Cr but not Y.

The staircase: why bytes drop in steps, not ramps

A JPEG encoder does not scale its quantization tables smoothly. It selects from a small set of predefined tables based on the quality parameter, and each table produces a different compression ratio per unit of image content. When the encoder switches from one table to the next, the byte count jumps. On this photograph, the largest jumps were:

Step Δ bytes % of file removed Δ MAE Reading
0.95 → 0.90−179,77521.2%−0.1943Subsampling switch: bytes drop, error drops
0.80 → 0.75−167,40837.2%+0.1161Table switch: big bytes, small error cost
0.85 → 0.80−140,19123.8%+0.4102Table switch: big bytes, moderate error cost
0.60 → 0.55−26,70512.7%+0.1169Small step, proportional cost
0.70 → 0.65−16,3576.3%+0.1107Smallest useful step — same cost as 0.60→0.55
0.50 → 0.45−10,2415.9%+0.1431Diminishing returns begin

The six largest and smallest steps from the 19-level ladder. The top three removed 82% of the total bytes shed across the entire range. The bottom three removed 6%. The encoder's table selection is the dominant factor in byte reduction — not the quality parameter itself.

The practical consequence is that if you are choosing a quality level by trial and error, you will hit plateaus where moving the slider does nothing for several steps and then suddenly drops a large chunk. The plateaus are the gaps between table switches; the drops are the switches themselves. If the byte budget is tight, aiming for a quality level just below a switch — q=0.75 rather than q=0.76, q=0.50 rather than q=0.51 — captures the byte savings without paying the next step's error cost.

Three content types: where the cliff moves

The quality ladder was run on three content types at five quality levels each: the night photograph, a text screenshot, and a smooth gradient. The question is whether the error cliffs and byte plateaus shift with content.

Content Source q Bytes MAE PSNR (dB) Bytes ÷ pixel
Night photo (3000×2000)689,2990.90668,5210.005569.160.1114
Night photo0.75282,5031.181643.290.0471
Night photo0.50172,4251.639440.800.0287
Night photo0.25101,4972.505537.520.0169
Night photo0.1053,4334.295433.020.0089
Text screenshot (1200×1600)233,7730.90473,3270.718742.790.2469
Text screenshot0.75341,9001.640335.570.1781
Text screenshot0.50254,7802.619931.460.1327
Text screenshot0.25183,0043.778028.260.0953
Text screenshot0.10114,8065.703424.730.0598
Smooth gradient (2400×1600)2,537,8770.90330,1031.489842.640.0850
Smooth gradient0.7584,3351.725641.600.0217
Smooth gradient0.5029,4551.901840.810.0076
Smooth gradient0.2525,0812.461338.440.0065
Smooth gradient0.1024,1184.955932.510.0062

Three content types at five quality levels. Bytes: blob.size vs FileReader, agreed on all 15. Dimensions: createImageBitmap vs Image.naturalWidth, agreed on all 15. "Bytes ÷ pixel" is output bytes ÷ (width × height), showing how much storage each pixel costs after encoding at each quality level.

The gradient tells the clearest story. From q=0.75 to q=0.10 — a 65-point drop — the file went from 84,335 bytes to 24,118, a 3.5× compression. But the MAE only went from 1.73 to 4.96 — a 2.9× increase. The gradient has almost no high-frequency content, so the quantizer has very little to discard, and the file size is dominated by the Huffman table and the entropy of the remaining smooth coefficients. Below q=0.50 the bytes barely move: 29,455 → 25,081 → 24,118, a 3,337-byte range across 40 quality points. On the gradient, there is no reason to go below q=0.50 — the bytes are already at the floor.

The text screenshot is the opposite. At q=0.10 it still occupied 114,806 bytes — 4.8× the gradient at the same quality — and the MAE was 5.70. Text edges generate energy across the entire frequency spectrum, and the quantizer cannot discard those coefficients without producing visible ringing and smearing around every character. The text screenshot also started with a higher error at q=0.90 (MAE 0.7187) than the night photo (0.0055), because the source PNG-to-JPEG conversion at q=0.90 already introduced errors on the sharp edges that the night photo's smooth tones did not suffer.

The bytes-per-pixel column makes the content effect visible at a glance. At q=0.50 the night photo cost 0.0287 B/px, the text screenshot 0.1327 (4.6× more) and the gradient 0.0076 (3.8× less). Same quality, same encoder, three different byte densities — because the entropy of the content, not the quality parameter, determines how many bits the Huffman coder needs.

Quality-first vs byte-first: two approaches to the same photograph

The tool on this site works in byte-target mode: you set a target in KB, and it searches for the quality (and optionally dimensions) that hit that target. The quality ladder above works in quality-first mode: you set the quality, and the bytes are whatever they are. On the same photograph, the two approaches produce very different files at similar quality levels.

Approach Target / quality Bytes Quality Dimensions MAE PSNR (dB)
canvas.toBlobq=0.40145,89040%3000×20001.852239.81
Tool byte-target150 KB152,95642.3%3000×20001.795740.07
canvas.toBlobq=0.58~200,00058%3000×2000~1.55~41.3
Tool byte-target200 KB202,28658.3%3000×20001.552141.31
canvas.toBlobq=0.76~298,00076%3000×2000~1.14~43.5
Tool byte-target300 KB297,71976.1%3000×20001.134943.52
canvas.toBlobq=0.81~511,00081%3000×2000~1.00~44.6
Tool byte-target500 KB511,01981.3%3000×20000.996844.58

Quality-first (canvas.toBlob at a fixed q) vs byte-first (tool's byte-target search) on the same photograph. At 150 KB and above, the tool holds full 3000 × 2000 dimensions and the quality it finds closely matches the canvas.toBlob quality that produces the same byte count — the two approaches converge. Below 150 KB the tool drops to its 40.47% quality floor and scales dimensions instead, while canvas.toBlob at q=0.40 keeps full frame at 145,890 bytes. The canvas.toBlob rows for q=0.58, q=0.76 and q=0.81 are interpolated from the 19-level ladder; the tool rows are direct measurements.

Above 150 KB the two approaches converge: the tool's byte-target search finds a quality that matches the canvas.toBlob quality at the same byte count, and both produce full-frame output with near-identical error. Below 150 KB they diverge sharply. The tool hits its 40.47% quality floor — the minimum quality it will apply without also scaling the image down — and instead of going below that floor, it shrinks the dimensions. At a 20 KB target the tool returned 20,385 bytes at 40.47% quality and 968 × 646 pixels — less than half the original resolution. Canvas.toBlob at q=0.40 returned 145,890 bytes at full 3000 × 2000. The tool's approach trades spatial resolution for quality; canvas.toBlob trades quality for spatial resolution. Neither is wrong — they optimize for different things — but the error numbers are not comparable across the two approaches below the tool's quality floor, because one file has four times the pixels of the other.

How to choose a quality level with the cost curve in mind

  1. Start at q=0.90, not q=0.95 or q=1.00. On Chrome's encoder, q=0.90 is the point where chroma subsampling switches off and full 4:4:4 colour encoding begins. The file is 21% smaller than q=0.95 and the error is 36× lower. There is no reason to go above q=0.90 on this encoder — you pay more bytes for more error.
  2. The plateau between q=0.70 and q=0.50 is the efficient middle. Each 5-point step in this range removed 10,000–33,000 bytes and added 0.11–0.13 to the MAE. The marginal cost was 0.003–0.007 MAE per kB — the lowest on the curve. If the byte budget is flexible, this is where the encoder is working hardest for the least damage.
  3. Below q=0.15, stop. The marginal cost jumped from 0.044 to 0.461 per kB — a 10× increase — and the MAE went from 3.54 to 9.93. The PSNR dropped below 35 dB, which is the threshold where compression artefacts become structurally visible rather than just soft. Every kilobyte saved below q=0.15 costs more in visual damage than the previous 70% of the quality range combined.
  4. Content determines where the bytes plateau. On a smooth gradient, the file size barely moves below q=0.75 — there is nothing to compress. On a text screenshot, the file stays large even at q=0.10 because the edges resist quantization. If the image is smooth, q=0.50 is the floor; if it is detailed, the floor is closer to q=0.15 but the error at that floor is higher.
  5. If the output must hit a byte target, use the tool, not canvas.toBlob. The tool's byte-target search handles the quality floor and dimension scaling that canvas.toBlob does not. But understand what the tool does below 150 KB on a 6-megapixel image: it holds quality at 40.47% and shrinks the picture. The segment audit page shows where those bytes come from inside the JPEG structure, and the 200 KB page shows where the quality floor kicks in.

Questions this page gets

What happens when you reduce image quality step by step?

The file shrinks in a staircase, not a ramp. On a 3000 × 2000 photograph encoded at 19 quality levels from q=0.95 to q=0.05, the two biggest byte drops — 179,775 bytes (q=0.95→0.90) and 167,408 bytes (q=0.80→0.75) — together removed 41% of the file across 10% of the quality range. The encoder switches quantization tables at specific thresholds, producing steps rather than a smooth curve.

At what quality level does reducing image quality stop being worth it?

Below q=0.15 on this photograph. The MAE went from 3.54 at q=0.15 to 9.93 at q=0.05 — a 2.8× jump in the final 10% of the quality range. Above q=0.15, each 5-point step added 0.07–0.75 to the MAE. Below it, one step added 5.63. The PSNR dropped from 34.79 dB to 26.35 dB in that same step.

Is q=0.95 better quality than q=0.90?

No — on Chrome's canvas.toBlob encoder, q=0.90 produced a file with 36 times less error than q=0.95. The MAE at q=0.95 was 0.1999 (PSNR 53.86 dB); at q=0.90 the MAE was 0.0055 (PSNR 69.16 dB). The q=0.95 file was also 27% larger. The cause is chroma subsampling: the encoder applies 4:2:0 subsampling at high quality levels and stops below ~q=0.90, so q=0.90 encodes full-resolution colour.

Does the quality cliff move with different image content?

Yes. On a smooth gradient, bytes plateaued at q=0.75: 84,335 bytes at q=0.75, 24,118 at q=0.10, with MAE only doubling. On a text screenshot, q=0.10 still produced 114,806 bytes with a MAE of 5.70, because text edges resist quantization at every level. At q=0.50 the text screenshot cost 4.6× more bytes per pixel than the night photo and 17.5× more than the gradient.

Is the image uploaded when I reduce its quality?

No. Decoding, encoding and the quality measurement all run inside the browser tab. Every number on this page was produced by Chrome's in-browser canvas.toBlob encoder and the same in-browser compressor you can use yourself, and no request carrying image data leaves the machine.

What is the difference between reducing quality and reducing file size?

Reducing quality means setting the encoding quality parameter directly — canvas.toBlob('image/jpeg', q) at a fixed quality, with no byte target and no dimension change. Reducing file size means setting a byte target and letting the tool search for the quality that hits it. Below 150 KB on this photograph, the tool held quality at 40.47% and shrank dimensions, while canvas.toBlob at q=0.40 kept full 3000 × 2000 at 145,890 bytes. The two approaches produce different files because the tool also scales the image.

Where the numbers come from. All figures measured on 8 November 2026 with Chrome 157.0.8103.38 (headed, driven over the DevTools protocol) on Windows 11. Experiment one: one 3000 × 2000 night photograph, 689,299 bytes, encoded with canvas.toBlob('image/jpeg', q) at 19 quality levels (0.95, 0.90, 0.85, … 0.10, 0.05), one run per level in a fresh tab. Each output blob was read with blob.size and FileReader.readAsArrayBuffer().byteLength (agreed on all 19). Dimensions were verified with createImageBitmap and Image.naturalWidth (agreed on all 19; every output was full-frame 3000 × 2000). Error was calculated by decoding each output into an ImageData buffer via a second canvas, then comparing each pixel's RGB channels against the source ImageData: MAE = Σ|src−out| ÷ (pixels × 3), MSE = Σ(src−out)² ÷ (pixels × 3), PSNR = 10 × log₁₀(255² ÷ MSE). Experiment two: three content types (night photograph, text screenshot, smooth gradient) at five quality levels (0.90, 0.75, 0.50, 0.25, 0.10), same measurement method. Experiment three: the site's own compressor (window.exactSize) in byte-target mode at 13 targets from 5 KB to 500 KB, same photograph, with error calculated against the decoded source for the four targets that preserved full 3000 × 2000 dimensions (150, 200, 300, 500 KB). Below 150 KB the tool scaled the image, so per-pixel error against the full-frame source was not calculable. The five whole-frame byte values from experiment three (20,385 / 61,310 / 102,087 / 202,286 / 511,019) are byte-identical to this site's runs on the same photograph on 30 October, 2 November, 4 November, 5 November, 6 November and 7 November — seven sessions, same bytes, confirming the tool's encoder is deterministic. The canvas.toBlob bytes are not comparable to the tool's bytes across quality levels because the two encoders may use different quantization table selections and different chroma subsampling thresholds; the comparison in the last table is at approximately matched byte counts, not at matched quality parameters. One photograph only: the staircase pattern and the q=0.95 anomaly are properties of Chrome's JPEG encoder and this image's content; a different image would produce different byte counts at each step, but the step structure and the subsampling switch are properties of the encoder.

Read these alongside: the JPG segment audit parses the output file's binary structure to show where the bytes come off; the JPEG size reducer splits the same error by colour channel (Y, Cb, Cr); the spatial distribution page cuts the photograph into 40 tiles and maps where the error lands; the 200 KB page measures where the tool's quality floor kicks in and dimension scaling starts; the second-pass page shows that a second encode at the same target returns the identical file; the photo compressor online page measures how long each of those compressions actually takes — the time peaks where the quality floor kicks in; the KB ladder puts six byte budgets in one table; and the PNG size reducer removes bytes without touching a pixel — a different mechanism entirely.