https://blog.demofox.org/2023/10/22/how-to-make-your-own-spooky-magic-eye-pictures-autostereograms/ The blog at the bottom of the sea Programming, Graphics, Gamedev, Exotic Computation, Audio Synthesis Menu Skip to content * Table of Contents * License * Contact [grand-teton] How To Make Your Own Spooky Magic Eye Pictures (Autostereograms) demofox2October 22, 20234 The simple standalone C++ code that goes with this post and makes autostereograms, can be found at https://github.com/Atrix256/ Autostereogram. The 1990s! They felt like a wasteland of culture at the time, but looking back, there was hyper color t-shirts, the beginning of mainstream computing and the internet, the height of alternative rock, and of course magic eye pictures. Unfocus your eyes such that the two dots overlap. A 3D image should emerge! [grave_colo] Quick PSA if you can't see it! [image-2] To make an autostereogram, you need two things: 1. Color Image: A tileable repeating pattern. This can also just be "white noise". 2. Depth Image: A grey scale depth map, or a black and white mask. This defines the 3D shape. Brighter pixel values are closer in depth. For the above, I snagged these two from pintrest. [color_pump] [grey_grave] The image you are making is going to be the width and height of the depth image, but is going to have as many color channels as the color image. You build the output image row by row, from left to right. To start out, we can just tile the output image with the color image. The Output Image pixel at (x,y) is the Color Image pixel at (x % ColorWidth, y % ColorHeight). That makes a tiled image, which does not have any 3d effect whatsoever: [grave_colo] To get a 3D effect we need to modify our algorithm. We need to read the Depth Image at pixel (x,y) to get a value from 0 to 255. We divide that by 255 to get a fractional value between 0 and 1. We then multiply that value by the "maximum offset amount", which is a tuneable parameter (i set it to 20), to get an offset amount. This offset is how much we should move ahead in the pattern. So, instead of Output Image pixel (x,y) using the Color Image pixel (x % ColorWidth, y % ColorHeight), we are calculating an offset from the Depth Image and using the Color Image pixel ((x + offset) % ColorWidth, y % ColorHeight). Doing that, we aren't quite there. Some 3D effects are starting to pop out, but it doesn't look quite right. [grave_colo] In fact, if you use the simpler depth map of the rectangles shown below, you can see the rectangles just fine, but there seems to be holes to the right of them. [grey_squar] [squares_co] What we need to do is not just look into the Color Image at an offset location, but that we need to look at the Output Image we are building, at an offset location. Specifically, we need to look at it in the previous color tile repetition. We use the Output Image pixel at ((x + offset - ColorWidth), y). A problem with that though, is that when x is less than ColorWidth, we'll be looking at a pixel x value that is less than 0 aka out of bounds. When x < ColorWidth, we should use the Color Image pixel instead, using the same formula we had before ((x + offset) % ColorWidth, y % ColorHeight). That fixes our problem with the simpler squares depth map. The holes to the right are gone. [squares_co] And it also mostly fixes our "grave" image: [grave_colo] There is one problem remaining with the grave image though. How these images work is that your left eye needs to lined up with an unmodified tile on the left, and your right eye needs to be lined up with a modified tile on the right. The grave image has depth information very close to the left side, which makes that not be possible. To fix this, you can add an extra "empty color tile" on the left. That makes our image a little bit wider but it makes it work. This also has the added benefit of centering the depth map, where it previously was shifted to the left a bit. [grave_colo] There we are, we are done! Other Details * I found it useful to normalize the greyscale depth map. Some of them don't use the full 0 to 1 range, which means they aren't making the most use of the depth available. Remapping them to 0 to 1 helps that. * Some masks were meant to be binary black or white, but the images i downloaded form the internet had some grey in them (they were .jpg which is part of the problem - lossy compression). Having an option to binarize these masks was useful, forcing each pixel value or 0 or 1, whichever was closer. * The binary masks i downloaded had the part i was interested in being black, with a white background. I made the code able to invert this, so the interest part would pop out instead of receeding in. * The distance between the helper dots on the images are the width of the Color Image. A wider color image means a person has to work harder to get those dots to overlap, and it may not even be possible for some people (I'm unsure of details there hehe). I used tile sizes of 128. * It's hard to make out fine detail from the depth maps. It seems like larger, coarse features are the way to go, instead of fine details. More Pictures Here is the grave, with a different color texture. I find this one harder to see. [grave_colo] And another, using RGB white noise (random numbers). I can see this one pretty easily, but it isn't as fun themed as the pumpkin image [grave_colo] And here is greyscale white noise (random numbers) used for the color image. I can see this just fine too. [grave_grey] I also tried using blue noise as a Color Image but I can't see the 3d at all, and it isn't a mystery what the 3d image is. You can see the repetition of the depth map object from the feedback. I think it's interesting that the repetition is needed to make it look right. I have no understanding of why that is, but if you do, please leave a comment! [grave_grey] Here are some images that are not the grave. I find the pumpkin color texture works pretty nicely [house_colo] [witch_colo] [yoda_color] [art_color_] Links This video kicked off this nerd snipe: https://youtu.be/-okxLz1UauA? si=y_QK8-Bv4EzZSGBv This was also helfpul: https://flothesof.github.io/ making-stereograms-Python.html Here is the code I wrote that makes these autostereograms again: https://github.com/Atrix256/Autostereogram Lastly, I think it would be really neat to make a game that used this technique to render 3d. It could be something simple like a brick breaking game, or as complex as a first person shooter. A challenge with this is that you need to process the image from left to right, due to the feedback loop needed. That won't be the fastest operation on the GPU, forcing it to serialize pixel processing unless anyone has any clever ideas to help that. Still, it would be pretty neat as a tech demo! Share this: * Twitter * Facebook * Like this: Like Loading... Related This entry was posted in Uncategorized. Bookmark the permalink. Post navigation - When There Are Too Many Experiments (Tests) To Do! Part 2: Orthogonal Arrays --------------------------------------------------------------------- 4 comments 1. [7c1c25] Ian says:| October 22, 2023 at 12:21 pm Very fun! Although, for me, I needed to invert all the depths (easy enough in code) to perceive that which should be near as near. Once I did this, the 3D shapes became much more obvious. I believe this is corroborated by the math used in the revision of the source as of posting this: Nearer things should have a larger separation. The depth offset scales linearly with depth map values that represent near pixels as 255 (normalized to 1), and then that depth offset is added to the position of the previous tile for sampling the line's row so far... but that means nearer depth values cause sampling from closer to the current pixel being written rather than further from the current pixel being written, and hence near values adopt the perception of being far away since there is less separation instead of more. I'm not sure how depth is quantized in the image -- assuming it's linear, then the separation for non-binarized cases could be improved by introducing a reciprocal? Something I unfortunately only have time to idly wonder about is if there's a way to minimize the permanent offsetting of the row. That might make the blue noise case better, although the blue noise is so hard to visually align at an arbitrary distance due to no easy nonuniform visual cues that it may not be sufficient even if the repeating pattern lines from the stereo separation from earlier in the row were eliminated. A simple example of this kind of minimization might be choosing a global offset for each row such that the squared distance of the effective sampling offsets (after accounting for the recursive sampling nature) is minimized. More interesting would be if you can 'fudge it' a bit and let offsets drift enough to help globally reduce bleeding offsets on the right hand side of rows -- I wonder if human perception even allows for this. Does anyone know if any autostereogram methods account for this kind of global optimization already? LikeLike Reply 2. [a8a647] Oran says:| October 22, 2023 at 3:18 pm OMG i got intregeded by this video too! to but you got farther! nice. i made a playable tetris game using this a while ago. but cheated and used a library i found online playable link https://wisehackermonkey.github.io/magic-eye-tetris/ https://github.com/wisehackermonkey/magic-eye-tetris here's myunfinished p5.js sketch. didnt work. but a start https://editor.p5js.org/wisemonkey/sketches/1JIkzBgNx open questions: is can smooth gradients be created using this method? think dithering but fort autosteriograms LikeLike Reply + [dc5639] demofox2 says:| October 22, 2023 at 2:30 pm Great question about smooth gradient, hmm! LikeLike Reply 3. [13a1a6] Hales says:| October 22, 2023 at 4:52 pm The 3d I perceive in these stereograms appears to be constructed of a stack of cutout pieces of paper at different depths, not smoothly varying depth. Have a look at the stepped 3d depth of the cross or the mount of soil underneath it, for example. Perhaps the algorithm is only performing whole (not fractional) pixel shifts and this depth quantisation is a result? Not sure. LikeLike Reply --------------------------------------------------------------------- Leave a Reply Cancel reply [ ] [ ] [ ] [ ] [ ] [ ] [ ] D[ ] Search [ ] [S] Archives * October 2023 (3) * September 2023 (3) * August 2023 (2) * April 2023 (1) * March 2023 (3) * February 2023 (2) * January 2023 (1) * October 2022 (1) * August 2022 (1) * July 2022 (4) * June 2022 (2) * March 2022 (2) * February 2022 (4) * January 2022 (2) * December 2021 (2) * July 2021 (1) * June 2021 (2) * April 2021 (2) * November 2020 (2) * October 2020 (1) * July 2020 (3) * June 2020 (4) * May 2020 (4) * March 2020 (4) * February 2020 (1) * January 2020 (2) * December 2019 (2) * October 2019 (1) * August 2019 (1) * July 2019 (1) * June 2019 (2) * May 2019 (2) * March 2019 (1) * November 2018 (2) * August 2018 (2) * July 2018 (2) * June 2018 (3) * April 2018 (2) * March 2018 (4) * January 2018 (1) * December 2017 (1) * November 2017 (4) * October 2017 (4) * September 2017 (1) * August 2017 (2) * July 2017 (4) * June 2017 (1) * May 2017 (2) * April 2017 (1) * March 2017 (5) * February 2017 (2) * January 2017 (3) * December 2016 (5) * November 2016 (1) * October 2016 (3) * September 2016 (3) * August 2016 (2) * July 2016 (2) * June 2016 (2) * May 2016 (1) * April 2016 (3) * March 2016 (4) * February 2016 (6) * January 2016 (1) * December 2015 (3) * November 2015 (2) * October 2015 (3) * September 2015 (5) * August 2015 (7) * July 2015 (3) * June 2015 (3) * May 2015 (2) * April 2015 (6) * March 2015 (6) * February 2015 (6) * January 2015 (3) * December 2014 (2) * November 2014 (1) * August 2014 (5) * July 2014 (1) * June 2014 (2) * May 2014 (1) * March 2014 (3) * February 2014 (3) * January 2014 (3) * November 2013 (1) * October 2013 (1) * September 2013 (3) * July 2013 (4) * June 2013 (4) * May 2013 (5) * November 2012 (1) * October 2012 (1) * September 2012 (11) * June 2012 (1) * May 2012 (3) Categories * assembly (1) * Audio Synthesis (19) * C++ (98) * Coding Style (2) * Computer Science (74) * Cryptography (15) * DSP (1) * Encryption + Security (9) * Fractals (2) * Game Development (97) * Gamedev Commentary (4) * Graphics (87) * Math (90) * My Old Master (2) * Network Programming (1) * Neural Networks (6) * Other (1) * Path Tracing (1) * People Skills (6) * Quantum Computing (4) * Ray Tracing (16) * Research (13) * Shadertoy (10) * Skeletal Animation (3) * Uncategorized (63) Category Cloud assembly Audio Synthesis C++ Coding Style Computer Science Cryptography DSP Encryption + Security Fractals Gamedev Commentary Game Development Graphics Math My Old Master Network Programming Neural Networks Other Path Tracing People Skills Quantum Computing Ray Tracing Research Shadertoy Skeletal Animation Uncategorized Recent Posts * How To Make Your Own Spooky Magic Eye Pictures (Autostereograms) October 22, 2023 * When There Are Too Many Experiments (Tests) To Do! Part 2: Orthogonal Arrays October 20, 2023 * Fractional Factorial Experiment Design: When There Are Too Many Experiments To Do October 17, 2023 * Alan Wolfe (Atrix256/Demofox) Banned (?) From X (Twitter) September 18, 2023 * Generating Hamiltonian Cycles on Graphs with Prime Numbers of Nodes, Without Repeating Edges September 11, 2023 Create a website or blog at WordPress.com * Comment * Follow Following + [croppe] The blog at the bottom of the sea Join 157 other followers [ ] Sign me up + Already have a WordPress.com account? Log in now. * + [croppe] The blog at the bottom of the sea + Customize + Follow Following + Sign up + Log in + Copy shortlink + Report this content + View post in Reader + Manage subscriptions + Collapse this bar Loading Comments... Write a Comment... [ ] Email (Required) [ ] Name (Required) [ ] Website [ ] [Post Comment] %d bloggers like this: [b]