3

I'm having trouble getting photos to orient the correct way in my angular/node app.

My app is set up so that I use ng2-file-upload to send the file from the angular app to the server where multer and multer-s3 saves that photo in AWS S3. I then save the filename in my postgres database and use it to call the photo by url.

My issue is, is the photo is uploaded by the iPhone (could be others I only tried iphone) and is returned rotated to the left.

Ive looked into different options on the server side that have not worked for me. This includes the libraries fix-orientation and jpeg-autorotate.

Does anyone have a solution they've implemented on the client-side in Angular2+ ?

Here is my image-upload code on the server side

aws.config.loadFromPath(path3);
var s3 = new aws.S3();
var fileName = '';
var uploadM = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'XXX',
    acl: 'public-read',
    metadata: function (req, file, cb) {
      console.log(file);
      console.log(req.file);
      cb(null, {fieldName: file.fieldname});
    },
    key: function (req, file, cb) {
      fileName = Date.now().toString() + "-" + (Math.round(Math.random() * 10000000000000000)).toString() + '-' + file.originalname;
      cb(null, fileName)
    }
  })
});

router.post('/upload', uploadM.array('photo', 3), function(req,res) {
    if (res.error) {
      return res.status(400).json({
        message: "Error",
        error: res.error
      });
    }
    return res.status(200).send(fileName);

});


module.exports = router;

And here is my code on the angular app

  public uploader:FileUploader = new FileUploader({url: this.devUrl, itemAlias: 'photo'});

  constructor(
    private userS: UserService,
    private authS: MyAuthService,
    private router: Router,
    private itemS: ItemService,
    private uis: UiService) {}

  ngOnInit() {
    this.uploader.onAfterAddingFile = (file)=> { file.withCredentials = false; };

    this.uploader.onCompleteItem = (item:any, response:any, status:any, headers:any) => {
      this.awsUrls.push('AWSURL' + response);
      this.filesUploaded = true;
      this.uploaderLoading = false;
    };

  }

  addItem() {
    this.uploaderLoading = true;
    this.itemS.onNewItem(this.awsUrls)
      .subscribe(data => {
        this.uis.onFlash('Posted Successfully. Add Details!', 'success');
        this.itemS.onSetUpdateItemId(data.id, false);
        this.uploaderLoading = false;
        this.onPhotoAdded();
      }, resp => {
        this.uploaderLoading = false;
        this.uis.onFlash('Error Posting', 'error');
        console.log(resp);
      });
  }

  onUploadClicked() {
    this.uploader.uploadAll();
    this.uploaderLoading = true;
  }
Jonathan Corrin
  • 1,219
  • 4
  • 17
  • 42
  • Dumb question but if you open the image in the browser directly, is it in the correct orientation? What about if you manualy place the image on the server? The image contains orientation data (exif I beleive) that is ignored by the browser [see here](https://bugs.chromium.org/p/chromium/issues/detail?id=56845) and [this](https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images) – Mathieu de Lorimier Sep 15 '17 at 16:27
  • The image is actually stored in S3. So when I try and open it in the browser it just downloads as a JPEG. I understand that the exif data is being ignored. I was hoping someone could enlighten me on how to read that data on the client-side so I could rotate the image using CSS. – Jonathan Corrin Sep 17 '17 at 20:27
  • Did you read this one https://stackoverflow.com/q/20600800/643039 – Mathieu de Lorimier Sep 17 '17 at 23:36
  • I did. It looks like a good solution but I cannot figure out how to implement it with my angular 4 app. – Jonathan Corrin Sep 18 '17 at 14:50

4 Answers4

3

In my oppinion, the ideal solution would be to rotate the images prior to sending them to S3 so that it is done only once (not every single time a user views an image).

You could use this librairy prior to uploading to S3 to ensure the images are in the corect orientation.

Look at this sample, it could be integrated easily in your code without much effort.

Regards,

Mathieu de Lorimier
  • 975
  • 3
  • 19
  • 32
  • How do I implement this with ng2-file-upload? Im not sure how to catch the file before the upload in my server. If you look at my image-upload code, that is all the exposure I get to the body of the request with the image. Ive tried a few console.logs but cant find the image before it reaches multer – Jonathan Corrin Sep 18 '17 at 20:41
  • It could probably be achieved using the fileFilter callback in multer. At that point, the file isn't uploaded to S3 and you have a reference to it. See the documentation [here](https://github.com/expressjs/multer#filefilter) – Mathieu de Lorimier Sep 19 '17 at 12:39
3

Fix your image orientation issue by reading Exif data in angular.

Install exif-js

npm i exif-js --save

Import EXIF to your component

import * as EXIF from 'exif-js';

setImgOrientation methos has file param which is uploaded file object, inputBase64String will base64 string of that same file

setImgOrientation(file, inputBase64String) {
 return new Promise((resolve, reject) => {
  const that = this;
  EXIF.getData(file, function () {
    if (this && this.exifdata && this.exifdata.Orientation) {
      that.resetOrientation(inputBase64String, this.exifdata.Orientation, function 
     (resetBase64Image) {
        inputBase64String = resetBase64Image;
        resolve(inputBase64String);
      });
    } else {
      resolve(inputBase64String);
    }
    });
   });
  }

  resetOrientation(srcBase64, srcOrientation, callback) {
  const img = new Image();

  img.onload = function () {
  const width = img.width,
    height = img.height,
    canvas = document.createElement('canvas'),
    ctx = canvas.getContext('2d');

  // set proper canvas dimensions before transform & export
  if (4 < srcOrientation && srcOrientation < 9) {
    canvas.width = height;
    canvas.height = width;
  } else {
    canvas.width = width;
    canvas.height = height;
  }

  // transform context before drawing image
  switch (srcOrientation) {
    case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;
    case 3: ctx.transform(-1, 0, 0, -1, width, height); break;
    case 4: ctx.transform(1, 0, 0, -1, 0, height); break;
    case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
    case 6: ctx.transform(0, 1, -1, 0, height, 0); break;
    case 7: ctx.transform(0, -1, -1, 0, height, width); break;
    case 8: ctx.transform(0, -1, 1, 0, 0, width); break;
    default: break;
  }

  // draw image
  ctx.drawImage(img, 0, 0);

  // export base64
  callback(canvas.toDataURL());
};

  img.src = srcBase64;
}
HD..
  • 1,456
  • 3
  • 28
  • 48
1

If it always happens to all iPhone images you can a column to your postgres table called iphone and set it to true when the photo is uploaded from an iPhone and then rotate it using CSS

-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-ms-transform: rotate(90deg);
-o-transform: rotate(90deg);
transform: rotate(90deg);
jmona789
  • 2,711
  • 6
  • 24
  • 57
  • Thanks. I knew this could be a solution and wanted to avoid it but it seems to be the only work! I'll give you the bounty and add my answer of what I did for the people looking for an Angular solution. – Jonathan Corrin Sep 18 '17 at 18:53
  • 1
    So I have to take back my check for the answer on this one. One thing I did not think about, if the photo is taken on the iPhone and it is viewed on the iPhone, the image will be distorted because IOS for some reason reads the EXIF data, even in Chrome. – Jonathan Corrin Sep 18 '17 at 20:30
  • incomplete answer. both portrait and landscape iphone pics are stored as wide/landscape, and portrait pictures have exif data for 90° orientation. so you would want to only apply the css to portrait pictures. – Todd Hale May 19 '20 at 18:34
0

After messing with a couple of JS libraries and JS-based fixes. I needed a fix for image-orientation in Angular 4 specifically. This is a workaround that I implemented that may help Angular developers searching for an answer.

Basically, I added inputs during the upload process that asks the user if the device is an iPhone or iPhone landscape photo. With the iPhone and iPhoneLandscape booleans in my model, I set these variables to true if the user indicated that the photo was taken with either parameter.

On the client-side, I retrieved the item photo and used ngStyle to rotate the photo 90 degrees if item.iPhone was true and 180 degrees if item.iPhoneLandscape was true.

// For postgres users.... my item model item.mode.js

module.exports = function(sequelize, DataTypes) {
  var Item = sequelize.define("Item", {
    photos: {
      type: DataTypes.ARRAY(DataTypes.STRING),
      allowNull: false
    },
    name: {
      type: DataTypes.STRING,
      isLowerCase: true,
      allowNull: true,
      defaultValue: null
    },
    description: {
      type: DataTypes.STRING,
      isLowerCase: true,
      allowNull: true,
      defaultValue: null
    },
    condition: {
      type: DataTypes.STRING,
      allowNull: true,
      defaultValue: null
    },
    price: {
      type: DataTypes.INTEGER,
      allowNull: true,
      defaultValue: null
    },
    interval: {
      type: DataTypes.INTEGER,
      allowNull: true,
      defaultValue: 1
    },
    category: {
      type: DataTypes.STRING,
      allowNull: true,
      defaultValue: null
    },
    negotiable: {
      type: DataTypes.BOOLEAN,
      allowNull: true,
      defaultValue: true
    },
    shipping: {
      type: DataTypes.BOOLEAN,
      allowNull: true,
      defaultValue: false
    },
    freeShipping: DataTypes.BOOLEAN,
    length: DataTypes.INTEGER,
    width: DataTypes.INTEGER,
    height: DataTypes.INTEGER,
    weight: DataTypes.INTEGER,
    unavailableDates: {
      type: DataTypes.ARRAY(DataTypes.RANGE(DataTypes.DATE)),
      allowNull: true,
      defaultValue: []
    },
    available: {
      type: DataTypes.BOOLEAN,
      allowNull: true,
      defaultValue: true
    },
    securityDeposit: {
      type: DataTypes.INTEGER,
      defaultValue: 0
    },
    iPhone: {
      type: DataTypes.BOOLEAN,
      defaultValue: false
    },
    iPhoneLandscape: {
      type: DataTypes.BOOLEAN,
      defaultValue: false
    }
  });

// my model on angular item.model.ts

export class Item {
  constructor(
    public id?: number,
    public photos?: string[],
    public name?: string,
    public description?: string,
    public condition?: string,
    public price?: number,
    public interval?: number,
    public category?: string,
    public negotiable?: boolean,
    public shipping?: boolean,
    public length?: number,
    public width?: number,
    public height?: number,
    public weight?: number,
    public unavailableDates?: Date[],
    public available?: boolean,
    public iPhone?: boolean,
    public iPhoneLandscape?: boolean,
    public ownerId?: number,
    public owner?: object
  ) {}
}

// Calling the item from the server in my item.service.ts

  onReturnItems() {
    return this.http.get(this.devUrl)
      .map(data => {
        let items: Item[] = data['obj'];
        return items;
      })
      .shareReplay();
  }

// My style function calling the CSS object in item-list.component.ts

  styleObject(s: string, item): Object {
     if (s === 'photo') {
      if (item.iPhone) {
        return {'-webkit-transform': 'rotate(90deg)',
          '-moz-transform': 'rotate(90deg)',
          '-ms-transform': 'rotate(90deg)',
          '-o-transform': 'rotate(90deg)',
          'transform': 'rotate(90deg)'}
      } else if (item.iPhoneLandscape) {
        return {'-webkit-transform': 'rotate(180deg)',
          '-moz-transform': 'rotate(180deg)',
          '-ms-transform': 'rotate(180deg)',
          '-o-transform': 'rotate(180deg)',
          'transform': 'rotate(180deg)'}
      }
    }

// And finally using ngStyle and ngFor in my template in item-list.component.html

<div class="card column is-one-quarter" style="padding: 0; margin-left: 12px; margin-right: 12px;" *ngFor="let item of items">
          <div class="card-image"  (click)="onSetItemId(item.id)">
            <!--<iframe [src]="item.photos[0]"></iframe>-->
            <figure class="image" style="border-radius: 0;">
              <img [src]="item.photos[0]" alt="Image" [ngStyle]="styleObject('photo', item)"  *ngIf="itemsRetrieved" style="border-radius: 0;">
            </figure>
          </div>

Hopefully you can figure it out from this! Good luck!

Jonathan Corrin
  • 1,219
  • 4
  • 17
  • 42