Asked  7 Months ago    Answers:  5   Viewed   42 times

The following PHP code snippet uses GD to resize a browser-uploaded PNG to 128x128. It works great, except that the transparent areas in the original image are being replaced with a solid color- black in my case.

Even though imagesavealpha is set, something isn't quite right.

What's the best way to preserve the transparency in the resampled image?

$uploadTempFile = $myField[ 'tmp_name' ]
list( $uploadWidth, $uploadHeight, $uploadType ) 
  = getimagesize( $uploadTempFile );

$srcImage = imagecreatefrompng( $uploadTempFile );    
imagesavealpha( $targetImage, true );

$targetImage = imagecreatetruecolor( 128, 128 );
imagecopyresampled( $targetImage, $srcImage, 
                    0, 0, 
                    0, 0, 
                    128, 128, 
                    $uploadWidth, $uploadHeight );

imagepng(  $targetImage, 'out.png', 9 );

 Answers

31
imagealphablending( $targetImage, false );
imagesavealpha( $targetImage, true );

did it for me. Thanks ceejayoz.

note, the target image needs the alpha settings, not the source image.

Edit: full replacement code. See also answers below and their comments. This is not guaranteed to be be perfect in any way, but did achieve my needs at the time.

$uploadTempFile = $myField[ 'tmp_name' ]
list( $uploadWidth, $uploadHeight, $uploadType ) 
  = getimagesize( $uploadTempFile );

$srcImage = imagecreatefrompng( $uploadTempFile ); 

$targetImage = imagecreatetruecolor( 128, 128 );   
imagealphablending( $targetImage, false );
imagesavealpha( $targetImage, true );

imagecopyresampled( $targetImage, $srcImage, 
                    0, 0, 
                    0, 0, 
                    128, 128, 
                    $uploadWidth, $uploadHeight );

imagepng(  $targetImage, 'out.png', 9 );
Wednesday, March 31, 2021
 
Oshrib
answered 7 Months ago
14

It probably depends on your PNG. A PNG file can contain a background color, which can be used when transparency doesn't work. Your PNG probably has a white background. When you set imageaplhablending to true it picks up the background color from your PNG and uses that when writing the JPEG. When you set it to false it picks the default for GD which is black.

You can try it for yourself. Create a transparent PNG and save it with an orange or pink background color. Your second example should show that color.

By the way, the PNG background color trick is a nice one for IE6 images. IE6 does not support transparent PNGs so it will display them with whatever background color you saved them with. When saving transparent PNGs, save them with the same background color as your website. It will look better than white or black boxes around your PNG images in IE6.

Wednesday, March 31, 2021
 
Juicy
answered 7 Months ago
94

Set imagealphablending($image,true); on each new layer.

Try this:

<?php
$image = imagecreatetruecolor(485, 500);
imagealphablending($image, false);
$col=imagecolorallocatealpha($image,255,255,255,127);
imagefilledrectangle($image,0,0,485, 500,$col);
imagealphablending($image,true);

/* add door glass */
$img_doorGlass = imagecreatefrompng("glass/$doorStyle/$doorGlass.png");
imagecopyresampled($image, $img_doorGlass, 106, 15, 0, 0, 185, 450, 185, 450);
imagealphablending($image,true);

/* add door */
$img_doorStyle = imagecreatefrompng("door/$doorStyle/$doorStyle"."_"."$doorColor.png");
imagecopyresampled($image, $img_doorStyle, 106, 15, 0, 0, 185, 450, 185, 450);
imagealphablending($image,true);

$fn = md5(microtime()."door_builder").".png";

imagealphablending($image,false);
imagesavealpha($image,true);
if(imagepng($image, "user_doors/$fn", 1)){
    echo "user_doors/$fn";
}
imagedestroy($image);

?>
Thursday, June 10, 2021
 
Exoon
answered 5 Months ago
59

I have worked on this some more, and believe I can achieve what you want... basically, I allow ImageMagick to do the repaging and joining of the images exactly as you had it, but then I get ImageMagick to output a NetPBM file in Portable Any Map PNM format. I then encode the PNM format file into a PNG using a Perl encoder I wrote to match your very specific needs as regards the palette. So, for every truecolour 24-bit RGB pixel I read in from the PNM file, I compute which palette entry it is nearest to by doing the sum of the square errors, and then outputting a single palette index.

NetPBM is described here.

The PNM format is really simple to parse and that's why I chose it. It is described here.

So, your original command would be almost identical, except a PNM file is output on stdout and read into the Perl script pnmtopng which then makes the PNG file you wanted:

convert -page 312x144+0+0 scacchiera.png -page +168+0 
   level-000.png -background black -layers flatten pnm:- |  ./pnmtopng > out.png

The Perl script is here:

#!/usr/bin/perl
use strict;
use warnings;
use Digest::CRC qw(crc32);
use IO::Compress::Deflate qw(deflate $DeflateError) ;

# Our beloved fixed palette
my @palette=(
      [0,0,0],
      [255,255,255],
      [104,55,43],
      [112,164,178],
      [111,61,134],
      [88,141,67],
      [53,40,121],
      [184,199,111],
      [111,79,37],
      [67,57,0],
      [154,103,89],
      [68,68,68],
      [108,108,108],
      [154,210,132],
      [108,94,181],
      [149,149,149]
   );

################################################################################
# Take chunk of PNG data as parameter, calculate its length & CRC, and output it
################################################################################
sub PNGoutputChunk()
{
   my $len=length($_[0])-4;
   my $crc = Digest::CRC->new(type=>"crc32");
   $crc->add($_[0]);
   print pack('N',$len),$_[0],pack('N',$crc->digest);
}

################################################################################
# Main
################################################################################

   # Read P6 PNM file from STDIN
   my $line = <STDIN>;
   chomp($line);
   if ($line ne "P6"){die "Expected P6 format PNM file"}

   # Read width and height from STDIN
   $line = <STDIN>;
   my ($width,$height) = ($line =~ /(d+)s+(d+)/); 
   print STDERR "DEBUG: width=$width, height=$heightn";

   # Read MAX PNM value and ignore
   $line = <STDIN>;

   # Read entire remainder of PNM file
   my $expectedsize=$width * $height * 3;
   my $PNMdata;
   my $bytesRead = read(STDIN,$PNMdata,$expectedsize);
   if($bytesRead != $expectedsize){die "Unable to read PNM data"}

   # Output PNG header chunk
   printf "x89PNGx0dx0ax1ax0a";

   my $bitdepth=8;
   my $colortype=3;
   my $compressiontype=0;
   my $filtertype=0;
   my $interlacetype=0;

   # Output PNG IHDR chunk
   my $IHDR='IHDR';
   $IHDR .= pack 'N',$width;
   $IHDR .= pack 'N',$height;
   $IHDR .= pack 'c',$bitdepth;
   $IHDR .= pack 'c',$colortype;
   $IHDR .= pack 'c',$compressiontype;
   $IHDR .= pack 'c',$filtertype;
   $IHDR .= pack 'c',$interlacetype;
   &PNGoutputChunk($IHDR);

   # Output PNG PLTE (palette)
   my $PLTE='PLTE';
   for(my $i=0;$i<scalar @palette;$i++){
      $PLTE .= sprintf('%c',$palette[$i][0]); # Red
      $PLTE .= sprintf('%c',$palette[$i][1]); # Green
      $PLTE .= sprintf('%c',$palette[$i][2]); # Blue
   }
   &PNGoutputChunk($PLTE);

   # Output PNG IDAT chunk
   # RFC-1950 zlib compression
   my $raw;
   # Go through PNM data, and for each RGB pixel, find nearest palette entry
   my @PNMvalues = unpack("C*",$PNMdata);
   print STDERR "Unpacked ",scalar @PNMvalues," from rawn";
   for(my $pixel=0;$pixel<(scalar @PNMvalues)/3;$pixel++){

      # Output filter type byte (0) at start of each scanline
      if($pixel%$width==0){$raw .= "x00";}

      my $r=$PNMvalues[(3*$pixel)];   # Red PNM value
      my $g=$PNMvalues[(3*$pixel)+1]; # Green PNM value
      my $b=$PNMvalues[(3*$pixel)+2]; # Blue PNM value
      my $nearest=0;
      my $distmin=(255*255)+(255*255)+(255*255); # Couldn't get further
      # Go through all palette entries to find nearest to this RGB
      for(my $pe=0;$pe<scalar @palette;$pe++){
         my $pr=$palette[$pe][0];   # Red palette value
         my $pg=$palette[$pe][1]; # Green palette value
         my $pb=$palette[$pe][2]; # Blue palette value
         my $dist = ($pr-$r)*($pr-$r) + ($pg-$g)*($pg-$g) + ($pb-$b)*($pb-$b);
         if($dist<$distmin){
            $distmin=$dist;
            $nearest=$pe;
         }
      }
      $raw .= sprintf "%c",$nearest;
      print STDERR "Pixel: $pixel, r=$r, g=$g, b=$b. Chose palette entry $nearestn";
   }
   print STDERR "Length of raw: ",length($raw);

   my $deflated;
   my $status = deflate $raw => $deflated
        or die "deflate failed: $DeflateErrorn";

   my $IDAT="IDAT" . $deflated;
   &PNGoutputChunk($IDAT);

   # Output PNG IEND chunk
   &PNGoutputChunk('IEND');

The result is this:

enter image description here

Friday, August 13, 2021
 
csi
answered 3 Months ago
csi
94

Basically, when you call super.paintComponent, it will call the UI delgate's update method. This is where the magic happens.

Below is the Nimbus's SynthTextAreaUI implementation

public void update(Graphics g, JComponent c) {
    SynthContext context = getContext(c);

    SynthLookAndFeel.update(context, g);
    context.getPainter().paintTextAreaBackground(context,
                      g, 0, 0, c.getWidth(), c.getHeight());
    paint(context, g);
    context.dispose();
}

As you can see, it actually paints the background, with out regard for the opaque state of the component, then calls paint, which will call the BasicTextUI.paint method (via super.paint)

This is important, as BasicTextUI.paint actually paints the text.

So, how does that help us? Normally, I'd crucify someone for not calling super.paintComponent, but this is exactly what we're going to do, but we're going to do it knowing in advance what responsibility we're taking on.

First, we're going to take over the responsibilities of update, fill the background, paint our background and then call paint on the UI delegate.

enter image description here

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class NimbusTest {

    public static void main(String[] args) {
        new NimbusTest();
    }

    public NimbusTest() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(new TestTextArea()));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestTextArea extends JTextArea {

        private BufferedImage bg;

        public TestTextArea() {
            try {
                bg = ImageIO.read(new File("C:\Users\swhitehead\Documents\My Dropbox\Ponies\Rainbow_Dash_flying_past_3_S2E16.png"));
            } catch (IOException ex) {
                Logger.getLogger(NimbusTest.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2d = (Graphics2D) g.create();
            // Fill the background, this is VERY important
            // fail to do this and you will have major problems
            g2d.setColor(getBackground());
            g2d.fillRect(0, 0, getWidth(), getHeight());
            // Draw the background
            g2d.drawImage(bg, 0, 0, this);
            // Paint the component content, ie the text
            getUI().paint(g2d, this);
            g2d.dispose();
        }

    }
}

Make no mistake. If you don't do this right, it will screw not only this component but probably most of the other components on your screen.

Friday, August 27, 2021
 
sassy_geekette
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share