Slingshot and Imagemagick / grapicksmagic

hi, i need to compress the uploads imgs, but i use meteor Slingshot, and upload files to google cloud storage,

i thinks use a package like classcraft:imagemagick but the docs is very poor. and i not know how can implement, and the most important, i don’t know exactly works slingshot:

for example. the input file convert a file into a blob objetc, this blob is upload the server (and in this moment i can convert the file with imagemagick) and later upload to cloud storage?, or is directly to cloud storage and i can’t use my server to edit the image?.

thanks!

If it’s ok for your application, you can manipulate the image on the client before the upload.

You can use: https://github.com/ccorcos/meteor-clientside-image-manipulation which provide processImage function but this will help only if you just need to resize or adjust the image orientation

new Promise((resolve) => {
  processImage(file, 510, 510, resolve); // resize 
}).then((data) => {
  const match = /^data:([^;]+);base64,(.+)$/.exec(data);
  return [file.name, match[1], match[2]];
}).then((params) => {
  const name = params[0];
  const type = params[1];
  const b64 = params[2];
  return new Promise((resolve, reject) => {
    let blob = b64ToBlob(b64, type);
    blob.name = name;
    const uploader = new Slingshot.Upload('yourslingshotuploader');
    uploader.send(blob, (error, downloadUrl) => {
      if (error) {
        console.error('Error uploading', uploader.xhr.response);
        reject(error.message);
      } else {
        resolve(downloadUrl);
      }
    });
  });
});

Slingshot upload directly to the cloud storage, so if you need to do a server side manipulation you actually lose the benefits of slingshot as you need to download the image from the cloud storage on the server, make the manipulation, and then upload again the image.

2 Likes

Thanks! i read to can chage the quality parameter ( i most need compress mor than resize images,) thanks, i implement this now

hey @siphion i try the code, and i cost at first. but when search the b64ToBlob function on google and implemented that, i can upload and optimize images, and do it great!

thank so much!,

1 Like

Sorry, i got that from my repository and didn’t caught that.

Here is the function, if anyone interested

function b64ToBlob(b64Data, contentType, sliceSize) {
  let byteNumbers, i, slice;
  let offset = 0;
  let byteCharacters = atob(b64Data);
  let byteArrays = [];
  sliceSize = sliceSize || 512;
  byteArrays = [];
  while (offset < byteCharacters.length) {
    slice = byteCharacters.slice(offset, offset + sliceSize);
    byteNumbers = [];
    for (i = 0; i < slice.length; ++i) {
      byteNumbers.push(slice.charCodeAt(i));
    }
    byteArrays.push(new Uint8Array(byteNumbers));
    offset += sliceSize;
  }
  return new Blob(byteArrays, {type: contentType});
}
1 Like

Just a side note, compressing an image (by reducing its quality) is not image optimization. It’s just image compression. For optimization, checkout projects like OptiPNG and JpegOptim.

A completely different approach would be to use Google Cloud Functions.

It’s still in alpha, and I haven’t tried it myself, but I’m keeping an eye on it for just the same reason: optimize image uploads after slingshot ( and possibly thumbnails on the side).

Looking at the specs, you can create a miniature javascript function that gets triggered by a google cloud storage file upload.

i try with the solution of @siphion and is very usefull, i don’t diference the image and the png of 1.48 mb pass to 219 kb and image jpg of 456 kb pass to 46 kb, its ok for me, the cloud function is better?

Thanks for the note, this is cool. Hope that amazon will do something like this too!
@levanlacroix i think that you can stick with the already working solution, Google Cloud Functions its still in alpha!

Isn’t this AWS Lambda?

1 Like

Oh thank you! Didn’t checked before writing!

1 Like

found that a while ago but didnt try it: http://blog.kaliloudiaby.com/index.php/serverless-image-resizing-aws-lambda-and-aws-s3/

anybody uses that technique together with slingshot? experiences?

1 Like

I’ve been using html canvas api to resize and crop images on the client before uploading to Amazon S3 servers. It works just great. If you want, I’ll be happy to share the code.

Ah, for cropping, I use this: http://www.utilnow.com/jquery-image-crop-plugin/

1 Like

Checkout the node package sharp - allegedly much faster than imagemagick. We use and it’a quite fast.

@kaufmae: I’ve been using Slingshot + S3 + Lambda and it’s been working great. I’m using it for resizing images.

1 Like

@sagannotcarl care to share a gist of what this looks like?

sure @jahosh, I’ll see what I can post here that would be helpful.

A lot of this is from Slingshot tutorials.

# imports/startup/server/file-restrictions.js
Meteor.startup( () => {
  Slingshot.fileRestrictions("myFileUploads", {
    allowedFileTypes: ["image/png", "image/jpeg", "image/jpg", "image/gif"],
    maxSize: 10 * 1024 * 1024 // 10 MB (use null for unlimited).
  });

  Slingshot.createDirective("myFileUploads", Slingshot.S3Storage, {
    bucket: "[S3-BUCKET-NAME]",

    acl: "public-read",

    authorize: function () {
      //Deny uploads if user is not logged in.
      if (!this.userId) {
        var message = "Please login before posting files";
        throw new Meteor.Error("Login Required", message);
      }

      return true;
    },

    key: function (file) {
      //Store file into a directory by the user's username.
      // var user = Meteor.users.findOne(this.userId);
      return file.name;
    }
  });
})

Then in my React component I have the following functions:

// [...]
import Dropzone from 'react-dropzone';
// [...]
onDrop(files) {
  this.setState({ newImageLoaded: false });

  // Upload file using Slingshot Directive
  const uploader = new Slingshot.Upload('myFileUploads');

  // Track Progress
  const computation = Tracker.autorun(() => {
    if (!isNaN(uploader.progress())) {
      const progressNumber = Math.floor(uploader.progress() * 100);
      this.setState({ progress: progressNumber });

      if (progressNumber > 0 && progressNumber < 100) {
        this.setState({ uploading: true });
      } else {
        this.setState({ uploading: false });
      }
    }
  });

  uploader.send(files[0], (error, url) => {
    computation.stop(); // Stop progress tracking on upload finish
    if (error) {
      this.setState({ progress: 0 }); // reset progress state
      this.setState({ uploadError: true });
    } else {
      // Wait until the Lambda script has finished resizing
      this.checkResizedImage(url);
    }
  });
}

checkResizedImage(src) {
  const { profile } = this.props;
  const { uploadAttempts } = this.state;

  const originalSrc = src;
  const resizedImageSrc = src.replace('https://wtm-dev-images', 'https://wtm-dev-images-resized');

  const cycleTime = 1000;

  const img = new Image();
  img.onload = function () {
    // code to set the src on success
    const newImage = {
      profileId: profile._id,
      image: originalSrc,
    };
    updateImage.call(
      newImage
    , displayError);

    // Reset the upload counter in case the user tries again
    this.setState({ uploadAttempts: 0 });

    // Remove the Almost done... message
    this.setState({ newImageLoaded: true });
  };

  img.onload = img.onload.bind(this);
  img.onerror = function () {
    const nextAttempt = uploadAttempts + 1;
    this.setState({ uploadAttempts: nextAttempt });
    // doesn't exist or error loading
    if (uploadAttempts < 10) {
      setTimeout(() => {
        this.checkResizedImage(originalSrc);
      }, cycleTime, originalSrc);
    } else {
      this.setState({ uploadError: true });
    }
  };
  img.onerror = img.onerror.bind(this);

  img.src = resizedImageSrc; // fires off loading of image
}

render() {
  <Dropzone 
    onDrop={this.onDrop}
    activeClassName="dropzone-target-active"
  >
    Text in the drop zone...
  </Dropzone>
}

Then I just followed a Lambda tutorial about resizing images. I think it was this one: http://docs.aws.amazon.com/lambda/latest/dg/with-s3-example.html

Basically Slingshot just puts the full image on one bucket, and lambda reacts to the bucket event and puts the altered image on another bucket. I manually alter the path to point to the new bucket and the this.checkResizedImage() function waits until that image is ready and writes it back to the database.

Hope that helps! If I’m missing anything let me know and I’ll post it.

4 Likes

@sagannotcarl thanks!

@Emin Could you share what your slingshot code looks like? I’m wanting to add resize and crop on the client.