mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-20 22:49:33 +02:00
feat: svg change colors
This commit is contained in:
37
.idea/workspace.xml
generated
37
.idea/workspace.xml
generated
@@ -4,8 +4,15 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: compress image">
|
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: remove labels">
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/compress/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/compress/index.tsx" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/change-colors/service.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/index.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/change-colors-in-png/change-colors-in-png.e2e.spec.ts" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/change-colors-in-png/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/change-colors/index.tsx" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/change-colors-in-png/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/change-colors/meta.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/change-colors-in-png/test.png" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/png/index.ts" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -218,11 +225,11 @@
|
|||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\assets" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\assets" />
|
||||||
</key>
|
</key>
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools\image\generic" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\@types" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\@types" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\public\assets" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\tools" />
|
||||||
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\categories" />
|
<recent name="C:\Users\Ibrahima\IdeaProjects\omni-tools\src\pages\categories" />
|
||||||
<recent name="C:\Users\HP\IdeaProjects\omni-tools\src\components" />
|
|
||||||
</key>
|
</key>
|
||||||
</component>
|
</component>
|
||||||
<component name="RunManager" selected="npm.dev">
|
<component name="RunManager" selected="npm.dev">
|
||||||
@@ -400,15 +407,7 @@
|
|||||||
<workItem from="1743569964813" duration="352000" />
|
<workItem from="1743569964813" duration="352000" />
|
||||||
<workItem from="1743570403937" duration="109000" />
|
<workItem from="1743570403937" duration="109000" />
|
||||||
<workItem from="1743607343305" duration="20000" />
|
<workItem from="1743607343305" duration="20000" />
|
||||||
<workItem from="1743619618671" duration="1888000" />
|
<workItem from="1743619618671" duration="3827000" />
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00134" summary="chore: prettify json in home">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1740662154978</created>
|
|
||||||
<option name="number" value="00134" />
|
|
||||||
<option name="presentableId" value="LOCAL-00134" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1740662154978</updated>
|
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00135" summary="feat: jakarta font">
|
<task id="LOCAL-00135" summary="feat: jakarta font">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -794,7 +793,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1743621506634</updated>
|
<updated>1743621506634</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="183" />
|
<task id="LOCAL-00183" summary="chore: remove labels">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1743621602590</created>
|
||||||
|
<option name="number" value="00183" />
|
||||||
|
<option name="presentableId" value="LOCAL-00183" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1743621602590</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="184" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -841,7 +848,6 @@
|
|||||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||||
<option name="CHECK_NEW_TODO" value="false" />
|
<option name="CHECK_NEW_TODO" value="false" />
|
||||||
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
||||||
<MESSAGE value="refactor: use ToolContent" />
|
|
||||||
<MESSAGE value="fix: prettify json" />
|
<MESSAGE value="fix: prettify json" />
|
||||||
<MESSAGE value="refactor: sum" />
|
<MESSAGE value="refactor: sum" />
|
||||||
<MESSAGE value="fix: tools by category scroll" />
|
<MESSAGE value="fix: tools by category scroll" />
|
||||||
@@ -866,7 +872,8 @@
|
|||||||
<MESSAGE value="chore: add color" />
|
<MESSAGE value="chore: add color" />
|
||||||
<MESSAGE value="docs: readme" />
|
<MESSAGE value="docs: readme" />
|
||||||
<MESSAGE value="feat: compress image" />
|
<MESSAGE value="feat: compress image" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="feat: compress image" />
|
<MESSAGE value="chore: remove labels" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="chore: remove labels" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
@@ -6,10 +6,10 @@ import { GetGroupsType } from '@components/options/ToolOptions';
|
|||||||
import ColorSelector from '@components/options/ColorSelector';
|
import ColorSelector from '@components/options/ColorSelector';
|
||||||
import Color from 'color';
|
import Color from 'color';
|
||||||
import TextFieldWithDesc from 'components/options/TextFieldWithDesc';
|
import TextFieldWithDesc from 'components/options/TextFieldWithDesc';
|
||||||
import { areColorsSimilar } from 'utils/color';
|
|
||||||
import ToolContent from '@components/ToolContent';
|
import ToolContent from '@components/ToolContent';
|
||||||
import { ToolComponentProps } from '@tools/defineTool';
|
import { ToolComponentProps } from '@tools/defineTool';
|
||||||
import ToolImageInput from '@components/input/ToolImageInput';
|
import ToolImageInput from '@components/input/ToolImageInput';
|
||||||
|
import { processImage } from './service';
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
fromColor: 'white',
|
fromColor: 'white',
|
||||||
@@ -19,7 +19,7 @@ const initialValues = {
|
|||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
// splitSeparator: Yup.string().required('The separator is required')
|
// splitSeparator: Yup.string().required('The separator is required')
|
||||||
});
|
});
|
||||||
export default function ChangeColorsInPng({ title }: ToolComponentProps) {
|
export default function ChangeColorsInImage({ title }: ToolComponentProps) {
|
||||||
const [input, setInput] = useState<File | null>(null);
|
const [input, setInput] = useState<File | null>(null);
|
||||||
const [result, setResult] = useState<File | null>(null);
|
const [result, setResult] = useState<File | null>(null);
|
||||||
|
|
||||||
@@ -36,54 +36,10 @@ export default function ChangeColorsInPng({ title }: ToolComponentProps) {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const processImage = async (
|
|
||||||
file: File,
|
|
||||||
fromColor: [number, number, number],
|
|
||||||
toColor: [number, number, number],
|
|
||||||
similarity: number
|
|
||||||
) => {
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
if (ctx == null) return;
|
|
||||||
const img = new Image();
|
|
||||||
|
|
||||||
img.src = URL.createObjectURL(file);
|
processImage(input, fromRgb, toRgb, Number(similarity), setResult);
|
||||||
await img.decode();
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i += 4) {
|
|
||||||
const currentColor: [number, number, number] = [
|
|
||||||
data[i],
|
|
||||||
data[i + 1],
|
|
||||||
data[i + 2]
|
|
||||||
];
|
|
||||||
if (areColorsSimilar(currentColor, fromColor, similarity)) {
|
|
||||||
data[i] = toColor[0]; // Red
|
|
||||||
data[i + 1] = toColor[1]; // Green
|
|
||||||
data[i + 2] = toColor[2]; // Blue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.putImageData(imageData, 0, 0);
|
|
||||||
|
|
||||||
canvas.toBlob((blob) => {
|
|
||||||
if (blob) {
|
|
||||||
const newFile = new File([blob], file.name, {
|
|
||||||
type: 'image/png'
|
|
||||||
});
|
|
||||||
setResult(newFile);
|
|
||||||
}
|
|
||||||
}, 'image/png');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
processImage(input, fromRgb, toRgb, Number(similarity));
|
|
||||||
};
|
|
||||||
const getGroups: GetGroupsType<typeof initialValues> = ({
|
const getGroups: GetGroupsType<typeof initialValues> = ({
|
||||||
values,
|
values,
|
||||||
updateField
|
updateField
|
||||||
@@ -127,22 +83,11 @@ export default function ChangeColorsInPng({ title }: ToolComponentProps) {
|
|||||||
<ToolImageInput
|
<ToolImageInput
|
||||||
value={input}
|
value={input}
|
||||||
onChange={setInput}
|
onChange={setInput}
|
||||||
accept={['image/png']}
|
accept={['image/*']}
|
||||||
title={'Input PNG'}
|
title={'Input image'}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
resultComponent={
|
resultComponent={<ToolFileResult title={'Result image'} value={result} />}
|
||||||
<ToolFileResult
|
|
||||||
title={'Transparent PNG'}
|
|
||||||
value={result}
|
|
||||||
extension={'png'}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
toolInfo={{
|
|
||||||
title: 'Make Colors Transparent',
|
|
||||||
description:
|
|
||||||
'This tool allows you to make specific colors in a PNG image transparent. You can select the color to replace and adjust the similarity threshold to include similar colors.'
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
13
src/pages/tools/image/generic/change-colors/meta.ts
Normal file
13
src/pages/tools/image/generic/change-colors/meta.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineTool } from '@tools/defineTool';
|
||||||
|
import { lazy } from 'react';
|
||||||
|
|
||||||
|
export const tool = defineTool('image-generic', {
|
||||||
|
name: 'Change colors in image',
|
||||||
|
path: 'change-colors',
|
||||||
|
icon: 'cil:color-fill',
|
||||||
|
description:
|
||||||
|
"World's simplest online Image color changer. Just import your image (JPG, PNG, SVG) in the editor on the left, select which colors to change, and you'll instantly get a new image with the new colors on the right. Free, quick, and very powerful. Import an image – replace its colors.",
|
||||||
|
shortDescription: 'Quickly swap colors in a image',
|
||||||
|
keywords: ['change', 'colors', 'in', 'png', 'image', 'jpg'],
|
||||||
|
component: lazy(() => import('./index'))
|
||||||
|
});
|
169
src/pages/tools/image/generic/change-colors/service.ts
Normal file
169
src/pages/tools/image/generic/change-colors/service.ts
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
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> => {
|
||||||
|
if (file.type === 'image/svg+xml') {
|
||||||
|
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]);
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
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(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;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (ctx == null) return;
|
||||||
|
const img = new Image();
|
||||||
|
|
||||||
|
img.src = URL.createObjectURL(file);
|
||||||
|
await img.decode();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
|
const currentColor: [number, number, number] = [
|
||||||
|
data[i],
|
||||||
|
data[i + 1],
|
||||||
|
data[i + 2]
|
||||||
|
];
|
||||||
|
if (areColorsSimilar(currentColor, fromColor, similarity)) {
|
||||||
|
data[i] = toColor[0]; // Red
|
||||||
|
data[i + 1] = toColor[1]; // Green
|
||||||
|
data[i + 2] = toColor[2]; // Blue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
|
||||||
|
canvas.toBlob((blob) => {
|
||||||
|
if (blob) {
|
||||||
|
const newFile = new File([blob], file.name, {
|
||||||
|
type: file.type
|
||||||
|
});
|
||||||
|
setResult(newFile);
|
||||||
|
}
|
||||||
|
}, 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('')
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to parse hex to RGB
|
||||||
|
const hexToRgb = (hex: string): [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)
|
||||||
|
]
|
||||||
|
: null;
|
||||||
|
};
|
@@ -1,4 +1,5 @@
|
|||||||
import { tool as resizeImage } from './resize/meta';
|
import { tool as resizeImage } from './resize/meta';
|
||||||
import { tool as compressImage } from './compress/meta';
|
import { tool as compressImage } from './compress/meta';
|
||||||
|
import { tool as changeColors } from './change-colors/meta';
|
||||||
|
|
||||||
export const imageGenericTools = [resizeImage, compressImage];
|
export const imageGenericTools = [resizeImage, compressImage, changeColors];
|
||||||
|
@@ -1,43 +0,0 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
|
||||||
import { Buffer } from 'buffer';
|
|
||||||
import path from 'path';
|
|
||||||
import Jimp from 'jimp';
|
|
||||||
import { convertHexToRGBA } from '@utils/color';
|
|
||||||
|
|
||||||
test.describe('Change colors in png', () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await page.goto('/png/change-colors-in-png');
|
|
||||||
});
|
|
||||||
|
|
||||||
// test('should change pixel color', async ({ page }) => {
|
|
||||||
// // Upload image
|
|
||||||
// const fileInput = page.locator('input[type="file"]');
|
|
||||||
// const imagePath = path.join(__dirname, 'test.png');
|
|
||||||
// await fileInput?.setInputFiles(imagePath);
|
|
||||||
//
|
|
||||||
// await page.getByTestId('from-color-input').fill('#FF0000');
|
|
||||||
// const toColor = '#0000FF';
|
|
||||||
// await page.getByTestId('to-color-input').fill(toColor);
|
|
||||||
//
|
|
||||||
// // Click on download
|
|
||||||
// const downloadPromise = page.waitForEvent('download');
|
|
||||||
// await page.getByText('Save as').click();
|
|
||||||
//
|
|
||||||
// // Intercept and read downloaded PNG
|
|
||||||
// const download = await downloadPromise;
|
|
||||||
// const downloadStream = await download.createReadStream();
|
|
||||||
//
|
|
||||||
// const chunks = [];
|
|
||||||
// for await (const chunk of downloadStream) {
|
|
||||||
// chunks.push(chunk);
|
|
||||||
// }
|
|
||||||
// const fileContent = Buffer.concat(chunks);
|
|
||||||
//
|
|
||||||
// expect(fileContent.length).toBeGreaterThan(0);
|
|
||||||
//
|
|
||||||
// // Check that the first pixel is transparent
|
|
||||||
// const image = await Jimp.read(fileContent);
|
|
||||||
// const color = image.getPixelColor(0, 0);
|
|
||||||
// expect(color).toBe(convertHexToRGBA(toColor));
|
|
||||||
// });
|
|
||||||
});
|
|
@@ -1,13 +0,0 @@
|
|||||||
import { defineTool } from '@tools/defineTool';
|
|
||||||
import { lazy } from 'react';
|
|
||||||
|
|
||||||
export const tool = defineTool('png', {
|
|
||||||
name: 'Change colors in png',
|
|
||||||
path: 'change-colors-in-png',
|
|
||||||
icon: 'cil:color-fill',
|
|
||||||
description:
|
|
||||||
"World's simplest online Portable Network Graphics (PNG) color changer. Just import your PNG image in the editor on the left, select which colors to change, and you'll instantly get a new PNG with the new colors on the right. Free, quick, and very powerful. Import a PNG – replace its colors.",
|
|
||||||
shortDescription: 'Quickly swap colors in a PNG image',
|
|
||||||
keywords: ['change', 'colors', 'in', 'png'],
|
|
||||||
component: lazy(() => import('./index'))
|
|
||||||
});
|
|
Binary file not shown.
Before Width: | Height: | Size: 3.6 KiB |
@@ -2,14 +2,12 @@ import { tool as pngCrop } from './crop/meta';
|
|||||||
import { tool as pngCompressPng } from './compress-png/meta';
|
import { tool as pngCompressPng } from './compress-png/meta';
|
||||||
import { tool as convertJgpToPng } from './convert-jgp-to-png/meta';
|
import { tool as convertJgpToPng } from './convert-jgp-to-png/meta';
|
||||||
import { tool as pngCreateTransparent } from './create-transparent/meta';
|
import { tool as pngCreateTransparent } from './create-transparent/meta';
|
||||||
import { tool as changeColorsInPng } from './change-colors-in-png/meta';
|
|
||||||
import { tool as changeOpacity } from './change-opacity/meta';
|
import { tool as changeOpacity } from './change-opacity/meta';
|
||||||
import { tool as removeBackground } from './remove-background/meta';
|
import { tool as removeBackground } from './remove-background/meta';
|
||||||
|
|
||||||
export const pngTools = [
|
export const pngTools = [
|
||||||
pngCompressPng,
|
pngCompressPng,
|
||||||
pngCreateTransparent,
|
pngCreateTransparent,
|
||||||
changeColorsInPng,
|
|
||||||
convertJgpToPng,
|
convertJgpToPng,
|
||||||
changeOpacity,
|
changeOpacity,
|
||||||
pngCrop,
|
pngCrop,
|
||||||
|
Reference in New Issue
Block a user