signalapp / Signal-Desktop

A private messenger for Windows, macOS, and Linux.
https://signal.org/download
GNU Affero General Public License v3.0
14.6k stars 2.66k forks source link

Transparent Png Doesn't Work (Even With Files) #6928

Open sglbl opened 3 months ago

sglbl commented 3 months ago

Using a supported version?

Overall summary

When an user sends transparent png to another person, png automatically converts with a black background. When image is selected as a file, it's the same.

Steps to reproduce

  1. Send a trasnparent image to somebody.
  2. Check the image

Expected result

File shouldn't be changed.

Actual result

File changes.

Screenshots

No response

Signal version

7.14.0

Operating system

Windows 11

Version of Signal on your phone

No response

Link to debug log

No response

ben-biddington commented 2 months ago

I suspect it is due to falling into this jpeg conversion?

I notice that when I download the image from Signal Desktop it has been converted to a jpeg.

For example:

https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png

Whether or not the image is converted to jpeg depends on file size ((not dimensions) ) (1) and quality (2).

This 63kB file does preserve its transparency for example as it exits early at the size <= thresholdSize guard.

https://raw.githubusercontent.com/signalapp/Signal-Desktop/main/fixtures/Animated_PNG_example_bouncing_beach_ball.png

// ts/util/scaleImageToLevel.ts
//...
  const level = highQuality ? MediaQualityLevels.Three : getMediaQualityLevel();
  const {
    maxDimensions,
    quality,
    size: targetSize,
    thresholdSize,
  } = MEDIA_QUALITY_LEVEL_DATA.get(level) || DEFAULT_LEVEL_DATA; // <------------------ (2)
  if (size <= thresholdSize) { // <---------------------------------------------------- (1)
    // Always encode through canvas as a temporary fix for a library bug
    const blob: Blob = await canvasToBlob(data.image, contentType);
    return {
      blob,
      contentType,
    };
  }

  for (let i = 0; i < SCALABLE_DIMENSIONS.length; i += 1) {
    const scalableDimensions = SCALABLE_DIMENSIONS[i];
    if (maxDimensions < scalableDimensions) {
      continue; // <------------------------------------------------------------------- (2)
    }

    // We need these operations to be in serial
    // eslint-disable-next-line no-await-in-loop
    const blob = await getCanvasBlobAsJPEG(
      data.image,
      scalableDimensions,
      quality
    );
    if (blob.size <= targetSize) {
      return {
        blob,
        contentType: IMAGE_JPEG,
      };
    }
  }

  const blob = await getCanvasBlobAsJPEG(data.image, MIN_DIMENSIONS, quality);
  return {
    blob,
    contentType: IMAGE_JPEG,
  };