365 karma · joined October 7, 2024
GCC has two attributes for marking functions, "pure" and "const" (not the language const qualifier). C23 introduced the [[reproducible]] and [[unsequenced]] attributes, that are mostly modeled by the GCC extensions, but with some subtle but important differences in their description.
Turns out it's pretty hard to define these concepts if the language is not built around immutability and pure functions from the ground up.
edit:
Reading back, viewing the gradients also not at 100% zoom level could also itself cause the mismatch, because browsers just suck at image scaling.
Others suggest that the error is using the wrong metric for choosing the closest color, but I disagree. That wouldn't such drastic systematic darkening like this, as the palette is probably still pretty dense in the RGB cube.
Where the linearisation really matters is the arithmetic for the error diffusion, you definitely want to diffuse the error in a linear colorspace, and you are free to choose a good perceptual space for choosing the closest color at each pixel, but calculate the error in a linear space.
Visual perception is weird. But when you squint your eyes to blur the image, you are definitely mixing in a linear colorspace, as that's physical mixing of light intensities before the light even reaches your retina. So you have to match that when diffusing the error.
edit:
It also doesn't help that most (all?) browsers do color mixing wrong when the images are scaled, so if you don't view the dithered images at 100% without DPI scaling than you might get significantly distorted colors due to that too.
edit2:
For comparison this is what imageworsener does:
You really need to open the image in a viewer where each image pixel is exactly one device pixel large, otherwise the color arithmetic used for scaling by viewers is of variable quality (often very poor).
At that point you have something like a "coroutine frame tree" instead of a "stack trace", where you potentially store multiple parent frames and source lines per coroutine frame. Could be presented something like:
$ python3 test_stacktrace.py
Traceback (most recent call last):
* File "/home/user/tmp/test_stacktrace.py", line 22, in <module>
| main_task = loop.create_task(main())
| * File "/home/user/tmp/test_stacktrace.py", line 24, in <module>
|/ loop.run_until_complete(main_task)
* File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
| return future.result()
* File "/home/user/tmp/test_stacktrace.py", line 17, in main
| await foo()
* File "/home/user/tmp/test_stacktrace.py", line 13, in foo
| await bar()
* File "/home/user/tmp/test_stacktrace.py", line 9, in bar
| await baz()
* File "/home/user/tmp/test_stacktrace.py", line 5, in baz
raise RuntimeError()
RuntimeError import asyncio
async def baz():
await asyncio.sleep(.1)
raise RuntimeError()
async def bar():
await asyncio.sleep(.1)
await baz()
async def foo():
await asyncio.sleep(.1)
await bar()
async def main():
await asyncio.sleep(.1)
await foo()
if __name__ == "__main__":
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
main_task = loop.create_task(main())
try:
loop.run_until_complete(main_task)
except KeyboardInterrupt:
main_task.cancel()
loop.run_until_complete(asyncio.wait([main_task]))
pass
And then run:
$ python3 test_stacktrace.py
Traceback (most recent call last):
File "/home/user/tmp/test_stacktrace.py", line 24, in <module>
loop.run_until_complete(main_task)
File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
return future.result()
File "/home/user/tmp/test_stacktrace.py", line 17, in main
await foo()
File "/home/user/tmp/test_stacktrace.py", line 13, in foo
await bar()
File "/home/user/tmp/test_stacktrace.py", line 9, in bar
await baz()
File "/home/user/tmp/test_stacktrace.py", line 5, in baz
raise RuntimeError()
RuntimeErrorMake the destructor (and possibly the constructors) private, have public factory functions that hand out unique_ptr with a custom deleter, or define a public destroying operator delete (C++20) and use unique_ptr with its default deleter.
I agree that having it in the header for everyone is maybe too obvious. But you could otherwise infer that from timing.
Without forward movement you miss the centrifugal force that tips the bike back up.