visualtoprompt.com

How Do You Weight a Prompt, and How Do You Write a Negative?

Forty photographs, 597 generated terms, not one bracket among them — the writer hands you flat weight and leaves every number to you.

A horizontal slider with a blue left half, and a boxed minus sign beside it.
A horizontal slider with a blue left half, and a boxed minus sign beside it.

The starting point is flat. Across the forty photographs in this site's sample set, the writer emits 597 terms: 557 arrive at exactly 1.00, and the other forty all sit at 9.00 for the same reason — the --ar 16:9 tail is read as a weight. No term in the batch carries a bracket. So nothing in a generated prompt arrives pre-weighted, and every number in yours is one you typed.

Run the range check on those forty lines as they come out and all forty fail, every hit at 9.00 from that same tail. Strip the parameter and forty of forty pass. The lines were never wrong; the checker was reading a parameter.

Negatives get a much smaller surface than the line suggests. Of the 597 terms, four name something a generator could be told to leave out — black and white photograph, on the four files whose monochrome share passed 0.85. The other 593 report a measurement: exposure, contrast, colour temperature, lighting distribution, a vertical, a mood. Negating a report does not change the photograph it reports on, which is why the negative field is where you say what you are adding.

Two runs on the same forty files, the writer at its longest Midjourney setting, then tools/weight-syntax.js. Every count on this page comes from these runs, and every command can be rerun.

Everything below is printed by one file in the library behind this tool, tools/weight-syntax.js. It parses AUTOMATIC1111 weight syntax and rewrites it with the number made visible. You can run every command shown here yourself.

What each mark actually does

Parsing a prompt with all three syntaxes in it — node tools/weight-syntax.js "((a running dog)), [blurry], (sky:1.3)":

   1.21  a running dog
   0.91  blurry
    1.3  sky
SyntaxEffectNumberNote
(term)multiply by roughly 1.11.1nesting compounds
((term))two levels of nesting1.211.1 × 1.1
[term]divide by roughly 1.10.909the de-emphasis bracket
(term:1.3)set the weight outright1.3an outer ( ) still multiplies it
{term}NovelAI strength mark—not reproduced here; the per-level multiplier differs, so no number is invented
From data/prompt-weight-syntax.csv, the table the parser is written against.

Two terms with no marks at all keep weight 1, and commas are the delimiter — so dog, (cat) parses as two terms, at 1 and 1.1.

Bracket depth, both directions

One level at a time, run through the parser. This is the whole argument for writing numbers:

Levels(((term)))[[[term]]]
11.10.91
21.210.83
31.330.75
41.460.68
51.61 — over 1.60.62
61.770.56
71.950.51
82.140.47
92.360.42
102.590.39 — under 0.4
Parens cross the ceiling at five levels. Brackets take ten to cross the floor, because 1.1n climbs faster than 1.1−n falls.

Five parentheses is a thing people type without thinking about it. It reads as "more emphasis"; it delivers 1.61, one hundredth past the top of the band. Ten brackets reads as "kill this"; it delivers 0.39, one hundredth under the bottom, where the token is ignored entirely. Rounded to two places, both look harmless.

The fix is one flag: --to explicit rewrites the whole prompt with the real number on every term.

$ node tools/weight-syntax.js "((a running dog)), [blurry], (sky:1.3)" --to explicit
(a running dog:1.21), (blurry:0.91), (sky:1.3)

And --check reports anything outside the band, exiting non-zero so you can wire it into a script:

$ node tools/weight-syntax.js "(dog:1.3), [cat]" --check
all weights inside 0.4–1.6

$ node tools/weight-syntax.js "(a:1.9)" --check
out of range: a @ 1.9

Two traps that hide a number you did not write

An outer parenthesis multiplies a weight you already set. (dog:1.3) is 1.3. Wrap it once more — ((dog:1.3)) — and the parser returns 1.43, because the explicit number is the inner level and the outer bracket still applies its 1.1. You asked for 1.3 and got 1.43. If a term already carries a number, leave it bare.

Aspect-ratio parameters are read as weights. The parser splits a term on a trailing colon-number, which is exactly what --ar 16:9 looks like. Run the Midjourney-shaped output through it and the whole tail becomes one term at weight 9:

$ node tools/weight-syntax.js "light falling on the centre with darker edges --ar 16:9"
      9  light falling on the centre with darker edges --ar 16

$ node tools/weight-syntax.js "light falling on the centre with darker edges --ar 16:9" --check
out of range: light falling on the centre with darker edges --ar 16 @ 9

Nothing is wrong with the prompt. Strip the trailing parameter before you check weights, or check the terms and leave the parameters out.

One related case: the same word can legitimately carry two different weights at once. blurry, (blurry:1.2) parses to two terms, 1 and 1.2 — which is how you nudge one reading of a word without losing the other.

How to write a negative

The negative field takes the same syntax, and the number means the same thing in both fields: how strongly the term is applied. That single fact decides how a negative should be written.

$ node tools/weight-syntax.js "((blurry)), (watermark:1.4), [[[[[[[[[[text]]]]]]]]]], lowres"
           1.21  blurry
            1.4  watermark
           0.39  text
              1  lowres
$ ... --check
out of range: text @ 0.39

Three of those four are fine. The ten-bracket text is not: at 0.39 it sits under the 0.4 floor, and the note in the syntax table for that floor says plainly that below 0.4 the token is ignored. Whoever stacked those brackets was trying to push text out of the frame harder and ended up removing the instruction instead. Suppression is not emphasis with a minus sign — weakening the term weakens what it does.

So the shape of a working negative is short, plain, mostly unweighted:

This tool does not write a negative for you. Searching every file in the library — 694 lines of JavaScript, plus the data files and the docs — for the string negative returns zero hits. None of the four output shapes carries one. It is the one part of the prompt you are expected to supply yourself, which is also why it is the part most often written by habit.

What a negative can actually remove: forty lines, 597 terms, four nouns

Run the forty real files used across this site through the writer at the longest Midjourney setting, then split every line at its commas. That is 597 terms: 15 per line on the median, 13 at the fewest, 17 at the most.

593 of them are properties — an exposure phrase, a contrast phrase, a colour temperature, a lighting distribution, a vertical, a mood — and every one of those is the output of a threshold sitting on a number that was measured off the pixels. The remaining four are the only terms in the whole batch that name a thing: the string black and white photograph, on the four files whose monochrome share passed 0.85.

So that is the entire surface a negative has to work with on the writer's side: four terms out of 597. Everything else it could bite lives in the generator, not in the line.

Counting terms is one way to size that surface. Counting words is the other, and it asks the half of the question this section does not: not which terms name a thing you could leave out, but how much of the line a negative can actually take away once the words the composer wrote around them are accounted for, and how many of those namings survive being read back by a weight parser. That measurement, across all four shapes and three lengths, is on how much of a generated line a negative prompt can name.

SlotPhrases usedMost commonFilesTerms a negative can name
framing1widescreen 16:9400
exposure4moderate, slightly dim lighting160
contrast3medium contrast280
saturation4moderately saturated colour170
temperature5warm golden colour temperature120
detail3highly detailed with intricate texture220
distribution3evenly lit210
vertical3brighter upper half220
mood4moody and dramatic atmosphere160
subject2empty on 36 of the 40364
all nine slots plus subject32—404

Forty real files, writer at the longest Midjourney setting. "Terms a negative can name" counts terms that denote a thing, not terms a negative deletes.

The parser does not know the word either. Hand tools/weight-syntax.js a line with a negative stuck on the end and it returns three tokens, all at weight 1.00 — the --no dark is glued onto the previous term as ordinary text, because the only characters it treats as syntax are (, [, { and an explicit :number:

   1.00  widescreen 16:9 composition
   1.00  dark
   1.00  low-key lighting --no dark

That is not a defect to route around. In the WebUI the negative has its own field, and in Midjourney --no sits at the end of the whole prompt, so in neither case is it a weighted term. What matters is what you aim it at.

Aim it at a phrase the writer has just measured and you are arguing with your own file. On these forty, moderate, slightly dim lighting is what sixteen of them actually measured; --no dim does not make those sixteen brighter, it asks the generator to draw something other than the photograph you fed in. The measurement has already happened, and the line is a report of it.

Three moves that do change the wording, cheapest first:

For where each of those phrases comes from, see what an image description generator actually writes; for the arithmetic behind a single word, see what the tool measures.

What you are weighting to begin with

The generator hands you four different shapes, because the four targets genuinely do not accept the same prompt. Midjourney gets a comma list plus a trailing --ar parameter; Stable Diffusion gets the same list with masterpiece, best quality, prepended; Flux and DALL·E get sentences, on the basis that they respond better to sentences than to tag soup.

All of it arrives unweighted. Running the parser over the Stable Diffusion and Flux output for each of the three sample images the site uses — night fireworks, strong backlight, code screenshot:

That flat starting point is only half the story, though: the Stable Diffusion line also spends its two strongest slots on the two fixed quality tags, and three of its phrases carry commas of their own, so twelve generated slots arrive as seventeen separate weighted terms. Which term your bracket actually lands on is the subject of where the weights go on a Stable Diffusion prompt built from an image.

SampleTargetTermsWeight range--to explicit changes anything
night-fireworkssd151–1no
night-fireworksflux71–1no
strong-backlightsd151–1no
strong-backlightflux71–1no
text-screenshotsd161–1no
text-screenshotflux71–1no
Every term sits at 1. There is nothing to fix and nothing hidden — the generated text is clean, and weighting is an edit you make on top of it.

That also tells you where weighting can land at all. A comma list is a list of terms; a sentence is one term. Putting (navy:1.3) into the middle of a Flux sentence gives the parser something to find and Flux nothing to act on — the shape it was given is prose. Weight the list, not the sentence.

The number the writer emits on its own: 120 of 480 lines, one term each

Almost everything arrives at 1.00, and every exception lives in one shape. Run the forty files in this site's sample set through all four output shapes at all three lengths: 480 lines, 5,046 terms. 4,926 of those terms sit at 1.00. The other 120 — one per Midjourney line, at every length — sit at 9.00. The other three shapes never emit a number at all, on any of their 360 lines:

ShapeLinesTermsMedian termsTerms not at 1.00Lines failing --check--to explicit returns the line unchanged
midjourney1201,498131201200
sd1201,7381500120
flux120745600120
dalle1201,065900120

What the 9.00 lands on

Not on the parameter. There is no comma in front of --ar 16:9, so the parser welds it to the phrase in front of it and reads the pair as one term — and a term that ends in a colon followed by a number is exactly what it reads as a weight. One real line, 259 characters, read back:

$ node tools/weight-syntax.js "widescreen 16:9 composition, bright, high-key lighting, dominant palette of silver, moss green, cream, grey, muted, desaturated tones, neutral colour temperature, medium contrast, moderate detail and texture, evenly lit, balanced, natural atmosphere --ar 16:9"
      1  widescreen 16:9 composition
      1  bright
      1  high-key lighting
      1  dominant palette of silver
      1  moss green
      1  cream
      1  grey
      1  muted
      1  desaturated tones
      1  neutral colour temperature
      1  medium contrast
      1  moderate detail and texture
      1  evenly lit
      1  balanced
      9  natural atmosphere --ar 16

Across the 120 Midjourney lines the weighted term is the last term in the line 120 times out of 120. It runs a median of 34 characters and 6 words — 18 to 53 characters, 4 to 10 words — which is a median 14.2% of the line, 6.6% at the thinnest and 22.9% at the fattest. The number is not attached to the aspect ratio. It is attached to whichever phrase the writer happened to place last, and that phrase is now reading nine times the rest of the line.

The ratio string itself appears 600 times across the 480 lines, and only 120 of those are read as a number. Every Midjourney line carries 16:9 twice — once inside the framing phrase, where it is mid-term and safe, and once as the tail parameter, where it is at the end of a term and gets read. The other three shapes carry it once each, inside the phrase, and none of their 360 lines produces a weight.

The number is the second half of your aspect ratio

Because the parser takes a trailing colon-number, the weight it invents is exactly the second number of the ratio. Sweep every pixel size from 200×200 to 2000×2000 — 3,243,601 of them — and the ratio strings that come back number 2,064. On all 2,064 the weight read off the tail equals the second number, and that second number takes 60 distinct values from 1 to 60.

Ten of the 2,064 fall inside the 0.4–1.6 band: 1:1 through 10:1, the whole-number panoramas. That is 3,866 of the 3,243,601 sizes, or 0.12%; the remaining 99.88% hand the checker a number you never wrote. Below, the same forty photographs run as Midjourney lines at sixteen real pixel sizes — 640 lines in total:

Pixel size--ar tailFraming phraseWeight readLines out of rangeCharacters deleted
1024×10241:1square 1:110 / 402
1000×99960:60square 1:16040 / 403
1920×9602:1widescreen 16:910 / 402
1200×4003:1ultra-wide panoramic10 / 402
2560×108060:25ultra-wide panoramic2540 / 403
3440×144043:18ultra-wide panoramic1840 / 403
1500×10003:2landscape 3:2240 / 402
2732×204860:45near-square 4:34540 / 403
640×36016:9widescreen 16:9940 / 402
800×45016:9widescreen 16:9940 / 402
1920×108016:9widescreen 16:9940 / 402
2560×144016:9widescreen 16:9940 / 402
3840×216016:9widescreen 16:9940 / 402
1080×13504:5portrait 3:4540 / 402
1440×25609:16portrait 2:31640 / 403
828×179228:60tall vertical 9:166040 / 403

The two square rows are the ones to read twice. A 1024×1024 file reduces to 1:1 and the weight read off it is 1 — inside the band, nothing flagged, 0 of 40 lines out of range. A 1000×999 file is the same picture for every purpose that matters, but the ratio routine never returns 1:1 for a near-square; it caps both sides at 60 and returns 60:60, and the weight read off that is 60. Nothing about the photograph changed. Only the two numbers the ratio was written with did.

Being inside the band does not mean the line came back intact. On the three in-band sizes the tail is still edited: --ar 2:1 comes back as the text --ar 2, the :1 having been consumed to make the weight, and --to explicit fails to return the original string on any of those 120 lines. A clean --check reports on the numbers it read. It does not report on the characters it removed.

Stripping the tail fixes it, and costs you no term

Delete --ar 16:9 from the end of the 120 Midjourney lines and check again: 0 out-of-range terms, 120 of 120 lines clean, and --to explicit now returns all 120 unchanged. The term count does not fall on any of them — 0 of 120 — because the parameter was welded to the phrase in front of it rather than separated from it by a comma, so removing it shortens the last term instead of deleting one.

That is the only edit this needs, and it is the one edit the parser cannot make for you: it cannot tell a parameter from a weight, because to it they are the same pattern. Strip the parameters, then check the terms.

Three rules that have nothing to do with brackets

All three are in docs/prompt-debugging-checklist.md, which is ordered by how often each one is actually the cause — and weighting sits at position four of nine. Position three is use the participle: run produced no running pose, running produced it immediately. Reach for word form before reaching for a heavier number.

Two things the parser refuses to do

Braces get no invented number, and the check that follows is emptier than it looks. Wrap each term of the forty generated lines in braces and rerun the parser:

A clean pass on a brace prompt means nothing was read. A failure on an unbraced one can mean the checker read a parameter. Both results come from the checker, not from the prompt.

Escaped brackets are literal. A backslash takes the next character at face value: \(dog\) parses to the text (dog) at weight 1. That is how you keep a parenthesis that is part of the subject instead of an instruction.

The routine, in the order it pays off

  1. Generate first, weight second. The tool's output is already flat at weight 1 — 15 or 16 terms for Stable Diffusion, 7 for Flux.
  2. Fix wording before numbers. Participle, then order, then weight. Three of the nine checklist items come before weighting.
  3. Write the number, not the brackets. (subject:1.3), never (((subject))).
  4. Strip parameters before you check. --ar 16:9 reads as weight 9.
  5. Run --to explicit and read it. If a number surprises you, it was already in your prompt.
  6. Write the negative plainly and raise it if needed. Plain terms at 1; (text:1.4) instead of ten brackets.

For the nine-item checklist in full and the order it is meant to be worked in, see fixing a wrong image to prompt output. For where the generated text comes from, see what an image to prompt tool measures, and for turning a picture into something you can paste into a generator, see getting a usable AI image prompt from any picture.