-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Image fit features #5784
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
Image fit features #5784
Conversation
🎉 Thanks for opening this pull request! Please check out our contributing guidelines if you haven't already. And be sure to add yourself to the list of contributors on the readme page! |
This is a working prototype. It doesn't include unit-tests, since I couldn't think of what the tests should look like. Here's how the logic works.
I am fairly new to open-source so any suggestions on how to improve this are welcome. |
) { | ||
// set defaults per spec: https://goo.gl/3ykfOq | ||
|
||
p5._validateParameters('image', arguments); | ||
|
||
let defW = img.width; | ||
let defH = img.height; | ||
yAlign = yAlign || constants.CENTER; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default is CENTER
if not specified by user
const _dy = dy; | ||
const _dw = dWidth || defW; | ||
const _dh = dHeight || defH; | ||
let _dx = dx; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the fit
mode needs to change these variables to allow fitting the entire image
src/image/loading_displaying.js
Outdated
vals.w = w; | ||
vals.h = h; | ||
} | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imageMode
shouldn't affect the image shown when fit
mode is specified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we still want imageMode
to affect positioning when fit
is specified, and it should just refine the positioning. of the source image within the destination box specified. e.g. starting from this non-fit
code:
rectMode(CENTER)
imageMode(CENTER)
rect(0, 0, dw, dh)
image(img, 0, 0, dw, dh, 0, 0, sw, sh) |
![]() |
adding some fit parameters, I would expect it to look like this:
rectMode(CENTER)
imageMode(CENTER)
rect(0, 0, dw, dh)
image(img, 0, 0, dw, dh, 0, 0, sw, sh, COVER, RIGHT, CENTER) |
![]() |
but without taking into account imageMode
, it looks like this currently:
rectMode(CENTER)
imageMode(CENTER)
rect(0, 0, dw, dh)
image(img, 0, 0, dw, dh, 0, 0, sw, sh, COVER, RIGHT, CENTER) |
![]() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update: I think I get what you were trying to convey!
Currently, it doesn't take imageMode
into account and produces the result as if imageMode
is always CORNER
. I was not able to reproduce this image going out of bound. I would appreciate if you could send me the src
for this sketch. Also, my question regarding imageMode
is if imageMode
is CENTER
and the user inputs
image(img, 0, 0, dw, dh, 0, 0, sw, sh, COVER, RIGHT, CENTER
should we consider yAlign: center
from the center of the image.
src/image/loading_displaying.js
Outdated
|
||
function _imageCover(img, xAlign, yAlign, dw, dh, sx, sy, sw, sh) { | ||
const r = Math.max(dw / sw, dh / sh, 1); | ||
img.resize(img.width * r, img.height * r); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we aren't able to use img.resize
in this method because not all input types have this method (e.g. p5.MediaElement
, the return type of createVideo
). Also, resize
modifies the image object, and I think we want to keep these methods from modifying the input.
src/image/loading_displaying.js
Outdated
*/ | ||
|
||
function _imageContain(xAlign, yAlign, dx, dy, dw, dh, sw, sh) { | ||
const r = Math.max(sw / dw, sh / dh, 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might need to get the value of r
before doing the Math.max(..., 1)
in order to handle the case where the source image is smaller than the destination box and needs to be scaled up past its initial size. Right now, if I add a tiny source image (20x20 in this example), it never draws larger than its initial size, but we'll want it to uniformly scale up to fit the destination box:
img.resize(20, 20)
rect(0, 0, dw, dh)
image(img, 0, 0, dw, dh, 0, 0, sw, sh, CONTAIN, RIGHT, CENTER)
Expected | Current |
---|---|
![]() |
![]() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It calculates, the minimum needed ratio that we need to scale the destination box by, so that we don't distort the image aspect ratio. Let me, know if it still confuses you.
Thanks for taking this on, and nice work on this! I tested different combinations of fits and fills and the alignment logic works well! I left a few comments about handling more edge cases. Let me know if I can clarify anything! |
Also, here's an idea of how we can unit test this: All test('COVER when the source image is taller than the destination', function() {
let src = myp5.createImage(20, 100);
sinon.spy(myp5._renderer, 'image');
myp5.image(src, 0, 0, 300, 400, 20, 100, myp5.COVER);
assert(myp5._renderer.image.calledOnce);
assert.equal(
myp5._renderer.image.getCall(0).args[3], // sw
20
);
assert.equal(
myp5._renderer.image.getCall(0).args[4], // sh
20 * (400 / 300)
);
// etc
}); |
src/image/loading_displaying.js
Outdated
@@ -539,6 +539,17 @@ p5.prototype.image = function( | |||
|
|||
let vals = { x: _dx, y: _dy, h: _dh, w: _dw }; | |||
if (fit) { | |||
switch (this._renderer._imageMode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is close to what we want! I think the image mode would change the destination box size (and shift the other way for CENTER
, i.e.:
switch (this._renderer._imageMode) {
case constants.CENTER:
_dx -= _dw * 0.5;
_dy -= _dh * 0.5;
break;
case constants.CORNERS:
_dw -= _dx;
_dh -= _dy;
break;
}
...although I wonder if we can reduce the complexity by using modeAdjust
, since that does these adjustments already? Something like this, where you pass vals.w
, vals.h
, vals.x
and vals.y
for dw
, dh
, dx
, and dy
:
const vals = canvas.modeAdjust(_dx, _dy, _dw, _dh, this._renderer._imageMode);
if (fit) {
if (fit === constants.COVER) {
const { x, y, w, h } = _imageCover(
xAlign,
yAlign,
vals.w, // Replacing dw
vals.h, // Replacing dh
_sx,
_sy,
_sw,
_sh
);
// etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried using the existing modeAdjust
function but the way it works is by shifting the dx or dy
so as the image appears to be drawn from the center. The reason I couldn't use it to is because it in fitMode COVER
moving away the destination box makes the image not cover the whole required box, which seems a bit counter intuitive.
Since from the definition:
imageMode(CENTER) interprets the second and third parameters of image() as the image's center point. If two additional parameters are specified, they are used to set the image's width and height.
Should the user expect the destination width be adjusted/[half in case of CENTER
] of what they specified in fitMode COVER
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the current behavior is the desired one. I think the complexity can be addressed by taking this logic outside into one function call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in fitMode COVER moving away the destination box makes the image not cover the whole required box
I think sx
and sy
, sw
, and sh
only change what part of the source image gets put into the destination box, wherever the destination box is, so when you shift the destination box, the source image shifts with it. This would be consistent with how imageMode
works with image
when you don't provide any fit parameters: changing from imageMode(CORNER)
to imageMode(CENTER)
just shifts where the image is drawn, but the image itself is the same as before.
Let me know if I'm misinterpreting what your concern is, but I think your _imageCover
and _imageContain
functions will produce the expected output if we give them updated dx
, dy
, dw
, and dh
values, either from manually applying shifts as we're doing currently, or by giving them the outputs of modeAdjust
.
test/unit/image/loading.js
Outdated
myp5.image(src, 0, 0, 300, 400, 0, 0, 20, 100, myp5.COVER); | ||
assert(myp5._renderer.image.calledOnce); | ||
assert.equal(myp5._renderer.image.getCall(0).args[3], 20); // sw | ||
assert.equal(myp5._renderer.image.getCall(0).args[4], 400 / 15); // sh |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work on these tests!
src/image/loading_displaying.js
Outdated
*/ | ||
|
||
function _imageCover(xAlign, yAlign, dw, dh, sx, sy, sw, sh) { | ||
const r = Math.max(dw / sw, dh / sh, 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I just noticed, if you use an image that's larger than the destination, it doesn't scale down to minimally cover (because that would involve a scale lower than 1.) I think if we remove the , 1
here then it will handle that case though!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix this, I assumed that minimal cover was not required. But, looking at css cover
it does actually minimally covers the larger image.
fix: updated tests improved: readability
vals = canvas.modeAdjust(_dx, _dy, _dw, _dh, this._renderer._imageMode); | ||
} | ||
let vals = canvas.modeAdjust(_dx, _dy, _dw, _dh, this._renderer._imageMode); | ||
vals = _imageFit( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, this code looks great!
I'll run the code again this evening to double check that everything works as expected, but unless anything new comes up, this seems good to go! Great work! The one final thing we might want to do is add an example to the comments of |
Just played around with it some more, this looks good to me! I think once we have an example in the docs, this is good to go 😁 |
src/image/loading_displaying.js
Outdated
@@ -424,6 +424,14 @@ function _sAssign(sVal, iVal) { | |||
* to explain further: | |||
* <img src="assets/drawImage.png"></img> | |||
* | |||
* This function can also be used to draw images without distorting the orginal aspect ratio, | |||
* by adding 9th parameter fit which can either be COVER or CONTAIN. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super minor grammar suggestion: I think a few commas on this line and the next will make this a tad easier to read:
- * by adding 9th parameter fit which can either be COVER or CONTAIN.
+ * by adding 9th parameter, fit, which can either be COVER or CONTAIN.
- * CONTAIN as the name suggests contains the whole image within the specified destination box
+ * CONTAIN, as the name suggests, contains the whole image within the specified destination box
src/image/loading_displaying.js
Outdated
* function setup() { | ||
* // COVER the whole destination box without distorting the image's aspect ratio | ||
* // COVER the specified destination box which is of dimension 100 x 100 | ||
* // Display the CENTER part of the image for both LEFT and TOP corner |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this comment to remind readers that CENTER is the default for x and y align? If so, maybe this wording will be a little clearer:
// Without specifying xAlign or yAlign, the image will be
// centered in the destination box in both axes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion, this makes it a lot more easier to read.
Thanks for adding those examples! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is all ready to go now! Thanks for the time you put into this feature 🙂
@all-contributors please add @Manpreet-Singh001 for doc, code, test |
I've put up a pull request to add @Manpreet-Singh001! 🎉 |
Addresses #5720
Changes:
Allows users to
CONTAIN/COVER
the specified destination box without distorting the aspect ratios.Screenshots of the change:





CONTAIN
CONTAIN CENTER CENTER
CONTAIN CENTER TOP
COVER
COVER CENTER CENTER
COVER RIGHT CENTER
PR Checklist
npm run lint
passes