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
2 changes: 2 additions & 0 deletions @types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ declare type Options = {
folderPath?: string;
width: number;
height?: number;
canvasWidth?: number;
canvasHeight?: number;
};
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ Command-line utility that slices input images into segments according to specifi
`node slice.cjs`

```
-f, --filename Input image filename [string]
-i, --folderPath Input folder [string]
-w, --width Output image width [number] [required]
-h, --height Output image height [number]
-f, --filename Input image filename [string]
-i, --folderPath Input folder [string]
-w, --width Width of each slice [number] [required]
-h, --height Height of each slice [number]
-d, --canvasWidth Width of canvas for final output [number]
-g, --canvasHeight Height of canvas for final output [number]
```

- If `filename` does not include an extension, `.png`, `.gif`, `.jpg` and `.jpeg` will be guessed
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-image-slice",
"version": "2.0.0",
"version": "2.1.0",
"description": "Slices an input image into segments according to specified width and height",
"repository": {
"type": "git",
Expand Down
24 changes: 20 additions & 4 deletions slice.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const options = yargs
})
.option('w', {
alias: 'width',
describe: 'Output image width',
describe: 'Width of each slice',
type: 'number',
demandOption: true,
coerce: (value) => {
Expand All @@ -31,19 +31,35 @@ const options = yargs
})
.option('h', {
alias: 'height',
describe: 'Output image height',
describe: 'Height of each slice',
type: 'number',
coerce: (value) => {
if (value !== undefined && value < 1) {
throw new Error('height should not be lower than 1');
}
return Math.round(value);
},
})
.option('d', {
alias: 'canvasWidth',
describe: 'Width of canvas for final output',
type: 'number',
})
.option('g', {
alias: 'canvasHeight',
describe: 'Height of canvas for final output',
type: 'number',
}).argv;
if (options.filename) {
// Process a single image
const { filename, width, height } = options;
(0, processImage_1.sliceImage)(filename, width, height);
const { filename, width, height, canvasWidth, canvasHeight } = options;
(0, processImage_1.sliceImage)(
filename,
width,
height,
canvasWidth,
canvasHeight,
);
} else if (options.folderPath) {
// Process all images in a folder, splitting the task into threads
let numCores = 2;
Expand Down
18 changes: 14 additions & 4 deletions src/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const options = yargs
})
.option('w', {
alias: 'width',
describe: 'Output image width',
describe: 'Width of each slice',
type: 'number',
demandOption: true,
coerce: (value) => {
Expand All @@ -32,20 +32,30 @@ const options = yargs
})
.option('h', {
alias: 'height',
describe: 'Output image height',
describe: 'Height of each slice',
type: 'number',
coerce: (value) => {
if (value !== undefined && value < 1) {
throw new Error('height should not be lower than 1');
}
return Math.round(value);
},
})
.option('d', {
alias: 'canvasWidth',
describe: 'Width of canvas for final output',
type: 'number',
})
.option('g', {
alias: 'canvasHeight',
describe: 'Height of canvas for final output',
type: 'number',
}).argv as unknown as Options;

if (options.filename) {
// Process a single image
const { filename, width, height } = options;
sliceImage(filename, width, height);
const { filename, width, height, canvasWidth, canvasHeight } = options;
sliceImage(filename, width, height, canvasWidth, canvasHeight);
} else if (options.folderPath) {
// Process all images in a folder, splitting the task into threads
let numCores = 2;
Expand Down
48 changes: 43 additions & 5 deletions src/utils/processImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export function sliceImage(
filename: string,
width: number,
height?: number,
canvasWidth?: number,
canvasHeight?: number,
skipExtCheck?: boolean,
): void {
Jimp.read(filename, (err, image) => {
Expand All @@ -20,7 +22,14 @@ export function sliceImage(
} else {
// Continue slicing if image is successfully read
if (image) {
continueSlicing(image, width, height, filename);
continueSlicing(
image,
width,
height,
canvasWidth,
canvasHeight,
filename,
);
return;
}
}
Expand All @@ -41,7 +50,14 @@ export function sliceImage(
Jimp.read(fullFilename, (err, image) => {
if (!foundImage && !err) {
foundImage = true;
continueSlicing(image, width, height, fullFilename);
continueSlicing(
image,
width,
height,
canvasWidth,
canvasHeight,
fullFilename,
);
}
});
}
Expand All @@ -55,6 +71,8 @@ function continueSlicing(
image: Jimp,
width: number,
height: number | undefined,
canvasWidth: number | undefined,
canvasHeight: number | undefined,
inputFilename: string,
): void {
// If height is not specified, use width as height
Expand Down Expand Up @@ -90,7 +108,27 @@ function continueSlicing(
);
const outputFilename = `${outputFolder}/${baseFilename}_${x}_${y}.png`;

slice.write(outputFilename);
if (canvasWidth || canvasHeight) {
// Calculate canvas dimensions
const finalCanvasWidth = canvasWidth || width;
const finalCanvasHeight = (canvasHeight || canvasWidth) ?? height;

// Create a new canvas with transparent background
const canvas = new Jimp(
finalCanvasWidth,
finalCanvasHeight,
0x00000000,
);

// Composite the image in the middle of the canvas
const startX2 = Math.floor((finalCanvasWidth - sliceWidth) / 2);
const startY2 = Math.floor((finalCanvasHeight - sliceHeight) / 2);
canvas.composite(slice, startX2, startY2);
canvas.write(outputFilename);
} else {
slice.write(outputFilename);
}

console.log(`Slice saved: ${outputFilename}`);
}
}
Expand All @@ -100,6 +138,6 @@ function continueSlicing(
if (!isMainThread) {
const { filePath, options } = workerData;
options.filename = filePath;
const { filename, width, height } = options;
sliceImage(filename, width, height, true);
const { filename, width, height, canvasWidth, canvasHeight } = options;
sliceImage(filename, width, height, canvasWidth, canvasHeight, true);
}
32 changes: 23 additions & 9 deletions utils/processImage.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ const Jimp = require("jimp");
const fs = require("fs");
const path = require("path");
const worker_threads_1 = require("worker_threads");
const outputFolder = "output";
const outputFolder = 'output';
/**
* Function to slice an image into smaller segments
*/
function sliceImage(filename, width, height, skipExtCheck) {
function sliceImage(filename, width, height, canvasWidth, canvasHeight, skipExtCheck) {
Jimp.read(filename, (err, image) => {
if (err && skipExtCheck) {
console.error(err);
}
else {
// Continue slicing if image is successfully read
if (image) {
continueSlicing(image, width, height, filename);
continueSlicing(image, width, height, canvasWidth, canvasHeight, filename);
return;
}
}
Expand All @@ -26,7 +26,7 @@ function sliceImage(filename, width, height, skipExtCheck) {
return;
}
// Check for supported image formats if skipExtCheck is false
const supportedFormats = [".png", ".gif", ".jpg", ".jpeg"];
const supportedFormats = ['.png', '.gif', '.jpg', '.jpeg'];
let foundImage = false;
// Attempt to read the image with different extensions
supportedFormats.forEach((ext) => {
Expand All @@ -35,7 +35,7 @@ function sliceImage(filename, width, height, skipExtCheck) {
Jimp.read(fullFilename, (err, image) => {
if (!foundImage && !err) {
foundImage = true;
continueSlicing(image, width, height, fullFilename);
continueSlicing(image, width, height, canvasWidth, canvasHeight, fullFilename);
}
});
}
Expand All @@ -45,7 +45,7 @@ exports.sliceImage = sliceImage;
/**
* Continue slicing the image into smaller segments
*/
function continueSlicing(image, width, height, inputFilename) {
function continueSlicing(image, width, height, canvasWidth, canvasHeight, inputFilename) {
// If height is not specified, use width as height
height = height || width;
const imageWidth = image.getWidth();
Expand All @@ -68,7 +68,21 @@ function continueSlicing(image, width, height, inputFilename) {
// Incorporate the input filename into the output filename
const baseFilename = path.basename(inputFilename, path.extname(inputFilename));
const outputFilename = `${outputFolder}/${baseFilename}_${x}_${y}.png`;
slice.write(outputFilename);
if (canvasWidth || canvasHeight) {
// Calculate canvas dimensions
const finalCanvasWidth = canvasWidth || width;
const finalCanvasHeight = (canvasHeight || canvasWidth) ?? height;
// Create a new canvas with transparent background
const canvas = new Jimp(finalCanvasWidth, finalCanvasHeight, 0x00000000);
// Composite the image in the middle of the canvas
const startX2 = Math.floor((finalCanvasWidth - sliceWidth) / 2);
const startY2 = Math.floor((finalCanvasHeight - sliceHeight) / 2);
canvas.composite(slice, startX2, startY2);
canvas.write(outputFilename);
}
else {
slice.write(outputFilename);
}
console.log(`Slice saved: ${outputFilename}`);
}
}
Expand All @@ -77,6 +91,6 @@ function continueSlicing(image, width, height, inputFilename) {
if (!worker_threads_1.isMainThread) {
const { filePath, options } = worker_threads_1.workerData;
options.filename = filePath;
const { filename, width, height } = options;
sliceImage(filename, width, height, true);
const { filename, width, height, canvasWidth, canvasHeight } = options;
sliceImage(filename, width, height, canvasWidth, canvasHeight, true);
}
8 changes: 4 additions & 4 deletions utils/workerPool.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ class WorkerPool {
* @param options - Image processing options for the file.
*/
createWorker(filePath, options) {
const worker = new worker_threads_1.Worker(path.join(__dirname, "processImage.js"), {
const worker = new worker_threads_1.Worker(path.join(__dirname, 'processImage.js'), {
workerData: { filePath, options },
});
// Listen for messages and errors from the worker
worker.on("message", (message) => {
worker.on('message', (message) => {
console.log(message);
this.processNextTask();
});
worker.on("error", (err) => {
worker.on('error', (err) => {
console.error(`Error in worker for file ${filePath}:`, err);
this.processNextTask();
});
Expand Down Expand Up @@ -66,7 +66,7 @@ class WorkerPool {
*/
waitForCompletion() {
this.workers.forEach((worker) => {
worker.on("exit", () => {
worker.on('exit', () => {
this.processNextTask();
});
});
Expand Down