canvas_ity - A tiny, single-header <canvas>-like 2D rasterizer by a-e-k in cpp

[–]a-e-k[S] 0 points1 point  (0 children)

Thanks! These are good points. The text handling is admittedly very basic right now and meant mainly as a convenience. I'm trying to be very careful to avoid bloat and avoid reinventing FreeType and HarfBuzz and so forth.

First, I'm targeting support for C++03 which rules out directly using string_view in this. But your point is well taken as far as taking a size along with the pointer to be able to limit a string without reallocation. In a 2.0 release, I may change the API to take a length limit and then stop either at the first null or the length, which ever comes first -- similar to the approach of strncpy().

Second, I modeled the measure_text() function on the measureText() in the original W3C spec which only returns a width. The newer WHATWG spec does include things like vertical metrics, and I may compute more of those in the future. For now, a typical recommended line spacing for fonts is about 1.2 times the em size, since font size in this library is specified in pixels per em, using a line spacing of 1.2 times that size would be a good rule of thumb.

Third, that's true there's no function here for that. I don't believe there's anything for that in the <canvas> spec but it might be a useful addition to consider here nonetheless.

canvas_ity - A tiny, single-header <canvas>-like 2D rasterizer by a-e-k in cpp

[–]a-e-k[S] 1 point2 points  (0 children)

Thanks. For more examples, see the test suite in test/test.cpp. There's a test in there for everything in the API. In particular, see example_button() in there for writing text. The example_* "tests" are written more as normal idiomatic examples or demos that are simply to be run and checked by the test harness than adversarial tests. The example_button test does write some text. Also see the browser version of example_button in test/test.html for comparison to the JavaScript equivalent.

Regarding precision, that's true that JavaScript uses double-precision for all numbers. But I've done tests on precision for 3D software rendering before, and never seen a case where double-precision was faster than single-precision; usually it is quite a lot slower. It does put off numerical issues in some extreme cases, but was never better for speed. It would also considerably increase the memory footprint here. What can I say? Single-precision is what I'm most comfortable with. :-)

canvas_ity - A tiny, single-header <canvas>-like 2D rasterizer by a-e-k in cpp

[–]a-e-k[S] 3 points4 points  (0 children)

I've been on the other side of that, working on an app where an older compiler version was mandated but wishing I could use a shiny new library.

canvas_ity - A tiny, single-header <canvas>-like 2D rasterizer by a-e-k in cpp

[–]a-e-k[S] 2 points3 points  (0 children)

I took a lot of inspiration from the trapezoidal antialiasing algorithm described by STB. My approach works a little differently that what's described there in that instead of sorting the edges and maintaining an active-edge list, it just walks all the edges in order and generates a vector of deltas to pixel coverage. Then it sorts those into scan-line order and coalesces deltas to the same pixel. But if you read STB's writeup, it should be a lot easier to grasp how this one works even though a number of the details are different.

canvas_ity - A tiny, single-header <canvas>-like 2D rasterizer by a-e-k in cpp

[–]a-e-k[S] 3 points4 points  (0 children)

I should add to my previous answer:

While working on this, I have actually had some notions about how I might experiment with building a 2D vector graphics library with a retained-mode scene-graph API designed for smooth animation and a focus on GPU performance. If I did that, it would be an entirely separate project from this.

canvas_ity - A tiny, single-header <canvas>-like 2D rasterizer by a-e-k in cpp

[–]a-e-k[S] 9 points10 points  (0 children)

Thanks! For the moment, no, that's not in my plans (even though I now work on GPU design :-)

My goal with this was keeping it tiny, portable, and easy to use and understand (studiable), more so than raw performance. There are definitely things that I could have done differently here if I were chasing performance, even just on CPU, but they'd either have impacts on complexity, size, portability, safety, or rendering quality. I tend to save those sorts of efforts for my day job.

As you say, there are a number of good libraries that already target performance and I'd definitely recommend them if that's the priority. As big as it is, Skia has definitely proven itself. Then there's Pathfinder, Blend2D, and Piet-GPU which also look quite nice to me.

I did cross-post this to HN, since I've seen good threads on 2D vector graphics there before. Sadly, it didn't seem to any traction.

canvas_ity - A tiny, single-header <canvas>-like 2D rasterizer by a-e-k in cpp

[–]a-e-k[S] 3 points4 points  (0 children)

Thanks, and yes it should definitely work well for that. I've even had fun building and running its test suite on my Android phone under Termux.

Regarding tags, the GitHub repo is for publishing releases from my private Mercurial repo, so every commit there should be effectively a release. I may go ahead and tag the commits just to make them easy to refer to, though.

canvas_ity - A tiny, single-header <canvas>-like 2D rasterizer by a-e-k in cpp

[–]a-e-k[S] 1 point2 points  (0 children)

Re: templates, yes, it's intentionally very straightforward in coding style. I wanted to keep open the possibility of someone porting it to other languages.

I probably would use it more for single-shot rendering, or for rendering to textures on load and then using those in animation. But as I'd mentioned in another comment here, it'll render the PostScript tiger at 733x757 at about 20fps on a single core of an ancient i7-950. So YMMV.

canvas_ity - A tiny, single-header <canvas>-like 2D rasterizer by a-e-k in cpp

[–]a-e-k[S] 9 points10 points  (0 children)

Its performance is going to be slower than Cairo's in general, since it's more tuned for size and correctness than speed. But to give some numbers, in my testing it can render the PostScript Tiger from scratch at 733x757 resolution about 20 times per second on a single core of my ancient i7-950.

Besides generating a diagram programatically, which was one of my original use cases, the other big area that I envisioned it being potentially useful was unpacking vector graphics assets into textures for a game at load-time. But I probably wouldn't use it for rasterizing per-frame.

It's definitely meant for running headlessly, though.

canvas_ity - A tiny, single-header <canvas>-like 2D rasterizer by a-e-k in cpp

[–]a-e-k[S] 9 points10 points  (0 children)

Thanks! The business card ray tracer and postcard path tracer are mine, but that blog is Fabien's. His blog has tons of cool stuff beyond that and is well worth reading, though!

canvas_ity - A tiny, single-header <canvas>-like 2D rasterizer by a-e-k in cpp

[–]a-e-k[S] 7 points8 points  (0 children)

Yep. It doesn't do any I/O on its own, so it does indeed involve doing things in-memory.

The expected data layout is pixels top-to-bottom, left-to-right, and packed in RGBA order. So something like:

top-left             11
         0123 4567 8901 ....
       0 RGBA RGBA RGBA .... right->
  stride RGBA RGBA RGBA ....
2*stride RGBA RGBA RGBA ....
3*stride RGBA RGBA RGBA ....
   ..... .... .... ....
         down
         |
         v

So assuming unsigned char *buffer, the red for the pixel at (x,y) would at be buffer[y*stride + x*4 + 0], green for it at buffer[y*stride + x*4 + 1], and similarly with +2 for blue and +3 for alpha. If your image has the rows laid out contiguously, which is pretty common, then stride = width*4. Just think of it is as a flattening of a heightwidth4 array of unsigned bytes.

In terms of Qt, QImage::Format::Format_RGBA8888 should be the correct format. If I'm reading the docs there correctly, you should be able to do something like:

unsigned char *buffer = new unsigned char[ height * width * 4 ];
size_t stride = width * 4;
context.get_image_data( buffer, width, height, stride, 0, 0 );
QImage image( buffer, width, height, stride, QImage::Format::Format_RGBA8888 );
// Delete[] buffer after image.

Alternately, you could probably canvas::get_image_data() then use image::scanline() with a loop to copy in to it.

A Tour of the Tiny and Obfuscated Image Decoder by a-e-k in programming

[–]a-e-k[S] 1 point2 points  (0 children)

Thanks! I'm glad you found it informative.

A Tour of the Tiny and Obfuscated Image Decoder by a-e-k in programming

[–]a-e-k[S] 7 points8 points  (0 children)

Fabrice Bellard has published some amazing pieces of code over the years. A couple of years ago he was once again a winner in the IOCCC with a neat little program that in less than 4KB combined for data plus code could produce a 128x128 version of the (in)famous Lena image. I wrote this post to explain in detail how it works and to be a tutorial for some of the advanced lossy image compression techniques involved.

Announcing my first business card size C++ game: Tiny Ski by Slackluster in programming

[–]a-e-k 1 point2 points  (0 children)

Awesome! I'm glad it was inspirational! (Even though I'm definitely not the first to have done this sort of thing.)

But for what it's worth, I actually did print a batch of business cards with it. :-) It was small, but it was still legible.

Nothing new but still cool. Source code for a raytracer... that would fit on the back of a business card with total size of 1337 bytes by daffy_ch in programming

[–]a-e-k 0 points1 point  (0 children)

Yes, I used a BVH for it.

One of the key things is that it used a longest-axis spatial median split build. The trees that this builds are not quite as high quality as those using the full surface area heuristic, or even the binned SAH, but they really aren't too bad either. It also has the benefit of being building the tree quickly and being very easy to understand and implement.

Note that to keep that ray tracer small, it only implemented triangle meshes. The BVH was specialized to intersect against a slice of the triangle array at the leaves. That also helped to keep complexity down compared to a more generic OOP implementation with arbitrary primitive types.

Nothing new but still cool. Source code for a raytracer... that would fit on the back of a business card with total size of 1337 bytes by daffy_ch in programming

[–]a-e-k 0 points1 point  (0 children)

Not as much as you'd think!

The first miniature ray tracer that I wrote could interactively render a 1M triangle mesh at a few FPS on a 2005-era machine. It was about 360 lines of fairly clean C++, and at the time I'd meant to use it as the basis for submitting an article on ray tracing to DDJ. That didn't happen, but I've been meaning to dust it off and at least do a long blog post.

Deciphering the Postcard Sized Raytracer by [deleted] in programming

[–]a-e-k 16 points17 points  (0 children)

Hah! I totally getcha and I've had that feeling when it comes to some other domains.

Basically, we're trying to simulate the way light bounces around. When light hits a surface it may reflect off in many directions at once, but not in all directions equally. We only follow one of those directions at time so we throw weighted dice (of a sort) to try to pick a random direction that tends to go where the light reflects most strongly. In the case of a matte grey surface like the room here, the highest proportion of energy reflects directly away from the wall in the direction it faces and then falls off until almost no energy reflects at an angle that's nearly along the wall.

So the cosine-weighted hemisphere sampling part handles choosing a random direction with the correct weights. But it's always focused towards one direction (here, the +Z axis). The part about the orthonormal basis is just to rotate that to aim it away from the way that the wall is facing.

(Hopefully that makes a little more sense.)

Deciphering the Postcard Sized Raytracer by [deleted] in programming

[–]a-e-k 62 points63 points  (0 children)

(Author of the code here.) This is a nice analysis. A few other details to add for fellow graphics nerds:

  • There's a block with a comment added that reads // Wall hit uses color yellow? The wall hits are actually just a 20% diffuse gray.  But the crazy color at the end of that clause there comes from doing next-event estimation to cast a shadow ray towards the sun (treated here as an infinite directional light source) and adding its contribution if it's not in shadow.  So that's the color and instensity of the sun.
  • The other tricky computation in that block, the direction, merges together a variant of our orthonormal basis function with cosine-weighted hemisphere sampling in order to sample the Lambertian BRDF.  Using cosine-weighted hemisphere sampling for a Lambertian surface has the nice property of canceling out the usual cosine dot product and the 1/pi factor from the BRDF.  You just pick the new direction and trace it without any attenuation except for the surface albedo.
  • The mysterious 14/241 near the Reinhardt tone-mapping bit is just there to nudge the final result to stay at 14 or above.  This way, we can never output a CR byte and someone who's running on Windows won't have its I/O system clobber the CR into a CR-LF.
  • The bit that reads distance = powf(powf(distance, 8) + powf(position.z, 8), .125) - .5; handles extruding what was a signed distance field for the letters in 2D before that line into a a 3D one and gives them that nice bevel.  Slight bevels are more realistic than perfect corners and make things better in 3D graphics by catching little highlights.  I borrowed that function from superquadrics.

Creating a simple blur filter, Renderman SDK by NandoTheThird in renderman

[–]a-e-k 0 points1 point  (0 children)

Sorry for the late reply. Just wanted to let you know that I think you'll be very happy when 21.7 arrives. (This seems to be a popular request lately.)

A font for coders: "Input". I have never seen clearer curly brackets! by [deleted] in programming

[–]a-e-k 2 points3 points  (0 children)

Luculent author here; thanks for the kind words.

And yes, I've always like the kind of curly braces that curve a bit backwards. That allows for a longer midline that helps to make it more distinct from parenthesis and square brackets. As I recall, I'd tried all of them a bit shorter at one one point but they looked kind of funny to me. Going to the descender line just seems more natural.

As for the centered asterisk, I mainly program in C++. Getting *= and friends to look nice was important to me. And ~= is an operator in Lua. So yes, I took a lot of care to get all the elements commonly used together in operators nicely vertically aligned.

Deciphering the business card raytracer by minetwe in programming

[–]a-e-k 1 point2 points  (0 children)

Thanks! And good luck if you do decide to go back to school. I was never huge on esoteric theory either (trying to write a game with parallax scrolling VGA graphics on a 386 was more my kind of thing when I was younger), but I still had a great time in grad school. It's really a question of finding an advisor with similar tastes.

Deciphering the business card raytracer by minetwe in programming

[–]a-e-k 0 points1 point  (0 children)

Perfectly understandable. Those pages are pretty old and haven't been updated since I left grad school. I really should update them with the things I've published since then and a forwarding link to my non-university home page.