I need help with using sharp, I want my images to resize when uploaded but I can't seem to get this right.
router.post("/", upload.single("image"), async (req, res) => {
const { filename: image } = req.file;
await sharp(req.file.path)
.resize(300, 200)
.jpeg({ quality: 50 })
.toFile(path.resolve(req.file.destination, "resized", image));
fs.unlinkSync(req.file.path);
res.send("sent");
});
As I know you should pass a Buffer to sharp not the path.
Instead of resizing saved image, resize it before save. to implement this, you should use multer.memoryStorage() as storage.
const multer = require('multer');
const sharp = require('sharp');
const storage = multer.memoryStorage();
const filter = (req, file, cb) => {
if (file.mimetype.split("/")[0] === 'image') {
cb(null, true);
} else {
cb(new Error("Only images are allowed!"));
}
};
exports.imageUploader = multer({
storage,
fileFilter: filter
});
app.post('/', imageUploader.single('photo'), async (req, res, next) => {
// req.file includes the buffer
// path: where to store resized photo
const path = `./public/img/${req.file.filename}`;
// toFile() method stores the image on disk
await sharp(req.file.buffer).resize(300, 300).toFile(path);
next();
});
Related
I have a simple node script to fetch tif image, use sharp to convert the image to jpeg and generate data:image/jpeg;base64 src for browser. The reason is to enable tiff image in browsers that do not support tif/tiff images.
In node this works great.
import sharp from "sharp";
import fetch from "node-fetch";
let link =
"https://people.math.sc.edu/Burkardt/data/tif/at3_1m4_01.tif";
// fetch tif image and save it as a buffer
async function fetchTifBuffer(link) {
const tifImg = await fetch(link);
const buffer = await tifImg.buffer();
return buffer;
}
// convert to png image save to buffer and save as base64
async function tifToPngToBase64(link) {
let inputImgBuffer = await fetchTifBuffer(link);
const buff = await sharp(inputImgBuffer).toFormat("jpeg").toBuffer();
let base64data = buff.toString("base64");
let imgsrc = "data:image/jpeg;base64," + base64data.toString("base64");
console.log(imgsrc);
// use in browser <img src="data:image/jpeg;base64,/9j/2wBDAAYEBQYFBAY ...==" alt="image" />
}
tifToPngToBase64(link);
I would like to implement this in SvelteKit. But I have no idea how to get buffer from SvelteKit fetch response. Any ideas?
Ok, so I figured it out by myself. SvelteKit fetch response has an arrayBuffer, so all that is needed is to convert the arraybuffer to the buffer.
This is the relevant SvelteKit endpoint:
// img.js
import sharp from "sharp";
export const get = async () => {
const res = await fetch(
"https://people.math.sc.edu/Burkardt/data/tif/at3_1m4_01.tif"
);
const abuffer = await res.arrayBuffer();
const buffer = Buffer.from(new Uint8Array(abuffer));
const buff = await sharp(buffer).toFormat("jpeg").toBuffer();
let base64data = buff.toString("base64");
let src = `data:image/jpeg;base64,${base64data.toString("base64")}`;
let img = `<img style='display:block; width:100px;height:100px;' id='base64image'
src='${src}' />`;
return {
body: {
img,
},
};
};
and this is the SvelteKit component which uses the endpont:
// img.svelte
<script>
export let img;
</script>
{#html img}
I had trouble testing my code that implement sharp and memfs. I has codes that download image and cropping it to certain dimension. I use sharp to achieve the cropping. When it comes testing, I use jest and memfs. My test code has 2 parts:
Download a image and save to mock volume/fileSystem that created with memfs.
Cropping the downloaded image to certain dimension with sharp.
Part 1 worked perfectly, was able to download image to the fake volume and asserted with jest (exist in the fake volume).
But part 2 gave me error:[Error: Input file is missing].
const sizeToCrop = {
width: 378,
height: 538,
left: 422,
top: 0,
};
sharp(`./downloadedImage.jpg`)
.extract(sizeToCrop)
.toFile(`./CroppedImage.jpg`)
.then(() => {
console.log(`resolve!`);
})
.catch((err: Error) => {
console.log(err);
return Promise.reject();
});
// Error message: [Error: Input file is missing]
But when I test it with real volume. It worked fine.
Anyone has idea how to solve this?
Thank you.
After some inspirations from here, here and here.
It is because sharp is not accessing memfs' faked file system, it is accessing the actual fs' file system, which ./donwloadedImage.jpg to not exist. Hence, in order to make sharp use memfs, it has to be mocked. And extract and toFile functions need to be mocked as well(for chaining function call):
// Inside code.test.ts
jest.mock('sharp', () => {
const sharp = jest.requireActual('sharp');
const { vol } = require('memfs');
let inputFilePath: string;
let sizeToCrop: any;
let outputFilePath: string;
const toFile = async (writePath: string): Promise<any> => {
outputFilePath = writePath;
try {
return await starCropping();
} catch (error) {
console.log(`Error in mocked toFile()`, error);
}
};
const extract = (dimensionSize: string): any => {
sizeToCrop = dimensionSize;
return { toFile };
};
const mockSharp = (readPath: string): any => {
inputFilePath = readPath;
return { extract };
};
async function starCropping(): Promise<void> {
try {
const inputFile = vol.readFileSync(inputFilePath);
const imageBuffer = await sharp(inputFile).extract(sizeToCrop).toBuffer();
vol.writeFileSync(outputFilePath, imageBuffer);
} catch (error) {
console.log(`Error in mocked sharp module`, error);
return Promise.reject();
}
}
return mockSharp;
});
When I take the file I want the data file size information, in the future the upload file limit will be made based on the file size. the following code that I use
void _openFileExplorer() async {
setState(() => _loadingPath = true);
try {
_directoryPath = null;
_paths = (await FilePicker.platform.pickFiles(
type: _pickingType,
allowMultiple: _multiPick,
allowedExtensions: ['jpg', 'pdf', 'doc', 'docx', 'png', 'jpeg'],
))
?.files;
} on PlatformException catch (e) {
print("Unsupported operation" + e.toString());
} catch (ex) {
print(ex);
}
if (!mounted) return;
setState(() {
_loadingPath = false;
_fileName = _paths != null ? _paths.map((e) => e.name).toString() : '...';
});
}
you can get file size using function named lengthSync.
just use this function like
var size = file.lengthSync()
it will give file size in bytes.
I am going to rotate the image in react–native and I would like to get base64 of rotated image.
I used several libraries
react-native-image-rotate: It's working well on Android but on iOS I get rct-image-store://1 as url so I tried getting base64 using rn-fetch-blob but it throws error that can't recognize that url.
react-native-image-resizer: I used this but the response is not good in iOS. If I set -90 then rotate -180, if I set -180 then it's rotating as -270.
Please help me on this problem, how can I rotate the image in iOS.
I need to rotate the image as -90, -180, -270, -360(original).
Finally, I found answer.
import ImageRotate from 'react-native-image-rotate';
import ImageResizer from 'react-native-image-resizer';
import RNFetchBlob from 'rn-fetch-blob';
ImageRotate.rotateImage(
this.state.image.uri,
rotateDegree,
uri => {
if (Platform.OS === 'android') {
console.log('rotate', uri);
RNFetchBlob.fs.readFile(uri, 'base64').then(data => {
const object = {};
object.base64 = data;
object.width = this.state.image.height;
object.height = this.state.image.width;
object.uri = uri;
this.setState({image: object, spinner: false});
});
} else {
console.log(uri);
const outputPath = `${RNFetchBlob.fs.dirs.DocumentDir}`;
ImageResizer.createResizedImage(
uri,
this.state.image.height,
this.state.image.width,
'JPEG',
100,
0,
outputPath,
).then(response => {
console.log(response.uri, response.size);
let imageUri = response.uri;
if (Platform.OS === 'ios') {
imageUri = imageUri.split('file://')[1];
}
RNFetchBlob.fs.readFile(imageUri, 'base64').then(resData => {
const object = {};
object.base64 = resData;
object.width = this.state.image.height;
object.height = this.state.image.width;
object.uri = response.uri;
this.setState({image: object, spinner: false});
});
});
}
},
error => {
console.error(error);
},
);
}
This is my work well code up to now
rotateImage = (angle) => {
const { currentImage } = this.state; // origin Image, you can pass it from params,... as you wish
ImageRotate.rotateImage( // using 'react-native-image-rotate'
currentImage.uri,
angle,
(rotatedUri) => {
if (Platform.OS === 'ios') {
ImageStore.getBase64ForTag( // import from react-native
rotatedUri,
(base64Image) => {
const imagePath = `${RNFS.DocumentDirectoryPath}/${new Date().getTime()}.jpg`;
RNFS.writeFile(imagePath, `${base64Image}`, 'base64') // using 'react-native-fs'
.then(() => {
// now your file path is imagePath (which is a real path)
if (success) {
this.updateCurrentImage(imagePath, currentImage.height, currentImage.width);
ImageStore.removeImageForTag(rotatedUri);
}
})
.catch(() => {});
},
() => {},
);
} else {
this.updateCurrentImage(rotatedUri, currentImage.height, currentImage.width);
}
},
(error) => {
console.error(error);
},
);
};
I think you have done rotatedUri.
Then you can get base64 by ImageStore from react-native.
Then write it to the local image with react-native-fs
After that you have imagePath is the local image.
Try to use Expo Image Manipulator
https://docs.expo.io/versions/latest/sdk/imagemanipulator/
const rotated = await ImageManipulator.manipulateAsync(
image.uri,
[{ rotate: -90 }],
{ base64: true }
);
This is the function I am using to upload file but is is giving me the error : Length is undefined. what I have to change in this code. where to give path of file to upload.
fileChange(event) {
let fileList: FileList = event.target.files;
if(fileList) {
let file: File = fileList[0];
let formData:FormData = new FormData();
formData.append('uploadFile', file, file.name);
let headers = new Headers();
/** No need to include Content-Type in Angular 4 */
headers.append('Content-Type', 'multipart/form-data');
headers.append('Accept', 'application/json');
let options = new RequestOptions({ headers: headers });
this.http.post(`assets/Files/info.txt`, formData, options)
.map(res => res.json())
.catch(error => Observable.throw(error))
.subscribe(
data => console.log(fileList),
error => console.log(error)
)
}
}
you need to use xhr request to transfer files
fileChange(event: EventTarget) {
let eventObj: MSInputMethodContext = <MSInputMethodContext> event;
let target: HTMLInputElement = <HTMLInputElement> eventObj.target;
let files: FileList = target.files;
if(files) {
let file: File = files[0];
this.upload(file)
}
}
public upload(filedata: File) {
let url = 'your url'
if (typeof filedata != 'undefined') {
return new Promise((resolve, reject) => {
let formData: any = new FormData();
let xhr = new XMLHttpRequest();
formData.append('icondata', filedata, filedata.name);
xhr.open('POST', url, true);
xhr.setRequestHeader('Authorization', 'JWT ' + localStorage.getItem('id_token'));
xhr.send(formData);
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
resolve(JSON.parse(xhr.responseText));
}
}
});
}
}
I understand that this is not the functionality you want to have but with no backend you can not upload files to be persistent, they should be stored somewhere. If you just wanna manipulate file names for instance, skip the express part in my answer. I personally used this code which I altered to upload multiple files.
In your Component :
import {FormArray, FormBuilder, FormControl, FormGroup} from "#angular/forms";
declare FormBuilder in the constructor:
constructor (private http: Http, private fb: FormBuilder) {}
in ngOnInit() set a variable as follows :
this.myForm = this.fb.group({chosenfiles: this.fb.array([])});
this is the code for the upload method :
// invoke the upload to server method
// TODO
// Should be in a service (injectable)
upload() {
const formData: any = new FormData();
const files: Array<File> = this.filesToUpload;
//console.log(files);
const chosenf = <FormArray> this.myForm.controls["chosenfiles"];
// iterate over the number of files
for(let i =0; i < files.length; i++){
formData.append("uploads[]", files[i], files[i]['name']);
// store file name in an array
chosenf.push(new FormControl(files[i]['name']));
}
this.http.post('http://localhost:3003/api/upload', formData)
.map(files => files.json())
.subscribe(files => console.log('upload completed, files are : ', files));
}
the method responsible for the file change :
fileChangeEvent(fileInput: any) {
this.filesToUpload = <Array<File>>fileInput.target.files;
const formData: any = new FormData();
const files: Array<File> = this.filesToUpload;
console.log(files);
const chosenf = <FormArray> this.myForm.controls["chosenfiles"];
// iterate over the number of files
for(let i =0; i < files.length; i++){
formData.append("uploads[]", files[i], files[i]['name']);
// store file name in an array
chosenf.push(new FormControl(files[i]['name']));
}
}
Template is something like this
<input id="cin" name="cin" type="file" (change)="fileChangeEvent($event)" placeholder="Upload ..." multiple/>
Notice multiple responsible for allowing multiple selections
The express API which will handle the request uses multer after an npm install
var multer = require('multer');
var path = require('path');
specify a static directory which will hold the files
// specify the folder
app.use(express.static(path.join(__dirname, 'uploads')));
As specified by multer
PS: I did not investigate multer, as soon as i got it working, i moved to another task but feel free to remove unnecessary code.
var storage = multer.diskStorage({
// destination
destination: function (req, file, cb) {
cb(null, './uploads/')
},
filename: function (req, file, cb) {
cb(null, file.originalname);
}
});
var upload = multer({ storage: storage });
And finally the endpoint
app.post("/api/upload", upload.array("uploads[]", 12), function (req, res) {
console.log('files', req.files);
res.send(req.files);
});