diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js
index 800d31ecc..87f99c5a3 100644
--- a/src/RenderWebGL.js
+++ b/src/RenderWebGL.js
@@ -188,9 +188,23 @@ class RenderWebGL extends EventEmitter {
         /** @type {function} */
         this._exitRegion = null;
 
+        /** @type {object} */
+        this._backgroundDrawRegionId = {
+            enter: () => this._enterDrawBackground(),
+            exit: () => this._exitDrawBackground()
+        };
+
         /** @type {Array.<snapshotCallback>} */
         this._snapshotCallbacks = [];
 
+        /** @type {Array<number>} */
+        // Don't set this directly-- use setBackgroundColor so it stays in sync with _backgroundColor3b
+        this._backgroundColor4f = [0, 0, 0, 1];
+
+        /** @type {Uint8ClampedArray} */
+        // Don't set this directly-- use setBackgroundColor so it stays in sync with _backgroundColor4f
+        this._backgroundColor3b = new Uint8ClampedArray(3);
+
         this._createGeometry();
 
         this.on(RenderConstants.Events.NativeSizeChanged, this.onNativeSizeChanged);
@@ -250,7 +264,14 @@ class RenderWebGL extends EventEmitter {
      * @param {number} blue The blue component for the background.
      */
     setBackgroundColor (red, green, blue) {
-        this._backgroundColor = [red, green, blue, 1];
+        this._backgroundColor4f[0] = red;
+        this._backgroundColor4f[1] = green;
+        this._backgroundColor4f[2] = blue;
+
+        this._backgroundColor3b[0] = red * 255;
+        this._backgroundColor3b[1] = green * 255;
+        this._backgroundColor3b[2] = blue * 255;
+
     }
 
     /**
@@ -629,7 +650,7 @@ class RenderWebGL extends EventEmitter {
 
         twgl.bindFramebufferInfo(gl, null);
         gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
-        gl.clearColor.apply(gl, this._backgroundColor);
+        gl.clearColor.apply(gl, this._backgroundColor4f);
         gl.clear(gl.COLOR_BUFFER_BIT);
 
         this._drawThese(this._drawList, ShaderManager.DRAW_MODE.default, this._projection);
@@ -745,12 +766,20 @@ class RenderWebGL extends EventEmitter {
      */
     isTouchingColor (drawableID, color3b, mask3b) {
         const candidates = this._candidatesTouching(drawableID, this._visibleDrawList);
-        if (candidates.length === 0) {
+
+        let bounds;
+        if (colorMatches(color3b, this._backgroundColor3b, 0)) {
+            // If the color we're checking for is the background color, don't confine the check to
+            // candidate drawables' bounds--since the background spans the entire stage, we must check
+            // everything that lies inside the drawable.
+            bounds = this._touchingBounds(drawableID);
+        } else if (candidates.length === 0) {
+            // If not checking for the background color, we can return early if there are no candidate drawables.
             return false;
+        } else {
+            bounds = this._candidatesBounds(candidates);
         }
 
-        const bounds = this._candidatesBounds(candidates);
-
         const maxPixelsForCPU = this._getMaxPixelsForCPU();
 
         const debugCanvasContext = this._debugCanvas && this._debugCanvas.getContext('2d');
@@ -811,6 +840,19 @@ class RenderWebGL extends EventEmitter {
         }
     }
 
+    _enterDrawBackground () {
+        const gl = this.gl;
+        const currentShader = this._shaderManager.getShader(ShaderManager.DRAW_MODE.background, 0);
+        gl.disable(gl.BLEND);
+        gl.useProgram(currentShader.program);
+        twgl.setBuffersAndAttributes(gl, currentShader, this._bufferInfo);
+    }
+
+    _exitDrawBackground () {
+        const gl = this.gl;
+        gl.enable(gl.BLEND);
+    }
+
     _isTouchingColorGpuStart (drawableID, candidateIDs, bounds, color3b, mask3b) {
         this._doExitDrawRegion();
 
@@ -822,15 +864,8 @@ class RenderWebGL extends EventEmitter {
         gl.viewport(0, 0, bounds.width, bounds.height);
         const projection = twgl.m4.ortho(bounds.left, bounds.right, bounds.top, bounds.bottom, -1, 1);
 
-        let fillBackgroundColor = this._backgroundColor;
-
-        // When using masking such that the background fill color will showing through, ensure we don't
-        // fill using the same color that we are trying to detect!
-        if (color3b[0] > 196 && color3b[1] > 196 && color3b[2] > 196) {
-            fillBackgroundColor = [0, 0, 0, 255];
-        }
-
-        gl.clearColor.apply(gl, fillBackgroundColor);
+        // Clear the query buffer to fully transparent. This will be the color of pixels that fail the stencil test.
+        gl.clearColor(0, 0, 0, 0);
         gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
 
         let extraUniforms;
@@ -842,6 +877,9 @@ class RenderWebGL extends EventEmitter {
         }
 
         try {
+            // Using the stencil buffer, mask out the drawing to either the drawable's alpha channel
+            // or pixels of the drawable which match the mask color, depending on whether a mask color is given.
+            // Masked-out pixels will not be checked.
             gl.enable(gl.STENCIL_TEST);
             gl.stencilFunc(gl.ALWAYS, 1, 1);
             gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
@@ -862,12 +900,25 @@ class RenderWebGL extends EventEmitter {
             gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
             gl.colorMask(true, true, true, true);
 
+            // Draw the background as a quad. Drawing a background with gl.clear will not mask to the stenciled area.
+            this.enterDrawRegion(this._backgroundDrawRegionId);
+
+            const uniforms = {
+                u_backgroundColor: this._backgroundColor4f
+            };
+
+            const currentShader = this._shaderManager.getShader(ShaderManager.DRAW_MODE.background, 0);
+            twgl.setUniforms(currentShader, uniforms);
+            twgl.drawBufferInfo(gl, this._bufferInfo, gl.TRIANGLES);
+
+            // Draw the candidate drawables on top of the background.
             this._drawThese(candidateIDs, ShaderManager.DRAW_MODE.default, projection,
                 {idFilterFunc: testID => testID !== drawableID}
             );
         } finally {
             gl.colorMask(true, true, true, true);
             gl.disable(gl.STENCIL_TEST);
+            this._doExitDrawRegion();
         }
     }
 
@@ -886,7 +937,8 @@ class RenderWebGL extends EventEmitter {
         }
 
         for (let pixelBase = 0; pixelBase < pixels.length; pixelBase += 4) {
-            if (colorMatches(color3b, pixels, pixelBase)) {
+            // Transparent pixels are masked (either by the drawable's alpha channel or color mask).
+            if (pixels[pixelBase + 3] !== 0 && colorMatches(color3b, pixels, pixelBase)) {
                 return true;
             }
         }
@@ -1321,7 +1373,7 @@ class RenderWebGL extends EventEmitter {
         gl.viewport(0, 0, bounds.width, bounds.height);
         const projection = twgl.m4.ortho(bounds.left, bounds.right, bounds.top, bounds.bottom, -1, 1);
 
-        gl.clearColor.apply(gl, this._backgroundColor);
+        gl.clearColor.apply(gl, this._backgroundColor4f);
         gl.clear(gl.COLOR_BUFFER_BIT);
         this._drawThese(this._drawList, ShaderManager.DRAW_MODE.default, projection);
 
@@ -1409,6 +1461,13 @@ class RenderWebGL extends EventEmitter {
                     // Update the CPU position data
                     drawable.updateCPURenderAttributes();
                     const candidateBounds = drawable.getFastBounds();
+
+                    // Push bounds out to integers. If a drawable extends out into half a pixel, that half-pixel still
+                    // needs to be tested. Plus, in some areas we construct another rectangle from the union of these,
+                    // and iterate over its pixels (width * height). Turns out that doesn't work so well when the
+                    // width/height aren't integers.
+                    candidateBounds.snapToInt();
+
                     if (bounds.intersects(candidateBounds)) {
                         result.push({
                             id,
diff --git a/src/ShaderManager.js b/src/ShaderManager.js
index 8c59d3b7d..37c24346d 100644
--- a/src/ShaderManager.js
+++ b/src/ShaderManager.js
@@ -176,7 +176,12 @@ ShaderManager.DRAW_MODE = {
     /**
      * Draw a line with caps.
      */
-    line: 'line'
+    line: 'line',
+
+    /**
+     * Draw the background in a certain color. Must sometimes be used instead of gl.clear.
+     */
+    background: 'background'
 };
 
 module.exports = ShaderManager;
diff --git a/src/shaders/sprite.frag b/src/shaders/sprite.frag
index f13005901..da286883d 100644
--- a/src/shaders/sprite.frag
+++ b/src/shaders/sprite.frag
@@ -39,9 +39,15 @@ uniform float u_lineThickness;
 uniform float u_lineLength;
 #endif // DRAW_MODE_line
 
+#ifdef DRAW_MODE_background
+uniform vec4 u_backgroundColor;
+#endif // DRAW_MODE_background
+
 uniform sampler2D u_skin;
 
+#ifndef DRAW_MODE_background
 varying vec2 v_texCoord;
+#endif
 
 // Add this to divisors to prevent division by 0, which results in NaNs propagating through calculations.
 // Smaller values can cause problems on some mobile devices.
@@ -109,7 +115,7 @@ const vec2 kCenter = vec2(0.5, 0.5);
 
 void main()
 {
-	#ifndef DRAW_MODE_line
+	#if !(defined(DRAW_MODE_line) || defined(DRAW_MODE_background))
 	vec2 texcoord0 = v_texCoord;
 
 	#ifdef ENABLE_mosaic
@@ -215,7 +221,9 @@ void main()
 	gl_FragColor.rgb /= gl_FragColor.a + epsilon;
 	#endif
 
-	#else // DRAW_MODE_line
+	#endif // !(defined(DRAW_MODE_line) || defined(DRAW_MODE_background))
+
+	#ifdef DRAW_MODE_line
 	// Maaaaagic antialiased-line-with-round-caps shader.
 
 	// "along-the-lineness". This increases parallel to the line.
@@ -234,4 +242,8 @@ void main()
 	// the closer we are to the line, invert it.
 	gl_FragColor = u_lineColor * clamp(1.0 - line, 0.0, 1.0);
 	#endif // DRAW_MODE_line
+
+	#ifdef DRAW_MODE_background
+	gl_FragColor = u_backgroundColor;
+	#endif
 }
diff --git a/src/shaders/sprite.vert b/src/shaders/sprite.vert
index ffc5150f0..24437c3fc 100644
--- a/src/shaders/sprite.vert
+++ b/src/shaders/sprite.vert
@@ -14,7 +14,7 @@ uniform vec4 u_penPoints;
 const float epsilon = 1e-3;
 #endif
 
-#ifndef DRAW_MODE_line
+#if !(defined(DRAW_MODE_line) || defined(DRAW_MODE_background))
 uniform mat4 u_projectionMatrix;
 uniform mat4 u_modelMatrix;
 attribute vec2 a_texCoord;
@@ -66,6 +66,8 @@ void main() {
 	// 4. Apply view transform
 	position *= 2.0 / u_stageSize;
 	gl_Position = vec4(position, 0, 1);
+	#elif defined(DRAW_MODE_background)
+	gl_Position = vec4(a_position * 2.0, 0, 1);
 	#else
 	gl_Position = u_projectionMatrix * u_modelMatrix * vec4(a_position, 0, 1);
 	v_texCoord = a_texCoord;
diff --git a/test/integration/scratch-tests/clear-color.sb3 b/test/integration/scratch-tests/clear-color.sb3
new file mode 100644
index 000000000..42b6d8516
Binary files /dev/null and b/test/integration/scratch-tests/clear-color.sb3 differ