Keeping Track of Useful Shell / CMD Commands / Scripts

I want to keep track of useful shell / cmd commands that I might want to use in the future.

Date Created:
Last Edited:

ImageMagick Scripts



Creating Correctly Sized Non-Transparent Banner Image


Task:

Need ImageMagick command / shell script that resizes an image to a certain size. If the image cannot fit to the specified size exactly, then a background color should be added to the image to make it confirm to the specified size. The background color should be determined by:

  1. Check the color of the top left, top right, bottom left, and bottom right pixels of the image. If they are all the same, then use that color as the background color to extend the image.
  2. If the image has a transparent background, then you should check the most prevalent color in the image and use the opposite color of that. (E.g., if the most prevalent color in an image is white, then use black as the background color to extend the image)
  3. Else, use the provided fallback background color.

Use:

I need to use this script for banner images for article previews / for articles. (See below)

In the case of the image above, the image would be resized, and then, because the image's aspect ratio does not fit what I want it to be, the image would have a white background added to it that extends the image to the preferred aspect ratio.

Implementation:

import { Magick } from 'magickwand.js';
import path, { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import child_process from 'node:child_process';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);


/**
 *
 * @param {Magick.Image} im
 */
function handleTransparent(im,{desiredWidth,desiredHeight,width,height,widthAndHeight}) {
  // Create temp image for dithering
  im.write(path.resolve(__dirname,'temp.png'));
  const im2 = new Magick.Image(path.resolve(__dirname,'temp.png'));
  // dither image to get the most frequent colors
  im2.orderedDither('4x4');
  im2.write(path.resolve(__dirname,'temp.png'));
  // https://imagemagick.org/Usage/quantize/ Get the most frequent colors
  child_process.exec(`magick ${path.resolve(__dirname,'temp.png')} -format %c  -depth 8  histogram:info:-`,function (error,stdout,stderr) {
    if (error) throw error;
    if (stderr) throw new Error(stderr);
    const matches = stdout.matchAll(/^\s+(?<count>\d+):\s\((?<red>\d+),(?<green>\d+),(?<blue>\d+),(?<alpha>\d+)\)/gm);
    if (!!!matches) throw new Error(`There were no matches for getting image colors: ${stdout}`);
    const matchArr = [];
    for (let match of matches) {
      const obj = { count: parseInt(match.groups.count), red: parseInt(match.groups.red), green: parseInt(match.groups.green), blue: parseInt(match.groups.blue), alpha: parseInt(match.groups.alpha) }
      if(obj.alpha!==0) matchArr.push(obj);
    }
    matchArr.sort((a,b) => b.count-a.count);
    // Get tha main color in the image thgat is not black or white, and set the background color to the opposite of that color
    var mainColor;
    for (let obj of matchArr) {
      if (obj.red===255&&obj.green===255&&obj.blue===255) {
        continue;
      } else if (obj.red===0&&obj.green===0&&obj.blue===0) {
        continue;
      } else {
        mainColor = obj;
        break;
      }
    }
    if (!!!mainColor) throw new Error("Unable to get main color from object.");
    const color = new Magick.ColorRGB();
    color.red(255 - mainColor.red);
    color.green(255 - mainColor.green);
    color.blue(255 - mainColor.blue);
    const canvas = new Magick.Image(widthAndHeight,color);
    const geom = new Magick.Geometry(desiredWidth/2,desiredHeight/2)
    geom.xOff((desiredWidth-width)/2);
    geom.yOff((desiredHeight-height)/2);
    canvas.composite(im,geom,2); // https://www.imagemagick.org/Magick++/Enumerations.html#CompositeOperator
    canvas.write(path.resolve(__dirname,'output.png'));
  })
}
/**
 * Resize / add background to banner image to prevent CLS and so that text is readable on both
 * light and dark modes
 * @param {string} pathToInput Path to Input
 * @param {string} widthAndHeight Desired with and height in the form widthxheight
 */
async function convertBannerImage(pathToInput,widthAndHeight) {
  const TRANSPARENT = "#0000000000000000";
  // Get the desired width and height of the banner image
  let desiredWidth, desiredHeight;
  const matches = widthAndHeight.match(/\d+(\.\d+)?/g);
  [desiredWidth,desiredHeight] = matches;
  if (!!!desiredWidth||!!!desiredHeight) throw new Error("Something went wrong getting the desired witdh / height of the image.");
  desiredWidth = parseInt(desiredWidth);
  desiredHeight = parseInt(desiredHeight);
  // Resize the image using ImageMagick - keep aspect ratio
  let im = new Magick.Image(pathToInput);
  im.resize(widthAndHeight);
  // Get the new width and height of the image
  const [width,height] = [im.size().width(),im.size().height()];
  // Get the colors od the pixels
  const [tl,tr,bl,br] = [
    im.pixelColor(0,0),
    im.pixelColor(width,0),
    im.pixelColor(0,height),
    im.pixelColor(width,height),
  ];
  // If the image is transparent, then you need to add a background color to the image and resize appropriatly
  if (tl.toString()===TRANSPARENT||tr.toString()===TRANSPARENT||bl.toString()===TRANSPARENT||br.toString()===TRANSPARENT) {
    handleTransparent(im,{desiredWidth,desiredHeight,width,height,widthAndHeight});
  } else if (width!==desiredWidth||height!==desiredHeight) {
    var bgColor;
    if (tl.toString()===tr.toString()&&tl.toString()===br.toString()&&tl.toString()===bl.toString()) {
      bgColor = tl;
    } else {
      bgColor = im.backgroundColor();
    }
   
    // If the background color is not transparent, then create a canvas with the
    // specified background color and overlay the image on the colored canvas
    if (bgColor.toString()!==TRANSPARENT) {
      const canvas = new Magick.Image(widthAndHeight,bgColor);
      const geom = new Magick.Geometry(desiredWidth/2,desiredHeight/2)
      geom.xOff((desiredWidth-width)/2);
      geom.yOff((desiredHeight-height)/2);
      canvas.composite(im,geom,2); // https://www.imagemagick.org/Magick++/Enumerations.html#CompositeOperator
      canvas.write(path.resolve(__dirname,'output.png'));
    } else {
    // If the background color id transparent, then you need to add a background color to the image and resize appropriatly
      handleTransparent(im,{desiredWidth,desiredHeight,width,height,widthAndHeight});
    }
  // If the image is the correct size after resize, then return the image
  } else {
    im.write(path.resolve(__dirname,'output.png'))
  }
}

Description:

Given an image, and a desired width and height of a banner image, in the form widthxheight, first resize the image using the Magick++ equivalent of magick -resize (preserving the aspect ratio of the original image), then:

  • if the image is now in the correct size, return it.
  • Else, if the image is not in the correct size:
    • If the background color of the image can be detected, use the detected background color.
    • If all four corners have the same pixel color and that pixel color is not transparent, then use that pixel color as the background.
    • If the background color cannot be detected, then through a process of quantization and dithering, find the most frequent non black / white background color of the image, and set the background color of the appropriately sized image to the opposite of the found most frequent color in the image.
Example of Dithered Image

Note: We use the composite operator from Magick++ to center the image correctly.

Comments

You must be logged in to post a comment!

Insert Math Markup

ESC
About Inserting Math Content
Display Style:

Embed News Content

ESC
About Embedding News Content

Embed Youtube Video

ESC
Embedding Youtube Videos

Embed TikTok Video

ESC
Embedding TikTok Videos

Embed X Post

ESC
Embedding X Posts

Embed Instagram Post

ESC
Embedding Instagram Posts

Insert Details Element

ESC

Example Output:

Summary Title
You will be able to insert content here after confirming the title of the <details> element.

Insert Table

ESC
Customization
Align:
Preview:

Insert Horizontal Rule

#000000

Preview:


Insert Chart

ESC

View Content At Different Sizes

ESC

Edit Style of Block Nodes

ESC

Edit the background color, default text color, margin, padding, and border of block nodes. Editable block nodes include paragraphs, headers, and lists.

#ffffff
#000000

Edit Selected Cells

Change the background color, vertical align, and borders of the cells in the current selection.

#ffffff
Vertical Align:
Border
#000000
Border Style:

Edit Table

ESC
Customization:
Align:

Upload Lexical State

ESC

Upload a .lexical file. If the file type matches the type of the current editor, then a preview will be shown below the file input.

Upload 3D Object

ESC

Upload Jupyter Notebook

ESC

Upload a Jupyter notebook and embed the resulting HTML in the text editor.

Insert Custom HTML

ESC

Edit Image Background Color

ESC
#ffffff

Insert Columns Layout

ESC
Column Type:

Select Code Language

ESC
Select Coding Language