Search code examples
javascriptfabricjs

Restore default filter for cloned image in fabric js (seems buggy)


As the title says, I use fabric js. In my scenario, I want to apply filters to an image but also undo them (even after saving). However, this seems to be problematic, because whenever I clone an image to edit it with filters, save it and then want to edit it again (e.g. to change the value of the applied filter), the default value (0) is not taken but the already applied value. Even if I delete the filters in the cloned object and set them to an empty array. For me this seems to be a bug but maybe I am doing something wrong. Can someone help me?

To illustrate, here is a fiddle: https://jsfiddle.net/z568udwn/ Explanation: An image is loaded. When you click on “start filter” it is cloned in a temporary canvas and after 1 second a filter is automatically applied to it. If you then click on “save filter”, it is applied to the main image and the temporary canvas with the cloned object is deleted. If you now click on “start filter” again, you will see the problem I have tried to describe.

code from fiddle:

var addEvent = function(el, type, handler) {
  if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler);
}

var image_base64 = ' data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/...'
var custom_image;
    var copy_canvas;
    var copy_image;

    const canvas = new fabric.Canvas('c', {
      width: 300,
      height: 300,
      backgroundColor: '#ffffff',
      selectable: false,
      selection: false,
      preserveObjectStacking: true,
      allowTouchScrolling: true,
    });
    canvas.renderAll();

    fabric.Image.fromURL(image_base64, (image) => {
      custom_image = image.set({
        top: 0,
        left: 0,
        selectable: false,
        scalable: false,
        controls: false,
      });
      custom_image.scaleToHeight(300);
      custom_image.scaleToWidth(300);
      canvas.add(custom_image);
      canvas.renderAll();
    });

    addEvent(document.querySelector('#start-edit'), 'click', (e) => {
      copy_canvas = new fabric.Canvas('copy-canvas', {
        width: 300,
        height: 300,
        backgroundColor: '#aaaaaa',
        selectable: false,
        selection: false,
        preserveObjectStacking: true,
        allowTouchScrolling: true,
      });
      copy_canvas.renderAll();

      custom_image.clone((img_clone) => {
        img_clone = img_clone.toDataURL();
        fabric.Image.fromURL(img_clone, (image) => {
          copy_image = image.set({
            top: 0,
            left: 0,
            selectable: false,
            scalable: false,
            controls: false,
          });
          copy_image.filters = [];
          copy_image.applyFilters();
          copy_image.scaleToHeight(300);
          copy_image.scaleToWidth(300);
          copy_canvas.add(copy_image);
          copy_canvas.renderAll();

          setTimeout(() => {
            image.filters = [new fabric.Image.filters.Saturation({
              saturation: 0.5
            })];
            image.applyFilters();
            copy_canvas.renderAll();
          }, 1000);
        });
        
      });
    });

    addEvent(document.querySelector('#finish-edit'), 'click', (e) => {
      custom_image.filters = copy_image.filters;
      custom_image.applyFilters();
      canvas.renderAll();
      copy_canvas.dispose();
    });

Solution

  • I found the issue and solution myself. I convert the fabric js image object to a data url, mainly to avoid issues when making a new image for the filter process. The filters are "hard rendered" in that moment. So instead of removing the filter when creating the fabric object I have to do it before like this

    custom_image.clone((img_clone) => {
            // remove filters for sure
            img_clone.filters = [];
            img_clone.applyFilters();
            // continue
            img_clone = img_clone.toDataURL();
            fabric.Image.fromURL(img_clone, (image) => {
    

    Dunno why I havn't noticed this earlier.