Reputation: 5150
I've made this little image resizer and I can't figure out why the images it creates are blurry, especially when I use a large image.
import React, { useState } from 'react';
interface PropsInterface {
placeholder: string;
resizeTo: number;
blob?: Function;
base64?: Function;
}
const Photo: React.FC<PropsInterface> = (props: PropsInterface) => {
const [state, setState] = useState({
imageURL: props.placeholder,
});
const {
imageURL,
} = state;
const dataURLToBlob = (dataURL: string) => {
const parts = dataURL.split(';base64,');
const contentType = parts[0].split(':')[1];
const raw = window.atob(parts[1]);
const rawLength = raw.length;
const uInt8Array = new Uint8Array(rawLength);
for (let i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], { type: contentType });
};
const resizeImage = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.currentTarget.files) {
const file = event.currentTarget.files[0];
const reader = new FileReader();
reader.onload = (readerEvent) => {
const image = new Image();
image.onload = async (imageEvent) => {
const canvas = document.createElement('canvas');
const maxSize = props.resizeTo;
let width = image.width;
let height = image.height;
if (width > height && width > maxSize) {
height *= maxSize / width;
width = maxSize;
} else if (height > maxSize) {
width *= maxSize / height;
height = maxSize;
}
canvas.width = width;
canvas.height = height;
const ctx: CanvasRenderingContext2D = canvas.getContext('2d')!;
ctx.imageSmoothingEnabled = false;
ctx.drawImage(image, 0, 0, width, height);
const dataUrl = canvas.toDataURL('image/jpeg');
const resizedImage = dataURLToBlob(dataUrl);
setState((prev) => ({ ...prev, imageURL: dataUrl }));
if (props.blob) props.blob(resizedImage);
if (props.base64) props.base64(dataUrl);
};
image.src = URL.createObjectURL(file);
};
reader.readAsDataURL(file);
}
};
return (
<div className='photo'>
<label>
<img
src={imageURL}
alt='your first name initial'
className='photo--preview'
/>
<input
type='file'
id='photo'
name='photo'
accept='image/png, image/jpeg'
onChange={resizeImage}
></input>
</label>
</div>
);
};
export { Photo };
I use it by adding
<Photo placeholder={placeholder} resizeTo={60} blob={blob} />
Where placeholder
is a URL to an image I want to display initially resizeTo
is the size in pixels I want to resize the image to and blob
is the name of the function that it returns a blob to.
Left is my image resizer and right is from another website.
The image from another website is 180px square, So I set resizeTo
180 and the result can be seen on the left.
The left image is 180 width but not 180pix high, maybe this is why the quality looks worse? How to crop the image to 180x180? so we can make a fair comparison?
Upvotes: 0
Views: 1594
Reputation: 887
When your file is loaded, you should save the loaded data as source of truth, and if you want to retain all the information, the source of truth cannot be modified.
Instead, you create a view for source, and change the view instead. In my example below, the source of truth is this.state.data
and the view is the <img>
tag. The slider is used to modify the img and the source is not changed.
See the code below.
JSFiddle: https://jsfiddle.net/ypoon196/yjprgfch/25/
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
// Base64
data: "",
// Original W/H
imageWidth: 0,
imageHeight: 0,
// resized W/H
currWidth: 0,
currHeight: 0,
lockAspect: true,
};
};
handleFileChange(evt) {
const file = evt.target.files[0];
if (!!file) {
const reader = new FileReader();
reader.onload = () => {
// Save to a temp location to get the height and width
const tmp = new Image();
tmp.onload = () => {
// Only display everythin when all is loaded
const height = tmp.height;
const width = tmp.width;
this.setState({
data: reader.result,
imageHeight: height,
imageWidth: width,
currHeight: height,
currWidth: width
})
};
tmp.src = reader.result;
}
reader.readAsDataURL(file);
}
}
handleSliderChange(type, evt) {
const val = evt.target.value;
const { lockAspect, imageHeight, imageWidth } = this.state;
switch (type) {
case "height":
if (lockAspect) {
const ratio = imageHeight / imageWidth;
const otherVal = val / ratio;
this.setState(prevState => {
return { currHeight: val, currWidth: otherVal }
});
} else {
this.setState(prevState => {
return { currHeight: val }
});
}
break;
case "width":
if (lockAspect) {
const ratio = imageHeight / imageWidth;
const otherVal = val * ratio;
this.setState(prevState => {
return { currHeight: otherVal, currWidth: val }
});
} else {
this.setState(prevState => {
return { currWidth: val }
});
}
break;
}
}
handleAspectToggle() {
this.setState(({lockAspect}) => {
return {lockAspect: !lockAspect}
})
}
render() {
const { data, imageHeight, imageWidth, currWidth, currHeight, lockAspect } = this.state;
return (
<div>
<div>
<input type="file" onChange={evt => this.handleFileChange(evt)} accept="image/*"/>
</div>
<div>
<label>Height</label>
<input
type="range"
min="1"
max={imageHeight}
value={currHeight}
onChange={evt => this.handleSliderChange("height", evt)}
/>
<span>{ currHeight }</span>
</div>
<div>
<label>Width</label>
<input
type="range"
min="1"
max={imageWidth}
value={currWidth}
onChange={evt => this.handleSliderChange("width", evt)}
/>
<span>{ currWidth }</span>
</div>
<div>
<label>Lock Aspect</label>
<input
type="checkbox"
checked={lockAspect}
onChange={() => this.handleAspectToggle()}
/>
</div>
<div>
<img height={currHeight} width={currWidth} src={data}/>
</div>
</div>
);
}
}
ReactDOM.render(
<App/>,
document.getElementById('container')
);
Upvotes: 1
Reputation: 792
Whenever you reduce an image you will loose some information. One thing to consider while resizing the image is Aspect Ratio and i believe this part of the code handles it
if (width > height && width > maxSize) {
height *= maxSize / width;
width = maxSize;
} else if (height > maxSize) {
width *= maxSize / height;
height = maxSize;
}
canvas.width = width;
canvas.height = height;
Other than that, the image on the right is not just resized but also cropped. If you look at the top right corner some part of the clouds are missing. You cannot compare the left and right, check if original image and the resized image's aspect ratio matches, if not fix it.
Upvotes: 1