feat: create transparent png

This commit is contained in:
Ibrahima G. Coulibaly
2024-06-25 07:44:36 +01:00
parent c49d18cac3
commit 3da580e3a9
7 changed files with 191 additions and 22 deletions

38
.idea/workspace.xml generated
View File

@@ -4,15 +4,15 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="fix: readme">
<change afterPath="$PROJECT_DIR$/src/components/ToolInputAndResult.tsx" afterDir="false" />
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="refactor: tool input and result">
<change afterPath="$PROJECT_DIR$/src/pages/image/png/create-transparent/index.tsx" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/pages/image/png/create-transparent/meta.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/pages/image/png/create-transparent/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/components/options/ToolOptionGroups.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/ToolOptionGroups.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/image/png/change-colors-in-png/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/image/png/change-colors-in-png/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/scripts/create-tool.mjs" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/create-tool.mjs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/home/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/home/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/image/png/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/image/png/index.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/number/sum/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/number/sum/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/string/join/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/join/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/string/split/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/split/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/string/to-morse/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/string/to-morse/index.tsx" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -187,15 +187,7 @@
<workItem from="1719168519203" duration="17675000" />
<workItem from="1719197816332" duration="1453000" />
<workItem from="1719273044735" duration="9847000" />
<workItem from="1719294110005" duration="1967000" />
</task>
<task id="LOCAL-00007" summary="fix: netlify">
<option name="closed" value="true" />
<created>1718833519062</created>
<option name="number" value="00007" />
<option name="presentableId" value="LOCAL-00007" />
<option name="project" value="LOCAL" />
<updated>1718833519062</updated>
<workItem from="1719294110005" duration="3709000" />
</task>
<task id="LOCAL-00008" summary="fix: index title">
<option name="closed" value="true" />
@@ -581,7 +573,15 @@
<option name="project" value="LOCAL" />
<updated>1719283122691</updated>
</task>
<option name="localTasksCounter" value="56" />
<task id="LOCAL-00056" summary="refactor: tool input and result">
<option name="closed" value="true" />
<created>1719296145698</created>
<option name="number" value="00056" />
<option name="presentableId" value="LOCAL-00056" />
<option name="project" value="LOCAL" />
<updated>1719296145699</updated>
</task>
<option name="localTasksCounter" value="57" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -602,7 +602,6 @@
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<option name="CHECK_NEW_TODO" value="false" />
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
<MESSAGE value="fix: deploy message" />
<MESSAGE value="chore: tools by category" />
<MESSAGE value="feat: copy and import file" />
<MESSAGE value="refactor: tool options components" />
@@ -627,7 +626,8 @@
<MESSAGE value="chore: printRunningSum" />
<MESSAGE value="chore: sum tests" />
<MESSAGE value="fix: readme" />
<option name="LAST_COMMIT_MESSAGE" value="fix: readme" />
<MESSAGE value="refactor: tool input and result" />
<option name="LAST_COMMIT_MESSAGE" value="refactor: tool input and result" />
</component>
<component name="XSLT-Support.FileAssociations.UIState">
<expand />

View File

@@ -18,7 +18,7 @@ import Button from '@mui/material/Button';
const exampleTools: { label: string; url: string }[] = [
{
label: 'Create a transparent image',
url: ''
url: '/png/create-transparent'
},
{ label: 'Convert text to morse code', url: '/string/to-morse' },
{ label: 'Change GIF speed', url: '' },

View File

@@ -0,0 +1,156 @@
import { Box } from '@mui/material';
import React, { useEffect, useState } from 'react';
import * as Yup from 'yup';
import ToolFileInput from '../../../../components/input/ToolFileInput';
import ToolFileResult from '../../../../components/result/ToolFileResult';
import ToolOptions from '../../../../components/options/ToolOptions';
import { Formik, useFormikContext } from 'formik';
import ColorSelector from '../../../../components/options/ColorSelector';
import Color from 'color';
import TextFieldWithDesc from 'components/options/TextFieldWithDesc';
import ToolInputAndResult from '../../../../components/ToolInputAndResult';
import ToolOptionGroups from '../../../../components/options/ToolOptionGroups';
const initialValues = {
fromColor: 'white',
similarity: '10'
};
const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required')
});
export default function ChangeColorsInPng() {
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const FormikListenerComponent = ({ input }: { input: File }) => {
const { values } = useFormikContext<typeof initialValues>();
const { fromColor, similarity } = values;
useEffect(() => {
let fromRgb: [number, number, number];
try {
//@ts-ignore
fromRgb = Color(fromColor).rgb().array();
} catch (err) {
return;
}
const processImage = async (
file: File,
fromColor: [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);
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;
const colorDistance = (
c1: [number, number, number],
c2: [number, number, number]
) => {
return Math.sqrt(
Math.pow(c1[0] - c2[0], 2) +
Math.pow(c1[1] - c2[1], 2) +
Math.pow(c1[2] - c2[2], 2)
);
};
const maxColorDistance = Math.sqrt(
Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2)
);
const similarityThreshold = (similarity / 100) * maxColorDistance;
for (let i = 0; i < data.length; i += 4) {
const currentColor: [number, number, number] = [
data[i],
data[i + 1],
data[i + 2]
];
if (colorDistance(currentColor, fromColor) <= similarityThreshold) {
data[i + 3] = 0; // Set alpha to 0 (transparent)
}
}
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, Number(similarity));
}, [input, fromColor]);
return null;
};
return (
<Box>
<ToolInputAndResult
input={
<ToolFileInput
value={input}
onChange={setInput}
accept={['image/png']}
title={'Input PNG'}
/>
}
result={
<ToolFileResult
title={'Transparent PNG'}
value={result}
extension={'png'}
/>
}
/>
<ToolOptions>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={() => {}}
>
{({ setFieldValue, values }) => (
<Box>
{input && <FormikListenerComponent input={input} />}
<ToolOptionGroups
groups={[
{
title: 'From color and similarity',
component: (
<Box>
<ColorSelector
value={values.fromColor}
onChange={(val) => setFieldValue('fromColor', val)}
description={'Replace this color (from color)'}
/>
<TextFieldWithDesc
value={values.similarity}
onChange={(val) => setFieldValue('similarity', val)}
description={
'Match this % of similar colors of the from color. For example, 10% white will match white and a little bit of gray.'
}
/>
</Box>
)
}
]}
/>
</Box>
)}
</Formik>
</ToolOptions>
</Box>
);
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('png', {
name: 'Create transparent PNG',
path: 'create-transparent',
// image,
description:
"World's simplest online Portable Network Graphics transparency maker. Just import your PNG image in the editor on the left and you will instantly get a transparent PNG on the right. Free, quick, and very powerful. Import a PNG get a transparent PNG.",
keywords: ['create', 'transparent'],
component: lazy(() => import('./index'))
});

View File

@@ -1,3 +1,4 @@
import { tool as pngCreateTransparent } from './create-transparent/meta';
import { tool as changeColorsInPng } from './change-colors-in-png/meta';
export const pngTools = [changeColorsInPng];
export const pngTools = [changeColorsInPng, pngCreateTransparent];

View File

@@ -1,5 +1,4 @@
import { Box, Stack } from '@mui/material';
import Grid from '@mui/material/Grid';
import React, { useContext, useEffect, useState } from 'react';
import ToolTextInput from '../../../components/input/ToolTextInput';
import ToolTextResult from '../../../components/result/ToolTextResult';