Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,34 @@ jobs:
strategy:
fail-fast: false
matrix:
# Node.js 18 is required by jQuery infra
NODE_VERSION: [18.x, 20.x, 22.x]
# Node.js 22 is required by jQuery infra
NODE_VERSION: [20.x, 22.x]
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Install xsltproc & ImageMagick
- name: Install xsltproc
run: |
sudo apt-get install xsltproc imagemagick
sudo apt-get install xsltproc

# When Ubuntu Plucky is available in GitHub Actions, switch to it, remove
# the following section and just install the `imagemagick` package normally
# via apt-get.
- name: Download and build ImageMagick 7
run: |
sudo apt-get install -y build-essential pkg-config \
libjpeg-dev libpng-dev libtiff-dev libwebp-dev

# Replace the version below with the desired release
IM_VERSION="7.1.1-44"
wget https://download.imagemagick.org/ImageMagick/download/releases/ImageMagick-${IM_VERSION}.tar.xz
tar -xf ImageMagick-${IM_VERSION}.tar.xz
cd ImageMagick-${IM_VERSION}

./configure
make -j$(nproc)
sudo make install
sudo ldconfig

- name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18
22
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:18-alpine
FROM node:22-alpine

WORKDIR /app
COPY package*.json ./
Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
jQuery UI DownloadBuilder & ThemeRoller backend and frontend application.

## Requirements
- [node >= 18 and npm](https://nodejs.org/en/download/)
- ImageMagick 6.6.x. ([see below for instructions to compile it from source](#compile-and-install-imagemagick-from-source))
- [node >= 20 and npm](https://nodejs.org/en/download/)
- ImageMagick 7.x. ([See below for instructions how to install it](#install-imagemagick))
- grunt-cli: `npm install -g grunt-cli`

## Getting Started
Expand Down Expand Up @@ -95,9 +95,11 @@ $ grunt deploy

## Appendix

### Compile and install ImageMagick from source
### Install ImageMagick

Follow instructions from https://legacy.imagemagick.org/script/install-source.php to install ImageMagic `6.6.9-10`. Then, in the ImageMagick directory, invoke:
You will need ImageMagic `7.x` with PNG support. If your distribution doesn't provide such a version (on macOS it is included in the `imagemagick` Homebrew package), you will need to compile ImageMagick from source.

Follow instructions from https://imagemagick.org/script/install-source.php to install ImageMagic `7.x`. Then, in the ImageMagick directory, invoke:
```
$ ./configure CFLAGS=-O5 CXXFLAGS=-O5 --prefix=/opt --enable-static --with-png --disable-shared
```
Expand All @@ -107,7 +109,7 @@ Make sure you have the below in the output.
PNG --with-png=yes yes
```

If "png=yes no", libpng is missing and needs to be installed, `apt-get install libpng-dev` on linux or `brew install libpng` on OS X.
If "png=yes no", `libpng` is missing and needs to be installed, `apt-get install libpng-dev` on linux or `brew install libpng` on macOS.

Continuing...
```
Expand All @@ -120,8 +122,8 @@ export DYLD_LIBRARY_PATH="$MAGICK_HOME/lib/"

Make sure you get the right bin when running it.
```
$ which convert
/opt/bin/convert
$ which magick
/opt/bin/magick
```

Hint: add those export statements into your .bash_profile.
Hint: add those export statements into your `.bash_profile`.
5 changes: 0 additions & 5 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,6 @@ export default [
{
files: [ "app/src/**/*.js" ],
languageOptions: {

// No need to keep IE support, so we could bump it to ES2022 as well,
// but we need to switch the minifier to something other than UglifJS
// which is ES5-only.
ecmaVersion: 5,
sourceType: "script",
parserOptions: {
globalReturn: false
Expand Down
132 changes: 58 additions & 74 deletions lib/themeroller-image.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
"use strict";

var cache, imVersion,
async = require( "async" ),
Cache = require( "./cache" ),
im = require( "gm" ).subClass( { imageMagick: true } ),
semver = require( "semver" ),
dimensionLimit = 3000,
namedColors = require( "./themeroller-colors" );
const path = require( "node:path" );
const async = require( "async" );
const spawn = require( "cross-spawn" );
const Cache = require( "./cache" );
const semver = require( "semver" );
const dimensionLimit = 3000;
const namedColors = require( "./themeroller-colors" );

cache = new Cache( "Image Cache" );
const cache = new Cache( "Image Cache" );

function processImageMagick( args, callback ) {
const proc = spawn( "magick", args );

const stdoutBuffers = [];
const stderrBuffers = [];

proc.stdout.on( "data", data => stdoutBuffers.push( data ) );
proc.stderr.on( "data", data => stderrBuffers.push( data ) );

proc.on( "close", code => {
if ( code !== 0 ) {
return callback( new Error( `magick process exited with code ${ code }: ${ Buffer.concat( stderrBuffers ).toString() }` ) );
}
callback( null, Buffer.concat( stdoutBuffers ) );
} );

proc.on( "error", callback );
}

function expandColor( color ) {
if ( color.length === 3 && /^[0-9a-f]+$/i.test( color ) ) {
Expand All @@ -26,44 +45,6 @@ function hashColor( color ) {
return color;
}

// I don't know if there's a better solution, but without the below conversion to Buffer we're not able to use it.
function stream2Buffer( callback ) {
return function( err, stdin, stderr ) {
if ( err ) {
return callback( err );
}
var chunks = [],
dataLen = 0;
err = "";

stdin.on( "data", function( chunk ) {
chunks.push( chunk );
dataLen += chunk.length;
} );

stderr.on( "data", function( chunk ) {
err += chunk;
} );

stdin.on( "end", function() {
var i = 0,
buffer = Buffer.alloc( dataLen );
if ( err.length ) {
return callback( new Error( err ) );
}
chunks.forEach( function( chunk ) {
chunk.copy( buffer, i, 0, chunk.length );
i += chunk.length;
} );
callback( null, buffer );
} );

stdin.on( "error", function( err ) {
callback( err );
} );
};
}

function validateColor( color ) {
color = color.replace( /^#/, "" );
if ( ( color.length === 3 || color.length === 6 ) && /^[0-9a-f]+$/i.test( color ) ) {
Expand Down Expand Up @@ -147,27 +128,21 @@ generateIcon = function( params, callback ) {
// Add '#' in the beginning of the colors if needed
color = hashColor( params.color );

// https://www.imagemagick.org/Usage/masking/#shapes
// IM 6.7.9 and below:
// $ convert <icons_mask_filename> -background <color> -alpha shape output.png
// IM > 6.7.9: (see https://github.com/jquery/download.jqueryui.com/issues/132)
// $ convert <icons_mask_filename> -set colorspace RGB -background <color> -alpha shape -set colorspace sRGB output.png
// https://usage.imagemagick.org/masking/#shapes
// See https://github.com/jquery/download.jqueryui.com/issues/132 for why
// `-set colorspace RGB` is needed (twice) in IM >6.7.9. Full command:
// $ magick <icons_mask_filename> -set colorspace RGB -background <color> -alpha shape -set colorspace sRGB output.png

imageQueue.push( function( innerCallback ) {
try {
if ( semver.gt( imVersion, "6.7.9" ) ) {
im( __dirname + "/../template/themeroller/icon/mask.png" )
.out( "-set", "colorspace", "RGB" )
.background( color )
.out( "-alpha", "shape" )
.out( "-set", "colorspace", "sRGB" )
.stream( "png", stream2Buffer( innerCallback ) );
} else {
im( __dirname + "/../template/themeroller/icon/mask.png" )
.background( color )
.out( "-alpha", "shape" )
.stream( "png", stream2Buffer( innerCallback ) );
}
processImageMagick( [
path.join( __dirname, "/../template/themeroller/icon/mask.png" ),
"-set", "colorspace", "RGB",
"-background", color,
"-alpha", "shape",
"-set", "colorspace", "sRGB",
"png:-"
], innerCallback );
} catch ( err ) {
return innerCallback( err );
}
Expand All @@ -182,14 +157,20 @@ generateTexture = function( params, callback ) {

filename = params.type.replace( /-/g, "_" ).replace( /$/, ".png" );

// https://www.imagemagick.org/Usage/compose/#dissolve
// $ convert -size <width>x<height> 'xc:<color>' <texture_filename> -compose dissolve -define compose:args=<opacity>,100 -composite output.png
// https://usage.imagemagick.org/compose/#dissolve
// $ magick -size <width>x<height> 'xc:<color>' <texture_filename> -compose dissolve -define compose:args=<opacity>,100 -composite output.png

imageQueue.push( function( innerCallback ) {
try {
im( params.width, params.height, color )
.out( __dirname + "/../template/themeroller/texture/" + filename, "-compose", "dissolve", "-define", "compose:args=" + params.opacity + ",100", "-composite" )
.stream( "png", stream2Buffer( innerCallback ) );
processImageMagick( [
"-size", `${ params.width }x${ params.height }`,
"canvas:" + color,
path.join( __dirname, "/../template/themeroller/texture/", filename ),
"-compose", "dissolve",
"-define", `compose:args=${ params.opacity },100`,
"-composite",
"png:-"
], innerCallback );
} catch ( err ) {
return innerCallback( err );
}
Expand Down Expand Up @@ -327,7 +308,7 @@ Image.prototype = {
}
};

// Check the ImageMagick installation using node-gm (in a hacky way).
// Check the ImageMagick installation.
async.series( [
function( callback ) {
var wrappedCallback = function( err ) {
Expand All @@ -337,24 +318,27 @@ async.series( [
callback();
};
try {
im()._spawn( [ "convert", "-version" ], true, wrappedCallback );
processImageMagick( [ "-version" ], wrappedCallback );
} catch ( err ) {
return wrappedCallback( err );
}
},
function( callback ) {
im()._spawn( [ "convert", "-version" ], false, stream2Buffer( function( err, buffer ) {
processImageMagick( [ "-version" ], function( err, buffer ) {
if ( err ) {
return callback( err );
}
var output = buffer.toString( "utf8" );
if ( !( /ImageMagick/ ).test( output ) ) {
return callback( new Error( "ImageMagick not installed.\n" + output ) );
}
imVersion = output.split( /\r?\n/ )[ 0 ].replace( /^Version: ImageMagick ([^ ]*).*/, "$1" );
const imVersion = output.split( /\r?\n/ )[ 0 ].replace( /^Version: ImageMagick ([^ ]*).*/, "$1" );
if ( !semver.valid( imVersion ) ) {
return callback( new Error( "Could not identify ImageMagick version.\n" + output ) );
}
imageQueue.resume();
callback();
} ) );
} );
}
], function( err ) {
if ( err ) {
Expand Down
47 changes: 2 additions & 45 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading