The Image API is really bad, can we make it better?


(Andreas Svensson) #1

The standard golang image API is in my humble opinion down-right horrible. I don’t say this to be rude, I find it genuinely sub-par and misguided for “serious use”, and to me it even seems unreasonably unintuitive for beginners. I’m writing this because I’m curious, interested and concerned. I hope to have a genuine discussion on this subject.

Here are some of my mine issues with the Image interface.

  • Images do not have a fixed (0,0) origin, but is an arbitrary rectangle. It’s absolutely not fundamental to an image type. It makes it harder as simple assumptions no longer hold true and often require additional checks.

  • The generic Image interface design with At/Set is inherently potentially lossy and slow without that being explicitly obvious or explained. This is further exacerbated by the next point.

  • Rectangle has an At method?! I’m not arguing that there’s a value in something like it, but Rectangle is absolutely not the right candidate. There’s no fundamental truth to a “rectangle” being a physical object that’s Opaque on the inside and transparent outside. It’s a description of physical properties.

  • PNG decoding returns images in different color spaces depending on the content of the PNG with no way of choosing a desired color model, and worse it doesn’t even consistently return RGBA/NRGBA but that depends on the content too.

  • A generic interface for images is bad, there’s not a single assumptions that holds true for all relevant image formats. They don’t have to be square, they don’t have to be constrained to integer positions/pixels, etc. At best most of the assumptions hold true for bitmaps.

  • Etc.

All this together creates an API where you cannot set what color model you will get, manipulation with mixed color models is lossy, slow and potentially even completely breaking the output (loading a gray PNG and try to draw red circle on it). This essentially forces you to always convert it to a suitable color model for your purpose which is costly and error-prone. It also has unnecessary complexities (rectangle bounds) and puts an implicit burden on libraries to support all the weird formats, this is easy with At/Set but that is slow, if you want to be fast you need to implement something like the monstrosity in the popular module https://github.com/disintegration/imaging/blob/master/scanner.go

I as a developer is not in control and becomes encumbered by decisions that are relevant for me, it become unnecessarily complex to implement fast image operations without breaking the philosophy. Although it’s not explicit, this also tends to subconsciously infect the design of other image libraries which generally try to stay true to the Image interface. All in the name of ease-of-use, but ease-of-use is not a good thing if there’s no way to get serious. Both should be considered.

This is not my final and fully thought-through opinion, and maybe I’m intentionally being a little bit too “technically hardcore” for effect, but I believe API would be far better if something like the following changes were made:

  • Images should have a fixed origin at (0,0). If you want offset images then golang could provide an OffsetImage type which has image+offset members, or you make your own.

  • Provide functions for explicitly converting between color models, e.g. CMYK->RGBA, RGBA->NRGBA, GRAY->RGBA, etc. With an interface for converting any color model to an explicit, e.g. ANY->RGBA, ANY->GRAY.

  • PNG decoding should at the very least support setting whether it should return color models as N or not. Preferably even allowing to explicitly set the color model with the most common ones hard-coded for performance (the rest simply performing a post-pass conversion).

  • Image functions should try to return explicit types, e.g. NRGBA or otherwise return “any”/interface{}. Image should no longer be used for this purpose.

  • The generic Image interface can remain but should be treated as an optional “easy” interface, making it explicit that it’s meant for ease of use and performance/correctness is not primary.

  • The explicit color model types can have At/Set methods but which never perform conversion, they’re only helpers for easily getting and setting raw values. There should also be an indexed version which takes a single index rather than x+y for operations that do require positional awareness.

  • Remove Rectangle in its entirety.

There are surely tweaks to improve this further, but it’s my opinion that this would be a much better interface. This should be an API which is now inherently transparent, minimal and optimizeable. The base implementation is then truly usable for serious use-cases and can easily be packaged into an easy-to-use interface too (which is also important for beginners of go/programming!).

Again, I’m not writing this to be strident. I think Go makes tons of good decisions where other languages fall all too short, it’s my language of choice for serious business development nowadays. The beginner-friendly-first design of the golang standard API is not wrong, but when that’s all there is and there’s no consideration for users with more advanced needs I become concerned.

This is not explicitly only about the Image API, but I find it to be most egregious instance that I know of at the moment. Maybe the authors are already aware and are displeased with it too, that would be interesting to know and maybe help with revising. If they genuinely feel that this it is great and the right approach then I would like to sincerely challenge that and have a healthy discussion about it.

There’s no doubt that I could’ve put more thought into this post either, but I think it’s a valid starting point to gauge sentiment and for discussion.


(Steve) #2

fasthttp package is an interesting alternative to the standard lib net/http. Realistically I cannot see the standard library packages going through significant breaking changes given what I’ve read about compatability and Go, though maybe I’m wrong.