rotate + translate + draw = problem

IMagick is a native PHP extension to create and modify images using the ImageMagick API. ImageMagick Studio LLC did not write nor does it maintain the IMagick extension, however, IMagick users are welcome to discuss the extension here.
Post Reply
naquad
Posts: 2
Joined: 2012-04-21T09:25:36-07:00
Authentication code: 13

rotate + translate + draw = problem

Post by naquad »

Hi.

I want to add text with background to my thumbnails. I've came up with following solution:

Code: Select all

const MiddleGravity = 0xDEADC0DE; /// own constant, doesn't get to IMagick methods
/// IMagick constants are aliased for better readability, i.e. CenterGravity is for imagick::GRAVITY_CENTER

protected static $translations = array(
    self::WestGravity => self::SouthGravity,
    self::MiddleGravity => self::CenterGravity,
    self::EastGravity => self::NorthGravity
);
//....
public function thumbnail($options = array()){
      $options += $this->config; /// merge default options with user passed

      $options['width'] = min($options['width'], $this->image->getImageWidth()); // in case thumbnail dimensions are bigger than original image
      $options['height'] = min($options['height'], $this->image->getImageHeight());
      
      if($options['crop'])
        $this->image->cropThumbnailImage($options['width'], $options['height']);
      else
        $this->image->thumbnailImage($options['width'], $options['height']);

      if(!array_key_exists('text', $options) || empty($options['text']))
        return;

      $draw = new ImagickDraw();
      $draw->setFont($options['font']);
      $draw->setFontSize($options['size']);
      $draw->setFillColor($this->color($options['background_color'])); /// color() is just a wrapper for new ImagickPixel($color);
      $draw->setFontStyle($options['font_style']);
      $draw->setFontWeight($options['font_weight']);
      $draw->setStrokeColor($this->color('transparent'));

      /// here comes the magick. in case when user wants vertical bars we just rotate drawing context and do translation of
      /// coords then just draw corresponding horizontal bar

      if(array_key_exists($options['gravity'], self::$translations)){
        switch($options['text_direction']){ /// if user wants text to be written from top to bottom or bottom to top then we should change rotation angle
        case self::TopToBottom:
          $draw->translate($this->image->getImageHeight(), 0);
          $draw->rotate(90);
          break;
        case self::BottomToTop:
          $draw->translate(0, $this->image->getImageWidth());
          $draw->rotate(270);
          break;
        default:
          throw new ImageOperationError('Invalid text direction');
        }

        $options['gravity'] = self::$translations[$options['gravity']];

        $thumb_height = $this->image->getImageWidth(); /// after rotation width and height are swapped
        $thumb_width = $this->image->getImageHeight();
      }else{
        $thumb_width = $this->image->getImageWidth();
        $thumb_height = $this->image->getImageHeight();
      }

      $metrics = $this->image->queryFontMetrics($draw, $options['text'], false); /// get text bounding box

      $box_width = $metrics['textWidth'];
      $box_height = round($metrics['boundingBox']['y2'] - $metrics['boundingBox']['y1']); /// thats tightest box that text fits to

      if($box_width > $thumb_width - $options['padding'] * 2) /// if text is too wide
        throw new ImageOperationError('Text too long');

      $draw_text = $draw->clone(); /// save current context for text drawing

      switch($options['text_align']){ /// where to start writing text (X)
      case self::LeftAlign:
        $text_x = $options['padding'];
        break;
      case self::CenterAlign:
        $text_x = $thumb_width / 2 - $box_width / 2;
        break;
      case self::RightAlign:
        $text_x = $thumb_width - $box_width - 1- $options['padding'];
        break;
      default:
        throw new ImageOperationError('Invalid text align');
      }

      switch($options['gravity']){ /// now depending on user given gravity we should calculate where to draw background (Y) and text (Y)
      case self::NorthGravity: /// top bar
        $y1 = 0;
        $y2 = $options['padding'] * 2 + $box_height;
        $text_y = $options['padding'] + $box_height + $metrics['boundingBox']['y1'];
        break;
      case self::CenterGravity: /// on the middle
        $y1 = round($thumb_height / 2 - $box_height / 2 - $options['padding']);
        $y2 = round($thumb_height / 2 + $box_height / 2 + $options['padding']);
        $text_y = round($thumb_height / 2 + $box_height / 2 + $metrics['boundingBox']['y1']);
        break;
      case self::SouthGravity: /// bottom one
        $y1 = $thumb_height - $options['padding'] * 2 - $box_height;
        $y2 = $thumb_height;
        $text_y = $thumb_height - $options['padding'] + $metrics['boundingBox']['y1'];
        break;
      default:
        throw new ImageOperationError('Invalid gravity');
      }

      if(array_key_exists('background', $options) && $options['background']){ /// should we draw background?
        $draw->rectangle(0, $y1, $thumb_width, $y2 - 1);
        $this->image->drawImage($draw);
      }

      $draw_text->setFillColor($options['color']);
      $draw_text->annotation($text_x, $text_y, $options['text']);
      $this->image->drawImage($draw_text);
    }
(whole code: http://pastebin.com/dtXiEGFP)

When drawing horizontal bars everything is ok and works as expected, but when I'm trying to make vertical annotate method goes insane: left bar is drawn w/o couple of bottom pixels which are still available, right bar is drawn from x1 = 1-3 instead of 0.

Am I doing something wrong or its some issue of IMagick? Same code works with RMagick perfectly
Post Reply