I initially rewrote this in Python but it was S L O W. Re-rewrote it in C and not it's a very nice little utility that I use on a regular basis, thanks for the video dude. Keep up the great work! Love how you are making this stuff accessible to everyone.
Hey Daniel, it's always impressive when you show us stuff in 30min, I use in Photoshop since 20 Years. Really love your channel and challenges and dithering ;-) .
legobudgie it's okay! I'm just a freshman in University, and just learning as well:) I know python and c++ the best, and any java or js I know is from that hunk on screen
Thanks for all the help, I was really struggling with this (this is part of my homework and I didn't catch the basics of the problem very much), but now I think I do understand a bit more. Thanks for all the work and effort you put in this.
Just a note on this, if translating to another language the "r = r + errR * 7/16.0" parts can give you values 255 which will cause artefacts. your rgbs need to be clamped so if 255 =255.
When mum asks you "what are you doing?" you answer "Watching quantized kittens" and she's going to be like "wow... he's learning some kind of a quantum physics..." :D
Just made a cool application where I can convert any image into a LEGO mosaic, and it looks really cool when looking at a distance! Thanks for the coding challenge!
I love these videos and think it a great introduction to algorithmic thinking. Your inspiration is contagious! If anything I would love to see a refactoring exercise of each to see it improved in maintainability and quality. Thanks a lot.
Really interesting how the video compression on the dithered cat @25:40 is completely lossless when viewed at the original 1080p scale. The pixel perfect black and white detail is completely preserved!
22:00 Order of operations. errR gets multiplied by 7, and that float is divided by 16. It is all floating point in the end because the float comes first. I know this because I often get wrecked with integer division, but I know this is safe. Also, consider consolidating the definition of r and the addition on to one line.
I think it would be great if you did a challenge where you create an upscaling algorithm, similar to bilinear and linear upscaling you can find in programs like photoshop. There are some very advanced ones out there too that you can find in emulators for old pixel art games, although I don't know the names of them on the top of my head
Don't you need a blank image to start with when you start accumulating the error distribution? It looks like you're adding to the original image over and over again ( you can see it change quite a few times 24:48 ). It should look much nicer than this.
Интересно будет попробовать использовать фиксированные палитры или поменять распределение цветовой ошибки на картинке (распределить с другим соотношением или по большему количеству пикселей). PS: Классный канал, где программирование и творчество встречаются вместе.
All of your coding challenges are great, they are so much entertaining and give tons of ideas to beginners wondering what to code. Plus you really make them easy to follow..... but perhaps a little bit too easy. I definitely know you do that for the sake of clarity, but sometimes it makes your code choices really ugly and unseemly. I honestly think it would be really important for your viewers to see you working more on the code rather than the ouput only, even though it doesn't make it more impressive.
I'm not great at coding but just found this channel and loving it. I feel like this guy would really like Return of the Obra Dinn since that's basically a game made with dithering.
thanks for great tutuorial! I"m attending the 2022 Genuary challenge and found your dithering challenge! Trying to translate the code from processing to P5js.
for " r = round(r/255)*255", if you are in optimization, I think that an if statement is better, since you only do one test rather than a division, a test and a multiplication (the test come from the round function, i'm not sure how it's implemented in java, but the fastest way that I can think about comes with a test... and a few more number manipulations (I'd guess a truncation, a subtraction, a test and an addition)... In this case the optimization doesn't matter much since you only compute a relatively small image once on a powerful enough computer, but I had once a occasion where little optimizations like this helped a small computer do image processing much faster, reduced the time by a factor of at least 50... Or you could do it with 'r = r/128' if r is an integer (which I think it is) to get only one line, I think it should be a bit slower than the if statement, though I'm not sure, not too too familiar with assembly yet
You have to consider when you do branching, the processor have to do a branch-predication. Basically, when it can take two different code branches, it'll guess which path the program will take and load code for one of the branches. This is so it can do optimizations as *out of order execution*, and *parallelism*. However, guessing wrong branch means that everything it has loaded and calculated in advance must be discarded, and the correct code has to be loaded. This is **slow**! So to know whether an if-statement is faster, you'll have to do what all optimization questions require; you have to measure it yourself!
You need to keep a temporary row of floats to buffer error values for the next row (you only need one float for the pixel to the right, since it's always going to be the next one to be processed). A pixel is made up of its own value, as well as the errors of 4 other pixels. With each partial error you're adding you're doing "max(0, min(255, round(current_value + error)));" . Also, suppose a (for the sake of simplicity, assume grayscale) pixel has a value of 255, and its top left neighbour has an error of 16. This means that it would propagate 1 to the pixel in question, making the color (which can go from 0 to 255) into 256... this is a value that can't be stored in a value of type color, which only has 8 bits per color. Processing will do its clamping et voila: the error wasn't propagated at all. It looks like it's working. It's not working right though. You need to add all the errors and *then* convert back to a color. Converting to a color after adding each partial error results in lost information
21:56 Is this really a problem? It's not just 7 divided by 16, it's the error * 7 / 16. And it goes in that order from left to right, so it's first multiplied by 7. It will not be zero unless error is less than 3.
I had the same question. Now I would like to see how the dithering behaves as we slowly introduce slight variations in the palette thresholds and definitions (or also mixing the RGB channels).
I had an idea, take a dithered image and reverse it back into a continuous image but make it so that new image is much smaller than the original but still approximates the dithered image so it could be recreated from the smaller data set. A sort of lossy compression.
10:50, wouldn't that be five options? 0,1,2,3,4 (0*4=0, 0*4=4) with 0 and 4 being least likely? Round should be floor or ceiling in this case right? Watched another five seconds hahaha..... 😬
If you're interested in image stippling, look up "poisson dart throwing" and "void-cluster method". A little trade secret, if you mean to apply relaxation to a stippled image, you might have to sharpen it a bit. Error diffusion will sharpen it a bit for you (that's partly why it's so popular), but other methods won't.
You did index ineffficiently ( 6:18 ). you should've done: int index = 0; for( int y = 0; y < kitten.width; y++ ){ for( int x = 0; x < kitten.height; x++ ){ /* drawiing */ index ++; } }
At 25:13 the dithered image seems washed out. Is that because you made operation in a gamma space? Shouldn't you work in linear space when summing colours?
Again super interesting and learned a lot, would have liked to see an example on the whiteboard to see what the formula of 7/16, 3/16, 5/16 and 1/16 on those surrounding dots was doing though You can sort of imagine it, but visualizing it would have been cool
Bruh, 28:00 HTML5 canvas is so fast, at least for me. Also, the HMTL5 index is litterally exactly the same but index*4, ad the coloring is so close to the same.
I ran into a toolbar today, which is a plugin for atom to quickly setup a server and quickly create new P5 projects. Nothing special but kind of cool. I am not related to the project. just a quicktip - "p5js"or sth in atom package manager. And HEY I totally love your channel. :)
Hmmm, this reminds me... have you ever seen Raven Kwok's work for Karma Fields? The videoclip for Greatness and the one for Skyline are just beautiful. Would love to see you try something like those. Anyway, thanks for the one more coding challenge!
i feel like this would be a very handy method for increasing the resolution of an image (if you added pixels between each existing one to create a larger image)
No, this wouldn't make any sense for several reasons. First of all there is no point in having the error of a pixel on the right border to affect the pixel on the left border. The idea is to spread the error to the neighbor pixels and the left side is pretty far away from the right side ^^. Next problem is this would completely mess up those pixels. The algorithm relies on the fact that a processed / quantized pixel is never touched again. The error of the current pixel is only spread onto pixels that hasn't been processed yet. So the error adding is actually a preparation step before you quantize the pixel. The proper handling would be to not doing those steps when the x or y index is out of bounds. The easiest solution in his code would be to implement the bounds check inside the index method and when the index is out of bounds return "-1". Now we can simply check before each of those 4 steps if we got a valid index (index >= 0) int index = index(x + 1, y); if (index >= 0) { // ... } The index method could be changed into float index(int x, int y) { if (x < 0 || y < 0 || x >= kitten.width || y >= kitten.height) return -1; return x + y * kitten.width; }
How are the weights 7, 3, 5, 1 determined? Well, if you feed a middle gray image (r=g=b=127 or 128) then you should get a checker board pattern when using 2 colors only (b/w). One big problem is that you don't get the expected result with these weights but strange artifacts will occur in the image. Try 7, 3, 4, 1 instead and then your result will be correct...
I like processing. I've been playing with it for a bit, it's just that for some reason visual Studio doesn't like it. My apache server no prob. Thanks these videos are helping me get better.
I probably have bad programming habits ! i'm learning ! I use a lot of conditional branching like if (r > 200 ) {r = 255} . will this slow down my program if a check each pixel in an image? i use about every 10th pixel to speed things up !
Dude I'm not even a programmer but this was really interesting! Kept my attention the entire time.
I initially rewrote this in Python but it was S L O W. Re-rewrote it in C and not it's a very nice little utility that I use on a regular basis, thanks for the video dude. Keep up the great work! Love how you are making this stuff accessible to everyone.
wiki makes this stuff accessible
Python can be made to run more efficiently if you understand its idiosyncrasies.
@@DrSpooglemonbut never faster than efficient C
@@Iuigi_t Sure, but it can be faster than people think!
Curb Your Enthusiasm for Coding. Loving the intensity and your videos. Thanks Dan(LD)!
"Sorry I lost my.... What I was doing here for a second"
Pretty much sums up Dan's way of working. :-p
You Dutch?
and that's a good thing
Hey Daniel, it's always impressive when you show us stuff in 30min, I use in Photoshop since 20 Years. Really love your channel and challenges and dithering ;-) .
Thank you!
Your videos are not only comprehensive but also FUN. Thank you.
I like this guy. he is so enthusiastic and full of energy.
I've known about Floyd-Steinberg Dithering, but never looked into the algorithm. I never knew it was so simple!
Someday when I'm bored I'm going to recreate all these in python
I have done so to a couple, and be warned, pixel manipulation is not as fast.
Carter Plasek I know, but it's still fun
Oh, of course, python is certain to grant a good time :D
legobudgie it's okay! I'm just a freshman in University, and just learning as well:) I know python and c++ the best, and any java or js I know is from that hunk on screen
Everybody started out like that. Even all those geniusses like Dan or Alan Turing or whoever.
Thanks for all the help, I was really struggling with this (this is part of my homework and I didn't catch the basics of the problem very much), but now I think I do understand a bit more. Thanks for all the work and effort you put in this.
Really teaches me how much of a newB I am at programming
3 years later. Are you an oldG yet?
Just a note on this, if translating to another language the "r = r + errR * 7/16.0" parts can give you values 255 which will cause artefacts. your rgbs need to be clamped so if 255 =255.
When mum asks you "what are you doing?" you answer "Watching quantized kittens" and she's going to be like "wow... he's learning some kind of a quantum physics..." :D
@LifE's TH-cam Channel
She can't prove that the cat is not alive if she can't see the screen
@@Skaggco Quantized schrodinger
goddamn nerd
@@FSFITA xDDDDDDDDD
InstaBlaster
Just made a cool application where I can convert any image into a LEGO mosaic, and it looks really cool when looking at a distance! Thanks for the coding challenge!
Hey man, just wanted to say that your videos are simply awesome. Cheers!
Wow.... this is actually helpful for my Shader Learning in GLSL :D Thank you!!!
I love these videos and think it a great introduction to algorithmic thinking. Your inspiration is contagious!
If anything I would love to see a refactoring exercise of each to see it improved in maintainability and quality.
Thanks a lot.
Really interesting how the video compression on the dithered cat @25:40 is completely lossless when viewed at the original 1080p scale. The pixel perfect black and white detail is completely preserved!
22:00 Order of operations. errR gets multiplied by 7, and that float is divided by 16. It is all floating point in the end because the float comes first. I know this because I often get wrecked with integer division, but I know this is safe. Also, consider consolidating the definition of r and the addition on to one line.
Thanks for this correction!
I think it would be great if you did a challenge where you create an upscaling algorithm, similar to bilinear and linear upscaling you can find in programs like photoshop. There are some very advanced ones out there too that you can find in emulators for old pixel art games, although I don't know the names of them on the top of my head
like hq4x
For another challenge, you could try using k-means to select an optimal set of palette colors, if you haven't done that in a video already.
k means sucks for photos, octree and median cut are better.
i mean, the coding train, tsoding, and theprimagen are the gods of youtube programming
I love this guy. He is hilarious... And a programmer!? Xd
Wow i've just discover your channel and it's awesomly pedagogically good and intertaining
wow... so this is the power of computer science... impressive...
Don't you need a blank image to start with when you start accumulating the error distribution? It looks like you're adding to the original image over and over again ( you can see it change quite a few times 24:48 ). It should look much nicer than this.
Darn, I love these coding videos 🙌🙌 well done
I love those "algorithm" video's, where you are putting them into processing!
Интересно будет попробовать использовать фиксированные палитры или поменять распределение цветовой ошибки на картинке (распределить с другим соотношением или по большему количеству пикселей).
PS:
Классный канал, где программирование и творчество встречаются вместе.
Thanks for the wonderful performance!
23:25 how I feel any time I finish writing in JavaScript and don't want to break it with missing closures again
All of your coding challenges are great, they are so much entertaining and give tons of ideas to beginners wondering what to code.
Plus you really make them easy to follow..... but perhaps a little bit too easy. I definitely know you do that for the sake of clarity, but sometimes it makes your code choices really ugly and unseemly.
I honestly think it would be really important for your viewers to see you working more on the code rather than the ouput only, even though it doesn't make it more impressive.
Thanks for this feedback, I completely understand what you mean and will consider this in the future.
Thanks a lot for your reply and even more for your consideration, I'll look forward to it!!
I love watching his videos just for entertainment but I would love to see more practical approaches to some subject
Too Easy? AHAHAHAHAH
I'm not great at coding but just found this channel and loving it. I feel like this guy would really like Return of the Obra Dinn since that's basically a game made with dithering.
...but for fast cycle is better to use only one loop, for index
thanks for great tutuorial! I"m attending the 2022 Genuary challenge and found your dithering challenge!
Trying to translate the code from processing to P5js.
For what it's worth, you can also explicitly make a float by appending 'f' to the number: 7/16f
for " r = round(r/255)*255", if you are in optimization, I think that an if statement is better, since you only do one test rather than a division, a test and a multiplication (the test come from the round function, i'm not sure how it's implemented in java, but the fastest way that I can think about comes with a test... and a few more number manipulations (I'd guess a truncation, a subtraction, a test and an addition)... In this case the optimization doesn't matter much since you only compute a relatively small image once on a powerful enough computer, but I had once a occasion where little optimizations like this helped a small computer do image processing much faster, reduced the time by a factor of at least 50...
Or you could do it with 'r = r/128' if r is an integer (which I think it is) to get only one line, I think it should be a bit slower than the if statement, though I'm not sure, not too too familiar with assembly yet
Thanks for the feedback!
You have to consider when you do branching, the processor have to do a branch-predication. Basically, when it can take two different code branches, it'll guess which path the program will take and load code for one of the branches. This is so it can do optimizations as *out of order execution*, and *parallelism*. However, guessing wrong branch means that everything it has loaded and calculated in advance must be discarded, and the correct code has to be loaded. This is **slow**!
So to know whether an if-statement is faster, you'll have to do what all optimization questions require; you have to measure it yourself!
Sitting here bored @ 12:30am... sees an upload from this mint fella! I love your attitude and enthusiasm, actually makes me interested in Hungary.
Damn autocorrect, meant to say that it makes me interested in things I would otherwise not.
Checked your time zone you are off.
Mazeyar Moeini I'm not in Hungary. That was autocorrect, I decided to leave it in there for the giggles.
Best channel on TH-cam.
Yes a function is what I was thinking for that very similar block of code and repeated 3 times. And just pass the variations as parameters.
You need to keep a temporary row of floats to buffer error values for the next row (you only need one float for the pixel to the right, since it's always going to be the next one to be processed).
A pixel is made up of its own value, as well as the errors of 4 other pixels. With each partial error you're adding you're doing "max(0, min(255, round(current_value + error)));" .
Also, suppose a (for the sake of simplicity, assume grayscale) pixel has a value of 255, and its top left neighbour has an error of 16. This means that it would propagate 1 to the pixel in question, making the color (which can go from 0 to 255) into 256... this is a value that can't be stored in a value of type color, which only has 8 bits per color. Processing will do its clamping et voila: the error wasn't propagated at all.
It looks like it's working. It's not working right though. You need to add all the errors and *then* convert back to a color. Converting to a color after adding each partial error results in lost information
I love seeing the quality dropping at 25:02 because of the fixed bitrate and the crazy detail :D
21:56 Is this really a problem? It's not just 7 divided by 16, it's the error * 7 / 16. And it goes in that order from left to right, so it's first multiplied by 7. It will not be zero unless error is less than 3.
A question Dan: why do that in the draw function? It's better do the loop once in the setup. Right?
Yeah, since this is generating a static image it makes sense for it all to happen one in setup!
I had the same question. Now I would like to see how the dithering behaves as we slowly introduce slight variations in the palette thresholds and definitions (or also mixing the RGB channels).
Great the way you explain your thinking! So extremely helpful as i'm a beginner
24:51 when you reach the bitrate limit because of the noise
I had an idea, take a dithered image and reverse it back into a continuous image but make it so that new image is much smaller than the original but still approximates the dithered image so it could be recreated from the smaller data set. A sort of lossy compression.
10:50, wouldn't that be five options?
0,1,2,3,4 (0*4=0, 0*4=4) with 0 and 4 being least likely? Round should be floor or ceiling in this case right?
Watched another five seconds hahaha..... 😬
If you're interested in image stippling, look up "poisson dart throwing" and "void-cluster method". A little trade secret, if you mean to apply relaxation to a stippled image, you might have to sharpen it a bit. Error diffusion will sharpen it a bit for you (that's partly why it's so popular), but other methods won't.
Thank you for these great tips!
Loved the part where you posed for the thumbnail :D
"Dithering Dithering Dithering"
-- The coding train 2018
I think I learned more about coding concepts from this video than I did from my $70,000 master's degree education.
In the p5 web editor, for some reason, even though you’re dithering it after displaying the original, it is showing the original dithered as well.
You did index ineffficiently ( 6:18 ). you should've done:
int index = 0;
for( int y = 0; y < kitten.width; y++ ){
for( int x = 0; x < kitten.height; x++ ){
/* drawiing */
index ++;
}
}
The beard is getting more and more majestic
At 25:13 the dithered image seems washed out. Is that because you made operation in a gamma space? Shouldn't you work in linear space when summing colours?
i wanna smoke with this guy i feel like he'll talk your ear off then fall asleep eating a microwave burrito
You're an eccentric genius!
Again super interesting and learned a lot, would have liked to see an example on the whiteboard to see what the formula of 7/16, 3/16, 5/16 and 1/16 on those surrounding dots was doing though
You can sort of imagine it, but visualizing it would have been cool
YIPPI! Processing3 is used = +1000 points
At around 10:00 I'm curious, does Processing have a map(,,,,) function?
That awesome dithering effect at 14:44.
1:24 lands perfectly on the nose
Fascinating! Go Dan!
"The times you have to see duplicate code before extracting it into a function is 1"
Hmmmm 🤔 23:00
Not gonna lie, I cried when I saw it xD
this is nice for those who wants to show nice things on e-paper :)
Oof! color pallete dithering? I didn't think of that genius artistry when I instantly came up with my own algorithm(s).
My man Daniel looking like Luke Skywalker
Ese Brooks Luke or Chewbacca - I can’t make up my mind... ;-)
Set that factor to 50, while using Greyscale. 50 shades of Gray!
Bruh, 28:00 HTML5 canvas is so fast, at least for me. Also, the HMTL5 index is litterally exactly the same but index*4, ad the coloring is so close to the same.
Lol... I just had a final exam from this topic
I wish I had final exams on topics like this
it's not as exiting when it's only theory and pseudocode :D :D
@@MrRys right
21:54 You actually didn't need to put 16.0 because errR|G|B are floats. Default arithmetic order is x7 first then /16.
5:35 arent the nested loops in the wrong order cause 2d arrays are stored image[row][column]? (code works cause the image is a perfect square)
I did this years ago in highschool for a project, i didnt know it was a thing that actually existed. I used processing also, thats so weird
Dan is looking good with the beared
Did you lock somebody out? They're knocking at 11:00
It would be interesting to have a video on index mode of photoshop in processing. To get custom colors with dithering types in an image.
Where do you get all these awesome ideas? I'd love to attempt something like this myself but I wouldn't know where to start
I ran into a toolbar today, which is a plugin for atom to quickly setup a server and quickly create new P5 projects. Nothing special but kind of cool. I am not related to the project. just a quicktip - "p5js"or sth in atom package manager. And HEY I totally love your channel. :)
Hmmm, this reminds me... have you ever seen Raven Kwok's work for Karma Fields? The videoclip for Greatness and the one for Skyline are just beautiful. Would love to see you try something like those. Anyway, thanks for the one more coding challenge!
Would also love to see the curvy lines version that sort of looks like a maze... better yet... that is a maze.
i feel like this would be a very handy method for increasing the resolution of an image (if you added pixels between each existing one to create a larger image)
there wouldnt be more detail tho
I can’t stop staring at the floating tuft of hair at the beginning...😅
Keep up the good work! My coding club is now doing a lesson on Processing :)
I saw dithering as an option in a Skyrim ENB and always wondered what it did
Could you use %amount of pixels in the index() function to get rid of out of bound errors, and make it rewind to the top?
Btw yes, rip bitrate
No, this wouldn't make any sense for several reasons.
First of all there is no point in having the error of a pixel on the right border to affect the pixel on the left border. The idea is to spread the error to the neighbor pixels and the left side is pretty far away from the right side ^^.
Next problem is this would completely mess up those pixels. The algorithm relies on the fact that a processed / quantized pixel is never touched again. The error of the current pixel is only spread onto pixels that hasn't been processed yet. So the error adding is actually a preparation step before you quantize the pixel.
The proper handling would be to not doing those steps when the x or y index is out of bounds. The easiest solution in his code would be to implement the bounds check inside the index method and when the index is out of bounds return "-1". Now we can simply check before each of those 4 steps if we got a valid index (index >= 0)
int index = index(x + 1, y);
if (index >= 0)
{
// ...
}
The index method could be changed into
float index(int x, int y)
{
if (x < 0 || y < 0 || x >= kitten.width || y >= kitten.height)
return -1;
return x + y * kitten.width;
}
Dayum! I always wanted to make these.
How are the weights 7, 3, 5, 1 determined? Well, if you feed a middle gray image (r=g=b=127 or 128) then you should get a checker board pattern when using 2 colors only (b/w). One big problem is that you don't get the expected result with these weights but strange artifacts will occur in the image. Try 7, 3, 4, 1 instead and then your result will be correct...
video compression at 24:50 ???
I like processing. I've been playing with it for a bit, it's just that for some reason visual Studio doesn't like it. My apache server no prob. Thanks these videos are helping me get better.
You should make a quantize function that takes a number and a factor.
I probably have bad programming habits ! i'm learning ! I use a lot of conditional branching like if (r > 200 ) {r = 255} . will this slow down my program if a check each pixel in an image?
i use about every 10th pixel to speed things up !
You: “pee-image”
My brain: “pie-mage”
Great video
But how do we extract the resulted Image to be used in a different location.. Laser engraver for example
Thanks
you could use PVectors to store the RGB colors
The easiest way to achieve this effect? GameBoy Camera.
Hello sir please make a playlists in all coding challenge video's.
Can you do a heat map based on mouse pointer?
Yes. Could be done by adding a gaussian-distributed "heat" around the pointer. Perhaps a future coding challenge?
I like your videos! And I was wondering if you could make an hypercube animation on Processing...
It doesn't allow me to quatize the color. It says "x cannot be resolved to a variable". Help please!