-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/main'
- Loading branch information
Showing
15 changed files
with
1,268 additions
and
46 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains 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
This file contains 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
43 changes: 43 additions & 0 deletions
43
src/pages/image/png/change-colors-in-png/change-colors-in-png.e2e.spec.ts
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
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)); | ||
}); | ||
}); |
This file contains 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
72 changes: 72 additions & 0 deletions
72
src/pages/image/png/convert-jgp-to-png/convert-jgp-to-png.e2e.spec.ts
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { expect, test } from '@playwright/test'; | ||
import { Buffer } from 'buffer'; | ||
import path from 'path'; | ||
import Jimp from 'jimp'; | ||
|
||
test.describe('Convert JPG to PNG tool', () => { | ||
test.beforeEach(async ({ page }) => { | ||
await page.goto('/png/convert-jgp-to-png'); | ||
}); | ||
|
||
test('should convert jpg to png', async ({ page }) => { | ||
// Upload image | ||
const fileInput = page.locator('input[type="file"]'); | ||
const imagePath = path.join(__dirname, 'test.jpg'); | ||
await fileInput?.setInputFiles(imagePath); | ||
|
||
// 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 0x808080ff | ||
const image = await Jimp.read(fileContent); | ||
const color = image.getPixelColor(0, 0); | ||
expect(color).toBe(0x808080ff); | ||
}); | ||
|
||
test('should apply transparency before converting jpg to png', async ({ | ||
page | ||
}) => { | ||
// Upload image | ||
const fileInput = page.locator('input[type="file"]'); | ||
const imagePath = path.join(__dirname, 'test.jpg'); | ||
await fileInput?.setInputFiles(imagePath); | ||
|
||
// Enable transparency on color 0x808080 | ||
await page.getByLabel('Enable PNG Transparency').check(); | ||
await page.getByTestId('color-input').fill('#808080'); | ||
|
||
// 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(0); | ||
}); | ||
}); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import { Box } from '@mui/material'; | ||
import ToolInputAndResult from 'components/ToolInputAndResult'; | ||
import ToolFileInput from 'components/input/ToolFileInput'; | ||
import CheckboxWithDesc from 'components/options/CheckboxWithDesc'; | ||
import ColorSelector from 'components/options/ColorSelector'; | ||
import TextFieldWithDesc from 'components/options/TextFieldWithDesc'; | ||
import ToolOptions from 'components/options/ToolOptions'; | ||
import ToolFileResult from 'components/result/ToolFileResult'; | ||
import Color from 'color'; | ||
import React, { useState } from 'react'; | ||
import * as Yup from 'yup'; | ||
import { areColorsSimilar } from 'utils/color'; | ||
|
||
const initialValues = { | ||
enableTransparency: false, | ||
color: 'white', | ||
similarity: '10' | ||
}; | ||
const validationSchema = Yup.object({ | ||
// splitSeparator: Yup.string().required('The separator is required') | ||
}); | ||
export default function ConvertJgpToPng() { | ||
const [input, setInput] = useState<File | null>(null); | ||
const [result, setResult] = useState<File | null>(null); | ||
|
||
const compute = async ( | ||
optionsValues: typeof initialValues, | ||
input: any | ||
): Promise<void> => { | ||
if (!input) return; | ||
|
||
const processImage = async ( | ||
file: File, | ||
transparencyTransform?: { | ||
color: [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); | ||
|
||
if (transparencyTransform) { | ||
const { color, similarity } = transparencyTransform; | ||
|
||
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, color, similarity)) { | ||
data[i + 3] = 0; | ||
} | ||
} | ||
|
||
ctx.putImageData(imageData, 0, 0); | ||
} | ||
|
||
canvas.toBlob((blob) => { | ||
if (blob) { | ||
const newFile = new File([blob], file.name, { | ||
type: 'image/png' | ||
}); | ||
setResult(newFile); | ||
} | ||
}, 'image/png'); | ||
}; | ||
|
||
if (optionsValues.enableTransparency) { | ||
let rgb: [number, number, number]; | ||
try { | ||
//@ts-ignore | ||
rgb = Color(optionsValues.color).rgb().array(); | ||
} catch (err) { | ||
return; | ||
} | ||
|
||
processImage(input, { | ||
color: rgb, | ||
similarity: Number(optionsValues.similarity) | ||
}); | ||
} else { | ||
processImage(input); | ||
} | ||
}; | ||
|
||
return ( | ||
<Box> | ||
<ToolInputAndResult | ||
input={ | ||
<ToolFileInput | ||
value={input} | ||
onChange={setInput} | ||
accept={['image/jpeg']} | ||
title={'Input JPG'} | ||
/> | ||
} | ||
result={ | ||
<ToolFileResult | ||
title={'Output PNG'} | ||
value={result} | ||
extension={'png'} | ||
/> | ||
} | ||
/> | ||
<ToolOptions | ||
compute={compute} | ||
getGroups={({ values, updateField }) => [ | ||
{ | ||
title: 'PNG Transparency Color', | ||
component: ( | ||
<Box> | ||
<CheckboxWithDesc | ||
key="enableTransparency" | ||
title="Enable PNG Transparency" | ||
checked={!!values.enableTransparency} | ||
onChange={(value) => updateField('enableTransparency', value)} | ||
description="Make the color below transparent." | ||
/> | ||
<ColorSelector | ||
value={values.color} | ||
onColorChange={(val) => updateField('color', val)} | ||
description={'With this color (to color)'} | ||
inputProps={{ 'data-testid': 'color-input' }} | ||
/> | ||
<TextFieldWithDesc | ||
value={values.similarity} | ||
onOwnChange={(val) => updateField('similarity', val)} | ||
description={ | ||
'Match this % of similar. For example, 10% white will match white and a little bit of gray.' | ||
} | ||
/> | ||
</Box> | ||
) | ||
} | ||
]} | ||
initialValues={initialValues} | ||
input={input} | ||
validationSchema={validationSchema} | ||
/> | ||
</Box> | ||
); | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { defineTool } from '@tools/defineTool'; | ||
import { lazy } from 'react'; | ||
import image from '@assets/image.png'; | ||
|
||
export const tool = defineTool('png', { | ||
name: 'Convert JPG to PNG', | ||
path: 'convert-jgp-to-png', | ||
image, | ||
description: | ||
'Quickly convert your JPG images to PNG. Just import your PNG image in the editor on the left', | ||
shortDescription: 'Quickly convert your JPG images to PNG', | ||
keywords: ['convert', 'jgp', 'png'], | ||
component: lazy(() => import('./index')) | ||
}); |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.