Image compression in react-native
Asked Answered
P

4

6

I am trying to compress images with mozjpeg when I implemented it in node.js according to the docs it worked fine.

const input = fs.readFileSync("in.ppm");
const out = mozjpeg.encode(input, { quality: 85 });

I need to do the compression on the client-side, so I tried to do the same with react-native since react-native doesn't contain core node modules such as fs, I need to go for a third party library react-native-fs for file reading.

When I tried to execute mozjpeg.encode(input, { quality: 85 }); in react-native it throws Unrecognized input file format --- perhaps you need -targa

server-side implementation

const mozjpeg = require("mozjpeg-js");
const fs = require("fs");

const input = fs.readFileSync(filePath);
const out = mozjpeg.encode(input, { quality: 85 });
console.error(out.stderr);
fs.writeFileSync("out.jpg", out.data);

client-side implementation

fs.readFile(image.path).then(data => {
    const out = mozjpeg.encode(data, { quality: 85 });
    console.log(out);
}

Here is the list of thing I tried

  • Tried giving input in hex, buffer, base64 and plain URL string.
  • Since the Android URL contains file:// as prefix I tried to remove them also.
Pretzel answered 6/3, 2020 at 14:57 Comment(3)
I used github.com/bamlab/react-native-image-resizer with quality to compress imageRucker
Yes, i am currently using that but i wanted to migrate to mozjpeg to reduce file size.Pretzel
The issue is almost certainly something with the encoding (assuming the file is being read correctly). Because mozjpeg expects the file to be binary encoded maybe something like Buffer.from(data, 'utf8') as readFile from react-native-fs is utf8 by default.Eyecup
M
2

Actually I don't know about mozjpeg but I prefer to use pure JavaScript way on the environment of problem to solve my problem.

I guess your thought fell in one-way bios, leave away the NodeJS solution, you are in the react-native environment so use React Native Compress Image or React Native Image Resizer.

Based on my experiences I prefer to use the second one, the React Native Image Resizer.

After install, use it like below:

ImageResizer.createResizedImage(
  imageUri,
  newWidth,
  newHeight,
  compressFormat,
  quality
)
  .then( resizedImageUri => {

    // the resizedImageUri is accessible here to use.

  }).catch( err => {

  // Catch any error here.

});
Midweek answered 16/3, 2020 at 5:18 Comment(7)
I am already using React Native Image Resizer, I just want to migrate from that to mozjpeg.Pretzel
Dear @Badri, why you wanna migrate from awesome and update library to a library for another environment that is not compatible and has many issues?Midweek
well i compared both the compressions, for me mozjpeg seems to have better compression technique and give the output file more 20% lower compared to react-native-compression which is important to what i am developing.Pretzel
@Badri, We have three environments, RN, Node, Browser, in the Browser, compressor methods use canvas and base64 to calculate on an image and make some changes like resizing, rotate or compress. in the Node environment the mozjpeg uses cjpeg that is based on MozJPEG and fs that doesn't exist in the RN, in the RN it is needed to use Device Native modules, iOS: objective-c and Android: JAVA, I know you said the mozjpeg is better in compressing but it not related to RN environment.Midweek
@Badri, Also there is another good RN library that we used inside our project. its purpose is cropping image but one of its abilities is compressing grabbed pictures. I will add a separate answer.Midweek
Yes i completely agree with you, RN doesn't have fs, there is a package for that react-native-fs which is used to read files the output will be in base64/ascii or utf-8. since MozJpeg only supports binary buffer i tried to convert which obviously didn't work,My question is whether i am missing something in the process of converting to binary buffer. BTW, i am already using what you suggested both libraries react-native-crop-picker to select the image and react-native-image-resizer to resize to various sizes,I did research as best as i could need to reduce that 10-15% size and get best Quality.Pretzel
Dear @Badri, I hope you find the best way and solution. By the way, I leave an upvote to your question. thanks for the good negotiation.❤️Midweek
M
2

Also, there is another good React-Native image crop picker library that purpose is grabbing some images and cropping them but it has a good ability to compress it. maybe this library has a good compress algorithm like mozjpeg.

It can open camera, open gallery or use a constant image, even you can turn of cropping:

ImagePicker.openCamera({
  width: 300,
  height: 400,
  compressImageQuality: 0.2
}).then(image => {
  // do what you want
}).catch(e => {
  // handle error
});

Good to config and it has many options for tuning. I hope it helps you.

Midweek answered 16/3, 2020 at 12:31 Comment(6)
Dear @Badri, if it is possible please release a simple tiny, re-production of your project into GitHub or GitLab then I clone it. maybe I could help you.Midweek
Thanks i post the link ASAPPretzel
react-native-image-crop-picker is a great package, but the compression quality is decent. You can find other packages, such as imagemin, that compress better quality and file size. But sadly it's not for react native.Manmade
@AnhNguyen, Sorry, I didn't get what you mean! what's your purpose for this comment? and how I can help you?Midweek
@Midweek No I'm not asking for your help. I just comment your answer. Can't I?Manmade
Dear @AnhNguyen, definitely, you can, I only haven't understood. ok. thanks.Midweek
N
1

You can find in mozjpeg-js doc that the input argument is:

a typed array or buffer of data


fs.readFile return type in client-side (react-native-fs) is Promise<string> and return contents. (Doc)

But in server-side (fs), fs.readFileSync return buffer object. (Doc)


So you can change string to a typed array with this function:

function str2ta(str) {
  var bufView = new Uint16Array(str.length*2); // 2 bytes for each char
  for (var i=0, strLen=str.length; i<strLen; i++) {
    bufView[i] = str.charCodeAt(i);
  }
  return bufView;
}
Nasion answered 9/3, 2020 at 6:56 Comment(3)
I have seen that on docs that's why i mentioned that i have given input in hex, buffer, base64 and plain URL string. BTW i used this function to convert function base64ToHex(str) { console.log(str) const raw = atob(str); let result = ''; for (let i = 0; i < raw.length; i++) { const hex = raw.charCodeAt(i).toString(16).toLowerCase(); result += ((hex.length === 2 ? hex : '0' + hex) + ' '); } return result.toLowerCase(); }Pretzel
Why you did not test typed array? Your base64ToHex function return string, but my str2ta return typed array @PretzelNasion
I tried typed arrays, unit8buffer, int8buffer after trying all that only i posted my question here sorry for not mentioning more clear on inputs i have tried, and i tried your function also still the same error throws. I tried hex bcoz fs from node returns hex buffer by default as it worked in my server code i tried that as a last resort @NasionPretzel
N
0

Expo has a component that does exactly this: Image Manipulator

Newkirk answered 13/3, 2023 at 0:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.