Show HN: A pure WebGL image editor with filters, crop and perspective correction
86 comments
·April 28, 2025rorylaitila
I've been looking for an open source image editor that I can embed in my app for managing my vintage ad archives (https://adretro.com). Basically I just need to rotate, crop, adjust color. But the images come from from the archive database rather than upload. And I want to work with them quickly, rather than save/open like a desktop app. Last time I looked I couldn't find something with good documentation that was easy to get started with. Right now I bulk edit in Google Photos.
neurostimulant
CropperJS is very simple and easy to use: https://fengyuanchen.github.io/cropperjs/
There is also React-Cropper if you want to CropperJS with React: https://github.com/react-cropper/react-cropper
rorylaitila
Thanks I'll check it out again. Last I looked I think it was v1 branch.
gtb_1
Did you consider using WebGPU, or creating a bridge between WebGPU / WebGL for this? Also, have you considered deploying on Cloudflare Pages [1] (unlimited bandwidth) instead of netlify?
titaphraz
Apparently it isn't unlimited: https://news.ycombinator.com/item?id=42713451
sam1r
oh wow, i had no idea. thanks!
axelMI
I did consider WebGPU but I couldn't find many advantages for the current use case.
Yes I'm planning to move to the edge as soon as it's in a later stage of development
zorgmonkey
The biggest difference for you is probably that WebGPU has compute shaders and WebGL does not, they are pretty nice if you want to implement more complex algorithms.
axelMI
That's true. But current shaders are very simple. The big issue I have with WebGPU is that it's still not supported by all browser (see Safari) while WebGL is nowadays everywhere.
I work on a MacBook and I need to make sure that at least Safari (soooo many issues/ bugs/ specific behaviours it drives my crazy) works fine
shunia_huang
Would be very nice if it is provided as a library to be able to integrated into another app with partial/full customizations for UI with different theme and stack.
talldan
The README mentions it uses this library - https://github.com/xdadda/mini-gl.
axelMI
As mentioned in another comment, this is just a UI for https://github.com/xdadda/mini-gl
ww520
Excellent work. The UI looks very clean and functional. It's feature packed. Good choice on using WebGL. It has support everywhere in most if not all browsers.
virtualritz
Does this do operations involving multiplications in linear color space?
It seems to use mini-gl[0] for the filtering. I had a brief look at the mini-gl source and it seems to use color as-is?
If that were true, for 99% of content that people would upload -- stuff in non-linear sRGB --, the filters would do the wrong thing.
Or am I missing something?
axelMI
You are right thanks for the heads up!! It currently works in linear color space. I wasn't familiar with the issue.
I need to figure out if it's enough in Webgl to add gl.SRGB8_ALPHA8 when loading/ copying textures or I should gamma correct in all the color handling shaders. Will do some testing to figure it out.
virtualritz
AFAIK gl.SRGB8_ALPHA8 will only make sure the filtering of the lookup happens in linear sRGB (because filtering involves multiplications).
But values returned from the texture lookup must also be linear for any calculations afterwards to make sense.
AFAIK you'd need to set internal format to SRGB8_ALPHA8 and the format to RGBA. Then the returned color is also linear.
However, according to [1], this combination is only allowed if the result type is requested UNSIGNED_BYTE, i.e. as 8bit/channel. That would mean you will get banding on gradients because 8bit/channel is not nearly enough to represent linear color.
I.e. the type should be at least HALF_FLOAT or FLOAT but the table suggest WebGL 2.0 does not support this. I'm not a realtime graphics person, so I may be missing something. And obviously, there must be some workaround for WebGL.
Furthermore, the final linear->non linear sRGB conversion must be done, too. I.e. before displaying the result. AFAIK the sRGB framebuffer GL extension can take care of this. But again, not sure.
The whole topic of color spaces (vs color models, i.e. RGB is a color model, not a color space) and gamma is not trivial.
Almost everyone starting out with any kind of computer graphics involving display/manipulation of colors gets this wrong first time because they do not know about this/assume this is trivial.
Don't be discouraged. A good read is [2]. While it specifically addresses color pipelines in VFX/film, all in there applies equally to a simple non-linear sRGB image (a typical JPG, e.g.) being manipulated and then displayed or saved out as 8bit/channel again.
[1] https://registry.khronos.org/webgl/specs/latest/2.0/#TEXTURE...
[2] https://www.imageworks.com/sites/default/files/2023-10/Cinem...
axelMI
I don't know if I have to thank you or just hate you! Spent best part of the day figuring out the whole srgb-linear thing.
Well the workflow should now be srgb correct! Had to fix some of the shaders and they now work, except for the vignette one that unexpectedly sucks in linear space but will replace it with another one soon.
Turns out adding gl.SRGB8_ALPHA8 for loading textures (excluding LUTs) and closing the pipeline with a linear-to-sRGB transform did the trick. Plus making sure the filters where working in the correct space.
No significant banding issues with 8bit depth images (except for the above-mentioned vignette filter). I did test with various gradients and sample images where I compared pixel by pixel. Apparently it holds quite well. Of course the 8bit limitation sucks and I'll keep looking for workarounds.
If you have the possibility to stress test it a little bit I would be forever grateful.
By the way, I'll add image blending soon which should help spotting colorspace errors.
axelMI
a whole new rabbit hole to get sucked into !! ahahah
esperent
Nice work.
Couple of issues I had: on mobile (Brave on Android) the touch controls for cropping are very janky. Feels like it steals control each time the picture updates maybe?
It's hard to see the controls under the picture on this small screen. Could you add a control to adjust the size of the controls and shrink the picture maybe?
axelMI
Sorry, this at the moment is desktop only. Maybe in the future I'll work on a separate PWA, but UI would need a complete re-design
GlassOwAter
I’m getting some touch control issues and inability to download the image on iOS.
amadeuspagel
Nice. I tried it with an image from Unsplash. Maybe you can use the Unsplash API[1] to give people an example to play with.
axelMI
Will have a look at this thanks! in the meantime I've added a quick sample gallery to test the editor out
popalchemist
Hey, awesome work! This is sorely needed in the OSS package space.
Are you taking into account separation of concerns? I could see myself adopting this if the UI were customizable in Vue, React, etc.
axelMI
The actual webgl engine is packaged as a separate library https://github.com/xdadda/mini-gl
As for the more complex UI modules (ie crop) in theory it shouldn't be too difficult to build a vanilla-js separate module, but not sure I'll have the time as these kind of solutions need to take into account so many use-cases and edge-cases that they easily become a nightmare to maintain ...
flashblaze
I'm so glad this exsist. I've been meaning to get something similar started, but did not due to one reason or the other. I'll definitely try to contribute.
axelMI
Whenever you feel like, ping me on github
rebelnz
Really nice work. I had been using Photopea for cropping and quick edits when preparing references for painting but this is super clean and simple.
munksbeer
This looks great, well done. However, I find one of the main tools I'm always looking for is the ability to resize an image. I want to retain the exact dimensions, just shrink the number of pixels. I can't spot that, is it there? Or would you consider adding it?
axelMI
I must admit this baffles me a little. Width and heights are measured in pixels. The number of pixels of an image is width x height (eg a raw image 256x256 means it has 256*256 = 65.536 pixels = 262.144 bytes - if RGBA8). I cannot reduce its pixels and keep the same width and height or viceversa. I can a) reduce the quality of an image with a lossy compression (in download if you select jpg you can reduce the quality which will in turn reduce the file size) b) scale an image when displayed in a browser (canvas.style.width > canvas.width) but the underlying image will still be canvas.width
Maybe I'm missing something
sjsdaiuasgdia
I assume they meant aspect ratio instead of dimensions.
axelMI
oh, in that case it's enough to go in the crop section, select "pic" to keep the current "picture" aspect ratio and resize the crop box
Dwedit
Is there any interference from "anti-fingerprinting" which corrupts the image canvas?
axelMI
ehm, what? I'm not familiar with this issue
Dwedit
https://support.mozilla.org/en-US/kb/firefox-protection-agai...
"Random data is introduced to background images when the image is read back by the website. If a website merely renders data to the background, it will render without alteration. Although typically this does not happen, if the website reads the image data in the background (and potentially displays it to you again), it will have subtle noise that may affect how the image is displayed."
axelMI
Ok interesting. I'll investigate thanks
I'm working on a pure js webgl image editor with effects, filters, crop & perspective correction, etc. My goal is to give the community an opensource solution as unfortunately most comparable apps are closed sources.
https://mini2-photo-editor.netlify.app to try it out (https://github.com/xdadda/mini-photo-editor)