操纵透明PNG会导致图像中的所有rgb(0,0,0)变为透明

时间:2011-06-13 17:31:59

标签: php image-processing png transparency gd

我有一个PHP图像上传系统,允许用户上传各种图像(JPG,GIF,PNG)。上传的每张图片都有一个图标(20x20),缩略图(150x150)和为其生成的明信片(500x500) - 我称之为“元图像”。如果原始图像尺寸不像元图像那样是方形的,则将其缩放到最佳拟合,并且以适当的尺寸生成透明画布,并将元图像放置在其上。由于这种透明的画布,以及其他目的,所有生成的元图像本身都是PNG(无论原始图像格式如何)。

例如,如果用户上传800px x 600px JPG图像,则文件系统中会出现以下内容:

  1. 原始* .jpg,800 x 600
  2. Meta图标* .png,原始图像的20 x 15副本水平和垂直居中于20 x 20 tansparent canvas
  3. Meta thumbail * .png,原始图像的150 x 112副本水平和垂直居中于150 x 150 tansparent canvas
  4. Meta图标* .png,原始图像的500 x 375副本水平和垂直居中于500 x 500 tansparent canvas
  5. 这适用于JPG和GIF文件 - 所有颜色都处理得正确,尺寸有效等等。

    但是,如果我上传其中有黑色的PNG(rgb(0,0,0),#000000),则生成的3个元图像全部变为透明黑色。其他一切都很好 - 尺寸等等。请记住,透明GIF似乎工作正常,即使内部有黑色。

    有人可以向我解释如何解决这个问题吗?下面是我为这个系统编写的代码(注意在它下面定义了抽象类的3个扩展)

    <?php
    /*
     * TODO: There is no reason for this class to be abstract except that I have not
     *       added any of the setters/getters/etc which would make it useful on its
     *       own.  As is, an instantiation of this class would not accomplish
     *       anything so I have made it abstract to avoid instantiation.  Three
     *       simple and usable extensions of this class exist below it in this file.
     */
    abstract class ObjectImageRenderer
    {
      const WRITE_DIR = SITE_UPLOAD_LOCATION;
    
      protected $iTargetWidth = 0;
      protected $iTargetHeight = 0;
      protected $iSourceWidth = 0;
      protected $iSourceHeight = 0;
      protected $iCalculatedWidth = 0;
      protected $iCalculatedHeight = 0;
    
      protected $sSourceLocation = '';
      protected $sSourceExt = '';
      protected $oSourceImage = null;
    
      protected $sTargetLocation = '';
      protected $sTargetNamePrefix = '';
      protected $oTargetImage = null;
    
      protected $oTransparentCanvas = null;
    
      protected $bNeedsCanvas = false;
      protected $bIsRendered = false;
    
      public function __construct( $sSourceLocation )
      {
        if( ! is_string( $sSourceLocation ) || $sSourceLocation === '' ||
            ! is_file( $sSourceLocation ) || ! is_readable( $sSourceLocation )
          )
        {
          throw new Exception( __CLASS__ . ' must be instantiated with valid path/filename of source image as first param.' );
        }
        $this->sSourceLocation = $sSourceLocation;
        $this->resolveNames();
      }
    
      public static function factory( $sSourceLocation, $size )
      {
        switch( $size )
        {
          case 'icon':
            return new ObjectIconRenderer( $sSourceLocation );
            break;
    
          case 'thumbnail':
            return new ObjectThumbnailRenderer( $sSourceLocation );
            break;
    
          case 'postcard':
            return new ObjectPostcardRenderer( $sSourceLocation );
            break;
        }
      }
    
      public static function batchRender( $Source )
      {
        if( is_string( $Source ) )
        {
          try
          {
            ObjectImageRenderer::factory( $Source, 'icon' )->render();
            ObjectImageRenderer::factory( $Source, 'thumbnail' )->render();
            ObjectImageRenderer::factory( $Source, 'postcard' )->render();
          }
          catch( Exception $exc )
          {
            LogProcessor::submit( 500, $exc->getMessage() );
          }
        }
        else if( is_array( $Source ) && count( $Source ) > 0 )
        {
          foreach( $Source as $sSourceLocation )
          {
            if( is_string( $sSourceLocation ) )
            {
              self::batchRender( $sSourceLocation );
            }
          }
        }
      }
    
      /**
       * loadImageGD - read image from filesystem into GD based image resource
       *
       * @access public
       * @static
       * @param STRING $sImageFilePath
       * @param STRING $sSourceExt OPTIONAL
       * @return RESOURCE
       */
      public static function loadImageGD( $sImageFilePath, $sSourceExt = null )
      {
        $oSourceImage = null;
    
        if( is_string( $sImageFilePath ) && $sImageFilePath !== '' &&
            is_file( $sImageFilePath ) && is_readable( $sImageFilePath )
          )
        {
          if( $sSourceExt === null )
          {
            $aPathInfo = pathinfo( $sImageFilePath );
            $sSourceExt = strtolower( (string) $aPathInfo['extension'] );
          }
    
          switch( $sSourceExt )
          {
            case 'jpg':
            case 'jpeg':
            case 'pjpeg':
              $oSourceImage = imagecreatefromjpeg( $sImageFilePath );
              break;
    
            case 'gif':
              $oSourceImage = imagecreatefromgif( $sImageFilePath );
              break;
    
            case 'png':
            case 'x-png':
              $oSourceImage = imagecreatefrompng( $sImageFilePath );
              break;
    
            default:
              break;
          }
        }
    
        return $oSourceImage;
      }
    
      protected function resolveNames()
      {
        $aPathInfo = pathinfo( $this->sSourceLocation );
        $this->sSourceExt = strtolower( (string) $aPathInfo['extension'] );
        $this->sTargetLocation = self::WRITE_DIR . $this->sTargetNamePrefix . $aPathInfo['basename'] . '.png';
      }
    
      protected function readSourceFileInfo()
      {
        $this->oSourceImage = self::loadImageGD( $this->sSourceLocation, $this->sSourceExt );
    
        if( ! is_resource( $this->oSourceImage ) )
        {
          throw new Exception( __METHOD__ . ': image read failed for ' . $this->sSourceLocation );
        }
    
        $this->iSourceWidth = imagesx( $this->oSourceImage );
        $this->iSourceHeight = imagesy( $this->oSourceImage );
    
        return $this;
      }
    
      protected function calculateNewDimensions()
      {
        if( $this->iSourceWidth === 0 || $this->iSourceHeight === 0 )
        {
          throw new Exception( __METHOD__ . ': source height or width is 0. Has ' . __CLASS__ . '::readSourceFileInfo() been called?' );
        }
    
        if( $this->iSourceWidth > $this->iTargetWidth || $this->iSourceHeight > $this->iTargetHeight )
        {
          $nDimensionRatio = ( $this->iSourceWidth / $this->iSourceHeight );
    
          if( $nDimensionRatio > 1 )
          {
            $this->iCalculatedWidth = $this->iTargetWidth;
            $this->iCalculatedHeight = (int) round( $this->iTargetWidth / $nDimensionRatio );
          }
          else
          {
            $this->iCalculatedWidth =  (int) round( $this->iTargetHeight * $nDimensionRatio );
            $this->iCalculatedHeight = $this->iTargetHeight;
          }
        }
        else
        {
          $this->iCalculatedWidth = $this->iSourceWidth;
          $this->iCalculatedHeight = $this->iSourceHeight;
        }
    
        if( $this->iCalculatedWidth < $this->iTargetWidth || $this->iCalculatedHeight < $this->iTargetHeight )
        {
          $this->bNeedsCanvas = true;
        }
    
        return $this;
      }
    
      protected function createTarget()
      {
        if( $this->iCalculatedWidth === 0 || $this->iCalculatedHeight === 0 )
        {
          throw new Exception( __METHOD__ . ': calculated height or width is 0. Has ' . __CLASS__ . '::calculateNewDimensions() been called?' );
        }
    
        $this->oTargetImage = imagecreatetruecolor( $this->iCalculatedWidth, $this->iCalculatedHeight );
    
        $aTransparentTypes = Array( 'gif', 'png', 'x-png' );
        if( in_array( $this->sSourceExt, $aTransparentTypes ) )
        {
          $oTransparentColor = imagecolorallocate( $this->oTargetImage, 0, 0, 0 );
          imagecolortransparent( $this->oTargetImage, $oTransparentColor);
          imagealphablending( $this->oTargetImage, false );
        }
    
        return $this;
      }
    
      protected function fitToMaxDimensions()
      {
        $iTargetX = (int) round( ( $this->iTargetWidth - $this->iCalculatedWidth ) / 2 );
        $iTargetY = (int) round( ( $this->iTargetHeight - $this->iCalculatedHeight ) / 2 );
    
        $this->oTransparentCanvas = imagecreatetruecolor( $this->iTargetWidth, $this->iTargetHeight );
        imagealphablending( $this->oTransparentCanvas, false );
        imagesavealpha( $this->oTransparentCanvas, true );
        $oTransparentColor = imagecolorallocatealpha( $this->oTransparentCanvas, 0, 0, 0, 127 );
        imagefill($this->oTransparentCanvas, 0, 0, $oTransparentColor );
    
        $bReturnValue = imagecopyresampled( $this->oTransparentCanvas, $this->oTargetImage, $iTargetX, $iTargetY, 0, 0, $this->iCalculatedWidth, $this->iCalculatedHeight, $this->iCalculatedWidth, $this->iCalculatedHeight );
    
        $this->oTargetImage = $this->oTransparentCanvas;
    
        return $bReturnValue;
      }
    
      public function render()
      {
        /*
         * TODO: If this base class is ever made instantiable, some re-working is
         *       needed such that the developer harnessing it can choose whether to
         *       write to the filesystem on render, he can ask for the
         *       image resources, determine whether cleanup needs to happen, etc.
         */
        $this
          ->readSourceFileInfo()
          ->calculateNewDimensions()
          ->createTarget();
    
        $this->bIsRendered = imagecopyresampled( $this->oTargetImage, $this->oSourceImage, 0, 0, 0, 0, $this->iCalculatedWidth, $this->iCalculatedHeight, $this->iSourceWidth, $this->iSourceHeight );
    
        if( $this->bIsRendered && $this->bNeedsCanvas )
        {
          $this->bIsRendered = $this->fitToMaxDimensions();
        }
    
        if( $this->bIsRendered )
        {
          imagepng( $this->oTargetImage, $this->sTargetLocation );
          @chmod( $this->sTargetLocation, 0644 );
        }
    
        if( ! $this->bIsRendered )
        {
          throw new Exception( __METHOD__ . ': failed to copy image' );
        }
    
        $this->cleanUp();
      }
    
      public function cleanUp()
      {
        if( is_resource( $this->oSourceImage ) )
        {
          imagedestroy( $this->oSourceImage );
        }
    
        if( is_resource( $this->oTargetImage ) )
        {
          imagedestroy( $this->oTargetImage );
        }
    
        if( is_resource( $this->oTransparentCanvas ) )
        {
          imagedestroy( $this->oTransparentCanvas );
        }
      }
    }
    
    
    
    
    class ObjectIconRenderer extends ObjectImageRenderer
    {
      public function __construct( $sSourceLocation )
      {
        /* These Height/Width values are also coded in
         * src/php/reference/display/IconUploadHandler.inc
         * so if you edit them here, do it there too
         */
        $this->iTargetWidth = 20;
        $this->iTargetHeight = 20;
        $this->sTargetNamePrefix = 'icon_';
    
        parent::__construct( $sSourceLocation );
      }
    }
    
    class ObjectThumbnailRenderer extends ObjectImageRenderer
    {
      public function __construct( $sSourceLocation )
      {
        /* These Height/Width values are also coded in
         * src/php/reference/display/IconUploadHandler.inc
         * so if you edit them here, do it there too
         */
        $this->iTargetWidth = 150;
        $this->iTargetHeight = 150;
        $this->sTargetNamePrefix = 'thumbnail_';
    
        parent::__construct( $sSourceLocation );
      }
    }
    
    class ObjectPostcardRenderer extends ObjectImageRenderer
    {
      public function __construct( $sSourceLocation )
      {
        /* These Height/Width values are also coded in
         * src/php/reference/display/IconUploadHandler.inc
         * so if you edit them here, do it there too
         */
        $this->iTargetWidth = 500;
        $this->iTargetHeight = 500;
        $this->sTargetNamePrefix = 'postcard_';
    
        parent::__construct( $sSourceLocation );
      }
    }
    

    我用来运行它的代码是:

    <?php
    ObjectImageRenderer::batchRender( $sSourceFile );
    

    主要问题似乎出现在createTarget()fitToMaxDimensions()方法中。在createTarget()中,如果我注释掉以下几行:

    $aTransparentTypes = Array( 'gif', 'png', 'x-png' );
    if( in_array( $this->sSourceExt, $aTransparentTypes ) )
    {
      $oTransparentColor = imagecolorallocate( $this->oTargetImage, 0, 0, 0 );
      imagecolortransparent( $this->oTargetImage, $oTransparentColor);
      imagealphablending( $this->oTargetImage, false );
    }
    

    我不再失去黑色,但所有现有的透明度都会变黑。

    我认为问题在于我使用黑色作为透明度通道。但是,如何避免使用图像中的颜色作为透明度通道?

    感谢任何能帮助我理解透明背后的奥秘的人!

    吉姆

1 个答案:

答案 0 :(得分:1)

你明确指出0,0,0是透明的:

$oTransparentColor = imagecolorallocatealpha( $this->oTransparentCanvas, 0, 0, 0, 127 );

这将适用于图像中颜色三联体为0,0,0的所有像素 - 换句话说,所有黑色,正如您所发现的那样。

如果您希望原始图像通过,您将转换为使用Alpha通道。它是图像中的单独“层”,专门为每个像素指定透明度/不透明度。那或扫描图像中的原始颜色未使用的颜色,然后将其指定为新的透明值而不是默认为0,0,0。