Nagłe pogorszenie jakości uploadowanych obrazów.

0

Przez jakiś czas używałem takiej funkcji do zmieniania wielkości obrazu do max 1MB i wysyłania go do express a następnie zapisywania w s3:

export const postImage = async (image_id, image) => {
  const axiosConfig = {
    headers: {
      'content-type': 'application/x-www-form-urlencoded'
    },
    responseType: 'arraybuffer'
  }

  // Create a new Image object
  const img = new Image();

  // Set the source of the Image object to the base64 string
  img.src = image;

  // Wait for the image to load
  img.onload = () => {
    // Create a new HTML5 Canvas object
    const canvas = document.createElement('canvas');

    // Calculate the new dimensions of the image
    let newWidth = img.width;
    let newHeight = img.height;
    const maxFileSize = 1 * 1024 * 1024; // 1mb in bytes
    const scaleFactor = Math.min(1, Math.sqrt(maxFileSize / (img.width * img.height * 4)));
    newWidth = Math.floor(scaleFactor * img.width);
    newHeight = Math.floor(scaleFactor * img.height);

    // Set the dimensions of the canvas
    canvas.width = newWidth;
    canvas.height = newHeight;

    // Draw the image onto the canvas at the new dimensions
    const ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0, newWidth, newHeight);

    // Convert the canvas to a new base64 string
    const resizedBase64 = canvas.toDataURL("image/jpg");
  
    // Use the resized base64 string as needed
    console.log(image)
    console.log(resizedBase64);

    return beStructionApi.post(`/image/${image_id}`, { data: resizedBase64 }, axiosConfig).then((result) => {
      console.log(result)
      return result 
    })
  };
}

Przez kilka tygodni wszystko działałao. Wczoraj zauważyłem, że obrazy które wysyłam mają znacznie gorszą jakość niż wcześniej. Dla porównania wysłałem tą samą grafikę, którą wysyłałem miesiąc temu. I następnie załadowałem obie z s3. Różnica jest ogromna. Ten 2 obraz wygląda jakby ważył kilka kb co najwyżej. W ogóle nie zmieniałem kodu, zaktualizowałem biblioteki. Czy ktoś wie co może być przyczyną?

0

Zrobiłem kilka testów i nie ma utraty jakości podczas przesyłania client -> express -> s3 oraz s3-> express -> client. Jakość grafiki znacznie pogarsza się po zmniejszeniu rozmiaru. Nawet jeśli zmniejszam obraz, który ma tylko nieco więcej niż 1MB, po odczytaniu z resizedBase64 jest całkowicie nieczytelny.

Poniżej ten sam plik pomniejszony przez ten sam kod przed 3 tygodniami i dzisiaj.

Screenshot_2023-07-26-14-36-10-621-edit_com.android.chrome.jpgScreenshot_2023-07-26-14-36-51-607-edit_com.android.chrome.jpg

3

A skąd pomysł że rozmiar obrazka to img.width * img.height * 4. To by była prawda dla 32 bitowego BMP a nie w JPG który jest kilkadziesiąt razy mniejszy.
Twój obrazek przykładowy ma 978 x 1637 pikseli i zajmuje 530368 bajtów. Według Twoich obliczeń musisz go zmniejszyć o 1024 * 1024 / 6403944 czyli 6 krotnie. Nic dziwnego że po zmniejszeniu rozmiaru obrazka 6 krotnie do rozmiarów 160 x 268 jest on nieczytelny.
Masz po prostu obliczenia z d**y które być może zadziałały przypadkiem dobrze dla jakiegoś obrazka ale dla JPG nie da się tak łatwo wyliczyć rozmiaru wyjściowego.
Poza tym lepiej manewrować współczynnikiem jakości zamiast rozmiarem:

canvas.toDataURL("image/jpg", 0.5); // jakość 0.5 = 50%

Możesz wyszukiwaniem binarnym sprawdzić jaka wartość pomiędzy 0 a 1 da rozmiar <1MB

0

Wielkie dzieki. Ostatecznie kod wygląda tak:

export const postImage = async (image_id, image) => {
  const axiosConfig = {
    headers: {
      'content-type': 'application/x-www-form-urlencoded'
    },
    responseType: 'arraybuffer'
  };

  // Create a new Image object
  const img = new Image();

  // Set the source of the Image object to the base64 string
  img.src = image;

  // Wait for the image to load
  img.onload = () => {
    // Calculate the original size of the image in bytes
    const originalSizeInBytes = Math.ceil(image.length * 0.75);

    // Calculate the target file size (1MB) in bytes
    const targetFileSize = 1 * 1024 * 1024;

    // Calculate the quality factor for canvas.toDataURL()
    let quality = 1;
    if (originalSizeInBytes > targetFileSize) {
      quality = targetFileSize / originalSizeInBytes;
    }

    // Create a new HTML5 Canvas object
    const canvas = document.createElement('canvas');

    // Set the dimensions of the canvas to the original image dimensions
    canvas.width = img.width;
    canvas.height = img.height;

    // Draw the image onto the canvas
    const ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0);

    // Convert the canvas to a new base64 string with JPEG format and adjustable quality
    const resizedBase64 = canvas.toDataURL('image/jpeg', quality);

    // Use the resized base64 string as needed
    console.log(image);
    console.log(resizedBase64);

    // Send the resized image to the server using the Axios request
    return beStructionApi.post(`/image/${image_id}`, { data: resizedBase64 }, axiosConfig)
      .then((result) => {
        console.log(result);
        return result;
      });
  };
};


i z tego co widzę działa. Jeszcze tylko zostaje zamienic base64 na blob.

1 użytkowników online, w tym zalogowanych: 0, gości: 1