-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Add a function for specifying how to draw an image within set bounds without distorting its aspect ratio #5720
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Welcome! 👋 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, please make sure to fill out the inputs in the issue forms. Thank you! |
I think this sort of feature would be really useful! I've had to write manual versions of this a number of times for projects. Thanks for all the detailed diagrams too! Some API questions and suggestions:
|
I do really like the idea to change the names of EXTEND and SHRINK to COVER and CONTAIN! I didn't know about the CSS API, but having them be consistent with that could help easily familiarize those who have done similar work in CSS with images. I feel that this function would definitely work better as a one-off drawing function rather than one that persists like imageMode(), since most of the time you wouldn't want this effect to happen on multiple images in succession. The extended image() function For example, image() would be redefined as being able to take the following arguments:
These changes should allow for all code currently using image() to still function identically, while providing the new fitting functionality as well in a way that reduces function clutter and allows for it to only affect a single image call instead of persisting. Some examples of the function being used in a program could look as follows:
This does come with the problem still of maybe being too many arguments for a p5 function, but because most of them are optional, I feel that it would be alright since new programmers could use the current image() functionality still by omitting the fitting parameters. |
This is great, I like this design a lot!
Starting from `image(img, bx, by, bWidth], bHeight) and then incrementally adding fit parameters feels right, and like a small enough jump. My only worry would be that seeing all the options in the docs would be overwhelming, but like you showed above, it's really just two options, with optional parameters. So maybe leaning on having good examples is the way to go. One other alternative could be using {
fit: 'cover' | 'contain'
xAlign?: 'left' | 'center' | 'right'
yAlign?: 'top' | 'center' | 'bottom'
} The benefits I see of this are:
The downside is that this would be a relatively new pattern in p5, so it would be a question of whether it's worth trading the above for a longer parameter list but a bit more API consistency. I could see that potentially being more important, so let me know if you have thoughts on that! |
Having the arguments be optional after the fit argument can help by showing which arguments are required for others to function naturally, as can be seen in image(). The fit parameter has no use unless a width and height are also passed, which must always be required currently since the fit argument is located after width and height. The same goes with the alignment parameters currently as they are after the fit parameter, which means a fit must currently be specified in order for alignments to also be specified. I could see these as possible benefits for the current arguments system, and this would be the one I would lean towards being implemented more because it does also match with the majority of the rest of p5 functions in syntax.
An options argument isn't actual unattested in p5 currently, as seen in the textBounds method for p5.Font objects, so it is definitely possible for this to be the way it gets implemented. |
That makes sense! I'll wait for some p5 image people to see if they've got any opinions but I'd feel good about going down your preferred route. @stalgiag I think this is your area, how do you feel about this proposal? |
Hi @tetrogem thank you this detailed and considered proposal! Also @davepagurek thanks for jumping in and generating a valuable conversation. I really enjoyed reading the back and forth. While I am a steward of the Image section, this is more out of my relationship to GIFs and image decoding/encoding. Much of the design of The concerns that are forefront in my mind have already been identified here: making sure old sketches don't break with any change to Just stepping back a bit, I would love to think through examples where one doesn't want to distort the aspect ratio of an image but approaches like the following are insufficient: I don't need to resize the image - image(myImage, x, y) I need to resize the image - image(myImage, x, y, myImage.width * 0.6, myImage.height * 0.6) I need to resize the image but I need it to be centered relative to original size - imageMode(CENTER);
image(myImage, x + myImage.width / 2, y + myImage.height / 2, myImage.width * 0.6, myImage.height * 0.6); I know some clumsiness is creeping in by that last example but it is possibly more legible than filling out the entire signature and adding an options object at the end. But it is very likely that I am missing some goals that would become much easier with the new proposal. Any ideas? |
My main use case is that I have an image that I want to be centered and resized to fit inside a box. Say I've got an image of a bird cage (the box) and an image of a bird (the image) and I want to always draw the bird centered inside the cage. The logic I'd need to write to accomplish this gets pretty complicated when you consider the different combinations of aspect ratios: const birdRatio = bird.width / bird.height
const cageRatio = cage.width / cage.height
let birdScale
if (birdRatio < cageRatio) {
birdScale = cage.height / bird.height
} else {
birdScale = cage.width / bird.width
}
const offsetX = (cage.width - birdScale*bird.width) / 2
const offsetY = (cage.height - birdScale*bird.height) / 2
image(cage, 0, 0)
image(bird, offsetX, offsetY, birdScale * bird.width, birdScale * bird.height) Another scenario is if I have a box that I want to completely fill with an image. Maybe I'm making thumbnails for an image gallery and I want each thumbnail to be the same size, completely filled edge to edge with the image. I'd have to write similar code but flip the contents of the if and else branches. With the new proposal, the bird and cage example would be: image(cage, 0, 0)
image(bird, 0, 0, cage.width, cage.height, CONTAIN) and making a thumbnail for an image gallery would be: image(myImage, 0, 0, thumbnailWidth, thumbnailHeight, COVER) |
Another example of using COVER I use very often would be to cover the entire background of a sketch with an image, and allowing it update live based on both the size of the canvas (if it changes) and making sure it would work with any image passed into it. Currently, the code to do this would look like: |
Thanks! I can see the value. I would personally opt to use DOM elements in the image gallery and background examples but I see the value for others. As for the design, wouldn't the simplest possible change that doesn't break current functionality be: image(myImage, 0, 0, width, height, dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight, fit, xAlign, yAlign); or something similar with an options object replacing |
I think so! Tell me if I'm not on the same page but that's also how I'm reading @tetrogem's second proposal -- just those extra parameters, but with the sx, sy, etc renamed to be bx, by, etc. In any case I think that change feels minimal but quite useful :) |
Oh, my apologies, it is slightly different. Here's everything in one place so you can compare side-by-side: // Original API
image(img, dx, dy, dWidth, dHeight, sx, sy, [sWidth], [sHeight])
// @TetroGem's proposal
image(img, dx, dy, dWidth, dHeight, bx, by, [bWidth], [bHeight], [fit], [xAlign], [yAlign])
// @stalgiag's proposal
image(img, dx, dy, dWidth, dHeight, bx, by, [sWidth], [sHeight], [fit], [xAlign], [yAlign]) The difference between the last two being, the destination/box size should be equivalent, and the |
I don't know how clear this was in my original comment as I tried to keep it as brief as possible, but the API using The d arguments represent the region of the original image to be drawn, and the b arguments represent the region on the canvas for the image to be drawn at, which should be the way it currently works if I'm not mistaken. It's just rephrasing it as being the shorter function's b arguments (from |
I think the source of the confusion might just be this. In the docs for
|
Ah, I see I got the two mixed up. In that case, having the extended arguments list be: |
Hi, I would like to work on this. |
Hi @Manpreet-Singh001, I've assigned this to you 🙂 |
In that example, the bottom of the image would be at the bottom of the bounding box, with the top of the image spilling out of the box instead. |
I think with xAlign: left and yAlign: bottom it would continue to look like your first image regardless of the imageMode specified. I think of imageMode as specifying the size/position of the purple box in your image, and then the xAlign/yAlign are responsible for positioning the image relative to the purple box. |
CSS cover definition |
CSS I believe that example is just a demonstration and doesn't cover the case where the input image is smaller, but the ideal implementation would. I think instead of checking if the width if the source is greater than the destination, it needs to be checking if the aspect ratio of the source is greater than the aspect ratio of the destination, and resize however much is necessary to fit or cover the destination. |
@all-contributors please add @tetrogem for idea |
I've put up a pull request to add @tetrogem! 🎉 |
Increasing Access
This would allow p5 users to easily do things such as have unstretched images cover the entire background of a sketch, without needing to do the math required to find the scaling needed to have it cover the needed area, or only fit inside of it if wanted instead.
Most appropriate sub-area of p5.js?
Feature request details
This proposal is to add a new function similar to rectMode(), ellipseMode(), imageMode(), and textAlign(), which would be called imageFit() (working name). This function would change the width and height arguments of image() function to specify a bounding area for the image to be drawn within, instead of the width and height the image should be drawn with.
The imageFit() function would be able to take three arguments, (fit, xAlign, yAlign).
The fit argument would take the following new p5 constants:
The xAlign argument would take the following p5 constants:
The yAlign argument would take the following p5 constants:
The imageFit() function would be able to be called as:
The imageFit() function would return the p5 instance, to allow for chaining (as do rect/ellipse/imageMode())
The following image will be used to demonstrate the different fitting types: (original file/aspect ratio)

The STRETCH fit would function identically to how images are drawn currently. It would draw the image on screen so that its width and height were equal to the arguments passed into the image() function.

The EXTEND fit would extend the shape so that it fills the entirety of the bounding area specified by the width and height arguments of the image() call. Parts of the image may also be drawn outside of this bounding area if the aspect ratio of the original image is not the same as the bounding area's.

The SHRINK fit would contain the shape within the bounding area specified in the image() function. This will result in some of the bounding area not being covered if the aspect ratio of the image is not equal to that of the bounding area.

The xAlign and yAlign arguments of the imageFit() function work identically to and use the same constants as the textAlign() function. These will specify how the image will be drawn within its bounding area after it has been fitted.


*** EDIT: THIS IMAGE IS MEANT TO SAY "EXTEND" IN THE TOP LEFT
(Optional) scale argument for the imageFit() function

The scale argument would allow a number to be passed in to scale the image by after it as been aligned and fitted to the bounding area:
This would also then allow the imageFit() function to be called using any of the following arguments:
The text was updated successfully, but these errors were encountered: