diff --git a/.all-contributorsrc b/.all-contributorsrc
index 514d88aace..9ce7dbd9a1 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -3059,6 +3059,150 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "tapioca24",
+ "name": "tapioca24",
+ "avatar_url": "https://avatars.githubusercontent.com/u/12683107?v=4",
+ "profile": "https://github.com/tapioca24",
+ "contributions": [
+ "plugin"
+ ]
+ },
+ {
+ "login": "Qianqianye",
+ "name": "Qianqian Ye",
+ "avatar_url": "https://avatars.githubusercontent.com/u/18587130?v=4",
+ "profile": "http://qianqian-ye.com",
+ "contributions": [
+ "code",
+ "design",
+ "doc",
+ "eventOrganizing",
+ "review",
+ "translation"
+ ]
+ },
+ {
+ "login": "adarrssh",
+ "name": "Adarsh",
+ "avatar_url": "https://avatars.githubusercontent.com/u/85433137?v=4",
+ "profile": "https://github.com/adarrssh",
+ "contributions": [
+ "translation"
+ ]
+ },
+ {
+ "login": "kaabe1",
+ "name": "kaabe1",
+ "avatar_url": "https://avatars.githubusercontent.com/u/78185255?v=4",
+ "profile": "https://github.com/kaabe1",
+ "contributions": [
+ "design",
+ "eventOrganizing"
+ ]
+ },
+ {
+ "login": "Guirdo",
+ "name": "Seb MΓ©ndez",
+ "avatar_url": "https://avatars.githubusercontent.com/u/21044700?v=4",
+ "profile": "https://www.guirdo.xyz/",
+ "contributions": [
+ "translation"
+ ]
+ },
+ {
+ "login": "3ru",
+ "name": "Ryuya",
+ "avatar_url": "https://avatars.githubusercontent.com/u/69892552?v=4",
+ "profile": "https://github.com/3ru",
+ "contributions": [
+ "bug",
+ "review",
+ "code"
+ ]
+ },
+ {
+ "login": "LEMIBANDDEXARI",
+ "name": "LEMIBANDDEXARI",
+ "avatar_url": "https://avatars.githubusercontent.com/u/70129787?v=4",
+ "profile": "https://github.com/LEMIBANDDEXARI",
+ "contributions": [
+ "translation"
+ ]
+ },
+ {
+ "login": "probablyvivek",
+ "name": "Vivek Tiwari",
+ "avatar_url": "https://avatars.githubusercontent.com/u/25459353?v=4",
+ "profile": "https://linktr.ee/probablyvivek",
+ "contributions": [
+ "translation"
+ ]
+ },
+ {
+ "login": "KevinGrajeda",
+ "name": "Kevin Grajeda",
+ "avatar_url": "https://avatars.githubusercontent.com/u/60023139?v=4",
+ "profile": "https://github.com/KevinGrajeda",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "anniezhengg",
+ "name": "anniezhengg",
+ "avatar_url": "https://avatars.githubusercontent.com/u/78184655?v=4",
+ "profile": "https://github.com/anniezhengg",
+ "contributions": [
+ "code",
+ "design"
+ ]
+ },
+ {
+ "login": "SNP0301",
+ "name": "Seung-Gi Kim(David)",
+ "avatar_url": "https://avatars.githubusercontent.com/u/68281918?v=4",
+ "profile": "https://github.com/SNP0301",
+ "contributions": [
+ "translation"
+ ]
+ },
+ {
+ "login": "IkeB108",
+ "name": "Ike Bischof",
+ "avatar_url": "https://avatars.githubusercontent.com/u/56776763?v=4",
+ "profile": "https://ikebot108.weebly.com/",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "ongzzzzzz",
+ "name": "Ong Zhi Zheng",
+ "avatar_url": "https://avatars.githubusercontent.com/u/47311100?v=4",
+ "profile": "https://ongzz.ml",
+ "contributions": [
+ "plugin"
+ ]
+ },
+ {
+ "login": "bsubbaraman",
+ "name": "bsubbaraman",
+ "avatar_url": "https://avatars.githubusercontent.com/u/11969085?v=4",
+ "profile": "https://github.com/bsubbaraman",
+ "contributions": [
+ "plugin"
+ ]
+ },
+ {
+ "login": "jdeboi",
+ "name": "Jenna deBoisblanc",
+ "avatar_url": "https://avatars.githubusercontent.com/u/1548679?v=4",
+ "profile": "http://jdeboi.com",
+ "contributions": [
+ "plugin"
+ ]
}
],
"repoType": "github",
diff --git a/.github/ISSUE_TEMPLATE/discussion.yml b/.github/ISSUE_TEMPLATE/discussion.yml
index 5946f12e9d..115fb834cb 100644
--- a/.github/ISSUE_TEMPLATE/discussion.yml
+++ b/.github/ISSUE_TEMPLATE/discussion.yml
@@ -1,6 +1,6 @@
name: π Discussion
description: This template is for starting a discussion.
-labels: [discussion]
+labels: [Discussion]
body:
- type: textarea
attributes:
diff --git a/.github/ISSUE_TEMPLATE/existing-feature-enhancement.yml b/.github/ISSUE_TEMPLATE/existing-feature-enhancement.yml
index 60d4e05ada..9d4808d754 100644
--- a/.github/ISSUE_TEMPLATE/existing-feature-enhancement.yml
+++ b/.github/ISSUE_TEMPLATE/existing-feature-enhancement.yml
@@ -1,38 +1,38 @@
name: π‘ Existing Feature Enhancement
description: This template is for suggesting an improvement for an existing feature.
-labels: [enhancement]
+labels: [Enhancement]
body:
-- type: textarea
- attributes:
- label: Increasing Access
- description: How would this new feature help [increase access](https://github.com/processing/p5.js/blob/main/contributor_docs/access.md) to p5.js? (If you're not sure, you can type "Unsure" here and let others from the community offer their thoughts.)
- validations:
- required: true
-- type: checkboxes
- id: sub-area
- attributes:
- label: Most appropriate sub-area of p5.js?
- description: You may select more than one.
- options:
- - label: Accessibility (Web Accessibility)
- - label: Build tools and processes
- - label: Color
- - label: Core/Environment/Rendering
- - label: Data
- - label: DOM
- - label: Events
- - label: Friendly error system
- - label: Image
- - label: IO (Input/Output)
- - label: Localization
- - label: Math
- - label: Unit Testing
- - label: Typography
- - label: Utilities
- - label: WebGL
- - label: Other (specify if possible)
-- type: textarea
- attributes:
- label: Feature enhancement details
- validations:
- required: true
+ - type: textarea
+ attributes:
+ label: Increasing Access
+ description: How would this new feature help [increase access](https://github.com/processing/p5.js/blob/main/contributor_docs/access.md) to p5.js? (If you're not sure, you can type "Unsure" here and let others from the community offer their thoughts.)
+ validations:
+ required: true
+ - type: checkboxes
+ id: sub-area
+ attributes:
+ label: Most appropriate sub-area of p5.js?
+ description: You may select more than one.
+ options:
+ - label: Accessibility
+ - label: Color
+ - label: Core/Environment/Rendering
+ - label: Data
+ - label: DOM
+ - label: Events
+ - label: Image
+ - label: IO
+ - label: Math
+ - label: Typography
+ - label: Utilities
+ - label: WebGL
+ - label: Build Process
+ - label: Unit Testing
+ - label: Internalization
+ - label: Friendly Errors
+ - label: Other (specify if possible)
+ - type: textarea
+ attributes:
+ label: Feature enhancement details
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
index 88f7531899..dd300bf37c 100644
--- a/.github/ISSUE_TEMPLATE/feature-request.yml
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -1,38 +1,38 @@
name: π± New Feature Request
description: This template is for requesting a new feature be added.
-labels: [feature request]
+labels: [Feature Request]
body:
-- type: textarea
- attributes:
- label: Increasing Access
- description: How would this new feature help [increase access](https://github.com/processing/p5.js/blob/main/contributor_docs/access.md) to p5.js? (If you're not sure, you can type "Unsure" here and let others from the community offer their thoughts.)
- validations:
- required: true
-- type: checkboxes
- id: sub-area
- attributes:
- label: Most appropriate sub-area of p5.js?
- description: You may select more than one.
- options:
- - label: Accessibility (Web Accessibility)
- - label: Build tools and processes
- - label: Color
- - label: Core/Environment/Rendering
- - label: Data
- - label: DOM
- - label: Events
- - label: Friendly error system
- - label: Image
- - label: IO (Input/Output)
- - label: Localization
- - label: Math
- - label: Unit Testing
- - label: Typography
- - label: Utilities
- - label: WebGL
- - label: Other (specify if possible)
-- type: textarea
- attributes:
- label: Feature request details
- validations:
- required: true
\ No newline at end of file
+ - type: textarea
+ attributes:
+ label: Increasing Access
+ description: How would this new feature help [increase access](https://github.com/processing/p5.js/blob/main/contributor_docs/access.md) to p5.js? (If you're not sure, you can type "Unsure" here and let others from the community offer their thoughts.)
+ validations:
+ required: true
+ - type: checkboxes
+ id: sub-area
+ attributes:
+ label: Most appropriate sub-area of p5.js?
+ description: You may select more than one.
+ options:
+ - label: Accessibility
+ - label: Color
+ - label: Core/Environment/Rendering
+ - label: Data
+ - label: DOM
+ - label: Events
+ - label: Image
+ - label: IO
+ - label: Math
+ - label: Typography
+ - label: Utilities
+ - label: WebGL
+ - label: Build Process
+ - label: Unit Testing
+ - label: Internalization
+ - label: Friendly Errors
+ - label: Other (specify if possible)
+ - type: textarea
+ attributes:
+ label: Feature request details
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/found-a-bug.yml b/.github/ISSUE_TEMPLATE/found-a-bug.yml
index 1286249b6b..12cff53559 100644
--- a/.github/ISSUE_TEMPLATE/found-a-bug.yml
+++ b/.github/ISSUE_TEMPLATE/found-a-bug.yml
@@ -1,6 +1,6 @@
name: π Found a Bug
description: This template is for reporting bugs (broken or incorrect behaviour). If you have questions about your own code, please visit our forum discourse.processing.org instead.
-labels: [bug]
+labels: [Bug]
body:
- type: checkboxes
id: sub-area
@@ -8,23 +8,23 @@ body:
label: Most appropriate sub-area of p5.js?
description: You may select more than one.
options:
- - label: Accessibility (Web Accessibility)
- - label: Build tools and processes
- - label: Color
- - label: Core/Environment/Rendering
- - label: Data
- - label: DOM
- - label: Events
- - label: Friendly error system
- - label: Image
- - label: IO (Input/Output)
- - label: Localization
- - label: Math
- - label: Unit Testing
- - label: Typography
- - label: Utilities
- - label: WebGL
- - label: Other (specify if possible)
+ - label: Accessibility
+ - label: Color
+ - label: Core/Environment/Rendering
+ - label: Data
+ - label: DOM
+ - label: Events
+ - label: Image
+ - label: IO
+ - label: Math
+ - label: Typography
+ - label: Utilities
+ - label: WebGL
+ - label: Build Process
+ - label: Unit Testing
+ - label: Internalization
+ - label: Friendly Errors
+ - label: Other (specify if possible)
- type: input
attributes:
label: p5.js version
diff --git a/.github/config.yml b/.github/config.yml
index 706ea19698..c4629c14ed 100644
--- a/.github/config.yml
+++ b/.github/config.yml
@@ -4,8 +4,7 @@
# Comment to be posted to on first time issues
newIssueWelcomeComment: >
- Welcome! π Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, be sure to follow the issue template if you haven't already.
-
+ 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!
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
# Comment to be posted to on PRs from first time contributors in your repository
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 70b33005f7..2cd071249f 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -1,12 +1,32 @@
-# Number of labels to fetch (optional). Defaults to 20
-numLabels: 20
-# These labels will not be used even if the issue contains them
-excludeLabels:
- - bug
- - can't reproduce
- - known issue
- - more info needed
- - will not fix
- - severity:critical
- - severity:major
- - severity:minor
+"Area:Accessibility":
+ - '\[[xX]\]\s*Accessibility'
+"Area:Color":
+ - '\[[xX]\]\s*Color'
+"Area:Core":
+ - '\[[xX]\]\s*Core'
+"Area:Data":
+ - '\[[xX]\]\s*Data'
+"Area:DOM":
+ - '\[[xX]\]\s*DOM'
+"Area:Events":
+ - '\[[xX]\]\s*Events'
+"Area:Image":
+ - '\[[xX]\]\s*Image'
+"Area:IO":
+ - '\[[xX]\]\s*IO'
+"Area:Math":
+ - '\[[xX]\]\s*Math'
+"Area:Typography":
+ - '\[[xX]\]\s*Typography'
+"Area:Utilities":
+ - '\[[xX]\]\s*Utilities'
+"Area:WebGL":
+ - '\[[xX]\]\s*WebGL'
+"Build Process":
+ - '\[[xX]\]\s*Build Process'
+"Unit Testing":
+ - '\[[xX]\]\s*Unit Testing'
+"Internalization":
+ - '\[[xX]\]\s*Internalization'
+"Friendly Errors":
+ - '\[[xX]\]\s*Friendly Errors'
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
new file mode 100644
index 0000000000..48d99cfd86
--- /dev/null
+++ b/.github/workflows/labeler.yml
@@ -0,0 +1,15 @@
+name: "Issue Labeler"
+on:
+ issues:
+ types: [opened, edited]
+permissions:
+ issues: write
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: github/issue-labeler@v2.0
+ with:
+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
+ configuration-path: .github/labeler.yml
+ enable-versioned-regex: 0
diff --git a/Gruntfile.js b/Gruntfile.js
index 2953b17d17..5ea366e622 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -150,7 +150,7 @@ module.exports = grunt => {
source: {
options: {
parserOptions: {
- ecmaVersion: 5
+ ecmaVersion: 8
}
},
src: ['src/**/*.js']
@@ -163,7 +163,7 @@ module.exports = grunt => {
'eslint-samples': {
options: {
parserOptions: {
- ecmaVersion: 6
+ ecmaVersion: 8
},
format: 'unix'
},
@@ -259,6 +259,16 @@ module.exports = grunt => {
}
}
},
+ babel: {
+ options: {
+ presets: ['@babel/preset-env']
+ },
+ dist: {
+ files: {
+ 'lib/p5.pre-min.js': 'lib/p5.js'
+ }
+ }
+ },
// This minifies the javascript into a single file and adds a banner to the
// front of the file.
@@ -274,8 +284,8 @@ module.exports = grunt => {
},
dist: {
files: {
- 'lib/p5.min.js': 'lib/p5.pre-min.js',
- 'lib/modules/p5Custom.min.js': 'lib/modules/p5Custom.pre-min.js'
+ 'lib/p5.min.js': ['lib/p5.pre-min.js'],
+ 'lib/modules/p5Custom.min.js': ['lib/modules/p5Custom.pre-min.js']
}
}
},
@@ -365,7 +375,19 @@ module.exports = grunt => {
options: {
archive: 'release/p5.zip'
},
- files: [{ cwd: 'lib/', src: ['**/*'], expand: true }]
+ files: [
+ {
+ cwd: 'lib/',
+ src: [
+ 'p5.js',
+ 'p5.min.js',
+ 'addons/*',
+ 'empty-example/*',
+ 'README.txt'
+ ],
+ expand: true
+ }
+ ]
}
},
@@ -511,10 +533,14 @@ module.exports = grunt => {
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-simple-nyc');
+ //this library converts the ES6 JS to ES5 so it can be properly minified
+ grunt.loadNpmTasks('grunt-babel');
+
// Create the multitasks.
grunt.registerTask('build', [
'browserify',
'browserify:min',
+ 'babel',
'uglify',
'browserify:test'
]);
diff --git a/README.md b/README.md
index a2b4ce7670..6cad85a68d 100644
--- a/README.md
+++ b/README.md
@@ -54,8 +54,8 @@ Stewards are contributors that are particularly involved, familiar, or responsiv
Anyone interested can volunteer to be a steward! There are no specific requirements for expertise, just an interest in actively learning and participating. If youβre familiar with one or more parts of this project, open an issue to volunteer as a steward!
-* [@outofambit](https://github.com/outofambit) - project co-lead
-* [@qianqianye](https://github.com/qianqianye) - project co-lead
+* [@qianqianye](https://github.com/qianqianye) - p5.js Project Lead
+* [@outofambit](https://github.com/outofambit) - p5.js Mentor
* [@lmccart](https://github.com/lmccart)
* [@limzykenneth](https://github.com/limzykenneth)
* [@stalgiag](https://github.com/stalgiag)
@@ -63,24 +63,25 @@ Anyone interested can volunteer to be a steward! There are no specific requireme
* [@dhowe](https://github.com/dhowe)
* [@rahulm2310](https://github.com/rahulm2310)
-| Area | Steward(s) |
-| :-------------------------------- | :------------------------------------------- |
-| Accessibility (Web Accessibility) | outofambit |
-| Color | outofambit |
-| Core/Environment/Rendering | outofambit
limzykenneth |
-| Data | |
-| DOM | outofambit |
-| Events | outofambit
limzykenneth |
-| Image | stalgiag |
-| IO | limzykenneth |
-| Math | limzykenneth |
-| Typography | dhowe |
-| Utilities | |
-| WebGL | stalgiag |
-| Build Process/Unit Testing | outofambit |
-| Localization Tools | outofambit |
-| Friendly Errors | outofambit |
-| [Website](https://github.com/processing/p5.js-website) | limzykenneth
rahulm2310 |
+| Area | Steward(s) |
+| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
+| Overall | [@qianqianye](https://github.com/qianqianye) |
+| [Accessibility](https://github.com/processing/p5.js/tree/main/src/accessibility) | [@kungfuchicken](https://github.com/kungfuchicken), [@cosmicbhejafry](https://github.com/cosmicbhejafry) |
+| [Color](https://github.com/processing/p5.js/tree/main/src/color) | [@KleoP](https://github.com/KleoP), [@murilopolese](https://github.com/murilopolese), [@aahdee](https://github.com/aahdee), [@paulaxisabel](https://github.com/paulaxisabel) |
+| [Core](https://github.com/processing/p5.js/tree/main/src/core)/Environment/Rendering | [@limzykenneth](https://github.com/limzykenneth), [@davepagurek](https://github.com/davepagurek), [@jeffawang](https://github.com/jeffawang) |
+| [Data](https://github.com/processing/p5.js/tree/main/src/data) | [@kungfuchicken](https://github.com/kungfuchicken), [@cosmicbhejafry](https://github.com/cosmicbhejafry) |
+| [DOM](https://github.com/processing/p5.js/tree/main/src/dom) | [@outofambit](https://github.com/outofambit), [@SarveshLimaye](https://github.com/SarveshLimaye), [@SamirDhoke](https://github.com/SamirDhoke) |
+| [Events](https://github.com/processing/p5.js/tree/main/src/events) | [@limzykenneth](https://github.com/limzykenneth) |
+| [Image](https://github.com/processing/p5.js/tree/main/src/image) | [@stalgiag](https://github.com/stalgiag), [@cgusb](https://github.com/cgusb), [@photon-niko](https://github.com/photon-niko), [@KleoP](https://github.com/KleoP)
+| [IO](https://github.com/processing/p5.js/tree/main/src/io) | [@limzykenneth](https://github.com/limzykenneth) |
+| [Math](https://github.com/processing/p5.js/tree/main/src/math) | [@limzykenneth](https://github.com/limzykenneth), [@jeffawang](https://github.com/jeffawang), [@AdilRabbani](https://github.com/AdilRabbani) |
+| [Typography](https://github.com/processing/p5.js/tree/main/src/typography) | [@dhowe](https://github.com/dhowe), [@SarveshLimaye](https://github.com/SarveshLimaye), [@paulaxisabel](https://github.com/paulaxisabel) |
+| [Utilities](https://github.com/processing/p5.js/tree/main/src/utilities) | [@kungfuchicken](https://github.com/kungfuchicken), [@cosmicbhejafry](https://github.com/cosmicbhejafry) |
+| [WebGL](https://github.com/processing/p5.js/tree/main/src/webgl) | [@stalgiag](https://github.com/stalgiag); GSoC 2022: [@aceslowman](https://github.com/aceslowman)(Contributor), [@kjhollen](https://github.com/kjhollen)(Mentor); [@ShenpaiSharma](https://github.com/ShenpaiSharma)(Contributor), [@calebfoss](https://github.com/calebfoss)(Mentor); [@davepagurek](https://github.com/davepagurek); [@jeffawang](https://github.com/jeffawang); [@AdilRabbani](https://github.com/AdilRabbani) |
+| Build Process/Unit Testing | [@outofambit](https://github.com/outofambit), [@kungfuchicken](https://github.com/kungfuchicken) |
+| Internalization | [@outofambit](https://github.com/outofambit), [@almchung](https://github.com/almchung) |
+| Friendly Errors | [@outofambit](https://github.com/outofambit), [@almchung](https://github.com/almchung) |
+| [Contributor Docs](https://github.com/processing/p5.js/tree/main/contributor_docs) | [SoD 2022](https://github.com/processing/p5.js/wiki/Season-of-Docs-2022-Organization-Application---p5.js): [@limzykenneth](https://github.com/limzykenneth) |
## Contributors
@@ -562,6 +563,25 @@ We recognize all types of contributions. This project follows the [all-contribut
LIGHTEST
- only the lightest colour succeeds: C =
* max(A*factor, B).DIFFERENCE
- subtract colors from underlying image.DIFFERENCE
- subtract colors from underlying image.
+ * (2D)EXCLUSION
- similar to DIFFERENCE
, but less
* extreme.MULTIPLY
- multiply the colors, result will always be
diff --git a/src/core/shape/2d_primitives.js b/src/core/shape/2d_primitives.js
index 7484433b16..841156e53c 100644
--- a/src/core/shape/2d_primitives.js
+++ b/src/core/shape/2d_primitives.js
@@ -370,7 +370,7 @@ p5.prototype._renderEllipse = function(x, y, w, h, detailX) {
* stroke(255);
* line(85, 75, 30, 75);
* describe(
- * '3 lines of various stroke sizes. Form top, bottom and right sides of a square'
+ * '3 lines of various stroke colors. Form top, bottom and right sides of a square'
* );
*
*
diff --git a/src/core/shape/attributes.js b/src/core/shape/attributes.js
index bf23b9c58e..84d6158bd3 100644
--- a/src/core/shape/attributes.js
+++ b/src/core/shape/attributes.js
@@ -103,11 +103,12 @@ p5.prototype.ellipseMode = function(m) {
* 2 pixelated 36Γ36 white ellipses to left & right of center, black background
*/
p5.prototype.noSmooth = function() {
- this.setAttributes('antialias', false);
if (!this._renderer.isP3D) {
if ('imageSmoothingEnabled' in this.drawingContext) {
this.drawingContext.imageSmoothingEnabled = false;
}
+ } else {
+ this.setAttributes('antialias', false);
}
return this;
};
diff --git a/src/core/transform.js b/src/core/transform.js
index f77523ab64..b14d14e7a9 100644
--- a/src/core/transform.js
+++ b/src/core/transform.js
@@ -30,36 +30,6 @@ import p5 from './main';
* @method applyMatrix
* @param {Array} arr an array of numbers - should be 6 or 16 length (2*3 or 4*4 matrix values)
* @chainable
- */
-/**
- * @method applyMatrix
- * @param {Number} a numbers which define the 2Γ3 or 4x4 matrix to be multiplied
- * @param {Number} b numbers which define the 2Γ3 or 4x4 matrix to be multiplied
- * @param {Number} c numbers which define the 2Γ3 or 4x4 matrix to be multiplied
- * @param {Number} d numbers which define the 2Γ3 or 4x4 matrix to be multiplied
- * @param {Number} e numbers which define the 2Γ3 or 4x4 matrix to be multiplied
- * @param {Number} f numbers which define the 2Γ3 or 4x4 matrix to be multiplied
- * @chainable
- */
-/**
- * @method applyMatrix
- * @param {Number} a
- * @param {Number} b
- * @param {Number} c
- * @param {Number} d
- * @param {Number} e
- * @param {Number} f
- * @param {Number} g numbers which define the 4x4 matrix to be multiplied
- * @param {Number} h numbers which define the 4x4 matrix to be multiplied
- * @param {Number} i numbers which define the 4x4 matrix to be multiplied
- * @param {Number} j numbers which define the 4x4 matrix to be multiplied
- * @param {Number} k numbers which define the 4x4 matrix to be multiplied
- * @param {Number} l numbers which define the 4x4 matrix to be multiplied
- * @param {Number} m numbers which define the 4x4 matrix to be multiplied
- * @param {Number} n numbers which define the 4x4 matrix to be multiplied
- * @param {Number} o numbers which define the 4x4 matrix to be multiplied
- * @param {Number} p numbers which define the 4x4 matrix to be multiplied
- * @chainable
* @example
*
@@ -183,6 +153,36 @@ import p5 from './main';
* A rectangle shearing
* A rectangle in the upper left corner
*/
+/**
+ * @method applyMatrix
+ * @param {Number} a numbers which define the 2Γ3 or 4x4 matrix to be multiplied
+ * @param {Number} b numbers which define the 2Γ3 or 4x4 matrix to be multiplied
+ * @param {Number} c numbers which define the 2Γ3 or 4x4 matrix to be multiplied
+ * @param {Number} d numbers which define the 2Γ3 or 4x4 matrix to be multiplied
+ * @param {Number} e numbers which define the 2Γ3 or 4x4 matrix to be multiplied
+ * @param {Number} f numbers which define the 2Γ3 or 4x4 matrix to be multiplied
+ * @chainable
+ */
+/**
+ * @method applyMatrix
+ * @param {Number} a
+ * @param {Number} b
+ * @param {Number} c
+ * @param {Number} d
+ * @param {Number} e
+ * @param {Number} f
+ * @param {Number} g numbers which define the 4x4 matrix to be multiplied
+ * @param {Number} h numbers which define the 4x4 matrix to be multiplied
+ * @param {Number} i numbers which define the 4x4 matrix to be multiplied
+ * @param {Number} j numbers which define the 4x4 matrix to be multiplied
+ * @param {Number} k numbers which define the 4x4 matrix to be multiplied
+ * @param {Number} l numbers which define the 4x4 matrix to be multiplied
+ * @param {Number} m numbers which define the 4x4 matrix to be multiplied
+ * @param {Number} n numbers which define the 4x4 matrix to be multiplied
+ * @param {Number} o numbers which define the 4x4 matrix to be multiplied
+ * @param {Number} p numbers which define the 4x4 matrix to be multiplied
+ * @chainable
+ */
p5.prototype.applyMatrix = function() {
let isTypedArray = arguments[0] instanceof Object.getPrototypeOf(Uint8Array);
if (Array.isArray(arguments[0]) || isTypedArray) {
diff --git a/src/dom/dom.js b/src/dom/dom.js
index a07e277370..af53e34793 100644
--- a/src/dom/dom.js
+++ b/src/dom/dom.js
@@ -622,8 +622,8 @@ p5.prototype.createCheckbox = function() {
/**
* Creates a dropdown menu `<select></select>` element in the DOM.
- * It also helps to assign select-box methods to p5.Element when selecting existing select box.
- * - `.option(name, [value])` can be used to set options for the select after it is created.
+ * It also assigns select-related methods to p5.Element when selecting an existing select box. Options in the menu are unique by `name` (the display text).
+ * - `.option(name, [value])` can be used to add an option with `name` (the display text) and `value` to the select element. If an option with `name` already exists within the select element, this method will change its value to `value`.
* - `.value()` will return the currently selected option.
* - `.selected()` will return the current dropdown element which is an instance of p5.Element.
* - `.selected(value)` can be used to make given option selected by default when the page first loads.
@@ -844,19 +844,32 @@ p5.prototype.createRadio = function() {
// If already given with a containerEl, will search for all input[radio]
// it, create a p5.Element out of it, add options to it and return the p5.Element.
+ let self;
let radioElement;
let name;
const arg0 = arguments[0];
- // If existing radio Element is provided as argument 0
- if (arg0 instanceof HTMLDivElement || arg0 instanceof HTMLSpanElement) {
+ if (
+ arg0 instanceof p5.Element &&
+ (arg0.elt instanceof HTMLDivElement || arg0.elt instanceof HTMLSpanElement)
+ ) {
+ // If given argument is p5.Element of div/span type
+ self = arg0;
+ this.elt = arg0.elt;
+ } else if (
+ // If existing radio Element is provided as argument 0
+ arg0 instanceof HTMLDivElement ||
+ arg0 instanceof HTMLSpanElement
+ ) {
+ self = addElement(arg0, this);
+ this.elt = arg0;
radioElement = arg0;
if (typeof arguments[1] === 'string') name = arguments[1];
} else {
if (typeof arg0 === 'string') name = arg0;
radioElement = document.createElement('div');
+ self = addElement(radioElement, this);
+ this.elt = radioElement;
}
- this.elt = radioElement;
- let self = addElement(radioElement, this);
self._name = name || 'radioOption';
// setup member functions
@@ -1305,17 +1318,8 @@ p5.prototype.createAudio = function(src, callback) {
/** CAMERA STUFF **/
-/**
- * @property {String} VIDEO
- * @final
- * @category Constants
- */
p5.prototype.VIDEO = 'video';
-/**
- * @property {String} AUDIO
- * @final
- * @category Constants
- */
+
p5.prototype.AUDIO = 'audio';
// from: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
@@ -1980,8 +1984,8 @@ p5.Element.prototype.style = function(prop, val) {
) {
let styles = window.getComputedStyle(self.elt);
let styleVal = styles.getPropertyValue(prop);
- let numVal = styleVal.replace(/\D+/g, '');
- this[prop] = parseInt(numVal, 10);
+ let numVal = styleVal.replace(/[^\d.]/g, '');
+ this[prop] = Math.round(parseFloat(numVal, 10));
}
}
return this;
diff --git a/src/image/image.js b/src/image/image.js
index 8260776c13..d2f0d82c99 100644
--- a/src/image/image.js
+++ b/src/image/image.js
@@ -184,7 +184,10 @@ p5.prototype.saveCanvas = function() {
}, mimeType);
};
-p5.prototype.saveGif = function(pImg, filename) {
+// this is the old saveGif, left here for compatibility purposes
+// the only place I found it being used was on image/p5.Image.js, on the
+// save function. that has been changed to use this function.
+p5.prototype.encodeAndDownloadGif = function(pImg, filename) {
const props = pImg.gifProperties;
//convert loopLimit back into Netscape Block formatting
@@ -420,15 +423,19 @@ p5.prototype.saveGif = function(pImg, filename) {
* as an argument to the callback function as an array of objects, with the
* size of array equal to the total number of frames.
*
- * Note that saveFrames() will only save the first 15 frames of an animation.
+ * The arguments `duration` and `framerate` are constrained to be less or equal to 15 and 22, respectively, which means you
+ * can only download a maximum of 15 seconds worth of frames at 22 frames per second, adding up to 330 frames.
+ * This is done in order to avoid memory problems since a large enough canvas can fill up the memory in your computer
+ * very easily and crash your program or even your browser.
+ *
* To export longer animations, you might look into a library like
* ccapture.js.
*
* @method saveFrames
* @param {String} filename
* @param {String} extension 'jpg' or 'png'
- * @param {Number} duration Duration in seconds to save the frames for.
- * @param {Number} framerate Framerate to save the frames in.
+ * @param {Number} duration Duration in seconds to save the frames for. This parameter will be constrained to be less or equal to 15.
+ * @param {Number} framerate Framerate to save the frames in. This parameter will be constrained to be less or equal to 22.
* @param {function(Array)} [callback] A callback function that will be executed
to handle the image data. This function
should accept an array as argument. The
diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js
index 3ff2b3f053..eb125d8309 100644
--- a/src/image/loading_displaying.js
+++ b/src/image/loading_displaying.js
@@ -6,10 +6,10 @@
*/
import p5 from '../core/main';
-import Filters from './filters';
import canvas from '../core/helpers';
import * as constants from '../core/constants';
import omggif from 'omggif';
+import { GIFEncoder, quantize, applyPalette } from 'gifenc';
import '../core/friendly_errors/validate_params';
import '../core/friendly_errors/file_errors';
@@ -159,6 +159,391 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) {
return pImg;
};
+/**
+ * Generates a gif of your current animation and downloads it to your computer!
+ *
+ * The duration argument specifies how many seconds you want to record from your animation.
+ * This value is then converted to the necessary number of frames to generate it, depending
+ * on the value of units. More on that on the next paragraph.
+ *
+ * An optional object that can contain two more arguments: delay (number) and units (string).
+ *
+ * `delay`, specifying how much time we should wait before recording
+ *
+ * `units`, a string that can be either 'seconds' or 'frames'. By default it's 'seconds'.
+ *
+ * `units` specifies how the duration and delay arguments will behave.
+ * If 'seconds', these arguments will correspond to seconds, meaning that 3 seconds worth of animation
+ * will be created. If 'frames', the arguments now correspond to the number of frames you want your
+ * animation to be, if you are very sure of this number.
+ *
+ * It is not recommended to write this function inside setup, since it won't work properly.
+ * The recommended use can be seen in the example, where we use it inside an event function,
+ * like keyPressed or mousePressed.
+ *
+ * @method saveGif
+ * @param {String} filename File name of your gif
+ * @param {Number} duration Duration in seconds that you wish to capture from your sketch
+ * @param {Object} options An optional object that can contain two more arguments: delay, specifying
+ * how much time we should wait before recording, and units, a string that can be either 'seconds' or
+ * 'frames'. By default it's 'seconds'.
+ *
+ * @example
+ *
+ *
+ * function setup() {
+ * createCanvas(100, 100);
+ * }
+ *
+ * function draw() {
+ * colorMode(RGB);
+ * background(30);
+ *
+ * // create a bunch of circles that move in... circles!
+ * for (let i = 0; i < 10; i++) {
+ * let opacity = map(i, 0, 10, 0, 255);
+ * noStroke();
+ * fill(230, 250, 90, opacity);
+ * circle(
+ * 30 * sin(frameCount / (30 - i)) + width / 2,
+ * 30 * cos(frameCount / (30 - i)) + height / 2,
+ * 10
+ * );
+ * }
+ * }
+ *
+ * // you can put it in the mousePressed function,
+ * // or keyPressed for example
+ * function keyPressed() {
+ * // this will download the first 5 seconds of the animation!
+ * if (key === 's') {
+ * saveGif('mySketch', 5);
+ * }
+ * }
+ *
+ *
+ *
+ * @alt
+ * animation of a group of yellow circles moving in circles over a dark background
+ */
+p5.prototype.saveGif = async function(
+ fileName,
+ duration,
+ options = { delay: 0, units: 'seconds' }
+) {
+ // validate parameters
+ if (typeof fileName !== 'string') {
+ throw TypeError('fileName parameter must be a string');
+ }
+ if (typeof duration !== 'number') {
+ throw TypeError('Duration parameter must be a number');
+ }
+ // if arguments in the options object are not correct, cancel operation
+ if (typeof options.delay !== 'number') {
+ throw TypeError('Delay parameter must be a number');
+ }
+ // if units is not seconds nor frames, throw error
+ if (options.units !== 'seconds' && options.units !== 'frames') {
+ throw TypeError('Units parameter must be either "frames" or "seconds"');
+ }
+
+ // extract variables for more comfortable use
+ let units = options.units;
+ let delay = options.delay;
+
+ // console.log(options);
+
+ // get the project's framerate
+ let _frameRate = this._targetFrameRate;
+ // if it is undefined or some non useful value, assume it's 60
+ if (_frameRate === Infinity || _frameRate === undefined || _frameRate === 0) {
+ _frameRate = 60;
+ }
+
+ // calculate frame delay based on frameRate
+
+ // this delay has nothing to do with the
+ // delay in options, but rather is the delay
+ // we have to specify to the gif encoder between frames.
+ let gifFrameDelay = 1 / _frameRate * 1000;
+
+ // constrain it to be always greater than 20,
+ // otherwise it won't work in some browsers and systems
+ // reference: https://stackoverflow.com/questions/64473278/gif-frame-duration-seems-slower-than-expected
+ gifFrameDelay = gifFrameDelay < 20 ? 20 : gifFrameDelay;
+
+ // check the mode we are in and how many frames
+ // that duration translates to
+ const nFrames = units === 'seconds' ? duration * _frameRate : duration;
+ const nFramesDelay = units === 'seconds' ? delay * _frameRate : delay;
+ const totalNumberOfFrames = nFrames + nFramesDelay;
+
+ // initialize variables for the frames processing
+ let frameIterator = nFramesDelay;
+ this.frameCount = frameIterator;
+
+ const lastPixelDensity = this._pixelDensity;
+ this.pixelDensity(1);
+
+ // We first take every frame that we are going to use for the animation
+ let frames = [];
+
+ let progressBarIdName = 'p5.gif.progressBar';
+ if (document.getElementById(progressBarIdName) !== null)
+ document.getElementById(progressBarIdName).remove();
+
+ let p = this.createP('');
+ p.id('progressBar');
+
+ p.style('font-size', '16px');
+ p.style('font-family', 'Montserrat');
+ p.style('background-color', '#ffffffa0');
+ p.style('padding', '8px');
+ p.style('border-radius', '10px');
+ p.position(0, 0);
+
+ let pixels;
+ let gl;
+ if (this.drawingContext instanceof WebGLRenderingContext) {
+ // if we have a WEBGL context, initialize the pixels array
+ // and the gl context to use them inside the loop
+ gl = document.getElementById('defaultCanvas0').getContext('webgl');
+ pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4);
+ }
+
+ // stop the loop since we are going to manually redraw
+ this.noLoop();
+
+ while (frameIterator < totalNumberOfFrames) {
+ /*
+ we draw the next frame. this is important, since
+ busy sketches or low end devices might take longer
+ to render some frames. So we just wait for the frame
+ to be drawn and immediately save it to a buffer and continue
+ */
+ this.redraw();
+
+ // depending on the context we'll extract the pixels one way
+ // or another
+ let data = undefined;
+
+ if (this.drawingContext instanceof WebGLRenderingContext) {
+ pixels = new Uint8Array(
+ gl.drawingBufferWidth * gl.drawingBufferHeight * 4
+ );
+ gl.readPixels(
+ 0,
+ 0,
+ gl.drawingBufferWidth,
+ gl.drawingBufferHeight,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ pixels
+ );
+
+ data = _flipPixels(pixels);
+ } else {
+ data = this.drawingContext.getImageData(0, 0, this.width, this.height)
+ .data;
+ }
+
+ frames.push(data);
+ frameIterator++;
+
+ p.html(
+ 'Saved frame ' +
+ frames.length.toString() +
+ ' out of ' +
+ nFrames.toString()
+ );
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+ p.html('Frames processed, generating color palette...');
+
+ this.loop();
+ this.pixelDensity(lastPixelDensity);
+
+ // create the gif encoder and the colorspace format
+ const gif = GIFEncoder();
+
+ // calculate the global palette for this set of frames
+ const globalPalette = _generateGlobalPalette(frames);
+
+ // the way we designed the palette means we always take the last index for transparency
+ const transparentIndex = globalPalette.length - 1;
+
+ // we are going to iterate the frames in pairs, n-1 and n
+ for (let i = 0; i < frames.length; i++) {
+ if (i === 0) {
+ const indexedFrame = applyPalette(frames[i], globalPalette, {
+ format: 'rgba4444'
+ });
+ gif.writeFrame(indexedFrame, this.width, this.height, {
+ palette: globalPalette,
+ delay: gifFrameDelay,
+ dispose: 1
+ });
+ continue;
+ }
+
+ // matching pixels between frames can be set to full transparency,
+ // kinda digging a "hole" into the frame to see the pixels that where behind it
+ // (which would be the exact same, so not noticeable changes)
+ // this helps make the file quite smaller
+ let currFramePixels = frames[i];
+ let lastFramePixels = frames[i - 1];
+ let matchingPixelsInFrames = [];
+ for (let p = 0; p < currFramePixels.length; p += 4) {
+ let currPixel = [
+ currFramePixels[p],
+ currFramePixels[p + 1],
+ currFramePixels[p + 2],
+ currFramePixels[p + 3]
+ ];
+ let lastPixel = [
+ lastFramePixels[p],
+ lastFramePixels[p + 1],
+ lastFramePixels[p + 2],
+ lastFramePixels[p + 3]
+ ];
+
+ // if the pixels are equal, save this index to be used later
+ if (_pixelEquals(currPixel, lastPixel)) {
+ matchingPixelsInFrames.push(p / 4);
+ }
+ }
+ // we decide on one of this colors to be fully transparent
+ // Apply palette to RGBA data to get an indexed bitmap
+ const indexedFrame = applyPalette(currFramePixels, globalPalette, {
+ format: 'rgba4444'
+ });
+
+ for (let i = 0; i < matchingPixelsInFrames.length; i++) {
+ // here, we overwrite whatever color this pixel was assigned to
+ // with the color that we decided we are going to use as transparent.
+ // down in writeFrame we are going to tell the encoder that whenever
+ // it runs into "transparentIndex", just dig a hole there allowing to
+ // see through what was in the frame before it.
+ let pixelIndex = matchingPixelsInFrames[i];
+ indexedFrame[pixelIndex] = transparentIndex;
+ }
+
+ // Write frame into the encoder
+ gif.writeFrame(indexedFrame, this.width, this.height, {
+ delay: gifFrameDelay,
+ transparent: true,
+ transparentIndex: transparentIndex,
+ dispose: 1
+ });
+
+ p.html(
+ 'Rendered frame ' + i.toString() + ' out of ' + nFrames.toString()
+ );
+
+ // this just makes the process asynchronous, preventing
+ // that the encoding locks up the browser
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ gif.finish();
+
+ // Get a direct typed array view into the buffer to avoid copying it
+ const buffer = gif.bytesView();
+ const extension = 'gif';
+
+ const blob = new Blob([buffer], {
+ type: 'image/gif'
+ });
+
+ frames = [];
+ this.loop();
+
+ p.html('Done. Downloading your gif!πΈ');
+ p5.prototype.downloadFile(blob, fileName, extension);
+};
+
+function _flipPixels(pixels) {
+ // extracting the pixels using readPixels returns
+ // an upside down image. we have to flip it back
+ // first. this solution is proposed by gman on
+ // this stack overflow answer:
+ // https://stackoverflow.com/questions/41969562/how-can-i-flip-the-result-of-webglrenderingcontext-readpixels
+
+ var halfHeight = parseInt(height / 2);
+ var bytesPerRow = width * 4;
+
+ // make a temp buffer to hold one row
+ var temp = new Uint8Array(width * 4);
+ for (var y = 0; y < halfHeight; ++y) {
+ var topOffset = y * bytesPerRow;
+ var bottomOffset = (height - y - 1) * bytesPerRow;
+
+ // make copy of a row on the top half
+ temp.set(pixels.subarray(topOffset, topOffset + bytesPerRow));
+
+ // copy a row from the bottom half to the top
+ pixels.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);
+
+ // copy the copy of the top half row to the bottom half
+ pixels.set(temp, bottomOffset);
+ }
+ return pixels;
+}
+
+function _generateGlobalPalette(frames) {
+ // make an array the size of every possible color in every possible frame
+ // that is: width * height * frames.
+ let allColors = new Uint8Array(frames.length * frames[0].length);
+
+ // put every frame one after the other in sequence.
+ // this array will hold absolutely every pixel from the animation.
+ // the set function on the Uint8Array works super fast tho!
+ for (let f = 0; f < frames.length; f++) {
+ allColors.set(frames[0], f * frames[0].length);
+ }
+
+ // quantize this massive array into 256 colors and return it!
+ let colorPalette = quantize(allColors, 256, {
+ format: 'rgba444',
+ oneBitAlpha: true
+ });
+
+ // when generating the palette, we have to leave space for 1 of the
+ // indices to be a random color that does not appear anywhere in our
+ // animation to use for transparency purposes. So, if the palette is full
+ // (has 256 colors), we overwrite the last one with a random, fully transparent
+ // color. Otherwise, we just push a new color into the palette the same way.
+
+ // this guarantees that when using the transparency index, there are no matches
+ // between some colors of the animation and the "holes" we want to dig on them,
+ // which would cause pieces of some frames to be transparent and thus look glitchy.
+ if (colorPalette.length === 256) {
+ colorPalette[colorPalette.length - 1] = [
+ Math.random() * 255,
+ Math.random() * 255,
+ Math.random() * 255,
+ 0
+ ];
+ } else {
+ colorPalette.push([
+ Math.random() * 255,
+ Math.random() * 255,
+ Math.random() * 255,
+ 0
+ ]);
+ }
+ return colorPalette;
+}
+
+function _pixelEquals(a, b) {
+ return (
+ Array.isArray(a) &&
+ Array.isArray(b) &&
+ a.length === b.length &&
+ a.every((val, index) => val === b[index])
+ );
+}
+
/**
* Helper function for loading GIF-based images
*/
@@ -602,33 +987,8 @@ p5.prototype.noTint = function() {
* @param {p5.Image} The image to be tinted
* @return {canvas} The resulting tinted canvas
*/
-p5.prototype._getTintedImageCanvas = function(img) {
- if (!img.canvas) {
- return img;
- }
- const pixels = Filters._toPixels(img.canvas);
- const tmpCanvas = document.createElement('canvas');
- tmpCanvas.width = img.canvas.width;
- tmpCanvas.height = img.canvas.height;
- const tmpCtx = tmpCanvas.getContext('2d');
- const id = tmpCtx.createImageData(img.canvas.width, img.canvas.height);
- const newPixels = id.data;
-
- for (let i = 0; i < pixels.length; i += 4) {
- const r = pixels[i];
- const g = pixels[i + 1];
- const b = pixels[i + 2];
- const a = pixels[i + 3];
-
- newPixels[i] = r * this._renderer._tint[0] / 255;
- newPixels[i + 1] = g * this._renderer._tint[1] / 255;
- newPixels[i + 2] = b * this._renderer._tint[2] / 255;
- newPixels[i + 3] = a * this._renderer._tint[3] / 255;
- }
-
- tmpCtx.putImageData(id, 0, 0);
- return tmpCanvas;
-};
+p5.prototype._getTintedImageCanvas =
+ p5.Renderer2D.prototype._getTintedImageCanvas;
/**
* Set image mode. Modifies the location from which images are drawn by
diff --git a/src/image/p5.Image.js b/src/image/p5.Image.js
index 97253ec1ad..87cd1b0954 100644
--- a/src/image/p5.Image.js
+++ b/src/image/p5.Image.js
@@ -629,10 +629,6 @@ p5.Image.prototype.copy = function(...args) {
* http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/
*/
// TODO: - Accept an array of alpha values.
-// - Use other channels of an image. p5 uses the
-// blue channel (which feels kind of arbitrary). Note: at the
-// moment this method does not match native processing's original
-// functionality exactly.
p5.Image.prototype.mask = function(p5Image) {
if (p5Image === undefined) {
p5Image = this;
@@ -657,7 +653,29 @@ p5.Image.prototype.mask = function(p5Image) {
];
this.drawingContext.globalCompositeOperation = 'destination-in';
- p5.Image.prototype.copy.apply(this, copyArgs);
+ if (this.gifProperties) {
+ for (let i = 0; i < this.gifProperties.frames.length; i++) {
+ this.drawingContext.putImageData(
+ this.gifProperties.frames[i].image,
+ 0,
+ 0
+ );
+ p5.Image.prototype.copy.apply(this, copyArgs);
+ this.gifProperties.frames[i].image = this.drawingContext.getImageData(
+ 0,
+ 0,
+ this.width,
+ this.height
+ );
+ }
+ this.drawingContext.putImageData(
+ this.gifProperties.frames[this.gifProperties.displayIndex].image,
+ 0,
+ 0
+ );
+ } else {
+ p5.Image.prototype.copy.apply(this, copyArgs);
+ }
this.drawingContext.globalCompositeOperation = currBlend;
this.setModified(true);
};
@@ -890,7 +908,7 @@ p5.Image.prototype.isModified = function() {
*/
p5.Image.prototype.save = function(filename, extension) {
if (this.gifProperties) {
- p5.prototype.saveGif(this, filename);
+ p5.prototype.encodeAndDownloadGif(this, filename);
} else {
p5.prototype.saveCanvas(this.canvas, filename, extension);
}
diff --git a/src/math/p5.Vector.js b/src/math/p5.Vector.js
index d7ba19b7e5..9ca2da949a 100644
--- a/src/math/p5.Vector.js
+++ b/src/math/p5.Vector.js
@@ -1490,6 +1490,7 @@ p5.Vector.prototype.heading = function heading() {
*/
p5.Vector.prototype.setHeading = function setHeading(a) {
+ if (this.isPInst) a = this._toRadians(a);
let m = this.mag();
this.x = m * Math.cos(a);
this.y = m * Math.sin(a);
diff --git a/src/math/trigonometry.js b/src/math/trigonometry.js
index dfbbfc1cb0..536d1d575d 100644
--- a/src/math/trigonometry.js
+++ b/src/math/trigonometry.js
@@ -281,9 +281,9 @@ p5.prototype.radians = angle => angle * constants.DEG_TO_RAD;
/**
* Sets the current mode of p5 to the given mode. Default mode is RADIANS.
*
+ * Calling angleMode() with no arguments returns current anglemode.
* @method angleMode
* @param {Constant} mode either RADIANS or DEGREES
- *
* @example
*
*
@@ -306,8 +306,15 @@ p5.prototype.radians = angle => angle * constants.DEG_TO_RAD;
*
*
*/
+/**
+ * @method angleMode
+ * @return {Constant} mode either RADIANS or DEGREES
+ */
p5.prototype.angleMode = function(mode) {
- if (mode === constants.DEGREES || mode === constants.RADIANS) {
+ p5._validateParameters('angleMode', arguments);
+ if (typeof mode === 'undefined') {
+ return this._angleMode;
+ } else if (mode === constants.DEGREES || mode === constants.RADIANS) {
this._angleMode = mode;
}
};
diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js
index 090a89479e..9e258c2bbe 100644
--- a/src/webgl/3d_primitives.js
+++ b/src/webgl/3d_primitives.js
@@ -27,6 +27,7 @@ import * as constants from '../core/constants';
* // with width 50 and height 50
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('a white plane with black wireframe lines');
* }
*
* function draw() {
@@ -98,7 +99,7 @@ p5.prototype.plane = function(width, height, detailX, detailY) {
* Draw a box with given width, height and depth
* @method box
* @param {Number} [width] width of the box
- * @param {Number} [Height] height of the box
+ * @param {Number} [height] height of the box
* @param {Number} [depth] depth of the box
* @param {Integer} [detailX] Optional number of triangle
* subdivisions in x-dimension
@@ -112,6 +113,7 @@ p5.prototype.plane = function(width, height, detailX, detailY) {
* // with width, height and depth of 50
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('a white box rotating in 3D space');
* }
*
* function draw() {
@@ -231,6 +233,7 @@ p5.prototype.box = function(width, height, depth, detailX, detailY) {
* // draw a sphere with radius 40
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('a white sphere with black wireframe lines');
* }
*
* function draw() {
@@ -250,6 +253,9 @@ p5.prototype.box = function(width, height, depth, detailX, detailY) {
* detailX = createSlider(3, 24, 3);
* detailX.position(10, height + 5);
* detailX.style('width', '80px');
+ * describe(
+ * 'a white sphere with low detail on the x-axis, including a slider to adjust detailX'
+ * );
* }
*
* function draw() {
@@ -270,6 +276,9 @@ p5.prototype.box = function(width, height, depth, detailX, detailY) {
* detailY = createSlider(3, 16, 3);
* detailY.position(10, height + 5);
* detailY.style('width', '80px');
+ * describe(
+ * 'a white sphere with low detail on the y-axis, including a slider to adjust detailY'
+ * );
* }
*
* function draw() {
@@ -441,6 +450,7 @@ const _truncatedCone = function(
* // with radius 20 and height 50
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('a rotating white cylinder');
* }
*
* function draw() {
@@ -462,6 +472,9 @@ const _truncatedCone = function(
* detailX = createSlider(3, 24, 3);
* detailX.position(10, height + 5);
* detailX.style('width', '80px');
+ * describe(
+ * 'a rotating white cylinder with limited X detail, with a slider that adjusts detailX'
+ * );
* }
*
* function draw() {
@@ -482,6 +495,9 @@ const _truncatedCone = function(
* detailY = createSlider(1, 16, 1);
* detailY.position(10, height + 5);
* detailY.style('width', '80px');
+ * describe(
+ * 'a rotating white cylinder with limited Y detail, with a slider that adjusts detailY'
+ * );
* }
*
* function draw() {
@@ -576,6 +592,7 @@ p5.prototype.cylinder = function(
* // with radius 40 and height 70
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('a rotating white cone');
* }
*
* function draw() {
@@ -597,6 +614,9 @@ p5.prototype.cylinder = function(
* detailX = createSlider(3, 16, 3);
* detailX.position(10, height + 5);
* detailX.style('width', '80px');
+ * describe(
+ * 'a rotating white cone with limited X detail, with a slider that adjusts detailX'
+ * );
* }
*
* function draw() {
@@ -617,6 +637,9 @@ p5.prototype.cylinder = function(
* detailY = createSlider(3, 16, 3);
* detailY.position(10, height + 5);
* detailY.style('width', '80px');
+ * describe(
+ * 'a rotating white cone with limited Y detail, with a slider that adjusts detailY'
+ * );
* }
*
* function draw() {
@@ -692,6 +715,7 @@ p5.prototype.cone = function(radius, height, detailX, detailY, cap) {
* // with radius 30, 40 and 40.
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('a white 3d ellipsoid');
* }
*
* function draw() {
@@ -711,6 +735,9 @@ p5.prototype.cone = function(radius, height, detailX, detailY, cap) {
* detailX = createSlider(2, 24, 12);
* detailX.position(10, height + 5);
* detailX.style('width', '80px');
+ * describe(
+ * 'a rotating white ellipsoid with limited X detail, with a slider that adjusts detailX'
+ * );
* }
*
* function draw() {
@@ -731,6 +758,9 @@ p5.prototype.cone = function(radius, height, detailX, detailY, cap) {
* detailY = createSlider(2, 24, 6);
* detailY.position(10, height + 5);
* detailY.style('width', '80px');
+ * describe(
+ * 'a rotating white ellipsoid with limited Y detail, with a slider that adjusts detailY'
+ * );
* }
*
* function draw() {
@@ -826,6 +856,7 @@ p5.prototype.ellipsoid = function(radiusX, radiusY, radiusZ, detailX, detailY) {
* // with ring radius 30 and tube radius 15
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('a rotating white torus');
* }
*
* function draw() {
@@ -847,6 +878,9 @@ p5.prototype.ellipsoid = function(radiusX, radiusY, radiusZ, detailX, detailY) {
* detailX = createSlider(3, 24, 3);
* detailX.position(10, height + 5);
* detailX.style('width', '80px');
+ * describe(
+ * 'a rotating white torus with limited X detail, with a slider that adjusts detailX'
+ * );
* }
*
* function draw() {
@@ -867,6 +901,9 @@ p5.prototype.ellipsoid = function(radiusX, radiusY, radiusZ, detailX, detailY) {
* detailY = createSlider(3, 16, 3);
* detailY.position(10, height + 5);
* detailY.style('width', '80px');
+ * describe(
+ * 'a rotating white torus with limited Y detail, with a slider that adjusts detailY'
+ * );
* }
*
* function draw() {
@@ -1180,56 +1217,134 @@ p5.RendererGL.prototype.arc = function(args) {
};
p5.RendererGL.prototype.rect = function(args) {
- const perPixelLighting = this._pInst._glAttributes.perPixelLighting;
const x = args[0];
const y = args[1];
const width = args[2];
const height = args[3];
- const detailX = args[4] || (perPixelLighting ? 1 : 24);
- const detailY = args[5] || (perPixelLighting ? 1 : 16);
- const gId = `rect|${detailX}|${detailY}`;
- if (!this.geometryInHash(gId)) {
- const _rect = function() {
- for (let i = 0; i <= this.detailY; i++) {
- const v = i / this.detailY;
- for (let j = 0; j <= this.detailX; j++) {
- const u = j / this.detailX;
- const p = new p5.Vector(u, v, 0);
- this.vertices.push(p);
- this.uvs.push(u, v);
+
+ if (typeof args[4] === 'undefined') {
+ // Use the retained mode for drawing rectangle,
+ // if args for rounding rectangle is not provided by user.
+ const perPixelLighting = this._pInst._glAttributes.perPixelLighting;
+ const detailX = args[4] || (perPixelLighting ? 1 : 24);
+ const detailY = args[5] || (perPixelLighting ? 1 : 16);
+ const gId = `rect|${detailX}|${detailY}`;
+ if (!this.geometryInHash(gId)) {
+ const _rect = function() {
+ for (let i = 0; i <= this.detailY; i++) {
+ const v = i / this.detailY;
+ for (let j = 0; j <= this.detailX; j++) {
+ const u = j / this.detailX;
+ const p = new p5.Vector(u, v, 0);
+ this.vertices.push(p);
+ this.uvs.push(u, v);
+ }
}
- }
- // using stroke indices to avoid stroke over face(s) of rectangle
- if (detailX > 0 && detailY > 0) {
- this.strokeIndices = [
- [0, detailX],
- [detailX, (detailX + 1) * (detailY + 1) - 1],
- [(detailX + 1) * (detailY + 1) - 1, (detailX + 1) * detailY],
- [(detailX + 1) * detailY, 0]
- ];
- }
- };
- const rectGeom = new p5.Geometry(detailX, detailY, _rect);
- rectGeom
- .computeFaces()
- .computeNormals()
- ._makeTriangleEdges()
- ._edgesToVertices();
- this.createBuffers(gId, rectGeom);
- }
+ // using stroke indices to avoid stroke over face(s) of rectangle
+ if (detailX > 0 && detailY > 0) {
+ this.strokeIndices = [
+ [0, detailX],
+ [detailX, (detailX + 1) * (detailY + 1) - 1],
+ [(detailX + 1) * (detailY + 1) - 1, (detailX + 1) * detailY],
+ [(detailX + 1) * detailY, 0]
+ ];
+ }
+ };
+ const rectGeom = new p5.Geometry(detailX, detailY, _rect);
+ rectGeom
+ .computeFaces()
+ .computeNormals()
+ ._makeTriangleEdges()
+ ._edgesToVertices();
+ this.createBuffers(gId, rectGeom);
+ }
- // only a single rectangle (of a given detail) is cached: a square with
- // opposite corners at (0,0) & (1,1).
- //
- // before rendering, this square is scaled & moved to the required location.
- const uMVMatrix = this.uMVMatrix.copy();
- try {
- this.uMVMatrix.translate([x, y, 0]);
- this.uMVMatrix.scale(width, height, 1);
+ // only a single rectangle (of a given detail) is cached: a square with
+ // opposite corners at (0,0) & (1,1).
+ //
+ // before rendering, this square is scaled & moved to the required location.
+ const uMVMatrix = this.uMVMatrix.copy();
+ try {
+ this.uMVMatrix.translate([x, y, 0]);
+ this.uMVMatrix.scale(width, height, 1);
+
+ this.drawBuffers(gId);
+ } finally {
+ this.uMVMatrix = uMVMatrix;
+ }
+ } else {
+ // Use Immediate mode to round the rectangle corner,
+ // if args for rounding corners is provided by user
+ let tl = args[4];
+ let tr = typeof args[5] === 'undefined' ? tl : args[5];
+ let br = typeof args[6] === 'undefined' ? tr : args[6];
+ let bl = typeof args[7] === 'undefined' ? br : args[7];
+
+ let a = x;
+ let b = y;
+ let c = width;
+ let d = height;
+
+ c += a;
+ d += b;
+
+ if (a > c) {
+ const temp = a;
+ a = c;
+ c = temp;
+ }
- this.drawBuffers(gId);
- } finally {
- this.uMVMatrix = uMVMatrix;
+ if (b > d) {
+ const temp = b;
+ b = d;
+ d = temp;
+ }
+
+ const maxRounding = Math.min((c - a) / 2, (d - b) / 2);
+ if (tl > maxRounding) tl = maxRounding;
+ if (tr > maxRounding) tr = maxRounding;
+ if (br > maxRounding) br = maxRounding;
+ if (bl > maxRounding) bl = maxRounding;
+
+ let x1 = a;
+ let y1 = b;
+ let x2 = c;
+ let y2 = d;
+
+ this.beginShape();
+ if (tr !== 0) {
+ this.vertex(x2 - tr, y1);
+ this.quadraticVertex(x2, y1, x2, y1 + tr);
+ } else {
+ this.vertex(x2, y1);
+ }
+ if (br !== 0) {
+ this.vertex(x2, y2 - br);
+ this.quadraticVertex(x2, y2, x2 - br, y2);
+ } else {
+ this.vertex(x2, y2);
+ }
+ if (bl !== 0) {
+ this.vertex(x1 + bl, y2);
+ this.quadraticVertex(x1, y2, x1, y2 - bl);
+ } else {
+ this.vertex(x1, y2);
+ }
+ if (tl !== 0) {
+ this.vertex(x1, y1 + tl);
+ this.quadraticVertex(x1, y1, x1 + tl, y1);
+ } else {
+ this.vertex(x1, y1);
+ }
+
+ this.immediateMode.geometry.uvs.length = 0;
+ for (const vert of this.immediateMode.geometry.vertices) {
+ const u = (vert.x - x1) / width;
+ const v = (vert.y - y1) / height;
+ this.immediateMode.geometry.uvs.push(u, v);
+ }
+
+ this.endShape(constants.CLOSE);
}
return this;
};
diff --git a/src/webgl/interaction.js b/src/webgl/interaction.js
index 87680921df..d13a9acd3e 100644
--- a/src/webgl/interaction.js
+++ b/src/webgl/interaction.js
@@ -30,6 +30,9 @@ import * as constants from '../core/constants';
* function setup() {
* createCanvas(100, 100, WEBGL);
* normalMaterial();
+ * describe(
+ * 'Camera orbits around a box when mouse is hold-clicked & then moved.'
+ * );
* }
* function draw() {
* background(200);
@@ -167,6 +170,9 @@ p5.prototype.orbitControl = function(sensitivityX, sensitivityY, sensitivityZ) {
* camera(0, -30, 100, 0, 0, 0, 0, 1, 0);
* normalMaterial();
* debugMode();
+ * describe(
+ * 'a 3D box is centered on a grid in a 3D sketch. an icon indicates the direction of each axis: a red line points +X, a green line +Y, and a blue line +Z. the grid and icon disappear when the spacebar is pressed.'
+ * );
* }
*
* function draw() {
@@ -194,6 +200,7 @@ p5.prototype.orbitControl = function(sensitivityX, sensitivityY, sensitivityZ) {
* camera(0, -30, 100, 0, 0, 0, 0, 1, 0);
* normalMaterial();
* debugMode(GRID);
+ * describe('a 3D box is centered on a grid in a 3D sketch.');
* }
*
* function draw() {
@@ -214,6 +221,9 @@ p5.prototype.orbitControl = function(sensitivityX, sensitivityY, sensitivityZ) {
* camera(0, -30, 100, 0, 0, 0, 0, 1, 0);
* normalMaterial();
* debugMode(AXES);
+ * describe(
+ * 'a 3D box is centered in a 3D sketch. an icon indicates the direction of each axis: a red line points +X, a green line +Y, and a blue line +Z.'
+ * );
* }
*
* function draw() {
@@ -236,6 +246,7 @@ p5.prototype.orbitControl = function(sensitivityX, sensitivityY, sensitivityZ) {
* camera(0, -30, 100, 0, 0, 0, 0, 1, 0);
* normalMaterial();
* debugMode(GRID, 100, 10, 0, 0, 0);
+ * describe('a 3D box is centered on a grid in a 3D sketch');
* }
*
* function draw() {
@@ -256,6 +267,9 @@ p5.prototype.orbitControl = function(sensitivityX, sensitivityY, sensitivityZ) {
* camera(0, -30, 100, 0, 0, 0, 0, 1, 0);
* normalMaterial();
* debugMode(100, 10, 0, 0, 0, 20, 0, -40, 0);
+ * describe(
+ * 'a 3D box is centered on a grid in a 3D sketch. an icon indicates the direction of each axis: a red line points +X, a green line +Y, and a blue line +Z.'
+ * );
* }
*
* function draw() {
@@ -361,6 +375,9 @@ p5.prototype.debugMode = function(...args) {
* camera(0, -30, 100, 0, 0, 0, 0, 1, 0);
* normalMaterial();
* debugMode();
+ * describe(
+ * 'a 3D box is centered on a grid in a 3D sketch. an icon indicates the direction of each axis: a red line points +X, a green line +Y, and a blue line +Z. the grid and icon disappear when the spacebar is pressed.'
+ * );
* }
*
* function draw() {
diff --git a/src/webgl/light.js b/src/webgl/light.js
index 559f3a468e..c5df0add0a 100644
--- a/src/webgl/light.js
+++ b/src/webgl/light.js
@@ -37,6 +37,7 @@ import * as constants from '../core/constants';
* function setup() {
* createCanvas(100, 100, WEBGL);
* noStroke();
+ * describe('sphere with coral color under black light');
* }
* function draw() {
* background(100);
@@ -55,6 +56,7 @@ import * as constants from '../core/constants';
* function setup() {
* createCanvas(100, 100, WEBGL);
* noStroke();
+ * describe('sphere with coral color under white light');
* }
* function draw() {
* background(100);
@@ -144,6 +146,9 @@ p5.prototype.ambientLight = function(v1, v2, v3, a) {
* function setup() {
* createCanvas(100, 100, WEBGL);
* noStroke();
+ * describe(
+ * 'Sphere with specular highlight. Clicking the mouse toggles the specular highlight color between red and the default white.'
+ * );
* }
*
* function draw() {
@@ -256,6 +261,9 @@ p5.prototype.specularColor = function(v1, v2, v3) {
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe(
+ * 'scene with sphere and directional light. The direction of the light is controlled with the mouse position.'
+ * );
* }
* function draw() {
* background(0);
@@ -374,6 +382,9 @@ p5.prototype.directionalLight = function(v1, v2, v3, x, y, z) {
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe(
+ * 'scene with sphere and point light. The position of the light is controlled with the mouse position.'
+ * );
* }
* function draw() {
* background(0);
@@ -481,6 +492,7 @@ p5.prototype.pointLight = function(v1, v2, v3, x, y, z) {
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('the light is partially ambient and partially directional');
* }
* function draw() {
* background(0);
@@ -534,6 +546,9 @@ p5.prototype.lights = function() {
* function setup() {
* createCanvas(100, 100, WEBGL);
* noStroke();
+ * describe(
+ * 'Two spheres with different falloff values show different intensity of light'
+ * );
* }
* function draw() {
* ortho();
@@ -654,6 +669,9 @@ p5.prototype.lightFalloff = function(
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe(
+ * 'scene with sphere and spot light. The position of the light is controlled with the mouse position.'
+ * );
* }
* function draw() {
* background(0);
@@ -985,6 +1003,9 @@ p5.prototype.spotLight = function(
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe(
+ * 'Three white spheres. Each appears as a different color due to lighting.'
+ * );
* }
* function draw() {
* background(200);
diff --git a/src/webgl/loading.js b/src/webgl/loading.js
index 78e19145c2..039eba7fea 100755
--- a/src/webgl/loading.js
+++ b/src/webgl/loading.js
@@ -50,6 +50,7 @@ import './p5.Geometry';
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('Vertically rotating 3-d octahedron.');
* }
*
* function draw() {
@@ -77,6 +78,7 @@ import './p5.Geometry';
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('Vertically rotating 3-d teapot with red, green and blue gradient.');
* }
*
* function draw() {
@@ -602,6 +604,7 @@ function parseASCIISTL(model, lines) {
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('Vertically rotating 3-d octahedron.');
* }
*
* function draw() {
diff --git a/src/webgl/material.js b/src/webgl/material.js
index 08b9f7ddd5..a42f569bf6 100644
--- a/src/webgl/material.js
+++ b/src/webgl/material.js
@@ -45,6 +45,7 @@ import './p5.Texture';
* shader(mandel);
* noStroke();
* mandel.setUniform('p', [-0.74364388703, 0.13182590421]);
+ * describe('zooming Mandelbrot set. a colorful, infinitely detailed fractal.');
* }
*
* function draw() {
@@ -162,6 +163,7 @@ p5.prototype.loadShader = function(
*
* // 'p' is the center point of the Mandelbrot image
* mandel.setUniform('p', [-0.74364388703, 0.13182590421]);
+ * describe('zooming Mandelbrot set. a colorful, infinitely detailed fractal.');
* }
*
* function draw() {
@@ -233,6 +235,10 @@ p5.prototype.createShader = function(vertSrc, fragSrc) {
* orangeBlue.setUniform('colorBackground', [0.226, 0.0, 0.615]);
*
* noStroke();
+ *
+ * describe(
+ * 'canvas toggles between a circular gradient of orange and blue vertically. and a circular gradient of red and green moving horizontally when mouse is clicked/pressed.'
+ * );
* }
*
* function draw() {
@@ -326,6 +332,10 @@ p5.prototype.shader = function(s) {
*
* // Create our shader
* shaderProgram = createShader(vertSrc, fragSrc);
+ *
+ * describe(
+ * 'Two rotating cubes. The left one is painted using a custom (user-defined) shader, while the right one is painted using the default fill shader.'
+ * );
* }
*
* // prettier-ignore
@@ -392,6 +402,7 @@ p5.prototype.resetShader = function() {
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('spinning cube with a texture from an image');
* }
*
* function draw() {
@@ -417,6 +428,7 @@ p5.prototype.resetShader = function() {
* createCanvas(100, 100, WEBGL);
* pg = createGraphics(200, 200);
* pg.textSize(75);
+ * describe('plane with a texture from an image created by createGraphics()');
* }
*
* function draw() {
@@ -444,6 +456,7 @@ p5.prototype.resetShader = function() {
* }
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('rectangle with video as texture');
* }
*
* function draw() {
@@ -473,6 +486,7 @@ p5.prototype.resetShader = function() {
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('quad with a texture, mapped using normalized coordinates');
* }
*
* function draw() {
@@ -529,6 +543,7 @@ p5.prototype.texture = function(tex) {
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('quad with a texture, mapped using normalized coordinates');
* }
*
* function draw() {
@@ -557,6 +572,7 @@ p5.prototype.texture = function(tex) {
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('quad with a texture, mapped using image coordinates');
* }
*
* function draw() {
@@ -615,6 +631,7 @@ p5.prototype.textureMode = function(mode) {
* function setup() {
* createCanvas(100, 100, WEBGL);
* textureWrap(MIRROR);
+ * describe('an image of the rocky mountains repeated in mirrored tiles');
* }
*
* function draw() {
@@ -675,6 +692,7 @@ p5.prototype.textureWrap = function(wrapX, wrapY = wrapX) {
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('Sphere with normal material');
* }
*
* function draw() {
@@ -731,6 +749,7 @@ p5.prototype.normalMaterial = function(...args) {
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('sphere reflecting red, blue, and green light');
* }
* function draw() {
* background(0);
@@ -751,6 +770,7 @@ p5.prototype.normalMaterial = function(...args) {
* // so object only reflects it's red and blue components
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('box reflecting only red and blue light');
* }
* function draw() {
* background(70);
@@ -770,6 +790,7 @@ p5.prototype.normalMaterial = function(...args) {
* // green, it does not reflect any light
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('box reflecting no light');
* }
* function draw() {
* background(70);
@@ -839,6 +860,7 @@ p5.prototype.ambientMaterial = function(v1, v2, v3) {
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('sphere with green emissive material');
* }
* function draw() {
* background(0);
@@ -913,6 +935,7 @@ p5.prototype.emissiveMaterial = function(v1, v2, v3, a) {
* function setup() {
* createCanvas(100, 100, WEBGL);
* noStroke();
+ * describe('torus with specular material');
* }
*
* function draw() {
@@ -982,6 +1005,7 @@ p5.prototype.specularMaterial = function(v1, v2, v3, alpha) {
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('two spheres, one more shiny than the other');
* }
* function draw() {
* background(0);
@@ -1026,7 +1050,10 @@ p5.RendererGL.prototype._applyColorBlend = function(colors) {
const isTexture = this.drawMode === constants.TEXTURE;
const doBlend =
- isTexture || colors[colors.length - 1] < 1.0 || this._isErasing;
+ isTexture ||
+ this.curBlendMode !== constants.BLEND ||
+ colors[colors.length - 1] < 1.0 ||
+ this._isErasing;
if (doBlend !== this._isBlending) {
if (
@@ -1057,10 +1084,13 @@ p5.RendererGL.prototype._applyBlendMode = function() {
const gl = this.GL;
switch (this.curBlendMode) {
case constants.BLEND:
- case constants.ADD:
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
break;
+ case constants.ADD:
+ gl.blendEquation(gl.FUNC_ADD);
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
+ break;
case constants.REMOVE:
gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT);
gl.blendFunc(gl.SRC_ALPHA, gl.DST_ALPHA);
diff --git a/src/webgl/p5.Camera.js b/src/webgl/p5.Camera.js
index f09dee3923..637ada8e69 100644
--- a/src/webgl/p5.Camera.js
+++ b/src/webgl/p5.Camera.js
@@ -45,6 +45,7 @@ import p5 from '../core/main';
*
* function setup() {
* createCanvas(100, 100, WEBGL);
+ * describe('a square moving closer and then away from the camera.');
* }
* function draw() {
* background(204);
@@ -82,6 +83,9 @@ import p5 from '../core/main';
* sliderGroup[i].position(10, height + h);
* sliderGroup[i].style('width', '80px');
* }
+ * describe(
+ * 'White square repeatedly grows to fill canvas and then shrinks. An interactive example of a red cube with 3 sliders for moving it across x, y, z axis and 3 sliders for shifting its center.'
+ * );
* }
*
* function draw() {
@@ -141,6 +145,9 @@ p5.prototype.camera = function(...args) {
* function setup() {
* createCanvas(100, 100, WEBGL);
* perspective(PI / 3.0, width / height, 0.1, 500);
+ * describe(
+ * 'two colored 3D boxes move back and forth, rotating as mouse is dragged.'
+ * );
* }
* function draw() {
* background(200);
@@ -203,6 +210,9 @@ p5.prototype.perspective = function(...args) {
* function setup() {
* createCanvas(100, 100, WEBGL);
* ortho(-width / 2, width / 2, height / 2, -height / 2, 0, 500);
+ * describe(
+ * 'two 3D boxes move back and forth along same plane, rotating as mouse is dragged.'
+ * );
* }
* function draw() {
* background(200);
@@ -266,6 +276,9 @@ p5.prototype.ortho = function(...args) {
* createCanvas(100, 100, WEBGL);
* setAttributes('antialias', true);
* frustum(-0.1, 0.1, -0.1, 0.1, 0.1, 200);
+ * describe(
+ * 'two 3D boxes move back and forth along same plane, rotating as mouse is dragged.'
+ * );
* }
* function draw() {
* background(200);
@@ -328,6 +341,7 @@ p5.prototype.frustum = function(...args) {
* createCanvas(100, 100, WEBGL);
* background(0);
* camera = createCamera();
+ * describe('An example that creates a camera and moves it around the box.');
* }
*
* function draw() {
@@ -398,6 +412,9 @@ p5.prototype.createCamera = function() {
* cam = createCamera();
* // set initial pan angle
* cam.pan(-0.8);
+ * describe(
+ * 'camera view pans left and right across a series of rotating 3D boxes.'
+ * );
* }
*
* function draw() {
@@ -455,6 +472,7 @@ p5.Camera = function(renderer) {
* cam = createCamera();
* div = createDiv();
* div.position(0, 0);
+ * describe('An example showing the use of camera object properties');
* }
*
* function draw() {
@@ -482,6 +500,7 @@ p5.Camera = function(renderer) {
* cam = createCamera();
* div = createDiv();
* div.position(0, 0);
+ * describe('An example showing the use of camera object properties');
* }
*
* function draw() {
@@ -509,6 +528,7 @@ p5.Camera = function(renderer) {
* cam = createCamera();
* div = createDiv();
* div.position(0, 0);
+ * describe('An example showing the use of camera object properties');
* }
*
* function draw() {
@@ -538,6 +558,7 @@ p5.Camera = function(renderer) {
* div = createDiv('centerX = ' + cam.centerX);
* div.position(0, 0);
* div.style('color', 'white');
+ * describe('An example showing the use of camera object properties');
* }
*
* function draw() {
@@ -566,6 +587,7 @@ p5.Camera = function(renderer) {
* div = createDiv('centerY = ' + cam.centerY);
* div.position(0, 0);
* div.style('color', 'white');
+ * describe('An example showing the use of camera object properties');
* }
*
* function draw() {
@@ -594,6 +616,7 @@ p5.Camera = function(renderer) {
* div = createDiv('centerZ = ' + cam.centerZ);
* div.position(0, 0);
* div.style('color', 'white');
+ * describe('An example showing the use of camera object properties');
* }
*
* function draw() {
@@ -622,6 +645,7 @@ p5.Camera = function(renderer) {
* div.position(0, 0);
* div.style('color', 'blue');
* div.style('font-size', '18px');
+ * describe('An example showing the use of camera object properties');
* }
*