TryGhost / Ghost

Independent technology for modern publishing, memberships, subscriptions and newsletters.
https://ghost.org
MIT License
46.9k stars 10.21k forks source link

[Investigation] Image Processing & Manipulation #4453

Closed ErisDS closed 5 years ago

ErisDS commented 9 years ago

This issue replaces (at least for now) issues #1688, #1734, and #4333 as the one true place to discuss all of our image processing needs in Ghost, and the possible solutions.

Please note that this issue is not about storing or managing uploaded images, only about processing them on the way to the server ;)


Image Processing Features

When it comes to uploading images in Ghost, there are a few issues we need to be able to resolve:

There are a few ideas for Ghost features that have been floating around forever that would require us to be able to generate multiple sizes of any one image:

Ghost doesn't currently have any form of image processing built in because we haven't yet found a good solution for doing this. Pretty much all node libraries for image processing have imagemagick as a dependency - and that is not a viable solution for Ghost due it it being a c++ program that's ridiculously hard to install. This leaves us with a few alternative options:

  1. Client - Do the processing on the client side
  2. PureJS - Find a pure JS module for image processing
  3. Compiler - Compile imagemagick using something like emscripten
  4. node-pre-gyp - Create or change an existing node module which wraps imagemagick to use node-pre-gyp which is the same thing we use to install sqlite3
  5. API - Use a 3rd party API for image processing
  6. Other Some other solution we haven't thought of yet.

Several of these solutions have been discussed or covered in the comments on #1688 and #1734, so they are worth a read ;)

What we know so far

  1. Client - it may be possible to do simple cropping or resizing using the canvas element and/or File API, but this won't work for any sort of regeneration of sizes, so it won't present a full solution. JavaScript-Load-Image (mentioned here is probably worth looking into though).
  2. PureJS - lwip is the only one I knew of, mentioned in #1734, but it turns out even that is actually largely written in C/C++! Is there really not a single PureJS one out there?! This needs investigating.
  3. Compilers - there was quite an extensive discussion on modules that we could potentially compile into JS and how on https://github.com/TryGhost/Ghost/issues/1688#issuecomment-30776652, this is worth further investigation as we might be a few small steps from a solution here.
  4. node-pre-gyp - if we could identify one of the imagemagick wrapper projects which has the features we need and might be active and willing to accept a PR this might be a viable option. node-pre-gyp has taken pretty much all of the pain out of sqlite3 installs. Still, as this is nowhere near as important a dependency as sqlite, I wonder if it will resolve the issue quite enough to be worth the problems some people will still have.
  5. API - there are a few that have been mentioned although I can't find the references now. Worth investigating, but this is likely to require keys and other complexities.
  6. Other - anyone got any bright ideas?

In summary, there's quite a bit of research and investigation to be done to come up with a couple of potential solutions for image processing in Ghost, and weigh up their pros and cons. I don't think there is going to be an obvious winner, but we need to gather together a clear picture of what's available to us so we can make a decision. The key thing is that we only* need crop, rotate, resize, exif modification & some optimisation tools - we aren't trying to detect faces or anything!

Anyone and everyone is welcome to jump in on this, with any solution they can come up with - I'm not looking for one person to do all the research but rather for people to volunteer their suggestions.

* I know I know only... haha

PaulAdamDavis commented 9 years ago

Doing any form of image processing client-side is a definite no from my point. I've worked on a project before which required resizing an uploaded image in canvas, but the browser (iOS 7 Safari in this case) had a file size limit of approx 2mp. I couldn't even process photos from the devices own camera. :)

Processing images that the browser couple code with was also super slow.

Edit:

The maximum size for a canvas element is 3 megapixels for devices with less than 256 MB RAM and 5 megapixels for devices with greater or equal than 256 MB RAM. ~ Scroll to "Know iOS Resource Limits"

I know this is iOS specific, but it's a large audience.

mattiascibien commented 9 years ago

I use cloudinary for image processing on my blog and my (in development) node art gallery. The Node API is fantastic and works, the only problem is that by being a third party service and so could be better served as a plugin (or app in Ghost terms).

For using a pure nodejs library i guess that JIMP can be an option.

It can actually be useful for creating the Multiple image sizes @ErisDS mentioned.

ErisDS commented 9 years ago

@mattiascibien JIMP is an awesome find - thank you! It looks like it has most of the features we need - crop, resize and quality that will help us generate images.

This just leaves the exif device orientation issue, as I don't think it will solve that.

Would be great if someone could try JIMP out and see how well it works.

mattiascibien commented 9 years ago

@ErisDS I'll set up a simple repo on my profile and share my test with the Ghost team. Always wanting to learn more node. :+1:

EDIT: for exif https://www.npmjs.org/package/exif-rotaten https://www.npmjs.org/package/fix-orientation and https://www.npmjs.org/package/jpegorientation. (I'll check those tomorrow since it is almost midnight here)

EDIT 2: Maybe with just a node exif parser and JIMP we can manipulate the image as we want.

Izhaki commented 9 years ago

While I understand the need for a "one true place" for a discussion on images, the features discussed here are momentous and will take ages to implement.

I wonder if an agile approach wouldn't be more appropriate here, so we can have quick time-to-market with image related issues? What's more, some requirements are far less common than others. In other words, I don't think other issues need to be closed due to this 'central discussion place'.

My particular case is that I user OmniGraffle to generate diagrams. I export them as PNGs, with may having 200px width. Ghost stretches these images, so pixilation appears. It makes little sense to add white margins to fit the blog width, as you don't want these margins when sharing these diagrams on social networks (like Pinterest).

All I need is a way to tell Ghost not to stretch diagrams. As far as implementation goes, this is nowhere on par with "providing themes various image sizes to display in different places" or many other ideas proposed here.

ErisDS commented 9 years ago

@Izhaki what you're describing is how the theme handles the images, not Ghost itself. If you don't want full-width images, you can use a different theme or modify the one you're using (I assume Casper).

This is intended as a place to gather and share knowledge about what solutions are available for processing and manipulating images in node. There may well be many smaller issues to implement solutions in future once we've decided on a direction - but it helps to keep all of the problems we have described in one location so that we can evaluate potential solutions.

ekulabuhov commented 9 years ago

Hey guys,

I decided to try and tackle the EXIF problem, so I grabbed jimp and built a small demo. It's running on node and is pure JS, so no 'hard to install' dependencies. Also, I'm thinking of turning it into express middleware. Any thoughts?

Demo: https://imageware.herokuapp.com/ Example images: https://github.com/recurser/exif-orientation-examples Repo: https://github.com/ekulabuhov/imageware

ErisDS commented 9 years ago

Hey @ekulabuhov this looks awesome! The main thing with making it middleware is would it work with the busboy setup in Ghost or would we need to change that?

I'd love to see a PR adding your new middleware to Ghost! :grinning:

ekulabuhov commented 9 years ago

Thanks @ErisDS! The busboy setup shouldn't be a problem because I'm using multer at the moment, which is a simple wrapper around busboy. What bothers me is the performance of this solution. The implementation is way too slow with real photos. Decoding and encoding JPEGs takes a big chunk of time from what I see. Nodejs is not a great candidate for long blocking operations from what I gathered.

ErisDS commented 9 years ago

I've been thinking about this quite a lot, off and on, over the past couple of months. It's such a killer that we haven't got basic image processing available to us, and I think we're going to need to be super creative with a solution if we move forward.

I tested out @ekulabuhov's demo and whilst it's not fast, it wasn't terribly slow either, and the laws of tech say it should only get faster, right? So it might not be a complete loss.

In addition, from reading through the (very minimal) documentation for blueimp it seems they support some resizing in the client - but whether you still need a backend or whether it can be done using canvas in browsers that support it is not 100% clear.

What blueimp definitely can do, is show a preview of the image being uploaded. I think we could do something clever and combine this with server-side workers. We can show the image before it uploads, and then after it uploads kick off a task to do the necessary processing. The user doesn't need to wait for it to finish, they can carry on working with the preview version.

If none of that works, there are definitely 3rd party services we could offload the work to over an API, and that approach could work very well.

I think there's more investigation and experimentation to be done, but I believe there is a solution out there somewhere!

marcfallows commented 9 years ago

I'm very interested in this feature. jimp seems like a pretty good solution. A PureJS solution feels like the most appropriate direction for this project.

As long as the chosen library is wrapped in some API that represents the features Ghost needs (crop, resize, etc) then a more appropriate library could always be dropped in later.

Attempting one feature to see if jimp will do the trick could be a worthwhile effort. "Multiple image sizes" is a good candidate as it only requires resize, and could easily fall back to presenting the original image until the processing is complete (if the processing does indeed take a significant amount of time).

I'm eager to help out in any way possible to see this land in Ghost.

ErisDS commented 9 years ago

Yes I'd love to see a demo which used blueimp's preview to mask the upload process taking a while. I really think the solution is out there it just needs someone with enough time & interest to have a play around with it and see what they can come up with.

nii236 commented 9 years ago

Jimp looks good and should do the trick. At the moment I'm having to locally batch resize photos taken from my camera, otherwise I'll be uploading 2mb per photo!

PaulBGD commented 9 years ago

I proposed a way we could optimize images at #5487

JamesJosephFinn commented 8 years ago

I would like to submit this new, free and open source api into consideration: ResponsiveBreakpoints.com via Cloudinary. I use the web gui for other non-ghost work, and it is great.

nii236 commented 8 years ago

I think it would be better to handle the image processing internally, on the Ghost server itself just in case the API dies on us one day.

JamesJosephFinn commented 8 years ago

@nii236 I've never actually gotten it running myself (that's a wee bit above my head at the moment) but the tool is open sourced on github

kevinansfield commented 8 years ago

@JamesJosephFinn the ResponsiveBreakpoints tool still goes through Cloudinary's service so it's not suitable for anyone who doesn't want to set up, use & pay for that service.

We're looking at making the image processing in Ghost modular so that users can swap in the processor of their choice similar to the modular fileStorage system we currently have. Once at that stage it should be possible to create a Cloudinary module, possibly even one that uses their ReponsiveBreakpoints tool.

abarcenas29 commented 8 years ago

I'm using JIMP on my node project as well and it works like a charm. What's the verdict on that? It might not solve all of the issues, but at least it solves 80% of it.

sakulstra commented 8 years ago

Sry for bringing this up, but this could be a good alternative. I'm using sharp in most of my node projects. Similar to jimp it's largely written in c/c++. All the needed features should also be possible with sharp. In comparison to jimp it's around 30x faster http://sharp.dimens.io/en/stable/performance/

lovell commented 8 years ago

Hi everyone, I was recently alerted to this discussion and as the maintainer of sharp, mentioned as a possible solution to all of the original requirements, may be able to help.

I love the all work that's been done to make ghost as easy as possible for newcomers to install so completely understand why adding support for image processing needs to be done carefully.

To avoid the magick-esque problem of having to install global runtime dependencies, sharp downloads a tarball containing a pre-compiled libvips and its dependencies into the local node_modules at npm install time. These are provided for Linux (x64 and ARM), Windows (x64) and OS X.

The work to avoid a C++ compilation step for as many people as possible via node-pre-gyp is being tracked at https://github.com/lovell/sharp/issues/186.

ekulabuhov commented 7 years ago

Hey guys, tried out sharp and damn, it's fast!

I didn't have much trouble compiling it on both OSX and Heroku. Win32 is not supported and I don't have Win64 to test it (but should be supported according to sharp documentation).

As for API, I think implementing it as an middleware that intercepts all the .jpg, .png requests could be a good solution. We could append parameters to the image URL to specify the kind of transformations we want applied to the image, for example:

http:///content/images/2016/10/cover.jpg?width=600&height=300

Links to the new updated demo are still the same: Demo: https://imageware.herokuapp.com/ Example images: https://github.com/recurser/exif-orientation-examples Repo: https://github.com/ekulabuhov/imageware

Also sharp has a very cool API. It includes the EXIF decoder and a rotate() method that automatically fixes the EXIF issue.

paulmaunders commented 7 years ago

Has anyone had a go at implementing sharp with Ghost yet?

ErisDS commented 7 years ago

Yes 😁

I worked on a demo, we are formulating plans around functionality for after we ship 1.0 😍

krainboltgreene commented 7 years ago

I've kept this issue in mind for a long time and I finally made a HTTP API that does this: https://github.com/lacqueristas/lumin

  1. It uses gm for the manipulation
  2. Facets are requested on upload or by default (?lenses=original,thumbnail,monocromatic)
  3. HATEOAS compliant
  4. Currently uses Google Cloud Storage
lucapost commented 7 years ago

Hi all, please add history images navigation in the editor. The goal in select old image in the new posts without uploading it but just select previus.

Thanks LP

mordka commented 7 years ago

Any updates on this? It's been 3 years since the first request was submitted.

josefglatz commented 6 years ago

Hi @ErisDS ! Are there any plans in near future on the agenda to add image processing (especially image rotation based on exif after upload)?

leapit commented 6 years ago

yes, should give priority to image processing feature @ErisDS ,any plans about this in further build?

admix commented 6 years ago

Is there still development going on for Ghost ? Any plans/roadmap for the image optimization? Thinking of switching to other blogging platform, because of that.. really drops performance by a factor.. Cheers.

Semertl commented 6 years ago

Ran into this issue aswell. Are there updates?

kevinansfield commented 6 years ago

It's definitely on the roadmap but there are higher priority issues at the moment. Any updates will be added to this issue.

rdetert commented 6 years ago

@ekulabuhov Do you have any info on how to setup sharp as a middleware layer to resize based on image url parameters?

ErisDS commented 6 years ago

I have a demo here: https://github.com/ErisDS/Ghost/commit/fb0a0fbcd756b7326fde0c21513b70ae7a698c7d.

lovell commented 6 years ago

Prebuilt binaries are now available in sharp v0.20.0.

zkanda commented 6 years ago

Can't wait to have this feature built in, @ErisDS any change it would get included in the next release?

venku122 commented 6 years ago

Hello, I am a happy ghost user and node programmer. The features defined in this issue would be awesome to have in the official ghost release. Is there a current roadmap of when this might be implemented? Is there a way I could look into contributing the functionality myself?

mg5thave commented 6 years ago

Any thought to accomplishing this with server side image resizing + caching? For instance: https://stumbles.id.au/nginx-dynamic-image-resizing-with-caching.html

If Ghost were able to determine that it is serving a thumbnail, preview, or full-sized image, it could simply pass the size in the URL, and have nginx process the image manipulation via a location proxy on the /content/images/ folder.

tsia commented 6 years ago

Interesting idea but that would require nginx in the first place. I don’t think such a feature should be implemented by requiring a specific webserver. Many people may be using apache or lighttpd or something else. Or they may be using a hoster without router access.

do-io commented 6 years ago

Has there been any updates or work being done towards this end? I do have people that are intrigued by using Ghost, but I know they are not really the type to resize images themselves. If you are looking for help, let me know.

Also, is there thoughts or options of a media browser situation?

nii236 commented 6 years ago

I've been tracking this issue for years now and I'm surprised it hasn't been solved yet. In terms of client side resizing, I've used picajs with great success.

kevinansfield commented 6 years ago

It's very much on our radar and has been discussed internally a number of times but no quick-win has emerged so far. Unfortunately we don't have the resources to tackle it just yet, it's a very large project to implement well and in a way that works across the huge variety of Ghost install environments and use-cases.

You can add your vote/input to the related feature requests on our Ideas board that we'll be referencing when we do get to the design & implementation stage:

https://forum.ghost.org/t/automatic-thumbnailing-resizing-of-image-uploads/475 https://forum.ghost.org/t/media-library-manage-all-files-in-one-place/675

farcaller commented 6 years ago

no quick-win has emerged so far

1734 was opened in 2013, that's about 5 years ago. I can understand that image processing isn't the scope of ghost and no one on the team wants to tackle this but then maybe you just need to phrase it in a way that doesn't create false expectations?

do-io commented 6 years ago

Put in my votes.

I have wanted to use Ghost as my main blog platform, but I have a problem with a PaaS that doesn't acknowledge the main focus for our Search Engine brethren - Well structured and a speedy website.

To that end, a minimum of resizing an image - or option at least to size to a size that can be turned on or off seems like a good starting point.

Personally, I can write these things myself, but if I do I am more likely to utilize a cloud function implementation.

From a starting point, perhaps start with a solution that can be used on Google (Pro) that supports your hosting solution adding a hook that extends into other services and grows with time.

It may not give everyone everything and serve everyone at once, but it is a starting point.

Also, thank you for creating this platform. I really do prefer using Markdown for writing posts.

parkerproject commented 6 years ago

Good points, the success of any platform is listening to its users because without the users there would be no product.

JohnONolan commented 6 years ago

This thread has become a rant and is no longer useful, so I'm restricting it to maintainers.

I'll summarise the answers to the questions which keep coming up:

Why don't you just do it already? If it was that straightforward, we would. It isn't straightforward at all, and to date there's still no good solution.

Then howcome [other platform] manages to do it so easily? Because it is either centralised, or written in PHP. Both of which make solving this very easy.

Omg I can't believe it has been 5 years and nobody has fixed this, what a joke The way open source works is that people who care about an issue write code to solve it. If you care about this issue, please write some code to solve it.

I'm not going to write any code, I don't have time for that! There's your answer to why this issue has been open 5 years.

So what now? We'll work on it as soon as we can. Right now there are other things which are more important to us.

But: Anyone else is more than welcome to contribute to this if they want to see it happen sooner rather than later. Hannah has posted very detailed info above, and even a prototype implementation :)

This is also a great read: https://nolanlawson.com/2017/03/05/what-it-feels-like-to-be-an-open-source-maintainer/

naz commented 6 years ago

Opened an issue in sharp repository regarding image being transformed even though no compression parameters were specified - https://github.com/lovell/sharp/issues/1360 . We have a workaround for it but would be nice to figure it out :thinking:

allouis commented 6 years ago

Cropping example here: https://github.com/tryghost/cropper

kirrg001 commented 6 years ago

FYI: We have merged and released (Ghost 2.1.0) the first iteration of image processing using sharp. sharp is very good maintained, offers all the functionality we need and is easy to install since 1.20.0.