DEV Community

Cover image for I Raised My WebP AVIF SSIMULACRA2 Target to 65. The Encoder Still Passed at 60
Sergei Solod
Sergei Solod

Posted on

I Raised My WebP AVIF SSIMULACRA2 Target to 65. The Encoder Still Passed at 60

I thought I was changing one threshold.

Instead, I found two different problems:

  1. I was measuring a second-generation encode against a reference that had already been compressed.
  2. My code could report the stricter threshold correctly while the adaptive encoder still made its decision using the old global one.

The first problem changed how I think about perceptual metrics.

The second changed how I design validation logic.

My original AVIF policy was simple:

target = 60
single allowed worst sample = 58
Enter fullscreen mode Exit fullscreen mode

For small image sets, every representative sample had to reach 60.

For larger sets, I allowed one sample to fall as low as 58 while requiring every other sample to remain at 60 or above.

That worked well enough when I was encoding from the source I actually cared about.

Then the input started looking like this:

higher-quality original: ~2 MB
          ↓
      lossy WebP: ~100 KB
          ↓
          AVIF
Enter fullscreen mode Exit fullscreen mode

The original was gone.

The WebP was all I had.

The obvious question was:

If SSIMULACRA2 60 is acceptable for source → AVIF, why shouldn't the same 60 be acceptable for lossy WebP → AVIF?

The answer depends on what SSIMULACRA2 can actually see.


SSIMULACRA2 measures the reference you give it

SSIMULACRA2 is a full-reference perceptual image metric.

Conceptually, it receives:

reference
candidate
Enter fullscreen mode Exit fullscreen mode

and estimates the perceptual difference between those two images.

Its published quality scale includes approximate anchors around:

50  ≈ medium / fair
70  ≈ high / good
80  ≈ very high
85  ≈ excellent
90  ≈ visually lossless
100 = mathematically lossless
Enter fullscreen mode Exit fullscreen mode

These are useful orientation points, not universal encoding thresholds.

The critical word is reference.

Suppose I still have the real source:

original
   ↓
AVIF
Enter fullscreen mode Exit fullscreen mode

The comparison is:

original ↔ AVIF
Enter fullscreen mode Exit fullscreen mode

If the AVIF scores 60, the metric is describing the perceptual change between the source I care about and the output I am about to ship.

That is exactly the question I want answered.

Now consider this:

original
   ↓ lossy encode
WebP
   ↓ lossy encode
AVIF
Enter fullscreen mode Exit fullscreen mode

If the original no longer exists, the metric sees only:

WebP ↔ AVIF
Enter fullscreen mode Exit fullscreen mode

The first lossy conversion is no longer part of the comparison.

That loss has not disappeared from the pixels.

It has disappeared from the measurement history.

That distinction is the foundation of the whole problem.


Once the derivative becomes the reference, its defects become reference content

Imagine the original contains a perfectly smooth gradient.

The first lossy encoder introduces a little banding.

The WebP still looks acceptable.

Now that WebP becomes the reference for the second conversion.

From SSIMULACRA2's perspective, the existing banding is no longer an error.

It is part of the reference.

If the AVIF introduces more banding, more blur, ringing, edge damage, or other distortion, the metric can react to that additional change.

But it cannot say:

This region already lost information one generation ago.

The earlier image is not available.

So a high score has different meanings depending on the reference.

For a direct encode:

high score
≈
close to the real source
Enter fullscreen mode Exit fullscreen mode

For a second-generation encode:

high score
≈
close to the already-compressed derivative
Enter fullscreen mode Exit fullscreen mode

Those statements are not equivalent.

This does not mean WebP→AVIF transcoding is inherently bad.

It does not mean AVIF automatically amplifies existing artifacts.

It means the second encoder is operating on pixels that may already contain irreversible decisions made by the first encoder.


Source provenance matters more than the extension

This led me away from rules based purely on .webp, .jpg, or .png.

The property I really care about is:

What happened to these pixels before they reached me?
Enter fullscreen mode Exit fullscreen mode

A simplistic implementation would be:

if (extension === ".webp") {
  target = 65;
}
Enter fullscreen mode Exit fullscreen mode

I no longer like that rule.

A better model is closer to:

if (sample.isKnownLossyDerivative) {
  target = 65;
} else {
  target = 60;
}
Enter fullscreen mode Exit fullscreen mode

Why?

Because WebP itself can be lossless.

And there is an important subtlety here:

lossless WebP does not automatically mean clean provenance.

Lossless WebP means that the WebP encoding itself can reproduce its input pixels exactly.

But imagine this chain:

original
   ↓
lossy JPEG
   ↓
lossless WebP
Enter fullscreen mode Exit fullscreen mode

The WebP step did not introduce new loss.

But the pixels inside the WebP already came from a lossy derivative.

So the strongest rule is not:

lossless WebP = clean source
Enter fullscreen mode Exit fullscreen mode

It is:

judge the image by the best provenance information available
Enter fullscreen mode Exit fullscreen mode

For my operational policy, that means roughly:

canonical / trusted source
→ 60 / 58

known already-lossy derivative
→ 65 / 63
Enter fullscreen mode Exit fullscreen mode

A lossless WebP created directly from trusted source pixels can stay on the normal policy.

A lossless WebP wrapping pixels that are already known to be a downstream lossy derivative should not magically erase that history.

The container is not the provenance.


Why I use 65 instead of 60

This part needs a disclaimer.

There is no official SSIMULACRA2 rule saying:

second lossy generation
=
add exactly 5 points
Enter fullscreen mode Exit fullscreen mode

There is no formula where:

60 + previous compression = 65
Enter fullscreen mode Exit fullscreen mode

SSIMULACRA2 scores are not a linear distortion budget that can be added across generations.

So 65 is not a scientific constant.

It is an engineering policy.

My reasoning is simpler.

A target of 60 is already more aggressive than the published high/good anchor around 70.

For a first encode from a trusted source, I am willing to spend that quality budget because reducing bytes is part of the objective.

For a source that I know has already gone through lossy compression, I want the next encoder to have less room to introduce additional distortion.

I could use 70.

That would be more conservative.

But it would also push more outputs toward higher AVIF qualities and larger files.

Without controlled evidence that 70 produces enough additional visual benefit for my workload to justify those bytes, I do not want to force it globally.

So I currently use:

trusted source
target = 60

known lossy derivative
target = 65
Enter fullscreen mode Exit fullscreen mode

I think of 65 as a guardrail.

Not a law.


Why 65/63 instead of 65/62

The original rule was:

target = 60
floor  = 58
Enter fullscreen mode Exit fullscreen mode

The one permitted outlier therefore had a two-point allowance:

60 - 58 = 2
Enter fullscreen mode Exit fullscreen mode

If I raise the normal target to 65 while preserving the same semantics:

65 - 63 = 2
Enter fullscreen mode Exit fullscreen mode

So the corresponding policy becomes:

target = 65
floor  = 63
Enter fullscreen mode Exit fullscreen mode

Using 62 would change the exception to three points:

65 - 62 = 3
Enter fullscreen mode Exit fullscreen mode

That would be odd.

I would be saying:

This source is already lossy, so I want to be stricter.

while also saying:

The worst sample is now allowed to miss its target by more than before.

I could not justify that.

Neither 65 nor 63 is magical.

The useful property is preserving the meaning of the existing policy.


File size cannot tell me how much perceptual damage already exists

A transformation like this looks dramatic:

2 MB → 100 KB
Enter fullscreen mode Exit fullscreen mode

That is roughly a 20× reduction.

It is tempting to turn that number into a quality judgment.

For example:

if (compressionRatio >= 20) {
  target = 70;
}
Enter fullscreen mode Exit fullscreen mode

I do not think that is defensible.

Compression efficiency depends on the content.

A clean illustration with flat regions can sometimes compress extremely well.

A noisy photograph may behave very differently.

Gradients, line art, texture, resolution, chroma, entropy, encoder settings, and source format all matter.

So:

compression ratio
!=
perceptual quality
Enter fullscreen mode Exit fullscreen mode

The question I care about is not:

How impressive was the byte reduction?
Enter fullscreen mode Exit fullscreen mode

It is:

Do I know that this image has already passed through a lossy generation?
Enter fullscreen mode Exit fullscreen mode

Then I found the bug that mattered more than 60 vs. 65

Once I decided on the stricter policy, I expected the implementation change to be trivial.

The code already had the concept of source-specific targets.

Conceptually:

const SOURCE_TARGET = 60;
const LOSSY_DERIVATIVE_TARGET = 65;
Enter fullscreen mode Exit fullscreen mode

There was also logic capable of reporting the correct target for an individual sample.

At first glance, everything looked fine.

But the adaptive search that actually chose the AVIF quality still used a global acceptance condition equivalent to:

const GLOBAL_TARGET = 60;
const GLOBAL_FLOOR = 58;

passed =
  worstScore >= GLOBAL_FLOOR &&
  secondWorstScore >= GLOBAL_TARGET;
Enter fullscreen mode Exit fullscreen mode

The stricter target appeared later in the reporting layer.

So the system could effectively produce this:

sample:
known lossy derivative

required target:
65

actual score:
61.2

log:
BELOW TARGET
Enter fullscreen mode Exit fullscreen mode

while the adaptive search itself still concluded:

61.2 >= 60

PASS
Enter fullscreen mode Exit fullscreen mode

The logs knew about 65.

The encoder was still optimizing against 60.

That was the real bug.

Nothing crashed.

There was no exception.

The build remained green.

The output image looked plausible.

The system could even print the correct stricter threshold.

But the code responsible for selecting the output was solving a different problem.


A threshold that only exists in reporting is not a threshold

That bug gave me a rule I now use beyond image compression:

A configuration value only matters if it reaches the decision that chooses the output.

Defining this:

const LOSSY_TARGET = 65;
Enter fullscreen mode Exit fullscreen mode

does not enforce anything.

Logging this:

required target: 65
Enter fullscreen mode Exit fullscreen mode

does not enforce anything either.

The value has to participate in the actual PASS/FAIL predicate used by the adaptive search.

So instead of keeping the quality policy far away from the measurement, I prefer attaching it directly to each sample.

For example:

function policyForSample(sample) {
  if (sample.isKnownLossyDerivative) {
    return {
      target: 65,
      floor: 63,
    };
  }

  return {
    target: 60,
    floor: 58,
  };
}
Enter fullscreen mode Exit fullscreen mode

Then every measured result can be assessed against its own policy:

function assessSample(result) {
  const policy = policyForSample(result.sample);

  return {
    ...result,

    target: policy.target,
    floor: policy.floor,

    targetMargin:
      result.score - policy.target,

    floorMargin:
      result.score - policy.floor,

    meetsTarget:
      result.score >= policy.target,

    meetsFloor:
      result.score >= policy.floor,
  };
}
Enter fullscreen mode Exit fullscreen mode

Now the measurement carries its own context:

score
target
floor
target margin
floor margin
Enter fullscreen mode Exit fullscreen mode

That makes the next step much safer.


The one-outlier rule should be expressed directly

Suppose the policy is:

For smaller collections:

every sample must reach its own target
Enter fullscreen mode Exit fullscreen mode

For larger collections:

at most one sample may miss its target

but

no sample may fall below its own floor
Enter fullscreen mode Exit fullscreen mode

Then I would implement exactly those semantics instead of reconstructing them indirectly from global score ordering.

function evaluateCandidate(
  results,
  allowSingleOutlier,
) {
  const assessed =
    results.map(assessSample);

  const belowFloor =
    assessed.filter(
      (item) => !item.meetsFloor,
    );

  if (belowFloor.length > 0) {
    return {
      passed: false,
      reason: "below-floor",
      assessed,
    };
  }

  const belowTarget =
    assessed.filter(
      (item) => !item.meetsTarget,
    );

  const allowedMisses =
    allowSingleOutlier ? 1 : 0;

  const passed =
    belowTarget.length <= allowedMisses;

  return {
    passed,
    reason: passed
      ? "passed"
      : "too-many-target-misses",
    assessed,
  };
}
Enter fullscreen mode Exit fullscreen mode

The rule is visible in the code.

There is no hidden assumption that every sample shares one target.


Mixed thresholds break raw "worst score" ordering

This was another important consequence.

Imagine:

Sample A

score  = 63
target = 60
margin = +3
Enter fullscreen mode Exit fullscreen mode

and:

Sample B

score  = 64
target = 65
margin = -1
Enter fullscreen mode Exit fullscreen mode

If I sort by raw SSIMULACRA2 score:

63 < 64
Enter fullscreen mode Exit fullscreen mode

Sample A looks worse.

But relative to the actual requirements:

Sample A
PASS

Sample B
FAIL
Enter fullscreen mode Exit fullscreen mode

The meaningful quantity is no longer just:

score
Enter fullscreen mode Exit fullscreen mode

It is closer to:

score - target
Enter fullscreen mode Exit fullscreen mode

A normalized target margin makes heterogeneous policies comparable:

const targetMargin =
  score - target;
Enter fullscreen mode Exit fullscreen mode

Now:

margin >= 0
→ target passed
Enter fullscreen mode Exit fullscreen mode

and:

margin < 0
→ target missed
Enter fullscreen mode Exit fullscreen mode

The floor can be represented the same way:

const floorMargin =
  score - floor;
Enter fullscreen mode Exit fullscreen mode

The one permitted outlier then has a very clear state:

targetMargin < 0
floorMargin >= 0
Enter fullscreen mode Exit fullscreen mode

This is much safer than sorting raw scores and comparing the two lowest values against global constants.


The adaptive search must use the same predicate

Fixing final validation is not enough.

Suppose the encoder searches for the minimum passing AVIF quality:

q=35
q=43
q=39
q=41
q=40
Enter fullscreen mode Exit fullscreen mode

The definition of passing drives the entire search.

The correct flow is:

encode candidate
      ↓
measure samples
      ↓
apply per-sample policies
      ↓
PASS / FAIL
      ↓
choose next quality
Enter fullscreen mode Exit fullscreen mode

The dangerous version is:

encode candidate
      ↓
compare everything to global 60
      ↓
choose next quality
      ↓
later report that some samples wanted 65
Enter fullscreen mode Exit fullscreen mode

If the search predicate is wrong, correct reporting afterward cannot repair the quality that was already selected.


Historical quality predictions need policy versioning too

There is another subtle issue if the encoder learns from previous runs.

Suppose historical data says:

similar images usually pass around q=38
Enter fullscreen mode Exit fullscreen mode

That prediction only makes sense together with the policy under which those images passed.

If the historical runs used:

60 / 58
Enter fullscreen mode Exit fullscreen mode

and the current source now requires:

65 / 63
Enter fullscreen mode Exit fullscreen mode

the old history is solving an easier problem.

A correct final acceptance predicate still protects correctness.

The search may simply start too low and climb.

But efficiency suffers, and debugging becomes harder.

So I prefer making the policy part of the history version.

For example:

sim-v1-60-58
Enter fullscreen mode Exit fullscreen mode

becomes:

sim-v2-source60-58-lossy65-63
Enter fullscreen mode Exit fullscreen mode

It is not glamorous.

It makes optimization results reproducible.


If the real original exists, I skip the second lossy generation

Whenever I still have the true source, the best solution is simpler.

Suppose I have:

original: 2 MB
Enter fullscreen mode Exit fullscreen mode

and:

WebP derivative: 100 KB
Enter fullscreen mode Exit fullscreen mode

I would rather do:

original
   ↓
AVIF
Enter fullscreen mode Exit fullscreen mode

than:

original
   ↓
WebP
   ↓
AVIF
Enter fullscreen mode Exit fullscreen mode

The original becomes the reference again.

A stricter second-generation threshold cannot recover information that disappeared during the first lossy encode.

This distinction matters:

WebP → AVIF at target 65
Enter fullscreen mode Exit fullscreen mode

means:

preserve this WebP more faithfully
Enter fullscreen mode Exit fullscreen mode

It does not mean:

reconstruct the image that existed
before the WebP was created
Enter fullscreen mode Exit fullscreen mode

Once information is gone from the pixels, SSIMULACRA2 cannot bring it back.

Neither can a higher AVIF quality setting.


Passing the quality gate does not mean transcoding is worth it

There is another decision SSIMULACRA2 cannot make for me.

Suppose:

source WebP = 100 KB
AVIF        = 96 KB
Enter fullscreen mode Exit fullscreen mode

and the AVIF passes 65/63.

The quality requirement is satisfied.

But I saved only 4 KB.

For that saving I added another encoding operation, another lossy generation, processing cost, and another transformation path to maintain.

Maybe the correct optimization is to keep the WebP.

Now consider:

source WebP = 100 KB
AVIF        = 65 KB
Enter fullscreen mode Exit fullscreen mode

with the same perceptual policy still passing.

That is a much more interesting trade-off.

So I separate two questions:

Quality gate: Is the additional distortion acceptable?

Size gate: Is the byte saving large enough to justify another encode?

A candidate can pass the first and still fail the second.


What would actually validate the 65 threshold?

The strongest experiment requires preserved originals.

I would build a representative corpus and compare these chains:

A
original → AVIF
target 60

B
original → lossy WebP → AVIF
target 60

C
original → lossy WebP → AVIF
target 63

D
original → lossy WebP → AVIF
target 65

E
original → lossy WebP → AVIF
target 70
Enter fullscreen mode Exit fullscreen mode

For each final image I would record:

final byte size

selected AVIF quality

SSIMULACRA2:
WebP → final AVIF

SSIMULACRA2:
true original → final AVIF
Enter fullscreen mode Exit fullscreen mode

The last comparison is the important one.

It restores the context that disappears when the WebP derivative becomes the only available reference.

I would also manually inspect difficult content: smooth gradients, fine line art, small text, dense texture, dark gradients, saturated transitions, and sources that already contain visible compression artifacts.

I have not run that controlled experiment across a sufficiently representative corpus of preserved originals.

So I cannot honestly claim:

65 is scientifically optimal
Enter fullscreen mode Exit fullscreen mode

What I can defend is:

For a known already-lossy derivative,
I want a stricter additional-loss budget
than I use for a trusted source.

My current policy is 65/63.
Enter fullscreen mode Exit fullscreen mode

That is an engineering policy.

Not a universal SSIMULACRA2 rule.


The rule I use now

My current mental model is:

  • Trusted / canonical source: 60 / 58
  • Known already-lossy derivative: 65 / 63
  • Lossless encoding does not erase earlier provenance: classify the pixels by their known history
  • Per-sample policy must participate in the real PASS/FAIL predicate
  • Mixed thresholds should be evaluated by policy margin, not raw score ordering
  • Adaptive search and historical prediction must understand the same policy
  • If the true original exists, encode from it
  • If AVIF barely saves bytes, consider keeping the current format

The exact thresholds may change as better controlled data becomes available.

The architecture should not.


The metric was not the bug

SSIMULACRA2 was doing exactly what it was supposed to do.

It compared the reference I gave it with the candidate I gave it.

My first mistake was expecting that score to describe quality loss that happened before the reference existed.

My second mistake was adding a stricter threshold without tracing whether that value actually reached the acceptance condition used by the adaptive search.

Both mistakes came from the same habit:

looking at a number without tracing the decision path around it.

A perceptual score has context.

A reference has history.

A threshold has semantics.

And a configuration value only matters if the code that chooses the output actually obeys it.

For me, that lesson ended up being more important than whether the threshold was 60, 63, or 65.

Top comments (0)