React: how can I get multiple image size? - javascript

I have an object with links
const urls = {
small: https://image1.com,
medium: https://image2.com,
large: https://image3.com,
};
output should look like that:
{
small: {width: image_width, height: image_height},
medium: {width: image_width, height: image_height},
large: {width: image_width, height: image_height}
}
I tried like this, but it turns out an infinite loop.
Help me fix my function or show how it is possible in another way
const [imageSize, setImageSize] = useState({});
const getImageSize = () => {
for (const url in urls) {
const img = new Image();
img.src = urls[url];
img.onload = () => {
setImageSize((prev) => {
return {
...prev,
[url]: {
width: img.width,
height: img.height,
},
};
});
};
}
};

To trigger image onload listener it should be mounted to the DOM, otherwise your setImageSize not called. To do this you should have valid images and attach it somewhere to the html, like this:
const urls = {
small: 'https://dummyimage.com/600x400/000/fff',
medium: 'https://dummyimage.com/600x400/000/fff',
large: 'https://dummyimage.com/600x400/000/fff',
}
const getImageSize = () => {
for (const url in urls) {
const img = new Image()
img.src = urls[url]
img.onload = () => {
setImageSize(prev => {
return {
...prev,
[url]: {
width: img.width,
height: img.height,
},
}
})
}
document.body.appendChild(img)
}
}
useEffect(() => {
getImageSize()
}, [])

Try this below code:
const [imageSize, setImageSize] = useState({});
const urls = {
small: https://image1.com,
medium: https://image2.com,
large: https://image3.com,
};
const getImageSize = () => {
for (const size in urls) {
var img = new Image();
img.onload = function() {
setImageSize((prev) => {
return {
...prev,
[size]: {width: this.width, height: this.height}
}
})
};
img.src = url;
}
}
useEffect(() => {
getImageSize()
}, [])

check the fiddle link I hope it will work[https://jsfiddle.net/k7bue0ho/][1]

Related

unrecognised content at end of stream with PNG

I'm new to use PNG and I get below error unrecognised content at end of stream
cy.readFile("cypress/e2e/Testdata/sample.png", "binary").then((image1) => {
cy.readFile("cypress/downloads/sample.png", "binary").then((image2) => {
const img1 = PNG.sync.read(image1);
const img2 = PNG.sync.read(image2);
const { width, height } = img1;
const diff = new PNG({ width, height });
pixelmatch(img1.data, img2.data, diff.data, width, height, {
threshold: 0.1,
});
fs.writeFileSync("diff.png", PNG.sync.write(diff));
expect(diff.width).to.equal(0);
expect(diff.height).to.equal(0);
});
});
You cannot mix browser commands like cy.readfile() with node commands like fs.writeFileSync().
You will have to create a Cypress task to run this code.
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('task', {
png({image1Path, image2Path}) {
const image1 = fs.readFileSync(image1Path)
const image2 = fs.readFileSync(image2Path)
const img1 = PNG.sync.read(image1);
const img2 = PNG.sync.read(image2);
const { width, height } = img1;
const diff = new PNG({ width, height });
pixelmatch(img1.data, img2.data, diff.data, width, height, {
threshold: 0.1,
});
fs.writeFileSync("diff.png", PNG.sync.write(diff));
return diff
},
})
},
},
})

How to re-render screen when updating array?

Using the image-crop-picker I am selecting an image from gallery then setting it to state. I then have the option to crop the image and in the array/state I replace the old image with the new cropped one which I am able to do so successfully but the screen doesn't update with the cropped image until I refresh it.
import ImagePicker from 'react-native-image-crop-picker';
const [renderImages, setRenderImages] = useState([]);
//Listens for images
useEffect(() => {
renderImages;
}, [renderImages]);
//Pick images from gallery
const pickGalleryImages = () => {
let imageList = [];
ImagePicker.openPicker({
multiple: true,
mediaType: 'any',
maxFiles: 10,
cropping: true,
})
.then(response => {
response.map(imgs => {
imageList.push(imgs.path);
});
setRenderImages(imageList);
})
.catch(() => null);
};
//Crop image
const cropImage = item => {
ImagePicker.openCropper({
path: item.imgs,
width: 400,
height: 400,
})
.then(image => {
const oldImage = renderImages.findIndex(img => img.imgs === item.imgs);
renderImages[oldImage] = {imgs: image.path};
})
.catch(() => null);
};
It seems that cropImage is not doing setRenderImages to update the state, therefore the component is not re-rendered.
Try do a setRenderImages in cropImage:
UPDATE
For keeping existing places of images in array:
//Crop image
const cropImage = (item) => {
ImagePicker.openCropper({
path: item.imgs,
width: 400,
height: 400,
})
.then((image) => {
setRenderImages((prev) => {
const oldIndex = prev.findIndex((img) => img.imgs === item.imgs);
const newImages = [...prev];
newImages[oldIndex] = { imgs: image.path };
return newImages;
});
})
.catch(() => null);
};
Original
For pushing latest cropped image to start of array:
//Crop image
const cropImage = (item) => {
ImagePicker.openCropper({
path: item.imgs,
width: 400,
height: 400,
})
.then((image) => {
setRenderImages((prev) => {
const oldImages = prev.filter((img) => img.imgs !== item.imgs);
return [{ imgs: image.path }, ...oldImages];
});
})
.catch(() => null);
};
Hope this will help!
you should use setRnderImages for update state:
//Crop image
const cropImage = item => {
ImagePicker.openCropper({
path: item.imgs,
width: 400,
height: 400,
})
.then(image => {
const oldImage = renderImages.findIndex(img => img.imgs === item.imgs);
let newImages= [...renderImages]
newImages[oldImage] = {imgs: image.path};
setRnderImages(newImages)
})
.catch(() => null);
};

Wait until WebGL has drawn onto screen

I switched from using 2D context to using WebGL context. I'm trying to figure out a way to wait until pixels have been drawn onto the screen.
With 2D context I would do something like this
const handlePaintFrame = () => {
const ctx: CanvasRenderingContext2D | any = videoCanvasRef?.current?.getContext("2d");
if (video.current && videoCanvasRef.current && ctx) {
if (chroma?.keyColor) {
drawChromaFrame(ctx, width, height);
} else {
ctx.drawImage(video.current, 0, 0, width, height);
}
const rendered = () => {
setTimeout(() => {
onReady("video");
}, 20);
};
const startRender = () => {
requestAnimationFrame(rendered);
};
const img = new Image();
img.onload = () => {
requestAnimationFrame(startRender);
};
img.onerror = (e: any) => {
console.log("Image error:", { e });
};
img.src = videoCanvasRef.current.toDataURL();
}
};
Currently my draw code for the WebGL context is something like this
const drawTexture = () => {
// render
if (gl && video.current) {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, video.current);
drawScene(locationRef.current, buffersRef.current);
}
};
const handlePaintFrame = () => {
if (video.current) {
if (chroma?.keyColor) {
// drawChromaFrame(ctx, width, height);
} else {
drawTexture();
// TODO: We need to wait until texture is drawn
setTimeout(() => {
onReady("video");
}, 100);
}
}
};

Testing a ResizeObserver inside an Effect Fn

I'm struggling a bit with unit testing the following:
const { element, state } = props;
const { theme } = state;
const { computedHeight, computedWidth } = theme;
const elementToSize = document.querySelector(element);
const observer = new ResizeObserver(element => {
const { contentRect } = element[0];
// set the viewport size in the state
dispatch(setViewportSize(state, contentRect.width, contentRect.height));
// perform the task function
wrapper(600, computedWidth, computedHeight, contentRect.width, contentRect.height).
then(() => {
elementToSize.style.height = `${contentRect.height <= computedHeight ? contentRect.height : computedHeight}px`;
elementToSize.style.width = `${contentRect.width <= computedWidth ? contentRect.width : computedWidth}px`;
}).catch( e => new Error(e));
});
observer.observe(document.documentElement);
const wrapper = async (ms, ...callbackArgs) => {
try {
await asyncInterval(checkSize, ms, ...callbackArgs);
} catch {
new Error('async Interval has failed...');
}
return await asyncInterval(checkSize, ms, ...callbackArgs);
};
};
export const SizeableEffect = (element, state) => [effectFn, { element, state } ];
In the code above, I have difficulties with unit testing the code inside the ResizeObserver.
I have the following in my test.
import ResizeObserver from 'resize-observer-polyfill';
import { SizeableEffect } from 'effects';
import { domMock } from '../mocks/dom';
jest.mock('resize-observer-polyfill');
describe('SizeableEffect', () => {
let stateMock, dispatch, effect, effectFn;
beforeEach(() => {
stateMock = {
viewportWidth: null,
viewportHeight: null,
theme: {
chatFrameSize: {
width: 300,
height: 500
}
}
};
dispatch = jest.fn();
effect = SizeableEffect('elementMock', stateMock);
effectFn = effect[0];
Object.defineProperties(window, {
document: {
value: domMock()
}
});
});
it('the effect should be called with the element', () => {
expect(effect[1]).toEqual(
expect.objectContaining({
element: 'elementMock',
state: stateMock
})
);
});
it('the effect function should perform the operations', () => {
effectFn(dispatch, { element: 'some', state: stateMock });
expect(document.querySelector).toHaveBeenCalledWith('some');
expect(dispatch).not.toHaveBeenCalled();
expect(ResizeObserver).toHaveBeenCalled();
});
});
And as you can see from the screenshot, I have uncovered lines where the ResizeObserver is doing the login by measuring the width' and height's against the viewport.
How can I cover those lines in the best way?

How can i return the resized image from filereader

I've created a function that takes care of resizing a image, now i want to return the resized image from the function.
I'm working with react and know i can fix the problem by using the state but i don't like this solution..
Tried returning every different scope, but i still get a undefined response. Also when i return the reader itself, i get the old data.
Interface
interface IHTMLInputEvent extends Event {
target: HTMLInputElement & EventTarget;
}
the resize function
function resizeImage(file: IHTMLInputEvent, width: number) {
const fileName = file.target.files[0].name;
const reader = new FileReader();
reader.readAsDataURL(file.target.files[0]);
reader.onload = (event) => {
const img = new Image();
img.src = event.target.result.toString();
img.onload = () => {
const elem = document.createElement('canvas');
const scaleFactor = width / img.width;
elem.width = width;
elem.height = img.height * scaleFactor;
const ctx = elem.getContext('2d');
ctx.drawImage(img, 0, 0, width, img.height * scaleFactor);
ctx.canvas.toBlob((blob) => {
return new File([blob], fileName, { type: 'image/jpeg', lastModified: Date.now() });
}, 'image/jpeg', 1);
};
};
}
function for handling the upload(so far)
function handleUpload(e: IHTMLInputEvent) {
const resizedImage = resizeImage(e, 600);
}
input field
<input
className={classes.inputForUpload}
accept='image/*'
type='file'
ref={uploadImage}
onChange={(e: IHTMLInputEvent) => handleUpload(e)}
/>
I would like to return the new created image.
You can solve this using Promise,
function resizeImage(file: IHTMLInputEvent, width: number) {
return new Promise((resolve, reject) => {
const fileName = file.target.files[0].name;
const reader = new FileReader();
reader.readAsDataURL(file.target.files[0]);
reader.onload = (event) => {
const img = new Image();
img.src = event.target.result.toString();
img.onload = () => {
const elem = document.createElement('canvas');
const scaleFactor = width / img.width;
elem.width = width;
elem.height = img.height * scaleFactor;
const ctx = elem.getContext('2d');
ctx.drawImage(img, 0, 0, width, img.height * scaleFactor);
ctx.canvas.toBlob((blob) => {
resolve(new File([blob], fileName, {
type: 'image/jpeg',
lastModified: Date.now()
}));
}, 'image/jpeg', 1);
};
};
});
}
With async, await ,
async function handleUpload(e: IHTMLInputEvent) {
const resizedImage = await resizeImage(e, 600);
// do you suff here
}
JSX,
<input
className={classes.inputForUpload}
accept='image/*'
type='file'
ref={uploadImage}
onChange={async (e: IHTMLInputEvent) => await handleUpload(e)}
/>
May I ask why you don't like the solution of using state? This seems like a pretty standard use case.
Your state could look something like this:
state = {
imageDescription: '',
imageUrl: null
};
Your action handler would simply setState upon success like so:
img.onload = () => {
...
this.setState({ imageDescription: fileName, imageSrc: img.src })
};
Finally your render function would look something like this:
render() {
const { imageDescription, imageUrl } = this.state;
return (
<Fragment>
<input
className={classes.inputForUpload}
accept='image/*'
type='file'
ref={uploadImage}
onChange={(e: IHTMLInputEvent) => handleUpload(e)}
/>
<img src={imageUrl} alt={imageDescription} />
</Fragment>
)
}
P.S. you can delete handleUpload and call resizeImage directly.

Categories

Resources