advanced-cropper / vue-advanced-cropper

The advanced vue cropper library that gives you opportunity to create your own croppers suited for any website design
https://advanced-cropper.github.io/vue-advanced-cropper/
Other
1k stars 136 forks source link

Issue with Cropping HEIC Files on Apple Devices #279

Open arachimi opened 5 months ago

arachimi commented 5 months ago

Dear Developer,

I encountered a problem with cropping HEIC files from the Apple Gallery. When I select a file from the Gallery and attempt to crop it, I receive the following error from the Safari log:

"Canvas area exceeds the maximum limit (width * height > 16777216)."

This might be causing the crop function to fail.

I have attached a sample file with the issue in this Google Drive link:

https://drive.google.com/file/d/1WOKcNmwsCHKGt95KKLBYe7W-eInZu0Nd/view?usp=drive_link

You can also test it on this website:

https://codesandbox.io/embed/vue-advanced-cropper-composition-api-5z0ww0?codemirror=1

I tried it on Safari iOS and found that after cropping, the resulting image is black.

Thanks as always

Norserium commented 5 months ago

@arachimi, you can use canvas-size library to detect the maximum width, height and area and pass this values to canvas prop.

<cropper
    :src="image"
    :canvas="{
        maxHeight: canvasSize.maxHeight,
        maxWidth: canvasSize.maxHeight,
        maxArea: canvasSize.maxArea,
    }"
/>

Perhaps, it should be a part of the cropper library, but I'm not sure because it will increase the library size the cropper's and users usually set the canvas size limitations anyway.

arachimi commented 5 months ago

Thank you for your suggestion. I think the problem doesn't come from the cropper itself but rather from the source images I choose from the Gallery.

I tried setting the cropper size smaller, but I still encounter the same issue.

What do you think?

Norserium commented 5 months ago

I tried setting the cropper size smaller, but I still encounter the same issue.

@arachimi, could you provide the source code and error text?

arachimi commented 5 months ago

@Norserium Thank you. I will provide source code below.

Template :

<template>
  <div class="cropper-wrapper">
    <div class="image-wrapper">
      <div :style="{ backgroundImage: 'url(' + img + ')' }" class="cropper" />

      <Cropper
        classname="upload-example-cropper"
        imageClassname="imgName"
        :src="img"
        ref="cropper"
        @change="change"
        :stencil-props="{
          handlers: {
            eastNorth: true,
            north: false,
            westNorth: true,
            west: false,
            westSouth: true,
            south: false,
            eastSouth: true,
            east: false,
          },
          movable: true,
          scalable: true,
          minAspectRatio,
          maxAspectRatio,
          autoZoom: true,
        }"
        :maxWidth = "maxWidth"
        :maxHeight = "maxHeight"
        :minWidth = "minWidth"
        :minHeight = "minHeight"
        :setCoordinates = "setCoordinates"
      />
    </div>
    <div class="button-wrapper">
      <f7-button class="rotate-button" @click="rotateLeft" style="margin-left: auto">
        <img src="static/svg/RotateLeft.svg" alt="Rotate Left Icon" />
      </f7-button>
      <f7-button class="rotate-button" @click="rotateRight" style="margin-right: auto">
        <img src="static/svg/RotateRight.svg" alt="Rotate Right Icon" />
      </f7-button>
    </div>
  </div>
</template>

Vue:

export default {
  props: {
    img: String,
    mode: String,
    f7router: Object,
  },
  components: {
    Cropper,
  },
  data() {
    return {
      maxAspectRatio: 3 / 2,
      minAspectRatio: 2 / 3,
      coordinates: {
        width: 0,
        height: 0,
        left: 0,
        top: 0,
      },
      rotation: 0,
      croppedImage: '',
      maxWidth: 4096,
      maxHeight: 4096,
      minWidth: 128,
      minHeight: 128,
      orginal: {
        w: 0,
        h: 0
      }

    }
  },
  mounted() {
      this.$refs.cropper.setCoordinates(this.setCoordinates)
      console.log("check", this.img.length)
  },
  emits: ["changeEvent"],
  methods: {
    setCoordinates({ coordinates, imageSize }) {
      this.orginal.w = imageSize.width
      this.orginal.h = imageSize.height

      console.log("mounted",imageSize.width,imageSize.height, coordinates)

      var w = Math.min(this.maxWidth,imageSize.width)
      var h = Math.min(this.maxHeight,imageSize.height)

      if (w > h * this.maxAspectRatio) {
        w = h * this.maxAspectRatio
      } else if (w < h * this.minAspectRatio) {
        h = w / this.minAspectRatio
      }
      return {
        width: w,
        height: h,
        left: imageSize.width / 2 - w / 2,
        top: imageSize.height / 2 - h / 2,
      }
    },
    async rotateRight() {
      this.$refs.cropper.rotate(90)
    },
    async rotateLeft() {
      this.$refs.cropper.rotate(-90)
    },
    back() {
      this.f7router.back()
      this.f7router.app.tab.show('#view-fifth', false)
      f7.preloader.hide()
    },
    change({ coordinates, canvas }) {
      console.log("coordinates", coordinates, canvas);
      const max = 2048
      var resizeCanvas = document.createElement("canvas");
      var w = canvas.width;
      var h = canvas.height;
      var wth = w > h;
      if (wth && w > max) {
        h = h/w * max
        w = max;
      }
      else if (h > max) {
        w = w/h * max
        h = max;
      }
      console.log("resize", w, h);
      resizeCanvas.width = w;
      resizeCanvas.height = h;
      var resizedContext = resizeCanvas.getContext("2d");
      resizedContext.drawImage(canvas, 0, 0, w, h);
      const croppedImage = resizeCanvas.toDataURL('image/jpeg', 0.9)
      this.$emit('changeEvent', croppedImage)
      f7.preloader.hide()
    },
  },
  computed: {

  },
}

Log from console. ![Uploading Screenshot 2567-06-17 at 12.03.50.png…]()

After crop process it's show black image. Could you please point out where I went wrong or what mistakes I made?

Norserium commented 5 months ago

@arachimi, you are doing it incorrectly. The cropper already has a functional to resize a canvas.

<template>
  <div class="cropper-wrapper">
    <div class="image-wrapper">
      <div :style="{ backgroundImage: 'url(' + img + ')' }" class="cropper" />

      <Cropper
        classname="upload-example-cropper"
        imageClassname="imgName"
        :src="img"
        ref="cropper"
        @change="change"
        :stencil-props="{
          handlers: {
            eastNorth: true,
            north: false,
            westNorth: true,
            west: false,
            westSouth: true,
            south: false,
            eastSouth: true,
            east: false,
          },
          movable: true,
          scalable: true,
          minAspectRatio,
          maxAspectRatio,
          autoZoom: true,
        }"
        :canvas="{
            maxWidth: 4096,
            maxHeight: 4096,
            maxArea: 4096 * 4096,
        }"
      />
    </div>
    <div class="button-wrapper">
      <f7-button class="rotate-button" @click="rotateLeft" style="margin-left: auto">
        <img src="static/svg/RotateLeft.svg" alt="Rotate Left Icon" />
      </f7-button>
      <f7-button class="rotate-button" @click="rotateRight" style="margin-right: auto">
        <img src="static/svg/RotateRight.svg" alt="Rotate Right Icon" />
      </f7-button>
    </div>
  </div>
</template>
arachimi commented 5 months ago

@Norserium I have change code to basic version.

<template>
  <div>
    <input type="file" @change="onFileChange" accept="image/*" />
    <div v-if="imageSrc">
      <cropper
        :src="imageSrc"
        :stencil-props="{ aspectRatio: 1 }"
        @change="onCrop"
      />
    </div>
    <div v-if="croppedImage">
      <h3>Cropped Image</h3>
      <img :src="croppedImage" alt="Cropped Image" style="width: 100%;" />
    </div>
  </div>
</template>

<script>
import { Cropper } from 'vue-advanced-cropper';
import 'vue-advanced-cropper/dist/style.css';

export default {
  components: {
    Cropper,
  },
  data() {
    return {
      imageSrc: null,
      croppedImage: null,
    };
  },
  methods: {
    onFileChange(event) {
      const file = event.target.files[0];
      console.log(file);
      if (file) {
        const reader = new FileReader();
        reader.onload = (e) => {
          this.imageSrc = e.target.result;
        };
        reader.readAsDataURL(file);
      }
    },
    onCrop({ coordinates, canvas }) {
      if (canvas) {
        console.log(coordinates);
        this.croppedImage = canvas.toDataURL('image/jpeg');
      }
    },
  },
};
</script>

I have made a video example for you to see as well

At first, when I select a normal image, you can see that the app can select and crop the image normally. But my problem is that there are some images that it cannot crop. I have sent a sample image file for you to check in the first comment, which is this file:

https://drive.google.com/file/d/1WOKcNmwsCHKGt95KKLBYe7W-eInZu0Nd/view

The mentioned image is an adapter charging cable image. I'm not sure why when this file is selected, it cannot be cropped. How can I fix it?

I have attached a video example showing my usage steps for you to watch and check.

https://drive.google.com/file/d/1qeOLXSWq2mQo0FEL8Gam1KZdMrzqcD3i/view?usp=drive_link

And finally, I have attached the log from the Safari Console.

Screenshot 2567-06-17 at 21 27 44

Note: I am using Mobile Safari for testing. Thank you in advance ❤️.

Norserium commented 5 months ago

@Norserium I have change code to basic version.

@arachimi, you should pass canvas prop with options like I've written above. Try it.

arachimi commented 5 months ago

@Norserium Okay. Do you mean this prop?

:canvas="{
            maxWidth: 4096,
            maxHeight: 4096,
            maxArea: 4096 * 4096,
        }"

Here is the result:

Screenshot 2567-06-18 at 12 55 23

Thank you.