Created
January 31, 2025 05:18
-
-
Save to/5565bcaadcea1b7e2dff191c2758d4fd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const sharp = require('sharp'); | |
| const colorConvert = require('color-convert'); | |
| async function colorTransfer() { | |
| // 画像を読み込む | |
| const sourceImage = await sharp('./source.jpg').raw().toBuffer({ resolveWithObject: true }); | |
| const targetImage = await sharp('./target.jpg').raw().toBuffer({ resolveWithObject: true }); | |
| const { data: sourceData, info: sourceInfo } = sourceImage; | |
| const { data: targetData, info: targetInfo } = targetImage; | |
| if (sourceInfo.width !== targetInfo.width || sourceInfo.height !== targetInfo.height) { | |
| throw new Error('Source and target images must have the same dimensions'); | |
| } | |
| const width = sourceInfo.width; | |
| const height = sourceInfo.height; | |
| const dstData = Buffer.alloc(sourceData.length); | |
| // RGBからLabへの変換 | |
| const sourceLab = []; | |
| const targetLab = []; | |
| for (let i = 0; i < height; i++) { | |
| for (let j = 0; j < width; j++) { | |
| const offset = (i * width + j) * 3; | |
| const sourceR = sourceData[offset]; | |
| const sourceG = sourceData[offset + 1]; | |
| const sourceB = sourceData[offset + 2]; | |
| const targetR = targetData[offset]; | |
| const targetG = targetData[offset + 1]; | |
| const targetB = targetData[offset + 2]; | |
| sourceLab.push(colorConvert.rgb.lab([sourceR, sourceG, sourceB])); | |
| targetLab.push(colorConvert.rgb.lab([targetR, targetG, targetB])); | |
| } | |
| } | |
| // 平均値と標準偏差を計算 | |
| const mean = { source: [0, 0, 0], target: [0, 0, 0] }; | |
| const stddev = { source: [0, 0, 0], target: [0, 0, 0] }; | |
| for (let i = 0; i < sourceLab.length; i++) { | |
| for (let c = 0; c < 3; c++) { | |
| mean.source[c] += sourceLab[i][c]; | |
| mean.target[c] += targetLab[i][c]; | |
| } | |
| } | |
| for (let c = 0; c < 3; c++) { | |
| mean.source[c] /= sourceLab.length; | |
| mean.target[c] /= targetLab.length; | |
| } | |
| for (let i = 0; i < sourceLab.length; i++) { | |
| for (let c = 0; c < 3; c++) { | |
| stddev.source[c] += Math.pow(sourceLab[i][c] - mean.source[c], 2); | |
| stddev.target[c] += Math.pow(targetLab[i][c] - mean.target[c], 2); | |
| } | |
| } | |
| for (let c = 0; c < 3; c++) { | |
| stddev.source[c] = Math.sqrt(stddev.source[c] / sourceLab.length); | |
| stddev.target[c] = Math.sqrt(stddev.target[c] / targetLab.length); | |
| } | |
| console.log('Source Mean:', mean.source); | |
| console.log('Source StdDev:', stddev.source); | |
| console.log('Target Mean:', mean.target); | |
| console.log('Target StdDev:', stddev.target); | |
| // 色変換の計算 | |
| for (let i = 0; i < height; i++) { | |
| for (let j = 0; j < width; j++) { | |
| const offset = (i * width + j) * 3; | |
| const sourceIndex = i * width + j; | |
| const newLab = [ | |
| ((sourceLab[sourceIndex][0] - mean.source[0]) * (stddev.target[0] / stddev.source[0])) + mean.target[0], | |
| ((sourceLab[sourceIndex][1] - mean.source[1]) * (stddev.target[1] / stddev.source[1])) + mean.target[1], | |
| ((sourceLab[sourceIndex][2] - mean.source[2]) * (stddev.target[2] / stddev.source[2])) + mean.target[2] | |
| ]; | |
| const newRgb = colorConvert.lab.rgb(newLab); | |
| dstData[offset] = Math.min(255, Math.max(0, newRgb[0])); // R | |
| dstData[offset + 1] = Math.min(255, Math.max(0, newRgb[1])); // G | |
| dstData[offset + 2] = Math.min(255, Math.max(0, newRgb[2])); // B | |
| } | |
| } | |
| // 結果を保存 | |
| await sharp(dstData, { | |
| raw: { | |
| width: width, | |
| height: height, | |
| channels: 3 | |
| } | |
| }).toFile('./dst.png'); | |
| } | |
| colorTransfer().catch(console.error); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment