27faa44c30
Don't really need these anymore now that we're not generating C++.
1201 linhas
32 KiB
C++
1201 linhas
32 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
| Copyright (c) 1997-2010 The PHP Group |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "hphp/runtime/base/zend/zend_php_config.h"
|
|
#include "hphp/runtime/ext/ext_curl.h"
|
|
#include "hphp/runtime/ext/ext_file.h"
|
|
#include "hphp/runtime/ext/ext_imagesprite.h"
|
|
#include "hphp/runtime/ext/ext_string.h"
|
|
#include "hphp/runtime/ext/ext_url.h"
|
|
#include <list>
|
|
#include <vector>
|
|
#include <queue>
|
|
|
|
#include "hphp/system/lib/systemlib.h"
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
static const StaticString
|
|
s_padding_top("padding_top"),
|
|
s_padding_right("padding_right"),
|
|
s_padding_bottom("padding_bottom"),
|
|
s_padding_left("padding_left"),
|
|
s_width("width"),
|
|
s_height("height"),
|
|
s_flush_left("flush_left"),
|
|
s_flush_right("flush_right"),
|
|
s_id("id");
|
|
|
|
namespace ImageSprite {
|
|
|
|
// PHP extension gd.c
|
|
#define PHP_GDIMG_TYPE_GIF 1
|
|
#define PHP_GDIMG_TYPE_PNG 2
|
|
#define PHP_GDIMG_TYPE_JPG 3
|
|
#define PHP_GDIMG_TYPE_WBM 4
|
|
#define PHP_GDIMG_TYPE_XBM 5
|
|
#define PHP_GDIMG_TYPE_XPM 6
|
|
#define PHP_GDIMG_CONVERT_WBM 7
|
|
#define PHP_GDIMG_TYPE_GD 8
|
|
#define PHP_GDIMG_TYPE_GD2 9
|
|
#define PHP_GDIMG_TYPE_GD2PART 10
|
|
|
|
// PHP extension STANDARD: image.c
|
|
// /* file type markers */
|
|
static const char php_sig_gif[3] = {'G', 'I', 'F'};
|
|
static const char php_sig_jpg[3] = {(char) 0xff, (char) 0xd8, (char) 0xff};
|
|
static const char php_sig_png[8] =
|
|
{(char) 0x89, (char) 0x50, (char) 0x4e, (char) 0x47,
|
|
(char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a};
|
|
static const char php_sig_gd2[3] = {'g', 'd', '2'};
|
|
|
|
void ev_req_complete(struct evhttp_request *req, void *obj) {
|
|
always_assert(obj);
|
|
((ImageFromHTTP*)obj)->completed();
|
|
}
|
|
|
|
bool image_height_comparator(const Image* lhs, const Image* rhs) {
|
|
return lhs->m_effheight < rhs->m_effheight;
|
|
}
|
|
|
|
bool image_max_dim_comparator(const Image* lhs, const Image* rhs) {
|
|
int l = (lhs->m_width > lhs->m_height) ? lhs->m_width : lhs->m_height;
|
|
int r = (rhs->m_width > rhs->m_height) ? rhs->m_width : rhs->m_height;
|
|
return l < r;
|
|
}
|
|
|
|
bool image_area_comparator(const Image* lhs, const Image* rhs) {
|
|
return lhs->m_effarea < rhs->m_effarea;
|
|
}
|
|
|
|
bool image_path_comparator(const Image* lhs, const Image* rhs) {
|
|
return lhs->m_path < rhs->m_path;
|
|
}
|
|
|
|
class BlockHeightComparator {
|
|
public:
|
|
bool operator()(const Block* lhs, const Block* rhs) {
|
|
return lhs->m_height < rhs->m_height;
|
|
}
|
|
};
|
|
|
|
class BlockAreaComparator {
|
|
public:
|
|
bool operator()(const Block* lhs, const Block* rhs) {
|
|
return lhs->m_area < rhs->m_area;
|
|
}
|
|
};
|
|
|
|
static int _php_image_type (char data[8]) {
|
|
if (data == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (!memcmp(data, php_sig_gd2, 3)) {
|
|
return PHP_GDIMG_TYPE_GD2;
|
|
} else if (!memcmp(data, php_sig_jpg, 3)) {
|
|
return PHP_GDIMG_TYPE_JPG;
|
|
} else if (!memcmp(data, php_sig_png, 3)) {
|
|
if (!memcmp(data, php_sig_png, 8)) {
|
|
return PHP_GDIMG_TYPE_PNG;
|
|
}
|
|
} else if (!memcmp(data, php_sig_gif, 3)) {
|
|
return PHP_GDIMG_TYPE_GIF;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void ResourceGroupEv::add(evhttp_connection* conn) {
|
|
m_connections += 1;
|
|
evhttp_connection_set_base(conn, m_base);
|
|
}
|
|
|
|
void ResourceGroupEv::complete(evhttp_connection* conn) {
|
|
m_connections -= 1;
|
|
if (m_connections <= 0) {
|
|
event_base_loopbreak(m_base);
|
|
}
|
|
}
|
|
|
|
void ResourceGroupEv::setTimeout(int milliseconds) {
|
|
int seconds;
|
|
if (milliseconds > 0) {
|
|
seconds = milliseconds / 1000;
|
|
milliseconds = milliseconds % 1000;
|
|
} else {
|
|
seconds = 10;
|
|
milliseconds = 0;
|
|
}
|
|
struct timeval timeout_struct;
|
|
timeout_struct.tv_sec = seconds;
|
|
timeout_struct.tv_usec = milliseconds * 1000;
|
|
int ret = event_base_loopexit(m_base, &timeout_struct);
|
|
if (ret != 0) {
|
|
// TODO: Handle timeout setting failure (how?)
|
|
}
|
|
}
|
|
|
|
void Image::loadDims(bool block /* = false */) {
|
|
always_assert(!m_error);
|
|
if (m_width <= 0 || m_height <= 0) {
|
|
loadImage(block);
|
|
if (block && !m_error) {
|
|
always_assert(m_image != NULL);
|
|
m_width = m_image->sx;
|
|
m_height = m_image->sy;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Image::reset() {
|
|
if (m_image) {
|
|
gdImageDestroy(m_image);
|
|
m_image = NULL;
|
|
}
|
|
m_width = -1;
|
|
m_height = -1;
|
|
m_area = -1;
|
|
m_error = false;
|
|
m_error_string = null_string;
|
|
}
|
|
|
|
void Image::setError(const String& err) {
|
|
m_error = true;
|
|
m_error_string = err;
|
|
String sprite_err = String("Error in image '") + m_path + "': " + err;
|
|
m_sprite->m_img_errors.append(sprite_err);
|
|
}
|
|
|
|
void Image::load_data_to_gd(int size, const void* data) {
|
|
always_assert(data);
|
|
always_assert(!m_error);
|
|
always_assert(m_image == NULL);
|
|
if (size < 8) {
|
|
setError("Invalid image file");
|
|
return;
|
|
}
|
|
|
|
char sig[8];
|
|
memcpy(sig, data, 8);
|
|
int imtype = _php_image_type(sig);
|
|
gdImagePtr img = NULL;
|
|
switch(imtype) {
|
|
case PHP_GDIMG_TYPE_JPG:
|
|
#ifdef HAVE_GD_JPG
|
|
img = gdImageCreateFromJpegPtr(size, (void*) data);
|
|
#else
|
|
setError("No JPEG support");
|
|
return;
|
|
#endif
|
|
break;
|
|
|
|
case PHP_GDIMG_TYPE_PNG:
|
|
#ifdef HAVE_GD_PNG
|
|
img = gdImageCreateFromPngPtr(size, (void*) data);
|
|
#else
|
|
setError("No PNG support");
|
|
return;
|
|
#endif
|
|
break;
|
|
|
|
case PHP_GDIMG_TYPE_GIF:
|
|
#ifdef HAVE_GD_GIF_READ
|
|
img = gdImageCreateFromGifPtr(size, (void*) data);
|
|
#else
|
|
setError("No GIF support");
|
|
return;
|
|
#endif
|
|
break;
|
|
|
|
case PHP_GDIMG_TYPE_GD2:
|
|
#ifdef HAVE_GD_GD2
|
|
img = gdImageCreateFromGd2Ptr(size, (void*) data);
|
|
#else
|
|
setError("No GD2 support");
|
|
return;
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
setError("Data format not supported");
|
|
return;
|
|
}
|
|
|
|
if (img) {
|
|
m_image = img;
|
|
if (m_width != img->sx || m_height != img->sy) {
|
|
m_width = img->sx;
|
|
m_height = img->sy;
|
|
m_area = img->sx * img->sy;
|
|
m_sprite->m_current = false;
|
|
}
|
|
} else {
|
|
setError("Error converting data to image.");
|
|
m_width = -1;
|
|
m_height = -1;
|
|
m_area = -1;
|
|
m_sprite->m_current = false;
|
|
}
|
|
}
|
|
|
|
void Image::setOptions(Array options) {
|
|
if (options.exists(s_padding_top)) {
|
|
int32_t v = options[s_padding_top].toInt32();
|
|
if (v < 0) {
|
|
m_padding[IMAGESPRITE_PAD_TOP] = 0;
|
|
} else {
|
|
m_padding[IMAGESPRITE_PAD_TOP] = v;
|
|
}
|
|
}
|
|
|
|
if (options.exists(s_padding_right)) {
|
|
int32_t v = options[s_padding_right].toInt32();
|
|
if (v < 0) {
|
|
m_padding[IMAGESPRITE_PAD_RIGHT] = 0;
|
|
} else {
|
|
m_padding[IMAGESPRITE_PAD_RIGHT] = v;
|
|
}
|
|
}
|
|
|
|
if (options.exists(s_padding_bottom)) {
|
|
int32_t v = options[s_padding_bottom].toInt32();
|
|
if (v < 0) {
|
|
m_padding[IMAGESPRITE_PAD_BOTTOM] = 0;
|
|
} else {
|
|
m_padding[IMAGESPRITE_PAD_BOTTOM] = v;
|
|
}
|
|
}
|
|
|
|
if (options.exists(s_padding_left)) {
|
|
int32_t v = options[s_padding_left].toInt32();
|
|
if (v < 0) {
|
|
m_padding[IMAGESPRITE_PAD_LEFT] = 0;
|
|
} else {
|
|
m_padding[IMAGESPRITE_PAD_LEFT] = v;
|
|
}
|
|
}
|
|
|
|
if (options.exists(s_width) && options.exists(s_height)) {
|
|
m_width = options[s_width].toInt32();
|
|
m_height = options[s_height].toInt32();
|
|
m_area = m_width * m_height;
|
|
}
|
|
|
|
if (options.exists(s_flush_left)) {
|
|
bool v = options[s_flush_left].toBoolean();
|
|
m_flush[IMAGESPRITE_FLUSH_LEFT] = v;
|
|
}
|
|
|
|
if (options.exists(s_flush_right)) {
|
|
bool v = options[s_flush_right].toBoolean();
|
|
m_flush[IMAGESPRITE_FLUSH_RIGHT] = v;
|
|
}
|
|
}
|
|
|
|
void ImageFromFile::loadImage(bool block /* = false */) {
|
|
always_assert(!m_error);
|
|
if (m_image != NULL) {
|
|
return;
|
|
}
|
|
// Don't do anything unless we are blocking
|
|
if (block) {
|
|
Variant contents = f_file_get_contents(m_path);
|
|
if (contents.isString()) {
|
|
String data = contents.toString();
|
|
load_data_to_gd(data.size(), data.c_str());
|
|
} else {
|
|
setError("Cannot locate file");
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImageFromString::loadImage(bool block /* = false */) {
|
|
always_assert(!m_error);
|
|
if (m_image != NULL) {
|
|
return;
|
|
}
|
|
// Don't do anything unless we are blocking
|
|
if (block) {
|
|
load_data_to_gd(m_data.size(), m_data.c_str());
|
|
}
|
|
}
|
|
|
|
ImageFromHTTP::ImageFromHTTP(String path, c_ImageSprite* sprite, int timeout)
|
|
: Image(path, sprite) {
|
|
m_ev_group = NULL;
|
|
m_host = null_string;
|
|
m_url = null_string;
|
|
m_query = "";
|
|
m_port = 80;
|
|
m_conn = NULL;
|
|
m_request = NULL;
|
|
Variant parts = f_parse_url(path);
|
|
if (parts.isArray()) {
|
|
for (ArrayIter iter(parts.toArray()); iter; ++iter) {
|
|
String key = iter.first().toString();
|
|
String val = iter.second().toString();
|
|
if (key == "scheme") {
|
|
if (val != "http") {
|
|
setError("Unsupported URL scheme");
|
|
break;
|
|
}
|
|
} else if (key == "host") {
|
|
m_host = val;
|
|
} else if (key == "port") {
|
|
m_port = val.toInt32();
|
|
} else if (key == "path") {
|
|
m_url = val;
|
|
} else if (key == "query") {
|
|
m_query += "?";
|
|
m_query += val;
|
|
}
|
|
}
|
|
|
|
// Now verify we got everything we need
|
|
if (same(m_host, null_string)) {
|
|
setError("No host specified");
|
|
} else if (same(m_url, null_string)) {
|
|
setError("No url path specified");
|
|
} else if (m_sprite->m_rsrc_groups["ev"] == NULL) {
|
|
// Get/initialize the event group with the base
|
|
m_ev_group = new ResourceGroupEv(m_sprite);
|
|
m_sprite->m_rsrc_groups["ev"] = m_ev_group;
|
|
} else {
|
|
m_ev_group = (ResourceGroupEv*) m_sprite->m_rsrc_groups["ev"];
|
|
}
|
|
|
|
if (m_ev_group != NULL) {
|
|
m_ev_group->setTimeout(timeout);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImageFromHTTP::loadImage(bool block /* = false */) {
|
|
always_assert(!m_error);
|
|
if (m_image != NULL) {
|
|
return;
|
|
}
|
|
|
|
if (m_conn == NULL) {
|
|
m_conn = evhttp_connection_new(m_host.c_str(), m_port);
|
|
m_ev_group->add(m_conn);
|
|
m_request = evhttp_request_new(ev_req_complete, this);
|
|
|
|
if (m_port == 80) {
|
|
evhttp_add_header(m_request->output_headers, "Host", m_host.c_str());
|
|
} else {
|
|
std::string ss = m_host.data();
|
|
ss += ":";
|
|
ss += boost::lexical_cast<std::string>(m_port);
|
|
evhttp_add_header(m_request->output_headers, "Host", ss.c_str());
|
|
}
|
|
|
|
int ret = evhttp_make_request(
|
|
m_conn,
|
|
m_request,
|
|
EVHTTP_REQ_GET,
|
|
(m_url + m_query).c_str());
|
|
|
|
if (ret) {
|
|
setError("http request failed");
|
|
}
|
|
}
|
|
|
|
if (block) {
|
|
event_base_dispatch(m_ev_group->m_base);
|
|
}
|
|
}
|
|
|
|
void ImageFromHTTP::reset() {
|
|
if (m_conn != NULL) {
|
|
evhttp_connection_free(m_conn);
|
|
m_conn = NULL;
|
|
}
|
|
|
|
m_request = NULL;
|
|
Image::reset();
|
|
}
|
|
|
|
void ImageFromHTTP::completed() {
|
|
always_assert(m_request);
|
|
always_assert(m_request->input_buffer);
|
|
always_assert(m_image == NULL);
|
|
always_assert(!m_error);
|
|
|
|
int code = m_request->response_code;
|
|
if (code >= 200 && code <= 300) {
|
|
// Looks like we got a real response
|
|
load_data_to_gd(
|
|
EVBUFFER_LENGTH(m_request->input_buffer),
|
|
EVBUFFER_DATA(m_request->input_buffer));
|
|
} else {
|
|
setError(String("Invalid Response code: ") + String((int64_t)code));
|
|
}
|
|
m_ev_group->complete(m_conn);
|
|
evhttp_connection_free(m_conn);
|
|
m_conn = NULL;
|
|
m_request = NULL;
|
|
}
|
|
|
|
} // Imagesprite
|
|
|
|
// PHP accessible classes/functions
|
|
|
|
c_ImageSprite::c_ImageSprite(Class* cb) :
|
|
ExtObjectData(cb) {
|
|
m_image_string_buffer = null_string;
|
|
m_image = NULL;
|
|
m_current = false;
|
|
m_width = 0;
|
|
m_height = 0;
|
|
m_mapping = Array::Create();
|
|
m_img_errors = Array::Create();
|
|
m_sprite_errors = Array::Create();
|
|
}
|
|
|
|
c_ImageSprite::~c_ImageSprite() {
|
|
}
|
|
|
|
void c_ImageSprite::t___construct() {
|
|
}
|
|
|
|
Object c_ImageSprite::t_addfile(CStrRef file,
|
|
CArrRef options /* = null */) {
|
|
m_current = false;
|
|
|
|
ImageSprite::Image *entry = new ImageSprite::ImageFromFile(file, this);
|
|
entry->setOptions(options);
|
|
if (m_image_data[file.c_str()] != NULL) {
|
|
delete m_image_data[file.c_str()];
|
|
}
|
|
m_image_data[file.c_str()] = entry;
|
|
|
|
return this;
|
|
}
|
|
|
|
Object c_ImageSprite::t_addstring(CStrRef id, CStrRef data,
|
|
CArrRef options /* = null */) {
|
|
m_current = false;
|
|
|
|
ImageSprite::Image *entry = new ImageSprite::ImageFromString(id, data, this);
|
|
entry->setOptions(options);
|
|
if (m_image_data[id.c_str()] != NULL) {
|
|
delete m_image_data[id.c_str()];
|
|
}
|
|
m_image_data[id.c_str()] = entry;
|
|
|
|
return this;
|
|
}
|
|
|
|
Object c_ImageSprite::t_addurl(CStrRef url, int timeout_ms /* = 0 */,
|
|
CArrRef options /* = null */) {
|
|
m_current = false;
|
|
|
|
ImageSprite::Image *entry = new ImageSprite::ImageFromHTTP(
|
|
url,
|
|
this,
|
|
timeout_ms);
|
|
|
|
entry->setOptions(options);
|
|
if (m_image_data[url.c_str()] != NULL) {
|
|
delete m_image_data[url.c_str()];
|
|
}
|
|
m_image_data[url.c_str()] = entry;
|
|
|
|
return this;
|
|
}
|
|
|
|
Object c_ImageSprite::t_clear(CVarRef files /* = null */) {
|
|
if (same(files, uninit_null())) {
|
|
// Clear them all, might as well __destruct
|
|
t___destruct();
|
|
} else if (files.isArray()) {
|
|
for (ArrayIter iter(files.toArray()); iter; ++iter) {
|
|
if (iter.second().isString()) {
|
|
t_clear(iter.second());
|
|
}
|
|
}
|
|
} else if (files.isString()) {
|
|
String path(files.toString());
|
|
if (m_image_data.find(path.c_str()) == m_image_data.end()) {
|
|
return this;
|
|
}
|
|
m_current = false;
|
|
delete m_image_data[path.c_str()];
|
|
m_image_data.erase(path.c_str());
|
|
} else {
|
|
// TODO: Wrong type passed to function
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
Object c_ImageSprite::t_loaddims(bool block /* = false */) {
|
|
if (m_current) {
|
|
return this;
|
|
}
|
|
|
|
if (block) {
|
|
// This is so http requests can be batched, etc
|
|
t_loaddims(false);
|
|
}
|
|
|
|
hphp_string_map<ImageSprite::Image*>::iterator iter;
|
|
hphp_string_map<ImageSprite::Image*>::iterator end;
|
|
|
|
iter = m_image_data.begin();
|
|
end = m_image_data.end();
|
|
|
|
for(; iter != end; iter++) {
|
|
ImageSprite::Image *val = iter->second;
|
|
if (val == NULL) {
|
|
continue;
|
|
} else if (!val->m_error) {
|
|
val->loadDims(block);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
Object c_ImageSprite::t_loadimages(bool block /* = false */) {
|
|
if (m_current) {
|
|
return this;
|
|
}
|
|
|
|
if (block) {
|
|
// This is so http requests can be batched, etc
|
|
t_loadimages(false);
|
|
}
|
|
|
|
hphp_string_map<ImageSprite::Image*>::iterator iter;
|
|
hphp_string_map<ImageSprite::Image*>::iterator end;
|
|
|
|
iter = m_image_data.begin();
|
|
end = m_image_data.end();
|
|
|
|
for(; iter != end; iter++) {
|
|
ImageSprite::Image *val = iter->second;
|
|
if (val == NULL) {
|
|
continue;
|
|
} else if (!val->m_error) {
|
|
val->loadImage(block);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
#define ImageList std::list<ImageSprite::Image*>
|
|
|
|
#define BlockHeightMinHeap std::priority_queue< \
|
|
ImageSprite::Block*, \
|
|
std::vector<ImageSprite::Block*>, \
|
|
ImageSprite::BlockHeightComparator>
|
|
|
|
#define BlockAreaMinHeap std::priority_queue< \
|
|
ImageSprite::Block*, \
|
|
std::vector<ImageSprite::Block*>, \
|
|
ImageSprite::BlockAreaComparator>
|
|
|
|
const StaticString
|
|
s_x("x"),
|
|
s_y("y"),
|
|
s_images("images"),
|
|
s_sprite("sprite");
|
|
|
|
void c_ImageSprite::map() {
|
|
if (m_current) {
|
|
return;
|
|
}
|
|
|
|
m_mapping.removeAll();
|
|
|
|
delete m_image;
|
|
m_image = NULL;
|
|
|
|
ImageList images;
|
|
|
|
/**
|
|
* We're putting all of the elements in the map into a list here because then
|
|
* we can sort them and guarantee the iteration order. Failure to guarantee
|
|
* iteration order may result in images of the same size being placed in
|
|
* opposite positions, and the wrong one being shown (if the css and output
|
|
* are generated on separate machines)
|
|
**/
|
|
|
|
hphp_string_map<ImageSprite::Image*>::iterator map_iter;
|
|
hphp_string_map<ImageSprite::Image*>::iterator map_end;
|
|
|
|
map_iter = m_image_data.begin();
|
|
map_end = m_image_data.end();
|
|
|
|
for(; map_iter != map_end; map_iter++) {
|
|
ImageSprite::Image *img = map_iter->second;
|
|
if (!img->m_error) {
|
|
images.push_front(img);
|
|
}
|
|
}
|
|
|
|
if (images.empty()) {
|
|
// Get out before it's too late
|
|
return;
|
|
}
|
|
|
|
// Here be dragons. Thou art forewarned
|
|
|
|
images.sort(ImageSprite::image_path_comparator);
|
|
|
|
int height = 0;
|
|
int width = 0;
|
|
|
|
ImageList left_flush;
|
|
ImageList right_flush;
|
|
ImageList no_flush;
|
|
BlockHeightMinHeap right_heap;
|
|
BlockAreaMinHeap general_heap;
|
|
int abs_width = 0;
|
|
|
|
ImageList::iterator iter = images.begin();
|
|
ImageList::iterator end = images.end();
|
|
|
|
// Loop through images
|
|
// sort them by whether they are left flush, right flush, or no flush
|
|
for(; iter != end; iter++) {
|
|
ImageSprite::Image *img = *iter;
|
|
|
|
if (img == NULL || img->m_error) {
|
|
continue;
|
|
}
|
|
|
|
img->m_effwidth = img->m_padding[IMAGESPRITE_PAD_LEFT] +
|
|
img->m_width +
|
|
img->m_padding[IMAGESPRITE_PAD_RIGHT];
|
|
img->m_effheight = img->m_padding[IMAGESPRITE_PAD_TOP] +
|
|
img->m_height +
|
|
img->m_padding[IMAGESPRITE_PAD_BOTTOM];
|
|
img->m_effarea = img->m_effwidth * img->m_effheight;
|
|
|
|
if (img->m_flush[IMAGESPRITE_FLUSH_LEFT]) {
|
|
left_flush.push_front(img);
|
|
|
|
if (img->m_flush[IMAGESPRITE_FLUSH_RIGHT]) {
|
|
if (abs_width && img->m_effwidth != abs_width) {
|
|
m_img_errors.append(
|
|
"Two images specified as flush both left and right, "
|
|
"but have different widths");
|
|
return;
|
|
} else {
|
|
abs_width = img->m_effwidth;
|
|
}
|
|
}
|
|
} else if (img->m_flush[IMAGESPRITE_FLUSH_RIGHT]) {
|
|
right_flush.push_front(img);
|
|
} else {
|
|
no_flush.push_front(img);
|
|
}
|
|
|
|
if (width < img->m_effwidth) {
|
|
width = img->m_effwidth;
|
|
if (abs_width && width > abs_width) {
|
|
m_img_errors.append(
|
|
"Widest image is wider that an image specified "
|
|
"as flush both left and right");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
right_flush.sort(ImageSprite::image_height_comparator);
|
|
no_flush.sort(ImageSprite::image_max_dim_comparator);
|
|
|
|
iter = left_flush.begin();
|
|
end = left_flush.end();
|
|
|
|
// Loop through the left flush images and stack them.
|
|
// Put the blocks in the flush right heap
|
|
for(; iter != end; iter++) {
|
|
ImageSprite::Image *img = *iter;
|
|
|
|
img->m_x = img->m_padding[IMAGESPRITE_PAD_LEFT];
|
|
img->m_y = img->m_padding[IMAGESPRITE_PAD_TOP] + height;
|
|
|
|
if (img->m_effwidth < width) {
|
|
int x = img->m_effwidth;
|
|
int y = height;
|
|
ImageSprite::Block *block = new ImageSprite::Block(
|
|
x,
|
|
y,
|
|
width - x,
|
|
img->m_effheight);
|
|
right_heap.push(block);
|
|
}
|
|
|
|
height += img->m_effheight;
|
|
}
|
|
|
|
// Loop the right images and place them
|
|
while(!right_heap.empty() && !right_flush.empty()) {
|
|
ImageSprite::Block* block = right_heap.top();
|
|
right_heap.pop();
|
|
|
|
bool fit = false;
|
|
ImageList::iterator best_iter;
|
|
|
|
iter = right_flush.begin();
|
|
end = right_flush.end();
|
|
for(; iter != end; iter++) {
|
|
ImageSprite::Image *img = *iter;
|
|
|
|
if (img->m_effheight > block->m_height) {
|
|
break;
|
|
}
|
|
|
|
if (img->m_effwidth > block->m_width) {
|
|
continue;
|
|
}
|
|
|
|
fit = true;
|
|
best_iter = iter;
|
|
}
|
|
|
|
if (fit) {
|
|
ImageSprite::Image *img = *best_iter;
|
|
img->m_x = width - img->m_effwidth + img->m_padding[IMAGESPRITE_PAD_LEFT];
|
|
img->m_y = block->m_y + img->m_padding[IMAGESPRITE_PAD_TOP];
|
|
|
|
if (img->m_effheight != block->m_height ||
|
|
img->m_effwidth != block->m_width) {
|
|
int vcut_b = img->m_effwidth * (block->m_height - img->m_effheight);
|
|
int vcut_l = block->m_height * (block->m_width - img->m_effwidth);
|
|
int hcut_b = block->m_width * (block->m_height - img->m_effheight);
|
|
int hcut_l = img->m_effheight * (block->m_width - img->m_effwidth);
|
|
int vcut_d = (vcut_b > vcut_l) ? vcut_b - vcut_l : vcut_l - vcut_b;
|
|
int hcut_d = (hcut_b > hcut_l) ? hcut_b - hcut_l : hcut_l - hcut_b;
|
|
if (hcut_d > vcut_d) {
|
|
// Horizontal cut is better than a vertical cut
|
|
if (img->m_effwidth != block->m_width) {
|
|
ImageSprite::Block* n_block = new ImageSprite::Block(
|
|
block->m_x,
|
|
block->m_y,
|
|
(block->m_width - img->m_effwidth),
|
|
img->m_effheight);
|
|
general_heap.push(n_block);
|
|
}
|
|
if (img->m_effheight != block->m_height) {
|
|
ImageSprite::Block* n_block = new ImageSprite::Block(
|
|
block->m_x,
|
|
block->m_y + img->m_effheight,
|
|
block->m_width,
|
|
block->m_height - img->m_effheight);
|
|
right_heap.push(n_block);
|
|
}
|
|
} else {
|
|
// Vertical cut is better than a horizontal cut
|
|
if (img->m_effwidth != block->m_width) {
|
|
ImageSprite::Block* n_block = new ImageSprite::Block(
|
|
block->m_x,
|
|
block->m_y,
|
|
(block->m_width - img->m_effwidth),
|
|
block->m_height);
|
|
general_heap.push(n_block);
|
|
}
|
|
if (img->m_effheight != block->m_height) {
|
|
ImageSprite::Block* n_block = new ImageSprite::Block(
|
|
block->m_x + block->m_width - img->m_effwidth,
|
|
block->m_y + img->m_effheight,
|
|
img->m_effwidth,
|
|
block->m_height - img->m_effheight);
|
|
right_heap.push(n_block);
|
|
}
|
|
}
|
|
}
|
|
|
|
right_flush.erase(best_iter);
|
|
delete block;
|
|
} else {
|
|
// Could be used by the no flush images
|
|
general_heap.push(block);
|
|
}
|
|
|
|
}
|
|
|
|
iter = right_flush.begin();
|
|
end = right_flush.end();
|
|
|
|
// Loop through the remaining right flush images
|
|
// put them below what we've already placed
|
|
for(; iter != end; iter++) {
|
|
ImageSprite::Image *img = *iter;
|
|
|
|
img->m_x = width - img->m_effwidth + img->m_padding[IMAGESPRITE_PAD_LEFT];
|
|
img->m_y = height + img->m_padding[IMAGESPRITE_PAD_TOP];
|
|
|
|
if (img->m_effwidth < width) {
|
|
ImageSprite::Block *block = new ImageSprite::Block(
|
|
0,
|
|
height,
|
|
width - img->m_effwidth,
|
|
img->m_effheight);
|
|
general_heap.push(block);
|
|
}
|
|
|
|
height += img->m_effheight;
|
|
}
|
|
|
|
// Now for the no flush images
|
|
while(!no_flush.empty()) {
|
|
while(!general_heap.empty() && !no_flush.empty()) {
|
|
ImageSprite::Block* block = general_heap.top();
|
|
general_heap.pop();
|
|
|
|
bool fit = false;
|
|
ImageList::iterator best_iter;
|
|
|
|
iter = no_flush.begin();
|
|
end = no_flush.end();
|
|
for(; iter != end; iter++) {
|
|
ImageSprite::Image *img = *iter;
|
|
|
|
if (img->m_effarea > block->m_area) {
|
|
break;
|
|
}
|
|
|
|
if (img->m_effheight > block->m_height ||
|
|
img->m_effwidth > block->m_width) {
|
|
continue;
|
|
}
|
|
|
|
fit = true;
|
|
best_iter = iter;
|
|
}
|
|
|
|
if (fit) {
|
|
ImageSprite::Image *img = *best_iter;
|
|
|
|
img->m_x = block->m_x + img->m_padding[IMAGESPRITE_PAD_LEFT];
|
|
img->m_y = block->m_y + img->m_padding[IMAGESPRITE_PAD_TOP];
|
|
|
|
if (img->m_effheight != block->m_height ||
|
|
img->m_effwidth != block->m_width) {
|
|
int vcut_b = img->m_effwidth * (block->m_height - img->m_effheight);
|
|
int vcut_l = block->m_height * (block->m_width - img->m_effwidth);
|
|
int hcut_b = block->m_width * (block->m_height - img->m_effheight);
|
|
int hcut_l = img->m_effheight * (block->m_width - img->m_effwidth);
|
|
int vcut_d = (vcut_b > vcut_l) ? vcut_b - vcut_l : vcut_l - vcut_b;
|
|
int hcut_d = (hcut_b > hcut_l) ? hcut_b - hcut_l : hcut_l - hcut_b;
|
|
if (hcut_d > vcut_d) {
|
|
// Horizontal cut is better than a vertical cut
|
|
if (img->m_effwidth != block->m_width) {
|
|
ImageSprite::Block* n_block = new ImageSprite::Block(
|
|
block->m_x + img->m_effwidth,
|
|
block->m_y,
|
|
(block->m_width - img->m_effwidth),
|
|
img->m_effheight);
|
|
general_heap.push(n_block);
|
|
}
|
|
if (img->m_effheight != block->m_height) {
|
|
ImageSprite::Block* n_block = new ImageSprite::Block(
|
|
block->m_x,
|
|
block->m_y + img->m_effheight,
|
|
block->m_width,
|
|
block->m_height - img->m_effheight);
|
|
general_heap.push(n_block);
|
|
}
|
|
} else {
|
|
// Vertical cut is better than a horizontal cut
|
|
if (img->m_effwidth != block->m_width) {
|
|
ImageSprite::Block* n_block = new ImageSprite::Block(
|
|
block->m_x + img->m_effwidth,
|
|
block->m_y,
|
|
(block->m_width - img->m_effwidth),
|
|
block->m_height);
|
|
general_heap.push(n_block);
|
|
}
|
|
if (img->m_effheight != block->m_height) {
|
|
ImageSprite::Block* n_block = new ImageSprite::Block(
|
|
block->m_x,
|
|
block->m_y + img->m_effheight,
|
|
img->m_effwidth,
|
|
block->m_height - img->m_effheight);
|
|
general_heap.push(n_block);
|
|
}
|
|
}
|
|
}
|
|
|
|
no_flush.erase(best_iter);
|
|
}
|
|
|
|
delete block;
|
|
}
|
|
|
|
while (general_heap.empty() && !no_flush.empty()) {
|
|
// We ran out of blocks, but there are still images left. Put it below!
|
|
// The largest is at the end of the list - lets use it
|
|
ImageSprite::Image *img = no_flush.back();
|
|
no_flush.pop_back();
|
|
|
|
img->m_x = img->m_padding[IMAGESPRITE_PAD_LEFT];
|
|
img->m_y = height + img->m_padding[IMAGESPRITE_PAD_TOP];
|
|
|
|
if (img->m_effwidth < width) {
|
|
ImageSprite::Block* n_block = new ImageSprite::Block(
|
|
img->m_effwidth,
|
|
height,
|
|
width - img->m_effwidth,
|
|
img->m_effheight);
|
|
general_heap.push(n_block);
|
|
}
|
|
|
|
height += img->m_effheight;
|
|
}
|
|
}
|
|
|
|
// Everything's been placed, now to put it in a nice array to return
|
|
iter = images.begin();
|
|
end = images.end();
|
|
|
|
Array image_map = Array::Create();
|
|
|
|
for(; iter != end; iter++) {
|
|
ImageSprite::Image *img = *iter;
|
|
ArrayInit map(11);
|
|
map.set(s_x, img->m_x);
|
|
map.set(s_y, img->m_y);
|
|
map.set(s_width, img->m_width);
|
|
map.set(s_height, img->m_height);
|
|
map.set(s_id, f_crc32(img->m_path));
|
|
map.set(s_padding_top, img->m_padding[IMAGESPRITE_PAD_TOP]);
|
|
map.set(s_padding_right, img->m_padding[IMAGESPRITE_PAD_RIGHT]);
|
|
map.set(s_padding_bottom, img->m_padding[IMAGESPRITE_PAD_BOTTOM]);
|
|
map.set(s_padding_left, img->m_padding[IMAGESPRITE_PAD_LEFT]);
|
|
map.set(s_flush_left, img->m_flush[IMAGESPRITE_FLUSH_LEFT]);
|
|
map.set(s_flush_right, img->m_flush[IMAGESPRITE_FLUSH_RIGHT]);
|
|
image_map.set(img->m_path, map.create());
|
|
}
|
|
|
|
m_width = width;
|
|
m_height = height;
|
|
|
|
m_mapping.set(s_images, image_map);
|
|
m_mapping.set(s_width, width);
|
|
m_mapping.set(s_height, height);
|
|
|
|
m_current = true;
|
|
}
|
|
|
|
String c_ImageSprite::t_output(CStrRef output_file /* = null_string*/,
|
|
CStrRef format /* = "png" */,
|
|
int32_t quality /* = 75 */) {
|
|
t_loadimages(true);
|
|
map();
|
|
|
|
if (m_width <= 0 || m_height <= 0) {
|
|
// Danger Will Robinson! Danger!
|
|
m_sprite_errors.append("Invalid sprite dimensions");
|
|
return null_string;
|
|
}
|
|
|
|
if (m_image == NULL) {
|
|
m_image = gdImageCreateTrueColor(m_width, m_height);
|
|
gdImageSaveAlpha(m_image, 1);
|
|
int transparent = gdImageColorAllocateAlpha(
|
|
m_image, 255, 255, 255, 127);
|
|
gdImageFill(m_image, 0, 0, transparent);
|
|
|
|
hphp_string_map<ImageSprite::Image*>::iterator iter;
|
|
hphp_string_map<ImageSprite::Image*>::iterator end;
|
|
|
|
iter = m_image_data.begin();
|
|
end = m_image_data.end();
|
|
|
|
for(; iter != end; iter++) {
|
|
ImageSprite::Image *img = iter->second;
|
|
if (img == NULL || img->m_image == NULL) {
|
|
continue;
|
|
} else if (!img->m_error) {
|
|
gdImageCopy(
|
|
m_image,
|
|
img->m_image,
|
|
img->m_x,
|
|
img->m_y,
|
|
0,
|
|
0,
|
|
img->m_width,
|
|
img->m_height);
|
|
}
|
|
}
|
|
}
|
|
|
|
String output_string = "Error";
|
|
int mem_size = 0;
|
|
void* mem_loc = NULL;
|
|
|
|
String format_l= f_strtolower(format);
|
|
|
|
if (same(format_l, "png")) {
|
|
mem_loc = gdImagePngPtr(m_image, &mem_size);
|
|
} else if (format_l == "jpg" || format_l == "jpeg") {
|
|
if (quality <= 0 || quality > 100) {
|
|
quality = 75;
|
|
}
|
|
mem_loc = gdImageJpegPtr(m_image, &mem_size, quality);
|
|
} else if (same(format_l, "gif")) {
|
|
mem_loc = gdImageGifPtr(m_image, &mem_size);
|
|
} else {
|
|
m_sprite_errors.append("Unsupported output format");
|
|
return null_string;
|
|
}
|
|
|
|
if (mem_loc != NULL) {
|
|
output_string = String((char*) mem_loc, mem_size, CopyString);
|
|
gdFree(mem_loc);
|
|
}
|
|
|
|
if (output_file.empty()) {
|
|
return output_string;
|
|
} else {
|
|
f_file_put_contents(output_file, output_string);
|
|
return null_string;
|
|
}
|
|
}
|
|
|
|
String c_ImageSprite::t_css(CStrRef css_namespace,
|
|
CStrRef sprite_file /* = null_string */,
|
|
CStrRef output_file /* = null_string */,
|
|
bool verbose /* = false */) {
|
|
t_loaddims(true);
|
|
map();
|
|
|
|
String output = "";
|
|
if (!sprite_file.empty()) {
|
|
if (verbose) {
|
|
output +=
|
|
String("/* Image dimensions: ") +
|
|
String(m_width) +
|
|
"x" +
|
|
String(m_height) +
|
|
" */\n";
|
|
}
|
|
output += String(".") + css_namespace + "{";
|
|
if (verbose) output += "\n ";
|
|
output += String("background-image: url('") + sprite_file + "');";
|
|
if (verbose) output += "\n ";
|
|
output += "background-repeat: no-repeat";
|
|
if (verbose) output += "\n";
|
|
output += "}";
|
|
}
|
|
|
|
for (ArrayIter iter(m_mapping); iter; ++iter) {
|
|
String path = iter.first().toString();
|
|
Array attr = iter.second().toArray();
|
|
|
|
if (verbose) {
|
|
output += String("/* File: ") + path + " */\n";
|
|
}
|
|
|
|
output += String(".") + css_namespace + "#i" + attr[s_id] + "{";
|
|
if (verbose) output += "\n ";
|
|
|
|
output += "background-position:";
|
|
if (more(attr[s_x], 0)) {
|
|
output += String("-") + attr[s_x] + "px";
|
|
} else {
|
|
output += "0";
|
|
}
|
|
if (more(attr[s_y], 0)) {
|
|
output += String(" -") + attr[s_y] + "px";
|
|
} else {
|
|
output += " 0";
|
|
}
|
|
output += ";";
|
|
if (sprite_file.empty()) {
|
|
if (verbose) output += "\n ";
|
|
output += "background-repeat: no-repeat;";
|
|
}
|
|
|
|
if (verbose) output += "\n ";
|
|
output += String("width:") + attr[s_width] + "px;";
|
|
if (verbose) output += "\n ";
|
|
output += String("height:") + attr[s_height] + "px";
|
|
if (verbose) output += "\n";
|
|
output += "}";
|
|
if (verbose) output += "\n";
|
|
}
|
|
|
|
if (output_file.empty()) {
|
|
return output;
|
|
} else {
|
|
f_file_put_contents(output_file, output);
|
|
return null_string;
|
|
}
|
|
}
|
|
|
|
Array c_ImageSprite::t_geterrors() {
|
|
Array ret = Array::Create();
|
|
ret.set(s_images, m_img_errors);
|
|
ret.set(s_sprite, m_sprite_errors);
|
|
return ret;
|
|
}
|
|
|
|
Array c_ImageSprite::t_mapping() {
|
|
t_loaddims(true);
|
|
map();
|
|
|
|
return m_mapping;
|
|
}
|
|
|
|
Variant c_ImageSprite::t___destruct() {
|
|
// Sic Trogdor on everything
|
|
if (m_image != NULL) {
|
|
delete m_image;
|
|
m_image = NULL;
|
|
}
|
|
|
|
// Destroy all the imagesprite_Image objects
|
|
hphp_string_map<ImageSprite::Image*>::iterator iter;
|
|
hphp_string_map<ImageSprite::Image*>::iterator end;
|
|
|
|
iter = m_image_data.begin();
|
|
end = m_image_data.end();
|
|
|
|
for(; iter != end; iter++) {
|
|
ImageSprite::Image *val = iter->second;
|
|
if (val != NULL) {
|
|
delete val;
|
|
// Cleared later
|
|
}
|
|
}
|
|
|
|
m_image_data.clear();
|
|
|
|
m_image_string_buffer = null_string;
|
|
m_image = NULL;
|
|
m_current = false;
|
|
m_width = 0;
|
|
m_height = 0;
|
|
m_mapping = Array::Create();
|
|
m_img_errors = Array::Create();
|
|
m_sprite_errors = Array::Create();
|
|
|
|
return uninit_null();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|