chore: service updated to match requirements
This commit is contained in:
@@ -2,168 +2,181 @@ import { areColorsSimilar } from '@utils/color';
|
||||
|
||||
export const processImage = async (
|
||||
file: File,
|
||||
fromColor: [number, number, number],
|
||||
toColor: [number, number, number],
|
||||
similarity: number,
|
||||
setResult: (result: File | null) => void
|
||||
): Promise<void> => {
|
||||
fromColor: [number, number, number, number],
|
||||
toColor: [number, number, number, number],
|
||||
similarity: number
|
||||
): Promise<File> => {
|
||||
if (file.type === 'image/svg+xml') {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
if (!e.target?.result) return;
|
||||
return new Promise((resolve) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
if (!e.target?.result) return;
|
||||
|
||||
let svgContent = e.target.result as string;
|
||||
const toColorHex = rgbToHex(toColor[0], toColor[1], toColor[2]);
|
||||
let svgContent = e.target.result as string;
|
||||
const toColorHex = rgbToHex(
|
||||
toColor[0],
|
||||
toColor[1],
|
||||
toColor[2],
|
||||
toColor[3]
|
||||
);
|
||||
|
||||
// Replace hex colors with various formats (#fff, #ffffff)
|
||||
const hexRegexShort = new RegExp(`#[0-9a-f]{3}\\b`, 'gi');
|
||||
const hexRegexLong = new RegExp(`#[0-9a-f]{6}\\b`, 'gi');
|
||||
const hexRegexShort = new RegExp(`#[0-9a-f]{3}\\b`, 'gi');
|
||||
const hexRegexLong = new RegExp(`#[0-9a-f]{6}\\b`, 'gi');
|
||||
const hexRegexAlpha = new RegExp(`#[0-9a-f]{8}\\b`, 'gi');
|
||||
|
||||
svgContent = svgContent.replace(hexRegexShort, (match) => {
|
||||
// Expand short hex to full form for comparison
|
||||
const expanded =
|
||||
'#' + match[1] + match[1] + match[2] + match[2] + match[3] + match[3];
|
||||
const matchRgb = hexToRgb(expanded);
|
||||
if (matchRgb && areColorsSimilar(matchRgb, fromColor, similarity)) {
|
||||
return toColorHex;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
svgContent = svgContent.replace(hexRegexShort, (match) => {
|
||||
const expanded =
|
||||
'#' +
|
||||
match[1] +
|
||||
match[1] +
|
||||
match[2] +
|
||||
match[2] +
|
||||
match[3] +
|
||||
match[3];
|
||||
const matchRgb = hexToRgb(expanded);
|
||||
if (matchRgb && areColorsSimilar(matchRgb, fromColor, similarity))
|
||||
return toColorHex;
|
||||
return match;
|
||||
});
|
||||
|
||||
svgContent = svgContent.replace(hexRegexLong, (match) => {
|
||||
const matchRgb = hexToRgb(match);
|
||||
if (matchRgb && areColorsSimilar(matchRgb, fromColor, similarity)) {
|
||||
return toColorHex;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
svgContent = svgContent.replace(hexRegexLong, (match) => {
|
||||
const matchRgb = hexToRgb(match);
|
||||
if (matchRgb && areColorsSimilar(matchRgb, fromColor, similarity))
|
||||
return toColorHex;
|
||||
return match;
|
||||
});
|
||||
|
||||
// Replace RGB colors
|
||||
const rgbRegex = new RegExp(
|
||||
`rgb\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)`,
|
||||
'gi'
|
||||
);
|
||||
svgContent = svgContent.replace(rgbRegex, (match, r, g, b) => {
|
||||
const matchRgb: [number, number, number] = [
|
||||
parseInt(r),
|
||||
parseInt(g),
|
||||
parseInt(b)
|
||||
];
|
||||
if (areColorsSimilar(matchRgb, fromColor, similarity)) {
|
||||
return `rgb(${toColor[0]}, ${toColor[1]}, ${toColor[2]})`;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
svgContent = svgContent.replace(hexRegexAlpha, (match) => {
|
||||
const matchRgba = hexToRgba(match);
|
||||
if (matchRgba && areColorsSimilar(matchRgba, fromColor, similarity))
|
||||
return toColorHex;
|
||||
return match;
|
||||
});
|
||||
|
||||
// Replace RGBA colors (preserving alpha)
|
||||
const rgbaRegex = new RegExp(
|
||||
`rgba\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*([\\d.]+)\\s*\\)`,
|
||||
'gi'
|
||||
);
|
||||
svgContent = svgContent.replace(rgbaRegex, (match, r, g, b, a) => {
|
||||
const matchRgb: [number, number, number] = [
|
||||
parseInt(r),
|
||||
parseInt(g),
|
||||
parseInt(b)
|
||||
];
|
||||
if (areColorsSimilar(matchRgb, fromColor, similarity)) {
|
||||
return `rgba(${toColor[0]}, ${toColor[1]}, ${toColor[2]}, ${a})`;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
const rgbRegex = new RegExp(
|
||||
`rgb\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)`,
|
||||
'gi'
|
||||
);
|
||||
svgContent = svgContent.replace(rgbRegex, (match, r, g, b) => {
|
||||
const matchRgb: [number, number, number, number] = [
|
||||
parseInt(r),
|
||||
parseInt(g),
|
||||
parseInt(b),
|
||||
255
|
||||
];
|
||||
if (areColorsSimilar(matchRgb, fromColor, similarity)) {
|
||||
return `rgb(${toColor[0]}, ${toColor[1]}, ${toColor[2]})`;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
|
||||
// Replace named SVG colors if they match our target color
|
||||
const namedColors = {
|
||||
red: [255, 0, 0],
|
||||
green: [0, 128, 0],
|
||||
blue: [0, 0, 255],
|
||||
black: [0, 0, 0],
|
||||
white: [255, 255, 255]
|
||||
// Add more named colors as needed
|
||||
const rgbaRegex = new RegExp(
|
||||
`rgba\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*([\\d.]+)\\s*\\)`,
|
||||
'gi'
|
||||
);
|
||||
svgContent = svgContent.replace(rgbaRegex, (match, r, g, b, a) => {
|
||||
const matchRgba: [number, number, number, number] = [
|
||||
parseInt(r),
|
||||
parseInt(g),
|
||||
parseInt(b),
|
||||
Math.round(parseFloat(a) * 255)
|
||||
];
|
||||
if (areColorsSimilar(matchRgba, fromColor, similarity)) {
|
||||
return `rgba(${toColor[0]}, ${toColor[1]}, ${toColor[2]}, ${
|
||||
toColor[3] / 255
|
||||
})`;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
|
||||
const namedColors: Record<string, [number, number, number, number]> = {
|
||||
red: [255, 0, 0, 255],
|
||||
green: [0, 128, 0, 255],
|
||||
blue: [0, 0, 255, 255],
|
||||
black: [0, 0, 0, 255],
|
||||
white: [255, 255, 255, 255]
|
||||
};
|
||||
|
||||
Object.entries(namedColors).forEach(([name, rgba]) => {
|
||||
if (areColorsSimilar(rgba, fromColor, similarity)) {
|
||||
const colorRegex = new RegExp(`\\b${name}\\b`, 'gi');
|
||||
svgContent = svgContent.replace(colorRegex, toColorHex);
|
||||
}
|
||||
});
|
||||
|
||||
resolve(new File([svgContent], file.name, { type: 'image/svg+xml' }));
|
||||
};
|
||||
|
||||
Object.entries(namedColors).forEach(([name, rgb]) => {
|
||||
if (
|
||||
areColorsSimilar(
|
||||
rgb as [number, number, number],
|
||||
fromColor,
|
||||
similarity
|
||||
)
|
||||
) {
|
||||
const colorRegex = new RegExp(`\\b${name}\\b`, 'gi');
|
||||
svgContent = svgContent.replace(colorRegex, toColorHex);
|
||||
}
|
||||
});
|
||||
|
||||
// Create new file with modified content
|
||||
const newFile = new File([svgContent], file.name, {
|
||||
type: 'image/svg+xml'
|
||||
});
|
||||
setResult(newFile);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
return;
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (ctx == null) return;
|
||||
const img = new Image();
|
||||
if (!ctx) throw new Error('Canvas not supported');
|
||||
|
||||
img.src = URL.createObjectURL(file);
|
||||
const img = new Image();
|
||||
const url = URL.createObjectURL(file);
|
||||
img.src = url;
|
||||
await img.decode();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const data: Uint8ClampedArray = imageData.data;
|
||||
const data = imageData.data;
|
||||
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
const currentColor: [number, number, number] = [
|
||||
const currentColor: [number, number, number, number] = [
|
||||
data[i],
|
||||
data[i + 1],
|
||||
data[i + 2]
|
||||
data[i + 2],
|
||||
data[i + 3]
|
||||
];
|
||||
if (areColorsSimilar(currentColor, fromColor, similarity)) {
|
||||
data[i] = toColor[0]; // Red
|
||||
data[i + 1] = toColor[1]; // Green
|
||||
data[i + 2] = toColor[2]; // Blue
|
||||
data[i] = toColor[0];
|
||||
data[i + 1] = toColor[1];
|
||||
data[i + 2] = toColor[2];
|
||||
data[i + 3] = toColor[3];
|
||||
}
|
||||
}
|
||||
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
const newFile = new File([blob], file.name, {
|
||||
type: file.type
|
||||
});
|
||||
setResult(newFile);
|
||||
}
|
||||
}, file.type);
|
||||
return new Promise((resolve, reject) => {
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) resolve(new File([blob], file.name, { type: file.type }));
|
||||
else reject(new Error('Failed to create blob'));
|
||||
}, file.type);
|
||||
});
|
||||
};
|
||||
|
||||
const rgbToHex = (r: number, g: number, b: number): string => {
|
||||
return (
|
||||
'#' +
|
||||
[r, g, b]
|
||||
.map((x) => {
|
||||
const hex = x.toString(16);
|
||||
return hex.length === 1 ? '0' + hex : hex;
|
||||
})
|
||||
.join('')
|
||||
);
|
||||
};
|
||||
const rgbToHex = (r: number, g: number, b: number, a = 255): string =>
|
||||
'#' + [r, g, b, a].map((x) => x.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
// Helper function to parse hex to RGB
|
||||
const hexToRgb = (hex: string): [number, number, number] | null => {
|
||||
const hexToRgb = (hex: string): [number, number, number, number] | null => {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result
|
||||
? [
|
||||
parseInt(result[1], 16),
|
||||
parseInt(result[2], 16),
|
||||
parseInt(result[3], 16)
|
||||
parseInt(result[3], 16),
|
||||
255
|
||||
]
|
||||
: null;
|
||||
};
|
||||
|
||||
const hexToRgba = (hex: string): [number, number, number, number] | null => {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(
|
||||
hex
|
||||
);
|
||||
return result
|
||||
? [
|
||||
parseInt(result[1], 16),
|
||||
parseInt(result[2], 16),
|
||||
parseInt(result[3], 16),
|
||||
parseInt(result[4], 16)
|
||||
]
|
||||
: null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user