/**
  * Check map_id value for allow chars and return map-config array by map_id.
  * 
  * First check if user qpimg_config class has get_map function and 
  * ask data by this function. Otherwise get data from map-array. 
  * Otherwise return false.
  * 
  * @param string $map_id
  * 
  * @return array map-config data or false 
  */
 public static function get_map($map_id)
 {
     $qpimg_config = $GLOBALS['qpimg_config'];
     if (preg_match('/[^a-zA-Z0-9_]/', $map_id) > 0) {
         qpimg_logger::write("QPIMG_WARNING: Used illegal chars in map name '{$map_id}'", __FILE__, __LINE__);
         return false;
     }
     $map_cfg = false;
     if (function_exists('qpimg_user_get_map') === true) {
         $map_cfg = qpimg_user_get_map($map_id);
     }
     if (!$map_cfg) {
         if (isset($qpimg_config['maps']) === false || isset($qpimg_config['maps'][$map_id]) === false) {
             qpimg_logger::write("QPIMG_WARNING: Asked map '{$map_id}' is not exists", __FILE__, __LINE__);
             return false;
         }
         $map_cfg = $qpimg_config['maps'][$map_id];
     }
     $map_cfg['id'] = $map_id;
     if (isset($map_cfg['hash']) === false) {
         $map_cfg['hash'] = qpimg_cache::get_basic_hash($map_cfg);
     }
     return $map_cfg;
 }
 /**
  * Base generate function. Detect all using maps (incoming map_id & it's attach
  * and sub-sub-...-attach maps). Check validate of cache files. Then if need
  * generate data and make redirect to CSS-file. 
  * If $get_link is true then return path to file
  * 
  * @param string $map_id
  *
  * @param bool $get_link
  * 
  * @return bool global execute status 
  */
 public static function execute($map_id, $get_link = FALSE)
 {
     list($map_id) = explode(':', $map_id);
     $need_get_maps_stack = array($map_id);
     $valid_maps_stack = array();
     $invalid_maps_stack = array();
     while (count($need_get_maps_stack) > 0) {
         $one_map_id = array_shift($need_get_maps_stack);
         $one_map_cfg = qpimg_config::get_map($one_map_id);
         if ($one_map_cfg === false) {
             return false;
         }
         if (isset($one_map_cfg['attach']) === true) {
             if (is_array($one_map_cfg['attach']) === false) {
                 $one_map_cfg['attach'] = array($one_map_cfg['attach']);
             }
             foreach ($one_map_cfg['attach'] as $attach_map_id) {
                 if (isset($valid_maps_stack[$attach_map_id]) === false && isset($invalid_maps_stack[$attach_map_id]) === false) {
                     $need_get_maps_stack[] = $attach_map_id;
                 }
             }
         }
         //---------------------------------------------------------------------
         // Check for data in cache
         if (qpimg_cache::validate($one_map_cfg) === true) {
             $valid_maps_stack[$one_map_id] = $one_map_cfg;
         } else {
             if (qpimg_cache::clean($one_map_cfg) === false) {
                 return false;
             }
             $invalid_maps_stack[$one_map_id] = $one_map_cfg;
         }
         if ($map_id == $one_map_id) {
             $map_cfg = $one_map_cfg;
         }
     }
     if (count($invalid_maps_stack) > 0) {
         if (isset($invalid_maps_stack[$map_id]) === false) {
             $invalid_maps_stack[$map_id] = $valid_maps_stack[$map_id];
             unset($valid_maps_stack[$map_id]);
         }
         //---------------------------------------------------------------------
         // Generate data
         foreach ($invalid_maps_stack as $one_map_id => $one_map_cfg) {
             if (qpimg_media::pack($one_map_cfg) === false) {
                 return false;
             }
             $valid_maps_stack[$one_map_id] = $invalid_maps_stack[$one_map_id];
         }
         qpimg_cache::clean($one_map_cfg, true);
         if (qpimg_media::pack_grouper_css($map_cfg, $valid_maps_stack) === false) {
             return false;
         }
         //---------------------------------------------------------------------
     }
     return $get_link ? qpimg_cache::get_valid_css_source($map_cfg, isset($map_cfg['attach'])) : qpimg_cache::redirect($map_cfg);
 }
 /**
  * Generate css & sprite (image) for single map
  * 
  * @param array $map_cfg map-config array
  * 
  * @return bool status 
  */
 public static function pack($map_cfg)
 {
     //---------------------------------------------------------------------
     // PreCheck incoming data
     if (is_array($map_cfg['objects']) === false) {
         $map_cfg['objects'] = array();
     }
     //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     if (isset($map_cfg['verbose_check']) === false) {
         $map_cfg['verbose_check'] = false;
     }
     $map_cfg['verbose_check'] = (bool) $map_cfg['verbose_check'];
     //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     $map_cfg['save_format'] = strtolower($map_cfg['save_format']);
     if (in_array($map_cfg['save_format'], array('png', 'gif', 'jpg')) === false) {
         $map_cfg['save_format'] = 'png';
     }
     //---------------------------------------------------------------------
     self::prepare_media_items($map_cfg);
     if ($map_cfg['objects:dataURI_files_count'] == 0) {
         // if no dataURI media-items -> then no need proecee MODE_DATAURI_REJECT
         $process_modes = array(self::MODE_STANDARD);
     } else {
         $process_modes = array(self::MODE_STANDARD, self::MODE_DATAURI_REJECT);
     }
     foreach ($process_modes as $mode) {
         switch ($mode) {
             case self::MODE_STANDARD:
                 $css_filename = qpimg_cache::gen_filename($map_cfg, 'css', qpimg::CSS_FILE_KEY_DEFAULT);
                 $sprite_filename = qpimg_cache::gen_filename($map_cfg, $map_cfg['save_format'], qpimg::SPRITE_FILE_KEY_DEFAULT);
                 $sprite_url = qpimg_cache::gen_filename($map_cfg, $map_cfg['save_format'], qpimg::SPRITE_FILE_KEY_DEFAULT, true);
                 break;
             case self::MODE_DATAURI_REJECT:
                 $css_filename = qpimg_cache::gen_filename($map_cfg, 'css', qpimg::CSS_FILE_KEY_DATAURI_REJECT);
                 $sprite_filename = qpimg_cache::gen_filename($map_cfg, $map_cfg['save_format'], qpimg::SPRITE_FILE_KEY_DATAURI_REJECT);
                 $sprite_url = qpimg_cache::gen_filename($map_cfg, $map_cfg['save_format'], qpimg::SPRITE_FILE_KEY_DATAURI_REJECT, true);
                 break;
         }
         //---------------------------------------------------------------------
         // Set positions of each media-item-element for sprite
         switch ($map_cfg['orientation']) {
             case 'static':
             default:
                 $set_position_funcname = 'set_position_static';
                 $css_bg_repeat_style = 'no-repeat';
                 break;
             case 'repeat_x':
                 $set_position_funcname = 'set_position_repeat_x';
                 $css_bg_repeat_style = 'repeat-x';
                 break;
             case 'repeat_y':
                 $set_position_funcname = 'set_position_repeat_y';
                 $css_bg_repeat_style = 'repeat-y';
                 break;
         }
         list($pos_media_items, $img_width, $img_height) = self::$set_position_funcname($map_cfg, $mode);
         //---------------------------------------------------------------------
         // Save CSS data (by mode)
         $css_data = array();
         if (count($pos_media_items) > 0) {
             $css_data[':main_sprite']['background-image'] = "url('{$sprite_url}')";
             $css_data[':main_sprite']['background-repeat'] = $css_bg_repeat_style;
             $css_data[':main_sprite'][':css-selector'] = array();
         }
         // Generate crash rules
         foreach ($map_cfg['objects:items'] as $media_id => $media_item) {
             if ($media_item->is_crashed() === false) {
                 continue;
             }
             $item_css_data = array();
             $item_css_data['background-image'] = 'none !important';
             $item_css_data['background-color'] = '#800000';
             $css_data[qpimg::get_obj_css_selector($map_cfg['id'], $media_id, $map_cfg)] = $item_css_data;
         }
         // Generate items data
         foreach ($pos_media_items as $media_id => $media_item) {
             $item_css_data = array();
             $item_css_data['background-position'] = -($media_item->get('left') + $media_item->get('space-left')) . "px " . -($media_item->get('top') + $media_item->get('space-top')) . "px";
             self::prepare_item_css($item_css_data, $media_item);
             $css_data[':main_sprite'][':css-selector'] = array_merge($css_data[':main_sprite'][':css-selector'], $item_css_data[':css-selector']);
             $css_data[qpimg::get_obj_css_selector($map_cfg['id'], $media_id, $map_cfg)] = $item_css_data;
         }
         unset($item_css_data);
         // Generate data:URI content (for MODE_STANDARD)
         if ($mode == self::MODE_STANDARD) {
             if (is_array($map_cfg['objects:items']) === true) {
                 foreach ($map_cfg['objects:items'] as $media_id => $media_item) {
                     if ($media_item->is_crashed() === true) {
                         continue;
                     }
                     if ($media_item->get('data:URI') === false) {
                         continue;
                     }
                     $item_css_data = array();
                     self::prepare_item_css($item_css_data, $media_item);
                     switch ($media_item->get('imagetype')) {
                         case IMAGETYPE_GIF:
                             $mimetype = 'image/gif';
                             break;
                         case IMAGETYPE_JPEG:
                             $mimetype = 'image/jpeg';
                             break;
                         case IMAGETYPE_PNG:
                             $mimetype = 'image/png';
                             break;
                     }
                     $item_css_data['background-image'] = "url(" . "data:{$mimetype};" . "base64," . base64_encode(@file_get_contents($media_item->get('source'))) . ")";
                     $css_data[qpimg::get_obj_css_selector($map_cfg['id'], $media_id, $map_cfg)] = $item_css_data;
                 }
             }
         }
         $css_hf = @fopen($css_filename, 'w');
         if (is_resource($css_hf) === false) {
             self::drop_temporary_media();
             return false;
         }
         ksort($css_data);
         foreach ($css_data as $css_selector => $item_css_data) {
             if ($css_selector == ':main_sprite') {
                 $css_selector = qpimg::get_map_css_selector($map_cfg['id'], $map_cfg);
             }
             fwrite($css_hf, ".{$css_selector}");
             if (is_array($item_css_data[':css-selector']) === true) {
                 foreach ($item_css_data[':css-selector'] as $sub_css_selector) {
                     $sub_css_selector = trim($sub_css_selector);
                     if ($sub_css_selector == '') {
                         continue;
                     }
                     fwrite($css_hf, ", {$sub_css_selector}");
                 }
             }
             fwrite($css_hf, " { ");
             foreach ($item_css_data as $css_attr_key => $css_attr_value) {
                 if ($css_attr_key[0] == ':') {
                     continue;
                 }
                 fwrite($css_hf, "{$css_attr_key}: {$css_attr_value}; ");
             }
             fwrite($css_hf, "}\n");
         }
         fclose($css_hf);
         //---------------------------------------------------------------------
         // Make SPRITE (image)
         if (count($pos_media_items) > 0) {
             $sprite_img = @imagecreatetruecolor($img_width, $img_height);
             if (is_resource($sprite_img) === false) {
                 self::drop_temporary_media();
                 return false;
             }
             imagealphablending($sprite_img, false);
             imagesavealpha($sprite_img, true);
             if (isset($map_cfg['bgcolor']) === true && $map_cfg['bgcolor']) {
                 list($color_r, $color_g, $color_b) = qpimg_utils::color2rgb($map_cfg['bgcolor']);
                 imagefill($sprite_img, 0, 0, imagecolorallocate($sprite_img, $color_r, $color_g, $color_b));
                 unset($color_r, $color_g, $color_b);
             } else {
                 imagefill($sprite_img, 0, 0, imagecolorallocatealpha($sprite_img, 0, 0, 0, 127));
             }
             //---------------------------------------------------------------------
             if (isset($map_cfg['transparent_color']) === true) {
                 list($color_r, $color_g, $color_b) = qpimg_utils::color2rgb($map_cfg['transparent_color']);
                 imagecolortransparent($sprite_img, imagecolorallocate($sprite_img, $color_r, $color_g, $color_b));
                 unset($color_r, $color_g, $color_b);
             }
             //---------------------------------------------------------------------
             // Copy images in sprite & save css attributes for each element
             foreach ($pos_media_items as $media_id => $media_item) {
                 $safe_image_source = self::parse_media_source($media_item->get('source'), $map_cfg['id']);
                 if ($safe_image_source === false) {
                     continue;
                 }
                 //- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                 switch ($media_item->get('imagetype')) {
                     case IMAGETYPE_GIF:
                         $tmp_img = @imagecreatefromgif($safe_image_source);
                         break;
                     case IMAGETYPE_JPEG:
                         $tmp_img = @imagecreatefromjpeg($safe_image_source);
                         break;
                     case IMAGETYPE_PNG:
                         $tmp_img = @imagecreatefrompng($safe_image_source);
                         break;
                 }
                 if (is_resource($tmp_img) === false) {
                     continue;
                 }
                 $src_x = 0;
                 $src_y = 0;
                 $src_w = $media_item->get('etalon-width');
                 $src_h = $media_item->get('etalon-height');
                 $dst_x = $media_item->get('left') + $media_item->get('space-left');
                 $dst_y = $media_item->get('top') + $media_item->get('space-top');
                 $dst_w = $media_item->get('scale-width');
                 $dst_h = $media_item->get('scale-height');
                 if ($media_item->get('bgcolor') !== false) {
                     list($color_r, $color_g, $color_b) = qpimg_utils::color2rgb($media_item->get('bgcolor'));
                     imagefilledrectangle($sprite_img, $media_item->get('left'), $media_item->get('top'), $media_item->get('left') + $media_item->get('full-width') - 1, $media_item->get('top') + $media_item->get('full-height') - 1, imagecolorallocate($sprite_img, $color_r, $color_g, $color_b));
                     unset($color_r, $color_g, $color_b);
                 }
                 if ($src_w == $dst_w && $src_h == $dst_h) {
                     imagecopy($sprite_img, $tmp_img, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h);
                 } else {
                     if ($media_item->get('scale-method') == 'resize') {
                         imagecopyresized($sprite_img, $tmp_img, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
                     } elseif ($media_item->get('scale-method') == 'resample') {
                         imagecopyresampled($sprite_img, $tmp_img, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
                     } elseif ($media_item->get('scale-method') == 'mosaic') {
                         $mosaic_y = $dst_y;
                         $mosaic_h = $dst_h;
                         do {
                             $mosaic_x = $dst_x;
                             $mosaic_w = $dst_w;
                             do {
                                 imagecopy($sprite_img, $tmp_img, $mosaic_x, $mosaic_y, 0, 0, min($mosaic_w, $src_w), min($mosaic_h, $src_h));
                                 $mosaic_x += $src_w;
                                 $mosaic_w -= $src_w;
                             } while ($mosaic_w > 0);
                             $mosaic_y += $src_h;
                             $mosaic_h -= $src_h;
                         } while ($mosaic_h > 0);
                         unset($mosaic_x, $mosaic_y, $mosaic_w, $mosaic_h);
                     } else {
                         qpimg_logger::write("QPIMG_WARNING: Undefined scale-method: " . $media_item->get('scale-method'), __FILE__, __LINE__);
                         continue;
                     }
                 }
                 unset($src_x, $src_y, $src_w, $src_h);
                 unset($dst_x, $dst_y, $dst_w, $dst_h);
                 imagedestroy($tmp_img);
             }
             //---------------------------------------------------------------------
             // Save image-data
             switch ($map_cfg['save_format']) {
                 case 'png':
                     if (isset($map_cfg['save_quality']) === false) {
                         $map_cfg['save_quality'] = 9;
                     }
                     $save_result = @imagepng($sprite_img, $sprite_filename, min(max($map_cfg['save_quality'], 0), 9));
                     break;
                 case 'gif':
                     $save_result = @imagegif($sprite_img, $sprite_filename);
                     break;
                 case 'jpg':
                     if (isset($map_cfg['save_quality']) === false) {
                         $map_cfg['save_quality'] = 95;
                     }
                     $save_result = @imagejpeg($sprite_img, $sprite_filename, min(max($map_cfg['save_quality'], 0), 100));
                     break;
             }
             if ($save_result === false) {
                 qpimg_logger::write("QPIMG_ERROR: Failed on save result sprite '{$sprite_filename}'", __FILE__, __LINE__);
             }
             imagedestroy($sprite_img);
         }
     }
     //---------------------------------------------------------------------
     // Save crc-hash [if set verbose_check mode]
     if ($map_cfg['verbose_check'] === true) {
         @file_put_contents(qpimg_cache::gen_filename($map_cfg, 'crc'), $map_cfg['hash'] . ':' . qpimg_cache::get_media_hash($map_cfg));
     }
     self::drop_temporary_media();
     return true;
 }