Split out from #196 so the palette-size discussion there is not buried under result reports.
#196 has been an argument about how large a SIXEL palette should be, conducted almost entirely from the encoder side. What has been missing is the other half: what a person actually perceives on a real terminal. I built a test to measure it, ran it on myself, and I would like other people to run it too — the scripts are linked below.
The short version
On 640×480 photographic content encoded with libsixel's current develop quantiser and dither, I cannot tell a 128-colour image from a 256-colour one. Not by looking at which is prettier, and not by deliberately hunting for the artefact I have spent a decade learning to spot.
The same procedure — same images, same display, same ten-second window — picks out a 32-colour palette at p = 0.035 and a 16-colour palette at p = 0.000046. So this is not a blind test that fails to detect anything; it detects small palettes easily and stops detecting somewhere between 64 and 128 colours.
What that does and does not mean. It does not show that a 256-colour limit is the right ceiling for SIXEL. It shows that for photographs — the content most often shown off in these discussions — quality saturates well below 256 colours once you have a decent adaptive palette and a decent dither. If the case for 1024 or 4096 colours is going to be made, it has to be made on the content where a small palette actually hurts: smooth gradients, flat-shaded CG, UI screenshots, scientific colour maps. I think that is where this discussion should go next, and I would rather get there with measurements than with assertions.
I am one observer on one laptop panel, which is not enough to conclude anything. Hence the request.
Results so far
All runs use the same 100 photographs. xterm on XQuartz, 43.6 ppd (the 1280 px image measures 256 mm on screen; viewing distance ~500 mm):
| Comparison |
Judging appearance |
Deliberately hunting the artefact |
| 256 vs 16 |
— |
70 / 99 (70.7 %), p = 0.000046 |
| 256 vs 32 |
— |
61 / 100, p = 0.035 |
| 256 vs 64 |
48 / 100, p = 0.76 |
60 / 98 (61.2 %), p = 0.033 |
| 256 vs 128 |
47 / 100, p = 0.62 |
51 / 100, p = 0.92 |
iTerm2, dot-by-dot, 87.3 ppd, same observer and distance, judging appearance: 256 vs 64 → 51/100, 256 vs 128 → 51/100.
Denominators below 100 are the trials actually answered; a trial with no keypress inside ten seconds is recorded as a timeout and excluded rather than scored as an error.
Three things come out of this.
The method has sensitivity. 16 colours at p = 0.000046 settles the obvious objection that ten seconds and a side-by-side layout are simply not enough to see anything. They are enough. The 128-colour result is a null of the phenomenon, not of the instrument.
How you attack the task matters at 64 colours and not at 128. Holding observer, images, display and distance fixed and changing only my strategy moved 64 colours from 48 to 60 (Fisher p = 0.066) and moved 128 colours from 47 to 51 (Fisher p = 0.67). What I was doing in the second pass was specific: find the small-area outlier colours and check whether their saturation has collapsed. That technique pays at 64 colours. At 128 there are too few images left containing a region where it applies.
Nothing survives at 128 colours. Pooling every 128-colour run — both strategies, both rendering paths — gives 149 / 300, p = 0.95, 95 % CI [43.9 %, 55.5 %]. For this observer on this display the true rate is within about six points of chance in either direction.
So my detection threshold sits between 64 and 128 colours, and that is a threshold for deliberate expert search. For ordinary appearance judgement it is already gone by 64.
Why you should not weight my numbers heavily
I am close to the worst available observer for this. I have worked on colour quantisation for over a decade; I know where a shrinking palette breaks first, and by the 16-colour run I was no longer judging appearance at all. That makes me a decent upper bound on detectability — if someone who knows exactly where to look cannot beat chance at 128, a person looking at a picture almost certainly cannot — and a poor witness for the appearance question that the test actually asks.
It also means the ladder above is not a clean psychometric function: the 64- and 128-colour appearance runs happened before I had adopted the search strategy, and the 16- and 32-colour runs after. That is why I re-ran 64 and 128 under the search strategy, and why both columns are shown.
What the discussion needs is naive observers, on ordinary displays. That is the ask.
Method
- 100 trials. Each trial shows the same photograph twice, side by side (1280×480 total): once encoded with 256 colours, once with the reduced palette.
- The side carrying the 256-colour image is an exact 50/50 shuffle (fixed seed, maximum run length 4), so holding down one key scores exactly 50.
- Press 1 for the left image or 2 for the right one — whichever looks better. You are never told which one has more colours. Please guess when you cannot tell; a timeout only costs statistical power.
- Ten seconds per trial. A summary line is printed at the end.
- Sources are 100 distinct photographs from the Wikimedia Commons Featured pictures collection, all under CC0, public domain, CC BY or CC BY-SA. Attribution is embedded in each script's header and listed at the bottom of this post.
- Each source is centre-cropped to 640×480 at 1:1 and never resampled, so what you judge is the encoder's output and not a scaler's.
Encoding, on the develop branch:
img2sixel --threads=2 \
--diffusion=lso2 \
--quantize-model=kmeans:inittype=pca:binning=none:merge=ward \
--clustering-colorspace=oklab --working-colorspace=cielab \
--colors=<256|128|64|32|16>
That is a k-means palette (PCA init, no binning, Ward merge) clustered in OKLab with libsixel's lso2 variable error-diffusion tables — the current best encoder path, not the historical median-cut default. The claim under test is "256 colours are enough given a good quantiser and a good dither", not "256 colours are enough however you encode".
The objective difference is not zero. Over a 20-image sample, PSNR between the 256-colour version and the reduced one:
| Palette |
PSNR vs 256 colours |
| 128 |
35.1 dB (30.5–40.0) |
| 64 |
33.4 dB (28.3–38.5) |
| 32 |
31.3 dB (25.8–37.5) |
| 16 |
28.6 dB (23.1–35.6) |
The question is whether that difference survives the trip through your display and your eye.
Why every test uses the same 100 photographs
The four scripts show the same images. The small-palette tests are not a second measurement of the same thing — they are a positive control.
If everyone scores 50 on the 128-colour test, the first reasonable objection is that the task is impossible. The only way to answer it is to show that the same method, on the same images, with the same observer, does detect a difference when a larger one is present. Holding the image set constant is what makes that argument work: with different photographs in each test, "detected at 16, not at 128" could just as easily be a difference in content difficulty.
The cost is carry-over from seeing the same photographs more than once, and it is worth being explicit about which way it pushes. Remembering which side you pressed is unlikely — these are near-coin-flip judgements — and the scripts use independent left/right sequences, so a repeated side-choice carries little information. Remembering where an image is weak is much more likely, and it makes you more sensitive on later runs. That is why the suggested order is smallest palette first: it puts the run that matters, 256 vs 128, where familiarity is working in favour of detecting a difference rather than against it.
If you prefer a different order, that is fine — just say which order you used.
Running it
curl -LO https://github.com/saitoha/libsixel/releases/download/perception-test-2026-08/test-p256-vs-p128-commons.bash
bash test-p256-vs-p128-commons.bash
Fifteen minutes: run 128. Half an hour: run 16 first, then 128 — the 16-colour run is what makes a null at 128 mean something.
The scripts read their own image payload from $0, so they must be run as files; curl … | bash will not work. Ctrl+C restores the terminal.
Requirements:
- A terminal with SIXEL support, at least 256 colour registers, and per-image (private) colour registers.
- At least 1280×496 px of usable text area — if the image makes the screen scroll, the two halves will not line up.
bash, base64 -d, gunzip.
- xterm:
xterm -ti vt340, with XTerm*maxGraphicColorRegisters: 256 or more and privateColorRegisters: true (the default). iTerm2 works too, but renders SIXEL dot-by-dot, so on a HiDPI panel the image appears at half its nominal size.
For terminal authors — 256 + 128 exceeds the 256-register limit, so each trial is drawn as two SIXEL images at the same cursor origin: a 1280×480 image with P2 = 1 (transparent background) painting the right half, then a 640×480 image painting the left half, each with its own palette. If your terminal shares one register file across images and stores graphics as indexed pixels, the second image's palette redefinition will corrupt the first and your result will be meaningless.
Sanity check: the first trial should show two clean, essentially identical photographs. If either half is garbled, blocky, or vertically offset, stop — your terminal is not rendering the test correctly, and that is itself worth reporting.
What to report
The score line, plus the two things that decide whether the difference is even resolvable for you:
-
Angular resolution (ppd) — or the raw numbers: the physical width of the 1280 px image on screen, and your eye-to-screen distance.
# usage: <image width in px> <measured width in mm> <viewing distance in mm>
python3 -c 'import math,sys; px,w,d=map(float,sys.argv[1:4]); print("%.1f ppd" % (1/math.degrees(2*math.atan(w/px/2/d))))' 1280 300 600
-
Visual acuity, corrected as you ran the test — decimal, Snellen or logMAR.
And, because my own data says it is worth more than ten points: how you did the task. Were you judging which picture looked nicer, or hunting for a specific artefact? If you work on image processing, say so.
Useful extras:
- Display model, panel type, and bit depth — true 8-bit versus 6-bit + FRC changes dither visibility a lot
- Colour gamut and profile, plus any night-shift or colour-management filter that was active
- Display scaling, which cuts both ways: fractional or 2× scaling resamples the image and can smear the dither, while a terminal rendering dot-by-dot on a HiDPI panel shows the image at half its nominal size and raises your effective ppd. Measure the on-screen width rather than assuming.
- Ambient lighting, OS, terminal emulator and version
Script: p128 / p64 / p32 / p16
Order: which ones you ran, in what order
Strategy: judged appearance / hunted for the artefact
Result: 256colors: __ / ______: __ / timeout: __
ppd: __ ppd (1280 px = ___ mm on screen, viewing distance ___ mm)
Visual acuity: ___ (corrected / uncorrected)
Background: do you work on image processing / quantisation?
Display: model, panel type, bit depth
Colour: gamut / profile / filters active
Scaling: 1:1 or ___ %
Ambient light: dark / dim / office / bright
Terminal: emulator + version, OS
Notes: anything you noticed
How to read your score
Chance is 50. Exact two-sided binomial test, with the Clopper–Pearson 95 % interval for your true rate:
| Score / 100 |
two-sided p |
95 % CI |
Reading |
| 35 |
0.0035 |
25.7 – 45.2 % |
clear preference for the reduced image |
| 39 |
0.035 |
29.4 – 49.3 % |
reliable preference for the reduced image |
| 45 |
0.37 |
35.0 – 55.3 % |
indistinguishable |
| 50 |
1.00 |
39.8 – 60.2 % |
indistinguishable |
| 55 |
0.37 |
44.7 – 65.0 % |
indistinguishable |
| 60 |
0.057 |
49.7 – 69.7 % |
borderline |
| 61 |
0.035 |
50.7 – 70.6 % |
reliable preference for 256 |
| 65 |
0.0035 |
54.8 – 74.3 % |
clear preference for 256 |
| 70 |
0.0001 |
60.0 – 78.8 % |
strong preference for 256 |
Anything inside 40–60 is what "I cannot tell these apart" looks like. A single participant can only bound their own rate to about ±10 points, so pooling is where the answer comes from: ten people is a thousand trials, enough to resolve a rate as small as 53 %.
Read the table in both directions. A score meaningfully below 50 is a real possible outcome, not noise — see the prior art below.
Prior art: this has essentially been done before
Doolittle MH, Doolittle KW, Winkelman Z, Weinberg DS. Color images in telepathology: how many colors do we need? Hum Pathol. 1997;28(1):36–41. doi:10.1016/S0046-8177(97)90276-8 (PMID 9013829)
Thirty pathologists each judged 30 image pairs of microscopy images: a 24-bit truecolor original against an 8-bit (256-colour) version produced with an adaptive colour reduction algorithm plus diffusion dithering — essentially what img2sixel does now. Two questions per pair: can you detect a difference, and, regardless, which is better?
- Conscious detection of a difference was not statistically significant.
- When forced to choose, there was a significant preference for the 8-bit images (p < .005).
On diagnostic microscopy — content where you would expect colour fidelity to matter most — 256 colours with a good quantiser and a dither was not merely indistinguishable from 16.7 million, it was preferred. The authors attribute this to the apparent crispness a dithered, reduced palette gives an image.
That is a 1997 result on a 1997 display and says nothing directly about SIXEL. But it means "more colours is obviously better" has already failed an experimental test once, and it tells you what to watch for here: a dithered, reduced palette adds high-frequency structure that reads as extra acutance, and an image with more apparent local contrast can genuinely look better in a forced choice. If several participants land in the 35–45 band, that is a finding in its own right.
What would change my mind
Angular resolution is the obvious moderator, and the arithmetic is simple: the finest structure an error-diffusion dither can produce alternates every pixel, so its fundamental frequency is half your display's ppd.
| Setup |
ppd |
finest dither structure |
| 96 ppi 1080p monitor, 500 mm |
33 ppd |
16.5 cycles/deg |
| 14″ MBP XDR via XQuartz at 2×, 500 mm |
43.6 ppd |
21.8 cycles/deg |
| 27″ 1440p (~109 ppi), 600 mm |
45 ppd |
22.5 cycles/deg |
| 27″ 4K (~163 ppi), 600 mm |
67 ppd |
33.6 cycles/deg |
| 14″ MBP XDR 1:1, 500 mm |
87 ppd |
43.6 cycles/deg |
Human contrast sensitivity peaks around 3–6 cycles/deg and falls off steeply above ~20, especially for low-contrast differences. Both of my measurement points, 43.6 and 87.3 ppd, sit above that knee and both are null — consistent with the geometry explanation, but they do not test it, because the interesting region is lower.
The measurement I want is somebody on a conventional, non-HiDPI monitor at around 33 ppd. If the difference is visible there and not on Retina panels, we know exactly which viewing conditions the palette-size argument applies to. A score of 65+ on a low-ppd display would be far more informative than ten more nulls from Retina laptops. Please run this on your least impressive monitor.
A result on non-photographic content would change my mind faster still, and I do not have that test yet.
Limitations
- This measures preference, not raw discriminability, and they are not the same number. Someone who can see a difference but has no consistent preference lands near 50 either way. Doolittle et al. separated them by asking both questions per pair. In my own runs the gap is twelve points at 64 colours: 48 judging appearance, 60 hunting the artefact. If you switch to hunting, you are answering the other question — worth doing, but say which you did. A proper ABX or triangle design would measure discriminability cleanly; I can build one if there is appetite.
- Photographs only. Gradient ramps, flat-shaded CG, UI screenshots and scientific colour maps are exactly where a small palette hurts most, and they are deliberately absent. A null here bounds the photographic case and nothing else.
- Dithering is on, using
lso2. This says nothing about undithered output or about the old median-cut path.
- Content is fixed at 640×480. A larger image gives the quantiser more pixels but not more colours, so the effect may differ at 1920×1080.
- The image set was selected without reference to the outcome variable. Candidates were filtered on licence, source dimensions (1400–3600 px wide, so a 1:1 centre crop frames comparably), mean saturation (excluding monochrome photographs, which carry almost no chroma-quantisation signal), entropy, and a cap of two images per photographer; non-photographic content was then removed by eye. Nothing was selected or rejected on the basis of how much the two encodings differ, since that is the quantity under test.
Where this goes
I will collate results in this thread and post a summary back to #196. If the 128-colour null replicates across displays and observers, then for photographic content the encoder's 256-colour ceiling is not the binding constraint, and the case for 1024 or 4096 has to be built on the content types this test deliberately excludes. Defining those cases concretely, and measuring them, seems to me the useful next step — and I would rather the palette-size decision rest on that than on anyone's intuition, mine included.
Image credits — 100 photographs from Wikimedia Commons Featured pictures (click to expand)
Every image has been modified: centre-cropped to 640×480 at 1:1 (no resampling) and re-encoded with a reduced colour palette. No other alteration was made. The modified versions are distributed under the licence of the original; adaptations of CC BY-SA 2.0 / 2.5 / 3.0 originals are distributed under CC BY-SA 4.0, as permitted by the "later version" clause of those licences. The images are provided without warranties, as set out in Section 5 of the applicable Creative Commons licence.
The shell code of the test scripts is under libsixel’s own licence (MIT). The photographs are included as a collection, not as an adaptation of that code, so the CC BY-SA images do not place the code under CC BY-SA.
The same attribution is embedded in the header of every test script, so it travels with the file.
| # |
Image |
Author |
Licence |
| 1 |
001 Humpback whale breaching in Ballena Marine National Park Photo by Giles Laurent.jpg |
Giles Laurent |
CC BY-SA 4.0 |
| 2 |
001 Volcano eruption of Litli-Hrútur in Iceland in 2023 Photo by Giles Laurent.jpg |
Giles Laurent |
CC BY-SA 4.0 |
| 3 |
02.Trinidad (59).JPG |
Elemaki |
CC BY-SA 3.0 |
| 4 |
04-09-12-Schaupflügen-Fahrenwalde-RalfR-IMG 1232.jpg |
Ralf Roletschek |
CC BY-SA 2.5 |
| 5 |
13-06-07 RaR MC Fitti 03.jpg |
Achim Raschka (talk) |
CC BY-SA 4.0 |
| 6 |
1928 Model A Ford.jpg |
Richard Smith |
CC BY 2.0 |
| 7 |
2 x Saab JAS-39 Gripen and 2 x Aero L-159 of the Czech Air Force, based at AFB Čáslav, inflight.jpg |
Milan Nykodym |
CC BY-SA 2.0 |
| 8 |
2004-tsunami.jpg |
David Rydevik (email: david.rydevikgmail.com), Stockholm, Sweden. |
Public domain |
| 9 |
2007 Matterhorn.jpg |
Alps |
Public domain |
| 10 |
2007 Nagaoka Festival 004 Edit.jpg |
Kropsoq |
CC BY-SA 3.0 |
| 11 |
20070818-0001-strolling reindeer.jpg |
Alexandre Buisse (Nattfodd) |
CC BY-SA 3.0 |
| 12 |
2009K4760 - Жовква (Львівська).jpg |
Мирослав Видрак |
CC BY-SA 4.0 |
| 13 |
2010-kodiak-bear-1.jpg |
Yathin S Krishnappa |
CC BY-SA 3.0 |
| 14 |
2011-03-09-fort-du-lomont-10.jpg |
Thomas Bresson |
CC BY 3.0 |
| 15 |
2012.06.07.-001-Erlichsee Oberhausen-Rheinhausen-Spitzenfleck-Maennchen im Flug-crop.jpg |
Andreas Eichler |
CC BY-SA 3.0 |
| 16 |
2012.08.10.-5-Mannheim Vogelstang-Kleiner Kohl-Weißling-Paarung-crop.jpg |
Andreas Eichler |
CC BY-SA 3.0 |
| 17 |
2014 Modding komputera 01.jpg |
Halicki |
CC BY 3.0 |
| 18 |
2016 Falcon 9 at Vandenberg Air Force Base.jpg |
SpaceX |
CC0 |
| 19 |
20160805 Inle Lake 7434.jpg |
Jakub Hałun |
CC BY-SA 4.0 |
| 20 |
2017 - Київ - Місячний вечір на Замковій горі.jpg |
Moahim |
CC BY-SA 4.0 |
| 21 |
20170624 Schneeberglandrallye DSC 7444.jpg |
Granada |
CC BY-SA 4.0 |
| 22 |
20180105 Men's handball Austria - Czechia Janko Bozovic 850 9203.jpg |
Granada |
CC BY-SA 4.0 |
| 23 |
2019-11-24 Men's World Cup at 2019-20 Luge World Cup in Igls by Sandro Halank–031.jpg |
Sandro Halank, Wikimedia Commons |
CC BY-SA 4.0 |
| 24 |
2021-01-15 Eberspächer Luge World Cup Oberhof 1DX 4602 by Stepro.jpg |
Steffen Prößdorf |
CC BY-SA 4.0 |
| 25 |
25 Cassutt (N-6291N) Formula One Racer 2014 Reno Air Races photo Don Ramey Logan.jpg |
Don Ramey Logan |
CC BY-SA 4.0 |
| 26 |
28-090504-black-headed-bunting-at-first-layby.jpg |
Mark S Jobling |
CC BY 3.0 |
| 27 |
2909 vallon moy res.jpg |
Berrucomons |
CC BY-SA 3.0 |
| 28 |
30th St. Moritz Polo World Cup on Snow - 20140202 - Cartier vs Ralph Lauren 9.jpg |
Clément Bucco-Lechat |
CC BY-SA 3.0 |
| 29 |
360 degrees fogbow.jpg |
Brocken Inaglory |
CC BY-SA 3.0 |
| 30 |
4-14 Marines in Fallujah.jpg |
Lance Corporal Samantha L. Jones, USMC |
Public domain |
| 31 |
4th FW Strike Eagles assist shuttle launch.jpg |
Capt. John Peltier, USAF |
Public domain |
| 32 |
A bad sales day.jpg |
Wilfredor |
CC BY-SA 4.0 |
| 33 |
A Brazilian family in Rio de Janeiro by Jean-Baptiste Debret 1839.jpg |
Jean-Baptiste Debret |
Public domain |
| 34 |
A bunch of rebar up close.jpg |
W.carter |
CC BY-SA 4.0 |
| 35 |
A chinstrap penguin (Pygoscelis antarcticus) on Deception Island in Antarctica.jpg |
Christopher Michel |
CC BY 2.0 |
| 36 |
A Larva of Papilio memnon 09sai.jpg |
池田正樹 (talk)masaki ikeda |
CC BY-SA 3.0 |
| 37 |
A member of the ATS (Auxiliary Territorial Service) serving with a 3.7-inch anti-aircraft gun battery, December 1942. TR452.jpg |
Malindine E G (Lt), Tanner (Lt), War Office official photographer |
Public domain |
| 38 |
A New View of the Tarantula Nebula.jpg |
NASA |
Public domain |
| 39 |
A sculpture at the entrance to the palace of Versailles.jpg |
Jean-Baptiste Tuby / After Charles Le Brun |
CC BY 2.0 |
| 40 |
A surfer at the wave edit.jpg |
Brocken Inaglory edit by user:Kallerna |
CC BY-SA 3.0 |
| 41 |
A Taraxacum Ruderalia dandelion clock.jpg |
Richard Bartz, Munich Makro Freak |
CC BY-SA 2.5 |
| 42 |
A USAF F-16 pilot breaking right on final approach over northern Las Vegas.jpg |
Master Sgt. Benjamin Bloker |
Public domain |
| 43 |
A5 Aarebruecke.jpg |
Ikiwaner |
CC BY-SA 3.0 |
| 44 |
Abeja (Bombus terrestris) en un Ptilotus exaltatus, jardín botánico de Tallin, Estonia, 2012-08-12, DD 01.JPG |
Diego Delso |
CC BY-SA 3.0 |
| 45 |
Abies koreana (szyszki).JPG |
Lestat (Jan Mehlich) |
CC BY-SA 3.0 |
| 46 |
Acanthiza pusilla - Austin's Ferry.jpg |
JJ Harrison (https://www.jjharrison.com.au/) |
CC BY-SA 3.0 |
| 47 |
Acanthodoris lutea laying eggs 1.jpg |
Brocken Inaglory |
CC BY-SA 4.0 |
| 48 |
Acapulco fishermen.jpg |
Tomascastelazo |
CC BY-SA 3.0 |
| 49 |
ACE EMD F40PH Fremont - San Jose.jpg |
Kabelleger / David Gubler (http://www.bahnbilder.ch) |
CC BY-SA 3.0 |
| 50 |
Aceras anthropophora (detail).jpg |
Hans Hillewaert |
CC BY-SA 4.0 |
| 51 |
Acinonyx jubatus -Southern Namibia-8.jpg |
Joachim Huber |
CC BY-SA 2.0 |
| 52 |
Acorn woodpecker holding a nut in its beak-0225.jpg |
Frank Schulenburg |
CC BY-SA 4.0 |
| 53 |
Actinoscyphia aurelia 1.jpg |
Image courtesy of Aquapix and Expedition to the Deep Slope 2007, NOAA-OE |
Public domain |
| 54 |
Actinostola.jpg |
Expedition to the Deep Slope 2007, NOAA-OE |
Public domain |
| 55 |
Actitis hypoleucos.jpg |
Ben Fredericson from Germany |
CC BY 2.0 |
| 56 |
AD2009Aug07 Natrix helvetica 01.jpg |
Bernie |
Public domain |
| 57 |
Adansonia digitata MS 6682.JPG |
Marco Schmidt |
CC BY-SA 3.0 |
| 58 |
Adansonia grandidieri04.jpg |
Bernard Gagnon |
CC BY-SA 3.0 |
| 59 |
Aegires gardineri Papua New Guinea by Nick Hobgood-001.jpg |
Nhobgood |
CC BY-SA 3.0 |
| 60 |
Aegithalos caudatus front-on 2.jpg |
Francis C. Franklin |
CC BY-SA 3.0 |
| 61 |
Aepyceros melampus petersi ♂.jpg |
Hans Hillewaert |
CC BY-SA 4.0 |
| 62 |
Aerial view of Portuguese calçada, Instituto Superior Técnico, Lisbon, Portugal julesvernex2.jpg |
Jules Verne Times Two |
CC BY-SA 4.0 |
| 63 |
Aeroflot Airbus A330 Kustov.jpg |
Sergey Kustov |
CC BY-SA 3.0 |
| 64 |
Aerogelflower filtered.jpg |
Aerogelflower.jpg: No information. derivative work: JovanCormac |
Public domain |
| 65 |
Aeshna cyanea female 1.jpg |
Böhringer böhringer friedrich |
CC BY-SA 2.5 |
| 66 |
Aethaloessa calidalis by Kadavoor.jpg |
Jeevan Jose, Kerala, India |
CC BY-SA 4.0 |
| 67 |
Afgevallen kastanje van een paardenkastanje (Aesculus) 10-10-2021 (d.j.b.) 02.jpg |
Dominicus Johannes Bergsma |
CC BY-SA 4.0 |
| 68 |
African emerald cuckoo (Chrysococcyx cupreus) male.jpg |
Charles J. Sharp |
CC BY-SA 4.0 |
| 69 |
African Leopard Near Okevi Waterhole Etosha Namibia.jpg |
Axel Tschentscher |
CC BY-SA 4.0 |
| 70 |
African Spoonbill-001.jpg |
Leo za1 |
CC BY-SA 3.0 |
| 71 |
Afternoon at Tennfjorden, Raftsundet, Hinnøya, Norway, 2015 September.jpg |
Ximonic (Simo Räsänen) |
CC BY-SA 3.0 |
| 72 |
Agapanthus 'White Heaven', ingetogen schoonheid van de ontluikende bloemknop. Locatie, Tuinreservaat Jonkervallei 03.jpg |
Dominicus Johannes Bergsma |
CC BY-SA 4.0 |
| 73 |
Agarplate redbloodcells edit.jpg |
Bill Branson – (Edited by Fir0002)(Edited by Drhx) |
Public domain |
| 74 |
Agrigent BW 2012-10-07 12-24-45.JPG |
Berthold Werner |
CC BY-SA 3.0 |
| 75 |
Agriocnemis pygmaea female, Burdwan, West Bengal, India 11 10 2012.jpg |
Joydeep |
CC BY-SA 3.0 |
| 76 |
Agrius convolvuli korseby.jpeg |
Kristian Peters -- Fabelfroh 15:17, 12 September 2007 (UTC) |
CC BY-SA 3.0 |
| 77 |
Air Chief Marshal Sir Arthur Tedder on the Italian coast, 17 December 1943.jpg |
British official photographer, minor restoration (sky and a small levels tweak only, no need to credit) by Adam Cuerden |
Public domain |
| 78 |
Air Force Fire Training.jpg |
U.S. Air Force photo/Tech. Sgt. Rey Ramon |
Public domain |
| 79 |
Air France A380 F-HPJA.jpg |
Joe Ravi |
CC BY-SA 3.0 |
| 80 |
Aix galericulata Luc Viatour.jpg |
Lviatour |
CC BY-SA 3.0 |
| 81 |
Al quartiere latino, bozzetto di Adolf Hohenstein per La Bohème (1896) - Archivio Storico Ricordi ICON000086 - Restoration.jpg |
Adolfo Hohenstein / Adam Cuerden |
CC BY-SA 4.0 |
| 82 |
Albaicin 2012 San Nicolas Sacromonte.jpg |
Jebulon |
CC0 |
| 83 |
Albi cathedral - choir and choir screen.jpg |
Pom² |
CC BY-SA 3.0 |
| 84 |
Alcazaba Antequera.jpg |
Jebulon |
CC0 |
| 85 |
Alcedo atthis - Riserve naturali e aree contigue della fascia fluviale del Po.jpg |
Luca Casale |
CC BY-SA 4.0 |
| 86 |
Alemania vs Italia - 2014 CERH European Championship - 11.jpg |
Carlos Delgado |
CC BY-SA 4.0 |
| 87 |
Aleuria aurantia fungus orange peel (second view).jpg |
Holger Krisp |
CC BY 3.0 |
| 88 |
Alexander Nevsky Cathedral, Sofia (by Pudelek).JPG |
Pudelek (Marcin Szala) |
CC BY-SA 3.0 |
| 89 |
AlfredPalmerM3tank1942b.jpg |
Alfred T. Palmer |
Public domain |
| 90 |
Alligator mississippiensis 2 babies.jpg |
Ianaré Sévi |
CC BY-SA 3.0 |
| 91 |
Allium 'Lucy Ball' Pink Flower Head 2236px.jpg |
Photo by and (c)2008 Derek Ramsey (Ram-Man). Co-attribution must be given to the Chanticleer Garden. |
CC BY-SA 3.0 |
| 92 |
Allium rothii 1.jpg |
Gideon Pisanty (Gidip) גדעון פיזנטי |
CC BY 3.0 |
| 93 |
Alpen Gelbling, Colias phicomone, Arnica montana 1.JPG |
Böhringer Friedrich |
CC BY-SA 2.5 |
| 94 |
Alphonse-Desjardins.jpg |
Cephas |
CC BY-SA 3.0 |
| 95 |
Alpinistes Aiguille du Midi 03.JPG |
Benh LIEU SONG |
CC BY-SA 3.0 |
| 96 |
Amanita muscaria qtl4.jpg |
Quartl |
CC BY-SA 3.0 |
| 97 |
Ambersweet oranges.jpg |
unknown |
Public domain |
| 98 |
American Bird Grasshopper.jpg |
http://www.birdphotos.com |
CC BY 3.0 |
| 99 |
American robin (71307).jpg |
Rhododendrites |
CC BY-SA 4.0 |
| 100 |
An F-A-18C Hornet launches from the flight deck of the conventionally powered aircraft carrier.jpg |
U.S. Navy photo by Photographer's Mate 3rd Class Jonathan Chandler |
Public domain |
Split out from #196 so the palette-size discussion there is not buried under result reports.
#196 has been an argument about how large a SIXEL palette should be, conducted almost entirely from the encoder side. What has been missing is the other half: what a person actually perceives on a real terminal. I built a test to measure it, ran it on myself, and I would like other people to run it too — the scripts are linked below.
The short version
On 640×480 photographic content encoded with libsixel's current
developquantiser and dither, I cannot tell a 128-colour image from a 256-colour one. Not by looking at which is prettier, and not by deliberately hunting for the artefact I have spent a decade learning to spot.The same procedure — same images, same display, same ten-second window — picks out a 32-colour palette at p = 0.035 and a 16-colour palette at p = 0.000046. So this is not a blind test that fails to detect anything; it detects small palettes easily and stops detecting somewhere between 64 and 128 colours.
What that does and does not mean. It does not show that a 256-colour limit is the right ceiling for SIXEL. It shows that for photographs — the content most often shown off in these discussions — quality saturates well below 256 colours once you have a decent adaptive palette and a decent dither. If the case for 1024 or 4096 colours is going to be made, it has to be made on the content where a small palette actually hurts: smooth gradients, flat-shaded CG, UI screenshots, scientific colour maps. I think that is where this discussion should go next, and I would rather get there with measurements than with assertions.
I am one observer on one laptop panel, which is not enough to conclude anything. Hence the request.
Results so far
All runs use the same 100 photographs. xterm on XQuartz, 43.6 ppd (the 1280 px image measures 256 mm on screen; viewing distance ~500 mm):
iTerm2, dot-by-dot, 87.3 ppd, same observer and distance, judging appearance: 256 vs 64 → 51/100, 256 vs 128 → 51/100.
Denominators below 100 are the trials actually answered; a trial with no keypress inside ten seconds is recorded as a timeout and excluded rather than scored as an error.
Three things come out of this.
The method has sensitivity. 16 colours at p = 0.000046 settles the obvious objection that ten seconds and a side-by-side layout are simply not enough to see anything. They are enough. The 128-colour result is a null of the phenomenon, not of the instrument.
How you attack the task matters at 64 colours and not at 128. Holding observer, images, display and distance fixed and changing only my strategy moved 64 colours from 48 to 60 (Fisher p = 0.066) and moved 128 colours from 47 to 51 (Fisher p = 0.67). What I was doing in the second pass was specific: find the small-area outlier colours and check whether their saturation has collapsed. That technique pays at 64 colours. At 128 there are too few images left containing a region where it applies.
Nothing survives at 128 colours. Pooling every 128-colour run — both strategies, both rendering paths — gives 149 / 300, p = 0.95, 95 % CI [43.9 %, 55.5 %]. For this observer on this display the true rate is within about six points of chance in either direction.
So my detection threshold sits between 64 and 128 colours, and that is a threshold for deliberate expert search. For ordinary appearance judgement it is already gone by 64.
Why you should not weight my numbers heavily
I am close to the worst available observer for this. I have worked on colour quantisation for over a decade; I know where a shrinking palette breaks first, and by the 16-colour run I was no longer judging appearance at all. That makes me a decent upper bound on detectability — if someone who knows exactly where to look cannot beat chance at 128, a person looking at a picture almost certainly cannot — and a poor witness for the appearance question that the test actually asks.
It also means the ladder above is not a clean psychometric function: the 64- and 128-colour appearance runs happened before I had adopted the search strategy, and the 16- and 32-colour runs after. That is why I re-ran 64 and 128 under the search strategy, and why both columns are shown.
What the discussion needs is naive observers, on ordinary displays. That is the ask.
Method
Encoding, on the
developbranch:That is a k-means palette (PCA init, no binning, Ward merge) clustered in OKLab with libsixel's
lso2variable error-diffusion tables — the current best encoder path, not the historical median-cut default. The claim under test is "256 colours are enough given a good quantiser and a good dither", not "256 colours are enough however you encode".The objective difference is not zero. Over a 20-image sample, PSNR between the 256-colour version and the reduced one:
The question is whether that difference survives the trip through your display and your eye.
Why every test uses the same 100 photographs
The four scripts show the same images. The small-palette tests are not a second measurement of the same thing — they are a positive control.
If everyone scores 50 on the 128-colour test, the first reasonable objection is that the task is impossible. The only way to answer it is to show that the same method, on the same images, with the same observer, does detect a difference when a larger one is present. Holding the image set constant is what makes that argument work: with different photographs in each test, "detected at 16, not at 128" could just as easily be a difference in content difficulty.
The cost is carry-over from seeing the same photographs more than once, and it is worth being explicit about which way it pushes. Remembering which side you pressed is unlikely — these are near-coin-flip judgements — and the scripts use independent left/right sequences, so a repeated side-choice carries little information. Remembering where an image is weak is much more likely, and it makes you more sensitive on later runs. That is why the suggested order is smallest palette first: it puts the run that matters, 256 vs 128, where familiarity is working in favour of detecting a difference rather than against it.
If you prefer a different order, that is fine — just say which order you used.
Running it
test-p256-vs-p128-commons.bashtest-p256-vs-p16-commons.bashtest-p256-vs-p32-commons.bashtest-p256-vs-p64-commons.bashFifteen minutes: run 128. Half an hour: run 16 first, then 128 — the 16-colour run is what makes a null at 128 mean something.
The scripts read their own image payload from
$0, so they must be run as files;curl … | bashwill not work. Ctrl+C restores the terminal.Requirements:
bash,base64 -d,gunzip.xterm -ti vt340, withXTerm*maxGraphicColorRegisters: 256or more andprivateColorRegisters: true(the default). iTerm2 works too, but renders SIXEL dot-by-dot, so on a HiDPI panel the image appears at half its nominal size.For terminal authors — 256 + 128 exceeds the 256-register limit, so each trial is drawn as two SIXEL images at the same cursor origin: a 1280×480 image with
P2 = 1(transparent background) painting the right half, then a 640×480 image painting the left half, each with its own palette. If your terminal shares one register file across images and stores graphics as indexed pixels, the second image's palette redefinition will corrupt the first and your result will be meaningless.Sanity check: the first trial should show two clean, essentially identical photographs. If either half is garbled, blocky, or vertically offset, stop — your terminal is not rendering the test correctly, and that is itself worth reporting.
What to report
The score line, plus the two things that decide whether the difference is even resolvable for you:
Angular resolution (ppd) — or the raw numbers: the physical width of the 1280 px image on screen, and your eye-to-screen distance.
Visual acuity, corrected as you ran the test — decimal, Snellen or logMAR.
And, because my own data says it is worth more than ten points: how you did the task. Were you judging which picture looked nicer, or hunting for a specific artefact? If you work on image processing, say so.
Useful extras:
How to read your score
Chance is 50. Exact two-sided binomial test, with the Clopper–Pearson 95 % interval for your true rate:
Anything inside 40–60 is what "I cannot tell these apart" looks like. A single participant can only bound their own rate to about ±10 points, so pooling is where the answer comes from: ten people is a thousand trials, enough to resolve a rate as small as 53 %.
Read the table in both directions. A score meaningfully below 50 is a real possible outcome, not noise — see the prior art below.
Prior art: this has essentially been done before
Doolittle MH, Doolittle KW, Winkelman Z, Weinberg DS. Color images in telepathology: how many colors do we need? Hum Pathol. 1997;28(1):36–41. doi:10.1016/S0046-8177(97)90276-8 (PMID 9013829)
Thirty pathologists each judged 30 image pairs of microscopy images: a 24-bit truecolor original against an 8-bit (256-colour) version produced with an adaptive colour reduction algorithm plus diffusion dithering — essentially what
img2sixeldoes now. Two questions per pair: can you detect a difference, and, regardless, which is better?On diagnostic microscopy — content where you would expect colour fidelity to matter most — 256 colours with a good quantiser and a dither was not merely indistinguishable from 16.7 million, it was preferred. The authors attribute this to the apparent crispness a dithered, reduced palette gives an image.
That is a 1997 result on a 1997 display and says nothing directly about SIXEL. But it means "more colours is obviously better" has already failed an experimental test once, and it tells you what to watch for here: a dithered, reduced palette adds high-frequency structure that reads as extra acutance, and an image with more apparent local contrast can genuinely look better in a forced choice. If several participants land in the 35–45 band, that is a finding in its own right.
What would change my mind
Angular resolution is the obvious moderator, and the arithmetic is simple: the finest structure an error-diffusion dither can produce alternates every pixel, so its fundamental frequency is half your display's ppd.
Human contrast sensitivity peaks around 3–6 cycles/deg and falls off steeply above ~20, especially for low-contrast differences. Both of my measurement points, 43.6 and 87.3 ppd, sit above that knee and both are null — consistent with the geometry explanation, but they do not test it, because the interesting region is lower.
The measurement I want is somebody on a conventional, non-HiDPI monitor at around 33 ppd. If the difference is visible there and not on Retina panels, we know exactly which viewing conditions the palette-size argument applies to. A score of 65+ on a low-ppd display would be far more informative than ten more nulls from Retina laptops. Please run this on your least impressive monitor.
A result on non-photographic content would change my mind faster still, and I do not have that test yet.
Limitations
lso2. This says nothing about undithered output or about the old median-cut path.Where this goes
I will collate results in this thread and post a summary back to #196. If the 128-colour null replicates across displays and observers, then for photographic content the encoder's 256-colour ceiling is not the binding constraint, and the case for 1024 or 4096 has to be built on the content types this test deliberately excludes. Defining those cases concretely, and measuring them, seems to me the useful next step — and I would rather the palette-size decision rest on that than on anyone's intuition, mine included.
Image credits — 100 photographs from Wikimedia Commons Featured pictures (click to expand)
Every image has been modified: centre-cropped to 640×480 at 1:1 (no resampling) and re-encoded with a reduced colour palette. No other alteration was made. The modified versions are distributed under the licence of the original; adaptations of CC BY-SA 2.0 / 2.5 / 3.0 originals are distributed under CC BY-SA 4.0, as permitted by the "later version" clause of those licences. The images are provided without warranties, as set out in Section 5 of the applicable Creative Commons licence.
The shell code of the test scripts is under libsixel’s own licence (MIT). The photographs are included as a collection, not as an adaptation of that code, so the CC BY-SA images do not place the code under CC BY-SA.
The same attribution is embedded in the header of every test script, so it travels with the file.