diff --git a/.all-contributorsrc b/.all-contributorsrc index a33da7aef5..cd706d5b4c 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -6370,9 +6370,9 @@ }, { "login": "martinleopold", - "name": "Martin Leopold", + "name": "Martin Leopold Groedl", "avatar_url": "https://avatars.githubusercontent.com/u/1692826?v=4", - "profile": "https://github.com/martinleopold", + "profile": "https://groedl.xyz", "contributions": [ "bug", "code" diff --git a/README.md b/README.md index d007be2f2d..afa580d3dd 100644 --- a/README.md +++ b/README.md @@ -1067,7 +1067,7 @@ We recognize all types of contributions. This project follows the [all-contribut computational mama
computational mama

πŸ’» Fabian MorΓ³n Zirfas
Fabian MorΓ³n Zirfas

πŸ“– πŸ’» Luke Plowden
Luke Plowden

πŸ’» - Martin Leopold
Martin Leopold

πŸ› πŸ’» + Martin Leopold Groedl
Martin Leopold Groedl

πŸ› πŸ’» diff --git a/src/core/helpers.js b/src/core/helpers.js index b6cfe371b2..5caa9e2b91 100644 --- a/src/core/helpers.js +++ b/src/core/helpers.js @@ -4,16 +4,47 @@ import * as constants from './constants'; +/* + This function normalizes the first four arguments given to rect, ellipse and arc + according to the mode. + It returns a 'bounding box' object containing the coordinates of the upper left corner (x, y), + and width and height (w, h). The returned width and height are always positive. +*/ function modeAdjust(a, b, c, d, mode) { + let bbox; + if (mode === constants.CORNER) { - return { x: a, y: b, w: c, h: d }; + + // CORNER mode already corresponds to a bounding box (top-left corner, width, height) + bbox = { x: a, y: b, w: c, h: d }; + } else if (mode === constants.CORNERS) { - return { x: a, y: b, w: c - a, h: d - b }; + + // CORNERS mode uses two opposite corners, in any configuration. + // Make sure to get the top left corner by using the minimum of the x and y coordniates. + bbox = { x: Math.min(a, c), y: Math.min(b, d), w: c - a, h: d - b }; + } else if (mode === constants.RADIUS) { - return { x: a - c, y: b - d, w: 2 * c, h: 2 * d }; + + // RADIUS mode uses the center point and half the width and height. + // c (half width) and d (half height) could be negative, so use the absolute value + // in calculating the top left corner (x, y). + bbox = { x: a - Math.abs(c), y: b - Math.abs(d), w: 2 * c, h: 2 * d }; + } else if (mode === constants.CENTER) { - return { x: a - c * 0.5, y: b - d * 0.5, w: c, h: d }; + + // CENTER mode uses the center point, width and height. + // c (width) and d (height) could be negative, so use the absolute value + // in calculating the top-left corner (x,y). + bbox = { x: a - Math.abs(c * 0.5), y: b - Math.abs(d * 0.5), w: c, h: d }; + } + + // p5 supports negative width and heights for rectangles, ellipses and arcs + bbox.w = Math.abs(bbox.w); + bbox.h = Math.abs(bbox.h); + + return bbox; } export default { modeAdjust }; diff --git a/src/core/shape/2d_primitives.js b/src/core/shape/2d_primitives.js index 8dd47e1299..92a5f527b2 100644 --- a/src/core/shape/2d_primitives.js +++ b/src/core/shape/2d_primitives.js @@ -327,10 +327,6 @@ p5.prototype.arc = function(x, y, w, h, start, stop, mode, detail) { start = this._toRadians(start); stop = this._toRadians(stop); - // p5 supports negative width and heights for ellipses - w = Math.abs(w); - h = Math.abs(h); - const vals = canvas.modeAdjust(x, y, w, h, this._renderer._ellipseMode); const angles = this._normalizeArcAngles(start, stop, vals.w, vals.h, true); @@ -547,16 +543,9 @@ p5.prototype._renderEllipse = function(x, y, w, h, detailX) { return this; } - // p5 supports negative width and heights for rects - if (w < 0) { - w = Math.abs(w); - } - + // Duplicate 3rd argument if only 3 given. if (typeof h === 'undefined') { - // Duplicate 3rd argument if only 3 given. h = w; - } else if (h < 0) { - h = Math.abs(h); } const vals = canvas.modeAdjust(x, y, w, h, this._renderer._ellipseMode); diff --git a/test/node/helpers.js b/test/node/helpers.js index 97e3474cc9..c2d4814378 100644 --- a/test/node/helpers.js +++ b/test/node/helpers.js @@ -15,7 +15,7 @@ suite('helpers/modeAdjust', function() { }); test('should set mode to corners', function() { result = helpers.modeAdjust(a, b, c, d, constants.CORNERS); - expect(result).to.eql({ x: 100, y: 200, w: -50, h: -50 }); + expect(result).to.eql({ x: 50, y: 150, w: 50, h: 50 }); }); test('should set mode to radius', function() { result = helpers.modeAdjust(a, b, c, d, constants.RADIUS); diff --git a/test/unit/spec.js b/test/unit/spec.js index c6a16946c0..664fbba11a 100644 --- a/test/unit/spec.js +++ b/test/unit/spec.js @@ -52,7 +52,8 @@ var spec = { // Add the visual tests that you want run as part of CI here. Feel free // to omit some for speed if they should only be run manually. 'webgl', - 'typography' + 'typography', + 'shape_modes' ] }; document.write( diff --git a/test/unit/visual/cases/shape_modes.js b/test/unit/visual/cases/shape_modes.js new file mode 100644 index 0000000000..d17565b320 --- /dev/null +++ b/test/unit/visual/cases/shape_modes.js @@ -0,0 +1,118 @@ +/* + Helper function that draws a shape using the specified shape mode + p5 ............... The p5 Instance + shape ............ The shape to draw. Either 'ellipse', 'arc', or 'rect' + mode ............. The ellipseMode (for ellipse and arc), or rectMode (for rect) + Either p5.CORNERS, p5.CORNER, p5.CENTER or p5.RADIUS + x1, x2, x2, y2 ... Coordinates specifying the shape in CORNERS mode, + i.e. (x1, y1) and (x2, y2) specify two opposite corners P1 and P2 +*/ +function shapeCorners(p5, shape, mode, x1, y1, x2, y2) { + // Adjust coordinates for testing modes other than CORNERS + if (mode === p5.CORNER) { + // Find top left corner + let x = p5.min(x1, x2); // x + let y = p5.min(y1, y2); // y + // Calculate width and height + // Don't use abs(), so we get negative values as well + let w = x2 - x1; // w + let h = y2 - y1; // h + x1 = x; y1 = y; x2 = w; y2 = h; + } else if (mode === p5.CENTER) { + // Find center + let x = (x2 + x1) / 2; // x + let y = (y2 + y1) / 2; // y + // Calculate width and height + // Don't use abs(), so we get negative values as well + let w = x2 - x1; + let h = y2 - y1; + x1 = x; y1 = y; x2 = w; y2 = h; + } else if (mode === p5.RADIUS) { + // Find Center + let x = (x2 + x1) / 2; // x + let y = (y2 + y1) / 2; // y + // Calculate radii + // Don't use abs(), so we get negative values as well + let r1 = (x2 - x1) / 2; // r1; + let r2 = (y2 - y1) / 2; // r2 + x1 = x; y1 = y; x2 = r1; y2 = r2; + } + + if (shape === 'ellipse') { + p5.ellipseMode(mode); + p5.ellipse(x1, y1, x2, y2); + } else if (shape === 'arc') { + // Draw four arcs with gaps inbetween + const GAP = p5.radians(20); + p5.ellipseMode(mode); + p5.arc(x1, y1, x2, y2, 0 + GAP, p5.HALF_PI - GAP); + p5.arc(x1, y1, x2, y2, p5.HALF_PI + GAP, p5.PI - GAP); + p5.arc(x1, y1, x2, y2, p5.PI + GAP, p5.PI + p5.HALF_PI - GAP); + p5.arc(x1, y1, x2, y2, p5.PI + p5.HALF_PI + GAP, p5.TWO_PI - GAP); + } else if (shape === 'rect') { + p5.rectMode(mode); + p5.rect(x1, y1, x2, y2); + } +} + + +/* + Comprehensive test for rendering ellipse(), arc(), and rect() + with the different ellipseMode() / rectMode() values: CORNERS, CORNER, CENTER, RADIUS. + Each of the 3 shapes is tested with each of the 4 possible modes, resulting in 12 test. + Each test renders the shape in 16 different coordinate configurations, + testing combinations of positive and negative coordinate values. +*/ +visualSuite('Shape Modes', function(...args) { + // Shapes to test + const SHAPES = [ 'ellipse', 'arc', 'rect' ]; + + // Modes to test (used with ellipseMode or rectMode, according to shape) + const MODES = [ 'CORNERS', 'CORNER', 'CENTER', 'RADIUS' ]; + + for (let shape of SHAPES) { + visualSuite(`Shape ${shape}`, function() { + + for (let mode of MODES) { + visualTest(`Mode ${mode}`, function(p5, screenshot) { + p5.createCanvas(60, 125); + p5.translate(p5.width/2, p5.height/2); + + // Make the following calls to shapeCorners shorter + // by omitting p5, shape and mode parameters + function _shapeCorners(x1, y1, x2, y2) { + shapeCorners(p5, shape, p5[mode], x1, y1, x2, y2); + } + + // Quadrant I (Bottom Right) + // P1 P2 + _shapeCorners( 5, 5, 25, 15); // P1 Top Left, P2 Bottom Right + _shapeCorners( 5, 20, 25, 30); // P1 Bottom Left, P2 Top Right + _shapeCorners(25, 45, 5, 35); // P1 Bottom Right, P2 Top Left + _shapeCorners(25, 50, 5, 60); // P1 Top Right, P2 Bottom Left + + // Quadrant II (Bottom Left) + _shapeCorners(-25, 5, -5, 15); + _shapeCorners(-25, 20, -5, 30); + _shapeCorners( -5, 45, -25, 35); + _shapeCorners( -5, 50, -25, 60); + + // Quadrant III (Top Left) + _shapeCorners(-25, -60, -5, -50); + _shapeCorners(-25, -35, -5, -45); + _shapeCorners( -5, -20, -25, -30); + _shapeCorners( -5, -15, -25, -5); + + // Quadrant IV (Top Right) + _shapeCorners( 5, -60, 25, -50); + _shapeCorners( 5, -35, 25, -45); + _shapeCorners(25, -20, 5, -30); + _shapeCorners(25, -15, 5, -5); + + screenshot(); + }); // End of: visualTest + } // End of: MODES loop + + }); // End of: Inner visualSuite + } // End of: SHAPES loop +}); // End of: Outer visualSuite diff --git a/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CENTER/000.png b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CENTER/000.png new file mode 100644 index 0000000000..d624061419 Binary files /dev/null and b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CENTER/000.png differ diff --git a/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CENTER/metadata.json b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CENTER/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CENTER/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CORNER/000.png b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CORNER/000.png new file mode 100644 index 0000000000..d624061419 Binary files /dev/null and b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CORNER/000.png differ diff --git a/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CORNER/metadata.json b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CORNER/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CORNER/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CORNERS/000.png b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CORNERS/000.png new file mode 100644 index 0000000000..d624061419 Binary files /dev/null and b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CORNERS/000.png differ diff --git a/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CORNERS/metadata.json b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CORNERS/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode CORNERS/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode RADIUS/000.png b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode RADIUS/000.png new file mode 100644 index 0000000000..d624061419 Binary files /dev/null and b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode RADIUS/000.png differ diff --git a/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode RADIUS/metadata.json b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode RADIUS/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape Modes/Shape arc/Mode RADIUS/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CENTER/000.png b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CENTER/000.png new file mode 100644 index 0000000000..7324222906 Binary files /dev/null and b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CENTER/000.png differ diff --git a/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CENTER/metadata.json b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CENTER/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CENTER/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CORNER/000.png b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CORNER/000.png new file mode 100644 index 0000000000..7324222906 Binary files /dev/null and b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CORNER/000.png differ diff --git a/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CORNER/metadata.json b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CORNER/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CORNER/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CORNERS/000.png b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CORNERS/000.png new file mode 100644 index 0000000000..7324222906 Binary files /dev/null and b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CORNERS/000.png differ diff --git a/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CORNERS/metadata.json b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CORNERS/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode CORNERS/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode RADIUS/000.png b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode RADIUS/000.png new file mode 100644 index 0000000000..7324222906 Binary files /dev/null and b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode RADIUS/000.png differ diff --git a/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode RADIUS/metadata.json b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode RADIUS/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape Modes/Shape ellipse/Mode RADIUS/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CENTER/000.png b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CENTER/000.png new file mode 100644 index 0000000000..5c096e42bb Binary files /dev/null and b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CENTER/000.png differ diff --git a/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CENTER/metadata.json b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CENTER/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CENTER/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CORNER/000.png b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CORNER/000.png new file mode 100644 index 0000000000..5c096e42bb Binary files /dev/null and b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CORNER/000.png differ diff --git a/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CORNER/metadata.json b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CORNER/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CORNER/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CORNERS/000.png b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CORNERS/000.png new file mode 100644 index 0000000000..5c096e42bb Binary files /dev/null and b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CORNERS/000.png differ diff --git a/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CORNERS/metadata.json b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CORNERS/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode CORNERS/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode RADIUS/000.png b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode RADIUS/000.png new file mode 100644 index 0000000000..5c096e42bb Binary files /dev/null and b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode RADIUS/000.png differ diff --git a/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode RADIUS/metadata.json b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode RADIUS/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/Shape Modes/Shape rect/Mode RADIUS/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/visualTest.js b/test/unit/visual/visualTest.js index ec66fc3e07..b9e902445d 100644 --- a/test/unit/visual/visualTest.js +++ b/test/unit/visual/visualTest.js @@ -59,6 +59,11 @@ window.checkMatch = function(actual, expected, p5) { Math.ceil(img.height * scale) ); } + // The below algorithm to calculate differences can produce false negatives in certain cases. + // Immediately return true for exact matches. + if (toBase64(expected) === toBase64(actual)) { + return { ok: true }; + } const diff = p5.createImage(actual.width, actual.height); diff.drawingContext.drawImage(actual.canvas, 0, 0); diff.drawingContext.globalCompositeOperation = 'difference'; diff --git a/test/visual/visualTestList.js b/test/visual/visualTestList.js index f52f971180..f21c1159cc 100644 --- a/test/visual/visualTestList.js +++ b/test/visual/visualTestList.js @@ -1,5 +1,5 @@ // List all visual test files here that should be manually run -const visualTestList = ['webgl', 'typography']; +const visualTestList = ['webgl', 'typography', 'shape_modes']; for (const file of visualTestList) { document.write(