diff --git a/src/accessibility/color_namer.js b/src/accessibility/color_namer.js index 4509280e5b..bfe1c96a58 100644 --- a/src/accessibility/color_namer.js +++ b/src/accessibility/color_namer.js @@ -5,711 +5,716 @@ * @requires core */ -import p5 from '../core/main'; import color_conversion from '../color/color_conversion'; -//stores the original hsb values -let originalHSB; +function colorNamer(p5, fn){ + //stores the original hsb values + let originalHSB; -//stores values for color name exceptions -const colorExceptions = [ - { - h: 0, - s: 0, - b: 0.8275, - name: 'gray' - }, - { - h: 0, - s: 0, - b: 0.8627, - name: 'gray' - }, - { - h: 0, - s: 0, - b: 0.7529, - name: 'gray' - }, - { - h: 0.0167, - s: 0.1176, - b: 1, - name: 'light pink' - } -]; - -//stores values for color names -const colorLookUp = [ - { - h: 0, - s: 0, - b: 0, - name: 'black' - }, - { - h: 0, - s: 0, - b: 0.5, - name: 'gray' - }, - { - h: 0, - s: 0, - b: 1, - name: 'white' - }, - { - h: 0, - s: 0.5, - b: 0.5, - name: 'dark maroon' - }, - { - h: 0, - s: 0.5, - b: 1, - name: 'salmon pink' - }, - { - h: 0, - s: 1, - b: 0, - name: 'black' - }, - { - h: 0, - s: 1, - b: 0.5, - name: 'dark red' - }, - { - h: 0, - s: 1, - b: 1, - name: 'red' - }, - { - h: 5, - s: 0, - b: 1, - name: 'very light peach' - }, - { - h: 5, - s: 0.5, - b: 0.5, - name: 'brown' - }, - { - h: 5, - s: 0.5, - b: 1, - name: 'peach' - }, - { - h: 5, - s: 1, - b: 0.5, - name: 'brick red' - }, - { - h: 5, - s: 1, - b: 1, - name: 'crimson' - }, - { - h: 10, - s: 0, - b: 1, - name: 'light peach' - }, - { - h: 10, - s: 0.5, - b: 0.5, - name: 'brown' - }, - { - h: 10, - s: 0.5, - b: 1, - name: 'light orange' - }, - { - h: 10, - s: 1, - b: 0.5, - name: 'brown' - }, - { - h: 10, - s: 1, - b: 1, - name: 'orange' - }, - { - h: 15, - s: 0, - b: 1, - name: 'very light yellow' - }, - { - h: 15, - s: 0.5, - b: 0.5, - name: 'olive green' - }, - { - h: 15, - s: 0.5, - b: 1, - name: 'light yellow' - }, - { - h: 15, - s: 1, - b: 0, - name: 'dark olive green' - }, - { - h: 15, - s: 1, - b: 0.5, - name: 'olive green' - }, - { - h: 15, - s: 1, - b: 1, - name: 'yellow' - }, - { - h: 20, - s: 0, - b: 1, - name: 'very light yellow' - }, - { - h: 20, - s: 0.5, - b: 0.5, - name: 'olive green' - }, - { - h: 20, - s: 0.5, - b: 1, - name: 'light yellow green' - }, - { - h: 20, - s: 1, - b: 0, - name: 'dark olive green' - }, - { - h: 20, - s: 1, - b: 0.5, - name: 'dark yellow green' - }, - { - h: 20, - s: 1, - b: 1, - name: 'yellow green' - }, - { - h: 25, - s: 0.5, - b: 0.5, - name: 'dark yellow green' - }, - { - h: 25, - s: 0.5, - b: 1, - name: 'light green' - }, - { - h: 25, - s: 1, - b: 0.5, - name: 'dark green' - }, - { - h: 25, - s: 1, - b: 1, - name: 'green' - }, - { - h: 30, - s: 0.5, - b: 1, - name: 'light green' - }, - { - h: 30, - s: 1, - b: 0.5, - name: 'dark green' - }, - { - h: 30, - s: 1, - b: 1, - name: 'green' - }, - { - h: 35, - s: 0, - b: 0.5, - name: 'light green' - }, - { - h: 35, - s: 0, - b: 1, - name: 'very light green' - }, - { - h: 35, - s: 0.5, - b: 0.5, - name: 'dark green' - }, - { - h: 35, - s: 0.5, - b: 1, - name: 'light green' - }, - { - h: 35, - s: 1, - b: 0, - name: 'very dark green' - }, - { - h: 35, - s: 1, - b: 0.5, - name: 'dark green' - }, - { - h: 35, - s: 1, - b: 1, - name: 'green' - }, - { - h: 40, - s: 0, - b: 1, - name: 'very light green' - }, - { - h: 40, - s: 0.5, - b: 0.5, - name: 'dark green' - }, - { - h: 40, - s: 0.5, - b: 1, - name: 'light green' - }, - { - h: 40, - s: 1, - b: 0.5, - name: 'dark green' - }, - { - h: 40, - s: 1, - b: 1, - name: 'green' - }, - { - h: 45, - s: 0.5, - b: 1, - name: 'light turquoise' - }, - { - h: 45, - s: 1, - b: 0.5, - name: 'dark turquoise' - }, - { - h: 45, - s: 1, - b: 1, - name: 'turquoise' - }, - { - h: 50, - s: 0, - b: 1, - name: 'light sky blue' - }, - { - h: 50, - s: 0.5, - b: 0.5, - name: 'dark cyan' - }, - { - h: 50, - s: 0.5, - b: 1, - name: 'light cyan' - }, - { - h: 50, - s: 1, - b: 0.5, - name: 'dark cyan' - }, - { - h: 50, - s: 1, - b: 1, - name: 'cyan' - }, - { - h: 55, - s: 0, - b: 1, - name: 'light sky blue' - }, - { - h: 55, - s: 0.5, - b: 1, - name: 'light sky blue' - }, - { - h: 55, - s: 1, - b: 0.5, - name: 'dark blue' - }, - { - h: 55, - s: 1, - b: 1, - name: 'sky blue' - }, - { - h: 60, - s: 0, - b: 0.5, - name: 'gray' - }, - { - h: 60, - s: 0, - b: 1, - name: 'very light blue' - }, - { - h: 60, - s: 0.5, - b: 0.5, - name: 'blue' - }, - { - h: 60, - s: 0.5, - b: 1, - name: 'light blue' - }, - { - h: 60, - s: 1, - b: 0.5, - name: 'navy blue' - }, - { - h: 60, - s: 1, - b: 1, - name: 'blue' - }, - { - h: 65, - s: 0, - b: 1, - name: 'lavender' - }, - { - h: 65, - s: 0.5, - b: 0.5, - name: 'navy blue' - }, - { - h: 65, - s: 0.5, - b: 1, - name: 'light purple' - }, - { - h: 65, - s: 1, - b: 0.5, - name: 'dark navy blue' - }, - { - h: 65, - s: 1, - b: 1, - name: 'blue' - }, - { - h: 70, - s: 0, - b: 1, - name: 'lavender' - }, - { - h: 70, - s: 0.5, - b: 0.5, - name: 'navy blue' - }, - { - h: 70, - s: 0.5, - b: 1, - name: 'lavender blue' - }, - { - h: 70, - s: 1, - b: 0.5, - name: 'dark navy blue' - }, - { - h: 70, - s: 1, - b: 1, - name: 'blue' - }, - { - h: 75, - s: 0.5, - b: 1, - name: 'lavender' - }, - { - h: 75, - s: 1, - b: 0.5, - name: 'dark purple' - }, - { - h: 75, - s: 1, - b: 1, - name: 'purple' - }, - { - h: 80, - s: 0.5, - b: 1, - name: 'pinkish purple' - }, - { - h: 80, - s: 1, - b: 0.5, - name: 'dark purple' - }, - { - h: 80, - s: 1, - b: 1, - name: 'purple' - }, - { - h: 85, - s: 0, - b: 1, - name: 'light pink' - }, - { - h: 85, - s: 0.5, - b: 0.5, - name: 'purple' - }, - { - h: 85, - s: 0.5, - b: 1, - name: 'light fuchsia' - }, - { - h: 85, - s: 1, - b: 0.5, - name: 'dark fuchsia' - }, - { - h: 85, - s: 1, - b: 1, - name: 'fuchsia' - }, - { - h: 90, - s: 0.5, - b: 0.5, - name: 'dark fuchsia' - }, - { - h: 90, - s: 0.5, - b: 1, - name: 'hot pink' - }, - { - h: 90, - s: 1, - b: 0.5, - name: 'dark fuchsia' - }, - { - h: 90, - s: 1, - b: 1, - name: 'fuchsia' - }, - { - h: 95, - s: 0, - b: 1, - name: 'pink' - }, - { - h: 95, - s: 0.5, - b: 1, - name: 'light pink' - }, - { - h: 95, - s: 1, - b: 0.5, - name: 'dark magenta' - }, - { - h: 95, - s: 1, - b: 1, - name: 'magenta' - } -]; + //stores values for color name exceptions + const colorExceptions = [ + { + h: 0, + s: 0, + b: 0.8275, + name: 'gray' + }, + { + h: 0, + s: 0, + b: 0.8627, + name: 'gray' + }, + { + h: 0, + s: 0, + b: 0.7529, + name: 'gray' + }, + { + h: 0.0167, + s: 0.1176, + b: 1, + name: 'light pink' + } + ]; -//returns text with color name -function _calculateColor(hsb) { - let colortext; - //round hue - if (hsb[0] !== 0) { - hsb[0] = Math.round(hsb[0] * 100); - let hue = hsb[0].toString().split(''); - const last = hue.length - 1; - hue[last] = parseInt(hue[last]); - //if last digit of hue is < 2.5 make it 0 - if (hue[last] < 2.5) { - hue[last] = 0; - //if last digit of hue is >= 2.5 and less than 7.5 make it 5 - } else if (hue[last] >= 2.5 && hue[last] < 7.5) { - hue[last] = 5; + //stores values for color names + const colorLookUp = [ + { + h: 0, + s: 0, + b: 0, + name: 'black' + }, + { + h: 0, + s: 0, + b: 0.5, + name: 'gray' + }, + { + h: 0, + s: 0, + b: 1, + name: 'white' + }, + { + h: 0, + s: 0.5, + b: 0.5, + name: 'dark maroon' + }, + { + h: 0, + s: 0.5, + b: 1, + name: 'salmon pink' + }, + { + h: 0, + s: 1, + b: 0, + name: 'black' + }, + { + h: 0, + s: 1, + b: 0.5, + name: 'dark red' + }, + { + h: 0, + s: 1, + b: 1, + name: 'red' + }, + { + h: 5, + s: 0, + b: 1, + name: 'very light peach' + }, + { + h: 5, + s: 0.5, + b: 0.5, + name: 'brown' + }, + { + h: 5, + s: 0.5, + b: 1, + name: 'peach' + }, + { + h: 5, + s: 1, + b: 0.5, + name: 'brick red' + }, + { + h: 5, + s: 1, + b: 1, + name: 'crimson' + }, + { + h: 10, + s: 0, + b: 1, + name: 'light peach' + }, + { + h: 10, + s: 0.5, + b: 0.5, + name: 'brown' + }, + { + h: 10, + s: 0.5, + b: 1, + name: 'light orange' + }, + { + h: 10, + s: 1, + b: 0.5, + name: 'brown' + }, + { + h: 10, + s: 1, + b: 1, + name: 'orange' + }, + { + h: 15, + s: 0, + b: 1, + name: 'very light yellow' + }, + { + h: 15, + s: 0.5, + b: 0.5, + name: 'olive green' + }, + { + h: 15, + s: 0.5, + b: 1, + name: 'light yellow' + }, + { + h: 15, + s: 1, + b: 0, + name: 'dark olive green' + }, + { + h: 15, + s: 1, + b: 0.5, + name: 'olive green' + }, + { + h: 15, + s: 1, + b: 1, + name: 'yellow' + }, + { + h: 20, + s: 0, + b: 1, + name: 'very light yellow' + }, + { + h: 20, + s: 0.5, + b: 0.5, + name: 'olive green' + }, + { + h: 20, + s: 0.5, + b: 1, + name: 'light yellow green' + }, + { + h: 20, + s: 1, + b: 0, + name: 'dark olive green' + }, + { + h: 20, + s: 1, + b: 0.5, + name: 'dark yellow green' + }, + { + h: 20, + s: 1, + b: 1, + name: 'yellow green' + }, + { + h: 25, + s: 0.5, + b: 0.5, + name: 'dark yellow green' + }, + { + h: 25, + s: 0.5, + b: 1, + name: 'light green' + }, + { + h: 25, + s: 1, + b: 0.5, + name: 'dark green' + }, + { + h: 25, + s: 1, + b: 1, + name: 'green' + }, + { + h: 30, + s: 0.5, + b: 1, + name: 'light green' + }, + { + h: 30, + s: 1, + b: 0.5, + name: 'dark green' + }, + { + h: 30, + s: 1, + b: 1, + name: 'green' + }, + { + h: 35, + s: 0, + b: 0.5, + name: 'light green' + }, + { + h: 35, + s: 0, + b: 1, + name: 'very light green' + }, + { + h: 35, + s: 0.5, + b: 0.5, + name: 'dark green' + }, + { + h: 35, + s: 0.5, + b: 1, + name: 'light green' + }, + { + h: 35, + s: 1, + b: 0, + name: 'very dark green' + }, + { + h: 35, + s: 1, + b: 0.5, + name: 'dark green' + }, + { + h: 35, + s: 1, + b: 1, + name: 'green' + }, + { + h: 40, + s: 0, + b: 1, + name: 'very light green' + }, + { + h: 40, + s: 0.5, + b: 0.5, + name: 'dark green' + }, + { + h: 40, + s: 0.5, + b: 1, + name: 'light green' + }, + { + h: 40, + s: 1, + b: 0.5, + name: 'dark green' + }, + { + h: 40, + s: 1, + b: 1, + name: 'green' + }, + { + h: 45, + s: 0.5, + b: 1, + name: 'light turquoise' + }, + { + h: 45, + s: 1, + b: 0.5, + name: 'dark turquoise' + }, + { + h: 45, + s: 1, + b: 1, + name: 'turquoise' + }, + { + h: 50, + s: 0, + b: 1, + name: 'light sky blue' + }, + { + h: 50, + s: 0.5, + b: 0.5, + name: 'dark cyan' + }, + { + h: 50, + s: 0.5, + b: 1, + name: 'light cyan' + }, + { + h: 50, + s: 1, + b: 0.5, + name: 'dark cyan' + }, + { + h: 50, + s: 1, + b: 1, + name: 'cyan' + }, + { + h: 55, + s: 0, + b: 1, + name: 'light sky blue' + }, + { + h: 55, + s: 0.5, + b: 1, + name: 'light sky blue' + }, + { + h: 55, + s: 1, + b: 0.5, + name: 'dark blue' + }, + { + h: 55, + s: 1, + b: 1, + name: 'sky blue' + }, + { + h: 60, + s: 0, + b: 0.5, + name: 'gray' + }, + { + h: 60, + s: 0, + b: 1, + name: 'very light blue' + }, + { + h: 60, + s: 0.5, + b: 0.5, + name: 'blue' + }, + { + h: 60, + s: 0.5, + b: 1, + name: 'light blue' + }, + { + h: 60, + s: 1, + b: 0.5, + name: 'navy blue' + }, + { + h: 60, + s: 1, + b: 1, + name: 'blue' + }, + { + h: 65, + s: 0, + b: 1, + name: 'lavender' + }, + { + h: 65, + s: 0.5, + b: 0.5, + name: 'navy blue' + }, + { + h: 65, + s: 0.5, + b: 1, + name: 'light purple' + }, + { + h: 65, + s: 1, + b: 0.5, + name: 'dark navy blue' + }, + { + h: 65, + s: 1, + b: 1, + name: 'blue' + }, + { + h: 70, + s: 0, + b: 1, + name: 'lavender' + }, + { + h: 70, + s: 0.5, + b: 0.5, + name: 'navy blue' + }, + { + h: 70, + s: 0.5, + b: 1, + name: 'lavender blue' + }, + { + h: 70, + s: 1, + b: 0.5, + name: 'dark navy blue' + }, + { + h: 70, + s: 1, + b: 1, + name: 'blue' + }, + { + h: 75, + s: 0.5, + b: 1, + name: 'lavender' + }, + { + h: 75, + s: 1, + b: 0.5, + name: 'dark purple' + }, + { + h: 75, + s: 1, + b: 1, + name: 'purple' + }, + { + h: 80, + s: 0.5, + b: 1, + name: 'pinkish purple' + }, + { + h: 80, + s: 1, + b: 0.5, + name: 'dark purple' + }, + { + h: 80, + s: 1, + b: 1, + name: 'purple' + }, + { + h: 85, + s: 0, + b: 1, + name: 'light pink' + }, + { + h: 85, + s: 0.5, + b: 0.5, + name: 'purple' + }, + { + h: 85, + s: 0.5, + b: 1, + name: 'light fuchsia' + }, + { + h: 85, + s: 1, + b: 0.5, + name: 'dark fuchsia' + }, + { + h: 85, + s: 1, + b: 1, + name: 'fuchsia' + }, + { + h: 90, + s: 0.5, + b: 0.5, + name: 'dark fuchsia' + }, + { + h: 90, + s: 0.5, + b: 1, + name: 'hot pink' + }, + { + h: 90, + s: 1, + b: 0.5, + name: 'dark fuchsia' + }, + { + h: 90, + s: 1, + b: 1, + name: 'fuchsia' + }, + { + h: 95, + s: 0, + b: 1, + name: 'pink' + }, + { + h: 95, + s: 0.5, + b: 1, + name: 'light pink' + }, + { + h: 95, + s: 1, + b: 0.5, + name: 'dark magenta' + }, + { + h: 95, + s: 1, + b: 1, + name: 'magenta' } - //if hue only has two digits - if (hue.length === 2) { - hue[0] = parseInt(hue[0]); - //if last is greater than 7.5 - if (hue[last] >= 7.5) { - //add one to the tens + ]; + + //returns text with color name + function _calculateColor(hsb) { + let colortext; + //round hue + if (hsb[0] !== 0) { + hsb[0] = Math.round(hsb[0] * 100); + let hue = hsb[0].toString().split(''); + const last = hue.length - 1; + hue[last] = parseInt(hue[last]); + //if last digit of hue is < 2.5 make it 0 + if (hue[last] < 2.5) { hue[last] = 0; - hue[0] = hue[0] + 1; + //if last digit of hue is >= 2.5 and less than 7.5 make it 5 + } else if (hue[last] >= 2.5 && hue[last] < 7.5) { + hue[last] = 5; } - hsb[0] = hue[0] * 10 + hue[1]; - } else { - if (hue[last] >= 7.5) { - hsb[0] = 10; + //if hue only has two digits + if (hue.length === 2) { + hue[0] = parseInt(hue[0]); + //if last is greater than 7.5 + if (hue[last] >= 7.5) { + //add one to the tens + hue[last] = 0; + hue[0] = hue[0] + 1; + } + hsb[0] = hue[0] * 10 + hue[1]; } else { - hsb[0] = hue[last]; + if (hue[last] >= 7.5) { + hsb[0] = 10; + } else { + hsb[0] = hue[last]; + } } } - } - //map brightness from 0 to 1 - hsb[2] = hsb[2] / 255; - //round saturation and brightness - for (let i = hsb.length - 1; i >= 1; i--) { - if (hsb[i] <= 0.25) { - hsb[i] = 0; - } else if (hsb[i] > 0.25 && hsb[i] < 0.75) { - hsb[i] = 0.5; - } else { - hsb[i] = 1; - } - } - //after rounding, if the values are hue 0, saturation 0 and brightness 1 - //look at color exceptions which includes several tones from white to gray - if (hsb[0] === 0 && hsb[1] === 0 && hsb[2] === 1) { - //round original hsb values - for (let i = 2; i >= 0; i--) { - originalHSB[i] = Math.round(originalHSB[i] * 10000) / 10000; - } - //compare with the values in the colorExceptions array - for (let e = 0; e < colorExceptions.length; e++) { - if ( - colorExceptions[e].h === originalHSB[0] && - colorExceptions[e].s === originalHSB[1] && - colorExceptions[e].b === originalHSB[2] - ) { - colortext = colorExceptions[e].name; - break; + //map brightness from 0 to 1 + hsb[2] = hsb[2] / 255; + //round saturation and brightness + for (let i = hsb.length - 1; i >= 1; i--) { + if (hsb[i] <= 0.25) { + hsb[i] = 0; + } else if (hsb[i] > 0.25 && hsb[i] < 0.75) { + hsb[i] = 0.5; } else { - //if there is no match return white - colortext = 'white'; + hsb[i] = 1; } } - } else { - //otherwise, compare with values in colorLookUp - for (let i = 0; i < colorLookUp.length; i++) { - if ( - colorLookUp[i].h === hsb[0] && - colorLookUp[i].s === hsb[1] && - colorLookUp[i].b === hsb[2] - ) { - colortext = colorLookUp[i].name; - break; + //after rounding, if the values are hue 0, saturation 0 and brightness 1 + //look at color exceptions which includes several tones from white to gray + if (hsb[0] === 0 && hsb[1] === 0 && hsb[2] === 1) { + //round original hsb values + for (let i = 2; i >= 0; i--) { + originalHSB[i] = Math.round(originalHSB[i] * 10000) / 10000; + } + //compare with the values in the colorExceptions array + for (let e = 0; e < colorExceptions.length; e++) { + if ( + colorExceptions[e].h === originalHSB[0] && + colorExceptions[e].s === originalHSB[1] && + colorExceptions[e].b === originalHSB[2] + ) { + colortext = colorExceptions[e].name; + break; + } else { + //if there is no match return white + colortext = 'white'; + } + } + } else { + //otherwise, compare with values in colorLookUp + for (let i = 0; i < colorLookUp.length; i++) { + if ( + colorLookUp[i].h === hsb[0] && + colorLookUp[i].s === hsb[1] && + colorLookUp[i].b === hsb[2] + ) { + colortext = colorLookUp[i].name; + break; + } } } + return colortext; } - return colortext; + + //gets rgba and returs a color name + fn._rgbColorName = function(arg) { + //conversts rgba to hsb + let hsb = color_conversion._rgbaToHSBA(arg); + //stores hsb in global variable + originalHSB = hsb; + //calculate color name + return _calculateColor([hsb[0], hsb[1], hsb[2]]); + }; } -//gets rgba and returs a color name -p5.prototype._rgbColorName = function(arg) { - //conversts rgba to hsb - let hsb = color_conversion._rgbaToHSBA(arg); - //stores hsb in global variable - originalHSB = hsb; - //calculate color name - return _calculateColor([hsb[0], hsb[1], hsb[2]]); -}; +export default colorNamer; -export default p5; +if(typeof p5 !== 'undefined'){ + colorNamer(p5, p5.prototype); +} diff --git a/src/accessibility/describe.js b/src/accessibility/describe.js index 8037a0f39d..5173a101de 100644 --- a/src/accessibility/describe.js +++ b/src/accessibility/describe.js @@ -5,497 +5,502 @@ * @requires core */ -import p5 from '../core/main'; -const descContainer = '_Description'; //Fallback container -const fallbackDescId = '_fallbackDesc'; //Fallback description -const fallbackTableId = '_fallbackTable'; //Fallback Table -const fallbackTableElId = '_fte_'; //Fallback Table Element -const labelContainer = '_Label'; //Label container -const labelDescId = '_labelDesc'; //Label description -const labelTableId = '_labelTable'; //Label Table -const labelTableElId = '_lte_'; //Label Table Element +function describe(p5, fn){ + const descContainer = '_Description'; //Fallback container + const fallbackDescId = '_fallbackDesc'; //Fallback description + const fallbackTableId = '_fallbackTable'; //Fallback Table + const fallbackTableElId = '_fte_'; //Fallback Table Element + const labelContainer = '_Label'; //Label container + const labelDescId = '_labelDesc'; //Label description + const labelTableId = '_labelTable'; //Label Table + const labelTableElId = '_lte_'; //Label Table Element -/** - * Creates a screen reader-accessible description of the canvas. - * - * The first parameter, `text`, is the description of the canvas. - * - * The second parameter, `display`, is optional. It determines how the - * description is displayed. If `LABEL` is passed, as in - * `describe('A description.', LABEL)`, the description will be visible in - * a div element next to the canvas. If `FALLBACK` is passed, as in - * `describe('A description.', FALLBACK)`, the description will only be - * visible to screen readers. This is the default mode. - * - * Read - * Writing accessible canvas descriptions - * to learn more about making sketches accessible. - * - * @method describe - * @param {String} text description of the canvas. - * @param {(FALLBACK|LABEL)} [display] either LABEL or FALLBACK. - * - * @example - *
- * - * function setup() { - * background('pink'); - * - * // Draw a heart. - * fill('red'); - * noStroke(); - * circle(67, 67, 20); - * circle(83, 67, 20); - * triangle(91, 73, 75, 95, 59, 73); - * - * // Add a general description of the canvas. - * describe('A pink square with a red heart in the bottom-right corner.'); - * } - * - *
- * - *
- * - * function setup() { - * background('pink'); - * - * // Draw a heart. - * fill('red'); - * noStroke(); - * circle(67, 67, 20); - * circle(83, 67, 20); - * triangle(91, 73, 75, 95, 59, 73); - * - * // Add a general description of the canvas - * // and display it for debugging. - * describe('A pink square with a red heart in the bottom-right corner.', LABEL); - * } - * - *
- * - *
- * - * function draw() { - * background(200); - * - * // The expression - * // frameCount % 100 - * // causes x to increase from 0 - * // to 99, then restart from 0. - * let x = frameCount % 100; - * - * // Draw the circle. - * fill(0, 255, 0); - * circle(x, 50, 40); - * - * // Add a general description of the canvas. - * describe(`A green circle at (${x}, 50) moves from left to right on a gray square.`); - * } - * - *
- * - *
- * - * function draw() { - * background(200); - * - * // The expression - * // frameCount % 100 - * // causes x to increase from 0 - * // to 99, then restart from 0. - * let x = frameCount % 100; - * - * // Draw the circle. - * fill(0, 255, 0); - * circle(x, 50, 40); - * - * // Add a general description of the canvas - * // and display it for debugging. - * describe(`A green circle at (${x}, 50) moves from left to right on a gray square.`, LABEL); - * } - * - *
- */ + /** + * Creates a screen reader-accessible description of the canvas. + * + * The first parameter, `text`, is the description of the canvas. + * + * The second parameter, `display`, is optional. It determines how the + * description is displayed. If `LABEL` is passed, as in + * `describe('A description.', LABEL)`, the description will be visible in + * a div element next to the canvas. If `FALLBACK` is passed, as in + * `describe('A description.', FALLBACK)`, the description will only be + * visible to screen readers. This is the default mode. + * + * Read + * Writing accessible canvas descriptions + * to learn more about making sketches accessible. + * + * @method describe + * @param {String} text description of the canvas. + * @param {(FALLBACK|LABEL)} [display] either LABEL or FALLBACK. + * + * @example + *
+ * + * function setup() { + * background('pink'); + * + * // Draw a heart. + * fill('red'); + * noStroke(); + * circle(67, 67, 20); + * circle(83, 67, 20); + * triangle(91, 73, 75, 95, 59, 73); + * + * // Add a general description of the canvas. + * describe('A pink square with a red heart in the bottom-right corner.'); + * } + * + *
+ * + *
+ * + * function setup() { + * background('pink'); + * + * // Draw a heart. + * fill('red'); + * noStroke(); + * circle(67, 67, 20); + * circle(83, 67, 20); + * triangle(91, 73, 75, 95, 59, 73); + * + * // Add a general description of the canvas + * // and display it for debugging. + * describe('A pink square with a red heart in the bottom-right corner.', LABEL); + * } + * + *
+ * + *
+ * + * function draw() { + * background(200); + * + * // The expression + * // frameCount % 100 + * // causes x to increase from 0 + * // to 99, then restart from 0. + * let x = frameCount % 100; + * + * // Draw the circle. + * fill(0, 255, 0); + * circle(x, 50, 40); + * + * // Add a general description of the canvas. + * describe(`A green circle at (${x}, 50) moves from left to right on a gray square.`); + * } + * + *
+ * + *
+ * + * function draw() { + * background(200); + * + * // The expression + * // frameCount % 100 + * // causes x to increase from 0 + * // to 99, then restart from 0. + * let x = frameCount % 100; + * + * // Draw the circle. + * fill(0, 255, 0); + * circle(x, 50, 40); + * + * // Add a general description of the canvas + * // and display it for debugging. + * describe(`A green circle at (${x}, 50) moves from left to right on a gray square.`, LABEL); + * } + * + *
+ */ -p5.prototype.describe = function(text, display) { - p5._validateParameters('describe', arguments); - if (typeof text !== 'string') { - return; - } - const cnvId = this.canvas.id; - //calls function that adds punctuation for better screen reading - text = _descriptionText(text); - //if there is no dummyDOM - if (!this.dummyDOM) { - this.dummyDOM = document.getElementById(cnvId).parentNode; - } - if (!this.descriptions) { - this.descriptions = {}; - } - //check if html structure for description is ready - if (this.descriptions.fallback) { - //check if text is different from current description - if (this.descriptions.fallback.innerHTML !== text) { - //update description - this.descriptions.fallback.innerHTML = text; + fn.describe = function(text, display) { + p5._validateParameters('describe', arguments); + if (typeof text !== 'string') { + return; } - } else { - //create fallback html structure - this._describeHTML('fallback', text); - } - //if display is LABEL - if (display === this.LABEL) { - //check if html structure for label is ready - if (this.descriptions.label) { - //check if text is different from current label - if (this.descriptions.label.innerHTML !== text) { - //update label description - this.descriptions.label.innerHTML = text; + const cnvId = this.canvas.id; + //calls function that adds punctuation for better screen reading + text = _descriptionText(text); + //if there is no dummyDOM + if (!this.dummyDOM) { + this.dummyDOM = document.getElementById(cnvId).parentNode; + } + if (!this.descriptions) { + this.descriptions = {}; + } + //check if html structure for description is ready + if (this.descriptions.fallback) { + //check if text is different from current description + if (this.descriptions.fallback.innerHTML !== text) { + //update description + this.descriptions.fallback.innerHTML = text; } } else { - //create label html structure - this._describeHTML('label', text); + //create fallback html structure + this._describeHTML('fallback', text); } - } -}; + //if display is LABEL + if (display === this.LABEL) { + //check if html structure for label is ready + if (this.descriptions.label) { + //check if text is different from current label + if (this.descriptions.label.innerHTML !== text) { + //update label description + this.descriptions.label.innerHTML = text; + } + } else { + //create label html structure + this._describeHTML('label', text); + } + } + }; -/** - * Creates a screen reader-accessible description of elements in the canvas. - * - * Elements are shapes or groups of shapes that create meaning together. For - * example, a few overlapping circles could make an "eye" element. - * - * The first parameter, `name`, is the name of the element. - * - * The second parameter, `text`, is the description of the element. - * - * The third parameter, `display`, is optional. It determines how the - * description is displayed. If `LABEL` is passed, as in - * `describe('A description.', LABEL)`, the description will be visible in - * a div element next to the canvas. Using `LABEL` creates unhelpful - * duplicates for screen readers. Only use `LABEL` during development. If - * `FALLBACK` is passed, as in `describe('A description.', FALLBACK)`, the - * description will only be visible to screen readers. This is the default - * mode. - * - * Read - * Writing accessible canvas descriptions - * to learn more about making sketches accessible. - * - * @method describeElement - * @param {String} name name of the element. - * @param {String} text description of the element. - * @param {(FALLBACK|LABEL)} [display] either LABEL or FALLBACK. - * - * @example - *
- * - * function setup() { - * background('pink'); - * - * // Describe the first element - * // and draw it. - * describeElement('Circle', 'A yellow circle in the top-left corner.'); - * noStroke(); - * fill('yellow'); - * circle(25, 25, 40); - * - * // Describe the second element - * // and draw it. - * describeElement('Heart', 'A red heart in the bottom-right corner.'); - * fill('red'); - * circle(66.6, 66.6, 20); - * circle(83.2, 66.6, 20); - * triangle(91.2, 72.6, 75, 95, 58.6, 72.6); - * - * // Add a general description of the canvas. - * describe('A red heart and yellow circle over a pink background.'); - * } - * - *
- * - *
- * - * function setup() { - * background('pink'); - * - * // Describe the first element - * // and draw it. Display the - * // description for debugging. - * describeElement('Circle', 'A yellow circle in the top-left corner.', LABEL); - * noStroke(); - * fill('yellow'); - * circle(25, 25, 40); - * - * // Describe the second element - * // and draw it. Display the - * // description for debugging. - * describeElement('Heart', 'A red heart in the bottom-right corner.', LABEL); - * fill('red'); - * circle(66.6, 66.6, 20); - * circle(83.2, 66.6, 20); - * triangle(91.2, 72.6, 75, 95, 58.6, 72.6); - * - * // Add a general description of the canvas. - * describe('A red heart and yellow circle over a pink background.'); - * } - * - *
- */ + /** + * Creates a screen reader-accessible description of elements in the canvas. + * + * Elements are shapes or groups of shapes that create meaning together. For + * example, a few overlapping circles could make an "eye" element. + * + * The first parameter, `name`, is the name of the element. + * + * The second parameter, `text`, is the description of the element. + * + * The third parameter, `display`, is optional. It determines how the + * description is displayed. If `LABEL` is passed, as in + * `describe('A description.', LABEL)`, the description will be visible in + * a div element next to the canvas. Using `LABEL` creates unhelpful + * duplicates for screen readers. Only use `LABEL` during development. If + * `FALLBACK` is passed, as in `describe('A description.', FALLBACK)`, the + * description will only be visible to screen readers. This is the default + * mode. + * + * Read + * Writing accessible canvas descriptions + * to learn more about making sketches accessible. + * + * @method describeElement + * @param {String} name name of the element. + * @param {String} text description of the element. + * @param {(FALLBACK|LABEL)} [display] either LABEL or FALLBACK. + * + * @example + *
+ * + * function setup() { + * background('pink'); + * + * // Describe the first element + * // and draw it. + * describeElement('Circle', 'A yellow circle in the top-left corner.'); + * noStroke(); + * fill('yellow'); + * circle(25, 25, 40); + * + * // Describe the second element + * // and draw it. + * describeElement('Heart', 'A red heart in the bottom-right corner.'); + * fill('red'); + * circle(66.6, 66.6, 20); + * circle(83.2, 66.6, 20); + * triangle(91.2, 72.6, 75, 95, 58.6, 72.6); + * + * // Add a general description of the canvas. + * describe('A red heart and yellow circle over a pink background.'); + * } + * + *
+ * + *
+ * + * function setup() { + * background('pink'); + * + * // Describe the first element + * // and draw it. Display the + * // description for debugging. + * describeElement('Circle', 'A yellow circle in the top-left corner.', LABEL); + * noStroke(); + * fill('yellow'); + * circle(25, 25, 40); + * + * // Describe the second element + * // and draw it. Display the + * // description for debugging. + * describeElement('Heart', 'A red heart in the bottom-right corner.', LABEL); + * fill('red'); + * circle(66.6, 66.6, 20); + * circle(83.2, 66.6, 20); + * triangle(91.2, 72.6, 75, 95, 58.6, 72.6); + * + * // Add a general description of the canvas. + * describe('A red heart and yellow circle over a pink background.'); + * } + * + *
+ */ -p5.prototype.describeElement = function(name, text, display) { - p5._validateParameters('describeElement', arguments); - if (typeof text !== 'string' || typeof name !== 'string') { - return; - } - const cnvId = this.canvas.id; - //calls function that adds punctuation for better screen reading - text = _descriptionText(text); - //calls function that adds punctuation for better screen reading - let elementName = _elementName(name); - //remove any special characters from name to use it as html id - name = name.replace(/[^a-zA-Z0-9]/g, ''); + fn.describeElement = function(name, text, display) { + p5._validateParameters('describeElement', arguments); + if (typeof text !== 'string' || typeof name !== 'string') { + return; + } + const cnvId = this.canvas.id; + //calls function that adds punctuation for better screen reading + text = _descriptionText(text); + //calls function that adds punctuation for better screen reading + let elementName = _elementName(name); + //remove any special characters from name to use it as html id + name = name.replace(/[^a-zA-Z0-9]/g, ''); - //store element description - let inner = `${elementName}${text}`; - //if there is no dummyDOM - if (!this.dummyDOM) { - this.dummyDOM = document.getElementById(cnvId).parentNode; - } - if (!this.descriptions) { - this.descriptions = { fallbackElements: {} }; - } else if (!this.descriptions.fallbackElements) { - this.descriptions.fallbackElements = {}; - } - //check if html structure for element description is ready - if (this.descriptions.fallbackElements[name]) { - //if current element description is not the same as inner - if (this.descriptions.fallbackElements[name].innerHTML !== inner) { - //update element description - this.descriptions.fallbackElements[name].innerHTML = inner; + //store element description + let inner = `${elementName}${text}`; + //if there is no dummyDOM + if (!this.dummyDOM) { + this.dummyDOM = document.getElementById(cnvId).parentNode; } - } else { - //create fallback html structure - this._describeElementHTML('fallback', name, inner); - } - //if display is LABEL - if (display === this.LABEL) { - if (!this.descriptions.labelElements) { - this.descriptions.labelElements = {}; + if (!this.descriptions) { + this.descriptions = { fallbackElements: {} }; + } else if (!this.descriptions.fallbackElements) { + this.descriptions.fallbackElements = {}; } - //if html structure for label element description is ready - if (this.descriptions.labelElements[name]) { - //if label element description is different - if (this.descriptions.labelElements[name].innerHTML !== inner) { - //update label element description - this.descriptions.labelElements[name].innerHTML = inner; + //check if html structure for element description is ready + if (this.descriptions.fallbackElements[name]) { + //if current element description is not the same as inner + if (this.descriptions.fallbackElements[name].innerHTML !== inner) { + //update element description + this.descriptions.fallbackElements[name].innerHTML = inner; } } else { - //create label element html structure - this._describeElementHTML('label', name, inner); + //create fallback html structure + this._describeElementHTML('fallback', name, inner); } - } -}; + //if display is LABEL + if (display === this.LABEL) { + if (!this.descriptions.labelElements) { + this.descriptions.labelElements = {}; + } + //if html structure for label element description is ready + if (this.descriptions.labelElements[name]) { + //if label element description is different + if (this.descriptions.labelElements[name].innerHTML !== inner) { + //update label element description + this.descriptions.labelElements[name].innerHTML = inner; + } + } else { + //create label element html structure + this._describeElementHTML('label', name, inner); + } + } + }; -/* - * - * Helper functions for describe() and describeElement(). - * - */ + /* + * + * Helper functions for describe() and describeElement(). + * + */ -// check that text is not LABEL or FALLBACK and ensure text ends with punctuation mark -function _descriptionText(text) { - if (text === 'label' || text === 'fallback') { - throw new Error('description should not be LABEL or FALLBACK'); - } - //if string does not end with '.' - if ( - !text.endsWith('.') && - !text.endsWith(';') && - !text.endsWith(',') && - !text.endsWith('?') && - !text.endsWith('!') - ) { - //add '.' to the end of string - text = text + '.'; + // check that text is not LABEL or FALLBACK and ensure text ends with punctuation mark + function _descriptionText(text) { + if (text === 'label' || text === 'fallback') { + throw new Error('description should not be LABEL or FALLBACK'); + } + //if string does not end with '.' + if ( + !text.endsWith('.') && + !text.endsWith(';') && + !text.endsWith(',') && + !text.endsWith('?') && + !text.endsWith('!') + ) { + //add '.' to the end of string + text = text + '.'; + } + return text; } - return text; -} -/* - * Helper functions for describe() - */ + /* + * Helper functions for describe() + */ -//creates HTML structure for canvas descriptions -p5.prototype._describeHTML = function(type, text) { - const cnvId = this.canvas.id; - if (type === 'fallback') { - //if there is no description container - if (!this.dummyDOM.querySelector(`#${cnvId + descContainer}`)) { - //if there are no accessible outputs (see textOutput() and gridOutput()) - let html = `

`; - if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutput`)) { - //create description container +

for fallback description - this.dummyDOM.querySelector(`#${cnvId}`).innerHTML = html; + //creates HTML structure for canvas descriptions + fn._describeHTML = function(type, text) { + const cnvId = this.canvas.id; + if (type === 'fallback') { + //if there is no description container + if (!this.dummyDOM.querySelector(`#${cnvId + descContainer}`)) { + //if there are no accessible outputs (see textOutput() and gridOutput()) + let html = `

`; + if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutput`)) { + //create description container +

for fallback description + this.dummyDOM.querySelector(`#${cnvId}`).innerHTML = html; + } else { + //create description container +

for fallback description before outputs + this.dummyDOM + .querySelector(`#${cnvId}accessibleOutput`) + .insertAdjacentHTML('beforebegin', html); + } } else { - //create description container +

for fallback description before outputs + //if describeElement() has already created the container and added a table of elements + //create fallback description

before the table this.dummyDOM - .querySelector(`#${cnvId}accessibleOutput`) - .insertAdjacentHTML('beforebegin', html); + .querySelector('#' + cnvId + fallbackTableId) + .insertAdjacentHTML( + 'beforebegin', + `

` + ); } - } else { - //if describeElement() has already created the container and added a table of elements - //create fallback description

before the table - this.dummyDOM - .querySelector('#' + cnvId + fallbackTableId) - .insertAdjacentHTML( - 'beforebegin', - `

` - ); - } - //if the container for the description exists - this.descriptions.fallback = this.dummyDOM.querySelector( - `#${cnvId}${fallbackDescId}` - ); - this.descriptions.fallback.innerHTML = text; - return; - } else if (type === 'label') { - //if there is no label container - if (!this.dummyDOM.querySelector(`#${cnvId + labelContainer}`)) { - let html = `

`; - //if there are no accessible outputs (see textOutput() and gridOutput()) - if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutputLabel`)) { - //create label container +

for label description - this.dummyDOM - .querySelector('#' + cnvId) - .insertAdjacentHTML('afterend', html); - } else { - //create label container +

for label description before outputs + //if the container for the description exists + this.descriptions.fallback = this.dummyDOM.querySelector( + `#${cnvId}${fallbackDescId}` + ); + this.descriptions.fallback.innerHTML = text; + return; + } else if (type === 'label') { + //if there is no label container + if (!this.dummyDOM.querySelector(`#${cnvId + labelContainer}`)) { + let html = `

`; + //if there are no accessible outputs (see textOutput() and gridOutput()) + if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutputLabel`)) { + //create label container +

for label description + this.dummyDOM + .querySelector('#' + cnvId) + .insertAdjacentHTML('afterend', html); + } else { + //create label container +

for label description before outputs + this.dummyDOM + .querySelector(`#${cnvId}accessibleOutputLabel`) + .insertAdjacentHTML('beforebegin', html); + } + } else if (this.dummyDOM.querySelector(`#${cnvId + labelTableId}`)) { + //if describeElement() has already created the container and added a table of elements + //create label description

before the table this.dummyDOM - .querySelector(`#${cnvId}accessibleOutputLabel`) - .insertAdjacentHTML('beforebegin', html); + .querySelector(`#${cnvId + labelTableId}`) + .insertAdjacentHTML( + 'beforebegin', + `

` + ); } - } else if (this.dummyDOM.querySelector(`#${cnvId + labelTableId}`)) { - //if describeElement() has already created the container and added a table of elements - //create label description

before the table - this.dummyDOM - .querySelector(`#${cnvId + labelTableId}`) - .insertAdjacentHTML( - 'beforebegin', - `

` - ); + this.descriptions.label = this.dummyDOM.querySelector( + '#' + cnvId + labelDescId + ); + this.descriptions.label.innerHTML = text; + return; } - this.descriptions.label = this.dummyDOM.querySelector( - '#' + cnvId + labelDescId - ); - this.descriptions.label.innerHTML = text; - return; - } -}; + }; -/* - * Helper functions for describeElement(). - */ + /* + * Helper functions for describeElement(). + */ -//check that name is not LABEL or FALLBACK and ensure text ends with colon -function _elementName(name) { - if (name === 'label' || name === 'fallback') { - throw new Error('element name should not be LABEL or FALLBACK'); - } - //check if last character of string n is '.', ';', or ',' - if (name.endsWith('.') || name.endsWith(';') || name.endsWith(',')) { - //replace last character with ':' - name = name.replace(/.$/, ':'); - } else if (!name.endsWith(':')) { - //if string n does not end with ':' - //add ':'' at the end of string - name = name + ':'; + //check that name is not LABEL or FALLBACK and ensure text ends with colon + function _elementName(name) { + if (name === 'label' || name === 'fallback') { + throw new Error('element name should not be LABEL or FALLBACK'); + } + //check if last character of string n is '.', ';', or ',' + if (name.endsWith('.') || name.endsWith(';') || name.endsWith(',')) { + //replace last character with ':' + name = name.replace(/.$/, ':'); + } else if (!name.endsWith(':')) { + //if string n does not end with ':' + //add ':'' at the end of string + name = name + ':'; + } + return name; } - return name; -} -//creates HTML structure for element descriptions -p5.prototype._describeElementHTML = function(type, name, text) { - const cnvId = this.canvas.id; - if (type === 'fallback') { - //if there is no description container - if (!this.dummyDOM.querySelector(`#${cnvId + descContainer}`)) { - //if there are no accessible outputs (see textOutput() and gridOutput()) - let html = `
Canvas elements and their descriptions
`; - if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutput`)) { - //create container + table for element descriptions - this.dummyDOM.querySelector('#' + cnvId).innerHTML = html; - } else { - //create container + table for element descriptions before outputs + //creates HTML structure for element descriptions + fn._describeElementHTML = function(type, name, text) { + const cnvId = this.canvas.id; + if (type === 'fallback') { + //if there is no description container + if (!this.dummyDOM.querySelector(`#${cnvId + descContainer}`)) { + //if there are no accessible outputs (see textOutput() and gridOutput()) + let html = `
Canvas elements and their descriptions
`; + if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutput`)) { + //create container + table for element descriptions + this.dummyDOM.querySelector('#' + cnvId).innerHTML = html; + } else { + //create container + table for element descriptions before outputs + this.dummyDOM + .querySelector(`#${cnvId}accessibleOutput`) + .insertAdjacentHTML('beforebegin', html); + } + } else if (!this.dummyDOM.querySelector('#' + cnvId + fallbackTableId)) { + //if describe() has already created the container and added a description + //and there is no table create fallback table for element description after + //fallback description this.dummyDOM - .querySelector(`#${cnvId}accessibleOutput`) - .insertAdjacentHTML('beforebegin', html); + .querySelector('#' + cnvId + fallbackDescId) + .insertAdjacentHTML( + 'afterend', + `
Canvas elements and their descriptions
` + ); } - } else if (!this.dummyDOM.querySelector('#' + cnvId + fallbackTableId)) { - //if describe() has already created the container and added a description - //and there is no table create fallback table for element description after - //fallback description + //create a table row for the element + let tableRow = document.createElement('tr'); + tableRow.id = cnvId + fallbackTableElId + name; this.dummyDOM - .querySelector('#' + cnvId + fallbackDescId) - .insertAdjacentHTML( - 'afterend', - `
Canvas elements and their descriptions
` - ); - } - //create a table row for the element - let tableRow = document.createElement('tr'); - tableRow.id = cnvId + fallbackTableElId + name; - this.dummyDOM - .querySelector('#' + cnvId + fallbackTableId) - .appendChild(tableRow); - //update element description - this.descriptions.fallbackElements[name] = this.dummyDOM.querySelector( - `#${cnvId}${fallbackTableElId}${name}` - ); - this.descriptions.fallbackElements[name].innerHTML = text; - return; - } else if (type === 'label') { - //If display is LABEL creates a div adjacent to the canvas element with - //a table, a row header cell with the name of the elements, - //and adds the description of the element in adjacent cell. - //if there is no label description container - if (!this.dummyDOM.querySelector(`#${cnvId + labelContainer}`)) { - //if there are no accessible outputs (see textOutput() and gridOutput()) - let html = `
`; - if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutputLabel`)) { - //create container + table for element descriptions - this.dummyDOM - .querySelector('#' + cnvId) - .insertAdjacentHTML('afterend', html); - } else { - //create container + table for element descriptions before outputs + .querySelector('#' + cnvId + fallbackTableId) + .appendChild(tableRow); + //update element description + this.descriptions.fallbackElements[name] = this.dummyDOM.querySelector( + `#${cnvId}${fallbackTableElId}${name}` + ); + this.descriptions.fallbackElements[name].innerHTML = text; + return; + } else if (type === 'label') { + //If display is LABEL creates a div adjacent to the canvas element with + //a table, a row header cell with the name of the elements, + //and adds the description of the element in adjacent cell. + //if there is no label description container + if (!this.dummyDOM.querySelector(`#${cnvId + labelContainer}`)) { + //if there are no accessible outputs (see textOutput() and gridOutput()) + let html = `
`; + if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutputLabel`)) { + //create container + table for element descriptions + this.dummyDOM + .querySelector('#' + cnvId) + .insertAdjacentHTML('afterend', html); + } else { + //create container + table for element descriptions before outputs + this.dummyDOM + .querySelector(`#${cnvId}accessibleOutputLabel`) + .insertAdjacentHTML('beforebegin', html); + } + } else if (!this.dummyDOM.querySelector(`#${cnvId + labelTableId}`)) { + //if describe() has already created the label container and added a description + //and there is no table create label table for element description after + //label description this.dummyDOM - .querySelector(`#${cnvId}accessibleOutputLabel`) - .insertAdjacentHTML('beforebegin', html); + .querySelector('#' + cnvId + labelDescId) + .insertAdjacentHTML( + 'afterend', + `
` + ); } - } else if (!this.dummyDOM.querySelector(`#${cnvId + labelTableId}`)) { - //if describe() has already created the label container and added a description - //and there is no table create label table for element description after - //label description + //create a table row for the element label description + let tableRow = document.createElement('tr'); + tableRow.id = cnvId + labelTableElId + name; this.dummyDOM - .querySelector('#' + cnvId + labelDescId) - .insertAdjacentHTML( - 'afterend', - `
` - ); + .querySelector('#' + cnvId + labelTableId) + .appendChild(tableRow); + //update element label description + this.descriptions.labelElements[name] = this.dummyDOM.querySelector( + `#${cnvId}${labelTableElId}${name}` + ); + this.descriptions.labelElements[name].innerHTML = text; } - //create a table row for the element label description - let tableRow = document.createElement('tr'); - tableRow.id = cnvId + labelTableElId + name; - this.dummyDOM - .querySelector('#' + cnvId + labelTableId) - .appendChild(tableRow); - //update element label description - this.descriptions.labelElements[name] = this.dummyDOM.querySelector( - `#${cnvId}${labelTableElId}${name}` - ); - this.descriptions.labelElements[name].innerHTML = text; - } -}; + }; +} + +export default describe; -export default p5; +if(typeof p5 !== 'undefined'){ + describe(p5, p5.prototype); +} diff --git a/src/accessibility/gridOutput.js b/src/accessibility/gridOutput.js index 5b52a23eb8..a7e5513a41 100644 --- a/src/accessibility/gridOutput.js +++ b/src/accessibility/gridOutput.js @@ -4,151 +4,156 @@ * @for p5 * @requires core */ -import p5 from '../core/main'; -//the functions in this file support updating the grid output +function gridOutput(p5, fn){ + //the functions in this file support updating the grid output -//updates gridOutput -p5.prototype._updateGridOutput = function(idT) { - //if html structure is not there yet - if (!this.dummyDOM.querySelector(`#${idT}_summary`)) { - return; - } - let current = this._accessibleOutputs[idT]; - //create shape details list - let innerShapeDetails = _gridShapeDetails(idT, this.ingredients.shapes); - //create summary - let innerSummary = _gridSummary( - innerShapeDetails.numShapes, - this.ingredients.colors.background, - this.width, - this.height - ); - //create grid map - let innerMap = _gridMap(idT, this.ingredients.shapes); - //if it is different from current summary - if (innerSummary !== current.summary.innerHTML) { - //update - current.summary.innerHTML = innerSummary; - } - //if it is different from current map - if (innerMap !== current.map.innerHTML) { - //update - current.map.innerHTML = innerMap; - } - //if it is different from current shape details - if (innerShapeDetails.details !== current.shapeDetails.innerHTML) { - //update - current.shapeDetails.innerHTML = innerShapeDetails.details; - } - this._accessibleOutputs[idT] = current; -}; - -//creates spatial grid that maps the location of shapes -function _gridMap(idT, ingredients) { - let shapeNumber = 0; - let table = ''; - //create an array of arrays 10*10 of empty cells - let cells = Array.from(Array(10), () => Array(10)); - for (let x in ingredients) { - for (let y in ingredients[x]) { - let fill; - if (x !== 'line') { - fill = `${ - ingredients[x][y].color - } ${x}`; - } else { - fill = `${ - ingredients[x][y].color - } ${x} midpoint`; - } + //updates gridOutput + fn._updateGridOutput = function(idT) { + //if html structure is not there yet + if (!this.dummyDOM.querySelector(`#${idT}_summary`)) { + return; + } + let current = this._accessibleOutputs[idT]; + //create shape details list + let innerShapeDetails = _gridShapeDetails(idT, this.ingredients.shapes); + //create summary + let innerSummary = _gridSummary( + innerShapeDetails.numShapes, + this.ingredients.colors.background, + this.width, + this.height + ); + //create grid map + let innerMap = _gridMap(idT, this.ingredients.shapes); + //if it is different from current summary + if (innerSummary !== current.summary.innerHTML) { + //update + current.summary.innerHTML = innerSummary; + } + //if it is different from current map + if (innerMap !== current.map.innerHTML) { + //update + current.map.innerHTML = innerMap; + } + //if it is different from current shape details + if (innerShapeDetails.details !== current.shapeDetails.innerHTML) { + //update + current.shapeDetails.innerHTML = innerShapeDetails.details; + } + this._accessibleOutputs[idT] = current; + }; - // Check if shape is in canvas, skip if not - if( - ingredients[x][y].loc.locY < cells.length && - ingredients[x][y].loc.locX < cells[ingredients[x][y].loc.locY].length - ){ - //if empty cell of location of shape is undefined - if (!cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX]) { - //fill it with shape info - cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX] = fill; - //if a shape is already in that location + //creates spatial grid that maps the location of shapes + function _gridMap(idT, ingredients) { + let shapeNumber = 0; + let table = ''; + //create an array of arrays 10*10 of empty cells + let cells = Array.from(Array(10), () => Array(10)); + for (let x in ingredients) { + for (let y in ingredients[x]) { + let fill; + if (x !== 'line') { + fill = `${ + ingredients[x][y].color + } ${x}`; } else { - //add it - cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX] = - cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX] + - ' ' + - fill; + fill = `${ + ingredients[x][y].color + } ${x} midpoint`; + } + + // Check if shape is in canvas, skip if not + if( + ingredients[x][y].loc.locY < cells.length && + ingredients[x][y].loc.locX < cells[ingredients[x][y].loc.locY].length + ){ + //if empty cell of location of shape is undefined + if (!cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX]) { + //fill it with shape info + cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX] = fill; + //if a shape is already in that location + } else { + //add it + cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX] = + cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX] + + ' ' + + fill; + } + shapeNumber++; } - shapeNumber++; } } - } - //make table based on array - for (let _r in cells) { - let row = ''; - for (let c in cells[_r]) { - row = row + ''; - if (cells[_r][c] !== undefined) { - row = row + cells[_r][c]; + //make table based on array + for (let _r in cells) { + let row = ''; + for (let c in cells[_r]) { + row = row + ''; + if (cells[_r][c] !== undefined) { + row = row + cells[_r][c]; + } + row = row + ''; } - row = row + ''; + table = table + row + ''; } - table = table + row + ''; + return table; } - return table; -} -//creates grid summary -function _gridSummary(numShapes, background, width, height) { - let text = `${background} canvas, ${width} by ${height} pixels, contains ${ - numShapes[0] - }`; - if (numShapes[0] === 1) { - text = `${text} shape: ${numShapes[1]}`; - } else { - text = `${text} shapes: ${numShapes[1]}`; + //creates grid summary + function _gridSummary(numShapes, background, width, height) { + let text = `${background} canvas, ${width} by ${height} pixels, contains ${ + numShapes[0] + }`; + if (numShapes[0] === 1) { + text = `${text} shape: ${numShapes[1]}`; + } else { + text = `${text} shapes: ${numShapes[1]}`; + } + return text; } - return text; -} -//creates list of shapes -function _gridShapeDetails(idT, ingredients) { - let shapeDetails = ''; - let shapes = ''; - let totalShapes = 0; - //goes trhough every shape type in ingredients - for (let x in ingredients) { - let shapeNum = 0; - for (let y in ingredients[x]) { - //it creates a line in a list - let line = `
  • ${ - ingredients[x][y].color - } ${x},`; - if (x === 'line') { - line = - line + - ` location = ${ingredients[x][y].pos}, length = ${ - ingredients[x][y].length - } pixels`; - } else { - line = line + ` location = ${ingredients[x][y].pos}`; - if (x !== 'point') { - line = line + `, area = ${ingredients[x][y].area} %`; + //creates list of shapes + function _gridShapeDetails(idT, ingredients) { + let shapeDetails = ''; + let shapes = ''; + let totalShapes = 0; + //goes trhough every shape type in ingredients + for (let x in ingredients) { + let shapeNum = 0; + for (let y in ingredients[x]) { + //it creates a line in a list + let line = `
  • ${ + ingredients[x][y].color + } ${x},`; + if (x === 'line') { + line = + line + + ` location = ${ingredients[x][y].pos}, length = ${ + ingredients[x][y].length + } pixels`; + } else { + line = line + ` location = ${ingredients[x][y].pos}`; + if (x !== 'point') { + line = line + `, area = ${ingredients[x][y].area} %`; + } + line = line + '
  • '; } - line = line + ''; + shapeDetails = shapeDetails + line; + shapeNum++; + totalShapes++; + } + if (shapeNum > 1) { + shapes = `${shapes} ${shapeNum} ${x}s`; + } else { + shapes = `${shapes} ${shapeNum} ${x}`; } - shapeDetails = shapeDetails + line; - shapeNum++; - totalShapes++; - } - if (shapeNum > 1) { - shapes = `${shapes} ${shapeNum} ${x}s`; - } else { - shapes = `${shapes} ${shapeNum} ${x}`; } + return { numShapes: [totalShapes, shapes], details: shapeDetails }; } - return { numShapes: [totalShapes, shapes], details: shapeDetails }; } -export default p5; +export default gridOutput; + +if(typeof p5 !== 'undefined'){ + gridOutput(p5, p5.prototype); +} diff --git a/src/accessibility/index.js b/src/accessibility/index.js new file mode 100644 index 0000000000..4469b4fdef --- /dev/null +++ b/src/accessibility/index.js @@ -0,0 +1,13 @@ +import describe from './describe.js'; +import gridOutput from './gridOutput.js'; +import textOutput from './textOutput.js'; +import outputs from './outputs.js'; +import colorNamer from './color_namer.js'; + +export default function(p5){ + p5.registerAddon(describe); + p5.registerAddon(gridOutput); + p5.registerAddon(textOutput); + p5.registerAddon(outputs); + p5.registerAddon(colorNamer); +} diff --git a/src/accessibility/outputs.js b/src/accessibility/outputs.js index 9165a61c65..450679abe0 100644 --- a/src/accessibility/outputs.js +++ b/src/accessibility/outputs.js @@ -5,686 +5,690 @@ * @requires core */ -import p5 from '../core/main'; +function outputs(p5, fn){ + /** + * Creates a screen reader-accessible description of shapes on the canvas. + * + * `textOutput()` adds a general description, list of shapes, and + * table of shapes to the web page. The general description includes the + * canvas size, canvas color, and number of shapes. For example, + * `Your output is a, 100 by 100 pixels, gray canvas containing the following 2 shapes:`. + * + * A list of shapes follows the general description. The list describes the + * color, location, and area of each shape. For example, + * `a red circle at middle covering 3% of the canvas`. Each shape can be + * selected to get more details. + * + * `textOutput()` uses its table of shapes as a list. The table describes the + * shape, color, location, coordinates and area. For example, + * `red circle location = middle area = 3%`. This is different from + * gridOutput(), which uses its table as a grid. + * + * The `display` parameter is optional. It determines how the description is + * displayed. If `LABEL` is passed, as in `textOutput(LABEL)`, the description + * will be visible in a div element next to the canvas. Using `LABEL` creates + * unhelpful duplicates for screen readers. Only use `LABEL` during + * development. If `FALLBACK` is passed, as in `textOutput(FALLBACK)`, the + * description will only be visible to screen readers. This is the default + * mode. + * + * Read + * Writing accessible canvas descriptions + * to learn more about making sketches accessible. + * + * @method textOutput + * @param {(FALLBACK|LABEL)} [display] either FALLBACK or LABEL. + * + * @example + *
    + * + * function setup() { + * // Add the text description. + * textOutput(); + * + * // Draw a couple of shapes. + * background(200); + * fill(255, 0, 0); + * circle(20, 20, 20); + * fill(0, 0, 255); + * square(50, 50, 50); + * + * // Add a general description of the canvas. + * describe('A red circle and a blue square on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * // Add the text description and + * // display it for debugging. + * textOutput(LABEL); + * + * // Draw a couple of shapes. + * background(200); + * fill(255, 0, 0); + * circle(20, 20, 20); + * fill(0, 0, 255); + * square(50, 50, 50); + * + * // Add a general description of the canvas. + * describe('A red circle and a blue square on a gray background.'); + * } + * + *
    + * + *
    + * + * function draw() { + * // Add the text description. + * textOutput(); + * + * // Draw a moving circle. + * background(200); + * let x = frameCount * 0.1; + * fill(255, 0, 0); + * circle(x, 20, 20); + * fill(0, 0, 255); + * square(50, 50, 50); + * + * // Add a general description of the canvas. + * describe('A red circle moves from left to right above a blue square.'); + * } + * + *
    + * + *
    + * + * function draw() { + * // Add the text description and + * // display it for debugging. + * textOutput(LABEL); + * + * // Draw a moving circle. + * background(200); + * let x = frameCount * 0.1; + * fill(255, 0, 0); + * circle(x, 20, 20); + * fill(0, 0, 255); + * square(50, 50, 50); + * + * // Add a general description of the canvas. + * describe('A red circle moves from left to right above a blue square.'); + * } + * + *
    + */ -/** - * Creates a screen reader-accessible description of shapes on the canvas. - * - * `textOutput()` adds a general description, list of shapes, and - * table of shapes to the web page. The general description includes the - * canvas size, canvas color, and number of shapes. For example, - * `Your output is a, 100 by 100 pixels, gray canvas containing the following 2 shapes:`. - * - * A list of shapes follows the general description. The list describes the - * color, location, and area of each shape. For example, - * `a red circle at middle covering 3% of the canvas`. Each shape can be - * selected to get more details. - * - * `textOutput()` uses its table of shapes as a list. The table describes the - * shape, color, location, coordinates and area. For example, - * `red circle location = middle area = 3%`. This is different from - * gridOutput(), which uses its table as a grid. - * - * The `display` parameter is optional. It determines how the description is - * displayed. If `LABEL` is passed, as in `textOutput(LABEL)`, the description - * will be visible in a div element next to the canvas. Using `LABEL` creates - * unhelpful duplicates for screen readers. Only use `LABEL` during - * development. If `FALLBACK` is passed, as in `textOutput(FALLBACK)`, the - * description will only be visible to screen readers. This is the default - * mode. - * - * Read - * Writing accessible canvas descriptions - * to learn more about making sketches accessible. - * - * @method textOutput - * @param {(FALLBACK|LABEL)} [display] either FALLBACK or LABEL. - * - * @example - *
    - * - * function setup() { - * // Add the text description. - * textOutput(); - * - * // Draw a couple of shapes. - * background(200); - * fill(255, 0, 0); - * circle(20, 20, 20); - * fill(0, 0, 255); - * square(50, 50, 50); - * - * // Add a general description of the canvas. - * describe('A red circle and a blue square on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * // Add the text description and - * // display it for debugging. - * textOutput(LABEL); - * - * // Draw a couple of shapes. - * background(200); - * fill(255, 0, 0); - * circle(20, 20, 20); - * fill(0, 0, 255); - * square(50, 50, 50); - * - * // Add a general description of the canvas. - * describe('A red circle and a blue square on a gray background.'); - * } - * - *
    - * - *
    - * - * function draw() { - * // Add the text description. - * textOutput(); - * - * // Draw a moving circle. - * background(200); - * let x = frameCount * 0.1; - * fill(255, 0, 0); - * circle(x, 20, 20); - * fill(0, 0, 255); - * square(50, 50, 50); - * - * // Add a general description of the canvas. - * describe('A red circle moves from left to right above a blue square.'); - * } - * - *
    - * - *
    - * - * function draw() { - * // Add the text description and - * // display it for debugging. - * textOutput(LABEL); - * - * // Draw a moving circle. - * background(200); - * let x = frameCount * 0.1; - * fill(255, 0, 0); - * circle(x, 20, 20); - * fill(0, 0, 255); - * square(50, 50, 50); - * - * // Add a general description of the canvas. - * describe('A red circle moves from left to right above a blue square.'); - * } - * - *
    - */ - -p5.prototype.textOutput = function(display) { - p5._validateParameters('textOutput', arguments); - //if textOutput is already true - if (this._accessibleOutputs.text) { - return; - } else { - //make textOutput true - this._accessibleOutputs.text = true; - //create output for fallback - this._createOutput('textOutput', 'Fallback'); - if (display === this.LABEL) { - //make textOutput label true - this._accessibleOutputs.textLabel = true; - //create output for label - this._createOutput('textOutput', 'Label'); + fn.textOutput = function(display) { + p5._validateParameters('textOutput', arguments); + //if textOutput is already true + if (this._accessibleOutputs.text) { + return; + } else { + //make textOutput true + this._accessibleOutputs.text = true; + //create output for fallback + this._createOutput('textOutput', 'Fallback'); + if (display === this.LABEL) { + //make textOutput label true + this._accessibleOutputs.textLabel = true; + //create output for label + this._createOutput('textOutput', 'Label'); + } } - } -}; + }; -/** - * Creates a screen reader-accessible description of shapes on the canvas. - * - * `gridOutput()` adds a general description, table of shapes, and list of - * shapes to the web page. The general description includes the canvas size, - * canvas color, and number of shapes. For example, - * `gray canvas, 100 by 100 pixels, contains 2 shapes: 1 circle 1 square`. - * - * `gridOutput()` uses its table of shapes as a grid. Each shape in the grid - * is placed in a cell whose row and column correspond to the shape's location - * on the canvas. The grid cells describe the color and type of shape at that - * location. For example, `red circle`. These descriptions can be selected - * individually to get more details. This is different from - * textOutput(), which uses its table as a list. - * - * A list of shapes follows the table. The list describes the color, type, - * location, and area of each shape. For example, - * `red circle, location = middle, area = 3 %`. - * - * The `display` parameter is optional. It determines how the description is - * displayed. If `LABEL` is passed, as in `gridOutput(LABEL)`, the description - * will be visible in a div element next to the canvas. Using `LABEL` creates - * unhelpful duplicates for screen readers. Only use `LABEL` during - * development. If `FALLBACK` is passed, as in `gridOutput(FALLBACK)`, the - * description will only be visible to screen readers. This is the default - * mode. - * - * Read - * Writing accessible canvas descriptions - * to learn more about making sketches accessible. - * - * @method gridOutput - * @param {(FALLBACK|LABEL)} [display] either FALLBACK or LABEL. - * - * @example - *
    - * - * function setup() { - * // Add the grid description. - * gridOutput(); - * - * // Draw a couple of shapes. - * background(200); - * fill(255, 0, 0); - * circle(20, 20, 20); - * fill(0, 0, 255); - * square(50, 50, 50); - * - * // Add a general description of the canvas. - * describe('A red circle and a blue square on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * // Add the grid description and - * // display it for debugging. - * gridOutput(LABEL); - * - * // Draw a couple of shapes. - * background(200); - * fill(255, 0, 0); - * circle(20, 20, 20); - * fill(0, 0, 255); - * square(50, 50, 50); - * - * // Add a general description of the canvas. - * describe('A red circle and a blue square on a gray background.'); - * } - * - *
    - * - *
    - * - * function draw() { - * // Add the grid description. - * gridOutput(); - * - * // Draw a moving circle. - * background(200); - * let x = frameCount * 0.1; - * fill(255, 0, 0); - * circle(x, 20, 20); - * fill(0, 0, 255); - * square(50, 50, 50); - * - * // Add a general description of the canvas. - * describe('A red circle moves from left to right above a blue square.'); - * } - * - *
    - * - *
    - * - * function draw() { - * // Add the grid description and - * // display it for debugging. - * gridOutput(LABEL); - * - * // Draw a moving circle. - * background(200); - * let x = frameCount * 0.1; - * fill(255, 0, 0); - * circle(x, 20, 20); - * fill(0, 0, 255); - * square(50, 50, 50); - * - * // Add a general description of the canvas. - * describe('A red circle moves from left to right above a blue square.'); - * } - * - *
    - */ + /** + * Creates a screen reader-accessible description of shapes on the canvas. + * + * `gridOutput()` adds a general description, table of shapes, and list of + * shapes to the web page. The general description includes the canvas size, + * canvas color, and number of shapes. For example, + * `gray canvas, 100 by 100 pixels, contains 2 shapes: 1 circle 1 square`. + * + * `gridOutput()` uses its table of shapes as a grid. Each shape in the grid + * is placed in a cell whose row and column correspond to the shape's location + * on the canvas. The grid cells describe the color and type of shape at that + * location. For example, `red circle`. These descriptions can be selected + * individually to get more details. This is different from + * textOutput(), which uses its table as a list. + * + * A list of shapes follows the table. The list describes the color, type, + * location, and area of each shape. For example, + * `red circle, location = middle, area = 3 %`. + * + * The `display` parameter is optional. It determines how the description is + * displayed. If `LABEL` is passed, as in `gridOutput(LABEL)`, the description + * will be visible in a div element next to the canvas. Using `LABEL` creates + * unhelpful duplicates for screen readers. Only use `LABEL` during + * development. If `FALLBACK` is passed, as in `gridOutput(FALLBACK)`, the + * description will only be visible to screen readers. This is the default + * mode. + * + * Read + * Writing accessible canvas descriptions + * to learn more about making sketches accessible. + * + * @method gridOutput + * @param {(FALLBACK|LABEL)} [display] either FALLBACK or LABEL. + * + * @example + *
    + * + * function setup() { + * // Add the grid description. + * gridOutput(); + * + * // Draw a couple of shapes. + * background(200); + * fill(255, 0, 0); + * circle(20, 20, 20); + * fill(0, 0, 255); + * square(50, 50, 50); + * + * // Add a general description of the canvas. + * describe('A red circle and a blue square on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * // Add the grid description and + * // display it for debugging. + * gridOutput(LABEL); + * + * // Draw a couple of shapes. + * background(200); + * fill(255, 0, 0); + * circle(20, 20, 20); + * fill(0, 0, 255); + * square(50, 50, 50); + * + * // Add a general description of the canvas. + * describe('A red circle and a blue square on a gray background.'); + * } + * + *
    + * + *
    + * + * function draw() { + * // Add the grid description. + * gridOutput(); + * + * // Draw a moving circle. + * background(200); + * let x = frameCount * 0.1; + * fill(255, 0, 0); + * circle(x, 20, 20); + * fill(0, 0, 255); + * square(50, 50, 50); + * + * // Add a general description of the canvas. + * describe('A red circle moves from left to right above a blue square.'); + * } + * + *
    + * + *
    + * + * function draw() { + * // Add the grid description and + * // display it for debugging. + * gridOutput(LABEL); + * + * // Draw a moving circle. + * background(200); + * let x = frameCount * 0.1; + * fill(255, 0, 0); + * circle(x, 20, 20); + * fill(0, 0, 255); + * square(50, 50, 50); + * + * // Add a general description of the canvas. + * describe('A red circle moves from left to right above a blue square.'); + * } + * + *
    + */ -p5.prototype.gridOutput = function(display) { - p5._validateParameters('gridOutput', arguments); - //if gridOutput is already true - if (this._accessibleOutputs.grid) { - return; - } else { - //make gridOutput true - this._accessibleOutputs.grid = true; - //create output for fallback - this._createOutput('gridOutput', 'Fallback'); - if (display === this.LABEL) { - //make gridOutput label true - this._accessibleOutputs.gridLabel = true; - //create output for label - this._createOutput('gridOutput', 'Label'); + fn.gridOutput = function(display) { + p5._validateParameters('gridOutput', arguments); + //if gridOutput is already true + if (this._accessibleOutputs.grid) { + return; + } else { + //make gridOutput true + this._accessibleOutputs.grid = true; + //create output for fallback + this._createOutput('gridOutput', 'Fallback'); + if (display === this.LABEL) { + //make gridOutput label true + this._accessibleOutputs.gridLabel = true; + //create output for label + this._createOutput('gridOutput', 'Label'); + } } - } -}; + }; -//helper function returns true when accessible outputs are true -p5.prototype._addAccsOutput = function() { - //if there are no accessible outputs create object with all false - if (!this._accessibleOutputs) { - this._accessibleOutputs = { - text: false, - grid: false, - textLabel: false, - gridLabel: false - }; - } - return this._accessibleOutputs.grid || this._accessibleOutputs.text; -}; + //helper function returns true when accessible outputs are true + fn._addAccsOutput = function() { + //if there are no accessible outputs create object with all false + if (!this._accessibleOutputs) { + this._accessibleOutputs = { + text: false, + grid: false, + textLabel: false, + gridLabel: false + }; + } + return this._accessibleOutputs.grid || this._accessibleOutputs.text; + }; -//helper function that creates html structure for accessible outputs -p5.prototype._createOutput = function(type, display) { - let cnvId = this.canvas.id; - //if there are no ingredients create object. this object stores data for the outputs - if (!this.ingredients) { - this.ingredients = { - shapes: {}, - colors: { background: 'white', fill: 'white', stroke: 'black' }, - pShapes: '', - pBackground: '' - }; - } - //if there is no dummyDOM create it - if (!this.dummyDOM) { - this.dummyDOM = document.getElementById(cnvId).parentNode; - } - let cIdT, container, inner; - let query = ''; - if (display === 'Fallback') { - cIdT = cnvId + type; - container = cnvId + 'accessibleOutput'; - if (!this.dummyDOM.querySelector(`#${container}`)) { - //if there is no canvas description (see describe() and describeElement()) - if (!this.dummyDOM.querySelector(`#${cnvId}_Description`)) { - //create html structure inside of canvas - this.dummyDOM.querySelector( - `#${cnvId}` - ).innerHTML = `
    `; - } else { - //create html structure after canvas description container - this.dummyDOM - .querySelector(`#${cnvId}_Description`) - .insertAdjacentHTML( - 'afterend', - `
    ` - ); + //helper function that creates html structure for accessible outputs + fn._createOutput = function(type, display) { + let cnvId = this.canvas.id; + //if there are no ingredients create object. this object stores data for the outputs + if (!this.ingredients) { + this.ingredients = { + shapes: {}, + colors: { background: 'white', fill: 'white', stroke: 'black' }, + pShapes: '', + pBackground: '' + }; + } + //if there is no dummyDOM create it + if (!this.dummyDOM) { + this.dummyDOM = document.getElementById(cnvId).parentNode; + } + let cIdT, container, inner; + let query = ''; + if (display === 'Fallback') { + cIdT = cnvId + type; + container = cnvId + 'accessibleOutput'; + if (!this.dummyDOM.querySelector(`#${container}`)) { + //if there is no canvas description (see describe() and describeElement()) + if (!this.dummyDOM.querySelector(`#${cnvId}_Description`)) { + //create html structure inside of canvas + this.dummyDOM.querySelector( + `#${cnvId}` + ).innerHTML = `
    `; + } else { + //create html structure after canvas description container + this.dummyDOM + .querySelector(`#${cnvId}_Description`) + .insertAdjacentHTML( + 'afterend', + `
    ` + ); + } + } + } else if (display === 'Label') { + query = display; + cIdT = cnvId + type + display; + container = cnvId + 'accessibleOutput' + display; + if (!this.dummyDOM.querySelector(`#${container}`)) { + //if there is no canvas description label (see describe() and describeElement()) + if (!this.dummyDOM.querySelector(`#${cnvId}_Label`)) { + //create html structure adjacent to canvas + this.dummyDOM + .querySelector(`#${cnvId}`) + .insertAdjacentHTML('afterend', `
    `); + } else { + //create html structure after canvas label + this.dummyDOM + .querySelector(`#${cnvId}_Label`) + .insertAdjacentHTML('afterend', `
    `); + } } } - } else if (display === 'Label') { - query = display; - cIdT = cnvId + type + display; - container = cnvId + 'accessibleOutput' + display; - if (!this.dummyDOM.querySelector(`#${container}`)) { - //if there is no canvas description label (see describe() and describeElement()) - if (!this.dummyDOM.querySelector(`#${cnvId}_Label`)) { - //create html structure adjacent to canvas + //create an object to store the latest output. this object is used in _updateTextOutput() and _updateGridOutput() + this._accessibleOutputs[cIdT] = {}; + if (type === 'textOutput') { + query = `#${cnvId}gridOutput${query}`; //query is used to check if gridOutput already exists + inner = `
    Text Output

    `; + //if gridOutput already exists + if (this.dummyDOM.querySelector(query)) { + //create textOutput before gridOutput this.dummyDOM - .querySelector(`#${cnvId}`) - .insertAdjacentHTML('afterend', `
    `); + .querySelector(query) + .insertAdjacentHTML('beforebegin', inner); } else { - //create html structure after canvas label - this.dummyDOM - .querySelector(`#${cnvId}_Label`) - .insertAdjacentHTML('afterend', `
    `); + //create output inside of container + this.dummyDOM.querySelector(`#${container}`).innerHTML = inner; } + //store output html elements + this._accessibleOutputs[cIdT].list = this.dummyDOM.querySelector( + `#${cIdT}_list` + ); + } else if (type === 'gridOutput') { + query = `#${cnvId}textOutput${query}`; //query is used to check if textOutput already exists + inner = `
    Grid Output

    `; + //if textOutput already exists + if (this.dummyDOM.querySelector(query)) { + //create gridOutput after textOutput + this.dummyDOM.querySelector(query).insertAdjacentHTML('afterend', inner); + } else { + //create output inside of container + this.dummyDOM.querySelector(`#${container}`).innerHTML = inner; + } + //store output html elements + this._accessibleOutputs[cIdT].map = this.dummyDOM.querySelector( + `#${cIdT}_map` + ); } - } - //create an object to store the latest output. this object is used in _updateTextOutput() and _updateGridOutput() - this._accessibleOutputs[cIdT] = {}; - if (type === 'textOutput') { - query = `#${cnvId}gridOutput${query}`; //query is used to check if gridOutput already exists - inner = `
    Text Output

    `; - //if gridOutput already exists - if (this.dummyDOM.querySelector(query)) { - //create textOutput before gridOutput - this.dummyDOM - .querySelector(query) - .insertAdjacentHTML('beforebegin', inner); - } else { - //create output inside of container - this.dummyDOM.querySelector(`#${container}`).innerHTML = inner; - } - //store output html elements - this._accessibleOutputs[cIdT].list = this.dummyDOM.querySelector( - `#${cIdT}_list` + this._accessibleOutputs[cIdT].shapeDetails = this.dummyDOM.querySelector( + `#${cIdT}_shapeDetails` ); - } else if (type === 'gridOutput') { - query = `#${cnvId}textOutput${query}`; //query is used to check if textOutput already exists - inner = `
    Grid Output

    `; - //if textOutput already exists - if (this.dummyDOM.querySelector(query)) { - //create gridOutput after textOutput - this.dummyDOM.querySelector(query).insertAdjacentHTML('afterend', inner); - } else { - //create output inside of container - this.dummyDOM.querySelector(`#${container}`).innerHTML = inner; - } - //store output html elements - this._accessibleOutputs[cIdT].map = this.dummyDOM.querySelector( - `#${cIdT}_map` + this._accessibleOutputs[cIdT].summary = this.dummyDOM.querySelector( + `#${cIdT}_summary` ); - } - this._accessibleOutputs[cIdT].shapeDetails = this.dummyDOM.querySelector( - `#${cIdT}_shapeDetails` - ); - this._accessibleOutputs[cIdT].summary = this.dummyDOM.querySelector( - `#${cIdT}_summary` - ); -}; + }; + + //this function is called at the end of setup and draw if using + //accessibleOutputs and calls update functions of outputs + fn._updateAccsOutput = function() { + let cnvId = this.canvas.id; + //if the shapes are not the same as before + if ( + JSON.stringify(this.ingredients.shapes) !== this.ingredients.pShapes || + this.ingredients.colors.background !== this.ingredients.pBackground + ) { + //save current shapes as string in pShapes + this.ingredients.pShapes = JSON.stringify(this.ingredients.shapes); + if (this._accessibleOutputs.text) { + this._updateTextOutput(cnvId + 'textOutput'); + } + if (this._accessibleOutputs.grid) { + this._updateGridOutput(cnvId + 'gridOutput'); + } + if (this._accessibleOutputs.textLabel) { + this._updateTextOutput(cnvId + 'textOutputLabel'); + } + if (this._accessibleOutputs.gridLabel) { + this._updateGridOutput(cnvId + 'gridOutputLabel'); + } + } + }; -//this function is called at the end of setup and draw if using -//accessibleOutputs and calls update functions of outputs -p5.prototype._updateAccsOutput = function() { - let cnvId = this.canvas.id; - //if the shapes are not the same as before - if ( - JSON.stringify(this.ingredients.shapes) !== this.ingredients.pShapes || - this.ingredients.colors.background !== this.ingredients.pBackground - ) { + //helper function that resets all ingredients when background is called + //and saves background color name + fn._accsBackground = function(args) { //save current shapes as string in pShapes this.ingredients.pShapes = JSON.stringify(this.ingredients.shapes); - if (this._accessibleOutputs.text) { - this._updateTextOutput(cnvId + 'textOutput'); - } - if (this._accessibleOutputs.grid) { - this._updateGridOutput(cnvId + 'gridOutput'); - } - if (this._accessibleOutputs.textLabel) { - this._updateTextOutput(cnvId + 'textOutputLabel'); + this.ingredients.pBackground = this.ingredients.colors.background; + //empty shapes JSON + this.ingredients.shapes = {}; + //update background different + if (this.ingredients.colors.backgroundRGBA !== args) { + this.ingredients.colors.backgroundRGBA = args; + this.ingredients.colors.background = this._rgbColorName(args); } - if (this._accessibleOutputs.gridLabel) { - this._updateGridOutput(cnvId + 'gridOutputLabel'); - } - } -}; - -//helper function that resets all ingredients when background is called -//and saves background color name -p5.prototype._accsBackground = function(args) { - //save current shapes as string in pShapes - this.ingredients.pShapes = JSON.stringify(this.ingredients.shapes); - this.ingredients.pBackground = this.ingredients.colors.background; - //empty shapes JSON - this.ingredients.shapes = {}; - //update background different - if (this.ingredients.colors.backgroundRGBA !== args) { - this.ingredients.colors.backgroundRGBA = args; - this.ingredients.colors.background = this._rgbColorName(args); - } -}; + }; -//helper function that gets fill and stroke of shapes -p5.prototype._accsCanvasColors = function(f, args) { - if (f === 'fill') { - //update fill different - if (this.ingredients.colors.fillRGBA !== args) { - this.ingredients.colors.fillRGBA = args; - this.ingredients.colors.fill = this._rgbColorName(args); - } - } else if (f === 'stroke') { - //update stroke if different - if (this.ingredients.colors.strokeRGBA !== args) { - this.ingredients.colors.strokeRGBA = args; - this.ingredients.colors.stroke = this._rgbColorName(args); + //helper function that gets fill and stroke of shapes + fn._accsCanvasColors = function(f, args) { + if (f === 'fill') { + //update fill different + if (this.ingredients.colors.fillRGBA !== args) { + this.ingredients.colors.fillRGBA = args; + this.ingredients.colors.fill = this._rgbColorName(args); + } + } else if (f === 'stroke') { + //update stroke if different + if (this.ingredients.colors.strokeRGBA !== args) { + this.ingredients.colors.strokeRGBA = args; + this.ingredients.colors.stroke = this._rgbColorName(args); + } } - } -}; + }; -//builds ingredients.shapes used for building outputs -p5.prototype._accsOutput = function(f, args) { - if (f === 'ellipse' && args[2] === args[3]) { - f = 'circle'; - } else if (f === 'rectangle' && args[2] === args[3]) { - f = 'square'; - } - let include = {}; - let add = true; - let middle = _getMiddle(f, args); - if (f === 'line') { - //make color stroke - include.color = this.ingredients.colors.stroke; - //get lenght - include.length = Math.round( - Math.hypot(args[2] - args[0], args[3] - args[1]) - ); - //get position of end points - let p1 = this._getPos(args[0], [1]); - let p2 = this._getPos(args[2], [3]); - include.loc = _canvasLocator(middle, this.width, this.height); - if (p1 === p2) { - include.pos = `at ${p1}`; - } else { - include.pos = `from ${p1} to ${p2}`; + //builds ingredients.shapes used for building outputs + fn._accsOutput = function(f, args) { + if (f === 'ellipse' && args[2] === args[3]) { + f = 'circle'; + } else if (f === 'rectangle' && args[2] === args[3]) { + f = 'square'; } - } else { - if (f === 'point') { + let include = {}; + let add = true; + let middle = _getMiddle(f, args); + if (f === 'line') { //make color stroke include.color = this.ingredients.colors.stroke; + //get lenght + include.length = Math.round( + Math.hypot(args[2] - args[0], args[3] - args[1]) + ); + //get position of end points + let p1 = this._getPos(args[0], [1]); + let p2 = this._getPos(args[2], [3]); + include.loc = _canvasLocator(middle, this.width, this.height); + if (p1 === p2) { + include.pos = `at ${p1}`; + } else { + include.pos = `from ${p1} to ${p2}`; + } } else { - //make color fill - include.color = this.ingredients.colors.fill; - //get area of shape - include.area = this._getArea(f, args); + if (f === 'point') { + //make color stroke + include.color = this.ingredients.colors.stroke; + } else { + //make color fill + include.color = this.ingredients.colors.fill; + //get area of shape + include.area = this._getArea(f, args); + } + //get middle of shapes + //calculate position using middle of shape + include.pos = this._getPos(...middle); + //calculate location using middle of shape + include.loc = _canvasLocator(middle, this.width, this.height); } - //get middle of shapes - //calculate position using middle of shape - include.pos = this._getPos(...middle); - //calculate location using middle of shape - include.loc = _canvasLocator(middle, this.width, this.height); - } - //if it is the first time this shape is created - if (!this.ingredients.shapes[f]) { - this.ingredients.shapes[f] = [include]; - //if other shapes of this type have been created - } else if (this.ingredients.shapes[f] !== [include]) { - //for every shape of this type - for (let y in this.ingredients.shapes[f]) { - //compare it with current shape and if it already exists make add false - if ( - JSON.stringify(this.ingredients.shapes[f][y]) === - JSON.stringify(include) - ) { - add = false; + //if it is the first time this shape is created + if (!this.ingredients.shapes[f]) { + this.ingredients.shapes[f] = [include]; + //if other shapes of this type have been created + } else if (this.ingredients.shapes[f] !== [include]) { + //for every shape of this type + for (let y in this.ingredients.shapes[f]) { + //compare it with current shape and if it already exists make add false + if ( + JSON.stringify(this.ingredients.shapes[f][y]) === + JSON.stringify(include) + ) { + add = false; + } + } + //add shape by pushing it to the end + if (add === true) { + this.ingredients.shapes[f].push(include); } } - //add shape by pushing it to the end - if (add === true) { - this.ingredients.shapes[f].push(include); + }; + + //gets middle point / centroid of shape + function _getMiddle(f, args) { + let x, y; + if ( + f === 'rectangle' || + f === 'ellipse' || + f === 'arc' || + f === 'circle' || + f === 'square' + ) { + x = Math.round(args[0] + args[2] / 2); + y = Math.round(args[1] + args[3] / 2); + } else if (f === 'triangle') { + x = (args[0] + args[2] + args[4]) / 3; + y = (args[1] + args[3] + args[5]) / 3; + } else if (f === 'quadrilateral') { + x = (args[0] + args[2] + args[4] + args[6]) / 4; + y = (args[1] + args[3] + args[5] + args[7]) / 4; + } else if (f === 'line') { + x = (args[0] + args[2]) / 2; + y = (args[1] + args[3]) / 2; + } else { + x = args[0]; + y = args[1]; } + return [x, y]; } -}; -//gets middle point / centroid of shape -function _getMiddle(f, args) { - let x, y; - if ( - f === 'rectangle' || - f === 'ellipse' || - f === 'arc' || - f === 'circle' || - f === 'square' - ) { - x = Math.round(args[0] + args[2] / 2); - y = Math.round(args[1] + args[3] / 2); - } else if (f === 'triangle') { - x = (args[0] + args[2] + args[4]) / 3; - y = (args[1] + args[3] + args[5]) / 3; - } else if (f === 'quadrilateral') { - x = (args[0] + args[2] + args[4] + args[6]) / 4; - y = (args[1] + args[3] + args[5] + args[7]) / 4; - } else if (f === 'line') { - x = (args[0] + args[2]) / 2; - y = (args[1] + args[3]) / 2; - } else { - x = args[0]; - y = args[1]; - } - return [x, y]; -} - -//gets position of shape in the canvas -p5.prototype._getPos = function (x, y) { - const untransformedPosition = new DOMPointReadOnly(x, y); - const currentTransform = this._renderer.isP3D ? - new DOMMatrix(this._renderer.uMVMatrix.mat4) : - this.drawingContext.getTransform(); - const { x: transformedX, y: transformedY } = untransformedPosition - .matrixTransform(currentTransform); - const canvasWidth = this.width * this._pixelDensity; - const canvasHeight = this.height * this._pixelDensity; - if (transformedX < 0.4 * canvasWidth) { - if (transformedY < 0.4 * canvasHeight) { - return 'top left'; - } else if (transformedY > 0.6 * canvasHeight) { - return 'bottom left'; + //gets position of shape in the canvas + fn._getPos = function (x, y) { + const untransformedPosition = new DOMPointReadOnly(x, y); + const currentTransform = this._renderer.isP3D ? + new DOMMatrix(this._renderer.uMVMatrix.mat4) : + this.drawingContext.getTransform(); + const { x: transformedX, y: transformedY } = untransformedPosition + .matrixTransform(currentTransform); + const canvasWidth = this.width * this._pixelDensity; + const canvasHeight = this.height * this._pixelDensity; + if (transformedX < 0.4 * canvasWidth) { + if (transformedY < 0.4 * canvasHeight) { + return 'top left'; + } else if (transformedY > 0.6 * canvasHeight) { + return 'bottom left'; + } else { + return 'mid left'; + } + } else if (transformedX > 0.6 * canvasWidth) { + if (transformedY < 0.4 * canvasHeight) { + return 'top right'; + } else if (transformedY > 0.6 * canvasHeight) { + return 'bottom right'; + } else { + return 'mid right'; + } } else { - return 'mid left'; + if (transformedY < 0.4 * canvasHeight) { + return 'top middle'; + } else if (transformedY > 0.6 * canvasHeight) { + return 'bottom middle'; + } else { + return 'middle'; + } } - } else if (transformedX > 0.6 * canvasWidth) { - if (transformedY < 0.4 * canvasHeight) { - return 'top right'; - } else if (transformedY > 0.6 * canvasHeight) { - return 'bottom right'; - } else { - return 'mid right'; + }; + + //locates shape in a 10*10 grid + function _canvasLocator(args, canvasWidth, canvasHeight) { + const noRows = 10; + const noCols = 10; + let locX = Math.floor(args[0] / canvasWidth * noRows); + let locY = Math.floor(args[1] / canvasHeight * noCols); + if (locX === noRows) { + locX = locX - 1; } - } else { - if (transformedY < 0.4 * canvasHeight) { - return 'top middle'; - } else if (transformedY > 0.6 * canvasHeight) { - return 'bottom middle'; - } else { - return 'middle'; + if (locY === noCols) { + locY = locY - 1; } + return { + locX, + locY + }; } -}; -//locates shape in a 10*10 grid -function _canvasLocator(args, canvasWidth, canvasHeight) { - const noRows = 10; - const noCols = 10; - let locX = Math.floor(args[0] / canvasWidth * noRows); - let locY = Math.floor(args[1] / canvasHeight * noCols); - if (locX === noRows) { - locX = locX - 1; - } - if (locY === noCols) { - locY = locY - 1; - } - return { - locX, - locY + //calculates area of shape + fn._getArea = function (objectType, shapeArgs) { + let objectArea = 0; + if (objectType === 'arc') { + // area of full ellipse = PI * horizontal radius * vertical radius. + // therefore, area of arc = difference bet. arc's start and end radians * horizontal radius * vertical radius. + // the below expression is adjusted for negative values and differences in arc's start and end radians over PI*2 + const arcSizeInRadians = + ((shapeArgs[5] - shapeArgs[4]) % (Math.PI * 2) + Math.PI * 2) % + (Math.PI * 2); + objectArea = arcSizeInRadians * shapeArgs[2] * shapeArgs[3] / 8; + if (shapeArgs[6] === 'open' || shapeArgs[6] === 'chord') { + // when the arc's mode is OPEN or CHORD, we need to account for the area of the triangle that is formed to close the arc + // (Ax( By − Cy) + Bx(Cy − Ay) + Cx(Ay − By ) )/2 + const Ax = shapeArgs[0]; + const Ay = shapeArgs[1]; + const Bx = + shapeArgs[0] + shapeArgs[2] / 2 * Math.cos(shapeArgs[4]).toFixed(2); + const By = + shapeArgs[1] + shapeArgs[3] / 2 * Math.sin(shapeArgs[4]).toFixed(2); + const Cx = + shapeArgs[0] + shapeArgs[2] / 2 * Math.cos(shapeArgs[5]).toFixed(2); + const Cy = + shapeArgs[1] + shapeArgs[3] / 2 * Math.sin(shapeArgs[5]).toFixed(2); + const areaOfExtraTriangle = + Math.abs(Ax * (By - Cy) + Bx * (Cy - Ay) + Cx * (Ay - By)) / 2; + if (arcSizeInRadians > Math.PI) { + objectArea = objectArea + areaOfExtraTriangle; + } else { + objectArea = objectArea - areaOfExtraTriangle; + } + } + } else if (objectType === 'ellipse' || objectType === 'circle') { + objectArea = 3.14 * shapeArgs[2] / 2 * shapeArgs[3] / 2; + } else if (objectType === 'line') { + objectArea = 0; + } else if (objectType === 'point') { + objectArea = 0; + } else if (objectType === 'quadrilateral') { + // ((x4+x1)*(y4-y1)+(x1+x2)*(y1-y2)+(x2+x3)*(y2-y3)+(x3+x4)*(y3-y4))/2 + objectArea = + Math.abs( + (shapeArgs[6] + shapeArgs[0]) * (shapeArgs[7] - shapeArgs[1]) + + (shapeArgs[0] + shapeArgs[2]) * (shapeArgs[1] - shapeArgs[3]) + + (shapeArgs[2] + shapeArgs[4]) * (shapeArgs[3] - shapeArgs[5]) + + (shapeArgs[4] + shapeArgs[6]) * (shapeArgs[5] - shapeArgs[7]) + ) / 2; + } else if (objectType === 'rectangle' || objectType === 'square') { + objectArea = shapeArgs[2] * shapeArgs[3]; + } else if (objectType === 'triangle') { + objectArea = + Math.abs( + shapeArgs[0] * (shapeArgs[3] - shapeArgs[5]) + + shapeArgs[2] * (shapeArgs[5] - shapeArgs[1]) + + shapeArgs[4] * (shapeArgs[1] - shapeArgs[3]) + ) / 2; + // (Ax( By − Cy) + Bx(Cy − Ay) + Cx(Ay − By ))/2 + } + // Store the positions of the canvas corners + const canvasWidth = this.width * this._pixelDensity; + const canvasHeight = this.height * this._pixelDensity; + const canvasCorners = [ + new DOMPoint(0, 0), + new DOMPoint(canvasWidth, 0), + new DOMPoint(canvasWidth, canvasHeight), + new DOMPoint(0, canvasHeight) + ]; + // Apply the inverse of the current transformations to the canvas corners + const currentTransform = this._renderer.isP3D ? + new DOMMatrix(this._renderer.uMVMatrix.mat4) : + this.drawingContext.getTransform(); + const invertedTransform = currentTransform.inverse(); + const tc = canvasCorners.map( + corner => corner.matrixTransform(invertedTransform) + ); + /* Use same shoelace formula used for quad area (above) to calculate + the area of the canvas with inverted transformation applied */ + const transformedCanvasArea = Math.abs( + (tc[3].x + tc[0].x) * (tc[3].y - tc[0].y) + + (tc[0].x + tc[1].x) * (tc[0].y - tc[1].y) + + (tc[1].x + tc[2].x) * (tc[1].y - tc[2].y)+ + (tc[2].x + tc[3].x) * (tc[2].y - tc[3].y) + ) / 2; + /* Compare area of shape (minus transformations) to area of canvas + with inverted transformation applied. + Return percentage */ + const untransformedArea = Math.round( + objectArea * 100 / (transformedCanvasArea) + ); + return untransformedArea; }; } -//calculates area of shape -p5.prototype._getArea = function (objectType, shapeArgs) { - let objectArea = 0; - if (objectType === 'arc') { - // area of full ellipse = PI * horizontal radius * vertical radius. - // therefore, area of arc = difference bet. arc's start and end radians * horizontal radius * vertical radius. - // the below expression is adjusted for negative values and differences in arc's start and end radians over PI*2 - const arcSizeInRadians = - ((shapeArgs[5] - shapeArgs[4]) % (Math.PI * 2) + Math.PI * 2) % - (Math.PI * 2); - objectArea = arcSizeInRadians * shapeArgs[2] * shapeArgs[3] / 8; - if (shapeArgs[6] === 'open' || shapeArgs[6] === 'chord') { - // when the arc's mode is OPEN or CHORD, we need to account for the area of the triangle that is formed to close the arc - // (Ax( By − Cy) + Bx(Cy − Ay) + Cx(Ay − By ) )/2 - const Ax = shapeArgs[0]; - const Ay = shapeArgs[1]; - const Bx = - shapeArgs[0] + shapeArgs[2] / 2 * Math.cos(shapeArgs[4]).toFixed(2); - const By = - shapeArgs[1] + shapeArgs[3] / 2 * Math.sin(shapeArgs[4]).toFixed(2); - const Cx = - shapeArgs[0] + shapeArgs[2] / 2 * Math.cos(shapeArgs[5]).toFixed(2); - const Cy = - shapeArgs[1] + shapeArgs[3] / 2 * Math.sin(shapeArgs[5]).toFixed(2); - const areaOfExtraTriangle = - Math.abs(Ax * (By - Cy) + Bx * (Cy - Ay) + Cx * (Ay - By)) / 2; - if (arcSizeInRadians > Math.PI) { - objectArea = objectArea + areaOfExtraTriangle; - } else { - objectArea = objectArea - areaOfExtraTriangle; - } - } - } else if (objectType === 'ellipse' || objectType === 'circle') { - objectArea = 3.14 * shapeArgs[2] / 2 * shapeArgs[3] / 2; - } else if (objectType === 'line') { - objectArea = 0; - } else if (objectType === 'point') { - objectArea = 0; - } else if (objectType === 'quadrilateral') { - // ((x4+x1)*(y4-y1)+(x1+x2)*(y1-y2)+(x2+x3)*(y2-y3)+(x3+x4)*(y3-y4))/2 - objectArea = - Math.abs( - (shapeArgs[6] + shapeArgs[0]) * (shapeArgs[7] - shapeArgs[1]) + - (shapeArgs[0] + shapeArgs[2]) * (shapeArgs[1] - shapeArgs[3]) + - (shapeArgs[2] + shapeArgs[4]) * (shapeArgs[3] - shapeArgs[5]) + - (shapeArgs[4] + shapeArgs[6]) * (shapeArgs[5] - shapeArgs[7]) - ) / 2; - } else if (objectType === 'rectangle' || objectType === 'square') { - objectArea = shapeArgs[2] * shapeArgs[3]; - } else if (objectType === 'triangle') { - objectArea = - Math.abs( - shapeArgs[0] * (shapeArgs[3] - shapeArgs[5]) + - shapeArgs[2] * (shapeArgs[5] - shapeArgs[1]) + - shapeArgs[4] * (shapeArgs[1] - shapeArgs[3]) - ) / 2; - // (Ax( By − Cy) + Bx(Cy − Ay) + Cx(Ay − By ))/2 - } - // Store the positions of the canvas corners - const canvasWidth = this.width * this._pixelDensity; - const canvasHeight = this.height * this._pixelDensity; - const canvasCorners = [ - new DOMPoint(0, 0), - new DOMPoint(canvasWidth, 0), - new DOMPoint(canvasWidth, canvasHeight), - new DOMPoint(0, canvasHeight) - ]; - // Apply the inverse of the current transformations to the canvas corners - const currentTransform = this._renderer.isP3D ? - new DOMMatrix(this._renderer.uMVMatrix.mat4) : - this.drawingContext.getTransform(); - const invertedTransform = currentTransform.inverse(); - const tc = canvasCorners.map( - corner => corner.matrixTransform(invertedTransform) - ); - /* Use same shoelace formula used for quad area (above) to calculate - the area of the canvas with inverted transformation applied */ - const transformedCanvasArea = Math.abs( - (tc[3].x + tc[0].x) * (tc[3].y - tc[0].y) + - (tc[0].x + tc[1].x) * (tc[0].y - tc[1].y) + - (tc[1].x + tc[2].x) * (tc[1].y - tc[2].y)+ - (tc[2].x + tc[3].x) * (tc[2].y - tc[3].y) - ) / 2; - /* Compare area of shape (minus transformations) to area of canvas - with inverted transformation applied. - Return percentage */ - const untransformedArea = Math.round( - objectArea * 100 / (transformedCanvasArea) - ); - return untransformedArea; -}; +export default outputs; -export default p5; +if(typeof p5 !== 'undefined'){ + outputs(p5, p5.prototype); +} diff --git a/src/accessibility/textOutput.js b/src/accessibility/textOutput.js index 5e37fd5781..59c55401e7 100644 --- a/src/accessibility/textOutput.js +++ b/src/accessibility/textOutput.js @@ -4,118 +4,123 @@ * @for p5 * @requires core */ -import p5 from '../core/main'; -//the functions in this file support updating the text output +function textOutput(p5, fn){ + //the functions in this file support updating the text output -//updates textOutput -p5.prototype._updateTextOutput = function(idT) { - //if html structure is not there yet - if (!this.dummyDOM.querySelector(`#${idT}_summary`)) { - return; - } - let current = this._accessibleOutputs[idT]; - //create shape list - let innerList = _shapeList(idT, this.ingredients.shapes); - //create output summary - let innerSummary = _textSummary( - innerList.numShapes, - this.ingredients.colors.background, - this.width, - this.height - ); - //create shape details - let innerShapeDetails = _shapeDetails(idT, this.ingredients.shapes); - //if it is different from current summary - if (innerSummary !== current.summary.innerHTML) { - //update - current.summary.innerHTML = innerSummary; - } - //if it is different from current shape list - if (innerList.listShapes !== current.list.innerHTML) { - //update - current.list.innerHTML = innerList.listShapes; - } - //if it is different from current shape details - if (innerShapeDetails !== current.shapeDetails.innerHTML) { - //update - current.shapeDetails.innerHTML = innerShapeDetails; - } - this._accessibleOutputs[idT] = current; -}; + //updates textOutput + fn._updateTextOutput = function(idT) { + //if html structure is not there yet + if (!this.dummyDOM.querySelector(`#${idT}_summary`)) { + return; + } + let current = this._accessibleOutputs[idT]; + //create shape list + let innerList = _shapeList(idT, this.ingredients.shapes); + //create output summary + let innerSummary = _textSummary( + innerList.numShapes, + this.ingredients.colors.background, + this.width, + this.height + ); + //create shape details + let innerShapeDetails = _shapeDetails(idT, this.ingredients.shapes); + //if it is different from current summary + if (innerSummary !== current.summary.innerHTML) { + //update + current.summary.innerHTML = innerSummary; + } + //if it is different from current shape list + if (innerList.listShapes !== current.list.innerHTML) { + //update + current.list.innerHTML = innerList.listShapes; + } + //if it is different from current shape details + if (innerShapeDetails !== current.shapeDetails.innerHTML) { + //update + current.shapeDetails.innerHTML = innerShapeDetails; + } + this._accessibleOutputs[idT] = current; + }; -//Builds textOutput summary -function _textSummary(numShapes, background, width, height) { - let text = `Your output is a, ${width} by ${height} pixels, ${background} canvas containing the following`; - if (numShapes === 1) { - text = `${text} shape:`; - } else { - text = `${text} ${numShapes} shapes:`; + //Builds textOutput summary + function _textSummary(numShapes, background, width, height) { + let text = `Your output is a, ${width} by ${height} pixels, ${background} canvas containing the following`; + if (numShapes === 1) { + text = `${text} shape:`; + } else { + text = `${text} ${numShapes} shapes:`; + } + return text; } - return text; -} -//Builds textOutput table with shape details -function _shapeDetails(idT, ingredients) { - let shapeDetails = ''; - let shapeNumber = 0; - //goes trhough every shape type in ingredients - for (let x in ingredients) { - //and for every shape - for (let y in ingredients[x]) { - //it creates a table row - let row = `${ - ingredients[x][y].color - } ${x}`; - if (x === 'line') { - row = - row + - `location = ${ingredients[x][y].pos}length = ${ - ingredients[x][y].length - } pixels`; - } else { - row = row + `location = ${ingredients[x][y].pos}`; - if (x !== 'point') { - row = row + ` area = ${ingredients[x][y].area}%`; + //Builds textOutput table with shape details + function _shapeDetails(idT, ingredients) { + let shapeDetails = ''; + let shapeNumber = 0; + //goes trhough every shape type in ingredients + for (let x in ingredients) { + //and for every shape + for (let y in ingredients[x]) { + //it creates a table row + let row = `${ + ingredients[x][y].color + } ${x}`; + if (x === 'line') { + row = + row + + `location = ${ingredients[x][y].pos}length = ${ + ingredients[x][y].length + } pixels`; + } else { + row = row + `location = ${ingredients[x][y].pos}`; + if (x !== 'point') { + row = row + ` area = ${ingredients[x][y].area}%`; + } + row = row + ''; } - row = row + ''; + shapeDetails = shapeDetails + row; + shapeNumber++; } - shapeDetails = shapeDetails + row; - shapeNumber++; } + return shapeDetails; } - return shapeDetails; -} -//Builds textOutput shape list -function _shapeList(idT, ingredients) { - let shapeList = ''; - let shapeNumber = 0; - //goes trhough every shape type in ingredients - for (let x in ingredients) { - for (let y in ingredients[x]) { - //it creates a line in a list - let _line = `
  • ${ - ingredients[x][y].color - } ${x}`; - if (x === 'line') { - _line = - _line + - `, ${ingredients[x][y].pos}, ${ - ingredients[x][y].length - } pixels long.
  • `; - } else { - _line = _line + `, at ${ingredients[x][y].pos}`; - if (x !== 'point') { - _line = _line + `, covering ${ingredients[x][y].area}% of the canvas`; + //Builds textOutput shape list + function _shapeList(idT, ingredients) { + let shapeList = ''; + let shapeNumber = 0; + //goes trhough every shape type in ingredients + for (let x in ingredients) { + for (let y in ingredients[x]) { + //it creates a line in a list + let _line = `
  • ${ + ingredients[x][y].color + } ${x}`; + if (x === 'line') { + _line = + _line + + `, ${ingredients[x][y].pos}, ${ + ingredients[x][y].length + } pixels long.
  • `; + } else { + _line = _line + `, at ${ingredients[x][y].pos}`; + if (x !== 'point') { + _line = _line + `, covering ${ingredients[x][y].area}% of the canvas`; + } + _line = _line + '.'; } - _line = _line + '.'; + shapeList = shapeList + _line; + shapeNumber++; } - shapeList = shapeList + _line; - shapeNumber++; } + return { numShapes: shapeNumber, listShapes: shapeList }; } - return { numShapes: shapeNumber, listShapes: shapeList }; } -export default p5; +export default textOutput; + +if(typeof p5 !== 'undefined'){ + textOutput(p5, p5.prototype); +} diff --git a/src/app.js b/src/app.js index b1282da4e5..0164a4d820 100644 --- a/src/app.js +++ b/src/app.js @@ -21,54 +21,37 @@ import './core/shape/2d_primitives'; import './core/shape/attributes'; import './core/shape/curves'; import './core/shape/vertex'; + //accessibility -import './accessibility/outputs'; -import './accessibility/textOutput'; -import './accessibility/gridOutput'; -import './accessibility/color_namer'; +import accessibility from './accessibility'; +accessibility(p5); + // color -import './color/creating_reading'; -import './color/p5.Color'; -import './color/setting'; +import color from './color'; +color(p5); // data -import './data/p5.TypedDict'; -import './data/local_storage.js'; +import data from './data'; +data(p5); // DOM import './dom/dom'; -// accessibility -import './accessibility/describe'; - // events -import './events/acceleration'; -import './events/keyboard'; -import './events/mouse'; -import './events/touch'; +import events from './events'; +events(p5); // image -import './image/filters'; -import './image/image'; -import './image/loading_displaying'; -import './image/p5.Image'; -import './image/pixels'; +import image from './image'; +image(p5); // io -import './io/files'; -import './io/p5.Table'; -import './io/p5.TableRow'; -import './io/p5.XML'; +import io from './io'; +io(p5); // math -// import './math/calculation'; -// import './math/math'; -// import './math/noise'; -// import './math/p5.Vector'; -// import './math/random'; -// import './math/trigonometry'; import math from './math'; -math(p5, p5.prototype); +math(p5); // typography import './typography/attributes'; @@ -76,10 +59,8 @@ import './typography/loading_displaying'; import './typography/p5.Font'; // utilities -import './utilities/array_functions'; -import './utilities/conversion'; -import './utilities/string_functions'; -import './utilities/time_date'; +import utilities from './utilities'; +utilities(p5); // webgl import './webgl/3d_primitives'; diff --git a/src/color/creating_reading.js b/src/color/creating_reading.js index a75223d8d8..d4f5ee8785 100644 --- a/src/color/creating_reading.js +++ b/src/color/creating_reading.js @@ -6,1489 +6,1491 @@ * @requires constants */ -import p5 from '../core/main'; import './p5.Color'; -import '../core/friendly_errors/validate_params'; -import '../core/friendly_errors/file_errors'; -import '../core/friendly_errors/fes_core'; import { range } from 'colorjs.io/fn'; -/** - * Gets the alpha (transparency) value of a color. - * - * `alpha()` extracts the alpha value from a - * p5.Color object, an array of color components, or - * a CSS color string. - * - * @method alpha - * @param {p5.Color|Number[]|String} color p5.Color object, array of - * color components, or CSS color string. - * @return {Number} the alpha value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object. - * let c = color(0, 126, 255, 102); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'alphaValue' to 102. - * let alphaValue = alpha(c); - * - * // Draw the right rectangle. - * fill(alphaValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light blue and the right one is charcoal gray.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a color array. - * let c = [0, 126, 255, 102]; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'alphaValue' to 102. - * let alphaValue = alpha(c); - * - * // Draw the left rectangle. - * fill(alphaValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light blue and the right one is charcoal gray.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a CSS color string. - * let c = 'rgba(0, 126, 255, 0.4)'; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'alphaValue' to 102. - * let alphaValue = alpha(c); - * - * // Draw the right rectangle. - * fill(alphaValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light blue and the right one is charcoal gray.'); - * } - * - *
    - */ -p5.prototype.alpha = function(c) { - p5._validateParameters('alpha', arguments); - return this.color(c)._getAlpha(); -}; +function creatingReading(p5, fn){ + /** + * Gets the alpha (transparency) value of a color. + * + * `alpha()` extracts the alpha value from a + * p5.Color object, an array of color components, or + * a CSS color string. + * + * @method alpha + * @param {p5.Color|Number[]|String} color p5.Color object, array of + * color components, or CSS color string. + * @return {Number} the alpha value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object. + * let c = color(0, 126, 255, 102); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'alphaValue' to 102. + * let alphaValue = alpha(c); + * + * // Draw the right rectangle. + * fill(alphaValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light blue and the right one is charcoal gray.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a color array. + * let c = [0, 126, 255, 102]; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'alphaValue' to 102. + * let alphaValue = alpha(c); + * + * // Draw the left rectangle. + * fill(alphaValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light blue and the right one is charcoal gray.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a CSS color string. + * let c = 'rgba(0, 126, 255, 0.4)'; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'alphaValue' to 102. + * let alphaValue = alpha(c); + * + * // Draw the right rectangle. + * fill(alphaValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light blue and the right one is charcoal gray.'); + * } + * + *
    + */ + fn.alpha = function(c) { + p5._validateParameters('alpha', arguments); + return this.color(c)._getAlpha(); + }; -/** - * Gets the blue value of a color. - * - * `blue()` extracts the blue value from a - * p5.Color object, an array of color components, or - * a CSS color string. - * - * By default, `blue()` returns a color's blue value in the range 0 - * to 255. If the colorMode() is set to RGB, it - * returns the blue value in the given range. - * - * @method blue - * @param {p5.Color|Number[]|String} color p5.Color object, array of - * color components, or CSS color string. - * @return {Number} the blue value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object using RGB values. - * let c = color(175, 100, 220); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'blueValue' to 220. - * let blueValue = blue(c); - * - * // Draw the right rectangle. - * fill(0, 0, blueValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light purple and the right one is royal blue.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a color array. - * let c = [175, 100, 220]; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'blueValue' to 220. - * let blueValue = blue(c); - * - * // Draw the right rectangle. - * fill(0, 0, blueValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light purple and the right one is royal blue.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a CSS color string. - * let c = 'rgb(175, 100, 220)'; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'blueValue' to 220. - * let blueValue = blue(c); - * - * // Draw the right rectangle. - * fill(0, 0, blueValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light purple and the right one is royal blue.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use RGB color with values in the range 0-100. - * colorMode(RGB, 100); - * - * // Create a p5.Color object using RGB values. - * let c = color(69, 39, 86); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'blueValue' to 86. - * let blueValue = blue(c); - * - * // Draw the right rectangle. - * fill(0, 0, blueValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light purple and the right one is royal blue.'); - * } - * - *
    - */ -p5.prototype.blue = function(c) { - p5._validateParameters('blue', arguments); - return this.color(c)._getBlue(); -}; + /** + * Gets the blue value of a color. + * + * `blue()` extracts the blue value from a + * p5.Color object, an array of color components, or + * a CSS color string. + * + * By default, `blue()` returns a color's blue value in the range 0 + * to 255. If the colorMode() is set to RGB, it + * returns the blue value in the given range. + * + * @method blue + * @param {p5.Color|Number[]|String} color p5.Color object, array of + * color components, or CSS color string. + * @return {Number} the blue value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object using RGB values. + * let c = color(175, 100, 220); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'blueValue' to 220. + * let blueValue = blue(c); + * + * // Draw the right rectangle. + * fill(0, 0, blueValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light purple and the right one is royal blue.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a color array. + * let c = [175, 100, 220]; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'blueValue' to 220. + * let blueValue = blue(c); + * + * // Draw the right rectangle. + * fill(0, 0, blueValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light purple and the right one is royal blue.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a CSS color string. + * let c = 'rgb(175, 100, 220)'; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'blueValue' to 220. + * let blueValue = blue(c); + * + * // Draw the right rectangle. + * fill(0, 0, blueValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light purple and the right one is royal blue.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use RGB color with values in the range 0-100. + * colorMode(RGB, 100); + * + * // Create a p5.Color object using RGB values. + * let c = color(69, 39, 86); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'blueValue' to 86. + * let blueValue = blue(c); + * + * // Draw the right rectangle. + * fill(0, 0, blueValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light purple and the right one is royal blue.'); + * } + * + *
    + */ + fn.blue = function(c) { + p5._validateParameters('blue', arguments); + return this.color(c)._getBlue(); + }; -/** - * Gets the brightness value of a color. - * - * `brightness()` extracts the HSB brightness value from a - * p5.Color object, an array of color components, or - * a CSS color string. - * - * By default, `brightness()` returns a color's HSB brightness in the range 0 - * to 100. If the colorMode() is set to HSB, it - * returns the brightness value in the given range. - * - * @method brightness - * @param {p5.Color|Number[]|String} color p5.Color object, array of - * color components, or CSS color string. - * @return {Number} the brightness value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use HSB color. - * colorMode(HSB); - * - * // Create a p5.Color object. - * let c = color(0, 50, 100); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'brightValue' to 100. - * let brightValue = brightness(c); - * - * // Draw the right rectangle. - * fill(brightValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is white.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use HSB color. - * colorMode(HSB); - * - * // Create a color array. - * let c = [0, 50, 100]; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'brightValue' to 100. - * let brightValue = brightness(c); - * - * // Draw the right rectangle. - * fill(brightValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is white.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use HSB color. - * colorMode(HSB); - * - * // Create a CSS color string. - * let c = 'rgb(255, 128, 128)'; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'brightValue' to 100. - * let brightValue = brightness(c); - * - * // Draw the right rectangle. - * fill(brightValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is white.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use HSB color with values in the range 0-255. - * colorMode(HSB, 255); - * - * // Create a p5.Color object. - * let c = color(0, 127, 255); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'brightValue' to 255. - * let brightValue = brightness(c); - * - * // Draw the right rectangle. - * fill(brightValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is white.'); - * } - * - *
    - */ -p5.prototype.brightness = function(c) { - p5._validateParameters('brightness', arguments); - return this.color(c)._getBrightness(); -}; + /** + * Gets the brightness value of a color. + * + * `brightness()` extracts the HSB brightness value from a + * p5.Color object, an array of color components, or + * a CSS color string. + * + * By default, `brightness()` returns a color's HSB brightness in the range 0 + * to 100. If the colorMode() is set to HSB, it + * returns the brightness value in the given range. + * + * @method brightness + * @param {p5.Color|Number[]|String} color p5.Color object, array of + * color components, or CSS color string. + * @return {Number} the brightness value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use HSB color. + * colorMode(HSB); + * + * // Create a p5.Color object. + * let c = color(0, 50, 100); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'brightValue' to 100. + * let brightValue = brightness(c); + * + * // Draw the right rectangle. + * fill(brightValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is white.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use HSB color. + * colorMode(HSB); + * + * // Create a color array. + * let c = [0, 50, 100]; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'brightValue' to 100. + * let brightValue = brightness(c); + * + * // Draw the right rectangle. + * fill(brightValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is white.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use HSB color. + * colorMode(HSB); + * + * // Create a CSS color string. + * let c = 'rgb(255, 128, 128)'; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'brightValue' to 100. + * let brightValue = brightness(c); + * + * // Draw the right rectangle. + * fill(brightValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is white.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use HSB color with values in the range 0-255. + * colorMode(HSB, 255); + * + * // Create a p5.Color object. + * let c = color(0, 127, 255); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'brightValue' to 255. + * let brightValue = brightness(c); + * + * // Draw the right rectangle. + * fill(brightValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is white.'); + * } + * + *
    + */ + fn.brightness = function(c) { + p5._validateParameters('brightness', arguments); + return this.color(c)._getBrightness(); + }; -/** - * Creates a p5.Color object. - * - * By default, the parameters are interpreted as RGB values. Calling - * `color(255, 204, 0)` will return a bright yellow color. The way these - * parameters are interpreted may be changed with the - * colorMode() function. - * - * The version of `color()` with one parameter interprets the value one of two - * ways. If the parameter is a number, it's interpreted as a grayscale value. - * If the parameter is a string, it's interpreted as a CSS color string. - * - * The version of `color()` with two parameters interprets the first one as a - * grayscale value. The second parameter sets the alpha (transparency) value. - * - * The version of `color()` with three parameters interprets them as RGB, HSB, - * or HSL colors, depending on the current `colorMode()`. - * - * The version of `color()` with four parameters interprets them as RGBA, HSBA, - * or HSLA colors, depending on the current `colorMode()`. The last parameter - * sets the alpha (transparency) value. - * - * @method color - * @param {Number} gray number specifying value between white and black. - * @param {Number} [alpha] alpha value relative to current color range - * (default is 0-255). - * @return {p5.Color} resulting color. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object using RGB values. - * let c = color(255, 204, 0); - * - * // Draw the square. - * fill(c); - * noStroke(); - * square(30, 20, 55); - * - * describe('A yellow square on a gray canvas.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object using RGB values. - * let c1 = color(255, 204, 0); - * - * // Draw the left circle. - * fill(c1); - * noStroke(); - * circle(25, 25, 80); - * - * // Create a p5.Color object using a grayscale value. - * let c2 = color(65); - * - * // Draw the right circle. - * fill(c2); - * circle(75, 75, 80); - * - * describe( - * 'Two circles on a gray canvas. The circle in the top-left corner is yellow and the one at the bottom-right is gray.' - * ); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object using a named color. - * let c = color('magenta'); - * - * // Draw the square. - * fill(c); - * noStroke(); - * square(20, 20, 60); - * - * describe('A magenta square on a gray canvas.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object using a hex color code. - * let c1 = color('#0f0'); - * - * // Draw the left rectangle. - * fill(c1); - * noStroke(); - * rect(0, 10, 45, 80); - * - * // Create a p5.Color object using a hex color code. - * let c2 = color('#00ff00'); - * - * // Draw the right rectangle. - * fill(c2); - * rect(55, 10, 45, 80); - * - * describe('Two bright green rectangles on a gray canvas.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object using a RGB color string. - * let c1 = color('rgb(0, 0, 255)'); - * - * // Draw the top-left square. - * fill(c1); - * square(10, 10, 35); - * - * // Create a p5.Color object using a RGB color string. - * let c2 = color('rgb(0%, 0%, 100%)'); - * - * // Draw the top-right square. - * fill(c2); - * square(55, 10, 35); - * - * // Create a p5.Color object using a RGBA color string. - * let c3 = color('rgba(0, 0, 255, 1)'); - * - * // Draw the bottom-left square. - * fill(c3); - * square(10, 55, 35); - * - * // Create a p5.Color object using a RGBA color string. - * let c4 = color('rgba(0%, 0%, 100%, 1)'); - * - * // Draw the bottom-right square. - * fill(c4); - * square(55, 55, 35); - * - * describe('Four blue squares in the corners of a gray canvas.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object using a HSL color string. - * let c1 = color('hsl(160, 100%, 50%)'); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c1); - * rect(0, 10, 45, 80); - * - * // Create a p5.Color object using a HSLA color string. - * let c2 = color('hsla(160, 100%, 50%, 0.5)'); - * - * // Draw the right rectangle. - * fill(c2); - * rect(55, 10, 45, 80); - * - * describe('Two sea green rectangles. A darker rectangle on the left and a brighter one on the right.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object using a HSB color string. - * let c1 = color('hsb(160, 100%, 50%)'); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c1); - * rect(0, 10, 45, 80); - * - * // Create a p5.Color object using a HSBA color string. - * let c2 = color('hsba(160, 100%, 50%, 0.5)'); - * - * // Draw the right rectangle. - * fill(c2); - * rect(55, 10, 45, 80); - * - * describe('Two green rectangles. A darker rectangle on the left and a brighter one on the right.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object using RGB values. - * let c1 = color(50, 55, 100); - * - * // Draw the left rectangle. - * fill(c1); - * rect(0, 10, 45, 80); - * - * // Switch the color mode to HSB. - * colorMode(HSB, 100); - * - * // Create a p5.Color object using HSB values. - * let c2 = color(50, 55, 100); - * - * // Draw the right rectangle. - * fill(c2); - * rect(55, 10, 45, 80); - * - * describe('Two blue rectangles. A darker rectangle on the left and a brighter one on the right.'); - * } - * - *
    - */ + /** + * Creates a p5.Color object. + * + * By default, the parameters are interpreted as RGB values. Calling + * `color(255, 204, 0)` will return a bright yellow color. The way these + * parameters are interpreted may be changed with the + * colorMode() function. + * + * The version of `color()` with one parameter interprets the value one of two + * ways. If the parameter is a number, it's interpreted as a grayscale value. + * If the parameter is a string, it's interpreted as a CSS color string. + * + * The version of `color()` with two parameters interprets the first one as a + * grayscale value. The second parameter sets the alpha (transparency) value. + * + * The version of `color()` with three parameters interprets them as RGB, HSB, + * or HSL colors, depending on the current `colorMode()`. + * + * The version of `color()` with four parameters interprets them as RGBA, HSBA, + * or HSLA colors, depending on the current `colorMode()`. The last parameter + * sets the alpha (transparency) value. + * + * @method color + * @param {Number} gray number specifying value between white and black. + * @param {Number} [alpha] alpha value relative to current color range + * (default is 0-255). + * @return {p5.Color} resulting color. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object using RGB values. + * let c = color(255, 204, 0); + * + * // Draw the square. + * fill(c); + * noStroke(); + * square(30, 20, 55); + * + * describe('A yellow square on a gray canvas.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object using RGB values. + * let c1 = color(255, 204, 0); + * + * // Draw the left circle. + * fill(c1); + * noStroke(); + * circle(25, 25, 80); + * + * // Create a p5.Color object using a grayscale value. + * let c2 = color(65); + * + * // Draw the right circle. + * fill(c2); + * circle(75, 75, 80); + * + * describe( + * 'Two circles on a gray canvas. The circle in the top-left corner is yellow and the one at the bottom-right is gray.' + * ); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object using a named color. + * let c = color('magenta'); + * + * // Draw the square. + * fill(c); + * noStroke(); + * square(20, 20, 60); + * + * describe('A magenta square on a gray canvas.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object using a hex color code. + * let c1 = color('#0f0'); + * + * // Draw the left rectangle. + * fill(c1); + * noStroke(); + * rect(0, 10, 45, 80); + * + * // Create a p5.Color object using a hex color code. + * let c2 = color('#00ff00'); + * + * // Draw the right rectangle. + * fill(c2); + * rect(55, 10, 45, 80); + * + * describe('Two bright green rectangles on a gray canvas.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object using a RGB color string. + * let c1 = color('rgb(0, 0, 255)'); + * + * // Draw the top-left square. + * fill(c1); + * square(10, 10, 35); + * + * // Create a p5.Color object using a RGB color string. + * let c2 = color('rgb(0%, 0%, 100%)'); + * + * // Draw the top-right square. + * fill(c2); + * square(55, 10, 35); + * + * // Create a p5.Color object using a RGBA color string. + * let c3 = color('rgba(0, 0, 255, 1)'); + * + * // Draw the bottom-left square. + * fill(c3); + * square(10, 55, 35); + * + * // Create a p5.Color object using a RGBA color string. + * let c4 = color('rgba(0%, 0%, 100%, 1)'); + * + * // Draw the bottom-right square. + * fill(c4); + * square(55, 55, 35); + * + * describe('Four blue squares in the corners of a gray canvas.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object using a HSL color string. + * let c1 = color('hsl(160, 100%, 50%)'); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c1); + * rect(0, 10, 45, 80); + * + * // Create a p5.Color object using a HSLA color string. + * let c2 = color('hsla(160, 100%, 50%, 0.5)'); + * + * // Draw the right rectangle. + * fill(c2); + * rect(55, 10, 45, 80); + * + * describe('Two sea green rectangles. A darker rectangle on the left and a brighter one on the right.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object using a HSB color string. + * let c1 = color('hsb(160, 100%, 50%)'); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c1); + * rect(0, 10, 45, 80); + * + * // Create a p5.Color object using a HSBA color string. + * let c2 = color('hsba(160, 100%, 50%, 0.5)'); + * + * // Draw the right rectangle. + * fill(c2); + * rect(55, 10, 45, 80); + * + * describe('Two green rectangles. A darker rectangle on the left and a brighter one on the right.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object using RGB values. + * let c1 = color(50, 55, 100); + * + * // Draw the left rectangle. + * fill(c1); + * rect(0, 10, 45, 80); + * + * // Switch the color mode to HSB. + * colorMode(HSB, 100); + * + * // Create a p5.Color object using HSB values. + * let c2 = color(50, 55, 100); + * + * // Draw the right rectangle. + * fill(c2); + * rect(55, 10, 45, 80); + * + * describe('Two blue rectangles. A darker rectangle on the left and a brighter one on the right.'); + * } + * + *
    + */ -/** - * @method color - * @param {Number} v1 red or hue value relative to - * the current color range. - * @param {Number} v2 green or saturation value - * relative to the current color range. - * @param {Number} v3 blue or brightness value - * relative to the current color range. - * @param {Number} [alpha] - * @return {p5.Color} - */ + /** + * @method color + * @param {Number} v1 red or hue value relative to + * the current color range. + * @param {Number} v2 green or saturation value + * relative to the current color range. + * @param {Number} v3 blue or brightness value + * relative to the current color range. + * @param {Number} [alpha] + * @return {p5.Color} + */ -/** - * @method color - * @param {String} value a color string. - * @return {p5.Color} - */ + /** + * @method color + * @param {String} value a color string. + * @return {p5.Color} + */ -/** - * @method color - * @param {Number[]} values an array containing the red, green, blue, - * and alpha components of the color. - * @return {p5.Color} - */ + /** + * @method color + * @param {Number[]} values an array containing the red, green, blue, + * and alpha components of the color. + * @return {p5.Color} + */ -/** - * @method color - * @param {p5.Color} color - * @return {p5.Color} - */ -p5.prototype.color = function(...args) { - p5._validateParameters('color', args); - if (args[0] instanceof p5.Color) { - return args[0]; // Do nothing if argument is already a color object. - } + /** + * @method color + * @param {p5.Color} color + * @return {p5.Color} + */ + fn.color = function(...args) { + p5._validateParameters('color', args); + if (args[0] instanceof p5.Color) { + return args[0]; // Do nothing if argument is already a color object. + } - const arg = Array.isArray(args[0]) ? args[0] : args; - return new p5.Color(this, arg); -}; + const arg = Array.isArray(args[0]) ? args[0] : args; + return new p5.Color(this, arg); + }; -/** - * Gets the green value of a color. - * - * `green()` extracts the green value from a - * p5.Color object, an array of color components, or - * a CSS color string. - * - * By default, `green()` returns a color's green value in the range 0 - * to 255. If the colorMode() is set to RGB, it - * returns the green value in the given range. - * - * @method green - * @param {p5.Color|Number[]|String} color p5.Color object, array of - * color components, or CSS color string. - * @return {Number} the green value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object. - * let c = color(175, 100, 220); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'greenValue' to 100. - * let greenValue = green(c); - * - * // Draw the right rectangle. - * fill(0, greenValue, 0); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light purple and the right one is dark green.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a color array. - * let c = [175, 100, 220]; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'greenValue' to 100. - * let greenValue = green(c); - * - * // Draw the right rectangle. - * fill(0, greenValue, 0); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light purple and the right one is dark green.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a CSS color string. - * let c = 'rgb(175, 100, 220)'; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'greenValue' to 100. - * let greenValue = green(c); - * - * // Draw the right rectangle. - * fill(0, greenValue, 0); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light purple and the right one is dark green.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use RGB color with values in the range 0-100. - * colorMode(RGB, 100); - * - * // Create a p5.Color object using RGB values. - * let c = color(69, 39, 86); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'greenValue' to 39. - * let greenValue = green(c); - * - * // Draw the right rectangle. - * fill(0, greenValue, 0); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light purple and the right one is dark green.'); - * } - * - *
    - */ -p5.prototype.green = function(c) { - p5._validateParameters('green', arguments); - return this.color(c)._getGreen(); -}; + /** + * Gets the green value of a color. + * + * `green()` extracts the green value from a + * p5.Color object, an array of color components, or + * a CSS color string. + * + * By default, `green()` returns a color's green value in the range 0 + * to 255. If the colorMode() is set to RGB, it + * returns the green value in the given range. + * + * @method green + * @param {p5.Color|Number[]|String} color p5.Color object, array of + * color components, or CSS color string. + * @return {Number} the green value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object. + * let c = color(175, 100, 220); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'greenValue' to 100. + * let greenValue = green(c); + * + * // Draw the right rectangle. + * fill(0, greenValue, 0); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light purple and the right one is dark green.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a color array. + * let c = [175, 100, 220]; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'greenValue' to 100. + * let greenValue = green(c); + * + * // Draw the right rectangle. + * fill(0, greenValue, 0); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light purple and the right one is dark green.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a CSS color string. + * let c = 'rgb(175, 100, 220)'; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'greenValue' to 100. + * let greenValue = green(c); + * + * // Draw the right rectangle. + * fill(0, greenValue, 0); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light purple and the right one is dark green.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use RGB color with values in the range 0-100. + * colorMode(RGB, 100); + * + * // Create a p5.Color object using RGB values. + * let c = color(69, 39, 86); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'greenValue' to 39. + * let greenValue = green(c); + * + * // Draw the right rectangle. + * fill(0, greenValue, 0); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light purple and the right one is dark green.'); + * } + * + *
    + */ + fn.green = function(c) { + p5._validateParameters('green', arguments); + return this.color(c)._getGreen(); + }; -/** - * Gets the hue value of a color. - * - * `hue()` extracts the hue value from a - * p5.Color object, an array of color components, or - * a CSS color string. - * - * Hue describes a color's position on the color wheel. By default, `hue()` - * returns a color's HSL hue in the range 0 to 360. If the - * colorMode() is set to HSB or HSL, it returns the hue - * value in the given mode. - * - * @method hue - * @param {p5.Color|Number[]|String} color p5.Color object, array of - * color components, or CSS color string. - * @return {Number} the hue value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use HSL color. - * colorMode(HSL); - * - * // Create a p5.Color object. - * let c = color(0, 50, 100); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 20, 35, 60); - * - * // Set 'hueValue' to 0. - * let hueValue = hue(c); - * - * // Draw the right rectangle. - * fill(hueValue); - * rect(50, 20, 35, 60); - * - * describe( - * 'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.' - * ); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use HSL color. - * colorMode(HSL); - * - * // Create a color array. - * let c = [0, 50, 100]; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 20, 35, 60); - * - * // Set 'hueValue' to 0. - * let hueValue = hue(c); - * - * // Draw the right rectangle. - * fill(hueValue); - * rect(50, 20, 35, 60); - * - * describe( - * 'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.' - * ); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use HSL color. - * colorMode(HSL); - * - * // Create a CSS color string. - * let c = 'rgb(255, 128, 128)'; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 20, 35, 60); - * - * // Set 'hueValue' to 0. - * let hueValue = hue(c); - * - * // Draw the right rectangle. - * fill(hueValue); - * rect(50, 20, 35, 60); - * - * describe( - * 'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.' - * ); - * } - * - *
    - */ -p5.prototype.hue = function(c) { - p5._validateParameters('hue', arguments); - return this.color(c)._getHue(); -}; + /** + * Gets the hue value of a color. + * + * `hue()` extracts the hue value from a + * p5.Color object, an array of color components, or + * a CSS color string. + * + * Hue describes a color's position on the color wheel. By default, `hue()` + * returns a color's HSL hue in the range 0 to 360. If the + * colorMode() is set to HSB or HSL, it returns the hue + * value in the given mode. + * + * @method hue + * @param {p5.Color|Number[]|String} color p5.Color object, array of + * color components, or CSS color string. + * @return {Number} the hue value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use HSL color. + * colorMode(HSL); + * + * // Create a p5.Color object. + * let c = color(0, 50, 100); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 20, 35, 60); + * + * // Set 'hueValue' to 0. + * let hueValue = hue(c); + * + * // Draw the right rectangle. + * fill(hueValue); + * rect(50, 20, 35, 60); + * + * describe( + * 'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.' + * ); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use HSL color. + * colorMode(HSL); + * + * // Create a color array. + * let c = [0, 50, 100]; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 20, 35, 60); + * + * // Set 'hueValue' to 0. + * let hueValue = hue(c); + * + * // Draw the right rectangle. + * fill(hueValue); + * rect(50, 20, 35, 60); + * + * describe( + * 'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.' + * ); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use HSL color. + * colorMode(HSL); + * + * // Create a CSS color string. + * let c = 'rgb(255, 128, 128)'; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 20, 35, 60); + * + * // Set 'hueValue' to 0. + * let hueValue = hue(c); + * + * // Draw the right rectangle. + * fill(hueValue); + * rect(50, 20, 35, 60); + * + * describe( + * 'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.' + * ); + * } + * + *
    + */ + fn.hue = function(c) { + p5._validateParameters('hue', arguments); + return this.color(c)._getHue(); + }; -/** - * Blends two colors to find a third color between them. - * - * The `amt` parameter specifies the amount to interpolate between the two - * values. 0 is equal to the first color, 0.1 is very near the first color, - * 0.5 is halfway between the two colors, and so on. Negative numbers are set - * to 0. Numbers greater than 1 are set to 1. This differs from the behavior of - * lerp. It's necessary because numbers outside of the - * interval [0, 1] will produce strange and unexpected colors. - * - * The way that colors are interpolated depends on the current - * colorMode(). - * - * @method lerpColor - * @param {p5.Color} c1 interpolate from this color. - * @param {p5.Color} c2 interpolate to this color. - * @param {Number} amt number between 0 and 1. - * @return {p5.Color} interpolated color. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create p5.Color objects to interpolate between. - * let from = color(218, 165, 32); - * let to = color(72, 61, 139); - * - * // Create intermediate colors. - * let interA = lerpColor(from, to, 0.33); - * let interB = lerpColor(from, to, 0.66); - * - * // Draw the left rectangle. - * noStroke(); - * fill(from); - * rect(10, 20, 20, 60); - * - * // Draw the left-center rectangle. - * fill(interA); - * rect(30, 20, 20, 60); - * - * // Draw the right-center rectangle. - * fill(interB); - * rect(50, 20, 20, 60); - * - * // Draw the right rectangle. - * fill(to); - * rect(70, 20, 20, 60); - * - * describe( - * 'Four rectangles. From left to right, the rectangles are tan, brown, brownish purple, and purple.' - * ); - * } - * - *
    - */ -p5.prototype.lerpColor = function(c1, c2, amt) { - p5._validateParameters('lerpColor', arguments); + /** + * Blends two colors to find a third color between them. + * + * The `amt` parameter specifies the amount to interpolate between the two + * values. 0 is equal to the first color, 0.1 is very near the first color, + * 0.5 is halfway between the two colors, and so on. Negative numbers are set + * to 0. Numbers greater than 1 are set to 1. This differs from the behavior of + * lerp. It's necessary because numbers outside of the + * interval [0, 1] will produce strange and unexpected colors. + * + * The way that colors are interpolated depends on the current + * colorMode(). + * + * @method lerpColor + * @param {p5.Color} c1 interpolate from this color. + * @param {p5.Color} c2 interpolate to this color. + * @param {Number} amt number between 0 and 1. + * @return {p5.Color} interpolated color. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create p5.Color objects to interpolate between. + * let from = color(218, 165, 32); + * let to = color(72, 61, 139); + * + * // Create intermediate colors. + * let interA = lerpColor(from, to, 0.33); + * let interB = lerpColor(from, to, 0.66); + * + * // Draw the left rectangle. + * noStroke(); + * fill(from); + * rect(10, 20, 20, 60); + * + * // Draw the left-center rectangle. + * fill(interA); + * rect(30, 20, 20, 60); + * + * // Draw the right-center rectangle. + * fill(interB); + * rect(50, 20, 20, 60); + * + * // Draw the right rectangle. + * fill(to); + * rect(70, 20, 20, 60); + * + * describe( + * 'Four rectangles. From left to right, the rectangles are tan, brown, brownish purple, and purple.' + * ); + * } + * + *
    + */ + fn.lerpColor = function(c1, c2, amt) { + p5._validateParameters('lerpColor', arguments); - // Find the closest common ancestor color space - let spaceIndex = -1; - while( - ( - spaceIndex+1 < c1.color.space.path.length || - spaceIndex+1 < c2.color.space.path.length - ) && - c1.color.space.path[spaceIndex+1] === c2.color.space.path[spaceIndex+1] - ){ - spaceIndex += 1; - } + // Find the closest common ancestor color space + let spaceIndex = -1; + while( + ( + spaceIndex+1 < c1.color.space.path.length || + spaceIndex+1 < c2.color.space.path.length + ) && + c1.color.space.path[spaceIndex+1] === c2.color.space.path[spaceIndex+1] + ){ + spaceIndex += 1; + } - if (spaceIndex === -1) { - // This probably will not occur in practice - throw new Error('Cannot lerp colors. No common color space found'); - } + if (spaceIndex === -1) { + // This probably will not occur in practice + throw new Error('Cannot lerp colors. No common color space found'); + } - // Get lerp value as a color in the common ancestor color space - const lerpColor = range(c1.color, c2.color, { - space: c1.color.space.path[spaceIndex].id - })(amt); + // Get lerp value as a color in the common ancestor color space + const lerpColor = range(c1.color, c2.color, { + space: c1.color.space.path[spaceIndex].id + })(amt); - return new p5.Color(this, lerpColor); -}; + return new p5.Color(this, lerpColor); + }; -/** - * Gets the lightness value of a color. - * - * `lightness()` extracts the HSL lightness value from a - * p5.Color object, an array of color components, or - * a CSS color string. - * - * By default, `lightness()` returns a color's HSL lightness in the range 0 - * to 100. If the colorMode() is set to HSL, it - * returns the lightness value in the given range. - * - * @method lightness - * @param {p5.Color|Number[]|String} color p5.Color object, array of - * color components, or CSS color string. - * @return {Number} the lightness value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Use HSL color. - * colorMode(HSL); - * - * // Create a p5.Color object using HSL values. - * let c = color(0, 100, 75); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'lightValue' to 75. - * let lightValue = lightness(c); - * - * // Draw the right rectangle. - * fill(lightValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is gray.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Use HSL color. - * colorMode(HSL); - * - * // Create a color array. - * let c = [0, 100, 75]; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'lightValue' to 75. - * let lightValue = lightness(c); - * - * // Draw the right rectangle. - * fill(lightValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is gray.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Use HSL color. - * colorMode(HSL); - * - * // Create a CSS color string. - * let c = 'rgb(255, 128, 128)'; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'lightValue' to 75. - * let lightValue = lightness(c); - * - * // Draw the right rectangle. - * fill(lightValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is gray.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Use HSL color with values in the range 0-255. - * colorMode(HSL, 255); - * - * // Create a p5.Color object using HSL values. - * let c = color(0, 255, 191.5); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'lightValue' to 191.5. - * let lightValue = lightness(c); - * - * // Draw the right rectangle. - * fill(lightValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is gray.'); - * } - * - *
    - */ -p5.prototype.lightness = function(c) { - p5._validateParameters('lightness', arguments); - return this.color(c)._getLightness(); -}; + /** + * Gets the lightness value of a color. + * + * `lightness()` extracts the HSL lightness value from a + * p5.Color object, an array of color components, or + * a CSS color string. + * + * By default, `lightness()` returns a color's HSL lightness in the range 0 + * to 100. If the colorMode() is set to HSL, it + * returns the lightness value in the given range. + * + * @method lightness + * @param {p5.Color|Number[]|String} color p5.Color object, array of + * color components, or CSS color string. + * @return {Number} the lightness value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Use HSL color. + * colorMode(HSL); + * + * // Create a p5.Color object using HSL values. + * let c = color(0, 100, 75); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'lightValue' to 75. + * let lightValue = lightness(c); + * + * // Draw the right rectangle. + * fill(lightValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is gray.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Use HSL color. + * colorMode(HSL); + * + * // Create a color array. + * let c = [0, 100, 75]; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'lightValue' to 75. + * let lightValue = lightness(c); + * + * // Draw the right rectangle. + * fill(lightValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is gray.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Use HSL color. + * colorMode(HSL); + * + * // Create a CSS color string. + * let c = 'rgb(255, 128, 128)'; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'lightValue' to 75. + * let lightValue = lightness(c); + * + * // Draw the right rectangle. + * fill(lightValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is gray.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Use HSL color with values in the range 0-255. + * colorMode(HSL, 255); + * + * // Create a p5.Color object using HSL values. + * let c = color(0, 255, 191.5); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'lightValue' to 191.5. + * let lightValue = lightness(c); + * + * // Draw the right rectangle. + * fill(lightValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is gray.'); + * } + * + *
    + */ + fn.lightness = function(c) { + p5._validateParameters('lightness', arguments); + return this.color(c)._getLightness(); + }; -/** - * Gets the red value of a color. - * - * `red()` extracts the red value from a - * p5.Color object, an array of color components, or - * a CSS color string. - * - * By default, `red()` returns a color's red value in the range 0 - * to 255. If the colorMode() is set to RGB, it - * returns the red value in the given range. - * - * @method red - * @param {p5.Color|Number[]|String} color p5.Color object, array of - * color components, or CSS color string. - * @return {Number} the red value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object. - * let c = color(175, 100, 220); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'redValue' to 175. - * let redValue = red(c); - * - * // Draw the right rectangle. - * fill(redValue, 0, 0); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light purple and the right one is red.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a color array. - * let c = [175, 100, 220]; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'redValue' to 175. - * let redValue = red(c); - * - * // Draw the right rectangle. - * fill(redValue, 0, 0); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light purple and the right one is red.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a CSS color string. - * let c = 'rgb(175, 100, 220)'; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'redValue' to 175. - * let redValue = red(c); - * - * // Draw the right rectangle. - * fill(redValue, 0, 0); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light purple and the right one is red.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use RGB color with values in the range 0-100. - * colorMode(RGB, 100); - * - * // Create a p5.Color object. - * let c = color(69, 39, 86); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'redValue' to 69. - * let redValue = red(c); - * - * // Draw the right rectangle. - * fill(redValue, 0, 0); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is light purple and the right one is red.'); - * } - * - *
    - */ -p5.prototype.red = function(c) { - p5._validateParameters('red', arguments); - return this.color(c)._getRed(); -}; + /** + * Gets the red value of a color. + * + * `red()` extracts the red value from a + * p5.Color object, an array of color components, or + * a CSS color string. + * + * By default, `red()` returns a color's red value in the range 0 + * to 255. If the colorMode() is set to RGB, it + * returns the red value in the given range. + * + * @method red + * @param {p5.Color|Number[]|String} color p5.Color object, array of + * color components, or CSS color string. + * @return {Number} the red value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object. + * let c = color(175, 100, 220); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'redValue' to 175. + * let redValue = red(c); + * + * // Draw the right rectangle. + * fill(redValue, 0, 0); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light purple and the right one is red.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a color array. + * let c = [175, 100, 220]; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'redValue' to 175. + * let redValue = red(c); + * + * // Draw the right rectangle. + * fill(redValue, 0, 0); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light purple and the right one is red.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a CSS color string. + * let c = 'rgb(175, 100, 220)'; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'redValue' to 175. + * let redValue = red(c); + * + * // Draw the right rectangle. + * fill(redValue, 0, 0); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light purple and the right one is red.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use RGB color with values in the range 0-100. + * colorMode(RGB, 100); + * + * // Create a p5.Color object. + * let c = color(69, 39, 86); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'redValue' to 69. + * let redValue = red(c); + * + * // Draw the right rectangle. + * fill(redValue, 0, 0); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is light purple and the right one is red.'); + * } + * + *
    + */ + fn.red = function(c) { + p5._validateParameters('red', arguments); + return this.color(c)._getRed(); + }; -/** - * Gets the saturation value of a color. - * - * `saturation()` extracts the saturation value from a - * p5.Color object, an array of color components, or - * a CSS color string. - * - * Saturation is scaled differently in HSB and HSL. By default, `saturation()` - * returns a color's HSL saturation in the range 0 to 100. If the - * colorMode() is set to HSB or HSL, it returns the - * saturation value in the given mode. - * - * @method saturation - * @param {p5.Color|Number[]|String} color p5.Color object, array of - * color components, or CSS color string. - * @return {Number} the saturation value - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Use HSB color. - * colorMode(HSB); - * - * // Create a p5.Color object. - * let c = color(0, 50, 100); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'satValue' to 50. - * let satValue = saturation(c); - * - * // Draw the right rectangle. - * fill(satValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is dark gray.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Use HSB color. - * colorMode(HSB); - * - * // Create a color array. - * let c = [0, 50, 100]; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'satValue' to 100. - * let satValue = saturation(c); - * - * // Draw the right rectangle. - * fill(satValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is gray.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Use HSB color. - * colorMode(HSB); - * - * // Create a CSS color string. - * let c = 'rgb(255, 128, 128)'; - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'satValue' to 100. - * let satValue = saturation(c); - * - * // Draw the right rectangle. - * fill(satValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is gray.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Use HSL color. - * colorMode(HSL); - * - * // Create a p5.Color object. - * let c = color(0, 100, 75); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'satValue' to 100. - * let satValue = saturation(c); - * - * // Draw the right rectangle. - * fill(satValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is white.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Use HSL color with values in the range 0-255. - * colorMode(HSL, 255); - * - * // Create a p5.Color object. - * let c = color(0, 255, 191.5); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 15, 35, 70); - * - * // Set 'satValue' to 255. - * let satValue = saturation(c); - * - * // Draw the right rectangle. - * fill(satValue); - * rect(50, 15, 35, 70); - * - * describe('Two rectangles. The left one is salmon pink and the right one is white.'); - * } - * - *
    - */ -p5.prototype.saturation = function(c) { - p5._validateParameters('saturation', arguments); - return this.color(c)._getSaturation(); -}; + /** + * Gets the saturation value of a color. + * + * `saturation()` extracts the saturation value from a + * p5.Color object, an array of color components, or + * a CSS color string. + * + * Saturation is scaled differently in HSB and HSL. By default, `saturation()` + * returns a color's HSL saturation in the range 0 to 100. If the + * colorMode() is set to HSB or HSL, it returns the + * saturation value in the given mode. + * + * @method saturation + * @param {p5.Color|Number[]|String} color p5.Color object, array of + * color components, or CSS color string. + * @return {Number} the saturation value + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Use HSB color. + * colorMode(HSB); + * + * // Create a p5.Color object. + * let c = color(0, 50, 100); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'satValue' to 50. + * let satValue = saturation(c); + * + * // Draw the right rectangle. + * fill(satValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is dark gray.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Use HSB color. + * colorMode(HSB); + * + * // Create a color array. + * let c = [0, 50, 100]; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'satValue' to 100. + * let satValue = saturation(c); + * + * // Draw the right rectangle. + * fill(satValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is gray.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Use HSB color. + * colorMode(HSB); + * + * // Create a CSS color string. + * let c = 'rgb(255, 128, 128)'; + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'satValue' to 100. + * let satValue = saturation(c); + * + * // Draw the right rectangle. + * fill(satValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is gray.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Use HSL color. + * colorMode(HSL); + * + * // Create a p5.Color object. + * let c = color(0, 100, 75); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'satValue' to 100. + * let satValue = saturation(c); + * + * // Draw the right rectangle. + * fill(satValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is white.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Use HSL color with values in the range 0-255. + * colorMode(HSL, 255); + * + * // Create a p5.Color object. + * let c = color(0, 255, 191.5); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 15, 35, 70); + * + * // Set 'satValue' to 255. + * let satValue = saturation(c); + * + * // Draw the right rectangle. + * fill(satValue); + * rect(50, 15, 35, 70); + * + * describe('Two rectangles. The left one is salmon pink and the right one is white.'); + * } + * + *
    + */ + fn.saturation = function(c) { + p5._validateParameters('saturation', arguments); + return this.color(c)._getSaturation(); + }; +} + +export default creatingReading; -export default p5; +if(typeof p5 !== 'undefined'){ + creatingReading(p5, p5.prototype); +} diff --git a/src/color/index.js b/src/color/index.js new file mode 100644 index 0000000000..bd0fb96f58 --- /dev/null +++ b/src/color/index.js @@ -0,0 +1,9 @@ +import creatingReading from './creating_reading.js'; +import p5color from './p5.Color.js'; +import setting from './setting.js'; + +export default function(p5){ + p5.registerAddon(creatingReading); + p5.registerAddon(p5color); + p5.registerAddon(setting); +} diff --git a/src/color/p5.Color.js b/src/color/p5.Color.js index 658ecb248d..70245005b8 100644 --- a/src/color/p5.Color.js +++ b/src/color/p5.Color.js @@ -7,7 +7,6 @@ * @requires color_conversion */ -import p5 from '../core/main'; import * as constants from '../core/constants'; import { ColorSpace, @@ -60,444 +59,450 @@ ColorSpace.register(P3); ColorSpace.register(A98RGB_Linear); ColorSpace.register(A98RGB); -/** - * A class to describe a color. - * - * Each `p5.Color` object stores the color mode - * and level maxes that were active during its construction. These values are - * used to interpret the arguments passed to the object's constructor. They - * also determine output formatting such as when - * saturation() is called. - * - * Color is stored internally as an array of ideal RGBA values in floating - * point form, normalized from 0 to 1. These values are used to calculate the - * closest screen colors, which are RGBA levels from 0 to 255. Screen colors - * are sent to the renderer. - * - * When different color representations are calculated, the results are cached - * for performance. These values are normalized, floating-point numbers. - * - * Note: color() is the recommended way to create an - * instance of this class. - * - * @class p5.Color - * @param {p5} [pInst] pointer to p5 instance. - * - * @param {Number[]|String} vals an array containing the color values - * for red, green, blue and alpha channel - * or CSS color. - */ -p5.Color = class Color { - color; - maxes; - mode; - - constructor(pInst, vals) { - // This changes with the sketch's setting - // NOTE: Maintaining separate maxes for different color space is awkward. - // Consider just one universal maxes. - this.maxes = pInst._colorMaxes; - // This changes with the color object - this.mode = pInst._colorMode; - - if (typeof vals === 'object' && !Array.isArray(vals) && vals !== null){ - this.color = vals; - } else if(typeof vals[0] === 'string') { - try{ - // NOTE: this will not necessarily have the right color mode - this.color = parse(vals[0]); - }catch(err){ - // TODO: Invalid color string - console.error('Invalid color string'); - } - - }else{ - let alpha; - - if(vals.length === 4){ - alpha = vals[vals.length-1]; - }else if (vals.length === 2){ - alpha = vals[1]; - vals = [vals[0], vals[0], vals[0]]; - }else if(vals.length === 1){ - vals = [vals[0], vals[0], vals[0]]; - } - alpha = alpha !== undefined - ? alpha / pInst._colorMaxes[pInst._colorMode][3] - : 1; - - // _colorMode can be 'rgb', 'hsb', or 'hsl' - // These should map to color.js color space - let space = 'srgb'; - let coords = vals; - switch(pInst._colorMode){ - case 'rgb': - space = 'srgb'; - coords = [ - vals[0] / pInst._colorMaxes[pInst._colorMode][0], - vals[1] / pInst._colorMaxes[pInst._colorMode][1], - vals[2] / pInst._colorMaxes[pInst._colorMode][2] - ]; - break; - case 'hsb': - // TODO: need implementation - space = 'hsb'; - coords = [ - vals[0] / pInst._colorMaxes[pInst._colorMode][0] * 360, - vals[1] / pInst._colorMaxes[pInst._colorMode][1] * 100, - vals[2] / pInst._colorMaxes[pInst._colorMode][2] * 100 - ]; - break; - case 'hsl': - space = 'hsl'; - coords = [ - vals[0] / pInst._colorMaxes[pInst._colorMode][0] * 360, - vals[1] / pInst._colorMaxes[pInst._colorMode][1] * 100, - vals[2] / pInst._colorMaxes[pInst._colorMode][2] * 100 - ]; - break; - default: - console.error('Invalid color mode'); - } - - const color = { - space, - coords, - alpha - }; - this.color = to(color, space); - } - } - +function color(p5, fn){ /** - * Returns the color formatted as a `String`. - * - * Calling `myColor.toString()` can be useful for debugging, as in - * `print(myColor.toString())`. It's also helpful for using p5.js with other - * libraries. + * A class to describe a color. * - * The parameter, `format`, is optional. If a format string is passed, as in - * `myColor.toString('#rrggbb')`, it will determine how the color string is - * formatted. By default, color strings are formatted as `'rgba(r, g, b, a)'`. + * Each `p5.Color` object stores the color mode + * and level maxes that were active during its construction. These values are + * used to interpret the arguments passed to the object's constructor. They + * also determine output formatting such as when + * saturation() is called. * - * @param {String} [format] how the color string will be formatted. - * Leaving this empty formats the string as rgba(r, g, b, a). - * '#rgb' '#rgba' '#rrggbb' and '#rrggbbaa' format as hexadecimal color codes. - * 'rgb' 'hsb' and 'hsl' return the color formatted in the specified color mode. - * 'rgba' 'hsba' and 'hsla' are the same as above but with alpha channels. - * 'rgb%' 'hsb%' 'hsl%' 'rgba%' 'hsba%' and 'hsla%' format as percentages. - * @return {String} the formatted string. + * Color is stored internally as an array of ideal RGBA values in floating + * point form, normalized from 0 to 1. These values are used to calculate the + * closest screen colors, which are RGBA levels from 0 to 255. Screen colors + * are sent to the renderer. * - *
    - * - * function setup() { - * createCanvas(100, 100); + * When different color representations are calculated, the results are cached + * for performance. These values are normalized, floating-point numbers. * - * background(200); + * Note: color() is the recommended way to create an + * instance of this class. * - * // Create a p5.Color object. - * let myColor = color('darkorchid'); + * @class p5.Color + * @param {p5} [pInst] pointer to p5 instance. * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display the text. - * text(myColor.toString('#rrggbb'), 50, 50); - * - * describe('The text "#9932cc" written in purple on a gray background.'); - * } - * - *
    + * @param {Number[]|String} vals an array containing the color values + * for red, green, blue and alpha channel + * or CSS color. */ - toString(format) { - // NOTE: memoize - return serialize(this.color, { - format - }); - } + p5.Color = class Color { + color; + maxes; + mode; + + constructor(pInst, vals) { + // This changes with the sketch's setting + // NOTE: Maintaining separate maxes for different color space is awkward. + // Consider just one universal maxes. + this.maxes = pInst._colorMaxes; + // This changes with the color object + this.mode = pInst._colorMode; + + if (typeof vals === 'object' && !Array.isArray(vals) && vals !== null){ + this.color = vals; + } else if(typeof vals[0] === 'string') { + try{ + // NOTE: this will not necessarily have the right color mode + this.color = parse(vals[0]); + }catch(err){ + // TODO: Invalid color string + console.error('Invalid color string'); + } + + }else{ + let alpha; + + if(vals.length === 4){ + alpha = vals[vals.length-1]; + }else if (vals.length === 2){ + alpha = vals[1]; + vals = [vals[0], vals[0], vals[0]]; + }else if(vals.length === 1){ + vals = [vals[0], vals[0], vals[0]]; + } + alpha = alpha !== undefined + ? alpha / pInst._colorMaxes[pInst._colorMode][3] + : 1; + + // _colorMode can be 'rgb', 'hsb', or 'hsl' + // These should map to color.js color space + let space = 'srgb'; + let coords = vals; + switch(pInst._colorMode){ + case 'rgb': + space = 'srgb'; + coords = [ + vals[0] / pInst._colorMaxes[pInst._colorMode][0], + vals[1] / pInst._colorMaxes[pInst._colorMode][1], + vals[2] / pInst._colorMaxes[pInst._colorMode][2] + ]; + break; + case 'hsb': + // TODO: need implementation + space = 'hsb'; + coords = [ + vals[0] / pInst._colorMaxes[pInst._colorMode][0] * 360, + vals[1] / pInst._colorMaxes[pInst._colorMode][1] * 100, + vals[2] / pInst._colorMaxes[pInst._colorMode][2] * 100 + ]; + break; + case 'hsl': + space = 'hsl'; + coords = [ + vals[0] / pInst._colorMaxes[pInst._colorMode][0] * 360, + vals[1] / pInst._colorMaxes[pInst._colorMode][1] * 100, + vals[2] / pInst._colorMaxes[pInst._colorMode][2] * 100 + ]; + break; + default: + console.error('Invalid color mode'); + } + + const color = { + space, + coords, + alpha + }; + this.color = to(color, space); + } + } - /** - * Sets the red component of a color. - * - * The range depends on the colorMode(). In the - * default RGB mode it's between 0 and 255. - * - * @param {Number} red the new red value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object. - * let c = color(255, 128, 128); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 20, 35, 60); - * - * // Change the red value. - * c.setRed(64); - * - * // Draw the right rectangle. - * fill(c); - * rect(50, 20, 35, 60); - * - * describe('Two rectangles. The left one is salmon pink and the right one is teal.'); - * } - * - *
    - */ - setRed(new_red) { - const red_val = new_red / this.maxes[constants.RGB][0]; - if(this.mode === constants.RGB){ - this.color.coords[0] = red_val; - }else{ - // Will do an imprecise conversion to 'srgb', not recommended - const space = this.color.space.id; - const representation = to(this.color, 'srgb'); - representation.coords[0] = red_val; - this.color = to(representation, space); + /** + * Returns the color formatted as a `String`. + * + * Calling `myColor.toString()` can be useful for debugging, as in + * `print(myColor.toString())`. It's also helpful for using p5.js with other + * libraries. + * + * The parameter, `format`, is optional. If a format string is passed, as in + * `myColor.toString('#rrggbb')`, it will determine how the color string is + * formatted. By default, color strings are formatted as `'rgba(r, g, b, a)'`. + * + * @param {String} [format] how the color string will be formatted. + * Leaving this empty formats the string as rgba(r, g, b, a). + * '#rgb' '#rgba' '#rrggbb' and '#rrggbbaa' format as hexadecimal color codes. + * 'rgb' 'hsb' and 'hsl' return the color formatted in the specified color mode. + * 'rgba' 'hsba' and 'hsla' are the same as above but with alpha channels. + * 'rgb%' 'hsb%' 'hsl%' 'rgba%' 'hsba%' and 'hsla%' format as percentages. + * @return {String} the formatted string. + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object. + * let myColor = color('darkorchid'); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display the text. + * text(myColor.toString('#rrggbb'), 50, 50); + * + * describe('The text "#9932cc" written in purple on a gray background.'); + * } + * + *
    + */ + toString(format) { + // NOTE: memoize + return serialize(this.color, { + format + }); } - } - /** - * Sets the green component of a color. - * - * The range depends on the colorMode(). In the - * default RGB mode it's between 0 and 255. - * - * @param {Number} green the new green value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object. - * let c = color(255, 128, 128); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 20, 35, 60); - * - * // Change the green value. - * c.setGreen(255); - * - * // Draw the right rectangle. - * fill(c); - * rect(50, 20, 35, 60); - * - * describe('Two rectangles. The left one is salmon pink and the right one is yellow.'); - * } - * - *
    - **/ - setGreen(new_green) { - const green_val = new_green / this.maxes[constants.RGB][1]; - if(this.mode === constants.RGB){ - this.color.coords[1] = green_val; - }else{ - // Will do an imprecise conversion to 'srgb', not recommended - const space = this.color.space.id; - const representation = to(this.color, 'srgb'); - representation.coords[1] = green_val; - this.color = to(representation, space); + /** + * Sets the red component of a color. + * + * The range depends on the colorMode(). In the + * default RGB mode it's between 0 and 255. + * + * @param {Number} red the new red value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object. + * let c = color(255, 128, 128); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 20, 35, 60); + * + * // Change the red value. + * c.setRed(64); + * + * // Draw the right rectangle. + * fill(c); + * rect(50, 20, 35, 60); + * + * describe('Two rectangles. The left one is salmon pink and the right one is teal.'); + * } + * + *
    + */ + setRed(new_red) { + const red_val = new_red / this.maxes[constants.RGB][0]; + if(this.mode === constants.RGB){ + this.color.coords[0] = red_val; + }else{ + // Will do an imprecise conversion to 'srgb', not recommended + const space = this.color.space.id; + const representation = to(this.color, 'srgb'); + representation.coords[0] = red_val; + this.color = to(representation, space); + } } - } - /** - * Sets the blue component of a color. - * - * The range depends on the colorMode(). In the - * default RGB mode it's between 0 and 255. - * - * @param {Number} blue the new blue value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object. - * let c = color(255, 128, 128); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 20, 35, 60); - * - * // Change the blue value. - * c.setBlue(255); - * - * // Draw the right rectangle. - * fill(c); - * rect(50, 20, 35, 60); - * - * describe('Two rectangles. The left one is salmon pink and the right one is pale fuchsia.'); - * } - * - *
    - **/ - setBlue(new_blue) { - const blue_val = new_blue / this.maxes[constants.RGB][2]; - if(this.mode === constants.RGB){ - this.color.coords[2] = blue_val; - }else{ - // Will do an imprecise conversion to 'srgb', not recommended - const space = this.color.space.id; - const representation = to(this.color, 'srgb'); - representation.coords[2] = blue_val; - this.color = to(representation, space); + /** + * Sets the green component of a color. + * + * The range depends on the colorMode(). In the + * default RGB mode it's between 0 and 255. + * + * @param {Number} green the new green value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object. + * let c = color(255, 128, 128); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 20, 35, 60); + * + * // Change the green value. + * c.setGreen(255); + * + * // Draw the right rectangle. + * fill(c); + * rect(50, 20, 35, 60); + * + * describe('Two rectangles. The left one is salmon pink and the right one is yellow.'); + * } + * + *
    + **/ + setGreen(new_green) { + const green_val = new_green / this.maxes[constants.RGB][1]; + if(this.mode === constants.RGB){ + this.color.coords[1] = green_val; + }else{ + // Will do an imprecise conversion to 'srgb', not recommended + const space = this.color.space.id; + const representation = to(this.color, 'srgb'); + representation.coords[1] = green_val; + this.color = to(representation, space); + } } - } - /** - * Sets the alpha (transparency) value of a color. - * - * The range depends on the - * colorMode(). In the default RGB mode it's - * between 0 and 255. - * - * @param {Number} alpha the new alpha value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object. - * let c = color(255, 128, 128); - * - * // Draw the left rectangle. - * noStroke(); - * fill(c); - * rect(15, 20, 35, 60); - * - * // Change the alpha value. - * c.setAlpha(128); - * - * // Draw the right rectangle. - * fill(c); - * rect(50, 20, 35, 60); - * - * describe('Two rectangles. The left one is salmon pink and the right one is faded pink.'); - * } - * - *
    - **/ - setAlpha(new_alpha) { - this.color.alpha = new_alpha / this.maxes[this.mode][3]; - } - - _getRed() { - if(this.mode === constants.RGB){ - return this.color.coords[0] * this.maxes[constants.RGB][0]; - }else{ - // Will do an imprecise conversion to 'srgb', not recommended - return to(this.color, 'srgb').coords[0] * this.maxes[constants.RGB][0]; + /** + * Sets the blue component of a color. + * + * The range depends on the colorMode(). In the + * default RGB mode it's between 0 and 255. + * + * @param {Number} blue the new blue value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object. + * let c = color(255, 128, 128); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 20, 35, 60); + * + * // Change the blue value. + * c.setBlue(255); + * + * // Draw the right rectangle. + * fill(c); + * rect(50, 20, 35, 60); + * + * describe('Two rectangles. The left one is salmon pink and the right one is pale fuchsia.'); + * } + * + *
    + **/ + setBlue(new_blue) { + const blue_val = new_blue / this.maxes[constants.RGB][2]; + if(this.mode === constants.RGB){ + this.color.coords[2] = blue_val; + }else{ + // Will do an imprecise conversion to 'srgb', not recommended + const space = this.color.space.id; + const representation = to(this.color, 'srgb'); + representation.coords[2] = blue_val; + this.color = to(representation, space); + } + } + + /** + * Sets the alpha (transparency) value of a color. + * + * The range depends on the + * colorMode(). In the default RGB mode it's + * between 0 and 255. + * + * @param {Number} alpha the new alpha value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object. + * let c = color(255, 128, 128); + * + * // Draw the left rectangle. + * noStroke(); + * fill(c); + * rect(15, 20, 35, 60); + * + * // Change the alpha value. + * c.setAlpha(128); + * + * // Draw the right rectangle. + * fill(c); + * rect(50, 20, 35, 60); + * + * describe('Two rectangles. The left one is salmon pink and the right one is faded pink.'); + * } + * + *
    + **/ + setAlpha(new_alpha) { + this.color.alpha = new_alpha / this.maxes[this.mode][3]; + } + + _getRed() { + if(this.mode === constants.RGB){ + return this.color.coords[0] * this.maxes[constants.RGB][0]; + }else{ + // Will do an imprecise conversion to 'srgb', not recommended + return to(this.color, 'srgb').coords[0] * this.maxes[constants.RGB][0]; + } } - } - - _getGreen() { - if(this.mode === constants.RGB){ - return this.color.coords[1] * this.maxes[constants.RGB][1]; - }else{ - // Will do an imprecise conversion to 'srgb', not recommended - return to(this.color, 'srgb').coords[1] * this.maxes[constants.RGB][1]; + + _getGreen() { + if(this.mode === constants.RGB){ + return this.color.coords[1] * this.maxes[constants.RGB][1]; + }else{ + // Will do an imprecise conversion to 'srgb', not recommended + return to(this.color, 'srgb').coords[1] * this.maxes[constants.RGB][1]; + } } - } - - _getBlue() { - if(this.mode === constants.RGB){ - return this.color.coords[2] * this.maxes[constants.RGB][2]; - }else{ - // Will do an imprecise conversion to 'srgb', not recommended - return to(this.color, 'srgb').coords[2] * this.maxes[constants.RGB][2]; + + _getBlue() { + if(this.mode === constants.RGB){ + return this.color.coords[2] * this.maxes[constants.RGB][2]; + }else{ + // Will do an imprecise conversion to 'srgb', not recommended + return to(this.color, 'srgb').coords[2] * this.maxes[constants.RGB][2]; + } } - } - _getAlpha() { - return this.color.alpha * this.maxes[this.mode][3]; - } + _getAlpha() { + return this.color.alpha * this.maxes[this.mode][3]; + } - _getMode() { - return this.mode; - } + _getMode() { + return this.mode; + } - _getMaxes() { - return this.maxes; - } + _getMaxes() { + return this.maxes; + } - /** - * Hue is the same in HSB and HSL, but the maximum value may be different. - * This function will return the HSB-normalized saturation when supplied with - * an HSB color object, but will default to the HSL-normalized saturation - * otherwise. - */ - _getHue() { - if(this.mode === constants.HSB || this.mode === constants.HSL){ - return this.color.coords[0] / 360 * this.maxes[this.mode][0]; - }else{ - // Will do an imprecise conversion to 'HSL', not recommended - return to(this.color, 'hsl').coords[0] / 360 * this.maxes[this.mode][0]; + /** + * Hue is the same in HSB and HSL, but the maximum value may be different. + * This function will return the HSB-normalized saturation when supplied with + * an HSB color object, but will default to the HSL-normalized saturation + * otherwise. + */ + _getHue() { + if(this.mode === constants.HSB || this.mode === constants.HSL){ + return this.color.coords[0] / 360 * this.maxes[this.mode][0]; + }else{ + // Will do an imprecise conversion to 'HSL', not recommended + return to(this.color, 'hsl').coords[0] / 360 * this.maxes[this.mode][0]; + } } - } - /** - * Saturation is scaled differently in HSB and HSL. This function will return - * the HSB saturation when supplied with an HSB color object, but will default - * to the HSL saturation otherwise. - */ - _getSaturation() { - if(this.mode === constants.HSB || this.mode === constants.HSL){ - return this.color.coords[1] / 100 * this.maxes[this.mode][1]; - }else{ - // Will do an imprecise conversion to 'HSL', not recommended - return to(this.color, 'hsl').coords[1] / 100 * this.maxes[this.mode][1]; + /** + * Saturation is scaled differently in HSB and HSL. This function will return + * the HSB saturation when supplied with an HSB color object, but will default + * to the HSL saturation otherwise. + */ + _getSaturation() { + if(this.mode === constants.HSB || this.mode === constants.HSL){ + return this.color.coords[1] / 100 * this.maxes[this.mode][1]; + }else{ + // Will do an imprecise conversion to 'HSL', not recommended + return to(this.color, 'hsl').coords[1] / 100 * this.maxes[this.mode][1]; + } + } + + _getBrightness() { + if(this.mode === constants.HSB){ + return this.color.coords[2] / 100 * this.maxes[this.mode][2]; + }else{ + // Will do an imprecise conversion to 'HSB', not recommended + return to(this.color, 'hsb').coords[2] / 100 * this.maxes[this.mode][2]; + } } - } - - _getBrightness() { - if(this.mode === constants.HSB){ - return this.color.coords[2] / 100 * this.maxes[this.mode][2]; - }else{ - // Will do an imprecise conversion to 'HSB', not recommended - return to(this.color, 'hsb').coords[2] / 100 * this.maxes[this.mode][2]; + + _getLightness() { + if(this.mode === constants.HSL){ + return this.color.coords[2] / 100 * this.maxes[this.mode][2]; + }else{ + // Will do an imprecise conversion to 'HSB', not recommended + return to(this.color, 'hsl').coords[2] / 100 * this.maxes[this.mode][2]; + } } - } - - _getLightness() { - if(this.mode === constants.HSL){ - return this.color.coords[2] / 100 * this.maxes[this.mode][2]; - }else{ - // Will do an imprecise conversion to 'HSB', not recommended - return to(this.color, 'hsl').coords[2] / 100 * this.maxes[this.mode][2]; + + get _array() { + return [...this.color.coords, this.color.alpha]; } - } - get _array() { - return [...this.color.coords, this.color.alpha]; - } + get levels() { + return this._array.map(v => v * 255); + } + }; +} - get levels() { - return this._array.map(v => v * 255); - } -}; +export default color; -export default p5.Color; +if(typeof p5 !== 'undefined'){ + color(p5, p5.prototype); +} diff --git a/src/color/setting.js b/src/color/setting.js index e994df5144..36361bd759 100644 --- a/src/color/setting.js +++ b/src/color/setting.js @@ -6,1713 +6,1717 @@ * @requires constants */ -import p5 from '../core/main'; import * as constants from '../core/constants'; -import './p5.Color'; -/** - * Starts defining a shape that will mask any shapes drawn afterward. - * - * Any shapes drawn between `beginClip()` and - * endClip() will add to the mask shape. The mask - * will apply to anything drawn after endClip(). - * - * The parameter, `options`, is optional. If an object with an `invert` - * property is passed, as in `beginClip({ invert: true })`, it will be used to - * set the masking mode. `{ invert: true }` inverts the mask, creating holes - * in shapes that are masked. `invert` is `false` by default. - * - * Masks can be contained between the - * push() and pop() functions. - * Doing so allows unmasked shapes to be drawn after masked shapes. - * - * Masks can also be defined in a callback function that's passed to - * clip(). - * - * @method beginClip - * @param {Object} [options] an object containing clip settings. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a mask. - * beginClip(); - * triangle(15, 37, 30, 13, 43, 37); - * circle(45, 45, 7); - * endClip(); - * - * // Draw a backing shape. - * square(5, 5, 45); - * - * describe('A white triangle and circle on a gray background.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an inverted mask. - * beginClip({ invert: true }); - * triangle(15, 37, 30, 13, 43, 37); - * circle(45, 45, 7); - * endClip(); - * - * // Draw a backing shape. - * square(5, 5, 45); - * - * describe('A white square at the top-left corner of a gray square. The white square has a triangle and a circle cut out of it.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * noStroke(); - * - * // Draw a masked shape. - * push(); - * // Create a mask. - * beginClip(); - * triangle(15, 37, 30, 13, 43, 37); - * circle(45, 45, 7); - * endClip(); - * - * // Draw a backing shape. - * square(5, 5, 45); - * pop(); - * - * // Translate the origin to the center. - * translate(50, 50); - * - * // Draw an inverted masked shape. - * push(); - * // Create an inverted mask. - * beginClip({ invert: true }); - * triangle(15, 37, 30, 13, 43, 37); - * circle(45, 45, 7); - * endClip(); - * - * // Draw a backing shape. - * square(5, 5, 45); - * pop(); - * - * describe('In the top left, a white triangle and circle. In the bottom right, a white square with a triangle and circle cut out of it.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe('A silhouette of a rotating torus colored fuchsia.'); - * } - * - * function draw() { - * background(200); - * - * // Create a mask. - * beginClip(); - * push(); - * rotateX(frameCount * 0.01); - * rotateY(frameCount * 0.01); - * scale(0.5); - * torus(30, 15); - * pop(); - * endClip(); - * - * // Draw a backing shape. - * noStroke(); - * fill('fuchsia'); - * plane(100); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe('A silhouette of a rotating torus colored with a gradient from cyan to purple.'); - * } - * - * function draw() { - * background(200); - * - * // Create a mask. - * beginClip(); - * push(); - * rotateX(frameCount * 0.01); - * rotateY(frameCount * 0.01); - * scale(0.5); - * torus(30, 15); - * pop(); - * endClip(); - * - * // Draw a backing shape. - * noStroke(); - * beginShape(QUAD_STRIP); - * fill(0, 255, 255); - * vertex(-width / 2, -height / 2); - * vertex(width / 2, -height / 2); - * fill(100, 0, 100); - * vertex(-width / 2, height / 2); - * vertex(width / 2, height / 2); - * endShape(); - * } - * - *
    - */ -p5.prototype.beginClip = function(options = {}) { - this._renderer.beginClip(options); -}; +function setting(p5, fn){ + /** + * Starts defining a shape that will mask any shapes drawn afterward. + * + * Any shapes drawn between `beginClip()` and + * endClip() will add to the mask shape. The mask + * will apply to anything drawn after endClip(). + * + * The parameter, `options`, is optional. If an object with an `invert` + * property is passed, as in `beginClip({ invert: true })`, it will be used to + * set the masking mode. `{ invert: true }` inverts the mask, creating holes + * in shapes that are masked. `invert` is `false` by default. + * + * Masks can be contained between the + * push() and pop() functions. + * Doing so allows unmasked shapes to be drawn after masked shapes. + * + * Masks can also be defined in a callback function that's passed to + * clip(). + * + * @method beginClip + * @param {Object} [options] an object containing clip settings. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a mask. + * beginClip(); + * triangle(15, 37, 30, 13, 43, 37); + * circle(45, 45, 7); + * endClip(); + * + * // Draw a backing shape. + * square(5, 5, 45); + * + * describe('A white triangle and circle on a gray background.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an inverted mask. + * beginClip({ invert: true }); + * triangle(15, 37, 30, 13, 43, 37); + * circle(45, 45, 7); + * endClip(); + * + * // Draw a backing shape. + * square(5, 5, 45); + * + * describe('A white square at the top-left corner of a gray square. The white square has a triangle and a circle cut out of it.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * noStroke(); + * + * // Draw a masked shape. + * push(); + * // Create a mask. + * beginClip(); + * triangle(15, 37, 30, 13, 43, 37); + * circle(45, 45, 7); + * endClip(); + * + * // Draw a backing shape. + * square(5, 5, 45); + * pop(); + * + * // Translate the origin to the center. + * translate(50, 50); + * + * // Draw an inverted masked shape. + * push(); + * // Create an inverted mask. + * beginClip({ invert: true }); + * triangle(15, 37, 30, 13, 43, 37); + * circle(45, 45, 7); + * endClip(); + * + * // Draw a backing shape. + * square(5, 5, 45); + * pop(); + * + * describe('In the top left, a white triangle and circle. In the bottom right, a white square with a triangle and circle cut out of it.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe('A silhouette of a rotating torus colored fuchsia.'); + * } + * + * function draw() { + * background(200); + * + * // Create a mask. + * beginClip(); + * push(); + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * scale(0.5); + * torus(30, 15); + * pop(); + * endClip(); + * + * // Draw a backing shape. + * noStroke(); + * fill('fuchsia'); + * plane(100); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe('A silhouette of a rotating torus colored with a gradient from cyan to purple.'); + * } + * + * function draw() { + * background(200); + * + * // Create a mask. + * beginClip(); + * push(); + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * scale(0.5); + * torus(30, 15); + * pop(); + * endClip(); + * + * // Draw a backing shape. + * noStroke(); + * beginShape(QUAD_STRIP); + * fill(0, 255, 255); + * vertex(-width / 2, -height / 2); + * vertex(width / 2, -height / 2); + * fill(100, 0, 100); + * vertex(-width / 2, height / 2); + * vertex(width / 2, height / 2); + * endShape(); + * } + * + *
    + */ + fn.beginClip = function(options = {}) { + this._renderer.beginClip(options); + }; -/** - * Ends defining a mask that was started with - * beginClip(). - * - * @method endClip - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a mask. - * beginClip(); - * triangle(15, 37, 30, 13, 43, 37); - * circle(45, 45, 7); - * endClip(); - * - * // Draw a backing shape. - * square(5, 5, 45); - * - * describe('A white triangle and circle on a gray background.'); - * } - * - *
    - */ -p5.prototype.endClip = function() { - this._renderer.endClip(); -}; + /** + * Ends defining a mask that was started with + * beginClip(). + * + * @method endClip + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a mask. + * beginClip(); + * triangle(15, 37, 30, 13, 43, 37); + * circle(45, 45, 7); + * endClip(); + * + * // Draw a backing shape. + * square(5, 5, 45); + * + * describe('A white triangle and circle on a gray background.'); + * } + * + *
    + */ + fn.endClip = function() { + this._renderer.endClip(); + }; -/** - * Defines a shape that will mask any shapes drawn afterward. - * - * The first parameter, `callback`, is a function that defines the mask. - * Any shapes drawn in `callback` will add to the mask shape. The mask - * will apply to anything drawn after `clip()` is called. - * - * The second parameter, `options`, is optional. If an object with an `invert` - * property is passed, as in `beginClip({ invert: true })`, it will be used to - * set the masking mode. `{ invert: true }` inverts the mask, creating holes - * in shapes that are masked. `invert` is `false` by default. - * - * Masks can be contained between the - * push() and pop() functions. - * Doing so allows unmasked shapes to be drawn after masked shapes. - * - * Masks can also be defined with beginClip() - * and endClip(). - * - * @method clip - * @param {Function} callback a function that draws the mask shape. - * @param {Object} [options] an object containing clip settings. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a mask. - * clip(mask); - * - * // Draw a backing shape. - * square(5, 5, 45); - * - * describe('A white triangle and circle on a gray background.'); - * } - * - * // Declare a function that defines the mask. - * function mask() { - * triangle(15, 37, 30, 13, 43, 37); - * circle(45, 45, 7); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an inverted mask. - * clip(mask, { invert: true }); - * - * // Draw a backing shape. - * square(5, 5, 45); - * - * describe('A white square at the top-left corner of a gray square. The white square has a triangle and a circle cut out of it.'); - * } - * - * // Declare a function that defines the mask. - * function mask() { - * triangle(15, 37, 30, 13, 43, 37); - * circle(45, 45, 7); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * noStroke(); - * - * // Draw a masked shape. - * push(); - * // Create a mask. - * clip(mask); - * - * // Draw a backing shape. - * square(5, 5, 45); - * pop(); - * - * // Translate the origin to the center. - * translate(50, 50); - * - * // Draw an inverted masked shape. - * push(); - * // Create an inverted mask. - * clip(mask, { invert: true }); - * - * // Draw a backing shape. - * square(5, 5, 45); - * pop(); - * - * describe('In the top left, a white triangle and circle. In the bottom right, a white square with a triangle and circle cut out of it.'); - * } - * - * // Declare a function that defines the mask. - * function mask() { - * triangle(15, 37, 30, 13, 43, 37); - * circle(45, 45, 7); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe('A silhouette of a rotating torus colored fuchsia.'); - * } - * - * function draw() { - * background(200); - * - * // Create a mask. - * clip(mask); - * - * // Draw a backing shape. - * noStroke(); - * fill('fuchsia'); - * plane(100); - * } - * - * // Declare a function that defines the mask. - * function mask() { - * push(); - * rotateX(frameCount * 0.01); - * rotateY(frameCount * 0.01); - * scale(0.5); - * torus(30, 15); - * pop(); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe('A silhouette of a rotating torus colored with a gradient from cyan to purple.'); - * } - * - * function draw() { - * background(200); - * - * // Create a mask. - * clip(mask); - * - * // Draw a backing shape. - * noStroke(); - * beginShape(QUAD_STRIP); - * fill(0, 255, 255); - * vertex(-width / 2, -height / 2); - * vertex(width / 2, -height / 2); - * fill(100, 0, 100); - * vertex(-width / 2, height / 2); - * vertex(width / 2, height / 2); - * endShape(); - * } - * - * // Declare a function that defines the mask. - * function mask() { - * push(); - * rotateX(frameCount * 0.01); - * rotateY(frameCount * 0.01); - * scale(0.5); - * torus(30, 15); - * pop(); - * } - * - *
    - */ -p5.prototype.clip = function(callback, options) { - this._renderer.beginClip(options); - callback(); - this._renderer.endClip(options); -}; + /** + * Defines a shape that will mask any shapes drawn afterward. + * + * The first parameter, `callback`, is a function that defines the mask. + * Any shapes drawn in `callback` will add to the mask shape. The mask + * will apply to anything drawn after `clip()` is called. + * + * The second parameter, `options`, is optional. If an object with an `invert` + * property is passed, as in `beginClip({ invert: true })`, it will be used to + * set the masking mode. `{ invert: true }` inverts the mask, creating holes + * in shapes that are masked. `invert` is `false` by default. + * + * Masks can be contained between the + * push() and pop() functions. + * Doing so allows unmasked shapes to be drawn after masked shapes. + * + * Masks can also be defined with beginClip() + * and endClip(). + * + * @method clip + * @param {Function} callback a function that draws the mask shape. + * @param {Object} [options] an object containing clip settings. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a mask. + * clip(mask); + * + * // Draw a backing shape. + * square(5, 5, 45); + * + * describe('A white triangle and circle on a gray background.'); + * } + * + * // Declare a function that defines the mask. + * function mask() { + * triangle(15, 37, 30, 13, 43, 37); + * circle(45, 45, 7); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an inverted mask. + * clip(mask, { invert: true }); + * + * // Draw a backing shape. + * square(5, 5, 45); + * + * describe('A white square at the top-left corner of a gray square. The white square has a triangle and a circle cut out of it.'); + * } + * + * // Declare a function that defines the mask. + * function mask() { + * triangle(15, 37, 30, 13, 43, 37); + * circle(45, 45, 7); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * noStroke(); + * + * // Draw a masked shape. + * push(); + * // Create a mask. + * clip(mask); + * + * // Draw a backing shape. + * square(5, 5, 45); + * pop(); + * + * // Translate the origin to the center. + * translate(50, 50); + * + * // Draw an inverted masked shape. + * push(); + * // Create an inverted mask. + * clip(mask, { invert: true }); + * + * // Draw a backing shape. + * square(5, 5, 45); + * pop(); + * + * describe('In the top left, a white triangle and circle. In the bottom right, a white square with a triangle and circle cut out of it.'); + * } + * + * // Declare a function that defines the mask. + * function mask() { + * triangle(15, 37, 30, 13, 43, 37); + * circle(45, 45, 7); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe('A silhouette of a rotating torus colored fuchsia.'); + * } + * + * function draw() { + * background(200); + * + * // Create a mask. + * clip(mask); + * + * // Draw a backing shape. + * noStroke(); + * fill('fuchsia'); + * plane(100); + * } + * + * // Declare a function that defines the mask. + * function mask() { + * push(); + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * scale(0.5); + * torus(30, 15); + * pop(); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe('A silhouette of a rotating torus colored with a gradient from cyan to purple.'); + * } + * + * function draw() { + * background(200); + * + * // Create a mask. + * clip(mask); + * + * // Draw a backing shape. + * noStroke(); + * beginShape(QUAD_STRIP); + * fill(0, 255, 255); + * vertex(-width / 2, -height / 2); + * vertex(width / 2, -height / 2); + * fill(100, 0, 100); + * vertex(-width / 2, height / 2); + * vertex(width / 2, height / 2); + * endShape(); + * } + * + * // Declare a function that defines the mask. + * function mask() { + * push(); + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * scale(0.5); + * torus(30, 15); + * pop(); + * } + * + *
    + */ + fn.clip = function(callback, options) { + this._renderer.beginClip(options); + callback(); + this._renderer.endClip(options); + }; -/** - * Sets the color used for the background of the canvas. - * - * By default, the background is transparent. `background()` is typically used - * within draw() to clear the display window at the - * beginning of each frame. It can also be used inside - * setup() to set the background on the first frame - * of animation. - * - * The version of `background()` with one parameter interprets the value one - * of four ways. If the parameter is a `Number`, it's interpreted as a grayscale - * value. If the parameter is a `String`, it's interpreted as a CSS color string. - * RGB, RGBA, HSL, HSLA, hex, and named color strings are supported. If the - * parameter is a p5.Color object, it will be used as - * the background color. If the parameter is a - * p5.Image object, it will be used as the background - * image. - * - * The version of `background()` with two parameters interprets the first one - * as a grayscale value. The second parameter sets the alpha (transparency) - * value. - * - * The version of `background()` with three parameters interprets them as RGB, - * HSB, or HSL colors, depending on the current - * colorMode(). By default, colors are specified - * in RGB values. Calling `background(255, 204, 0)` sets the background a bright - * yellow color. - * - * @method background - * @param {p5.Color} color any value created by the color() function - * @chainable - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // A grayscale value. - * background(51); - * - * describe('A canvas with a dark charcoal gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // A grayscale value and an alpha value. - * background(51, 0.4); - * describe('A canvas with a transparent gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // R, G & B values. - * background(255, 204, 0); - * - * describe('A canvas with a yellow background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Use HSB color. - * colorMode(HSB); - * - * // H, S & B values. - * background(255, 204, 100); - * - * describe('A canvas with a royal blue background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // A CSS named color. - * background('red'); - * - * describe('A canvas with a red background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Three-digit hex RGB notation. - * background('#fae'); - * - * describe('A canvas with a pink background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Six-digit hex RGB notation. - * background('#222222'); - * - * describe('A canvas with a black background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Integer RGB notation. - * background('rgb(0, 255, 0)'); - * - * describe('A canvas with a bright green background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Integer RGBA notation. - * background('rgba(0, 255, 0, 0.25)'); - * - * describe('A canvas with a transparent green background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Percentage RGB notation. - * background('rgb(100%, 0%, 10%)'); - * - * describe('A canvas with a red background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Percentage RGBA notation. - * background('rgba(100%, 0%, 100%, 0.5)'); - * - * describe('A canvas with a transparent purple background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // A p5.Color object. - * let c = color(0, 0, 255); - * background(c); - * - * describe('A canvas with a blue background.'); - * } - * - *
    - * - */ + /** + * Sets the color used for the background of the canvas. + * + * By default, the background is transparent. `background()` is typically used + * within draw() to clear the display window at the + * beginning of each frame. It can also be used inside + * setup() to set the background on the first frame + * of animation. + * + * The version of `background()` with one parameter interprets the value one + * of four ways. If the parameter is a `Number`, it's interpreted as a grayscale + * value. If the parameter is a `String`, it's interpreted as a CSS color string. + * RGB, RGBA, HSL, HSLA, hex, and named color strings are supported. If the + * parameter is a p5.Color object, it will be used as + * the background color. If the parameter is a + * p5.Image object, it will be used as the background + * image. + * + * The version of `background()` with two parameters interprets the first one + * as a grayscale value. The second parameter sets the alpha (transparency) + * value. + * + * The version of `background()` with three parameters interprets them as RGB, + * HSB, or HSL colors, depending on the current + * colorMode(). By default, colors are specified + * in RGB values. Calling `background(255, 204, 0)` sets the background a bright + * yellow color. + * + * @method background + * @param {p5.Color} color any value created by the color() function + * @chainable + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // A grayscale value. + * background(51); + * + * describe('A canvas with a dark charcoal gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // A grayscale value and an alpha value. + * background(51, 0.4); + * describe('A canvas with a transparent gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // R, G & B values. + * background(255, 204, 0); + * + * describe('A canvas with a yellow background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Use HSB color. + * colorMode(HSB); + * + * // H, S & B values. + * background(255, 204, 100); + * + * describe('A canvas with a royal blue background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // A CSS named color. + * background('red'); + * + * describe('A canvas with a red background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Three-digit hex RGB notation. + * background('#fae'); + * + * describe('A canvas with a pink background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Six-digit hex RGB notation. + * background('#222222'); + * + * describe('A canvas with a black background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Integer RGB notation. + * background('rgb(0, 255, 0)'); + * + * describe('A canvas with a bright green background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Integer RGBA notation. + * background('rgba(0, 255, 0, 0.25)'); + * + * describe('A canvas with a transparent green background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Percentage RGB notation. + * background('rgb(100%, 0%, 10%)'); + * + * describe('A canvas with a red background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Percentage RGBA notation. + * background('rgba(100%, 0%, 100%, 0.5)'); + * + * describe('A canvas with a transparent purple background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // A p5.Color object. + * let c = color(0, 0, 255); + * background(c); + * + * describe('A canvas with a blue background.'); + * } + * + *
    + * + */ -/** - * @method background - * @param {String} colorstring color string, possible formats include: integer - * rgb() or rgba(), percentage rgb() or rgba(), - * 3-digit hex, 6-digit hex. - * @param {Number} [a] opacity of the background relative to current - * color range (default is 0-255). - * @chainable - */ + /** + * @method background + * @param {String} colorstring color string, possible formats include: integer + * rgb() or rgba(), percentage rgb() or rgba(), + * 3-digit hex, 6-digit hex. + * @param {Number} [a] opacity of the background relative to current + * color range (default is 0-255). + * @chainable + */ -/** - * @method background - * @param {Number} gray specifies a value between white and black. - * @param {Number} [a] - * @chainable - */ + /** + * @method background + * @param {Number} gray specifies a value between white and black. + * @param {Number} [a] + * @chainable + */ -/** - * @method background - * @param {Number} v1 red value if color mode is RGB, or hue value if color mode is HSB. - * @param {Number} v2 green value if color mode is RGB, or saturation value if color mode is HSB. - * @param {Number} v3 blue value if color mode is RGB, or brightness value if color mode is HSB. - * @param {Number} [a] - * @chainable - */ + /** + * @method background + * @param {Number} v1 red value if color mode is RGB, or hue value if color mode is HSB. + * @param {Number} v2 green value if color mode is RGB, or saturation value if color mode is HSB. + * @param {Number} v3 blue value if color mode is RGB, or brightness value if color mode is HSB. + * @param {Number} [a] + * @chainable + */ -/** - * @method background - * @param {Number[]} values an array containing the red, green, blue - * and alpha components of the color. - * @chainable - */ + /** + * @method background + * @param {Number[]} values an array containing the red, green, blue + * and alpha components of the color. + * @chainable + */ -/** - * @method background - * @param {p5.Image} image image created with loadImage() - * or createImage(), - * to set as background. - * (must be same size as the sketch window). - * @param {Number} [a] - * @chainable - */ -p5.prototype.background = function(...args) { - this._renderer.background(...args); - return this; -}; + /** + * @method background + * @param {p5.Image} image image created with loadImage() + * or createImage(), + * to set as background. + * (must be same size as the sketch window). + * @param {Number} [a] + * @chainable + */ + fn.background = function(...args) { + this._renderer.background(...args); + return this; + }; -/** - * Clears the pixels on the canvas. - * - * `clear()` makes every pixel 100% transparent. Calling `clear()` doesn't - * clear objects created by `createX()` functions such as - * createGraphics(), - * createVideo(), and - * createImg(). These objects will remain - * unchanged after calling `clear()` and can be redrawn. - * - * In WebGL mode, this function can clear the screen to a specific color. It - * interprets four numeric parameters as normalized RGBA color values. It also - * clears the depth buffer. If you are not using the WebGL renderer, these - * parameters will have no effect. - * - * @method clear - * @chainable - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * describe('A gray square. White circles are drawn as the user moves the mouse. The circles disappear when the user presses the mouse.'); - * } - * - * function draw() { - * circle(mouseX, mouseY, 20); - * } - * - * function mousePressed() { - * clear(); - * background(200); - * } - * - *
    - * - *
    - * - * let pg; - * - * function setup() { - * createCanvas(100, 100); - * background(200); - * - * pg = createGraphics(60, 60); - * pg.background(200); - * pg.noStroke(); - * pg.circle(pg.width / 2, pg.height / 2, 15); - * image(pg, 20, 20); - * - * describe('A white circle drawn on a gray square. The square gets smaller when the mouse is pressed.'); - * } - * - * function mousePressed() { - * clear(); - * image(pg, 20, 20); - * } - * - *
    - * - * @param {Number} [r] normalized red value. - * @param {Number} [g] normalized green value. - * @param {Number} [b] normalized blue value. - * @param {Number} [a] normalized alpha value. - */ -p5.prototype.clear = function(...args) { - const _r = args[0] || 0; - const _g = args[1] || 0; - const _b = args[2] || 0; - const _a = args[3] || 0; + /** + * Clears the pixels on the canvas. + * + * `clear()` makes every pixel 100% transparent. Calling `clear()` doesn't + * clear objects created by `createX()` functions such as + * createGraphics(), + * createVideo(), and + * createImg(). These objects will remain + * unchanged after calling `clear()` and can be redrawn. + * + * In WebGL mode, this function can clear the screen to a specific color. It + * interprets four numeric parameters as normalized RGBA color values. It also + * clears the depth buffer. If you are not using the WebGL renderer, these + * parameters will have no effect. + * + * @method clear + * @chainable + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * describe('A gray square. White circles are drawn as the user moves the mouse. The circles disappear when the user presses the mouse.'); + * } + * + * function draw() { + * circle(mouseX, mouseY, 20); + * } + * + * function mousePressed() { + * clear(); + * background(200); + * } + * + *
    + * + *
    + * + * let pg; + * + * function setup() { + * createCanvas(100, 100); + * background(200); + * + * pg = createGraphics(60, 60); + * pg.background(200); + * pg.noStroke(); + * pg.circle(pg.width / 2, pg.height / 2, 15); + * image(pg, 20, 20); + * + * describe('A white circle drawn on a gray square. The square gets smaller when the mouse is pressed.'); + * } + * + * function mousePressed() { + * clear(); + * image(pg, 20, 20); + * } + * + *
    + * + * @param {Number} [r] normalized red value. + * @param {Number} [g] normalized green value. + * @param {Number} [b] normalized blue value. + * @param {Number} [a] normalized alpha value. + */ + fn.clear = function(...args) { + const _r = args[0] || 0; + const _g = args[1] || 0; + const _b = args[2] || 0; + const _a = args[3] || 0; - this._renderer.clear(_r, _g, _b, _a); - return this; -}; + this._renderer.clear(_r, _g, _b, _a); + return this; + }; -/** - * Changes the way color values are interpreted. - * - * By default, the `Number` parameters for fill(), - * stroke(), - * background(), and - * color() are defined by values between 0 and 255 - * using the RGB color model. This is equivalent to calling - * `colorMode(RGB, 255)`. Pure red is `color(255, 0, 0)` in this model. - * - * Calling `colorMode(RGB, 100)` sets colors to use RGB color values - * between 0 and 100. Pure red is `color(100, 0, 0)` in this model. - * - * Calling `colorMode(HSB)` or `colorMode(HSL)` changes to HSB or HSL system - * instead of RGB. Pure red is `color(0, 100, 100)` in HSB and - * `color(0, 100, 50)` in HSL. - * - * p5.Color objects remember the mode that they were - * created in. Changing modes doesn't affect their appearance. - * - * @method colorMode - * @param {(RGB|HSB|HSL)} mode either RGB, HSB or HSL, corresponding to - * Red/Green/Blue and Hue/Saturation/Brightness - * (or Lightness). - * @param {Number} [max] range for all values. - * @chainable - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Fill with pure red. - * fill(255, 0, 0); - * - * circle(50, 50, 25); - * - * describe('A gray square with a red circle at its center.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use RGB color with values in the range 0-100. - * colorMode(RGB, 100); - * - * // Fill with pure red. - * fill(100, 0, 0); - * - * circle(50, 50, 25); - * - * describe('A gray square with a red circle at its center.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use HSB color. - * colorMode(HSB); - * - * // Fill with pure red. - * fill(0, 100, 100); - * - * circle(50, 50, 25); - * - * describe('A gray square with a red circle at its center.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use HSL color. - * colorMode(HSL); - * - * // Fill with pure red. - * fill(0, 100, 50); - * - * circle(50, 50, 25); - * - * describe('A gray square with a red circle at its center.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Use RGB color with values in the range 0-100. - * colorMode(RGB, 100); - * - * for (let x = 0; x < 100; x += 1) { - * for (let y = 0; y < 100; y += 1) { - * stroke(x, y, 0); - * point(x, y); - * } - * } - * - * describe( - * 'A diagonal green to red gradient from bottom-left to top-right with shading transitioning to black at top-left corner.' - * ); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Use HSB color with values in the range 0-100. - * colorMode(HSB, 100); - * - * for (let x = 0; x < 100; x += 1) { - * for (let y = 0; y < 100; y += 1) { - * stroke(x, y, 100); - * point(x, y); - * } - * } - * - * describe('A rainbow gradient from left-to-right. Brightness transitions to white at the top.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Create a p5.Color object. - * let myColor = color(180, 175, 230); - * background(myColor); - * - * // Use RGB color with values in the range 0-1. - * colorMode(RGB, 1); - * - * // Get the red, green, and blue color components. - * let redValue = red(myColor); - * let greenValue = green(myColor); - * let blueValue = blue(myColor); - * - * // Round the color components for display. - * redValue = round(redValue, 2); - * greenValue = round(greenValue, 2); - * blueValue = round(blueValue, 2); - * - * // Display the color components. - * text(`Red: ${redValue}`, 10, 10, 80, 80); - * text(`Green: ${greenValue}`, 10, 40, 80, 80); - * text(`Blue: ${blueValue}`, 10, 70, 80, 80); - * - * describe('A purple canvas with the red, green, and blue decimal values of the color written on it.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(255); - * - * // Use RGB color with alpha values in the range 0-1. - * colorMode(RGB, 255, 255, 255, 1); - * - * noFill(); - * strokeWeight(4); - * stroke(255, 0, 10, 0.3); - * circle(40, 40, 50); - * circle(50, 60, 50); - * - * describe('Two overlapping translucent pink circle outlines.'); - * } - * - *
    - */ + /** + * Changes the way color values are interpreted. + * + * By default, the `Number` parameters for fill(), + * stroke(), + * background(), and + * color() are defined by values between 0 and 255 + * using the RGB color model. This is equivalent to calling + * `colorMode(RGB, 255)`. Pure red is `color(255, 0, 0)` in this model. + * + * Calling `colorMode(RGB, 100)` sets colors to use RGB color values + * between 0 and 100. Pure red is `color(100, 0, 0)` in this model. + * + * Calling `colorMode(HSB)` or `colorMode(HSL)` changes to HSB or HSL system + * instead of RGB. Pure red is `color(0, 100, 100)` in HSB and + * `color(0, 100, 50)` in HSL. + * + * p5.Color objects remember the mode that they were + * created in. Changing modes doesn't affect their appearance. + * + * @method colorMode + * @param {(RGB|HSB|HSL)} mode either RGB, HSB or HSL, corresponding to + * Red/Green/Blue and Hue/Saturation/Brightness + * (or Lightness). + * @param {Number} [max] range for all values. + * @chainable + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Fill with pure red. + * fill(255, 0, 0); + * + * circle(50, 50, 25); + * + * describe('A gray square with a red circle at its center.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use RGB color with values in the range 0-100. + * colorMode(RGB, 100); + * + * // Fill with pure red. + * fill(100, 0, 0); + * + * circle(50, 50, 25); + * + * describe('A gray square with a red circle at its center.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use HSB color. + * colorMode(HSB); + * + * // Fill with pure red. + * fill(0, 100, 100); + * + * circle(50, 50, 25); + * + * describe('A gray square with a red circle at its center.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use HSL color. + * colorMode(HSL); + * + * // Fill with pure red. + * fill(0, 100, 50); + * + * circle(50, 50, 25); + * + * describe('A gray square with a red circle at its center.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Use RGB color with values in the range 0-100. + * colorMode(RGB, 100); + * + * for (let x = 0; x < 100; x += 1) { + * for (let y = 0; y < 100; y += 1) { + * stroke(x, y, 0); + * point(x, y); + * } + * } + * + * describe( + * 'A diagonal green to red gradient from bottom-left to top-right with shading transitioning to black at top-left corner.' + * ); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Use HSB color with values in the range 0-100. + * colorMode(HSB, 100); + * + * for (let x = 0; x < 100; x += 1) { + * for (let y = 0; y < 100; y += 1) { + * stroke(x, y, 100); + * point(x, y); + * } + * } + * + * describe('A rainbow gradient from left-to-right. Brightness transitions to white at the top.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Create a p5.Color object. + * let myColor = color(180, 175, 230); + * background(myColor); + * + * // Use RGB color with values in the range 0-1. + * colorMode(RGB, 1); + * + * // Get the red, green, and blue color components. + * let redValue = red(myColor); + * let greenValue = green(myColor); + * let blueValue = blue(myColor); + * + * // Round the color components for display. + * redValue = round(redValue, 2); + * greenValue = round(greenValue, 2); + * blueValue = round(blueValue, 2); + * + * // Display the color components. + * text(`Red: ${redValue}`, 10, 10, 80, 80); + * text(`Green: ${greenValue}`, 10, 40, 80, 80); + * text(`Blue: ${blueValue}`, 10, 70, 80, 80); + * + * describe('A purple canvas with the red, green, and blue decimal values of the color written on it.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(255); + * + * // Use RGB color with alpha values in the range 0-1. + * colorMode(RGB, 255, 255, 255, 1); + * + * noFill(); + * strokeWeight(4); + * stroke(255, 0, 10, 0.3); + * circle(40, 40, 50); + * circle(50, 60, 50); + * + * describe('Two overlapping translucent pink circle outlines.'); + * } + * + *
    + */ -/** - * @method colorMode - * @param {(RGB|HSB|HSL)} mode - * @param {Number} max1 range for the red or hue depending on the - * current color mode. - * @param {Number} max2 range for the green or saturation depending - * on the current color mode. - * @param {Number} max3 range for the blue or brightness/lightness - * depending on the current color mode. - * @param {Number} [maxA] range for the alpha. - * @chainable - */ -p5.prototype.colorMode = function(mode, max1, max2, max3, maxA) { - p5._validateParameters('colorMode', arguments); - if ( - mode === constants.RGB || - mode === constants.HSB || - mode === constants.HSL - ) { - // Set color mode. - this._colorMode = mode; + /** + * @method colorMode + * @param {(RGB|HSB|HSL)} mode + * @param {Number} max1 range for the red or hue depending on the + * current color mode. + * @param {Number} max2 range for the green or saturation depending + * on the current color mode. + * @param {Number} max3 range for the blue or brightness/lightness + * depending on the current color mode. + * @param {Number} [maxA] range for the alpha. + * @chainable + */ + fn.colorMode = function(mode, max1, max2, max3, maxA) { + p5._validateParameters('colorMode', arguments); + if ( + mode === constants.RGB || + mode === constants.HSB || + mode === constants.HSL + ) { + // Set color mode. + this._colorMode = mode; - // Set color maxes. - const maxes = this._colorMaxes[mode]; - if (arguments.length === 2) { - maxes[0] = max1; // Red - maxes[1] = max1; // Green - maxes[2] = max1; // Blue - maxes[3] = max1; // Alpha - } else if (arguments.length === 4) { - maxes[0] = max1; // Red - maxes[1] = max2; // Green - maxes[2] = max3; // Blue - } else if (arguments.length === 5) { - maxes[0] = max1; // Red - maxes[1] = max2; // Green - maxes[2] = max3; // Blue - maxes[3] = maxA; // Alpha + // Set color maxes. + const maxes = this._colorMaxes[mode]; + if (arguments.length === 2) { + maxes[0] = max1; // Red + maxes[1] = max1; // Green + maxes[2] = max1; // Blue + maxes[3] = max1; // Alpha + } else if (arguments.length === 4) { + maxes[0] = max1; // Red + maxes[1] = max2; // Green + maxes[2] = max3; // Blue + } else if (arguments.length === 5) { + maxes[0] = max1; // Red + maxes[1] = max2; // Green + maxes[2] = max3; // Blue + maxes[3] = maxA; // Alpha + } } - } - return this; -}; + return this; + }; -/** - * Sets the color used to fill shapes. - * - * Calling `fill(255, 165, 0)` or `fill('orange')` means all shapes drawn - * after the fill command will be filled with the color orange. - * - * The version of `fill()` with one parameter interprets the value one of - * three ways. If the parameter is a `Number`, it's interpreted as a grayscale - * value. If the parameter is a `String`, it's interpreted as a CSS color - * string. A p5.Color object can also be provided to - * set the fill color. - * - * The version of `fill()` with three parameters interprets them as RGB, HSB, - * or HSL colors, depending on the current - * colorMode(). The default color space is RGB, - * with each value in the range from 0 to 255. - * - * @method fill - * @param {Number} v1 red value if color mode is RGB or hue value if color mode is HSB. - * @param {Number} v2 green value if color mode is RGB or saturation value if color mode is HSB. - * @param {Number} v3 blue value if color mode is RGB or brightness value if color mode is HSB. - * @param {Number} [alpha] - * @chainable - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // A grayscale value. - * fill(51); - * square(20, 20, 60); - * - * describe('A dark charcoal gray square with a black outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // R, G & B values. - * fill(255, 204, 0); - * square(20, 20, 60); - * - * describe('A yellow square with a black outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(100); - * - * // Use HSB color. - * colorMode(HSB); - * - * // H, S & B values. - * fill(255, 204, 100); - * square(20, 20, 60); - * - * describe('A royal blue square with a black outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // A CSS named color. - * fill('red'); - * square(20, 20, 60); - * - * describe('A red square with a black outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Three-digit hex RGB notation. - * fill('#fae'); - * square(20, 20, 60); - * - * describe('A pink square with a black outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Six-digit hex RGB notation. - * fill('#A251FA'); - * square(20, 20, 60); - * - * describe('A purple square with a black outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Integer RGB notation. - * fill('rgb(0, 255, 0)'); - * square(20, 20, 60); - * - * describe('A bright green square with a black outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Integer RGBA notation. - * fill('rgba(0, 255, 0, 0.25)'); - * square(20, 20, 60); - * - * describe('A soft green rectange with a black outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Percentage RGB notation. - * fill('rgb(100%, 0%, 10%)'); - * square(20, 20, 60); - * - * describe('A red square with a black outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Percentage RGBA notation. - * fill('rgba(100%, 0%, 100%, 0.5)'); - * square(20, 20, 60); - * - * describe('A dark fuchsia square with a black outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // A p5.Color object. - * let c = color(0, 0, 255); - * fill(c); - * square(20, 20, 60); - * - * describe('A blue square with a black outline.'); - * } - * - *
    - */ + /** + * Sets the color used to fill shapes. + * + * Calling `fill(255, 165, 0)` or `fill('orange')` means all shapes drawn + * after the fill command will be filled with the color orange. + * + * The version of `fill()` with one parameter interprets the value one of + * three ways. If the parameter is a `Number`, it's interpreted as a grayscale + * value. If the parameter is a `String`, it's interpreted as a CSS color + * string. A p5.Color object can also be provided to + * set the fill color. + * + * The version of `fill()` with three parameters interprets them as RGB, HSB, + * or HSL colors, depending on the current + * colorMode(). The default color space is RGB, + * with each value in the range from 0 to 255. + * + * @method fill + * @param {Number} v1 red value if color mode is RGB or hue value if color mode is HSB. + * @param {Number} v2 green value if color mode is RGB or saturation value if color mode is HSB. + * @param {Number} v3 blue value if color mode is RGB or brightness value if color mode is HSB. + * @param {Number} [alpha] + * @chainable + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // A grayscale value. + * fill(51); + * square(20, 20, 60); + * + * describe('A dark charcoal gray square with a black outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // R, G & B values. + * fill(255, 204, 0); + * square(20, 20, 60); + * + * describe('A yellow square with a black outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(100); + * + * // Use HSB color. + * colorMode(HSB); + * + * // H, S & B values. + * fill(255, 204, 100); + * square(20, 20, 60); + * + * describe('A royal blue square with a black outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // A CSS named color. + * fill('red'); + * square(20, 20, 60); + * + * describe('A red square with a black outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Three-digit hex RGB notation. + * fill('#fae'); + * square(20, 20, 60); + * + * describe('A pink square with a black outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Six-digit hex RGB notation. + * fill('#A251FA'); + * square(20, 20, 60); + * + * describe('A purple square with a black outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Integer RGB notation. + * fill('rgb(0, 255, 0)'); + * square(20, 20, 60); + * + * describe('A bright green square with a black outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Integer RGBA notation. + * fill('rgba(0, 255, 0, 0.25)'); + * square(20, 20, 60); + * + * describe('A soft green rectange with a black outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Percentage RGB notation. + * fill('rgb(100%, 0%, 10%)'); + * square(20, 20, 60); + * + * describe('A red square with a black outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Percentage RGBA notation. + * fill('rgba(100%, 0%, 100%, 0.5)'); + * square(20, 20, 60); + * + * describe('A dark fuchsia square with a black outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // A p5.Color object. + * let c = color(0, 0, 255); + * fill(c); + * square(20, 20, 60); + * + * describe('A blue square with a black outline.'); + * } + * + *
    + */ -/** - * @method fill - * @param {String} value a color string. - * @chainable - */ + /** + * @method fill + * @param {String} value a color string. + * @chainable + */ -/** - * @method fill - * @param {Number} gray a grayscale value. - * @param {Number} [alpha] - * @chainable - */ + /** + * @method fill + * @param {Number} gray a grayscale value. + * @param {Number} [alpha] + * @chainable + */ -/** - * @method fill - * @param {Number[]} values an array containing the red, green, blue & - * and alpha components of the color. - * @chainable - */ + /** + * @method fill + * @param {Number[]} values an array containing the red, green, blue & + * and alpha components of the color. + * @chainable + */ -/** - * @method fill - * @param {p5.Color} color the fill color. - * @chainable - */ -p5.prototype.fill = function(...args) { - this._renderer._setProperty('_fillSet', true); - this._renderer._setProperty('_doFill', true); - this._renderer.fill(...args); - return this; -}; + /** + * @method fill + * @param {p5.Color} color the fill color. + * @chainable + */ + fn.fill = function(...args) { + this._renderer._setProperty('_fillSet', true); + this._renderer._setProperty('_doFill', true); + this._renderer.fill(...args); + return this; + }; -/** - * Disables setting the fill color for shapes. - * - * Calling `noFill()` is the same as making the fill completely transparent, - * as in `fill(0, 0)`. If both noStroke() and - * `noFill()` are called, nothing will be drawn to the screen. - * - * @method noFill - * @chainable - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Draw the top square. - * square(32, 10, 35); - * - * // Draw the bottom square. - * noFill(); - * square(32, 55, 35); - * - * describe('A white square on above an empty square. Both squares have black outlines.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe('A purple cube wireframe spinning on a black canvas.'); - * } - * - * function draw() { - * background(0); - * - * // Style the box. - * noFill(); - * stroke(100, 100, 240); - * - * // Rotate the coordinates. - * rotateX(frameCount * 0.01); - * rotateY(frameCount * 0.01); - * - * // Draw the box. - * box(45); - * } - * - *
    - */ -p5.prototype.noFill = function() { - this._renderer._setProperty('_doFill', false); - return this; -}; + /** + * Disables setting the fill color for shapes. + * + * Calling `noFill()` is the same as making the fill completely transparent, + * as in `fill(0, 0)`. If both noStroke() and + * `noFill()` are called, nothing will be drawn to the screen. + * + * @method noFill + * @chainable + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Draw the top square. + * square(32, 10, 35); + * + * // Draw the bottom square. + * noFill(); + * square(32, 55, 35); + * + * describe('A white square on above an empty square. Both squares have black outlines.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe('A purple cube wireframe spinning on a black canvas.'); + * } + * + * function draw() { + * background(0); + * + * // Style the box. + * noFill(); + * stroke(100, 100, 240); + * + * // Rotate the coordinates. + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * + * // Draw the box. + * box(45); + * } + * + *
    + */ + fn.noFill = function() { + this._renderer._setProperty('_doFill', false); + return this; + }; -/** - * Disables drawing points, lines, and the outlines of shapes. - * - * Calling `noStroke()` is the same as making the stroke completely transparent, - * as in `stroke(0, 0)`. If both `noStroke()` and - * noFill() are called, nothing will be drawn to the - * screen. - * - * @method noStroke - * @chainable - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * noStroke(); - * square(20, 20, 60); - * - * describe('A white square with no outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe('A pink cube with no edge outlines spinning on a black canvas.'); - * } - * - * function draw() { - * background(0); - * - * // Style the box. - * noStroke(); - * fill(240, 150, 150); - * - * // Rotate the coordinates. - * rotateX(frameCount * 0.01); - * rotateY(frameCount * 0.01); - * - * // Draw the box. - * box(45); - * } - * - *
    - */ -p5.prototype.noStroke = function() { - this._renderer._setProperty('_doStroke', false); - return this; -}; + /** + * Disables drawing points, lines, and the outlines of shapes. + * + * Calling `noStroke()` is the same as making the stroke completely transparent, + * as in `stroke(0, 0)`. If both `noStroke()` and + * noFill() are called, nothing will be drawn to the + * screen. + * + * @method noStroke + * @chainable + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * noStroke(); + * square(20, 20, 60); + * + * describe('A white square with no outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe('A pink cube with no edge outlines spinning on a black canvas.'); + * } + * + * function draw() { + * background(0); + * + * // Style the box. + * noStroke(); + * fill(240, 150, 150); + * + * // Rotate the coordinates. + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * + * // Draw the box. + * box(45); + * } + * + *
    + */ + fn.noStroke = function() { + this._renderer._setProperty('_doStroke', false); + return this; + }; -/** - * Sets the color used to draw points, lines, and the outlines of shapes. - * - * Calling `stroke(255, 165, 0)` or `stroke('orange')` means all shapes drawn - * after calling `stroke()` will be filled with the color orange. The way - * these parameters are interpreted may be changed with the - * colorMode() function. - * - * The version of `stroke()` with one parameter interprets the value one of - * three ways. If the parameter is a `Number`, it's interpreted as a grayscale - * value. If the parameter is a `String`, it's interpreted as a CSS color - * string. A p5.Color object can also be provided to - * set the stroke color. - * - * The version of `stroke()` with two parameters interprets the first one as a - * grayscale value. The second parameter sets the alpha (transparency) value. - * - * The version of `stroke()` with three parameters interprets them as RGB, HSB, - * or HSL colors, depending on the current `colorMode()`. - * - * The version of `stroke()` with four parameters interprets them as RGBA, HSBA, - * or HSLA colors, depending on the current `colorMode()`. The last parameter - * sets the alpha (transparency) value. - * - * @method stroke - * @param {Number} v1 red value if color mode is RGB or hue value if color mode is HSB. - * @param {Number} v2 green value if color mode is RGB or saturation value if color mode is HSB. - * @param {Number} v3 blue value if color mode is RGB or brightness value if color mode is HSB. - * @param {Number} [alpha] - * @chainable - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // A grayscale value. - * strokeWeight(4); - * stroke(51); - * square(20, 20, 60); - * - * describe('A white square with a dark charcoal gray outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // R, G & B values. - * stroke(255, 204, 0); - * strokeWeight(4); - * square(20, 20, 60); - * - * describe('A white square with a yellow outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use HSB color. - * colorMode(HSB); - * - * // H, S & B values. - * strokeWeight(4); - * stroke(255, 204, 100); - * square(20, 20, 60); - * - * describe('A white square with a royal blue outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // A CSS named color. - * stroke('red'); - * strokeWeight(4); - * square(20, 20, 60); - * - * describe('A white square with a red outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Three-digit hex RGB notation. - * stroke('#fae'); - * strokeWeight(4); - * square(20, 20, 60); - * - * describe('A white square with a pink outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Six-digit hex RGB notation. - * stroke('#222222'); - * strokeWeight(4); - * square(20, 20, 60); - * - * describe('A white square with a black outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Integer RGB notation. - * stroke('rgb(0, 255, 0)'); - * strokeWeight(4); - * square(20, 20, 60); - * - * describe('A whiite square with a bright green outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Integer RGBA notation. - * stroke('rgba(0, 255, 0, 0.25)'); - * strokeWeight(4); - * square(20, 20, 60); - * - * describe('A white square with a soft green outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Percentage RGB notation. - * stroke('rgb(100%, 0%, 10%)'); - * strokeWeight(4); - * square(20, 20, 60); - * - * describe('A white square with a red outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Percentage RGBA notation. - * stroke('rgba(100%, 0%, 100%, 0.5)'); - * strokeWeight(4); - * square(20, 20, 60); - * - * describe('A white square with a dark fuchsia outline.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // A p5.Color object. - * stroke(color(0, 0, 255)); - * strokeWeight(4); - * square(20, 20, 60); - * - * describe('A white square with a blue outline.'); - * } - * - *
    - */ + /** + * Sets the color used to draw points, lines, and the outlines of shapes. + * + * Calling `stroke(255, 165, 0)` or `stroke('orange')` means all shapes drawn + * after calling `stroke()` will be filled with the color orange. The way + * these parameters are interpreted may be changed with the + * colorMode() function. + * + * The version of `stroke()` with one parameter interprets the value one of + * three ways. If the parameter is a `Number`, it's interpreted as a grayscale + * value. If the parameter is a `String`, it's interpreted as a CSS color + * string. A p5.Color object can also be provided to + * set the stroke color. + * + * The version of `stroke()` with two parameters interprets the first one as a + * grayscale value. The second parameter sets the alpha (transparency) value. + * + * The version of `stroke()` with three parameters interprets them as RGB, HSB, + * or HSL colors, depending on the current `colorMode()`. + * + * The version of `stroke()` with four parameters interprets them as RGBA, HSBA, + * or HSLA colors, depending on the current `colorMode()`. The last parameter + * sets the alpha (transparency) value. + * + * @method stroke + * @param {Number} v1 red value if color mode is RGB or hue value if color mode is HSB. + * @param {Number} v2 green value if color mode is RGB or saturation value if color mode is HSB. + * @param {Number} v3 blue value if color mode is RGB or brightness value if color mode is HSB. + * @param {Number} [alpha] + * @chainable + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // A grayscale value. + * strokeWeight(4); + * stroke(51); + * square(20, 20, 60); + * + * describe('A white square with a dark charcoal gray outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // R, G & B values. + * stroke(255, 204, 0); + * strokeWeight(4); + * square(20, 20, 60); + * + * describe('A white square with a yellow outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use HSB color. + * colorMode(HSB); + * + * // H, S & B values. + * strokeWeight(4); + * stroke(255, 204, 100); + * square(20, 20, 60); + * + * describe('A white square with a royal blue outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // A CSS named color. + * stroke('red'); + * strokeWeight(4); + * square(20, 20, 60); + * + * describe('A white square with a red outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Three-digit hex RGB notation. + * stroke('#fae'); + * strokeWeight(4); + * square(20, 20, 60); + * + * describe('A white square with a pink outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Six-digit hex RGB notation. + * stroke('#222222'); + * strokeWeight(4); + * square(20, 20, 60); + * + * describe('A white square with a black outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Integer RGB notation. + * stroke('rgb(0, 255, 0)'); + * strokeWeight(4); + * square(20, 20, 60); + * + * describe('A whiite square with a bright green outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Integer RGBA notation. + * stroke('rgba(0, 255, 0, 0.25)'); + * strokeWeight(4); + * square(20, 20, 60); + * + * describe('A white square with a soft green outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Percentage RGB notation. + * stroke('rgb(100%, 0%, 10%)'); + * strokeWeight(4); + * square(20, 20, 60); + * + * describe('A white square with a red outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Percentage RGBA notation. + * stroke('rgba(100%, 0%, 100%, 0.5)'); + * strokeWeight(4); + * square(20, 20, 60); + * + * describe('A white square with a dark fuchsia outline.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // A p5.Color object. + * stroke(color(0, 0, 255)); + * strokeWeight(4); + * square(20, 20, 60); + * + * describe('A white square with a blue outline.'); + * } + * + *
    + */ -/** - * @method stroke - * @param {String} value a color string. - * @chainable - */ + /** + * @method stroke + * @param {String} value a color string. + * @chainable + */ -/** - * @method stroke - * @param {Number} gray a grayscale value. - * @param {Number} [alpha] - * @chainable - */ + /** + * @method stroke + * @param {Number} gray a grayscale value. + * @param {Number} [alpha] + * @chainable + */ -/** - * @method stroke - * @param {Number[]} values an array containing the red, green, blue, - * and alpha components of the color. - * @chainable - */ + /** + * @method stroke + * @param {Number[]} values an array containing the red, green, blue, + * and alpha components of the color. + * @chainable + */ -/** - * @method stroke - * @param {p5.Color} color the stroke color. - * @chainable - */ + /** + * @method stroke + * @param {p5.Color} color the stroke color. + * @chainable + */ -p5.prototype.stroke = function(...args) { - this._renderer._setProperty('_strokeSet', true); - this._renderer._setProperty('_doStroke', true); - this._renderer.stroke(...args); - return this; -}; + fn.stroke = function(...args) { + this._renderer._setProperty('_strokeSet', true); + this._renderer._setProperty('_doStroke', true); + this._renderer.stroke(...args); + return this; + }; -/** - * Starts using shapes to erase parts of the canvas. - * - * All drawing that follows `erase()` will subtract from the canvas, revealing - * the web page underneath. The erased areas will become transparent, allowing - * the content behind the canvas to show through. The - * fill(), stroke(), and - * blendMode() have no effect once `erase()` is - * called. - * - * The `erase()` function has two optional parameters. The first parameter - * sets the strength of erasing by the shape's interior. A value of 0 means - * that no erasing will occur. A value of 255 means that the shape's interior - * will fully erase the content underneath. The default value is 255 - * (full strength). - * - * The second parameter sets the strength of erasing by the shape's edge. A - * value of 0 means that no erasing will occur. A value of 255 means that the - * shape's edge will fully erase the content underneath. The default value is - * 255 (full strength). - * - * To cancel the erasing effect, use the noErase() - * function. - * - * `erase()` has no effect on drawing done with the - * image() and - * background() functions. - * - * @method erase - * @param {Number} [strengthFill] a number (0-255) for the strength of erasing under a shape's interior. - * Defaults to 255, which is full strength. - * @param {Number} [strengthStroke] a number (0-255) for the strength of erasing under a shape's edge. - * Defaults to 255, which is full strength. - * - * @chainable - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(100, 100, 250); - * - * // Draw a pink square. - * fill(250, 100, 100); - * square(20, 20, 60); - * - * // Erase a circular area. - * erase(); - * circle(25, 30, 30); - * noErase(); - * - * describe('A purple canvas with a pink square in the middle. A circle is erased from the top-left, leaving a hole.'); - * } - * - *
    - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(100, 100, 250); - * - * // Draw a pink square. - * fill(250, 100, 100); - * square(20, 20, 60); - * - * // Erase a circular area. - * strokeWeight(5); - * erase(150, 255); - * circle(25, 30, 30); - * noErase(); - * - * describe('A purple canvas with a pink square in the middle. A circle at the top-left partially erases its interior and a fully erases its outline.'); - * } - * - *
    - */ -p5.prototype.erase = function(opacityFill = 255, opacityStroke = 255) { - this._renderer.erase(opacityFill, opacityStroke); + /** + * Starts using shapes to erase parts of the canvas. + * + * All drawing that follows `erase()` will subtract from the canvas, revealing + * the web page underneath. The erased areas will become transparent, allowing + * the content behind the canvas to show through. The + * fill(), stroke(), and + * blendMode() have no effect once `erase()` is + * called. + * + * The `erase()` function has two optional parameters. The first parameter + * sets the strength of erasing by the shape's interior. A value of 0 means + * that no erasing will occur. A value of 255 means that the shape's interior + * will fully erase the content underneath. The default value is 255 + * (full strength). + * + * The second parameter sets the strength of erasing by the shape's edge. A + * value of 0 means that no erasing will occur. A value of 255 means that the + * shape's edge will fully erase the content underneath. The default value is + * 255 (full strength). + * + * To cancel the erasing effect, use the noErase() + * function. + * + * `erase()` has no effect on drawing done with the + * image() and + * background() functions. + * + * @method erase + * @param {Number} [strengthFill] a number (0-255) for the strength of erasing under a shape's interior. + * Defaults to 255, which is full strength. + * @param {Number} [strengthStroke] a number (0-255) for the strength of erasing under a shape's edge. + * Defaults to 255, which is full strength. + * + * @chainable + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(100, 100, 250); + * + * // Draw a pink square. + * fill(250, 100, 100); + * square(20, 20, 60); + * + * // Erase a circular area. + * erase(); + * circle(25, 30, 30); + * noErase(); + * + * describe('A purple canvas with a pink square in the middle. A circle is erased from the top-left, leaving a hole.'); + * } + * + *
    + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(100, 100, 250); + * + * // Draw a pink square. + * fill(250, 100, 100); + * square(20, 20, 60); + * + * // Erase a circular area. + * strokeWeight(5); + * erase(150, 255); + * circle(25, 30, 30); + * noErase(); + * + * describe('A purple canvas with a pink square in the middle. A circle at the top-left partially erases its interior and a fully erases its outline.'); + * } + * + *
    + */ + fn.erase = function(opacityFill = 255, opacityStroke = 255) { + this._renderer.erase(opacityFill, opacityStroke); - return this; -}; + return this; + }; -/** - * Ends erasing that was started with erase(). - * - * The fill(), stroke(), and - * blendMode() settings will return to what they - * were prior to calling erase(). - * - * @method noErase - * @chainable - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(235, 145, 15); - * - * // Draw the left rectangle. - * noStroke(); - * fill(30, 45, 220); - * rect(30, 10, 10, 80); - * - * // Erase a circle. - * erase(); - * circle(50, 50, 60); - * noErase(); - * - * // Draw the right rectangle. - * rect(70, 10, 10, 80); - * - * describe('An orange canvas with two tall blue rectangles. A circular hole in the center erases the rectangle on the left but not the one on the right.'); - * } - * - *
    - */ -p5.prototype.noErase = function() { - this._renderer.noErase(); - return this; -}; + /** + * Ends erasing that was started with erase(). + * + * The fill(), stroke(), and + * blendMode() settings will return to what they + * were prior to calling erase(). + * + * @method noErase + * @chainable + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(235, 145, 15); + * + * // Draw the left rectangle. + * noStroke(); + * fill(30, 45, 220); + * rect(30, 10, 10, 80); + * + * // Erase a circle. + * erase(); + * circle(50, 50, 60); + * noErase(); + * + * // Draw the right rectangle. + * rect(70, 10, 10, 80); + * + * describe('An orange canvas with two tall blue rectangles. A circular hole in the center erases the rectangle on the left but not the one on the right.'); + * } + * + *
    + */ + fn.noErase = function() { + this._renderer.noErase(); + return this; + }; +} + +export default setting; -export default p5; +if(typeof p5 !== 'undefined'){ + setting(p5, p5.prototype); +} diff --git a/src/data/index.js b/src/data/index.js new file mode 100644 index 0000000000..6b80d2be0c --- /dev/null +++ b/src/data/index.js @@ -0,0 +1,7 @@ +import storage from './local_storage.js'; +import typedDict from './p5.TypedDict.js'; + +export default function(p5){ + p5.registerAddon(storage); + p5.registerAddon(typedDict); +} diff --git a/src/data/local_storage.js b/src/data/local_storage.js index b0e92fe501..9bd400fcea 100644 --- a/src/data/local_storage.js +++ b/src/data/local_storage.js @@ -6,448 +6,455 @@ * This module defines the p5 methods for working with local storage */ -import p5 from '../core/main'; -/** - * Stores a value in the web browser's local storage. - * - * Web browsers can save small amounts of data using the built-in - * localStorage object. - * Data stored in `localStorage` can be retrieved at any point, even after - * refreshing a page or restarting the browser. Data are stored as key-value - * pairs. - * - * `storeItem()` makes it easy to store values in `localStorage` and - * getItem() makes it easy to retrieve them. - * - * The first parameter, `key`, is the name of the value to be stored as a - * string. - * - * The second parameter, `value`, is the value to be stored. Values can have - * any type. - * - * Note: Sensitive data such as passwords or personal information shouldn't be - * stored in `localStorage`. - * - * @method storeItem - * @for p5 - * @param {String} key name of the value. - * @param {String|Number|Boolean|Object|Array} value value to be stored. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Store the player's name. - * storeItem('name', 'Feist'); - * - * // Store the player's score. - * storeItem('score', 1234); - * - * describe('The text "Feist: 1234" written in black on a gray background.'); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(14); - * - * // Retrieve the name. - * let name = getItem('name'); - * - * // Retrieve the score. - * let score = getItem('score'); - * - * // Display the score. - * text(`${name}: ${score}`, 50, 50); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Create an object. - * let p = { x: 50, y: 50 }; - * - * // Store the object. - * storeItem('position', p); - * - * describe('A white circle on a gray background.'); - * } - * - * function draw() { - * background(200); - * - * // Retrieve the object. - * let p = getItem('position'); - * - * // Draw the circle. - * circle(p.x, p.y, 30); - * } - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Create a p5.Color object. - * let c = color('deeppink'); - * - * // Store the object. - * storeItem('color', c); - * - * describe('A pink circle on a gray background.'); - * } - * - * function draw() { - * background(200); - * - * // Retrieve the object. - * let c = getItem('color'); - * - * // Style the circle. - * fill(c); - * - * // Draw the circle. - * circle(50, 50, 30); - * } - * - * - */ -p5.prototype.storeItem = function(key, value) { - if (typeof key !== 'string') { - console.log( - `The argument that you passed to storeItem() - ${key} is not a string.` - ); - } - if (key.endsWith('p5TypeID')) { - console.log( - `The argument that you passed to storeItem() - ${key} must not end with 'p5TypeID'.` - ); - } - - if (typeof value === 'undefined') { - console.log('You cannot store undefined variables using storeItem().'); - } - let type = typeof value; - switch (type) { - case 'number': - case 'boolean': - value = value.toString(); - break; - case 'object': - if (value instanceof p5.Color) { - type = 'p5.Color'; - value = value.toString(); - } else if (value instanceof p5.Vector) { - type = 'p5.Vector'; - const coord = [value.x, value.y, value.z]; - value = coord; - } - value = JSON.stringify(value); - break; - case 'string': - default: - break; - } - - localStorage.setItem(key, value); - const typeKey = `${key}p5TypeID`; - localStorage.setItem(typeKey, type); -}; +function storage(p5, fn){ + /** + * Stores a value in the web browser's local storage. + * + * Web browsers can save small amounts of data using the built-in + * localStorage object. + * Data stored in `localStorage` can be retrieved at any point, even after + * refreshing a page or restarting the browser. Data are stored as key-value + * pairs. + * + * `storeItem()` makes it easy to store values in `localStorage` and + * getItem() makes it easy to retrieve them. + * + * The first parameter, `key`, is the name of the value to be stored as a + * string. + * + * The second parameter, `value`, is the value to be stored. Values can have + * any type. + * + * Note: Sensitive data such as passwords or personal information shouldn't be + * stored in `localStorage`. + * + * @method storeItem + * @for p5 + * @param {String} key name of the value. + * @param {String|Number|Boolean|Object|Array} value value to be stored. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Store the player's name. + * storeItem('name', 'Feist'); + * + * // Store the player's score. + * storeItem('score', 1234); + * + * describe('The text "Feist: 1234" written in black on a gray background.'); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(14); + * + * // Retrieve the name. + * let name = getItem('name'); + * + * // Retrieve the score. + * let score = getItem('score'); + * + * // Display the score. + * text(`${name}: ${score}`, 50, 50); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Create an object. + * let p = { x: 50, y: 50 }; + * + * // Store the object. + * storeItem('position', p); + * + * describe('A white circle on a gray background.'); + * } + * + * function draw() { + * background(200); + * + * // Retrieve the object. + * let p = getItem('position'); + * + * // Draw the circle. + * circle(p.x, p.y, 30); + * } + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Create a p5.Color object. + * let c = color('deeppink'); + * + * // Store the object. + * storeItem('color', c); + * + * describe('A pink circle on a gray background.'); + * } + * + * function draw() { + * background(200); + * + * // Retrieve the object. + * let c = getItem('color'); + * + * // Style the circle. + * fill(c); + * + * // Draw the circle. + * circle(50, 50, 30); + * } + * + * + */ + fn.storeItem = function(key, value) { + if (typeof key !== 'string') { + console.log( + `The argument that you passed to storeItem() - ${key} is not a string.` + ); + } + if (key.endsWith('p5TypeID')) { + console.log( + `The argument that you passed to storeItem() - ${key} must not end with 'p5TypeID'.` + ); + } -/** - * Returns a value in the web browser's local storage. - * - * Web browsers can save small amounts of data using the built-in - * localStorage object. - * Data stored in `localStorage` can be retrieved at any point, even after - * refreshing a page or restarting the browser. Data are stored as key-value - * pairs. - * - * storeItem() makes it easy to store values in - * `localStorage` and `getItem()` makes it easy to retrieve them. - * - * The first parameter, `key`, is the name of the value to be stored as a - * string. - * - * The second parameter, `value`, is the value to be retrieved a string. For - * example, calling `getItem('size')` retrieves the value with the key `size`. - * - * Note: Sensitive data such as passwords or personal information shouldn't be - * stored in `localStorage`. - * - * @method getItem - * @for p5 - * @param {String} key name of the value. - * @return {String|Number|Boolean|Object|Array} stored item. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Store the player's name. - * storeItem('name', 'Feist'); - * - * // Store the player's score. - * storeItem('score', 1234); - * - * describe('The text "Feist: 1234" written in black on a gray background.'); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(14); - * - * // Retrieve the name. - * let name = getItem('name'); - * - * // Retrieve the score. - * let score = getItem('score'); - * - * // Display the score. - * text(`${name}: ${score}`, 50, 50); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Create an object. - * let p = { x: 50, y: 50 }; - * - * // Store the object. - * storeItem('position', p); - * - * describe('A white circle on a gray background.'); - * } - * - * function draw() { - * background(200); - * - * // Retrieve the object. - * let p = getItem('position'); - * - * // Draw the circle. - * circle(p.x, p.y, 30); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Create a p5.Color object. - * let c = color('deeppink'); - * - * // Store the object. - * storeItem('color', c); - * - * describe('A pink circle on a gray background.'); - * } - * - * function draw() { - * background(200); - * - * // Retrieve the object. - * let c = getItem('color'); - * - * // Style the circle. - * fill(c); - * - * // Draw the circle. - * circle(50, 50, 30); - * } - * - *
    - */ -p5.prototype.getItem = function(key) { - let value = localStorage.getItem(key); - const type = localStorage.getItem(`${key}p5TypeID`); - if (typeof type === 'undefined') { - console.log( - `Unable to determine type of item stored under ${key}in local storage. Did you save the item with something other than setItem()?` - ); - } else if (value !== null) { + if (typeof value === 'undefined') { + console.log('You cannot store undefined variables using storeItem().'); + } + let type = typeof value; switch (type) { case 'number': - value = parseFloat(value); - break; case 'boolean': - value = value === 'true'; + value = value.toString(); break; case 'object': - value = JSON.parse(value); - break; - case 'p5.Color': - value = this.color(value); - break; - case 'p5.Vector': - value = JSON.parse(value); - value = this.createVector(...value); + if (value instanceof p5.Color) { + type = 'p5.Color'; + value = value.toString(); + } else if (value instanceof p5.Vector) { + type = 'p5.Vector'; + const coord = [value.x, value.y, value.z]; + value = coord; + } + value = JSON.stringify(value); break; case 'string': default: break; } - } - return value; -}; -/** - * Removes all items in the web browser's local storage. - * - * Web browsers can save small amounts of data using the built-in - * localStorage object. - * Data stored in `localStorage` can be retrieved at any point, even after - * refreshing a page or restarting the browser. Data are stored as key-value - * pairs. Calling `clearStorage()` removes all data from `localStorage`. - * - * Note: Sensitive data such as passwords or personal information shouldn't be - * stored in `localStorage`. - * - * @method clearStorage - * @for p5 - * - * @example - *
    - * - * // Double-click to clear localStorage. - * - * function setup() { - * createCanvas(100, 100); - * - * // Store the player's name. - * storeItem('name', 'Feist'); - * - * // Store the player's score. - * storeItem('score', 1234); - * - * describe( - * 'The text "Feist: 1234" written in black on a gray background. The text "null: null" appears when the user double-clicks.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(14); - * - * // Retrieve the name. - * let name = getItem('name'); - * - * // Retrieve the score. - * let score = getItem('score'); - * - * // Display the score. - * text(`${name}: ${score}`, 50, 50); - * } - * - * // Clear localStorage when the user double-clicks. - * function doubleClicked() { - * clearStorage(); - * } - * - *
    - */ -p5.prototype.clearStorage = function () { - const keys = Object.keys(localStorage); - keys.forEach(key => { - if (key.endsWith('p5TypeID')) { - this.removeItem(key.replace('p5TypeID', '')); + localStorage.setItem(key, value); + const typeKey = `${key}p5TypeID`; + localStorage.setItem(typeKey, type); + }; + + /** + * Returns a value in the web browser's local storage. + * + * Web browsers can save small amounts of data using the built-in + * localStorage object. + * Data stored in `localStorage` can be retrieved at any point, even after + * refreshing a page or restarting the browser. Data are stored as key-value + * pairs. + * + * storeItem() makes it easy to store values in + * `localStorage` and `getItem()` makes it easy to retrieve them. + * + * The first parameter, `key`, is the name of the value to be stored as a + * string. + * + * The second parameter, `value`, is the value to be retrieved a string. For + * example, calling `getItem('size')` retrieves the value with the key `size`. + * + * Note: Sensitive data such as passwords or personal information shouldn't be + * stored in `localStorage`. + * + * @method getItem + * @for p5 + * @param {String} key name of the value. + * @return {String|Number|Boolean|Object|Array} stored item. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Store the player's name. + * storeItem('name', 'Feist'); + * + * // Store the player's score. + * storeItem('score', 1234); + * + * describe('The text "Feist: 1234" written in black on a gray background.'); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(14); + * + * // Retrieve the name. + * let name = getItem('name'); + * + * // Retrieve the score. + * let score = getItem('score'); + * + * // Display the score. + * text(`${name}: ${score}`, 50, 50); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Create an object. + * let p = { x: 50, y: 50 }; + * + * // Store the object. + * storeItem('position', p); + * + * describe('A white circle on a gray background.'); + * } + * + * function draw() { + * background(200); + * + * // Retrieve the object. + * let p = getItem('position'); + * + * // Draw the circle. + * circle(p.x, p.y, 30); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Create a p5.Color object. + * let c = color('deeppink'); + * + * // Store the object. + * storeItem('color', c); + * + * describe('A pink circle on a gray background.'); + * } + * + * function draw() { + * background(200); + * + * // Retrieve the object. + * let c = getItem('color'); + * + * // Style the circle. + * fill(c); + * + * // Draw the circle. + * circle(50, 50, 30); + * } + * + *
    + */ + fn.getItem = function(key) { + let value = localStorage.getItem(key); + const type = localStorage.getItem(`${key}p5TypeID`); + if (typeof type === 'undefined') { + console.log( + `Unable to determine type of item stored under ${key}in local storage. Did you save the item with something other than setItem()?` + ); + } else if (value !== null) { + switch (type) { + case 'number': + value = parseFloat(value); + break; + case 'boolean': + value = value === 'true'; + break; + case 'object': + value = JSON.parse(value); + break; + case 'p5.Color': + value = this.color(value); + break; + case 'p5.Vector': + value = JSON.parse(value); + value = this.createVector(...value); + break; + case 'string': + default: + break; + } } - }); -}; + return value; + }; -/** - * Removes an item from the web browser's local storage. - * - * Web browsers can save small amounts of data using the built-in - * localStorage object. - * Data stored in `localStorage` can be retrieved at any point, even after - * refreshing a page or restarting the browser. Data are stored as key-value - * pairs. - * - * storeItem() makes it easy to store values in - * `localStorage` and `removeItem()` makes it easy to delete them. - * - * The parameter, `key`, is the name of the value to remove as a string. For - * example, calling `removeItem('size')` removes the item with the key `size`. - * - * Note: Sensitive data such as passwords or personal information shouldn't be - * stored in `localStorage`. - * - * @method removeItem - * @param {String} key name of the value to remove. - * @for p5 - * - * @example - *
    - * - * // Double-click to remove an item from localStorage. - * - * function setup() { - * createCanvas(100, 100); - * - * // Store the player's name. - * storeItem('name', 'Feist'); - * - * // Store the player's score. - * storeItem('score', 1234); - * - * describe( - * 'The text "Feist: 1234" written in black on a gray background. The text "Feist: null" appears when the user double-clicks.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(14); - * - * // Retrieve the name. - * let name = getItem('name'); - * - * // Retrieve the score. - * let score = getItem('score'); - * - * // Display the score. - * text(`${name}: ${score}`, 50, 50); - * } - * - * // Remove the word from localStorage when the user double-clicks. - * function doubleClicked() { - * removeItem('score'); - * } - * - *
    - */ -p5.prototype.removeItem = function(key) { - if (typeof key !== 'string') { - console.log( - `The argument that you passed to removeItem() - ${key} is not a string.` - ); - } - localStorage.removeItem(key); - localStorage.removeItem(`${key}p5TypeID`); -}; + /** + * Removes all items in the web browser's local storage. + * + * Web browsers can save small amounts of data using the built-in + * localStorage object. + * Data stored in `localStorage` can be retrieved at any point, even after + * refreshing a page or restarting the browser. Data are stored as key-value + * pairs. Calling `clearStorage()` removes all data from `localStorage`. + * + * Note: Sensitive data such as passwords or personal information shouldn't be + * stored in `localStorage`. + * + * @method clearStorage + * @for p5 + * + * @example + *
    + * + * // Double-click to clear localStorage. + * + * function setup() { + * createCanvas(100, 100); + * + * // Store the player's name. + * storeItem('name', 'Feist'); + * + * // Store the player's score. + * storeItem('score', 1234); + * + * describe( + * 'The text "Feist: 1234" written in black on a gray background. The text "null: null" appears when the user double-clicks.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(14); + * + * // Retrieve the name. + * let name = getItem('name'); + * + * // Retrieve the score. + * let score = getItem('score'); + * + * // Display the score. + * text(`${name}: ${score}`, 50, 50); + * } + * + * // Clear localStorage when the user double-clicks. + * function doubleClicked() { + * clearStorage(); + * } + * + *
    + */ + fn.clearStorage = function () { + const keys = Object.keys(localStorage); + keys.forEach(key => { + if (key.endsWith('p5TypeID')) { + this.removeItem(key.replace('p5TypeID', '')); + } + }); + }; + + /** + * Removes an item from the web browser's local storage. + * + * Web browsers can save small amounts of data using the built-in + * localStorage object. + * Data stored in `localStorage` can be retrieved at any point, even after + * refreshing a page or restarting the browser. Data are stored as key-value + * pairs. + * + * storeItem() makes it easy to store values in + * `localStorage` and `removeItem()` makes it easy to delete them. + * + * The parameter, `key`, is the name of the value to remove as a string. For + * example, calling `removeItem('size')` removes the item with the key `size`. + * + * Note: Sensitive data such as passwords or personal information shouldn't be + * stored in `localStorage`. + * + * @method removeItem + * @param {String} key name of the value to remove. + * @for p5 + * + * @example + *
    + * + * // Double-click to remove an item from localStorage. + * + * function setup() { + * createCanvas(100, 100); + * + * // Store the player's name. + * storeItem('name', 'Feist'); + * + * // Store the player's score. + * storeItem('score', 1234); + * + * describe( + * 'The text "Feist: 1234" written in black on a gray background. The text "Feist: null" appears when the user double-clicks.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(14); + * + * // Retrieve the name. + * let name = getItem('name'); + * + * // Retrieve the score. + * let score = getItem('score'); + * + * // Display the score. + * text(`${name}: ${score}`, 50, 50); + * } + * + * // Remove the word from localStorage when the user double-clicks. + * function doubleClicked() { + * removeItem('score'); + * } + * + *
    + */ + fn.removeItem = function(key) { + if (typeof key !== 'string') { + console.log( + `The argument that you passed to removeItem() - ${key} is not a string.` + ); + } + localStorage.removeItem(key); + localStorage.removeItem(`${key}p5TypeID`); + }; +} + +export default storage; + +if(typeof p5 !== 'undefined'){ + storage(p5, p5.prototype); +} diff --git a/src/data/p5.TypedDict.js b/src/data/p5.TypedDict.js index 20f0aef23b..d816769c0c 100644 --- a/src/data/p5.TypedDict.js +++ b/src/data/p5.TypedDict.js @@ -9,120 +9,17 @@ * with key-value pairs. */ -import p5 from '../core/main'; - -/** - * - * Creates a new instance of p5.StringDict using the key-value pair - * or the object you provide. - * - * @method createStringDict - * @for p5 - * @param {String} key - * @param {String} value - * @return {p5.StringDict} - * - * @example - *
    - * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * print(myDictionary.hasKey('p5')); // logs true to console - * - * let anotherDictionary = createStringDict({ happy: 'coding' }); - * print(anotherDictionary.hasKey('happy')); // logs true to console - * } - *
    - */ -/** - * @method createStringDict - * @param {Object} object object - * @return {p5.StringDict} - */ - -p5.prototype.createStringDict = function (key, value) { - p5._validateParameters('createStringDict', arguments); - return new p5.StringDict(key, value); -}; - -/** - * - * Creates a new instance of p5.NumberDict using the key-value pair - * or object you provide. - * - * @method createNumberDict - * @for p5 - * @param {Number} key - * @param {Number} value - * @return {p5.NumberDict} - * - * @example - *
    - * - * function setup() { - * let myDictionary = createNumberDict(100, 42); - * print(myDictionary.hasKey(100)); // logs true to console - * - * let anotherDictionary = createNumberDict({ 200: 84 }); - * print(anotherDictionary.hasKey(200)); // logs true to console - * } - *
    - */ -/** - * @method createNumberDict - * @param {Object} object object - * @return {p5.NumberDict} - */ - -p5.prototype.createNumberDict = function (key, value) { - p5._validateParameters('createNumberDict', arguments); - return new p5.NumberDict(key, value); -}; - -/** - * - * Base class for all p5.Dictionary types. Specifically - * typed Dictionary classes inherit from this class. - * - * @class p5.TypedDict - */ -p5.TypedDict = class TypedDict { - constructor(key, value) { - if (key instanceof Object) { - this.data = key; - } else { - this.data = {}; - this.data[key] = value; - } - return this; - } - +function typedDict(p5, fn){ /** - * Returns the number of key-value pairs currently stored in the Dictionary. - * - * @return {Integer} the number of key-value pairs in the Dictionary * - * @example - *
    - * - * function setup() { - * let myDictionary = createNumberDict(1, 10); - * myDictionary.create(2, 20); - * myDictionary.create(3, 30); - * print(myDictionary.size()); // logs 3 to the console - * } - *
    - */ - size() { - return Object.keys(this.data).length; - } - - /** - * Returns true if the given key exists in the Dictionary, - * otherwise returns false. + * Creates a new instance of p5.StringDict using the key-value pair + * or the object you provide. * - * @param {Number|String} key that you want to look up - * @return {Boolean} whether that key exists in Dictionary + * @method createStringDict + * @for p5 + * @param {String} key + * @param {String} value + * @return {p5.StringDict} * * @example *
    @@ -130,499 +27,602 @@ p5.TypedDict = class TypedDict { * function setup() { * let myDictionary = createStringDict('p5', 'js'); * print(myDictionary.hasKey('p5')); // logs true to console + * + * let anotherDictionary = createStringDict({ happy: 'coding' }); + * print(anotherDictionary.hasKey('happy')); // logs true to console * } *
    */ - - hasKey(key) { - return this.data.hasOwnProperty(key); - } - /** - * Returns the value stored at the given key. - * - * @param {Number|String} the key you want to access - * @return {Number|String} the value stored at that key - * - * @example - *
    - * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * let myValue = myDictionary.get('p5'); - * print(myValue === 'js'); // logs true to console - * } - *
    + * @method createStringDict + * @param {Object} object object + * @return {p5.StringDict} */ - get(key) { - if (this.data.hasOwnProperty(key)) { - return this.data[key]; - } else { - console.log(`${key} does not exist in this Dictionary`); - } - } + fn.createStringDict = function (key, value) { + p5._validateParameters('createStringDict', arguments); + return new p5.StringDict(key, value); + }; /** - * Updates the value associated with the given key in case it already exists - * in the Dictionary. Otherwise a new key-value pair is added. * - * @param {Number|String} key - * @param {Number|String} value + * Creates a new instance of p5.NumberDict using the key-value pair + * or object you provide. + * + * @method createNumberDict + * @for p5 + * @param {Number} key + * @param {Number} value + * @return {p5.NumberDict} * * @example *
    * * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * myDictionary.set('p5', 'JS'); - * myDictionary.print(); // logs "key: p5 - value: JS" to console + * let myDictionary = createNumberDict(100, 42); + * print(myDictionary.hasKey(100)); // logs true to console + * + * let anotherDictionary = createNumberDict({ 200: 84 }); + * print(anotherDictionary.hasKey(200)); // logs true to console * } *
    */ - - set(key, value) { - if (this._validate(value)) { - this.data[key] = value; - } else { - console.log('Those values dont work for this dictionary type.'); - } - } - /** - * private helper function to handle the user passing in objects - * during construction or calls to create() + * @method createNumberDict + * @param {Object} object object + * @return {p5.NumberDict} */ - _addObj(obj) { - for (const key in obj) { - this.set(key, obj[key]); - } - } + fn.createNumberDict = function (key, value) { + p5._validateParameters('createNumberDict', arguments); + return new p5.NumberDict(key, value); + }; /** - * Creates a new key-value pair in the Dictionary. * - * @param {Number|String} key - * @param {Number|String} value + * Base class for all p5.Dictionary types. Specifically + * typed Dictionary classes inherit from this class. * - * @example - *
    - * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * myDictionary.create('happy', 'coding'); - * myDictionary.print(); - * // above logs "key: p5 - value: js, key: happy - value: coding" to console - * } - *
    + * @class p5.TypedDict */ - /** - * @param {Object} obj key/value pair - */ - create(key, value) { - if (key instanceof Object && typeof value === 'undefined') { - this._addObj(key); - } else if (typeof key !== 'undefined') { - this.set(key, value); - } else { - console.log( - 'In order to create a new Dictionary entry you must pass ' + - 'an object or a key, value pair' - ); + p5.TypedDict = class TypedDict { + constructor(key, value) { + if (key instanceof Object) { + this.data = key; + } else { + this.data = {}; + this.data[key] = value; + } + return this; } - } - /** - * Removes all previously stored key-value pairs from the Dictionary. - * - * @example - *
    - * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * print(myDictionary.hasKey('p5')); // prints 'true' - * myDictionary.clear(); - * print(myDictionary.hasKey('p5')); // prints 'false' - * } - * - *
    - */ - clear() { - this.data = {}; - } + /** + * Returns the number of key-value pairs currently stored in the Dictionary. + * + * @return {Integer} the number of key-value pairs in the Dictionary + * + * @example + *
    + * + * function setup() { + * let myDictionary = createNumberDict(1, 10); + * myDictionary.create(2, 20); + * myDictionary.create(3, 30); + * print(myDictionary.size()); // logs 3 to the console + * } + *
    + */ + size() { + return Object.keys(this.data).length; + } - /** - * Removes the key-value pair stored at the given key from the Dictionary. - * - * @param {Number|String} key for the pair to remove - * - * @example - *
    - * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * myDictionary.create('happy', 'coding'); - * myDictionary.print(); - * // above logs "key: p5 - value: js, key: happy - value: coding" to console - * myDictionary.remove('p5'); - * myDictionary.print(); - * // above logs "key: happy value: coding" to console - * } - *
    - */ - remove(key) { - if (this.data.hasOwnProperty(key)) { - delete this.data[key]; - } else { - throw new Error(`${key} does not exist in this Dictionary`); + /** + * Returns true if the given key exists in the Dictionary, + * otherwise returns false. + * + * @param {Number|String} key that you want to look up + * @return {Boolean} whether that key exists in Dictionary + * + * @example + *
    + * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * print(myDictionary.hasKey('p5')); // logs true to console + * } + *
    + */ + + hasKey(key) { + return this.data.hasOwnProperty(key); } - } - /** - * Logs the set of items currently stored in the Dictionary to the console. - * - * @example - *
    - * - * function setup() { - * let myDictionary = createStringDict('p5', 'js'); - * myDictionary.create('happy', 'coding'); - * myDictionary.print(); - * // above logs "key: p5 - value: js, key: happy - value: coding" to console - * } - * - *
    - */ - print() { - for (const item in this.data) { - console.log(`key:${item} value:${this.data[item]}`); + /** + * Returns the value stored at the given key. + * + * @param {Number|String} the key you want to access + * @return {Number|String} the value stored at that key + * + * @example + *
    + * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * let myValue = myDictionary.get('p5'); + * print(myValue === 'js'); // logs true to console + * } + *
    + */ + + get(key) { + if (this.data.hasOwnProperty(key)) { + return this.data[key]; + } else { + console.log(`${key} does not exist in this Dictionary`); + } } - } - /** - * Converts the Dictionary into a CSV file for local download. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * background(200); - * text('click here to save', 10, 10, 70, 80); - * } - * - * function mousePressed() { - * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { - * createStringDict({ - * john: 1940, - * paul: 1942, - * george: 1943, - * ringo: 1940 - * }).saveTable('beatles'); - * } - * } - * - *
    - */ - saveTable(filename) { - let output = ''; + /** + * Updates the value associated with the given key in case it already exists + * in the Dictionary. Otherwise a new key-value pair is added. + * + * @param {Number|String} key + * @param {Number|String} value + * + * @example + *
    + * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * myDictionary.set('p5', 'JS'); + * myDictionary.print(); // logs "key: p5 - value: JS" to console + * } + *
    + */ + + set(key, value) { + if (this._validate(value)) { + this.data[key] = value; + } else { + console.log('Those values dont work for this dictionary type.'); + } + } - for (const key in this.data) { - output += `${key},${this.data[key]}\n`; + /** + * private helper function to handle the user passing in objects + * during construction or calls to create() + */ + + _addObj(obj) { + for (const key in obj) { + this.set(key, obj[key]); + } } - const blob = new Blob([output], { type: 'text/csv' }); - p5.prototype.downloadFile(blob, filename || 'mycsv', 'csv'); - } + /** + * Creates a new key-value pair in the Dictionary. + * + * @param {Number|String} key + * @param {Number|String} value + * + * @example + *
    + * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * myDictionary.create('happy', 'coding'); + * myDictionary.print(); + * // above logs "key: p5 - value: js, key: happy - value: coding" to console + * } + *
    + */ + /** + * @param {Object} obj key/value pair + */ + create(key, value) { + if (key instanceof Object && typeof value === 'undefined') { + this._addObj(key); + } else if (typeof key !== 'undefined') { + this.set(key, value); + } else { + console.log( + 'In order to create a new Dictionary entry you must pass ' + + 'an object or a key, value pair' + ); + } + } - /** - * Converts the Dictionary into a JSON file for local download. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * background(200); - * text('click here to save', 10, 10, 70, 80); - * } - * - * function mousePressed() { - * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { - * createStringDict({ - * john: 1940, - * paul: 1942, - * george: 1943, - * ringo: 1940 - * }).saveJSON('beatles'); - * } - * } - * - *
    - */ - saveJSON(filename, opt) { - p5.prototype.saveJSON(this.data, filename, opt); - } + /** + * Removes all previously stored key-value pairs from the Dictionary. + * + * @example + *
    + * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * print(myDictionary.hasKey('p5')); // prints 'true' + * myDictionary.clear(); + * print(myDictionary.hasKey('p5')); // prints 'false' + * } + * + *
    + */ + clear() { + this.data = {}; + } - /** - * private helper function to ensure that the user passed in valid - * values for the Dictionary type - */ - _validate(value) { - return true; - } -}; + /** + * Removes the key-value pair stored at the given key from the Dictionary. + * + * @param {Number|String} key for the pair to remove + * + * @example + *
    + * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * myDictionary.create('happy', 'coding'); + * myDictionary.print(); + * // above logs "key: p5 - value: js, key: happy - value: coding" to console + * myDictionary.remove('p5'); + * myDictionary.print(); + * // above logs "key: happy value: coding" to console + * } + *
    + */ + remove(key) { + if (this.data.hasOwnProperty(key)) { + delete this.data[key]; + } else { + throw new Error(`${key} does not exist in this Dictionary`); + } + } -/** - * - * A simple Dictionary class for Strings. - * - * @class p5.StringDict - * @extends p5.TypedDict - */ -p5.StringDict = class StringDict extends p5.TypedDict { - constructor(...args) { - super(...args); - } + /** + * Logs the set of items currently stored in the Dictionary to the console. + * + * @example + *
    + * + * function setup() { + * let myDictionary = createStringDict('p5', 'js'); + * myDictionary.create('happy', 'coding'); + * myDictionary.print(); + * // above logs "key: p5 - value: js, key: happy - value: coding" to console + * } + * + *
    + */ + print() { + for (const item in this.data) { + console.log(`key:${item} value:${this.data[item]}`); + } + } - _validate(value) { - return typeof value === 'string'; - } -}; + /** + * Converts the Dictionary into a CSV file for local download. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * background(200); + * text('click here to save', 10, 10, 70, 80); + * } + * + * function mousePressed() { + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * createStringDict({ + * john: 1940, + * paul: 1942, + * george: 1943, + * ringo: 1940 + * }).saveTable('beatles'); + * } + * } + * + *
    + */ + saveTable(filename) { + let output = ''; -/** - * - * A simple Dictionary class for Numbers. - * - * @class p5.NumberDict - * @extends p5.TypedDict - */ + for (const key in this.data) { + output += `${key},${this.data[key]}\n`; + } -p5.NumberDict = class NumberDict extends p5.TypedDict { - constructor(...args) { - super(...args); - } + const blob = new Blob([output], { type: 'text/csv' }); + fn.downloadFile(blob, filename || 'mycsv', 'csv'); + } - /** - * private helper function to ensure that the user passed in valid - * values for the Dictionary type - */ - _validate(value) { - return typeof value === 'number'; - } + /** + * Converts the Dictionary into a JSON file for local download. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * background(200); + * text('click here to save', 10, 10, 70, 80); + * } + * + * function mousePressed() { + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * createStringDict({ + * john: 1940, + * paul: 1942, + * george: 1943, + * ringo: 1940 + * }).saveJSON('beatles'); + * } + * } + * + *
    + */ + saveJSON(filename, opt) { + fn.saveJSON(this.data, filename, opt); + } - /** - * Add the given number to the value currently stored at the given key. - * The sum then replaces the value previously stored in the Dictionary. - * - * @param {Number} Key for the value you wish to add to - * @param {Number} Number to add to the value - * @example - *
    - * - * function setup() { - * let myDictionary = createNumberDict(2, 5); - * myDictionary.add(2, 2); - * print(myDictionary.get(2)); // logs 7 to console. - * } - *
    - * - */ - add(key, amount) { - if (this.data.hasOwnProperty(key)) { - this.data[key] += amount; - } else { - console.log(`The key - ${key} does not exist in this dictionary.`); + /** + * private helper function to ensure that the user passed in valid + * values for the Dictionary type + */ + _validate(value) { + return true; } - } + }; /** - * Subtract the given number from the value currently stored at the given key. - * The difference then replaces the value previously stored in the Dictionary. * - * @param {Number} Key for the value you wish to subtract from - * @param {Number} Number to subtract from the value - * @example - *
    - * - * function setup() { - * let myDictionary = createNumberDict(2, 5); - * myDictionary.sub(2, 2); - * print(myDictionary.get(2)); // logs 3 to console. - * } - *
    + * A simple Dictionary class for Strings. * + * @class p5.StringDict + * @extends p5.TypedDict */ - sub(key, amount) { - this.add(key, -amount); - } + p5.StringDict = class StringDict extends p5.TypedDict { + constructor(...args) { + super(...args); + } - /** - * Multiply the given number with the value currently stored at the given key. - * The product then replaces the value previously stored in the Dictionary. - * - * @param {Number} Key for value you wish to multiply - * @param {Number} Amount to multiply the value by - * @example - *
    - * - * function setup() { - * let myDictionary = createNumberDict(2, 4); - * myDictionary.mult(2, 2); - * print(myDictionary.get(2)); // logs 8 to console. - * } - *
    - * - */ - mult(key, amount) { - if (this.data.hasOwnProperty(key)) { - this.data[key] *= amount; - } else { - console.log(`The key - ${key} does not exist in this dictionary.`); + _validate(value) { + return typeof value === 'string'; } - } + }; /** - * Divide the given number with the value currently stored at the given key. - * The quotient then replaces the value previously stored in the Dictionary. * - * @param {Number} Key for value you wish to divide - * @param {Number} Amount to divide the value by - * @example - *
    - * - * function setup() { - * let myDictionary = createNumberDict(2, 8); - * myDictionary.div(2, 2); - * print(myDictionary.get(2)); // logs 4 to console. - * } - *
    + * A simple Dictionary class for Numbers. * + * @class p5.NumberDict + * @extends p5.TypedDict */ - div(key, amount) { - if (this.data.hasOwnProperty(key)) { - this.data[key] /= amount; - } else { - console.log(`The key - ${key} does not exist in this dictionary.`); + + p5.NumberDict = class NumberDict extends p5.TypedDict { + constructor(...args) { + super(...args); } - } - /** - * private helper function for finding lowest or highest value - * the argument 'flip' is used to flip the comparison arrow - * from 'less than' to 'greater than' - */ - _valueTest(flip) { - if (Object.keys(this.data).length === 0) { - throw new Error( - 'Unable to search for a minimum or maximum value on an empty NumberDict' - ); - } else if (Object.keys(this.data).length === 1) { - return this.data[Object.keys(this.data)[0]]; - } else { - let result = this.data[Object.keys(this.data)[0]]; - for (const key in this.data) { - if (this.data[key] * flip < result * flip) { - result = this.data[key]; + /** + * private helper function to ensure that the user passed in valid + * values for the Dictionary type + */ + _validate(value) { + return typeof value === 'number'; + } + + /** + * Add the given number to the value currently stored at the given key. + * The sum then replaces the value previously stored in the Dictionary. + * + * @param {Number} Key for the value you wish to add to + * @param {Number} Number to add to the value + * @example + *
    + * + * function setup() { + * let myDictionary = createNumberDict(2, 5); + * myDictionary.add(2, 2); + * print(myDictionary.get(2)); // logs 7 to console. + * } + *
    + * + */ + add(key, amount) { + if (this.data.hasOwnProperty(key)) { + this.data[key] += amount; + } else { + console.log(`The key - ${key} does not exist in this dictionary.`); + } + } + + /** + * Subtract the given number from the value currently stored at the given key. + * The difference then replaces the value previously stored in the Dictionary. + * + * @param {Number} Key for the value you wish to subtract from + * @param {Number} Number to subtract from the value + * @example + *
    + * + * function setup() { + * let myDictionary = createNumberDict(2, 5); + * myDictionary.sub(2, 2); + * print(myDictionary.get(2)); // logs 3 to console. + * } + *
    + * + */ + sub(key, amount) { + this.add(key, -amount); + } + + /** + * Multiply the given number with the value currently stored at the given key. + * The product then replaces the value previously stored in the Dictionary. + * + * @param {Number} Key for value you wish to multiply + * @param {Number} Amount to multiply the value by + * @example + *
    + * + * function setup() { + * let myDictionary = createNumberDict(2, 4); + * myDictionary.mult(2, 2); + * print(myDictionary.get(2)); // logs 8 to console. + * } + *
    + * + */ + mult(key, amount) { + if (this.data.hasOwnProperty(key)) { + this.data[key] *= amount; + } else { + console.log(`The key - ${key} does not exist in this dictionary.`); + } + } + + /** + * Divide the given number with the value currently stored at the given key. + * The quotient then replaces the value previously stored in the Dictionary. + * + * @param {Number} Key for value you wish to divide + * @param {Number} Amount to divide the value by + * @example + *
    + * + * function setup() { + * let myDictionary = createNumberDict(2, 8); + * myDictionary.div(2, 2); + * print(myDictionary.get(2)); // logs 4 to console. + * } + *
    + * + */ + div(key, amount) { + if (this.data.hasOwnProperty(key)) { + this.data[key] /= amount; + } else { + console.log(`The key - ${key} does not exist in this dictionary.`); + } + } + + /** + * private helper function for finding lowest or highest value + * the argument 'flip' is used to flip the comparison arrow + * from 'less than' to 'greater than' + */ + _valueTest(flip) { + if (Object.keys(this.data).length === 0) { + throw new Error( + 'Unable to search for a minimum or maximum value on an empty NumberDict' + ); + } else if (Object.keys(this.data).length === 1) { + return this.data[Object.keys(this.data)[0]]; + } else { + let result = this.data[Object.keys(this.data)[0]]; + for (const key in this.data) { + if (this.data[key] * flip < result * flip) { + result = this.data[key]; + } } + return result; } - return result; } - } - /** - * Return the lowest number currently stored in the Dictionary. - * - * @return {Number} - * @example - *
    - * - * function setup() { - * let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 }); - * let lowestValue = myDictionary.minValue(); // value is -10 - * print(lowestValue); - * } - *
    - */ - minValue() { - return this._valueTest(1); - } + /** + * Return the lowest number currently stored in the Dictionary. + * + * @return {Number} + * @example + *
    + * + * function setup() { + * let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 }); + * let lowestValue = myDictionary.minValue(); // value is -10 + * print(lowestValue); + * } + *
    + */ + minValue() { + return this._valueTest(1); + } - /** - * Return the highest number currently stored in the Dictionary. - * - * @return {Number} - * @example - *
    - * - * function setup() { - * let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 }); - * let highestValue = myDictionary.maxValue(); // value is 3 - * print(highestValue); - * } - *
    - */ - maxValue() { - return this._valueTest(-1); - } + /** + * Return the highest number currently stored in the Dictionary. + * + * @return {Number} + * @example + *
    + * + * function setup() { + * let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 }); + * let highestValue = myDictionary.maxValue(); // value is 3 + * print(highestValue); + * } + *
    + */ + maxValue() { + return this._valueTest(-1); + } - /** - * private helper function for finding lowest or highest key - * the argument 'flip' is used to flip the comparison arrow - * from 'less than' to 'greater than' - */ - _keyTest(flip) { - if (Object.keys(this.data).length === 0) { - throw new Error('Unable to use minValue on an empty NumberDict'); - } else if (Object.keys(this.data).length === 1) { - return Object.keys(this.data)[0]; - } else { - let result = Object.keys(this.data)[0]; - for (let i = 1; i < Object.keys(this.data).length; i++) { - if (Object.keys(this.data)[i] * flip < result * flip) { - result = Object.keys(this.data)[i]; + /** + * private helper function for finding lowest or highest key + * the argument 'flip' is used to flip the comparison arrow + * from 'less than' to 'greater than' + */ + _keyTest(flip) { + if (Object.keys(this.data).length === 0) { + throw new Error('Unable to use minValue on an empty NumberDict'); + } else if (Object.keys(this.data).length === 1) { + return Object.keys(this.data)[0]; + } else { + let result = Object.keys(this.data)[0]; + for (let i = 1; i < Object.keys(this.data).length; i++) { + if (Object.keys(this.data)[i] * flip < result * flip) { + result = Object.keys(this.data)[i]; + } } + return result; } - return result; } - } - /** - * Return the lowest key currently used in the Dictionary. - * - * @return {Number} - * @example - *
    - * - * function setup() { - * let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 }); - * let lowestKey = myDictionary.minKey(); // value is 1.2 - * print(lowestKey); - * } - *
    - */ - minKey() { - return this._keyTest(1); - } + /** + * Return the lowest key currently used in the Dictionary. + * + * @return {Number} + * @example + *
    + * + * function setup() { + * let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 }); + * let lowestKey = myDictionary.minKey(); // value is 1.2 + * print(lowestKey); + * } + *
    + */ + minKey() { + return this._keyTest(1); + } - /** - * Return the highest key currently used in the Dictionary. - * - * @return {Number} - * @example - *
    - * - * function setup() { - * let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 }); - * let highestKey = myDictionary.maxKey(); // value is 4 - * print(highestKey); - * } - *
    - */ - maxKey() { - return this._keyTest(-1); - } -}; + /** + * Return the highest key currently used in the Dictionary. + * + * @return {Number} + * @example + *
    + * + * function setup() { + * let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 }); + * let highestKey = myDictionary.maxKey(); // value is 4 + * print(highestKey); + * } + *
    + */ + maxKey() { + return this._keyTest(-1); + } + }; +} -export default p5.TypedDict; +export default typedDict; diff --git a/src/events/acceleration.js b/src/events/acceleration.js index 1d402cb6cd..126a9942de 100644 --- a/src/events/acceleration.js +++ b/src/events/acceleration.js @@ -6,738 +6,742 @@ * @main Events */ -import p5 from '../core/main'; - -/** - * The system variable deviceOrientation always contains the orientation of - * the device. The value of this variable will either be set 'landscape' - * or 'portrait'. If no data is available it will be set to 'undefined'. - * either LANDSCAPE or PORTRAIT. - * - * @property {(LANDSCAPE|PORTRAIT)} deviceOrientation - * @readOnly - */ -p5.prototype.deviceOrientation = - window.innerWidth / window.innerHeight > 1.0 ? 'landscape' : 'portrait'; - -/** - * The system variable accelerationX always contains the acceleration of the - * device along the x axis. Value is represented as meters per second squared. - * - * @property {Number} accelerationX - * @readOnly - * @example - *
    - * - * // Move a touchscreen device to register - * // acceleration changes. - * function draw() { - * background(220, 50); - * fill('magenta'); - * ellipse(width / 2, height / 2, accelerationX); - * describe('Magnitude of device acceleration is displayed as ellipse size.'); - * } - * - *
    - */ -p5.prototype.accelerationX = 0; - -/** - * The system variable accelerationY always contains the acceleration of the - * device along the y axis. Value is represented as meters per second squared. - * - * @property {Number} accelerationY - * @readOnly - * @example - *
    - * - * // Move a touchscreen device to register - * // acceleration changes. - * function draw() { - * background(220, 50); - * fill('magenta'); - * ellipse(width / 2, height / 2, accelerationY); - * describe('Magnitude of device acceleration is displayed as ellipse size'); - * } - * - *
    - */ -p5.prototype.accelerationY = 0; - -/** - * The system variable accelerationZ always contains the acceleration of the - * device along the z axis. Value is represented as meters per second squared. - * - * @property {Number} accelerationZ - * @readOnly - * - * @example - *
    - * - * // Move a touchscreen device to register - * // acceleration changes. - * function draw() { - * background(220, 50); - * fill('magenta'); - * ellipse(width / 2, height / 2, accelerationZ); - * describe('Magnitude of device acceleration is displayed as ellipse size'); - * } - * - *
    - */ -p5.prototype.accelerationZ = 0; - -/** - * The system variable pAccelerationX always contains the acceleration of the - * device along the x axis in the frame previous to the current frame. Value - * is represented as meters per second squared. - * - * @property {Number} pAccelerationX - * @readOnly - */ -p5.prototype.pAccelerationX = 0; - -/** - * The system variable pAccelerationY always contains the acceleration of the - * device along the y axis in the frame previous to the current frame. Value - * is represented as meters per second squared. - * - * @property {Number} pAccelerationY - * @readOnly - */ -p5.prototype.pAccelerationY = 0; - -/** - * The system variable pAccelerationZ always contains the acceleration of the - * device along the z axis in the frame previous to the current frame. Value - * is represented as meters per second squared. - * - * @property {Number} pAccelerationZ - * @readOnly - */ -p5.prototype.pAccelerationZ = 0; - -/** - * _updatePAccelerations updates the pAcceleration values - * - * @private - */ -p5.prototype._updatePAccelerations = function () { - this._setProperty('pAccelerationX', this.accelerationX); - this._setProperty('pAccelerationY', this.accelerationY); - this._setProperty('pAccelerationZ', this.accelerationZ); -}; - -/** - * The system variable rotationX always contains the rotation of the - * device along the x axis. If the sketch - * angleMode() is set to DEGREES, the value will be -180 to 180. If - * it is set to RADIANS, the value will be -PI to PI. - * - * Note: The order the rotations are called is important, ie. if used - * together, it must be called in the order Z-X-Y or there might be - * unexpected behaviour. - * - * @property {Number} rotationX - * @readOnly - * @example - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * } - * - * function draw() { - * background(200); - * //rotateZ(radians(rotationZ)); - * rotateX(radians(rotationX)); - * //rotateY(radians(rotationY)); - * box(200, 200, 200); - * describe(`red horizontal line right, green vertical line bottom. - * black background.`); - * } - * - *
    - */ -p5.prototype.rotationX = 0; - -/** - * The system variable rotationY always contains the rotation of the - * device along the y axis. If the sketch - * angleMode() is set to DEGREES, the value will be -90 to 90. If - * it is set to RADIANS, the value will be -PI/2 to PI/2. - * - * Note: The order the rotations are called is important, ie. if used - * together, it must be called in the order Z-X-Y or there might be - * unexpected behaviour. - * - * @property {Number} rotationY - * @readOnly - * @example - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * } - * - * function draw() { - * background(200); - * //rotateZ(radians(rotationZ)); - * //rotateX(radians(rotationX)); - * rotateY(radians(rotationY)); - * box(200, 200, 200); - * describe(`red horizontal line right, green vertical line bottom. - * black background.`); - * } - * - *
    - */ -p5.prototype.rotationY = 0; - -/** - * The system variable rotationZ always contains the rotation of the - * device along the z axis. If the sketch - * angleMode() is set to DEGREES, the value will be 0 to 360. If - * it is set to RADIANS, the value will be 0 to 2*PI. - * - * Unlike rotationX and rotationY, this variable is available for devices - * with a built-in compass only. - * - * Note: The order the rotations are called is important, ie. if used - * together, it must be called in the order Z-X-Y or there might be - * unexpected behaviour. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * } - * - * function draw() { - * background(200); - * rotateZ(radians(rotationZ)); - * //rotateX(radians(rotationX)); - * //rotateY(radians(rotationY)); - * box(200, 200, 200); - * describe(`red horizontal line right, green vertical line bottom. - * black background.`); - * } - * - *
    - * - * @property {Number} rotationZ - * @readOnly - */ -p5.prototype.rotationZ = 0; - -/** - * The system variable pRotationX always contains the rotation of the - * device along the x axis in the frame previous to the current frame. - * If the sketch angleMode() is set to DEGREES, - * the value will be -180 to 180. If it is set to RADIANS, the value will - * be -PI to PI. - * - * pRotationX can also be used with rotationX to determine the rotate - * direction of the device along the X-axis. - * @example - *
    - * - * // A simple if statement looking at whether - * // rotationX - pRotationX < 0 is true or not will be - * // sufficient for determining the rotate direction - * // in most cases. - * - * // Some extra logic is needed to account for cases where - * // the angles wrap around. - * let rotateDirection = 'clockwise'; - * - * // Simple range conversion to make things simpler. - * // This is not absolutely necessary but the logic - * // will be different in that case. - * - * let rX = rotationX + 180; - * let pRX = pRotationX + 180; - * - * if ((rX - pRX > 0 && rX - pRX < 270) || rX - pRX < -270) { - * rotateDirection = 'clockwise'; - * } else if (rX - pRX < 0 || rX - pRX > 270) { - * rotateDirection = 'counter-clockwise'; - * } - * - * print(rotateDirection); - * describe('no image to display.'); - * - *
    - * - * @property {Number} pRotationX - * @readOnly - */ -p5.prototype.pRotationX = 0; - -/** - * The system variable pRotationY always contains the rotation of the - * device along the y axis in the frame previous to the current frame. - * If the sketch angleMode() is set to DEGREES, - * the value will be -90 to 90. If it is set to RADIANS, the value will - * be -PI/2 to PI/2. - * - * pRotationY can also be used with rotationY to determine the rotate - * direction of the device along the Y-axis. - * @example - *
    - * - * // A simple if statement looking at whether - * // rotationY - pRotationY < 0 is true or not will be - * // sufficient for determining the rotate direction - * // in most cases. - * - * // Some extra logic is needed to account for cases where - * // the angles wrap around. - * let rotateDirection = 'clockwise'; - * - * // Simple range conversion to make things simpler. - * // This is not absolutely necessary but the logic - * // will be different in that case. - * - * let rY = rotationY + 180; - * let pRY = pRotationY + 180; - * - * if ((rY - pRY > 0 && rY - pRY < 270) || rY - pRY < -270) { - * rotateDirection = 'clockwise'; - * } else if (rY - pRY < 0 || rY - pRY > 270) { - * rotateDirection = 'counter-clockwise'; - * } - * print(rotateDirection); - * describe('no image to display.'); - * - *
    - * - * @property {Number} pRotationY - * @readOnly - */ -p5.prototype.pRotationY = 0; - -/** - * The system variable pRotationZ always contains the rotation of the - * device along the z axis in the frame previous to the current frame. - * If the sketch angleMode() is set to DEGREES, - * the value will be 0 to 360. If it is set to RADIANS, the value will - * be 0 to 2*PI. - * - * pRotationZ can also be used with rotationZ to determine the rotate - * direction of the device along the Z-axis. - * @example - *
    - * - * // A simple if statement looking at whether - * // rotationZ - pRotationZ < 0 is true or not will be - * // sufficient for determining the rotate direction - * // in most cases. - * - * // Some extra logic is needed to account for cases where - * // the angles wrap around. - * let rotateDirection = 'clockwise'; - * - * if ( - * (rotationZ - pRotationZ > 0 && rotationZ - pRotationZ < 270) || - * rotationZ - pRotationZ < -270 - * ) { - * rotateDirection = 'clockwise'; - * } else if (rotationZ - pRotationZ < 0 || rotationZ - pRotationZ > 270) { - * rotateDirection = 'counter-clockwise'; - * } - * print(rotateDirection); - * describe('no image to display.'); - * - *
    - * - * @property {Number} pRotationZ - * @readOnly - */ -p5.prototype.pRotationZ = 0; - -let startAngleX = 0; -let startAngleY = 0; -let startAngleZ = 0; - -let rotateDirectionX = 'clockwise'; -let rotateDirectionY = 'clockwise'; -let rotateDirectionZ = 'clockwise'; - -p5.prototype.pRotateDirectionX = undefined; -p5.prototype.pRotateDirectionY = undefined; -p5.prototype.pRotateDirectionZ = undefined; - -p5.prototype._updatePRotations = function () { - this._setProperty('pRotationX', this.rotationX); - this._setProperty('pRotationY', this.rotationY); - this._setProperty('pRotationZ', this.rotationZ); -}; - -/** - * When a device is rotated, the axis that triggers the deviceTurned() - * method is stored in the turnAxis variable. The turnAxis variable is only defined within - * the scope of deviceTurned(). - * @property {String} turnAxis - * @readOnly - * @example - *
    - * - * // Run this example on a mobile device - * // Rotate the device by 90 degrees in the - * // X-axis to change the value. - * - * let value = 0; - * function draw() { - * fill(value); - * rect(25, 25, 50, 50); - * describe(`50-by-50 black rect in center of canvas. - * turns white on mobile when device turns`); - * describe(`50-by-50 black rect in center of canvas. - * turns white on mobile when x-axis turns`); - * } - * function deviceTurned() { - * if (turnAxis === 'X') { - * if (value === 0) { - * value = 255; - * } else if (value === 255) { - * value = 0; - * } - * } - * } - * - *
    - */ -p5.prototype.turnAxis = undefined; - -let move_threshold = 0.5; -let shake_threshold = 30; - -/** - * The setMoveThreshold() function is used to set the movement threshold for - * the deviceMoved() function. The default threshold is set to 0.5. - * - * @method setMoveThreshold - * @param {Number} value The threshold value - * @example - *
    - * - * // Run this example on a mobile device - * // You will need to move the device incrementally further - * // the closer the square's color gets to white in order to change the value. - * - * let value = 0; - * let threshold = 0.5; - * function setup() { - * setMoveThreshold(threshold); - * } - * function draw() { - * fill(value); - * rect(25, 25, 50, 50); - * describe(`50-by-50 black rect in center of canvas. - * turns white on mobile when device moves`); - * } - * function deviceMoved() { - * value = value + 5; - * threshold = threshold + 0.1; - * if (value > 255) { - * value = 0; - * threshold = 30; - * } - * setMoveThreshold(threshold); - * } - * - *
    - */ - -p5.prototype.setMoveThreshold = function (val) { - p5._validateParameters('setMoveThreshold', arguments); - move_threshold = val; -}; - -/** - * The setShakeThreshold() function is used to set the movement threshold for - * the deviceShaken() function. The default threshold is set to 30. - * - * @method setShakeThreshold - * @param {Number} value The threshold value - * @example - *
    - * - * // Run this example on a mobile device - * // You will need to shake the device more firmly - * // the closer the box's fill gets to white in order to change the value. - * - * let value = 0; - * let threshold = 30; - * function setup() { - * setShakeThreshold(threshold); - * } - * function draw() { - * fill(value); - * rect(25, 25, 50, 50); - * describe(`50-by-50 black rect in center of canvas. - * turns white on mobile when device is being shaked`); - * } - * function deviceMoved() { - * value = value + 5; - * threshold = threshold + 5; - * if (value > 255) { - * value = 0; - * threshold = 30; - * } - * setShakeThreshold(threshold); - * } - * - *
    - */ - -p5.prototype.setShakeThreshold = function (val) { - p5._validateParameters('setShakeThreshold', arguments); - shake_threshold = val; -}; - -/** - * The deviceMoved() function is called when the device is moved by more than - * the threshold value along X, Y or Z axis. The default threshold is set to 0.5. - * The threshold value can be changed using setMoveThreshold(). - * - * @method deviceMoved - * @example - *
    - * - * // Run this example on a mobile device - * // Move the device around - * // to change the value. - * - * let value = 0; - * function draw() { - * fill(value); - * rect(25, 25, 50, 50); - * describe(`50-by-50 black rect in center of canvas. - * turns white on mobile when device moves`); - * } - * function deviceMoved() { - * value = value + 5; - * if (value > 255) { - * value = 0; - * } - * } - * - *
    - */ - -/** - * The deviceTurned() function is called when the device rotates by - * more than 90 degrees continuously. - * - * The axis that triggers the deviceTurned() method is stored in the turnAxis - * variable. The deviceTurned() method can be locked to trigger on any axis: - * X, Y or Z by comparing the turnAxis variable to 'X', 'Y' or 'Z'. - * - * @method deviceTurned - * @example - *
    - * - * // Run this example on a mobile device - * // Rotate the device by 90 degrees - * // to change the value. - * - * let value = 0; - * function draw() { - * fill(value); - * rect(25, 25, 50, 50); - * describe(`50-by-50 black rect in center of canvas. - * turns white on mobile when device turns`); - * } - * function deviceTurned() { - * if (value === 0) { - * value = 255; - * } else if (value === 255) { - * value = 0; - * } - * } - * - *
    - *
    - * - * // Run this example on a mobile device - * // Rotate the device by 90 degrees in the - * // X-axis to change the value. - * - * let value = 0; - * function draw() { - * fill(value); - * rect(25, 25, 50, 50); - * describe(`50-by-50 black rect in center of canvas. - * turns white on mobile when x-axis turns`); - * } - * function deviceTurned() { - * if (turnAxis === 'X') { - * if (value === 0) { - * value = 255; - * } else if (value === 255) { - * value = 0; - * } - * } - * } - * - *
    - */ - -/** - * The deviceShaken() function is called when the device total acceleration - * changes of accelerationX and accelerationY values is more than - * the threshold value. The default threshold is set to 30. - * The threshold value can be changed using setShakeThreshold(). - * - * @method deviceShaken - * @example - *
    - * - * // Run this example on a mobile device - * // Shake the device to change the value. - * - * let value = 0; - * function draw() { - * fill(value); - * rect(25, 25, 50, 50); - * describe(`50-by-50 black rect in center of canvas. - * turns white on mobile when device shakes`); - * } - * function deviceShaken() { - * value = value + 5; - * if (value > 255) { - * value = 0; - * } - * } - * - *
    - */ - -p5.prototype._ondeviceorientation = function (e) { - this._updatePRotations(); - - // Convert from degrees into current angle mode - this._setProperty('rotationX', this._fromDegrees(e.beta)); - this._setProperty('rotationY', this._fromDegrees(e.gamma)); - this._setProperty('rotationZ', this._fromDegrees(e.alpha)); - this._handleMotion(); -}; -p5.prototype._ondevicemotion = function (e) { - this._updatePAccelerations(); - this._setProperty('accelerationX', e.acceleration.x * 2); - this._setProperty('accelerationY', e.acceleration.y * 2); - this._setProperty('accelerationZ', e.acceleration.z * 2); - this._handleMotion(); -}; -p5.prototype._handleMotion = function () { - if (window.orientation === 90 || window.orientation === -90) { - this._setProperty('deviceOrientation', 'landscape'); - } else if (window.orientation === 0) { - this._setProperty('deviceOrientation', 'portrait'); - } else if (window.orientation === undefined) { - this._setProperty('deviceOrientation', 'undefined'); - } - const context = this._isGlobal ? window : this; - if (typeof context.deviceMoved === 'function') { - if ( - Math.abs(this.accelerationX - this.pAccelerationX) > move_threshold || - Math.abs(this.accelerationY - this.pAccelerationY) > move_threshold || - Math.abs(this.accelerationZ - this.pAccelerationZ) > move_threshold - ) { - context.deviceMoved(); - } - } - - if (typeof context.deviceTurned === 'function') { - // The angles given by rotationX etc is from range [-180 to 180]. - // The following will convert them to [0 to 360] for ease of calculation - // of cases when the angles wrapped around. - // _startAngleX will be converted back at the end and updated. - - // Rotations are converted to degrees and all calculations are done in degrees - const wRX = this._toDegrees(this.rotationX) + 180; - const wPRX = this._toDegrees(this.pRotationX) + 180; - let wSAX = startAngleX + 180; - if ((wRX - wPRX > 0 && wRX - wPRX < 270) || wRX - wPRX < -270) { - rotateDirectionX = 'clockwise'; - } else if (wRX - wPRX < 0 || wRX - wPRX > 270) { - rotateDirectionX = 'counter-clockwise'; +function acceleration(p5, fn){ + /** + * The system variable deviceOrientation always contains the orientation of + * the device. The value of this variable will either be set 'landscape' + * or 'portrait'. If no data is available it will be set to 'undefined'. + * either LANDSCAPE or PORTRAIT. + * + * @property {(LANDSCAPE|PORTRAIT)} deviceOrientation + * @readOnly + */ + fn.deviceOrientation = + window.innerWidth / window.innerHeight > 1.0 ? 'landscape' : 'portrait'; + + /** + * The system variable accelerationX always contains the acceleration of the + * device along the x axis. Value is represented as meters per second squared. + * + * @property {Number} accelerationX + * @readOnly + * @example + *
    + * + * // Move a touchscreen device to register + * // acceleration changes. + * function draw() { + * background(220, 50); + * fill('magenta'); + * ellipse(width / 2, height / 2, accelerationX); + * describe('Magnitude of device acceleration is displayed as ellipse size.'); + * } + * + *
    + */ + fn.accelerationX = 0; + + /** + * The system variable accelerationY always contains the acceleration of the + * device along the y axis. Value is represented as meters per second squared. + * + * @property {Number} accelerationY + * @readOnly + * @example + *
    + * + * // Move a touchscreen device to register + * // acceleration changes. + * function draw() { + * background(220, 50); + * fill('magenta'); + * ellipse(width / 2, height / 2, accelerationY); + * describe('Magnitude of device acceleration is displayed as ellipse size'); + * } + * + *
    + */ + fn.accelerationY = 0; + + /** + * The system variable accelerationZ always contains the acceleration of the + * device along the z axis. Value is represented as meters per second squared. + * + * @property {Number} accelerationZ + * @readOnly + * + * @example + *
    + * + * // Move a touchscreen device to register + * // acceleration changes. + * function draw() { + * background(220, 50); + * fill('magenta'); + * ellipse(width / 2, height / 2, accelerationZ); + * describe('Magnitude of device acceleration is displayed as ellipse size'); + * } + * + *
    + */ + fn.accelerationZ = 0; + + /** + * The system variable pAccelerationX always contains the acceleration of the + * device along the x axis in the frame previous to the current frame. Value + * is represented as meters per second squared. + * + * @property {Number} pAccelerationX + * @readOnly + */ + fn.pAccelerationX = 0; + + /** + * The system variable pAccelerationY always contains the acceleration of the + * device along the y axis in the frame previous to the current frame. Value + * is represented as meters per second squared. + * + * @property {Number} pAccelerationY + * @readOnly + */ + fn.pAccelerationY = 0; + + /** + * The system variable pAccelerationZ always contains the acceleration of the + * device along the z axis in the frame previous to the current frame. Value + * is represented as meters per second squared. + * + * @property {Number} pAccelerationZ + * @readOnly + */ + fn.pAccelerationZ = 0; + + /** + * _updatePAccelerations updates the pAcceleration values + * + * @private + */ + fn._updatePAccelerations = function () { + this._setProperty('pAccelerationX', this.accelerationX); + this._setProperty('pAccelerationY', this.accelerationY); + this._setProperty('pAccelerationZ', this.accelerationZ); + }; + + /** + * The system variable rotationX always contains the rotation of the + * device along the x axis. If the sketch + * angleMode() is set to DEGREES, the value will be -180 to 180. If + * it is set to RADIANS, the value will be -PI to PI. + * + * Note: The order the rotations are called is important, ie. if used + * together, it must be called in the order Z-X-Y or there might be + * unexpected behaviour. + * + * @property {Number} rotationX + * @readOnly + * @example + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * } + * + * function draw() { + * background(200); + * //rotateZ(radians(rotationZ)); + * rotateX(radians(rotationX)); + * //rotateY(radians(rotationY)); + * box(200, 200, 200); + * describe(`red horizontal line right, green vertical line bottom. + * black background.`); + * } + * + *
    + */ + fn.rotationX = 0; + + /** + * The system variable rotationY always contains the rotation of the + * device along the y axis. If the sketch + * angleMode() is set to DEGREES, the value will be -90 to 90. If + * it is set to RADIANS, the value will be -PI/2 to PI/2. + * + * Note: The order the rotations are called is important, ie. if used + * together, it must be called in the order Z-X-Y or there might be + * unexpected behaviour. + * + * @property {Number} rotationY + * @readOnly + * @example + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * } + * + * function draw() { + * background(200); + * //rotateZ(radians(rotationZ)); + * //rotateX(radians(rotationX)); + * rotateY(radians(rotationY)); + * box(200, 200, 200); + * describe(`red horizontal line right, green vertical line bottom. + * black background.`); + * } + * + *
    + */ + fn.rotationY = 0; + + /** + * The system variable rotationZ always contains the rotation of the + * device along the z axis. If the sketch + * angleMode() is set to DEGREES, the value will be 0 to 360. If + * it is set to RADIANS, the value will be 0 to 2*PI. + * + * Unlike rotationX and rotationY, this variable is available for devices + * with a built-in compass only. + * + * Note: The order the rotations are called is important, ie. if used + * together, it must be called in the order Z-X-Y or there might be + * unexpected behaviour. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * } + * + * function draw() { + * background(200); + * rotateZ(radians(rotationZ)); + * //rotateX(radians(rotationX)); + * //rotateY(radians(rotationY)); + * box(200, 200, 200); + * describe(`red horizontal line right, green vertical line bottom. + * black background.`); + * } + * + *
    + * + * @property {Number} rotationZ + * @readOnly + */ + fn.rotationZ = 0; + + /** + * The system variable pRotationX always contains the rotation of the + * device along the x axis in the frame previous to the current frame. + * If the sketch angleMode() is set to DEGREES, + * the value will be -180 to 180. If it is set to RADIANS, the value will + * be -PI to PI. + * + * pRotationX can also be used with rotationX to determine the rotate + * direction of the device along the X-axis. + * @example + *
    + * + * // A simple if statement looking at whether + * // rotationX - pRotationX < 0 is true or not will be + * // sufficient for determining the rotate direction + * // in most cases. + * + * // Some extra logic is needed to account for cases where + * // the angles wrap around. + * let rotateDirection = 'clockwise'; + * + * // Simple range conversion to make things simpler. + * // This is not absolutely necessary but the logic + * // will be different in that case. + * + * let rX = rotationX + 180; + * let pRX = pRotationX + 180; + * + * if ((rX - pRX > 0 && rX - pRX < 270) || rX - pRX < -270) { + * rotateDirection = 'clockwise'; + * } else if (rX - pRX < 0 || rX - pRX > 270) { + * rotateDirection = 'counter-clockwise'; + * } + * + * print(rotateDirection); + * describe('no image to display.'); + * + *
    + * + * @property {Number} pRotationX + * @readOnly + */ + fn.pRotationX = 0; + + /** + * The system variable pRotationY always contains the rotation of the + * device along the y axis in the frame previous to the current frame. + * If the sketch angleMode() is set to DEGREES, + * the value will be -90 to 90. If it is set to RADIANS, the value will + * be -PI/2 to PI/2. + * + * pRotationY can also be used with rotationY to determine the rotate + * direction of the device along the Y-axis. + * @example + *
    + * + * // A simple if statement looking at whether + * // rotationY - pRotationY < 0 is true or not will be + * // sufficient for determining the rotate direction + * // in most cases. + * + * // Some extra logic is needed to account for cases where + * // the angles wrap around. + * let rotateDirection = 'clockwise'; + * + * // Simple range conversion to make things simpler. + * // This is not absolutely necessary but the logic + * // will be different in that case. + * + * let rY = rotationY + 180; + * let pRY = pRotationY + 180; + * + * if ((rY - pRY > 0 && rY - pRY < 270) || rY - pRY < -270) { + * rotateDirection = 'clockwise'; + * } else if (rY - pRY < 0 || rY - pRY > 270) { + * rotateDirection = 'counter-clockwise'; + * } + * print(rotateDirection); + * describe('no image to display.'); + * + *
    + * + * @property {Number} pRotationY + * @readOnly + */ + fn.pRotationY = 0; + + /** + * The system variable pRotationZ always contains the rotation of the + * device along the z axis in the frame previous to the current frame. + * If the sketch angleMode() is set to DEGREES, + * the value will be 0 to 360. If it is set to RADIANS, the value will + * be 0 to 2*PI. + * + * pRotationZ can also be used with rotationZ to determine the rotate + * direction of the device along the Z-axis. + * @example + *
    + * + * // A simple if statement looking at whether + * // rotationZ - pRotationZ < 0 is true or not will be + * // sufficient for determining the rotate direction + * // in most cases. + * + * // Some extra logic is needed to account for cases where + * // the angles wrap around. + * let rotateDirection = 'clockwise'; + * + * if ( + * (rotationZ - pRotationZ > 0 && rotationZ - pRotationZ < 270) || + * rotationZ - pRotationZ < -270 + * ) { + * rotateDirection = 'clockwise'; + * } else if (rotationZ - pRotationZ < 0 || rotationZ - pRotationZ > 270) { + * rotateDirection = 'counter-clockwise'; + * } + * print(rotateDirection); + * describe('no image to display.'); + * + *
    + * + * @property {Number} pRotationZ + * @readOnly + */ + fn.pRotationZ = 0; + + let startAngleX = 0; + let startAngleY = 0; + let startAngleZ = 0; + + let rotateDirectionX = 'clockwise'; + let rotateDirectionY = 'clockwise'; + let rotateDirectionZ = 'clockwise'; + + fn.pRotateDirectionX = undefined; + fn.pRotateDirectionY = undefined; + fn.pRotateDirectionZ = undefined; + + fn._updatePRotations = function () { + this._setProperty('pRotationX', this.rotationX); + this._setProperty('pRotationY', this.rotationY); + this._setProperty('pRotationZ', this.rotationZ); + }; + + /** + * When a device is rotated, the axis that triggers the deviceTurned() + * method is stored in the turnAxis variable. The turnAxis variable is only defined within + * the scope of deviceTurned(). + * @property {String} turnAxis + * @readOnly + * @example + *
    + * + * // Run this example on a mobile device + * // Rotate the device by 90 degrees in the + * // X-axis to change the value. + * + * let value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * describe(`50-by-50 black rect in center of canvas. + * turns white on mobile when device turns`); + * describe(`50-by-50 black rect in center of canvas. + * turns white on mobile when x-axis turns`); + * } + * function deviceTurned() { + * if (turnAxis === 'X') { + * if (value === 0) { + * value = 255; + * } else if (value === 255) { + * value = 0; + * } + * } + * } + * + *
    + */ + fn.turnAxis = undefined; + + let move_threshold = 0.5; + let shake_threshold = 30; + + /** + * The setMoveThreshold() function is used to set the movement threshold for + * the deviceMoved() function. The default threshold is set to 0.5. + * + * @method setMoveThreshold + * @param {Number} value The threshold value + * @example + *
    + * + * // Run this example on a mobile device + * // You will need to move the device incrementally further + * // the closer the square's color gets to white in order to change the value. + * + * let value = 0; + * let threshold = 0.5; + * function setup() { + * setMoveThreshold(threshold); + * } + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * describe(`50-by-50 black rect in center of canvas. + * turns white on mobile when device moves`); + * } + * function deviceMoved() { + * value = value + 5; + * threshold = threshold + 0.1; + * if (value > 255) { + * value = 0; + * threshold = 30; + * } + * setMoveThreshold(threshold); + * } + * + *
    + */ + + fn.setMoveThreshold = function (val) { + p5._validateParameters('setMoveThreshold', arguments); + move_threshold = val; + }; + + /** + * The setShakeThreshold() function is used to set the movement threshold for + * the deviceShaken() function. The default threshold is set to 30. + * + * @method setShakeThreshold + * @param {Number} value The threshold value + * @example + *
    + * + * // Run this example on a mobile device + * // You will need to shake the device more firmly + * // the closer the box's fill gets to white in order to change the value. + * + * let value = 0; + * let threshold = 30; + * function setup() { + * setShakeThreshold(threshold); + * } + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * describe(`50-by-50 black rect in center of canvas. + * turns white on mobile when device is being shaked`); + * } + * function deviceMoved() { + * value = value + 5; + * threshold = threshold + 5; + * if (value > 255) { + * value = 0; + * threshold = 30; + * } + * setShakeThreshold(threshold); + * } + * + *
    + */ + + fn.setShakeThreshold = function (val) { + p5._validateParameters('setShakeThreshold', arguments); + shake_threshold = val; + }; + + /** + * The deviceMoved() function is called when the device is moved by more than + * the threshold value along X, Y or Z axis. The default threshold is set to 0.5. + * The threshold value can be changed using setMoveThreshold(). + * + * @method deviceMoved + * @example + *
    + * + * // Run this example on a mobile device + * // Move the device around + * // to change the value. + * + * let value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * describe(`50-by-50 black rect in center of canvas. + * turns white on mobile when device moves`); + * } + * function deviceMoved() { + * value = value + 5; + * if (value > 255) { + * value = 0; + * } + * } + * + *
    + */ + + /** + * The deviceTurned() function is called when the device rotates by + * more than 90 degrees continuously. + * + * The axis that triggers the deviceTurned() method is stored in the turnAxis + * variable. The deviceTurned() method can be locked to trigger on any axis: + * X, Y or Z by comparing the turnAxis variable to 'X', 'Y' or 'Z'. + * + * @method deviceTurned + * @example + *
    + * + * // Run this example on a mobile device + * // Rotate the device by 90 degrees + * // to change the value. + * + * let value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * describe(`50-by-50 black rect in center of canvas. + * turns white on mobile when device turns`); + * } + * function deviceTurned() { + * if (value === 0) { + * value = 255; + * } else if (value === 255) { + * value = 0; + * } + * } + * + *
    + *
    + * + * // Run this example on a mobile device + * // Rotate the device by 90 degrees in the + * // X-axis to change the value. + * + * let value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * describe(`50-by-50 black rect in center of canvas. + * turns white on mobile when x-axis turns`); + * } + * function deviceTurned() { + * if (turnAxis === 'X') { + * if (value === 0) { + * value = 255; + * } else if (value === 255) { + * value = 0; + * } + * } + * } + * + *
    + */ + + /** + * The deviceShaken() function is called when the device total acceleration + * changes of accelerationX and accelerationY values is more than + * the threshold value. The default threshold is set to 30. + * The threshold value can be changed using setShakeThreshold(). + * + * @method deviceShaken + * @example + *
    + * + * // Run this example on a mobile device + * // Shake the device to change the value. + * + * let value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * describe(`50-by-50 black rect in center of canvas. + * turns white on mobile when device shakes`); + * } + * function deviceShaken() { + * value = value + 5; + * if (value > 255) { + * value = 0; + * } + * } + * + *
    + */ + + fn._ondeviceorientation = function (e) { + this._updatePRotations(); + + // Convert from degrees into current angle mode + this._setProperty('rotationX', this._fromDegrees(e.beta)); + this._setProperty('rotationY', this._fromDegrees(e.gamma)); + this._setProperty('rotationZ', this._fromDegrees(e.alpha)); + this._handleMotion(); + }; + fn._ondevicemotion = function (e) { + this._updatePAccelerations(); + this._setProperty('accelerationX', e.acceleration.x * 2); + this._setProperty('accelerationY', e.acceleration.y * 2); + this._setProperty('accelerationZ', e.acceleration.z * 2); + this._handleMotion(); + }; + fn._handleMotion = function () { + if (window.orientation === 90 || window.orientation === -90) { + this._setProperty('deviceOrientation', 'landscape'); + } else if (window.orientation === 0) { + this._setProperty('deviceOrientation', 'portrait'); + } else if (window.orientation === undefined) { + this._setProperty('deviceOrientation', 'undefined'); } - if (rotateDirectionX !== this.pRotateDirectionX) { - wSAX = wRX; + const context = this._isGlobal ? window : this; + if (typeof context.deviceMoved === 'function') { + if ( + Math.abs(this.accelerationX - this.pAccelerationX) > move_threshold || + Math.abs(this.accelerationY - this.pAccelerationY) > move_threshold || + Math.abs(this.accelerationZ - this.pAccelerationZ) > move_threshold + ) { + context.deviceMoved(); + } } - if (Math.abs(wRX - wSAX) > 90 && Math.abs(wRX - wSAX) < 270) { - wSAX = wRX; - this._setProperty('turnAxis', 'X'); - context.deviceTurned(); - } - this.pRotateDirectionX = rotateDirectionX; - startAngleX = wSAX - 180; - - // Y-axis is identical to X-axis except for changing some names. - const wRY = this._toDegrees(this.rotationY) + 180; - const wPRY = this._toDegrees(this.pRotationY) + 180; - let wSAY = startAngleY + 180; - if ((wRY - wPRY > 0 && wRY - wPRY < 270) || wRY - wPRY < -270) { - rotateDirectionY = 'clockwise'; - } else if (wRY - wPRY < 0 || wRY - this.pRotationY > 270) { - rotateDirectionY = 'counter-clockwise'; - } - if (rotateDirectionY !== this.pRotateDirectionY) { - wSAY = wRY; - } - if (Math.abs(wRY - wSAY) > 90 && Math.abs(wRY - wSAY) < 270) { - wSAY = wRY; - this._setProperty('turnAxis', 'Y'); - context.deviceTurned(); - } - this.pRotateDirectionY = rotateDirectionY; - startAngleY = wSAY - 180; - - // Z-axis is already in the range 0 to 360 - // so no conversion is needed. - const rotZ = this._toDegrees(this.rotationZ); - const pRotZ = this._toDegrees(this.pRotationZ); - if ( - (rotZ - pRotZ > 0 && rotZ - pRotZ < 270) || - rotZ - pRotZ < -270 - ) { - rotateDirectionZ = 'clockwise'; - } else if ( - rotZ - pRotZ < 0 || - rotZ - pRotZ > 270 - ) { - rotateDirectionZ = 'counter-clockwise'; - } - if (rotateDirectionZ !== this.pRotateDirectionZ) { - startAngleZ = rotZ; - } - if ( - Math.abs(rotZ - startAngleZ) > 90 && - Math.abs(rotZ - startAngleZ) < 270 - ) { - startAngleZ = rotZ; - this._setProperty('turnAxis', 'Z'); - context.deviceTurned(); - } - this.pRotateDirectionZ = rotateDirectionZ; - this._setProperty('turnAxis', undefined); - } - if (typeof context.deviceShaken === 'function') { - let accelerationChangeX; - let accelerationChangeY; - // Add accelerationChangeZ if acceleration change on Z is needed - if (this.pAccelerationX !== null) { - accelerationChangeX = Math.abs(this.accelerationX - this.pAccelerationX); - accelerationChangeY = Math.abs(this.accelerationY - this.pAccelerationY); + + if (typeof context.deviceTurned === 'function') { + // The angles given by rotationX etc is from range [-180 to 180]. + // The following will convert them to [0 to 360] for ease of calculation + // of cases when the angles wrapped around. + // _startAngleX will be converted back at the end and updated. + + // Rotations are converted to degrees and all calculations are done in degrees + const wRX = this._toDegrees(this.rotationX) + 180; + const wPRX = this._toDegrees(this.pRotationX) + 180; + let wSAX = startAngleX + 180; + if ((wRX - wPRX > 0 && wRX - wPRX < 270) || wRX - wPRX < -270) { + rotateDirectionX = 'clockwise'; + } else if (wRX - wPRX < 0 || wRX - wPRX > 270) { + rotateDirectionX = 'counter-clockwise'; + } + if (rotateDirectionX !== this.pRotateDirectionX) { + wSAX = wRX; + } + if (Math.abs(wRX - wSAX) > 90 && Math.abs(wRX - wSAX) < 270) { + wSAX = wRX; + this._setProperty('turnAxis', 'X'); + context.deviceTurned(); + } + this.pRotateDirectionX = rotateDirectionX; + startAngleX = wSAX - 180; + + // Y-axis is identical to X-axis except for changing some names. + const wRY = this._toDegrees(this.rotationY) + 180; + const wPRY = this._toDegrees(this.pRotationY) + 180; + let wSAY = startAngleY + 180; + if ((wRY - wPRY > 0 && wRY - wPRY < 270) || wRY - wPRY < -270) { + rotateDirectionY = 'clockwise'; + } else if (wRY - wPRY < 0 || wRY - this.pRotationY > 270) { + rotateDirectionY = 'counter-clockwise'; + } + if (rotateDirectionY !== this.pRotateDirectionY) { + wSAY = wRY; + } + if (Math.abs(wRY - wSAY) > 90 && Math.abs(wRY - wSAY) < 270) { + wSAY = wRY; + this._setProperty('turnAxis', 'Y'); + context.deviceTurned(); + } + this.pRotateDirectionY = rotateDirectionY; + startAngleY = wSAY - 180; + + // Z-axis is already in the range 0 to 360 + // so no conversion is needed. + const rotZ = this._toDegrees(this.rotationZ); + const pRotZ = this._toDegrees(this.pRotationZ); + if ( + (rotZ - pRotZ > 0 && rotZ - pRotZ < 270) || + rotZ - pRotZ < -270 + ) { + rotateDirectionZ = 'clockwise'; + } else if ( + rotZ - pRotZ < 0 || + rotZ - pRotZ > 270 + ) { + rotateDirectionZ = 'counter-clockwise'; + } + if (rotateDirectionZ !== this.pRotateDirectionZ) { + startAngleZ = rotZ; + } + if ( + Math.abs(rotZ - startAngleZ) > 90 && + Math.abs(rotZ - startAngleZ) < 270 + ) { + startAngleZ = rotZ; + this._setProperty('turnAxis', 'Z'); + context.deviceTurned(); + } + this.pRotateDirectionZ = rotateDirectionZ; + this._setProperty('turnAxis', undefined); } - if (accelerationChangeX + accelerationChangeY > shake_threshold) { - context.deviceShaken(); + if (typeof context.deviceShaken === 'function') { + let accelerationChangeX; + let accelerationChangeY; + // Add accelerationChangeZ if acceleration change on Z is needed + if (this.pAccelerationX !== null) { + accelerationChangeX = Math.abs(this.accelerationX - this.pAccelerationX); + accelerationChangeY = Math.abs(this.accelerationY - this.pAccelerationY); + } + if (accelerationChangeX + accelerationChangeY > shake_threshold) { + context.deviceShaken(); + } } - } -}; + }; +} + +export default acceleration; -export default p5; +if(typeof p5 !== 'undefined'){ + acceleration(p5, p5.prototype); +} diff --git a/src/events/index.js b/src/events/index.js new file mode 100644 index 0000000000..773424d42b --- /dev/null +++ b/src/events/index.js @@ -0,0 +1,11 @@ +import acceleration from './acceleration.js'; +import keyboard from './keyboard.js'; +import mouse from './mouse.js'; +import touch from './touch.js'; + +export default function(p5){ + p5.registerAddon(acceleration); + p5.registerAddon(keyboard); + p5.registerAddon(mouse); + p5.registerAddon(touch); +} diff --git a/src/events/keyboard.js b/src/events/keyboard.js index 9e336022a9..cc6dd45339 100644 --- a/src/events/keyboard.js +++ b/src/events/keyboard.js @@ -5,925 +5,929 @@ * @requires core */ -import p5 from '../core/main'; +function keyboard(p5, fn){ + /** + * A `Boolean` system variable that's `true` if any key is currently pressed + * and `false` if not. + * + * @property {Boolean} keyIsPressed + * @readOnly + * + * @example + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a white square at its center. The white square turns black when the user presses a key.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * if (keyIsPressed === true) { + * fill(0); + * } else { + * fill(255); + * } + * + * // Draw the square. + * square(25, 25, 50); + * } + * + *
    + * + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a white square at its center. The white square turns black when the user presses a key.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * if (keyIsPressed) { + * fill(0); + * } else { + * fill(255); + * } + * + * // Draw the square. + * square(25, 25, 50); + * } + * + *
    + * + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with the word "false" at its center. The word switches to "true" when the user presses a key.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display the value of keyIsPressed. + * text(keyIsPressed, 50, 50); + * } + * + *
    + */ + fn.isKeyPressed = false; + fn.keyIsPressed = false; // khan -/** - * A `Boolean` system variable that's `true` if any key is currently pressed - * and `false` if not. - * - * @property {Boolean} keyIsPressed - * @readOnly - * - * @example - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a white square at its center. The white square turns black when the user presses a key.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * if (keyIsPressed === true) { - * fill(0); - * } else { - * fill(255); - * } - * - * // Draw the square. - * square(25, 25, 50); - * } - * - *
    - * - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a white square at its center. The white square turns black when the user presses a key.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * if (keyIsPressed) { - * fill(0); - * } else { - * fill(255); - * } - * - * // Draw the square. - * square(25, 25, 50); - * } - * - *
    - * - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with the word "false" at its center. The word switches to "true" when the user presses a key.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display the value of keyIsPressed. - * text(keyIsPressed, 50, 50); - * } - * - *
    - */ -p5.prototype.isKeyPressed = false; -p5.prototype.keyIsPressed = false; // khan + /** + * A `String` system variable that contains the value of the last key typed. + * + * The key variable is helpful for checking whether an + * ASCII + * key has been typed. For example, the expression `key === "a"` evaluates to + * `true` if the `a` key was typed and `false` if not. `key` doesn’t update + * for special keys such as `LEFT_ARROW` and `ENTER`. Use keyCode instead for + * special keys. The keyIsDown() function should + * be used to check for multiple different key presses at the same time. + * + * @property {String} key + * @readOnly + * + * @example + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square. The last key pressed is displayed at the center.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display the last key pressed. + * text(key, 50, 50); + * } + * + *
    + * + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * let x = 50; + * let y = 50; + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * describe( + * 'A gray square with a black circle at its center. The circle moves when the user presses the keys "w", "a", "s", or "d". It leaves a trail as it moves.' + * ); + * } + * + * function draw() { + * // Update x and y if a key is pressed. + * if (keyIsPressed === true) { + * if (key === 'w') { + * y -= 1; + * } else if (key === 's') { + * y += 1; + * } else if (key === 'a') { + * x -= 1; + * } else if (key === 'd') { + * x += 1; + * } + * } + * + * // Style the circle. + * fill(0); + * + * // Draw the circle at (x, y). + * circle(x, y, 5); + * } + * + *
    + */ + fn.key = ''; -/** - * A `String` system variable that contains the value of the last key typed. - * - * The key variable is helpful for checking whether an - * ASCII - * key has been typed. For example, the expression `key === "a"` evaluates to - * `true` if the `a` key was typed and `false` if not. `key` doesn’t update - * for special keys such as `LEFT_ARROW` and `ENTER`. Use keyCode instead for - * special keys. The keyIsDown() function should - * be used to check for multiple different key presses at the same time. - * - * @property {String} key - * @readOnly - * - * @example - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square. The last key pressed is displayed at the center.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display the last key pressed. - * text(key, 50, 50); - * } - * - *
    - * - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * let x = 50; - * let y = 50; - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * describe( - * 'A gray square with a black circle at its center. The circle moves when the user presses the keys "w", "a", "s", or "d". It leaves a trail as it moves.' - * ); - * } - * - * function draw() { - * // Update x and y if a key is pressed. - * if (keyIsPressed === true) { - * if (key === 'w') { - * y -= 1; - * } else if (key === 's') { - * y += 1; - * } else if (key === 'a') { - * x -= 1; - * } else if (key === 'd') { - * x += 1; - * } - * } - * - * // Style the circle. - * fill(0); - * - * // Draw the circle at (x, y). - * circle(x, y, 5); - * } - * - *
    - */ -p5.prototype.key = ''; + /** + * A `Number` system variable that contains the code of the last key typed. + * + * All keys have a `keyCode`. For example, the `a` key has the `keyCode` 65. + * The `keyCode` variable is helpful for checking whether a special key has + * been typed. For example, the following conditional checks whether the enter + * key has been typed: + * + * ```js + * if (keyCode === 13) { + * // Code to run if the enter key was pressed. + * } + * ``` + * + * The same code can be written more clearly using the system variable `ENTER` + * which has a value of 13: + * + * ```js + * if (keyCode === ENTER) { + * // Code to run if the enter key was pressed. + * } + * ``` + * + * The system variables `BACKSPACE`, `DELETE`, `ENTER`, `RETURN`, `TAB`, + * `ESCAPE`, `SHIFT`, `CONTROL`, `OPTION`, `ALT`, `UP_ARROW`, `DOWN_ARROW`, + * `LEFT_ARROW`, and `RIGHT_ARROW` are all helpful shorthands the key codes of + * special keys. Key codes can be found on websites such as + * keycode.info. + * + * @property {Integer} keyCode + * @readOnly + * + * @example + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square. The last key pressed and its code are displayed at the center.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display the last key pressed and its code. + * text(`${key} : ${keyCode}`, 50, 50); + * } + * + *
    + * + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * let x = 50; + * let y = 50; + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * describe( + * 'A gray square with a black circle at its center. The circle moves when the user presses an arrow key. It leaves a trail as it moves.' + * ); + * } + * + * function draw() { + * // Update x and y if an arrow key is pressed. + * if (keyIsPressed === true) { + * if (keyCode === UP_ARROW) { + * y -= 1; + * } else if (keyCode === DOWN_ARROW) { + * y += 1; + * } else if (keyCode === LEFT_ARROW) { + * x -= 1; + * } else if (keyCode === RIGHT_ARROW) { + * x += 1; + * } + * } + * + * // Style the circle. + * fill(0); + * + * // Draw the circle at (x, y). + * circle(x, y, 5); + * } + * + *
    + */ + fn.keyCode = 0; -/** - * A `Number` system variable that contains the code of the last key typed. - * - * All keys have a `keyCode`. For example, the `a` key has the `keyCode` 65. - * The `keyCode` variable is helpful for checking whether a special key has - * been typed. For example, the following conditional checks whether the enter - * key has been typed: - * - * ```js - * if (keyCode === 13) { - * // Code to run if the enter key was pressed. - * } - * ``` - * - * The same code can be written more clearly using the system variable `ENTER` - * which has a value of 13: - * - * ```js - * if (keyCode === ENTER) { - * // Code to run if the enter key was pressed. - * } - * ``` - * - * The system variables `BACKSPACE`, `DELETE`, `ENTER`, `RETURN`, `TAB`, - * `ESCAPE`, `SHIFT`, `CONTROL`, `OPTION`, `ALT`, `UP_ARROW`, `DOWN_ARROW`, - * `LEFT_ARROW`, and `RIGHT_ARROW` are all helpful shorthands the key codes of - * special keys. Key codes can be found on websites such as - * keycode.info. - * - * @property {Integer} keyCode - * @readOnly - * - * @example - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square. The last key pressed and its code are displayed at the center.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display the last key pressed and its code. - * text(`${key} : ${keyCode}`, 50, 50); - * } - * - *
    - * - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * let x = 50; - * let y = 50; - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * describe( - * 'A gray square with a black circle at its center. The circle moves when the user presses an arrow key. It leaves a trail as it moves.' - * ); - * } - * - * function draw() { - * // Update x and y if an arrow key is pressed. - * if (keyIsPressed === true) { - * if (keyCode === UP_ARROW) { - * y -= 1; - * } else if (keyCode === DOWN_ARROW) { - * y += 1; - * } else if (keyCode === LEFT_ARROW) { - * x -= 1; - * } else if (keyCode === RIGHT_ARROW) { - * x += 1; - * } - * } - * - * // Style the circle. - * fill(0); - * - * // Draw the circle at (x, y). - * circle(x, y, 5); - * } - * - *
    - */ -p5.prototype.keyCode = 0; - -/** - * A function that's called once when any key is pressed. - * - * Declaring the function `keyPressed()` sets a code block to run once - * automatically when the user presses any key: - * - * ```js - * function keyPressed() { - * // Code to run. - * } - * ``` - * - * The key and keyCode - * variables will be updated with the most recently typed value when - * `keyPressed()` is called by p5.js: - * - * ```js - * function keyPressed() { - * if (key === 'c') { - * // Code to run. - * } - * - * if (keyCode === ENTER) { - * // Code to run. - * } - * } - * ``` - * - * The parameter, `event`, is optional. `keyPressed()` is always passed a - * KeyboardEvent - * object with properties that describe the key press event: - * - * ```js - * function keyPressed(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * Browsers may have default behaviors attached to various key events. For - * example, some browsers may jump to the bottom of a web page when the - * `SPACE` key is pressed. To prevent any default behavior for this event, add - * `return false;` to the end of the function. - * - * @method keyPressed - * @param {KeyboardEvent} [event] optional `KeyboardEvent` callback argument. - * - * @example - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square changes color when the user presses a key.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * // Toggle the background color when the user presses a key. - * function keyPressed() { - * if (value === 0) { - * value = 255; - * } else { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - * - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a white square at its center. The inner square turns black when the user presses the "b" key. It turns white when the user presses the "a" key.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * // Reassign value when the user presses the 'a' or 'b' key. - * function keyPressed() { - * if (key === 'a') { - * value = 255; - * } else if (key === 'b') { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - * - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square turns white when the user presses the left arrow key. It turns black when the user presses the right arrow key.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * // Toggle the background color when the user presses an arrow key. - * function keyPressed() { - * if (keyCode === LEFT_ARROW) { - * value = 255; - * } else if (keyCode === RIGHT_ARROW) { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - */ -p5.prototype._onkeydown = function(e) { - if (this._downKeys[e.which]) { - // prevent multiple firings - return; - } - this._setProperty('isKeyPressed', true); - this._setProperty('keyIsPressed', true); - this._setProperty('keyCode', e.which); - this._downKeys[e.which] = true; - this._setProperty('key', e.key || String.fromCharCode(e.which) || e.which); - const context = this._isGlobal ? window : this; - if (typeof context.keyPressed === 'function' && !e.charCode) { - const executeDefault = context.keyPressed(e); - if (executeDefault === false) { - e.preventDefault(); + /** + * A function that's called once when any key is pressed. + * + * Declaring the function `keyPressed()` sets a code block to run once + * automatically when the user presses any key: + * + * ```js + * function keyPressed() { + * // Code to run. + * } + * ``` + * + * The key and keyCode + * variables will be updated with the most recently typed value when + * `keyPressed()` is called by p5.js: + * + * ```js + * function keyPressed() { + * if (key === 'c') { + * // Code to run. + * } + * + * if (keyCode === ENTER) { + * // Code to run. + * } + * } + * ``` + * + * The parameter, `event`, is optional. `keyPressed()` is always passed a + * KeyboardEvent + * object with properties that describe the key press event: + * + * ```js + * function keyPressed(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * Browsers may have default behaviors attached to various key events. For + * example, some browsers may jump to the bottom of a web page when the + * `SPACE` key is pressed. To prevent any default behavior for this event, add + * `return false;` to the end of the function. + * + * @method keyPressed + * @param {KeyboardEvent} [event] optional `KeyboardEvent` callback argument. + * + * @example + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square changes color when the user presses a key.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * // Toggle the background color when the user presses a key. + * function keyPressed() { + * if (value === 0) { + * value = 255; + * } else { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + * + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a white square at its center. The inner square turns black when the user presses the "b" key. It turns white when the user presses the "a" key.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * // Reassign value when the user presses the 'a' or 'b' key. + * function keyPressed() { + * if (key === 'a') { + * value = 255; + * } else if (key === 'b') { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + * + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square turns white when the user presses the left arrow key. It turns black when the user presses the right arrow key.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * // Toggle the background color when the user presses an arrow key. + * function keyPressed() { + * if (keyCode === LEFT_ARROW) { + * value = 255; + * } else if (keyCode === RIGHT_ARROW) { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + */ + fn._onkeydown = function(e) { + if (this._downKeys[e.which]) { + // prevent multiple firings + return; } - } -}; -/** - * A function that's called once when any key is released. - * - * Declaring the function `keyReleased()` sets a code block to run once - * automatically when the user releases any key: - * - * ```js - * function keyReleased() { - * // Code to run. - * } - * ``` - * - * The key and keyCode - * variables will be updated with the most recently released value when - * `keyReleased()` is called by p5.js: - * - * ```js - * function keyReleased() { - * if (key === 'c') { - * // Code to run. - * } - * - * if (keyCode === ENTER) { - * // Code to run. - * } - * } - * ``` - * - * The parameter, `event`, is optional. `keyReleased()` is always passed a - * KeyboardEvent - * object with properties that describe the key press event: - * - * ```js - * function keyReleased(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * Browsers may have default behaviors attached to various key events. To - * prevent any default behavior for this event, add `return false;` to the end - * of the function. - * - * @method keyReleased - * @param {KeyboardEvent} [event] optional `KeyboardEvent` callback argument. - * - * @example - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square changes color when the user releases a key.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * // Toggle value when the user releases a key. - * function keyReleased() { - * if (value === 0) { - * value = 255; - * } else { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - * - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square becomes white when the user releases the "w" key.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * // Set value to 255 the user releases the 'w' key. - * function keyReleased() { - * if (key === 'w') { - * value = 255; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - * - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square turns white when the user presses and releases the left arrow key. It turns black when the user presses and releases the right arrow key.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * // Toggle the background color when the user releases an arrow key. - * function keyReleased() { - * if (keyCode === LEFT_ARROW) { - * value = 255; - * } else if (keyCode === RIGHT_ARROW) { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - */ -p5.prototype._onkeyup = function(e) { - this._downKeys[e.which] = false; + this._setProperty('isKeyPressed', true); + this._setProperty('keyIsPressed', true); + this._setProperty('keyCode', e.which); + this._downKeys[e.which] = true; + this._setProperty('key', e.key || String.fromCharCode(e.which) || e.which); + const context = this._isGlobal ? window : this; + if (typeof context.keyPressed === 'function' && !e.charCode) { + const executeDefault = context.keyPressed(e); + if (executeDefault === false) { + e.preventDefault(); + } + } + }; + /** + * A function that's called once when any key is released. + * + * Declaring the function `keyReleased()` sets a code block to run once + * automatically when the user releases any key: + * + * ```js + * function keyReleased() { + * // Code to run. + * } + * ``` + * + * The key and keyCode + * variables will be updated with the most recently released value when + * `keyReleased()` is called by p5.js: + * + * ```js + * function keyReleased() { + * if (key === 'c') { + * // Code to run. + * } + * + * if (keyCode === ENTER) { + * // Code to run. + * } + * } + * ``` + * + * The parameter, `event`, is optional. `keyReleased()` is always passed a + * KeyboardEvent + * object with properties that describe the key press event: + * + * ```js + * function keyReleased(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * Browsers may have default behaviors attached to various key events. To + * prevent any default behavior for this event, add `return false;` to the end + * of the function. + * + * @method keyReleased + * @param {KeyboardEvent} [event] optional `KeyboardEvent` callback argument. + * + * @example + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square changes color when the user releases a key.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * // Toggle value when the user releases a key. + * function keyReleased() { + * if (value === 0) { + * value = 255; + * } else { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + * + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square becomes white when the user releases the "w" key.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * // Set value to 255 the user releases the 'w' key. + * function keyReleased() { + * if (key === 'w') { + * value = 255; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + * + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square turns white when the user presses and releases the left arrow key. It turns black when the user presses and releases the right arrow key.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * // Toggle the background color when the user releases an arrow key. + * function keyReleased() { + * if (keyCode === LEFT_ARROW) { + * value = 255; + * } else if (keyCode === RIGHT_ARROW) { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + */ + fn._onkeyup = function(e) { + this._downKeys[e.which] = false; - if (!this._areDownKeys()) { - this._setProperty('isKeyPressed', false); - this._setProperty('keyIsPressed', false); - } + if (!this._areDownKeys()) { + this._setProperty('isKeyPressed', false); + this._setProperty('keyIsPressed', false); + } - this._setProperty('_lastKeyCodeTyped', null); + this._setProperty('_lastKeyCodeTyped', null); - this._setProperty('key', e.key || String.fromCharCode(e.which) || e.which); - this._setProperty('keyCode', e.which); + this._setProperty('key', e.key || String.fromCharCode(e.which) || e.which); + this._setProperty('keyCode', e.which); - const context = this._isGlobal ? window : this; - if (typeof context.keyReleased === 'function') { - const executeDefault = context.keyReleased(e); - if (executeDefault === false) { - e.preventDefault(); + const context = this._isGlobal ? window : this; + if (typeof context.keyReleased === 'function') { + const executeDefault = context.keyReleased(e); + if (executeDefault === false) { + e.preventDefault(); + } } - } -}; + }; -/** - * A function that's called once when keys with printable characters are pressed. - * - * Declaring the function `keyTyped()` sets a code block to run once - * automatically when the user presses any key with a printable character such - * as `a` or 1. Modifier keys such as `SHIFT`, `CONTROL`, and the arrow keys - * will be ignored: - * - * ```js - * function keyTyped() { - * // Code to run. - * } - * ``` - * - * The key and keyCode - * variables will be updated with the most recently released value when - * `keyTyped()` is called by p5.js: - * - * ```js - * function keyTyped() { - * // Check for the "c" character using key. - * if (key === 'c') { - * // Code to run. - * } - * - * // Check for "c" using keyCode. - * if (keyCode === 67) { - * // Code to run. - * } - * } - * ``` - * - * The parameter, `event`, is optional. `keyTyped()` is always passed a - * KeyboardEvent - * object with properties that describe the key press event: - * - * ```js - * function keyReleased(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * Note: Use the keyPressed() function and - * keyCode system variable to respond to modifier - * keys such as `ALT`. - * - * Browsers may have default behaviors attached to various key events. To - * prevent any default behavior for this event, add `return false;` to the end - * of the function. - * - * @method keyTyped - * @param {KeyboardEvent} [event] optional `KeyboardEvent` callback argument. - * - * @example - *
    - * - * // Click on the canvas to begin detecting key presses. - * // Note: Pressing special keys such as SPACE have no effect. - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a white square at its center. The inner square changes color when the user presses a key.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * // Toggle the square's color when the user types a printable key. - * function keyTyped() { - * if (value === 0) { - * value = 255; - * } else { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - * - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a white square at its center. The inner square turns black when the user types the "b" key. It turns white when the user types the "a" key.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * // Reassign value when the user types the 'a' or 'b' key. - * function keyTyped() { - * if (key === 'a') { - * value = 255; - * } else if (key === 'b') { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - */ -p5.prototype._onkeypress = function(e) { - if (e.which === this._lastKeyCodeTyped) { - // prevent multiple firings - return; - } - this._setProperty('_lastKeyCodeTyped', e.which); // track last keyCode - this._setProperty('key', e.key || String.fromCharCode(e.which) || e.which); + /** + * A function that's called once when keys with printable characters are pressed. + * + * Declaring the function `keyTyped()` sets a code block to run once + * automatically when the user presses any key with a printable character such + * as `a` or 1. Modifier keys such as `SHIFT`, `CONTROL`, and the arrow keys + * will be ignored: + * + * ```js + * function keyTyped() { + * // Code to run. + * } + * ``` + * + * The key and keyCode + * variables will be updated with the most recently released value when + * `keyTyped()` is called by p5.js: + * + * ```js + * function keyTyped() { + * // Check for the "c" character using key. + * if (key === 'c') { + * // Code to run. + * } + * + * // Check for "c" using keyCode. + * if (keyCode === 67) { + * // Code to run. + * } + * } + * ``` + * + * The parameter, `event`, is optional. `keyTyped()` is always passed a + * KeyboardEvent + * object with properties that describe the key press event: + * + * ```js + * function keyReleased(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * Note: Use the keyPressed() function and + * keyCode system variable to respond to modifier + * keys such as `ALT`. + * + * Browsers may have default behaviors attached to various key events. To + * prevent any default behavior for this event, add `return false;` to the end + * of the function. + * + * @method keyTyped + * @param {KeyboardEvent} [event] optional `KeyboardEvent` callback argument. + * + * @example + *
    + * + * // Click on the canvas to begin detecting key presses. + * // Note: Pressing special keys such as SPACE have no effect. + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a white square at its center. The inner square changes color when the user presses a key.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * // Toggle the square's color when the user types a printable key. + * function keyTyped() { + * if (value === 0) { + * value = 255; + * } else { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + * + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a white square at its center. The inner square turns black when the user types the "b" key. It turns white when the user types the "a" key.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * // Reassign value when the user types the 'a' or 'b' key. + * function keyTyped() { + * if (key === 'a') { + * value = 255; + * } else if (key === 'b') { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + */ + fn._onkeypress = function(e) { + if (e.which === this._lastKeyCodeTyped) { + // prevent multiple firings + return; + } + this._setProperty('_lastKeyCodeTyped', e.which); // track last keyCode + this._setProperty('key', e.key || String.fromCharCode(e.which) || e.which); - const context = this._isGlobal ? window : this; - if (typeof context.keyTyped === 'function') { - const executeDefault = context.keyTyped(e); - if (executeDefault === false) { - e.preventDefault(); + const context = this._isGlobal ? window : this; + if (typeof context.keyTyped === 'function') { + const executeDefault = context.keyTyped(e); + if (executeDefault === false) { + e.preventDefault(); + } } - } -}; -/** - * The onblur function is called when the user is no longer focused - * on the p5 element. Because the keyup events will not fire if the user is - * not focused on the element we must assume all keys currently down have - * been released. - */ -p5.prototype._onblur = function(e) { - this._downKeys = {}; -}; + }; + /** + * The onblur function is called when the user is no longer focused + * on the p5 element. Because the keyup events will not fire if the user is + * not focused on the element we must assume all keys currently down have + * been released. + */ + fn._onblur = function(e) { + this._downKeys = {}; + }; -/** - * Returns `true` if the key it’s checking is pressed and `false` if not. - * - * `keyIsDown()` is helpful when checking for multiple different key presses. - * For example, `keyIsDown()` can be used to check if both `LEFT_ARROW` and - * `UP_ARROW` are pressed: - * - * ```js - * if (keyIsDown(LEFT_ARROW) && keyIsDown(UP_ARROW)) { - * // Move diagonally. - * } - * ``` - * - * `keyIsDown()` can check for key presses using - * keyCode values, as in `keyIsDown(37)` or - * `keyIsDown(LEFT_ARROW)`. Key codes can be found on websites such as - * keycode.info. - * - * @method keyIsDown - * @param {Number} code key to check. - * @return {Boolean} whether the key is down or not. - * - * @example - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * let x = 50; - * let y = 50; - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * describe( - * 'A gray square with a black circle at its center. The circle moves when the user presses an arrow key. It leaves a trail as it moves.' - * ); - * } - * - * function draw() { - * // Update x and y if an arrow key is pressed. - * if (keyIsDown(LEFT_ARROW) === true) { - * x -= 1; - * } - * - * if (keyIsDown(RIGHT_ARROW) === true) { - * x += 1; - * } - * - * if (keyIsDown(UP_ARROW) === true) { - * y -= 1; - * } - * - * if (keyIsDown(DOWN_ARROW) === true) { - * y += 1; - * } - * - * // Style the circle. - * fill(0); - * - * // Draw the circle. - * circle(x, y, 5); - * } - * - *
    - * - *
    - * - * // Click on the canvas to begin detecting key presses. - * - * let x = 50; - * let y = 50; - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * describe( - * 'A gray square with a black circle at its center. The circle moves when the user presses an arrow key. It leaves a trail as it moves.' - * ); - * } - * - * function draw() { - * // Update x and y if an arrow key is pressed. - * if (keyIsDown(37) === true) { - * x -= 1; - * } - * - * if (keyIsDown(39) === true) { - * x += 1; - * } - * - * if (keyIsDown(38) === true) { - * y -= 1; - * } - * - * if (keyIsDown(40) === true) { - * y += 1; - * } - * - * // Style the circle. - * fill(0); - * - * // Draw the circle. - * circle(x, y, 5); - * } - * - *
    - */ -p5.prototype.keyIsDown = function(code) { - p5._validateParameters('keyIsDown', arguments); - return this._downKeys[code] || false; -}; + /** + * Returns `true` if the key it’s checking is pressed and `false` if not. + * + * `keyIsDown()` is helpful when checking for multiple different key presses. + * For example, `keyIsDown()` can be used to check if both `LEFT_ARROW` and + * `UP_ARROW` are pressed: + * + * ```js + * if (keyIsDown(LEFT_ARROW) && keyIsDown(UP_ARROW)) { + * // Move diagonally. + * } + * ``` + * + * `keyIsDown()` can check for key presses using + * keyCode values, as in `keyIsDown(37)` or + * `keyIsDown(LEFT_ARROW)`. Key codes can be found on websites such as + * keycode.info. + * + * @method keyIsDown + * @param {Number} code key to check. + * @return {Boolean} whether the key is down or not. + * + * @example + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * let x = 50; + * let y = 50; + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * describe( + * 'A gray square with a black circle at its center. The circle moves when the user presses an arrow key. It leaves a trail as it moves.' + * ); + * } + * + * function draw() { + * // Update x and y if an arrow key is pressed. + * if (keyIsDown(LEFT_ARROW) === true) { + * x -= 1; + * } + * + * if (keyIsDown(RIGHT_ARROW) === true) { + * x += 1; + * } + * + * if (keyIsDown(UP_ARROW) === true) { + * y -= 1; + * } + * + * if (keyIsDown(DOWN_ARROW) === true) { + * y += 1; + * } + * + * // Style the circle. + * fill(0); + * + * // Draw the circle. + * circle(x, y, 5); + * } + * + *
    + * + *
    + * + * // Click on the canvas to begin detecting key presses. + * + * let x = 50; + * let y = 50; + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * describe( + * 'A gray square with a black circle at its center. The circle moves when the user presses an arrow key. It leaves a trail as it moves.' + * ); + * } + * + * function draw() { + * // Update x and y if an arrow key is pressed. + * if (keyIsDown(37) === true) { + * x -= 1; + * } + * + * if (keyIsDown(39) === true) { + * x += 1; + * } + * + * if (keyIsDown(38) === true) { + * y -= 1; + * } + * + * if (keyIsDown(40) === true) { + * y += 1; + * } + * + * // Style the circle. + * fill(0); + * + * // Draw the circle. + * circle(x, y, 5); + * } + * + *
    + */ + fn.keyIsDown = function(code) { + p5._validateParameters('keyIsDown', arguments); + return this._downKeys[code] || false; + }; -/** - * The _areDownKeys function returns a boolean true if any keys pressed - * and a false if no keys are currently pressed. + /** + * The _areDownKeys function returns a boolean true if any keys pressed + * and a false if no keys are currently pressed. - * Helps avoid instances where multiple keys are pressed simultaneously and - * releasing a single key will then switch the - * keyIsPressed property to true. - * @private -**/ -p5.prototype._areDownKeys = function() { - for (const key in this._downKeys) { - if (this._downKeys.hasOwnProperty(key) && this._downKeys[key] === true) { - return true; + * Helps avoid instances where multiple keys are pressed simultaneously and + * releasing a single key will then switch the + * keyIsPressed property to true. + * @private + **/ + fn._areDownKeys = function() { + for (const key in this._downKeys) { + if (this._downKeys.hasOwnProperty(key) && this._downKeys[key] === true) { + return true; + } } - } - return false; -}; + return false; + }; +} + +export default keyboard; -export default p5; +if(typeof p5 !== 'undefined'){ + keyboard(p5, p5.prototype); +} diff --git a/src/events/mouse.js b/src/events/mouse.js index a32fd6f9d5..5ddb28f4c8 100644 --- a/src/events/mouse.js +++ b/src/events/mouse.js @@ -6,1987 +6,1992 @@ * @requires constants */ -import p5 from '../core/main'; import * as constants from '../core/constants'; -/** - * A `Number` system variable that tracks the mouse's horizontal movement. - * - * `movedX` tracks how many pixels the mouse moves left or right between - * frames. `movedX` will have a negative value if the mouse moves left between - * frames and a positive value if it moves right. `movedX` can be calculated - * as `mouseX - pmouseX`. - * - * Note: `movedX` continues updating even when - * requestPointerLock() is active. - * - * @property {Number} movedX - * @readOnly - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square. The text ">>" appears when the user moves the mouse to the right. The text "<<" appears when the user moves the mouse to the left.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display >> when movedX is positive and - * // << when it's negative. - * if (movedX > 0) { - * text('>>', 50, 50); - * } else if (movedX < 0) { - * text('<<', 50, 50); - * } - * } - * - *
    - */ -p5.prototype.movedX = 0; +function mouse(p5, fn){ + /** + * A `Number` system variable that tracks the mouse's horizontal movement. + * + * `movedX` tracks how many pixels the mouse moves left or right between + * frames. `movedX` will have a negative value if the mouse moves left between + * frames and a positive value if it moves right. `movedX` can be calculated + * as `mouseX - pmouseX`. + * + * Note: `movedX` continues updating even when + * requestPointerLock() is active. + * + * @property {Number} movedX + * @readOnly + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square. The text ">>" appears when the user moves the mouse to the right. The text "<<" appears when the user moves the mouse to the left.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display >> when movedX is positive and + * // << when it's negative. + * if (movedX > 0) { + * text('>>', 50, 50); + * } else if (movedX < 0) { + * text('<<', 50, 50); + * } + * } + * + *
    + */ + fn.movedX = 0; -/** - * A `Number` system variable that tracks the mouse's vertical movement. - * - * `movedY` tracks how many pixels the mouse moves up or down between - * frames. `movedY` will have a negative value if the mouse moves up between - * frames and a positive value if it moves down. `movedY` can be calculated - * as `mouseY - pmouseY`. - * - * Note: `movedY` continues updating even when - * requestPointerLock() is active. - * - * @property {Number} movedY - * @readOnly - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square. The text "▲" appears when the user moves the mouse upward. The text "▼" appears when the user moves the mouse downward.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display ▼ when movedY is positive and - * // ▲ when it's negative. - * if (movedY > 0) { - * text('▼', 50, 50); - * } else if (movedY < 0) { - * text('▲', 50, 50); - * } - * } - * - *
    - */ -p5.prototype.movedY = 0; -/* - * This is a flag which is false until the first time - * we receive a mouse event. The pmouseX and pmouseY - * values will match the mouseX and mouseY values until - * this interaction takes place. - */ -p5.prototype._hasMouseInteracted = false; + /** + * A `Number` system variable that tracks the mouse's vertical movement. + * + * `movedY` tracks how many pixels the mouse moves up or down between + * frames. `movedY` will have a negative value if the mouse moves up between + * frames and a positive value if it moves down. `movedY` can be calculated + * as `mouseY - pmouseY`. + * + * Note: `movedY` continues updating even when + * requestPointerLock() is active. + * + * @property {Number} movedY + * @readOnly + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square. The text "▲" appears when the user moves the mouse upward. The text "▼" appears when the user moves the mouse downward.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display ▼ when movedY is positive and + * // ▲ when it's negative. + * if (movedY > 0) { + * text('▼', 50, 50); + * } else if (movedY < 0) { + * text('▲', 50, 50); + * } + * } + * + *
    + */ + fn.movedY = 0; + /* + * This is a flag which is false until the first time + * we receive a mouse event. The pmouseX and pmouseY + * values will match the mouseX and mouseY values until + * this interaction takes place. + */ + fn._hasMouseInteracted = false; -/** - * A `Number` system variable that tracks the mouse's horizontal position. - * - * In 2D mode, `mouseX` keeps track of the mouse's position relative to the - * top-left corner of the canvas. For example, if the mouse is 50 pixels from - * the left edge of the canvas, then `mouseX` will be 50. - * - * In WebGL mode, `mouseX` keeps track of the mouse's position relative to the - * center of the canvas. For example, if the mouse is 50 pixels to the right - * of the canvas' center, then `mouseX` will be 50. - * - * If touch is used instead of the mouse, then `mouseX` will hold the - * x-coordinate of the most recent touch point. - * - * @property {Number} mouseX - * @readOnly - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe("A vertical black line moves left and right following the mouse's x-position."); - * } - * - * function draw() { - * background(200); - * - * // Draw a vertical line that follows the mouse's x-coordinate. - * line(mouseX, 0, mouseX, 100); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse."); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display the mouse's coordinates. - * text(`x: ${mouseX} y: ${mouseY}`, 50, 50); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe("A vertical black line moves left and right following the mouse's x-position."); - * } - * - * function draw() { - * background(200); - * - * // Adjust coordinates for WebGL mode. - * // The origin (0, 0) is at the center of the canvas. - * let mx = mouseX - 50; - * - * // Draw the line. - * line(mx, -50, mx, 50); - * } - * - *
    - * - *
    - * - * let font; - * - * // Load a font for WebGL mode. - * function preload() { - * font = loadFont('assets/inconsolata.otf'); - * } - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe( - * "A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse." - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * textFont(font); - * fill(0); - * - * // Display the mouse's coordinates. - * text(`x: ${mouseX} y: ${mouseY}`, 0, 0); - * } - * - *
    - */ -p5.prototype.mouseX = 0; + /** + * A `Number` system variable that tracks the mouse's horizontal position. + * + * In 2D mode, `mouseX` keeps track of the mouse's position relative to the + * top-left corner of the canvas. For example, if the mouse is 50 pixels from + * the left edge of the canvas, then `mouseX` will be 50. + * + * In WebGL mode, `mouseX` keeps track of the mouse's position relative to the + * center of the canvas. For example, if the mouse is 50 pixels to the right + * of the canvas' center, then `mouseX` will be 50. + * + * If touch is used instead of the mouse, then `mouseX` will hold the + * x-coordinate of the most recent touch point. + * + * @property {Number} mouseX + * @readOnly + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe("A vertical black line moves left and right following the mouse's x-position."); + * } + * + * function draw() { + * background(200); + * + * // Draw a vertical line that follows the mouse's x-coordinate. + * line(mouseX, 0, mouseX, 100); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse."); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display the mouse's coordinates. + * text(`x: ${mouseX} y: ${mouseY}`, 50, 50); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe("A vertical black line moves left and right following the mouse's x-position."); + * } + * + * function draw() { + * background(200); + * + * // Adjust coordinates for WebGL mode. + * // The origin (0, 0) is at the center of the canvas. + * let mx = mouseX - 50; + * + * // Draw the line. + * line(mx, -50, mx, 50); + * } + * + *
    + * + *
    + * + * let font; + * + * // Load a font for WebGL mode. + * function preload() { + * font = loadFont('assets/inconsolata.otf'); + * } + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe( + * "A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse." + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * textFont(font); + * fill(0); + * + * // Display the mouse's coordinates. + * text(`x: ${mouseX} y: ${mouseY}`, 0, 0); + * } + * + *
    + */ + fn.mouseX = 0; -/** - * A `Number` system variable that tracks the mouse's vertical position. - * - * In 2D mode, `mouseY` keeps track of the mouse's position relative to the - * top-left corner of the canvas. For example, if the mouse is 50 pixels from - * the top edge of the canvas, then `mouseY` will be 50. - * - * In WebGL mode, `mouseY` keeps track of the mouse's position relative to the - * center of the canvas. For example, if the mouse is 50 pixels below the - * canvas' center, then `mouseY` will be 50. - * - * If touch is used instead of the mouse, then `mouseY` will hold the - * y-coordinate of the most recent touch point. - * - * @property {Number} mouseY - * @readOnly - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe("A horizontal black line moves up and down following the mouse's y-position."); - * } - * - * function draw() { - * background(200); - * - * // Draw a horizontal line that follows the mouse's y-coordinate. - * line(0, mouseY, 0, mouseY); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse."); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display the mouse's coordinates. - * text(`x: ${mouseX} y: ${mouseY}`, 50, 50); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe("A horizontal black line moves up and down following the mouse's y-position."); - * } - * - * function draw() { - * background(200); - * - * // Adjust coordinates for WebGL mode. - * // The origin (0, 0) is at the center of the canvas. - * let my = mouseY - 50; - * - * // Draw the line. - * line(-50, my, 50, my); - * } - * - *
    - * - *
    - * - * let font; - * - * // Load a font for WebGL mode. - * function preload() { - * font = loadFont('assets/inconsolata.otf'); - * } - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe( - * "A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse." - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * textFont(font); - * fill(0); - * - * // Display the mouse's coordinates. - * text(`x: ${mouseX} y: ${mouseY}`, 0, 0); - * } - * - *
    - */ -p5.prototype.mouseY = 0; + /** + * A `Number` system variable that tracks the mouse's vertical position. + * + * In 2D mode, `mouseY` keeps track of the mouse's position relative to the + * top-left corner of the canvas. For example, if the mouse is 50 pixels from + * the top edge of the canvas, then `mouseY` will be 50. + * + * In WebGL mode, `mouseY` keeps track of the mouse's position relative to the + * center of the canvas. For example, if the mouse is 50 pixels below the + * canvas' center, then `mouseY` will be 50. + * + * If touch is used instead of the mouse, then `mouseY` will hold the + * y-coordinate of the most recent touch point. + * + * @property {Number} mouseY + * @readOnly + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe("A horizontal black line moves up and down following the mouse's y-position."); + * } + * + * function draw() { + * background(200); + * + * // Draw a horizontal line that follows the mouse's y-coordinate. + * line(0, mouseY, 0, mouseY); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse."); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display the mouse's coordinates. + * text(`x: ${mouseX} y: ${mouseY}`, 50, 50); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe("A horizontal black line moves up and down following the mouse's y-position."); + * } + * + * function draw() { + * background(200); + * + * // Adjust coordinates for WebGL mode. + * // The origin (0, 0) is at the center of the canvas. + * let my = mouseY - 50; + * + * // Draw the line. + * line(-50, my, 50, my); + * } + * + *
    + * + *
    + * + * let font; + * + * // Load a font for WebGL mode. + * function preload() { + * font = loadFont('assets/inconsolata.otf'); + * } + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe( + * "A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse." + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * textFont(font); + * fill(0); + * + * // Display the mouse's coordinates. + * text(`x: ${mouseX} y: ${mouseY}`, 0, 0); + * } + * + *
    + */ + fn.mouseY = 0; -/** - * A `Number` system variable that tracks the mouse's previous horizontal - * position. - * - * In 2D mode, `pmouseX` keeps track of the mouse's position relative to the - * top-left corner of the canvas. Its value is - * mouseX from the previous frame. For example, if - * the mouse was 50 pixels from the left edge of the canvas during the last - * frame, then `pmouseX` will be 50. - * - * In WebGL mode, `pmouseX` keeps track of the mouse's position relative to the - * center of the canvas. For example, if the mouse was 50 pixels to the right - * of the canvas' center during the last frame, then `pmouseX` will be 50. - * - * If touch is used instead of the mouse, then `pmouseX` will hold the - * x-coordinate of the last touch point. - * - * Note: `pmouseX` is reset to the current mouseX - * value at the start of each touch event. - * - * @property {Number} pmouseX - * @readOnly - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Slow the frame rate. - * frameRate(10); - * - * describe('A line follows the mouse as it moves. The line grows longer with faster movements.'); - * } - * - * function draw() { - * background(200); - * - * line(pmouseX, pmouseY, mouseX, mouseY); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe('A line follows the mouse as it moves. The line grows longer with faster movements.'); - * } - * - * function draw() { - * background(200); - * - * // Adjust coordinates for WebGL mode. - * // The origin (0, 0) is at the center of the canvas. - * let pmx = pmouseX - 50; - * let pmy = pmouseY - 50; - * let mx = mouseX - 50; - * let my = mouseY - 50; - * - * // Draw the line. - * line(pmx, pmy, mx, my); - * } - * - *
    - */ -p5.prototype.pmouseX = 0; + /** + * A `Number` system variable that tracks the mouse's previous horizontal + * position. + * + * In 2D mode, `pmouseX` keeps track of the mouse's position relative to the + * top-left corner of the canvas. Its value is + * mouseX from the previous frame. For example, if + * the mouse was 50 pixels from the left edge of the canvas during the last + * frame, then `pmouseX` will be 50. + * + * In WebGL mode, `pmouseX` keeps track of the mouse's position relative to the + * center of the canvas. For example, if the mouse was 50 pixels to the right + * of the canvas' center during the last frame, then `pmouseX` will be 50. + * + * If touch is used instead of the mouse, then `pmouseX` will hold the + * x-coordinate of the last touch point. + * + * Note: `pmouseX` is reset to the current mouseX + * value at the start of each touch event. + * + * @property {Number} pmouseX + * @readOnly + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Slow the frame rate. + * frameRate(10); + * + * describe('A line follows the mouse as it moves. The line grows longer with faster movements.'); + * } + * + * function draw() { + * background(200); + * + * line(pmouseX, pmouseY, mouseX, mouseY); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe('A line follows the mouse as it moves. The line grows longer with faster movements.'); + * } + * + * function draw() { + * background(200); + * + * // Adjust coordinates for WebGL mode. + * // The origin (0, 0) is at the center of the canvas. + * let pmx = pmouseX - 50; + * let pmy = pmouseY - 50; + * let mx = mouseX - 50; + * let my = mouseY - 50; + * + * // Draw the line. + * line(pmx, pmy, mx, my); + * } + * + *
    + */ + fn.pmouseX = 0; -/** - * A `Number` system variable that tracks the mouse's previous vertical - * position. - * - * In 2D mode, `pmouseY` keeps track of the mouse's position relative to the - * top-left corner of the canvas. Its value is - * mouseY from the previous frame. For example, if - * the mouse was 50 pixels from the top edge of the canvas during the last - * frame, then `pmouseY` will be 50. - * - * In WebGL mode, `pmouseY` keeps track of the mouse's position relative to the - * center of the canvas. For example, if the mouse was 50 pixels below the - * canvas' center during the last frame, then `pmouseY` will be 50. - * - * If touch is used instead of the mouse, then `pmouseY` will hold the - * y-coordinate of the last touch point. - * - * Note: `pmouseY` is reset to the current mouseY - * value at the start of each touch event. - * - * @property {Number} pmouseY - * @readOnly - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Slow the frame rate. - * frameRate(10); - * - * describe('A line follows the mouse as it moves. The line grows longer with faster movements.'); - * } - * - * function draw() { - * background(200); - * - * line(pmouseX, pmouseY, mouseX, mouseY); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * - * describe('A line follows the mouse as it moves. The line grows longer with faster movements.'); - * } - * - * function draw() { - * background(200); - * - * // Adjust coordinates for WebGL mode. - * // The origin (0, 0) is at the center of the canvas. - * let pmx = pmouseX - 50; - * let pmy = pmouseY - 50; - * let mx = mouseX - 50; - * let my = mouseY - 50; - * - * // Draw the line. - * line(pmx, pmy, mx, my); - * } - * - *
    - */ -p5.prototype.pmouseY = 0; + /** + * A `Number` system variable that tracks the mouse's previous vertical + * position. + * + * In 2D mode, `pmouseY` keeps track of the mouse's position relative to the + * top-left corner of the canvas. Its value is + * mouseY from the previous frame. For example, if + * the mouse was 50 pixels from the top edge of the canvas during the last + * frame, then `pmouseY` will be 50. + * + * In WebGL mode, `pmouseY` keeps track of the mouse's position relative to the + * center of the canvas. For example, if the mouse was 50 pixels below the + * canvas' center during the last frame, then `pmouseY` will be 50. + * + * If touch is used instead of the mouse, then `pmouseY` will hold the + * y-coordinate of the last touch point. + * + * Note: `pmouseY` is reset to the current mouseY + * value at the start of each touch event. + * + * @property {Number} pmouseY + * @readOnly + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Slow the frame rate. + * frameRate(10); + * + * describe('A line follows the mouse as it moves. The line grows longer with faster movements.'); + * } + * + * function draw() { + * background(200); + * + * line(pmouseX, pmouseY, mouseX, mouseY); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * describe('A line follows the mouse as it moves. The line grows longer with faster movements.'); + * } + * + * function draw() { + * background(200); + * + * // Adjust coordinates for WebGL mode. + * // The origin (0, 0) is at the center of the canvas. + * let pmx = pmouseX - 50; + * let pmy = pmouseY - 50; + * let mx = mouseX - 50; + * let my = mouseY - 50; + * + * // Draw the line. + * line(pmx, pmy, mx, my); + * } + * + *
    + */ + fn.pmouseY = 0; -/** - * A `Number` variable that tracks the mouse's horizontal position within the - * browser. - * - * `winMouseX` keeps track of the mouse's position relative to the top-left - * corner of the browser window. For example, if the mouse is 50 pixels from - * the left edge of the browser, then `winMouseX` will be 50. - * - * On a touchscreen device, `winMouseX` will hold the x-coordinate of the most - * recent touch point. - * - * Note: Use mouseX to track the mouse’s - * x-coordinate within the canvas. - * - * @property {Number} winMouseX - * @readOnly - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse."); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display the mouse's coordinates within the browser window. - * text(`x: ${winMouseX} y: ${winMouseY}`, 50, 50); - * } - * - *
    - */ -p5.prototype.winMouseX = 0; + /** + * A `Number` variable that tracks the mouse's horizontal position within the + * browser. + * + * `winMouseX` keeps track of the mouse's position relative to the top-left + * corner of the browser window. For example, if the mouse is 50 pixels from + * the left edge of the browser, then `winMouseX` will be 50. + * + * On a touchscreen device, `winMouseX` will hold the x-coordinate of the most + * recent touch point. + * + * Note: Use mouseX to track the mouse’s + * x-coordinate within the canvas. + * + * @property {Number} winMouseX + * @readOnly + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse."); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display the mouse's coordinates within the browser window. + * text(`x: ${winMouseX} y: ${winMouseY}`, 50, 50); + * } + * + *
    + */ + fn.winMouseX = 0; -/** - * A `Number` variable that tracks the mouse's vertical position within the - * browser. - * - * `winMouseY` keeps track of the mouse's position relative to the top-left - * corner of the browser window. For example, if the mouse is 50 pixels from - * the top edge of the browser, then `winMouseY` will be 50. - * - * On a touchscreen device, `winMouseY` will hold the y-coordinate of the most - * recent touch point. - * - * Note: Use mouseY to track the mouse’s - * y-coordinate within the canvas. - * - * @property {Number} winMouseY - * @readOnly - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse."); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display the mouse's coordinates within the browser window. - * text(`x: ${winMouseX} y: ${winMouseY}`, 50, 50); - * } - * - *
    - */ -p5.prototype.winMouseY = 0; + /** + * A `Number` variable that tracks the mouse's vertical position within the + * browser. + * + * `winMouseY` keeps track of the mouse's position relative to the top-left + * corner of the browser window. For example, if the mouse is 50 pixels from + * the top edge of the browser, then `winMouseY` will be 50. + * + * On a touchscreen device, `winMouseY` will hold the y-coordinate of the most + * recent touch point. + * + * Note: Use mouseY to track the mouse’s + * y-coordinate within the canvas. + * + * @property {Number} winMouseY + * @readOnly + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse."); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display the mouse's coordinates within the browser window. + * text(`x: ${winMouseX} y: ${winMouseY}`, 50, 50); + * } + * + *
    + */ + fn.winMouseY = 0; -/** - * A `Number` variable that tracks the mouse's previous horizontal position - * within the browser. - * - * `pwinMouseX` keeps track of the mouse's position relative to the top-left - * corner of the browser window. Its value is - * winMouseX from the previous frame. For - * example, if the mouse was 50 pixels from - * the left edge of the browser during the last frame, then `pwinMouseX` will - * be 50. - * - * On a touchscreen device, `pwinMouseX` will hold the x-coordinate of the most - * recent touch point. `pwinMouseX` is reset to the current - * winMouseX value at the start of each touch - * event. - * - * Note: Use pmouseX to track the mouse’s previous - * x-coordinate within the canvas. - * - * @property {Number} pwinMouseX - * @readOnly - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Slow the frame rate. - * frameRate(10); - * - * describe('A gray square. A white circle at its center grows larger when the mouse moves horizontally.'); - * } - * - * function draw() { - * background(200); - * - * // Calculate the circle's diameter. - * let d = winMouseX - pwinMouseX; - * - * // Draw the circle. - * circle(50, 50, d); - * } - * - *
    - * - *
    - * - * function setup() { - * // Create the canvas and set its position. - * let cnv = createCanvas(100, 100); - * cnv.position(20, 20); - * - * describe('A gray square with a number at its center. The number changes as the user moves the mouse vertically.'); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display pwinMouseX. - * text(pwinMouseX, 50, 50); - * } - * - *
    - */ -p5.prototype.pwinMouseX = 0; + /** + * A `Number` variable that tracks the mouse's previous horizontal position + * within the browser. + * + * `pwinMouseX` keeps track of the mouse's position relative to the top-left + * corner of the browser window. Its value is + * winMouseX from the previous frame. For + * example, if the mouse was 50 pixels from + * the left edge of the browser during the last frame, then `pwinMouseX` will + * be 50. + * + * On a touchscreen device, `pwinMouseX` will hold the x-coordinate of the most + * recent touch point. `pwinMouseX` is reset to the current + * winMouseX value at the start of each touch + * event. + * + * Note: Use pmouseX to track the mouse’s previous + * x-coordinate within the canvas. + * + * @property {Number} pwinMouseX + * @readOnly + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Slow the frame rate. + * frameRate(10); + * + * describe('A gray square. A white circle at its center grows larger when the mouse moves horizontally.'); + * } + * + * function draw() { + * background(200); + * + * // Calculate the circle's diameter. + * let d = winMouseX - pwinMouseX; + * + * // Draw the circle. + * circle(50, 50, d); + * } + * + *
    + * + *
    + * + * function setup() { + * // Create the canvas and set its position. + * let cnv = createCanvas(100, 100); + * cnv.position(20, 20); + * + * describe('A gray square with a number at its center. The number changes as the user moves the mouse vertically.'); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display pwinMouseX. + * text(pwinMouseX, 50, 50); + * } + * + *
    + */ + fn.pwinMouseX = 0; -/** - * A `Number` variable that tracks the mouse's previous vertical position - * within the browser. - * - * `pwinMouseY` keeps track of the mouse's position relative to the top-left - * corner of the browser window. Its value is - * winMouseY from the previous frame. For - * example, if the mouse was 50 pixels from - * the top edge of the browser during the last frame, then `pwinMouseY` will - * be 50. - * - * On a touchscreen device, `pwinMouseY` will hold the y-coordinate of the most - * recent touch point. `pwinMouseY` is reset to the current - * winMouseY value at the start of each touch - * event. - * - * Note: Use pmouseY to track the mouse’s previous - * y-coordinate within the canvas. - * - * @property {Number} pwinMouseY - * @readOnly - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Slow the frame rate. - * frameRate(10); - * - * describe('A gray square. A white circle at its center grows larger when the mouse moves vertically.'); - * } - * - * function draw() { - * background(200); - * - * // Calculate the circle's diameter. - * let d = winMouseY - pwinMouseY; - * - * // Draw the circle. - * circle(50, 50, d); - * } - * - *
    - * - *
    - * - * function setup() { - * // Create the canvas and set its position. - * let cnv = createCanvas(100, 100); - * cnv.position(20, 20); - * - * describe('A gray square with a number at its center. The number changes as the user moves the mouse vertically.'); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display pwinMouseY. - * text(pwinMouseY, 50, 50); - * } - * - *
    - */ -p5.prototype.pwinMouseY = 0; + /** + * A `Number` variable that tracks the mouse's previous vertical position + * within the browser. + * + * `pwinMouseY` keeps track of the mouse's position relative to the top-left + * corner of the browser window. Its value is + * winMouseY from the previous frame. For + * example, if the mouse was 50 pixels from + * the top edge of the browser during the last frame, then `pwinMouseY` will + * be 50. + * + * On a touchscreen device, `pwinMouseY` will hold the y-coordinate of the most + * recent touch point. `pwinMouseY` is reset to the current + * winMouseY value at the start of each touch + * event. + * + * Note: Use pmouseY to track the mouse’s previous + * y-coordinate within the canvas. + * + * @property {Number} pwinMouseY + * @readOnly + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Slow the frame rate. + * frameRate(10); + * + * describe('A gray square. A white circle at its center grows larger when the mouse moves vertically.'); + * } + * + * function draw() { + * background(200); + * + * // Calculate the circle's diameter. + * let d = winMouseY - pwinMouseY; + * + * // Draw the circle. + * circle(50, 50, d); + * } + * + *
    + * + *
    + * + * function setup() { + * // Create the canvas and set its position. + * let cnv = createCanvas(100, 100); + * cnv.position(20, 20); + * + * describe('A gray square with a number at its center. The number changes as the user moves the mouse vertically.'); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display pwinMouseY. + * text(pwinMouseY, 50, 50); + * } + * + *
    + */ + fn.pwinMouseY = 0; -/** - * A String system variable that contains the value of the last mouse button - * pressed. - * - * The `mouseButton` variable is either `LEFT`, `RIGHT`, or `CENTER`, - * depending on which button was pressed last. - * - * Note: Different browsers may track `mouseButton` differently. See - * MDN - * for more information. - * - * @property {(LEFT|RIGHT|CENTER)} mouseButton - * @readOnly - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with black text at its center. The text changes from 0 to either "left" or "right" when the user clicks a mouse button.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display the mouse button. - * text(mouseButton, 50, 50); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * "A gray square. Different shapes appear at its center depending on the mouse button that's clicked." - * ); - * } - * - * function draw() { - * background(200); - * - * if (mouseIsPressed === true) { - * if (mouseButton === LEFT) { - * circle(50, 50, 50); - * } - * if (mouseButton === RIGHT) { - * square(25, 25, 50); - * } - * if (mouseButton === CENTER) { - * triangle(23, 75, 50, 20, 78, 75); - * } - * } - * } - * - *
    - */ -p5.prototype.mouseButton = 0; + /** + * A String system variable that contains the value of the last mouse button + * pressed. + * + * The `mouseButton` variable is either `LEFT`, `RIGHT`, or `CENTER`, + * depending on which button was pressed last. + * + * Note: Different browsers may track `mouseButton` differently. See + * MDN + * for more information. + * + * @property {(LEFT|RIGHT|CENTER)} mouseButton + * @readOnly + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with black text at its center. The text changes from 0 to either "left" or "right" when the user clicks a mouse button.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display the mouse button. + * text(mouseButton, 50, 50); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * "A gray square. Different shapes appear at its center depending on the mouse button that's clicked." + * ); + * } + * + * function draw() { + * background(200); + * + * if (mouseIsPressed === true) { + * if (mouseButton === LEFT) { + * circle(50, 50, 50); + * } + * if (mouseButton === RIGHT) { + * square(25, 25, 50); + * } + * if (mouseButton === CENTER) { + * triangle(23, 75, 50, 20, 78, 75); + * } + * } + * } + * + *
    + */ + fn.mouseButton = 0; -/** - * A `Boolean` system variable that's `true` if the mouse is pressed and - * `false` if not. - * - * @property {Boolean} mouseIsPressed - * @readOnly - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with the word "false" at its center. The word changes to "true" when the user presses a mouse button.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display the mouseIsPressed variable. - * text(mouseIsPressed, 25, 50); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a white square at its center. The inner square turns black when the user presses the mouse.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * if (mouseIsPressed === true) { - * fill(0); - * } else { - * fill(255); - * } - * - * // Draw the square. - * square(25, 25, 50); - * } - * - *
    - */ -p5.prototype.mouseIsPressed = false; + /** + * A `Boolean` system variable that's `true` if the mouse is pressed and + * `false` if not. + * + * @property {Boolean} mouseIsPressed + * @readOnly + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with the word "false" at its center. The word changes to "true" when the user presses a mouse button.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display the mouseIsPressed variable. + * text(mouseIsPressed, 25, 50); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a white square at its center. The inner square turns black when the user presses the mouse.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * if (mouseIsPressed === true) { + * fill(0); + * } else { + * fill(255); + * } + * + * // Draw the square. + * square(25, 25, 50); + * } + * + *
    + */ + fn.mouseIsPressed = false; -p5.prototype._updateNextMouseCoords = function(e) { - if (this._curElement !== null && (!e.touches || e.touches.length > 0)) { - const mousePos = getMousePos( - this._curElement.elt, - this.width, - this.height, - e - ); - this._setProperty('movedX', e.movementX); - this._setProperty('movedY', e.movementY); - this._setProperty('mouseX', mousePos.x); - this._setProperty('mouseY', mousePos.y); - this._setProperty('winMouseX', mousePos.winX); - this._setProperty('winMouseY', mousePos.winY); - } - if (!this._hasMouseInteracted) { - // For first draw, make previous and next equal - this._updateMouseCoords(); - this._setProperty('_hasMouseInteracted', true); - } -}; + fn._updateNextMouseCoords = function(e) { + if (this._curElement !== null && (!e.touches || e.touches.length > 0)) { + const mousePos = getMousePos( + this._curElement.elt, + this.width, + this.height, + e + ); + this._setProperty('movedX', e.movementX); + this._setProperty('movedY', e.movementY); + this._setProperty('mouseX', mousePos.x); + this._setProperty('mouseY', mousePos.y); + this._setProperty('winMouseX', mousePos.winX); + this._setProperty('winMouseY', mousePos.winY); + } + if (!this._hasMouseInteracted) { + // For first draw, make previous and next equal + this._updateMouseCoords(); + this._setProperty('_hasMouseInteracted', true); + } + }; -p5.prototype._updateMouseCoords = function() { - this._setProperty('pmouseX', this.mouseX); - this._setProperty('pmouseY', this.mouseY); - this._setProperty('pwinMouseX', this.winMouseX); - this._setProperty('pwinMouseY', this.winMouseY); + fn._updateMouseCoords = function() { + this._setProperty('pmouseX', this.mouseX); + this._setProperty('pmouseY', this.mouseY); + this._setProperty('pwinMouseX', this.winMouseX); + this._setProperty('pwinMouseY', this.winMouseY); - this._setProperty('_pmouseWheelDeltaY', this._mouseWheelDeltaY); -}; + this._setProperty('_pmouseWheelDeltaY', this._mouseWheelDeltaY); + }; -function getMousePos(canvas, w, h, evt) { - if (evt && !evt.clientX) { - // use touches if touch and not mouse - if (evt.touches) { - evt = evt.touches[0]; - } else if (evt.changedTouches) { - evt = evt.changedTouches[0]; + function getMousePos(canvas, w, h, evt) { + if (evt && !evt.clientX) { + // use touches if touch and not mouse + if (evt.touches) { + evt = evt.touches[0]; + } else if (evt.changedTouches) { + evt = evt.changedTouches[0]; + } } + const rect = canvas.getBoundingClientRect(); + const sx = canvas.scrollWidth / w || 1; + const sy = canvas.scrollHeight / h || 1; + return { + x: (evt.clientX - rect.left) / sx, + y: (evt.clientY - rect.top) / sy, + winX: evt.clientX, + winY: evt.clientY, + id: evt.identifier + }; } - const rect = canvas.getBoundingClientRect(); - const sx = canvas.scrollWidth / w || 1; - const sy = canvas.scrollHeight / h || 1; - return { - x: (evt.clientX - rect.left) / sx, - y: (evt.clientY - rect.top) / sy, - winX: evt.clientX, - winY: evt.clientY, - id: evt.identifier - }; -} -p5.prototype._setMouseButton = function(e) { - if (e.button === 1) { - this._setProperty('mouseButton', constants.CENTER); - } else if (e.button === 2) { - this._setProperty('mouseButton', constants.RIGHT); - } else { - this._setProperty('mouseButton', constants.LEFT); - } -}; + fn._setMouseButton = function(e) { + if (e.button === 1) { + this._setProperty('mouseButton', constants.CENTER); + } else if (e.button === 2) { + this._setProperty('mouseButton', constants.RIGHT); + } else { + this._setProperty('mouseButton', constants.LEFT); + } + }; -/** - * A function that's called when the mouse moves. - * - * Declaring the function `mouseMoved()` sets a code block to run - * automatically when the user moves the mouse without clicking any mouse - * buttons: - * - * ```js - * function mouseMoved() { - * // Code to run. - * } - * ``` - * - * The mouse system variables, such as mouseX and - * mouseY, will be updated with their most recent - * value when `mouseMoved()` is called by p5.js: - * - * ```js - * function mouseMoved() { - * if (mouseX < 50) { - * // Code to run if the mouse is on the left. - * } - * - * if (mouseY > 50) { - * // Code to run if the mouse is near the bottom. - * } - * } - * ``` - * - * The parameter, `event`, is optional. `mouseMoved()` is always passed a - * MouseEvent - * object with properties that describe the mouse move event: - * - * ```js - * function mouseMoved(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * Browsers may have default behaviors attached to various mouse events. For - * example, some browsers highlight text when the user moves the mouse while - * pressing a mouse button. To prevent any default behavior for this event, - * add `return false;` to the end of the function. - * - * @method mouseMoved - * @param {MouseEvent} [event] optional `MouseEvent` argument. - * - * @example - *
    - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square becomes lighter as the mouse moves.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * function mouseMoved() { - * // Update the grayscale value. - * value += 5; - * - * // Reset the grayscale value. - * if (value > 255) { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - */ + /** + * A function that's called when the mouse moves. + * + * Declaring the function `mouseMoved()` sets a code block to run + * automatically when the user moves the mouse without clicking any mouse + * buttons: + * + * ```js + * function mouseMoved() { + * // Code to run. + * } + * ``` + * + * The mouse system variables, such as mouseX and + * mouseY, will be updated with their most recent + * value when `mouseMoved()` is called by p5.js: + * + * ```js + * function mouseMoved() { + * if (mouseX < 50) { + * // Code to run if the mouse is on the left. + * } + * + * if (mouseY > 50) { + * // Code to run if the mouse is near the bottom. + * } + * } + * ``` + * + * The parameter, `event`, is optional. `mouseMoved()` is always passed a + * MouseEvent + * object with properties that describe the mouse move event: + * + * ```js + * function mouseMoved(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * Browsers may have default behaviors attached to various mouse events. For + * example, some browsers highlight text when the user moves the mouse while + * pressing a mouse button. To prevent any default behavior for this event, + * add `return false;` to the end of the function. + * + * @method mouseMoved + * @param {MouseEvent} [event] optional `MouseEvent` argument. + * + * @example + *
    + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square becomes lighter as the mouse moves.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * function mouseMoved() { + * // Update the grayscale value. + * value += 5; + * + * // Reset the grayscale value. + * if (value > 255) { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + */ -/** - * A function that's called when the mouse moves while a button is pressed. - * - * Declaring the function `mouseDragged()` sets a code block to run - * automatically when the user clicks and drags the mouse: - * - * ```js - * function mouseDragged() { - * // Code to run. - * } - * ``` - * - * The mouse system variables, such as mouseX and - * mouseY, will be updated with their most recent - * value when `mouseDragged()` is called by p5.js: - * - * ```js - * function mouseDragged() { - * if (mouseX < 50) { - * // Code to run if the mouse is on the left. - * } - * - * if (mouseY > 50) { - * // Code to run if the mouse is near the bottom. - * } - * } - * ``` - * - * The parameter, `event`, is optional. `mouseDragged()` is always passed a - * MouseEvent - * object with properties that describe the mouse drag event: - * - * ```js - * function mouseDragged(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * On touchscreen devices, `mouseDragged()` will run when a user moves a touch - * point if touchMoved() isn’t declared. If - * touchMoved() is declared, then - * touchMoved() will run when a user moves a - * touch point and `mouseDragged()` won’t. - * - * Browsers may have default behaviors attached to various mouse events. For - * example, some browsers highlight text when the user moves the mouse while - * pressing a mouse button. To prevent any default behavior for this event, - * add `return false;` to the end of the function. - * - * @method mouseDragged - * @param {MouseEvent} [event] optional `MouseEvent` argument. - * - * @example - *
    - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square becomes lighter as the user drags the mouse.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * function mouseDragged() { - * // Update the grayscale value. - * value += 5; - * - * // Reset the grayscale value. - * if (value > 255) { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - */ -p5.prototype._onmousemove = function(e) { - const context = this._isGlobal ? window : this; - let executeDefault; - this._updateNextMouseCoords(e); - if (!this.mouseIsPressed) { - if (typeof context.mouseMoved === 'function') { - executeDefault = context.mouseMoved(e); - if (executeDefault === false) { - e.preventDefault(); + /** + * A function that's called when the mouse moves while a button is pressed. + * + * Declaring the function `mouseDragged()` sets a code block to run + * automatically when the user clicks and drags the mouse: + * + * ```js + * function mouseDragged() { + * // Code to run. + * } + * ``` + * + * The mouse system variables, such as mouseX and + * mouseY, will be updated with their most recent + * value when `mouseDragged()` is called by p5.js: + * + * ```js + * function mouseDragged() { + * if (mouseX < 50) { + * // Code to run if the mouse is on the left. + * } + * + * if (mouseY > 50) { + * // Code to run if the mouse is near the bottom. + * } + * } + * ``` + * + * The parameter, `event`, is optional. `mouseDragged()` is always passed a + * MouseEvent + * object with properties that describe the mouse drag event: + * + * ```js + * function mouseDragged(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * On touchscreen devices, `mouseDragged()` will run when a user moves a touch + * point if touchMoved() isn’t declared. If + * touchMoved() is declared, then + * touchMoved() will run when a user moves a + * touch point and `mouseDragged()` won’t. + * + * Browsers may have default behaviors attached to various mouse events. For + * example, some browsers highlight text when the user moves the mouse while + * pressing a mouse button. To prevent any default behavior for this event, + * add `return false;` to the end of the function. + * + * @method mouseDragged + * @param {MouseEvent} [event] optional `MouseEvent` argument. + * + * @example + *
    + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square becomes lighter as the user drags the mouse.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * function mouseDragged() { + * // Update the grayscale value. + * value += 5; + * + * // Reset the grayscale value. + * if (value > 255) { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + */ + fn._onmousemove = function(e) { + const context = this._isGlobal ? window : this; + let executeDefault; + this._updateNextMouseCoords(e); + if (!this.mouseIsPressed) { + if (typeof context.mouseMoved === 'function') { + executeDefault = context.mouseMoved(e); + if (executeDefault === false) { + e.preventDefault(); + } } + } else { + if (typeof context.mouseDragged === 'function') { + executeDefault = context.mouseDragged(e); + if (executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.touchMoved === 'function') { + executeDefault = context.touchMoved(e); + if (executeDefault === false) { + e.preventDefault(); + } + } + } + }; + + /** + * A function that's called once when a mouse button is pressed. + * + * Declaring the function `mousePressed()` sets a code block to run + * automatically when the user presses a mouse button: + * + * ```js + * function mousePressed() { + * // Code to run. + * } + * ``` + * + * The mouse system variables, such as mouseX and + * mouseY, will be updated with their most recent + * value when `mousePressed()` is called by p5.js: + * + * ```js + * function mousePressed() { + * if (mouseX < 50) { + * // Code to run if the mouse is on the left. + * } + * + * if (mouseY > 50) { + * // Code to run if the mouse is near the bottom. + * } + * } + * ``` + * + * The parameter, `event`, is optional. `mousePressed()` is always passed a + * MouseEvent + * object with properties that describe the mouse press event: + * + * ```js + * function mousePressed(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * On touchscreen devices, `mousePressed()` will run when a user’s touch + * begins if touchStarted() isn’t declared. If + * touchStarted() is declared, then + * touchStarted() will run when a user’s touch + * begins and `mousePressed()` won’t. + * + * Browsers may have default behaviors attached to various mouse events. For + * example, some browsers highlight text when the user moves the mouse while + * pressing a mouse button. To prevent any default behavior for this event, + * add `return false;` to the end of the function. + * + * Note: `mousePressed()`, mouseReleased(), + * and mouseClicked() are all related. + * `mousePressed()` runs as soon as the user clicks the mouse. + * mouseReleased() runs as soon as the user + * releases the mouse click. mouseClicked() + * runs immediately after mouseReleased(). + * + * @method mousePressed + * @param {MouseEvent} [event] optional `MouseEvent` argument. + * + * @example + *
    + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square becomes lighter when the user presses a mouse button.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * function mousePressed() { + * // Update the grayscale value. + * value += 5; + * + * // Reset the grayscale value. + * if (value > 255) { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Style the circle. + * fill('orange'); + * stroke('royalblue'); + * strokeWeight(10); + * + * describe( + * 'An orange circle with a thick, blue border drawn on a gray background. When the user presses and holds the mouse, the border becomes thin and pink. When the user releases the mouse, the border becomes thicker and changes color to blue.' + * ); + * } + * + * function draw() { + * background(220); + * + * // Draw the circle. + * circle(50, 50, 20); + * } + * + * // Set the stroke color and weight as soon as the user clicks. + * function mousePressed() { + * stroke('deeppink'); + * strokeWeight(3); + * } + * + * // Set the stroke and fill colors as soon as the user releases + * // the mouse. + * function mouseReleased() { + * stroke('royalblue'); + * + * // This is never visible because fill() is called + * // in mouseClicked() which runs immediately after + * // mouseReleased(); + * fill('limegreen'); + * } + * + * // Set the fill color and stroke weight after + * // mousePressed() and mouseReleased() are called. + * function mouseClicked() { + * fill('orange'); + * strokeWeight(10); + * } + * + *
    + */ + fn._onmousedown = function(e) { + const context = this._isGlobal ? window : this; + let executeDefault; + this._setProperty('mouseIsPressed', true); + this._setMouseButton(e); + this._updateNextMouseCoords(e); + + // _ontouchstart triggers first and sets this.touchstart + if (this.touchstart) { + return; } - } else { - if (typeof context.mouseDragged === 'function') { - executeDefault = context.mouseDragged(e); + + if (typeof context.mousePressed === 'function') { + executeDefault = context.mousePressed(e); if (executeDefault === false) { e.preventDefault(); } - } else if (typeof context.touchMoved === 'function') { - executeDefault = context.touchMoved(e); + } else if (typeof context.touchStarted === 'function') { + executeDefault = context.touchStarted(e); if (executeDefault === false) { e.preventDefault(); } } - } -}; -/** - * A function that's called once when a mouse button is pressed. - * - * Declaring the function `mousePressed()` sets a code block to run - * automatically when the user presses a mouse button: - * - * ```js - * function mousePressed() { - * // Code to run. - * } - * ``` - * - * The mouse system variables, such as mouseX and - * mouseY, will be updated with their most recent - * value when `mousePressed()` is called by p5.js: - * - * ```js - * function mousePressed() { - * if (mouseX < 50) { - * // Code to run if the mouse is on the left. - * } - * - * if (mouseY > 50) { - * // Code to run if the mouse is near the bottom. - * } - * } - * ``` - * - * The parameter, `event`, is optional. `mousePressed()` is always passed a - * MouseEvent - * object with properties that describe the mouse press event: - * - * ```js - * function mousePressed(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * On touchscreen devices, `mousePressed()` will run when a user’s touch - * begins if touchStarted() isn’t declared. If - * touchStarted() is declared, then - * touchStarted() will run when a user’s touch - * begins and `mousePressed()` won’t. - * - * Browsers may have default behaviors attached to various mouse events. For - * example, some browsers highlight text when the user moves the mouse while - * pressing a mouse button. To prevent any default behavior for this event, - * add `return false;` to the end of the function. - * - * Note: `mousePressed()`, mouseReleased(), - * and mouseClicked() are all related. - * `mousePressed()` runs as soon as the user clicks the mouse. - * mouseReleased() runs as soon as the user - * releases the mouse click. mouseClicked() - * runs immediately after mouseReleased(). - * - * @method mousePressed - * @param {MouseEvent} [event] optional `MouseEvent` argument. - * - * @example - *
    - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square becomes lighter when the user presses a mouse button.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * function mousePressed() { - * // Update the grayscale value. - * value += 5; - * - * // Reset the grayscale value. - * if (value > 255) { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Style the circle. - * fill('orange'); - * stroke('royalblue'); - * strokeWeight(10); - * - * describe( - * 'An orange circle with a thick, blue border drawn on a gray background. When the user presses and holds the mouse, the border becomes thin and pink. When the user releases the mouse, the border becomes thicker and changes color to blue.' - * ); - * } - * - * function draw() { - * background(220); - * - * // Draw the circle. - * circle(50, 50, 20); - * } - * - * // Set the stroke color and weight as soon as the user clicks. - * function mousePressed() { - * stroke('deeppink'); - * strokeWeight(3); - * } - * - * // Set the stroke and fill colors as soon as the user releases - * // the mouse. - * function mouseReleased() { - * stroke('royalblue'); - * - * // This is never visible because fill() is called - * // in mouseClicked() which runs immediately after - * // mouseReleased(); - * fill('limegreen'); - * } - * - * // Set the fill color and stroke weight after - * // mousePressed() and mouseReleased() are called. - * function mouseClicked() { - * fill('orange'); - * strokeWeight(10); - * } - * - *
    - */ -p5.prototype._onmousedown = function(e) { - const context = this._isGlobal ? window : this; - let executeDefault; - this._setProperty('mouseIsPressed', true); - this._setMouseButton(e); - this._updateNextMouseCoords(e); + this.touchstart = false; + }; - // _ontouchstart triggers first and sets this.touchstart - if (this.touchstart) { - return; - } + /** + * A function that's called once when a mouse button is released. + * + * Declaring the function `mouseReleased()` sets a code block to run + * automatically when the user releases a mouse button after having pressed + * it: + * + * ```js + * function mouseReleased() { + * // Code to run. + * } + * ``` + * + * The mouse system variables, such as mouseX and + * mouseY, will be updated with their most recent + * value when `mouseReleased()` is called by p5.js: + * + * ```js + * function mouseReleased() { + * if (mouseX < 50) { + * // Code to run if the mouse is on the left. + * } + * + * if (mouseY > 50) { + * // Code to run if the mouse is near the bottom. + * } + * } + * ``` + * + * The parameter, `event`, is optional. `mouseReleased()` is always passed a + * MouseEvent + * object with properties that describe the mouse release event: + * + * ```js + * function mouseReleased(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * On touchscreen devices, `mouseReleased()` will run when a user’s touch + * ends if touchEnded() isn’t declared. If + * touchEnded() is declared, then + * touchEnded() will run when a user’s touch + * ends and `mouseReleased()` won’t. + * + * Browsers may have default behaviors attached to various mouse events. For + * example, some browsers highlight text when the user moves the mouse while + * pressing a mouse button. To prevent any default behavior for this event, + * add `return false;` to the end of the function. + * + * Note: mousePressed(), `mouseReleased()`, + * and mouseClicked() are all related. + * mousePressed() runs as soon as the user + * clicks the mouse. `mouseReleased()` runs as soon as the user releases the + * mouse click. mouseClicked() runs + * immediately after `mouseReleased()`. + * + * @method mouseReleased + * @param {MouseEvent} [event] optional `MouseEvent` argument. + * + * @example + *
    + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square becomes lighter when the user presses and releases a mouse button.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * function mouseReleased() { + * // Update the grayscale value. + * value += 5; + * + * // Reset the grayscale value. + * if (value > 255) { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Style the circle. + * fill('orange'); + * stroke('royalblue'); + * strokeWeight(10); + * + * describe( + * 'An orange circle with a thick, blue border drawn on a gray background. When the user presses and holds the mouse, the border becomes thin and pink. When the user releases the mouse, the border becomes thicker and changes color to blue.' + * ); + * } + * + * function draw() { + * background(220); + * + * // Draw the circle. + * circle(50, 50, 20); + * } + * + * // Set the stroke color and weight as soon as the user clicks. + * function mousePressed() { + * stroke('deeppink'); + * strokeWeight(3); + * } + * + * // Set the stroke and fill colors as soon as the user releases + * // the mouse. + * function mouseReleased() { + * stroke('royalblue'); + * + * // This is never visible because fill() is called + * // in mouseClicked() which runs immediately after + * // mouseReleased(); + * fill('limegreen'); + * } + * + * // Set the fill color and stroke weight after + * // mousePressed() and mouseReleased() are called. + * function mouseClicked() { + * fill('orange'); + * strokeWeight(10); + * } + * + *
    + */ + fn._onmouseup = function(e) { + const context = this._isGlobal ? window : this; + let executeDefault; + this._setProperty('mouseIsPressed', false); - if (typeof context.mousePressed === 'function') { - executeDefault = context.mousePressed(e); - if (executeDefault === false) { - e.preventDefault(); + // _ontouchend triggers first and sets this.touchend + if (this.touchend) { + return; } - } else if (typeof context.touchStarted === 'function') { - executeDefault = context.touchStarted(e); - if (executeDefault === false) { - e.preventDefault(); - } - } - - this.touchstart = false; -}; - -/** - * A function that's called once when a mouse button is released. - * - * Declaring the function `mouseReleased()` sets a code block to run - * automatically when the user releases a mouse button after having pressed - * it: - * - * ```js - * function mouseReleased() { - * // Code to run. - * } - * ``` - * - * The mouse system variables, such as mouseX and - * mouseY, will be updated with their most recent - * value when `mouseReleased()` is called by p5.js: - * - * ```js - * function mouseReleased() { - * if (mouseX < 50) { - * // Code to run if the mouse is on the left. - * } - * - * if (mouseY > 50) { - * // Code to run if the mouse is near the bottom. - * } - * } - * ``` - * - * The parameter, `event`, is optional. `mouseReleased()` is always passed a - * MouseEvent - * object with properties that describe the mouse release event: - * - * ```js - * function mouseReleased(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * On touchscreen devices, `mouseReleased()` will run when a user’s touch - * ends if touchEnded() isn’t declared. If - * touchEnded() is declared, then - * touchEnded() will run when a user’s touch - * ends and `mouseReleased()` won’t. - * - * Browsers may have default behaviors attached to various mouse events. For - * example, some browsers highlight text when the user moves the mouse while - * pressing a mouse button. To prevent any default behavior for this event, - * add `return false;` to the end of the function. - * - * Note: mousePressed(), `mouseReleased()`, - * and mouseClicked() are all related. - * mousePressed() runs as soon as the user - * clicks the mouse. `mouseReleased()` runs as soon as the user releases the - * mouse click. mouseClicked() runs - * immediately after `mouseReleased()`. - * - * @method mouseReleased - * @param {MouseEvent} [event] optional `MouseEvent` argument. - * - * @example - *
    - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square becomes lighter when the user presses and releases a mouse button.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * function mouseReleased() { - * // Update the grayscale value. - * value += 5; - * - * // Reset the grayscale value. - * if (value > 255) { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Style the circle. - * fill('orange'); - * stroke('royalblue'); - * strokeWeight(10); - * - * describe( - * 'An orange circle with a thick, blue border drawn on a gray background. When the user presses and holds the mouse, the border becomes thin and pink. When the user releases the mouse, the border becomes thicker and changes color to blue.' - * ); - * } - * - * function draw() { - * background(220); - * - * // Draw the circle. - * circle(50, 50, 20); - * } - * - * // Set the stroke color and weight as soon as the user clicks. - * function mousePressed() { - * stroke('deeppink'); - * strokeWeight(3); - * } - * - * // Set the stroke and fill colors as soon as the user releases - * // the mouse. - * function mouseReleased() { - * stroke('royalblue'); - * - * // This is never visible because fill() is called - * // in mouseClicked() which runs immediately after - * // mouseReleased(); - * fill('limegreen'); - * } - * - * // Set the fill color and stroke weight after - * // mousePressed() and mouseReleased() are called. - * function mouseClicked() { - * fill('orange'); - * strokeWeight(10); - * } - * - *
    - */ -p5.prototype._onmouseup = function(e) { - const context = this._isGlobal ? window : this; - let executeDefault; - this._setProperty('mouseIsPressed', false); - - // _ontouchend triggers first and sets this.touchend - if (this.touchend) { - return; - } - if (typeof context.mouseReleased === 'function') { - executeDefault = context.mouseReleased(e); - if (executeDefault === false) { - e.preventDefault(); - } - } else if (typeof context.touchEnded === 'function') { - executeDefault = context.touchEnded(e); - if (executeDefault === false) { - e.preventDefault(); + if (typeof context.mouseReleased === 'function') { + executeDefault = context.mouseReleased(e); + if (executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.touchEnded === 'function') { + executeDefault = context.touchEnded(e); + if (executeDefault === false) { + e.preventDefault(); + } } - } - this.touchend = false; -}; + this.touchend = false; + }; -p5.prototype._ondragend = p5.prototype._onmouseup; -p5.prototype._ondragover = p5.prototype._onmousemove; + fn._ondragend = fn._onmouseup; + fn._ondragover = fn._onmousemove; -/** - * A function that's called once after a mouse button is pressed and released. - * - * Declaring the function `mouseClicked()` sets a code block to run - * automatically when the user releases a mouse button after having pressed - * it: - * - * ```js - * function mouseClicked() { - * // Code to run. - * } - * ``` - * - * The mouse system variables, such as mouseX and - * mouseY, will be updated with their most recent - * value when `mouseClicked()` is called by p5.js: - * - * ```js - * function mouseClicked() { - * if (mouseX < 50) { - * // Code to run if the mouse is on the left. - * } - * - * if (mouseY > 50) { - * // Code to run if the mouse is near the bottom. - * } - * } - * ``` - * - * The parameter, `event`, is optional. `mouseClicked()` is always passed a - * MouseEvent - * object with properties that describe the mouse click event: - * - * ```js - * function mouseClicked(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * On touchscreen devices, `mouseClicked()` will run when a user’s touch - * ends if touchEnded() isn’t declared. If - * touchEnded() is declared, then - * touchEnded() will run when a user’s touch - * ends and `mouseClicked()` won’t. - * - * Browsers may have default behaviors attached to various mouse events. For - * example, some browsers highlight text when the user moves the mouse while - * pressing a mouse button. To prevent any default behavior for this event, - * add `return false;` to the end of the function. - * - * Note: mousePressed(), - * mouseReleased(), - * and `mouseClicked()` are all related. - * mousePressed() runs as soon as the user - * clicks the mouse. mouseReleased() runs as - * soon as the user releases the mouse click. `mouseClicked()` runs - * immediately after mouseReleased(). - * - * @method mouseClicked - * @param {MouseEvent} [event] optional `MouseEvent` argument. - * - * @example - *
    - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square changes color when the user presses and releases a mouse button.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * // Toggle the square's color when the user clicks. - * function mouseClicked() { - * if (value === 0) { - * value = 255; - * } else { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Style the circle. - * fill('orange'); - * stroke('royalblue'); - * strokeWeight(10); - * - * describe( - * 'An orange circle with a thick, blue border drawn on a gray background. When the user presses and holds the mouse, the border becomes thin and pink. When the user releases the mouse, the border becomes thicker and changes color to blue.' - * ); - * } - * - * function draw() { - * background(220); - * - * // Draw the circle. - * circle(50, 50, 20); - * } - * - * // Set the stroke color and weight as soon as the user clicks. - * function mousePressed() { - * stroke('deeppink'); - * strokeWeight(3); - * } - * - * // Set the stroke and fill colors as soon as the user releases - * // the mouse. - * function mouseReleased() { - * stroke('royalblue'); - * - * // This is never visible because fill() is called - * // in mouseClicked() which runs immediately after - * // mouseReleased(); - * fill('limegreen'); - * } - * - * // Set the fill color and stroke weight after - * // mousePressed() and mouseReleased() are called. - * function mouseClicked() { - * fill('orange'); - * strokeWeight(10); - * } - * - *
    - */ -p5.prototype._onclick = function(e) { - const context = this._isGlobal ? window : this; - if (typeof context.mouseClicked === 'function') { - const executeDefault = context.mouseClicked(e); - if (executeDefault === false) { - e.preventDefault(); + /** + * A function that's called once after a mouse button is pressed and released. + * + * Declaring the function `mouseClicked()` sets a code block to run + * automatically when the user releases a mouse button after having pressed + * it: + * + * ```js + * function mouseClicked() { + * // Code to run. + * } + * ``` + * + * The mouse system variables, such as mouseX and + * mouseY, will be updated with their most recent + * value when `mouseClicked()` is called by p5.js: + * + * ```js + * function mouseClicked() { + * if (mouseX < 50) { + * // Code to run if the mouse is on the left. + * } + * + * if (mouseY > 50) { + * // Code to run if the mouse is near the bottom. + * } + * } + * ``` + * + * The parameter, `event`, is optional. `mouseClicked()` is always passed a + * MouseEvent + * object with properties that describe the mouse click event: + * + * ```js + * function mouseClicked(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * On touchscreen devices, `mouseClicked()` will run when a user’s touch + * ends if touchEnded() isn’t declared. If + * touchEnded() is declared, then + * touchEnded() will run when a user’s touch + * ends and `mouseClicked()` won’t. + * + * Browsers may have default behaviors attached to various mouse events. For + * example, some browsers highlight text when the user moves the mouse while + * pressing a mouse button. To prevent any default behavior for this event, + * add `return false;` to the end of the function. + * + * Note: mousePressed(), + * mouseReleased(), + * and `mouseClicked()` are all related. + * mousePressed() runs as soon as the user + * clicks the mouse. mouseReleased() runs as + * soon as the user releases the mouse click. `mouseClicked()` runs + * immediately after mouseReleased(). + * + * @method mouseClicked + * @param {MouseEvent} [event] optional `MouseEvent` argument. + * + * @example + *
    + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square changes color when the user presses and releases a mouse button.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * // Toggle the square's color when the user clicks. + * function mouseClicked() { + * if (value === 0) { + * value = 255; + * } else { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Style the circle. + * fill('orange'); + * stroke('royalblue'); + * strokeWeight(10); + * + * describe( + * 'An orange circle with a thick, blue border drawn on a gray background. When the user presses and holds the mouse, the border becomes thin and pink. When the user releases the mouse, the border becomes thicker and changes color to blue.' + * ); + * } + * + * function draw() { + * background(220); + * + * // Draw the circle. + * circle(50, 50, 20); + * } + * + * // Set the stroke color and weight as soon as the user clicks. + * function mousePressed() { + * stroke('deeppink'); + * strokeWeight(3); + * } + * + * // Set the stroke and fill colors as soon as the user releases + * // the mouse. + * function mouseReleased() { + * stroke('royalblue'); + * + * // This is never visible because fill() is called + * // in mouseClicked() which runs immediately after + * // mouseReleased(); + * fill('limegreen'); + * } + * + * // Set the fill color and stroke weight after + * // mousePressed() and mouseReleased() are called. + * function mouseClicked() { + * fill('orange'); + * strokeWeight(10); + * } + * + *
    + */ + fn._onclick = function(e) { + const context = this._isGlobal ? window : this; + if (typeof context.mouseClicked === 'function') { + const executeDefault = context.mouseClicked(e); + if (executeDefault === false) { + e.preventDefault(); + } } - } -}; + }; -/** - * A function that's called once when a mouse button is clicked twice quickly. - * - * Declaring the function `doubleClicked()` sets a code block to run - * automatically when the user presses and releases the mouse button twice - * quickly: - * - * ```js - * function doubleClicked() { - * // Code to run. - * } - * ``` - * - * The mouse system variables, such as mouseX and - * mouseY, will be updated with their most recent - * value when `doubleClicked()` is called by p5.js: - * - * ```js - * function doubleClicked() { - * if (mouseX < 50) { - * // Code to run if the mouse is on the left. - * } - * - * if (mouseY > 50) { - * // Code to run if the mouse is near the bottom. - * } - * } - * ``` - * - * The parameter, `event`, is optional. `doubleClicked()` is always passed a - * MouseEvent - * object with properties that describe the double-click event: - * - * ```js - * function doubleClicked(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * On touchscreen devices, code placed in `doubleClicked()` will run after two - * touches that occur within a short time. - * - * Browsers may have default behaviors attached to various mouse events. For - * example, some browsers highlight text when the user moves the mouse while - * pressing a mouse button. To prevent any default behavior for this event, - * add `return false;` to the end of the function. - * - * @method doubleClicked - * @param {MouseEvent} [event] optional `MouseEvent` argument. - * - * @example - *
    - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square changes color when the user double-clicks.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * // Toggle the square's color when the user double-clicks. - * function doubleClicked() { - * if (value === 0) { - * value = 255; - * } else { - * value = 0; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - * - *
    - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black circle at its center. When the user double-clicks on the circle, it changes color to white.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the circle. - * fill(value); - * - * // Draw the circle. - * circle(50, 50, 80); - * } - * - * // Reassign value to 255 when the user double-clicks on the circle. - * function doubleClicked() { - * if (dist(50, 50, mouseX, mouseY) < 40) { - * value = 255; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - */ + /** + * A function that's called once when a mouse button is clicked twice quickly. + * + * Declaring the function `doubleClicked()` sets a code block to run + * automatically when the user presses and releases the mouse button twice + * quickly: + * + * ```js + * function doubleClicked() { + * // Code to run. + * } + * ``` + * + * The mouse system variables, such as mouseX and + * mouseY, will be updated with their most recent + * value when `doubleClicked()` is called by p5.js: + * + * ```js + * function doubleClicked() { + * if (mouseX < 50) { + * // Code to run if the mouse is on the left. + * } + * + * if (mouseY > 50) { + * // Code to run if the mouse is near the bottom. + * } + * } + * ``` + * + * The parameter, `event`, is optional. `doubleClicked()` is always passed a + * MouseEvent + * object with properties that describe the double-click event: + * + * ```js + * function doubleClicked(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * On touchscreen devices, code placed in `doubleClicked()` will run after two + * touches that occur within a short time. + * + * Browsers may have default behaviors attached to various mouse events. For + * example, some browsers highlight text when the user moves the mouse while + * pressing a mouse button. To prevent any default behavior for this event, + * add `return false;` to the end of the function. + * + * @method doubleClicked + * @param {MouseEvent} [event] optional `MouseEvent` argument. + * + * @example + *
    + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square changes color when the user double-clicks.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * // Toggle the square's color when the user double-clicks. + * function doubleClicked() { + * if (value === 0) { + * value = 255; + * } else { + * value = 0; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + * + *
    + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black circle at its center. When the user double-clicks on the circle, it changes color to white.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the circle. + * fill(value); + * + * // Draw the circle. + * circle(50, 50, 80); + * } + * + * // Reassign value to 255 when the user double-clicks on the circle. + * function doubleClicked() { + * if (dist(50, 50, mouseX, mouseY) < 40) { + * value = 255; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + */ -p5.prototype._ondblclick = function(e) { - const context = this._isGlobal ? window : this; - if (typeof context.doubleClicked === 'function') { - const executeDefault = context.doubleClicked(e); - if (executeDefault === false) { - e.preventDefault(); + fn._ondblclick = function(e) { + const context = this._isGlobal ? window : this; + if (typeof context.doubleClicked === 'function') { + const executeDefault = context.doubleClicked(e); + if (executeDefault === false) { + e.preventDefault(); + } } - } -}; + }; -/** - * For use with WebGL orbitControl. - * @property {Number} _mouseWheelDeltaY - * @readOnly - * @private - */ -p5.prototype._mouseWheelDeltaY = 0; + /** + * For use with WebGL orbitControl. + * @property {Number} _mouseWheelDeltaY + * @readOnly + * @private + */ + fn._mouseWheelDeltaY = 0; -/** - * For use with WebGL orbitControl. - * @property {Number} _pmouseWheelDeltaY - * @readOnly - * @private - */ -p5.prototype._pmouseWheelDeltaY = 0; + /** + * For use with WebGL orbitControl. + * @property {Number} _pmouseWheelDeltaY + * @readOnly + * @private + */ + fn._pmouseWheelDeltaY = 0; -/** - * A function that's called once when the mouse wheel moves. - * - * Declaring the function `mouseWheel()` sets a code block to run - * automatically when the user scrolls with the mouse wheel: - * - * ```js - * function mouseWheel() { - * // Code to run. - * } - * ``` - * - * The mouse system variables, such as mouseX and - * mouseY, will be updated with their most recent - * value when `mouseWheel()` is called by p5.js: - * - * ```js - * function mouseWheel() { - * if (mouseX < 50) { - * // Code to run if the mouse is on the left. - * } - * - * if (mouseY > 50) { - * // Code to run if the mouse is near the bottom. - * } - * } - * ``` - * - * The parameter, `event`, is optional. `mouseWheel()` is always passed a - * MouseEvent - * object with properties that describe the mouse scroll event: - * - * ```js - * function mouseWheel(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * The `event` object has many properties including `delta`, a `Number` - * containing the distance that the user scrolled. For example, `event.delta` - * might have the value 5 when the user scrolls up. `event.delta` is positive - * if the user scrolls up and negative if they scroll down. The signs are - * opposite on macOS with "natural" scrolling enabled. - * - * Browsers may have default behaviors attached to various mouse events. For - * example, some browsers highlight text when the user moves the mouse while - * pressing a mouse button. To prevent any default behavior for this event, - * add `return false;` to the end of the function. - * - * Note: On Safari, `mouseWheel()` may only work as expected if - * `return false;` is added at the end of the function. - * - * @method mouseWheel - * @param {WheelEvent} [event] optional `WheelEvent` argument. - * - * @example - *
    - * - * let circleSize = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square. A white circle at its center grows up when the user scrolls the mouse wheel.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Draw the circle - * circle(circleSize, 50, 50); - * } - * - * // Increment circleSize when the user scrolls the mouse wheel. - * function mouseWheel() { - * circleSize += 1; - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - * - *
    - * - * let direction = ''; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square. An arrow at its center points up when the user scrolls up. The arrow points down when the user scrolls down.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Draw an arrow that points where - * // the mouse last scrolled. - * text(direction, 50, 50); - * } - * - * // Change direction when the user scrolls the mouse wheel. - * function mouseWheel(event) { - * if (event.delta > 0) { - * direction = '▲'; - * } else { - * direction = '▼'; - * } - * // Uncomment to prevent any default behavior. - * // return false; - * } - * - *
    - */ -p5.prototype._onwheel = function(e) { - const context = this._isGlobal ? window : this; - this._setProperty('_mouseWheelDeltaY', e.deltaY); - if (typeof context.mouseWheel === 'function') { - e.delta = e.deltaY; - const executeDefault = context.mouseWheel(e); - if (executeDefault === false) { - e.preventDefault(); + /** + * A function that's called once when the mouse wheel moves. + * + * Declaring the function `mouseWheel()` sets a code block to run + * automatically when the user scrolls with the mouse wheel: + * + * ```js + * function mouseWheel() { + * // Code to run. + * } + * ``` + * + * The mouse system variables, such as mouseX and + * mouseY, will be updated with their most recent + * value when `mouseWheel()` is called by p5.js: + * + * ```js + * function mouseWheel() { + * if (mouseX < 50) { + * // Code to run if the mouse is on the left. + * } + * + * if (mouseY > 50) { + * // Code to run if the mouse is near the bottom. + * } + * } + * ``` + * + * The parameter, `event`, is optional. `mouseWheel()` is always passed a + * MouseEvent + * object with properties that describe the mouse scroll event: + * + * ```js + * function mouseWheel(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * The `event` object has many properties including `delta`, a `Number` + * containing the distance that the user scrolled. For example, `event.delta` + * might have the value 5 when the user scrolls up. `event.delta` is positive + * if the user scrolls up and negative if they scroll down. The signs are + * opposite on macOS with "natural" scrolling enabled. + * + * Browsers may have default behaviors attached to various mouse events. For + * example, some browsers highlight text when the user moves the mouse while + * pressing a mouse button. To prevent any default behavior for this event, + * add `return false;` to the end of the function. + * + * Note: On Safari, `mouseWheel()` may only work as expected if + * `return false;` is added at the end of the function. + * + * @method mouseWheel + * @param {WheelEvent} [event] optional `WheelEvent` argument. + * + * @example + *
    + * + * let circleSize = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square. A white circle at its center grows up when the user scrolls the mouse wheel.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Draw the circle + * circle(circleSize, 50, 50); + * } + * + * // Increment circleSize when the user scrolls the mouse wheel. + * function mouseWheel() { + * circleSize += 1; + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + * + *
    + * + * let direction = ''; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square. An arrow at its center points up when the user scrolls up. The arrow points down when the user scrolls down.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Draw an arrow that points where + * // the mouse last scrolled. + * text(direction, 50, 50); + * } + * + * // Change direction when the user scrolls the mouse wheel. + * function mouseWheel(event) { + * if (event.delta > 0) { + * direction = '▲'; + * } else { + * direction = '▼'; + * } + * // Uncomment to prevent any default behavior. + * // return false; + * } + * + *
    + */ + fn._onwheel = function(e) { + const context = this._isGlobal ? window : this; + this._setProperty('_mouseWheelDeltaY', e.deltaY); + if (typeof context.mouseWheel === 'function') { + e.delta = e.deltaY; + const executeDefault = context.mouseWheel(e); + if (executeDefault === false) { + e.preventDefault(); + } } - } -}; + }; -/** - * Locks the mouse pointer to its current position and makes it invisible. - * - * `requestPointerLock()` allows the mouse to move forever without leaving the - * screen. Calling `requestPointerLock()` locks the values of - * mouseX, mouseY, - * pmouseX, and pmouseY. - * movedX and movedY - * continue updating and can be used to get the distance the mouse moved since - * the last frame was drawn. Calling - * exitPointerLock() resumes updating the - * mouse system variables. - * - * Note: Most browsers require an input, such as a click, before calling - * `requestPointerLock()`. It’s recommended to call `requestPointerLock()` in - * an event function such as doubleClicked(). - * - * @method requestPointerLock - * - * @example - *
    - * - * let score = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with the text "Score: X" at its center. The score increases when the user moves the mouse upward. It decreases when the user moves the mouse downward.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Update the score. - * score -= movedY; - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Display the score. - * text(`Score: ${score}`, 50, 50); - * } - * - * // Lock the pointer when the user double-clicks. - * function doubleClicked() { - * requestPointerLock(); - * } - * - *
    - */ -p5.prototype.requestPointerLock = function() { - // pointer lock object forking for cross browser - const canvas = this._curElement.elt; - canvas.requestPointerLock = - canvas.requestPointerLock || canvas.mozRequestPointerLock; - if (!canvas.requestPointerLock) { - console.log('requestPointerLock is not implemented in this browser'); - return false; - } - canvas.requestPointerLock(); - return true; -}; + /** + * Locks the mouse pointer to its current position and makes it invisible. + * + * `requestPointerLock()` allows the mouse to move forever without leaving the + * screen. Calling `requestPointerLock()` locks the values of + * mouseX, mouseY, + * pmouseX, and pmouseY. + * movedX and movedY + * continue updating and can be used to get the distance the mouse moved since + * the last frame was drawn. Calling + * exitPointerLock() resumes updating the + * mouse system variables. + * + * Note: Most browsers require an input, such as a click, before calling + * `requestPointerLock()`. It’s recommended to call `requestPointerLock()` in + * an event function such as doubleClicked(). + * + * @method requestPointerLock + * + * @example + *
    + * + * let score = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with the text "Score: X" at its center. The score increases when the user moves the mouse upward. It decreases when the user moves the mouse downward.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Update the score. + * score -= movedY; + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Display the score. + * text(`Score: ${score}`, 50, 50); + * } + * + * // Lock the pointer when the user double-clicks. + * function doubleClicked() { + * requestPointerLock(); + * } + * + *
    + */ + fn.requestPointerLock = function() { + // pointer lock object forking for cross browser + const canvas = this._curElement.elt; + canvas.requestPointerLock = + canvas.requestPointerLock || canvas.mozRequestPointerLock; + if (!canvas.requestPointerLock) { + console.log('requestPointerLock is not implemented in this browser'); + return false; + } + canvas.requestPointerLock(); + return true; + }; -/** - * Exits a pointer lock started with - * requestPointerLock. - * - * Calling `requestPointerLock()` locks the values of - * mouseX, mouseY, - * pmouseX, and pmouseY. - * Calling `exitPointerLock()` resumes updating the mouse system variables. - * - * Note: Most browsers require an input, such as a click, before calling - * `requestPointerLock()`. It’s recommended to call `requestPointerLock()` in - * an event function such as doubleClicked(). - * - * @method exitPointerLock - * - * @example - *
    - * - * let isLocked = false; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a word at its center. The word changes between "Unlocked" and "Locked" when the user double-clicks.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * - * // Tell the user whether the pointer is locked. - * if (isLocked === true) { - * text('Locked', 50, 50); - * } else { - * text('Unlocked', 50, 50); - * } - * } - * - * // Toggle the pointer lock when the user double-clicks. - * function doubleClicked() { - * if (isLocked === true) { - * exitPointerLock(); - * isLocked = false; - * } else { - * requestPointerLock(); - * isLocked = true; - * } - * } - * - *
    - */ -p5.prototype.exitPointerLock = function() { - document.exitPointerLock(); -}; + /** + * Exits a pointer lock started with + * requestPointerLock. + * + * Calling `requestPointerLock()` locks the values of + * mouseX, mouseY, + * pmouseX, and pmouseY. + * Calling `exitPointerLock()` resumes updating the mouse system variables. + * + * Note: Most browsers require an input, such as a click, before calling + * `requestPointerLock()`. It’s recommended to call `requestPointerLock()` in + * an event function such as doubleClicked(). + * + * @method exitPointerLock + * + * @example + *
    + * + * let isLocked = false; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a word at its center. The word changes between "Unlocked" and "Locked" when the user double-clicks.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * + * // Tell the user whether the pointer is locked. + * if (isLocked === true) { + * text('Locked', 50, 50); + * } else { + * text('Unlocked', 50, 50); + * } + * } + * + * // Toggle the pointer lock when the user double-clicks. + * function doubleClicked() { + * if (isLocked === true) { + * exitPointerLock(); + * isLocked = false; + * } else { + * requestPointerLock(); + * isLocked = true; + * } + * } + * + *
    + */ + fn.exitPointerLock = function() { + document.exitPointerLock(); + }; +} + +export default mouse; -export default p5; +if(typeof p5 !== 'undefined'){ + mouse(p5, p5.prototype); +} diff --git a/src/events/touch.js b/src/events/touch.js index ff2587f1bc..6ed8ca89cf 100644 --- a/src/events/touch.js +++ b/src/events/touch.js @@ -5,633 +5,637 @@ * @requires core */ -import p5 from '../core/main'; +function touch(p5, fn){ + /** + * An `Array` of all the current touch points on a touchscreen device. + * + * The `touches` array is empty by default. When the user touches their + * screen, a new touch point is tracked and added to the array. Touch points + * are `Objects` with the following properties: + * + * ```js + * // Iterate over the touches array. + * for (let touch of touches) { + * // x-coordinate relative to the top-left + * // corner of the canvas. + * console.log(touch.x); + * + * // y-coordinate relative to the top-left + * // corner of the canvas. + * console.log(touch.y); + * + * // x-coordinate relative to the top-left + * // corner of the browser. + * console.log(touch.winX); + * + * // y-coordinate relative to the top-left + * // corner of the browser. + * console.log(touch.winY); + * + * // ID number + * console.log(touch.id); + * } + * ``` + * + * @property {Object[]} touches + * @readOnly + * + * @example + *
    + * + * // On a touchscreen device, touch the canvas using one or more fingers + * // at the same time. + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square. White circles appear where the user touches the square.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Draw a circle at each touch point. + * for (let touch of touches) { + * circle(touch.x, touch.y, 40); + * } + * } + * + *
    + * + *
    + * + * // On a touchscreen device, touch the canvas using one or more fingers + * // at the same time. + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square. Labels appear where the user touches the square, displaying the coordinates.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Draw a label above each touch point. + * for (let touch of touches) { + * text(`${touch.x}, ${touch.y}`, touch.x, touch.y - 40); + * } + * } + * + *
    + */ + fn.touches = []; -/** - * An `Array` of all the current touch points on a touchscreen device. - * - * The `touches` array is empty by default. When the user touches their - * screen, a new touch point is tracked and added to the array. Touch points - * are `Objects` with the following properties: - * - * ```js - * // Iterate over the touches array. - * for (let touch of touches) { - * // x-coordinate relative to the top-left - * // corner of the canvas. - * console.log(touch.x); - * - * // y-coordinate relative to the top-left - * // corner of the canvas. - * console.log(touch.y); - * - * // x-coordinate relative to the top-left - * // corner of the browser. - * console.log(touch.winX); - * - * // y-coordinate relative to the top-left - * // corner of the browser. - * console.log(touch.winY); - * - * // ID number - * console.log(touch.id); - * } - * ``` - * - * @property {Object[]} touches - * @readOnly - * - * @example - *
    - * - * // On a touchscreen device, touch the canvas using one or more fingers - * // at the same time. - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square. White circles appear where the user touches the square.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Draw a circle at each touch point. - * for (let touch of touches) { - * circle(touch.x, touch.y, 40); - * } - * } - * - *
    - * - *
    - * - * // On a touchscreen device, touch the canvas using one or more fingers - * // at the same time. - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square. Labels appear where the user touches the square, displaying the coordinates.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Draw a label above each touch point. - * for (let touch of touches) { - * text(`${touch.x}, ${touch.y}`, touch.x, touch.y - 40); - * } - * } - * - *
    - */ -p5.prototype.touches = []; - -p5.prototype._updateTouchCoords = function(e) { - if (this._curElement !== null) { - const touches = []; - for (let i = 0; i < e.touches.length; i++) { - touches[i] = getTouchInfo( - this._curElement.elt, - this.width, - this.height, - e, - i - ); + fn._updateTouchCoords = function(e) { + if (this._curElement !== null) { + const touches = []; + for (let i = 0; i < e.touches.length; i++) { + touches[i] = getTouchInfo( + this._curElement.elt, + this.width, + this.height, + e, + i + ); + } + this._setProperty('touches', touches); } - this._setProperty('touches', touches); - } -}; - -function getTouchInfo(canvas, w, h, e, i = 0) { - const rect = canvas.getBoundingClientRect(); - const sx = canvas.scrollWidth / w || 1; - const sy = canvas.scrollHeight / h || 1; - const touch = e.touches[i] || e.changedTouches[i]; - return { - x: (touch.clientX - rect.left) / sx, - y: (touch.clientY - rect.top) / sy, - winX: touch.clientX, - winY: touch.clientY, - id: touch.identifier }; -} -/** - * A function that's called once each time the user touches the screen. - * - * Declaring a function called `touchStarted()` sets a code block to run - * automatically each time the user begins touching a touchscreen device: - * - * ```js - * function touchStarted() { - * // Code to run. - * } - * ``` - * - * The touches array will be updated with the most - * recent touch points when `touchStarted()` is called by p5.js: - * - * ```js - * function touchStarted() { - * // Paint over the background. - * background(200); - * - * // Mark each touch point once with a circle. - * for (let touch of touches) { - * circle(touch.x, touch.y, 40); - * } - * } - * ``` - * - * The parameter, event, is optional. `touchStarted()` will be passed a - * TouchEvent - * object with properties that describe the touch event: - * - * ```js - * function touchStarted(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * On touchscreen devices, mousePressed() will - * run when a user’s touch starts if `touchStarted()` isn’t declared. If - * `touchStarted()` is declared, then `touchStarted()` will run when a user’s - * touch starts and mousePressed() won’t. - * - * Note: `touchStarted()`, touchEnded(), and - * touchMoved() are all related. - * `touchStarted()` runs as soon as the user touches a touchscreen device. - * touchEnded() runs as soon as the user ends a - * touch. touchMoved() runs repeatedly as the - * user moves any touch points. - * - * @method touchStarted - * @param {TouchEvent} [event] optional `TouchEvent` argument. - * - * @example - *
    - * - * // On a touchscreen device, touch the canvas using one or more fingers - * // at the same time. - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square switches color between black and white each time the user touches the screen.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * // Toggle colors with each touch. - * function touchStarted() { - * if (value === 0) { - * value = 255; - * } else { - * value = 0; - * } - * } - * - *
    - * - *
    - * - * // On a touchscreen device, touch the canvas using one or more fingers - * // at the same time. - * - * let bgColor = 50; - * let fillColor = 255; - * let borderWidth = 0.5; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with the number 0 at the top-center. The number tracks the number of places the user is touching the screen. Circles appear at each touch point and change style in response to events.' - * ); - * } - * - * function draw() { - * background(bgColor); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * fill(0); - * noStroke(); - * - * // Display the number of touch points. - * text(touches.length, 50, 20); - * - * // Style the touch points. - * fill(fillColor); - * stroke(0); - * strokeWeight(borderWidth); - * - * // Display the touch points as circles. - * for (let touch of touches) { - * circle(touch.x, touch.y, 40); - * } - * } - * - * // Set the background color to a random grayscale value. - * function touchStarted() { - * bgColor = random(80, 255); - * } - * - * // Set the fill color to a random grayscale value. - * function touchEnded() { - * fillColor = random(0, 255); - * } - * - * // Set the stroke weight. - * function touchMoved() { - * // Increment the border width. - * borderWidth += 0.1; - * - * // Reset the border width once it's too thick. - * if (borderWidth > 20) { - * borderWidth = 0.5; - * } - * } - * - *
    - */ -p5.prototype._ontouchstart = function(e) { - const context = this._isGlobal ? window : this; - let executeDefault; - this._setProperty('mouseIsPressed', true); - this._updateTouchCoords(e); - this._updateNextMouseCoords(e); - this._updateMouseCoords(); // reset pmouseXY at the start of each touch event - - if (typeof context.touchStarted === 'function') { - executeDefault = context.touchStarted(e); - if (executeDefault === false) { - e.preventDefault(); - } - this.touchstart = true; + function getTouchInfo(canvas, w, h, e, i = 0) { + const rect = canvas.getBoundingClientRect(); + const sx = canvas.scrollWidth / w || 1; + const sy = canvas.scrollHeight / h || 1; + const touch = e.touches[i] || e.changedTouches[i]; + return { + x: (touch.clientX - rect.left) / sx, + y: (touch.clientY - rect.top) / sy, + winX: touch.clientX, + winY: touch.clientY, + id: touch.identifier + }; } -}; -/** - * A function that's called when the user touches the screen and moves. - * - * Declaring the function `touchMoved()` sets a code block to run - * automatically when the user touches a touchscreen device and moves: - * - * ```js - * function touchMoved() { - * // Code to run. - * } - * ``` - * - * The touches array will be updated with the most - * recent touch points when `touchMoved()` is called by p5.js: - * - * ```js - * function touchMoved() { - * // Paint over the background. - * background(200); - * - * // Mark each touch point while the user moves. - * for (let touch of touches) { - * circle(touch.x, touch.y, 40); - * } - * } - * ``` - * - * The parameter, event, is optional. `touchMoved()` will be passed a - * TouchEvent - * object with properties that describe the touch event: - * - * ```js - * function touchMoved(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * On touchscreen devices, mouseDragged() will - * run when the user’s touch points move if `touchMoved()` isn’t declared. If - * `touchMoved()` is declared, then `touchMoved()` will run when a user’s - * touch points move and mouseDragged() won’t. - * - * Note: touchStarted(), - * touchEnded(), and - * `touchMoved()` are all related. - * touchStarted() runs as soon as the user - * touches a touchscreen device. touchEnded() - * runs as soon as the user ends a touch. `touchMoved()` runs repeatedly as - * the user moves any touch points. - * - * @method touchMoved - * @param {TouchEvent} [event] optional TouchEvent argument. - * - * @example - *
    - * - * // On a touchscreen device, touch the canvas using one or more fingers - * // at the same time. - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square becomes lighter when the user touches the screen and moves.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * function touchMoved() { - * // Update the grayscale value. - * value += 5; - * - * // Reset the grayscale value. - * if (value > 255) { - * value = 0; - * } - * } - * - *
    - * - *
    - * - * // On a touchscreen device, touch the canvas using one or more fingers - * // at the same time. - * - * let bgColor = 50; - * let fillColor = 255; - * let borderWidth = 0.5; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with the number 0 at the top-center. The number tracks the number of places the user is touching the screen. Circles appear at each touch point and change style in response to events.' - * ); - * } - * - * function draw() { - * background(bgColor); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * fill(0); - * noStroke(); - * - * // Display the number of touch points. - * text(touches.length, 50, 20); - * - * // Style the touch points. - * fill(fillColor); - * stroke(0); - * strokeWeight(borderWidth); - * - * // Display the touch points as circles. - * for (let touch of touches) { - * circle(touch.x, touch.y, 40); - * } - * } - * - * // Set the background color to a random grayscale value. - * function touchStarted() { - * bgColor = random(80, 255); - * } - * - * // Set the fill color to a random grayscale value. - * function touchEnded() { - * fillColor = random(0, 255); - * } - * - * // Set the stroke weight. - * function touchMoved() { - * // Increment the border width. - * borderWidth += 0.1; - * - * // Reset the border width once it's too thick. - * if (borderWidth > 20) { - * borderWidth = 0.5; - * } - * } - * - *
    - */ -p5.prototype._ontouchmove = function(e) { - const context = this._isGlobal ? window : this; - let executeDefault; - this._updateTouchCoords(e); - this._updateNextMouseCoords(e); - if (typeof context.touchMoved === 'function') { - executeDefault = context.touchMoved(e); - if (executeDefault === false) { - e.preventDefault(); + /** + * A function that's called once each time the user touches the screen. + * + * Declaring a function called `touchStarted()` sets a code block to run + * automatically each time the user begins touching a touchscreen device: + * + * ```js + * function touchStarted() { + * // Code to run. + * } + * ``` + * + * The touches array will be updated with the most + * recent touch points when `touchStarted()` is called by p5.js: + * + * ```js + * function touchStarted() { + * // Paint over the background. + * background(200); + * + * // Mark each touch point once with a circle. + * for (let touch of touches) { + * circle(touch.x, touch.y, 40); + * } + * } + * ``` + * + * The parameter, event, is optional. `touchStarted()` will be passed a + * TouchEvent + * object with properties that describe the touch event: + * + * ```js + * function touchStarted(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * On touchscreen devices, mousePressed() will + * run when a user’s touch starts if `touchStarted()` isn’t declared. If + * `touchStarted()` is declared, then `touchStarted()` will run when a user’s + * touch starts and mousePressed() won’t. + * + * Note: `touchStarted()`, touchEnded(), and + * touchMoved() are all related. + * `touchStarted()` runs as soon as the user touches a touchscreen device. + * touchEnded() runs as soon as the user ends a + * touch. touchMoved() runs repeatedly as the + * user moves any touch points. + * + * @method touchStarted + * @param {TouchEvent} [event] optional `TouchEvent` argument. + * + * @example + *
    + * + * // On a touchscreen device, touch the canvas using one or more fingers + * // at the same time. + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square switches color between black and white each time the user touches the screen.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * // Toggle colors with each touch. + * function touchStarted() { + * if (value === 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * + *
    + * + *
    + * + * // On a touchscreen device, touch the canvas using one or more fingers + * // at the same time. + * + * let bgColor = 50; + * let fillColor = 255; + * let borderWidth = 0.5; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with the number 0 at the top-center. The number tracks the number of places the user is touching the screen. Circles appear at each touch point and change style in response to events.' + * ); + * } + * + * function draw() { + * background(bgColor); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * fill(0); + * noStroke(); + * + * // Display the number of touch points. + * text(touches.length, 50, 20); + * + * // Style the touch points. + * fill(fillColor); + * stroke(0); + * strokeWeight(borderWidth); + * + * // Display the touch points as circles. + * for (let touch of touches) { + * circle(touch.x, touch.y, 40); + * } + * } + * + * // Set the background color to a random grayscale value. + * function touchStarted() { + * bgColor = random(80, 255); + * } + * + * // Set the fill color to a random grayscale value. + * function touchEnded() { + * fillColor = random(0, 255); + * } + * + * // Set the stroke weight. + * function touchMoved() { + * // Increment the border width. + * borderWidth += 0.1; + * + * // Reset the border width once it's too thick. + * if (borderWidth > 20) { + * borderWidth = 0.5; + * } + * } + * + *
    + */ + fn._ontouchstart = function(e) { + const context = this._isGlobal ? window : this; + let executeDefault; + this._setProperty('mouseIsPressed', true); + this._updateTouchCoords(e); + this._updateNextMouseCoords(e); + this._updateMouseCoords(); // reset pmouseXY at the start of each touch event + + if (typeof context.touchStarted === 'function') { + executeDefault = context.touchStarted(e); + if (executeDefault === false) { + e.preventDefault(); + } + this.touchstart = true; } - } else if (typeof context.mouseDragged === 'function') { - executeDefault = context.mouseDragged(e); - if (executeDefault === false) { - e.preventDefault(); + }; + + /** + * A function that's called when the user touches the screen and moves. + * + * Declaring the function `touchMoved()` sets a code block to run + * automatically when the user touches a touchscreen device and moves: + * + * ```js + * function touchMoved() { + * // Code to run. + * } + * ``` + * + * The touches array will be updated with the most + * recent touch points when `touchMoved()` is called by p5.js: + * + * ```js + * function touchMoved() { + * // Paint over the background. + * background(200); + * + * // Mark each touch point while the user moves. + * for (let touch of touches) { + * circle(touch.x, touch.y, 40); + * } + * } + * ``` + * + * The parameter, event, is optional. `touchMoved()` will be passed a + * TouchEvent + * object with properties that describe the touch event: + * + * ```js + * function touchMoved(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * On touchscreen devices, mouseDragged() will + * run when the user’s touch points move if `touchMoved()` isn’t declared. If + * `touchMoved()` is declared, then `touchMoved()` will run when a user’s + * touch points move and mouseDragged() won’t. + * + * Note: touchStarted(), + * touchEnded(), and + * `touchMoved()` are all related. + * touchStarted() runs as soon as the user + * touches a touchscreen device. touchEnded() + * runs as soon as the user ends a touch. `touchMoved()` runs repeatedly as + * the user moves any touch points. + * + * @method touchMoved + * @param {TouchEvent} [event] optional TouchEvent argument. + * + * @example + *
    + * + * // On a touchscreen device, touch the canvas using one or more fingers + * // at the same time. + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square becomes lighter when the user touches the screen and moves.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * function touchMoved() { + * // Update the grayscale value. + * value += 5; + * + * // Reset the grayscale value. + * if (value > 255) { + * value = 0; + * } + * } + * + *
    + * + *
    + * + * // On a touchscreen device, touch the canvas using one or more fingers + * // at the same time. + * + * let bgColor = 50; + * let fillColor = 255; + * let borderWidth = 0.5; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with the number 0 at the top-center. The number tracks the number of places the user is touching the screen. Circles appear at each touch point and change style in response to events.' + * ); + * } + * + * function draw() { + * background(bgColor); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * fill(0); + * noStroke(); + * + * // Display the number of touch points. + * text(touches.length, 50, 20); + * + * // Style the touch points. + * fill(fillColor); + * stroke(0); + * strokeWeight(borderWidth); + * + * // Display the touch points as circles. + * for (let touch of touches) { + * circle(touch.x, touch.y, 40); + * } + * } + * + * // Set the background color to a random grayscale value. + * function touchStarted() { + * bgColor = random(80, 255); + * } + * + * // Set the fill color to a random grayscale value. + * function touchEnded() { + * fillColor = random(0, 255); + * } + * + * // Set the stroke weight. + * function touchMoved() { + * // Increment the border width. + * borderWidth += 0.1; + * + * // Reset the border width once it's too thick. + * if (borderWidth > 20) { + * borderWidth = 0.5; + * } + * } + * + *
    + */ + fn._ontouchmove = function(e) { + const context = this._isGlobal ? window : this; + let executeDefault; + this._updateTouchCoords(e); + this._updateNextMouseCoords(e); + if (typeof context.touchMoved === 'function') { + executeDefault = context.touchMoved(e); + if (executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.mouseDragged === 'function') { + executeDefault = context.mouseDragged(e); + if (executeDefault === false) { + e.preventDefault(); + } } - } -}; + }; -/** - * A function that's called once each time a screen touch ends. - * - * Declaring the function `touchEnded()` sets a code block to run - * automatically when the user stops touching a touchscreen device: - * - * ```js - * function touchEnded() { - * // Code to run. - * } - * ``` - * - * The touches array will be updated with the most - * recent touch points when `touchEnded()` is called by p5.js: - * - * ```js - * function touchEnded() { - * // Paint over the background. - * background(200); - * - * // Mark each remaining touch point when the user stops - * // a touch. - * for (let touch of touches) { - * circle(touch.x, touch.y, 40); - * } - * } - * ``` - * - * The parameter, event, is optional. `touchEnded()` will be passed a - * TouchEvent - * object with properties that describe the touch event: - * - * ```js - * function touchEnded(event) { - * // Code to run that uses the event. - * console.log(event); - * } - * ``` - * - * On touchscreen devices, mouseReleased() will - * run when the user’s touch ends if `touchEnded()` isn’t declared. If - * `touchEnded()` is declared, then `touchEnded()` will run when a user’s - * touch ends and mouseReleased() won’t. - * - * Note: touchStarted(), - * `touchEnded()`, and touchMoved() are all - * related. touchStarted() runs as soon as the - * user touches a touchscreen device. `touchEnded()` runs as soon as the user - * ends a touch. touchMoved() runs repeatedly as - * the user moves any touch points. - * - * @method touchEnded - * @param {TouchEvent} [event] optional `TouchEvent` argument. - * - * @example - *
    - * - * // On a touchscreen device, touch the canvas using one or more fingers - * // at the same time. - * - * let value = 0; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with a black square at its center. The inner square switches color between black and white each time the user stops touching the screen.' - * ); - * } - * - * function draw() { - * background(200); - * - * // Style the square. - * fill(value); - * - * // Draw the square. - * square(25, 25, 50); - * } - * - * // Toggle colors when a touch ends. - * function touchEnded() { - * if (value === 0) { - * value = 255; - * } else { - * value = 0; - * } - * } - * - *
    - * - *
    - * - * // On a touchscreen device, touch the canvas using one or more fingers - * // at the same time. - * - * let bgColor = 50; - * let fillColor = 255; - * let borderWidth = 0.5; - * - * function setup() { - * createCanvas(100, 100); - * - * describe( - * 'A gray square with the number 0 at the top-center. The number tracks the number of places the user is touching the screen. Circles appear at each touch point and change style in response to events.' - * ); - * } - * - * function draw() { - * background(bgColor); - * - * // Style the text. - * textAlign(CENTER); - * textSize(16); - * fill(0); - * noStroke(); - * - * // Display the number of touch points. - * text(touches.length, 50, 20); - * - * // Style the touch points. - * fill(fillColor); - * stroke(0); - * strokeWeight(borderWidth); - * - * // Display the touch points as circles. - * for (let touch of touches) { - * circle(touch.x, touch.y, 40); - * } - * } - * - * // Set the background color to a random grayscale value. - * function touchStarted() { - * bgColor = random(80, 255); - * } - * - * // Set the fill color to a random grayscale value. - * function touchEnded() { - * fillColor = random(0, 255); - * } - * - * // Set the stroke weight. - * function touchMoved() { - * // Increment the border width. - * borderWidth += 0.1; - * - * // Reset the border width once it's too thick. - * if (borderWidth > 20) { - * borderWidth = 0.5; - * } - * } - * - *
    - */ -p5.prototype._ontouchend = function(e) { - this._setProperty('mouseIsPressed', false); - this._updateTouchCoords(e); - this._updateNextMouseCoords(e); - const context = this._isGlobal ? window : this; - let executeDefault; - if (typeof context.touchEnded === 'function') { - executeDefault = context.touchEnded(e); - if (executeDefault === false) { - e.preventDefault(); + /** + * A function that's called once each time a screen touch ends. + * + * Declaring the function `touchEnded()` sets a code block to run + * automatically when the user stops touching a touchscreen device: + * + * ```js + * function touchEnded() { + * // Code to run. + * } + * ``` + * + * The touches array will be updated with the most + * recent touch points when `touchEnded()` is called by p5.js: + * + * ```js + * function touchEnded() { + * // Paint over the background. + * background(200); + * + * // Mark each remaining touch point when the user stops + * // a touch. + * for (let touch of touches) { + * circle(touch.x, touch.y, 40); + * } + * } + * ``` + * + * The parameter, event, is optional. `touchEnded()` will be passed a + * TouchEvent + * object with properties that describe the touch event: + * + * ```js + * function touchEnded(event) { + * // Code to run that uses the event. + * console.log(event); + * } + * ``` + * + * On touchscreen devices, mouseReleased() will + * run when the user’s touch ends if `touchEnded()` isn’t declared. If + * `touchEnded()` is declared, then `touchEnded()` will run when a user’s + * touch ends and mouseReleased() won’t. + * + * Note: touchStarted(), + * `touchEnded()`, and touchMoved() are all + * related. touchStarted() runs as soon as the + * user touches a touchscreen device. `touchEnded()` runs as soon as the user + * ends a touch. touchMoved() runs repeatedly as + * the user moves any touch points. + * + * @method touchEnded + * @param {TouchEvent} [event] optional `TouchEvent` argument. + * + * @example + *
    + * + * // On a touchscreen device, touch the canvas using one or more fingers + * // at the same time. + * + * let value = 0; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with a black square at its center. The inner square switches color between black and white each time the user stops touching the screen.' + * ); + * } + * + * function draw() { + * background(200); + * + * // Style the square. + * fill(value); + * + * // Draw the square. + * square(25, 25, 50); + * } + * + * // Toggle colors when a touch ends. + * function touchEnded() { + * if (value === 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * + *
    + * + *
    + * + * // On a touchscreen device, touch the canvas using one or more fingers + * // at the same time. + * + * let bgColor = 50; + * let fillColor = 255; + * let borderWidth = 0.5; + * + * function setup() { + * createCanvas(100, 100); + * + * describe( + * 'A gray square with the number 0 at the top-center. The number tracks the number of places the user is touching the screen. Circles appear at each touch point and change style in response to events.' + * ); + * } + * + * function draw() { + * background(bgColor); + * + * // Style the text. + * textAlign(CENTER); + * textSize(16); + * fill(0); + * noStroke(); + * + * // Display the number of touch points. + * text(touches.length, 50, 20); + * + * // Style the touch points. + * fill(fillColor); + * stroke(0); + * strokeWeight(borderWidth); + * + * // Display the touch points as circles. + * for (let touch of touches) { + * circle(touch.x, touch.y, 40); + * } + * } + * + * // Set the background color to a random grayscale value. + * function touchStarted() { + * bgColor = random(80, 255); + * } + * + * // Set the fill color to a random grayscale value. + * function touchEnded() { + * fillColor = random(0, 255); + * } + * + * // Set the stroke weight. + * function touchMoved() { + * // Increment the border width. + * borderWidth += 0.1; + * + * // Reset the border width once it's too thick. + * if (borderWidth > 20) { + * borderWidth = 0.5; + * } + * } + * + *
    + */ + fn._ontouchend = function(e) { + this._setProperty('mouseIsPressed', false); + this._updateTouchCoords(e); + this._updateNextMouseCoords(e); + const context = this._isGlobal ? window : this; + let executeDefault; + if (typeof context.touchEnded === 'function') { + executeDefault = context.touchEnded(e); + if (executeDefault === false) { + e.preventDefault(); + } + this.touchend = true; } - this.touchend = true; - } -}; + }; +} + +export default touch; -export default p5; +if(typeof p5 !== 'undefined'){ + touch(p5, p5.prototype); +} diff --git a/src/image/image.js b/src/image/image.js index 106d59b23d..b8a9034cd0 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -9,718 +9,723 @@ * This module defines the p5 methods for the p5.Image class * for drawing images to the main display canvas. */ -import p5 from '../core/main'; import * as omggif from 'omggif'; -/** - * Creates a new p5.Image object. - * - * `createImage()` uses the `width` and `height` parameters to set the new - * p5.Image object's dimensions in pixels. The new - * p5.Image can be modified by updating its - * pixels array or by calling its - * get() and - * set() methods. The - * loadPixels() method must be called - * before reading or modifying pixel values. The - * updatePixels() method must be called - * for updates to take effect. - * - * Note: The new p5.Image object is transparent by - * default. - * - * @method createImage - * @param {Integer} width width in pixels. - * @param {Integer} height height in pixels. - * @return {p5.Image} new p5.Image object. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Image object. - * let img = createImage(66, 66); - * - * // Load the image's pixels into memory. - * img.loadPixels(); - * - * // Set all the image's pixels to black. - * for (let x = 0; x < img.width; x += 1) { - * for (let y = 0; y < img.height; y += 1) { - * img.set(x, y, 0); - * } - * } - * - * // Update the image's pixel values. - * img.updatePixels(); - * - * // Draw the image. - * image(img, 17, 17); - * - * describe('A black square drawn in the middle of a gray square.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Image object. - * let img = createImage(66, 66); - * - * // Load the image's pixels into memory. - * img.loadPixels(); - * - * // Create a color gradient. - * for (let x = 0; x < img.width; x += 1) { - * for (let y = 0; y < img.height; y += 1) { - * // Calculate the transparency. - * let a = map(x, 0, img.width, 0, 255); - * - * // Create a p5.Color object. - * let c = color(0, a); - * - * // Set the pixel's color. - * img.set(x, y, c); - * } - * } - * - * // Update the image's pixels. - * img.updatePixels(); - * - * // Display the image. - * image(img, 17, 17); - * - * describe('A square with a horizontal color gradient that transitions from gray to black.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Image object. - * let img = createImage(66, 66); - * - * // Load the pixels into memory. - * img.loadPixels(); - * // Get the current pixel density. - * let d = pixelDensity(); - * - * // Calculate the pixel that is halfway through the image's pixel array. - * let halfImage = 4 * (d * img.width) * (d * img.height / 2); - * - * // Set half of the image's pixels to black. - * for (let i = 0; i < halfImage; i += 4) { - * // Red. - * img.pixels[i] = 0; - * // Green. - * img.pixels[i + 1] = 0; - * // Blue. - * img.pixels[i + 2] = 0; - * // Alpha. - * img.pixels[i + 3] = 255; - * } - * - * // Update the image's pixels. - * img.updatePixels(); - * - * // Display the image. - * image(img, 17, 17); - * - * describe('A black square drawn in the middle of a gray square.'); - * } - * - *
    - */ -p5.prototype.createImage = function(width, height) { - p5._validateParameters('createImage', arguments); - return new p5.Image(width, height); -}; +function image(p5, fn){ + /** + * Creates a new p5.Image object. + * + * `createImage()` uses the `width` and `height` parameters to set the new + * p5.Image object's dimensions in pixels. The new + * p5.Image can be modified by updating its + * pixels array or by calling its + * get() and + * set() methods. The + * loadPixels() method must be called + * before reading or modifying pixel values. The + * updatePixels() method must be called + * for updates to take effect. + * + * Note: The new p5.Image object is transparent by + * default. + * + * @method createImage + * @param {Integer} width width in pixels. + * @param {Integer} height height in pixels. + * @return {p5.Image} new p5.Image object. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Image object. + * let img = createImage(66, 66); + * + * // Load the image's pixels into memory. + * img.loadPixels(); + * + * // Set all the image's pixels to black. + * for (let x = 0; x < img.width; x += 1) { + * for (let y = 0; y < img.height; y += 1) { + * img.set(x, y, 0); + * } + * } + * + * // Update the image's pixel values. + * img.updatePixels(); + * + * // Draw the image. + * image(img, 17, 17); + * + * describe('A black square drawn in the middle of a gray square.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Image object. + * let img = createImage(66, 66); + * + * // Load the image's pixels into memory. + * img.loadPixels(); + * + * // Create a color gradient. + * for (let x = 0; x < img.width; x += 1) { + * for (let y = 0; y < img.height; y += 1) { + * // Calculate the transparency. + * let a = map(x, 0, img.width, 0, 255); + * + * // Create a p5.Color object. + * let c = color(0, a); + * + * // Set the pixel's color. + * img.set(x, y, c); + * } + * } + * + * // Update the image's pixels. + * img.updatePixels(); + * + * // Display the image. + * image(img, 17, 17); + * + * describe('A square with a horizontal color gradient that transitions from gray to black.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Image object. + * let img = createImage(66, 66); + * + * // Load the pixels into memory. + * img.loadPixels(); + * // Get the current pixel density. + * let d = pixelDensity(); + * + * // Calculate the pixel that is halfway through the image's pixel array. + * let halfImage = 4 * (d * img.width) * (d * img.height / 2); + * + * // Set half of the image's pixels to black. + * for (let i = 0; i < halfImage; i += 4) { + * // Red. + * img.pixels[i] = 0; + * // Green. + * img.pixels[i + 1] = 0; + * // Blue. + * img.pixels[i + 2] = 0; + * // Alpha. + * img.pixels[i + 3] = 255; + * } + * + * // Update the image's pixels. + * img.updatePixels(); + * + * // Display the image. + * image(img, 17, 17); + * + * describe('A black square drawn in the middle of a gray square.'); + * } + * + *
    + */ + fn.createImage = function(width, height) { + p5._validateParameters('createImage', arguments); + return new p5.Image(width, height); + }; -/** - * Saves the current canvas as an image. - * - * By default, `saveCanvas()` saves the canvas as a PNG image called - * `untitled.png`. - * - * The first parameter, `filename`, is optional. It's a string that sets the - * file's name. If a file extension is included, as in - * `saveCanvas('drawing.png')`, then the image will be saved using that - * format. - * - * The second parameter, `extension`, is also optional. It sets the files format. - * Either `'png'`, `'webp'`, or `'jpg'` can be used. For example, `saveCanvas('drawing', 'jpg')` - * saves the canvas to a file called `drawing.jpg`. - * - * Note: The browser will either save the file immediately or prompt the user - * with a dialogue window. - * - * @method saveCanvas - * @param {p5.Framebuffer|p5.Element|HTMLCanvasElement} selectedCanvas reference to a - * specific HTML5 canvas element. - * @param {String} [filename] file name. Defaults to 'untitled'. - * @param {String} [extension] file extension, either 'png', 'webp', or 'jpg'. Defaults to 'png'. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * background(255); - * - * // Save the canvas to 'untitled.png'. - * saveCanvas(); - * - * describe('A white square.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(255); - * - * // Save the canvas to 'myCanvas.jpg'. - * saveCanvas('myCanvas.jpg'); - * - * describe('A white square.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(255); - * - * // Save the canvas to 'myCanvas.jpg'. - * saveCanvas('myCanvas', 'jpg'); - * - * describe('A white square.'); - * } - * - *
    - * - *
    - * - * function setup() { - * let cnv = createCanvas(100, 100); - * - * background(255); - * - * // Save the canvas to 'untitled.png'. - * saveCanvas(cnv); - * - * describe('A white square.'); - * } - * - *
    - * - *
    - * - * function setup() { - * let cnv = createCanvas(100, 100); - * - * background(255); - * - * // Save the canvas to 'myCanvas.jpg'. - * saveCanvas(cnv, 'myCanvas.jpg'); - * - * describe('A white square.'); - * } - * - *
    - * - *
    - * - * function setup() { - * let cnv = createCanvas(100, 100); - * - * background(255); - * - * // Save the canvas to 'myCanvas.jpg'. - * saveCanvas(cnv, 'myCanvas', 'jpg'); - * - * describe('A white square.'); - * } - * - *
    - */ -/** - * @method saveCanvas - * @param {String} [filename] - * @param {String} [extension] - */ -p5.prototype.saveCanvas = function(...args) { - p5._validateParameters('saveCanvas', args); - - // copy arguments to array - let htmlCanvas, filename, extension, temporaryGraphics; - - if (args[0] instanceof HTMLCanvasElement) { - htmlCanvas = args[0]; - args.shift(); - } else if (args[0] instanceof p5.Element) { - htmlCanvas = args[0].elt; - args.shift(); - } else if (args[0] instanceof p5.Framebuffer) { - const framebuffer = args[0]; - temporaryGraphics = this.createGraphics(framebuffer.width, - framebuffer.height); - temporaryGraphics.pixelDensity(pixelDensity()); - framebuffer.loadPixels(); - temporaryGraphics.loadPixels(); - temporaryGraphics.pixels.set(framebuffer.pixels); - temporaryGraphics.updatePixels(); - - htmlCanvas = temporaryGraphics.elt; - args.shift(); - } else { - htmlCanvas = this._curElement && this._curElement.elt; - } - - if (args.length >= 1) { - filename = args[0]; - } - if (args.length >= 2) { - extension = args[1]; - } - - extension = - extension || - p5.prototype._checkFileExtension(filename, extension)[1] || - 'png'; - - let mimeType; - switch (extension) { - default: - //case 'png': - mimeType = 'image/png'; - break; - case 'webp': - mimeType = 'image/webp'; - break; - case 'jpeg': - case 'jpg': - mimeType = 'image/jpeg'; - break; - } - - htmlCanvas.toBlob(blob => { - p5.prototype.downloadFile(blob, filename, extension); - if(temporaryGraphics) temporaryGraphics.remove(); - }, mimeType); -}; - -// 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 - let loopLimit = props.loopLimit; - if (loopLimit === 1) { - loopLimit = null; - } else if (loopLimit === null) { - loopLimit = 0; - } - const buffer = new Uint8Array(pImg.width * pImg.height * props.numFrames); - - const allFramesPixelColors = []; - - // Used to determine the occurrence of unique palettes and the frames - // which use them - const paletteFreqsAndFrames = {}; - - // Pass 1: - //loop over frames and get the frequency of each palette - for (let i = 0; i < props.numFrames; i++) { - const paletteSet = new Set(); - const data = props.frames[i].image.data; - const dataLength = data.length; - // The color for each pixel in this frame ( for easier lookup later ) - const pixelColors = new Uint32Array(pImg.width * pImg.height); - for (let j = 0, k = 0; j < dataLength; j += 4, k++) { - const r = data[j + 0]; - const g = data[j + 1]; - const b = data[j + 2]; - const color = (r << 16) | (g << 8) | (b << 0); - paletteSet.add(color); - - // What color does this pixel have in this frame ? - pixelColors[k] = color; + /** + * Saves the current canvas as an image. + * + * By default, `saveCanvas()` saves the canvas as a PNG image called + * `untitled.png`. + * + * The first parameter, `filename`, is optional. It's a string that sets the + * file's name. If a file extension is included, as in + * `saveCanvas('drawing.png')`, then the image will be saved using that + * format. + * + * The second parameter, `extension`, is also optional. It sets the files format. + * Either `'png'`, `'webp'`, or `'jpg'` can be used. For example, `saveCanvas('drawing', 'jpg')` + * saves the canvas to a file called `drawing.jpg`. + * + * Note: The browser will either save the file immediately or prompt the user + * with a dialogue window. + * + * @method saveCanvas + * @param {p5.Framebuffer|p5.Element|HTMLCanvasElement} selectedCanvas reference to a + * specific HTML5 canvas element. + * @param {String} [filename] file name. Defaults to 'untitled'. + * @param {String} [extension] file extension, either 'png', 'webp', or 'jpg'. Defaults to 'png'. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * background(255); + * + * // Save the canvas to 'untitled.png'. + * saveCanvas(); + * + * describe('A white square.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(255); + * + * // Save the canvas to 'myCanvas.jpg'. + * saveCanvas('myCanvas.jpg'); + * + * describe('A white square.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(255); + * + * // Save the canvas to 'myCanvas.jpg'. + * saveCanvas('myCanvas', 'jpg'); + * + * describe('A white square.'); + * } + * + *
    + * + *
    + * + * function setup() { + * let cnv = createCanvas(100, 100); + * + * background(255); + * + * // Save the canvas to 'untitled.png'. + * saveCanvas(cnv); + * + * describe('A white square.'); + * } + * + *
    + * + *
    + * + * function setup() { + * let cnv = createCanvas(100, 100); + * + * background(255); + * + * // Save the canvas to 'myCanvas.jpg'. + * saveCanvas(cnv, 'myCanvas.jpg'); + * + * describe('A white square.'); + * } + * + *
    + * + *
    + * + * function setup() { + * let cnv = createCanvas(100, 100); + * + * background(255); + * + * // Save the canvas to 'myCanvas.jpg'. + * saveCanvas(cnv, 'myCanvas', 'jpg'); + * + * describe('A white square.'); + * } + * + *
    + */ + /** + * @method saveCanvas + * @param {String} [filename] + * @param {String} [extension] + */ + fn.saveCanvas = function(...args) { + p5._validateParameters('saveCanvas', args); + + // copy arguments to array + let htmlCanvas, filename, extension, temporaryGraphics; + + if (args[0] instanceof HTMLCanvasElement) { + htmlCanvas = args[0]; + args.shift(); + } else if (args[0] instanceof p5.Element) { + htmlCanvas = args[0].elt; + args.shift(); + } else if (args[0] instanceof p5.Framebuffer) { + const framebuffer = args[0]; + temporaryGraphics = this.createGraphics(framebuffer.width, + framebuffer.height); + temporaryGraphics.pixelDensity(pixelDensity()); + framebuffer.loadPixels(); + temporaryGraphics.loadPixels(); + temporaryGraphics.pixels.set(framebuffer.pixels); + temporaryGraphics.updatePixels(); + + htmlCanvas = temporaryGraphics.elt; + args.shift(); + } else { + htmlCanvas = this._curElement && this._curElement.elt; } - // A way to put use the entire palette as an object key - const paletteStr = [...paletteSet].sort().toString(); - if (paletteFreqsAndFrames[paletteStr] === undefined) { - paletteFreqsAndFrames[paletteStr] = { freq: 1, frames: [i] }; - } else { - paletteFreqsAndFrames[paletteStr].freq += 1; - paletteFreqsAndFrames[paletteStr].frames.push(i); + if (args.length >= 1) { + filename = args[0]; + } + if (args.length >= 2) { + extension = args[1]; } - allFramesPixelColors.push(pixelColors); - } - - let framesUsingGlobalPalette = []; - - // Now to build the global palette - // Sort all the unique palettes in descending order of their occurrence - const palettesSortedByFreq = Object.keys(paletteFreqsAndFrames).sort(function( - a, - b - ) { - return paletteFreqsAndFrames[b].freq - paletteFreqsAndFrames[a].freq; - }); - - // The initial global palette is the one with the most occurrence - const globalPalette = palettesSortedByFreq[0] - .split(',') - .map(a => parseInt(a)); - - framesUsingGlobalPalette = framesUsingGlobalPalette.concat( - paletteFreqsAndFrames[globalPalette].frames - ); - - const globalPaletteSet = new Set(globalPalette); - - // Build a more complete global palette - // Iterate over the remaining palettes in the order of - // their occurrence and see if the colors in this palette which are - // not in the global palette can be added there, while keeping the length - // of the global palette <= 256 - for (let i = 1; i < palettesSortedByFreq.length; i++) { - const palette = palettesSortedByFreq[i].split(',').map(a => parseInt(a)); - - const difference = palette.filter(x => !globalPaletteSet.has(x)); - if (globalPalette.length + difference.length <= 256) { - for (let j = 0; j < difference.length; j++) { - globalPalette.push(difference[j]); - globalPaletteSet.add(difference[j]); - } + extension = + extension || + fn._checkFileExtension(filename, extension)[1] || + 'png'; - // All frames using this palette now use the global palette - framesUsingGlobalPalette = framesUsingGlobalPalette.concat( - paletteFreqsAndFrames[palettesSortedByFreq[i]].frames - ); + let mimeType; + switch (extension) { + default: + //case 'png': + mimeType = 'image/png'; + break; + case 'webp': + mimeType = 'image/webp'; + break; + case 'jpeg': + case 'jpg': + mimeType = 'image/jpeg'; + break; } - } - framesUsingGlobalPalette = new Set(framesUsingGlobalPalette); + htmlCanvas.toBlob(blob => { + fn.downloadFile(blob, filename, extension); + if(temporaryGraphics) temporaryGraphics.remove(); + }, mimeType); + }; - // Build a lookup table of the index of each color in the global palette - // Maps a color to its index - const globalIndicesLookup = {}; - for (let i = 0; i < globalPalette.length; i++) { - if (!globalIndicesLookup[globalPalette[i]]) { - globalIndicesLookup[globalPalette[i]] = i; + // 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. + fn.encodeAndDownloadGif = function(pImg, filename) { + const props = pImg.gifProperties; + + //convert loopLimit back into Netscape Block formatting + let loopLimit = props.loopLimit; + if (loopLimit === 1) { + loopLimit = null; + } else if (loopLimit === null) { + loopLimit = 0; } - } - - // force palette to be power of 2 - let powof2 = 1; - while (powof2 < globalPalette.length) { - powof2 <<= 1; - } - globalPalette.length = powof2; - - // global opts - const opts = { - loop: loopLimit, - palette: new Uint32Array(globalPalette) - }; - const gifWriter = new omggif.GifWriter(buffer, pImg.width, pImg.height, opts); - let previousFrame = {}; - - // Pass 2 - // Determine if the frame needs a local palette - // Also apply transparency optimization. This function will often blow up - // the size of a GIF if not for transparency. If a pixel in one frame has - // the same color in the previous frame, that pixel can be marked as - // transparent. We decide one particular color as transparent and make all - // transparent pixels take this color. This helps in later in compression. - for (let i = 0; i < props.numFrames; i++) { - const localPaletteRequired = !framesUsingGlobalPalette.has(i); - const palette = localPaletteRequired ? [] : globalPalette; - const pixelPaletteIndex = new Uint8Array(pImg.width * pImg.height); - - // Lookup table mapping color to its indices - const colorIndicesLookup = {}; - - // All the colors that cannot be marked transparent in this frame - const cannotBeTransparent = new Set(); - - allFramesPixelColors[i].forEach((color, k) => { - if (localPaletteRequired) { - if (colorIndicesLookup[color] === undefined) { - colorIndicesLookup[color] = palette.length; - palette.push(color); - } - pixelPaletteIndex[k] = colorIndicesLookup[color]; - } else { - pixelPaletteIndex[k] = globalIndicesLookup[color]; + const buffer = new Uint8Array(pImg.width * pImg.height * props.numFrames); + + const allFramesPixelColors = []; + + // Used to determine the occurrence of unique palettes and the frames + // which use them + const paletteFreqsAndFrames = {}; + + // Pass 1: + //loop over frames and get the frequency of each palette + for (let i = 0; i < props.numFrames; i++) { + const paletteSet = new Set(); + const data = props.frames[i].image.data; + const dataLength = data.length; + // The color for each pixel in this frame ( for easier lookup later ) + const pixelColors = new Uint32Array(pImg.width * pImg.height); + for (let j = 0, k = 0; j < dataLength; j += 4, k++) { + const r = data[j + 0]; + const g = data[j + 1]; + const b = data[j + 2]; + const color = (r << 16) | (g << 8) | (b << 0); + paletteSet.add(color); + + // What color does this pixel have in this frame ? + pixelColors[k] = color; } - if (i > 0) { - // If even one pixel of this color has changed in this frame - // from the previous frame, we cannot mark it as transparent - if (allFramesPixelColors[i - 1][k] !== color) { - cannotBeTransparent.add(color); - } + // A way to put use the entire palette as an object key + const paletteStr = [...paletteSet].sort().toString(); + if (paletteFreqsAndFrames[paletteStr] === undefined) { + paletteFreqsAndFrames[paletteStr] = { freq: 1, frames: [i] }; + } else { + paletteFreqsAndFrames[paletteStr].freq += 1; + paletteFreqsAndFrames[paletteStr].frames.push(i); } - }); - const frameOpts = {}; + allFramesPixelColors.push(pixelColors); + } - // Transparency optimization - const canBeTransparent = palette.filter(a => !cannotBeTransparent.has(a)); - if (canBeTransparent.length > 0) { - // Select a color to mark as transparent - const transparent = canBeTransparent[0]; - const transparentIndex = localPaletteRequired - ? colorIndicesLookup[transparent] - : globalIndicesLookup[transparent]; - if (i > 0) { - for (let k = 0; k < allFramesPixelColors[i].length; k++) { - // If this pixel in this frame has the same color in previous frame - if (allFramesPixelColors[i - 1][k] === allFramesPixelColors[i][k]) { - pixelPaletteIndex[k] = transparentIndex; - } + let framesUsingGlobalPalette = []; + + // Now to build the global palette + // Sort all the unique palettes in descending order of their occurrence + const palettesSortedByFreq = Object.keys(paletteFreqsAndFrames).sort(function( + a, + b + ) { + return paletteFreqsAndFrames[b].freq - paletteFreqsAndFrames[a].freq; + }); + + // The initial global palette is the one with the most occurrence + const globalPalette = palettesSortedByFreq[0] + .split(',') + .map(a => parseInt(a)); + + framesUsingGlobalPalette = framesUsingGlobalPalette.concat( + paletteFreqsAndFrames[globalPalette].frames + ); + + const globalPaletteSet = new Set(globalPalette); + + // Build a more complete global palette + // Iterate over the remaining palettes in the order of + // their occurrence and see if the colors in this palette which are + // not in the global palette can be added there, while keeping the length + // of the global palette <= 256 + for (let i = 1; i < palettesSortedByFreq.length; i++) { + const palette = palettesSortedByFreq[i].split(',').map(a => parseInt(a)); + + const difference = palette.filter(x => !globalPaletteSet.has(x)); + if (globalPalette.length + difference.length <= 256) { + for (let j = 0; j < difference.length; j++) { + globalPalette.push(difference[j]); + globalPaletteSet.add(difference[j]); } - frameOpts.transparent = transparentIndex; - // If this frame has any transparency, do not dispose the previous frame - previousFrame.frameOpts.disposal = 1; + + // All frames using this palette now use the global palette + framesUsingGlobalPalette = framesUsingGlobalPalette.concat( + paletteFreqsAndFrames[palettesSortedByFreq[i]].frames + ); } } - frameOpts.delay = props.frames[i].delay / 10; // Move timing back into GIF formatting - if (localPaletteRequired) { - // force palette to be power of 2 - let powof2 = 1; - while (powof2 < palette.length) { - powof2 <<= 1; + + framesUsingGlobalPalette = new Set(framesUsingGlobalPalette); + + // Build a lookup table of the index of each color in the global palette + // Maps a color to its index + const globalIndicesLookup = {}; + for (let i = 0; i < globalPalette.length; i++) { + if (!globalIndicesLookup[globalPalette[i]]) { + globalIndicesLookup[globalPalette[i]] = i; } - palette.length = powof2; - frameOpts.palette = new Uint32Array(palette); } - if (i > 0) { - // add the frame that came before the current one - gifWriter.addFrame( - 0, - 0, - pImg.width, - pImg.height, - previousFrame.pixelPaletteIndex, - previousFrame.frameOpts - ); + + // force palette to be power of 2 + let powof2 = 1; + while (powof2 < globalPalette.length) { + powof2 <<= 1; } - // previous frame object should now have details of this frame - previousFrame = { - pixelPaletteIndex, - frameOpts + globalPalette.length = powof2; + + // global opts + const opts = { + loop: loopLimit, + palette: new Uint32Array(globalPalette) }; - } - - previousFrame.frameOpts.disposal = 1; - // add the last frame - gifWriter.addFrame( - 0, - 0, - pImg.width, - pImg.height, - previousFrame.pixelPaletteIndex, - previousFrame.frameOpts - ); - - const extension = 'gif'; - const blob = new Blob([buffer.slice(0, gifWriter.end())], { - type: 'image/gif' - }); - p5.prototype.downloadFile(blob, filename, extension); -}; + const gifWriter = new omggif.GifWriter(buffer, pImg.width, pImg.height, opts); + let previousFrame = {}; + + // Pass 2 + // Determine if the frame needs a local palette + // Also apply transparency optimization. This function will often blow up + // the size of a GIF if not for transparency. If a pixel in one frame has + // the same color in the previous frame, that pixel can be marked as + // transparent. We decide one particular color as transparent and make all + // transparent pixels take this color. This helps in later in compression. + for (let i = 0; i < props.numFrames; i++) { + const localPaletteRequired = !framesUsingGlobalPalette.has(i); + const palette = localPaletteRequired ? [] : globalPalette; + const pixelPaletteIndex = new Uint8Array(pImg.width * pImg.height); + + // Lookup table mapping color to its indices + const colorIndicesLookup = {}; + + // All the colors that cannot be marked transparent in this frame + const cannotBeTransparent = new Set(); + + allFramesPixelColors[i].forEach((color, k) => { + if (localPaletteRequired) { + if (colorIndicesLookup[color] === undefined) { + colorIndicesLookup[color] = palette.length; + palette.push(color); + } + pixelPaletteIndex[k] = colorIndicesLookup[color]; + } else { + pixelPaletteIndex[k] = globalIndicesLookup[color]; + } -/** - * Captures a sequence of frames from the canvas that can be saved as images. - * - * `saveFrames()` creates an array of frame objects. Each frame is stored as - * an object with its file type, file name, and image data as a string. For - * example, the first saved frame might have the following properties: - * - * `{ ext: 'png', filenmame: 'frame0', imageData: 'data:image/octet-stream;base64, abc123' }`. - * - * The first parameter, `filename`, sets the prefix for the file names. For - * example, setting the prefix to `'frame'` would generate the image files - * `frame0.png`, `frame1.png`, and so on. - * - * The second parameter, `extension`, sets the file type to either `'png'` or - * `'jpg'`. - * - * The third parameter, `duration`, sets the duration to record in seconds. - * The maximum duration is 15 seconds. - * - * The fourth parameter, `framerate`, sets the number of frames to record per - * second. The maximum frame rate value is 22. Limits are placed on `duration` - * and `framerate` to avoid using too much memory. Recording large canvases - * can easily crash sketches or even web browsers. - * - * The fifth parameter, `callback`, is optional. If a function is passed, - * image files won't be saved by default. The callback function can be used - * to process an array containing the data for each captured frame. The array - * of image data contains a sequence of objects with three properties for each - * frame: `imageData`, `filename`, and `extension`. - * - * Note: Frames are downloaded as individual image files by default. - * - * @method saveFrames - * @param {String} filename prefix of file name. - * @param {String} extension file extension, either 'jpg' or 'png'. - * @param {Number} duration duration in seconds to record. This parameter will be constrained to be less or equal to 15. - * @param {Number} framerate number of frames to save per second. This parameter will be constrained to be less or equal to 22. - * @param {function(Array)} [callback] callback function that will be executed - to handle the image data. This function - should accept an array as argument. The - array will contain the specified number of - frames of objects. Each object has three - properties: `imageData`, `filename`, and `extension`. - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe('A square repeatedly changes color from blue to pink.'); - * } - * - * function draw() { - * let r = frameCount % 255; - * let g = 50; - * let b = 100; - * background(r, g, b); - * } - * - * // Save the frames when the user presses the 's' key. - * function keyPressed() { - * if (key === 's') { - * saveFrames('frame', 'png', 1, 5); - * } - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe('A square repeatedly changes color from blue to pink.'); - * } - * - * function draw() { - * let r = frameCount % 255; - * let g = 50; - * let b = 100; - * background(r, g, b); - * } - * - * // Print 5 frames when the user presses the mouse. - * function mousePressed() { - * saveFrames('frame', 'png', 1, 5, printFrames); - * } - * - * // Prints an array of objects containing raw image data, filenames, and extensions. - * function printFrames(frames) { - * for (let frame of frames) { - * print(frame); - * } - * } - * - *
    - */ -p5.prototype.saveFrames = function(fName, ext, _duration, _fps, callback) { - p5._validateParameters('saveFrames', arguments); - let duration = _duration || 3; - duration = p5.prototype.constrain(duration, 0, 15); - duration = duration * 1000; - let fps = _fps || 15; - fps = p5.prototype.constrain(fps, 0, 22); - let count = 0; - - const makeFrame = p5.prototype._makeFrame; - const cnv = this._curElement.elt; - let frames = []; - const frameFactory = setInterval(() => { - frames.push(makeFrame(fName + count, ext, cnv)); - count++; - }, 1000 / fps); - - setTimeout(() => { - clearInterval(frameFactory); - if (callback) { - callback(frames); - } else { - for (const f of frames) { - p5.prototype.downloadFile(f.imageData, f.filename, f.ext); + if (i > 0) { + // If even one pixel of this color has changed in this frame + // from the previous frame, we cannot mark it as transparent + if (allFramesPixelColors[i - 1][k] !== color) { + cannotBeTransparent.add(color); + } + } + }); + + const frameOpts = {}; + + // Transparency optimization + const canBeTransparent = palette.filter(a => !cannotBeTransparent.has(a)); + if (canBeTransparent.length > 0) { + // Select a color to mark as transparent + const transparent = canBeTransparent[0]; + const transparentIndex = localPaletteRequired + ? colorIndicesLookup[transparent] + : globalIndicesLookup[transparent]; + if (i > 0) { + for (let k = 0; k < allFramesPixelColors[i].length; k++) { + // If this pixel in this frame has the same color in previous frame + if (allFramesPixelColors[i - 1][k] === allFramesPixelColors[i][k]) { + pixelPaletteIndex[k] = transparentIndex; + } + } + frameOpts.transparent = transparentIndex; + // If this frame has any transparency, do not dispose the previous frame + previousFrame.frameOpts.disposal = 1; + } + } + frameOpts.delay = props.frames[i].delay / 10; // Move timing back into GIF formatting + if (localPaletteRequired) { + // force palette to be power of 2 + let powof2 = 1; + while (powof2 < palette.length) { + powof2 <<= 1; + } + palette.length = powof2; + frameOpts.palette = new Uint32Array(palette); } + if (i > 0) { + // add the frame that came before the current one + gifWriter.addFrame( + 0, + 0, + pImg.width, + pImg.height, + previousFrame.pixelPaletteIndex, + previousFrame.frameOpts + ); + } + // previous frame object should now have details of this frame + previousFrame = { + pixelPaletteIndex, + frameOpts + }; } - frames = []; // clear frames - }, duration + 0.01); -}; - -p5.prototype._makeFrame = function(filename, extension, _cnv) { - let cnv; - if (this) { - cnv = this._curElement.elt; - } else { - cnv = _cnv; - } - let mimeType; - if (!extension) { - extension = 'png'; - mimeType = 'image/png'; - } else { - switch (extension.toLowerCase()) { - case 'png': - mimeType = 'image/png'; - break; - case 'jpeg': - mimeType = 'image/jpeg'; - break; - case 'jpg': - mimeType = 'image/jpeg'; - break; - default: - mimeType = 'image/png'; - break; + + previousFrame.frameOpts.disposal = 1; + // add the last frame + gifWriter.addFrame( + 0, + 0, + pImg.width, + pImg.height, + previousFrame.pixelPaletteIndex, + previousFrame.frameOpts + ); + + const extension = 'gif'; + const blob = new Blob([buffer.slice(0, gifWriter.end())], { + type: 'image/gif' + }); + fn.downloadFile(blob, filename, extension); + }; + + /** + * Captures a sequence of frames from the canvas that can be saved as images. + * + * `saveFrames()` creates an array of frame objects. Each frame is stored as + * an object with its file type, file name, and image data as a string. For + * example, the first saved frame might have the following properties: + * + * `{ ext: 'png', filenmame: 'frame0', imageData: 'data:image/octet-stream;base64, abc123' }`. + * + * The first parameter, `filename`, sets the prefix for the file names. For + * example, setting the prefix to `'frame'` would generate the image files + * `frame0.png`, `frame1.png`, and so on. + * + * The second parameter, `extension`, sets the file type to either `'png'` or + * `'jpg'`. + * + * The third parameter, `duration`, sets the duration to record in seconds. + * The maximum duration is 15 seconds. + * + * The fourth parameter, `framerate`, sets the number of frames to record per + * second. The maximum frame rate value is 22. Limits are placed on `duration` + * and `framerate` to avoid using too much memory. Recording large canvases + * can easily crash sketches or even web browsers. + * + * The fifth parameter, `callback`, is optional. If a function is passed, + * image files won't be saved by default. The callback function can be used + * to process an array containing the data for each captured frame. The array + * of image data contains a sequence of objects with three properties for each + * frame: `imageData`, `filename`, and `extension`. + * + * Note: Frames are downloaded as individual image files by default. + * + * @method saveFrames + * @param {String} filename prefix of file name. + * @param {String} extension file extension, either 'jpg' or 'png'. + * @param {Number} duration duration in seconds to record. This parameter will be constrained to be less or equal to 15. + * @param {Number} framerate number of frames to save per second. This parameter will be constrained to be less or equal to 22. + * @param {function(Array)} [callback] callback function that will be executed + to handle the image data. This function + should accept an array as argument. The + array will contain the specified number of + frames of objects. Each object has three + properties: `imageData`, `filename`, and `extension`. + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe('A square repeatedly changes color from blue to pink.'); + * } + * + * function draw() { + * let r = frameCount % 255; + * let g = 50; + * let b = 100; + * background(r, g, b); + * } + * + * // Save the frames when the user presses the 's' key. + * function keyPressed() { + * if (key === 's') { + * saveFrames('frame', 'png', 1, 5); + * } + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe('A square repeatedly changes color from blue to pink.'); + * } + * + * function draw() { + * let r = frameCount % 255; + * let g = 50; + * let b = 100; + * background(r, g, b); + * } + * + * // Print 5 frames when the user presses the mouse. + * function mousePressed() { + * saveFrames('frame', 'png', 1, 5, printFrames); + * } + * + * // Prints an array of objects containing raw image data, filenames, and extensions. + * function printFrames(frames) { + * for (let frame of frames) { + * print(frame); + * } + * } + * + *
    + */ + fn.saveFrames = function(fName, ext, _duration, _fps, callback) { + p5._validateParameters('saveFrames', arguments); + let duration = _duration || 3; + duration = fn.constrain(duration, 0, 15); + duration = duration * 1000; + let fps = _fps || 15; + fps = fn.constrain(fps, 0, 22); + let count = 0; + + const makeFrame = fn._makeFrame; + const cnv = this._curElement.elt; + let frames = []; + const frameFactory = setInterval(() => { + frames.push(makeFrame(fName + count, ext, cnv)); + count++; + }, 1000 / fps); + + setTimeout(() => { + clearInterval(frameFactory); + if (callback) { + callback(frames); + } else { + for (const f of frames) { + fn.downloadFile(f.imageData, f.filename, f.ext); + } + } + frames = []; // clear frames + }, duration + 0.01); + }; + + fn._makeFrame = function(filename, extension, _cnv) { + let cnv; + if (this) { + cnv = this._curElement.elt; + } else { + cnv = _cnv; + } + let mimeType; + if (!extension) { + extension = 'png'; + mimeType = 'image/png'; + } else { + switch (extension.toLowerCase()) { + case 'png': + mimeType = 'image/png'; + break; + case 'jpeg': + mimeType = 'image/jpeg'; + break; + case 'jpg': + mimeType = 'image/jpeg'; + break; + default: + mimeType = 'image/png'; + break; + } } - } - const downloadMime = 'image/octet-stream'; - let imageData = cnv.toDataURL(mimeType); - imageData = imageData.replace(mimeType, downloadMime); - - const thisFrame = {}; - thisFrame.imageData = imageData; - thisFrame.filename = filename; - thisFrame.ext = extension; - return thisFrame; -}; - -export default p5; + const downloadMime = 'image/octet-stream'; + let imageData = cnv.toDataURL(mimeType); + imageData = imageData.replace(mimeType, downloadMime); + + const thisFrame = {}; + thisFrame.imageData = imageData; + thisFrame.filename = filename; + thisFrame.ext = extension; + return thisFrame; + }; +} + +export default image; + +if(typeof p5 !== 'undefined'){ + image(p5, p5.prototype); +} diff --git a/src/image/index.js b/src/image/index.js new file mode 100644 index 0000000000..b017b05544 --- /dev/null +++ b/src/image/index.js @@ -0,0 +1,11 @@ +import image from './image.js'; +import loadingDisplaying from './loading_displaying.js'; +import p5image from './p5.Image.js'; +import pixels from './pixels.js'; + +export default function(p5){ + p5.registerAddon(image); + p5.registerAddon(loadingDisplaying); + p5.registerAddon(p5image); + p5.registerAddon(pixels); +} diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 90184ab915..584cf7604b 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -5,1516 +5,1517 @@ * @requires core */ -import p5 from '../core/main'; import canvas from '../core/helpers'; import * as constants from '../core/constants'; import * as omggif from 'omggif'; import { GIFEncoder, quantize, nearestColorIndex } from 'gifenc'; -import '../core/friendly_errors/validate_params'; -import '../core/friendly_errors/file_errors'; -import '../core/friendly_errors/fes_core'; +function loadingDisplaying(p5, fn){ + /** + * Loads an image to create a p5.Image object. + * + * `loadImage()` interprets the first parameter one of three ways. If the path + * to an image file is provided, `loadImage()` will load it. Paths to local + * files should be relative, such as `'assets/thundercat.jpg'`. URLs such as + * `'https://example.com/thundercat.jpg'` may be blocked due to browser + * security. Raw image data can also be passed as a base64 encoded image in + * the form `'data:image/png;base64,arandomsequenceofcharacters'`. + * + * The second parameter is optional. If a function is passed, it will be + * called once the image has loaded. The callback function can optionally use + * the new p5.Image object. + * + * The third parameter is also optional. If a function is passed, it will be + * called if the image fails to load. The callback function can optionally use + * the event error. + * + * Images can take time to load. Calling `loadImage()` in + * preload() ensures images load before they're + * used in setup() or draw(). + * + * @method loadImage + * @param {String} path path of the image to be loaded or base64 encoded image. + * @param {function(p5.Image)} [successCallback] function called with + * p5.Image once it + * loads. + * @param {function(Event)} [failureCallback] function called with event + * error if the image fails to load. + * @return {p5.Image} the p5.Image object. + * + * @example + *
    + * + * let img; + * + * // Load the image and create a p5.Image object. + * function preload() { + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Draw the image. + * image(img, 0, 0); + * + * describe('Image of the underside of a white umbrella and a gridded ceiling.'); + * } + * + *
    + * + *
    + * + * function setup() { + * // Call handleImage() once the image loads. + * loadImage('assets/laDefense.jpg', handleImage); + * + * describe('Image of the underside of a white umbrella and a gridded ceiling.'); + * } + * + * // Display the image. + * function handleImage(img) { + * image(img, 0, 0); + * } + * + *
    + * + *
    + * + * function setup() { + * // Call handleImage() once the image loads or + * // call handleError() if an error occurs. + * loadImage('assets/laDefense.jpg', handleImage, handleError); + * } + * + * // Display the image. + * function handleImage(img) { + * image(img, 0, 0); + * + * describe('Image of the underside of a white umbrella and a gridded ceiling.'); + * } + * + * // Log the error. + * function handleError(event) { + * console.error('Oops!', event); + * } + * + *
    + */ + fn.loadImage = async function( + path, + successCallback, + failureCallback + ) { + p5._validateParameters('loadImage', arguments); + const pImg = new p5.Image(1, 1, this); + const self = this; -/** - * Loads an image to create a p5.Image object. - * - * `loadImage()` interprets the first parameter one of three ways. If the path - * to an image file is provided, `loadImage()` will load it. Paths to local - * files should be relative, such as `'assets/thundercat.jpg'`. URLs such as - * `'https://example.com/thundercat.jpg'` may be blocked due to browser - * security. Raw image data can also be passed as a base64 encoded image in - * the form `'data:image/png;base64,arandomsequenceofcharacters'`. - * - * The second parameter is optional. If a function is passed, it will be - * called once the image has loaded. The callback function can optionally use - * the new p5.Image object. - * - * The third parameter is also optional. If a function is passed, it will be - * called if the image fails to load. The callback function can optionally use - * the event error. - * - * Images can take time to load. Calling `loadImage()` in - * preload() ensures images load before they're - * used in setup() or draw(). - * - * @method loadImage - * @param {String} path path of the image to be loaded or base64 encoded image. - * @param {function(p5.Image)} [successCallback] function called with - * p5.Image once it - * loads. - * @param {function(Event)} [failureCallback] function called with event - * error if the image fails to load. - * @return {p5.Image} the p5.Image object. - * - * @example - *
    - * - * let img; - * - * // Load the image and create a p5.Image object. - * function preload() { - * img = loadImage('assets/laDefense.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Draw the image. - * image(img, 0, 0); - * - * describe('Image of the underside of a white umbrella and a gridded ceiling.'); - * } - * - *
    - * - *
    - * - * function setup() { - * // Call handleImage() once the image loads. - * loadImage('assets/laDefense.jpg', handleImage); - * - * describe('Image of the underside of a white umbrella and a gridded ceiling.'); - * } - * - * // Display the image. - * function handleImage(img) { - * image(img, 0, 0); - * } - * - *
    - * - *
    - * - * function setup() { - * // Call handleImage() once the image loads or - * // call handleError() if an error occurs. - * loadImage('assets/laDefense.jpg', handleImage, handleError); - * } - * - * // Display the image. - * function handleImage(img) { - * image(img, 0, 0); - * - * describe('Image of the underside of a white umbrella and a gridded ceiling.'); - * } - * - * // Log the error. - * function handleError(event) { - * console.error('Oops!', event); - * } - * - *
    - */ -p5.prototype.loadImage = async function( - path, - successCallback, - failureCallback -) { - p5._validateParameters('loadImage', arguments); - const pImg = new p5.Image(1, 1, this); - const self = this; - - const req = new Request(path, { - method: 'GET', - mode: 'cors' - }); - - return fetch(path, req) - .then(async response => { - // GIF section - const contentType = response.headers.get('content-type'); - if (contentType === null) { - console.warn( - 'The image you loaded does not have a Content-Type header. If you are using the online editor consider reuploading the asset.' - ); - } - if (contentType && contentType.includes('image/gif')) { - await response.arrayBuffer().then( - arrayBuffer => new Promise((resolve, reject) => { - if (arrayBuffer) { - const byteArray = new Uint8Array(arrayBuffer); - try{ - _createGif( - byteArray, - pImg, - successCallback, - failureCallback, - (pImg => { - resolve(pImg); - }).bind(self) - ); - }catch(e){ - console.error(e.toString(), e.stack); - if (typeof failureCallback === 'function') { - failureCallback(e); - } else { - console.error(e); + const req = new Request(path, { + method: 'GET', + mode: 'cors' + }); + + return fetch(path, req) + .then(async response => { + // GIF section + const contentType = response.headers.get('content-type'); + if (contentType === null) { + console.warn( + 'The image you loaded does not have a Content-Type header. If you are using the online editor consider reuploading the asset.' + ); + } + if (contentType && contentType.includes('image/gif')) { + await response.arrayBuffer().then( + arrayBuffer => new Promise((resolve, reject) => { + if (arrayBuffer) { + const byteArray = new Uint8Array(arrayBuffer); + try{ + _createGif( + byteArray, + pImg, + successCallback, + failureCallback, + (pImg => { + resolve(pImg); + }).bind(self) + ); + }catch(e){ + console.error(e.toString(), e.stack); + if (typeof failureCallback === 'function') { + failureCallback(e); + } else { + console.error(e); + } + reject(e); } - reject(e); + } + }) + ).catch( + e => { + if (typeof failureCallback === 'function') { + failureCallback(e); + } else { + console.error(e); } } - }) - ).catch( - e => { - if (typeof failureCallback === 'function') { - failureCallback(e); - } else { - console.error(e); - } - } - ); - } else { - // Non-GIF Section - const img = new Image(); - - await new Promise((resolve, reject) => { - img.onload = () => { - pImg.width = pImg.canvas.width = img.width; - pImg.height = pImg.canvas.height = img.height; - - // Draw the image into the backing canvas of the p5.Image - pImg.drawingContext.drawImage(img, 0, 0); - pImg.modified = true; - if (typeof successCallback === 'function') { - successCallback(pImg); - } - resolve(); - }; - - img.onerror = e => { - p5._friendlyFileLoadError(0, img.src); - if (typeof failureCallback === 'function') { - failureCallback(e); - } else { - console.error(e); + ); + } else { + // Non-GIF Section + const img = new Image(); + + await new Promise((resolve, reject) => { + img.onload = () => { + pImg.width = pImg.canvas.width = img.width; + pImg.height = pImg.canvas.height = img.height; + + // Draw the image into the backing canvas of the p5.Image + pImg.drawingContext.drawImage(img, 0, 0); + pImg.modified = true; + if (typeof successCallback === 'function') { + successCallback(pImg); + } + resolve(); + }; + + img.onerror = e => { + p5._friendlyFileLoadError(0, img.src); + if (typeof failureCallback === 'function') { + failureCallback(e); + } else { + console.error(e); + } + reject(); + }; + + // Set crossOrigin in case image is served with CORS headers. + // This will let us draw to the canvas without tainting it. + // See https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image + // When using data-uris the file will be loaded locally + // so we don't need to worry about crossOrigin with base64 file types. + if (path.indexOf('data:image/') !== 0) { + img.crossOrigin = 'Anonymous'; } - reject(); - }; - - // Set crossOrigin in case image is served with CORS headers. - // This will let us draw to the canvas without tainting it. - // See https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image - // When using data-uris the file will be loaded locally - // so we don't need to worry about crossOrigin with base64 file types. - if (path.indexOf('data:image/') !== 0) { - img.crossOrigin = 'Anonymous'; - } - // start loading the image - img.src = path; - }); - } - pImg.modified = true; - return pImg; - }) - .catch(e => { - p5._friendlyFileLoadError(0, path); - if (typeof failureCallback === 'function') { - failureCallback(e); - } else { - console.error(e); - } - }); - // return pImg; -}; + // start loading the image + img.src = path; + }); + } + pImg.modified = true; + return pImg; + }) + .catch(e => { + p5._friendlyFileLoadError(0, path); + if (typeof failureCallback === 'function') { + failureCallback(e); + } else { + console.error(e); + } + }); + // return pImg; + }; -/** - * Generates a gif from a sketch and saves it to a file. - * - * `saveGif()` may be called in setup() or at any - * point while a sketch is running. - * - * The first parameter, `fileName`, sets the gif's file name. - * - * The second parameter, `duration`, sets the gif's duration in seconds. - * - * The third parameter, `options`, is optional. If an object is passed, - * `saveGif()` will use its properties to customize the gif. `saveGif()` - * recognizes the properties `delay`, `units`, `silent`, - * `notificationDuration`, and `notificationID`. - * - * @method saveGif - * @param {String} filename file name of gif. - * @param {Number} duration duration in seconds to capture from the sketch. - * @param {Object} [options] an object that can contain five more properties: - * `delay`, a Number specifying how much time to wait before recording; - * `units`, a String that can be either 'seconds' or 'frames'. By default it's 'seconds’; - * `silent`, a Boolean that defines presence of progress notifications. By default it’s `false`; - * `notificationDuration`, a Number that defines how long in seconds the final notification - * will live. By default it's `0`, meaning the notification will never be removed; - * `notificationID`, a String that specifies the id of the notification's DOM element. By default it’s `'progressBar’`. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe('A circle drawn in the middle of a gray square. The circle changes color from black to white, then repeats.'); - * } - * - * function draw() { - * background(200); - * - * // Style the circle. - * let c = frameCount % 255; - * fill(c); - * - * // Display the circle. - * circle(50, 50, 25); - * } - * - * // Save a 5-second gif when the user presses the 's' key. - * function keyPressed() { - * if (key === 's') { - * saveGif('mySketch', 5); - * } - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe('A circle drawn in the middle of a gray square. The circle changes color from black to white, then repeats.'); - * } - * - * function draw() { - * background(200); - * - * // Style the circle. - * let c = frameCount % 255; - * fill(c); - * - * // Display the circle. - * circle(50, 50, 25); - * } - * - * // Save a 5-second gif when the user presses the 's' key. - * // Wait 1 second after the key press before recording. - * function keyPressed() { - * if (key === 's') { - * saveGif('mySketch', 5, { delay: 1 }); - * } - * } - * - *
    - */ -p5.prototype.saveGif = async function( - fileName, - duration, - options = { - delay: 0, - units: 'seconds', - silent: false, - notificationDuration: 0, - notificationID: 'progressBar' - } -) { - // 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'); - } + /** + * Generates a gif from a sketch and saves it to a file. + * + * `saveGif()` may be called in setup() or at any + * point while a sketch is running. + * + * The first parameter, `fileName`, sets the gif's file name. + * + * The second parameter, `duration`, sets the gif's duration in seconds. + * + * The third parameter, `options`, is optional. If an object is passed, + * `saveGif()` will use its properties to customize the gif. `saveGif()` + * recognizes the properties `delay`, `units`, `silent`, + * `notificationDuration`, and `notificationID`. + * + * @method saveGif + * @param {String} filename file name of gif. + * @param {Number} duration duration in seconds to capture from the sketch. + * @param {Object} [options] an object that can contain five more properties: + * `delay`, a Number specifying how much time to wait before recording; + * `units`, a String that can be either 'seconds' or 'frames'. By default it's 'seconds’; + * `silent`, a Boolean that defines presence of progress notifications. By default it’s `false`; + * `notificationDuration`, a Number that defines how long in seconds the final notification + * will live. By default it's `0`, meaning the notification will never be removed; + * `notificationID`, a String that specifies the id of the notification's DOM element. By default it’s `'progressBar’`. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe('A circle drawn in the middle of a gray square. The circle changes color from black to white, then repeats.'); + * } + * + * function draw() { + * background(200); + * + * // Style the circle. + * let c = frameCount % 255; + * fill(c); + * + * // Display the circle. + * circle(50, 50, 25); + * } + * + * // Save a 5-second gif when the user presses the 's' key. + * function keyPressed() { + * if (key === 's') { + * saveGif('mySketch', 5); + * } + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe('A circle drawn in the middle of a gray square. The circle changes color from black to white, then repeats.'); + * } + * + * function draw() { + * background(200); + * + * // Style the circle. + * let c = frameCount % 255; + * fill(c); + * + * // Display the circle. + * circle(50, 50, 25); + * } + * + * // Save a 5-second gif when the user presses the 's' key. + * // Wait 1 second after the key press before recording. + * function keyPressed() { + * if (key === 's') { + * saveGif('mySketch', 5, { delay: 1 }); + * } + * } + * + *
    + */ + fn.saveGif = async function( + fileName, + duration, + options = { + delay: 0, + units: 'seconds', + silent: false, + notificationDuration: 0, + notificationID: 'progressBar' + } + ) { + // 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'); + } - // extract variables for more comfortable use - const delay = (options && options.delay) || 0; // in seconds - const units = (options && options.units) || 'seconds'; // either 'seconds' or 'frames' - const silent = (options && options.silent) || false; - const notificationDuration = (options && options.notificationDuration) || 0; - const notificationID = (options && options.notificationID) || 'progressBar'; + // extract variables for more comfortable use + const delay = (options && options.delay) || 0; // in seconds + const units = (options && options.units) || 'seconds'; // either 'seconds' or 'frames' + const silent = (options && options.silent) || false; + const notificationDuration = (options && options.notificationDuration) || 0; + const notificationID = (options && options.notificationID) || 'progressBar'; - // if arguments in the options object are not correct, cancel operation - if (typeof delay !== 'number') { - throw TypeError('Delay parameter must be a number'); - } - // if units is not seconds nor frames, throw error - if (units !== 'seconds' && units !== 'frames') { - throw TypeError('Units parameter must be either "frames" or "seconds"'); - } + // if arguments in the options object are not correct, cancel operation + if (typeof delay !== 'number') { + throw TypeError('Delay parameter must be a number'); + } + // if units is not seconds nor frames, throw error + if (units !== 'seconds' && units !== 'frames') { + throw TypeError('Units parameter must be either "frames" or "seconds"'); + } - if (typeof silent !== 'boolean') { - throw TypeError('Silent parameter must be a boolean'); - } + if (typeof silent !== 'boolean') { + throw TypeError('Silent parameter must be a boolean'); + } - if (typeof notificationDuration !== 'number') { - throw TypeError('Notification duration parameter must be a number'); - } + if (typeof notificationDuration !== 'number') { + throw TypeError('Notification duration parameter must be a number'); + } - if (typeof notificationID !== 'string') { - throw TypeError('Notification ID parameter must be a string'); - } + if (typeof notificationID !== 'string') { + throw TypeError('Notification ID parameter must be a string'); + } - this._recording = true; + this._recording = true; - // 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; - } + // 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 = []; - - if (document.getElementById(notificationID) !== null) - document.getElementById(notificationID).remove(); - - let p; - if (!silent){ - p = this.createP(''); - p.id(notificationID); - 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); - } + // 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 = []; + + if (document.getElementById(notificationID) !== null) + document.getElementById(notificationID).remove(); + + let p; + if (!silent){ + p = this.createP(''); + p.id(notificationID); + 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._renderer instanceof p5.RendererGL) { - // if we have a WEBGL context, initialize the pixels array - // and the gl context to use them inside the loop - gl = this.drawingContext; - pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4); - } + let pixels; + let gl; + if (this._renderer instanceof p5.RendererGL) { + // if we have a WEBGL context, initialize the pixels array + // and the gl context to use them inside the loop + gl = this.drawingContext; + pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4); + } - // stop the loop since we are going to manually redraw - this.noLoop(); - - // Defer execution until the rest of the call stack finishes, allowing the - // rest of `setup` to be called (and, importantly, canvases hidden in setup - // to be unhidden.) - // - // Waiting on this empty promise means we'll continue as soon as setup - // finishes without waiting for another frame. - await Promise.resolve(); - - 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; + // stop the loop since we are going to manually redraw + this.noLoop(); - if (this._renderer instanceof p5.RendererGL) { - pixels = new Uint8Array( - gl.drawingBufferWidth * gl.drawingBufferHeight * 4 - ); - gl.readPixels( - 0, - 0, - gl.drawingBufferWidth, - gl.drawingBufferHeight, - gl.RGBA, - gl.UNSIGNED_BYTE, - pixels - ); + // Defer execution until the rest of the call stack finishes, allowing the + // rest of `setup` to be called (and, importantly, canvases hidden in setup + // to be unhidden.) + // + // Waiting on this empty promise means we'll continue as soon as setup + // finishes without waiting for another frame. + await Promise.resolve(); + + 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._renderer instanceof p5.RendererGL) { + 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, this.width, this.height); - } else { - data = this.drawingContext.getImageData(0, 0, this.width, this.height) - .data; - } + data = _flipPixels(pixels, this.width, this.height); + } else { + data = this.drawingContext.getImageData(0, 0, this.width, this.height) + .data; + } - frames.push(data); - frameIterator++; + frames.push(data); + frameIterator++; - if (!silent) { - p.html( - 'Saved frame ' + - frames.length.toString() + - ' out of ' + - nFrames.toString() - ); - } - await new Promise(resolve => setTimeout(resolve, 0)); - } - if (!silent) 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); - - // Rather than using applyPalette() from the gifenc library, we use our - // own function to map frame pixels to a palette color. This way, we can - // cache palette color mappings between frames for extra performance, and - // use our own caching mechanism to avoid flickering colors from cache - // key collisions. - const paletteCache = {}; - const getIndexedFrame = frame => { - const length = frame.length / 4; - const index = new Uint8Array(length); - for (let i = 0; i < length; i++) { - const key = - (frame[i * 4] << 24) | - (frame[i * 4 + 1] << 16) | - (frame[i * 4 + 2] << 8) | - frame[i * 4 + 3]; - if (paletteCache[key] === undefined) { - paletteCache[key] = nearestColorIndex( - globalPalette, - frame.slice(i * 4, (i + 1) * 4) + if (!silent) { + p.html( + 'Saved frame ' + + frames.length.toString() + + ' out of ' + + nFrames.toString() ); } - index[i] = paletteCache[key]; + await new Promise(resolve => setTimeout(resolve, 0)); } - return index; - }; + if (!silent) 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); + + // Rather than using applyPalette() from the gifenc library, we use our + // own function to map frame pixels to a palette color. This way, we can + // cache palette color mappings between frames for extra performance, and + // use our own caching mechanism to avoid flickering colors from cache + // key collisions. + const paletteCache = {}; + const getIndexedFrame = frame => { + const length = frame.length / 4; + const index = new Uint8Array(length); + for (let i = 0; i < length; i++) { + const key = + (frame[i * 4] << 24) | + (frame[i * 4 + 1] << 16) | + (frame[i * 4 + 2] << 8) | + frame[i * 4 + 3]; + if (paletteCache[key] === undefined) { + paletteCache[key] = nearestColorIndex( + globalPalette, + frame.slice(i * 4, (i + 1) * 4) + ); + } + index[i] = paletteCache[key]; + } + return index; + }; - // the way we designed the palette means we always take the last index for transparency - const transparentIndex = globalPalette.length - 1; + // 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 - let prevIndexedFrame = []; - for (let i = 0; i < frames.length; i++) { - //const indexedFrame = applyPalette(frames[i], globalPaletteWithoutAlpha, 'rgba565'); - const indexedFrame = getIndexedFrame(frames[i]); + // we are going to iterate the frames in pairs, n-1 and n + let prevIndexedFrame = []; + for (let i = 0; i < frames.length; i++) { + //const indexedFrame = applyPalette(frames[i], globalPaletteWithoutAlpha, 'rgba565'); + const indexedFrame = getIndexedFrame(frames[i]); - // Make a copy of the palette-applied frame before editing the original - // to use transparent pixels - const originalIndexedFrame = indexedFrame.slice(); + // Make a copy of the palette-applied frame before editing the original + // to use transparent pixels + const originalIndexedFrame = indexedFrame.slice(); - if (i === 0) { - gif.writeFrame(indexedFrame, this.width, this.height, { - palette: globalPalette, - delay: gifFrameDelay, - dispose: 1 - }); - } else { - // Matching pixels between frames can be set to full transparency, - // allowing the previous frame's pixels to show through. We only do - // this for pixels that get mapped to the same quantized color so that - // the resulting image would be the same. - for (let i = 0; i < indexedFrame.length; i++) { - if (indexedFrame[i] === prevIndexedFrame[i]) { - indexedFrame[i] = transparentIndex; + if (i === 0) { + gif.writeFrame(indexedFrame, this.width, this.height, { + palette: globalPalette, + delay: gifFrameDelay, + dispose: 1 + }); + } else { + // Matching pixels between frames can be set to full transparency, + // allowing the previous frame's pixels to show through. We only do + // this for pixels that get mapped to the same quantized color so that + // the resulting image would be the same. + for (let i = 0; i < indexedFrame.length; i++) { + if (indexedFrame[i] === prevIndexedFrame[i]) { + indexedFrame[i] = transparentIndex; + } } - } - - // Write frame into the encoder - gif.writeFrame(indexedFrame, this.width, this.height, { - delay: gifFrameDelay, - transparent: true, - transparentIndex, - dispose: 1 - }); - } - prevIndexedFrame = originalIndexedFrame; - - if (!silent) { - p.html( - 'Rendered frame ' + i.toString() + ' out of ' + nFrames.toString() - ); - } + // Write frame into the encoder + gif.writeFrame(indexedFrame, this.width, this.height, { + delay: gifFrameDelay, + transparent: true, + transparentIndex, + dispose: 1 + }); + } + prevIndexedFrame = originalIndexedFrame; - // this just makes the process asynchronous, preventing - // that the encoding locks up the browser - await new Promise(resolve => setTimeout(resolve, 0)); - } + if (!silent) { + p.html( + 'Rendered frame ' + i.toString() + ' out of ' + nFrames.toString() + ); + } - gif.finish(); - // Get a direct typed array view into the buffer to avoid copying it - const buffer = gif.bytesView(); - const extension = 'gif'; + // this just makes the process asynchronous, preventing + // that the encoding locks up the browser + await new Promise(resolve => setTimeout(resolve, 0)); + } - const blob = new Blob([buffer], { - type: 'image/gif' - }); + gif.finish(); - frames = []; - this._recording = false; - this.loop(); + // Get a direct typed array view into the buffer to avoid copying it + const buffer = gif.bytesView(); + const extension = 'gif'; - if (!silent){ - p.html('Done. Downloading your gif!🌸'); - if(notificationDuration > 0) - setTimeout(() => p.remove(), notificationDuration * 1000); - } + const blob = new Blob([buffer], { + type: 'image/gif' + }); - p5.prototype.downloadFile(blob, fileName, extension); -}; + frames = []; + this._recording = false; + this.loop(); -function _flipPixels(pixels, width, height) { - // 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 + if (!silent){ + p.html('Done. Downloading your gif!🌸'); + if(notificationDuration > 0) + setTimeout(() => p.remove(), notificationDuration * 1000); + } - const halfHeight = parseInt(height / 2); - const bytesPerRow = width * 4; + fn.downloadFile(blob, fileName, extension); + }; - // make a temp buffer to hold one row - const temp = new Uint8Array(width * 4); - for (let y = 0; y < halfHeight; ++y) { - const topOffset = y * bytesPerRow; - const bottomOffset = (height - y - 1) * bytesPerRow; + function _flipPixels(pixels, width, height) { + // 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 - // make copy of a row on the top half - temp.set(pixels.subarray(topOffset, topOffset + bytesPerRow)); + const halfHeight = parseInt(height / 2); + const bytesPerRow = width * 4; - // copy a row from the bottom half to the top - pixels.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow); + // make a temp buffer to hold one row + const temp = new Uint8Array(width * 4); + for (let y = 0; y < halfHeight; ++y) { + const topOffset = y * bytesPerRow; + const bottomOffset = (height - y - 1) * bytesPerRow; - // copy the copy of the top half row to the bottom half - pixels.set(temp, bottomOffset); - } - return pixels; -} + // make copy of a row on the top half + temp.set(pixels.subarray(topOffset, topOffset + bytesPerRow)); -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); + // copy a row from the bottom half to the top + pixels.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow); - // 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[f], f * frames[0].length); + // copy the copy of the top half row to the bottom half + pixels.set(temp, bottomOffset); + } + return pixels; } - // quantize this massive array into 256 colors and return it! - let colorPalette = quantize(allColors, 256, { - format: 'rgba4444', - 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 _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); -/** - * Helper function for loading GIF-based images - */ -function _createGif( - arrayBuffer, - pImg, - successCallback, - failureCallback, - finishCallback -) { - const gifReader = new omggif.GifReader(arrayBuffer); - pImg.width = pImg.canvas.width = gifReader.width; - pImg.height = pImg.canvas.height = gifReader.height; - const frames = []; - const numFrames = gifReader.numFrames(); - let framePixels = new Uint8ClampedArray(pImg.width * pImg.height * 4); - const loadGIFFrameIntoImage = (frameNum, gifReader) => { - try { - gifReader.decodeAndBlitFrameRGBA(frameNum, framePixels); - } catch (e) { - p5._friendlyFileLoadError(8, pImg.src); - if (typeof failureCallback === 'function') { - failureCallback(e); - } else { - console.error(e); - } - } - }; - for (let j = 0; j < numFrames; j++) { - const frameInfo = gifReader.frameInfo(j); - const prevFrameData = pImg.drawingContext.getImageData( - 0, - 0, - pImg.width, - pImg.height - ); - framePixels = prevFrameData.data.slice(); - loadGIFFrameIntoImage(j, gifReader); - const imageData = new ImageData(framePixels, pImg.width, pImg.height); - pImg.drawingContext.putImageData(imageData, 0, 0); - let frameDelay = frameInfo.delay; - // To maintain the default of 10FPS when frameInfo.delay equals to 0 - if (frameDelay === 0) { - frameDelay = 10; + // 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[f], f * frames[0].length); } - frames.push({ - image: pImg.drawingContext.getImageData(0, 0, pImg.width, pImg.height), - delay: frameDelay * 10 //GIF stores delay in one-hundredth of a second, shift to ms + + // quantize this massive array into 256 colors and return it! + let colorPalette = quantize(allColors, 256, { + format: 'rgba4444', + oneBitAlpha: true }); - // Some GIFs are encoded so that they expect the previous frame - // to be under the current frame. This can occur at a sub-frame level - // - // Values : 0 - No disposal specified. The decoder is - // not required to take any action. - // 1 - Do not dispose. The graphic is to be left - // in place. - // 2 - Restore to background color. The area used by the - // graphic must be restored to the background color. - // 3 - Restore to previous. The decoder is required to - // restore the area overwritten by the graphic with - // what was there prior to rendering the graphic. - // 4-7 - To be defined. - if (frameInfo.disposal === 2) { - // Restore background color - pImg.drawingContext.clearRect( - frameInfo.x, - frameInfo.y, - frameInfo.width, - frameInfo.height - ); - } else if (frameInfo.disposal === 3) { - // Restore previous - pImg.drawingContext.putImageData( - prevFrameData, - 0, - 0, - frameInfo.x, - frameInfo.y, - frameInfo.width, - frameInfo.height - ); + // 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; } - //Uses Netscape block encoding - //to repeat forever, this will be 0 - //to repeat just once, this will be null - //to repeat N times (1 1) { - pImg.gifProperties = { - displayIndex: 0, - loopLimit, - loopCount: 0, - frames, - numFrames, - playing: true, - timeDisplayed: 0, - lastChangeTime: 0 + /** + * Helper function for loading GIF-based images + */ + function _createGif( + arrayBuffer, + pImg, + successCallback, + failureCallback, + finishCallback + ) { + const gifReader = new omggif.GifReader(arrayBuffer); + pImg.width = pImg.canvas.width = gifReader.width; + pImg.height = pImg.canvas.height = gifReader.height; + const frames = []; + const numFrames = gifReader.numFrames(); + let framePixels = new Uint8ClampedArray(pImg.width * pImg.height * 4); + const loadGIFFrameIntoImage = (frameNum, gifReader) => { + try { + gifReader.decodeAndBlitFrameRGBA(frameNum, framePixels); + } catch (e) { + p5._friendlyFileLoadError(8, pImg.src); + if (typeof failureCallback === 'function') { + failureCallback(e); + } else { + console.error(e); + } + } }; - } - - if (typeof successCallback === 'function') { - successCallback(pImg); - } - finishCallback(); -} + for (let j = 0; j < numFrames; j++) { + const frameInfo = gifReader.frameInfo(j); + const prevFrameData = pImg.drawingContext.getImageData( + 0, + 0, + pImg.width, + pImg.height + ); + framePixels = prevFrameData.data.slice(); + loadGIFFrameIntoImage(j, gifReader); + const imageData = new ImageData(framePixels, pImg.width, pImg.height); + pImg.drawingContext.putImageData(imageData, 0, 0); + let frameDelay = frameInfo.delay; + // To maintain the default of 10FPS when frameInfo.delay equals to 0 + if (frameDelay === 0) { + frameDelay = 10; + } + frames.push({ + image: pImg.drawingContext.getImageData(0, 0, pImg.width, pImg.height), + delay: frameDelay * 10 //GIF stores delay in one-hundredth of a second, shift to ms + }); -/** - * @private - * @param {(LEFT|RIGHT|CENTER)} xAlign either LEFT, RIGHT or CENTER - * @param {(TOP|BOTTOM|CENTER)} yAlign either TOP, BOTTOM or CENTER - * @param {Number} dx - * @param {Number} dy - * @param {Number} dw - * @param {Number} dh - * @param {Number} sw - * @param {Number} sh - * @returns {Object} - */ + // Some GIFs are encoded so that they expect the previous frame + // to be under the current frame. This can occur at a sub-frame level + // + // Values : 0 - No disposal specified. The decoder is + // not required to take any action. + // 1 - Do not dispose. The graphic is to be left + // in place. + // 2 - Restore to background color. The area used by the + // graphic must be restored to the background color. + // 3 - Restore to previous. The decoder is required to + // restore the area overwritten by the graphic with + // what was there prior to rendering the graphic. + // 4-7 - To be defined. + if (frameInfo.disposal === 2) { + // Restore background color + pImg.drawingContext.clearRect( + frameInfo.x, + frameInfo.y, + frameInfo.width, + frameInfo.height + ); + } else if (frameInfo.disposal === 3) { + // Restore previous + pImg.drawingContext.putImageData( + prevFrameData, + 0, + 0, + frameInfo.x, + frameInfo.y, + frameInfo.width, + frameInfo.height + ); + } + } -function _imageContain(xAlign, yAlign, dx, dy, dw, dh, sw, sh) { - const r = Math.max(sw / dw, sh / dh); - const [adjusted_dw, adjusted_dh] = [sw / r, sh / r]; - let x = dx; - let y = dy; + //Uses Netscape block encoding + //to repeat forever, this will be 0 + //to repeat just once, this will be null + //to repeat N times (1 1) { + pImg.gifProperties = { + displayIndex: 0, + loopLimit, + loopCount: 0, + frames, + numFrames, + playing: true, + timeDisplayed: 0, + lastChangeTime: 0 + }; + } - if (yAlign === constants.CENTER) { - y += (dh - adjusted_dh) / 2; - } else if (yAlign === constants.BOTTOM) { - y += dh - adjusted_dh; + if (typeof successCallback === 'function') { + successCallback(pImg); + } + finishCallback(); } - return { x, y, w: adjusted_dw, h: adjusted_dh }; -} -/** - * @private - * @param {(LEFT|RIGHT|CENTER)} xAlign either LEFT, RIGHT or CENTER - * @param {(TOP|BOTTOM|CENTER)} yAlign either TOP, BOTTOM or CENTER - * @param {Number} dw - * @param {Number} dh - * @param {Number} sx - * @param {Number} sy - * @param {Number} sw - * @param {Number} sh - * @returns {Object} - */ -function _imageCover(xAlign, yAlign, dw, dh, sx, sy, sw, sh) { - const r = Math.max(dw / sw, dh / sh); - const [adjusted_sw, adjusted_sh] = [dw / r, dh / r]; - - let x = sx; - let y = sy; - - if (xAlign === constants.CENTER) { - x += (sw - adjusted_sw) / 2; - } else if (xAlign === constants.RIGHT) { - x += sw - adjusted_sw; - } + /** + * @private + * @param {(LEFT|RIGHT|CENTER)} xAlign either LEFT, RIGHT or CENTER + * @param {(TOP|BOTTOM|CENTER)} yAlign either TOP, BOTTOM or CENTER + * @param {Number} dx + * @param {Number} dy + * @param {Number} dw + * @param {Number} dh + * @param {Number} sw + * @param {Number} sh + * @returns {Object} + */ + + function _imageContain(xAlign, yAlign, dx, dy, dw, dh, sw, sh) { + const r = Math.max(sw / dw, sh / dh); + const [adjusted_dw, adjusted_dh] = [sw / r, sh / r]; + let x = dx; + let y = dy; + + if (xAlign === constants.CENTER) { + x += (dw - adjusted_dw) / 2; + } else if (xAlign === constants.RIGHT) { + x += dw - adjusted_dw; + } - if (yAlign === constants.CENTER) { - y += (sh - adjusted_sh) / 2; - } else if (yAlign === constants.BOTTOM) { - y += sh - adjusted_sh; + if (yAlign === constants.CENTER) { + y += (dh - adjusted_dh) / 2; + } else if (yAlign === constants.BOTTOM) { + y += dh - adjusted_dh; + } + return { x, y, w: adjusted_dw, h: adjusted_dh }; } - return { x, y, w: adjusted_sw, h: adjusted_sh }; -} + /** + * @private + * @param {(LEFT|RIGHT|CENTER)} xAlign either LEFT, RIGHT or CENTER + * @param {(TOP|BOTTOM|CENTER)} yAlign either TOP, BOTTOM or CENTER + * @param {Number} dw + * @param {Number} dh + * @param {Number} sx + * @param {Number} sy + * @param {Number} sw + * @param {Number} sh + * @returns {Object} + */ + function _imageCover(xAlign, yAlign, dw, dh, sx, sy, sw, sh) { + const r = Math.max(dw / sw, dh / sh); + const [adjusted_sw, adjusted_sh] = [dw / r, dh / r]; + + let x = sx; + let y = sy; + + if (xAlign === constants.CENTER) { + x += (sw - adjusted_sw) / 2; + } else if (xAlign === constants.RIGHT) { + x += sw - adjusted_sw; + } -/** - * @private - * @param {(CONTAIN|COVER)} [fit] either CONTAIN or COVER - * @param {(LEFT|RIGHT|CENTER)} xAlign either LEFT, RIGHT or CENTER - * @param {(TOP|BOTTOM|CENTER)} yAlign either TOP, BOTTOM or CENTER - * @param {Number} dx - * @param {Number} dy - * @param {Number} dw - * @param {Number} dh - * @param {Number} sx - * @param {Number} sy - * @param {Number} sw - * @param {Number} sh - * @returns {Object} - */ -function _imageFit(fit, xAlign, yAlign, dx, dy, dw, dh, sx, sy, sw, sh) { - if (fit === constants.COVER) { - const { x, y, w, h } = _imageCover(xAlign, yAlign, dw, dh, sx, sy, sw, sh); - sx = x; - sy = y; - sw = w; - sh = h; - } + if (yAlign === constants.CENTER) { + y += (sh - adjusted_sh) / 2; + } else if (yAlign === constants.BOTTOM) { + y += sh - adjusted_sh; + } - if (fit === constants.CONTAIN) { - const { x, y, w, h } = _imageContain( - xAlign, - yAlign, - dx, - dy, - dw, - dh, - sw, - sh - ); - dx = x; - dy = y; - dw = w; - dh = h; + return { x, y, w: adjusted_sw, h: adjusted_sh }; } - return { sx, sy, sw, sh, dx, dy, dw, dh }; -} -/** - * Validates clipping params. Per drawImage spec sWidth and sHight cannot be - * negative or greater than image intrinsic width and height - * @private - * @param {Number} sVal - * @param {Number} iVal - * @returns {Number} - * @private - */ -function _sAssign(sVal, iVal) { - if (sVal > 0 && sVal < iVal) { - return sVal; - } else { - return iVal; - } -} + /** + * @private + * @param {(CONTAIN|COVER)} [fit] either CONTAIN or COVER + * @param {(LEFT|RIGHT|CENTER)} xAlign either LEFT, RIGHT or CENTER + * @param {(TOP|BOTTOM|CENTER)} yAlign either TOP, BOTTOM or CENTER + * @param {Number} dx + * @param {Number} dy + * @param {Number} dw + * @param {Number} dh + * @param {Number} sx + * @param {Number} sy + * @param {Number} sw + * @param {Number} sh + * @returns {Object} + */ + function _imageFit(fit, xAlign, yAlign, dx, dy, dw, dh, sx, sy, sw, sh) { + if (fit === constants.COVER) { + const { x, y, w, h } = _imageCover(xAlign, yAlign, dw, dh, sx, sy, sw, sh); + sx = x; + sy = y; + sw = w; + sh = h; + } -/** - * Draws an image to the canvas. - * - * The first parameter, `img`, is the source image to be drawn. `img` can be - * any of the following objects: - * - p5.Image - * - p5.Element - * - p5.Texture - * - p5.Framebuffer - * - p5.FramebufferTexture - * - * The second and third parameters, `dx` and `dy`, set the coordinates of the - * destination image's top left corner. See - * imageMode() for other ways to position images. - * - * Here's a diagram that explains how optional parameters work in `image()`: - * - * - * - * The fourth and fifth parameters, `dw` and `dh`, are optional. They set the - * the width and height to draw the destination image. By default, `image()` - * draws the full source image at its original size. - * - * The sixth and seventh parameters, `sx` and `sy`, are also optional. - * These coordinates define the top left corner of a subsection to draw from - * the source image. - * - * The eighth and ninth parameters, `sw` and `sh`, are also optional. - * They define the width and height of a subsection to draw from the source - * image. By default, `image()` draws the full subsection that begins at - * `(sx, sy)` and extends to the edges of the source image. - * - * The ninth parameter, `fit`, is also optional. It enables a subsection of - * the source image to be drawn without affecting its aspect ratio. If - * `CONTAIN` is passed, the full subsection will appear within the destination - * rectangle. If `COVER` is passed, the subsection will completely cover the - * destination rectangle. This may have the effect of zooming into the - * subsection. - * - * The tenth and eleventh paremeters, `xAlign` and `yAlign`, are also - * optional. They determine how to align the fitted subsection. `xAlign` can - * be set to either `LEFT`, `RIGHT`, or `CENTER`. `yAlign` can be set to - * either `TOP`, `BOTTOM`, or `CENTER`. By default, both `xAlign` and `yAlign` - * are set to `CENTER`. - * - * @method image - * @param {p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture} img image to display. - * @param {Number} x x-coordinate of the top-left corner of the image. - * @param {Number} y y-coordinate of the top-left corner of the image. - * @param {Number} [width] width to draw the image. - * @param {Number} [height] height to draw the image. - * - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/laDefense.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Draw the image. - * image(img, 0, 0); - * - * describe('An image of the underside of a white umbrella with a gridded ceiling above.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/laDefense.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Draw the image. - * image(img, 10, 10); - * - * describe('An image of the underside of a white umbrella with a gridded ceiling above. The image has dark gray borders on its left and top.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/laDefense.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Draw the image 50x50. - * image(img, 0, 0, 50, 50); - * - * describe('An image of the underside of a white umbrella with a gridded ceiling above. The image is drawn in the top left corner of a dark gray square.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/laDefense.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Draw the center of the image. - * image(img, 25, 25, 50, 50, 25, 25, 50, 50); - * - * describe('An image of a gridded ceiling drawn in the center of a dark gray square.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/moonwalk.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Draw the image and scale it to fit within the canvas. - * image(img, 0, 0, width, height, 0, 0, img.width, img.height, CONTAIN); - * - * describe('An image of an astronaut on the moon. The top and bottom borders of the image are dark gray.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * // Image is 50 x 50 pixels. - * img = loadImage('assets/laDefense50.png'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(50); - * - * // Draw the image and scale it to cover the canvas. - * image(img, 0, 0, width, height, 0, 0, img.width, img.height, COVER); - * - * describe('A pixelated image of the underside of a white umbrella with a gridded ceiling above.'); - * } - * - *
    - */ -/** - * @method image - * @param {p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture} img - * @param {Number} dx the x-coordinate of the destination - * rectangle in which to draw the source image - * @param {Number} dy the y-coordinate of the destination - * rectangle in which to draw the source image - * @param {Number} dWidth the width of the destination rectangle - * @param {Number} dHeight the height of the destination rectangle - * @param {Number} sx the x-coordinate of the subsection of the source - * image to draw into the destination rectangle - * @param {Number} sy the y-coordinate of the subsection of the source - * image to draw into the destination rectangle - * @param {Number} [sWidth] the width of the subsection of the - * source image to draw into the destination - * rectangle - * @param {Number} [sHeight] the height of the subsection of the - * source image to draw into the destination rectangle - * @param {(CONTAIN|COVER)} [fit] either CONTAIN or COVER - * @param {(LEFT|RIGHT|CENTER)} [xAlign=CENTER] either LEFT, RIGHT or CENTER default is CENTER - * @param {(TOP|BOTTOM|CENTER)} [yAlign=CENTER] either TOP, BOTTOM or CENTER default is CENTER - */ -p5.prototype.image = function( - img, - dx, - dy, - dWidth, - dHeight, - sx, - sy, - sWidth, - sHeight, - fit, - xAlign, - yAlign -) { - // set defaults per spec: https://goo.gl/3ykfOq - - p5._validateParameters('image', arguments); - - let defW = img.width; - let defH = img.height; - yAlign = yAlign || constants.CENTER; - xAlign = xAlign || constants.CENTER; - - if (img.elt) { - defW = defW !== undefined ? defW : img.elt.width; - defH = defH !== undefined ? defH : img.elt.height; - } - if (img.elt && img.elt.videoWidth && !img.canvas) { - // video no canvas - defW = defW !== undefined ? defW : img.elt.videoWidth; - defH = defH !== undefined ? defH : img.elt.videoHeight; + if (fit === constants.CONTAIN) { + const { x, y, w, h } = _imageContain( + xAlign, + yAlign, + dx, + dy, + dw, + dh, + sw, + sh + ); + dx = x; + dy = y; + dw = w; + dh = h; + } + return { sx, sy, sw, sh, dx, dy, dw, dh }; } - let _dx = dx; - let _dy = dy; - let _dw = dWidth || defW; - let _dh = dHeight || defH; - let _sx = sx || 0; - let _sy = sy || 0; - let _sw = sWidth !== undefined ? sWidth : defW; - let _sh = sHeight !== undefined ? sHeight : defH; - - _sw = _sAssign(_sw, defW); - _sh = _sAssign(_sh, defH); - - // This part needs cleanup and unit tests - // see issues https://github.com/processing/p5.js/issues/1741 - // and https://github.com/processing/p5.js/issues/1673 - let pd = 1; - - if (img.elt && !img.canvas && img.elt.style.width) { - //if img is video and img.elt.size() has been used and - //no width passed to image() - if (img.elt.videoWidth && !dWidth) { - pd = img.elt.videoWidth; + /** + * Validates clipping params. Per drawImage spec sWidth and sHight cannot be + * negative or greater than image intrinsic width and height + * @private + * @param {Number} sVal + * @param {Number} iVal + * @returns {Number} + * @private + */ + function _sAssign(sVal, iVal) { + if (sVal > 0 && sVal < iVal) { + return sVal; } else { - //all other cases - pd = img.elt.width; + return iVal; } - pd /= parseInt(img.elt.style.width, 10); } - _sx *= pd; - _sy *= pd; - _sh *= pd; - _sw *= pd; - - let vals = canvas.modeAdjust(_dx, _dy, _dw, _dh, this._renderer._imageMode); - vals = _imageFit( + /** + * Draws an image to the canvas. + * + * The first parameter, `img`, is the source image to be drawn. `img` can be + * any of the following objects: + * - p5.Image + * - p5.Element + * - p5.Texture + * - p5.Framebuffer + * - p5.FramebufferTexture + * + * The second and third parameters, `dx` and `dy`, set the coordinates of the + * destination image's top left corner. See + * imageMode() for other ways to position images. + * + * Here's a diagram that explains how optional parameters work in `image()`: + * + * + * + * The fourth and fifth parameters, `dw` and `dh`, are optional. They set the + * the width and height to draw the destination image. By default, `image()` + * draws the full source image at its original size. + * + * The sixth and seventh parameters, `sx` and `sy`, are also optional. + * These coordinates define the top left corner of a subsection to draw from + * the source image. + * + * The eighth and ninth parameters, `sw` and `sh`, are also optional. + * They define the width and height of a subsection to draw from the source + * image. By default, `image()` draws the full subsection that begins at + * `(sx, sy)` and extends to the edges of the source image. + * + * The ninth parameter, `fit`, is also optional. It enables a subsection of + * the source image to be drawn without affecting its aspect ratio. If + * `CONTAIN` is passed, the full subsection will appear within the destination + * rectangle. If `COVER` is passed, the subsection will completely cover the + * destination rectangle. This may have the effect of zooming into the + * subsection. + * + * The tenth and eleventh paremeters, `xAlign` and `yAlign`, are also + * optional. They determine how to align the fitted subsection. `xAlign` can + * be set to either `LEFT`, `RIGHT`, or `CENTER`. `yAlign` can be set to + * either `TOP`, `BOTTOM`, or `CENTER`. By default, both `xAlign` and `yAlign` + * are set to `CENTER`. + * + * @method image + * @param {p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture} img image to display. + * @param {Number} x x-coordinate of the top-left corner of the image. + * @param {Number} y y-coordinate of the top-left corner of the image. + * @param {Number} [width] width to draw the image. + * @param {Number} [height] height to draw the image. + * + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Draw the image. + * image(img, 0, 0); + * + * describe('An image of the underside of a white umbrella with a gridded ceiling above.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Draw the image. + * image(img, 10, 10); + * + * describe('An image of the underside of a white umbrella with a gridded ceiling above. The image has dark gray borders on its left and top.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Draw the image 50x50. + * image(img, 0, 0, 50, 50); + * + * describe('An image of the underside of a white umbrella with a gridded ceiling above. The image is drawn in the top left corner of a dark gray square.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Draw the center of the image. + * image(img, 25, 25, 50, 50, 25, 25, 50, 50); + * + * describe('An image of a gridded ceiling drawn in the center of a dark gray square.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/moonwalk.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Draw the image and scale it to fit within the canvas. + * image(img, 0, 0, width, height, 0, 0, img.width, img.height, CONTAIN); + * + * describe('An image of an astronaut on the moon. The top and bottom borders of the image are dark gray.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * // Image is 50 x 50 pixels. + * img = loadImage('assets/laDefense50.png'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(50); + * + * // Draw the image and scale it to cover the canvas. + * image(img, 0, 0, width, height, 0, 0, img.width, img.height, COVER); + * + * describe('A pixelated image of the underside of a white umbrella with a gridded ceiling above.'); + * } + * + *
    + */ + /** + * @method image + * @param {p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture} img + * @param {Number} dx the x-coordinate of the destination + * rectangle in which to draw the source image + * @param {Number} dy the y-coordinate of the destination + * rectangle in which to draw the source image + * @param {Number} dWidth the width of the destination rectangle + * @param {Number} dHeight the height of the destination rectangle + * @param {Number} sx the x-coordinate of the subsection of the source + * image to draw into the destination rectangle + * @param {Number} sy the y-coordinate of the subsection of the source + * image to draw into the destination rectangle + * @param {Number} [sWidth] the width of the subsection of the + * source image to draw into the destination + * rectangle + * @param {Number} [sHeight] the height of the subsection of the + * source image to draw into the destination rectangle + * @param {(CONTAIN|COVER)} [fit] either CONTAIN or COVER + * @param {(LEFT|RIGHT|CENTER)} [xAlign=CENTER] either LEFT, RIGHT or CENTER default is CENTER + * @param {(TOP|BOTTOM|CENTER)} [yAlign=CENTER] either TOP, BOTTOM or CENTER default is CENTER + */ + fn.image = function( + img, + dx, + dy, + dWidth, + dHeight, + sx, + sy, + sWidth, + sHeight, fit, xAlign, - yAlign, - vals.x, - vals.y, - vals.w, - vals.h, - _sx, - _sy, - _sw, - _sh - ); - - // tint the image if there is a tint - this._renderer.image( - img, - vals.sx, - vals.sy, - vals.sw, - vals.sh, - vals.dx, - vals.dy, - vals.dw, - vals.dh - ); -}; + yAlign + ) { + // set defaults per spec: https://goo.gl/3ykfOq -/** - * Tints images using a color. - * - * The version of `tint()` with one parameter interprets it one of four ways. - * If the parameter is a number, it's interpreted as a grayscale value. If the - * parameter is a string, it's interpreted as a CSS color string. An array of - * `[R, G, B, A]` values or a p5.Color object can - * also be used to set the tint color. - * - * The version of `tint()` with two parameters uses the first one as a - * grayscale value and the second as an alpha value. For example, calling - * `tint(255, 128)` will make an image 50% transparent. - * - * The version of `tint()` with three parameters interprets them as RGB or - * HSB values, depending on the current - * colorMode(). The optional fourth parameter - * sets the alpha value. For example, `tint(255, 0, 0, 100)` will give images - * a red tint and make them transparent. - * - * @method tint - * @param {Number} v1 red or hue value. - * @param {Number} v2 green or saturation value. - * @param {Number} v3 blue or brightness. - * @param {Number} [alpha] - * - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/laDefense.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Left image. - * image(img, 0, 0); - * - * // Right image. - * // Tint with a CSS color string. - * tint('red'); - * image(img, 50, 0); - * - * describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a red tint.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/laDefense.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Left image. - * image(img, 0, 0); - * - * // Right image. - * // Tint with RGB values. - * tint(255, 0, 0); - * image(img, 50, 0); - * - * describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a red tint.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/laDefense.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Left. - * image(img, 0, 0); - * - * // Right. - * // Tint with RGBA values. - * tint(255, 0, 0, 100); - * image(img, 50, 0); - * - * describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a transparent red tint.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/laDefense.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Left. - * image(img, 0, 0); - * - * // Right. - * // Tint with grayscale and alpha values. - * tint(255, 180); - * image(img, 50, 0); - * - * describe('Two images of an umbrella and a ceiling side-by-side. The image on the right is transparent.'); - * } - * - *
    - */ -/** - * @method tint - * @param {String} value CSS color string. - */ + p5._validateParameters('image', arguments); -/** - * @method tint - * @param {Number} gray grayscale value. - * @param {Number} [alpha] - */ + let defW = img.width; + let defH = img.height; + yAlign = yAlign || constants.CENTER; + xAlign = xAlign || constants.CENTER; -/** - * @method tint - * @param {Number[]} values array containing the red, green, blue & - * alpha components of the color. - */ + if (img.elt) { + defW = defW !== undefined ? defW : img.elt.width; + defH = defH !== undefined ? defH : img.elt.height; + } + if (img.elt && img.elt.videoWidth && !img.canvas) { + // video no canvas + defW = defW !== undefined ? defW : img.elt.videoWidth; + defH = defH !== undefined ? defH : img.elt.videoHeight; + } -/** - * @method tint - * @param {p5.Color} color the tint color - */ -p5.prototype.tint = function(...args) { - p5._validateParameters('tint', args); - const c = this.color(...args); - this._renderer._tint = c.levels; -}; + let _dx = dx; + let _dy = dy; + let _dw = dWidth || defW; + let _dh = dHeight || defH; + let _sx = sx || 0; + let _sy = sy || 0; + let _sw = sWidth !== undefined ? sWidth : defW; + let _sh = sHeight !== undefined ? sHeight : defH; + + _sw = _sAssign(_sw, defW); + _sh = _sAssign(_sh, defH); + + // This part needs cleanup and unit tests + // see issues https://github.com/processing/p5.js/issues/1741 + // and https://github.com/processing/p5.js/issues/1673 + let pd = 1; + + if (img.elt && !img.canvas && img.elt.style.width) { + //if img is video and img.elt.size() has been used and + //no width passed to image() + if (img.elt.videoWidth && !dWidth) { + pd = img.elt.videoWidth; + } else { + //all other cases + pd = img.elt.width; + } + pd /= parseInt(img.elt.style.width, 10); + } -/** - * Removes the current tint set by tint(). - * - * `noTint()` restores images to their original colors. - * - * @method noTint - * - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/laDefense.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Left. - * // Tint with a CSS color string. - * tint('red'); - * image(img, 0, 0); - * - * // Right. - * // Remove the tint. - * noTint(); - * image(img, 50, 0); - * - * describe('Two images of an umbrella and a ceiling side-by-side. The image on the left has a red tint.'); - * } - * - *
    - */ -p5.prototype.noTint = function() { - this._renderer._tint = null; -}; + _sx *= pd; + _sy *= pd; + _sh *= pd; + _sw *= pd; -/** - * Apply the current tint color to the input image, return the resulting - * canvas. - * - * @private - * @param {p5.Image} The image to be tinted - * @return {canvas} The resulting tinted canvas - */ -p5.prototype._getTintedImageCanvas = - p5.Renderer2D.prototype._getTintedImageCanvas; + let vals = canvas.modeAdjust(_dx, _dy, _dw, _dh, this._renderer._imageMode); + vals = _imageFit( + fit, + xAlign, + yAlign, + vals.x, + vals.y, + vals.w, + vals.h, + _sx, + _sy, + _sw, + _sh + ); -/** - * Changes the location from which images are drawn when - * image() is called. - * - * By default, the first - * two parameters of image() are the x- and - * y-coordinates of the image's upper-left corner. The next parameters are - * its width and height. This is the same as calling `imageMode(CORNER)`. - * - * `imageMode(CORNERS)` also uses the first two parameters of - * image() as the x- and y-coordinates of the image's - * top-left corner. The third and fourth parameters are the coordinates of its - * bottom-right corner. - * - * `imageMode(CENTER)` uses the first two parameters of - * image() as the x- and y-coordinates of the image's - * center. The next parameters are its width and height. - * - * @method imageMode - * @param {(CORNER|CORNERS|CENTER)} mode either CORNER, CORNERS, or CENTER. - * - * @example - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use CORNER mode. - * imageMode(CORNER); - * - * // Display the image. - * image(img, 10, 10, 50, 50); - * - * describe('A square image of a brick wall is drawn at the top left of a gray square.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use CORNERS mode. - * imageMode(CORNERS); - * - * // Display the image. - * image(img, 10, 10, 90, 40); - * - * describe('An image of a brick wall is drawn on a gray square. The image is squeezed into a small rectangular area.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Use CENTER mode. - * imageMode(CENTER); - * - * // Display the image. - * image(img, 50, 50, 80, 80); - * - * describe('A square image of a brick wall is drawn on a gray square.'); - * } - * - *
    - */ -p5.prototype.imageMode = function(m) { - p5._validateParameters('imageMode', arguments); - if ( - m === constants.CORNER || - m === constants.CORNERS || - m === constants.CENTER - ) { - this._renderer._imageMode = m; - } -}; + // tint the image if there is a tint + this._renderer.image( + img, + vals.sx, + vals.sy, + vals.sw, + vals.sh, + vals.dx, + vals.dy, + vals.dw, + vals.dh + ); + }; + + /** + * Tints images using a color. + * + * The version of `tint()` with one parameter interprets it one of four ways. + * If the parameter is a number, it's interpreted as a grayscale value. If the + * parameter is a string, it's interpreted as a CSS color string. An array of + * `[R, G, B, A]` values or a p5.Color object can + * also be used to set the tint color. + * + * The version of `tint()` with two parameters uses the first one as a + * grayscale value and the second as an alpha value. For example, calling + * `tint(255, 128)` will make an image 50% transparent. + * + * The version of `tint()` with three parameters interprets them as RGB or + * HSB values, depending on the current + * colorMode(). The optional fourth parameter + * sets the alpha value. For example, `tint(255, 0, 0, 100)` will give images + * a red tint and make them transparent. + * + * @method tint + * @param {Number} v1 red or hue value. + * @param {Number} v2 green or saturation value. + * @param {Number} v3 blue or brightness. + * @param {Number} [alpha] + * + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Left image. + * image(img, 0, 0); + * + * // Right image. + * // Tint with a CSS color string. + * tint('red'); + * image(img, 50, 0); + * + * describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a red tint.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Left image. + * image(img, 0, 0); + * + * // Right image. + * // Tint with RGB values. + * tint(255, 0, 0); + * image(img, 50, 0); + * + * describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a red tint.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Left. + * image(img, 0, 0); + * + * // Right. + * // Tint with RGBA values. + * tint(255, 0, 0, 100); + * image(img, 50, 0); + * + * describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a transparent red tint.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Left. + * image(img, 0, 0); + * + * // Right. + * // Tint with grayscale and alpha values. + * tint(255, 180); + * image(img, 50, 0); + * + * describe('Two images of an umbrella and a ceiling side-by-side. The image on the right is transparent.'); + * } + * + *
    + */ + /** + * @method tint + * @param {String} value CSS color string. + */ + + /** + * @method tint + * @param {Number} gray grayscale value. + * @param {Number} [alpha] + */ + + /** + * @method tint + * @param {Number[]} values array containing the red, green, blue & + * alpha components of the color. + */ + + /** + * @method tint + * @param {p5.Color} color the tint color + */ + fn.tint = function(...args) { + p5._validateParameters('tint', args); + const c = this.color(...args); + this._renderer._tint = c.levels; + }; + + /** + * Removes the current tint set by tint(). + * + * `noTint()` restores images to their original colors. + * + * @method noTint + * + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Left. + * // Tint with a CSS color string. + * tint('red'); + * image(img, 0, 0); + * + * // Right. + * // Remove the tint. + * noTint(); + * image(img, 50, 0); + * + * describe('Two images of an umbrella and a ceiling side-by-side. The image on the left has a red tint.'); + * } + * + *
    + */ + fn.noTint = function() { + this._renderer._tint = null; + }; -export default p5; + /** + * Apply the current tint color to the input image, return the resulting + * canvas. + * + * @private + * @param {p5.Image} The image to be tinted + * @return {canvas} The resulting tinted canvas + */ + fn._getTintedImageCanvas = + p5.Renderer2D.prototype._getTintedImageCanvas; + + /** + * Changes the location from which images are drawn when + * image() is called. + * + * By default, the first + * two parameters of image() are the x- and + * y-coordinates of the image's upper-left corner. The next parameters are + * its width and height. This is the same as calling `imageMode(CORNER)`. + * + * `imageMode(CORNERS)` also uses the first two parameters of + * image() as the x- and y-coordinates of the image's + * top-left corner. The third and fourth parameters are the coordinates of its + * bottom-right corner. + * + * `imageMode(CENTER)` uses the first two parameters of + * image() as the x- and y-coordinates of the image's + * center. The next parameters are its width and height. + * + * @method imageMode + * @param {(CORNER|CORNERS|CENTER)} mode either CORNER, CORNERS, or CENTER. + * + * @example + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use CORNER mode. + * imageMode(CORNER); + * + * // Display the image. + * image(img, 10, 10, 50, 50); + * + * describe('A square image of a brick wall is drawn at the top left of a gray square.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use CORNERS mode. + * imageMode(CORNERS); + * + * // Display the image. + * image(img, 10, 10, 90, 40); + * + * describe('An image of a brick wall is drawn on a gray square. The image is squeezed into a small rectangular area.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Use CENTER mode. + * imageMode(CENTER); + * + * // Display the image. + * image(img, 50, 50, 80, 80); + * + * describe('A square image of a brick wall is drawn on a gray square.'); + * } + * + *
    + */ + fn.imageMode = function(m) { + p5._validateParameters('imageMode', arguments); + if ( + m === constants.CORNER || + m === constants.CORNERS || + m === constants.CENTER + ) { + this._renderer._imageMode = m; + } + }; +} + +export default loadingDisplaying; + +if(typeof p5 !== 'undefined'){ + loadingDisplaying(p5, p5.prototype); +} diff --git a/src/image/p5.Image.js b/src/image/p5.Image.js index 5505e8a30a..fc45772c00 100644 --- a/src/image/p5.Image.js +++ b/src/image/p5.Image.js @@ -10,213 +10,67 @@ * This module defines the p5.Image class and P5 methods for * drawing images to the main display canvas. */ - -import p5 from '../core/main'; import Filters from './filters'; import Renderer from '../core/p5.Renderer'; -/* - * Class methods - */ - -/** - * A class to describe an image. - * - * Images are rectangular grids of pixels that can be displayed and modified. - * - * Existing images can be loaded by calling - * loadImage(). Blank images can be created by - * calling createImage(). `p5.Image` objects - * have methods for common tasks such as applying filters and modifying - * pixel values. - * - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('An image of a brick wall.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Apply the GRAY filter. - * img.filter(GRAY); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('A grayscale image of a brick wall.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Image object. - * let img = createImage(66, 66); - * - * // Load the image's pixels. - * img.loadPixels(); - * - * // Set the pixels to black. - * for (let x = 0; x < img.width; x += 1) { - * for (let y = 0; y < img.height; y += 1) { - * img.set(x, y, 0); - * } - * } - * - * // Update the image. - * img.updatePixels(); - * - * // Display the image. - * image(img, 17, 17); - * - * describe('A black square drawn in the middle of a gray square.'); - * } - * - *
    - * - * @class p5.Image - * @param {Number} width - * @param {Number} height - */ -p5.Image = class Image { - constructor(width, height) { - this.width = width; - this.height = height; - this.canvas = document.createElement('canvas'); - this.canvas.width = this.width; - this.canvas.height = this.height; - this.drawingContext = this.canvas.getContext('2d'); - this._pixelsState = this; - this._pixelDensity = 1; - //Object for working with GIFs, defaults to null - this.gifProperties = null; - //For WebGL Texturing only: used to determine whether to reupload texture to GPU - this._modified = false; - this.pixels = []; - } - - /** - * Gets or sets the pixel density for high pixel density displays. - * - * By default, the density will be set to 1. - * - * Call this method with no arguments to get the default density, or pass - * in a number to set the density. If a non-positive number is provided, - * it defaults to 1. - * - * @param {Number} [density] A scaling factor for the number of pixels per - * side - * @returns {Number} The current density if called without arguments, or the instance for chaining if setting density. - */ - pixelDensity(density) { - if (typeof density !== 'undefined') { - // Setter: set the density and handle resize - if (density <= 0) { - const errorObj = { - type: 'INVALID_VALUE', - format: { types: ['Number'] }, - position: 1 - }; - - p5._friendlyParamError(errorObj, 'pixelDensity'); - - // Default to 1 in case of an invalid value - density = 1; - } - - this._pixelDensity = density; - - // Adjust canvas dimensions based on pixel density - this.width /= density; - this.height /= density; - - return this; // Return the image instance for chaining if needed - } else { - // Getter: return the default density - return this._pixelDensity; - } - } - - /** - * Helper function for animating GIF-based images with time - */ - _animateGif(pInst) { - const props = this.gifProperties; - const curTime = pInst._lastRealFrameTime || window.performance.now(); - if (props.lastChangeTime === 0) { - props.lastChangeTime = curTime; - } - if (props.playing) { - props.timeDisplayed = curTime - props.lastChangeTime; - const curDelay = props.frames[props.displayIndex].delay; - if (props.timeDisplayed >= curDelay) { - //GIF is bound to 'realtime' so can skip frames - const skips = Math.floor(props.timeDisplayed / curDelay); - props.timeDisplayed = 0; - props.lastChangeTime = curTime; - props.displayIndex += skips; - props.loopCount = Math.floor(props.displayIndex / props.numFrames); - if (props.loopLimit !== null && props.loopCount >= props.loopLimit) { - props.playing = false; - } else { - const ind = props.displayIndex % props.numFrames; - this.drawingContext.putImageData(props.frames[ind].image, 0, 0); - props.displayIndex = ind; - this.setModified(true); - } - } - } - } - - /** - * Helper fxn for sharing pixel methods - */ - _setProperty(prop, value) { - this[prop] = value; - this.setModified(true); - } - +function image(p5, fn){ /** - * Loads the current value of each pixel in the image into the `img.pixels` - * array. + * A class to describe an image. * - * `img.loadPixels()` must be called before reading or modifying pixel - * values. + * Images are rectangular grids of pixels that can be displayed and modified. + * + * Existing images can be loaded by calling + * loadImage(). Blank images can be created by + * calling createImage(). `p5.Image` objects + * have methods for common tasks such as applying filters and modifying + * pixel values. * * @example *
    * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('An image of a brick wall.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Apply the GRAY filter. + * img.filter(GRAY); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('A grayscale image of a brick wall.'); + * } + * + *
    + * + *
    + * * function setup() { * createCanvas(100, 100); * @@ -246,7 +100,244 @@ p5.Image = class Image { * *
    * - *
    + * @class p5.Image + * @param {Number} width + * @param {Number} height + */ + p5.Image = class Image { + constructor(width, height) { + this.width = width; + this.height = height; + this.canvas = document.createElement('canvas'); + this.canvas.width = this.width; + this.canvas.height = this.height; + this.drawingContext = this.canvas.getContext('2d'); + this._pixelsState = this; + this._pixelDensity = 1; + //Object for working with GIFs, defaults to null + this.gifProperties = null; + //For WebGL Texturing only: used to determine whether to reupload texture to GPU + this._modified = false; + this.pixels = []; + } + + /** + * Gets or sets the pixel density for high pixel density displays. + * + * By default, the density will be set to 1. + * + * Call this method with no arguments to get the default density, or pass + * in a number to set the density. If a non-positive number is provided, + * it defaults to 1. + * + * @param {Number} [density] A scaling factor for the number of pixels per + * side + * @returns {Number} The current density if called without arguments, or the instance for chaining if setting density. + */ + pixelDensity(density) { + if (typeof density !== 'undefined') { + // Setter: set the density and handle resize + if (density <= 0) { + const errorObj = { + type: 'INVALID_VALUE', + format: { types: ['Number'] }, + position: 1 + }; + + p5._friendlyParamError(errorObj, 'pixelDensity'); + + // Default to 1 in case of an invalid value + density = 1; + } + + this._pixelDensity = density; + + // Adjust canvas dimensions based on pixel density + this.width /= density; + this.height /= density; + + return this; // Return the image instance for chaining if needed + } else { + // Getter: return the default density + return this._pixelDensity; + } + } + + /** + * Helper function for animating GIF-based images with time + */ + _animateGif(pInst) { + const props = this.gifProperties; + const curTime = pInst._lastRealFrameTime || window.performance.now(); + if (props.lastChangeTime === 0) { + props.lastChangeTime = curTime; + } + if (props.playing) { + props.timeDisplayed = curTime - props.lastChangeTime; + const curDelay = props.frames[props.displayIndex].delay; + if (props.timeDisplayed >= curDelay) { + //GIF is bound to 'realtime' so can skip frames + const skips = Math.floor(props.timeDisplayed / curDelay); + props.timeDisplayed = 0; + props.lastChangeTime = curTime; + props.displayIndex += skips; + props.loopCount = Math.floor(props.displayIndex / props.numFrames); + if (props.loopLimit !== null && props.loopCount >= props.loopLimit) { + props.playing = false; + } else { + const ind = props.displayIndex % props.numFrames; + this.drawingContext.putImageData(props.frames[ind].image, 0, 0); + props.displayIndex = ind; + this.setModified(true); + } + } + } + } + + /** + * Helper fxn for sharing pixel methods + */ + _setProperty(prop, value) { + this[prop] = value; + this.setModified(true); + } + + /** + * Loads the current value of each pixel in the image into the `img.pixels` + * array. + * + * `img.loadPixels()` must be called before reading or modifying pixel + * values. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Image object. + * let img = createImage(66, 66); + * + * // Load the image's pixels. + * img.loadPixels(); + * + * // Set the pixels to black. + * for (let x = 0; x < img.width; x += 1) { + * for (let y = 0; y < img.height; y += 1) { + * img.set(x, y, 0); + * } + * } + * + * // Update the image. + * img.updatePixels(); + * + * // Display the image. + * image(img, 17, 17); + * + * describe('A black square drawn in the middle of a gray square.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Image object. + * let img = createImage(66, 66); + * + * // Load the image's pixels. + * img.loadPixels(); + * + * for (let i = 0; i < img.pixels.length; i += 4) { + * // Red. + * img.pixels[i] = 0; + * // Green. + * img.pixels[i + 1] = 0; + * // Blue. + * img.pixels[i + 2] = 0; + * // Alpha. + * img.pixels[i + 3] = 255; + * } + * + * // Update the image. + * img.updatePixels(); + * + * // Display the image. + * image(img, 17, 17); + * + * describe('A black square drawn in the middle of a gray square.'); + * } + * + *
    + */ + loadPixels() { + p5.Renderer2D.prototype.loadPixels.call(this); + this.setModified(true); + } + + /** + * Updates the canvas with the RGBA values in the + * img.pixels array. + * + * `img.updatePixels()` only needs to be called after changing values in + * the img.pixels array. Such changes can be + * made directly after calling + * img.loadPixels() or by calling + * img.set(). + * + * The optional parameters `x`, `y`, `width`, and `height` define a + * subsection of the image to update. Doing so can improve performance in + * some cases. + * + * If the image was loaded from a GIF, then calling `img.updatePixels()` + * will update the pixels in current frame. + * + * @param {Integer} x x-coordinate of the upper-left corner + * of the subsection to update. + * @param {Integer} y y-coordinate of the upper-left corner + * of the subsection to update. + * @param {Integer} w width of the subsection to update. + * @param {Integer} h height of the subsection to update. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Image object. + * let img = createImage(66, 66); + * + * // Load the image's pixels. + * img.loadPixels(); + * + * // Set the pixels to black. + * for (let x = 0; x < img.width; x += 1) { + * for (let y = 0; y < img.height; y += 1) { + * img.set(x, y, 0); + * } + * } + * + * // Update the image. + * img.updatePixels(); + * + * // Display the image. + * image(img, 17, 17); + * + * describe('A black square drawn in the middle of a gray square.'); + * } + * + *
    + * + *
    * * function setup() { * createCanvas(100, 100); @@ -259,6 +350,7 @@ p5.Image = class Image { * // Load the image's pixels. * img.loadPixels(); * + * // Set the pixels to black. * for (let i = 0; i < img.pixels.length; i += 4) { * // Red. * img.pixels[i] = 0; @@ -280,1160 +372,1476 @@ p5.Image = class Image { * } * *
    - */ - loadPixels() { - p5.Renderer2D.prototype.loadPixels.call(this); - this.setModified(true); - } - - /** - * Updates the canvas with the RGBA values in the - * img.pixels array. - * - * `img.updatePixels()` only needs to be called after changing values in - * the img.pixels array. Such changes can be - * made directly after calling - * img.loadPixels() or by calling - * img.set(). - * - * The optional parameters `x`, `y`, `width`, and `height` define a - * subsection of the image to update. Doing so can improve performance in - * some cases. - * - * If the image was loaded from a GIF, then calling `img.updatePixels()` - * will update the pixels in current frame. - * - * @param {Integer} x x-coordinate of the upper-left corner - * of the subsection to update. - * @param {Integer} y y-coordinate of the upper-left corner - * of the subsection to update. - * @param {Integer} w width of the subsection to update. - * @param {Integer} h height of the subsection to update. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Image object. - * let img = createImage(66, 66); - * - * // Load the image's pixels. - * img.loadPixels(); - * - * // Set the pixels to black. - * for (let x = 0; x < img.width; x += 1) { - * for (let y = 0; y < img.height; y += 1) { - * img.set(x, y, 0); - * } - * } - * - * // Update the image. - * img.updatePixels(); - * - * // Display the image. - * image(img, 17, 17); - * - * describe('A black square drawn in the middle of a gray square.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Image object. - * let img = createImage(66, 66); - * - * // Load the image's pixels. - * img.loadPixels(); - * - * // Set the pixels to black. - * for (let i = 0; i < img.pixels.length; i += 4) { - * // Red. - * img.pixels[i] = 0; - * // Green. - * img.pixels[i + 1] = 0; - * // Blue. - * img.pixels[i + 2] = 0; - * // Alpha. - * img.pixels[i + 3] = 255; - * } - * - * // Update the image. - * img.updatePixels(); - * - * // Display the image. - * image(img, 17, 17); - * - * describe('A black square drawn in the middle of a gray square.'); - * } - * - *
    - */ - /** - */ - updatePixels(x, y, w, h) { - p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h); - this.setModified(true); - } + */ + /** + */ + updatePixels(x, y, w, h) { + p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h); + this.setModified(true); + } - /** - * Gets a pixel or a region of pixels from the image. - * - * `img.get()` is easy to use but it's not as fast as - * img.pixels. Use - * img.pixels to read many pixel values. - * - * The version of `img.get()` with no parameters returns the entire image. - * - * The version of `img.get()` with two parameters, as in `img.get(10, 20)`, - * interprets them as coordinates. It returns an array with the - * `[R, G, B, A]` values of the pixel at the given point. - * - * The version of `img.get()` with four parameters, as in - * `img,get(10, 20, 50, 90)`, interprets them as - * coordinates and dimensions. The first two parameters are the coordinates - * of the upper-left corner of the subsection. The last two parameters are - * the width and height of the subsection. It returns a subsection of the - * canvas in a new p5.Image object. - * - * Use `img.get()` instead of get() to work directly - * with images. - * - * @param {Number} x x-coordinate of the pixel. - * @param {Number} y y-coordinate of the pixel. - * @param {Number} w width of the subsection to be returned. - * @param {Number} h height of the subsection to be returned. - * @return {p5.Image} subsection as a p5.Image object. - * - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Display the image. - * image(img, 0, 0); - * - * // Copy the image. - * let img2 = get(); - * - * // Display the copied image on the right. - * image(img2, 50, 0); - * - * describe('Two identical mountain landscapes shown side-by-side.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Get a pixel's color. - * let c = img.get(50, 90); - * - * // Style the square using the pixel's color. - * fill(c); - * noStroke(); - * - * // Draw the square. - * square(25, 25, 50); - * - * describe('A mountain landscape with an olive green square in its center.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Copy half of the image. - * let img2 = img.get(0, 0, img.width / 2, img.height / 2); - * - * // Display half of the image. - * image(img2, 50, 50); - * - * describe('A mountain landscape drawn on top of another mountain landscape.'); - * } - * - *
    - */ - /** - * @return {p5.Image} whole p5.Image - */ - /** - * @param {Number} x - * @param {Number} y - * @return {Number[]} color of the pixel at (x, y) in array format `[R, G, B, A]`. - */ - get(x, y, w, h) { - p5._validateParameters('p5.Image.get', arguments); - return p5.Renderer2D.prototype.get.apply(this, arguments); - } - - _getPixel(...args) { - return p5.Renderer2D.prototype._getPixel.apply(this, args); - } + /** + * Gets a pixel or a region of pixels from the image. + * + * `img.get()` is easy to use but it's not as fast as + * img.pixels. Use + * img.pixels to read many pixel values. + * + * The version of `img.get()` with no parameters returns the entire image. + * + * The version of `img.get()` with two parameters, as in `img.get(10, 20)`, + * interprets them as coordinates. It returns an array with the + * `[R, G, B, A]` values of the pixel at the given point. + * + * The version of `img.get()` with four parameters, as in + * `img,get(10, 20, 50, 90)`, interprets them as + * coordinates and dimensions. The first two parameters are the coordinates + * of the upper-left corner of the subsection. The last two parameters are + * the width and height of the subsection. It returns a subsection of the + * canvas in a new p5.Image object. + * + * Use `img.get()` instead of get() to work directly + * with images. + * + * @param {Number} x x-coordinate of the pixel. + * @param {Number} y y-coordinate of the pixel. + * @param {Number} w width of the subsection to be returned. + * @param {Number} h height of the subsection to be returned. + * @return {p5.Image} subsection as a p5.Image object. + * + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Display the image. + * image(img, 0, 0); + * + * // Copy the image. + * let img2 = get(); + * + * // Display the copied image on the right. + * image(img2, 50, 0); + * + * describe('Two identical mountain landscapes shown side-by-side.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Get a pixel's color. + * let c = img.get(50, 90); + * + * // Style the square using the pixel's color. + * fill(c); + * noStroke(); + * + * // Draw the square. + * square(25, 25, 50); + * + * describe('A mountain landscape with an olive green square in its center.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Copy half of the image. + * let img2 = img.get(0, 0, img.width / 2, img.height / 2); + * + * // Display half of the image. + * image(img2, 50, 50); + * + * describe('A mountain landscape drawn on top of another mountain landscape.'); + * } + * + *
    + */ + /** + * @return {p5.Image} whole p5.Image + */ + /** + * @param {Number} x + * @param {Number} y + * @return {Number[]} color of the pixel at (x, y) in array format `[R, G, B, A]`. + */ + get(x, y, w, h) { + p5._validateParameters('p5.Image.get', arguments); + return p5.Renderer2D.prototype.get.apply(this, arguments); + } - /** - * Sets the color of one or more pixels within an image. - * - * `img.set()` is easy to use but it's not as fast as - * img.pixels. Use - * img.pixels to set many pixel values. - * - * `img.set()` interprets the first two parameters as x- and y-coordinates. It - * interprets the last parameter as a grayscale value, a `[R, G, B, A]` pixel - * array, a p5.Color object, or another - * p5.Image object. - * - * img.updatePixels() must be called - * after using `img.set()` for changes to appear. - * - * @param {Number} x x-coordinate of the pixel. - * @param {Number} y y-coordinate of the pixel. - * @param {Number|Number[]|Object} a grayscale value | pixel array | - * p5.Color object | - * p5.Image to copy. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Image object. - * let img = createImage(100, 100); - * - * // Set four pixels to black. - * img.set(30, 20, 0); - * img.set(85, 20, 0); - * img.set(85, 75, 0); - * img.set(30, 75, 0); - * - * // Update the image. - * img.updatePixels(); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('Four black dots arranged in a square drawn on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Image object. - * let img = createImage(100, 100); - * - * // Create a p5.Color object. - * let black = color(0); - * - * // Set four pixels to black. - * img.set(30, 20, black); - * img.set(85, 20, black); - * img.set(85, 75, black); - * img.set(30, 75, black); - * - * // Update the image. - * img.updatePixels(); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('Four black dots arranged in a square drawn on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Image object. - * let img = createImage(66, 66); - * - * // Draw a color gradient. - * for (let x = 0; x < img.width; x += 1) { - * for (let y = 0; y < img.height; y += 1) { - * let c = map(x, 0, img.width, 0, 255); - * img.set(x, y, c); - * } - * } - * - * // Update the image. - * img.updatePixels(); - * - * // Display the image. - * image(img, 17, 17); - * - * describe('A square with a horiztonal color gradient from black to white drawn on a gray background.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Create a p5.Image object. - * let img2 = createImage(100, 100); - * - * // Set the blank image's pixels using the landscape. - * img2.set(0, 0, img); - * - * // Display the second image. - * image(img2, 0, 0); - * - * describe('An image of a mountain landscape.'); - * } - * - *
    - */ - set(x, y, imgOrCol) { - p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol); - this.setModified(true); - } + _getPixel(...args) { + return p5.Renderer2D.prototype._getPixel.apply(this, args); + } - /** - * Resizes the image to a given width and height. - * - * The image's original aspect ratio can be kept by passing 0 for either - * `width` or `height`. For example, calling `img.resize(50, 0)` on an image - * that was 500 × 300 pixels will resize it to 50 × 30 pixels. - * - * @param {Number} width resized image width. - * @param {Number} height resized image height. - * - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Resize the image. - * img.resize(50, 100); - * - * // Display the resized image. - * image(img, 0, 0); - * - * describe('Two images of a mountain landscape. One copy of the image is squeezed horizontally.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Resize the image, keeping the aspect ratio. - * img.resize(0, 30); - * - * // Display the resized image. - * image(img, 0, 0); - * - * describe('Two images of a mountain landscape. The small copy of the image covers the top-left corner of the larger image.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Resize the image, keeping the aspect ratio. - * img.resize(60, 0); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('Two images of a mountain landscape. The small copy of the image covers the top-left corner of the larger image.'); - * } - * - *
    - */ - resize(width, height) { - // Copy contents to a temporary canvas, resize the original - // and then copy back. - // - // There is a faster approach that involves just one copy and swapping the - // this.canvas reference. We could switch to that approach if (as i think - // is the case) there an expectation that the user would not hold a - // reference to the backing canvas of a p5.Image. But since we do not - // enforce that at the moment, I am leaving in the slower, but safer - // implementation. + /** + * Sets the color of one or more pixels within an image. + * + * `img.set()` is easy to use but it's not as fast as + * img.pixels. Use + * img.pixels to set many pixel values. + * + * `img.set()` interprets the first two parameters as x- and y-coordinates. It + * interprets the last parameter as a grayscale value, a `[R, G, B, A]` pixel + * array, a p5.Color object, or another + * p5.Image object. + * + * img.updatePixels() must be called + * after using `img.set()` for changes to appear. + * + * @param {Number} x x-coordinate of the pixel. + * @param {Number} y y-coordinate of the pixel. + * @param {Number|Number[]|Object} a grayscale value | pixel array | + * p5.Color object | + * p5.Image to copy. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Image object. + * let img = createImage(100, 100); + * + * // Set four pixels to black. + * img.set(30, 20, 0); + * img.set(85, 20, 0); + * img.set(85, 75, 0); + * img.set(30, 75, 0); + * + * // Update the image. + * img.updatePixels(); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('Four black dots arranged in a square drawn on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Image object. + * let img = createImage(100, 100); + * + * // Create a p5.Color object. + * let black = color(0); + * + * // Set four pixels to black. + * img.set(30, 20, black); + * img.set(85, 20, black); + * img.set(85, 75, black); + * img.set(30, 75, black); + * + * // Update the image. + * img.updatePixels(); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('Four black dots arranged in a square drawn on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Image object. + * let img = createImage(66, 66); + * + * // Draw a color gradient. + * for (let x = 0; x < img.width; x += 1) { + * for (let y = 0; y < img.height; y += 1) { + * let c = map(x, 0, img.width, 0, 255); + * img.set(x, y, c); + * } + * } + * + * // Update the image. + * img.updatePixels(); + * + * // Display the image. + * image(img, 17, 17); + * + * describe('A square with a horiztonal color gradient from black to white drawn on a gray background.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Create a p5.Image object. + * let img2 = createImage(100, 100); + * + * // Set the blank image's pixels using the landscape. + * img2.set(0, 0, img); + * + * // Display the second image. + * image(img2, 0, 0); + * + * describe('An image of a mountain landscape.'); + * } + * + *
    + */ + set(x, y, imgOrCol) { + p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol); + this.setModified(true); + } + + /** + * Resizes the image to a given width and height. + * + * The image's original aspect ratio can be kept by passing 0 for either + * `width` or `height`. For example, calling `img.resize(50, 0)` on an image + * that was 500 × 300 pixels will resize it to 50 × 30 pixels. + * + * @param {Number} width resized image width. + * @param {Number} height resized image height. + * + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Resize the image. + * img.resize(50, 100); + * + * // Display the resized image. + * image(img, 0, 0); + * + * describe('Two images of a mountain landscape. One copy of the image is squeezed horizontally.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Resize the image, keeping the aspect ratio. + * img.resize(0, 30); + * + * // Display the resized image. + * image(img, 0, 0); + * + * describe('Two images of a mountain landscape. The small copy of the image covers the top-left corner of the larger image.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Resize the image, keeping the aspect ratio. + * img.resize(60, 0); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('Two images of a mountain landscape. The small copy of the image covers the top-left corner of the larger image.'); + * } + * + *
    + */ + resize(width, height) { + // Copy contents to a temporary canvas, resize the original + // and then copy back. + // + // There is a faster approach that involves just one copy and swapping the + // this.canvas reference. We could switch to that approach if (as i think + // is the case) there an expectation that the user would not hold a + // reference to the backing canvas of a p5.Image. But since we do not + // enforce that at the moment, I am leaving in the slower, but safer + // implementation. + + // auto-resize + if (width === 0 && height === 0) { + width = this.canvas.width; + height = this.canvas.height; + } else if (width === 0) { + width = this.canvas.width * height / this.canvas.height; + } else if (height === 0) { + height = this.canvas.height * width / this.canvas.width; + } + + width = Math.floor(width); + height = Math.floor(height); + + const tempCanvas = document.createElement('canvas'); + tempCanvas.width = width; + tempCanvas.height = height; + + if (this.gifProperties) { + const props = this.gifProperties; + //adapted from github.com/LinusU/resize-image-data + const nearestNeighbor = (src, dst) => { + let pos = 0; + for (let y = 0; y < dst.height; y++) { + for (let x = 0; x < dst.width; x++) { + const srcX = Math.floor(x * src.width / dst.width); + const srcY = Math.floor(y * src.height / dst.height); + let srcPos = (srcY * src.width + srcX) * 4; + dst.data[pos++] = src.data[srcPos++]; // R + dst.data[pos++] = src.data[srcPos++]; // G + dst.data[pos++] = src.data[srcPos++]; // B + dst.data[pos++] = src.data[srcPos++]; // A + } + } + }; + for (let i = 0; i < props.numFrames; i++) { + const resizedImageData = this.drawingContext.createImageData( + width, + height + ); + nearestNeighbor(props.frames[i].image, resizedImageData); + props.frames[i].image = resizedImageData; + } + } + + tempCanvas.getContext('2d').drawImage( + this.canvas, + 0, 0, this.canvas.width, this.canvas.height, + 0, 0, tempCanvas.width, tempCanvas.height + ); + + // Resize the original canvas, which will clear its contents + this.canvas.width = this.width = width; + this.canvas.height = this.height = height; + + //Copy the image back + this.drawingContext.drawImage( + tempCanvas, + 0, 0, width, height, + 0, 0, width, height + ); + + if (this.pixels.length > 0) { + this.loadPixels(); + } + + this.setModified(true); + } + + /** + * Copies pixels from a source image to this image. + * + * The first parameter, `srcImage`, is an optional + * p5.Image object to copy. If a source image isn't + * passed, then `img.copy()` can copy a region of this image to another + * region. + * + * The next four parameters, `sx`, `sy`, `sw`, and `sh` determine the region + * to copy from the source image. `(sx, sy)` is the top-left corner of the + * region. `sw` and `sh` are the region's width and height. + * + * The next four parameters, `dx`, `dy`, `dw`, and `dh` determine the region + * of this image to copy into. `(dx, dy)` is the top-left corner of the + * region. `dw` and `dh` are the region's width and height. + * + * Calling `img.copy()` will scale pixels from the source region if it isn't + * the same size as the destination region. + * + * @param {p5.Image|p5.Element} srcImage source image. + * @param {Integer} sx x-coordinate of the source's upper-left corner. + * @param {Integer} sy y-coordinate of the source's upper-left corner. + * @param {Integer} sw source image width. + * @param {Integer} sh source image height. + * @param {Integer} dx x-coordinate of the destination's upper-left corner. + * @param {Integer} dy y-coordinate of the destination's upper-left corner. + * @param {Integer} dw destination image width. + * @param {Integer} dh destination image height. + * + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Copy one region of the image to another. + * img.copy(7, 22, 10, 10, 35, 25, 50, 50); + * + * // Display the image. + * image(img, 0, 0); + * + * // Outline the copied region. + * stroke(255); + * noFill(); + * square(7, 22, 10); + * + * describe('An image of a mountain landscape. A square region is outlined in white. A larger square contains a pixelated view of the outlined region.'); + * } + * + *
    + * + *
    + * + * let mountains; + * let bricks; + * + * // Load the images. + * function preload() { + * mountains = loadImage('assets/rockies.jpg'); + * bricks = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Calculate the center of the bricks image. + * let x = bricks.width / 2; + * let y = bricks.height / 2; + * + * // Copy the bricks to the mountains image. + * mountains.copy(bricks, 0, 0, x, y, 0, 0, x, y); + * + * // Display the mountains image. + * image(mountains, 0, 0); + * + * describe('An image of a brick wall drawn at the top-left of an image of a mountain landscape.'); + * } + * + *
    + */ + /** + * @param {Integer} sx + * @param {Integer} sy + * @param {Integer} sw + * @param {Integer} sh + * @param {Integer} dx + * @param {Integer} dy + * @param {Integer} dw + * @param {Integer} dh + */ + copy(...args) { + fn.copy.apply(this, args); + } + + /** + * Masks part of the image with another. + * + * `img.mask()` uses another p5.Image object's + * alpha channel as the alpha channel for this image. Masks are cumulative + * and can't be removed once applied. If the mask has a different + * pixel density from this image, the mask will be scaled. + * + * @param {p5.Image} srcImage source image. + * + * @example + *
    + * + * let photo; + * let maskImage; + * + * // Load the images. + * function preload() { + * photo = loadImage('assets/rockies.jpg'); + * maskImage = loadImage('assets/mask2.png'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Apply the mask. + * photo.mask(maskImage); + * + * // Display the image. + * image(photo, 0, 0); + * + * describe('An image of a mountain landscape. The right side of the image has a faded patch of white.'); + * } + * + *
    + */ + // TODO: - Accept an array of alpha values. + mask(p5Image) { + if (p5Image === undefined) { + p5Image = this; + } + const currBlend = this.drawingContext.globalCompositeOperation; + + let imgScaleFactor = this._pixelDensity; + let maskScaleFactor = 1; + if (p5Image instanceof Renderer) { + maskScaleFactor = p5Image._pInst._pixelDensity; + } + + const copyArgs = [ + p5Image, + 0, + 0, + maskScaleFactor * p5Image.width, + maskScaleFactor * p5Image.height, + 0, + 0, + imgScaleFactor * this.width, + imgScaleFactor * this.height + ]; + + this.drawingContext.globalCompositeOperation = 'destination-in'; + if (this.gifProperties) { + for (let i = 0; i < this.gifProperties.frames.length; i++) { + this.drawingContext.putImageData( + this.gifProperties.frames[i].image, + 0, + 0 + ); + this.copy(...copyArgs); + this.gifProperties.frames[i].image = this.drawingContext.getImageData( + 0, + 0, + imgScaleFactor * this.width, + imgScaleFactor * this.height + ); + } + this.drawingContext.putImageData( + this.gifProperties.frames[this.gifProperties.displayIndex].image, + 0, + 0 + ); + } else { + this.copy(...copyArgs); + } + this.drawingContext.globalCompositeOperation = currBlend; + this.setModified(true); + } + + /** + * Applies an image filter to the image. + * + * The preset options are: + * + * `INVERT` + * Inverts the colors in the image. No parameter is used. + * + * `GRAY` + * Converts the image to grayscale. No parameter is used. + * + * `THRESHOLD` + * Converts the image to black and white. Pixels with a grayscale value + * above a given threshold are converted to white. The rest are converted to + * black. The threshold must be between 0.0 (black) and 1.0 (white). If no + * value is specified, 0.5 is used. + * + * `OPAQUE` + * Sets the alpha channel to be entirely opaque. No parameter is used. + * + * `POSTERIZE` + * Limits the number of colors in the image. Each color channel is limited to + * the number of colors specified. Values between 2 and 255 are valid, but + * results are most noticeable with lower values. The default value is 4. + * + * `BLUR` + * Blurs the image. The level of blurring is specified by a blur radius. Larger + * values increase the blur. The default value is 4. A gaussian blur is used + * in `P2D` mode. A box blur is used in `WEBGL` mode. + * + * `ERODE` + * Reduces the light areas. No parameter is used. + * + * `DILATE` + * Increases the light areas. No parameter is used. + * + * @param {(THRESHOLD|GRAY|OPAQUE|INVERT|POSTERIZE|ERODE|DILATE|BLUR)} filterType either THRESHOLD, GRAY, OPAQUE, INVERT, + * POSTERIZE, ERODE, DILATE or BLUR. + * @param {Number} [filterParam] parameter unique to each filter. + * + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Apply the INVERT filter. + * img.filter(INVERT); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('A blue brick wall.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Apply the GRAY filter. + * img.filter(GRAY); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('A brick wall drawn in grayscale.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Apply the THRESHOLD filter. + * img.filter(THRESHOLD); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('A brick wall drawn in black and white.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Apply the OPAQUE filter. + * img.filter(OPAQUE); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('A red brick wall.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Apply the POSTERIZE filter. + * img.filter(POSTERIZE, 3); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('An image of a red brick wall drawn with a limited color palette.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Apply the BLUR filter. + * img.filter(BLUR, 3); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('A blurry image of a red brick wall.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Apply the DILATE filter. + * img.filter(DILATE); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('A red brick wall with bright lines between each brick.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Apply the ERODE filter. + * img.filter(ERODE); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('A red brick wall with faint lines between each brick.'); + * } + * + *
    + */ + filter(operation, value) { + Filters.apply(this.canvas, Filters[operation], value); + this.setModified(true); + } - // auto-resize - if (width === 0 && height === 0) { - width = this.canvas.width; - height = this.canvas.height; - } else if (width === 0) { - width = this.canvas.width * height / this.canvas.height; - } else if (height === 0) { - height = this.canvas.height * width / this.canvas.width; + /** + * Copies a region of pixels from another image into this one. + * + * The first parameter, `srcImage`, is the + * p5.Image object to blend. + * + * The next four parameters, `sx`, `sy`, `sw`, and `sh` determine the region + * to blend from the source image. `(sx, sy)` is the top-left corner of the + * region. `sw` and `sh` are the regions width and height. + * + * The next four parameters, `dx`, `dy`, `dw`, and `dh` determine the region + * of the canvas to blend into. `(dx, dy)` is the top-left corner of the + * region. `dw` and `dh` are the regions width and height. + * + * The tenth parameter, `blendMode`, sets the effect used to blend the images' + * colors. The options are `BLEND`, `DARKEST`, `LIGHTEST`, `DIFFERENCE`, + * `MULTIPLY`, `EXCLUSION`, `SCREEN`, `REPLACE`, `OVERLAY`, `HARD_LIGHT`, + * `SOFT_LIGHT`, `DODGE`, `BURN`, `ADD`, or `NORMAL`. + * + * @param {p5.Image} srcImage source image + * @param {Integer} sx x-coordinate of the source's upper-left corner. + * @param {Integer} sy y-coordinate of the source's upper-left corner. + * @param {Integer} sw source image width. + * @param {Integer} sh source image height. + * @param {Integer} dx x-coordinate of the destination's upper-left corner. + * @param {Integer} dy y-coordinate of the destination's upper-left corner. + * @param {Integer} dw destination image width. + * @param {Integer} dh destination image height. + * @param {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode the blend mode. either + * BLEND, DARKEST, LIGHTEST, DIFFERENCE, + * MULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT, + * SOFT_LIGHT, DODGE, BURN, ADD or NORMAL. + * + * Available blend modes are: normal | multiply | screen | overlay | + * darken | lighten | color-dodge | color-burn | hard-light | + * soft-light | difference | exclusion | hue | saturation | + * color | luminosity + * + * http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/ + * + * @example + *
    + * + * let mountains; + * let bricks; + * + * // Load the images. + * function preload() { + * mountains = loadImage('assets/rockies.jpg'); + * bricks = loadImage('assets/bricks_third.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Blend the bricks image into the mountains. + * mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, ADD); + * + * // Display the mountains image. + * image(mountains, 0, 0); + * + * // Display the bricks image. + * image(bricks, 0, 0); + * + * describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears faded on the right of the image.'); + * } + * + *
    + * + *
    + * + * let mountains; + * let bricks; + * + * // Load the images. + * function preload() { + * mountains = loadImage('assets/rockies.jpg'); + * bricks = loadImage('assets/bricks_third.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Blend the bricks image into the mountains. + * mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, DARKEST); + * + * // Display the mountains image. + * image(mountains, 0, 0); + * + * // Display the bricks image. + * image(bricks, 0, 0); + * + * describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears transparent on the right of the image.'); + * } + * + *
    + * + *
    + * + * let mountains; + * let bricks; + * + * // Load the images. + * function preload() { + * mountains = loadImage('assets/rockies.jpg'); + * bricks = loadImage('assets/bricks_third.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Blend the bricks image into the mountains. + * mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST); + * + * // Display the mountains image. + * image(mountains, 0, 0); + * + * // Display the bricks image. + * image(bricks, 0, 0); + * + * describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears washed out on the right of the image.'); + * } + * + *
    + */ + /** + * @param {Integer} sx + * @param {Integer} sy + * @param {Integer} sw + * @param {Integer} sh + * @param {Integer} dx + * @param {Integer} dy + * @param {Integer} dw + * @param {Integer} dh + * @param {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode + */ + blend(...args) { + p5._validateParameters('p5.Image.blend', arguments); + fn.blend.apply(this, args); + this.setModified(true); } - width = Math.floor(width); - height = Math.floor(height); + /** + * helper method for web GL mode to indicate that an image has been + * changed or unchanged since last upload. gl texture upload will + * set this value to false after uploading the texture. + * @param {boolean} val sets whether or not the image has been + * modified. + * @private + */ + setModified(val) { + this._modified = val; //enforce boolean? + } - const tempCanvas = document.createElement('canvas'); - tempCanvas.width = width; - tempCanvas.height = height; + /** + * helper method for web GL mode to figure out if the image + * has been modified and might need to be re-uploaded to texture + * memory between frames. + * @private + * @return {boolean} a boolean indicating whether or not the + * image has been updated or modified since last texture upload. + */ + isModified() { + return this._modified; + } - if (this.gifProperties) { - const props = this.gifProperties; - //adapted from github.com/LinusU/resize-image-data - const nearestNeighbor = (src, dst) => { - let pos = 0; - for (let y = 0; y < dst.height; y++) { - for (let x = 0; x < dst.width; x++) { - const srcX = Math.floor(x * src.width / dst.width); - const srcY = Math.floor(y * src.height / dst.height); - let srcPos = (srcY * src.width + srcX) * 4; - dst.data[pos++] = src.data[srcPos++]; // R - dst.data[pos++] = src.data[srcPos++]; // G - dst.data[pos++] = src.data[srcPos++]; // B - dst.data[pos++] = src.data[srcPos++]; // A - } - } - }; - for (let i = 0; i < props.numFrames; i++) { - const resizedImageData = this.drawingContext.createImageData( - width, - height - ); - nearestNeighbor(props.frames[i].image, resizedImageData); - props.frames[i].image = resizedImageData; + /** + * Saves the image to a file. + * + * By default, `img.save()` saves the image as a PNG image called + * `untitled.png`. + * + * The first parameter, `filename`, is optional. It's a string that sets the + * file's name. If a file extension is included, as in + * `img.save('drawing.png')`, then the image will be saved using that + * format. + * + * The second parameter, `extension`, is also optional. It sets the files format. + * Either `'png'` or `'jpg'` can be used. For example, `img.save('drawing', 'jpg')` + * saves the canvas to a file called `drawing.jpg`. + * + * Note: The browser will either save the file immediately or prompt the user + * with a dialogue window. + * + * The image will only be downloaded as an animated GIF if it was loaded + * from a GIF file. See saveGif() to create new + * GIFs. + * + * @param {String} filename filename. Defaults to 'untitled'. + * @param {String} [extension] file extension, either 'png' or 'jpg'. + * Defaults to 'png'. + * + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * describe('An image of a mountain landscape. The image is downloaded when the user presses the "s", "j", or "p" key.'); + * } + * + * // Save the image with different options when the user presses a key. + * function keyPressed() { + * if (key === 's') { + * img.save(); + * } else if (key === 'j') { + * img.save('rockies.jpg'); + * } else if (key === 'p') { + * img.save('rockies', 'png'); + * } + * } + * + *
    + */ + save(filename, extension) { + if (this.gifProperties) { + fn.encodeAndDownloadGif(this, filename); + } else { + fn.saveCanvas(this.canvas, filename, extension); } } - tempCanvas.getContext('2d').drawImage( - this.canvas, - 0, 0, this.canvas.width, this.canvas.height, - 0, 0, tempCanvas.width, tempCanvas.height - ); - - // Resize the original canvas, which will clear its contents - this.canvas.width = this.width = width; - this.canvas.height = this.height = height; - - //Copy the image back - this.drawingContext.drawImage( - tempCanvas, - 0, 0, width, height, - 0, 0, width, height - ); - - if (this.pixels.length > 0) { - this.loadPixels(); + // GIF Section + /** + * Restarts an animated GIF at its first frame. + * + * @example + *
    + * + * let gif; + * + * // Load the image. + * function preload() { + * gif = loadImage('assets/arnott-wallace-wink-loop-once.gif'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * describe('A cartoon face winks once and then freezes. Clicking resets the face and makes it wink again.'); + * } + * + * function draw() { + * background(255); + * + * // Display the image. + * image(gif, 0, 0); + * } + * + * // Reset the GIF when the user presses the mouse. + * function mousePressed() { + * gif.reset(); + * } + * + *
    + */ + reset() { + if (this.gifProperties) { + const props = this.gifProperties; + props.playing = true; + props.timeSinceStart = 0; + props.timeDisplayed = 0; + props.lastChangeTime = 0; + props.loopCount = 0; + props.displayIndex = 0; + this.drawingContext.putImageData(props.frames[0].image, 0, 0); + } } - this.setModified(true); - } - - /** - * Copies pixels from a source image to this image. - * - * The first parameter, `srcImage`, is an optional - * p5.Image object to copy. If a source image isn't - * passed, then `img.copy()` can copy a region of this image to another - * region. - * - * The next four parameters, `sx`, `sy`, `sw`, and `sh` determine the region - * to copy from the source image. `(sx, sy)` is the top-left corner of the - * region. `sw` and `sh` are the region's width and height. - * - * The next four parameters, `dx`, `dy`, `dw`, and `dh` determine the region - * of this image to copy into. `(dx, dy)` is the top-left corner of the - * region. `dw` and `dh` are the region's width and height. - * - * Calling `img.copy()` will scale pixels from the source region if it isn't - * the same size as the destination region. - * - * @param {p5.Image|p5.Element} srcImage source image. - * @param {Integer} sx x-coordinate of the source's upper-left corner. - * @param {Integer} sy y-coordinate of the source's upper-left corner. - * @param {Integer} sw source image width. - * @param {Integer} sh source image height. - * @param {Integer} dx x-coordinate of the destination's upper-left corner. - * @param {Integer} dy y-coordinate of the destination's upper-left corner. - * @param {Integer} dw destination image width. - * @param {Integer} dh destination image height. - * - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Copy one region of the image to another. - * img.copy(7, 22, 10, 10, 35, 25, 50, 50); - * - * // Display the image. - * image(img, 0, 0); - * - * // Outline the copied region. - * stroke(255); - * noFill(); - * square(7, 22, 10); - * - * describe('An image of a mountain landscape. A square region is outlined in white. A larger square contains a pixelated view of the outlined region.'); - * } - * - *
    - * - *
    - * - * let mountains; - * let bricks; - * - * // Load the images. - * function preload() { - * mountains = loadImage('assets/rockies.jpg'); - * bricks = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Calculate the center of the bricks image. - * let x = bricks.width / 2; - * let y = bricks.height / 2; - * - * // Copy the bricks to the mountains image. - * mountains.copy(bricks, 0, 0, x, y, 0, 0, x, y); - * - * // Display the mountains image. - * image(mountains, 0, 0); - * - * describe('An image of a brick wall drawn at the top-left of an image of a mountain landscape.'); - * } - * - *
    - */ - /** - * @param {Integer} sx - * @param {Integer} sy - * @param {Integer} sw - * @param {Integer} sh - * @param {Integer} dx - * @param {Integer} dy - * @param {Integer} dw - * @param {Integer} dh - */ - copy(...args) { - p5.prototype.copy.apply(this, args); - } - - /** - * Masks part of the image with another. - * - * `img.mask()` uses another p5.Image object's - * alpha channel as the alpha channel for this image. Masks are cumulative - * and can't be removed once applied. If the mask has a different - * pixel density from this image, the mask will be scaled. - * - * @param {p5.Image} srcImage source image. - * - * @example - *
    - * - * let photo; - * let maskImage; - * - * // Load the images. - * function preload() { - * photo = loadImage('assets/rockies.jpg'); - * maskImage = loadImage('assets/mask2.png'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Apply the mask. - * photo.mask(maskImage); - * - * // Display the image. - * image(photo, 0, 0); - * - * describe('An image of a mountain landscape. The right side of the image has a faded patch of white.'); - * } - * - *
    - */ - // TODO: - Accept an array of alpha values. - mask(p5Image) { - if (p5Image === undefined) { - p5Image = this; + /** + * Gets the index of the current frame in an animated GIF. + * + * @return {Number} index of the GIF's current frame. + * + * @example + *
    + * + * let gif; + * + * // Load the image. + * function preload() { + * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * describe('A cartoon eye repeatedly looks around, then outwards. A number displayed in the bottom-left corner increases from 0 to 124, then repeats.'); + * } + * + * function draw() { + * // Get the index of the current GIF frame. + * let index = gif.getCurrentFrame(); + * + * // Display the image. + * image(gif, 0, 0); + * + * // Display the current frame. + * text(index, 10, 90); + * } + * + *
    + */ + getCurrentFrame() { + if (this.gifProperties) { + const props = this.gifProperties; + return props.displayIndex % props.numFrames; + } } - const currBlend = this.drawingContext.globalCompositeOperation; - let imgScaleFactor = this._pixelDensity; - let maskScaleFactor = 1; - if (p5Image instanceof Renderer) { - maskScaleFactor = p5Image._pInst._pixelDensity; + /** + * Sets the current frame in an animated GIF. + * + * @param {Number} index index of the frame to display. + * + * @example + *
    + * + * let gif; + * let frameSlider; + * + * // Load the image. + * function preload() { + * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Get the index of the last frame. + * let maxFrame = gif.numFrames() - 1; + * + * // Create a slider to control which frame is drawn. + * frameSlider = createSlider(0, maxFrame); + * frameSlider.position(10, 80); + * frameSlider.size(80); + * + * describe('A cartoon eye looks around when a slider is moved.'); + * } + * + * function draw() { + * // Get the slider's value. + * let index = frameSlider.value(); + * + * // Set the GIF's frame. + * gif.setFrame(index); + * + * // Display the image. + * image(gif, 0, 0); + * } + * + *
    + */ + setFrame(index) { + if (this.gifProperties) { + const props = this.gifProperties; + if (index < props.numFrames && index >= 0) { + props.timeDisplayed = 0; + props.lastChangeTime = 0; + props.displayIndex = index; + this.drawingContext.putImageData(props.frames[index].image, 0, 0); + } else { + console.log( + 'Cannot set GIF to a frame number that is higher than total number of frames or below zero.' + ); + } + } } - const copyArgs = [ - p5Image, - 0, - 0, - maskScaleFactor * p5Image.width, - maskScaleFactor * p5Image.height, - 0, - 0, - imgScaleFactor * this.width, - imgScaleFactor * this.height - ]; + /** + * Returns the number of frames in an animated GIF. + * + * @return {Number} number of frames in the GIF. + * + * @example + *
    + * + * let gif; + * + * // Load the image. + * function preload() { + * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * describe('A cartoon eye looks around. The text "n / 125" is shown at the bottom of the canvas.'); + * } + * + * function draw() { + * // Display the image. + * image(gif, 0, 0); + * + * // Display the current state of playback. + * let total = gif.numFrames(); + * let index = gif.getCurrentFrame(); + * text(`${index} / ${total}`, 30, 90); + * } + * + *
    + */ + numFrames() { + if (this.gifProperties) { + return this.gifProperties.numFrames; + } + } - this.drawingContext.globalCompositeOperation = 'destination-in'; - if (this.gifProperties) { - for (let i = 0; i < this.gifProperties.frames.length; i++) { - this.drawingContext.putImageData( - this.gifProperties.frames[i].image, - 0, - 0 - ); - this.copy(...copyArgs); - this.gifProperties.frames[i].image = this.drawingContext.getImageData( - 0, - 0, - imgScaleFactor * this.width, - imgScaleFactor * this.height - ); + /** + * Plays an animated GIF that was paused with + * img.pause(). + * + * @example + *
    + * + * let gif; + * + * // Load the image. + * function preload() { + * gif = loadImage('assets/nancy-liang-wind-loop-forever.gif'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * describe('A drawing of a child with hair blowing in the wind. The animation freezes when clicked and resumes when released.'); + * } + * + * function draw() { + * background(255); + * image(gif, 0, 0); + * } + * + * // Pause the GIF when the user presses the mouse. + * function mousePressed() { + * gif.pause(); + * } + * + * // Play the GIF when the user releases the mouse. + * function mouseReleased() { + * gif.play(); + * } + * + *
    + */ + play() { + if (this.gifProperties) { + this.gifProperties.playing = true; } - this.drawingContext.putImageData( - this.gifProperties.frames[this.gifProperties.displayIndex].image, - 0, - 0 - ); - } else { - this.copy(...copyArgs); } - this.drawingContext.globalCompositeOperation = currBlend; - this.setModified(true); - } - - /** - * Applies an image filter to the image. - * - * The preset options are: - * - * `INVERT` - * Inverts the colors in the image. No parameter is used. - * - * `GRAY` - * Converts the image to grayscale. No parameter is used. - * - * `THRESHOLD` - * Converts the image to black and white. Pixels with a grayscale value - * above a given threshold are converted to white. The rest are converted to - * black. The threshold must be between 0.0 (black) and 1.0 (white). If no - * value is specified, 0.5 is used. - * - * `OPAQUE` - * Sets the alpha channel to be entirely opaque. No parameter is used. - * - * `POSTERIZE` - * Limits the number of colors in the image. Each color channel is limited to - * the number of colors specified. Values between 2 and 255 are valid, but - * results are most noticeable with lower values. The default value is 4. - * - * `BLUR` - * Blurs the image. The level of blurring is specified by a blur radius. Larger - * values increase the blur. The default value is 4. A gaussian blur is used - * in `P2D` mode. A box blur is used in `WEBGL` mode. - * - * `ERODE` - * Reduces the light areas. No parameter is used. - * - * `DILATE` - * Increases the light areas. No parameter is used. - * - * @param {(THRESHOLD|GRAY|OPAQUE|INVERT|POSTERIZE|ERODE|DILATE|BLUR)} filterType either THRESHOLD, GRAY, OPAQUE, INVERT, - * POSTERIZE, ERODE, DILATE or BLUR. - * @param {Number} [filterParam] parameter unique to each filter. - * - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Apply the INVERT filter. - * img.filter(INVERT); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('A blue brick wall.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Apply the GRAY filter. - * img.filter(GRAY); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('A brick wall drawn in grayscale.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Apply the THRESHOLD filter. - * img.filter(THRESHOLD); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('A brick wall drawn in black and white.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Apply the OPAQUE filter. - * img.filter(OPAQUE); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('A red brick wall.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Apply the POSTERIZE filter. - * img.filter(POSTERIZE, 3); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('An image of a red brick wall drawn with a limited color palette.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Apply the BLUR filter. - * img.filter(BLUR, 3); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('A blurry image of a red brick wall.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Apply the DILATE filter. - * img.filter(DILATE); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('A red brick wall with bright lines between each brick.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Apply the ERODE filter. - * img.filter(ERODE); - * - * // Display the image. - * image(img, 0, 0); - * - * describe('A red brick wall with faint lines between each brick.'); - * } - * - *
    - */ - filter(operation, value) { - Filters.apply(this.canvas, Filters[operation], value); - this.setModified(true); - } - - /** - * Copies a region of pixels from another image into this one. - * - * The first parameter, `srcImage`, is the - * p5.Image object to blend. - * - * The next four parameters, `sx`, `sy`, `sw`, and `sh` determine the region - * to blend from the source image. `(sx, sy)` is the top-left corner of the - * region. `sw` and `sh` are the regions width and height. - * - * The next four parameters, `dx`, `dy`, `dw`, and `dh` determine the region - * of the canvas to blend into. `(dx, dy)` is the top-left corner of the - * region. `dw` and `dh` are the regions width and height. - * - * The tenth parameter, `blendMode`, sets the effect used to blend the images' - * colors. The options are `BLEND`, `DARKEST`, `LIGHTEST`, `DIFFERENCE`, - * `MULTIPLY`, `EXCLUSION`, `SCREEN`, `REPLACE`, `OVERLAY`, `HARD_LIGHT`, - * `SOFT_LIGHT`, `DODGE`, `BURN`, `ADD`, or `NORMAL`. - * - * @param {p5.Image} srcImage source image - * @param {Integer} sx x-coordinate of the source's upper-left corner. - * @param {Integer} sy y-coordinate of the source's upper-left corner. - * @param {Integer} sw source image width. - * @param {Integer} sh source image height. - * @param {Integer} dx x-coordinate of the destination's upper-left corner. - * @param {Integer} dy y-coordinate of the destination's upper-left corner. - * @param {Integer} dw destination image width. - * @param {Integer} dh destination image height. - * @param {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode the blend mode. either - * BLEND, DARKEST, LIGHTEST, DIFFERENCE, - * MULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT, - * SOFT_LIGHT, DODGE, BURN, ADD or NORMAL. - * - * Available blend modes are: normal | multiply | screen | overlay | - * darken | lighten | color-dodge | color-burn | hard-light | - * soft-light | difference | exclusion | hue | saturation | - * color | luminosity - * - * http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/ - * - * @example - *
    - * - * let mountains; - * let bricks; - * - * // Load the images. - * function preload() { - * mountains = loadImage('assets/rockies.jpg'); - * bricks = loadImage('assets/bricks_third.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Blend the bricks image into the mountains. - * mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, ADD); - * - * // Display the mountains image. - * image(mountains, 0, 0); - * - * // Display the bricks image. - * image(bricks, 0, 0); - * - * describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears faded on the right of the image.'); - * } - * - *
    - * - *
    - * - * let mountains; - * let bricks; - * - * // Load the images. - * function preload() { - * mountains = loadImage('assets/rockies.jpg'); - * bricks = loadImage('assets/bricks_third.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Blend the bricks image into the mountains. - * mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, DARKEST); - * - * // Display the mountains image. - * image(mountains, 0, 0); - * - * // Display the bricks image. - * image(bricks, 0, 0); - * - * describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears transparent on the right of the image.'); - * } - * - *
    - * - *
    - * - * let mountains; - * let bricks; - * - * // Load the images. - * function preload() { - * mountains = loadImage('assets/rockies.jpg'); - * bricks = loadImage('assets/bricks_third.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Blend the bricks image into the mountains. - * mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST); - * - * // Display the mountains image. - * image(mountains, 0, 0); - * - * // Display the bricks image. - * image(bricks, 0, 0); - * - * describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears washed out on the right of the image.'); - * } - * - *
    - */ - /** - * @param {Integer} sx - * @param {Integer} sy - * @param {Integer} sw - * @param {Integer} sh - * @param {Integer} dx - * @param {Integer} dy - * @param {Integer} dw - * @param {Integer} dh - * @param {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode - */ - blend(...args) { - p5._validateParameters('p5.Image.blend', arguments); - p5.prototype.blend.apply(this, args); - this.setModified(true); - } - /** - * helper method for web GL mode to indicate that an image has been - * changed or unchanged since last upload. gl texture upload will - * set this value to false after uploading the texture. - * @param {boolean} val sets whether or not the image has been - * modified. - * @private - */ - setModified(val) { - this._modified = val; //enforce boolean? - } + /** + * Pauses an animated GIF. + * + * The GIF can be resumed by calling + * img.play(). + * + * @example + *
    + * + * let gif; + * + * // Load the image. + * function preload() { + * gif = loadImage('assets/nancy-liang-wind-loop-forever.gif'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * describe('A drawing of a child with hair blowing in the wind. The animation freezes when clicked and resumes when released.'); + * } + * + * function draw() { + * background(255); + * + * // Display the image. + * image(gif, 0, 0); + * } + * + * // Pause the GIF when the user presses the mouse. + * function mousePressed() { + * gif.pause(); + * } + * + * // Play the GIF when the user presses the mouse. + * function mouseReleased() { + * gif.play(); + * } + * + *
    + */ + pause() { + if (this.gifProperties) { + this.gifProperties.playing = false; + } + } - /** - * helper method for web GL mode to figure out if the image - * has been modified and might need to be re-uploaded to texture - * memory between frames. - * @private - * @return {boolean} a boolean indicating whether or not the - * image has been updated or modified since last texture upload. - */ - isModified() { - return this._modified; - } + /** + * Changes the delay between frames in an animated GIF. + * + * The first parameter, `delay`, is the length of the delay in milliseconds. + * + * The second parameter, `index`, is optional. If provided, only the frame + * at `index` will have its delay modified. All other frames will keep + * their default delay. + * + * @param {Number} d delay in milliseconds between switching frames. + * @param {Number} [index] index of the frame that will have its delay modified. + * + * @example + *
    + * + * let gifFast; + * let gifSlow; + * + * // Load the images. + * function preload() { + * gifFast = loadImage('assets/arnott-wallace-eye-loop-forever.gif'); + * gifSlow = loadImage('assets/arnott-wallace-eye-loop-forever.gif'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Resize the images. + * gifFast.resize(50, 50); + * gifSlow.resize(50, 50); + * + * // Set the delay lengths. + * gifFast.delay(10); + * gifSlow.delay(100); + * + * describe('Two animated eyes looking around. The eye on the left moves faster than the eye on the right.'); + * } + * + * function draw() { + * // Display the images. + * image(gifFast, 0, 0); + * image(gifSlow, 50, 0); + * } + * + *
    + * + *
    + * + * let gif; + * + * // Load the image. + * function preload() { + * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Set the delay of frame 67. + * gif.delay(3000, 67); + * + * describe('An animated eye looking around. It pauses for three seconds while it looks down.'); + * } + * + * function draw() { + * // Display the image. + * image(gif, 0, 0); + * } + * + *
    + */ + delay(d, index) { + if (this.gifProperties) { + const props = this.gifProperties; + if (index < props.numFrames && index >= 0) { + props.frames[index].delay = d; + } else { + // change all frames + for (const frame of props.frames) { + frame.delay = d; + } + } + } + } + }; /** - * Saves the image to a file. - * - * By default, `img.save()` saves the image as a PNG image called - * `untitled.png`. - * - * The first parameter, `filename`, is optional. It's a string that sets the - * file's name. If a file extension is included, as in - * `img.save('drawing.png')`, then the image will be saved using that - * format. + * The image's width in pixels. * - * The second parameter, `extension`, is also optional. It sets the files format. - * Either `'png'` or `'jpg'` can be used. For example, `img.save('drawing', 'jpg')` - * saves the canvas to a file called `drawing.jpg`. - * - * Note: The browser will either save the file immediately or prompt the user - * with a dialogue window. - * - * The image will only be downloaded as an animated GIF if it was loaded - * from a GIF file. See saveGif() to create new - * GIFs. - * - * @param {String} filename filename. Defaults to 'untitled'. - * @param {String} [extension] file extension, either 'png' or 'jpg'. - * Defaults to 'png'. + * @type {Number} + * @property {Number} width + * @for p5.Image + * @name width + * @readOnly * * @example *
    @@ -1451,568 +1859,160 @@ p5.Image = class Image { * // Display the image. * image(img, 0, 0); * - * describe('An image of a mountain landscape. The image is downloaded when the user presses the "s", "j", or "p" key.'); - * } - * - * // Save the image with different options when the user presses a key. - * function keyPressed() { - * if (key === 's') { - * img.save(); - * } else if (key === 'j') { - * img.save('rockies.jpg'); - * } else if (key === 'p') { - * img.save('rockies', 'png'); - * } - * } - * - *
    - */ - save(filename, extension) { - if (this.gifProperties) { - p5.prototype.encodeAndDownloadGif(this, filename); - } else { - p5.prototype.saveCanvas(this.canvas, filename, extension); - } - } - - // GIF Section - /** - * Restarts an animated GIF at its first frame. - * - * @example - *
    - * - * let gif; - * - * // Load the image. - * function preload() { - * gif = loadImage('assets/arnott-wallace-wink-loop-once.gif'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * describe('A cartoon face winks once and then freezes. Clicking resets the face and makes it wink again.'); - * } - * - * function draw() { - * background(255); - * - * // Display the image. - * image(gif, 0, 0); - * } - * - * // Reset the GIF when the user presses the mouse. - * function mousePressed() { - * gif.reset(); - * } - * - *
    - */ - reset() { - if (this.gifProperties) { - const props = this.gifProperties; - props.playing = true; - props.timeSinceStart = 0; - props.timeDisplayed = 0; - props.lastChangeTime = 0; - props.loopCount = 0; - props.displayIndex = 0; - this.drawingContext.putImageData(props.frames[0].image, 0, 0); - } - } - - /** - * Gets the index of the current frame in an animated GIF. - * - * @return {Number} index of the GIF's current frame. - * - * @example - *
    - * - * let gif; - * - * // Load the image. - * function preload() { - * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * describe('A cartoon eye repeatedly looks around, then outwards. A number displayed in the bottom-left corner increases from 0 to 124, then repeats.'); - * } - * - * function draw() { - * // Get the index of the current GIF frame. - * let index = gif.getCurrentFrame(); + * // Calculate the center coordinates. + * let x = img.width / 2; + * let y = img.height / 2; * - * // Display the image. - * image(gif, 0, 0); + * // Draw a circle at the image's center. + * circle(x, y, 20); * - * // Display the current frame. - * text(index, 10, 90); + * describe('An image of a mountain landscape with a white circle drawn in the middle.'); * } * *
    */ - getCurrentFrame() { - if (this.gifProperties) { - const props = this.gifProperties; - return props.displayIndex % props.numFrames; - } - } /** - * Sets the current frame in an animated GIF. + * The image's height in pixels. * - * @param {Number} index index of the frame to display. + * @type {Number} + * @property height + * @for p5.Image + * @name height + * @readOnly * * @example *
    * - * let gif; - * let frameSlider; + * let img; * * // Load the image. * function preload() { - * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif'); + * img = loadImage('assets/rockies.jpg'); * } * * function setup() { * createCanvas(100, 100); * - * // Get the index of the last frame. - * let maxFrame = gif.numFrames() - 1; - * - * // Create a slider to control which frame is drawn. - * frameSlider = createSlider(0, maxFrame); - * frameSlider.position(10, 80); - * frameSlider.size(80); - * - * describe('A cartoon eye looks around when a slider is moved.'); - * } - * - * function draw() { - * // Get the slider's value. - * let index = frameSlider.value(); - * - * // Set the GIF's frame. - * gif.setFrame(index); - * * // Display the image. - * image(gif, 0, 0); - * } - * - *
    - */ - setFrame(index) { - if (this.gifProperties) { - const props = this.gifProperties; - if (index < props.numFrames && index >= 0) { - props.timeDisplayed = 0; - props.lastChangeTime = 0; - props.displayIndex = index; - this.drawingContext.putImageData(props.frames[index].image, 0, 0); - } else { - console.log( - 'Cannot set GIF to a frame number that is higher than total number of frames or below zero.' - ); - } - } - } - - /** - * Returns the number of frames in an animated GIF. - * - * @return {Number} number of frames in the GIF. - * - * @example - *
    - * - * let gif; - * - * // Load the image. - * function preload() { - * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif'); - * } - * - * function setup() { - * createCanvas(100, 100); + * image(img, 0, 0); * - * describe('A cartoon eye looks around. The text "n / 125" is shown at the bottom of the canvas.'); - * } + * // Calculate the center coordinates. + * let x = img.width / 2; + * let y = img.height / 2; * - * function draw() { - * // Display the image. - * image(gif, 0, 0); + * // Draw a circle at the image's center. + * circle(x, y, 20); * - * // Display the current state of playback. - * let total = gif.numFrames(); - * let index = gif.getCurrentFrame(); - * text(`${index} / ${total}`, 30, 90); + * describe('An image of a mountain landscape with a white circle drawn in the middle.'); * } * *
    */ - numFrames() { - if (this.gifProperties) { - return this.gifProperties.numFrames; - } - } /** - * Plays an animated GIF that was paused with - * img.pause(). + * An array containing the color of each pixel in the image. + * + * Colors are stored as numbers representing red, green, blue, and alpha + * (RGBA) values. `img.pixels` is a one-dimensional array for performance + * reasons. + * + * Each pixel occupies four elements in the pixels array, one for each + * RGBA value. For example, the pixel at coordinates (0, 0) stores its + * RGBA values at `img.pixels[0]`, `img.pixels[1]`, `img.pixels[2]`, + * and `img.pixels[3]`, respectively. The next pixel at coordinates (1, 0) + * stores its RGBA values at `img.pixels[4]`, `img.pixels[5]`, + * `img.pixels[6]`, and `img.pixels[7]`. And so on. The `img.pixels` array + * for a 100×100 p5.Image object has + * 100 × 100 × 4 = 40,000 elements. + * + * Accessing the RGBA values for a pixel in the image requires a little + * math as shown in the examples below. The + * img.loadPixels() + * method must be called before accessing the `img.pixels` array. The + * img.updatePixels() method must be + * called after any changes are made. + * + * @property {Number[]} pixels + * @for p5.Image + * @name pixels * * @example *
    * - * let gif; - * - * // Load the image. - * function preload() { - * gif = loadImage('assets/nancy-liang-wind-loop-forever.gif'); - * } - * * function setup() { * createCanvas(100, 100); * - * describe('A drawing of a child with hair blowing in the wind. The animation freezes when clicked and resumes when released.'); - * } - * - * function draw() { - * background(255); - * image(gif, 0, 0); - * } - * - * // Pause the GIF when the user presses the mouse. - * function mousePressed() { - * gif.pause(); - * } - * - * // Play the GIF when the user releases the mouse. - * function mouseReleased() { - * gif.play(); - * } - * - *
    - */ - play() { - if (this.gifProperties) { - this.gifProperties.playing = true; - } - } - - /** - * Pauses an animated GIF. - * - * The GIF can be resumed by calling - * img.play(). - * - * @example - *
    - * - * let gif; + * background(200); * - * // Load the image. - * function preload() { - * gif = loadImage('assets/nancy-liang-wind-loop-forever.gif'); - * } + * // Create a p5.Image object. + * let img = createImage(66, 66); * - * function setup() { - * createCanvas(100, 100); + * // Load the image's pixels. + * img.loadPixels(); * - * describe('A drawing of a child with hair blowing in the wind. The animation freezes when clicked and resumes when released.'); - * } + * for (let i = 0; i < img.pixels.length; i += 4) { + * // Red. + * img.pixels[i] = 0; + * // Green. + * img.pixels[i + 1] = 0; + * // Blue. + * img.pixels[i + 2] = 0; + * // Alpha. + * img.pixels[i + 3] = 255; + * } * - * function draw() { - * background(255); + * // Update the image. + * img.updatePixels(); * * // Display the image. - * image(gif, 0, 0); - * } - * - * // Pause the GIF when the user presses the mouse. - * function mousePressed() { - * gif.pause(); - * } + * image(img, 17, 17); * - * // Play the GIF when the user presses the mouse. - * function mouseReleased() { - * gif.play(); + * describe('A black square drawn in the middle of a gray square.'); * } * *
    - */ - pause() { - if (this.gifProperties) { - this.gifProperties.playing = false; - } - } - - /** - * Changes the delay between frames in an animated GIF. - * - * The first parameter, `delay`, is the length of the delay in milliseconds. - * - * The second parameter, `index`, is optional. If provided, only the frame - * at `index` will have its delay modified. All other frames will keep - * their default delay. - * - * @param {Number} d delay in milliseconds between switching frames. - * @param {Number} [index] index of the frame that will have its delay modified. * - * @example *
    * - * let gifFast; - * let gifSlow; - * - * // Load the images. - * function preload() { - * gifFast = loadImage('assets/arnott-wallace-eye-loop-forever.gif'); - * gifSlow = loadImage('assets/arnott-wallace-eye-loop-forever.gif'); - * } - * * function setup() { * createCanvas(100, 100); * * background(200); * - * // Resize the images. - * gifFast.resize(50, 50); - * gifSlow.resize(50, 50); - * - * // Set the delay lengths. - * gifFast.delay(10); - * gifSlow.delay(100); - * - * describe('Two animated eyes looking around. The eye on the left moves faster than the eye on the right.'); - * } - * - * function draw() { - * // Display the images. - * image(gifFast, 0, 0); - * image(gifSlow, 50, 0); - * } - * - *
    - * - *
    - * - * let gif; - * - * // Load the image. - * function preload() { - * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif'); - * } + * // Create a p5.Image object. + * let img = createImage(66, 66); * - * function setup() { - * createCanvas(100, 100); + * // Load the image's pixels. + * img.loadPixels(); * - * // Set the delay of frame 67. - * gif.delay(3000, 67); + * // Set the pixels to red. + * for (let i = 0; i < img.pixels.length; i += 4) { + * // Red. + * img.pixels[i] = 255; + * // Green. + * img.pixels[i + 1] = 0; + * // Blue. + * img.pixels[i + 2] = 0; + * // Alpha. + * img.pixels[i + 3] = 255; + * } * - * describe('An animated eye looking around. It pauses for three seconds while it looks down.'); - * } + * // Update the image. + * img.updatePixels(); * - * function draw() { * // Display the image. - * image(gif, 0, 0); + * image(img, 17, 17); + * + * describe('A red square drawn in the middle of a gray square.'); * } * *
    */ - delay(d, index) { - if (this.gifProperties) { - const props = this.gifProperties; - if (index < props.numFrames && index >= 0) { - props.frames[index].delay = d; - } else { - // change all frames - for (const frame of props.frames) { - frame.delay = d; - } - } - } - } -}; +} -/** - * The image's width in pixels. - * - * @type {Number} - * @property {Number} width - * @for p5.Image - * @name width - * @readOnly - * - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Calculate the center coordinates. - * let x = img.width / 2; - * let y = img.height / 2; - * - * // Draw a circle at the image's center. - * circle(x, y, 20); - * - * describe('An image of a mountain landscape with a white circle drawn in the middle.'); - * } - * - *
    - */ - -/** - * The image's height in pixels. - * - * @type {Number} - * @property height - * @for p5.Image - * @name height - * @readOnly - * - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Calculate the center coordinates. - * let x = img.width / 2; - * let y = img.height / 2; - * - * // Draw a circle at the image's center. - * circle(x, y, 20); - * - * describe('An image of a mountain landscape with a white circle drawn in the middle.'); - * } - * - *
    - */ - -/** - * An array containing the color of each pixel in the image. - * - * Colors are stored as numbers representing red, green, blue, and alpha - * (RGBA) values. `img.pixels` is a one-dimensional array for performance - * reasons. - * - * Each pixel occupies four elements in the pixels array, one for each - * RGBA value. For example, the pixel at coordinates (0, 0) stores its - * RGBA values at `img.pixels[0]`, `img.pixels[1]`, `img.pixels[2]`, - * and `img.pixels[3]`, respectively. The next pixel at coordinates (1, 0) - * stores its RGBA values at `img.pixels[4]`, `img.pixels[5]`, - * `img.pixels[6]`, and `img.pixels[7]`. And so on. The `img.pixels` array - * for a 100×100 p5.Image object has - * 100 × 100 × 4 = 40,000 elements. - * - * Accessing the RGBA values for a pixel in the image requires a little - * math as shown in the examples below. The - * img.loadPixels() - * method must be called before accessing the `img.pixels` array. The - * img.updatePixels() method must be - * called after any changes are made. - * - * @property {Number[]} pixels - * @for p5.Image - * @name pixels - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Image object. - * let img = createImage(66, 66); - * - * // Load the image's pixels. - * img.loadPixels(); - * - * for (let i = 0; i < img.pixels.length; i += 4) { - * // Red. - * img.pixels[i] = 0; - * // Green. - * img.pixels[i + 1] = 0; - * // Blue. - * img.pixels[i + 2] = 0; - * // Alpha. - * img.pixels[i + 3] = 255; - * } - * - * // Update the image. - * img.updatePixels(); - * - * // Display the image. - * image(img, 17, 17); - * - * describe('A black square drawn in the middle of a gray square.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Image object. - * let img = createImage(66, 66); - * - * // Load the image's pixels. - * img.loadPixels(); - * - * // Set the pixels to red. - * for (let i = 0; i < img.pixels.length; i += 4) { - * // Red. - * img.pixels[i] = 255; - * // Green. - * img.pixels[i + 1] = 0; - * // Blue. - * img.pixels[i + 2] = 0; - * // Alpha. - * img.pixels[i + 3] = 255; - * } - * - * // Update the image. - * img.updatePixels(); - * - * // Display the image. - * image(img, 17, 17); - * - * describe('A red square drawn in the middle of a gray square.'); - * } - * - *
    - */ +export default image; -export default p5.Image; +if(typeof p5 !== 'undefined'){ + image(p5, p5.prototype); +} diff --git a/src/image/pixels.js b/src/image/pixels.js index 936d453055..3b0716c657 100644 --- a/src/image/pixels.js +++ b/src/image/pixels.js @@ -5,1179 +5,1183 @@ * @requires core */ -import p5 from '../core/main'; import Filters from './filters'; -import '../color/p5.Color'; -/** - * An array containing the color of each pixel on the canvas. - * - * Colors are stored as numbers representing red, green, blue, and alpha - * (RGBA) values. `pixels` is a one-dimensional array for performance reasons. - * - * Each pixel occupies four elements in the `pixels` array, one for each RGBA - * value. For example, the pixel at coordinates (0, 0) stores its RGBA values - * at `pixels[0]`, `pixels[1]`, `pixels[2]`, and `pixels[3]`, respectively. - * The next pixel at coordinates (1, 0) stores its RGBA values at `pixels[4]`, - * `pixels[5]`, `pixels[6]`, and `pixels[7]`. And so on. The `pixels` array - * for a 100×100 canvas has 100 × 100 × 4 = 40,000 elements. - * - * Some displays use several smaller pixels to set the color at a single - * point. The pixelDensity() function returns - * the pixel density of the canvas. High density displays often have a - * pixelDensity() of 2. On such a display, the - * `pixels` array for a 100×100 canvas has 200 × 200 × 4 = - * 160,000 elements. - * - * Accessing the RGBA values for a point on the canvas requires a little math - * as shown below. The loadPixels() function - * must be called before accessing the `pixels` array. The - * updatePixels() function must be called - * after any changes are made. - * - * @property {Number[]} pixels - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Load the pixels array. - * loadPixels(); - * - * // Set the dot's coordinates. - * let x = 50; - * let y = 50; - * - * // Get the pixel density. - * let d = pixelDensity(); - * - * // Set the pixel(s) at the center of the canvas black. - * for (let i = 0; i < d; i += 1) { - * for (let j = 0; j < d; j += 1) { - * let index = 4 * ((y * d + j) * width * d + (x * d + i)); - * // Red. - * pixels[index] = 0; - * // Green. - * pixels[index + 1] = 0; - * // Blue. - * pixels[index + 2] = 0; - * // Alpha. - * pixels[index + 3] = 255; - * } - * } - * - * // Update the canvas. - * updatePixels(); - * - * describe('A black dot in the middle of a gray rectangle.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Load the pixels array. - * loadPixels(); - * - * // Get the pixel density. - * let d = pixelDensity(); - * - * // Calculate the halfway index in the pixels array. - * let halfImage = 4 * (d * width) * (d * height / 2); - * - * // Make the top half of the canvas red. - * for (let i = 0; i < halfImage; i += 4) { - * // Red. - * pixels[i] = 255; - * // Green. - * pixels[i + 1] = 0; - * // Blue. - * pixels[i + 2] = 0; - * // Alpha. - * pixels[i + 3] = 255; - * } - * - * // Update the canvas. - * updatePixels(); - * - * describe('A red rectangle drawn above a gray rectangle.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * // Create a p5.Color object. - * let pink = color(255, 102, 204); - * - * // Load the pixels array. - * loadPixels(); - * - * // Get the pixel density. - * let d = pixelDensity(); - * - * // Calculate the halfway index in the pixels array. - * let halfImage = 4 * (d * width) * (d * height / 2); - * - * // Make the top half of the canvas red. - * for (let i = 0; i < halfImage; i += 4) { - * pixels[i] = red(pink); - * pixels[i + 1] = green(pink); - * pixels[i + 2] = blue(pink); - * pixels[i + 3] = alpha(pink); - * } - * - * // Update the canvas. - * updatePixels(); - * - * describe('A pink rectangle drawn above a gray rectangle.'); - * } - * - *
    - */ -p5.prototype.pixels = []; +function pixels(p5, fn){ + /** + * An array containing the color of each pixel on the canvas. + * + * Colors are stored as numbers representing red, green, blue, and alpha + * (RGBA) values. `pixels` is a one-dimensional array for performance reasons. + * + * Each pixel occupies four elements in the `pixels` array, one for each RGBA + * value. For example, the pixel at coordinates (0, 0) stores its RGBA values + * at `pixels[0]`, `pixels[1]`, `pixels[2]`, and `pixels[3]`, respectively. + * The next pixel at coordinates (1, 0) stores its RGBA values at `pixels[4]`, + * `pixels[5]`, `pixels[6]`, and `pixels[7]`. And so on. The `pixels` array + * for a 100×100 canvas has 100 × 100 × 4 = 40,000 elements. + * + * Some displays use several smaller pixels to set the color at a single + * point. The pixelDensity() function returns + * the pixel density of the canvas. High density displays often have a + * pixelDensity() of 2. On such a display, the + * `pixels` array for a 100×100 canvas has 200 × 200 × 4 = + * 160,000 elements. + * + * Accessing the RGBA values for a point on the canvas requires a little math + * as shown below. The loadPixels() function + * must be called before accessing the `pixels` array. The + * updatePixels() function must be called + * after any changes are made. + * + * @property {Number[]} pixels + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Load the pixels array. + * loadPixels(); + * + * // Set the dot's coordinates. + * let x = 50; + * let y = 50; + * + * // Get the pixel density. + * let d = pixelDensity(); + * + * // Set the pixel(s) at the center of the canvas black. + * for (let i = 0; i < d; i += 1) { + * for (let j = 0; j < d; j += 1) { + * let index = 4 * ((y * d + j) * width * d + (x * d + i)); + * // Red. + * pixels[index] = 0; + * // Green. + * pixels[index + 1] = 0; + * // Blue. + * pixels[index + 2] = 0; + * // Alpha. + * pixels[index + 3] = 255; + * } + * } + * + * // Update the canvas. + * updatePixels(); + * + * describe('A black dot in the middle of a gray rectangle.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Load the pixels array. + * loadPixels(); + * + * // Get the pixel density. + * let d = pixelDensity(); + * + * // Calculate the halfway index in the pixels array. + * let halfImage = 4 * (d * width) * (d * height / 2); + * + * // Make the top half of the canvas red. + * for (let i = 0; i < halfImage; i += 4) { + * // Red. + * pixels[i] = 255; + * // Green. + * pixels[i + 1] = 0; + * // Blue. + * pixels[i + 2] = 0; + * // Alpha. + * pixels[i + 3] = 255; + * } + * + * // Update the canvas. + * updatePixels(); + * + * describe('A red rectangle drawn above a gray rectangle.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * // Create a p5.Color object. + * let pink = color(255, 102, 204); + * + * // Load the pixels array. + * loadPixels(); + * + * // Get the pixel density. + * let d = pixelDensity(); + * + * // Calculate the halfway index in the pixels array. + * let halfImage = 4 * (d * width) * (d * height / 2); + * + * // Make the top half of the canvas red. + * for (let i = 0; i < halfImage; i += 4) { + * pixels[i] = red(pink); + * pixels[i + 1] = green(pink); + * pixels[i + 2] = blue(pink); + * pixels[i + 3] = alpha(pink); + * } + * + * // Update the canvas. + * updatePixels(); + * + * describe('A pink rectangle drawn above a gray rectangle.'); + * } + * + *
    + */ + fn.pixels = []; -/** - * Copies a region of pixels from one image to another. - * - * The first parameter, `srcImage`, is the - * p5.Image object to blend. - * - * The next four parameters, `sx`, `sy`, `sw`, and `sh` determine the region - * to blend from the source image. `(sx, sy)` is the top-left corner of the - * region. `sw` and `sh` are the regions width and height. - * - * The next four parameters, `dx`, `dy`, `dw`, and `dh` determine the region - * of the canvas to blend into. `(dx, dy)` is the top-left corner of the - * region. `dw` and `dh` are the regions width and height. - * - * The tenth parameter, `blendMode`, sets the effect used to blend the images' - * colors. The options are `BLEND`, `DARKEST`, `LIGHTEST`, `DIFFERENCE`, - * `MULTIPLY`, `EXCLUSION`, `SCREEN`, `REPLACE`, `OVERLAY`, `HARD_LIGHT`, - * `SOFT_LIGHT`, `DODGE`, `BURN`, `ADD`, or `NORMAL` - * - * @method blend - * @param {p5.Image} srcImage source image. - * @param {Integer} sx x-coordinate of the source's upper-left corner. - * @param {Integer} sy y-coordinate of the source's upper-left corner. - * @param {Integer} sw source image width. - * @param {Integer} sh source image height. - * @param {Integer} dx x-coordinate of the destination's upper-left corner. - * @param {Integer} dy y-coordinate of the destination's upper-left corner. - * @param {Integer} dw destination image width. - * @param {Integer} dh destination image height. - * @param {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode the blend mode. either - * BLEND, DARKEST, LIGHTEST, DIFFERENCE, - * MULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT, - * SOFT_LIGHT, DODGE, BURN, ADD or NORMAL. - * - * @example - *
    - * - * let img0; - * let img1; - * - * // Load the images. - * function preload() { - * img0 = loadImage('assets/rockies.jpg'); - * img1 = loadImage('assets/bricks_third.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Use the mountains as the background. - * background(img0); - * - * // Display the bricks. - * image(img1, 0, 0); - * - * // Display the bricks faded into the landscape. - * blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST); - * - * describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears faded on the right of the image.'); - * } - * - *
    - * - *
    - * - * let img0; - * let img1; - * - * // Load the images. - * function preload() { - * img0 = loadImage('assets/rockies.jpg'); - * img1 = loadImage('assets/bricks_third.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Use the mountains as the background. - * background(img0); - * - * // Display the bricks. - * image(img1, 0, 0); - * - * // Display the bricks partially transparent. - * blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, DARKEST); - * - * describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears transparent on the right of the image.'); - * } - * - *
    - * - *
    - * - * let img0; - * let img1; - * - * // Load the images. - * function preload() { - * img0 = loadImage('assets/rockies.jpg'); - * img1 = loadImage('assets/bricks_third.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Use the mountains as the background. - * background(img0); - * - * // Display the bricks. - * image(img1, 0, 0); - * - * // Display the bricks washed out into the landscape. - * blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, ADD); - * - * describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears washed out on the right of the image.'); - * } - * - *
    - */ -/** - * @method blend - * @param {Integer} sx - * @param {Integer} sy - * @param {Integer} sw - * @param {Integer} sh - * @param {Integer} dx - * @param {Integer} dy - * @param {Integer} dw - * @param {Integer} dh - * @param {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode - */ -p5.prototype.blend = function(...args) { - p5._validateParameters('blend', args); - if (this._renderer) { - this._renderer.blend(...args); - } else { - p5.Renderer2D.prototype.blend.apply(this, args); - } -}; + /** + * Copies a region of pixels from one image to another. + * + * The first parameter, `srcImage`, is the + * p5.Image object to blend. + * + * The next four parameters, `sx`, `sy`, `sw`, and `sh` determine the region + * to blend from the source image. `(sx, sy)` is the top-left corner of the + * region. `sw` and `sh` are the regions width and height. + * + * The next four parameters, `dx`, `dy`, `dw`, and `dh` determine the region + * of the canvas to blend into. `(dx, dy)` is the top-left corner of the + * region. `dw` and `dh` are the regions width and height. + * + * The tenth parameter, `blendMode`, sets the effect used to blend the images' + * colors. The options are `BLEND`, `DARKEST`, `LIGHTEST`, `DIFFERENCE`, + * `MULTIPLY`, `EXCLUSION`, `SCREEN`, `REPLACE`, `OVERLAY`, `HARD_LIGHT`, + * `SOFT_LIGHT`, `DODGE`, `BURN`, `ADD`, or `NORMAL` + * + * @method blend + * @param {p5.Image} srcImage source image. + * @param {Integer} sx x-coordinate of the source's upper-left corner. + * @param {Integer} sy y-coordinate of the source's upper-left corner. + * @param {Integer} sw source image width. + * @param {Integer} sh source image height. + * @param {Integer} dx x-coordinate of the destination's upper-left corner. + * @param {Integer} dy y-coordinate of the destination's upper-left corner. + * @param {Integer} dw destination image width. + * @param {Integer} dh destination image height. + * @param {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode the blend mode. either + * BLEND, DARKEST, LIGHTEST, DIFFERENCE, + * MULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT, + * SOFT_LIGHT, DODGE, BURN, ADD or NORMAL. + * + * @example + *
    + * + * let img0; + * let img1; + * + * // Load the images. + * function preload() { + * img0 = loadImage('assets/rockies.jpg'); + * img1 = loadImage('assets/bricks_third.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Use the mountains as the background. + * background(img0); + * + * // Display the bricks. + * image(img1, 0, 0); + * + * // Display the bricks faded into the landscape. + * blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST); + * + * describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears faded on the right of the image.'); + * } + * + *
    + * + *
    + * + * let img0; + * let img1; + * + * // Load the images. + * function preload() { + * img0 = loadImage('assets/rockies.jpg'); + * img1 = loadImage('assets/bricks_third.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Use the mountains as the background. + * background(img0); + * + * // Display the bricks. + * image(img1, 0, 0); + * + * // Display the bricks partially transparent. + * blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, DARKEST); + * + * describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears transparent on the right of the image.'); + * } + * + *
    + * + *
    + * + * let img0; + * let img1; + * + * // Load the images. + * function preload() { + * img0 = loadImage('assets/rockies.jpg'); + * img1 = loadImage('assets/bricks_third.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Use the mountains as the background. + * background(img0); + * + * // Display the bricks. + * image(img1, 0, 0); + * + * // Display the bricks washed out into the landscape. + * blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, ADD); + * + * describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears washed out on the right of the image.'); + * } + * + *
    + */ + /** + * @method blend + * @param {Integer} sx + * @param {Integer} sy + * @param {Integer} sw + * @param {Integer} sh + * @param {Integer} dx + * @param {Integer} dy + * @param {Integer} dw + * @param {Integer} dh + * @param {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode + */ + fn.blend = function(...args) { + p5._validateParameters('blend', args); + if (this._renderer) { + this._renderer.blend(...args); + } else { + p5.Renderer2D.prototype.blend.apply(this, args); + } + }; -/** - * Copies pixels from a source image to a region of the canvas. - * - * The first parameter, `srcImage`, is the - * p5.Image object to blend. The source image can be - * the canvas itself or a - * p5.Image object. `copy()` will scale pixels from - * the source region if it isn't the same size as the destination region. - * - * The next four parameters, `sx`, `sy`, `sw`, and `sh` determine the region - * to copy from the source image. `(sx, sy)` is the top-left corner of the - * region. `sw` and `sh` are the region's width and height. - * - * The next four parameters, `dx`, `dy`, `dw`, and `dh` determine the region - * of the canvas to copy into. `(dx, dy)` is the top-left corner of the - * region. `dw` and `dh` are the region's width and height. - * - * @method copy - * @param {p5.Image|p5.Element} srcImage source image. - * @param {Integer} sx x-coordinate of the source's upper-left corner. - * @param {Integer} sy y-coordinate of the source's upper-left corner. - * @param {Integer} sw source image width. - * @param {Integer} sh source image height. - * @param {Integer} dx x-coordinate of the destination's upper-left corner. - * @param {Integer} dy y-coordinate of the destination's upper-left corner. - * @param {Integer} dw destination image width. - * @param {Integer} dh destination image height. - * - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Use the mountains as the background. - * background(img); - * - * // Copy a region of pixels to another spot. - * copy(img, 7, 22, 10, 10, 35, 25, 50, 50); - * - * // Outline the copied region. - * stroke(255); - * noFill(); - * square(7, 22, 10); - * - * describe('An image of a mountain landscape. A square region is outlined in white. A larger square contains a pixelated view of the outlined region.'); - * } - * - *
    - */ -/** - * @method copy - * @param {Integer} sx - * @param {Integer} sy - * @param {Integer} sw - * @param {Integer} sh - * @param {Integer} dx - * @param {Integer} dy - * @param {Integer} dw - * @param {Integer} dh - */ -p5.prototype.copy = function(...args) { - p5._validateParameters('copy', args); + /** + * Copies pixels from a source image to a region of the canvas. + * + * The first parameter, `srcImage`, is the + * p5.Image object to blend. The source image can be + * the canvas itself or a + * p5.Image object. `copy()` will scale pixels from + * the source region if it isn't the same size as the destination region. + * + * The next four parameters, `sx`, `sy`, `sw`, and `sh` determine the region + * to copy from the source image. `(sx, sy)` is the top-left corner of the + * region. `sw` and `sh` are the region's width and height. + * + * The next four parameters, `dx`, `dy`, `dw`, and `dh` determine the region + * of the canvas to copy into. `(dx, dy)` is the top-left corner of the + * region. `dw` and `dh` are the region's width and height. + * + * @method copy + * @param {p5.Image|p5.Element} srcImage source image. + * @param {Integer} sx x-coordinate of the source's upper-left corner. + * @param {Integer} sy y-coordinate of the source's upper-left corner. + * @param {Integer} sw source image width. + * @param {Integer} sh source image height. + * @param {Integer} dx x-coordinate of the destination's upper-left corner. + * @param {Integer} dy y-coordinate of the destination's upper-left corner. + * @param {Integer} dw destination image width. + * @param {Integer} dh destination image height. + * + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Use the mountains as the background. + * background(img); + * + * // Copy a region of pixels to another spot. + * copy(img, 7, 22, 10, 10, 35, 25, 50, 50); + * + * // Outline the copied region. + * stroke(255); + * noFill(); + * square(7, 22, 10); + * + * describe('An image of a mountain landscape. A square region is outlined in white. A larger square contains a pixelated view of the outlined region.'); + * } + * + *
    + */ + /** + * @method copy + * @param {Integer} sx + * @param {Integer} sy + * @param {Integer} sw + * @param {Integer} sh + * @param {Integer} dx + * @param {Integer} dy + * @param {Integer} dw + * @param {Integer} dh + */ + fn.copy = function(...args) { + p5._validateParameters('copy', args); - let srcImage, sx, sy, sw, sh, dx, dy, dw, dh; - if (args.length === 9) { - srcImage = args[0]; - sx = args[1]; - sy = args[2]; - sw = args[3]; - sh = args[4]; - dx = args[5]; - dy = args[6]; - dw = args[7]; - dh = args[8]; - } else if (args.length === 8) { - srcImage = this; - sx = args[0]; - sy = args[1]; - sw = args[2]; - sh = args[3]; - dx = args[4]; - dy = args[5]; - dw = args[6]; - dh = args[7]; - } else { - throw new Error('Signature not supported'); - } + let srcImage, sx, sy, sw, sh, dx, dy, dw, dh; + if (args.length === 9) { + srcImage = args[0]; + sx = args[1]; + sy = args[2]; + sw = args[3]; + sh = args[4]; + dx = args[5]; + dy = args[6]; + dw = args[7]; + dh = args[8]; + } else if (args.length === 8) { + srcImage = this; + sx = args[0]; + sy = args[1]; + sw = args[2]; + sh = args[3]; + dx = args[4]; + dy = args[5]; + dw = args[6]; + dh = args[7]; + } else { + throw new Error('Signature not supported'); + } - p5.prototype._copyHelper(this, srcImage, sx, sy, sw, sh, dx, dy, dw, dh); -}; + fn._copyHelper(this, srcImage, sx, sy, sw, sh, dx, dy, dw, dh); + }; -p5.prototype._copyHelper = ( - dstImage, - srcImage, - sx, - sy, - sw, - sh, - dx, - dy, - dw, - dh -) => { - const s = srcImage.canvas.width / srcImage.width; - // adjust coord system for 3D when renderer - // ie top-left = -width/2, -height/2 - let sxMod = 0; - let syMod = 0; - if (srcImage._renderer && srcImage._renderer.isP3D) { - sxMod = srcImage.width / 2; - syMod = srcImage.height / 2; - } - if (dstImage._renderer && dstImage._renderer.isP3D) { - dstImage.push(); - dstImage.resetMatrix(); - dstImage.noLights(); - dstImage.blendMode(dstImage.BLEND); - dstImage.imageMode(dstImage.CORNER); - p5.RendererGL.prototype.image.call( - dstImage._renderer, - srcImage, - sx + sxMod, - sy + syMod, - sw, - sh, - dx, - dy, - dw, - dh - ); - dstImage.pop(); - } else { - dstImage.drawingContext.drawImage( - srcImage.canvas, - s * (sx + sxMod), - s * (sy + syMod), - s * sw, - s * sh, - dx, - dy, - dw, - dh - ); - } -}; + fn._copyHelper = ( + dstImage, + srcImage, + sx, + sy, + sw, + sh, + dx, + dy, + dw, + dh + ) => { + const s = srcImage.canvas.width / srcImage.width; + // adjust coord system for 3D when renderer + // ie top-left = -width/2, -height/2 + let sxMod = 0; + let syMod = 0; + if (srcImage._renderer && srcImage._renderer.isP3D) { + sxMod = srcImage.width / 2; + syMod = srcImage.height / 2; + } + if (dstImage._renderer && dstImage._renderer.isP3D) { + dstImage.push(); + dstImage.resetMatrix(); + dstImage.noLights(); + dstImage.blendMode(dstImage.BLEND); + dstImage.imageMode(dstImage.CORNER); + p5.RendererGL.prototype.image.call( + dstImage._renderer, + srcImage, + sx + sxMod, + sy + syMod, + sw, + sh, + dx, + dy, + dw, + dh + ); + dstImage.pop(); + } else { + dstImage.drawingContext.drawImage( + srcImage.canvas, + s * (sx + sxMod), + s * (sy + syMod), + s * sw, + s * sh, + dx, + dy, + dw, + dh + ); + } + }; -/** - * Applies an image filter to the canvas. - * - * The preset options are: - * - * `INVERT` - * Inverts the colors in the image. No parameter is used. - * - * `GRAY` - * Converts the image to grayscale. No parameter is used. - * - * `THRESHOLD` - * Converts the image to black and white. Pixels with a grayscale value - * above a given threshold are converted to white. The rest are converted to - * black. The threshold must be between 0.0 (black) and 1.0 (white). If no - * value is specified, 0.5 is used. - * - * `OPAQUE` - * Sets the alpha channel to entirely opaque. No parameter is used. - * - * `POSTERIZE` - * Limits the number of colors in the image. Each color channel is limited to - * the number of colors specified. Values between 2 and 255 are valid, but - * results are most noticeable with lower values. The default value is 4. - * - * `BLUR` - * Blurs the image. The level of blurring is specified by a blur radius. Larger - * values increase the blur. The default value is 4. A gaussian blur is used - * in `P2D` mode. A box blur is used in `WEBGL` mode. - * - * `ERODE` - * Reduces the light areas. No parameter is used. - * - * `DILATE` - * Increases the light areas. No parameter is used. - * - * `filter()` uses WebGL in the background by default because it's faster. - * This can be disabled in `P2D` mode by adding a `false` argument, as in - * `filter(BLUR, false)`. This may be useful to keep computation off the GPU - * or to work around a lack of WebGL support. - * - * In WebgL mode, `filter()` can also use custom shaders. See - * createFilterShader() for more - * information. - * - * - * @method filter - * @param {(THRESHOLD|GRAY|OPAQUE|INVERT|POSTERIZE|BLUR|ERODE|DILATE|BLUR)} filterType either THRESHOLD, GRAY, OPAQUE, INVERT, - * POSTERIZE, BLUR, ERODE, DILATE or BLUR. - * @param {Number} [filterParam] parameter unique to each filter. - * @param {Boolean} [useWebGL=true] flag to control whether to use fast - * WebGL filters (GPU) or original image - * filters (CPU); defaults to `true`. - * - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Apply the INVERT filter. - * filter(INVERT); - * - * describe('A blue brick wall.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Apply the GRAY filter. - * filter(GRAY); - * - * describe('A brick wall drawn in grayscale.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Apply the THRESHOLD filter. - * filter(THRESHOLD); - * - * describe('A brick wall drawn in black and white.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Apply the OPAQUE filter. - * filter(OPAQUE); - * - * describe('A red brick wall.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Apply the POSTERIZE filter. - * filter(POSTERIZE, 3); - * - * describe('An image of a red brick wall drawn with limited color palette.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Apply the BLUR filter. - * filter(BLUR, 3); - * - * describe('A blurry image of a red brick wall.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Apply the DILATE filter. - * filter(DILATE); - * - * describe('A red brick wall with bright lines between each brick.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Apply the ERODE filter. - * filter(ERODE); - * - * describe('A red brick wall with faint lines between each brick.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/bricks.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Apply the BLUR filter. - * // Don't use WebGL. - * filter(BLUR, 3, false); - * - * describe('A blurry image of a red brick wall.'); - * } - * - *
    - */ + /** + * Applies an image filter to the canvas. + * + * The preset options are: + * + * `INVERT` + * Inverts the colors in the image. No parameter is used. + * + * `GRAY` + * Converts the image to grayscale. No parameter is used. + * + * `THRESHOLD` + * Converts the image to black and white. Pixels with a grayscale value + * above a given threshold are converted to white. The rest are converted to + * black. The threshold must be between 0.0 (black) and 1.0 (white). If no + * value is specified, 0.5 is used. + * + * `OPAQUE` + * Sets the alpha channel to entirely opaque. No parameter is used. + * + * `POSTERIZE` + * Limits the number of colors in the image. Each color channel is limited to + * the number of colors specified. Values between 2 and 255 are valid, but + * results are most noticeable with lower values. The default value is 4. + * + * `BLUR` + * Blurs the image. The level of blurring is specified by a blur radius. Larger + * values increase the blur. The default value is 4. A gaussian blur is used + * in `P2D` mode. A box blur is used in `WEBGL` mode. + * + * `ERODE` + * Reduces the light areas. No parameter is used. + * + * `DILATE` + * Increases the light areas. No parameter is used. + * + * `filter()` uses WebGL in the background by default because it's faster. + * This can be disabled in `P2D` mode by adding a `false` argument, as in + * `filter(BLUR, false)`. This may be useful to keep computation off the GPU + * or to work around a lack of WebGL support. + * + * In WebgL mode, `filter()` can also use custom shaders. See + * createFilterShader() for more + * information. + * + * + * @method filter + * @param {(THRESHOLD|GRAY|OPAQUE|INVERT|POSTERIZE|BLUR|ERODE|DILATE|BLUR)} filterType either THRESHOLD, GRAY, OPAQUE, INVERT, + * POSTERIZE, BLUR, ERODE, DILATE or BLUR. + * @param {Number} [filterParam] parameter unique to each filter. + * @param {Boolean} [useWebGL=true] flag to control whether to use fast + * WebGL filters (GPU) or original image + * filters (CPU); defaults to `true`. + * + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Apply the INVERT filter. + * filter(INVERT); + * + * describe('A blue brick wall.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Apply the GRAY filter. + * filter(GRAY); + * + * describe('A brick wall drawn in grayscale.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Apply the THRESHOLD filter. + * filter(THRESHOLD); + * + * describe('A brick wall drawn in black and white.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Apply the OPAQUE filter. + * filter(OPAQUE); + * + * describe('A red brick wall.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Apply the POSTERIZE filter. + * filter(POSTERIZE, 3); + * + * describe('An image of a red brick wall drawn with limited color palette.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Apply the BLUR filter. + * filter(BLUR, 3); + * + * describe('A blurry image of a red brick wall.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Apply the DILATE filter. + * filter(DILATE); + * + * describe('A red brick wall with bright lines between each brick.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Apply the ERODE filter. + * filter(ERODE); + * + * describe('A red brick wall with faint lines between each brick.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/bricks.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Apply the BLUR filter. + * // Don't use WebGL. + * filter(BLUR, 3, false); + * + * describe('A blurry image of a red brick wall.'); + * } + * + *
    + */ -/** - * @method getFilterGraphicsLayer - * @private - * @returns {p5.Graphics} - */ -p5.prototype.getFilterGraphicsLayer = function() { - return this._renderer.getFilterGraphicsLayer(); -}; + /** + * @method getFilterGraphicsLayer + * @private + * @returns {p5.Graphics} + */ + fn.getFilterGraphicsLayer = function() { + return this._renderer.getFilterGraphicsLayer(); + }; -/** - * @method filter - * @param {(THRESHOLD|GRAY|OPAQUE|INVERT|POSTERIZE|BLUR|ERODE|DILATE|BLUR)} filterType - * @param {Number} [filterParam] - * @param {Boolean} [useWebGL=true] - */ -/** - * @method filter - * @param {p5.Shader} shaderFilter shader that's been loaded, with the - * frag shader using a `tex0` uniform. - */ -p5.prototype.filter = function(...args) { - p5._validateParameters('filter', args); + /** + * @method filter + * @param {(THRESHOLD|GRAY|OPAQUE|INVERT|POSTERIZE|BLUR|ERODE|DILATE|BLUR)} filterType + * @param {Number} [filterParam] + * @param {Boolean} [useWebGL=true] + */ + /** + * @method filter + * @param {p5.Shader} shaderFilter shader that's been loaded, with the + * frag shader using a `tex0` uniform. + */ + fn.filter = function(...args) { + p5._validateParameters('filter', args); - let { shader, operation, value, useWebGL } = parseFilterArgs(...args); + let { shader, operation, value, useWebGL } = parseFilterArgs(...args); - // when passed a shader, use it directly - if (this._renderer.isP3D && shader) { - p5.RendererGL.prototype.filter.call(this._renderer, shader); - return; - } + // when passed a shader, use it directly + if (this._renderer.isP3D && shader) { + p5.RendererGL.prototype.filter.call(this._renderer, shader); + return; + } - // when opting out of webgl, use old pixels method - if (!useWebGL && !this._renderer.isP3D) { - if (this.canvas !== undefined) { - Filters.apply(this.canvas, Filters[operation], value); - } else { - Filters.apply(this.elt, Filters[operation], value); + // when opting out of webgl, use old pixels method + if (!useWebGL && !this._renderer.isP3D) { + if (this.canvas !== undefined) { + Filters.apply(this.canvas, Filters[operation], value); + } else { + Filters.apply(this.elt, Filters[operation], value); + } + return; } - return; - } - if(!useWebGL && this._renderer.isP3D) { - console.warn('filter() with useWebGL=false is not supported in WEBGL'); - } + if(!useWebGL && this._renderer.isP3D) { + console.warn('filter() with useWebGL=false is not supported in WEBGL'); + } - // when this is a webgl renderer, apply constant shader filter - if (this._renderer.isP3D) { - p5.RendererGL.prototype.filter.call(this._renderer, operation, value); - } + // when this is a webgl renderer, apply constant shader filter + if (this._renderer.isP3D) { + p5.RendererGL.prototype.filter.call(this._renderer, operation, value); + } - // when this is P2D renderer, create/use hidden webgl renderer - else { - const filterGraphicsLayer = this.getFilterGraphicsLayer(); + // when this is P2D renderer, create/use hidden webgl renderer + else { + const filterGraphicsLayer = this.getFilterGraphicsLayer(); - // copy p2d canvas contents to secondary webgl renderer - // dest - filterGraphicsLayer.copy( - // src - this._renderer, - // src coods - 0, 0, this.width, this.height, - // dest coords - -this.width/2, -this.height/2, this.width, this.height - ); - //clearing the main canvas - this._renderer.clear(); + // copy p2d canvas contents to secondary webgl renderer + // dest + filterGraphicsLayer.copy( + // src + this._renderer, + // src coods + 0, 0, this.width, this.height, + // dest coords + -this.width/2, -this.height/2, this.width, this.height + ); + //clearing the main canvas + this._renderer.clear(); - this._renderer.resetMatrix(); - // filter it with shaders - filterGraphicsLayer.filter(...args); + this._renderer.resetMatrix(); + // filter it with shaders + filterGraphicsLayer.filter(...args); - // copy secondary webgl renderer back to original p2d canvas - this.copy( - // src - filterGraphicsLayer._renderer, - // src coods - 0, 0, this.width, this.height, - // dest coords - 0, 0, this.width, this.height - ); - filterGraphicsLayer.clear(); // prevent feedback effects on p2d canvas - } -}; + // copy secondary webgl renderer back to original p2d canvas + this.copy( + // src + filterGraphicsLayer._renderer, + // src coods + 0, 0, this.width, this.height, + // dest coords + 0, 0, this.width, this.height + ); + filterGraphicsLayer.clear(); // prevent feedback effects on p2d canvas + } + }; -function parseFilterArgs(...args) { - // args could be: - // - operation, value, [useWebGL] - // - operation, [useWebGL] - // - shader + function parseFilterArgs(...args) { + // args could be: + // - operation, value, [useWebGL] + // - operation, [useWebGL] + // - shader - let result = { - shader: undefined, - operation: undefined, - value: undefined, - useWebGL: true - }; + let result = { + shader: undefined, + operation: undefined, + value: undefined, + useWebGL: true + }; - if (args[0] instanceof p5.Shader) { - result.shader = args[0]; - return result; - } - else { - result.operation = args[0]; - } + if (args[0] instanceof p5.Shader) { + result.shader = args[0]; + return result; + } + else { + result.operation = args[0]; + } - if (args.length > 1 && typeof args[1] === 'number') { - result.value = args[1]; - } + if (args.length > 1 && typeof args[1] === 'number') { + result.value = args[1]; + } - if (args[args.length-1] === false) { - result.useWebGL = false; + if (args[args.length-1] === false) { + result.useWebGL = false; + } + return result; } - return result; -} -/** - * Gets a pixel or a region of pixels from the canvas. - * - * `get()` is easy to use but it's not as fast as - * pixels. Use pixels - * to read many pixel values. - * - * The version of `get()` with no parameters returns the entire canvas. - * - * The version of `get()` with two parameters interprets them as - * coordinates. It returns an array with the `[R, G, B, A]` values of the - * pixel at the given point. - * - * The version of `get()` with four parameters interprets them as coordinates - * and dimensions. It returns a subsection of the canvas as a - * p5.Image object. The first two parameters are the - * coordinates for the upper-left corner of the subsection. The last two - * parameters are the width and height of the subsection. - * - * Use p5.Image.get() to work directly with - * p5.Image objects. - * - * @method get - * @param {Number} x x-coordinate of the pixel. - * @param {Number} y y-coordinate of the pixel. - * @param {Number} w width of the subsection to be returned. - * @param {Number} h height of the subsection to be returned. - * @return {p5.Image} subsection as a p5.Image object. - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Get the entire canvas. - * let c = get(); - * - * // Display half the canvas. - * image(c, 50, 0); - * - * describe('Two identical mountain landscapes shown side-by-side.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Get the color of a pixel. - * let c = get(50, 90); - * - * // Style the square with the pixel's color. - * fill(c); - * noStroke(); - * - * // Display the square. - * square(25, 25, 50); - * - * describe('A mountain landscape with an olive green square in its center.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0); - * - * // Get a region of the image. - * let c = get(0, 0, 50, 50); - * - * // Display the region. - * image(c, 50, 50); - * - * describe('A mountain landscape drawn on top of another mountain landscape.'); - * } - * - *
    - */ -/** - * @method get - * @return {p5.Image} whole canvas as a p5.Image. - */ -/** - * @method get - * @param {Number} x - * @param {Number} y - * @return {Number[]} color of the pixel at (x, y) in array format `[R, G, B, A]`. - */ -p5.prototype.get = function(x, y, w, h) { - p5._validateParameters('get', arguments); - return this._renderer.get(...arguments); -}; + /** + * Gets a pixel or a region of pixels from the canvas. + * + * `get()` is easy to use but it's not as fast as + * pixels. Use pixels + * to read many pixel values. + * + * The version of `get()` with no parameters returns the entire canvas. + * + * The version of `get()` with two parameters interprets them as + * coordinates. It returns an array with the `[R, G, B, A]` values of the + * pixel at the given point. + * + * The version of `get()` with four parameters interprets them as coordinates + * and dimensions. It returns a subsection of the canvas as a + * p5.Image object. The first two parameters are the + * coordinates for the upper-left corner of the subsection. The last two + * parameters are the width and height of the subsection. + * + * Use p5.Image.get() to work directly with + * p5.Image objects. + * + * @method get + * @param {Number} x x-coordinate of the pixel. + * @param {Number} y y-coordinate of the pixel. + * @param {Number} w width of the subsection to be returned. + * @param {Number} h height of the subsection to be returned. + * @return {p5.Image} subsection as a p5.Image object. + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Get the entire canvas. + * let c = get(); + * + * // Display half the canvas. + * image(c, 50, 0); + * + * describe('Two identical mountain landscapes shown side-by-side.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Get the color of a pixel. + * let c = get(50, 90); + * + * // Style the square with the pixel's color. + * fill(c); + * noStroke(); + * + * // Display the square. + * square(25, 25, 50); + * + * describe('A mountain landscape with an olive green square in its center.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0); + * + * // Get a region of the image. + * let c = get(0, 0, 50, 50); + * + * // Display the region. + * image(c, 50, 50); + * + * describe('A mountain landscape drawn on top of another mountain landscape.'); + * } + * + *
    + */ + /** + * @method get + * @return {p5.Image} whole canvas as a p5.Image. + */ + /** + * @method get + * @param {Number} x + * @param {Number} y + * @return {Number[]} color of the pixel at (x, y) in array format `[R, G, B, A]`. + */ + fn.get = function(x, y, w, h) { + p5._validateParameters('get', arguments); + return this._renderer.get(...arguments); + }; -/** - * Loads the current value of each pixel on the canvas into the - * pixels array. - * - * `loadPixels()` must be called before reading from or writing to - * pixels. - * - * @method loadPixels - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0, 100, 100); - * - * // Get the pixel density. - * let d = pixelDensity(); - * - * // Calculate the halfway index in the pixels array. - * let halfImage = 4 * (d * width) * (d * height / 2); - * - * // Load the pixels array. - * loadPixels(); - * - * // Copy the top half of the canvas to the bottom. - * for (let i = 0; i < halfImage; i += 1) { - * pixels[i + halfImage] = pixels[i]; - * } - * - * // Update the canvas. - * updatePixels(); - * - * describe('Two identical images of mountain landscapes, one on top of the other.'); - * } - * - *
    - */ -p5.prototype.loadPixels = function(...args) { - p5._validateParameters('loadPixels', args); - this._renderer.loadPixels(); -}; + /** + * Loads the current value of each pixel on the canvas into the + * pixels array. + * + * `loadPixels()` must be called before reading from or writing to + * pixels. + * + * @method loadPixels + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0, 100, 100); + * + * // Get the pixel density. + * let d = pixelDensity(); + * + * // Calculate the halfway index in the pixels array. + * let halfImage = 4 * (d * width) * (d * height / 2); + * + * // Load the pixels array. + * loadPixels(); + * + * // Copy the top half of the canvas to the bottom. + * for (let i = 0; i < halfImage; i += 1) { + * pixels[i + halfImage] = pixels[i]; + * } + * + * // Update the canvas. + * updatePixels(); + * + * describe('Two identical images of mountain landscapes, one on top of the other.'); + * } + * + *
    + */ + fn.loadPixels = function(...args) { + p5._validateParameters('loadPixels', args); + this._renderer.loadPixels(); + }; -/** - * Sets the color of a pixel or draws an image to the canvas. - * - * `set()` is easy to use but it's not as fast as - * pixels. Use pixels - * to set many pixel values. - * - * `set()` interprets the first two parameters as x- and y-coordinates. It - * interprets the last parameter as a grayscale value, a `[R, G, B, A]` pixel - * array, a p5.Color object, or a - * p5.Image object. If an image is passed, the first - * two parameters set the coordinates for the image's upper-left corner, - * regardless of the current imageMode(). - * - * updatePixels() must be called after using - * `set()` for changes to appear. - * - * @method set - * @param {Number} x x-coordinate of the pixel. - * @param {Number} y y-coordinate of the pixel. - * @param {Number|Number[]|Object} c grayscale value | pixel array | - * p5.Color object | p5.Image to copy. - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Set four pixels to black. - * set(30, 20, 0); - * set(85, 20, 0); - * set(85, 75, 0); - * set(30, 75, 0); - * - * // Update the canvas. - * updatePixels(); - * - * describe('Four black dots arranged in a square drawn on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object. - * let black = color(0); - * - * // Set four pixels to black. - * set(30, 20, black); - * set(85, 20, black); - * set(85, 75, black); - * set(30, 75, black); - * - * // Update the canvas. - * updatePixels(); - * - * describe('Four black dots arranged in a square drawn on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(255); - * - * // Draw a horizontal color gradient. - * for (let x = 0; x < 100; x += 1) { - * for (let y = 0; y < 100; y += 1) { - * // Calculate the grayscale value. - * let c = map(x, 0, 100, 0, 255); - * - * // Set the pixel using the grayscale value. - * set(x, y, c); - * } - * } - * - * // Update the canvas. - * updatePixels(); - * - * describe('A horiztonal color gradient from black to white.'); - * } - * - *
    - * - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Use the image to set all pixels. - * set(0, 0, img); - * - * // Update the canvas. - * updatePixels(); - * - * describe('An image of a mountain landscape.'); - * } - * - *
    - */ -p5.prototype.set = function(x, y, imgOrCol) { - this._renderer.set(x, y, imgOrCol); -}; + /** + * Sets the color of a pixel or draws an image to the canvas. + * + * `set()` is easy to use but it's not as fast as + * pixels. Use pixels + * to set many pixel values. + * + * `set()` interprets the first two parameters as x- and y-coordinates. It + * interprets the last parameter as a grayscale value, a `[R, G, B, A]` pixel + * array, a p5.Color object, or a + * p5.Image object. If an image is passed, the first + * two parameters set the coordinates for the image's upper-left corner, + * regardless of the current imageMode(). + * + * updatePixels() must be called after using + * `set()` for changes to appear. + * + * @method set + * @param {Number} x x-coordinate of the pixel. + * @param {Number} y y-coordinate of the pixel. + * @param {Number|Number[]|Object} c grayscale value | pixel array | + * p5.Color object | p5.Image to copy. + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Set four pixels to black. + * set(30, 20, 0); + * set(85, 20, 0); + * set(85, 75, 0); + * set(30, 75, 0); + * + * // Update the canvas. + * updatePixels(); + * + * describe('Four black dots arranged in a square drawn on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object. + * let black = color(0); + * + * // Set four pixels to black. + * set(30, 20, black); + * set(85, 20, black); + * set(85, 75, black); + * set(30, 75, black); + * + * // Update the canvas. + * updatePixels(); + * + * describe('Four black dots arranged in a square drawn on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(255); + * + * // Draw a horizontal color gradient. + * for (let x = 0; x < 100; x += 1) { + * for (let y = 0; y < 100; y += 1) { + * // Calculate the grayscale value. + * let c = map(x, 0, 100, 0, 255); + * + * // Set the pixel using the grayscale value. + * set(x, y, c); + * } + * } + * + * // Update the canvas. + * updatePixels(); + * + * describe('A horiztonal color gradient from black to white.'); + * } + * + *
    + * + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Use the image to set all pixels. + * set(0, 0, img); + * + * // Update the canvas. + * updatePixels(); + * + * describe('An image of a mountain landscape.'); + * } + * + *
    + */ + fn.set = function(x, y, imgOrCol) { + this._renderer.set(x, y, imgOrCol); + }; -/** - * Updates the canvas with the RGBA values in the - * pixels array. - * - * `updatePixels()` only needs to be called after changing values in the - * pixels array. Such changes can be made directly - * after calling loadPixels() or by calling - * set(). - * - * @method updatePixels - * @param {Number} [x] x-coordinate of the upper-left corner of region - * to update. - * @param {Number} [y] y-coordinate of the upper-left corner of region - * to update. - * @param {Number} [w] width of region to update. - * @param {Number} [h] height of region to update. - * @example - *
    - * - * let img; - * - * // Load the image. - * function preload() { - * img = loadImage('assets/rockies.jpg'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * // Display the image. - * image(img, 0, 0, 100, 100); - * - * // Get the pixel density. - * let d = pixelDensity(); - * - * // Calculate the halfway index in the pixels array. - * let halfImage = 4 * (d * width) * (d * height / 2); - * - * // Load the pixels array. - * loadPixels(); - * - * // Copy the top half of the canvas to the bottom. - * for (let i = 0; i < halfImage; i += 1) { - * pixels[i + halfImage] = pixels[i]; - * } - * - * // Update the canvas. - * updatePixels(); - * - * describe('Two identical images of mountain landscapes, one on top of the other.'); - * } - * - *
    - */ -p5.prototype.updatePixels = function(x, y, w, h) { - p5._validateParameters('updatePixels', arguments); - // graceful fail - if loadPixels() or set() has not been called, pixel - // array will be empty, ignore call to updatePixels() - if (this.pixels.length === 0) { - return; - } - this._renderer.updatePixels(x, y, w, h); -}; + /** + * Updates the canvas with the RGBA values in the + * pixels array. + * + * `updatePixels()` only needs to be called after changing values in the + * pixels array. Such changes can be made directly + * after calling loadPixels() or by calling + * set(). + * + * @method updatePixels + * @param {Number} [x] x-coordinate of the upper-left corner of region + * to update. + * @param {Number} [y] y-coordinate of the upper-left corner of region + * to update. + * @param {Number} [w] width of region to update. + * @param {Number} [h] height of region to update. + * @example + *
    + * + * let img; + * + * // Load the image. + * function preload() { + * img = loadImage('assets/rockies.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * // Display the image. + * image(img, 0, 0, 100, 100); + * + * // Get the pixel density. + * let d = pixelDensity(); + * + * // Calculate the halfway index in the pixels array. + * let halfImage = 4 * (d * width) * (d * height / 2); + * + * // Load the pixels array. + * loadPixels(); + * + * // Copy the top half of the canvas to the bottom. + * for (let i = 0; i < halfImage; i += 1) { + * pixels[i + halfImage] = pixels[i]; + * } + * + * // Update the canvas. + * updatePixels(); + * + * describe('Two identical images of mountain landscapes, one on top of the other.'); + * } + * + *
    + */ + fn.updatePixels = function(x, y, w, h) { + p5._validateParameters('updatePixels', arguments); + // graceful fail - if loadPixels() or set() has not been called, pixel + // array will be empty, ignore call to updatePixels() + if (this.pixels.length === 0) { + return; + } + this._renderer.updatePixels(x, y, w, h); + }; +} -export default p5; +export default pixels; + +if(typeof p5 !== 'undefined'){ + pixels(p5, p5.prototype); +} diff --git a/src/io/files.js b/src/io/files.js index e6711ea20f..cb5404cbe6 100644 --- a/src/io/files.js +++ b/src/io/files.js @@ -5,1596 +5,1541 @@ * @requires core */ -import p5 from '../core/main'; import * as fileSaver from 'file-saver'; -import '../core/friendly_errors/validate_params'; -import '../core/friendly_errors/file_errors'; -import '../core/friendly_errors/fes_core'; import Renderer from '../core/p5.Renderer'; -/** - * Loads a JSON file to create an `Object`. - * - * JavaScript Object Notation - * (JSON) - * is a standard format for sending data between applications. The format is - * based on JavaScript objects which have keys and values. JSON files store - * data in an object with strings as keys. Values can be strings, numbers, - * Booleans, arrays, `null`, or other objects. - * - * The first parameter, `path`, is always a string with the path to the file. - * Paths to local files should be relative, as in - * `loadJSON('assets/data.json')`. URLs such as - * `'https://example.com/data.json'` may be blocked due to browser security. - * - * The second parameter, `successCallback`, is optional. If a function is - * passed, as in `loadJSON('assets/data.json', handleData)`, then the - * `handleData()` function will be called once the data loads. The object - * created from the JSON data will be passed to `handleData()` as its only argument. - * - * The third parameter, `failureCallback`, is also optional. If a function is - * passed, as in `loadJSON('assets/data.json', handleData, handleFailure)`, - * then the `handleFailure()` function will be called if an error occurs while - * loading. The `Error` object will be passed to `handleFailure()` as its only - * argument. - * - * Note: Data can take time to load. Calling `loadJSON()` within - * preload() ensures data loads before it's used in - * setup() or draw(). - * - * @method loadJSON - * @param {String} path path of the JSON file to be loaded. - * @param {Function} [successCallback] function to call once the data is loaded. Will be passed the object. - * @param {Function} [errorCallback] function to call if the data fails to load. Will be passed an `Error` event object. - * @return {Object} object containing the loaded data. - * - * @example - * - *
    - * - * let myData; - * - * // Load the JSON and create an object. - * function preload() { - * myData = loadJSON('assets/data.json'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the circle. - * fill(myData.color); - * noStroke(); - * - * // Draw the circle. - * circle(myData.x, myData.y, myData.d); - * - * describe('A pink circle on a gray background.'); - * } - * - *
    - * - *
    - * - * let myData; - * - * // Load the JSON and create an object. - * function preload() { - * myData = loadJSON('assets/data.json'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.Color object and make it transparent. - * let c = color(myData.color); - * c.setAlpha(80); - * - * // Style the circles. - * fill(c); - * noStroke(); - * - * // Iterate over the myData.bubbles array. - * for (let b of myData.bubbles) { - * // Draw a circle for each bubble. - * circle(b.x, b.y, b.d); - * } - * - * describe('Several pink bubbles floating in a blue sky.'); - * } - * - *
    - * - *
    - * - * let myData; - * - * // Load the GeoJSON and create an object. - * function preload() { - * myData = loadJSON('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get data about the most recent earthquake. - * let quake = myData.features[0].properties; - * - * // Draw a circle based on the earthquake's magnitude. - * circle(50, 50, quake.mag * 10); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(11); - * - * // Display the earthquake's location. - * text(quake.place, 5, 80, 100); - * - * describe(`A white circle on a gray background. The text "${quake.place}" is written beneath the circle.`); - * } - * - *
    - * - *
    - * - * let bigQuake; - * - * // Load the GeoJSON and preprocess it. - * function preload() { - * loadJSON( - * 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson', - * handleData - * ); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Draw a circle based on the earthquake's magnitude. - * circle(50, 50, bigQuake.mag * 10); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(11); - * - * // Display the earthquake's location. - * text(bigQuake.place, 5, 80, 100); - * - * describe(`A white circle on a gray background. The text "${bigQuake.place}" is written beneath the circle.`); - * } - * - * // Find the biggest recent earthquake. - * function handleData(data) { - * let maxMag = 0; - * // Iterate over the earthquakes array. - * for (let quake of data.features) { - * // Reassign bigQuake if a larger - * // magnitude quake is found. - * if (quake.properties.mag > maxMag) { - * bigQuake = quake.properties; - * } - * } - * } - * - *
    - * - *
    - * - * let bigQuake; - * - * // Load the GeoJSON and preprocess it. - * function preload() { - * loadJSON( - * 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson', - * handleData, - * handleError - * ); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Draw a circle based on the earthquake's magnitude. - * circle(50, 50, bigQuake.mag * 10); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(11); - * - * // Display the earthquake's location. - * text(bigQuake.place, 5, 80, 100); - * - * describe(`A white circle on a gray background. The text "${bigQuake.place}" is written beneath the circle.`); - * } - * - * // Find the biggest recent earthquake. - * function handleData(data) { - * let maxMag = 0; - * // Iterate over the earthquakes array. - * for (let quake of data.features) { - * // Reassign bigQuake if a larger - * // magnitude quake is found. - * if (quake.properties.mag > maxMag) { - * bigQuake = quake.properties; - * } - * } - * } - * - * // Log any errors to the console. - * function handleError(error) { - * console.log('Oops!', error); - * } - * - *
    - */ -p5.prototype.loadJSON = async function (...args) { - p5._validateParameters('loadJSON', args); - const path = args[0]; - let callback; - let errorCallback; - let options; - - const ret = {}; // object needed for preload - let t = 'json'; - - // check for explicit data type argument - for (let i = 1; i < args.length; i++) { - const arg = args[i]; - if (typeof arg === 'string') { - if (arg === 'json') { - t = arg; - } - } else if (typeof arg === 'function') { - if (!callback) { - callback = arg; - } else { - errorCallback = arg; +function files(p5, fn){ + /** + * Loads a JSON file to create an `Object`. + * + * JavaScript Object Notation + * (JSON) + * is a standard format for sending data between applications. The format is + * based on JavaScript objects which have keys and values. JSON files store + * data in an object with strings as keys. Values can be strings, numbers, + * Booleans, arrays, `null`, or other objects. + * + * The first parameter, `path`, is always a string with the path to the file. + * Paths to local files should be relative, as in + * `loadJSON('assets/data.json')`. URLs such as + * `'https://example.com/data.json'` may be blocked due to browser security. + * + * The second parameter, `successCallback`, is optional. If a function is + * passed, as in `loadJSON('assets/data.json', handleData)`, then the + * `handleData()` function will be called once the data loads. The object + * created from the JSON data will be passed to `handleData()` as its only argument. + * + * The third parameter, `failureCallback`, is also optional. If a function is + * passed, as in `loadJSON('assets/data.json', handleData, handleFailure)`, + * then the `handleFailure()` function will be called if an error occurs while + * loading. The `Error` object will be passed to `handleFailure()` as its only + * argument. + * + * Note: Data can take time to load. Calling `loadJSON()` within + * preload() ensures data loads before it's used in + * setup() or draw(). + * + * @method loadJSON + * @param {String} path path of the JSON file to be loaded. + * @param {Function} [successCallback] function to call once the data is loaded. Will be passed the object. + * @param {Function} [errorCallback] function to call if the data fails to load. Will be passed an `Error` event object. + * @return {Object} object containing the loaded data. + * + * @example + * + *
    + * + * let myData; + * + * // Load the JSON and create an object. + * function preload() { + * myData = loadJSON('assets/data.json'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the circle. + * fill(myData.color); + * noStroke(); + * + * // Draw the circle. + * circle(myData.x, myData.y, myData.d); + * + * describe('A pink circle on a gray background.'); + * } + * + *
    + * + *
    + * + * let myData; + * + * // Load the JSON and create an object. + * function preload() { + * myData = loadJSON('assets/data.json'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.Color object and make it transparent. + * let c = color(myData.color); + * c.setAlpha(80); + * + * // Style the circles. + * fill(c); + * noStroke(); + * + * // Iterate over the myData.bubbles array. + * for (let b of myData.bubbles) { + * // Draw a circle for each bubble. + * circle(b.x, b.y, b.d); + * } + * + * describe('Several pink bubbles floating in a blue sky.'); + * } + * + *
    + * + *
    + * + * let myData; + * + * // Load the GeoJSON and create an object. + * function preload() { + * myData = loadJSON('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get data about the most recent earthquake. + * let quake = myData.features[0].properties; + * + * // Draw a circle based on the earthquake's magnitude. + * circle(50, 50, quake.mag * 10); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(11); + * + * // Display the earthquake's location. + * text(quake.place, 5, 80, 100); + * + * describe(`A white circle on a gray background. The text "${quake.place}" is written beneath the circle.`); + * } + * + *
    + * + *
    + * + * let bigQuake; + * + * // Load the GeoJSON and preprocess it. + * function preload() { + * loadJSON( + * 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson', + * handleData + * ); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Draw a circle based on the earthquake's magnitude. + * circle(50, 50, bigQuake.mag * 10); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(11); + * + * // Display the earthquake's location. + * text(bigQuake.place, 5, 80, 100); + * + * describe(`A white circle on a gray background. The text "${bigQuake.place}" is written beneath the circle.`); + * } + * + * // Find the biggest recent earthquake. + * function handleData(data) { + * let maxMag = 0; + * // Iterate over the earthquakes array. + * for (let quake of data.features) { + * // Reassign bigQuake if a larger + * // magnitude quake is found. + * if (quake.properties.mag > maxMag) { + * bigQuake = quake.properties; + * } + * } + * } + * + *
    + * + *
    + * + * let bigQuake; + * + * // Load the GeoJSON and preprocess it. + * function preload() { + * loadJSON( + * 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson', + * handleData, + * handleError + * ); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Draw a circle based on the earthquake's magnitude. + * circle(50, 50, bigQuake.mag * 10); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(11); + * + * // Display the earthquake's location. + * text(bigQuake.place, 5, 80, 100); + * + * describe(`A white circle on a gray background. The text "${bigQuake.place}" is written beneath the circle.`); + * } + * + * // Find the biggest recent earthquake. + * function handleData(data) { + * let maxMag = 0; + * // Iterate over the earthquakes array. + * for (let quake of data.features) { + * // Reassign bigQuake if a larger + * // magnitude quake is found. + * if (quake.properties.mag > maxMag) { + * bigQuake = quake.properties; + * } + * } + * } + * + * // Log any errors to the console. + * function handleError(error) { + * console.log('Oops!', error); + * } + * + *
    + */ + fn.loadJSON = async function (...args) { + p5._validateParameters('loadJSON', args); + const path = args[0]; + let callback; + let errorCallback; + let options; + + const ret = {}; // object needed for preload + let t = 'json'; + + // check for explicit data type argument + for (let i = 1; i < args.length; i++) { + const arg = args[i]; + if (typeof arg === 'string') { + if (arg === 'json') { + t = arg; + } + } else if (typeof arg === 'function') { + if (!callback) { + callback = arg; + } else { + errorCallback = arg; + } } } - } - await new Promise(resolve => this.httpDo( - path, - 'GET', - options, - t, - resp => { - for (const k in resp) { - ret[k] = resp[k]; - } - if (typeof callback !== 'undefined') { - callback(resp); - } + await new Promise(resolve => this.httpDo( + path, + 'GET', + options, + t, + resp => { + for (const k in resp) { + ret[k] = resp[k]; + } + if (typeof callback !== 'undefined') { + callback(resp); + } - resolve() - }, - err => { - // Error handling - p5._friendlyFileLoadError(5, path); + resolve() + }, + err => { + // Error handling + p5._friendlyFileLoadError(5, path); - if (errorCallback) { - errorCallback(err); - } else { - throw err; + if (errorCallback) { + errorCallback(err); + } else { + throw err; + } } - } - )); + )); - return ret; -}; + return ret; + }; -/** - * Loads a text file to create an `Array`. - * - * The first parameter, `path`, is always a string with the path to the file. - * Paths to local files should be relative, as in - * `loadStrings('assets/data.txt')`. URLs such as - * `'https://example.com/data.txt'` may be blocked due to browser security. - * - * The second parameter, `successCallback`, is optional. If a function is - * passed, as in `loadStrings('assets/data.txt', handleData)`, then the - * `handleData()` function will be called once the data loads. The array - * created from the text data will be passed to `handleData()` as its only - * argument. - * - * The third parameter, `failureCallback`, is also optional. If a function is - * passed, as in `loadStrings('assets/data.txt', handleData, handleFailure)`, - * then the `handleFailure()` function will be called if an error occurs while - * loading. The `Error` object will be passed to `handleFailure()` as its only - * argument. - * - * Note: Data can take time to load. Calling `loadStrings()` within - * preload() ensures data loads before it's used in - * setup() or draw(). - * - * @method loadStrings - * @param {String} path path of the text file to be loaded. - * @param {Function} [successCallback] function to call once the data is - * loaded. Will be passed the array. - * @param {Function} [errorCallback] function to call if the data fails to - * load. Will be passed an `Error` event - * object. - * @return {String[]} new array containing the loaded text. - * - * @example - * - *
    - * - * let myData; - * - * // Load the text and create an array. - * function preload() { - * myData = loadStrings('assets/test.txt'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Select a random line from the text. - * let phrase = random(myData); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display the text. - * text(phrase, 10, 50, 90); - * - * describe(`The text "${phrase}" written in black on a gray background.`); - * } - * - *
    - * - *
    - * - * let lastLine; - * - * // Load the text and preprocess it. - * function preload() { - * loadStrings('assets/test.txt', handleData); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display the text. - * text(lastLine, 10, 50, 90); - * - * describe('The text "I talk like an orange" written in black on a gray background.'); - * } - * - * // Select the last line from the text. - * function handleData(data) { - * lastLine = data[data.length - 1]; - * } - * - *
    - * - *
    - * - * let lastLine; - * - * // Load the text and preprocess it. - * function preload() { - * loadStrings('assets/test.txt', handleData, handleError); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display the text. - * text(lastLine, 10, 50, 90); - * - * describe('The text "I talk like an orange" written in black on a gray background.'); - * } - * - * // Select the last line from the text. - * function handleData(data) { - * lastLine = data[data.length - 1]; - * } - * - * // Log any errors to the console. - * function handleError(error) { - * console.error('Oops!', error); - * } - * - *
    - */ -p5.prototype.loadStrings = async function (...args) { - p5._validateParameters('loadStrings', args); - - const ret = []; - let callback, errorCallback; - - for (let i = 1; i < args.length; i++) { - const arg = args[i]; - if (typeof arg === 'function') { - if (typeof callback === 'undefined') { - callback = arg; - } else if (typeof errorCallback === 'undefined') { - errorCallback = arg; + /** + * Loads a text file to create an `Array`. + * + * The first parameter, `path`, is always a string with the path to the file. + * Paths to local files should be relative, as in + * `loadStrings('assets/data.txt')`. URLs such as + * `'https://example.com/data.txt'` may be blocked due to browser security. + * + * The second parameter, `successCallback`, is optional. If a function is + * passed, as in `loadStrings('assets/data.txt', handleData)`, then the + * `handleData()` function will be called once the data loads. The array + * created from the text data will be passed to `handleData()` as its only + * argument. + * + * The third parameter, `failureCallback`, is also optional. If a function is + * passed, as in `loadStrings('assets/data.txt', handleData, handleFailure)`, + * then the `handleFailure()` function will be called if an error occurs while + * loading. The `Error` object will be passed to `handleFailure()` as its only + * argument. + * + * Note: Data can take time to load. Calling `loadStrings()` within + * preload() ensures data loads before it's used in + * setup() or draw(). + * + * @method loadStrings + * @param {String} path path of the text file to be loaded. + * @param {Function} [successCallback] function to call once the data is + * loaded. Will be passed the array. + * @param {Function} [errorCallback] function to call if the data fails to + * load. Will be passed an `Error` event + * object. + * @return {String[]} new array containing the loaded text. + * + * @example + * + *
    + * + * let myData; + * + * // Load the text and create an array. + * function preload() { + * myData = loadStrings('assets/test.txt'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Select a random line from the text. + * let phrase = random(myData); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display the text. + * text(phrase, 10, 50, 90); + * + * describe(`The text "${phrase}" written in black on a gray background.`); + * } + * + *
    + * + *
    + * + * let lastLine; + * + * // Load the text and preprocess it. + * function preload() { + * loadStrings('assets/test.txt', handleData); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display the text. + * text(lastLine, 10, 50, 90); + * + * describe('The text "I talk like an orange" written in black on a gray background.'); + * } + * + * // Select the last line from the text. + * function handleData(data) { + * lastLine = data[data.length - 1]; + * } + * + *
    + * + *
    + * + * let lastLine; + * + * // Load the text and preprocess it. + * function preload() { + * loadStrings('assets/test.txt', handleData, handleError); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display the text. + * text(lastLine, 10, 50, 90); + * + * describe('The text "I talk like an orange" written in black on a gray background.'); + * } + * + * // Select the last line from the text. + * function handleData(data) { + * lastLine = data[data.length - 1]; + * } + * + * // Log any errors to the console. + * function handleError(error) { + * console.error('Oops!', error); + * } + * + *
    + */ + fn.loadStrings = async function (...args) { + p5._validateParameters('loadStrings', args); + + const ret = []; + let callback, errorCallback; + + for (let i = 1; i < args.length; i++) { + const arg = args[i]; + if (typeof arg === 'function') { + if (typeof callback === 'undefined') { + callback = arg; + } else if (typeof errorCallback === 'undefined') { + errorCallback = arg; + } } } - } - await new Promise(resolve => p5.prototype.httpDo.call( - this, - args[0], - 'GET', - 'text', - data => { - // split lines handling mac/windows/linux endings - const lines = data - .replace(/\r\n/g, '\r') - .replace(/\n/g, '\r') - .split(/\r/); - - // safe insert approach which will not blow up stack when inserting - // >100k lines, but still be faster than iterating line-by-line. based on - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Examples - const QUANTUM = 32768; - for (let i = 0, len = lines.length; i < len; i += QUANTUM) { - Array.prototype.push.apply( - ret, - lines.slice(i, Math.min(i + QUANTUM, len)) - ); - } + await new Promise(resolve => fn.httpDo.call( + this, + args[0], + 'GET', + 'text', + data => { + // split lines handling mac/windows/linux endings + const lines = data + .replace(/\r\n/g, '\r') + .replace(/\n/g, '\r') + .split(/\r/); + + // safe insert approach which will not blow up stack when inserting + // >100k lines, but still be faster than iterating line-by-line. based on + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Examples + const QUANTUM = 32768; + for (let i = 0, len = lines.length; i < len; i += QUANTUM) { + Array.prototype.push.apply( + ret, + lines.slice(i, Math.min(i + QUANTUM, len)) + ); + } + + if (typeof callback !== 'undefined') { + callback(ret); + } - if (typeof callback !== 'undefined') { - callback(ret); + resolve() + }, + function (err) { + // Error handling + p5._friendlyFileLoadError(3, arguments[0]); + + if (errorCallback) { + errorCallback(err); + } else { + throw err; + } } + )); - resolve() - }, - function (err) { - // Error handling - p5._friendlyFileLoadError(3, arguments[0]); + return ret; + }; - if (errorCallback) { - errorCallback(err); - } else { - throw err; + /** + * Reads the contents of a file or URL and creates a p5.Table object with + * its values. If a file is specified, it must be located in the sketch's + * "data" folder. The filename parameter can also be a URL to a file found + * online. By default, the file is assumed to be comma-separated (in CSV + * format). Table only looks for a header row if the 'header' option is + * included. + * + * This method is asynchronous, meaning it may not finish before the next + * line in your sketch is executed. Calling loadTable() inside preload() + * guarantees to complete the operation before setup() and draw() are called. + * Outside of preload(), you may supply a callback function to handle the + * object: + * + * All files loaded and saved use UTF-8 encoding. This method is suitable for fetching files up to size of 64MB. + * @method loadTable + * @param {String} filename name of the file or URL to load + * @param {String} [extension] parse the table by comma-separated values "csv", semicolon-separated + * values "ssv", or tab-separated values "tsv" + * @param {String} [header] "header" to indicate table has header row + * @param {Function} [callback] function to be executed after + * loadTable() completes. On success, the + * Table object is passed in as the + * first argument. + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + * @return {Object} Table object containing data + * + * @example + *
    + * + * // Given the following CSV file called "mammals.csv" + * // located in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * //the file can be remote + * //table = loadTable("http://p5js.org/reference/assets/mammals.csv", + * // "csv", "header"); + * } + * + * function setup() { + * //count the columns + * print(table.getRowCount() + ' total rows in table'); + * print(table.getColumnCount() + ' total columns in table'); + * + * print(table.getColumn('name')); + * //["Goat", "Leopard", "Zebra"] + * + * //cycle through the table + * for (let r = 0; r < table.getRowCount(); r++) + * for (let c = 0; c < table.getColumnCount(); c++) { + * print(table.getString(r, c)); + * } + * describe(`randomly generated text from a file, + * for example "i smell like butter"`); + * } + * + *
    + */ + fn.loadTable = async function (path) { + // p5._validateParameters('loadTable', arguments); + let callback; + let errorCallback; + const options = []; + let header = false; + const ext = path.substring(path.lastIndexOf('.') + 1, path.length); + + let sep; + if (ext === 'csv') { + sep = ','; + } else if (ext === 'ssv') { + sep = ';'; + } else if (ext === 'tsv') { + sep = '\t'; + } + + for (let i = 1; i < arguments.length; i++) { + if (typeof arguments[i] === 'function') { + if (typeof callback === 'undefined') { + callback = arguments[i]; + } else if (typeof errorCallback === 'undefined') { + errorCallback = arguments[i]; + } + } else if (typeof arguments[i] === 'string') { + options.push(arguments[i]); + if (arguments[i] === 'header') { + header = true; + } + if (arguments[i] === 'csv') { + sep = ','; + } else if (arguments[i] === 'ssv') { + sep = ';'; + } else if (arguments[i] === 'tsv') { + sep = '\t'; + } } } - )); - return ret; -}; + const t = new p5.Table(); + + await new Promise(resolve => this.httpDo( + path, + 'GET', + 'table', + resp => { + const state = {}; + + // define constants + const PRE_TOKEN = 0, + MID_TOKEN = 1, + POST_TOKEN = 2, + POST_RECORD = 4; + + const QUOTE = '"', + CR = '\r', + LF = '\n'; + + const records = []; + let offset = 0; + let currentRecord = null; + let currentChar; + + const tokenBegin = () => { + state.currentState = PRE_TOKEN; + state.token = ''; + }; + + const tokenEnd = () => { + currentRecord.push(state.token); + tokenBegin(); + }; + + const recordBegin = () => { + state.escaped = false; + currentRecord = []; + tokenBegin(); + }; + + const recordEnd = () => { + state.currentState = POST_RECORD; + records.push(currentRecord); + currentRecord = null; + }; + + for (; ;) { + currentChar = resp[offset++]; + + // EOF + if (currentChar == null) { + if (state.escaped) { + throw new Error('Unclosed quote in file.'); + } + if (currentRecord) { + tokenEnd(); + recordEnd(); + break; + } + } + if (currentRecord === null) { + recordBegin(); + } -/** - * Reads the contents of a file or URL and creates a p5.Table object with - * its values. If a file is specified, it must be located in the sketch's - * "data" folder. The filename parameter can also be a URL to a file found - * online. By default, the file is assumed to be comma-separated (in CSV - * format). Table only looks for a header row if the 'header' option is - * included. - * - * This method is asynchronous, meaning it may not finish before the next - * line in your sketch is executed. Calling loadTable() inside preload() - * guarantees to complete the operation before setup() and draw() are called. - * Outside of preload(), you may supply a callback function to handle the - * object: - * - * All files loaded and saved use UTF-8 encoding. This method is suitable for fetching files up to size of 64MB. - * @method loadTable - * @param {String} filename name of the file or URL to load - * @param {String} [extension] parse the table by comma-separated values "csv", semicolon-separated - * values "ssv", or tab-separated values "tsv" - * @param {String} [header] "header" to indicate table has header row - * @param {Function} [callback] function to be executed after - * loadTable() completes. On success, the - * Table object is passed in as the - * first argument. - * @param {Function} [errorCallback] function to be executed if - * there is an error, response is passed - * in as first argument - * @return {Object} Table object containing data - * - * @example - *
    - * - * // Given the following CSV file called "mammals.csv" - * // located in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * //the file can be remote - * //table = loadTable("http://p5js.org/reference/assets/mammals.csv", - * // "csv", "header"); - * } - * - * function setup() { - * //count the columns - * print(table.getRowCount() + ' total rows in table'); - * print(table.getColumnCount() + ' total columns in table'); - * - * print(table.getColumn('name')); - * //["Goat", "Leopard", "Zebra"] - * - * //cycle through the table - * for (let r = 0; r < table.getRowCount(); r++) - * for (let c = 0; c < table.getColumnCount(); c++) { - * print(table.getString(r, c)); - * } - * describe(`randomly generated text from a file, - * for example "i smell like butter"`); - * } - * - *
    - */ -p5.prototype.loadTable = async function (path) { - // p5._validateParameters('loadTable', arguments); - let callback; - let errorCallback; - const options = []; - let header = false; - const ext = path.substring(path.lastIndexOf('.') + 1, path.length); - - let sep; - if (ext === 'csv') { - sep = ','; - } else if (ext === 'ssv') { - sep = ';'; - } else if (ext === 'tsv') { - sep = '\t'; - } + // Handle opening quote + if (state.currentState === PRE_TOKEN) { + if (currentChar === QUOTE) { + state.escaped = true; + state.currentState = MID_TOKEN; + continue; + } + state.currentState = MID_TOKEN; + } + + // mid-token and escaped, look for sequences and end quote + if (state.currentState === MID_TOKEN && state.escaped) { + if (currentChar === QUOTE) { + if (resp[offset] === QUOTE) { + state.token += QUOTE; + offset++; + } else { + state.escaped = false; + state.currentState = POST_TOKEN; + } + } else if (currentChar === CR) { + continue; + } else { + state.token += currentChar; + } + continue; + } + + // fall-through: mid-token or post-token, not escaped + if (currentChar === CR) { + if (resp[offset] === LF) { + offset++; + } + tokenEnd(); + recordEnd(); + } else if (currentChar === LF) { + tokenEnd(); + recordEnd(); + } else if (currentChar === sep) { + tokenEnd(); + } else if (state.currentState === MID_TOKEN) { + state.token += currentChar; + } + } + + // set up column names + if (header) { + t.columns = records.shift(); + } else { + for (let i = 0; i < records[0].length; i++) { + t.columns[i] = 'null'; + } + } + let row; + for (let i = 0; i < records.length; i++) { + //Handles row of 'undefined' at end of some CSVs + if (records[i].length === 1) { + if (records[i][0] === 'undefined' || records[i][0] === '') { + continue; + } + } + row = new p5.TableRow(); + row.arr = records[i]; + row.obj = makeObject(records[i], t.columns); + t.addRow(row); + } + if (typeof callback === 'function') { + callback(t); + } - for (let i = 1; i < arguments.length; i++) { - if (typeof arguments[i] === 'function') { - if (typeof callback === 'undefined') { - callback = arguments[i]; - } else if (typeof errorCallback === 'undefined') { - errorCallback = arguments[i]; + resolve() + }, + err => { + // Error handling + p5._friendlyFileLoadError(2, path); + + if (errorCallback) { + errorCallback(err); + } else { + console.error(err); + } } - } else if (typeof arguments[i] === 'string') { - options.push(arguments[i]); - if (arguments[i] === 'header') { - header = true; + )); + + return t; + }; + + // helper function to turn a row into a JSON object + function makeObject(row, headers) { + headers = headers || []; + if (typeof headers === 'undefined') { + for (let j = 0; j < row.length; j++) { + headers[j.toString()] = j; } - if (arguments[i] === 'csv') { - sep = ','; - } else if (arguments[i] === 'ssv') { - sep = ';'; - } else if (arguments[i] === 'tsv') { - sep = '\t'; + } + return Object.fromEntries( + headers + .map((key, i) => [key, row[i]]) + ); + } + + /** + * Loads an XML file to create a p5.XML object. + * + * Extensible Markup Language + * (XML) + * is a standard format for sending data between applications. Like HTML, the + * XML format is based on tags and attributes, as in + * `<time units="s">1234</time>`. + * + * The first parameter, `path`, is always a string with the path to the file. + * Paths to local files should be relative, as in + * `loadXML('assets/data.xml')`. URLs such as `'https://example.com/data.xml'` + * may be blocked due to browser security. + * + * The second parameter, `successCallback`, is optional. If a function is + * passed, as in `loadXML('assets/data.xml', handleData)`, then the + * `handleData()` function will be called once the data loads. The + * p5.XML object created from the data will be passed + * to `handleData()` as its only argument. + * + * The third parameter, `failureCallback`, is also optional. If a function is + * passed, as in `loadXML('assets/data.xml', handleData, handleFailure)`, then + * the `handleFailure()` function will be called if an error occurs while + * loading. The `Error` object will be passed to `handleFailure()` as its only + * argument. + * + * Note: Data can take time to load. Calling `loadXML()` within + * preload() ensures data loads before it's used in + * setup() or draw(). + * + * @method loadXML + * @param {String} path path of the XML file to be loaded. + * @param {Function} [successCallback] function to call once the data is + * loaded. Will be passed the + * p5.XML object. + * @param {Function} [errorCallback] function to call if the data fails to + * load. Will be passed an `Error` event + * object. + * @return {p5.XML} XML data loaded into a p5.XML + * object. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get an array with all mammal tags. + * let mammals = myXML.getChildren('mammal'); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Iterate over the mammals array. + * for (let i = 0; i < mammals.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 25; + * + * // Get the mammal's common name. + * let name = mammals[i].getContent(); + * + * // Display the mammal's name. + * text(name, 20, y); + * } + * + * describe( + * 'The words "Goat", "Leopard", and "Zebra" written on three separate lines. The text is black on a gray background.' + * ); + * } + * + *
    + * + *
    + * + * let lastMammal; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * loadXML('assets/animals.xml', handleData); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(16); + * + * // Display the content of the last mammal element. + * text(lastMammal, 50, 50); + * + * describe('The word "Zebra" written in black on a gray background.'); + * } + * + * // Get the content of the last mammal element. + * function handleData(data) { + * // Get an array with all mammal elements. + * let mammals = data.getChildren('mammal'); + * + * // Get the content of the last mammal. + * lastMammal = mammals[mammals.length - 1].getContent(); + * } + * + *
    + * + *
    + * + * let lastMammal; + * + * // Load the XML and preprocess it. + * function preload() { + * loadXML('assets/animals.xml', handleData, handleError); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(16); + * + * // Display the content of the last mammal element. + * text(lastMammal, 50, 50); + * + * describe('The word "Zebra" written in black on a gray background.'); + * } + * + * // Get the content of the last mammal element. + * function handleData(data) { + * // Get an array with all mammal elements. + * let mammals = data.getChildren('mammal'); + * + * // Get the content of the last mammal. + * lastMammal = mammals[mammals.length - 1].getContent(); + * } + * + * // Log any errors to the console. + * function handleError(error) { + * console.error('Oops!', error); + * } + * + *
    + */ + fn.loadXML = async function (...args) { + const ret = new p5.XML(); + let callback, errorCallback; + + for (let i = 1; i < args.length; i++) { + const arg = args[i]; + if (typeof arg === 'function') { + if (typeof callback === 'undefined') { + callback = arg; + } else if (typeof errorCallback === 'undefined') { + errorCallback = arg; + } } } - } - const t = new p5.Table(); - - await new Promise(resolve => this.httpDo( - path, - 'GET', - 'table', - resp => { - const state = {}; - - // define constants - const PRE_TOKEN = 0, - MID_TOKEN = 1, - POST_TOKEN = 2, - POST_RECORD = 4; - - const QUOTE = '"', - CR = '\r', - LF = '\n'; - - const records = []; - let offset = 0; - let currentRecord = null; - let currentChar; - - const tokenBegin = () => { - state.currentState = PRE_TOKEN; - state.token = ''; - }; - - const tokenEnd = () => { - currentRecord.push(state.token); - tokenBegin(); - }; - - const recordBegin = () => { - state.escaped = false; - currentRecord = []; - tokenBegin(); - }; - - const recordEnd = () => { - state.currentState = POST_RECORD; - records.push(currentRecord); - currentRecord = null; - }; - - for (; ;) { - currentChar = resp[offset++]; - - // EOF - if (currentChar == null) { - if (state.escaped) { - throw new Error('Unclosed quote in file.'); - } - if (currentRecord) { - tokenEnd(); - recordEnd(); - break; - } - } - if (currentRecord === null) { - recordBegin(); + await new Promise(resolve => this.httpDo( + args[0], + 'GET', + 'xml', + xml => { + for (const key in xml) { + ret[key] = xml[key]; } - - // Handle opening quote - if (state.currentState === PRE_TOKEN) { - if (currentChar === QUOTE) { - state.escaped = true; - state.currentState = MID_TOKEN; - continue; - } - state.currentState = MID_TOKEN; + if (typeof callback !== 'undefined') { + callback(ret); } - // mid-token and escaped, look for sequences and end quote - if (state.currentState === MID_TOKEN && state.escaped) { - if (currentChar === QUOTE) { - if (resp[offset] === QUOTE) { - state.token += QUOTE; - offset++; - } else { - state.escaped = false; - state.currentState = POST_TOKEN; - } - } else if (currentChar === CR) { - continue; - } else { - state.token += currentChar; - } - continue; - } + resolve() + }, + function (err) { + // Error handling + p5._friendlyFileLoadError(1, arguments[0]); - // fall-through: mid-token or post-token, not escaped - if (currentChar === CR) { - if (resp[offset] === LF) { - offset++; - } - tokenEnd(); - recordEnd(); - } else if (currentChar === LF) { - tokenEnd(); - recordEnd(); - } else if (currentChar === sep) { - tokenEnd(); - } else if (state.currentState === MID_TOKEN) { - state.token += currentChar; + if (errorCallback) { + errorCallback(err); + } else { + throw err; } } + )); - // set up column names - if (header) { - t.columns = records.shift(); - } else { - for (let i = 0; i < records[0].length; i++) { - t.columns[i] = 'null'; - } - } - let row; - for (let i = 0; i < records.length; i++) { - //Handles row of 'undefined' at end of some CSVs - if (records[i].length === 1) { - if (records[i][0] === 'undefined' || records[i][0] === '') { - continue; - } + return ret; + }; + + /** + * This method is suitable for fetching files up to size of 64MB. + * @method loadBytes + * @param {String} file name of the file or URL to load + * @param {Function} [callback] function to be executed after loadBytes() + * completes + * @param {Function} [errorCallback] function to be executed if there + * is an error + * @returns {Object} an object whose 'bytes' property will be the loaded buffer + * + * @example + *
    + * let data; + * + * function preload() { + * data = loadBytes('assets/mammals.xml'); + * } + * + * function setup() { + * for (let i = 0; i < 5; i++) { + * console.log(data.bytes[i].toString(16)); + * } + * describe('no image displayed'); + * } + *
    + */ + fn.loadBytes = async function (file, callback, errorCallback) { + const ret = {}; + + await new Promise(resolve => this.httpDo( + file, + 'GET', + 'arrayBuffer', + arrayBuffer => { + ret.bytes = new Uint8Array(arrayBuffer); + + if (typeof callback === 'function') { + callback(ret); } - row = new p5.TableRow(); - row.arr = records[i]; - row.obj = makeObject(records[i], t.columns); - t.addRow(row); - } - if (typeof callback === 'function') { - callback(t); - } - resolve() - }, - err => { - // Error handling - p5._friendlyFileLoadError(2, path); + resolve(); + }, + err => { + // Error handling + p5._friendlyFileLoadError(6, file); - if (errorCallback) { - errorCallback(err); - } else { - console.error(err); + if (errorCallback) { + errorCallback(err); + } else { + throw err; + } } - } - )); - - return t; -}; + )); + return ret; + }; -// helper function to turn a row into a JSON object -function makeObject(row, headers) { - headers = headers || []; - if (typeof headers === 'undefined') { - for (let j = 0; j < row.length; j++) { - headers[j.toString()] = j; - } - } - return Object.fromEntries( - headers - .map((key, i) => [key, row[i]]) - ); -} + /** + * Method for executing an HTTP GET request. If data type is not specified, + * p5 will try to guess based on the URL, defaulting to text. This is equivalent to + * calling httpDo(path, 'GET'). The 'binary' datatype will return + * a Blob object, and the 'arrayBuffer' datatype will return an ArrayBuffer + * which can be used to initialize typed arrays (such as Uint8Array). + * + * @method httpGet + * @param {String} path name of the file or url to load + * @param {String} [datatype] "json", "jsonp", "binary", "arrayBuffer", + * "xml", or "text" + * @param {Object|Boolean} [data] param data passed sent with request + * @param {Function} [callback] function to be executed after + * httpGet() completes, data is passed in + * as first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + * @return {Promise} A promise that resolves with the data when the operation + * completes successfully or rejects with the error after + * one occurs. + * @example + *
    + * // Examples use USGS Earthquake API: + * // https://earthquake.usgs.gov/fdsnws/event/1/#methods + * let earthquakes; + * function preload() { + * // Get the most recent earthquake in the database + * let url = + 'https://earthquake.usgs.gov/fdsnws/event/1/query?' + + * 'format=geojson&limit=1&orderby=time'; + * httpGet(url, 'json', function(response) { + * // when the HTTP request completes, populate the variable that holds the + * // earthquake data used in the visualization. + * earthquakes = response; + * }); + * } + * + * function draw() { + * if (!earthquakes) { + * // Wait until the earthquake data has loaded before drawing. + * return; + * } + * background(200); + * // Get the magnitude and name of the earthquake out of the loaded JSON + * let earthquakeMag = earthquakes.features[0].properties.mag; + * let earthquakeName = earthquakes.features[0].properties.place; + * ellipse(width / 2, height / 2, earthquakeMag * 10, earthquakeMag * 10); + * textAlign(CENTER); + * text(earthquakeName, 0, height - 30, width, 30); + * noLoop(); + * } + *
    + */ + /** + * @method httpGet + * @param {String} path + * @param {Object|Boolean} data + * @param {Function} [callback] + * @param {Function} [errorCallback] + * @return {Promise} + */ + /** + * @method httpGet + * @param {String} path + * @param {Function} callback + * @param {Function} [errorCallback] + * @return {Promise} + */ + fn.httpGet = function (...args) { + p5._validateParameters('httpGet', args); -/** - * Loads an XML file to create a p5.XML object. - * - * Extensible Markup Language - * (XML) - * is a standard format for sending data between applications. Like HTML, the - * XML format is based on tags and attributes, as in - * `<time units="s">1234</time>`. - * - * The first parameter, `path`, is always a string with the path to the file. - * Paths to local files should be relative, as in - * `loadXML('assets/data.xml')`. URLs such as `'https://example.com/data.xml'` - * may be blocked due to browser security. - * - * The second parameter, `successCallback`, is optional. If a function is - * passed, as in `loadXML('assets/data.xml', handleData)`, then the - * `handleData()` function will be called once the data loads. The - * p5.XML object created from the data will be passed - * to `handleData()` as its only argument. - * - * The third parameter, `failureCallback`, is also optional. If a function is - * passed, as in `loadXML('assets/data.xml', handleData, handleFailure)`, then - * the `handleFailure()` function will be called if an error occurs while - * loading. The `Error` object will be passed to `handleFailure()` as its only - * argument. - * - * Note: Data can take time to load. Calling `loadXML()` within - * preload() ensures data loads before it's used in - * setup() or draw(). - * - * @method loadXML - * @param {String} path path of the XML file to be loaded. - * @param {Function} [successCallback] function to call once the data is - * loaded. Will be passed the - * p5.XML object. - * @param {Function} [errorCallback] function to call if the data fails to - * load. Will be passed an `Error` event - * object. - * @return {p5.XML} XML data loaded into a p5.XML - * object. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get an array with all mammal tags. - * let mammals = myXML.getChildren('mammal'); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Iterate over the mammals array. - * for (let i = 0; i < mammals.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 25; - * - * // Get the mammal's common name. - * let name = mammals[i].getContent(); - * - * // Display the mammal's name. - * text(name, 20, y); - * } - * - * describe( - * 'The words "Goat", "Leopard", and "Zebra" written on three separate lines. The text is black on a gray background.' - * ); - * } - * - *
    - * - *
    - * - * let lastMammal; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * loadXML('assets/animals.xml', handleData); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(16); - * - * // Display the content of the last mammal element. - * text(lastMammal, 50, 50); - * - * describe('The word "Zebra" written in black on a gray background.'); - * } - * - * // Get the content of the last mammal element. - * function handleData(data) { - * // Get an array with all mammal elements. - * let mammals = data.getChildren('mammal'); - * - * // Get the content of the last mammal. - * lastMammal = mammals[mammals.length - 1].getContent(); - * } - * - *
    - * - *
    - * - * let lastMammal; - * - * // Load the XML and preprocess it. - * function preload() { - * loadXML('assets/animals.xml', handleData, handleError); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(16); - * - * // Display the content of the last mammal element. - * text(lastMammal, 50, 50); - * - * describe('The word "Zebra" written in black on a gray background.'); - * } - * - * // Get the content of the last mammal element. - * function handleData(data) { - * // Get an array with all mammal elements. - * let mammals = data.getChildren('mammal'); - * - * // Get the content of the last mammal. - * lastMammal = mammals[mammals.length - 1].getContent(); - * } - * - * // Log any errors to the console. - * function handleError(error) { - * console.error('Oops!', error); - * } - * - *
    - */ -p5.prototype.loadXML = async function (...args) { - const ret = new p5.XML(); - let callback, errorCallback; - - for (let i = 1; i < args.length; i++) { - const arg = args[i]; - if (typeof arg === 'function') { - if (typeof callback === 'undefined') { - callback = arg; - } else if (typeof errorCallback === 'undefined') { - errorCallback = arg; - } - } - } + args.splice(1, 0, 'GET'); + return fn.httpDo.apply(this, args); + }; - await new Promise(resolve => this.httpDo( - args[0], - 'GET', - 'xml', - xml => { - for (const key in xml) { - ret[key] = xml[key]; - } - if (typeof callback !== 'undefined') { - callback(ret); - } + /** + * Method for executing an HTTP POST request. If data type is not specified, + * p5 will try to guess based on the URL, defaulting to text. This is equivalent to + * calling httpDo(path, 'POST'). + * + * @method httpPost + * @param {String} path name of the file or url to load + * @param {String} [datatype] "json", "jsonp", "xml", or "text". + * If omitted, httpPost() will guess. + * @param {Object|Boolean} [data] param data passed sent with request + * @param {Function} [callback] function to be executed after + * httpPost() completes, data is passed in + * as first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + * @return {Promise} A promise that resolves with the data when the operation + * completes successfully or rejects with the error after + * one occurs. + * + * @example + *
    + * + * // Examples use jsonplaceholder.typicode.com for a Mock Data API + * + * let url = 'https://jsonplaceholder.typicode.com/posts'; + * let postData = { userId: 1, title: 'p5 Clicked!', body: 'p5.js is very cool.' }; + * + * function setup() { + * createCanvas(100, 100); + * background(200); + * } + * + * function mousePressed() { + * httpPost(url, 'json', postData, function(result) { + * strokeWeight(2); + * text(result.body, mouseX, mouseY); + * }); + * } + * + *
    + * + *
    + * let url = 'ttps://invalidURL'; // A bad URL that will cause errors + * let postData = { title: 'p5 Clicked!', body: 'p5.js is very cool.' }; + * + * function setup() { + * createCanvas(100, 100); + * background(200); + * } + * + * function mousePressed() { + * httpPost( + * url, + * 'json', + * postData, + * function(result) { + * // ... won't be called + * }, + * function(error) { + * strokeWeight(2); + * text(error.toString(), mouseX, mouseY); + * } + * ); + * } + *
    + */ + /** + * @method httpPost + * @param {String} path + * @param {Object|Boolean} data + * @param {Function} [callback] + * @param {Function} [errorCallback] + * @return {Promise} + */ + /** + * @method httpPost + * @param {String} path + * @param {Function} callback + * @param {Function} [errorCallback] + * @return {Promise} + */ + fn.httpPost = function (...args) { + p5._validateParameters('httpPost', args); - resolve() - }, - function (err) { - // Error handling - p5._friendlyFileLoadError(1, arguments[0]); + args.splice(1, 0, 'POST'); + return fn.httpDo.apply(this, args); + }; - if (errorCallback) { - errorCallback(err); + /** + * Method for executing an HTTP request. If data type is not specified, + * p5 will try to guess based on the URL, defaulting to text.

    + * For more advanced use, you may also pass in the path as the first argument + * and a object as the second argument, the signature follows the one specified + * in the Fetch API specification. + * This method is suitable for fetching files up to size of 64MB when "GET" is used. + * + * @method httpDo + * @param {String} path name of the file or url to load + * @param {String} [method] either "GET", "POST", or "PUT", + * defaults to "GET" + * @param {String} [datatype] "json", "jsonp", "xml", or "text" + * @param {Object} [data] param data passed sent with request + * @param {Function} [callback] function to be executed after + * httpGet() completes, data is passed in + * as first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + * @return {Promise} A promise that resolves with the data when the operation + * completes successfully or rejects with the error after + * one occurs. + * + * @example + *
    + * + * // Examples use USGS Earthquake API: + * // https://earthquake.usgs.gov/fdsnws/event/1/#methods + * + * // displays an animation of all USGS earthquakes + * let earthquakes; + * let eqFeatureIndex = 0; + * + * function preload() { + * let url = 'https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson'; + * httpDo( + * url, + * { + * method: 'GET', + * // Other Request options, like special headers for apis + * headers: { authorization: 'Bearer secretKey' } + * }, + * function(res) { + * earthquakes = res; + * } + * ); + * } + * + * function draw() { + * // wait until the data is loaded + * if (!earthquakes || !earthquakes.features[eqFeatureIndex]) { + * return; + * } + * clear(); + * + * let feature = earthquakes.features[eqFeatureIndex]; + * let mag = feature.properties.mag; + * let rad = mag / 11 * ((width + height) / 2); + * fill(255, 0, 0, 100); + * ellipse(width / 2 + random(-2, 2), height / 2 + random(-2, 2), rad, rad); + * + * if (eqFeatureIndex >= earthquakes.features.length) { + * eqFeatureIndex = 0; + * } else { + * eqFeatureIndex += 1; + * } + * } + * + *
    + */ + /** + * @method httpDo + * @param {String} path + * @param {Object} options Request object options as documented in the + * "fetch" API + * reference + * @param {Function} [callback] + * @param {Function} [errorCallback] + * @return {Promise} + */ + fn.httpDo = function (...args) { + let type; + let callback; + let errorCallback; + let request; + let promise; + let cbCount = 0; + let contentType = 'text/plain'; + // Trim the callbacks off the end to get an idea of how many arguments are passed + for (let i = args.length - 1; i > 0; i--) { + if (typeof args[i] === 'function') { + cbCount++; } else { - throw err; + break; } } - )); - - return ret; -}; - -/** - * This method is suitable for fetching files up to size of 64MB. - * @method loadBytes - * @param {String} file name of the file or URL to load - * @param {Function} [callback] function to be executed after loadBytes() - * completes - * @param {Function} [errorCallback] function to be executed if there - * is an error - * @returns {Object} an object whose 'bytes' property will be the loaded buffer - * - * @example - *
    - * let data; - * - * function preload() { - * data = loadBytes('assets/mammals.xml'); - * } - * - * function setup() { - * for (let i = 0; i < 5; i++) { - * console.log(data.bytes[i].toString(16)); - * } - * describe('no image displayed'); - * } - *
    - */ -p5.prototype.loadBytes = async function (file, callback, errorCallback) { - const ret = {}; - - await new Promise(resolve => this.httpDo( - file, - 'GET', - 'arrayBuffer', - arrayBuffer => { - ret.bytes = new Uint8Array(arrayBuffer); - - if (typeof callback === 'function') { - callback(ret); + // The number of arguments minus callbacks + const argsCount = args.length - cbCount; + const path = args[0]; + if ( + argsCount === 2 && + typeof path === 'string' && + typeof args[1] === 'object' + ) { + // Intended for more advanced use, pass in Request parameters directly + request = new Request(path, args[1]); + callback = args[2]; + errorCallback = args[3]; + } else { + // Provided with arguments + let method = 'GET'; + let data; + + for (let j = 1; j < args.length; j++) { + const a = args[j]; + if (typeof a === 'string') { + if (a === 'GET' || a === 'POST' || a === 'PUT' || a === 'DELETE') { + method = a; + } else if ( + a === 'json' || + a === 'binary' || + a === 'arrayBuffer' || + a === 'xml' || + a === 'text' || + a === 'table' + ) { + type = a; + } else { + data = a; + } + } else if (typeof a === 'number') { + data = a.toString(); + } else if (typeof a === 'object') { + if (a instanceof p5.XML) { + data = a.serialize(); + contentType = 'application/xml'; + } else { + data = JSON.stringify(a); + contentType = 'application/json'; + } + } else if (typeof a === 'function') { + if (!callback) { + callback = a; + } else { + errorCallback = a; + } + } } - resolve(); - }, - err => { - // Error handling - p5._friendlyFileLoadError(6, file); - - if (errorCallback) { - errorCallback(err); + let headers = + method === 'GET' + ? new Headers() + : new Headers({ 'Content-Type': contentType }); + + request = new Request(path, { + method, + mode: 'cors', + body: data, + headers + }); + } + // do some sort of smart type checking + if (!type) { + if (path.includes('json')) { + type = 'json'; + } else if (path.includes('xml')) { + type = 'xml'; } else { - throw err; + type = 'text'; } } - )); - return ret; -}; - -/** - * Method for executing an HTTP GET request. If data type is not specified, - * p5 will try to guess based on the URL, defaulting to text. This is equivalent to - * calling httpDo(path, 'GET'). The 'binary' datatype will return - * a Blob object, and the 'arrayBuffer' datatype will return an ArrayBuffer - * which can be used to initialize typed arrays (such as Uint8Array). - * - * @method httpGet - * @param {String} path name of the file or url to load - * @param {String} [datatype] "json", "jsonp", "binary", "arrayBuffer", - * "xml", or "text" - * @param {Object|Boolean} [data] param data passed sent with request - * @param {Function} [callback] function to be executed after - * httpGet() completes, data is passed in - * as first argument - * @param {Function} [errorCallback] function to be executed if - * there is an error, response is passed - * in as first argument - * @return {Promise} A promise that resolves with the data when the operation - * completes successfully or rejects with the error after - * one occurs. - * @example - *
    - * // Examples use USGS Earthquake API: - * // https://earthquake.usgs.gov/fdsnws/event/1/#methods - * let earthquakes; - * function preload() { - * // Get the most recent earthquake in the database - * let url = - 'https://earthquake.usgs.gov/fdsnws/event/1/query?' + - * 'format=geojson&limit=1&orderby=time'; - * httpGet(url, 'json', function(response) { - * // when the HTTP request completes, populate the variable that holds the - * // earthquake data used in the visualization. - * earthquakes = response; - * }); - * } - * - * function draw() { - * if (!earthquakes) { - * // Wait until the earthquake data has loaded before drawing. - * return; - * } - * background(200); - * // Get the magnitude and name of the earthquake out of the loaded JSON - * let earthquakeMag = earthquakes.features[0].properties.mag; - * let earthquakeName = earthquakes.features[0].properties.place; - * ellipse(width / 2, height / 2, earthquakeMag * 10, earthquakeMag * 10); - * textAlign(CENTER); - * text(earthquakeName, 0, height - 30, width, 30); - * noLoop(); - * } - *
    - */ -/** - * @method httpGet - * @param {String} path - * @param {Object|Boolean} data - * @param {Function} [callback] - * @param {Function} [errorCallback] - * @return {Promise} - */ -/** - * @method httpGet - * @param {String} path - * @param {Function} callback - * @param {Function} [errorCallback] - * @return {Promise} - */ -p5.prototype.httpGet = function (...args) { - p5._validateParameters('httpGet', args); - - args.splice(1, 0, 'GET'); - return p5.prototype.httpDo.apply(this, args); -}; - -/** - * Method for executing an HTTP POST request. If data type is not specified, - * p5 will try to guess based on the URL, defaulting to text. This is equivalent to - * calling httpDo(path, 'POST'). - * - * @method httpPost - * @param {String} path name of the file or url to load - * @param {String} [datatype] "json", "jsonp", "xml", or "text". - * If omitted, httpPost() will guess. - * @param {Object|Boolean} [data] param data passed sent with request - * @param {Function} [callback] function to be executed after - * httpPost() completes, data is passed in - * as first argument - * @param {Function} [errorCallback] function to be executed if - * there is an error, response is passed - * in as first argument - * @return {Promise} A promise that resolves with the data when the operation - * completes successfully or rejects with the error after - * one occurs. - * - * @example - *
    - * - * // Examples use jsonplaceholder.typicode.com for a Mock Data API - * - * let url = 'https://jsonplaceholder.typicode.com/posts'; - * let postData = { userId: 1, title: 'p5 Clicked!', body: 'p5.js is very cool.' }; - * - * function setup() { - * createCanvas(100, 100); - * background(200); - * } - * - * function mousePressed() { - * httpPost(url, 'json', postData, function(result) { - * strokeWeight(2); - * text(result.body, mouseX, mouseY); - * }); - * } - * - *
    - * - *
    - * let url = 'ttps://invalidURL'; // A bad URL that will cause errors - * let postData = { title: 'p5 Clicked!', body: 'p5.js is very cool.' }; - * - * function setup() { - * createCanvas(100, 100); - * background(200); - * } - * - * function mousePressed() { - * httpPost( - * url, - * 'json', - * postData, - * function(result) { - * // ... won't be called - * }, - * function(error) { - * strokeWeight(2); - * text(error.toString(), mouseX, mouseY); - * } - * ); - * } - *
    - */ -/** - * @method httpPost - * @param {String} path - * @param {Object|Boolean} data - * @param {Function} [callback] - * @param {Function} [errorCallback] - * @return {Promise} - */ -/** - * @method httpPost - * @param {String} path - * @param {Function} callback - * @param {Function} [errorCallback] - * @return {Promise} - */ -p5.prototype.httpPost = function (...args) { - p5._validateParameters('httpPost', args); - args.splice(1, 0, 'POST'); - return p5.prototype.httpDo.apply(this, args); -}; - -/** - * Method for executing an HTTP request. If data type is not specified, - * p5 will try to guess based on the URL, defaulting to text.

    - * For more advanced use, you may also pass in the path as the first argument - * and a object as the second argument, the signature follows the one specified - * in the Fetch API specification. - * This method is suitable for fetching files up to size of 64MB when "GET" is used. - * - * @method httpDo - * @param {String} path name of the file or url to load - * @param {String} [method] either "GET", "POST", or "PUT", - * defaults to "GET" - * @param {String} [datatype] "json", "jsonp", "xml", or "text" - * @param {Object} [data] param data passed sent with request - * @param {Function} [callback] function to be executed after - * httpGet() completes, data is passed in - * as first argument - * @param {Function} [errorCallback] function to be executed if - * there is an error, response is passed - * in as first argument - * @return {Promise} A promise that resolves with the data when the operation - * completes successfully or rejects with the error after - * one occurs. - * - * @example - *
    - * - * // Examples use USGS Earthquake API: - * // https://earthquake.usgs.gov/fdsnws/event/1/#methods - * - * // displays an animation of all USGS earthquakes - * let earthquakes; - * let eqFeatureIndex = 0; - * - * function preload() { - * let url = 'https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson'; - * httpDo( - * url, - * { - * method: 'GET', - * // Other Request options, like special headers for apis - * headers: { authorization: 'Bearer secretKey' } - * }, - * function(res) { - * earthquakes = res; - * } - * ); - * } - * - * function draw() { - * // wait until the data is loaded - * if (!earthquakes || !earthquakes.features[eqFeatureIndex]) { - * return; - * } - * clear(); - * - * let feature = earthquakes.features[eqFeatureIndex]; - * let mag = feature.properties.mag; - * let rad = mag / 11 * ((width + height) / 2); - * fill(255, 0, 0, 100); - * ellipse(width / 2 + random(-2, 2), height / 2 + random(-2, 2), rad, rad); - * - * if (eqFeatureIndex >= earthquakes.features.length) { - * eqFeatureIndex = 0; - * } else { - * eqFeatureIndex += 1; - * } - * } - * - *
    - */ -/** - * @method httpDo - * @param {String} path - * @param {Object} options Request object options as documented in the - * "fetch" API - * reference - * @param {Function} [callback] - * @param {Function} [errorCallback] - * @return {Promise} - */ -p5.prototype.httpDo = function (...args) { - let type; - let callback; - let errorCallback; - let request; - let promise; - let cbCount = 0; - let contentType = 'text/plain'; - // Trim the callbacks off the end to get an idea of how many arguments are passed - for (let i = args.length - 1; i > 0; i--) { - if (typeof args[i] === 'function') { - cbCount++; - } else { - break; - } - } - // The number of arguments minus callbacks - const argsCount = args.length - cbCount; - const path = args[0]; - if ( - argsCount === 2 && - typeof path === 'string' && - typeof args[1] === 'object' - ) { - // Intended for more advanced use, pass in Request parameters directly - request = new Request(path, args[1]); - callback = args[2]; - errorCallback = args[3]; - } else { - // Provided with arguments - let method = 'GET'; - let data; - - for (let j = 1; j < args.length; j++) { - const a = args[j]; - if (typeof a === 'string') { - if (a === 'GET' || a === 'POST' || a === 'PUT' || a === 'DELETE') { - method = a; - } else if ( - a === 'json' || - a === 'binary' || - a === 'arrayBuffer' || - a === 'xml' || - a === 'text' || - a === 'table' - ) { - type = a; - } else { - data = a; - } - } else if (typeof a === 'number') { - data = a.toString(); - } else if (typeof a === 'object') { - if (a instanceof p5.XML) { - data = a.serialize(); - contentType = 'application/xml'; - } else { - data = JSON.stringify(a); - contentType = 'application/json'; - } - } else if (typeof a === 'function') { - if (!callback) { - callback = a; - } else { - errorCallback = a; + promise = fetch(request); + promise = promise.then(res => { + if (!res.ok) { + const err = new Error(res.body); + err.status = res.status; + err.ok = false; + throw err; + } else { + switch (type) { + case 'json': + return res.json(); + case 'binary': + return res.blob(); + case 'arrayBuffer': + return res.arrayBuffer(); + case 'xml': + return res.text().then(text => { + const parser = new DOMParser(); + const xml = parser.parseFromString(text, 'text/xml'); + return new p5.XML(xml.documentElement); + }); + default: + return res.text(); } } - } - - let headers = - method === 'GET' - ? new Headers() - : new Headers({ 'Content-Type': contentType }); - - request = new Request(path, { - method, - mode: 'cors', - body: data, - headers }); - } - // do some sort of smart type checking - if (!type) { - if (path.includes('json')) { - type = 'json'; - } else if (path.includes('xml')) { - type = 'xml'; - } else { - type = 'text'; - } - } - - promise = fetch(request); - promise = promise.then(res => { - if (!res.ok) { - const err = new Error(res.body); - err.status = res.status; - err.ok = false; - throw err; - } else { - switch (type) { - case 'json': - return res.json(); - case 'binary': - return res.blob(); - case 'arrayBuffer': - return res.arrayBuffer(); - case 'xml': - return res.text().then(text => { - const parser = new DOMParser(); - const xml = parser.parseFromString(text, 'text/xml'); - return new p5.XML(xml.documentElement); - }); - default: - return res.text(); - } - } - }); - promise.then(callback || (() => { })); - promise.catch(errorCallback || console.error); - return promise; -}; + promise.then(callback || (() => { })); + promise.catch(errorCallback || console.error); + return promise; + }; -/** - * @module IO - * @submodule Output - * @for p5 - */ + /** + * @module IO + * @submodule Output + * @for p5 + */ -window.URL = window.URL || window.webkitURL; + window.URL = window.URL || window.webkitURL; -// private array of p5.PrintWriter objects -p5.prototype._pWriters = []; + // private array of p5.PrintWriter objects + fn._pWriters = []; -/** - * Creates a new p5.PrintWriter object. - * - * p5.PrintWriter objects provide a way to - * save a sequence of text data, called the *print stream*, to the user's - * computer. They're low-level objects that enable precise control of text - * output. Functions such as - * saveStrings() and - * saveJSON() are easier to use for simple file - * saving. - * - * The first parameter, `filename`, is the name of the file to be written. If - * a string is passed, as in `createWriter('words.txt')`, a new - * p5.PrintWriter object will be created that - * writes to a file named `words.txt`. - * - * The second parameter, `extension`, is optional. If a string is passed, as - * in `createWriter('words', 'csv')`, the first parameter will be interpreted - * as the file name and the second parameter as the extension. - * - * @method createWriter - * @param {String} name name of the file to create. - * @param {String} [extension] format to use for the file. - * @return {p5.PrintWriter} stream for writing data. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display instructions. - * text('Double-click to save', 5, 50, 90); - * - * describe('The text "Double-click to save" written in black on a gray background.'); - * } - * - * // Save the file when the user double-clicks. - * function doubleClicked() { - * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { - * // Create a p5.PrintWriter object. - * let myWriter = createWriter('xo.txt'); - * - * // Add some lines to the print stream. - * myWriter.print('XOO'); - * myWriter.print('OXO'); - * myWriter.print('OOX'); - * - * // Save the file and close the print stream. - * myWriter.close(); - * } - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display instructions. - * text('Double-click to save', 5, 50, 90); - * - * describe('The text "Double-click to save" written in black on a gray background.'); - * } - * - * // Save the file when the user double-clicks. - * function doubleClicked() { - * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { - * // Create a p5.PrintWriter object. - * // Use the file format .csv. - * let myWriter = createWriter('mauna_loa_co2', 'csv'); - * - * // Add some lines to the print stream. - * myWriter.print('date,ppm_co2'); - * myWriter.print('1960-01-01,316.43'); - * myWriter.print('1970-01-01,325.06'); - * myWriter.print('1980-01-01,337.9'); - * myWriter.print('1990-01-01,353.86'); - * myWriter.print('2000-01-01,369.45'); - * myWriter.print('2020-01-01,413.61'); - * - * // Save the file and close the print stream. - * myWriter.close(); - * } - * } - * - *
    - */ -p5.prototype.createWriter = function (name, extension) { - let newPW; - // check that it doesn't already exist - for (const i in p5.prototype._pWriters) { - if (p5.prototype._pWriters[i].name === name) { - // if a p5.PrintWriter w/ this name already exists... - // return p5.prototype._pWriters[i]; // return it w/ contents intact. - // or, could return a new, empty one with a unique name: - newPW = new p5.PrintWriter(name + this.millis(), extension); - p5.prototype._pWriters.push(newPW); - return newPW; + /** + * Creates a new p5.PrintWriter object. + * + * p5.PrintWriter objects provide a way to + * save a sequence of text data, called the *print stream*, to the user's + * computer. They're low-level objects that enable precise control of text + * output. Functions such as + * saveStrings() and + * saveJSON() are easier to use for simple file + * saving. + * + * The first parameter, `filename`, is the name of the file to be written. If + * a string is passed, as in `createWriter('words.txt')`, a new + * p5.PrintWriter object will be created that + * writes to a file named `words.txt`. + * + * The second parameter, `extension`, is optional. If a string is passed, as + * in `createWriter('words', 'csv')`, the first parameter will be interpreted + * as the file name and the second parameter as the extension. + * + * @method createWriter + * @param {String} name name of the file to create. + * @param {String} [extension] format to use for the file. + * @return {p5.PrintWriter} stream for writing data. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display instructions. + * text('Double-click to save', 5, 50, 90); + * + * describe('The text "Double-click to save" written in black on a gray background.'); + * } + * + * // Save the file when the user double-clicks. + * function doubleClicked() { + * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { + * // Create a p5.PrintWriter object. + * let myWriter = createWriter('xo.txt'); + * + * // Add some lines to the print stream. + * myWriter.print('XOO'); + * myWriter.print('OXO'); + * myWriter.print('OOX'); + * + * // Save the file and close the print stream. + * myWriter.close(); + * } + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display instructions. + * text('Double-click to save', 5, 50, 90); + * + * describe('The text "Double-click to save" written in black on a gray background.'); + * } + * + * // Save the file when the user double-clicks. + * function doubleClicked() { + * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { + * // Create a p5.PrintWriter object. + * // Use the file format .csv. + * let myWriter = createWriter('mauna_loa_co2', 'csv'); + * + * // Add some lines to the print stream. + * myWriter.print('date,ppm_co2'); + * myWriter.print('1960-01-01,316.43'); + * myWriter.print('1970-01-01,325.06'); + * myWriter.print('1980-01-01,337.9'); + * myWriter.print('1990-01-01,353.86'); + * myWriter.print('2000-01-01,369.45'); + * myWriter.print('2020-01-01,413.61'); + * + * // Save the file and close the print stream. + * myWriter.close(); + * } + * } + * + *
    + */ + fn.createWriter = function (name, extension) { + let newPW; + // check that it doesn't already exist + for (const i in fn._pWriters) { + if (fn._pWriters[i].name === name) { + // if a p5.PrintWriter w/ this name already exists... + // return fn._pWriters[i]; // return it w/ contents intact. + // or, could return a new, empty one with a unique name: + newPW = new p5.PrintWriter(name + this.millis(), extension); + fn._pWriters.push(newPW); + return newPW; + } } - } - newPW = new p5.PrintWriter(name, extension); - p5.prototype._pWriters.push(newPW); - return newPW; -}; - -/** - * A class to describe a print stream. - * - * Each `p5.PrintWriter` object provides a way to save a sequence of text - * data, called the *print stream*, to the user's computer. It's a low-level - * object that enables precise control of text output. Functions such as - * saveStrings() and - * saveJSON() are easier to use for simple file - * saving. - * - * Note: createWriter() is the recommended way - * to make an instance of this class. - * - * @class p5.PrintWriter - * @param {String} filename name of the file to create. - * @param {String} [extension] format to use for the file. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display instructions. - * text('Double-click to save', 5, 50, 90); - * - * describe('The text "Double-click to save" written in black on a gray background.'); - * } - * - * // Save the file when the user double-clicks. - * function doubleClicked() { - * // Create a p5.PrintWriter object. - * let myWriter = createWriter('xo.txt'); - * - * // Add some lines to the print stream. - * myWriter.print('XOO'); - * myWriter.print('OXO'); - * myWriter.print('OOX'); - * - * // Save the file and close the print stream. - * myWriter.close(); - * } - * - *
    - */ -p5.PrintWriter = function (filename, extension) { - let self = this; - this.name = filename; - this.content = ''; + newPW = new p5.PrintWriter(name, extension); + fn._pWriters.push(newPW); + return newPW; + }; /** - * Writes data to the print stream without adding new lines. + * A class to describe a print stream. * - * The parameter, `data`, is the data to write. `data` can be a number or - * string, as in `myWriter.write('hi')`, or an array of numbers and strings, - * as in `myWriter.write([1, 2, 3])`. A comma will be inserted between array - * array elements when they're added to the print stream. + * Each `p5.PrintWriter` object provides a way to save a sequence of text + * data, called the *print stream*, to the user's computer. It's a low-level + * object that enables precise control of text output. Functions such as + * saveStrings() and + * saveJSON() are easier to use for simple file + * saving. * - * @method write - * @param {String|Number|Array} data data to be written as a string, number, - * or array of strings and numbers. + * Note: createWriter() is the recommended way + * to make an instance of this class. + * + * @class p5.PrintWriter + * @param {String} filename name of the file to create. + * @param {String} [extension] format to use for the file. * * @example *
    @@ -1618,11 +1563,12 @@ p5.PrintWriter = function (filename, extension) { * // Save the file when the user double-clicks. * function doubleClicked() { * // Create a p5.PrintWriter object. - * let myWriter = createWriter('numbers.txt'); + * let myWriter = createWriter('xo.txt'); * - * // Add some data to the print stream. - * myWriter.write('1,2,3,'); - * myWriter.write(['4', '5', '6']); + * // Add some lines to the print stream. + * myWriter.print('XOO'); + * myWriter.print('OXO'); + * myWriter.print('OOX'); * * // Save the file and close the print stream. * myWriter.close(); @@ -1630,21 +1576,398 @@ p5.PrintWriter = function (filename, extension) { * *
    */ - this.write = function (data) { - this.content += data; + p5.PrintWriter = function (filename, extension) { + let self = this; + this.name = filename; + this.content = ''; + + /** + * Writes data to the print stream without adding new lines. + * + * The parameter, `data`, is the data to write. `data` can be a number or + * string, as in `myWriter.write('hi')`, or an array of numbers and strings, + * as in `myWriter.write([1, 2, 3])`. A comma will be inserted between array + * array elements when they're added to the print stream. + * + * @method write + * @param {String|Number|Array} data data to be written as a string, number, + * or array of strings and numbers. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display instructions. + * text('Double-click to save', 5, 50, 90); + * + * describe('The text "Double-click to save" written in black on a gray background.'); + * } + * + * // Save the file when the user double-clicks. + * function doubleClicked() { + * // Create a p5.PrintWriter object. + * let myWriter = createWriter('numbers.txt'); + * + * // Add some data to the print stream. + * myWriter.write('1,2,3,'); + * myWriter.write(['4', '5', '6']); + * + * // Save the file and close the print stream. + * myWriter.close(); + * } + * + *
    + */ + this.write = function (data) { + this.content += data; + }; + + /** + * Writes data to the print stream with new lines added. + * + * The parameter, `data`, is the data to write. `data` can be a number or + * string, as in `myWriter.print('hi')`, or an array of numbers and strings, + * as in `myWriter.print([1, 2, 3])`. A comma will be inserted between array + * array elements when they're added to the print stream. + * + * @method print + * @param {String|Number|Array} data data to be written as a string, number, + * or array of strings and numbers. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display instructions. + * text('Double-click to save', 5, 50, 90); + * + * describe('The text "Double-click to save" written in black on a gray background.'); + * } + * + * // Save the file when the user double-clicks. + * function doubleClicked() { + * // Create a p5.PrintWriter object. + * let myWriter = createWriter('numbers.txt'); + * + * // Add some data to the print stream. + * myWriter.print('1,2,3,'); + * myWriter.print(['4', '5', '6']); + * + * // Save the file and close the print stream. + * myWriter.close(); + * } + * + *
    + */ + this.print = function (data) { + this.content += `${data}\n`; + }; + + /** + * Clears all data from the print stream. + * + * @method clear + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display instructions. + * text('Double-click to save', 5, 50, 90); + * + * describe('The text "Double-click to save" written in black on a gray background.'); + * } + * + * // Save the file when the user double-clicks. + * function doubleClicked() { + * // Create a p5.PrintWriter object. + * let myWriter = createWriter('numbers.txt'); + * + * // Add some data to the print stream. + * myWriter.print('Hello p5*js!'); + * + * // Clear the print stream. + * myWriter.clear(); + * + * // Save the file and close the print stream. + * myWriter.close(); + * } + * + *
    + */ + this.clear = function () { + this.content = ''; + }; + + /** + * Saves the file and closes the print stream. + * + * @method close + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display instructions. + * text('Double-click to save', 5, 50, 90); + * + * describe('The text "Double-click to save" written in black on a gray background.'); + * } + * + * // Save the file when the user double-clicks. + * function doubleClicked() { + * // Create a p5.PrintWriter object. + * let myWriter = createWriter('cat.txt'); + * + * // Add some data to the print stream. + * // ASCII art courtesy Wikipedia: + * // https://en.wikipedia.org/wiki/ASCII_art + * myWriter.print(' (\\_/) '); + * myWriter.print("(='.'=)"); + * myWriter.print('(")_(")'); + * + * // Save the file and close the print stream. + * myWriter.close(); + * } + * + *
    + */ + this.close = function () { + // convert String to Array for the writeFile Blob + const arr = []; + arr.push(this.content); + fn.writeFile(arr, filename, extension); + // remove from _pWriters array and delete self + for (const i in fn._pWriters) { + if (fn._pWriters[i].name === this.name) { + // remove from _pWriters array + fn._pWriters.splice(i, 1); + } + } + self.clear(); + self = {}; + }; + }; + + /** + * @module IO + * @submodule Output + * @for p5 + */ + + // object, filename, options --> saveJSON, saveStrings, + // filename, [extension] [canvas] --> saveImage + + /** + * Saves a given element(image, text, json, csv, wav, or html) to the client's + * computer. The first parameter can be a pointer to element we want to save. + * The element can be one of p5.Element,an Array of + * Strings, an Array of JSON, a JSON object, a p5.Table + * , a p5.Image, or a p5.SoundFile (requires + * p5.sound). The second parameter is a filename (including extension).The + * third parameter is for options specific to this type of object. This method + * will save a file that fits the given parameters. + * If it is called without specifying an element, by default it will save the + * whole canvas as an image file. You can optionally specify a filename as + * the first parameter in such a case. + * **Note that it is not recommended to + * call this method within draw, as it will open a new save dialog on every + * render.** + * + * @method save + * @param {Object|String} [objectOrFilename] If filename is provided, will + * save canvas as an image with + * either png or jpg extension + * depending on the filename. + * If object is provided, will + * save depending on the object + * and filename (see examples + * above). + * @param {String} [filename] If an object is provided as the first + * parameter, then the second parameter + * indicates the filename, + * and should include an appropriate + * file extension (see examples above). + * @param {Boolean|String} [options] Additional options depend on + * filetype. For example, when saving JSON, + * true indicates that the + * output will be optimized for filesize, + * rather than readability. + * + * @example + *
    + * // Saves the canvas as an image + * cnv = createCanvas(300, 300); + * save(cnv, 'myCanvas.jpg'); + * + * // Saves the canvas as an image by default + * save('myCanvas.jpg'); + * describe('An example for saving a canvas as an image.'); + *
    + * + *
    + * // Saves p5.Image as an image + * img = createImage(10, 10); + * save(img, 'myImage.png'); + * describe('An example for saving a p5.Image element as an image.'); + *
    + * + *
    + * // Saves p5.Renderer object as an image + * obj = createGraphics(100, 100); + * save(obj, 'myObject.png'); + * describe('An example for saving a p5.Renderer element.'); + *
    + * + *
    + * let myTable = new p5.Table(); + * // Saves table as html file + * save(myTable, 'myTable.html'); + * + * // Comma Separated Values + * save(myTable, 'myTable.csv'); + * + * // Tab Separated Values + * save(myTable, 'myTable.tsv'); + * + * describe(`An example showing how to save a table in formats of + * HTML, CSV and TSV.`); + *
    + * + *
    + * let myJSON = { a: 1, b: true }; + * + * // Saves pretty JSON + * save(myJSON, 'my.json'); + * + * // Optimizes JSON filesize + * save(myJSON, 'my.json', true); + * + * describe('An example for saving JSON to a txt file with some extra arguments.'); + *
    + * + *
    + * // Saves array of strings to text file with line breaks after each item + * let arrayOfStrings = ['a', 'b']; + * save(arrayOfStrings, 'my.txt'); + * describe(`An example for saving an array of strings to text file + * with line breaks.`); + *
    + */ + + fn.save = function (object, _filename, _options) { + // parse the arguments and figure out which things we are saving + const args = arguments; + // ================================================= + // OPTION 1: saveCanvas... + + // if no arguments are provided, save canvas + const cnv = this._curElement ? this._curElement.elt : this.elt; + if (args.length === 0) { + fn.saveCanvas(cnv); + return; + } else if (args[0] instanceof Renderer || args[0] instanceof p5.Graphics) { + // otherwise, parse the arguments + + // if first param is a p5Graphics, then saveCanvas + fn.saveCanvas(args[0].elt, args[1], args[2]); + return; + } else if (args.length === 1 && typeof args[0] === 'string') { + // if 1st param is String and only one arg, assume it is canvas filename + fn.saveCanvas(cnv, args[0]); + } else { + // ================================================= + // OPTION 2: extension clarifies saveStrings vs. saveJSON + const extension = _checkFileExtension(args[1], args[2])[1]; + switch (extension) { + case 'json': + fn.saveJSON(args[0], args[1], args[2]); + return; + case 'txt': + fn.saveStrings(args[0], args[1], args[2]); + return; + // ================================================= + // OPTION 3: decide based on object... + default: + if (args[0] instanceof Array) { + fn.saveStrings(args[0], args[1], args[2]); + } else if (args[0] instanceof p5.Table) { + fn.saveTable(args[0], args[1], args[2]); + } else if (args[0] instanceof p5.Image) { + fn.saveCanvas(args[0].canvas, args[1]); + } else if (args[0] instanceof p5.SoundFile) { + fn.saveSound(args[0], args[1], args[2], args[3]); + } + } + } }; /** - * Writes data to the print stream with new lines added. + * Saves an `Object` or `Array` to a JSON file. + * + * JavaScript Object Notation + * (JSON) + * is a standard format for sending data between applications. The format is + * based on JavaScript objects which have keys and values. JSON files store + * data in an object with strings as keys. Values can be strings, numbers, + * Booleans, arrays, `null`, or other objects. + * + * The first parameter, `json`, is the data to save. The data can be an array, + * as in `[1, 2, 3]`, or an object, as in + * `{ x: 50, y: 50, color: 'deeppink' }`. + * + * The second parameter, `filename`, is a string that sets the file's name. + * For example, calling `saveJSON([1, 2, 3], 'data.json')` saves the array + * `[1, 2, 3]` to a file called `data.json` on the user's computer. * - * The parameter, `data`, is the data to write. `data` can be a number or - * string, as in `myWriter.print('hi')`, or an array of numbers and strings, - * as in `myWriter.print([1, 2, 3])`. A comma will be inserted between array - * array elements when they're added to the print stream. + * The third parameter, `optimize`, is optional. If `true` is passed, as in + * `saveJSON([1, 2, 3], 'data.json', true)`, then all unneeded whitespace will + * be removed to reduce the file size. * - * @method print - * @param {String|Number|Array} data data to be written as a string, number, - * or array of strings and numbers. + * Note: The browser will either save the file immediately or prompt the user + * with a dialogue window. + * + * @method saveJSON + * @param {Array|Object} json data to save. + * @param {String} filename name of the file to be saved. + * @param {Boolean} [optimize] whether to trim unneeded whitespace. Defaults + * to `true`. * * @example *
    @@ -1667,29 +1990,17 @@ p5.PrintWriter = function (filename, extension) { * * // Save the file when the user double-clicks. * function doubleClicked() { - * // Create a p5.PrintWriter object. - * let myWriter = createWriter('numbers.txt'); + * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { + * // Create an array. + * let data = [1, 2, 3]; * - * // Add some data to the print stream. - * myWriter.print('1,2,3,'); - * myWriter.print(['4', '5', '6']); - * - * // Save the file and close the print stream. - * myWriter.close(); + * // Save the JSON file. + * saveJSON(data, 'numbers.json'); + * } * } * *
    - */ - this.print = function (data) { - this.content += `${data}\n`; - }; - - /** - * Clears all data from the print stream. - * - * @method clear * - * @example *
    * * function setup() { @@ -1710,29 +2021,92 @@ p5.PrintWriter = function (filename, extension) { * * // Save the file when the user double-clicks. * function doubleClicked() { - * // Create a p5.PrintWriter object. - * let myWriter = createWriter('numbers.txt'); + * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { + * // Create an object. + * let data = { x: mouseX, y: mouseY }; * - * // Add some data to the print stream. - * myWriter.print('Hello p5*js!'); + * // Save the JSON file. + * saveJSON(data, 'state.json'); + * } + * } + * + *
    * - * // Clear the print stream. - * myWriter.clear(); + *
    + * + * function setup() { + * createCanvas(100, 100); * - * // Save the file and close the print stream. - * myWriter.close(); + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display instructions. + * text('Double-click to save', 5, 50, 90); + * + * describe('The text "Double-click to save" written in black on a gray background.'); + * } + * + * // Save the file when the user double-clicks. + * function doubleClicked() { + * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { + * // Create an object. + * let data = { x: mouseX, y: mouseY }; + * + * // Save the JSON file and reduce its size. + * saveJSON(data, 'state.json', true); + * } * } * *
    */ - this.clear = function () { - this.content = ''; + fn.saveJSON = function (json, filename, opt) { + p5._validateParameters('saveJSON', arguments); + let stringify; + if (opt) { + stringify = JSON.stringify(json); + } else { + stringify = JSON.stringify(json, undefined, 2); + } + this.saveStrings(stringify.split('\n'), filename, 'json'); }; + fn.saveJSONObject = fn.saveJSON; + fn.saveJSONArray = fn.saveJSON; + /** - * Saves the file and closes the print stream. + * Saves an `Array` of `String`s to a file, one per line. + * + * The first parameter, `list`, is an array with the strings to save. + * + * The second parameter, `filename`, is a string that sets the file's name. + * For example, calling `saveStrings(['0', '01', '011'], 'data.txt')` saves + * the array `['0', '01', '011']` to a file called `data.txt` on the user's + * computer. + * + * The third parameter, `extension`, is optional. If a string is passed, as in + * `saveStrings(['0', '01', '0`1'], 'data', 'txt')`, the second parameter will + * be interpreted as the file name and the third parameter as the extension. * - * @method close + * The fourth parameter, `isCRLF`, is also optional, If `true` is passed, as + * in `saveStrings(['0', '01', '011'], 'data', 'txt', true)`, then two + * characters, `\r\n` , will be added to the end of each string to create new + * lines in the saved file. `\r` is a carriage return (CR) and `\n` is a line + * feed (LF). By default, only `\n` (line feed) is added to each string in + * order to create new lines. + * + * Note: The browser will either save the file immediately or prompt the user + * with a dialogue window. + * + * @method saveStrings + * @param {String[]} list data to save. + * @param {String} filename name of file to be saved. + * @param {String} [extension] format to use for the file. + * @param {Boolean} [isCRLF] whether to add `\r\n` to the end of each + * string. Defaults to `false`. * * @example *
    @@ -1755,741 +2129,369 @@ p5.PrintWriter = function (filename, extension) { * * // Save the file when the user double-clicks. * function doubleClicked() { - * // Create a p5.PrintWriter object. - * let myWriter = createWriter('cat.txt'); + * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { + * // Create an array. + * let data = ['0', '01', '011']; * - * // Add some data to the print stream. - * // ASCII art courtesy Wikipedia: - * // https://en.wikipedia.org/wiki/ASCII_art - * myWriter.print(' (\\_/) '); - * myWriter.print("(='.'=)"); - * myWriter.print('(")_(")'); + * // Save the text file. + * saveStrings(data, 'data.txt'); + * } + * } + * + *
    * - * // Save the file and close the print stream. - * myWriter.close(); + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display instructions. + * text('Double-click to save', 5, 50, 90); + * + * describe('The text "Double-click to save" written in black on a gray background.'); + * } + * + * // Save the file when the user double-clicks. + * function doubleClicked() { + * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { + * // Create an array. + * // ASCII art courtesy Wikipedia: + * // https://en.wikipedia.org/wiki/ASCII_art + * let data = [' (\\_/) ', "(='.'=)", '(")_(")']; + * + * // Save the text file. + * saveStrings(data, 'cat', 'txt'); + * } + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display instructions. + * text('Double-click to save', 5, 50, 90); + * + * describe('The text "Double-click to save" written in black on a gray background.'); + * } + * + * // Save the file when the user double-clicks. + * function doubleClicked() { + * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { + * // Create an array. + * // +--+ + * // / /| + * // +--+ + + * // | |/ + * // +--+ + * let data = [' +--+', ' / /|', '+--+ +', '| |/', '+--+']; + * + * // Save the text file. + * // Use CRLF for line endings. + * saveStrings(data, 'box', 'txt', true); + * } * } * *
    */ - this.close = function () { - // convert String to Array for the writeFile Blob - const arr = []; - arr.push(this.content); - p5.prototype.writeFile(arr, filename, extension); - // remove from _pWriters array and delete self - for (const i in p5.prototype._pWriters) { - if (p5.prototype._pWriters[i].name === this.name) { - // remove from _pWriters array - p5.prototype._pWriters.splice(i, 1); - } + fn.saveStrings = function (list, filename, extension, isCRLF) { + p5._validateParameters('saveStrings', arguments); + const ext = extension || 'txt'; + const pWriter = this.createWriter(filename, ext); + for (let i = 0; i < list.length; i++) { + isCRLF ? pWriter.write(list[i] + '\r\n') : pWriter.write(list[i] + '\n'); } - self.clear(); - self = {}; + pWriter.close(); + pWriter.clear(); }; -}; - -/** - * @module IO - * @submodule Output - * @for p5 - */ - -// object, filename, options --> saveJSON, saveStrings, -// filename, [extension] [canvas] --> saveImage - -/** - * Saves a given element(image, text, json, csv, wav, or html) to the client's - * computer. The first parameter can be a pointer to element we want to save. - * The element can be one of p5.Element,an Array of - * Strings, an Array of JSON, a JSON object, a p5.Table - * , a p5.Image, or a p5.SoundFile (requires - * p5.sound). The second parameter is a filename (including extension).The - * third parameter is for options specific to this type of object. This method - * will save a file that fits the given parameters. - * If it is called without specifying an element, by default it will save the - * whole canvas as an image file. You can optionally specify a filename as - * the first parameter in such a case. - * **Note that it is not recommended to - * call this method within draw, as it will open a new save dialog on every - * render.** - * - * @method save - * @param {Object|String} [objectOrFilename] If filename is provided, will - * save canvas as an image with - * either png or jpg extension - * depending on the filename. - * If object is provided, will - * save depending on the object - * and filename (see examples - * above). - * @param {String} [filename] If an object is provided as the first - * parameter, then the second parameter - * indicates the filename, - * and should include an appropriate - * file extension (see examples above). - * @param {Boolean|String} [options] Additional options depend on - * filetype. For example, when saving JSON, - * true indicates that the - * output will be optimized for filesize, - * rather than readability. - * - * @example - *
    - * // Saves the canvas as an image - * cnv = createCanvas(300, 300); - * save(cnv, 'myCanvas.jpg'); - * - * // Saves the canvas as an image by default - * save('myCanvas.jpg'); - * describe('An example for saving a canvas as an image.'); - *
    - * - *
    - * // Saves p5.Image as an image - * img = createImage(10, 10); - * save(img, 'myImage.png'); - * describe('An example for saving a p5.Image element as an image.'); - *
    - * - *
    - * // Saves p5.Renderer object as an image - * obj = createGraphics(100, 100); - * save(obj, 'myObject.png'); - * describe('An example for saving a p5.Renderer element.'); - *
    - * - *
    - * let myTable = new p5.Table(); - * // Saves table as html file - * save(myTable, 'myTable.html'); - * - * // Comma Separated Values - * save(myTable, 'myTable.csv'); - * - * // Tab Separated Values - * save(myTable, 'myTable.tsv'); - * - * describe(`An example showing how to save a table in formats of - * HTML, CSV and TSV.`); - *
    - * - *
    - * let myJSON = { a: 1, b: true }; - * - * // Saves pretty JSON - * save(myJSON, 'my.json'); - * - * // Optimizes JSON filesize - * save(myJSON, 'my.json', true); - * - * describe('An example for saving JSON to a txt file with some extra arguments.'); - *
    - * - *
    - * // Saves array of strings to text file with line breaks after each item - * let arrayOfStrings = ['a', 'b']; - * save(arrayOfStrings, 'my.txt'); - * describe(`An example for saving an array of strings to text file - * with line breaks.`); - *
    - */ -p5.prototype.save = function (object, _filename, _options) { - // parse the arguments and figure out which things we are saving - const args = arguments; - // ================================================= - // OPTION 1: saveCanvas... - - // if no arguments are provided, save canvas - const cnv = this._curElement ? this._curElement.elt : this.elt; - if (args.length === 0) { - p5.prototype.saveCanvas(cnv); - return; - } else if (args[0] instanceof Renderer || args[0] instanceof p5.Graphics) { - // otherwise, parse the arguments - - // if first param is a p5Graphics, then saveCanvas - p5.prototype.saveCanvas(args[0].elt, args[1], args[2]); - return; - } else if (args.length === 1 && typeof args[0] === 'string') { - // if 1st param is String and only one arg, assume it is canvas filename - p5.prototype.saveCanvas(cnv, args[0]); - } else { - // ================================================= - // OPTION 2: extension clarifies saveStrings vs. saveJSON - const extension = _checkFileExtension(args[1], args[2])[1]; - switch (extension) { - case 'json': - p5.prototype.saveJSON(args[0], args[1], args[2]); - return; - case 'txt': - p5.prototype.saveStrings(args[0], args[1], args[2]); - return; - // ================================================= - // OPTION 3: decide based on object... - default: - if (args[0] instanceof Array) { - p5.prototype.saveStrings(args[0], args[1], args[2]); - } else if (args[0] instanceof p5.Table) { - p5.prototype.saveTable(args[0], args[1], args[2]); - } else if (args[0] instanceof p5.Image) { - p5.prototype.saveCanvas(args[0].canvas, args[1]); - } else if (args[0] instanceof p5.SoundFile) { - p5.prototype.saveSound(args[0], args[1], args[2], args[3]); - } - } - } -}; - -/** - * Saves an `Object` or `Array` to a JSON file. - * - * JavaScript Object Notation - * (JSON) - * is a standard format for sending data between applications. The format is - * based on JavaScript objects which have keys and values. JSON files store - * data in an object with strings as keys. Values can be strings, numbers, - * Booleans, arrays, `null`, or other objects. - * - * The first parameter, `json`, is the data to save. The data can be an array, - * as in `[1, 2, 3]`, or an object, as in - * `{ x: 50, y: 50, color: 'deeppink' }`. - * - * The second parameter, `filename`, is a string that sets the file's name. - * For example, calling `saveJSON([1, 2, 3], 'data.json')` saves the array - * `[1, 2, 3]` to a file called `data.json` on the user's computer. - * - * The third parameter, `optimize`, is optional. If `true` is passed, as in - * `saveJSON([1, 2, 3], 'data.json', true)`, then all unneeded whitespace will - * be removed to reduce the file size. - * - * Note: The browser will either save the file immediately or prompt the user - * with a dialogue window. - * - * @method saveJSON - * @param {Array|Object} json data to save. - * @param {String} filename name of the file to be saved. - * @param {Boolean} [optimize] whether to trim unneeded whitespace. Defaults - * to `true`. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display instructions. - * text('Double-click to save', 5, 50, 90); - * - * describe('The text "Double-click to save" written in black on a gray background.'); - * } - * - * // Save the file when the user double-clicks. - * function doubleClicked() { - * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { - * // Create an array. - * let data = [1, 2, 3]; - * - * // Save the JSON file. - * saveJSON(data, 'numbers.json'); - * } - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display instructions. - * text('Double-click to save', 5, 50, 90); - * - * describe('The text "Double-click to save" written in black on a gray background.'); - * } - * - * // Save the file when the user double-clicks. - * function doubleClicked() { - * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { - * // Create an object. - * let data = { x: mouseX, y: mouseY }; - * - * // Save the JSON file. - * saveJSON(data, 'state.json'); - * } - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display instructions. - * text('Double-click to save', 5, 50, 90); - * - * describe('The text "Double-click to save" written in black on a gray background.'); - * } - * - * // Save the file when the user double-clicks. - * function doubleClicked() { - * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { - * // Create an object. - * let data = { x: mouseX, y: mouseY }; - * - * // Save the JSON file and reduce its size. - * saveJSON(data, 'state.json', true); - * } - * } - * - *
    - */ -p5.prototype.saveJSON = function (json, filename, opt) { - p5._validateParameters('saveJSON', arguments); - let stringify; - if (opt) { - stringify = JSON.stringify(json); - } else { - stringify = JSON.stringify(json, undefined, 2); + // ======= + // HELPERS + // ======= + + function escapeHelper(content) { + return content + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); } - this.saveStrings(stringify.split('\n'), filename, 'json'); -}; -p5.prototype.saveJSONObject = p5.prototype.saveJSON; -p5.prototype.saveJSONArray = p5.prototype.saveJSON; - -/** - * Saves an `Array` of `String`s to a file, one per line. - * - * The first parameter, `list`, is an array with the strings to save. - * - * The second parameter, `filename`, is a string that sets the file's name. - * For example, calling `saveStrings(['0', '01', '011'], 'data.txt')` saves - * the array `['0', '01', '011']` to a file called `data.txt` on the user's - * computer. - * - * The third parameter, `extension`, is optional. If a string is passed, as in - * `saveStrings(['0', '01', '0`1'], 'data', 'txt')`, the second parameter will - * be interpreted as the file name and the third parameter as the extension. - * - * The fourth parameter, `isCRLF`, is also optional, If `true` is passed, as - * in `saveStrings(['0', '01', '011'], 'data', 'txt', true)`, then two - * characters, `\r\n` , will be added to the end of each string to create new - * lines in the saved file. `\r` is a carriage return (CR) and `\n` is a line - * feed (LF). By default, only `\n` (line feed) is added to each string in - * order to create new lines. - * - * Note: The browser will either save the file immediately or prompt the user - * with a dialogue window. - * - * @method saveStrings - * @param {String[]} list data to save. - * @param {String} filename name of file to be saved. - * @param {String} [extension] format to use for the file. - * @param {Boolean} [isCRLF] whether to add `\r\n` to the end of each - * string. Defaults to `false`. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display instructions. - * text('Double-click to save', 5, 50, 90); - * - * describe('The text "Double-click to save" written in black on a gray background.'); - * } - * - * // Save the file when the user double-clicks. - * function doubleClicked() { - * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { - * // Create an array. - * let data = ['0', '01', '011']; - * - * // Save the text file. - * saveStrings(data, 'data.txt'); - * } - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display instructions. - * text('Double-click to save', 5, 50, 90); - * - * describe('The text "Double-click to save" written in black on a gray background.'); - * } - * - * // Save the file when the user double-clicks. - * function doubleClicked() { - * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { - * // Create an array. - * // ASCII art courtesy Wikipedia: - * // https://en.wikipedia.org/wiki/ASCII_art - * let data = [' (\\_/) ', "(='.'=)", '(")_(")']; - * - * // Save the text file. - * saveStrings(data, 'cat', 'txt'); - * } - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display instructions. - * text('Double-click to save', 5, 50, 90); - * - * describe('The text "Double-click to save" written in black on a gray background.'); - * } - * - * // Save the file when the user double-clicks. - * function doubleClicked() { - * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { - * // Create an array. - * // +--+ - * // / /| - * // +--+ + - * // | |/ - * // +--+ - * let data = [' +--+', ' / /|', '+--+ +', '| |/', '+--+']; - * - * // Save the text file. - * // Use CRLF for line endings. - * saveStrings(data, 'box', 'txt', true); - * } - * } - * - *
    - */ -p5.prototype.saveStrings = function (list, filename, extension, isCRLF) { - p5._validateParameters('saveStrings', arguments); - const ext = extension || 'txt'; - const pWriter = this.createWriter(filename, ext); - for (let i = 0; i < list.length; i++) { - isCRLF ? pWriter.write(list[i] + '\r\n') : pWriter.write(list[i] + '\n'); - } - pWriter.close(); - pWriter.clear(); -}; - -// ======= -// HELPERS -// ======= - -function escapeHelper(content) { - return content - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); -} - -/** - * Writes the contents of a Table object to a file. Defaults to a - * text file with comma-separated-values ('csv') but can also - * use tab separation ('tsv'), or generate an HTML table ('html'). - * The file saving process and location of the saved file will - * vary between web browsers. - * - * @method saveTable - * @param {p5.Table} Table the Table object to save to a file - * @param {String} filename the filename to which the Table should be saved - * @param {String} [options] can be one of "tsv", "csv", or "html" - * @example - *
    - * let table; - * - * function setup() { - * table = new p5.Table(); - * - * table.addColumn('id'); - * table.addColumn('species'); - * table.addColumn('name'); - * - * let newRow = table.addRow(); - * newRow.setNum('id', table.getRowCount() - 1); - * newRow.setString('species', 'Panthera leo'); - * newRow.setString('name', 'Lion'); - * - * // To save, un-comment next line then click 'run' - * // saveTable(table, 'new.csv'); - * - * describe('no image displayed'); - * } - * - * // Saves the following to a file called 'new.csv': - * // id,species,name - * // 0,Panthera leo,Lion - *
    - */ -p5.prototype.saveTable = function (table, filename, options) { - p5._validateParameters('saveTable', arguments); - let ext; - if (options === undefined) { - ext = filename.substring(filename.lastIndexOf('.') + 1, filename.length); - } else { - ext = options; - } - const pWriter = this.createWriter(filename, ext); + /** + * Writes the contents of a Table object to a file. Defaults to a + * text file with comma-separated-values ('csv') but can also + * use tab separation ('tsv'), or generate an HTML table ('html'). + * The file saving process and location of the saved file will + * vary between web browsers. + * + * @method saveTable + * @param {p5.Table} Table the Table object to save to a file + * @param {String} filename the filename to which the Table should be saved + * @param {String} [options] can be one of "tsv", "csv", or "html" + * @example + *
    + * let table; + * + * function setup() { + * table = new p5.Table(); + * + * table.addColumn('id'); + * table.addColumn('species'); + * table.addColumn('name'); + * + * let newRow = table.addRow(); + * newRow.setNum('id', table.getRowCount() - 1); + * newRow.setString('species', 'Panthera leo'); + * newRow.setString('name', 'Lion'); + * + * // To save, un-comment next line then click 'run' + * // saveTable(table, 'new.csv'); + * + * describe('no image displayed'); + * } + * + * // Saves the following to a file called 'new.csv': + * // id,species,name + * // 0,Panthera leo,Lion + *
    + */ + fn.saveTable = function (table, filename, options) { + p5._validateParameters('saveTable', arguments); + let ext; + if (options === undefined) { + ext = filename.substring(filename.lastIndexOf('.') + 1, filename.length); + } else { + ext = options; + } + const pWriter = this.createWriter(filename, ext); - const header = table.columns; + const header = table.columns; - let sep = ','; // default to CSV - if (ext === 'tsv') { - sep = '\t'; - } - if (ext !== 'html') { - // make header if it has values - if (header[0] !== '0') { - for (let h = 0; h < header.length; h++) { - if (h < header.length - 1) { - pWriter.write(header[h] + sep); - } else { - pWriter.write(header[h]); - } - } - pWriter.write('\n'); + let sep = ','; // default to CSV + if (ext === 'tsv') { + sep = '\t'; } - - // make rows - for (let i = 0; i < table.rows.length; i++) { - let j; - for (j = 0; j < table.rows[i].arr.length; j++) { - if (j < table.rows[i].arr.length - 1) { - //double quotes should be inserted in csv only if contains comma separated single value - if (ext === 'csv' && String(table.rows[i].arr[j]).includes(',')) { - pWriter.write('"' + table.rows[i].arr[j] + '"' + sep); + if (ext !== 'html') { + // make header if it has values + if (header[0] !== '0') { + for (let h = 0; h < header.length; h++) { + if (h < header.length - 1) { + pWriter.write(header[h] + sep); } else { - pWriter.write(table.rows[i].arr[j] + sep); + pWriter.write(header[h]); } - } else { - //double quotes should be inserted in csv only if contains comma separated single value - if (ext === 'csv' && String(table.rows[i].arr[j]).includes(',')) { - pWriter.write('"' + table.rows[i].arr[j] + '"'); + } + pWriter.write('\n'); + } + + // make rows + for (let i = 0; i < table.rows.length; i++) { + let j; + for (j = 0; j < table.rows[i].arr.length; j++) { + if (j < table.rows[i].arr.length - 1) { + //double quotes should be inserted in csv only if contains comma separated single value + if (ext === 'csv' && String(table.rows[i].arr[j]).includes(',')) { + pWriter.write('"' + table.rows[i].arr[j] + '"' + sep); + } else { + pWriter.write(table.rows[i].arr[j] + sep); + } } else { - pWriter.write(table.rows[i].arr[j]); + //double quotes should be inserted in csv only if contains comma separated single value + if (ext === 'csv' && String(table.rows[i].arr[j]).includes(',')) { + pWriter.write('"' + table.rows[i].arr[j] + '"'); + } else { + pWriter.write(table.rows[i].arr[j]); + } } } + pWriter.write('\n'); } - pWriter.write('\n'); - } - } else { - // otherwise, make HTML - pWriter.print(''); - pWriter.print(''); - let str = ' '); - - pWriter.print(''); - pWriter.print(' '); - - // make header if it has values - if (header[0] !== '0') { - pWriter.print(' '); - for (let k = 0; k < header.length; k++) { - const e = escapeHelper(header[k]); - pWriter.print(` '); + } else { + // otherwise, make HTML + pWriter.print(''); + pWriter.print(''); + let str = ' '); + + pWriter.print(''); + pWriter.print('
    ${e}`); - pWriter.print('
    '); + + // make header if it has values + if (header[0] !== '0') { + pWriter.print(' '); + for (let k = 0; k < header.length; k++) { + const e = escapeHelper(header[k]); + pWriter.print(` '); + } + pWriter.print(' '); } - pWriter.print(' '); - } - // make rows - for (let row = 0; row < table.rows.length; row++) { - pWriter.print(' '); - for (let col = 0; col < table.columns.length; col++) { - const entry = table.rows[row].getString(col); - const htmlEntry = escapeHelper(entry); - pWriter.print(` '); + // make rows + for (let row = 0; row < table.rows.length; row++) { + pWriter.print(' '); + for (let col = 0; col < table.columns.length; col++) { + const entry = table.rows[row].getString(col); + const htmlEntry = escapeHelper(entry); + pWriter.print(` '); + } + pWriter.print(' '); } - pWriter.print(' '); + pWriter.print('
    ${e}`); + pWriter.print('
    ${htmlEntry}`); - pWriter.print('
    ${htmlEntry}`); + pWriter.print('
    '); + pWriter.print(''); + pWriter.print(''); } - pWriter.print(' '); - pWriter.print(''); - pWriter.print(''); - } - // close and clear the pWriter - pWriter.close(); - pWriter.clear(); -}; // end saveTable() - -/** - * Generate a blob of file data as a url to prepare for download. - * Accepts an array of data, a filename, and an extension (optional). - * This is a private function because it does not do any formatting, - * but it is used by saveStrings, saveJSON, saveTable etc. - * - * @param {Array} dataToDownload - * @param {String} filename - * @param {String} [extension] - * @private - */ -p5.prototype.writeFile = function (dataToDownload, filename, extension) { - let type = 'application/octet-stream'; - if (p5.prototype._isSafari()) { - type = 'text/plain'; - } - const blob = new Blob(dataToDownload, { - type - }); - p5.prototype.downloadFile(blob, filename, extension); -}; + // close and clear the pWriter + pWriter.close(); + pWriter.clear(); + }; // end saveTable() -/** - * Forces download. Accepts a url to filedata/blob, a filename, - * and an extension (optional). - * This is a private function because it does not do any formatting, - * but it is used by saveStrings, saveJSON, saveTable etc. - * - * @method downloadFile - * @private - * @param {String|Blob} data either an href generated by createObjectURL, - * or a Blob object containing the data - * @param {String} [filename] - * @param {String} [extension] - */ -p5.prototype.downloadFile = function (data, fName, extension) { - const fx = _checkFileExtension(fName, extension); - const filename = fx[0]; + /** + * Generate a blob of file data as a url to prepare for download. + * Accepts an array of data, a filename, and an extension (optional). + * This is a private function because it does not do any formatting, + * but it is used by saveStrings, saveJSON, saveTable etc. + * + * @param {Array} dataToDownload + * @param {String} filename + * @param {String} [extension] + * @private + */ + fn.writeFile = function (dataToDownload, filename, extension) { + let type = 'application/octet-stream'; + if (fn._isSafari()) { + type = 'text/plain'; + } + const blob = new Blob(dataToDownload, { + type + }); + fn.downloadFile(blob, filename, extension); + }; - if (data instanceof Blob) { - fileSaver.saveAs(data, filename); - return; - } + /** + * Forces download. Accepts a url to filedata/blob, a filename, + * and an extension (optional). + * This is a private function because it does not do any formatting, + * but it is used by saveStrings, saveJSON, saveTable etc. + * + * @method downloadFile + * @private + * @param {String|Blob} data either an href generated by createObjectURL, + * or a Blob object containing the data + * @param {String} [filename] + * @param {String} [extension] + */ + fn.downloadFile = function (data, fName, extension) { + const fx = _checkFileExtension(fName, extension); + const filename = fx[0]; - const a = document.createElement('a'); - a.href = data; - a.download = filename; + if (data instanceof Blob) { + fileSaver.saveAs(data, filename); + return; + } - // Firefox requires the link to be added to the DOM before click() - a.onclick = e => { - destroyClickedElement(e); - e.stopPropagation(); + const a = document.createElement('a'); + a.href = data; + a.download = filename; + + // Firefox requires the link to be added to the DOM before click() + a.onclick = e => { + destroyClickedElement(e); + e.stopPropagation(); + }; + + a.style.display = 'none'; + document.body.appendChild(a); + + // Safari will open this file in the same page as a confusing Blob. + if (fn._isSafari()) { + let aText = 'Hello, Safari user! To download this file...\n'; + aText += '1. Go to File --> Save As.\n'; + aText += '2. Choose "Page Source" as the Format.\n'; + aText += `3. Name it with this extension: ."${fx[1]}"`; + alert(aText); + } + a.click(); }; - a.style.display = 'none'; - document.body.appendChild(a); - - // Safari will open this file in the same page as a confusing Blob. - if (p5.prototype._isSafari()) { - let aText = 'Hello, Safari user! To download this file...\n'; - aText += '1. Go to File --> Save As.\n'; - aText += '2. Choose "Page Source" as the Format.\n'; - aText += `3. Name it with this extension: ."${fx[1]}"`; - alert(aText); + /** + * Returns a file extension, or another string + * if the provided parameter has no extension. + * + * @param {String} filename + * @param {String} [extension] + * @return {String[]} [fileName, fileExtension] + * + * @private + */ + function _checkFileExtension(filename, extension) { + if (!extension || extension === true || extension === 'true') { + extension = ''; + } + if (!filename) { + filename = 'untitled'; + } + let ext = ''; + // make sure the file will have a name, see if filename needs extension + if (filename && filename.includes('.')) { + ext = filename.split('.').pop(); + } + // append extension if it doesn't exist + if (extension) { + if (ext !== extension) { + ext = extension; + filename = `${filename}.${ext}`; + } + } + return [filename, ext]; } - a.click(); -}; + fn._checkFileExtension = _checkFileExtension; -/** - * Returns a file extension, or another string - * if the provided parameter has no extension. - * - * @param {String} filename - * @param {String} [extension] - * @return {String[]} [fileName, fileExtension] - * - * @private - */ -function _checkFileExtension(filename, extension) { - if (!extension || extension === true || extension === 'true') { - extension = ''; - } - if (!filename) { - filename = 'untitled'; - } - let ext = ''; - // make sure the file will have a name, see if filename needs extension - if (filename && filename.includes('.')) { - ext = filename.split('.').pop(); - } - // append extension if it doesn't exist - if (extension) { - if (ext !== extension) { - ext = extension; - filename = `${filename}.${ext}`; - } + /** + * Returns true if the browser is Safari, false if not. + * Safari makes trouble for downloading files. + * + * @return {Boolean} [description] + * @private + */ + fn._isSafari = function () { + return window.HTMLElement.toString().includes('Constructor'); + }; + + /** + * Helper function, a callback for download that deletes + * an invisible anchor element from the DOM once the file + * has been automatically downloaded. + * + * @private + */ + function destroyClickedElement(event) { + document.body.removeChild(event.target); } - return [filename, ext]; } -p5.prototype._checkFileExtension = _checkFileExtension; -/** - * Returns true if the browser is Safari, false if not. - * Safari makes trouble for downloading files. - * - * @return {Boolean} [description] - * @private - */ -p5.prototype._isSafari = function () { - return window.HTMLElement.toString().includes('Constructor'); -}; +export default files; -/** - * Helper function, a callback for download that deletes - * an invisible anchor element from the DOM once the file - * has been automatically downloaded. - * - * @private - */ -function destroyClickedElement(event) { - document.body.removeChild(event.target); +if(typeof p5 !== 'undefined'){ + files(p5, p5.prototype); } - -export default p5; diff --git a/src/io/index.js b/src/io/index.js new file mode 100644 index 0000000000..474d035cd9 --- /dev/null +++ b/src/io/index.js @@ -0,0 +1,11 @@ +import files from './files.js'; +import table from './p5.Table.js'; +import tableRow from './p5.TableRow.js'; +import xml from './p5.XML.js'; + +export default function(p5){ + p5.registerAddon(files); + p5.registerAddon(table); + p5.registerAddon(tableRow); + p5.registerAddon(xml); +} diff --git a/src/io/p5.Table.js b/src/io/p5.Table.js index 36ab056631..7dca60a9bd 100644 --- a/src/io/p5.Table.js +++ b/src/io/p5.Table.js @@ -4,1303 +4,1306 @@ * @requires core */ -import p5 from '../core/main'; - -/** - * Table Options - * Generic class for handling tabular data, typically from a - * CSV, TSV, or other sort of spreadsheet file. - * CSV files are - * - * comma separated values, often with the data in quotes. TSV - * files use tabs as separators, and usually don't bother with the - * quotes. - * File names should end with .csv if they're comma separated. - * A rough "spec" for CSV can be found - * here. - * To load files, use the loadTable method. - * To save tables to your computer, use the save method - * or the saveTable method. - * - * Possible options include: - *
      - *
    • csv - parse the table as comma-separated values - *
    • tsv - parse the table as tab-separated values - *
    • header - this table has a header (title) row - *
    - */ - -/** - * Table objects store data with multiple rows and columns, much - * like in a traditional spreadsheet. Tables can be generated from - * scratch, dynamically, or using data from an existing file. - * - * @class p5.Table - * @param {p5.TableRow[]} [rows] An array of p5.TableRow objects - */ -p5.Table = class Table { - constructor(rows) { - this.columns = []; - this.rows = []; - } +function table(p5, fn){ + /** + * Table Options + * Generic class for handling tabular data, typically from a + * CSV, TSV, or other sort of spreadsheet file. + * CSV files are + * + * comma separated values, often with the data in quotes. TSV + * files use tabs as separators, and usually don't bother with the + * quotes. + * File names should end with .csv if they're comma separated. + * A rough "spec" for CSV can be found + * here. + * To load files, use the loadTable method. + * To save tables to your computer, use the save method + * or the saveTable method. + * + * Possible options include: + *
      + *
    • csv - parse the table as comma-separated values + *
    • tsv - parse the table as tab-separated values + *
    • header - this table has a header (title) row + *
    + */ /** - * Use addRow() to add a new row of data to a p5.Table object. By default, - * an empty row is created. Typically, you would store a reference to - * the new row in a TableRow object (see newRow in the example above), - * and then set individual values using set(). - * - * If a p5.TableRow object is included as a parameter, then that row is - * duplicated and added to the table. - * - * @param {p5.TableRow} [row] row to be added to the table - * @return {p5.TableRow} the row that was added - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * //add a row - * let newRow = table.addRow(); - * newRow.setString('id', table.getRowCount() - 1); - * newRow.setString('species', 'Canis Lupus'); - * newRow.setString('name', 'Wolf'); - * - * //print the results - * for (let r = 0; r < table.getRowCount(); r++) - * for (let c = 0; c < table.getColumnCount(); c++) - * print(table.getString(r, c)); - * - * describe('no image displayed'); - * } - * - *
    - */ - addRow (row) { - // make sure it is a valid TableRow - const r = row || new p5.TableRow(); + * Table objects store data with multiple rows and columns, much + * like in a traditional spreadsheet. Tables can be generated from + * scratch, dynamically, or using data from an existing file. + * + * @class p5.Table + * @param {p5.TableRow[]} [rows] An array of p5.TableRow objects + */ + p5.Table = class Table { + constructor(rows) { + this.columns = []; + this.rows = []; + } + + /** + * Use addRow() to add a new row of data to a p5.Table object. By default, + * an empty row is created. Typically, you would store a reference to + * the new row in a TableRow object (see newRow in the example above), + * and then set individual values using set(). + * + * If a p5.TableRow object is included as a parameter, then that row is + * duplicated and added to the table. + * + * @param {p5.TableRow} [row] row to be added to the table + * @return {p5.TableRow} the row that was added + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * //add a row + * let newRow = table.addRow(); + * newRow.setString('id', table.getRowCount() - 1); + * newRow.setString('species', 'Canis Lupus'); + * newRow.setString('name', 'Wolf'); + * + * //print the results + * for (let r = 0; r < table.getRowCount(); r++) + * for (let c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * + * describe('no image displayed'); + * } + * + *
    + */ + addRow (row) { + // make sure it is a valid TableRow + const r = row || new p5.TableRow(); - if (typeof r.arr === 'undefined' || typeof r.obj === 'undefined') { - //r = new p5.prototype.TableRow(r); - throw new Error(`invalid TableRow: ${r}`); + if (typeof r.arr === 'undefined' || typeof r.obj === 'undefined') { + //r = new p5.prototype.TableRow(r); + throw new Error(`invalid TableRow: ${r}`); + } + r.table = this; + this.rows.push(r); + return r; } - r.table = this; - this.rows.push(r); - return r; - } - /** - * Removes a row from the table object. - * - * @param {Integer} id ID number of the row to remove - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * //remove the first row - * table.removeRow(0); - * - * //print the results - * for (let r = 0; r < table.getRowCount(); r++) - * for (let c = 0; c < table.getColumnCount(); c++) - * print(table.getString(r, c)); - * - * describe('no image displayed'); - * } - * - *
    - */ - removeRow (id) { - this.rows[id].table = null; // remove reference to table - const chunk = this.rows.splice(id + 1, this.rows.length); - this.rows.pop(); - this.rows = this.rows.concat(chunk); - } + /** + * Removes a row from the table object. + * + * @param {Integer} id ID number of the row to remove + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * //remove the first row + * table.removeRow(0); + * + * //print the results + * for (let r = 0; r < table.getRowCount(); r++) + * for (let c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * + * describe('no image displayed'); + * } + * + *
    + */ + removeRow (id) { + this.rows[id].table = null; // remove reference to table + const chunk = this.rows.splice(id + 1, this.rows.length); + this.rows.pop(); + this.rows = this.rows.concat(chunk); + } - /** - * Returns a reference to the specified p5.TableRow. The reference - * can then be used to get and set values of the selected row. - * - * @param {Integer} rowID ID number of the row to get - * @return {p5.TableRow} p5.TableRow object - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * let row = table.getRow(1); - * //print it column by column - * //note: a row is an object, not an array - * for (let c = 0; c < table.getColumnCount(); c++) { - * print(row.getString(c)); - * } - * - * describe('no image displayed'); - * } - * - *
    - */ - getRow (r) { - return this.rows[r]; - } + /** + * Returns a reference to the specified p5.TableRow. The reference + * can then be used to get and set values of the selected row. + * + * @param {Integer} rowID ID number of the row to get + * @return {p5.TableRow} p5.TableRow object + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * let row = table.getRow(1); + * //print it column by column + * //note: a row is an object, not an array + * for (let c = 0; c < table.getColumnCount(); c++) { + * print(row.getString(c)); + * } + * + * describe('no image displayed'); + * } + * + *
    + */ + getRow (r) { + return this.rows[r]; + } - /** - * Gets all rows from the table. Returns an array of p5.TableRows. - * - * @return {p5.TableRow[]} Array of p5.TableRows - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * let rows = table.getRows(); - * - * //warning: rows is an array of objects - * for (let r = 0; r < rows.length; r++) { - * rows[r].set('name', 'Unicorn'); - * } - * - * //print the results - * for (let r = 0; r < table.getRowCount(); r++) - * for (let c = 0; c < table.getColumnCount(); c++) - * print(table.getString(r, c)); - * - * describe('no image displayed'); - * } - * - *
    - */ - getRows () { - return this.rows; - } + /** + * Gets all rows from the table. Returns an array of p5.TableRows. + * + * @return {p5.TableRow[]} Array of p5.TableRows + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * let rows = table.getRows(); + * + * //warning: rows is an array of objects + * for (let r = 0; r < rows.length; r++) { + * rows[r].set('name', 'Unicorn'); + * } + * + * //print the results + * for (let r = 0; r < table.getRowCount(); r++) + * for (let c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * + * describe('no image displayed'); + * } + * + *
    + */ + getRows () { + return this.rows; + } - /** - * Finds the first row in the Table that contains the value - * provided, and returns a reference to that row. Even if - * multiple rows are possible matches, only the first matching - * row is returned. The column to search may be specified by - * either its ID or title. - * - * @param {String} value The value to match - * @param {Integer|String} column ID number or title of the - * column to search - * @return {p5.TableRow} - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * //find the animal named zebra - * let row = table.findRow('Zebra', 'name'); - * //find the corresponding species - * print(row.getString('species')); - * describe('no image displayed'); - * } - * - *
    - */ - findRow (value, column) { - // try the Object - if (typeof column === 'string') { - for (let i = 0; i < this.rows.length; i++) { - if (this.rows[i].obj[column] === value) { - return this.rows[i]; + /** + * Finds the first row in the Table that contains the value + * provided, and returns a reference to that row. Even if + * multiple rows are possible matches, only the first matching + * row is returned. The column to search may be specified by + * either its ID or title. + * + * @param {String} value The value to match + * @param {Integer|String} column ID number or title of the + * column to search + * @return {p5.TableRow} + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * //find the animal named zebra + * let row = table.findRow('Zebra', 'name'); + * //find the corresponding species + * print(row.getString('species')); + * describe('no image displayed'); + * } + * + *
    + */ + findRow (value, column) { + // try the Object + if (typeof column === 'string') { + for (let i = 0; i < this.rows.length; i++) { + if (this.rows[i].obj[column] === value) { + return this.rows[i]; + } } - } - } else { - // try the Array - for (let j = 0; j < this.rows.length; j++) { - if (this.rows[j].arr[column] === value) { - return this.rows[j]; + } else { + // try the Array + for (let j = 0; j < this.rows.length; j++) { + if (this.rows[j].arr[column] === value) { + return this.rows[j]; + } } } + // otherwise... + return null; } - // otherwise... - return null; - } - /** - * Finds the rows in the Table that contain the value - * provided, and returns references to those rows. Returns an - * Array, so for must be used to iterate through all the rows, - * as shown in the example above. The column to search may be - * specified by either its ID or title. - * - * @param {String} value The value to match - * @param {Integer|String} column ID number or title of the - * column to search - * @return {p5.TableRow[]} An Array of TableRow objects - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * //add another goat - * let newRow = table.addRow(); - * newRow.setString('id', table.getRowCount() - 1); - * newRow.setString('species', 'Scape Goat'); - * newRow.setString('name', 'Goat'); - * - * //find the rows containing animals named Goat - * let rows = table.findRows('Goat', 'name'); - * print(rows.length + ' Goats found'); - * describe('no image displayed'); - * } - * - *
    - */ - findRows (value, column) { - const ret = []; - if (typeof column === 'string') { - for (let i = 0; i < this.rows.length; i++) { - if (this.rows[i].obj[column] === value) { - ret.push(this.rows[i]); + /** + * Finds the rows in the Table that contain the value + * provided, and returns references to those rows. Returns an + * Array, so for must be used to iterate through all the rows, + * as shown in the example above. The column to search may be + * specified by either its ID or title. + * + * @param {String} value The value to match + * @param {Integer|String} column ID number or title of the + * column to search + * @return {p5.TableRow[]} An Array of TableRow objects + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * //add another goat + * let newRow = table.addRow(); + * newRow.setString('id', table.getRowCount() - 1); + * newRow.setString('species', 'Scape Goat'); + * newRow.setString('name', 'Goat'); + * + * //find the rows containing animals named Goat + * let rows = table.findRows('Goat', 'name'); + * print(rows.length + ' Goats found'); + * describe('no image displayed'); + * } + * + *
    + */ + findRows (value, column) { + const ret = []; + if (typeof column === 'string') { + for (let i = 0; i < this.rows.length; i++) { + if (this.rows[i].obj[column] === value) { + ret.push(this.rows[i]); + } } - } - } else { - // try the Array - for (let j = 0; j < this.rows.length; j++) { - if (this.rows[j].arr[column] === value) { - ret.push(this.rows[j]); + } else { + // try the Array + for (let j = 0; j < this.rows.length; j++) { + if (this.rows[j].arr[column] === value) { + ret.push(this.rows[j]); + } } } + return ret; } - return ret; - } - /** - * Finds the first row in the Table that matches the regular - * expression provided, and returns a reference to that row. - * Even if multiple rows are possible matches, only the first - * matching row is returned. The column to search may be - * specified by either its ID or title. - * - * @param {String|RegExp} regexp The regular expression to match - * @param {String|Integer} column The column ID (number) or - * title (string) - * @return {p5.TableRow} TableRow object - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * //Search using specified regex on a given column, return TableRow object - * let mammal = table.matchRow(new RegExp('ant'), 1); - * print(mammal.getString(1)); - * //Output "Panthera pardus" - * } - * - *
    - */ - matchRow (regexp, column) { - if (typeof column === 'number') { - for (let j = 0; j < this.rows.length; j++) { - if (this.rows[j].arr[column].match(regexp)) { - return this.rows[j]; + /** + * Finds the first row in the Table that matches the regular + * expression provided, and returns a reference to that row. + * Even if multiple rows are possible matches, only the first + * matching row is returned. The column to search may be + * specified by either its ID or title. + * + * @param {String|RegExp} regexp The regular expression to match + * @param {String|Integer} column The column ID (number) or + * title (string) + * @return {p5.TableRow} TableRow object + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * //Search using specified regex on a given column, return TableRow object + * let mammal = table.matchRow(new RegExp('ant'), 1); + * print(mammal.getString(1)); + * //Output "Panthera pardus" + * } + * + *
    + */ + matchRow (regexp, column) { + if (typeof column === 'number') { + for (let j = 0; j < this.rows.length; j++) { + if (this.rows[j].arr[column].match(regexp)) { + return this.rows[j]; + } } - } - } else { - for (let i = 0; i < this.rows.length; i++) { - if (this.rows[i].obj[column].match(regexp)) { - return this.rows[i]; + } else { + for (let i = 0; i < this.rows.length; i++) { + if (this.rows[i].obj[column].match(regexp)) { + return this.rows[i]; + } } } + return null; } - return null; - } - /** - * Finds the rows in the Table that match the regular expression provided, - * and returns references to those rows. Returns an array, so for must be - * used to iterate through all the rows, as shown in the example. The - * column to search may be specified by either its ID or title. - * - * @param {String} regexp The regular expression to match - * @param {String|Integer} [column] The column ID (number) or - * title (string) - * @return {p5.TableRow[]} An Array of TableRow objects - * @example - *
    - * - * let table; - * - * function setup() { - * table = new p5.Table(); - * - * table.addColumn('name'); - * table.addColumn('type'); - * - * let newRow = table.addRow(); - * newRow.setString('name', 'Lion'); - * newRow.setString('type', 'Mammal'); - * - * newRow = table.addRow(); - * newRow.setString('name', 'Snake'); - * newRow.setString('type', 'Reptile'); - * - * newRow = table.addRow(); - * newRow.setString('name', 'Mosquito'); - * newRow.setString('type', 'Insect'); - * - * newRow = table.addRow(); - * newRow.setString('name', 'Lizard'); - * newRow.setString('type', 'Reptile'); - * - * let rows = table.matchRows('R.*', 'type'); - * for (let i = 0; i < rows.length; i++) { - * print(rows[i].getString('name') + ': ' + rows[i].getString('type')); - * } - * } - * // Sketch prints: - * // Snake: Reptile - * // Lizard: Reptile - * - *
    - */ - matchRows (regexp, column) { - const ret = []; - if (typeof column === 'number') { - for (let j = 0; j < this.rows.length; j++) { - if (this.rows[j].arr[column].match(regexp)) { - ret.push(this.rows[j]); + /** + * Finds the rows in the Table that match the regular expression provided, + * and returns references to those rows. Returns an array, so for must be + * used to iterate through all the rows, as shown in the example. The + * column to search may be specified by either its ID or title. + * + * @param {String} regexp The regular expression to match + * @param {String|Integer} [column] The column ID (number) or + * title (string) + * @return {p5.TableRow[]} An Array of TableRow objects + * @example + *
    + * + * let table; + * + * function setup() { + * table = new p5.Table(); + * + * table.addColumn('name'); + * table.addColumn('type'); + * + * let newRow = table.addRow(); + * newRow.setString('name', 'Lion'); + * newRow.setString('type', 'Mammal'); + * + * newRow = table.addRow(); + * newRow.setString('name', 'Snake'); + * newRow.setString('type', 'Reptile'); + * + * newRow = table.addRow(); + * newRow.setString('name', 'Mosquito'); + * newRow.setString('type', 'Insect'); + * + * newRow = table.addRow(); + * newRow.setString('name', 'Lizard'); + * newRow.setString('type', 'Reptile'); + * + * let rows = table.matchRows('R.*', 'type'); + * for (let i = 0; i < rows.length; i++) { + * print(rows[i].getString('name') + ': ' + rows[i].getString('type')); + * } + * } + * // Sketch prints: + * // Snake: Reptile + * // Lizard: Reptile + * + *
    + */ + matchRows (regexp, column) { + const ret = []; + if (typeof column === 'number') { + for (let j = 0; j < this.rows.length; j++) { + if (this.rows[j].arr[column].match(regexp)) { + ret.push(this.rows[j]); + } } - } - } else { - for (let i = 0; i < this.rows.length; i++) { - if (this.rows[i].obj[column].match(regexp)) { - ret.push(this.rows[i]); + } else { + for (let i = 0; i < this.rows.length; i++) { + if (this.rows[i].obj[column].match(regexp)) { + ret.push(this.rows[i]); + } } } + return ret; } - return ret; - } - /** - * Retrieves all values in the specified column, and returns them - * as an array. The column may be specified by either its ID or title. - * - * @param {String|Number} column String or Number of the column to return - * @return {Array} Array of column values - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * //getColumn returns an array that can be printed directly - * print(table.getColumn('species')); - * //outputs ["Capra hircus", "Panthera pardus", "Equus zebra"] - * describe('no image displayed'); - * } - * - *
    - */ - getColumn (value) { - const ret = []; - if (typeof value === 'string') { - for (let i = 0; i < this.rows.length; i++) { - ret.push(this.rows[i].obj[value]); - } - } else { - for (let j = 0; j < this.rows.length; j++) { - ret.push(this.rows[j].arr[value]); + /** + * Retrieves all values in the specified column, and returns them + * as an array. The column may be specified by either its ID or title. + * + * @param {String|Number} column String or Number of the column to return + * @return {Array} Array of column values + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * //getColumn returns an array that can be printed directly + * print(table.getColumn('species')); + * //outputs ["Capra hircus", "Panthera pardus", "Equus zebra"] + * describe('no image displayed'); + * } + * + *
    + */ + getColumn (value) { + const ret = []; + if (typeof value === 'string') { + for (let i = 0; i < this.rows.length; i++) { + ret.push(this.rows[i].obj[value]); + } + } else { + for (let j = 0; j < this.rows.length; j++) { + ret.push(this.rows[j].arr[value]); + } } + return ret; } - return ret; - } - /** - * Removes all rows from a Table. While all rows are removed, - * columns and column titles are maintained. - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * table.clearRows(); - * print(table.getRowCount() + ' total rows in table'); - * print(table.getColumnCount() + ' total columns in table'); - * describe('no image displayed'); - * } - * - *
    - */ - clearRows () { - delete this.rows; - this.rows = []; - } - - /** - * Use addColumn() to add a new column to a Table object. - * Typically, you will want to specify a title, so the column - * may be easily referenced later by name. (If no title is - * specified, the new column's title will be null.) - * - * @param {String} [title] title of the given column - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * table.addColumn('carnivore'); - * table.set(0, 'carnivore', 'no'); - * table.set(1, 'carnivore', 'yes'); - * table.set(2, 'carnivore', 'no'); - * - * //print the results - * for (let r = 0; r < table.getRowCount(); r++) - * for (let c = 0; c < table.getColumnCount(); c++) - * print(table.getString(r, c)); - * - * describe('no image displayed'); - * } - * - *
    - */ - addColumn (title) { - const t = title || null; - this.columns.push(t); - } + /** + * Removes all rows from a Table. While all rows are removed, + * columns and column titles are maintained. + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * table.clearRows(); + * print(table.getRowCount() + ' total rows in table'); + * print(table.getColumnCount() + ' total columns in table'); + * describe('no image displayed'); + * } + * + *
    + */ + clearRows () { + delete this.rows; + this.rows = []; + } - /** - * Returns the total number of columns in a Table. - * - * @return {Integer} Number of columns in this table - * @example - *
    - * - * // given the cvs file "blobs.csv" in /assets directory - * // ID, Name, Flavor, Shape, Color - * // Blob1, Blobby, Sweet, Blob, Pink - * // Blob2, Saddy, Savory, Blob, Blue - * - * let table; - * - * function preload() { - * table = loadTable('assets/blobs.csv'); - * } - * - * function setup() { - * createCanvas(200, 100); - * textAlign(CENTER); - * background(255); - * } - * - * function draw() { - * let numOfColumn = table.getColumnCount(); - * text('There are ' + numOfColumn + ' columns in the table.', 100, 50); - * } - * - *
    - */ - getColumnCount () { - return this.columns.length; - } + /** + * Use addColumn() to add a new column to a Table object. + * Typically, you will want to specify a title, so the column + * may be easily referenced later by name. (If no title is + * specified, the new column's title will be null.) + * + * @param {String} [title] title of the given column + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * table.addColumn('carnivore'); + * table.set(0, 'carnivore', 'no'); + * table.set(1, 'carnivore', 'yes'); + * table.set(2, 'carnivore', 'no'); + * + * //print the results + * for (let r = 0; r < table.getRowCount(); r++) + * for (let c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * + * describe('no image displayed'); + * } + * + *
    + */ + addColumn (title) { + const t = title || null; + this.columns.push(t); + } - /** - * Returns the total number of rows in a Table. - * - * @return {Integer} Number of rows in this table - * @example - *
    - * - * // given the cvs file "blobs.csv" in /assets directory - * // - * // ID, Name, Flavor, Shape, Color - * // Blob1, Blobby, Sweet, Blob, Pink - * // Blob2, Saddy, Savory, Blob, Blue - * - * let table; - * - * function preload() { - * table = loadTable('assets/blobs.csv'); - * } - * - * function setup() { - * createCanvas(200, 100); - * textAlign(CENTER); - * background(255); - * } - * - * function draw() { - * text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50); - * } - * - *
    - */ - getRowCount () { - return this.rows.length; - } + /** + * Returns the total number of columns in a Table. + * + * @return {Integer} Number of columns in this table + * @example + *
    + * + * // given the cvs file "blobs.csv" in /assets directory + * // ID, Name, Flavor, Shape, Color + * // Blob1, Blobby, Sweet, Blob, Pink + * // Blob2, Saddy, Savory, Blob, Blue + * + * let table; + * + * function preload() { + * table = loadTable('assets/blobs.csv'); + * } + * + * function setup() { + * createCanvas(200, 100); + * textAlign(CENTER); + * background(255); + * } + * + * function draw() { + * let numOfColumn = table.getColumnCount(); + * text('There are ' + numOfColumn + ' columns in the table.', 100, 50); + * } + * + *
    + */ + getColumnCount () { + return this.columns.length; + } - /** - * Removes any of the specified characters (or "tokens"). - * - * If no column is specified, then the values in all columns and - * rows are processed. A specific column may be referenced by - * either its ID or title. - * - * @param {String} chars String listing characters to be removed - * @param {String|Integer} [column] Column ID (number) - * or name (string) - * - * @example - *
    - * function setup() { - * let table = new p5.Table(); - * - * table.addColumn('name'); - * table.addColumn('type'); - * - * let newRow = table.addRow(); - * newRow.setString('name', ' $Lion ,'); - * newRow.setString('type', ',,,Mammal'); - * - * newRow = table.addRow(); - * newRow.setString('name', '$Snake '); - * newRow.setString('type', ',,,Reptile'); - * - * table.removeTokens(',$ '); - * print(table.getArray()); - * } - * - * // prints: - * // 0 "Lion" "Mamal" - * // 1 "Snake" "Reptile" - *
    - */ - removeTokens (chars, column) { - const escape = s => s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); - const charArray = []; - for (let i = 0; i < chars.length; i++) { - charArray.push(escape(chars.charAt(i))); + /** + * Returns the total number of rows in a Table. + * + * @return {Integer} Number of rows in this table + * @example + *
    + * + * // given the cvs file "blobs.csv" in /assets directory + * // + * // ID, Name, Flavor, Shape, Color + * // Blob1, Blobby, Sweet, Blob, Pink + * // Blob2, Saddy, Savory, Blob, Blue + * + * let table; + * + * function preload() { + * table = loadTable('assets/blobs.csv'); + * } + * + * function setup() { + * createCanvas(200, 100); + * textAlign(CENTER); + * background(255); + * } + * + * function draw() { + * text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50); + * } + * + *
    + */ + getRowCount () { + return this.rows.length; } - const regex = new RegExp(charArray.join('|'), 'g'); - if (typeof column === 'undefined') { - for (let c = 0; c < this.columns.length; c++) { - for (let d = 0; d < this.rows.length; d++) { - let s = this.rows[d].arr[c]; - s = s.replace(regex, ''); - this.rows[d].arr[c] = s; - this.rows[d].obj[this.columns[c]] = s; - } + /** + * Removes any of the specified characters (or "tokens"). + * + * If no column is specified, then the values in all columns and + * rows are processed. A specific column may be referenced by + * either its ID or title. + * + * @param {String} chars String listing characters to be removed + * @param {String|Integer} [column] Column ID (number) + * or name (string) + * + * @example + *
    + * function setup() { + * let table = new p5.Table(); + * + * table.addColumn('name'); + * table.addColumn('type'); + * + * let newRow = table.addRow(); + * newRow.setString('name', ' $Lion ,'); + * newRow.setString('type', ',,,Mammal'); + * + * newRow = table.addRow(); + * newRow.setString('name', '$Snake '); + * newRow.setString('type', ',,,Reptile'); + * + * table.removeTokens(',$ '); + * print(table.getArray()); + * } + * + * // prints: + * // 0 "Lion" "Mamal" + * // 1 "Snake" "Reptile" + *
    + */ + removeTokens (chars, column) { + const escape = s => s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + const charArray = []; + for (let i = 0; i < chars.length; i++) { + charArray.push(escape(chars.charAt(i))); } - } else if (typeof column === 'string') { - for (let j = 0; j < this.rows.length; j++) { - let val = this.rows[j].obj[column]; - val = val.replace(regex, ''); - this.rows[j].obj[column] = val; - const pos = this.columns.indexOf(column); - this.rows[j].arr[pos] = val; - } - } else { - for (let k = 0; k < this.rows.length; k++) { - let str = this.rows[k].arr[column]; - str = str.replace(regex, ''); - this.rows[k].arr[column] = str; - this.rows[k].obj[this.columns[column]] = str; + const regex = new RegExp(charArray.join('|'), 'g'); + + if (typeof column === 'undefined') { + for (let c = 0; c < this.columns.length; c++) { + for (let d = 0; d < this.rows.length; d++) { + let s = this.rows[d].arr[c]; + s = s.replace(regex, ''); + this.rows[d].arr[c] = s; + this.rows[d].obj[this.columns[c]] = s; + } + } + } else if (typeof column === 'string') { + for (let j = 0; j < this.rows.length; j++) { + let val = this.rows[j].obj[column]; + val = val.replace(regex, ''); + this.rows[j].obj[column] = val; + const pos = this.columns.indexOf(column); + this.rows[j].arr[pos] = val; + } + } else { + for (let k = 0; k < this.rows.length; k++) { + let str = this.rows[k].arr[column]; + str = str.replace(regex, ''); + this.rows[k].arr[column] = str; + this.rows[k].obj[this.columns[column]] = str; + } } } - } - /** - * Trims leading and trailing whitespace, such as spaces and tabs, - * from String table values. If no column is specified, then the - * values in all columns and rows are trimmed. A specific column - * may be referenced by either its ID or title. - * - * @param {String|Integer} [column] Column ID (number) - * or name (string) - * @example - *
    - * function setup() { - * let table = new p5.Table(); - * - * table.addColumn('name'); - * table.addColumn('type'); - * - * let newRow = table.addRow(); - * newRow.setString('name', ' Lion ,'); - * newRow.setString('type', ' Mammal '); - * - * newRow = table.addRow(); - * newRow.setString('name', ' Snake '); - * newRow.setString('type', ' Reptile '); - * - * table.trim(); - * print(table.getArray()); - * } - * - * // prints: - * // 0 "Lion" "Mamal" - * // 1 "Snake" "Reptile" - *
    - */ - trim (column) { - const regex = new RegExp(' ', 'g'); + /** + * Trims leading and trailing whitespace, such as spaces and tabs, + * from String table values. If no column is specified, then the + * values in all columns and rows are trimmed. A specific column + * may be referenced by either its ID or title. + * + * @param {String|Integer} [column] Column ID (number) + * or name (string) + * @example + *
    + * function setup() { + * let table = new p5.Table(); + * + * table.addColumn('name'); + * table.addColumn('type'); + * + * let newRow = table.addRow(); + * newRow.setString('name', ' Lion ,'); + * newRow.setString('type', ' Mammal '); + * + * newRow = table.addRow(); + * newRow.setString('name', ' Snake '); + * newRow.setString('type', ' Reptile '); + * + * table.trim(); + * print(table.getArray()); + * } + * + * // prints: + * // 0 "Lion" "Mamal" + * // 1 "Snake" "Reptile" + *
    + */ + trim (column) { + const regex = new RegExp(' ', 'g'); - if (typeof column === 'undefined') { - for (let c = 0; c < this.columns.length; c++) { - for (let d = 0; d < this.rows.length; d++) { - let s = this.rows[d].arr[c]; - s = s.replace(regex, ''); - this.rows[d].arr[c] = s; - this.rows[d].obj[this.columns[c]] = s; + if (typeof column === 'undefined') { + for (let c = 0; c < this.columns.length; c++) { + for (let d = 0; d < this.rows.length; d++) { + let s = this.rows[d].arr[c]; + s = s.replace(regex, ''); + this.rows[d].arr[c] = s; + this.rows[d].obj[this.columns[c]] = s; + } + } + } else if (typeof column === 'string') { + for (let j = 0; j < this.rows.length; j++) { + let val = this.rows[j].obj[column]; + val = val.replace(regex, ''); + this.rows[j].obj[column] = val; + const pos = this.columns.indexOf(column); + this.rows[j].arr[pos] = val; + } + } else { + for (let k = 0; k < this.rows.length; k++) { + let str = this.rows[k].arr[column]; + str = str.replace(regex, ''); + this.rows[k].arr[column] = str; + this.rows[k].obj[this.columns[column]] = str; } - } - } else if (typeof column === 'string') { - for (let j = 0; j < this.rows.length; j++) { - let val = this.rows[j].obj[column]; - val = val.replace(regex, ''); - this.rows[j].obj[column] = val; - const pos = this.columns.indexOf(column); - this.rows[j].arr[pos] = val; - } - } else { - for (let k = 0; k < this.rows.length; k++) { - let str = this.rows[k].arr[column]; - str = str.replace(regex, ''); - this.rows[k].arr[column] = str; - this.rows[k].obj[this.columns[column]] = str; } } - } - /** - * Use removeColumn() to remove an existing column from a Table - * object. The column to be removed may be identified by either - * its title (a String) or its index value (an int). - * removeColumn(0) would remove the first column, removeColumn(1) - * would remove the second column, and so on. - * - * @param {String|Integer} column columnName (string) or ID (number) - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * table.removeColumn('id'); - * print(table.getColumnCount()); - * describe('no image displayed'); - * } - * - *
    - */ - removeColumn (c) { - let cString; - let cNumber; - if (typeof c === 'string') { - // find the position of c in the columns - cString = c; - cNumber = this.columns.indexOf(c); - } else { - cNumber = c; - cString = this.columns[c]; - } + /** + * Use removeColumn() to remove an existing column from a Table + * object. The column to be removed may be identified by either + * its title (a String) or its index value (an int). + * removeColumn(0) would remove the first column, removeColumn(1) + * would remove the second column, and so on. + * + * @param {String|Integer} column columnName (string) or ID (number) + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * table.removeColumn('id'); + * print(table.getColumnCount()); + * describe('no image displayed'); + * } + * + *
    + */ + removeColumn (c) { + let cString; + let cNumber; + if (typeof c === 'string') { + // find the position of c in the columns + cString = c; + cNumber = this.columns.indexOf(c); + } else { + cNumber = c; + cString = this.columns[c]; + } - const chunk = this.columns.splice(cNumber + 1, this.columns.length); - this.columns.pop(); - this.columns = this.columns.concat(chunk); + const chunk = this.columns.splice(cNumber + 1, this.columns.length); + this.columns.pop(); + this.columns = this.columns.concat(chunk); - for (let i = 0; i < this.rows.length; i++) { - const tempR = this.rows[i].arr; - const chip = tempR.splice(cNumber + 1, tempR.length); - tempR.pop(); - this.rows[i].arr = tempR.concat(chip); - delete this.rows[i].obj[cString]; + for (let i = 0; i < this.rows.length; i++) { + const tempR = this.rows[i].arr; + const chip = tempR.splice(cNumber + 1, tempR.length); + tempR.pop(); + this.rows[i].arr = tempR.concat(chip); + delete this.rows[i].obj[cString]; + } } - } - /** - * Stores a value in the Table's specified row and column. - * The row is specified by its ID, while the column may be specified - * by either its ID or title. - * - * @param {Integer} row row ID - * @param {String|Integer} column column ID (Number) - * or title (String) - * @param {String|Number} value value to assign - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * table.set(0, 'species', 'Canis Lupus'); - * table.set(0, 'name', 'Wolf'); - * - * //print the results - * for (let r = 0; r < table.getRowCount(); r++) - * for (let c = 0; c < table.getColumnCount(); c++) - * print(table.getString(r, c)); - * - * describe('no image displayed'); - * } - * - *
    - */ - set (row, column, value) { - this.rows[row].set(column, value); - } - - /** - * Stores a Float value in the Table's specified row and column. - * The row is specified by its ID, while the column may be specified - * by either its ID or title. - * - * @param {Integer} row row ID - * @param {String|Integer} column column ID (Number) - * or title (String) - * @param {Number} value value to assign - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * table.setNum(1, 'id', 1); - * - * print(table.getColumn(0)); - * //["0", 1, "2"] - * - * describe('no image displayed'); - * } - * - *
    - */ - setNum (row, column, value) { - this.rows[row].setNum(column, value); - } + /** + * Stores a value in the Table's specified row and column. + * The row is specified by its ID, while the column may be specified + * by either its ID or title. + * + * @param {Integer} row row ID + * @param {String|Integer} column column ID (Number) + * or title (String) + * @param {String|Number} value value to assign + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * table.set(0, 'species', 'Canis Lupus'); + * table.set(0, 'name', 'Wolf'); + * + * //print the results + * for (let r = 0; r < table.getRowCount(); r++) + * for (let c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * + * describe('no image displayed'); + * } + * + *
    + */ + set (row, column, value) { + this.rows[row].set(column, value); + } - /** - * Stores a String value in the Table's specified row and column. - * The row is specified by its ID, while the column may be specified - * by either its ID or title. - * - * @param {Integer} row row ID - * @param {String|Integer} column column ID (Number) - * or title (String) - * @param {String} value value to assign - * @example - *
    - * // Given the CSV file "mammals.csv" in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * //add a row - * let newRow = table.addRow(); - * newRow.setString('id', table.getRowCount() - 1); - * newRow.setString('species', 'Canis Lupus'); - * newRow.setString('name', 'Wolf'); - * - * print(table.getArray()); - * - * describe('no image displayed'); - * } - *
    - */ - setString (row, column, value) { - this.rows[row].setString(column, value); - } + /** + * Stores a Float value in the Table's specified row and column. + * The row is specified by its ID, while the column may be specified + * by either its ID or title. + * + * @param {Integer} row row ID + * @param {String|Integer} column column ID (Number) + * or title (String) + * @param {Number} value value to assign + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * table.setNum(1, 'id', 1); + * + * print(table.getColumn(0)); + * //["0", 1, "2"] + * + * describe('no image displayed'); + * } + * + *
    + */ + setNum (row, column, value) { + this.rows[row].setNum(column, value); + } - /** - * Retrieves a value from the Table's specified row and column. - * The row is specified by its ID, while the column may be specified by - * either its ID or title. - * - * @param {Integer} row row ID - * @param {String|Integer} column columnName (string) or - * ID (number) - * @return {String|Number} - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * print(table.get(0, 1)); - * //Capra hircus - * print(table.get(0, 'species')); - * //Capra hircus - * describe('no image displayed'); - * } - * - *
    - */ - get (row, column) { - return this.rows[row].get(column); - } + /** + * Stores a String value in the Table's specified row and column. + * The row is specified by its ID, while the column may be specified + * by either its ID or title. + * + * @param {Integer} row row ID + * @param {String|Integer} column column ID (Number) + * or title (String) + * @param {String} value value to assign + * @example + *
    + * // Given the CSV file "mammals.csv" in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * //add a row + * let newRow = table.addRow(); + * newRow.setString('id', table.getRowCount() - 1); + * newRow.setString('species', 'Canis Lupus'); + * newRow.setString('name', 'Wolf'); + * + * print(table.getArray()); + * + * describe('no image displayed'); + * } + *
    + */ + setString (row, column, value) { + this.rows[row].setString(column, value); + } - /** - * Retrieves a Float value from the Table's specified row and column. - * The row is specified by its ID, while the column may be specified by - * either its ID or title. - * - * @param {Integer} row row ID - * @param {String|Integer} column columnName (string) or - * ID (number) - * @return {Number} - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * print(table.getNum(1, 0) + 100); - * //id 1 + 100 = 101 - * describe('no image displayed'); - * } - * - *
    - */ - getNum (row, column) { - return this.rows[row].getNum(column); - } + /** + * Retrieves a value from the Table's specified row and column. + * The row is specified by its ID, while the column may be specified by + * either its ID or title. + * + * @param {Integer} row row ID + * @param {String|Integer} column columnName (string) or + * ID (number) + * @return {String|Number} + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * print(table.get(0, 1)); + * //Capra hircus + * print(table.get(0, 'species')); + * //Capra hircus + * describe('no image displayed'); + * } + * + *
    + */ + get (row, column) { + return this.rows[row].get(column); + } - /** - * Retrieves a String value from the Table's specified row and column. - * The row is specified by its ID, while the column may be specified by - * either its ID or title. - * - * @param {Integer} row row ID - * @param {String|Integer} column columnName (string) or - * ID (number) - * @return {String} - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * // table is comma separated value "CSV" - * // and has specifiying header for column labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * print(table.getString(0, 0)); // 0 - * print(table.getString(0, 1)); // Capra hircus - * print(table.getString(0, 2)); // Goat - * print(table.getString(1, 0)); // 1 - * print(table.getString(1, 1)); // Panthera pardus - * print(table.getString(1, 2)); // Leopard - * print(table.getString(2, 0)); // 2 - * print(table.getString(2, 1)); // Equus zebra - * print(table.getString(2, 2)); // Zebra - * describe('no image displayed'); - * } - * - *
    - */ + /** + * Retrieves a Float value from the Table's specified row and column. + * The row is specified by its ID, while the column may be specified by + * either its ID or title. + * + * @param {Integer} row row ID + * @param {String|Integer} column columnName (string) or + * ID (number) + * @return {Number} + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * print(table.getNum(1, 0) + 100); + * //id 1 + 100 = 101 + * describe('no image displayed'); + * } + * + *
    + */ + getNum (row, column) { + return this.rows[row].getNum(column); + } - getString (row, column) { - return this.rows[row].getString(column); - } + /** + * Retrieves a String value from the Table's specified row and column. + * The row is specified by its ID, while the column may be specified by + * either its ID or title. + * + * @param {Integer} row row ID + * @param {String|Integer} column columnName (string) or + * ID (number) + * @return {String} + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * // table is comma separated value "CSV" + * // and has specifiying header for column labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * print(table.getString(0, 0)); // 0 + * print(table.getString(0, 1)); // Capra hircus + * print(table.getString(0, 2)); // Goat + * print(table.getString(1, 0)); // 1 + * print(table.getString(1, 1)); // Panthera pardus + * print(table.getString(1, 2)); // Leopard + * print(table.getString(2, 0)); // 2 + * print(table.getString(2, 1)); // Equus zebra + * print(table.getString(2, 2)); // Zebra + * describe('no image displayed'); + * } + * + *
    + */ + getString (row, column) { + return this.rows[row].getString(column); + } - /** - * Retrieves all table data and returns as an object. If a column name is - * passed in, each row object will be stored with that attribute as its - * title. - * - * @param {String} [headerColumn] Name of the column which should be used to - * title each row object (optional) - * @return {Object} - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * let tableObject = table.getObject(); - * - * print(tableObject); - * //outputs an object - * - * describe('no image displayed'); - * } - * - *
    - */ - getObject (headerColumn) { - const tableObject = {}; - let obj, cPos, index; + /** + * Retrieves all table data and returns as an object. If a column name is + * passed in, each row object will be stored with that attribute as its + * title. + * + * @param {String} [headerColumn] Name of the column which should be used to + * title each row object (optional) + * @return {Object} + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * let tableObject = table.getObject(); + * + * print(tableObject); + * //outputs an object + * + * describe('no image displayed'); + * } + * + *
    + */ + getObject (headerColumn) { + const tableObject = {}; + let obj, cPos, index; - for (let i = 0; i < this.rows.length; i++) { - obj = this.rows[i].obj; + for (let i = 0; i < this.rows.length; i++) { + obj = this.rows[i].obj; - if (typeof headerColumn === 'string') { - cPos = this.columns.indexOf(headerColumn); // index of columnID - if (cPos >= 0) { - index = obj[headerColumn]; - tableObject[index] = obj; + if (typeof headerColumn === 'string') { + cPos = this.columns.indexOf(headerColumn); // index of columnID + if (cPos >= 0) { + index = obj[headerColumn]; + tableObject[index] = obj; + } else { + throw new Error(`This table has no column named "${headerColumn}"`); + } } else { - throw new Error(`This table has no column named "${headerColumn}"`); + tableObject[i] = this.rows[i].obj; } - } else { - tableObject[i] = this.rows[i].obj; } + return tableObject; } - return tableObject; - } - /** - * Retrieves all table data and returns it as a multidimensional array. - * - * @return {Array} - * - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leoperd - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * // table is comma separated value "CSV" - * // and has specifiying header for column labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * let tableArray = table.getArray(); - * for (let i = 0; i < tableArray.length; i++) { - * print(tableArray[i]); - * } - * describe('no image displayed'); - * } - * - *
    - */ - getArray () { - const tableArray = []; - for (let i = 0; i < this.rows.length; i++) { - tableArray.push(this.rows[i].arr); + /** + * Retrieves all table data and returns it as a multidimensional array. + * + * @return {Array} + * + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leoperd + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * // table is comma separated value "CSV" + * // and has specifiying header for column labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * let tableArray = table.getArray(); + * for (let i = 0; i < tableArray.length; i++) { + * print(tableArray[i]); + * } + * describe('no image displayed'); + * } + * + *
    + */ + getArray () { + const tableArray = []; + for (let i = 0; i < this.rows.length; i++) { + tableArray.push(this.rows[i].arr); + } + return tableArray; } - return tableArray; - } -}; + }; -/** - * An array containing the names of the columns in the table, if the "header" the table is - * loaded with the "header" parameter. - * @type {String[]} - * @property columns - * @for p5.Table - * @name columns - * @example - *
    - * - * // Given the CSV file "mammals.csv" - * // in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * //print the column names - * for (let c = 0; c < table.getColumnCount(); c++) { - * print('column ' + c + ' is named ' + table.columns[c]); - * } - * } - * - *
    - */ + /** + * An array containing the names of the columns in the table, if the "header" the table is + * loaded with the "header" parameter. + * @type {String[]} + * @property columns + * @for p5.Table + * @name columns + * @example + *
    + * + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * //print the column names + * for (let c = 0; c < table.getColumnCount(); c++) { + * print('column ' + c + ' is named ' + table.columns[c]); + * } + * } + * + *
    + */ -/** - * An array containing the p5.TableRow objects that make up the - * rows of the table. The same result as calling getRows() - * @type {p5.TableRow[]} - * @property rows - * @for p5.Table - * @name rows -*/ + /** + * An array containing the p5.TableRow objects that make up the + * rows of the table. The same result as calling getRows() + * @type {p5.TableRow[]} + * @property rows + * @for p5.Table + * @name rows + */ +} + +export default table; -export default p5; +if(typeof p5 !== 'undefined'){ + table(p5, p5.prototype); +} diff --git a/src/io/p5.TableRow.js b/src/io/p5.TableRow.js index 652c8521d6..eb171e2766 100644 --- a/src/io/p5.TableRow.js +++ b/src/io/p5.TableRow.js @@ -4,332 +4,337 @@ * @requires core */ -import p5 from '../core/main'; +function tableRow(p5, fn){ + /** + * A TableRow object represents a single row of data values, + * stored in columns, from a table. + * + * A Table Row contains both an ordered array, and an unordered + * JSON object. + * + * @class p5.TableRow + * @constructor + * @param {String} [str] optional: populate the row with a + * string of values, separated by the + * separator + * @param {String} [separator] comma separated values (csv) by default + */ + p5.TableRow = class { + constructor(str, separator){ + let arr = []; + if (str) { + separator = separator || ','; + arr = str.split(separator); + } -/** - * A TableRow object represents a single row of data values, - * stored in columns, from a table. - * - * A Table Row contains both an ordered array, and an unordered - * JSON object. - * - * @class p5.TableRow - * @constructor - * @param {String} [str] optional: populate the row with a - * string of values, separated by the - * separator - * @param {String} [separator] comma separated values (csv) by default - */ -p5.TableRow = class { - constructor(str, separator){ - let arr = []; - if (str) { - separator = separator || ','; - arr = str.split(separator); + this.arr = arr; + this.obj = Object.fromEntries(arr.entries()); + this.table = null; } - this.arr = arr; - this.obj = Object.fromEntries(arr.entries()); - this.table = null; - } - - /** - * Stores a value in the TableRow's specified column. - * The column may be specified by either its ID or title. - * - * @method set - * @param {String|Integer} column Column ID (Number) - * or Title (String) - * @param {String|Number} value The value to be stored - * - * @example - *
    - * // Given the CSV file "mammals.csv" in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * let rows = table.getRows(); - * for (let r = 0; r < rows.length; r++) { - * rows[r].set('name', 'Unicorn'); - * } - * - * //print the results - * print(table.getArray()); - * - * describe('no image displayed'); - * } - *
    - */ - set(column, value) { - // if typeof column is string, use .obj - if (typeof column === 'string') { - const cPos = this.table.columns.indexOf(column); // index of columnID - if (cPos >= 0) { - this.obj[column] = value; - this.arr[cPos] = value; - } else { - throw new Error(`This table has no column named "${column}"`); - } - } else { - // if typeof column is number, use .arr - if (column < this.table.columns.length) { - this.arr[column] = value; - const cTitle = this.table.columns[column]; - this.obj[cTitle] = value; + /** + * Stores a value in the TableRow's specified column. + * The column may be specified by either its ID or title. + * + * @method set + * @param {String|Integer} column Column ID (Number) + * or Title (String) + * @param {String|Number} value The value to be stored + * + * @example + *
    + * // Given the CSV file "mammals.csv" in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * let rows = table.getRows(); + * for (let r = 0; r < rows.length; r++) { + * rows[r].set('name', 'Unicorn'); + * } + * + * //print the results + * print(table.getArray()); + * + * describe('no image displayed'); + * } + *
    + */ + set(column, value) { + // if typeof column is string, use .obj + if (typeof column === 'string') { + const cPos = this.table.columns.indexOf(column); // index of columnID + if (cPos >= 0) { + this.obj[column] = value; + this.arr[cPos] = value; + } else { + throw new Error(`This table has no column named "${column}"`); + } } else { - throw new Error(`Column #${column} is out of the range of this table`); + // if typeof column is number, use .arr + if (column < this.table.columns.length) { + this.arr[column] = value; + const cTitle = this.table.columns[column]; + this.obj[cTitle] = value; + } else { + throw new Error(`Column #${column} is out of the range of this table`); + } } } - } - - /** - * Stores a Float value in the TableRow's specified column. - * The column may be specified by either its ID or title. - * - * @method setNum - * @param {String|Integer} column Column ID (Number) - * or Title (String) - * @param {Number|String} value The value to be stored - * as a Float - * @example - *
    - * // Given the CSV file "mammals.csv" in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * let rows = table.getRows(); - * for (let r = 0; r < rows.length; r++) { - * rows[r].setNum('id', r + 10); - * } - * - * print(table.getArray()); - * - * describe('no image displayed'); - * } - *
    - */ - setNum(column, value) { - const floatVal = parseFloat(value); - this.set(column, floatVal); - } - /** - * Stores a String value in the TableRow's specified column. - * The column may be specified by either its ID or title. - * - * @method setString - * @param {String|Integer} column Column ID (Number) - * or Title (String) - * @param {String|Number|Boolean|Object} value The value to be stored - * as a String - * @example - *
    - * // Given the CSV file "mammals.csv" in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * let rows = table.getRows(); - * for (let r = 0; r < rows.length; r++) { - * let name = rows[r].getString('name'); - * rows[r].setString('name', 'A ' + name + ' named George'); - * } - * - * print(table.getArray()); - * - * describe('no image displayed'); - * } - *
    - */ - setString(column, value) { - const stringVal = value.toString(); - this.set(column, stringVal); - } + /** + * Stores a Float value in the TableRow's specified column. + * The column may be specified by either its ID or title. + * + * @method setNum + * @param {String|Integer} column Column ID (Number) + * or Title (String) + * @param {Number|String} value The value to be stored + * as a Float + * @example + *
    + * // Given the CSV file "mammals.csv" in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * let rows = table.getRows(); + * for (let r = 0; r < rows.length; r++) { + * rows[r].setNum('id', r + 10); + * } + * + * print(table.getArray()); + * + * describe('no image displayed'); + * } + *
    + */ + setNum(column, value) { + const floatVal = parseFloat(value); + this.set(column, floatVal); + } - /** - * Retrieves a value from the TableRow's specified column. - * The column may be specified by either its ID or title. - * - * @method get - * @param {String|Integer} column columnName (string) or - * ID (number) - * @return {String|Number} - * - * @example - *
    - * // Given the CSV file "mammals.csv" in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * let names = []; - * let rows = table.getRows(); - * for (let r = 0; r < rows.length; r++) { - * names.push(rows[r].get('name')); - * } - * - * print(names); - * - * describe('no image displayed'); - * } - *
    - */ - get(column) { - if (typeof column === 'string') { - return this.obj[column]; - } else { - return this.arr[column]; + /** + * Stores a String value in the TableRow's specified column. + * The column may be specified by either its ID or title. + * + * @method setString + * @param {String|Integer} column Column ID (Number) + * or Title (String) + * @param {String|Number|Boolean|Object} value The value to be stored + * as a String + * @example + *
    + * // Given the CSV file "mammals.csv" in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * let rows = table.getRows(); + * for (let r = 0; r < rows.length; r++) { + * let name = rows[r].getString('name'); + * rows[r].setString('name', 'A ' + name + ' named George'); + * } + * + * print(table.getArray()); + * + * describe('no image displayed'); + * } + *
    + */ + setString(column, value) { + const stringVal = value.toString(); + this.set(column, stringVal); } - } - /** - * Retrieves a Float value from the TableRow's specified - * column. The column may be specified by either its ID or - * title. - * - * @method getNum - * @param {String|Integer} column columnName (string) or - * ID (number) - * @return {Number} Float Floating point number - * @example - *
    - * // Given the CSV file "mammals.csv" in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * let rows = table.getRows(); - * let minId = Infinity; - * let maxId = -Infinity; - * for (let r = 0; r < rows.length; r++) { - * let id = rows[r].getNum('id'); - * minId = min(minId, id); - * maxId = min(maxId, id); - * } - * print('minimum id = ' + minId + ', maximum id = ' + maxId); - * describe('no image displayed'); - * } - *
    - */ - getNum(column) { - let ret; - if (typeof column === 'string') { - ret = parseFloat(this.obj[column]); - } else { - ret = parseFloat(this.arr[column]); + /** + * Retrieves a value from the TableRow's specified column. + * The column may be specified by either its ID or title. + * + * @method get + * @param {String|Integer} column columnName (string) or + * ID (number) + * @return {String|Number} + * + * @example + *
    + * // Given the CSV file "mammals.csv" in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * let names = []; + * let rows = table.getRows(); + * for (let r = 0; r < rows.length; r++) { + * names.push(rows[r].get('name')); + * } + * + * print(names); + * + * describe('no image displayed'); + * } + *
    + */ + get(column) { + if (typeof column === 'string') { + return this.obj[column]; + } else { + return this.arr[column]; + } } - if (ret.toString() === 'NaN') { - throw `Error: ${this.obj[column]} is NaN (Not a Number)`; + /** + * Retrieves a Float value from the TableRow's specified + * column. The column may be specified by either its ID or + * title. + * + * @method getNum + * @param {String|Integer} column columnName (string) or + * ID (number) + * @return {Number} Float Floating point number + * @example + *
    + * // Given the CSV file "mammals.csv" in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * let rows = table.getRows(); + * let minId = Infinity; + * let maxId = -Infinity; + * for (let r = 0; r < rows.length; r++) { + * let id = rows[r].getNum('id'); + * minId = min(minId, id); + * maxId = min(maxId, id); + * } + * print('minimum id = ' + minId + ', maximum id = ' + maxId); + * describe('no image displayed'); + * } + *
    + */ + getNum(column) { + let ret; + if (typeof column === 'string') { + ret = parseFloat(this.obj[column]); + } else { + ret = parseFloat(this.arr[column]); + } + + if (ret.toString() === 'NaN') { + throw `Error: ${this.obj[column]} is NaN (Not a Number)`; + } + return ret; } - return ret; - } - /** - * Retrieves an String value from the TableRow's specified - * column. The column may be specified by either its ID or - * title. - * - * @method getString - * @param {String|Integer} column columnName (string) or - * ID (number) - * @return {String} String - * @example - *
    - * // Given the CSV file "mammals.csv" in the project's "assets" folder: - * // - * // id,species,name - * // 0,Capra hircus,Goat - * // 1,Panthera pardus,Leopard - * // 2,Equus zebra,Zebra - * - * let table; - * - * function preload() { - * //my table is comma separated value "csv" - * //and has a header specifying the columns labels - * table = loadTable('assets/mammals.csv', 'csv', 'header'); - * } - * - * function setup() { - * let rows = table.getRows(); - * let longest = ''; - * for (let r = 0; r < rows.length; r++) { - * let species = rows[r].getString('species'); - * if (longest.length < species.length) { - * longest = species; - * } - * } - * - * print('longest: ' + longest); - * - * describe('no image displayed'); - * } - *
    - */ - getString(column) { - if (typeof column === 'string') { - return this.obj[column].toString(); - } else { - return this.arr[column].toString(); + /** + * Retrieves an String value from the TableRow's specified + * column. The column may be specified by either its ID or + * title. + * + * @method getString + * @param {String|Integer} column columnName (string) or + * ID (number) + * @return {String} String + * @example + *
    + * // Given the CSV file "mammals.csv" in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * let table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable('assets/mammals.csv', 'csv', 'header'); + * } + * + * function setup() { + * let rows = table.getRows(); + * let longest = ''; + * for (let r = 0; r < rows.length; r++) { + * let species = rows[r].getString('species'); + * if (longest.length < species.length) { + * longest = species; + * } + * } + * + * print('longest: ' + longest); + * + * describe('no image displayed'); + * } + *
    + */ + getString(column) { + if (typeof column === 'string') { + return this.obj[column].toString(); + } else { + return this.arr[column].toString(); + } } - } -}; -export default p5; + }; +} + +export default tableRow; + +if(typeof p5 !== 'undefined'){ + tableRow(p5, p5.prototype); +} diff --git a/src/io/p5.XML.js b/src/io/p5.XML.js index a3666c6165..2afd665bb3 100644 --- a/src/io/p5.XML.js +++ b/src/io/p5.XML.js @@ -4,1336 +4,1340 @@ * @requires core */ -import p5 from '../core/main'; - -/** - * A class to describe an XML object. - * - * Each `p5.XML` object provides an easy way to interact with XML data. - * Extensible Markup Language - * (XML) - * is a standard format for sending data between applications. Like HTML, the - * XML format is based on tags and attributes, as in - * `<time units="s">1234</time>`. - * - * Note: Use loadXML() to load external XML files. - * - * @class p5.XML - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get an array with all mammal tags. - * let mammals = myXML.getChildren('mammal'); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Iterate over the mammals array. - * for (let i = 0; i < mammals.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 25; - * - * // Get the mammal's common name. - * let name = mammals[i].getContent(); - * - * // Display the mammal's name. - * text(name, 20, y); - * } - * - * describe( - * 'The words "Goat", "Leopard", and "Zebra" written on three separate lines. The text is black on a gray background.' - * ); - * } - * - *
    - */ -p5.XML = class { - constructor(DOM){ - if (!DOM) { - const xmlDoc = document.implementation.createDocument(null, 'doc'); - this.DOM = xmlDoc.createElement('root'); - } else { - this.DOM = DOM; +function xml(p5, fn){ + /** + * A class to describe an XML object. + * + * Each `p5.XML` object provides an easy way to interact with XML data. + * Extensible Markup Language + * (XML) + * is a standard format for sending data between applications. Like HTML, the + * XML format is based on tags and attributes, as in + * `<time units="s">1234</time>`. + * + * Note: Use loadXML() to load external XML files. + * + * @class p5.XML + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get an array with all mammal tags. + * let mammals = myXML.getChildren('mammal'); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Iterate over the mammals array. + * for (let i = 0; i < mammals.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 25; + * + * // Get the mammal's common name. + * let name = mammals[i].getContent(); + * + * // Display the mammal's name. + * text(name, 20, y); + * } + * + * describe( + * 'The words "Goat", "Leopard", and "Zebra" written on three separate lines. The text is black on a gray background.' + * ); + * } + * + *
    + */ + p5.XML = class { + constructor(DOM){ + if (!DOM) { + const xmlDoc = document.implementation.createDocument(null, 'doc'); + this.DOM = xmlDoc.createElement('root'); + } else { + this.DOM = DOM; + } } - } - /** - * Returns the element's parent element as a new p5.XML - * object. - * - * @return {p5.XML} parent element. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get an array with all mammal elements. - * let mammals = myXML.getChildren('mammal'); - * - * // Get the first mammal element. - * let firstMammal = mammals[0]; - * - * // Get the parent element. - * let parent = firstMammal.getParent(); - * - * // Get the parent element's name. - * let name = parent.getName(); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display the parent element's name. - * text(name, 50, 50); - * - * describe('The word "animals" written in black on a gray background.'); - * } - * - *
    - */ - getParent() { - return new p5.XML(this.DOM.parentElement); - } + /** + * Returns the element's parent element as a new p5.XML + * object. + * + * @return {p5.XML} parent element. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get an array with all mammal elements. + * let mammals = myXML.getChildren('mammal'); + * + * // Get the first mammal element. + * let firstMammal = mammals[0]; + * + * // Get the parent element. + * let parent = firstMammal.getParent(); + * + * // Get the parent element's name. + * let name = parent.getName(); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display the parent element's name. + * text(name, 50, 50); + * + * describe('The word "animals" written in black on a gray background.'); + * } + * + *
    + */ + getParent() { + return new p5.XML(this.DOM.parentElement); + } - /** - * Returns the element's name as a `String`. - * - * An XML element's name is given by its tag. For example, the element - * `<language>JavaScript</language>` has the name `language`. - * - * @return {String} name of the element. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get an array with all mammal elements. - * let mammals = myXML.getChildren('mammal'); - * - * // Get the first mammal element. - * let firstMammal = mammals[0]; - * - * // Get the mammal element's name. - * let name = firstMammal.getName(); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display the element's name. - * text(name, 50, 50); - * - * describe('The word "mammal" written in black on a gray background.'); - * } - * - *
    - */ - getName() { - return this.DOM.tagName; - } + /** + * Returns the element's name as a `String`. + * + * An XML element's name is given by its tag. For example, the element + * `<language>JavaScript</language>` has the name `language`. + * + * @return {String} name of the element. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get an array with all mammal elements. + * let mammals = myXML.getChildren('mammal'); + * + * // Get the first mammal element. + * let firstMammal = mammals[0]; + * + * // Get the mammal element's name. + * let name = firstMammal.getName(); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display the element's name. + * text(name, 50, 50); + * + * describe('The word "mammal" written in black on a gray background.'); + * } + * + *
    + */ + getName() { + return this.DOM.tagName; + } - /** - * Sets the element's tag name. - * - * An XML element's name is given by its tag. For example, the element - * `<language>JavaScript</language>` has the name `language`. - * - * The parameter, `name`, is the element's new name as a string. For example, - * calling `myXML.setName('planet')` will make the element's new tag name - * `<planet></planet>`. - * - * @param {String} name new tag name of the element. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the element's original name. - * let oldName = myXML.getName(); - * - * // Set the element's name. - * myXML.setName('monsters'); - * - * // Get the element's new name. - * let newName = myXML.getName(); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display the element's names. - * text(oldName, 50, 33); - * text(newName, 50, 67); - * - * describe( - * 'The words "animals" and "monsters" written on separate lines. The text is black on a gray background.' - * ); - * } - *
    - */ - setName(name) { - const content = this.DOM.innerHTML; - const attributes = this.DOM.attributes; - const xmlDoc = document.implementation.createDocument(null, 'default'); - const newDOM = xmlDoc.createElement(name); - newDOM.innerHTML = content; - for (let i = 0; i < attributes.length; i++) { - newDOM.setAttribute(attributes[i].nodeName, attributes[i].nodeValue); + /** + * Sets the element's tag name. + * + * An XML element's name is given by its tag. For example, the element + * `<language>JavaScript</language>` has the name `language`. + * + * The parameter, `name`, is the element's new name as a string. For example, + * calling `myXML.setName('planet')` will make the element's new tag name + * `<planet></planet>`. + * + * @param {String} name new tag name of the element. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the element's original name. + * let oldName = myXML.getName(); + * + * // Set the element's name. + * myXML.setName('monsters'); + * + * // Get the element's new name. + * let newName = myXML.getName(); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display the element's names. + * text(oldName, 50, 33); + * text(newName, 50, 67); + * + * describe( + * 'The words "animals" and "monsters" written on separate lines. The text is black on a gray background.' + * ); + * } + *
    + */ + setName(name) { + const content = this.DOM.innerHTML; + const attributes = this.DOM.attributes; + const xmlDoc = document.implementation.createDocument(null, 'default'); + const newDOM = xmlDoc.createElement(name); + newDOM.innerHTML = content; + for (let i = 0; i < attributes.length; i++) { + newDOM.setAttribute(attributes[i].nodeName, attributes[i].nodeValue); + } + this.DOM = newDOM; } - this.DOM = newDOM; - } - /** - * Returns `true` if the element has child elements and `false` if not. - * - * @return {boolean} whether the element has children. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Check whether the element has child elements. - * let isParent = myXML.hasChildren(); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Style the text. - * if (isParent === true) { - * text('Parent', 50, 50); - * } else { - * text('Not Parent', 50, 50); - * } - * - * describe('The word "Parent" written in black on a gray background.'); - * } - * - *
    - */ - hasChildren() { - return this.DOM.children.length > 0; - } + /** + * Returns `true` if the element has child elements and `false` if not. + * + * @return {boolean} whether the element has children. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Check whether the element has child elements. + * let isParent = myXML.hasChildren(); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Style the text. + * if (isParent === true) { + * text('Parent', 50, 50); + * } else { + * text('Not Parent', 50, 50); + * } + * + * describe('The word "Parent" written in black on a gray background.'); + * } + * + *
    + */ + hasChildren() { + return this.DOM.children.length > 0; + } - /** - * Returns an array with the names of the element's child elements as - * `String`s. - * - * @return {String[]} names of the child elements. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the names of the element's children as an array. - * let children = myXML.listChildren(); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Iterate over the array. - * for (let i = 0; i < children.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 25; - * - * // Display the child element's name. - * text(children[i], 10, y); - * } - * - * describe( - * 'The words "mammal", "mammal", "mammal", and "reptile" written on separate lines. The text is black on a gray background.' - * ); - * } - * - *
    - */ - listChildren() { - const arr = []; - for (let i = 0; i < this.DOM.childNodes.length; i++) { - arr.push(this.DOM.childNodes[i].nodeName); + /** + * Returns an array with the names of the element's child elements as + * `String`s. + * + * @return {String[]} names of the child elements. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the names of the element's children as an array. + * let children = myXML.listChildren(); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Iterate over the array. + * for (let i = 0; i < children.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 25; + * + * // Display the child element's name. + * text(children[i], 10, y); + * } + * + * describe( + * 'The words "mammal", "mammal", "mammal", and "reptile" written on separate lines. The text is black on a gray background.' + * ); + * } + * + *
    + */ + listChildren() { + const arr = []; + for (let i = 0; i < this.DOM.childNodes.length; i++) { + arr.push(this.DOM.childNodes[i].nodeName); + } + return arr; } - return arr; - } - /** - * Returns an array with the element's child elements as new - * p5.XML objects. - * - * The parameter, `name`, is optional. If a string is passed, as in - * `myXML.getChildren('cat')`, then the method will only return child elements - * with the tag `<cat>`. - * - * @param {String} [name] name of the elements to return. - * @return {p5.XML[]} child elements. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get an array of the child elements. - * let children = myXML.getChildren(); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Iterate over the array. - * for (let i = 0; i < children.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 20; - * - * // Get the child element's content. - * let content = children[i].getContent(); - * - * // Display the child element's content. - * text(content, 10, y); - * } - * - * describe( - * 'The words "Goat", "Leopard", "Zebra", and "Turtle" written on separate lines. The text is black on a gray background.' - * ); - * } - * - *
    - * - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get an array of the child elements - * // that are mammals. - * let children = myXML.getChildren('mammal'); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Iterate over the array. - * for (let i = 0; i < children.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 20; - * - * // Get the child element's content. - * let content = children[i].getContent(); - * - * // Display the child element's content. - * text(content, 10, y); - * } - * - * describe( - * 'The words "Goat", "Leopard", and "Zebra" written on separate lines. The text is black on a gray background.' - * ); - * } - * - *
    - */ - getChildren(param) { - if (param) { - return elementsToP5XML(this.DOM.getElementsByTagName(param)); - } else { - return elementsToP5XML(this.DOM.children); + /** + * Returns an array with the element's child elements as new + * p5.XML objects. + * + * The parameter, `name`, is optional. If a string is passed, as in + * `myXML.getChildren('cat')`, then the method will only return child elements + * with the tag `<cat>`. + * + * @param {String} [name] name of the elements to return. + * @return {p5.XML[]} child elements. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get an array of the child elements. + * let children = myXML.getChildren(); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Iterate over the array. + * for (let i = 0; i < children.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 20; + * + * // Get the child element's content. + * let content = children[i].getContent(); + * + * // Display the child element's content. + * text(content, 10, y); + * } + * + * describe( + * 'The words "Goat", "Leopard", "Zebra", and "Turtle" written on separate lines. The text is black on a gray background.' + * ); + * } + * + *
    + * + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get an array of the child elements + * // that are mammals. + * let children = myXML.getChildren('mammal'); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Iterate over the array. + * for (let i = 0; i < children.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 20; + * + * // Get the child element's content. + * let content = children[i].getContent(); + * + * // Display the child element's content. + * text(content, 10, y); + * } + * + * describe( + * 'The words "Goat", "Leopard", and "Zebra" written on separate lines. The text is black on a gray background.' + * ); + * } + * + *
    + */ + getChildren(param) { + if (param) { + return elementsToP5XML(this.DOM.getElementsByTagName(param)); + } else { + return elementsToP5XML(this.DOM.children); + } } - } - /** - * Returns the first matching child element as a new - * p5.XML object. - * - * The parameter, `name`, is optional. If a string is passed, as in - * `myXML.getChild('cat')`, then the first child element with the tag - * `<cat>` will be returned. If a number is passed, as in - * `myXML.getChild(1)`, then the child element at that index will be returned. - * - * @param {String|Integer} name element name or index. - * @return {p5.XML} child element. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the first child element that is a mammal. - * let goat = myXML.getChild('mammal'); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Get the child element's content. - * let content = goat.getContent(); - * - * // Display the child element's content. - * text(content, 50, 50); - * - * describe('The word "Goat" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the child element at index 1. - * let leopard = myXML.getChild(1); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Get the child element's content. - * let content = leopard.getContent(); - * - * // Display the child element's content. - * text(content, 50, 50); - * - * describe('The word "Leopard" written in black on a gray background.'); - * } - * - *
    - */ - getChild(param) { - if (typeof param === 'string') { - for (const child of this.DOM.children) { - if (child.tagName === param) return new p5.XML(child); + /** + * Returns the first matching child element as a new + * p5.XML object. + * + * The parameter, `name`, is optional. If a string is passed, as in + * `myXML.getChild('cat')`, then the first child element with the tag + * `<cat>` will be returned. If a number is passed, as in + * `myXML.getChild(1)`, then the child element at that index will be returned. + * + * @param {String|Integer} name element name or index. + * @return {p5.XML} child element. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the first child element that is a mammal. + * let goat = myXML.getChild('mammal'); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Get the child element's content. + * let content = goat.getContent(); + * + * // Display the child element's content. + * text(content, 50, 50); + * + * describe('The word "Goat" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the child element at index 1. + * let leopard = myXML.getChild(1); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Get the child element's content. + * let content = leopard.getContent(); + * + * // Display the child element's content. + * text(content, 50, 50); + * + * describe('The word "Leopard" written in black on a gray background.'); + * } + * + *
    + */ + getChild(param) { + if (typeof param === 'string') { + for (const child of this.DOM.children) { + if (child.tagName === param) return new p5.XML(child); + } + } else { + return new p5.XML(this.DOM.children[param]); } - } else { - return new p5.XML(this.DOM.children[param]); } - } - /** - * Adds a new child element and returns a reference to it. - * - * The parameter, `child`, is the p5.XML object to add - * as a child element. For example, calling `myXML.addChild(otherXML)` inserts - * `otherXML` as a child element of `myXML`. - * - * @param {p5.XML} child child element to add. - * @return {p5.XML} added child element. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a new p5.XML object. - * let newAnimal = new p5.XML(); - * - * // Set its properties. - * newAnimal.setName('hydrozoa'); - * newAnimal.setAttribute('id', 4); - * newAnimal.setAttribute('species', 'Physalia physalis'); - * newAnimal.setContent('Bluebottle'); - * - * // Add the child element. - * myXML.addChild(newAnimal); - * - * // Get the first child element that is a hydrozoa. - * let blueBottle = myXML.getChild('hydrozoa'); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Get the child element's content. - * let content = blueBottle.getContent(); - * - * // Display the child element's content. - * text(content, 50, 50); - * - * describe('The word "Bluebottle" written in black on a gray background.'); - * } - * - *
    - */ - addChild(node) { - if (node instanceof p5.XML) { - this.DOM.appendChild(node.DOM); - } else { - // PEND + /** + * Adds a new child element and returns a reference to it. + * + * The parameter, `child`, is the p5.XML object to add + * as a child element. For example, calling `myXML.addChild(otherXML)` inserts + * `otherXML` as a child element of `myXML`. + * + * @param {p5.XML} child child element to add. + * @return {p5.XML} added child element. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a new p5.XML object. + * let newAnimal = new p5.XML(); + * + * // Set its properties. + * newAnimal.setName('hydrozoa'); + * newAnimal.setAttribute('id', 4); + * newAnimal.setAttribute('species', 'Physalia physalis'); + * newAnimal.setContent('Bluebottle'); + * + * // Add the child element. + * myXML.addChild(newAnimal); + * + * // Get the first child element that is a hydrozoa. + * let blueBottle = myXML.getChild('hydrozoa'); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Get the child element's content. + * let content = blueBottle.getContent(); + * + * // Display the child element's content. + * text(content, 50, 50); + * + * describe('The word "Bluebottle" written in black on a gray background.'); + * } + * + *
    + */ + addChild(node) { + if (node instanceof p5.XML) { + this.DOM.appendChild(node.DOM); + } else { + // PEND + } } - } - /** - * Removes the first matching child element. - * - * The parameter, `name`, is the child element to remove. If a string is - * passed, as in `myXML.removeChild('cat')`, then the first child element - * with the tag `<cat>` will be removed. If a number is passed, as in - * `myXML.removeChild(1)`, then the child element at that index will be - * removed. - * - * @param {String|Integer} name name or index of the child element to remove. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Remove the first mammal element. - * myXML.removeChild('mammal'); - * - * // Get an array of child elements. - * let children = myXML.getChildren(); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Iterate over the array. - * for (let i = 0; i < children.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 25; - * - * // Get the child element's content. - * let content = children[i].getContent(); - * - * // Display the child element's content. - * text(content, 10, y); - * } - * - * describe( - * 'The words "Leopard", "Zebra", and "Turtle" written on separate lines. The text is black on a gray background.' - * ); - * } - * - *
    - * - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Remove the element at index 2. - * myXML.removeChild(2); - * - * // Get an array of child elements. - * let children = myXML.getChildren(); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Iterate over the array. - * for (let i = 0; i < children.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 25; - * - * // Get the child element's content. - * let content = children[i].getContent(); - * - * // Display the child element's content. - * text(content, 10, y); - * } - * - * describe( - * 'The words "Goat", "Leopard", and "Turtle" written on separate lines. The text is black on a gray background.' - * ); - * } - * - *
    - */ - removeChild(param) { - let ind = -1; - if (typeof param === 'string') { - for (let i = 0; i < this.DOM.children.length; i++) { - if (this.DOM.children[i].tagName === param) { - ind = i; - break; + /** + * Removes the first matching child element. + * + * The parameter, `name`, is the child element to remove. If a string is + * passed, as in `myXML.removeChild('cat')`, then the first child element + * with the tag `<cat>` will be removed. If a number is passed, as in + * `myXML.removeChild(1)`, then the child element at that index will be + * removed. + * + * @param {String|Integer} name name or index of the child element to remove. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Remove the first mammal element. + * myXML.removeChild('mammal'); + * + * // Get an array of child elements. + * let children = myXML.getChildren(); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Iterate over the array. + * for (let i = 0; i < children.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 25; + * + * // Get the child element's content. + * let content = children[i].getContent(); + * + * // Display the child element's content. + * text(content, 10, y); + * } + * + * describe( + * 'The words "Leopard", "Zebra", and "Turtle" written on separate lines. The text is black on a gray background.' + * ); + * } + * + *
    + * + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Remove the element at index 2. + * myXML.removeChild(2); + * + * // Get an array of child elements. + * let children = myXML.getChildren(); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Iterate over the array. + * for (let i = 0; i < children.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 25; + * + * // Get the child element's content. + * let content = children[i].getContent(); + * + * // Display the child element's content. + * text(content, 10, y); + * } + * + * describe( + * 'The words "Goat", "Leopard", and "Turtle" written on separate lines. The text is black on a gray background.' + * ); + * } + * + *
    + */ + removeChild(param) { + let ind = -1; + if (typeof param === 'string') { + for (let i = 0; i < this.DOM.children.length; i++) { + if (this.DOM.children[i].tagName === param) { + ind = i; + break; + } } + } else { + ind = param; + } + if (ind !== -1) { + this.DOM.removeChild(this.DOM.children[ind]); } - } else { - ind = param; } - if (ind !== -1) { - this.DOM.removeChild(this.DOM.children[ind]); + + /** + * Returns the number of attributes the element has. + * + * @return {Integer} number of attributes. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the first child element. + * let first = myXML.getChild(0); + * + * // Get the number of attributes. + * let numAttributes = first.getAttributeCount(); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display the number of attributes. + * text(numAttributes, 50, 50); + * + * describe('The number "2" written in black on a gray background.'); + * } + * + *
    + */ + getAttributeCount() { + return this.DOM.attributes.length; } - } - /** - * Returns the number of attributes the element has. - * - * @return {Integer} number of attributes. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the first child element. - * let first = myXML.getChild(0); - * - * // Get the number of attributes. - * let numAttributes = first.getAttributeCount(); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display the number of attributes. - * text(numAttributes, 50, 50); - * - * describe('The number "2" written in black on a gray background.'); - * } - * - *
    - */ - getAttributeCount() { - return this.DOM.attributes.length; - } + /** + * Returns an `Array` with the names of the element's attributes. + * + * Note: Use + * myXML.getString() or + * myXML.getNum() to return an attribute's value. + * + * @return {String[]} attribute names. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the first child element. + * let first = myXML.getChild(0); + * + * // Get the number of attributes. + * let attributes = first.listAttributes(); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display the element's attributes. + * text(attributes, 50, 50); + * + * describe('The text "id,species" written in black on a gray background.'); + * } + * + *
    + */ + listAttributes() { + const arr = []; - /** - * Returns an `Array` with the names of the element's attributes. - * - * Note: Use - * myXML.getString() or - * myXML.getNum() to return an attribute's value. - * - * @return {String[]} attribute names. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the first child element. - * let first = myXML.getChild(0); - * - * // Get the number of attributes. - * let attributes = first.listAttributes(); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display the element's attributes. - * text(attributes, 50, 50); - * - * describe('The text "id,species" written in black on a gray background.'); - * } - * - *
    - */ - listAttributes() { - const arr = []; + for (const attribute of this.DOM.attributes) { + arr.push(attribute.nodeName); + } - for (const attribute of this.DOM.attributes) { - arr.push(attribute.nodeName); + return arr; } - return arr; - } + /** + * Returns `true` if the element has a given attribute and `false` if not. + * + * The parameter, `name`, is a string with the name of the attribute being + * checked. + * + * Note: Use + * myXML.getString() or + * myXML.getNum() to return an attribute's value. + * + * @param {String} name name of the attribute to be checked. + * @return {boolean} whether the element has the attribute. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the first mammal child element. + * let mammal = myXML.getChild('mammal'); + * + * // Check whether the element has an + * // species attribute. + * let hasSpecies = mammal.hasAttribute('species'); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display whether the element has a species attribute. + * if (hasSpecies === true) { + * text('Species', 50, 50); + * } else { + * text('No species', 50, 50); + * } + * + * describe('The text "Species" written in black on a gray background.'); + * } + * + *
    + */ + hasAttribute(name) { + const obj = {}; - /** - * Returns `true` if the element has a given attribute and `false` if not. - * - * The parameter, `name`, is a string with the name of the attribute being - * checked. - * - * Note: Use - * myXML.getString() or - * myXML.getNum() to return an attribute's value. - * - * @param {String} name name of the attribute to be checked. - * @return {boolean} whether the element has the attribute. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the first mammal child element. - * let mammal = myXML.getChild('mammal'); - * - * // Check whether the element has an - * // species attribute. - * let hasSpecies = mammal.hasAttribute('species'); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display whether the element has a species attribute. - * if (hasSpecies === true) { - * text('Species', 50, 50); - * } else { - * text('No species', 50, 50); - * } - * - * describe('The text "Species" written in black on a gray background.'); - * } - * - *
    - */ - hasAttribute(name) { - const obj = {}; + for (const attribute of this.DOM.attributes) { + obj[attribute.nodeName] = attribute.nodeValue; + } - for (const attribute of this.DOM.attributes) { - obj[attribute.nodeName] = attribute.nodeValue; + return obj[name] ? true : false; } - return obj[name] ? true : false; - } + /** + * Return an attribute's value as a `Number`. + * + * The first parameter, `name`, is a string with the name of the attribute + * being checked. For example, calling `myXML.getNum('id')` returns the + * element's `id` attribute as a number. + * + * The second parameter, `defaultValue`, is optional. If a number is passed, + * as in `myXML.getNum('id', -1)`, it will be returned if the attribute + * doesn't exist or can't be converted to a number. + * + * Note: Use + * myXML.getString() or + * myXML.getNum() to return an attribute's value. + * + * @param {String} name name of the attribute to be checked. + * @param {Number} [defaultValue] value to return if the attribute doesn't exist. + * @return {Number} attribute value as a number. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the first reptile child element. + * let reptile = myXML.getChild('reptile'); + * + * // Get the reptile's content. + * let content = reptile.getContent(); + * + * // Get the reptile's ID. + * let id = reptile.getNum('id'); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display the ID attribute. + * text(`${content} is ${id + 1}th`, 5, 50, 90); + * + * describe(`The text "${content} is ${id + 1}th" written in black on a gray background.`); + * } + * + *
    + * + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the first reptile child element. + * let reptile = myXML.getChild('reptile'); + * + * // Get the reptile's content. + * let content = reptile.getContent(); + * + * // Get the reptile's size. + * let weight = reptile.getNum('weight', 135); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display the ID attribute. + * text(`${content} is ${weight}kg`, 5, 50, 90); + * + * describe( + * `The text "${content} is ${weight}kg" written in black on a gray background.` + * ); + * } + * + *
    + */ + getNum(name, defaultValue) { + const obj = {}; - /** - * Return an attribute's value as a `Number`. - * - * The first parameter, `name`, is a string with the name of the attribute - * being checked. For example, calling `myXML.getNum('id')` returns the - * element's `id` attribute as a number. - * - * The second parameter, `defaultValue`, is optional. If a number is passed, - * as in `myXML.getNum('id', -1)`, it will be returned if the attribute - * doesn't exist or can't be converted to a number. - * - * Note: Use - * myXML.getString() or - * myXML.getNum() to return an attribute's value. - * - * @param {String} name name of the attribute to be checked. - * @param {Number} [defaultValue] value to return if the attribute doesn't exist. - * @return {Number} attribute value as a number. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the first reptile child element. - * let reptile = myXML.getChild('reptile'); - * - * // Get the reptile's content. - * let content = reptile.getContent(); - * - * // Get the reptile's ID. - * let id = reptile.getNum('id'); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display the ID attribute. - * text(`${content} is ${id + 1}th`, 5, 50, 90); - * - * describe(`The text "${content} is ${id + 1}th" written in black on a gray background.`); - * } - * - *
    - * - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the first reptile child element. - * let reptile = myXML.getChild('reptile'); - * - * // Get the reptile's content. - * let content = reptile.getContent(); - * - * // Get the reptile's size. - * let weight = reptile.getNum('weight', 135); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display the ID attribute. - * text(`${content} is ${weight}kg`, 5, 50, 90); - * - * describe( - * `The text "${content} is ${weight}kg" written in black on a gray background.` - * ); - * } - * - *
    - */ - getNum(name, defaultValue) { - const obj = {}; + for (const attribute of this.DOM.attributes) { + obj[attribute.nodeName] = attribute.nodeValue; + } - for (const attribute of this.DOM.attributes) { - obj[attribute.nodeName] = attribute.nodeValue; + return Number(obj[name]) || defaultValue || 0; } - return Number(obj[name]) || defaultValue || 0; - } + /** + * Return an attribute's value as a string. + * + * The first parameter, `name`, is a string with the name of the attribute + * being checked. For example, calling `myXML.getString('color')` returns the + * element's `id` attribute as a string. + * + * The second parameter, `defaultValue`, is optional. If a string is passed, + * as in `myXML.getString('color', 'deeppink')`, it will be returned if the + * attribute doesn't exist. + * + * Note: Use + * myXML.getString() or + * myXML.getNum() to return an attribute's value. + * + * @param {String} name name of the attribute to be checked. + * @param {Number} [defaultValue] value to return if the attribute doesn't exist. + * @return {String} attribute value as a string. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the first reptile child element. + * let reptile = myXML.getChild('reptile'); + * + * // Get the reptile's content. + * let content = reptile.getContent(); + * + * // Get the reptile's species. + * let species = reptile.getString('species'); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display the species attribute. + * text(`${content}: ${species}`, 5, 50, 90); + * + * describe(`The text "${content}: ${species}" written in black on a gray background.`); + * } + * + *
    + * + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the first reptile child element. + * let reptile = myXML.getChild('reptile'); + * + * // Get the reptile's content. + * let content = reptile.getContent(); + * + * // Get the reptile's color. + * let attribute = reptile.getString('color', 'green'); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * fill(attribute); + * + * // Display the element's content. + * text(content, 50, 50); + * + * describe(`The text "${content}" written in green on a gray background.`); + * } + * + *
    + */ + getString(name, defaultValue) { + const obj = {}; - /** - * Return an attribute's value as a string. - * - * The first parameter, `name`, is a string with the name of the attribute - * being checked. For example, calling `myXML.getString('color')` returns the - * element's `id` attribute as a string. - * - * The second parameter, `defaultValue`, is optional. If a string is passed, - * as in `myXML.getString('color', 'deeppink')`, it will be returned if the - * attribute doesn't exist. - * - * Note: Use - * myXML.getString() or - * myXML.getNum() to return an attribute's value. - * - * @param {String} name name of the attribute to be checked. - * @param {Number} [defaultValue] value to return if the attribute doesn't exist. - * @return {String} attribute value as a string. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the first reptile child element. - * let reptile = myXML.getChild('reptile'); - * - * // Get the reptile's content. - * let content = reptile.getContent(); - * - * // Get the reptile's species. - * let species = reptile.getString('species'); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display the species attribute. - * text(`${content}: ${species}`, 5, 50, 90); - * - * describe(`The text "${content}: ${species}" written in black on a gray background.`); - * } - * - *
    - * - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the first reptile child element. - * let reptile = myXML.getChild('reptile'); - * - * // Get the reptile's content. - * let content = reptile.getContent(); - * - * // Get the reptile's color. - * let attribute = reptile.getString('color', 'green'); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * fill(attribute); - * - * // Display the element's content. - * text(content, 50, 50); - * - * describe(`The text "${content}" written in green on a gray background.`); - * } - * - *
    - */ - getString(name, defaultValue) { - const obj = {}; + for (const attribute of this.DOM.attributes) { + obj[attribute.nodeName] = attribute.nodeValue; + } - for (const attribute of this.DOM.attributes) { - obj[attribute.nodeName] = attribute.nodeValue; + return obj[name] ? String(obj[name]) : defaultValue || null; } - return obj[name] ? String(obj[name]) : defaultValue || null; - } - - /** - * Sets an attribute to a given value. - * - * The first parameter, `name`, is a string with the name of the attribute - * being set. - * - * The second parameter, `value`, is the attribute's new value. For example, - * calling `myXML.setAttribute('id', 123)` sets the `id` attribute to the - * value 123. - * - * @param {String} name name of the attribute to be set. - * @param {Number|String|Boolean} value attribute's new value. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the first reptile child element. - * let reptile = myXML.getChild('reptile'); - * - * // Set the reptile's color. - * reptile.setAttribute('color', 'green'); - * - * // Get the reptile's content. - * let content = reptile.getContent(); - * - * // Get the reptile's color. - * let attribute = reptile.getString('color'); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display the element's content. - * text(`${content} is ${attribute}`, 5, 50, 90); - * - * describe( - * `The text "${content} is ${attribute}" written in green on a gray background.` - * ); - * } - * - *
    - */ - setAttribute(name, value) { - this.DOM.setAttribute(name, value); - } + /** + * Sets an attribute to a given value. + * + * The first parameter, `name`, is a string with the name of the attribute + * being set. + * + * The second parameter, `value`, is the attribute's new value. For example, + * calling `myXML.setAttribute('id', 123)` sets the `id` attribute to the + * value 123. + * + * @param {String} name name of the attribute to be set. + * @param {Number|String|Boolean} value attribute's new value. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the first reptile child element. + * let reptile = myXML.getChild('reptile'); + * + * // Set the reptile's color. + * reptile.setAttribute('color', 'green'); + * + * // Get the reptile's content. + * let content = reptile.getContent(); + * + * // Get the reptile's color. + * let attribute = reptile.getString('color'); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display the element's content. + * text(`${content} is ${attribute}`, 5, 50, 90); + * + * describe( + * `The text "${content} is ${attribute}" written in green on a gray background.` + * ); + * } + * + *
    + */ + setAttribute(name, value) { + this.DOM.setAttribute(name, value); + } - /** - * Returns the element's content as a `String`. - * - * The parameter, `defaultValue`, is optional. If a string is passed, as in - * `myXML.getContent('???')`, it will be returned if the element has no - * content. - * - * @param {String} [defaultValue] value to return if the element has no - * content. - * @return {String} element's content as a string. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the first reptile child element. - * let reptile = myXML.getChild('reptile'); - * - * // Get the reptile's content. - * let content = reptile.getContent(); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display the element's content. - * text(content, 5, 50, 90); - * - * describe(`The text "${content}" written in green on a gray background.`); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a p5.XML object. - * let blankSpace = new p5.XML(); - * - * // Get the element's content and use a default value. - * let content = blankSpace.getContent('Your name'); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display the element's content. - * text(content, 5, 50, 90); - * - * describe(`The text "${content}" written in green on a gray background.`); - * } - * - *
    - */ - getContent(defaultValue) { - let str; - str = this.DOM.textContent; - str = str.replace(/\s\s+/g, ','); - return str || defaultValue || null; - } + /** + * Returns the element's content as a `String`. + * + * The parameter, `defaultValue`, is optional. If a string is passed, as in + * `myXML.getContent('???')`, it will be returned if the element has no + * content. + * + * @param {String} [defaultValue] value to return if the element has no + * content. + * @return {String} element's content as a string. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the first reptile child element. + * let reptile = myXML.getChild('reptile'); + * + * // Get the reptile's content. + * let content = reptile.getContent(); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display the element's content. + * text(content, 5, 50, 90); + * + * describe(`The text "${content}" written in green on a gray background.`); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a p5.XML object. + * let blankSpace = new p5.XML(); + * + * // Get the element's content and use a default value. + * let content = blankSpace.getContent('Your name'); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display the element's content. + * text(content, 5, 50, 90); + * + * describe(`The text "${content}" written in green on a gray background.`); + * } + * + *
    + */ + getContent(defaultValue) { + let str; + str = this.DOM.textContent; + str = str.replace(/\s\s+/g, ','); + return str || defaultValue || null; + } - /** - * Sets the element's content. - * - * An element's content is the text between its tags. For example, the element - * `<language>JavaScript</language>` has the content `JavaScript`. - * - * The parameter, `content`, is a string with the element's new content. - * - * @method setContent - * @param {String} content new content for the element. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the first reptile child element. - * let reptile = myXML.getChild('reptile'); - * - * // Get the reptile's original content. - * let oldContent = reptile.getContent(); - * - * // Set the reptile's content. - * reptile.setContent('Loggerhead'); - * - * // Get the reptile's new content. - * let newContent = reptile.getContent(); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(14); - * - * // Display the element's old and new content. - * text(`${oldContent}: ${newContent}`, 5, 50, 90); - * - * describe( - * `The text "${oldContent}: ${newContent}" written in green on a gray background.` - * ); - * } - * - *
    - */ - setContent(content) { - if (!this.DOM.children.length) { - this.DOM.textContent = content; + /** + * Sets the element's content. + * + * An element's content is the text between its tags. For example, the element + * `<language>JavaScript</language>` has the content `JavaScript`. + * + * The parameter, `content`, is a string with the element's new content. + * + * @method setContent + * @param {String} content new content for the element. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the first reptile child element. + * let reptile = myXML.getChild('reptile'); + * + * // Get the reptile's original content. + * let oldContent = reptile.getContent(); + * + * // Set the reptile's content. + * reptile.setContent('Loggerhead'); + * + * // Get the reptile's new content. + * let newContent = reptile.getContent(); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(14); + * + * // Display the element's old and new content. + * text(`${oldContent}: ${newContent}`, 5, 50, 90); + * + * describe( + * `The text "${oldContent}: ${newContent}" written in green on a gray background.` + * ); + * } + * + *
    + */ + setContent(content) { + if (!this.DOM.children.length) { + this.DOM.textContent = content; + } } - } - /** - * Returns the element as a `String`. - * - * `myXML.serialize()` is useful for sending the element over the network or - * saving it to a file. - * - * @return {String} element as a string. - * - * @example - *
    - * - * let myXML; - * - * // Load the XML and create a p5.XML object. - * function preload() { - * myXML = loadXML('assets/animals.xml'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Display instructions. - * text('Double-click to save', 5, 50, 90); - * - * describe('The text "Double-click to save" written in black on a gray background.'); - * } - * - * // Save the file when the user double-clicks. - * function doubleClicked() { - * // Create a p5.PrintWriter object. - * // Use the file format .xml. - * let myWriter = createWriter('animals', 'xml'); - * - * // Serialize the XML data to a string. - * let data = myXML.serialize(); - * - * // Write the data to the print stream. - * myWriter.write(data); - * - * // Save the file and close the print stream. - * myWriter.close(); - * } - * - *
    - */ - serialize() { - const xmlSerializer = new XMLSerializer(); - return xmlSerializer.serializeToString(this.DOM); - } -}; + /** + * Returns the element as a `String`. + * + * `myXML.serialize()` is useful for sending the element over the network or + * saving it to a file. + * + * @return {String} element as a string. + * + * @example + *
    + * + * let myXML; + * + * // Load the XML and create a p5.XML object. + * function preload() { + * myXML = loadXML('assets/animals.xml'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Display instructions. + * text('Double-click to save', 5, 50, 90); + * + * describe('The text "Double-click to save" written in black on a gray background.'); + * } + * + * // Save the file when the user double-clicks. + * function doubleClicked() { + * // Create a p5.PrintWriter object. + * // Use the file format .xml. + * let myWriter = createWriter('animals', 'xml'); + * + * // Serialize the XML data to a string. + * let data = myXML.serialize(); + * + * // Write the data to the print stream. + * myWriter.write(data); + * + * // Save the file and close the print stream. + * myWriter.close(); + * } + * + *
    + */ + serialize() { + const xmlSerializer = new XMLSerializer(); + return xmlSerializer.serializeToString(this.DOM); + } + }; -function elementsToP5XML(elements) { - const arr = []; - for (let i = 0; i < elements.length; i++) { - arr.push(new p5.XML(elements[i])); + function elementsToP5XML(elements) { + const arr = []; + for (let i = 0; i < elements.length; i++) { + arr.push(new p5.XML(elements[i])); + } + return arr; } - return arr; } -export default p5; +export default xml; + +if(typeof p5 !== 'undefined'){ + xml(p5, p5.prototype); +} diff --git a/src/utilities/array_functions.js b/src/utilities/array_functions.js index 3a701d7d1b..378810c715 100644 --- a/src/utilities/array_functions.js +++ b/src/utilities/array_functions.js @@ -5,413 +5,417 @@ * @requires core */ -import p5 from '../core/main'; +function arrayFunctions(p5, fn){ + /** + * Adds a value to the end of an array. Extends the length of + * the array by one. Maps to Array.push(). + * + * @method append + * @deprecated Use array.push(value) instead. + * @param {Array} array Array to append + * @param {Any} value to be added to the Array + * @return {Array} the array that was appended to + * @example + *
    + * function setup() { + * let myArray = ['Mango', 'Apple', 'Papaya']; + * print(myArray); // ['Mango', 'Apple', 'Papaya'] + * + * append(myArray, 'Peach'); + * print(myArray); // ['Mango', 'Apple', 'Papaya', 'Peach'] + * } + *
    + */ + fn.append = function (array, value) { + array.push(value); + return array; + }; -/** - * Adds a value to the end of an array. Extends the length of - * the array by one. Maps to Array.push(). - * - * @method append - * @deprecated Use array.push(value) instead. - * @param {Array} array Array to append - * @param {Any} value to be added to the Array - * @return {Array} the array that was appended to - * @example - *
    - * function setup() { - * let myArray = ['Mango', 'Apple', 'Papaya']; - * print(myArray); // ['Mango', 'Apple', 'Papaya'] - * - * append(myArray, 'Peach'); - * print(myArray); // ['Mango', 'Apple', 'Papaya', 'Peach'] - * } - *
    - */ -p5.prototype.append = function (array, value) { - array.push(value); - return array; -}; + /** + * Copies an array (or part of an array) to another array. The src array is + * copied to the dst array, beginning at the position specified by + * srcPosition and into the position specified by dstPosition. The number of + * elements to copy is determined by length. Note that copying values + * overwrites existing values in the destination array. To append values + * instead of overwriting them, use concat(). + * + * The simplified version with only two arguments, arrayCopy(src, dst), + * copies an entire array to another of the same size. It is equivalent to + * arrayCopy(src, 0, dst, 0, src.length). + * + * Using this function is far more efficient for copying array data than + * iterating through a for() loop and copying each element individually. + * + * @method arrayCopy + * @deprecated Use arr1.copyWithin(arr2) instead. + * @param {Array} src the source Array + * @param {Integer} srcPosition starting position in the source Array + * @param {Array} dst the destination Array + * @param {Integer} dstPosition starting position in the destination Array + * @param {Integer} length number of Array elements to be copied + * + * @example + *
    + * let src = ['A', 'B', 'C']; + * let dst = [1, 2, 3]; + * let srcPosition = 1; + * let dstPosition = 0; + * let length = 2; + * + * print(src); // ['A', 'B', 'C'] + * print(dst); // [ 1 , 2 , 3 ] + * + * arrayCopy(src, srcPosition, dst, dstPosition, length); + * print(dst); // ['B', 'C', 3] + *
    + */ + /** + * @method arrayCopy + * @deprecated Use arr1.copyWithin(arr2) instead. + * @param {Array} src + * @param {Array} dst + * @param {Integer} [length] + */ + fn.arrayCopy = function (src, srcPosition, dst, dstPosition, length) { + // the index to begin splicing from dst array + let start; + let end; -/** - * Copies an array (or part of an array) to another array. The src array is - * copied to the dst array, beginning at the position specified by - * srcPosition and into the position specified by dstPosition. The number of - * elements to copy is determined by length. Note that copying values - * overwrites existing values in the destination array. To append values - * instead of overwriting them, use concat(). - * - * The simplified version with only two arguments, arrayCopy(src, dst), - * copies an entire array to another of the same size. It is equivalent to - * arrayCopy(src, 0, dst, 0, src.length). - * - * Using this function is far more efficient for copying array data than - * iterating through a for() loop and copying each element individually. - * - * @method arrayCopy - * @deprecated Use arr1.copyWithin(arr2) instead. - * @param {Array} src the source Array - * @param {Integer} srcPosition starting position in the source Array - * @param {Array} dst the destination Array - * @param {Integer} dstPosition starting position in the destination Array - * @param {Integer} length number of Array elements to be copied - * - * @example - *
    - * let src = ['A', 'B', 'C']; - * let dst = [1, 2, 3]; - * let srcPosition = 1; - * let dstPosition = 0; - * let length = 2; - * - * print(src); // ['A', 'B', 'C'] - * print(dst); // [ 1 , 2 , 3 ] - * - * arrayCopy(src, srcPosition, dst, dstPosition, length); - * print(dst); // ['B', 'C', 3] - *
    - */ -/** - * @method arrayCopy - * @deprecated Use arr1.copyWithin(arr2) instead. - * @param {Array} src - * @param {Array} dst - * @param {Integer} [length] - */ -p5.prototype.arrayCopy = function (src, srcPosition, dst, dstPosition, length) { - // the index to begin splicing from dst array - let start; - let end; + if (typeof length !== 'undefined') { + end = Math.min(length, src.length); + start = dstPosition; + src = src.slice(srcPosition, end + srcPosition); + } else { + if (typeof dst !== 'undefined') { + // src, dst, length + // rename so we don't get confused + end = dst; + end = Math.min(end, src.length); + } else { + // src, dst + end = src.length; + } - if (typeof length !== 'undefined') { - end = Math.min(length, src.length); - start = dstPosition; - src = src.slice(srcPosition, end + srcPosition); - } else { - if (typeof dst !== 'undefined') { - // src, dst, length + start = 0; // rename so we don't get confused - end = dst; - end = Math.min(end, src.length); - } else { - // src, dst - end = src.length; + dst = srcPosition; + src = src.slice(0, end); } - start = 0; - // rename so we don't get confused - dst = srcPosition; - src = src.slice(0, end); - } + // Since we are not returning the array and JavaScript is pass by reference + // we must modify the actual values of the array + // instead of reassigning arrays + Array.prototype.splice.apply(dst, [start, end].concat(src)); + }; - // Since we are not returning the array and JavaScript is pass by reference - // we must modify the actual values of the array - // instead of reassigning arrays - Array.prototype.splice.apply(dst, [start, end].concat(src)); -}; + /** + * Concatenates two arrays, maps to Array.concat(). Does not modify the + * input arrays. + * + * @method concat + * @deprecated Use arr1.concat(arr2) instead. + * @param {Array} a first Array to concatenate + * @param {Array} b second Array to concatenate + * @return {Array} concatenated array + * + * @example + *
    + * function setup() { + * let arr1 = ['A', 'B', 'C']; + * let arr2 = [1, 2, 3]; + * + * print(arr1); // ['A','B','C'] + * print(arr2); // [1,2,3] + * + * let arr3 = concat(arr1, arr2); + * + * print(arr1); // ['A','B','C'] + * print(arr2); // [1, 2, 3] + * print(arr3); // ['A','B','C', 1, 2, 3] + * } + *
    + */ + fn.concat = (list0, list1) => list0.concat(list1); -/** - * Concatenates two arrays, maps to Array.concat(). Does not modify the - * input arrays. - * - * @method concat - * @deprecated Use arr1.concat(arr2) instead. - * @param {Array} a first Array to concatenate - * @param {Array} b second Array to concatenate - * @return {Array} concatenated array - * - * @example - *
    - * function setup() { - * let arr1 = ['A', 'B', 'C']; - * let arr2 = [1, 2, 3]; - * - * print(arr1); // ['A','B','C'] - * print(arr2); // [1,2,3] - * - * let arr3 = concat(arr1, arr2); - * - * print(arr1); // ['A','B','C'] - * print(arr2); // [1, 2, 3] - * print(arr3); // ['A','B','C', 1, 2, 3] - * } - *
    - */ -p5.prototype.concat = (list0, list1) => list0.concat(list1); + /** + * Reverses the order of an array, maps to Array.reverse() + * + * @method reverse + * @deprecated Use array.reverse() instead. + * @param {Array} list Array to reverse + * @return {Array} the reversed list + * @example + *
    + * function setup() { + * let myArray = ['A', 'B', 'C']; + * print(myArray); // ['A','B','C'] + * + * reverse(myArray); + * print(myArray); // ['C','B','A'] + * } + *
    + */ + fn.reverse = list => list.reverse(); -/** - * Reverses the order of an array, maps to Array.reverse() - * - * @method reverse - * @deprecated Use array.reverse() instead. - * @param {Array} list Array to reverse - * @return {Array} the reversed list - * @example - *
    - * function setup() { - * let myArray = ['A', 'B', 'C']; - * print(myArray); // ['A','B','C'] - * - * reverse(myArray); - * print(myArray); // ['C','B','A'] - * } - *
    - */ -p5.prototype.reverse = list => list.reverse(); + /** + * Decreases an array by one element and returns the shortened array, + * maps to Array.pop(). + * + * @method shorten + * @deprecated Use array.pop() instead. + * @param {Array} list Array to shorten + * @return {Array} shortened Array + * @example + *
    + * function setup() { + * let myArray = ['A', 'B', 'C']; + * print(myArray); // ['A', 'B', 'C'] + * let newArray = shorten(myArray); + * print(myArray); // ['A','B','C'] + * print(newArray); // ['A','B'] + * } + *
    + */ + fn.shorten = function (list) { + list.pop(); + return list; + }; -/** - * Decreases an array by one element and returns the shortened array, - * maps to Array.pop(). - * - * @method shorten - * @deprecated Use array.pop() instead. - * @param {Array} list Array to shorten - * @return {Array} shortened Array - * @example - *
    - * function setup() { - * let myArray = ['A', 'B', 'C']; - * print(myArray); // ['A', 'B', 'C'] - * let newArray = shorten(myArray); - * print(myArray); // ['A','B','C'] - * print(newArray); // ['A','B'] - * } - *
    - */ -p5.prototype.shorten = function (list) { - list.pop(); - return list; -}; + /** + * Shuffles the elements of an array. + * + * The first parameter, `array`, is the array to be shuffled. For example, + * calling `shuffle(myArray)` will shuffle the elements of `myArray`. By + * default, the original array won’t be modified. Instead, a copy will be + * created, shuffled, and returned. + * + * The second parameter, `modify`, is optional. If `true` is passed, as in + * `shuffle(myArray, true)`, then the array will be shuffled in place without + * making a copy. + * + * @method shuffle + * @param {Array} array array to shuffle. + * @param {Boolean} [bool] if `true`, shuffle the original array in place. Defaults to `false`. + * @return {Array} shuffled array. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of colors. + * let colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; + * + * // Create a shuffled copy of the array. + * let shuffledColors = shuffle(colors); + * + * // Draw a row of circles using the original array. + * for (let i = 0; i < colors.length; i += 1) { + * // Calculate the x-coordinate. + * let x = (i + 1) * 12.5; + * + * // Style the circle. + * let c = colors[i]; + * fill(c); + * + * // Draw the circle. + * circle(x, 33, 10); + * } + * + * // Draw a row of circles using the original array. + * for (let i = 0; i < shuffledColors.length; i += 1) { + * // Calculate the x-coordinate. + * let x = (i + 1) * 12.5; + * + * // Style the circle. + * let c = shuffledColors[i]; + * fill(c); + * + * // Draw the circle. + * circle(x, 67, 10); + * } + * + * describe( + * 'Two rows of circles on a gray background. The top row follows the color sequence ROYGBIV. The bottom row has all the same colors but they are shuffled.' + * ); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of colors. + * let colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; + * + * // Shuffle the array. + * shuffle(colors, true); + * + * // Draw a row of circles using the original array. + * for (let i = 0; i < colors.length; i += 1) { + * // Calculate the x-coordinate. + * let x = (i + 1) * 12.5; + * + * // Style the circle. + * let c = colors[i]; + * fill(c); + * + * // Draw the circle. + * circle(x, 50, 10); + * } + * + * describe( + * 'A row of colorful circles on a gray background. Their sequence changes each time the sketch runs.' + * ); + * } + * + *
    + */ + fn.shuffle = function (arr, bool) { + const isView = ArrayBuffer && ArrayBuffer.isView && ArrayBuffer.isView(arr); + arr = bool || isView ? arr : arr.slice(); -/** - * Shuffles the elements of an array. - * - * The first parameter, `array`, is the array to be shuffled. For example, - * calling `shuffle(myArray)` will shuffle the elements of `myArray`. By - * default, the original array won’t be modified. Instead, a copy will be - * created, shuffled, and returned. - * - * The second parameter, `modify`, is optional. If `true` is passed, as in - * `shuffle(myArray, true)`, then the array will be shuffled in place without - * making a copy. - * - * @method shuffle - * @param {Array} array array to shuffle. - * @param {Boolean} [bool] if `true`, shuffle the original array in place. Defaults to `false`. - * @return {Array} shuffled array. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of colors. - * let colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; - * - * // Create a shuffled copy of the array. - * let shuffledColors = shuffle(colors); - * - * // Draw a row of circles using the original array. - * for (let i = 0; i < colors.length; i += 1) { - * // Calculate the x-coordinate. - * let x = (i + 1) * 12.5; - * - * // Style the circle. - * let c = colors[i]; - * fill(c); - * - * // Draw the circle. - * circle(x, 33, 10); - * } - * - * // Draw a row of circles using the original array. - * for (let i = 0; i < shuffledColors.length; i += 1) { - * // Calculate the x-coordinate. - * let x = (i + 1) * 12.5; - * - * // Style the circle. - * let c = shuffledColors[i]; - * fill(c); - * - * // Draw the circle. - * circle(x, 67, 10); - * } - * - * describe( - * 'Two rows of circles on a gray background. The top row follows the color sequence ROYGBIV. The bottom row has all the same colors but they are shuffled.' - * ); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of colors. - * let colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; - * - * // Shuffle the array. - * shuffle(colors, true); - * - * // Draw a row of circles using the original array. - * for (let i = 0; i < colors.length; i += 1) { - * // Calculate the x-coordinate. - * let x = (i + 1) * 12.5; - * - * // Style the circle. - * let c = colors[i]; - * fill(c); - * - * // Draw the circle. - * circle(x, 50, 10); - * } - * - * describe( - * 'A row of colorful circles on a gray background. Their sequence changes each time the sketch runs.' - * ); - * } - * - *
    - */ -p5.prototype.shuffle = function (arr, bool) { - const isView = ArrayBuffer && ArrayBuffer.isView && ArrayBuffer.isView(arr); - arr = bool || isView ? arr : arr.slice(); + let rnd, + tmp, + idx = arr.length; + while (idx > 1) { + rnd = (this.random(0, 1) * idx) | 0; - let rnd, - tmp, - idx = arr.length; - while (idx > 1) { - rnd = (this.random(0, 1) * idx) | 0; + tmp = arr[--idx]; + arr[idx] = arr[rnd]; + arr[rnd] = tmp; + } - tmp = arr[--idx]; - arr[idx] = arr[rnd]; - arr[rnd] = tmp; - } + return arr; + }; - return arr; -}; + /** + * Sorts an array of numbers from smallest to largest, or puts an array of + * words in alphabetical order. The original array is not modified; a + * re-ordered array is returned. The count parameter states the number of + * elements to sort. For example, if there are 12 elements in an array and + * count is set to 5, only the first 5 elements in the array will be sorted. + * + * @method sort + * @deprecated Use array.sort() instead. + * @param {Array} list Array to sort + * @param {Integer} [count] number of elements to sort, starting from 0 + * @return {Array} the sorted list + * + * @example + *
    + * function setup() { + * let words = ['banana', 'apple', 'pear', 'lime']; + * print(words); // ['banana', 'apple', 'pear', 'lime'] + * let count = 4; // length of array + * + * words = sort(words, count); + * print(words); // ['apple', 'banana', 'lime', 'pear'] + * } + *
    + *
    + * function setup() { + * let numbers = [2, 6, 1, 5, 14, 9, 8, 12]; + * print(numbers); // [2, 6, 1, 5, 14, 9, 8, 12] + * let count = 5; // Less than the length of the array + * + * numbers = sort(numbers, count); + * print(numbers); // [1,2,5,6,14,9,8,12] + * } + *
    + */ + fn.sort = function (list, count) { + let arr = count ? list.slice(0, Math.min(count, list.length)) : list; + const rest = count ? list.slice(Math.min(count, list.length)) : []; + if (typeof arr[0] === 'string') { + arr = arr.sort(); + } else { + arr = arr.sort((a, b) => a - b); + } + return arr.concat(rest); + }; -/** - * Sorts an array of numbers from smallest to largest, or puts an array of - * words in alphabetical order. The original array is not modified; a - * re-ordered array is returned. The count parameter states the number of - * elements to sort. For example, if there are 12 elements in an array and - * count is set to 5, only the first 5 elements in the array will be sorted. - * - * @method sort - * @deprecated Use array.sort() instead. - * @param {Array} list Array to sort - * @param {Integer} [count] number of elements to sort, starting from 0 - * @return {Array} the sorted list - * - * @example - *
    - * function setup() { - * let words = ['banana', 'apple', 'pear', 'lime']; - * print(words); // ['banana', 'apple', 'pear', 'lime'] - * let count = 4; // length of array - * - * words = sort(words, count); - * print(words); // ['apple', 'banana', 'lime', 'pear'] - * } - *
    - *
    - * function setup() { - * let numbers = [2, 6, 1, 5, 14, 9, 8, 12]; - * print(numbers); // [2, 6, 1, 5, 14, 9, 8, 12] - * let count = 5; // Less than the length of the array - * - * numbers = sort(numbers, count); - * print(numbers); // [1,2,5,6,14,9,8,12] - * } - *
    - */ -p5.prototype.sort = function (list, count) { - let arr = count ? list.slice(0, Math.min(count, list.length)) : list; - const rest = count ? list.slice(Math.min(count, list.length)) : []; - if (typeof arr[0] === 'string') { - arr = arr.sort(); - } else { - arr = arr.sort((a, b) => a - b); - } - return arr.concat(rest); -}; + /** + * Inserts a value or an array of values into an existing array. The first + * parameter specifies the initial array to be modified, and the second + * parameter defines the data to be inserted. The third parameter is an index + * value which specifies the array position from which to insert data. + * (Remember that array index numbering starts at zero, so the first position + * is 0, the second position is 1, and so on.) + * + * @method splice + * @deprecated Use array.splice() instead. + * @param {Array} list Array to splice into + * @param {Any} value value to be spliced in + * @param {Integer} position in the array from which to insert data + * @return {Array} the list + * + * @example + *
    + * function setup() { + * let myArray = [0, 1, 2, 3, 4]; + * let insArray = ['A', 'B', 'C']; + * print(myArray); // [0, 1, 2, 3, 4] + * print(insArray); // ['A','B','C'] + * + * splice(myArray, insArray, 3); + * print(myArray); // [0,1,2,'A','B','C',3,4] + * } + *
    + */ + fn.splice = function (list, value, index) { + // note that splice returns spliced elements and not an array + Array.prototype.splice.apply(list, [index, 0].concat(value)); -/** - * Inserts a value or an array of values into an existing array. The first - * parameter specifies the initial array to be modified, and the second - * parameter defines the data to be inserted. The third parameter is an index - * value which specifies the array position from which to insert data. - * (Remember that array index numbering starts at zero, so the first position - * is 0, the second position is 1, and so on.) - * - * @method splice - * @deprecated Use array.splice() instead. - * @param {Array} list Array to splice into - * @param {Any} value value to be spliced in - * @param {Integer} position in the array from which to insert data - * @return {Array} the list - * - * @example - *
    - * function setup() { - * let myArray = [0, 1, 2, 3, 4]; - * let insArray = ['A', 'B', 'C']; - * print(myArray); // [0, 1, 2, 3, 4] - * print(insArray); // ['A','B','C'] - * - * splice(myArray, insArray, 3); - * print(myArray); // [0,1,2,'A','B','C',3,4] - * } - *
    - */ -p5.prototype.splice = function (list, value, index) { - // note that splice returns spliced elements and not an array - Array.prototype.splice.apply(list, [index, 0].concat(value)); + return list; + }; - return list; -}; + /** + * Extracts an array of elements from an existing array. The list parameter + * defines the array from which the elements will be copied, and the start + * and count parameters specify which elements to extract. If no count is + * given, elements will be extracted from the start to the end of the array. + * When specifying the start, remember that the first array element is 0. + * This function does not change the source array. + * + * @method subset + * @deprecated Use array.slice() instead. + * @param {Array} list Array to extract from + * @param {Integer} start position to begin + * @param {Integer} [count] number of values to extract + * @return {Array} Array of extracted elements + * + * @example + *
    + * function setup() { + * let myArray = [1, 2, 3, 4, 5]; + * print(myArray); // [1, 2, 3, 4, 5] + * + * let sub1 = subset(myArray, 0, 3); + * let sub2 = subset(myArray, 2, 2); + * print(sub1); // [1,2,3] + * print(sub2); // [3,4] + * } + *
    + */ + fn.subset = function (list, start, count) { + if (typeof count !== 'undefined') { + return list.slice(start, start + count); + } else { + return list.slice(start, list.length); + } + }; +} -/** - * Extracts an array of elements from an existing array. The list parameter - * defines the array from which the elements will be copied, and the start - * and count parameters specify which elements to extract. If no count is - * given, elements will be extracted from the start to the end of the array. - * When specifying the start, remember that the first array element is 0. - * This function does not change the source array. - * - * @method subset - * @deprecated Use array.slice() instead. - * @param {Array} list Array to extract from - * @param {Integer} start position to begin - * @param {Integer} [count] number of values to extract - * @return {Array} Array of extracted elements - * - * @example - *
    - * function setup() { - * let myArray = [1, 2, 3, 4, 5]; - * print(myArray); // [1, 2, 3, 4, 5] - * - * let sub1 = subset(myArray, 0, 3); - * let sub2 = subset(myArray, 2, 2); - * print(sub1); // [1,2,3] - * print(sub2); // [3,4] - * } - *
    - */ -p5.prototype.subset = function (list, start, count) { - if (typeof count !== 'undefined') { - return list.slice(start, start + count); - } else { - return list.slice(start, list.length); - } -}; +export default arrayFunctions; -export default p5; +if(typeof p5 !== 'undefined'){ + arrayFunctions(p5, p5.prototype); +} diff --git a/src/utilities/conversion.js b/src/utilities/conversion.js index f0524ec6f5..84254e83b5 100644 --- a/src/utilities/conversion.js +++ b/src/utilities/conversion.js @@ -5,1042 +5,1046 @@ * @requires core */ -import p5 from '../core/main'; - -/** - * Converts a `String` to a floating point (decimal) `Number`. - * - * `float()` converts strings that resemble numbers, such as `'12.34'`, into - * numbers. - * - * The parameter, `str`, is the string value to convert. For example, calling - * `float('12.34')` returns the number `12.34`. If an array of strings is - * passed, as in `float(['12.34', '56.78'])`, then an array of numbers will be - * returned. - * - * Note: If a string can't be converted to a number, as in `float('giraffe')`, - * then the value `NaN` (not a number) will be returned. - * - * @method float - * @param {String} str string to convert. - * @return {Number} converted number. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let original = '12.3'; - * - * // Convert the string to a number. - * let converted = float(original); - * - * // Double the converted value. - * let twice = converted * 2; - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(12); - * - * // Display the original and converted values. - * text(`${original} × 2 = ${twice}`, 50, 50); - * - * describe('The text "12.3 × 2 = 24.6" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of strings. - * let original = ['60', '30', '15']; - * - * // Convert the strings to numbers. - * let diameters = float(original); - * - * for (let d of diameters) { - * // Draw a circle. - * circle(50, 50, d); - * } - * - * describe('Three white, concentric circles on a gray background.'); - * } - * - *
    - */ -/** - * @method float - * @param {String[]} ns array of strings to convert. - * @return {Number[]} converted numbers. - */ -p5.prototype.float = function(str) { - if (str instanceof Array) { - return str.map(parseFloat); - } - return parseFloat(str); -}; - -/** - * Converts a `Boolean`, `String`, or decimal `Number` to an integer. - * - * `int()` converts values to integers. Integers are positive or negative - * numbers without decimals. If the original value has decimals, as in -34.56, - * they're removed to produce an integer such as -34. - * - * The parameter, `n`, is the value to convert. If `n` is a Boolean, as in - * `int(false)` or `int(true)`, then the number 0 (`false`) or 1 (`true`) will - * be returned. If `n` is a string or number, as in `int('45')` or - * `int(67.89)`, then an integer will be returned. If an array is passed, as - * in `int([12.34, 56.78])`, then an array of integers will be returned. - * - * Note: If a value can't be converted to a number, as in `int('giraffe')`, - * then the value `NaN` (not a number) will be returned. - * - * @method int - * @param {String|Boolean|Number} n value to convert. - * @return {Number} converted number. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a Boolean variable. - * let original = false; - * - * // Convert the Boolean to an integer. - * let converted = int(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the original and converted values. - * text(`${original} : ${converted}`, 50, 50); - * - * describe('The text "false : 0" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let original = '12.34'; - * - * // Convert the string to an integer. - * let converted = int(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(14); - * - * // Display the original and converted values. - * text(`${original} ≈ ${converted}`, 50, 50); - * - * describe('The text "12.34 ≈ 12" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a decimal number variable. - * let original = 12.34; - * - * // Convert the decimal number to an integer. - * let converted = int(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(14); - * - * // Display the original and converted values. - * text(`${original} ≈ ${converted}`, 50, 50); - * - * describe('The text "12.34 ≈ 12" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of strings. - * let original = ['60', '30', '15']; - * - * // Convert the strings to integers. - * let diameters = int(original); - * - * for (let d of diameters) { - * // Draw a circle. - * circle(50, 50, d); - * } - * - * describe('Three white, concentric circles on a gray background.'); - * } - * - *
    - */ -/** - * @method int - * @param {Array} ns values to convert. - * @return {Number[]} converted numbers. - */ -p5.prototype.int = function(n, radix = 10) { - if (n === Infinity || n === 'Infinity') { - return Infinity; - } else if (n === -Infinity || n === '-Infinity') { - return -Infinity; - } else if (typeof n === 'string') { - return parseInt(n, radix); - } else if (typeof n === 'number') { - return n | 0; - } else if (typeof n === 'boolean') { - return n ? 1 : 0; - } else if (n instanceof Array) { - return n.map(n => p5.prototype.int(n, radix)); - } -}; +function conversion(p5, fn){ + /** + * Converts a `String` to a floating point (decimal) `Number`. + * + * `float()` converts strings that resemble numbers, such as `'12.34'`, into + * numbers. + * + * The parameter, `str`, is the string value to convert. For example, calling + * `float('12.34')` returns the number `12.34`. If an array of strings is + * passed, as in `float(['12.34', '56.78'])`, then an array of numbers will be + * returned. + * + * Note: If a string can't be converted to a number, as in `float('giraffe')`, + * then the value `NaN` (not a number) will be returned. + * + * @method float + * @param {String} str string to convert. + * @return {Number} converted number. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let original = '12.3'; + * + * // Convert the string to a number. + * let converted = float(original); + * + * // Double the converted value. + * let twice = converted * 2; + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(12); + * + * // Display the original and converted values. + * text(`${original} × 2 = ${twice}`, 50, 50); + * + * describe('The text "12.3 × 2 = 24.6" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of strings. + * let original = ['60', '30', '15']; + * + * // Convert the strings to numbers. + * let diameters = float(original); + * + * for (let d of diameters) { + * // Draw a circle. + * circle(50, 50, d); + * } + * + * describe('Three white, concentric circles on a gray background.'); + * } + * + *
    + */ + /** + * @method float + * @param {String[]} ns array of strings to convert. + * @return {Number[]} converted numbers. + */ + fn.float = function(str) { + if (str instanceof Array) { + return str.map(parseFloat); + } + return parseFloat(str); + }; -/** - * Converts a `Boolean` or `Number` to `String`. - * - * `str()` converts values to strings. See the - * String reference page for guidance on using - * template literals instead. - * - * The parameter, `n`, is the value to convert. If `n` is a Boolean, as in - * `str(false)` or `str(true)`, then the value will be returned as a string, - * as in `'false'` or `'true'`. If `n` is a number, as in `str(123)`, then its - * value will be returned as a string, as in `'123'`. If an array is passed, - * as in `str([12.34, 56.78])`, then an array of strings will be returned. - * - * @method str - * @param {String|Boolean|Number} n value to convert. - * @return {String} converted string. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a Boolean variable. - * let original = false; - * - * // Convert the Boolean to a string. - * let converted = str(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the original and converted values. - * text(`${original} : ${converted}`, 50, 50); - * - * describe('The text "false : false" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a number variable. - * let original = 123; - * - * // Convert the number to a string. - * let converted = str(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the original and converted values. - * text(`${original} = ${converted}`, 50, 50); - * - * describe('The text "123 = 123" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of numbers. - * let original = [12, 34, 56]; - * - * // Convert the numbers to strings. - * let strings = str(original); - * - * // Create an empty string variable. - * let final = ''; - * - * // Concatenate all the strings. - * for (let s of strings) { - * final += s; - * } - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the concatenated string. - * text(final, 50, 50); - * - * describe('The text "123456" written in black on a gray background.'); - * } - * - *
    - */ -p5.prototype.str = function(n) { - if (n instanceof Array) { - return n.map(p5.prototype.str); - } else { - return String(n); - } -}; + /** + * Converts a `Boolean`, `String`, or decimal `Number` to an integer. + * + * `int()` converts values to integers. Integers are positive or negative + * numbers without decimals. If the original value has decimals, as in -34.56, + * they're removed to produce an integer such as -34. + * + * The parameter, `n`, is the value to convert. If `n` is a Boolean, as in + * `int(false)` or `int(true)`, then the number 0 (`false`) or 1 (`true`) will + * be returned. If `n` is a string or number, as in `int('45')` or + * `int(67.89)`, then an integer will be returned. If an array is passed, as + * in `int([12.34, 56.78])`, then an array of integers will be returned. + * + * Note: If a value can't be converted to a number, as in `int('giraffe')`, + * then the value `NaN` (not a number) will be returned. + * + * @method int + * @param {String|Boolean|Number} n value to convert. + * @return {Number} converted number. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a Boolean variable. + * let original = false; + * + * // Convert the Boolean to an integer. + * let converted = int(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the original and converted values. + * text(`${original} : ${converted}`, 50, 50); + * + * describe('The text "false : 0" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let original = '12.34'; + * + * // Convert the string to an integer. + * let converted = int(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(14); + * + * // Display the original and converted values. + * text(`${original} ≈ ${converted}`, 50, 50); + * + * describe('The text "12.34 ≈ 12" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a decimal number variable. + * let original = 12.34; + * + * // Convert the decimal number to an integer. + * let converted = int(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(14); + * + * // Display the original and converted values. + * text(`${original} ≈ ${converted}`, 50, 50); + * + * describe('The text "12.34 ≈ 12" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of strings. + * let original = ['60', '30', '15']; + * + * // Convert the strings to integers. + * let diameters = int(original); + * + * for (let d of diameters) { + * // Draw a circle. + * circle(50, 50, d); + * } + * + * describe('Three white, concentric circles on a gray background.'); + * } + * + *
    + */ + /** + * @method int + * @param {Array} ns values to convert. + * @return {Number[]} converted numbers. + */ + fn.int = function(n, radix = 10) { + if (n === Infinity || n === 'Infinity') { + return Infinity; + } else if (n === -Infinity || n === '-Infinity') { + return -Infinity; + } else if (typeof n === 'string') { + return parseInt(n, radix); + } else if (typeof n === 'number') { + return n | 0; + } else if (typeof n === 'boolean') { + return n ? 1 : 0; + } else if (n instanceof Array) { + return n.map(n => fn.int(n, radix)); + } + }; -/** - * Converts a `String` or `Number` to a `Boolean`. - * - * `boolean()` converts values to `true` or `false`. - * - * The parameter, `n`, is the value to convert. If `n` is a string, then - * `boolean('true')` will return `true` and every other string value will - * return `false`. If `n` is a number, then `boolean(0)` will return `false` - * and every other numeric value will return `true`. If an array is passed, as - * `in boolean([0, 1, 'true', 'blue'])`, then an array of Boolean values will - * be returned. - * - * @method boolean - * @param {String|Boolean|Number} n value to convert. - * @return {Boolean} converted Boolean value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a number variable. - * let original = 0; - * - * // Convert the number to a Boolean value. - * let converted = boolean(original); - * - * // Style the circle based on the converted value. - * if (converted === true) { - * fill('blue'); - * } else { - * fill('red'); - * } - * - * // Draw the circle. - * circle(50, 50, 40); - * - * describe('A red circle on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let original = 'true'; - * - * // Convert the string to a Boolean value. - * let converted = boolean(original); - * - * // Style the circle based on the converted value. - * if (converted === true) { - * fill('blue'); - * } else { - * fill('red'); - * } - * - * // Draw the circle. - * circle(50, 50, 40); - * - * describe('A blue circle on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of values. - * let original = [0, 'hi', 123, 'true']; - * - * // Convert the array to a Boolean values. - * let converted = boolean(original); - * - * // Iterate over the array of converted Boolean values. - * for (let i = 0; i < converted.length; i += 1) { - * - * // Style the circle based on the converted value. - * if (converted[i] === true) { - * fill('blue'); - * } else { - * fill('red'); - * } - * - * // Calculate the x-coordinate. - * let x = (i + 1) * 20; - * - * // Draw the circle. - * circle(x, 50, 15); - * } - * - * describe( - * 'A row of circles on a gray background. The two circles on the left are red and the two on the right are blue.' - * ); - * } - * - *
    - */ -/** - * @method boolean - * @param {Array} ns values to convert. - * @return {Boolean[]} converted Boolean values. - */ -p5.prototype.boolean = function(n) { - if (typeof n === 'number') { - return n !== 0; - } else if (typeof n === 'string') { - return n.toLowerCase() === 'true'; - } else if (typeof n === 'boolean') { - return n; - } else if (n instanceof Array) { - return n.map(p5.prototype.boolean); - } -}; + /** + * Converts a `Boolean` or `Number` to `String`. + * + * `str()` converts values to strings. See the + * String reference page for guidance on using + * template literals instead. + * + * The parameter, `n`, is the value to convert. If `n` is a Boolean, as in + * `str(false)` or `str(true)`, then the value will be returned as a string, + * as in `'false'` or `'true'`. If `n` is a number, as in `str(123)`, then its + * value will be returned as a string, as in `'123'`. If an array is passed, + * as in `str([12.34, 56.78])`, then an array of strings will be returned. + * + * @method str + * @param {String|Boolean|Number} n value to convert. + * @return {String} converted string. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a Boolean variable. + * let original = false; + * + * // Convert the Boolean to a string. + * let converted = str(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the original and converted values. + * text(`${original} : ${converted}`, 50, 50); + * + * describe('The text "false : false" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a number variable. + * let original = 123; + * + * // Convert the number to a string. + * let converted = str(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the original and converted values. + * text(`${original} = ${converted}`, 50, 50); + * + * describe('The text "123 = 123" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of numbers. + * let original = [12, 34, 56]; + * + * // Convert the numbers to strings. + * let strings = str(original); + * + * // Create an empty string variable. + * let final = ''; + * + * // Concatenate all the strings. + * for (let s of strings) { + * final += s; + * } + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the concatenated string. + * text(final, 50, 50); + * + * describe('The text "123456" written in black on a gray background.'); + * } + * + *
    + */ + fn.str = function(n) { + if (n instanceof Array) { + return n.map(fn.str); + } else { + return String(n); + } + }; -/** - * Converts a `Boolean`, `String`, or `Number` to its byte value. - * - * `byte()` converts a value to an integer (whole number) between -128 and - * 127. Values greater than 127 wrap around while negative values are - * unchanged. For example, 128 becomes -128 and -129 remains the same. - * - * The parameter, `n`, is the value to convert. If `n` is a Boolean, as in - * `byte(false)` or `byte(true)`, the number 0 (`false`) or 1 (`true`) will be - * returned. If `n` is a string or number, as in `byte('256')` or `byte(256)`, - * then the byte value will be returned. Decimal values are ignored. If an - * array is passed, as in `byte([true, 123, '456'])`, then an array of byte - * values will be returned. - * - * Note: If a value can't be converted to a number, as in `byte('giraffe')`, - * then the value `NaN` (not a number) will be returned. - * - * @method byte - * @param {String|Boolean|Number} n value to convert. - * @return {Number} converted byte value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a Boolean variable. - * let original = true; - * - * // Convert the Boolean to its byte value. - * let converted = byte(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the original and converted values. - * text(`${original} : ${converted}`, 50, 50); - * - * describe('The text "true : 1" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let original = '256'; - * - * // Convert the string to its byte value. - * let converted = byte(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the original and converted values. - * text(`${original} : ${converted}`, 50, 50); - * - * describe('The text "256 : 0" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a number variable. - * let original = 256; - * - * // Convert the number to its byte value. - * let converted = byte(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the original and converted values. - * text(`${original} : ${converted}`, 50, 50); - * - * describe('The text "256 : 0" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of values. - * let original = [false, '64', 383]; - * - * // Convert the array elements to their byte values. - * let converted = byte(original); - * - * // Iterate over the converted array elements. - * for (let i = 0; i < converted.length; i += 1) { - * - * // Style the circle. - * fill(converted[i]); - * - * // Calculate the x-coordinate. - * let x = (i + 1) * 25; - * - * // Draw the circle. - * circle(x, 50, 20); - * } - * - * describe( - * 'Three gray circles on a gray background. The circles get lighter from left to right.' - * ); - * } - * - *
    - */ -/** - * @method byte - * @param {Array} ns values to convert. - * @return {Number[]} converted byte values. - */ -p5.prototype.byte = function(n) { - const nn = p5.prototype.int(n, 10); - if (typeof nn === 'number') { - return (nn + 128) % 256 - 128; - } else if (nn instanceof Array) { - return nn.map(p5.prototype.byte); - } -}; + /** + * Converts a `String` or `Number` to a `Boolean`. + * + * `boolean()` converts values to `true` or `false`. + * + * The parameter, `n`, is the value to convert. If `n` is a string, then + * `boolean('true')` will return `true` and every other string value will + * return `false`. If `n` is a number, then `boolean(0)` will return `false` + * and every other numeric value will return `true`. If an array is passed, as + * `in boolean([0, 1, 'true', 'blue'])`, then an array of Boolean values will + * be returned. + * + * @method boolean + * @param {String|Boolean|Number} n value to convert. + * @return {Boolean} converted Boolean value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a number variable. + * let original = 0; + * + * // Convert the number to a Boolean value. + * let converted = boolean(original); + * + * // Style the circle based on the converted value. + * if (converted === true) { + * fill('blue'); + * } else { + * fill('red'); + * } + * + * // Draw the circle. + * circle(50, 50, 40); + * + * describe('A red circle on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let original = 'true'; + * + * // Convert the string to a Boolean value. + * let converted = boolean(original); + * + * // Style the circle based on the converted value. + * if (converted === true) { + * fill('blue'); + * } else { + * fill('red'); + * } + * + * // Draw the circle. + * circle(50, 50, 40); + * + * describe('A blue circle on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of values. + * let original = [0, 'hi', 123, 'true']; + * + * // Convert the array to a Boolean values. + * let converted = boolean(original); + * + * // Iterate over the array of converted Boolean values. + * for (let i = 0; i < converted.length; i += 1) { + * + * // Style the circle based on the converted value. + * if (converted[i] === true) { + * fill('blue'); + * } else { + * fill('red'); + * } + * + * // Calculate the x-coordinate. + * let x = (i + 1) * 20; + * + * // Draw the circle. + * circle(x, 50, 15); + * } + * + * describe( + * 'A row of circles on a gray background. The two circles on the left are red and the two on the right are blue.' + * ); + * } + * + *
    + */ + /** + * @method boolean + * @param {Array} ns values to convert. + * @return {Boolean[]} converted Boolean values. + */ + fn.boolean = function(n) { + if (typeof n === 'number') { + return n !== 0; + } else if (typeof n === 'string') { + return n.toLowerCase() === 'true'; + } else if (typeof n === 'boolean') { + return n; + } else if (n instanceof Array) { + return n.map(fn.boolean); + } + }; -/** - * Converts a `Number` or `String` to a single-character `String`. - * - * `char()` converts numbers to their single-character string representations. - * - * The parameter, `n`, is the value to convert. If a number is passed, as in - * `char(65)`, the corresponding single-character string is returned. If a - * string is passed, as in `char('65')`, the string is converted to an integer - * (whole number) and the corresponding single-character string is returned. - * If an array is passed, as in `char([65, 66, 67])`, an array of - * single-character strings is returned. - * - * See MDN - * for more information about conversions. - * - * @method char - * @param {String|Number} n value to convert. - * @return {String} converted single-character string. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a number variable. - * let original = 65; - * - * // Convert the number to a char. - * let converted = char(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the original and converted values. - * text(`${original} : ${converted}`, 50, 50); - * - * describe('The text "65 : A" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let original = '65'; - * - * // Convert the string to a char. - * let converted = char(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the original and converted values. - * text(`${original} : ${converted}`, 50, 50); - * - * describe('The text "65 : A" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of numbers. - * let original = ['65', 66, '67']; - * - * // Convert the string to a char. - * let converted = char(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Iterate over elements of the converted array. - * for (let i = 0; i < converted.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 25; - * - * // Display the original and converted values. - * text(`${original[i]} : ${converted[i]}`, 50, y); - * } - * - * describe( - * 'The text "65 : A", "66 : B", and "67 : C" written on three separate lines. The text is in black on a gray background.' - * ); - * } - * - *
    - */ -/** - * @method char - * @param {Array} ns values to convert. - * @return {String[]} converted single-character strings. - */ -p5.prototype.char = function(n) { - if (typeof n === 'number' && !isNaN(n)) { - return String.fromCharCode(n); - } else if (n instanceof Array) { - return n.map(p5.prototype.char); - } else if (typeof n === 'string') { - return p5.prototype.char(parseInt(n, 10)); - } -}; + /** + * Converts a `Boolean`, `String`, or `Number` to its byte value. + * + * `byte()` converts a value to an integer (whole number) between -128 and + * 127. Values greater than 127 wrap around while negative values are + * unchanged. For example, 128 becomes -128 and -129 remains the same. + * + * The parameter, `n`, is the value to convert. If `n` is a Boolean, as in + * `byte(false)` or `byte(true)`, the number 0 (`false`) or 1 (`true`) will be + * returned. If `n` is a string or number, as in `byte('256')` or `byte(256)`, + * then the byte value will be returned. Decimal values are ignored. If an + * array is passed, as in `byte([true, 123, '456'])`, then an array of byte + * values will be returned. + * + * Note: If a value can't be converted to a number, as in `byte('giraffe')`, + * then the value `NaN` (not a number) will be returned. + * + * @method byte + * @param {String|Boolean|Number} n value to convert. + * @return {Number} converted byte value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a Boolean variable. + * let original = true; + * + * // Convert the Boolean to its byte value. + * let converted = byte(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the original and converted values. + * text(`${original} : ${converted}`, 50, 50); + * + * describe('The text "true : 1" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let original = '256'; + * + * // Convert the string to its byte value. + * let converted = byte(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the original and converted values. + * text(`${original} : ${converted}`, 50, 50); + * + * describe('The text "256 : 0" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a number variable. + * let original = 256; + * + * // Convert the number to its byte value. + * let converted = byte(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the original and converted values. + * text(`${original} : ${converted}`, 50, 50); + * + * describe('The text "256 : 0" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of values. + * let original = [false, '64', 383]; + * + * // Convert the array elements to their byte values. + * let converted = byte(original); + * + * // Iterate over the converted array elements. + * for (let i = 0; i < converted.length; i += 1) { + * + * // Style the circle. + * fill(converted[i]); + * + * // Calculate the x-coordinate. + * let x = (i + 1) * 25; + * + * // Draw the circle. + * circle(x, 50, 20); + * } + * + * describe( + * 'Three gray circles on a gray background. The circles get lighter from left to right.' + * ); + * } + * + *
    + */ + /** + * @method byte + * @param {Array} ns values to convert. + * @return {Number[]} converted byte values. + */ + fn.byte = function(n) { + const nn = fn.int(n, 10); + if (typeof nn === 'number') { + return (nn + 128) % 256 - 128; + } else if (nn instanceof Array) { + return nn.map(fn.byte); + } + }; -/** - * Converts a single-character `String` to a `Number`. - * - * `unchar()` converts single-character strings to their corresponding - * integer (whole number). - * - * The parameter, `n`, is the character to convert. For example, - * `unchar('A')`, returns the number 65. If an array is passed, as in - * `unchar(['A', 'B', 'C'])`, an array of integers is returned. - * - * @method unchar - * @param {String} n value to convert. - * @return {Number} converted number. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let original = 'A'; - * - * // Convert the string to a number. - * let converted = unchar(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the original and converted values. - * text(`${original} : ${converted}`, 50, 50); - * - * describe('The text "A : 65" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of characters. - * let original = ['A', 'B', 'C']; - * - * // Convert the string to a number. - * let converted = unchar(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Iterate over elements of the converted array. - * for (let i = 0; i < converted.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 25; - * - * // Display the original and converted values. - * text(`${original[i]} : ${converted[i]}`, 50, y); - * } - * - * describe( - * 'The text "A : 65", "B : 66", and "C :67" written on three separate lines. The text is in black on a gray background.' - * ); - * } - * - *
    - */ -/** - * @method unchar - * @param {String[]} ns values to convert. - * @return {Number[]} converted numbers. - */ -p5.prototype.unchar = function(n) { - if (typeof n === 'string' && n.length === 1) { - return n.charCodeAt(0); - } else if (n instanceof Array) { - return n.map(p5.prototype.unchar); - } -}; + /** + * Converts a `Number` or `String` to a single-character `String`. + * + * `char()` converts numbers to their single-character string representations. + * + * The parameter, `n`, is the value to convert. If a number is passed, as in + * `char(65)`, the corresponding single-character string is returned. If a + * string is passed, as in `char('65')`, the string is converted to an integer + * (whole number) and the corresponding single-character string is returned. + * If an array is passed, as in `char([65, 66, 67])`, an array of + * single-character strings is returned. + * + * See MDN + * for more information about conversions. + * + * @method char + * @param {String|Number} n value to convert. + * @return {String} converted single-character string. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a number variable. + * let original = 65; + * + * // Convert the number to a char. + * let converted = char(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the original and converted values. + * text(`${original} : ${converted}`, 50, 50); + * + * describe('The text "65 : A" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let original = '65'; + * + * // Convert the string to a char. + * let converted = char(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the original and converted values. + * text(`${original} : ${converted}`, 50, 50); + * + * describe('The text "65 : A" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of numbers. + * let original = ['65', 66, '67']; + * + * // Convert the string to a char. + * let converted = char(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Iterate over elements of the converted array. + * for (let i = 0; i < converted.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 25; + * + * // Display the original and converted values. + * text(`${original[i]} : ${converted[i]}`, 50, y); + * } + * + * describe( + * 'The text "65 : A", "66 : B", and "67 : C" written on three separate lines. The text is in black on a gray background.' + * ); + * } + * + *
    + */ + /** + * @method char + * @param {Array} ns values to convert. + * @return {String[]} converted single-character strings. + */ + fn.char = function(n) { + if (typeof n === 'number' && !isNaN(n)) { + return String.fromCharCode(n); + } else if (n instanceof Array) { + return n.map(fn.char); + } else if (typeof n === 'string') { + return fn.char(parseInt(n, 10)); + } + }; -/** - * Converts a `Number` to a `String` with its hexadecimal value. - * - * `hex()` converts a number to a string with its hexadecimal number value. - * Hexadecimal (hex) numbers are base-16, which means there are 16 unique - * digits. Hex extends the numbers 0–9 with the letters A–F. For example, the - * number `11` (eleven) in base-10 is written as the letter `B` in hex. - * - * The first parameter, `n`, is the number to convert. For example, `hex(20)`, - * returns the string `'00000014'`. If an array is passed, as in - * `hex([1, 10, 100])`, an array of hexadecimal strings is returned. - * - * The second parameter, `digits`, is optional. If a number is passed, as in - * `hex(20, 2)`, it sets the number of hexadecimal digits to display. For - * example, calling `hex(20, 2)` returns the string `'14'`. - * - * @method hex - * @param {Number} n value to convert. - * @param {Number} [digits] number of digits to include. - * @return {String} converted hexadecimal value. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a number variable. - * let original = 20; - * - * // Convert the number to a hex string. - * let converted = hex(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(14); - * - * // Display the original and converted values. - * text(`${original} = ${converted}`, 50, 50); - * - * describe('The text "20 = 00000014" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a number variable. - * let original = 20; - * - * // Convert the number to a hex string. - * // Only display two hex digits. - * let converted = hex(original, 2); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the original and converted values. - * text(`${original} = ${converted}`, 50, 50); - * - * describe('The text "20 = 14" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of numbers. - * let original = [1, 10, 100]; - * - * // Convert the numbers to hex strings. - * // Only use two hex digits. - * let converted = hex(original, 2); - * - * // Style the text. - * textAlign(RIGHT, CENTER); - * textSize(16); - * - * // Iterate over the converted values. - * for (let i = 0; i < converted.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 25; - * - * // Display the original and converted values. - * text(`${ original[i]} = ${converted[i]}`, 75, y); - * } - * - * describe( - * 'The text "1 = 01", "10 = 0A", and "100 = 64" written on three separate lines. The text is in black on a gray background.' - * ); - * } - * - *
    - */ -/** - * @method hex - * @param {Number[]} ns values to convert. - * @param {Number} [digits] - * @return {String[]} converted hexadecimal values. - */ -p5.prototype.hex = function(n, digits) { - digits = digits === undefined || digits === null ? (digits = 8) : digits; - if (n instanceof Array) { - return n.map(n => p5.prototype.hex(n, digits)); - } else if (n === Infinity || n === -Infinity) { - const c = n === Infinity ? 'F' : '0'; - return c.repeat(digits); - } else if (typeof n === 'number') { - if (n < 0) { - n = 0xffffffff + n + 1; + /** + * Converts a single-character `String` to a `Number`. + * + * `unchar()` converts single-character strings to their corresponding + * integer (whole number). + * + * The parameter, `n`, is the character to convert. For example, + * `unchar('A')`, returns the number 65. If an array is passed, as in + * `unchar(['A', 'B', 'C'])`, an array of integers is returned. + * + * @method unchar + * @param {String} n value to convert. + * @return {Number} converted number. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let original = 'A'; + * + * // Convert the string to a number. + * let converted = unchar(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the original and converted values. + * text(`${original} : ${converted}`, 50, 50); + * + * describe('The text "A : 65" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of characters. + * let original = ['A', 'B', 'C']; + * + * // Convert the string to a number. + * let converted = unchar(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Iterate over elements of the converted array. + * for (let i = 0; i < converted.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 25; + * + * // Display the original and converted values. + * text(`${original[i]} : ${converted[i]}`, 50, y); + * } + * + * describe( + * 'The text "A : 65", "B : 66", and "C :67" written on three separate lines. The text is in black on a gray background.' + * ); + * } + * + *
    + */ + /** + * @method unchar + * @param {String[]} ns values to convert. + * @return {Number[]} converted numbers. + */ + fn.unchar = function(n) { + if (typeof n === 'string' && n.length === 1) { + return n.charCodeAt(0); + } else if (n instanceof Array) { + return n.map(fn.unchar); } - let hex = Number(n) - .toString(16) - .toUpperCase(); - while (hex.length < digits) { - hex = `0${hex}`; + }; + + /** + * Converts a `Number` to a `String` with its hexadecimal value. + * + * `hex()` converts a number to a string with its hexadecimal number value. + * Hexadecimal (hex) numbers are base-16, which means there are 16 unique + * digits. Hex extends the numbers 0–9 with the letters A–F. For example, the + * number `11` (eleven) in base-10 is written as the letter `B` in hex. + * + * The first parameter, `n`, is the number to convert. For example, `hex(20)`, + * returns the string `'00000014'`. If an array is passed, as in + * `hex([1, 10, 100])`, an array of hexadecimal strings is returned. + * + * The second parameter, `digits`, is optional. If a number is passed, as in + * `hex(20, 2)`, it sets the number of hexadecimal digits to display. For + * example, calling `hex(20, 2)` returns the string `'14'`. + * + * @method hex + * @param {Number} n value to convert. + * @param {Number} [digits] number of digits to include. + * @return {String} converted hexadecimal value. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a number variable. + * let original = 20; + * + * // Convert the number to a hex string. + * let converted = hex(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(14); + * + * // Display the original and converted values. + * text(`${original} = ${converted}`, 50, 50); + * + * describe('The text "20 = 00000014" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a number variable. + * let original = 20; + * + * // Convert the number to a hex string. + * // Only display two hex digits. + * let converted = hex(original, 2); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the original and converted values. + * text(`${original} = ${converted}`, 50, 50); + * + * describe('The text "20 = 14" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of numbers. + * let original = [1, 10, 100]; + * + * // Convert the numbers to hex strings. + * // Only use two hex digits. + * let converted = hex(original, 2); + * + * // Style the text. + * textAlign(RIGHT, CENTER); + * textSize(16); + * + * // Iterate over the converted values. + * for (let i = 0; i < converted.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 25; + * + * // Display the original and converted values. + * text(`${ original[i]} = ${converted[i]}`, 75, y); + * } + * + * describe( + * 'The text "1 = 01", "10 = 0A", and "100 = 64" written on three separate lines. The text is in black on a gray background.' + * ); + * } + * + *
    + */ + /** + * @method hex + * @param {Number[]} ns values to convert. + * @param {Number} [digits] + * @return {String[]} converted hexadecimal values. + */ + fn.hex = function(n, digits) { + digits = digits === undefined || digits === null ? (digits = 8) : digits; + if (n instanceof Array) { + return n.map(n => fn.hex(n, digits)); + } else if (n === Infinity || n === -Infinity) { + const c = n === Infinity ? 'F' : '0'; + return c.repeat(digits); + } else if (typeof n === 'number') { + if (n < 0) { + n = 0xffffffff + n + 1; + } + let hex = Number(n) + .toString(16) + .toUpperCase(); + while (hex.length < digits) { + hex = `0${hex}`; + } + if (hex.length >= digits) { + hex = hex.substring(hex.length - digits, hex.length); + } + return hex; } - if (hex.length >= digits) { - hex = hex.substring(hex.length - digits, hex.length); + }; + + /** + * Converts a `String` with a hexadecimal value to a `Number`. + * + * `unhex()` converts a string with its hexadecimal number value to a number. + * Hexadecimal (hex) numbers are base-16, which means there are 16 unique + * digits. Hex extends the numbers 0–9 with the letters A–F. For example, the + * number `11` (eleven) in base-10 is written as the letter `B` in hex. + * + * The first parameter, `n`, is the hex string to convert. For example, + * `unhex('FF')`, returns the number 255. If an array is passed, as in + * `unhex(['00', '80', 'FF'])`, an array of numbers is returned. + * + * @method unhex + * @param {String} n value to convert. + * @return {Number} converted number. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a a hex string variable + * let original = 'FF'; + * + * // Convert the hex string to a number. + * let converted = unhex(original); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the original and converted values. + * text(`${original} = ${converted}`, 50, 50); + * + * describe('The text "FF = 255" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of numbers. + * let original = ['00', '80', 'FF']; + * + * // Convert the numbers to hex strings. + * // Only use two hex digits. + * let converted = unhex(original, 2); + * + * // Style the text. + * textAlign(RIGHT, CENTER); + * textSize(16); + * + * // Iterate over the converted values. + * for (let i = 0; i < converted.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 25; + * + * // Display the original and converted values. + * text(`${ original[i]} = ${converted[i]}`, 80, y); + * } + * + * describe( + * 'The text "00 = 0", "80 = 128", and "FF = 255" written on three separate lines. The text is in black on a gray background.' + * ); + * } + * + *
    + */ + /** + * @method unhex + * @param {String[]} ns values to convert. + * @return {Number[]} converted numbers. + */ + fn.unhex = function(n) { + if (n instanceof Array) { + return n.map(fn.unhex); + } else { + return parseInt(`0x${n}`, 16); } - return hex; - } -}; + }; +} -/** - * Converts a `String` with a hexadecimal value to a `Number`. - * - * `unhex()` converts a string with its hexadecimal number value to a number. - * Hexadecimal (hex) numbers are base-16, which means there are 16 unique - * digits. Hex extends the numbers 0–9 with the letters A–F. For example, the - * number `11` (eleven) in base-10 is written as the letter `B` in hex. - * - * The first parameter, `n`, is the hex string to convert. For example, - * `unhex('FF')`, returns the number 255. If an array is passed, as in - * `unhex(['00', '80', 'FF'])`, an array of numbers is returned. - * - * @method unhex - * @param {String} n value to convert. - * @return {Number} converted number. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a a hex string variable - * let original = 'FF'; - * - * // Convert the hex string to a number. - * let converted = unhex(original); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the original and converted values. - * text(`${original} = ${converted}`, 50, 50); - * - * describe('The text "FF = 255" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of numbers. - * let original = ['00', '80', 'FF']; - * - * // Convert the numbers to hex strings. - * // Only use two hex digits. - * let converted = unhex(original, 2); - * - * // Style the text. - * textAlign(RIGHT, CENTER); - * textSize(16); - * - * // Iterate over the converted values. - * for (let i = 0; i < converted.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 25; - * - * // Display the original and converted values. - * text(`${ original[i]} = ${converted[i]}`, 80, y); - * } - * - * describe( - * 'The text "00 = 0", "80 = 128", and "FF = 255" written on three separate lines. The text is in black on a gray background.' - * ); - * } - * - *
    - */ -/** - * @method unhex - * @param {String[]} ns values to convert. - * @return {Number[]} converted numbers. - */ -p5.prototype.unhex = function(n) { - if (n instanceof Array) { - return n.map(p5.prototype.unhex); - } else { - return parseInt(`0x${n}`, 16); - } -}; +export default conversion; -export default p5; +if(typeof p5 !== 'undefined'){ + conversion(p5, p5.prototype); +} diff --git a/src/utilities/index.js b/src/utilities/index.js new file mode 100644 index 0000000000..b05893fb44 --- /dev/null +++ b/src/utilities/index.js @@ -0,0 +1,11 @@ +import arrayFunctions from './array_functions.js'; +import conversion from './conversion.js'; +import stringFunctions from './string_functions.js'; +import timeDate from './time_date.js'; + +export default function(p5){ + p5.registerAddon(arrayFunctions); + p5.registerAddon(conversion); + p5.registerAddon(stringFunctions); + p5.registerAddon(timeDate); +} diff --git a/src/utilities/string_functions.js b/src/utilities/string_functions.js index 9da5871826..07bb5dc073 100644 --- a/src/utilities/string_functions.js +++ b/src/utilities/string_functions.js @@ -5,985 +5,986 @@ * @requires core */ -import p5 from '../core/main'; -import '../core/friendly_errors/validate_params'; -import '../core/friendly_errors/file_errors'; -import '../core/friendly_errors/fes_core'; +function stringFunctions(p5, fn){ + /** + * Combines an array of strings into one string. + * + * The first parameter, `list`, is the array of strings to join. + * + * The second parameter, `separator`, is the character(s) that should be used + * to separate the combined strings. For example, calling + * `join(myWords, ' : ')` would return a string of words each separated by a + * colon and spaces. + * + * @method join + * @param {Array} list array of strings to combine. + * @param {String} separator character(s) to place between strings when they're combined. + * @return {String} combined string. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of strings. + * let myWords = ['one', 'two', 'three']; + * + * // Create a combined string + * let combined = join(myWords, ' : '); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * + * // Display the combined string. + * text(combined, 50, 50); + * + * describe('The text "one : two : three" written in black on a gray canvas.'); + * } + * + *
    + */ + fn.join = function(list, separator) { + p5._validateParameters('join', arguments); + return list.join(separator); + }; -/** - * Combines an array of strings into one string. - * - * The first parameter, `list`, is the array of strings to join. - * - * The second parameter, `separator`, is the character(s) that should be used - * to separate the combined strings. For example, calling - * `join(myWords, ' : ')` would return a string of words each separated by a - * colon and spaces. - * - * @method join - * @param {Array} list array of strings to combine. - * @param {String} separator character(s) to place between strings when they're combined. - * @return {String} combined string. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of strings. - * let myWords = ['one', 'two', 'three']; - * - * // Create a combined string - * let combined = join(myWords, ' : '); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * - * // Display the combined string. - * text(combined, 50, 50); - * - * describe('The text "one : two : three" written in black on a gray canvas.'); - * } - * - *
    - */ -p5.prototype.join = function(list, separator) { - p5._validateParameters('join', arguments); - return list.join(separator); -}; - -/** - * Applies a regular expression to a string and returns an array with the - * first match. - * - * `match()` uses regular expressions (regex) to match patterns in text. For - * example, the regex `abc` can be used to search a string for the exact - * sequence of characters `abc`. See - * MDN. - * for more information about regexes. - * - * The first parameter, `str`, is the string to search. - * - * The second parameter, `regex`, is a string with the regular expression to - * apply. For example, calling `match('Hello, p5*js!', '[a-z][0-9]')` would - * return the array `['p5']`. - * - * Note: If no matches are found, `null` is returned. - * - * @method match - * @param {String} str string to search. - * @param {String} regexp regular expression to match. - * @return {String[]} match if found. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let string = 'Hello, p5*js!'; - * - * // Match the characters that are lowercase - * // letters followed by digits. - * let matches = match(string, '[a-z][0-9]'); - * - * // Print the matches array to the console: - * // ['p5'] - * print(matches); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the matches. - * text(matches, 50, 50); - * - * describe('The text "p5" written in black on a gray canvas.'); - * } - * - *
    - */ -p5.prototype.match = function(str, reg) { - p5._validateParameters('match', arguments); - return str.match(reg); -}; + /** + * Applies a regular expression to a string and returns an array with the + * first match. + * + * `match()` uses regular expressions (regex) to match patterns in text. For + * example, the regex `abc` can be used to search a string for the exact + * sequence of characters `abc`. See + * MDN. + * for more information about regexes. + * + * The first parameter, `str`, is the string to search. + * + * The second parameter, `regex`, is a string with the regular expression to + * apply. For example, calling `match('Hello, p5*js!', '[a-z][0-9]')` would + * return the array `['p5']`. + * + * Note: If no matches are found, `null` is returned. + * + * @method match + * @param {String} str string to search. + * @param {String} regexp regular expression to match. + * @return {String[]} match if found. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let string = 'Hello, p5*js!'; + * + * // Match the characters that are lowercase + * // letters followed by digits. + * let matches = match(string, '[a-z][0-9]'); + * + * // Print the matches array to the console: + * // ['p5'] + * print(matches); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the matches. + * text(matches, 50, 50); + * + * describe('The text "p5" written in black on a gray canvas.'); + * } + * + *
    + */ + fn.match = function(str, reg) { + p5._validateParameters('match', arguments); + return str.match(reg); + }; -/** - * Applies a regular expression to a string and returns an array of matches. - * - * `match()` uses regular expressions (regex) to match patterns in text. For - * example, the regex `abc` can be used to search a string for the exact - * sequence of characters `abc`. See - * MDN. - * for more information about regexes. `matchAll()` is different from - * match() because it returns every match, not just - * the first. - * - * The first parameter, `str`, is the string to search. - * - * The second parameter, `regex`, is a string with the regular expression to - * apply. For example, calling - * `matchAll('p5*js is easier than abc123', '[a-z][0-9]')` would return the - * 2D array `[['p5'], ['c1']]`. - * - * Note: If no matches are found, an empty array `[]` is returned. - * - * @method matchAll - * @param {String} str string to search. - * @param {String} regexp regular expression to match. - * @return {String[]} matches found. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let string = 'p5*js is easier than abc123'; - * - * // Match the character sequences that are - * // lowercase letters followed by digits. - * let matches = matchAll(string, '[a-z][0-9]'); - * - * // Print the matches array to the console: - * // [['p5'], ['c1']] - * print(matches); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Iterate over the matches array. - * for (let i = 0; i < matches.length; i += 1) { - * - * // Calculate the y-coordainate. - * let y = (i + 1) * 33; - * - * // Display the match. - * text(matches[i], 50, y); - * } - * - * describe( - * 'The text "p5" and "c1" written on separate lines. The text is black on a gray background.' - * ); - * } - * - *
    - */ -p5.prototype.matchAll = function(str, reg) { - p5._validateParameters('matchAll', arguments); - const re = new RegExp(reg, 'g'); - let match = re.exec(str); - const matches = []; - while (match !== null) { - matches.push(match); - // matched text: match[0] - // match start: match.index - // capturing group n: match[n] - match = re.exec(str); - } - return matches; -}; + /** + * Applies a regular expression to a string and returns an array of matches. + * + * `match()` uses regular expressions (regex) to match patterns in text. For + * example, the regex `abc` can be used to search a string for the exact + * sequence of characters `abc`. See + * MDN. + * for more information about regexes. `matchAll()` is different from + * match() because it returns every match, not just + * the first. + * + * The first parameter, `str`, is the string to search. + * + * The second parameter, `regex`, is a string with the regular expression to + * apply. For example, calling + * `matchAll('p5*js is easier than abc123', '[a-z][0-9]')` would return the + * 2D array `[['p5'], ['c1']]`. + * + * Note: If no matches are found, an empty array `[]` is returned. + * + * @method matchAll + * @param {String} str string to search. + * @param {String} regexp regular expression to match. + * @return {String[]} matches found. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let string = 'p5*js is easier than abc123'; + * + * // Match the character sequences that are + * // lowercase letters followed by digits. + * let matches = matchAll(string, '[a-z][0-9]'); + * + * // Print the matches array to the console: + * // [['p5'], ['c1']] + * print(matches); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Iterate over the matches array. + * for (let i = 0; i < matches.length; i += 1) { + * + * // Calculate the y-coordainate. + * let y = (i + 1) * 33; + * + * // Display the match. + * text(matches[i], 50, y); + * } + * + * describe( + * 'The text "p5" and "c1" written on separate lines. The text is black on a gray background.' + * ); + * } + * + *
    + */ + fn.matchAll = function(str, reg) { + p5._validateParameters('matchAll', arguments); + const re = new RegExp(reg, 'g'); + let match = re.exec(str); + const matches = []; + while (match !== null) { + matches.push(match); + // matched text: match[0] + // match start: match.index + // capturing group n: match[n] + match = re.exec(str); + } + return matches; + }; -/** - * Converts a `Number` into a `String` with a given number of digits. - * - * `nf()` converts numbers such as `123.45` into strings formatted with a set - * number of digits, as in `'123.4500'`. - * - * The first parameter, `num`, is the number to convert to a string. For - * example, calling `nf(123.45)` returns the string `'123.45'`. If an array of - * numbers is passed, as in `nf([123.45, 67.89])`, an array of formatted - * strings will be returned. - * - * The second parameter, `left`, is optional. If a number is passed, as in - * `nf(123.45, 4)`, it sets the minimum number of digits to include to the - * left of the decimal place. If `left` is larger than the number of digits in - * `num`, then unused digits will be set to 0. For example, calling - * `nf(123.45, 4)` returns the string `'0123.45'`. - * - * The third parameter, `right`, is also optional. If a number is passed, as - * in `nf(123.45, 4, 1)`, it sets the minimum number of digits to include to - * the right of the decimal place. If `right` is smaller than the number of - * decimal places in `num`, then `num` will be rounded to the given number of - * decimal places. For example, calling `nf(123.45, 4, 1)` returns the string - * `'0123.5'`. If right is larger than the number of decimal places in `num`, - * then unused decimal places will be set to 0. For example, calling - * `nf(123.45, 4, 3)` returns the string `'0123.450'`. - * - * @method nf - * @param {Number|String} num number to format. - * @param {Integer|String} [left] number of digits to include to the left of - * the decimal point. - * @param {Integer|String} [right] number of digits to include to the right - * of the decimal point. - * @return {String} formatted string. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textSize(16); - * - * // Create a number variable. - * let number = 123.45; - * - * // Display the number as a string. - * let formatted = nf(number); - * text(formatted, 20, 25); - * - * // Display the number with four digits - * // to the left of the decimal. - * let left = nf(number, 4); - * text(left, 20, 50); - * - * // Display the number with four digits - * // to the left of the decimal and one - * // to the right. - * let right = nf(number, 4, 1); - * text(right, 20, 75); - * - * describe( - * 'The numbers "123.45", "0123.45", and "0123.5" written on three separate lines. The text is in black on a gray background.' - * ); - * } - * - *
    - */ -/** - * @method nf - * @param {Number[]} nums numbers to format. - * @param {Integer|String} [left] - * @param {Integer|String} [right] - * @return {String[]} formatted strings. - */ -p5.prototype.nf = function(nums, left, right) { - p5._validateParameters('nf', arguments); - if (nums instanceof Array) { - return nums.map(x => doNf(x, left, right)); - } else { - const typeOfFirst = Object.prototype.toString.call(nums); - if (typeOfFirst === '[object Arguments]') { - if (nums.length === 3) { - return this.nf(nums[0], nums[1], nums[2]); - } else if (nums.length === 2) { - return this.nf(nums[0], nums[1]); + /** + * Converts a `Number` into a `String` with a given number of digits. + * + * `nf()` converts numbers such as `123.45` into strings formatted with a set + * number of digits, as in `'123.4500'`. + * + * The first parameter, `num`, is the number to convert to a string. For + * example, calling `nf(123.45)` returns the string `'123.45'`. If an array of + * numbers is passed, as in `nf([123.45, 67.89])`, an array of formatted + * strings will be returned. + * + * The second parameter, `left`, is optional. If a number is passed, as in + * `nf(123.45, 4)`, it sets the minimum number of digits to include to the + * left of the decimal place. If `left` is larger than the number of digits in + * `num`, then unused digits will be set to 0. For example, calling + * `nf(123.45, 4)` returns the string `'0123.45'`. + * + * The third parameter, `right`, is also optional. If a number is passed, as + * in `nf(123.45, 4, 1)`, it sets the minimum number of digits to include to + * the right of the decimal place. If `right` is smaller than the number of + * decimal places in `num`, then `num` will be rounded to the given number of + * decimal places. For example, calling `nf(123.45, 4, 1)` returns the string + * `'0123.5'`. If right is larger than the number of decimal places in `num`, + * then unused decimal places will be set to 0. For example, calling + * `nf(123.45, 4, 3)` returns the string `'0123.450'`. + * + * @method nf + * @param {Number|String} num number to format. + * @param {Integer|String} [left] number of digits to include to the left of + * the decimal point. + * @param {Integer|String} [right] number of digits to include to the right + * of the decimal point. + * @return {String} formatted string. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textSize(16); + * + * // Create a number variable. + * let number = 123.45; + * + * // Display the number as a string. + * let formatted = nf(number); + * text(formatted, 20, 25); + * + * // Display the number with four digits + * // to the left of the decimal. + * let left = nf(number, 4); + * text(left, 20, 50); + * + * // Display the number with four digits + * // to the left of the decimal and one + * // to the right. + * let right = nf(number, 4, 1); + * text(right, 20, 75); + * + * describe( + * 'The numbers "123.45", "0123.45", and "0123.5" written on three separate lines. The text is in black on a gray background.' + * ); + * } + * + *
    + */ + /** + * @method nf + * @param {Number[]} nums numbers to format. + * @param {Integer|String} [left] + * @param {Integer|String} [right] + * @return {String[]} formatted strings. + */ + fn.nf = function(nums, left, right) { + p5._validateParameters('nf', arguments); + if (nums instanceof Array) { + return nums.map(x => doNf(x, left, right)); + } else { + const typeOfFirst = Object.prototype.toString.call(nums); + if (typeOfFirst === '[object Arguments]') { + if (nums.length === 3) { + return this.nf(nums[0], nums[1], nums[2]); + } else if (nums.length === 2) { + return this.nf(nums[0], nums[1]); + } else { + return this.nf(nums[0]); + } } else { - return this.nf(nums[0]); + return doNf(nums, left, right); } - } else { - return doNf(nums, left, right); } - } -}; + }; -function doNf(num, left, right) { - let [leftPart, rightPart] = num.toString().split('.'); + function doNf(num, left, right) { + let [leftPart, rightPart] = num.toString().split('.'); - if (typeof right === 'undefined') { - leftPart = leftPart.padStart(left, '0'); - return rightPart ? leftPart + '.' + rightPart : leftPart; - } else { - let roundedOff = num.toFixed(right); - [leftPart, rightPart] = roundedOff.toString().split('.'); - leftPart = leftPart.padStart(left, '0'); - if(typeof rightPart === 'undefined'){ - return leftPart; - }else{ - return leftPart + '.' + rightPart; + if (typeof right === 'undefined') { + leftPart = leftPart.padStart(left, '0'); + return rightPart ? leftPart + '.' + rightPart : leftPart; + } else { + let roundedOff = num.toFixed(right); + [leftPart, rightPart] = roundedOff.toString().split('.'); + leftPart = leftPart.padStart(left, '0'); + if(typeof rightPart === 'undefined'){ + return leftPart; + }else{ + return leftPart + '.' + rightPart; + } } } -} -/** - * Converts a `Number` into a `String` with commas to mark units of 1,000. - * - * `nfc()` converts numbers such as 12345 into strings formatted with commas - * to mark the thousands place, as in `'12,345'`. - * - * The first parameter, `num`, is the number to convert to a string. For - * example, calling `nfc(12345)` returns the string `'12,345'`. - * - * The second parameter, `right`, is optional. If a number is passed, as in - * `nfc(12345, 1)`, it sets the minimum number of digits to include to the - * right of the decimal place. If `right` is smaller than the number of - * decimal places in `num`, then `num` will be rounded to the given number of - * decimal places. For example, calling `nfc(12345.67, 1)` returns the string - * `'12,345.7'`. If `right` is larger than the number of decimal places in - * `num`, then unused decimal places will be set to 0. For example, calling - * `nfc(12345.67, 3)` returns the string `'12,345.670'`. - * - * @method nfc - * @param {Number|String} num number to format. - * @param {Integer|String} [right] number of digits to include to the right - * of the decimal point. - * @return {String} formatted string. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textSize(16); - * - * // Create a number variable. - * let number = 12345; - * - * // Display the number as a string. - * let commas = nfc(number); - * text(commas, 15, 33); - * - * // Display the number with four digits - * // to the left of the decimal. - * let decimals = nfc(number, 2); - * text(decimals, 15, 67); - * - * describe( - * 'The numbers "12,345" and "12,345.00" written on separate lines. The text is in black on a gray background.' - * ); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of numbers. - * let numbers = [12345, 6789]; - * - * // Convert the numbers to formatted strings. - * let formatted = nfc(numbers); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(14); - * - * // Iterate over the array. - * for (let i = 0; i < formatted.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 33; - * - * // Display the original and formatted numbers. - * text(`${numbers[i]} : ${formatted[i]}`, 50, y); - * } - * - * describe( - * 'The text "12345 : 12,345" and "6789 : 6,789" written on two separate lines. The text is in black on a gray background.' - * ); - * } - * - *
    - */ -/** - * @method nfc - * @param {Number[]} nums numbers to format. - * @param {Integer|String} [right] - * @return {String[]} formatted strings. - */ -p5.prototype.nfc = function(num, right) { - p5._validateParameters('nfc', arguments); - if (num instanceof Array) { - return num.map(x => doNfc(x, right)); - } else { - return doNfc(num, right); - } -}; -function doNfc(num, right) { - num = num.toString(); - const dec = num.indexOf('.'); - let rem = dec !== -1 ? num.substring(dec) : ''; - let n = dec !== -1 ? num.substring(0, dec) : num; - n = n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); - if (right === 0) { - rem = ''; - } else if (typeof right !== 'undefined') { - if (right > rem.length) { - rem += dec === -1 ? '.' : ''; - const len = right - rem.length + 1; - for (let i = 0; i < len; i++) { - rem += '0'; - } + /** + * Converts a `Number` into a `String` with commas to mark units of 1,000. + * + * `nfc()` converts numbers such as 12345 into strings formatted with commas + * to mark the thousands place, as in `'12,345'`. + * + * The first parameter, `num`, is the number to convert to a string. For + * example, calling `nfc(12345)` returns the string `'12,345'`. + * + * The second parameter, `right`, is optional. If a number is passed, as in + * `nfc(12345, 1)`, it sets the minimum number of digits to include to the + * right of the decimal place. If `right` is smaller than the number of + * decimal places in `num`, then `num` will be rounded to the given number of + * decimal places. For example, calling `nfc(12345.67, 1)` returns the string + * `'12,345.7'`. If `right` is larger than the number of decimal places in + * `num`, then unused decimal places will be set to 0. For example, calling + * `nfc(12345.67, 3)` returns the string `'12,345.670'`. + * + * @method nfc + * @param {Number|String} num number to format. + * @param {Integer|String} [right] number of digits to include to the right + * of the decimal point. + * @return {String} formatted string. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textSize(16); + * + * // Create a number variable. + * let number = 12345; + * + * // Display the number as a string. + * let commas = nfc(number); + * text(commas, 15, 33); + * + * // Display the number with four digits + * // to the left of the decimal. + * let decimals = nfc(number, 2); + * text(decimals, 15, 67); + * + * describe( + * 'The numbers "12,345" and "12,345.00" written on separate lines. The text is in black on a gray background.' + * ); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of numbers. + * let numbers = [12345, 6789]; + * + * // Convert the numbers to formatted strings. + * let formatted = nfc(numbers); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(14); + * + * // Iterate over the array. + * for (let i = 0; i < formatted.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 33; + * + * // Display the original and formatted numbers. + * text(`${numbers[i]} : ${formatted[i]}`, 50, y); + * } + * + * describe( + * 'The text "12345 : 12,345" and "6789 : 6,789" written on two separate lines. The text is in black on a gray background.' + * ); + * } + * + *
    + */ + /** + * @method nfc + * @param {Number[]} nums numbers to format. + * @param {Integer|String} [right] + * @return {String[]} formatted strings. + */ + fn.nfc = function(num, right) { + p5._validateParameters('nfc', arguments); + if (num instanceof Array) { + return num.map(x => doNfc(x, right)); } else { - rem = rem.substring(0, right + 1); + return doNfc(num, right); } + }; + function doNfc(num, right) { + num = num.toString(); + const dec = num.indexOf('.'); + let rem = dec !== -1 ? num.substring(dec) : ''; + let n = dec !== -1 ? num.substring(0, dec) : num; + n = n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); + if (right === 0) { + rem = ''; + } else if (typeof right !== 'undefined') { + if (right > rem.length) { + rem += dec === -1 ? '.' : ''; + const len = right - rem.length + 1; + for (let i = 0; i < len; i++) { + rem += '0'; + } + } else { + rem = rem.substring(0, right + 1); + } + } + return n + rem; } - return n + rem; -} -/** - * Converts a `Number` into a `String` with a plus or minus sign. - * - * `nfp()` converts numbers such as 123 into strings formatted with a `+` or - * `-` symbol to mark whether they're positive or negative, as in `'+123'`. - * - * The first parameter, `num`, is the number to convert to a string. For - * example, calling `nfp(123.45)` returns the string `'+123.45'`. If an array - * of numbers is passed, as in `nfp([123.45, -6.78])`, an array of formatted - * strings will be returned. - * - * The second parameter, `left`, is optional. If a number is passed, as in - * `nfp(123.45, 4)`, it sets the minimum number of digits to include to the - * left of the decimal place. If `left` is larger than the number of digits in - * `num`, then unused digits will be set to 0. For example, calling - * `nfp(123.45, 4)` returns the string `'+0123.45'`. - * - * The third parameter, `right`, is also optional. If a number is passed, as - * in `nfp(123.45, 4, 1)`, it sets the minimum number of digits to include to - * the right of the decimal place. If `right` is smaller than the number of - * decimal places in `num`, then `num` will be rounded to the given number of - * decimal places. For example, calling `nfp(123.45, 4, 1)` returns the - * string `'+0123.5'`. If `right` is larger than the number of decimal places - * in `num`, then unused decimal places will be set to 0. For example, - * calling `nfp(123.45, 4, 3)` returns the string `'+0123.450'`. - * - * @method nfp - * @param {Number} num number to format. - * @param {Integer} [left] number of digits to include to the left of the - * decimal point. - * @param {Integer} [right] number of digits to include to the right of the - * decimal point. - * @return {String} formatted string. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create number variables. - * let positive = 123; - * let negative = -123; - * - * // Convert the positive number to a formatted string. - * let p = nfp(positive); - * - * // Convert the negative number to a formatted string - * // with four digits to the left of the decimal - * // and two digits to the right of the decimal. - * let n = nfp(negative, 4, 2); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(14); - * - * // Display the original and formatted numbers. - * text(`${positive} : ${p}`, 50, 33); - * text(`${negative} : ${n}`, 50, 67); - * - * describe( - * 'The text "123 : +123" and "-123 : -123.00" written on separate lines. The text is in black on a gray background.' - * ); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create number variables. - * let numbers = [123, -4.56]; - * - * // Convert the numbers to formatted strings - * // with four digits to the left of the decimal - * // and one digit to the right of the decimal. - * let formatted = nfp(numbers, 4, 1); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(14); - * - * // Iterate over the array. - * for (let i = 0; i < formatted.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 33; - * - * // Display the original and formatted numbers. - * text(`${numbers[i]} : ${formatted[i]}`, 50, y); - * } - * - * describe( - * 'The text "123 : +0123.0" and "-4.56 : 00-4.6" written on separate lines. The text is in black on a gray background.' - * ); - * } - * - *
    - */ -/** - * @method nfp - * @param {Number[]} nums numbers to format. - * @param {Integer} [left] - * @param {Integer} [right] - * @return {String[]} formatted strings. - */ -p5.prototype.nfp = function(...args) { - p5._validateParameters('nfp', args); - const nfRes = p5.prototype.nf.apply(this, args); - if (nfRes instanceof Array) { - return nfRes.map(addNfp); - } else { - return addNfp(nfRes); + /** + * Converts a `Number` into a `String` with a plus or minus sign. + * + * `nfp()` converts numbers such as 123 into strings formatted with a `+` or + * `-` symbol to mark whether they're positive or negative, as in `'+123'`. + * + * The first parameter, `num`, is the number to convert to a string. For + * example, calling `nfp(123.45)` returns the string `'+123.45'`. If an array + * of numbers is passed, as in `nfp([123.45, -6.78])`, an array of formatted + * strings will be returned. + * + * The second parameter, `left`, is optional. If a number is passed, as in + * `nfp(123.45, 4)`, it sets the minimum number of digits to include to the + * left of the decimal place. If `left` is larger than the number of digits in + * `num`, then unused digits will be set to 0. For example, calling + * `nfp(123.45, 4)` returns the string `'+0123.45'`. + * + * The third parameter, `right`, is also optional. If a number is passed, as + * in `nfp(123.45, 4, 1)`, it sets the minimum number of digits to include to + * the right of the decimal place. If `right` is smaller than the number of + * decimal places in `num`, then `num` will be rounded to the given number of + * decimal places. For example, calling `nfp(123.45, 4, 1)` returns the + * string `'+0123.5'`. If `right` is larger than the number of decimal places + * in `num`, then unused decimal places will be set to 0. For example, + * calling `nfp(123.45, 4, 3)` returns the string `'+0123.450'`. + * + * @method nfp + * @param {Number} num number to format. + * @param {Integer} [left] number of digits to include to the left of the + * decimal point. + * @param {Integer} [right] number of digits to include to the right of the + * decimal point. + * @return {String} formatted string. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create number variables. + * let positive = 123; + * let negative = -123; + * + * // Convert the positive number to a formatted string. + * let p = nfp(positive); + * + * // Convert the negative number to a formatted string + * // with four digits to the left of the decimal + * // and two digits to the right of the decimal. + * let n = nfp(negative, 4, 2); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(14); + * + * // Display the original and formatted numbers. + * text(`${positive} : ${p}`, 50, 33); + * text(`${negative} : ${n}`, 50, 67); + * + * describe( + * 'The text "123 : +123" and "-123 : -123.00" written on separate lines. The text is in black on a gray background.' + * ); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create number variables. + * let numbers = [123, -4.56]; + * + * // Convert the numbers to formatted strings + * // with four digits to the left of the decimal + * // and one digit to the right of the decimal. + * let formatted = nfp(numbers, 4, 1); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(14); + * + * // Iterate over the array. + * for (let i = 0; i < formatted.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 33; + * + * // Display the original and formatted numbers. + * text(`${numbers[i]} : ${formatted[i]}`, 50, y); + * } + * + * describe( + * 'The text "123 : +0123.0" and "-4.56 : 00-4.6" written on separate lines. The text is in black on a gray background.' + * ); + * } + * + *
    + */ + /** + * @method nfp + * @param {Number[]} nums numbers to format. + * @param {Integer} [left] + * @param {Integer} [right] + * @return {String[]} formatted strings. + */ + fn.nfp = function(...args) { + p5._validateParameters('nfp', args); + const nfRes = fn.nf.apply(this, args); + if (nfRes instanceof Array) { + return nfRes.map(addNfp); + } else { + return addNfp(nfRes); + } + }; + + function addNfp(num) { + return parseFloat(num) > 0 ? `+${num.toString()}` : num.toString(); } -}; -function addNfp(num) { - return parseFloat(num) > 0 ? `+${num.toString()}` : num.toString(); -} + /** + * Converts a positive `Number` into a `String` with an extra space in front. + * + * `nfs()` converts positive numbers such as 123.45 into strings formatted + * with an extra space in front, as in ' 123.45'. Doing so can be helpful for + * aligning positive and negative numbers. + * + * The first parameter, `num`, is the number to convert to a string. For + * example, calling `nfs(123.45)` returns the string `' 123.45'`. + * + * The second parameter, `left`, is optional. If a number is passed, as in + * `nfs(123.45, 4)`, it sets the minimum number of digits to include to the + * left of the decimal place. If `left` is larger than the number of digits in + * `num`, then unused digits will be set to 0. For example, calling + * `nfs(123.45, 4)` returns the string `' 0123.45'`. + * + * The third parameter, `right`, is also optional. If a number is passed, as + * in `nfs(123.45, 4, 1)`, it sets the minimum number of digits to include to + * the right of the decimal place. If `right` is smaller than the number of + * decimal places in `num`, then `num` will be rounded to the given number of + * decimal places. For example, calling `nfs(123.45, 4, 1)` returns the + * string `' 0123.5'`. If `right` is larger than the number of decimal places + * in `num`, then unused decimal places will be set to 0. For example, + * calling `nfs(123.45, 4, 3)` returns the string `' 0123.450'`. + * + * @method nfs + * @param {Number} num number to format. + * @param {Integer} [left] number of digits to include to the left of the + * decimal point. + * @param {Integer} [right] number of digits to include to the right of the + * decimal point. + * @return {String} formatted string. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create number variables. + * let positive = 123; + * let negative = -123; + * + * // Convert the positive number to a formatted string. + * let formatted = nfs(positive); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(16); + * + * // Display the negative number and the formatted positive number. + * text(negative, 50, 33); + * text(formatted, 50, 67); + * + * describe( + * 'The numbers -123 and 123 written on separate lines. The numbers align vertically. The text is in black on a gray background.' + * ); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a number variable. + * let number = 123.45; + * + * // Convert the positive number to a formatted string. + * // Use four digits to the left of the decimal and + * // one digit to the right. + * let formatted = nfs(number, 4, 1); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(16); + * + * // Display a negative version of the number and + * // the formatted positive version. + * text('-0123.5', 50, 33); + * text(formatted, 50, 67); + * + * describe( + * 'The numbers "-0123.5" and "0123.5" written on separate lines. The numbers align vertically. The text is in black on a gray background.' + * ); + * } + * + *
    + */ + /** + * @method nfs + * @param {Array} nums numbers to format. + * @param {Integer} [left] + * @param {Integer} [right] + * @return {String[]} formatted strings. + */ + fn.nfs = function(...args) { + p5._validateParameters('nfs', args); + const nfRes = fn.nf.apply(this, args); + if (nfRes instanceof Array) { + return nfRes.map(addNfs); + } else { + return addNfs(nfRes); + } + }; -/** - * Converts a positive `Number` into a `String` with an extra space in front. - * - * `nfs()` converts positive numbers such as 123.45 into strings formatted - * with an extra space in front, as in ' 123.45'. Doing so can be helpful for - * aligning positive and negative numbers. - * - * The first parameter, `num`, is the number to convert to a string. For - * example, calling `nfs(123.45)` returns the string `' 123.45'`. - * - * The second parameter, `left`, is optional. If a number is passed, as in - * `nfs(123.45, 4)`, it sets the minimum number of digits to include to the - * left of the decimal place. If `left` is larger than the number of digits in - * `num`, then unused digits will be set to 0. For example, calling - * `nfs(123.45, 4)` returns the string `' 0123.45'`. - * - * The third parameter, `right`, is also optional. If a number is passed, as - * in `nfs(123.45, 4, 1)`, it sets the minimum number of digits to include to - * the right of the decimal place. If `right` is smaller than the number of - * decimal places in `num`, then `num` will be rounded to the given number of - * decimal places. For example, calling `nfs(123.45, 4, 1)` returns the - * string `' 0123.5'`. If `right` is larger than the number of decimal places - * in `num`, then unused decimal places will be set to 0. For example, - * calling `nfs(123.45, 4, 3)` returns the string `' 0123.450'`. - * - * @method nfs - * @param {Number} num number to format. - * @param {Integer} [left] number of digits to include to the left of the - * decimal point. - * @param {Integer} [right] number of digits to include to the right of the - * decimal point. - * @return {String} formatted string. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create number variables. - * let positive = 123; - * let negative = -123; - * - * // Convert the positive number to a formatted string. - * let formatted = nfs(positive); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(16); - * - * // Display the negative number and the formatted positive number. - * text(negative, 50, 33); - * text(formatted, 50, 67); - * - * describe( - * 'The numbers -123 and 123 written on separate lines. The numbers align vertically. The text is in black on a gray background.' - * ); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a number variable. - * let number = 123.45; - * - * // Convert the positive number to a formatted string. - * // Use four digits to the left of the decimal and - * // one digit to the right. - * let formatted = nfs(number, 4, 1); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(16); - * - * // Display a negative version of the number and - * // the formatted positive version. - * text('-0123.5', 50, 33); - * text(formatted, 50, 67); - * - * describe( - * 'The numbers "-0123.5" and "0123.5" written on separate lines. The numbers align vertically. The text is in black on a gray background.' - * ); - * } - * - *
    - */ -/** - * @method nfs - * @param {Array} nums numbers to format. - * @param {Integer} [left] - * @param {Integer} [right] - * @return {String[]} formatted strings. - */ -p5.prototype.nfs = function(...args) { - p5._validateParameters('nfs', args); - const nfRes = p5.prototype.nf.apply(this, args); - if (nfRes instanceof Array) { - return nfRes.map(addNfs); - } else { - return addNfs(nfRes); + function addNfs(num) { + return parseFloat(num) >= 0 ? ` ${num.toString()}` : num.toString(); } -}; -function addNfs(num) { - return parseFloat(num) >= 0 ? ` ${num.toString()}` : num.toString(); -} + /** + * Splits a `String` into pieces and returns an array containing the pieces. + * + * The first parameter, `value`, is the string to split. + * + * The second parameter, `delim`, is the character(s) that should be used to + * split the string. For example, calling + * `split('rock...paper...scissors', '...')` would return the array + * `['rock', 'paper', 'scissors']` because there are three periods `...` + * between each word. + * + * @method split + * @param {String} value the String to be split + * @param {String} delim the String used to separate the data + * @return {String[]} Array of Strings + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let string = 'rock...paper...scissors'; + * + * // Split the string at each ... + * let words = split(string, '...'); + * + * // Print the array to the console: + * // ["rock", "paper", "scissors"] + * print(words); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(16); + * + * // Iterate over the words array. + * for (let i = 0; i < words.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 25; + * + * // Display the word. + * text(words[i], 50, y); + * } + * + * describe( + * 'The words "rock", "paper", and "scissors" written on separate lines. The text is black on a gray background.' + * ); + * } + * + *
    + */ + fn.split = function(str, delim) { + p5._validateParameters('split', arguments); + return str.split(delim); + }; -/** - * Splits a `String` into pieces and returns an array containing the pieces. - * - * The first parameter, `value`, is the string to split. - * - * The second parameter, `delim`, is the character(s) that should be used to - * split the string. For example, calling - * `split('rock...paper...scissors', '...')` would return the array - * `['rock', 'paper', 'scissors']` because there are three periods `...` - * between each word. - * - * @method split - * @param {String} value the String to be split - * @param {String} delim the String used to separate the data - * @return {String[]} Array of Strings - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let string = 'rock...paper...scissors'; - * - * // Split the string at each ... - * let words = split(string, '...'); - * - * // Print the array to the console: - * // ["rock", "paper", "scissors"] - * print(words); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(16); - * - * // Iterate over the words array. - * for (let i = 0; i < words.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 25; - * - * // Display the word. - * text(words[i], 50, y); - * } - * - * describe( - * 'The words "rock", "paper", and "scissors" written on separate lines. The text is black on a gray background.' - * ); - * } - * - *
    - */ -p5.prototype.split = function(str, delim) { - p5._validateParameters('split', arguments); - return str.split(delim); -}; + /** + * Splits a `String` into pieces and returns an array containing the pieces. + * + * `splitTokens()` is an enhanced version of + * split(). It can split a string when any characters + * from a list are detected. + * + * The first parameter, `value`, is the string to split. + * + * The second parameter, `delim`, is optional. It sets the character(s) that + * should be used to split the string. `delim` can be a single string, as in + * `splitTokens('rock...paper...scissors...shoot', '...')`, or an array of + * strings, as in + * `splitTokens('rock;paper,scissors...shoot, [';', ',', '...'])`. By default, + * if no `delim` characters are specified, then any whitespace character is + * used to split. Whitespace characters include tab (`\t`), line feed (`\n`), + * carriage return (`\r`), form feed (`\f`), and space. + * + * @method splitTokens + * @param {String} value string to split. + * @param {String} [delim] character(s) to use for splitting the string. + * @return {String[]} separated strings. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let string = 'rock paper scissors shoot'; + * + * // Split the string at each space. + * let words = splitTokens(string); + * + * // Print the array to the console. + * print(words); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Iterate over the words array. + * for (let i = 0; i < words.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 20; + * + * // Display the word. + * text(words[i], 50, y); + * } + * + * describe( + * 'The words "rock", "paper", "scissors", and "shoot" written on separate lines. The text is black on a gray background.' + * ); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let string = 'rock...paper...scissors...shoot'; + * + * // Split the string at each ... + * let words = splitTokens(string, '...'); + * + * // Print the array to the console. + * print(words); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Iterate over the words array. + * for (let i = 0; i < words.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 20; + * + * // Display the word. + * text(words[i], 50, y); + * } + * + * describe( + * 'The words "rock", "paper", "scissors", and "shoot" written on separate lines. The text is black on a gray background.' + * ); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let string = 'rock;paper,scissors...shoot'; + * + * // Split the string at each semicolon, comma, or ... + * let words = splitTokens(string, [';', ',', '...']); + * + * // Print the array to the console. + * print(words); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(12); + * + * // Iterate over the words array. + * for (let i = 0; i < words.length; i += 1) { + * + * // Calculate the y-coordinate. + * let y = (i + 1) * 20; + * + * // Display the word. + * text(words[i], 50, y); + * } + * + * describe( + * 'The words "rock", "paper", "scissors", and "shoot" written on separate lines. The text is black on a gray background.' + * ); + * } + * + *
    + */ + fn.splitTokens = function(value, delims) { + p5._validateParameters('splitTokens', arguments); + let d; + if (typeof delims !== 'undefined') { + let str = delims; + const sqc = /\]/g.exec(str); + let sqo = /\[/g.exec(str); + if (sqo && sqc) { + str = str.slice(0, sqc.index) + str.slice(sqc.index + 1); + sqo = /\[/g.exec(str); + str = str.slice(0, sqo.index) + str.slice(sqo.index + 1); + d = new RegExp(`[\\[${str}\\]]`, 'g'); + } else if (sqc) { + str = str.slice(0, sqc.index) + str.slice(sqc.index + 1); + d = new RegExp(`[${str}\\]]`, 'g'); + } else if (sqo) { + str = str.slice(0, sqo.index) + str.slice(sqo.index + 1); + d = new RegExp(`[${str}\\[]`, 'g'); + } else { + d = new RegExp(`[${str}]`, 'g'); + } + } else { + d = /\s/g; + } + return value.split(d).filter(n => n); + }; -/** - * Splits a `String` into pieces and returns an array containing the pieces. - * - * `splitTokens()` is an enhanced version of - * split(). It can split a string when any characters - * from a list are detected. - * - * The first parameter, `value`, is the string to split. - * - * The second parameter, `delim`, is optional. It sets the character(s) that - * should be used to split the string. `delim` can be a single string, as in - * `splitTokens('rock...paper...scissors...shoot', '...')`, or an array of - * strings, as in - * `splitTokens('rock;paper,scissors...shoot, [';', ',', '...'])`. By default, - * if no `delim` characters are specified, then any whitespace character is - * used to split. Whitespace characters include tab (`\t`), line feed (`\n`), - * carriage return (`\r`), form feed (`\f`), and space. - * - * @method splitTokens - * @param {String} value string to split. - * @param {String} [delim] character(s) to use for splitting the string. - * @return {String[]} separated strings. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let string = 'rock paper scissors shoot'; - * - * // Split the string at each space. - * let words = splitTokens(string); - * - * // Print the array to the console. - * print(words); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Iterate over the words array. - * for (let i = 0; i < words.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 20; - * - * // Display the word. - * text(words[i], 50, y); - * } - * - * describe( - * 'The words "rock", "paper", "scissors", and "shoot" written on separate lines. The text is black on a gray background.' - * ); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let string = 'rock...paper...scissors...shoot'; - * - * // Split the string at each ... - * let words = splitTokens(string, '...'); - * - * // Print the array to the console. - * print(words); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Iterate over the words array. - * for (let i = 0; i < words.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 20; - * - * // Display the word. - * text(words[i], 50, y); - * } - * - * describe( - * 'The words "rock", "paper", "scissors", and "shoot" written on separate lines. The text is black on a gray background.' - * ); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let string = 'rock;paper,scissors...shoot'; - * - * // Split the string at each semicolon, comma, or ... - * let words = splitTokens(string, [';', ',', '...']); - * - * // Print the array to the console. - * print(words); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(12); - * - * // Iterate over the words array. - * for (let i = 0; i < words.length; i += 1) { - * - * // Calculate the y-coordinate. - * let y = (i + 1) * 20; - * - * // Display the word. - * text(words[i], 50, y); - * } - * - * describe( - * 'The words "rock", "paper", "scissors", and "shoot" written on separate lines. The text is black on a gray background.' - * ); - * } - * - *
    - */ -p5.prototype.splitTokens = function(value, delims) { - p5._validateParameters('splitTokens', arguments); - let d; - if (typeof delims !== 'undefined') { - let str = delims; - const sqc = /\]/g.exec(str); - let sqo = /\[/g.exec(str); - if (sqo && sqc) { - str = str.slice(0, sqc.index) + str.slice(sqc.index + 1); - sqo = /\[/g.exec(str); - str = str.slice(0, sqo.index) + str.slice(sqo.index + 1); - d = new RegExp(`[\\[${str}\\]]`, 'g'); - } else if (sqc) { - str = str.slice(0, sqc.index) + str.slice(sqc.index + 1); - d = new RegExp(`[${str}\\]]`, 'g'); - } else if (sqo) { - str = str.slice(0, sqo.index) + str.slice(sqo.index + 1); - d = new RegExp(`[${str}\\[]`, 'g'); + /** + * Removes whitespace from the start and end of a `String` without changing the middle. + * + * `trim()` trims + * whitespace characters + * such as spaces, carriage returns, tabs, Unicode "nbsp" character. + * + * The parameter, `str`, is the string to trim. If a single string is passed, + * as in `trim(' pad ')`, a single string is returned. If an array of + * strings is passed, as in `trim([' pad ', '\n space \n'])`, an array of + * strings is returned. + * + * @method trim + * @param {String} str string to trim. + * @return {String} trimmed string. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create a string variable. + * let string = ' p5*js '; + * + * // Trim the whitespace. + * let trimmed = trim(string); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textSize(16); + * + * // Display the text. + * text(`Hello, ${trimmed}!`, 50, 50); + * + * describe('The text "Hello, p5*js!" written in black on a gray background.'); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create an array of strings. + * let strings = [' wide ', '\n open ', '\n spaces ']; + * + * // Trim the whitespace. + * let trimmed = trim(strings); + * + * // Style the text. + * textAlign(CENTER, CENTER); + * textFont('Courier New'); + * textSize(10); + * + * // Display the text. + * text(`${trimmed[0]} ${trimmed[1]} ${trimmed[2]}`, 50, 50); + * + * describe('The text "wide open spaces" written in black on a gray background.'); + * } + * + *
    + */ + /** + * @method trim + * @param {String[]} strs strings to trim. + * @return {String[]} trimmed strings. + */ + fn.trim = function(str) { + p5._validateParameters('trim', arguments); + if (str instanceof Array) { + return str.map(this.trim); } else { - d = new RegExp(`[${str}]`, 'g'); + return str.trim(); } - } else { - d = /\s/g; - } - return value.split(d).filter(n => n); -}; + }; +} -/** - * Removes whitespace from the start and end of a `String` without changing the middle. - * - * `trim()` trims - * whitespace characters - * such as spaces, carriage returns, tabs, Unicode "nbsp" character. - * - * The parameter, `str`, is the string to trim. If a single string is passed, - * as in `trim(' pad ')`, a single string is returned. If an array of - * strings is passed, as in `trim([' pad ', '\n space \n'])`, an array of - * strings is returned. - * - * @method trim - * @param {String} str string to trim. - * @return {String} trimmed string. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create a string variable. - * let string = ' p5*js '; - * - * // Trim the whitespace. - * let trimmed = trim(string); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textSize(16); - * - * // Display the text. - * text(`Hello, ${trimmed}!`, 50, 50); - * - * describe('The text "Hello, p5*js!" written in black on a gray background.'); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Create an array of strings. - * let strings = [' wide ', '\n open ', '\n spaces ']; - * - * // Trim the whitespace. - * let trimmed = trim(strings); - * - * // Style the text. - * textAlign(CENTER, CENTER); - * textFont('Courier New'); - * textSize(10); - * - * // Display the text. - * text(`${trimmed[0]} ${trimmed[1]} ${trimmed[2]}`, 50, 50); - * - * describe('The text "wide open spaces" written in black on a gray background.'); - * } - * - *
    - */ -/** - * @method trim - * @param {String[]} strs strings to trim. - * @return {String[]} trimmed strings. - */ -p5.prototype.trim = function(str) { - p5._validateParameters('trim', arguments); - if (str instanceof Array) { - return str.map(this.trim); - } else { - return str.trim(); - } -}; +export default stringFunctions; -export default p5; +if(typeof p5 !== 'undefined'){ + stringFunctions(p5, p5.prototype); +} diff --git a/src/utilities/time_date.js b/src/utilities/time_date.js index 88d91b3371..6177b7093f 100644 --- a/src/utilities/time_date.js +++ b/src/utilities/time_date.js @@ -5,341 +5,345 @@ * @requires core */ -import p5 from '../core/main'; +function timeDate(p5, fn){ + /** + * Returns the current day as a number from 1–31. + * + * @method day + * @return {Integer} current day between 1 and 31. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the current day. + * let d = day(); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textSize(12); + * textFont('Courier New'); + * + * // Display the day. + * text(`Current day: ${d}`, 20, 50, 60); + * + * describe(`The text 'Current day: ${d}' written in black on a gray background.`); + * } + * + *
    + */ + fn.day = function() { + return new Date().getDate(); + }; -/** - * Returns the current day as a number from 1–31. - * - * @method day - * @return {Integer} current day between 1 and 31. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the current day. - * let d = day(); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textSize(12); - * textFont('Courier New'); - * - * // Display the day. - * text(`Current day: ${d}`, 20, 50, 60); - * - * describe(`The text 'Current day: ${d}' written in black on a gray background.`); - * } - * - *
    - */ -p5.prototype.day = function() { - return new Date().getDate(); -}; + /** + * Returns the current hour as a number from 0–23. + * + * @method hour + * @return {Integer} current hour between 0 and 23. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the current hour. + * let h = hour(); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textSize(12); + * textFont('Courier New'); + * + * // Display the hour. + * text(`Current hour: ${h}`, 20, 50, 60); + * + * describe(`The text 'Current hour: ${h}' written in black on a gray background.`); + * } + * + *
    + */ + fn.hour = function() { + return new Date().getHours(); + }; -/** - * Returns the current hour as a number from 0–23. - * - * @method hour - * @return {Integer} current hour between 0 and 23. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the current hour. - * let h = hour(); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textSize(12); - * textFont('Courier New'); - * - * // Display the hour. - * text(`Current hour: ${h}`, 20, 50, 60); - * - * describe(`The text 'Current hour: ${h}' written in black on a gray background.`); - * } - * - *
    - */ -p5.prototype.hour = function() { - return new Date().getHours(); -}; + /** + * Returns the current minute as a number from 0–59. + * + * @method minute + * @return {Integer} current minute between 0 and 59. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the current minute. + * let m = minute(); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textSize(12); + * textFont('Courier New'); + * + * // Display the minute. + * text(`Current minute: ${m}`, 10, 50, 80); + * + * describe(`The text 'Current minute: ${m}' written in black on a gray background.`); + * } + * + *
    + */ + fn.minute = function() { + return new Date().getMinutes(); + }; -/** - * Returns the current minute as a number from 0–59. - * - * @method minute - * @return {Integer} current minute between 0 and 59. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the current minute. - * let m = minute(); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textSize(12); - * textFont('Courier New'); - * - * // Display the minute. - * text(`Current minute: ${m}`, 10, 50, 80); - * - * describe(`The text 'Current minute: ${m}' written in black on a gray background.`); - * } - * - *
    - */ -p5.prototype.minute = function() { - return new Date().getMinutes(); -}; + /** + * Returns the number of milliseconds since a sketch started running. + * + * `millis()` keeps track of how long a sketch has been running in + * milliseconds (thousandths of a second). This information is often + * helpful for timing events and animations. + * + * If a sketch has a + * setup() function, then `millis()` begins tracking + * time before the code in setup() runs. If a + * sketch includes a preload() function, then + * `millis()` begins tracking time as soon as the code in + * preload() starts running. + * + * @method millis + * @return {Number} number of milliseconds since starting the sketch. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the number of milliseconds the sketch has run. + * let ms = millis(); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textSize(10); + * textFont('Courier New'); + * + * // Display how long it took setup() to be called. + * text(`Startup time: ${round(ms, 2)} ms`, 5, 50, 90); + * + * describe( + * `The text 'Startup time: ${round(ms, 2)} ms' written in black on a gray background.` + * ); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe('The text "Running time: S sec" written in black on a gray background. The number S increases as the sketch runs.'); + * } + * + * function draw() { + * background(200); + * + * // Get the number of seconds the sketch has run. + * let s = millis() / 1000; + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textSize(10); + * textFont('Courier New'); + * + * // Display how long the sketch has run. + * text(`Running time: ${nf(s, 1, 1)} sec`, 5, 50, 90); + * } + * + *
    + * + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * describe('A white circle oscillates left and right on a gray background.'); + * } + * + * function draw() { + * background(200); + * + * // Get the number of seconds the sketch has run. + * let s = millis() / 1000; + * + * // Calculate an x-coordinate. + * let x = 30 * sin(s) + 50; + * + * // Draw the circle. + * circle(x, 50, 30); + * } + * + *
    + * + *
    + * + * // Load the GeoJSON. + * function preload() { + * loadJSON('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson'); + * } + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the number of milliseconds the sketch has run. + * let ms = millis(); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textFont('Courier New'); + * textSize(11); + * + * // Display how long it took to load the data. + * text(`It took ${round(ms, 2)} ms to load the data`, 5, 50, 100); + * + * describe( + * `The text "It took ${round(ms, 2)} ms to load the data" written in black on a gray background.` + * ); + * } + * + *
    + */ + fn.millis = function() { + if (this._millisStart === -1) { + // Sketch has not started + return 0; + } else { + return window.performance.now() - this._millisStart; + } + }; -/** - * Returns the number of milliseconds since a sketch started running. - * - * `millis()` keeps track of how long a sketch has been running in - * milliseconds (thousandths of a second). This information is often - * helpful for timing events and animations. - * - * If a sketch has a - * setup() function, then `millis()` begins tracking - * time before the code in setup() runs. If a - * sketch includes a preload() function, then - * `millis()` begins tracking time as soon as the code in - * preload() starts running. - * - * @method millis - * @return {Number} number of milliseconds since starting the sketch. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the number of milliseconds the sketch has run. - * let ms = millis(); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textSize(10); - * textFont('Courier New'); - * - * // Display how long it took setup() to be called. - * text(`Startup time: ${round(ms, 2)} ms`, 5, 50, 90); - * - * describe( - * `The text 'Startup time: ${round(ms, 2)} ms' written in black on a gray background.` - * ); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe('The text "Running time: S sec" written in black on a gray background. The number S increases as the sketch runs.'); - * } - * - * function draw() { - * background(200); - * - * // Get the number of seconds the sketch has run. - * let s = millis() / 1000; - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textSize(10); - * textFont('Courier New'); - * - * // Display how long the sketch has run. - * text(`Running time: ${nf(s, 1, 1)} sec`, 5, 50, 90); - * } - * - *
    - * - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * describe('A white circle oscillates left and right on a gray background.'); - * } - * - * function draw() { - * background(200); - * - * // Get the number of seconds the sketch has run. - * let s = millis() / 1000; - * - * // Calculate an x-coordinate. - * let x = 30 * sin(s) + 50; - * - * // Draw the circle. - * circle(x, 50, 30); - * } - * - *
    - * - *
    - * - * // Load the GeoJSON. - * function preload() { - * loadJSON('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson'); - * } - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the number of milliseconds the sketch has run. - * let ms = millis(); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textFont('Courier New'); - * textSize(11); - * - * // Display how long it took to load the data. - * text(`It took ${round(ms, 2)} ms to load the data`, 5, 50, 100); - * - * describe( - * `The text "It took ${round(ms, 2)} ms to load the data" written in black on a gray background.` - * ); - * } - * - *
    - */ -p5.prototype.millis = function() { - if (this._millisStart === -1) { - // Sketch has not started - return 0; - } else { - return window.performance.now() - this._millisStart; - } -}; + /** + * Returns the current month as a number from 1–12. + * + * @method month + * @return {Integer} current month between 1 and 12. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the current month. + * let m = month(); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textSize(12); + * textFont('Courier New'); + * + * // Display the month. + * text(`Current month: ${m}`, 10, 50, 80); + * + * describe(`The text 'Current month: ${m}' written in black on a gray background.`); + * } + * + *
    + */ + fn.month = function() { + //January is 0! + return new Date().getMonth() + 1; + }; -/** - * Returns the current month as a number from 1–12. - * - * @method month - * @return {Integer} current month between 1 and 12. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the current month. - * let m = month(); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textSize(12); - * textFont('Courier New'); - * - * // Display the month. - * text(`Current month: ${m}`, 10, 50, 80); - * - * describe(`The text 'Current month: ${m}' written in black on a gray background.`); - * } - * - *
    - */ -p5.prototype.month = function() { - //January is 0! - return new Date().getMonth() + 1; -}; + /** + * Returns the current second as a number from 0–59. + * + * @method second + * @return {Integer} current second between 0 and 59. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the current second. + * let s = second(); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textSize(12); + * textFont('Courier New'); + * + * // Display the second. + * text(`Current second: ${s}`, 10, 50, 80); + * + * describe(`The text 'Current second: ${s}' written in black on a gray background.`); + * } + * + *
    + */ + fn.second = function() { + return new Date().getSeconds(); + }; -/** - * Returns the current second as a number from 0–59. - * - * @method second - * @return {Integer} current second between 0 and 59. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the current second. - * let s = second(); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textSize(12); - * textFont('Courier New'); - * - * // Display the second. - * text(`Current second: ${s}`, 10, 50, 80); - * - * describe(`The text 'Current second: ${s}' written in black on a gray background.`); - * } - * - *
    - */ -p5.prototype.second = function() { - return new Date().getSeconds(); -}; + /** + * Returns the current year as a number such as 1999. + * + * @method year + * @return {Integer} current year. + * + * @example + *
    + * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Get the current year. + * let y = year(); + * + * // Style the text. + * textAlign(LEFT, CENTER); + * textSize(12); + * textFont('Courier New'); + * + * // Display the year. + * text(`Current year: ${y}`, 10, 50, 80); + * + * describe(`The text 'Current year: ${y}' written in black on a gray background.`); + * } + * + *
    + */ + fn.year = function() { + return new Date().getFullYear(); + }; +} -/** - * Returns the current year as a number such as 1999. - * - * @method year - * @return {Integer} current year. - * - * @example - *
    - * - * function setup() { - * createCanvas(100, 100); - * - * background(200); - * - * // Get the current year. - * let y = year(); - * - * // Style the text. - * textAlign(LEFT, CENTER); - * textSize(12); - * textFont('Courier New'); - * - * // Display the year. - * text(`Current year: ${y}`, 10, 50, 80); - * - * describe(`The text 'Current year: ${y}' written in black on a gray background.`); - * } - * - *
    - */ -p5.prototype.year = function() { - return new Date().getFullYear(); -}; +export default timeDate; -export default p5; +if(typeof p5 !== 'undefined'){ + timeDate(p5, p5.prototype); +} diff --git a/vitest.workspace.mjs b/vitest.workspace.mjs index 18ec5254c1..c5f497ce64 100644 --- a/vitest.workspace.mjs +++ b/vitest.workspace.mjs @@ -22,7 +22,7 @@ export default defineWorkspace([ exclude: [ './test/unit/spec.js', './test/unit/assets/**/*', - './test/unit/visual/visualTest.js', + './test/unit/visual/visualTest.js', ], testTimeout: 1000, globals: true,