summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Acosta2012-10-03 23:10:35 (GMT)
committer Matt Acosta2012-10-03 23:10:35 (GMT)
commitb2d6ea14aef0fdff1efa3db01015866d3d1d0eea (patch)
tree1f94d1bb567d87fcd338acade40fc9b0e5605c39
parent5f056491a39081e6b6ce58e9ef674e71c5a2a965 (diff)
by MattA: Made the tabard generation code more classy.
-rw-r--r--blizzardapi.info2
-rw-r--r--includes/blizzardapi.emblem.inc (renamed from includes/blizzardapi.tabard.inc)354
2 files changed, 205 insertions, 151 deletions
diff --git a/blizzardapi.info b/blizzardapi.info
index 0b3f925..1af0e58 100644
--- a/blizzardapi.info
+++ b/blizzardapi.info
@@ -8,7 +8,7 @@ configure = admin/config/services/blizzardapi
; Includes
files[] = includes/blizzardapi.abstract.inc
files[] = includes/blizzardapi.diablo.inc
-files[] = includes/blizzardapi.tabard.inc
+files[] = includes/blizzardapi.emblem.inc
files[] = includes/blizzardapi.wow.inc
; Tests
diff --git a/includes/blizzardapi.tabard.inc b/includes/blizzardapi.emblem.inc
index 507b15d..4d13aab 100644
--- a/includes/blizzardapi.tabard.inc
+++ b/includes/blizzardapi.emblem.inc
@@ -2,39 +2,58 @@
/**
* @file
- * Implements World of Warcraft guild tabard image creation.
+ * Implements emblem creation using layered base images.
*/
/**
- * A World of Warcraft guild tabard renderer.
- *
- * Requires PHP's GD image library.
+ * Interface for renderable emblems.
*/
-abstract class BlizzardApiWowTabard {
+interface BlizzardApiRenderInterface {
/**
- * The generated tabard's maximum size: 240 pixels.
+ * Determines the destination path of a rendered emblem image.
+ *
+ * @return mixed
+ * The full path of the emblem image, or FALSE if there was a problem
+ * setting up the directory.
*/
- const TABARD_MAX_SIZE = 240;
+ public static function getRenderPath($emblem, $options = array());
+
/**
- * The generated tabard's minimum size: 16 pixels.
+ * Renders and emblem image.
+ *
+ * @param array $emblem
+ * An array containing emblem creation information.
+ * @param int $size
+ * The size of the rendered emblem.
+ *
+ * @return mixed
+ * The path of the generated emblem, or FALSE if there was a problem
+ * creating the image.
*/
- const TABARD_MIN_SIZE = 16;
-
+ public static function render($emblem, $size, $options = array());
+}
+
+/**
+ * Base class for rendering emblems.
+ */
+abstract class BlizzardApiEmblem implements BlizzardApiRenderInterface {
/**
- * Copies a rectangular portion of a tabard base image onto a target image.
+ * Copies a rectangular portion of an emblem base image onto a target image.
*
* All coordinates refer to the upper left corner.
*
* @param resource $dst
* A GD image handle of the target image.
* @param string $src
- * Path of the image to overlay on the target. This must be a PNG image.
- * @param array $dst_point
- * The position to place the source image on the destination.
+ * A GD image handle of the image to overlay on the target.
+ * @param array $dst_rect
+ * The area of the destination to place the source image.
*
* Requires the following keys:
* - x: The x-coordinate of the destination point.
* - y: The y-coordinate of the destination point.
+ * - width: The width of the destination area.
+ * - height: The height of the destination area.
* @param array $src_rect
* (optional) The area of the source image to copy. Defaults to the entire
* image.
@@ -44,38 +63,27 @@ abstract class BlizzardApiWowTabard {
* - y: The y-coordinate of the source point.
* - width: The width of the source area.
* - height: The height of the source area.
- * @param mixed $overlay_color
+ * @param mixed $color
* (optional) The color to overlay on the source image.
*/
- protected static function copyResampledImage($dst, $src, $dst_point, $src_rect = array(), $overlay_color = NULL) {
- $src = imagecreatefrompng($src);
-
+ protected static function copyResampledImage($dst, $src, $dst_rect, $src_rect = array(), $color = NULL) {
// Hexadecimal -> RGB
- if (is_string($overlay_color)) {
- $overlay_color = self::unpackColor($overlay_color);
+ if (is_string($color)) {
+ $color = self::unpackColor($color);
}
// RGB -> GD color
- if (is_array($overlay_color)) {
- $overlay_color = imagecolorallocate($src, $overlay_color['r'], $overlay_color['g'], $overlay_color['b']);
+ if (is_array($color)) {
+ $color = imagecolorallocate($src, $color['r'], $color['g'], $color['b']);
}
$width = imagesx($src);
$height = imagesy($src);
- if (!is_null($overlay_color)) {
+ if (!is_null($color)) {
imagelayereffect($src, IMG_EFFECT_OVERLAY);
- imagefilledrectangle($src, 0, 0, $width, $height, $overlay_color);
+ imagefilledrectangle($src, 0, 0, $width, $height, $color);
}
- // The tabard is always a square, so width and height sizes are the same.
- $size = imagesx($dst);
- // Scale the source image onto the destination image at the given point.
- $dst_rect = array(
- 'x' => $size * $dst_point['x'] / self::TABARD_MAX_SIZE,
- 'y' => $size * $dst_point['y'] / self::TABARD_MAX_SIZE,
- 'width' => $size * $width / self::TABARD_MAX_SIZE,
- 'height' => $size * $height / self::TABARD_MAX_SIZE
- );
// Copy the entire source image if a rectangle wasn't provided.
$src_rect += array(
'x' => 0,
@@ -87,7 +95,6 @@ abstract class BlizzardApiWowTabard {
imagecopyresampled($dst, $src,
$dst_rect['x'], $dst_rect['y'], $src_rect['x'], $src_rect['y'],
$dst_rect['width'], $dst_rect['height'], $src_rect['width'], $src_rect['height']);
- imagedestroy($src);
}
/**
@@ -103,7 +110,7 @@ abstract class BlizzardApiWowTabard {
* @return resource
* A GD image handle.
*/
- protected static function createImageResource($width, $height) {
+ protected static function createImage($width, $height) {
$res = imagecreatetruecolor($width, $height);
// PNG images only!
@@ -117,6 +124,139 @@ abstract class BlizzardApiWowTabard {
}
/**
+ * Corrects base images with transparency issues that cause color overlays
+ * to fail in spectacular fashion.
+ *
+ * @param string $filename
+ * The location of the base image to fix.
+ */
+ protected static function fixTransparency($filename) {
+ $source = imagecreatefrompng($filename);
+
+ // Quit if the image has no transparent color.
+ if (imagecolortransparent($source) == -1) {
+ imagedestroy($source);
+ return;
+ }
+
+ $width = imagesx($source);
+ $height = imagesy($source);
+
+ // Copy the image onto a new transparent image of the same size.
+ $target = self::createImage($width, $height);
+ imagecopy($target, $source, 0, 0, 0, 0, $width, $height);
+
+ // Overwrite the existing image.
+ self::saveImage($target, $filename);
+
+ // Clean up any resources.
+ imagedestroy($source);
+ imagedestroy($target);
+ }
+
+ /**
+ * Checks if the emblem render directory exists and is writable.
+ *
+ * @param string $path
+ * The render directory (appended to the default file path).
+ *
+ * @return mixed
+ * The destination file path, or FALSE if there was a problem setting up the
+ * directory.
+ */
+ protected static function prepareRenderPath($path) {
+ $destination = file_default_scheme() . '://blizzardapi/' . $path;
+ $destination = file_stream_wrapper_uri_normalize($destination);
+
+ $dirname = drupal_dirname($destination);
+ if (!file_prepare_directory($dirname, FILE_CREATE_DIRECTORY)) {
+ watchdog('blizzardapi', 'The directory %directory does not exist or is not writable.',
+ array('%directory' => $dirname, WATCHDOG_ERROR));
+ return FALSE;
+ }
+
+ return $destination;
+ }
+
+ /**
+ * Saves an image to the specified destination (needed for stream wrapper support).
+ *
+ * This method is a modified version of image_gd_save().
+ *
+ * @param resource $res
+ * A GD image handle.
+ * @param string $destination
+ * A file URI or path where the image should be saved.
+ *
+ * @return bool
+ * The result of the save operation, either TRUE or FALSE.
+ */
+ protected static function saveImage($res, $destination) {
+ $scheme = file_uri_scheme($destination);
+ if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
+ $local_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
+ if (!isset($local_wrappers[$scheme])) {
+ $permanent_destination = $destination;
+ $destination = drupal_tempnam('temporary://', 'gd_');
+ }
+ $destination = drupal_realpath($destination);
+ }
+
+ imagealphablending($res, FALSE);
+ imagesavealpha($res, TRUE);
+ $success = imagepng($res, $destination);
+
+ if (isset($permanent_destination) && $success) {
+ return (bool) file_unmanaged_move($destination, $permanent_destination, FILE_EXISTS_REPLACE);
+ }
+ return $success;
+ }
+
+ /**
+ * Converts a hexadecimal color string into its aRGB components.
+ */
+ protected static function unpackColor($hex, $normalize = FALSE) {
+ $rgb = array();
+
+ if (strlen($hex) == 8) {
+ // Split the segments.
+ $alpha = substr($hex, 0, 2);
+ $hex = substr($hex, 2);
+
+ // GD functions expect a 7-bit alpha (0-127).
+ $rgb['a'] = abs(hexdec($alpha) - 255) >> 1;
+ }
+
+ // This is borrowed from _color_unpack().
+ $c = hexdec($hex);
+ for ($i = 16; $i >= 0; $i -= 8) {
+ $out[] = (($c >> $i) & 0xFF) / ($normalize ? 255 : 1);
+ }
+
+ $rgb += array(
+ 'r' => $out[0],
+ 'g' => $out[1],
+ 'b' => $out[2]
+ );
+
+ return $rgb;
+ }
+}
+
+/**
+ * A World of Warcraft guild tabard renderer.
+ */
+class BlizzardApiWowTabard extends BlizzardApiEmblem {
+ /**
+ * The generated tabard's maximum size: 240 pixels.
+ */
+ const TABARD_MAX_SIZE = 240;
+ /**
+ * The generated tabard's minimum size: 16 pixels.
+ */
+ const TABARD_MIN_SIZE = 16;
+
+ /**
* Determines the destination path for a rendered tabard image.
*
* Note: You should use file_exists() to determine if the file named in this
@@ -139,9 +279,9 @@ abstract class BlizzardApiWowTabard {
* The full path of the tabard image, or FALSE if there was a problem
* setting up the directory.
*/
- public static function getDestination($guild = array(), $options = array()) {
+ public static function getRenderPath($guild = array(), $options = array()) {
if (empty($guild)) {
- return self::prepareRenderPath('default.png');
+ return self::prepareRenderPath('tabards/render/default.png');
}
$options += array('region' => BlizzardApi::REGION_US, 'language' => BlizzardApi::LANGUAGE_DEFAULT);
@@ -162,31 +302,7 @@ abstract class BlizzardApiWowTabard {
$name = drupal_hash_base64($guild['name']);
}
- return self::prepareRenderPath("$slug/$name.png");
- }
-
- /**
- * Prepares the tabard render directory.
- *
- * @param string $suffix
- * (optional) The desired final path, appended to the default render path.
- *
- * @return mixed
- * The destination file path, or FALSE if there was a problem setting up the
- * directory.
- */
- protected static function prepareRenderPath($suffix = '') {
- $destination = file_default_scheme() . '://blizzardapi/tabards/render/' . $suffix;
- $destination = file_stream_wrapper_uri_normalize($destination);
-
- $dirname = drupal_dirname($destination);
- if (!file_prepare_directory($dirname, FILE_CREATE_DIRECTORY)) {
- watchdog('blizzardapi', 'The directory %directory does not exist or is not writable.',
- array('%directory' => $dirname, WATCHDOG_ERROR));
- return FALSE;
- }
-
- return $destination;
+ return self::prepareRenderPath("tabards/render/$slug/$name.png");
}
/**
@@ -223,7 +339,7 @@ abstract class BlizzardApiWowTabard {
* The path of the generated tabard image, or FALSE if there was a problem
* creating the image.
*/
- public static function render($tabard = array(), $size = self::TABARD_MAX_SIZE, $options = array()) {
+ public static function render($tabard, $size = self::TABARD_MAX_SIZE, $options = array()) {
if ($size < self::TABARD_MIN_SIZE) {
$size = self::TABARD_MIN_SIZE;
}
@@ -243,12 +359,6 @@ abstract class BlizzardApiWowTabard {
return FALSE;
}
- // The following images are added to the blank target image in the following order.
- // Each image supports the following keys:
- // - file: The file name of the image to add.
- // - position: The coordinates on the target image to place this image.
- // - color: (optional) If specified, this color will be blended with the
- // grey pixels of the source image.
$images = array(
'ring' => array(
'file' => 'ring-alliance.png',
@@ -256,7 +366,7 @@ abstract class BlizzardApiWowTabard {
),
'shadow' => array(
'file' => 'shadow_00.png',
- 'position' => array('x' => 18, 'y' => 27)
+ 'position' => array('x' => 18, 'y' => 27, 'height' => 216)
),
'background' => array(
'file' => 'bg_00.png',
@@ -289,9 +399,8 @@ abstract class BlizzardApiWowTabard {
$images['ring']['file'] = 'ring-horde.png';
}
- // If the API returns -1 for these values, fall back to the default tabard.
- // Presumably this happens if a guild was just created, but a tabard
- // hasn't been setup yet.
+ // If a tabard has never been customized, the API will return -1 for these
+ // values. In that case, fallback to the default tabard.
if ($tabard['emblem']['icon'] < 0 || $tabard['emblem']['border'] < 0) {
unset($tabard['emblem']);
}
@@ -301,11 +410,12 @@ abstract class BlizzardApiWowTabard {
}
}
+ // Generate a default tabard without a border or icon.
if (empty($tabard['emblem'])) {
unset($images['border'], $images['icon']);
}
- $target = self::createImageResource($size, $size);
+ $target = self::createImage($size, $size);
foreach ($images as $layer => $info) {
$source = self::retrieveBaseImage($info['file'], $options['region']);
@@ -314,11 +424,23 @@ abstract class BlizzardApiWowTabard {
return FALSE;
}
+ $source = imagecreatefrompng($source);
$color = isset($info['color']) ? $info['color'] : NULL;
- self::copyResampledImage($target, $source, $info['position'], array(), $color);
+ $s_width = isset($info['position']['width']) ? $info['position']['width'] : imagesx($source);
+ $s_height = isset($info['position']['height']) ? $info['position']['height'] : imagesy($source);
+
+ $dst_rect = array(
+ 'x' => $info['position']['x'] * $size / self::TABARD_MAX_SIZE,
+ 'y' => $info['position']['y'] * $size / self::TABARD_MAX_SIZE,
+ 'width' => $s_width * $size / self::TABARD_MAX_SIZE,
+ 'height' => $s_height * $size / self::TABARD_MAX_SIZE
+ );
+
+ self::copyResampledImage($target, $source, $dst_rect, array(), $color);
+ imagedestroy($source);
}
- $destination = self::getDestination($tabard, $options);
+ $destination = self::getRenderPath($tabard, $options);
if (empty($destination)) {
return FALSE;
}
@@ -349,17 +471,8 @@ abstract class BlizzardApiWowTabard {
* retrieving the file.
*/
protected static function retrieveBaseImage($filename, $region = BlizzardApi::REGION_US) {
- $destination = file_default_scheme() . '://blizzardapi/tabards/base/' . drupal_basename($filename);
- $destination = file_stream_wrapper_uri_normalize($destination);
-
- $dirname = drupal_dirname($destination);
- if (!file_prepare_directory($dirname, FILE_CREATE_DIRECTORY)) {
- watchdog('blizzardapi', 'The directory %directory does not exist or is not writable.',
- array('%directory' => $dirname, WATCHDOG_ERROR));
- return FALSE;
- }
-
- if (file_exists($destination)) {
+ $destination = self::prepareRenderPath('tabards/base/' . drupal_basename($filename));
+ if ($destination && file_exists($destination)) {
return $destination;
}
@@ -368,70 +481,11 @@ abstract class BlizzardApiWowTabard {
$url = 'http:' . $host . BlizzardApiWow::STATIC_IMAGE_PATH . '/guild/tabards/' . $filename;
$url = str_replace('{region}', $region, $url);
- return system_retrieve_file($url, $destination, FALSE, FILE_EXISTS_ERROR);
- }
-
- /**
- * Saves an image to the specified destination (needed for stream wrapper support).
- *
- * This method is a modified version of image_gd_save().
- *
- * @param resource $res
- * A GD image handle.
- * @param string $destination
- * A file URI or path where the image should be saved.
- *
- * @return bool
- * The result of the save operation, either TRUE or FALSE.
- */
- protected static function saveImage($res, $destination) {
- $scheme = file_uri_scheme($destination);
- if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
- $local_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
- if (!isset($local_wrappers[$scheme])) {
- $permanent_destination = $destination;
- $destination = drupal_tempnam('temporary://', 'gd_');
- }
- $destination = drupal_realpath($destination);
+ $image = system_retrieve_file($url, $destination, FALSE, FILE_EXISTS_ERROR);
+ if ($image !== FALSE) {
+ self::fixTransparency($image);
}
- imagealphablending($res, FALSE);
- imagesavealpha($res, TRUE);
- $success = imagepng($res, $destination);
-
- if (isset($permanent_destination) && $success) {
- return (bool) file_unmanaged_move($destination, $permanent_destination, FILE_EXISTS_REPLACE);
- }
- return $success;
- }
-
- /**
- * Converts a hexadecimal color string into its aRGB components.
- */
- protected static function unpackColor($hex, $normalize = FALSE) {
- $rgb = array();
-
- if (strlen($hex) == 8) {
- // Split the segments.
- $alpha = substr($hex, 0, 2);
- $hex = substr($hex, 2);
-
- // GD functions expect a 7-bit alpha (0-127).
- $rgb['a'] = abs(hexdec($alpha) - 255) >> 1;
- }
-
- $c = hexdec($hex);
-
- for ($i = 16; $i >= 0; $i -= 8) {
- $out[] = (($c >> $i) & 0xFF) / ($normalize ? 255 : 1);
- }
-
- $rgb += array(
- 'r' => $out[0],
- 'g' => $out[1],
- 'b' => $out[2]
- );
-
- return $rgb;
+ return $image;
}
}