Skip to content

Commit 5a44d40

Browse files
authored
Merge pull request #5784 from Manpreet-Singh001/image-fit-features
Image fit features
2 parents 3e0dc95 + 4336765 commit 5a44d40

File tree

4 files changed

+255
-8
lines changed

4 files changed

+255
-8
lines changed
4.45 KB
Loading

src/core/constants.js

+12
Original file line numberDiff line numberDiff line change
@@ -725,3 +725,15 @@ export const LABEL = 'label';
725725
* @final
726726
*/
727727
export const FALLBACK = 'fallback';
728+
729+
/**
730+
* @property {String} CONTAIN
731+
* @final
732+
*/
733+
export const CONTAIN = 'contain';
734+
735+
/**
736+
* @property {String} COVER
737+
* @final
738+
*/
739+
export const COVER = 'cover';

src/image/loading_displaying.js

+188-8
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,116 @@ function _createGif(
666666
finishCallback();
667667
}
668668

669+
/**
670+
* @private
671+
* @param {Constant} xAlign either LEFT, RIGHT or CENTER
672+
* @param {Constant} yAlign either TOP, BOTTOM or CENTER
673+
* @param {Number} dx
674+
* @param {Number} dy
675+
* @param {Number} dw
676+
* @param {Number} dh
677+
* @param {Number} sw
678+
* @param {Number} sh
679+
* @returns {Object}
680+
*/
681+
682+
function _imageContain(xAlign, yAlign, dx, dy, dw, dh, sw, sh) {
683+
const r = Math.max(sw / dw, sh / dh);
684+
const [adjusted_dw, adjusted_dh] = [sw / r, sh / r];
685+
let x = dx;
686+
let y = dy;
687+
688+
if (xAlign === constants.CENTER) {
689+
x += (dw - adjusted_dw) / 2;
690+
} else if (xAlign === constants.RIGHT) {
691+
x += dw - adjusted_dw;
692+
}
693+
694+
if (yAlign === constants.CENTER) {
695+
y += (dh - adjusted_dh) / 2;
696+
} else if (yAlign === constants.BOTTOM) {
697+
y += dh - adjusted_dh;
698+
}
699+
return { x, y, w: adjusted_dw, h: adjusted_dh };
700+
}
701+
/**
702+
* @private
703+
* @param {Constant} xAlign either LEFT, RIGHT or CENTER
704+
* @param {Constant} yAlign either TOP, BOTTOM or CENTER
705+
* @param {Number} dw
706+
* @param {Number} dh
707+
* @param {Number} sx
708+
* @param {Number} sy
709+
* @param {Number} sw
710+
* @param {Number} sh
711+
* @returns {Object}
712+
*/
713+
714+
function _imageCover(xAlign, yAlign, dw, dh, sx, sy, sw, sh) {
715+
const r = Math.max(dw / sw, dh / sh);
716+
const [adjusted_sw, adjusted_sh] = [dw / r, dh / r];
717+
718+
let x = sx;
719+
let y = sy;
720+
721+
if (xAlign === constants.CENTER) {
722+
x += (sw - adjusted_sw) / 2;
723+
} else if (xAlign === constants.RIGHT) {
724+
x += sw - adjusted_sw;
725+
}
726+
727+
if (yAlign === constants.CENTER) {
728+
y += (sh - adjusted_sh) / 2;
729+
} else if (yAlign === constants.BOTTOM) {
730+
y += sh - adjusted_sh;
731+
}
732+
733+
return { x, y, w: adjusted_sw, h: adjusted_sh };
734+
}
735+
736+
/**
737+
* @private
738+
* @param {Constant} [fit] either CONTAIN or COVER
739+
* @param {Constant} xAlign either LEFT, RIGHT or CENTER
740+
* @param {Constant} yAlign either TOP, BOTTOM or CENTER
741+
* @param {Number} dx
742+
* @param {Number} dy
743+
* @param {Number} dw
744+
* @param {Number} dh
745+
* @param {Number} sx
746+
* @param {Number} sy
747+
* @param {Number} sw
748+
* @param {Number} sh
749+
* @returns {Object}
750+
*/
751+
function _imageFit(fit, xAlign, yAlign, dx, dy, dw, dh, sx, sy, sw, sh) {
752+
if (fit === constants.COVER) {
753+
const { x, y, w, h } = _imageCover(xAlign, yAlign, dw, dh, sx, sy, sw, sh);
754+
sx = x;
755+
sy = y;
756+
sw = w;
757+
sh = h;
758+
}
759+
760+
if (fit === constants.CONTAIN) {
761+
const { x, y, w, h } = _imageContain(
762+
xAlign,
763+
yAlign,
764+
dx,
765+
dy,
766+
dw,
767+
dh,
768+
sw,
769+
sh
770+
);
771+
dx = x;
772+
dy = y;
773+
dw = w;
774+
dh = h;
775+
}
776+
return { sx, sy, sw, sh, dx, dy, dw, dh };
777+
}
778+
669779
/**
670780
* Validates clipping params. Per drawImage spec sWidth and sHight cannot be
671781
* negative or greater than image intrinsic width and height
@@ -691,7 +801,7 @@ function _sAssign(sVal, iVal) {
691801
* the position of the image. Two more parameters can optionally be added to
692802
* specify the width and height of the image.
693803
*
694-
* This function can also be used with all eight Number parameters. To
804+
* This function can also be used with eight Number parameters. To
695805
* differentiate between all these parameters, p5.js uses the language of
696806
* "destination rectangle" (which corresponds to "dx", "dy", etc.) and "source
697807
* image" (which corresponds to "sx", "sy", etc.) below. Specifying the
@@ -700,6 +810,14 @@ function _sAssign(sVal, iVal) {
700810
* to explain further:
701811
* <img src="assets/drawImage.png"></img>
702812
*
813+
* This function can also be used to draw images without distorting the orginal aspect ratio,
814+
* by adding 9th parameter, fit, which can either be COVER or CONTAIN.
815+
* CONTAIN, as the name suggests, contains the whole image within the specified destination box
816+
* without distorting the image ratio.
817+
* COVER covers the entire destination box.
818+
*
819+
*
820+
*
703821
* @method image
704822
* @param {p5.Image|p5.Element|p5.Texture} img the image to display
705823
* @param {Number} x the x-coordinate of the top-left corner of the image
@@ -766,6 +884,37 @@ function _sAssign(sVal, iVal) {
766884
* }
767885
* </code>
768886
* </div>
887+
* <div>
888+
* <code>
889+
* let img;
890+
* function preload() {
891+
* // dimensions of image are 780 x 440
892+
* // dimensions of canvas are 100 x 100
893+
* img = loadImage('assets/moonwalk.jpg');
894+
* }
895+
* function setup() {
896+
* // CONTAIN the whole image without distorting the image's aspect ratio
897+
* // CONTAIN the image within the specified destination box and display at LEFT,CENTER position
898+
* background(color('green'));
899+
* image(img, 0, 0, width, height, 0, 0, img.width, img.height, CONTAIN, LEFT);
900+
* }
901+
* </code>
902+
* </div>
903+
* <div>
904+
* <code>
905+
* let img;
906+
* function preload() {
907+
* img = loadImage('assets/laDefense50.png'); // dimensions of image are 50 x 50
908+
* }
909+
* function setup() {
910+
* // COVER the whole destination box without distorting the image's aspect ratio
911+
* // COVER the specified destination box which is of dimension 100 x 100
912+
* // Without specifying xAlign or yAlign, the image will be
913+
* // centered in the destination box in both axes
914+
* image(img, 0, 0, width, height, 0, 0, img.width, img.height, COVER);
915+
* }
916+
* </code>
917+
* </div>
769918
* @alt
770919
* image of the underside of a white umbrella and gridded ceiling above
771920
* image of the underside of a white umbrella and gridded ceiling above
@@ -788,6 +937,9 @@ function _sAssign(sVal, iVal) {
788937
* rectangle
789938
* @param {Number} [sHeight] the height of the subsection of the
790939
* source image to draw into the destination rectangle
940+
* @param {Constant} [fit] either CONTAIN or COVER
941+
* @param {Constant} [xAlign] either LEFT, RIGHT or CENTER default is CENTER
942+
* @param {Constant} [yAlign] either TOP, BOTTOM or CENTER default is CENTER
791943
*/
792944
p5.prototype.image = function(
793945
img,
@@ -798,25 +950,30 @@ p5.prototype.image = function(
798950
sx,
799951
sy,
800952
sWidth,
801-
sHeight
953+
sHeight,
954+
fit,
955+
xAlign,
956+
yAlign
802957
) {
803958
// set defaults per spec: https://goo.gl/3ykfOq
804959

805960
p5._validateParameters('image', arguments);
806961

807962
let defW = img.width;
808963
let defH = img.height;
964+
yAlign = yAlign || constants.CENTER;
965+
xAlign = xAlign || constants.CENTER;
809966

810967
if (img.elt && img.elt.videoWidth && !img.canvas) {
811968
// video no canvas
812969
defW = img.elt.videoWidth;
813970
defH = img.elt.videoHeight;
814971
}
815972

816-
const _dx = dx;
817-
const _dy = dy;
818-
const _dw = dWidth || defW;
819-
const _dh = dHeight || defH;
973+
let _dx = dx;
974+
let _dy = dy;
975+
let _dw = dWidth || defW;
976+
let _dh = dHeight || defH;
820977
let _sx = sx || 0;
821978
let _sy = sy || 0;
822979
let _sw = sWidth || defW;
@@ -847,10 +1004,33 @@ p5.prototype.image = function(
8471004
_sh *= pd;
8481005
_sw *= pd;
8491006

850-
const vals = canvas.modeAdjust(_dx, _dy, _dw, _dh, this._renderer._imageMode);
1007+
let vals = canvas.modeAdjust(_dx, _dy, _dw, _dh, this._renderer._imageMode);
1008+
vals = _imageFit(
1009+
fit,
1010+
xAlign,
1011+
yAlign,
1012+
vals.x,
1013+
vals.y,
1014+
vals.w,
1015+
vals.h,
1016+
_sx,
1017+
_sy,
1018+
_sw,
1019+
_sh
1020+
);
8511021

8521022
// tint the image if there is a tint
853-
this._renderer.image(img, _sx, _sy, _sw, _sh, vals.x, vals.y, vals.w, vals.h);
1023+
this._renderer.image(
1024+
img,
1025+
vals.sx,
1026+
vals.sy,
1027+
vals.sw,
1028+
vals.sh,
1029+
vals.dx,
1030+
vals.dy,
1031+
vals.dw,
1032+
vals.dh
1033+
);
8541034
};
8551035

8561036
/**

test/unit/image/loading.js

+55
Original file line numberDiff line numberDiff line change
@@ -443,3 +443,58 @@ suite('displaying images', function() {
443443
checkTint(tintColor);
444444
});
445445
});
446+
447+
suite('displaying images that use fit mode', function() {
448+
var myp5;
449+
450+
setup(function(done) {
451+
new p5(function(p) {
452+
p.setup = function() {
453+
myp5 = p;
454+
done();
455+
};
456+
});
457+
});
458+
459+
teardown(function() {
460+
myp5.remove();
461+
});
462+
463+
test('CONTAIN when source image is larger than destination', function() {
464+
let src = myp5.createImage(400, 1000);
465+
sinon.spy(myp5._renderer, 'image');
466+
myp5.image(src, 0, 0, 300, 400, 0, 0, 400, 1000, myp5.CONTAIN);
467+
assert(myp5._renderer.image.calledOnce);
468+
assert.equal(myp5._renderer.image.getCall(0).args[7], 400 / (1000 / 400)); // dw
469+
assert.equal(myp5._renderer.image.getCall(0).args[8], 1000 / (1000 / 400)); // dh
470+
});
471+
472+
test('CONTAIN when source image is smaller than destination', function() {
473+
let src = myp5.createImage(40, 90);
474+
sinon.spy(myp5._renderer, 'image');
475+
myp5.image(src, 0, 0, 300, 500, 0, 0, 400, 1000, myp5.CONTAIN);
476+
assert(myp5._renderer.image.calledOnce);
477+
assert.equal(myp5._renderer.image.getCall(0).args[7], 40 / (90 / 500)); // dw
478+
assert.equal(myp5._renderer.image.getCall(0).args[8], 90 / (90 / 500)); // dh
479+
});
480+
481+
test('COVER when source image is larger than destination', function() {
482+
let src = myp5.createImage(400, 1000);
483+
sinon.spy(myp5._renderer, 'image');
484+
myp5.image(src, 0, 0, 300, 400, 0, 0, 400, 1000, myp5.COVER);
485+
const r = Math.max(300 / 400, 400 / 1000);
486+
assert(myp5._renderer.image.calledOnce);
487+
assert.equal(myp5._renderer.image.getCall(0).args[3], 300 / r); // sw
488+
assert.equal(myp5._renderer.image.getCall(0).args[4], 400 / r); // sh
489+
});
490+
491+
test('COVER when source image is smaller than destination', function() {
492+
let src = myp5.createImage(20, 100);
493+
sinon.spy(myp5._renderer, 'image');
494+
myp5.image(src, 0, 0, 300, 400, 0, 0, 20, 100, myp5.COVER);
495+
const r = Math.max(300 / 20, 400 / 100);
496+
assert(myp5._renderer.image.calledOnce);
497+
assert.equal(myp5._renderer.image.getCall(0).args[3], 300 / r); // sw
498+
assert.equal(myp5._renderer.image.getCall(0).args[4], 400 / r); // sh
499+
});
500+
});

0 commit comments

Comments
 (0)