form.follows.function
form.follows.function

Reputation: 53

Pixelated resize using canvas with transparent PNG

I want to accomplish a pixelated effect using the canvas option imageSmoothingEnabled=false; so the image "unblurs" on scroll.

Everything works fine until using transparent images namely PNGs. The scaled image is projected, which stays in the background.

Also the image does not get loaded until the user has scrolled a few pixels.

I've found out that the canvas.drawImage() function owns parameters to set the offset. However I haven't found a solution to this.

Demo https://jsfiddle.net/aLjfemru/

var ctx = canvas.getContext('2d'),
  img = new Image(),
  play = false;

/// turn off image smoothing - this will give the pixelated effect
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;


/// wait until image is actually available
img.onload = function(){
                image1.src="nf.png";

                context.drawImage(image1, 50, 50, 10, 10);
                };

img.src = 'https://upload.wikimedia.org/wikipedia/commons/b/bb/Gorgosaurus_BW_transparent.png';

/// MAIN function
function pixelate(v) {
  document.getElementById("v").innerHTML = "(v): " + v;

  /// if in play mode use that value, else use slider value
  var size = v * 0.01;

  var w = canvas.width * size;
  var h = canvas.height * size;

  /// draw original image to the scaled size
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.drawImage(img, 0, 0, w, h);
  ctx.drawImage(canvas, 0, 0, w, h, 0, 0, canvas.width, canvas.height);
}

function onScroll() {
  $(window).on('scroll', function() {
    var y = window.pageYOffset;
    if (y > 10) {
      y = Math.pow(y, 0.8);
      if (y >= 60) {
        y = 100;
      }
      pixelate(y);
    }
  });
}
onScroll();

Upvotes: 4

Views: 1341

Answers (2)

Aaron Meese
Aaron Meese

Reputation: 2213

This has since been made into an extremely minimalist library, and my PR for PNG support can be found here.

Once it has been merged I will come back and update this answer.

The full code, generalized and simplified from @Blindman67's answer:

/**
 * 8bit
 *
 * A module that converts an image into a pixelated version (just like
 * 8bit artwork).
 *
 * @author rogeriopvl <https://github.com/rogeriopvl>
 * @license MIT
 */

(function (root, factory) {
  if (typeof define === "function" && define.amd) {
    define([], factory);
  } else if (typeof exports === "object") {
    module.exports = factory();
  } else {
    root.eightBit = factory();
  }
} (this, function () {
  // Necessary to hide the original image with PNG transparency
  const invisibleCanvas = document.createElement("canvas");
  const invisibleCtx = invisibleCanvas.getContext("2d");

  /**
   * Draws a pixelated version of an image in a given canvas.
   * @param {object} canvas - a canvas object
   * @param {object} image - an image HTMLElement object
   * @param {number} quality - the new quality: between 0 and 100
   */
  const eightBit = function (canvas, image, quality) {
    quality /= 100;

    canvas.width = invisibleCanvas.width = image.width;
    canvas.height = invisibleCanvas.height = image.height;

    const scaledW = canvas.width * quality;
    const scaledH = canvas.height * quality;
    const ctx = canvas.getContext("2d");

    ctx.mozImageSmoothingEnabled = false;
    ctx.webkitImageSmoothingEnabled = false;
    ctx.imageSmoothingEnabled = false;

    // Draws image scaled to desired quality on the invisible canvas, then
    // draws that scaled image on the visible canvas.
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    invisibleCtx.clearRect(0, 0, invisibleCtx.canvas.width, invisibleCtx.canvas.height);
    invisibleCtx.drawImage(image, 0, 0, scaledW, scaledH);
    ctx.drawImage(invisibleCtx.canvas, 0, 0, scaledW, scaledH, 0, 0, canvas.width, canvas.height);
  };

  return eightBit;
}));

Upvotes: 0

Blindman67
Blindman67

Reputation: 54026

Some quick changes to get it happening

Use a second canvas to do the pixelation

Wait for the images to load before doing the rendering.

The onscroll will not fire until you scroll, so when image has loaded call the rendering function to display the image.

canvas.width = innerWidth-20;
ctx = canvas.getContext("2d");
var ctxImage;
const img = new Image;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/b/bb/Gorgosaurus_BW_transparent.png';
/// wait until image is actually available
img.onload = function(){
     // I dont knwo what this is for so removed the following two lines
    //image1.src="nf.png"; 
    //context.drawImage(image1, 50, 50, 10, 10);
    // Create a canvas to match the image
    var c = document.createElement("canvas");
    canvas.width = Math.min(canvas.width,(c.width = this.naturalWidth));
    canvas.height = c.height = this.naturalHeight;
    ctxImage = c.getContext("2d");
    // changing canvas size resets the state so need to set this again.
    ctx.imageSmoothingEnabled = false;
    onScroll();
    pixelate(100); // call first time
};

ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.fillText("Loading please wait.",ctx.canvas.width /2, ctx.canvas.height / 4);
/// MAIN function
function pixelate(v) {
  document.getElementById("v").innerHTML = "(v): " + v;

  /// if in play mode use that value, else use slider value
  var size = Number(v) * 0.01;

  var w = img.width * size;
  var h = img.height * size;

 
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctxImage.clearRect(0, 0, ctxImage.canvas.width, ctxImage.canvas.height);
  ctxImage.drawImage(img, 0, 0, w, h);
  ctx.drawImage(ctxImage.canvas, 0, 0, w, h, 0, 0, canvas.width, canvas.height);
}

function onScroll() {
  addEventListener("scroll", function() {

    var y = window.pageYOffset;
    if (y > 10) {
      y = Math.pow(y, 0.65);
      if (y >= 100) {
        y = 100;
      }
      pixelate(y);
    }
  });
      
}
#fix {
      position: fixed;
    }

    html {
      height: 2000px;
    }
<div id="fix">
<p id="v" value="Animate">1</p><br />

<canvas id="canvas"></canvas>
</div>

Upvotes: 2

Related Questions