premultiplied Usage: When compress png with premulitiplied, then output png shows bad. · Issue #4413 · lovell/sharp · GitHub
Skip to content

premultiplied Usage: When compress png with premulitiplied, then output png shows bad. #4413

Open

Description

@tangkaikk

Possible bug

// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0

'use strict';

const sharp = require('sharp');
sharp('PariteShip.png',{ premultiplied: true })
  .png({ quality: 80},{ premultiplied: true })
  .toFile('compressed.png',{ premultiplied: true })
  

What is the expected behaviour?

Image

If I delete the compress code, then output png shows good.
below is the correct result
Image

Activity

  1. lovell commented on Jun 12, 2025

    @lovell
    Owner

    Is this LLM-generated code? None of these functions accept premultiplied as an option.

    When you provide a quality option to png() you are selecting to use lossy, quantised, palette-based output.

    https://sharp.pixelplumbing.com/api-output/#png

  2. tangkaikk commented on Jun 12, 2025

    @tangkaikk
    Author

    Is this LLM-generated code? None of these functions accept premultiplied as an option.

    When you provide a quality option to png() you are selecting to use lossy, quantised, palette-based output.

    https://sharp.pixelplumbing.com/api-output/#png

    I wrote it , just for test the premultiplied usage. Is that possible to compress the png to support premultiplied ? Instead I use the tinypng to compress the png, the output png shows good.

  3. lovell commented on Jun 12, 2025

    @lovell
    Owner

    I think the best thing for you to do is create a complete, minimal, standalone repo with code and image(s) that allows someone else to reproduce and therefore understand what you're looking for.

  4. lovell commented on Jul 7, 2025

    @lovell
    Owner

    @tangkaikk Were you able to make any progress with this? If you still require help, please can you provide the requested info.

  5. tangkaikk commented on Jul 16, 2025

    @tangkaikk
    Author

    pic-compressShowBad.zip
    Here is the minimal repo for you. There are two pics under folder pic-compressShowBad\assets\main\native\4a, the one named "4a28a44c-8feb-4ca5-9913-9dd8d2b023c7" shows bad which compressed by sharp, the other one "PariteShip-goodPic" shows good which is the original picture.

    The compress code is below:

    const sharp = require('sharp');
    sharp('PariteShip.png')
      .png({ quality: 80})
      .toFile('compressed.png')
    
  6. lovell commented on Jul 16, 2025

    @lovell
    Owner

    Thanks, as I mentioned above, when you provide a quality option to png() you are selecting to use lossy, quantised, palette-based output.

    https://sharp.pixelplumbing.com/api-output/#png

    The sample image provided is already a palette-based image that has the maximum 256 palette entries (768 bytes is 256 RGB entries):

    $ pngchunks PariteShip-goodPic.png | grep PLTE
    Chunk: Data Length 768 (max 2147483647), Type 1163152464 [PLTE]
    

    By selecting a quality of 80 you are reducing the palette size (474 bytes is 158 RGB entries):

    $ pngchunks 4a28a44c-8feb-4ca5-9913-9dd8d2b023c7.png | grep PLTE
    Chunk: Data Length 474 (max 2147483647), Type 1163152464 [PLTE]
    

    I would suggest you use palette: true and do not provide a quality:

    -   .png({ quality: 80 })
    +   .png({ palette: true })
  7. tangkaikk commented on Jul 16, 2025

    @tangkaikk
    Author
    Image

    Use the suggestion, the problem still exist.

  8. lovell commented on Jul 16, 2025

    @lovell
    Owner

    This looks like an artefact of the quantisation process used to reduce the number of colours in an image, hence my original comment about palette size.

    You might have some luck removing the use of dithering by setting dither to zero.

    .png({ palette: true, dither: 0 })

    If this doesn't produce suitable output, I'd probably avoid palette-based PNG entirely given quality appears to be of your greatest concern.

  9. tangkaikk commented on Jul 17, 2025

    @tangkaikk
    Author

    The output still the same. The original picture has been premultiplied Alpha, how can I keep it? Of course, when I remove premultiplied Alpha, the output turns right. My greatest concern is use the sharp to shrink the picture size meanwhile it can still keep the premultiplied Alpha result.

  10. tangkaikk commented on Jul 17, 2025

    @tangkaikk
    Author

    I set the premultiplied option true, but output info it turns false.

    Image Image
  11. lovell commented on Jul 17, 2025

    @lovell
    Owner

    The premultiplied input property you've highlighted is for raw pixel based input, which you do not appear to be using.

    The premultiplied output property you've highlighted tells you whether any operations in the pipeline resulted in sharp applying a premultiply/unpremultiply roundtrip whilst processing, as documented by toFile:

    info contains the output image format, size (bytes), width, height, channels and premultiplied (indicating if premultiplication was used).

    As I mentioned above, I think the best thing for you to do is create a complete, minimal, standalone repo with code and image(s) that allows someone else to reproduce and therefore understand what you're looking for.

  12. tangkaikk commented on Jul 18, 2025

    @tangkaikk
    Author

    As I mention before, the original pic is premultiplied one. I use raw.premultiplied option to avoid sharp premultiplying the image, but the output file which I copy to the folder pic-compressShowBad\assets\main\native\4a and renamed is as "4a28a44c-8feb-4ca5-9913-9dd8d2b023c7" shows bad.

    Here is the code I use:

    const sharp = require('sharp');
    
    async function checkAndCompressPng(inputPath, outputPath) {
      try {
        // Load image and check metadata
    	let options =  new Object();
    	  options["raw"]= new Object();
    	  options["raw"]["premultiplied"] = true;
    	  options["raw"]["width"] = 2048;
    	  options["raw"]["height"] = 1024;
    	  options["raw"]["channels"] = 4;
        const image = sharp(inputPath, options);
    
        // Compress PNG
        await image
          .png({
            progressive: true,
    		  palette: true,
    		  quality: 60
          })
    	  .keepMetadata()
          .toFile(outputPath,(err,info)=>{
    		  console.log("info",info);
    		  });
      } catch (err) {
        console.error("Error:", err);
      }
    }
    checkAndCompressPng('PariteShip.png', 'output.png');
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions