Skip to content

Commit

Permalink
setup a conversion script to debug sharp source code and fix issue lo…
Browse files Browse the repository at this point in the history
  • Loading branch information
adriaanmeuris committed Apr 9, 2024
1 parent 5b1f69d commit d115cc4
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 20 deletions.
2 changes: 1 addition & 1 deletion nodemon.json
Expand Up @@ -2,5 +2,5 @@
"watch": ["src/**/*.cc", "src/**/*.h", "printlane/convert.js"],
"ext": "cc",
"verbose": false,
"exec": "npm i --build-from-source && clear && node printlane/convert.js"
"exec": "clear && npm i --build-from-source && clear && node printlane/convert.js"
}
89 changes: 70 additions & 19 deletions printlane/convert.js
@@ -1,34 +1,85 @@
const sharp = require('../lib');
const icc = require('icc');
const { resolve: pathResolve } = require('path');
const assert = require('assert');
const spawnChild = require('./utils/spawnChild');

// Define input and output files
const inFile = pathResolve(__dirname, 'files/sticker.jpg');
const outFile = pathResolve(__dirname, 'files/sticker-OUT.jpg');
const inFile = pathResolve(__dirname, 'files/100-0-0-100-fogra.tif');
const swop = pathResolve(__dirname, 'files/swop.icc');

(async function main () {
console.log('HALLO1', inFile);
// Helper to get a pixel value (uses `vips`, install with `brew install vips`)
async function getPixelValues(fileName, x, y) {
const data = await spawnChild(
'vips',
[
`getpoint`,
`${fileName}`,
`${x}`, // pixel x
`${y}`, // pixel y
],
);

// Remove linebreaks and trim spaces from output and return (output is a string, e.g. '255 128 0 0`)
return data.replace(/\n/g, '').trim();
}

// Get ICC profile from inFile
const metaInFile = await sharp(inFile).metadata();
assert.ok(metaInFile.icc instanceof Buffer, 'icc not set in inFile');
const { description: iccDescrInFile } = icc.parse(metaInFile.icc);
// Helper to validate if a pixel equals to specific values (uses `vips`, install with `brew install vips`)
async function validatePixelValues(fileName, expectedPixelValues) {
for (let i = 0; i < 10; i += 1) {
const pixelValues = await getPixelValues(fileName, i, i);
assert.strictEqual(pixelValues, expectedPixelValues);
}
}

// Convert, keep ICC from inFile
// Main function
(async function main () {
// ##############################################################################
// Invert without color space change (WORKING)
// ##############################################################################
// Source file: 1000x1000 file with CMYK colors 0/100/100/0 in Coated Fogra39
await validatePixelValues(inFile, '255 0 0 255');
const outFileFogra = pathResolve(__dirname, 'files/0-100-100-0-fogra.tif');
await sharp(inFile)
.pipelineColourspace('cmyk')
.toColourspace('cmyk')
.pipelineColourspace('cmyk')
.keepIccProfile()
.toFile(outFile);
.negate()
.toFile(outFileFogra);
await validatePixelValues(outFileFogra, '0 255 255 0');
// Output file: 1000x1000 file with CMYK colors 100/0/0/100 (inverted) in Coated Fogra39

// Extract metadata from outFile, assert icc profile is present
const metaOutFile = await sharp(outFile).metadata();
assert.ok(metaOutFile.icc instanceof Buffer, 'icc not set in outFile');
// ##############################################################################
// Invert with color space change in 2 steps (WORKING)
// ##############################################################################
const outFileSwop2 = pathResolve(__dirname, 'files/218-187-153-197-swop.tif');
await sharp(inFile)
.toColourspace('cmyk')
.pipelineColourspace('cmyk')
.withIccProfile(swop)
.toFile(outFileSwop2);
await validatePixelValues(outFileSwop2, '218 187 153 197');
// Output file: 1000x1000 file with CMYK colors 218/187/153/197 in U.S. Web Coated Swop V2

// Check if description of ICC profile in output file matches description of ICC profile in input file
const { description: iccDescrOutFile } = icc.parse(metaOutFile.icc);
assert.strictEqual(iccDescrOutFile, iccDescrInFile);
const outFileSwopInverted2Steps = pathResolve(__dirname, 'files/37-68-102-58-swop-inverted-2step.tif');
await sharp(outFileSwop2)
.toColourspace('cmyk')
.pipelineColourspace('cmyk')
.keepIccProfile()
.negate()
.toFile(outFileSwopInverted2Steps);
await validatePixelValues(outFileSwopInverted2Steps, `${255 - 218} ${255 - 187} ${255 - 153} ${255 - 197}`);
// Output file: 1000x1000 file with CMYK colors 37/68/102/58 in U.S. Web Coated Swop V2, so the values are inverted (resulting value = 255 - source value)

console.log('HALLO3', outFile);
// ##############################################################################
// Invert with color space change in 1 step (NOT WORKING)
// ##############################################################################
const outFileSwopInverted1Step = pathResolve(__dirname, 'files/37-68-102-58-swop-inverted-1step.tif');
await sharp(inFile)
.toColourspace('cmyk')
.pipelineColourspace('cmyk')
.withIccProfile(swop)
.negate()
.toFile(outFileSwopInverted1Step);
await validatePixelValues(outFileSwopInverted1Step, `${255 - 218} ${255 - 187} ${255 - 153} ${255 - 197}`);
// Output file: 1000x1000 file with CMYK colors 37/68/102/58 in U.S. Web Coated Swop V2, so the values are inverted (resulting value = 255 - source value)
})();
Binary file added printlane/files/100-0-0-100-fogra.tif
Binary file not shown.
Binary file added printlane/files/tiger-cmyk-fogra.tif
Binary file not shown.
71 changes: 71 additions & 0 deletions printlane/utils/spawnChild.js
@@ -0,0 +1,71 @@
const { spawn } = require('child_process');

/**
* Spawns a child process to run a program with the specified arguments and environment variables.
*
* This asynchronous function uses Node.js's `spawn` from the `child_process` module to create a new child process.
* It optionally logs standard output and standard error streams based on the provided options.
*
* @param {string} programPath - The path to the program to execute.
* @param {Array<string>} args - Arguments to pass to the program.
* @param {Object} options - Options for the `spawn` command. Can include a key `env` to set environment variables
* @param {Object} opts - Options for logging standard output and error. Defaults to not logging either.
* @param {boolean} opts.logStdOut - Flag to log standard output. Default is false.
* @param {boolean} opts.logStdErr - Flag to log standard error. Default is false.
*
* @returns {Promise<string>} A promise that resolves with the standard output data from the child process
* or rejects with an error message if the child process exits with a non-zero exit code.
*
* @throws {Error} Throws an error with the standard error output if the child process exits with a non-zero exit code.
*
* @example
* // Example usage:
* spawnChild('path/to/program', ['arg1', 'arg2'], { ENV_VAR: 'value' }, { logStdOut: true, logStdErr: true })
* .then(output => console.log(output))
* .catch(error => console.error(error));
*/
module.exports = async function spawnChild(programPath, args, options = {}, opts = {
logStdOut: false,
logStdErr: false,
}) {
// Spawn child
const child = spawn(
programPath,
args,
{
...options,
env: {
...process.env,
...options.env,
},
},
);

// Gather data
let data = '';
for await (const chunk of child.stdout) {
if (opts.logStdOut) console.log(chunk.toString()); // eslint-disable-line no-console
data += chunk;
}

// Gather errors
let error = '';
for await (const chunk of child.stderr) {
if (opts.logStdErr) console.log(chunk.toString()); // eslint-disable-line no-console
error += chunk;
}

// Await for the program to exist
const exitCode = await new Promise((resolve) => {
child.on('close', resolve);
});

// Reject the promise if exit code > 0 (an error occurred)
if (exitCode) {
if (opts.logStdErr) console.log(`subprocess error exit ${exitCode}, ${error}`); // eslint-disable-line no-console
throw new Error(error);
}

// Return the data
return data;
}

0 comments on commit d115cc4

Please sign in to comment.