8
8
import p5 from '../core/main' ;
9
9
import Filters from './filters' ;
10
10
import '../color/p5.Color' ;
11
+ import * as constants from '../core/constants' ;
11
12
12
13
/**
13
14
* <a href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference
@@ -325,12 +326,14 @@ p5.prototype._copyHelper = (
325
326
* `POSTERIZE`
326
327
* Limits each channel of the image to the number of colors specified as the
327
328
* parameter. The parameter can be set to values between 2 and 255, but
328
- * results are most noticeable in the lower ranges.
329
+ * results are most noticeable in the lower ranges. The default parameter is 4.
329
330
*
330
331
* `BLUR`
331
- * Executes a Gaussian blur with the level parameter specifying the extent
332
+ * Executes a blur with the level parameter specifying the extent
332
333
* of the blurring. If no parameter is used, the blur is equivalent to
333
- * Gaussian blur of radius 1. Larger values increase the blur.
334
+ * a blur of radius 4. Larger values increase the blur. In P2D mode a
335
+ * gaussian blur is performed on the CPU. When in webGL mode, a box blur is
336
+ * used instead.
334
337
*
335
338
* `ERODE`
336
339
* Reduces the light areas. No parameter is used.
@@ -340,23 +343,24 @@ p5.prototype._copyHelper = (
340
343
*
341
344
* ---
342
345
*
343
- * In WEBGL mode, `filter()` can also accept a shader. The fragment shader
344
- * is given a `uniform sampler2D` named `tex0` that contains the current
345
- * state of the canvas. For more information on using shaders, check
346
- * <a href="https://p5js.org/learn/getting-started-in-webgl-shaders.html">
347
- * the introduction to shaders</a> tutorial.
346
+ * These filter options use WebGL in the background by default (they're faster that way).
347
+ * To opt out of this in P2D mode, add a `false` parameter when calling `filter()`.
348
+ * This may be useful to keep computation off the GPU or to work around a lack of WebGL support.
349
+ *
350
+ * On a renderer in WEBGL mode, `filter()` can also accept a user-provided shader.
351
+ * For more information, see <a href="#/p5/createFilterShader">createFilterShader()</a>.
348
352
*
349
- * See also <a href="https://github.com/aferriss/p5jsShaderExamples"
350
- * target='_blank'>a selection of shader examples</a> by Adam Ferriss
351
- * that contains many similar filter effects.
352
353
*
353
354
* @method filter
354
355
* @param {Constant } filterType either THRESHOLD, GRAY, OPAQUE, INVERT,
355
356
* POSTERIZE, BLUR, ERODE, DILATE or BLUR.
356
357
* See Filters.js for docs on
357
358
* each available filter
358
- * @param {Number } [ filterParam] an optional parameter unique
359
+ * @param {Number } filterParam an optional parameter unique
359
360
* to each filter, see above
361
+ * @param {Boolean } [useWebGL] a flag to control whether to use fast
362
+ * WebGL filters (GPU) or original image
363
+ * filters (CPU); defaults to true
360
364
*
361
365
* @example
362
366
* <div>
@@ -465,39 +469,45 @@ p5.prototype._copyHelper = (
465
469
*
466
470
* <div>
467
471
* <code>
468
- * createCanvas(100, 100, WEBGL);
469
- * let myShader = createShader(
470
- * `attribute vec3 aPosition;
471
- * attribute vec2 aTexCoord;
472
+ * let img;
473
+ * function preload() {
474
+ * img = loadImage('assets/bricks.jpg');
475
+ * }
476
+ * function setup() {
477
+ * image(img, 0, 0);
478
+ * filter(BLUR, 3, useWebGL=false);
479
+ * }
480
+ * </code>
481
+ * </div>
472
482
*
473
- * varying vec2 vTexCoord;
483
+ * <div>
484
+ * <code>
485
+ * let img, s;
486
+ * function preload() {
487
+ * img = loadImage('assets/bricks.jpg');
488
+ * }
489
+ * function setup() {
490
+ * let fragSrc = `precision highp float;
474
491
*
475
- * void main() {
476
- * vTexCoord = aTexCoord;
477
- * vec4 positionVec4 = vec4(aPosition, 1.0);
478
- * positionVec4.xy = positionVec4.xy * 2.0 - 1.0;
479
- * gl_Position = positionVec4;
480
- * }`,
481
- * `precision mediump float;
482
- * varying mediump vec2 vTexCoord;
483
- *
484
- * uniform sampler2D tex0;
485
- *
486
- * float luma(vec3 color) {
487
- * return dot(color, vec3(0.299, 0.587, 0.114));
488
- * }
492
+ * varying vec2 vTexCoord; // x,y coordinates
493
+ * uniform sampler2D tex0; // the canvas contents
489
494
*
490
495
* void main() {
491
- * vec2 uv = vTexCoord;
492
- * uv.y = 1.0 - uv.y;
493
- * vec4 sampledColor = texture2D(tex0, uv);
494
- * float gray = luma(sampledColor.rgb);
495
- * gl_FragColor = vec4(gray, gray, gray, 1);
496
- * }`
497
- * );
498
- * background('RED');
499
- * filter(myShader);
500
- * describe('a canvas becomes gray after being filtered by shader');
496
+ * // get the color at current pixel
497
+ * vec4 color = texture2D(tex0, vTexCoord);
498
+ * // set the output color
499
+ * color.b = 1.0;
500
+ * gl_FragColor = vec4(color);
501
+ * }`;
502
+ *
503
+ * createCanvas(100, 100, WEBGL);
504
+ * s = createFilterShader(fragSrc);
505
+ * }
506
+ * function draw() {
507
+ * image(img, -50, -50);
508
+ * filter(s);
509
+ * describe('a image of bricks tinted blue');
510
+ * }
501
511
* </code>
502
512
* </div>
503
513
*
@@ -514,27 +524,117 @@ p5.prototype._copyHelper = (
514
524
* gray square
515
525
*/
516
526
527
+ /**
528
+ * @method filter
529
+ * @param {Constant } filterType
530
+ * @param {Boolean } [useWebGL]
531
+ */
517
532
/**
518
533
* @method filter
519
534
* @param {p5.Shader } shaderFilter A shader that's been loaded, with the
520
535
* frag shader using a `tex0` uniform
521
536
*/
522
- p5 . prototype . filter = function ( operation , value ) {
537
+ p5 . prototype . filter = function ( ... args ) {
523
538
p5 . _validateParameters ( 'filter' , arguments ) ;
524
539
525
- // TODO: use shader filters always, and provide an opt out
526
- if ( this . _renderer . isP3D ) {
527
- p5 . RendererGL . prototype . filter . call ( this . _renderer , arguments ) ;
540
+ let { shader, operation, value, useWebGL } = parseFilterArgs ( ...args ) ;
541
+
542
+ // when passed a shader, use it directly
543
+ if ( shader ) {
544
+ p5 . RendererGL . prototype . filter . call ( this . _renderer , shader ) ;
528
545
return ;
529
546
}
530
547
531
- if ( this . canvas !== undefined ) {
532
- Filters . apply ( this . canvas , Filters [ operation ] , value ) ;
533
- } else {
534
- Filters . apply ( this . elt , Filters [ operation ] , value ) ;
548
+ // when opting out of webgl, use old pixels method
549
+ if ( ! useWebGL && ! this . _renderer . isP3D ) {
550
+ if ( this . canvas !== undefined ) {
551
+ Filters . apply ( this . canvas , Filters [ operation ] , value ) ;
552
+ } else {
553
+ Filters . apply ( this . elt , Filters [ operation ] , value ) ;
554
+ }
555
+ return ;
556
+ }
557
+
558
+ if ( ! useWebGL && this . _renderer . isP3D ) {
559
+ console . warn ( 'filter() with useWebGL=false is not supported in WEBGL' ) ;
560
+ }
561
+
562
+ // when this is a webgl renderer, apply constant shader filter
563
+ if ( this . _renderer . isP3D ) {
564
+ p5 . RendererGL . prototype . filter . call ( this . _renderer , operation , value ) ;
565
+ }
566
+
567
+ // when this is P2D renderer, create/use hidden webgl renderer
568
+ else {
569
+ // create hidden webgl renderer if it doesn't exist
570
+ if ( ! this . filterGraphicsLayer ) {
571
+ // the real _pInst is buried when this is a secondary p5.Graphics
572
+ const pInst =
573
+ this . _renderer . _pInst instanceof p5 . Graphics ?
574
+ this . _renderer . _pInst . _pInst :
575
+ this . _renderer . _pInst ;
576
+
577
+ // create secondary layer
578
+ this . filterGraphicsLayer =
579
+ new p5 . Graphics (
580
+ this . width ,
581
+ this . height ,
582
+ constants . WEBGL ,
583
+ pInst
584
+ ) ;
585
+ }
586
+
587
+ // copy p2d canvas contents to secondary webgl renderer
588
+ // dest
589
+ this . filterGraphicsLayer . copy (
590
+ // src
591
+ this . _renderer ,
592
+ // src coods
593
+ 0 , 0 , this . width , this . height ,
594
+ // dest coords
595
+ - this . width / 2 , - this . height / 2 , this . width , this . height
596
+ ) ;
597
+
598
+ // filter it with shaders
599
+ this . filterGraphicsLayer . filter ( operation , value ) ;
600
+
601
+ // copy secondary webgl renderer back to original p2d canvas
602
+ this . _renderer . _pInst . image ( this . filterGraphicsLayer , 0 , 0 ) ;
603
+ this . filterGraphicsLayer . clear ( ) ; // prevent feedback effects on p2d canvas
535
604
}
536
605
} ;
537
606
607
+ function parseFilterArgs ( ...args ) {
608
+ // args could be:
609
+ // - operation, value, [useWebGL]
610
+ // - operation, [useWebGL]
611
+ // - shader
612
+
613
+ let result = {
614
+ shader : undefined ,
615
+ operation : undefined ,
616
+ value : undefined ,
617
+ useWebGL : true
618
+ } ;
619
+
620
+ if ( args [ 0 ] instanceof p5 . Shader ) {
621
+ result . shader = args [ 0 ] ;
622
+ return result ;
623
+ }
624
+ else {
625
+ result . operation = args [ 0 ] ;
626
+ }
627
+
628
+ if ( args . length > 1 && typeof args [ 1 ] === 'number' ) {
629
+ result . value = args [ 1 ] ;
630
+ }
631
+
632
+ if ( args [ args . length - 1 ] === false ) {
633
+ result . useWebGL = false ;
634
+ }
635
+ return result ;
636
+ }
637
+
538
638
/**
539
639
* Get a region of pixels, or a single pixel, from the canvas.
540
640
*
0 commit comments