Download as Photo Collage

A user script to allow user to select any images online then download them as a photo collage.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Download as Photo Collage
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  A user script to allow user to select any images online then download them as a photo collage.
// @author       Lamonkey
// @match        *://*/*
// @match        https://twitter.com/*
// @match        https://www.instagram.com/*
// @match        https://www.facebook.com/*
// @match        https://www.pinterest.com/*
// @match        https://www.tumblr.com/*
// @match        https://www.reddit.com/*
// @match        https://www.flickr.com/*
// @icon         https://cdn-icons-png.flaticon.com/512/9813/9813564.png
// @grant        none
// @license      MIT
// ==/UserScript==
(function () {
  "use strict";
  //create floating circle
  const floatingCircle = document.createElement("div");
  floatingCircle.setAttribute("id", "floating-circle");
  const floatingCircleContent = document.createElement("div");
  floatingCircleContent.setAttribute("id", "floating-circle-content");
  const counterDisplay = document.createElement("h1");
  counterDisplay.innerText = "count";
  // set the CSS styles for floatingCircle
  floatingCircle.style.position = "fixed";
  floatingCircle.style.top = "1em";
  floatingCircle.style.right = "1em";
  floatingCircle.style.zIndex = "10000";
  floatingCircle.style.height = "100px";
  floatingCircle.style.width = "100px";
  floatingCircle.style.backgroundColor = "aquamarine";
  floatingCircle.style.opacity = "0.6";
  floatingCircle.style.borderRadius = "50%";
  floatingCircle.style.display = "flex";
  floatingCircle.style.color = "white";
  floatingCircle.style.alignItems = "center";
  floatingCircle.style.justifyContent = "center";
  //css for floating-circle-content
  floatingCircleContent.style.display = "flex";
  floatingCircleContent.style.flexDirection = "column";
  floatingCircleContent.style.alignItems = "center";
  floatingCircleContent.style.justifyContent = "center";
  floatingCircleContent.style.height = "100%";
  floatingCircleContent.style.width = "100%";
  //add floating circle to DOM
  floatingCircle.appendChild(floatingCircleContent);
  floatingCircleContent.appendChild(counterDisplay);
  document.body.appendChild(floatingCircle);

  //create modal component
  const modalBox = document.createElement("div");
  modalBox.setAttribute("class", "modalBox");
  const modalNav = document.createElement("div");
  modalNav.setAttribute("class", "modalNav");
  const modalClose = document.createElement("span");
  modalClose.setAttribute("class", "modalClose");
  modalClose.innerHTML = "&times"; // x symbol
  const modalContent = document.createElement("div");
  modalContent.setAttribute("class", "modalContent");
  const photosContainer = document.createElement("ol");
  photosContainer.setAttribute("class", "photosContainer");
  const modalCanvas = document.createElement("canvas");
  modalCanvas.class = "modalCanvas";
  //style for modelBox
  modalBox.style.position = "fixed";
  modalBox.style.zIndex = "10000";
  modalBox.style.display = "none";
  modalBox.style.flexDirection = "column";
  modalBox.style.left = "20%";
  modalBox.style.top = "10%";
  modalBox.style.width = "60vw";
  modalBox.style.height = "60vh";
  modalBox.style.backgroundColor = "rgba(255,255,255,0.4)";
  //styling modalContent
  modalContent.style.zIndex = "10000";
  // modalContent.style.padding = "0";
  modalContent.style.height = "100%";
  modalContent.style.overflowY = "auto";
  modalContent.style.overflowX = "auto";
  modalContent.style.display = "block";
  modalContent.style.flexWrap = "wrap";
  modalContent.style.alignItems = "flex-start";
  //styling modalNav
  modalNav.style.backgroundColor = "white";
  modalNav.style.display = "flex";
  modalNav.style.justifyContent = "end";
  //close styleing
  modalClose.style.marginLeft = "10px";
  modalClose.style.color = "rgb(48, 56, 71)";
  modalClose.style.fontSize = "28px";
  modalClose.style.fontWeight = "bold";
  modalClose.style.cursor = "pointer";
  //photosContainer styling
  photosContainer.style.display = "flex";
  photosContainer.style.flexWrap = "wrap";
  photosContainer.style.margin = "2%";
  //clone a photo container for canvas
  const layoutContent = photosContainer.cloneNode();
  layoutContent.className = "layoutContent";
  const layoutContainer = modalContent.cloneNode();
  layoutContainer.className = "layoutContainer";
  layoutContainer.appendChild(layoutContent);
  layoutContainer.style.display = "none";
  //nav button
  const creatCanvas = document.createElement("button");
  creatCanvas.style.border = "none";
  creatCanvas.style.backgroundColor = "white";
  creatCanvas.className = "createCanvas";
  creatCanvas.innerText = `${String.fromCodePoint(0x1f304)}Create Collage`;
  const canvasContainner = modalContent.cloneNode();
  //styling nav button
  creatCanvas.style.marginLeft = "10px";
  creatCanvas.style.color = "rgb(48, 56, 71)";
  creatCanvas.style.fontSize = "28px";
  creatCanvas.style.fontWeight = "bold";
  canvasContainner.style.display = "none";
  //add modalBox to DOM
  modalContent.appendChild(photosContainer);
  modalBox.appendChild(modalNav);
  canvasContainner.appendChild(modalCanvas);
  modalBox.appendChild(canvasContainner);
  modalBox.appendChild(modalContent);
  modalBox.appendChild(layoutContainer);
  document.body.appendChild(modalBox);
  //create check image
  const checkImages = creatCanvas.cloneNode();
  checkImages.className = "checkImages";
  // checkImages.innerHTML = String.fromCodePoint(0x1f5bc);
  checkImages.innerText = `${String.fromCodePoint(0x1f5bc)}Check Images`;
  modalNav.appendChild(checkImages);
  modalNav.appendChild(creatCanvas);
  modalNav.appendChild(modalClose);

  //glable setting
  var imageSourceMap = new Map();
  var imagesSrcList = [];
  var imagePerRow = 1;
  var highlightedImages = [];
  var maxImgheight = 1000;
  var padding_between_images = 10;

  //utility function
  function getRenderedSize(contains, cWidth, cHeight, width, height, pos) {
    //get the size of the image after being rendered
    var oRatio = width / height,
      cRatio = cWidth / cHeight;
    return function () {
      if (contains ? oRatio > cRatio : oRatio < cRatio) {
        this.width = cWidth;
        this.height = cWidth / oRatio;
      } else {
        this.width = cHeight * oRatio;
        this.height = cHeight;
      }
      this.left = (cWidth - this.width) * (pos / 100);
      this.right = this.width + this.left;
      return this;
    }.call({});
  }
  function getImgSizeInfo(img) {
    //interface for getrenderSize
    var pos = window
      .getComputedStyle(img)
      .getPropertyValue("object-position")
      .split(" ");
    return getRenderedSize(
      true,
      img.width,
      img.height,
      img.naturalWidth,
      img.naturalHeight,
      parseInt(pos[0])
    );
  }
  function putImageIntoRow() {
    //put images into different row, key is row number value is list of imgs
    let row = new Map();
    for (let i = 0; i < highlightedImages.length; i++) {
      let y = Math.floor(highlightedImages[i].getBoundingClientRect().y);
      if (row.has(y)) {
        row.get(y).push(highlightedImages[i]);
      } else {
        row.set(y, [highlightedImages[i]]);
      }
    }
    return row;
  }
  function toggle_images(event) {
    /**
     * toggle the image border and add to or remove from hightlightedImages
     */
    let selected_image = event.target;
    console.log(event.target.getBoundingClientRect());
    console.log(getImgSizeInfo(selected_image));
    //unselect
    if (selected_image.classList.contains("selected")) {
      const index = highlightedImages.indexOf(event.target);
      if (index > -1) {
        highlightedImages.splice(index, 1);
      }
      selected_image.style.border = "";
      selected_image.classList.remove("selected");
      //also mark the containner
      selected_image.parentElement.classList("selected");
    }
    //select
    else {
      selected_image.style.border = "2px solid red";
      selected_image.classList.add("selected");
      //also mark the containner
      selected_image.parentElement.classList.add("selected");
      highlightedImages.push(event.target);
    }
  }
  function resize_canvas(setting) {
    /**
     * resize the canvas based on setting
     * */
    const ctx = modalCanvas.getContext("2d");
    modalCanvas.width = setting.width;
    modalCanvas.height = setting.height;
    ctx.clearRect(0, 0, modalCanvas.width, modalCanvas.height);
    modalCanvas.style.border = "1px solid black";
  }

  function get_canvas_height_and_width() {
    /**
     * return the size of the canvas based on the layout of layoutContainner
     */
    //TODO: maybe could just simply get the size of the layoutcontainer
    let smallestX = Infinity;
    let smallestY = Infinity;
    for (let i = 0; i < highlightedImages.length; i++) {
      let x = highlightedImages[i].getBoundingClientRect().x;
      let y = highlightedImages[i].getBoundingClientRect().y;
      if (x < smallestX) {
        smallestX = x;
      }
      if (y < smallestY) {
        smallestY = y;
      }
    }
    //find the ending position which is the largest x and y + width and height
    let largestX = 0;
    let largestY = 0;
    for (let i = 0; i < highlightedImages.length; i++) {
      let x = highlightedImages[i].getBoundingClientRect().x;
      let y = highlightedImages[i].getBoundingClientRect().y;
      let width = highlightedImages[i].getBoundingClientRect().width;
      let height = highlightedImages[i].getBoundingClientRect().height;
      if (x + width > largestX) {
        largestX = x + width;
      }
      if (y + height > largestY) {
        largestY = y + height;
      }
    }
    let width = largestX - smallestX;
    let height = largestY - smallestY;
    return {
      height: Math.floor(height),
      width: Math.floor(width),
      startingPoint: { x: smallestX, y: smallestY },
    };
  }

  //factor method
  function wrap_image(imgSrc) {
    //return a containner that contains an image
    const img_li = document.createElement("li");
    const img = document.createElement("img");
    img.src = imgSrc;
    img_li.appendChild(img);
    //styling img_li
    img_li.style.height = "20vh";
    img_li.style.flexGrow = "1";
    img_li.style.margin = "10px";
    //styling img
    img.style.maxHeight = "100%";
    img.style.minWidth = "100%";
    img.style.objectFit = "scale-down";
    img.style.verticalAlign = "bottom";
    img.style.borderRadius = "1%";
    // img_li.style.backgroundColor = "red";
    img_li.classList.add("modalImage");
    img.classList.add("modalImage");

    return img_li;
  }

  function resize_canvas(setting) {
    const ctx = modalCanvas.getContext("2d");
    modalCanvas.width = setting.width;
    modalCanvas.height = setting.height;
    ctx.clearRect(0, 0, modalCanvas.width, modalCanvas.height);
    modalCanvas.style.border = "1px solid black";
  }

  function generate_photo_grid(startingPoint) {
    let rows = putImageIntoRow();
    const ctx = modalCanvas.getContext("2d");

    // ctx.clearRect(0,0, modalCanvas.width, modalCanvas.height);
    for (let [key, imgs] of rows) {
      // let current_position = highlightedImages[i].getBoundingClientRect();
      let x = 0;
      let y = key - Math.floor(startingPoint.y);
      for (let i = 0; i < imgs.length; i++) {
        let image_size = getImgSizeInfo(imgs[i]);
        ctx.drawImage(imgs[i], x, y, image_size.width, image_size.height);
        x += image_size.width + 10;
      }
    }
  }

  function countAndAddImages() {
    let newImages = document.getElementsByTagName("img");
    for (let i = 0; i < newImages.length; i++) {
      //   create a new newImage from source
      if (imageSourceMap.has(newImages[i].src)) {
        continue;
      }
      imageSourceMap.set(newImages[i].src, true);
      let newImageSrc = newImages[i].src;
      imagesSrcList.push(newImageSrc);
    }

    return imagesSrcList.length;
  }

  function add_count_to_view(imageCount) {
    counterDisplay.innerHTML = imageCount;
  }
  function close_modal() {
    modalBox.style.display = "none";
    while (photosContainer.firstChild) {
      photosContainer.removeChild(photosContainer.firstChild);
    }
  }
  document.addEventListener("scroll", async () => {
    //add number of image to the floating window
    const imageCount = countAndAddImages();
    const scrollPosition = window.innerHeight + window.scrollY;
    const bodyHeight = document.body.offsetHeight;
    if (scrollPosition >= bodyHeight) {
      add_count_to_view(imageCount);
    }
  });
  document.addEventListener("click", (event) => {
    const target = event.target;
    if (target === floatingCircleContent || target === counterDisplay) {
      //render selected image to image containner
      modalBox.style.display = "flex";
      imagesSrcList.forEach((src) => {
        const img = wrap_image(src);
        photosContainer.appendChild(img);
      });
    } else if (event.target == modalClose) {
      close_modal();
    } else if (event.target == creatCanvas) {
      //show the canvas and hide content and unhighlighted images
      const modalImages = document.querySelectorAll(
        ".modalImage:not(.selected)"
      );
      modalImages.forEach((image) => {
        image.style.display = "none";
      });
      //wait one sec for the images to be hidden
      setTimeout(function () {
        const canvasSetting = get_canvas_height_and_width();
        resize_canvas(canvasSetting);
        generate_photo_grid(canvasSetting.startingPoint);
        canvasContainner.style.display = "";
        modalContent.style.display = "none";
      }, 1000);
    } else if (event.target == checkImages) {
      const modalImages = document.querySelectorAll(
        ".modalImage:not(.selected)"
      );
      modalImages.forEach((image) => {
        image.style.display = "block";
      });
      modalContent.style.display = "block";
      canvasContainner.style.display = "none";
    } else if (event.target.classList.contains("modalImage")) {
      toggle_images(event);
    }
  });

  // set a once second delay
  setTimeout(function () {
    const count = countAndAddImages();
    add_count_to_view(count);
  }, 1000);
})();