Search code examples
javascriptimageasynchronouslayerpaperjs

Paper.js Loading Images and the Active Layer


I'm using Paper.js to make an image editor that handles a queue of images. These images come from PDFs that are uploaded to a server and converted to images. As per the suggestions in this question, I'm loading each image into its own layer.

// Load images of pdf, each into its own layer
var loadPdf = function(pdfAsImages) {
  toolsSetup();

  // Loop through images for pdf
  pdfToLayerMap[pdfAsImages.id] = [];
  for(var i = 0; i < pdfAsImages.images.length; i++) {
    var layerForImage = new paper.Layer();
    layerForImage.visible = false;
    pdfToLayerMap[pdfAsImages.id][i] = {
      layerIndex: paper.project.activeLayer.index,
      imageHeight: pdfAsImages.images[i].height
    };

    var imageDataUrl = 'data:image/png;base64,' + pdfAsImages.images[i].imageData;
    var raster = new paper.Raster(imageDataUrl, new paper.Point(canvas.width / 2, canvas.height / 2));
    layerForImage.addChild(raster);

    selectedItemsByLayer[layerForImage.index] = new paper.Group();

    registerLayerEvents(layerForImage);
  }
};

var setActive = function(pdfIndex, imageIndex) {
  var imageHeight =  pdfToLayerMap[pdfIndex][imageIndex].imageHeight;
  paper.project.activeLayer.visible = false;
  var layerToActivate = pdfToLayerMap[pdfIndex][imageIndex].layerIndex;
  paper.project.layers[layerToActivate].activate();
  paper.project.layers[layerToActivate].visible = true;
  fitToPage(imageHeight);
};

loadPdf is called each time we receive an image from the server. This adds a new layer for each image of each page in the pdf. The trouble is that when a new layer is created it is set as the active layer. So while images are loading from the server, we want to be able to perform edits on images already loaded. But as images loaded, the active layer gets out of sync with whatever already loaded image we're currently editing. So if I try to add text, for example, it will add it to the wrong image because it will add it to the active layer, which is the wrong layer. Somehow we need to keep the active layer the layer with the image that we're currently editing. (Could this be done with a separate Paper context?)

Since we don't know the number of images beforehand (we know the number of PDFs, but not how many pages each has), we can't do something like this, initializing all the layers beforehand:

var initializeLayers = function(numberOfLayers) {
  for(var i = 0; i < numberOfLayers; i++) {
    var layerForImage = new paper.Layer();
    layerForImage.visible = false;
    selectedItemsByLayer[i] = new paper.Group();
  }

  paper.project.layers[0].activate();
};

So is there a way I can prevent Paper.js from setting a new layer to active upon creation? Or is there another way around this?


Solution

  • Simple answer, yes.

    var layer = new paper.Layer({insert: false});

    Edit to address questioner's comment below:

    The sketch you refer to is trying to manipulate paper's internal data structures directly. That's why it doesn't work.

    Here's a modified sketch that handles things without messing with paper's internals.

    BTW, just in case of interest, here's a meta-solution of the on-demand approach I threw together yesterday before I realized that paper could create a layer but not insert it into the project. I say meta-solution because I just coded it here without any testing, so if you encounter problems let me know and I'll fix them.

    // Load images of pdf, each into its own layer
    var loadPdf = function(pdfAsImages) {
        toolsSetup();
    
        // Loop through images for pdf
        pdfToLayerMap[pdfAsImages.id] = [];
        for(var i = 0; i < pdfAsImages.images.length; i++) {
            pdfToLayerMap[pdfAsImages.id][i] = {
                layerIndex: null,
                imageHeight: pdfAsImages.images[i].height,
                dataURL: 'data:image/png;base64,' + pdfAsImages.images[i].imageData;
            };
        };
    };
    
    var setActive = function(pdfIndex, imageIndex) {
        var image = pdfToLayerMap[pdfIndex][imageIndex];
    
        // make the current layer invisible
        paper.project.activeLayer.visible = false;
    
        // if the layer has already been created just enable it and return
        if (image.layerIndex !== null) {
            paper.project.layers[image.layerIndex].activate();
            paper.project.layers[image.layerIndex].visible = true;
            fitToPage(image.imageHeight);
            return;
        } 
    
        // the layer hasn't been created, create it
        var layer = new paper.Layer();
        image.layerIndex = layer.index;
        layer.addChild(new paper.Raster(image.dataURL,
                                    paper.view.bounds.center));
        selectedItemsByLayer[layer.index] = new paper.Group();
        registerLayerEvents(layer);
        }
    
    };
    

    You may want to do things like null out the dataURL after creating the raster to save some memory, depending on how many of these might be loaded at any given time.