Category: "ImageMagick"

PHP - ImageMagick command line Pie Chart

This code was converted from a bash version to create pie charts. It is much faster and does not require bc on the server.

PHP is used to create the ImageMagick command line.

This code creates pie chart and legend images which can then be placed on a web page or added in to a document.

To call the code, create an array of values, instantiate the GFX objects, establish the label_color_map as an associative array where the label text is the key and the value is a hex color code, and finally create the chart, sending a name and the data array, like so:


                        $data = array(50, 70, 100, 3, 49);
                        $chart = new GFX();
                        $chart->label_color_map(array('one' => '#1c28a1', 
                          'two' => '#600087',
                          'three' => '#107a3f',
                          'four' => '#bf0000',
                          'five' => '#ff6d00'));
                        $chart->piechart('project',$data);

<?php 
Class GFX extends Base {

        public function __construct($parms = null) {
                $this->_properties = array('centerx','centery','width','height',
'radius');
                if (!is_array($parms)) {
                        $parms['width'] = $parms ['height'] = 330;
                        $parms['centerx'] = $parms['centery'] = 160;
                        $parms['radius'] = 135;
                }
                parent::__construct($parms);
        }

        public function label_color_map ($map = array()) {
                $this->_data['labels'] = array();
                foreach ($map as $k => $v) {
                        $this->_data['labels'][$k] = $v;
                }
        }
        public function piechart($name = null, $data = array()) {
                // Thanks to: http://jbkflex.wordpress.com/2011/07/28/creating-a-svg-pie-chart-html5/
                if ($name === null) return;

                $total = array_sum($data);

                $max=0;$kmax='None';
                foreach ($data as $k => $v) {
                        $value[$k] = (int)(($v/$total) * 100);
                        if ($value[$k] > $max) {
                                $kmax=$k;
                                $max=$value[$k];
                        }
                }
                while (array_sum($value) < 100) {
                        $value[$kmax]++;
                }

                foreach ($data as $k => $v) {
                        $value[$k] = (int)(($v/$total) * 100);
                        if ($value[$k] > $max) {
                                $kmax=$k;
                                $max=$value[$k];
                        }
                }
                while (array_sum($value) < 100) {
                        $value[$kmax]++;
                }
                $centerx = $this->_data['centerx'];
                $centery = $this->_data['centery'];
                $radius= $this->_data['radius'];
                $count=0;
                $startAngle=0;
                $endAngle=0;
                $arc=0;
                $x1=0;
                $x2=0;
                $y1=0;
                $y2=0;
                $pi=pi();
                $cmd='convert -size '.$this->_data['width'].'x'.$this->_data['height'].' xc:white -stroke white -strokewidth 5 ';

                foreach ($value as $k => $v) {
                        $startAngle=$endAngle;
                        $endAngle=$startAngle+(360*$v/100);
                        $theta = $pi*$startAngle/180;
                        $x1=$centerx+$radius*cos($theta);
                        $y1=$centery+$radius*sin($theta);
                        $theta = $pi*$endAngle/180;
                        $x2=$centerx+$radius*cos($theta);
                        $y2=$centery+$radius*sin($theta);
                        $arc = ($v >= 50) ? '1' : '0';
                        $cmd.=' -fill "'.$this->_data['labels'][$k].'" -draw "path \'M '.$centerx.','.$centery.' L '.$x1.','.$y1.
                                ' A '.$radius.','.$radius.' 0 '.$arc.',1 '.$x2.','.$y2.' Z"';
                        $count++;
                }
                $cmd.=' '.escapeshellarg(_GFX_DIR_.'/'.$name.'_chart.jpg');
                `$cmd`;

                if (!is_array($this->_data['labels'])) return;

                $KEY_SIZE=20;
                $MARGIN=5;
                $TEXT_X=$KEY_SIZE+$MARGIN;

                $height =  $TEXT_X * count($value);

                $cmd = 'convert -size 135x'.$height.' xc:white -fill white ';
                $label = " -font 'Nimbus-Sans-Bold' -stroke none -pointsize 12 ";
                $count=0;
                $y1=5;
                foreach ($value as $k => $v) {
                        $y2=$y1+$KEY_SIZE;
                        $y3=$y2-$MARGIN;
                        $label.=' -fill "'.$this->_data['labels'][$k].'" -draw "rectangle 0,'.$y1.' '.$KEY_SIZE.','.$y2.'"'.
                                ' -draw "text '.$TEXT_X.','.$y3.' '.escapeshellarg(/*$data[$k].' '.*/$v.'% '.$k).'"';
                        $count++;
                        $y1=$y1+$KEY_SIZE+$MARGIN;
                }
                $cmd.= $label.' '.escapeshellarg(_GFX_DIR_.'/'.$name.'_legend.jpg');
                `$cmd`;
        }
}

<?php
Abstract Class Base {

        protected $_properties;
        protected $_data;
        protected $_valid = false;
        protected $_error_message = array();

        function __construct($props) {
                $this->_data = array_fill_keys($this->_properties, null);
                $this->_data = array_merge($this->_data, $props);
        }

        function __get($property) {
                return isset($this->_data[$property]) ? $this->_data[$property] : null;
        }

        function __set($property, $value) {
                $this->_data[$property] = $value;
        }

        function isValid($property = null) {
                if ($property === null)
                        return $this->_valid || (count($this->_error_message) == 0);
                else
                        return !isset($this->_error_message[$property]);
        }

        function error($property) {
                if (isset($this->_error_message[$property])) {
                        return $this->_error_message[$property];
                } else {
                        return '';
                }
        }
}

Many thanks to the link above for the chart algorithm.

This post courtesy of Worktrainer.

bash - ImageMagick - Pie Chart Script

This is a very simple script that will create a pie chart and legend using bash, bc, and ImageMagick. Many thanks to the link above which provided the algorithms to complete the SVG paths.

It accepts a list of parameters which must add up to 100. The script has two arrays, LABELS and COLORS which are applied to each parameter in order.


#!/bin/bash

function usage {
        echo "Usage: `basename $0` piece1 piece2 piece3 ..."
        echo -e "\tWhere each piece is a percentage of the pie"
        echo -e "\tThe pieces must add up to 100"
        exit $E_BADARGS
}

LABELS=("None"  "Accept" "Defer" "Discard")
COLORS=("#1c28a1"  "#107a3f" "#ff6d00" "#bf0000");

TARGET_DIR='images'

if [ $# -lt 3 ];
then
        usage
fi;

arc=()
sum=0
for piece in "$@"
do
        sum=$(( $piece + $sum ))
done

if [ $sum -ne 100 ];
then
        usage
fi

WIDTHxHEIGHT='330x330'
RADIUS=135
CENTERX=160
CENTERY=160

count=0
startAngle=0
endAngle=0
arc=0
total=0
x1=0
x2=0
y1=0
y2=0
pi=$(echo "scale=10; 4*a(1)" | bc -l)
cmd='convert -size '$WIDTHxHEIGHT' xc:white -stroke white -strokewidth 5 '
first=0
for piece in "$@"
do
        startAngle=$endAngle
        endAngle=$(echo "scale=10;$startAngle+(360*$piece/100)" | bc -l)
        x1=$(echo "scale=10;$CENTERX+$RADIUS*c($pi*$startAngle/180)" | bc -l)
        y1=$(echo "scale=10;$CENTERY+$RADIUS*s($pi*$startAngle/180)" | bc -l)
        x2=$(echo "scale=10;$CENTERX+$RADIUS*c($pi*$endAngle/180)" | bc -l)
        y2=$(echo "scale=10;$CENTERY+$RADIUS*s($pi*$endAngle/180)" | bc -l)
        if [ $piece -ge 50 ]
        then
                FIFTY=1
        else
                FIFTY=0
          fi
        cmd=$cmd"-fill '${COLORS[count]}' -draw \"path 'M $CENTERX,$CENTERY L $x1,$y1 A $RADIUS,$RADIUS 0 $FIFTY,1 $x2,$y2 Z'\" "

        count=$(( $count + 1 ))
done
cmd=$cmd" $TARGET_DIR/idea_chart.jpg"

eval $cmd

KEY_SIZE=20
MARGIN=5
TEXT_X=$(( $KEY_SIZE+$MARGIN ))

legends=$(( $#*($KEY_SIZE+$MARGIN) ))

cmd='convert -size 125x'$legends' xc:white -fill white '
label=" -font 'Nimbus-Sans-Bold' -stroke none -pointsize 12 "
count=0;
y1=5
for piece in "$@"
do
        y2=$(( $y1+$KEY_SIZE ))
        y3=$(( $y2-$MARGIN ))
        label=$label"-fill '${COLORS[count]}' -draw 'rectangle 0,$y1 $KEY_SIZE,$y2 ' -draw \"text $TEXT_X,$y3 '$piece% ${LABELS[count]}'\" "
        count=$(( $count + 1 ))
        y1=$(( $y1+$KEY_SIZE+$MARGIN ))
done
cmd=$cmd$label" $TARGET_DIR/idea_legend.jpg"

eval $cmd

exit; 

The FIFTY is used when an arc will span 50% or more of the pie, it sets the large-arc-flag.

This post courtesy of Worktrainer.

Image - Round corners, add a credit

A script that uses ImageMagick to round the corners of an image, apply a credit, and optionally resize it. This script leaves the interim images, you could also delete them. The image credit is also written to a text file.

Original image

Credited Image

#!/bin/bash
if [ "$#" -lt 2 ]; then
        echo "usage: $0 <image file> <credit> [<resize>]"
else
ORIGINAL_IMAGE=$1
BASE_FILENAME=`basename $1 .jpg`
echo "$2" > $BASE_FILENAME.credit
convert $BASE_FILENAME.jpg \( +clone -alpha extract -draw 'fill black polygon 0,0 0,5 5,0 fill white circle 5,5, 5,0' \( +clone -flip \) \
    -compose Multiply -composite \( +clone -flop \) -compose Multiply -composite \) -alpha off  \
    -compose CopyOpacity -composite $BASE_FILENAME.rounded.png
convert -background white $BASE_FILENAME.rounded.png  +flatten $BASE_FILENAME.rounded.jpg
convert -background '#00000080' -pointsize 12 -fill white label:"$2" miff:- | \
    composite -gravity south -geometry +0+3 - \
    $BASE_FILENAME.rounded.jpg $BASE_FILENAME.credited.jpg
if [ "$#" -gt 2 ]; then
    mogrify -resize $3 $BASE_FILENAME.credited.jpg
fi
fi

Tested with:

convert -version
Version: ImageMagick 6.5.4-2 2009-07-08 Q16 OpenMP http://www.imagemagick.org
Copyright: Copyright © 1999-2009 ImageMagick Studio LLC

Label with Every Font - ImageMagick

The goal of this adventure was to find the font that looked the best on a Massachusetts license plate, using ImageMagick.

I got the list of fonts using -list type fonts. This was run on two servers with different versions of ImageMagick, so there are two different ways to get the font list. Pick the one that works for you, or make your own.

The license plate blank came from R.T’s Blank Plates.

# This isn't worth automating, you will need to edit the font_list to strip out extra lines
#convert -list type fonts | cut -f1 -d ' ' | sort | uniq > font_list
# If the above won't work, try the next line
#convert -list font | grep 'Font:' | cut -f 2 -d ':' | tr -d ' ' > font_list
for f in $(cat font_list); do
echo $f
convert  ma1987.jpg -compose srcOver -gravity center \( -background transparent -fill "#7c2128" -strokewidth 1 -stroke "#777777" -font "$f" -pointsize "72" label:TAGLNZ -resize 230x -trim -sharpen 0x1.0 \) +composite MA-$f.png
done

This is one of the 270+ images that were created:

MA - Helvetica-Narrow

This is a good example of centering a label. The command line begins with the convert command, followed by the blank plate. ‘compose srcOver’ and ‘gravity center’ position the image labeled in parenthesis over the plate, and ‘+composite’ assembles the image.

Many thanks to the ImageMagick examples.

You can create your own license plate avatar at taglinez.com.

Quick Sprite Builder

Sprites allow you to combine many images into a single file, reducing both the number of requests and bandwidth required to deliver pages.

I had 51 images, each was about 10K, so the total was about 510K.

These images had dimensions of about 250px width and 125px height.

I wanted to combine them all into a single image, and generate the CSS to compute the offsets into the sprite.


#!/bin/bash

# Remove the prior appended image
rm appended.jpg

# Create a list of all the original jpg files
ls *.jpg > jpg_files

# Resize all the images to ensure they have the same height and width
for f in $(cat jpg_files); do
        convert "$f" -resize 250x125! +repage "$f";
done

# Break the list into rows of 10 images
split -l 10 jpg_files jpg_row.

# Generate the ImageMagick command to append the images into rows 
c=convert
for f in $(ls jpg_row.*); do 
        h=`cat "$f" | tr '\n' ' '`
        c="$c"' ( '"$h"' +append ) '
done
# Combine the rows into the appended image, reduce the quality to save space
c="$c"' -background transparent -append -quality 70  appended.jpg'
`$c`

echo '.tag{height:125px;width:250px;overflow:hidden;background-color:transparent;background-image:url("appended.jpg");}' > ap.css

# Generate the CSS
r=0
for f in $(ls jpg_row.*); do 
        c=0
        for g in $(cut -f 1 -d '.' "$f"); do
                echo ."$g"'{background-position:-'$((c*250))'px -'$((r*125))'px;}' >> ap.css
                c=$((c+1))
        done
        r=$((r+1))
done

The final image was about 260K, still large, but the quality is good. Compressed for transfer, this image will serve well.

This code isn’t generalized, if you would like to use it, you’ll need to adjust the image dimensions and the number used to calculate the offsets.

1 3 4 5