├── .gitignore ├── tests ├── CropTest.php ├── images │ ├── side.png │ └── test.png └── CropEntropyTest.php ├── docs ├── img │ ├── favicon.ico │ ├── loader.gif │ ├── icons │ │ ├── ok.png │ │ ├── class.png │ │ ├── file.gif │ │ ├── folder.gif │ │ ├── method.png │ │ ├── search.gif │ │ ├── constant.png │ │ ├── favicon.ico │ │ ├── file-php.png │ │ ├── function.png │ │ ├── property.png │ │ ├── variable.png │ │ ├── arrow_down.png │ │ ├── arrow_right.png │ │ ├── icon-th-big.png │ │ ├── interface.png │ │ ├── view_source.png │ │ ├── visibility_private.png │ │ ├── visibility_public.png │ │ ├── icon-folder-open-big.png │ │ ├── visibility_protected.png │ │ └── icon_template.svg │ ├── iviewer │ │ ├── grab.cur │ │ ├── hand.cur │ │ ├── iviewer.zoom_fit.png │ │ ├── iviewer.zoom_in.png │ │ ├── iviewer.zoom_in2.gif │ │ ├── iviewer.zoom_out.png │ │ ├── iviewer.rotate_left.png │ │ ├── iviewer.zoom_fit2.gif │ │ ├── iviewer.zoom_out2.gif │ │ ├── iviewer.zoom_zero.png │ │ ├── iviewer.zoom_zero2.gif │ │ └── iviewer.rotate_right.png │ ├── apple-touch-icon.png │ ├── apple-touch-icon-72x72.png │ ├── glyphicons-halflings.png │ ├── apple-touch-icon-114x114.png │ └── glyphicons-halflings-white.png ├── js │ ├── prettify │ │ ├── lang-go.js │ │ ├── lang-ml.js │ │ ├── lang-vb.js │ │ ├── lang-lua.js │ │ ├── lang-scala.js │ │ ├── lang-sql.js │ │ ├── lang-tex.js │ │ ├── lang-vhdl.js │ │ ├── lang-wiki.js │ │ ├── lang-apollo.js │ │ ├── lang-proto.js │ │ ├── lang-yaml.js │ │ ├── lang-hs.js │ │ ├── lang-lisp.js │ │ ├── lang-css.js │ │ ├── lang-n.js │ │ └── lang-clj.js │ ├── menu.js │ ├── jquery.mousewheel.min.js │ ├── sidebar.js │ ├── jquery.cookie.js │ ├── SVGPan.js │ ├── template.js │ ├── jquery.treeview.js │ └── jquery.splitter.js ├── phpdoc-cache-2e │ └── phpdoc-cache-settings.dat ├── css │ ├── prettify.css │ ├── jquery.iviewer.css │ ├── bootstrap-responsive.min.css │ └── template.css ├── phpdoc-cache-56 │ └── phpdoc-cache-file_ecc0bc6e0c3c88779fc339498217f0a4.dat ├── phpdoc-cache-10 │ └── phpdoc-cache-file_bcba727f46776285921554058dfc1886.dat ├── classes.svg ├── deprecated.html ├── graph_class.html ├── markers.html ├── index.html ├── phpdoc-cache-51 │ └── phpdoc-cache-file_28dd9fff2c7f32cbf7c5a9d47b836f35.dat ├── namespaces │ ├── stojg.crop.html │ ├── stojg.html │ └── default.html └── packages │ └── default.html ├── .scrutinizer.yml ├── composer.json ├── phpunit.xml ├── LICENCE ├── src └── stojg │ └── crop │ ├── CropCenter.php │ ├── CropFace.php │ ├── Crop.php │ ├── CropBalanced.php │ └── CropEntropy.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | docs/phpdoc-cache* -------------------------------------------------------------------------------- /tests/CropTest.php: -------------------------------------------------------------------------------- 1 | ?|]+/,a,":|>?"],["dec",/^%(?:YAML|TAG)[^\n\r#]+/,a,"%"],["typ",/^&\S+/,a,"&"],["typ",/^!\S*/,a,"!"],["str",/^"(?:[^"\\]|\\.)*(?:"|$)/,a,'"'],["str",/^'(?:[^']|'')*(?:'|$)/,a,"'"],["com",/^#[^\n\r]*/,a,"#"],["pln",/^\s+/,a," \t\r\n"]],[["dec",/^(?:---|\.\.\.)(?:[\n\r]|$)/],["pun",/^-/],["kwd",/^\w+:[\n\r ]/],["pln",/^\w+/]]),["yaml","yml"]); 3 | -------------------------------------------------------------------------------- /docs/js/prettify/lang-hs.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t-\r ]+/,null,"\t\n \r "],["str",/^"(?:[^\n\f\r"\\]|\\[\S\s])*(?:"|$)/,null,'"'],["str",/^'(?:[^\n\f\r'\\]|\\[^&])'?/,null,"'"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)/i,null,"0123456789"]],[["com",/^(?:--+[^\n\f\r]*|{-(?:[^-]|-+[^}-])*-})/],["kwd",/^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^\d'A-Za-z]|$)/, 2 | null],["pln",/^(?:[A-Z][\w']*\.)*[A-Za-z][\w']*/],["pun",/^[^\d\t-\r "'A-Za-z]+/]]),["hs"]); 3 | -------------------------------------------------------------------------------- /docs/css/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stojg/crop", 3 | "type": "library", 4 | "description": "Image cropping classes", 5 | "keywords": ["image"], 6 | "homepage": "http://github.com/stojg/crop", 7 | "license": "BSD-2", 8 | "authors": [ 9 | { 10 | "name": "Stig Lindqvist", 11 | "email": "stojg.lindqvist@gmail.com", 12 | "homepage": "https://stojg.se/" 13 | }, 14 | { 15 | "name": "Julien Deniau jdeniau", 16 | "email": "julien.deniau@gmail.com", 17 | "homepage": "http://www.mapado.com" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=5.3.0" 22 | }, 23 | "autoload": { 24 | "psr-0": { "stojg\\crop": "src/" } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/js/prettify/lang-lisp.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,a,"("],["clo",/^\)+/,a,")"],["com",/^;[^\n\r]*/,a,";"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \xa0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/,a], 3 | ["lit",/^[+-]?(?:[#0]x[\da-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[de][+-]?\d+)?)/i],["lit",/^'(?:-*(?:\w|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?)?/],["pln",/^-*(?:[_a-z]|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?/i],["pun",/^[^\w\t\n\r "'-);\\\xa0]+/]]),["cl","el","lisp","scm"]); 4 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | tests 6 | 7 | 8 | 9 | 10 | src 11 | 12 | tests/ 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/js/menu.js: -------------------------------------------------------------------------------- 1 | var timeout = 500; 2 | var closetimer = 0; 3 | var ddmenuitem = 0; 4 | 5 | function menu_open() { 6 | menu_canceltimer(); 7 | menu_close(); 8 | ddmenuitem = $(this).find('ul').css('visibility', 'visible'); 9 | } 10 | 11 | function menu_close() { 12 | if (ddmenuitem) ddmenuitem.css('visibility', 'hidden'); 13 | } 14 | 15 | function menu_timer() { 16 | closetimer = window.setTimeout(menu_close, timeout); 17 | } 18 | 19 | function menu_canceltimer() { 20 | if (closetimer) { 21 | window.clearTimeout(closetimer); 22 | closetimer = null; 23 | } 24 | } 25 | 26 | $(document).ready(function() { 27 | $('#file-nav > li').bind('mouseover', menu_open); 28 | $('#file-nav > li').bind('mouseout', menu_timer); 29 | }); 30 | 31 | document.onclick = menu_close; -------------------------------------------------------------------------------- /docs/js/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/phpdoc-cache-56/phpdoc-cache-file_ecc0bc6e0c3c88779fc339498217f0a4.dat: -------------------------------------------------------------------------------- 1 | O:39:"phpDocumentor\Descriptor\FileDescriptor":20:{s:7:"*hash";s:32:"ce407ff5715c837d02b1aba7975bf512";s:9:"*source";s:6:"]*(?:#>|$)/,a],["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,a],["com",/^\/\/[^\n\r]*/,a],["com",/^\/\*[\S\s]*?(?:\*\/|$)/, 3 | a],["kwd",/^(?:abstract|and|as|base|catch|class|def|delegate|enum|event|extern|false|finally|fun|implements|interface|internal|is|macro|match|matches|module|mutable|namespace|new|null|out|override|params|partial|private|protected|public|ref|sealed|static|struct|syntax|this|throw|true|try|type|typeof|using|variant|virtual|volatile|when|where|with|assert|assert2|async|break|checked|continue|do|else|ensures|for|foreach|if|late|lock|new|nolate|otherwise|regexp|repeat|requires|return|surroundwith|unchecked|unless|using|while|yield)\b/, 4 | a],["typ",/^(?:array|bool|byte|char|decimal|double|float|int|list|long|object|sbyte|short|string|ulong|uint|ufloat|ulong|ushort|void)\b/,a],["lit",/^@[$_a-z][\w$@]*/i,a],["typ",/^@[A-Z]+[a-z][\w$@]*/,a],["pln",/^'?[$_a-z][\w$@]*/i,a],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,a,"0123456789"],["pun",/^.[^\s\w"-$'./@`]*/,a]]),["n","nemerle"]); 5 | -------------------------------------------------------------------------------- /docs/js/jquery.mousewheel.min.js: -------------------------------------------------------------------------------- 1 | /*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net) 2 | * Licensed under the MIT License (LICENSE.txt). 3 | * 4 | * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. 5 | * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. 6 | * Thanks to: Seamus Leahy for adding deltaX and deltaY 7 | * 8 | * Version: 3.0.6 9 | * 10 | * Requires: 1.2.2+ 11 | */ 12 | (function(d){function e(a){var b=a||window.event,c=[].slice.call(arguments,1),f=0,e=0,g=0,a=d.event.fix(b);a.type="mousewheel";b.wheelDelta&&(f=b.wheelDelta/120);b.detail&&(f=-b.detail/3);g=f;b.axis!==void 0&&b.axis===b.HORIZONTAL_AXIS&&(g=0,e=-1*f);b.wheelDeltaY!==void 0&&(g=b.wheelDeltaY/120);b.wheelDeltaX!==void 0&&(e=-1*b.wheelDeltaX/120);c.unshift(a,f,e,g);return(d.event.dispatch||d.event.handle).apply(this,c)}var c=["DOMMouseScroll","mousewheel"];if(d.event.fixHooks)for(var h=c.length;h;)d.event.fixHooks[c[--h]]= 13 | d.event.mouseHooks;d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=c.length;a;)this.addEventListener(c[--a],e,false);else this.onmousewheel=e},teardown:function(){if(this.removeEventListener)for(var a=c.length;a;)this.removeEventListener(c[--a],e,false);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery); -------------------------------------------------------------------------------- /src/stojg/crop/CropCenter.php: -------------------------------------------------------------------------------- 1 | getCenterOffset($original, $targetWidth, $targetHeight); 28 | } 29 | 30 | /** 31 | * Get the cropping offset for the image based on the center of the image 32 | * 33 | * @param \Imagick $image 34 | * @param int $targetWidth 35 | * @param int $targetHeight 36 | * @return array 37 | */ 38 | protected function getCenterOffset(\Imagick $image, $targetWidth, $targetHeight) 39 | { 40 | $size = $image->getImageGeometry(); 41 | $originalWidth = $size['width']; 42 | $originalHeight = $size['height']; 43 | $goalX = (int) (($originalWidth-$targetWidth)/2); 44 | $goalY = (int) (($originalHeight-$targetHeight)/2); 45 | 46 | return array('x' => $goalX, 'y' => $goalY); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/js/sidebar.js: -------------------------------------------------------------------------------- 1 | jQuery.expr[':'].Contains = function(a, i, m) { 2 | return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0; 3 | }; 4 | 5 | $(function() { 6 | $("#sidebar-nav").accordion({ 7 | autoHeight: false, 8 | navigation: true, 9 | collapsible: true 10 | }).accordion("activate", false) 11 | .find('a.link').unbind('click').click( 12 | function(ev) { 13 | ev.cancelBubble = true; // IE 14 | if (ev.stopPropagation) { 15 | ev.stopPropagation(); // the rest 16 | } 17 | 18 | return true; 19 | }).prev().prev().remove(); 20 | 21 | $("#sidebar-nav>h3").click(function() { 22 | if ($(this).attr('initialized') == 'true') return; 23 | 24 | $(this).next().find(".sidebar-nav-tree").treeview({ 25 | collapsed: true, 26 | persist: "cookie" 27 | }); 28 | $(this).attr('initialized', true); 29 | }); 30 | }); 31 | 32 | function tree_search(input) { 33 | treeview = $(input).parent().parent().next(); 34 | 35 | // Expand all items 36 | treeview.find('.expandable-hitarea').click(); 37 | 38 | // make all items visible again 39 | treeview.find('li:hidden').show(); 40 | 41 | // hide all items that do not match the given search criteria 42 | if ($(input).val()) { 43 | treeview.find('li').not(':has(a:Contains(' + $(input).val() + '))').hide(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /docs/js/prettify/lang-clj.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2011 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | var a=null; 17 | PR.registerLangHandler(PR.createSimpleLexer([["opn",/^[([{]+/,a,"([{"],["clo",/^[)\]}]+/,a,")]}"],["com",/^;[^\n\r]*/,a,";"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \xa0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:def|if|do|let|quote|var|fn|loop|recur|throw|try|monitor-enter|monitor-exit|defmacro|defn|defn-|macroexpand|macroexpand-1|for|doseq|dosync|dotimes|and|or|when|not|assert|doto|proxy|defstruct|first|rest|cons|defprotocol|deftype|defrecord|reify|defmulti|defmethod|meta|with-meta|ns|in-ns|create-ns|import|intern|refer|alias|namespace|resolve|ref|deref|refset|new|set!|memfn|to-array|into-array|aset|gen-class|reduce|map|filter|find|nil?|empty?|hash-map|hash-set|vec|vector|seq|flatten|reverse|assoc|dissoc|list|list?|disj|get|union|difference|intersection|extend|extend-type|extend-protocol|prn)\b/,a], 18 | ["typ",/^:[\dA-Za-z-]+/]]),["clj"]); 19 | -------------------------------------------------------------------------------- /docs/css/jquery.iviewer.css: -------------------------------------------------------------------------------- 1 | .iviewer_common { 2 | position:absolute; 3 | bottom:10px; 4 | border: 1px solid #000; 5 | height: 28px; 6 | z-index: 5000; 7 | } 8 | 9 | .iviewer_cursor { 10 | cursor: url(../img/iviewer/hand.cur) 6 8, pointer; 11 | } 12 | 13 | .iviewer_drag_cursor { 14 | cursor: url(../img/iviewer/grab.cur) 6 8, pointer; 15 | } 16 | 17 | .iviewer_button { 18 | width: 28px; 19 | cursor: pointer; 20 | background-position: center center; 21 | background-repeat: no-repeat; 22 | } 23 | 24 | .iviewer_zoom_in { 25 | left: 20px; 26 | background: url(../img/iviewer/iviewer.zoom_in.png); 27 | } 28 | 29 | .iviewer_zoom_out { 30 | left: 55px; 31 | background: url(../img/iviewer/iviewer.zoom_out.png); 32 | } 33 | 34 | .iviewer_zoom_zero { 35 | left: 90px; 36 | background: url(../img/iviewer/iviewer.zoom_zero.png); 37 | } 38 | 39 | .iviewer_zoom_fit { 40 | left: 125px; 41 | background: url(../img/iviewer/iviewer.zoom_fit.png); 42 | } 43 | 44 | .iviewer_zoom_status { 45 | left: 160px; 46 | font: 1em/28px Sans; 47 | color: #000; 48 | background-color: #fff; 49 | text-align: center; 50 | width: 60px; 51 | } 52 | 53 | .iviewer_rotate_left { 54 | left: 227px; 55 | background: #fff url(../img/iviewer/iviewer.rotate_left.png) center center no-repeat; 56 | } 57 | 58 | .iviewer_rotate_right { 59 | left: 262px; 60 | background: #fff url(../img/iviewer/iviewer.rotate_right.png) center center no-repeat; 61 | } 62 | 63 | .viewer 64 | { 65 | width: 100%; 66 | height: 500px; 67 | position: relative; 68 | background: transparent url('../img/loader.gif') no-repeat center center; 69 | } 70 | 71 | .viewer img 72 | { 73 | max-width: none; 74 | } 75 | 76 | .wrapper 77 | { 78 | overflow: hidden; 79 | } 80 | 81 | .iviewer_common 82 | { 83 | border: 0; 84 | bottom: auto; 85 | top: 10px; 86 | } 87 | 88 | .iviewer_zoom_status 89 | { 90 | border: 1px solid black; 91 | } 92 | -------------------------------------------------------------------------------- /tests/CropEntropyTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('The imagick extension is not available.'); 22 | return; 23 | } 24 | $this->tempDir = sys_get_temp_dir().DIRECTORY_SEPARATOR.'croptest'; 25 | 26 | if(file_exists($this->tempDir)) { 27 | $this->cleanup(); 28 | } 29 | 30 | if(!mkdir($this->tempDir)) { 31 | $this->markTestSkipped('Can\'t create temp directory '. $this->tempDir .' skipping test'); 32 | } 33 | } 34 | 35 | /** 36 | * 37 | */ 38 | public function tearDown() { 39 | $this->cleanup(); 40 | } 41 | 42 | public function testEntropy() { 43 | $center = new CropEntropy(__DIR__ . self::EXAMPLE_IMAGE); 44 | $croppedImage = $center->resizeAndCrop(200, 200); 45 | $croppedImage->writeimage($this->tempDir.'/entropy-test.png'); 46 | } 47 | 48 | public function testEntropyWithPreviusImagick() { 49 | $image = new Imagick(__DIR__ . self::EXAMPLE_IMAGE); 50 | 51 | $center = new CropEntropy(); 52 | $center->setImage($image); 53 | 54 | $croppedImage = $center->resizeAndCrop(200, 200); 55 | 56 | $this->assertSame($image, $croppedImage); 57 | 58 | $croppedImage->writeimage($this->tempDir.'/entropy-test.png'); 59 | } 60 | 61 | private function cleanup() { 62 | $testFiles = glob($this->tempDir.DIRECTORY_SEPARATOR.'*'); 63 | foreach($testFiles as $file) { 64 | unlink($file); 65 | } 66 | rmdir($this->tempDir); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crop 2 | 3 | This is a small set of image croppers that I created for testing automated cropping. 4 | 5 | [![Build Status](https://secure.travis-ci.org/stojg/crop.png?branch=master)](http://travis-ci.org/stojg/crop) 6 | [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/stojg/crop/badges/quality-score.png?s=3fb9e495961dba3ae96d96d7632236ef0227d3cb)](https://scrutinizer-ci.com/g/stojg/crop/) 7 | [![Code Coverage](https://scrutinizer-ci.com/g/stojg/crop/badges/coverage.png?s=cbd5ef15ca6f4f886bbcd88d45eb79cc861368cc)](https://scrutinizer-ci.com/g/stojg/crop/) 8 | 9 | ## Requirements 10 | 11 | - PHP 5.3 with Imagick extension 12 | 13 | ## Description 14 | 15 | This little project includes three functional image cropers: 16 | 17 | ### CropCenter 18 | 19 | This is the most basic of cropping techniques: 20 | 21 | 1. Find the exact center of the image 22 | 2. Trim any edges that is bigger than the targetWidth and targetHeight 23 | 24 | ### CropEntropy 25 | 26 | This class finds the a position in the picture with the most "energy" in it. Energy (or entropy) in 27 | images are defined by 'edginess' in the image. For example a image of the sky have low edginess and 28 | an image of an anthill has very high edginess. 29 | 30 | Energy is in this case calculated like this 31 | 32 | 1. Take the image and turn it into black and white 33 | 2. Run a edge filter so that we're left with only edges. 34 | 3. Find a piece in the picture that has the highest entropy (i.e. most edges) 35 | 4. Return coordinates that makes sure that this piece of the picture is not cropped 'away' 36 | 37 | ### CropBalanced 38 | 39 | Crop balanced is a variant of CropEntropy where I tried to the cropping a bit more balanced. 40 | 41 | 1. Dividing the image into four equally squares 42 | 2. Find the most energetic point per square 43 | 3. Finding the images weighted mean interest point for all squares 44 | 45 | ### CropFace 46 | 47 | Crop face uses [PHP Facedetect Extension](http://www.xarg.org/project/php-facedetect/) (which uses OpenCV). 48 | 49 | In details, the FaceCrop uses Entropy Crop but puts blocking "limits" on the faces. 50 | If the program faces two limits, we let the entropy decide the best crop. 51 | 52 | 53 | ## Usage 54 | 55 | $center = new \stojg\crop\CropCenter($filepath); 56 | $croppedImage = $center->resizeAndCrop($width, $height); 57 | $croppedImage->writeimage('assets/thumbs/cropped-center.jpg'); 58 | -------------------------------------------------------------------------------- /docs/phpdoc-cache-10/phpdoc-cache-file_bcba727f46776285921554058dfc1886.dat: -------------------------------------------------------------------------------- 1 | O:39:"phpDocumentor\Descriptor\FileDescriptor":20:{s:7:"*hash";s:32:"61267d3c38aefbb5756a356ddc64ab8d";s:9:"*source";s:321:" 2 | 3 | 4 | 21 | 23 | 42 | 49 | 50 | 52 | 53 | 55 | image/svg+xml 56 | 58 | 59 | 60 | 61 | 62 | 67 | 73 | 79 | Co 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/stojg/crop/CropFace.php: -------------------------------------------------------------------------------- 1 | imagePath = $imagePath; 42 | parent::__construct($imagePath); 43 | } 44 | 45 | /** 46 | * getFaceList get faces positions and sizes 47 | * 48 | * @access protected 49 | * @return array 50 | */ 51 | protected function getFaceList() 52 | { 53 | if (!function_exists('face_detect')) { 54 | $msg = 'PHP Facedetect extension must be installed. 55 | See http://www.xarg.org/project/php-facedetect/ for more details'; 56 | throw new \Exception($msg); 57 | } 58 | 59 | $faceList = $this->getFaceListFromClassifier(self::CLASSIFIER_FACE); 60 | 61 | $profileList = $this->getFaceListFromClassifier(self::CLASSIFIER_PROFILE); 62 | 63 | $faceList = array_merge($faceList, $profileList); 64 | 65 | return $faceList; 66 | } 67 | 68 | /** 69 | * getFaceListFromClassifier 70 | * 71 | * @param string $classifier 72 | * @access protected 73 | * @return array 74 | */ 75 | protected function getFaceListFromClassifier($classifier) 76 | { 77 | $faceList = face_detect($this->imagePath, __DIR__ . $classifier); 78 | 79 | return $faceList; 80 | } 81 | 82 | /** 83 | * getSafeZoneList 84 | * 85 | * @access private 86 | * @return array 87 | */ 88 | protected function getSafeZoneList() 89 | { 90 | if (!isset($this->safeZoneList)) { 91 | $this->safeZoneList = []; 92 | } 93 | // the local key is the current image width-height 94 | $key = $this->originalImage->getImageWidth() . '-' . $this->originalImage->getImageHeight(); 95 | 96 | if (!isset($this->safeZoneList[$key])) { 97 | $faceList = $this->getFaceList(); 98 | 99 | // getFaceList works on the main image, so we use a ratio between main/current image 100 | $xRatio = $this->getBaseDimension('width') / $this->originalImage->getImageWidth(); 101 | $yRatio = $this->getBaseDimension('height') / $this->originalImage->getImageHeight(); 102 | 103 | $safeZoneList = array(); 104 | foreach ($faceList as $face) { 105 | $hw = ceil($face['w'] / 2); 106 | $hh = ceil($face['h'] / 2); 107 | $safeZone = array( 108 | 'left' => $face['x'] - $hw, 109 | 'right' => $face['x'] + $face['w'] + $hw, 110 | 'top' => $face['y'] - $hh, 111 | 'bottom' => $face['y'] + $face['h'] + $hh 112 | ); 113 | 114 | $safeZoneList[] = [ 115 | 'left' => round($safeZone['left'] / $xRatio), 116 | 'right' => round($safeZone['right'] / $xRatio), 117 | 'top' => round($safeZone['top'] / $yRatio), 118 | 'bottom' => round($safeZone['bottom'] / $yRatio), 119 | ]; 120 | } 121 | $this->safeZoneList[$key] = $safeZoneList; 122 | } 123 | 124 | return $this->safeZoneList[$key]; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /docs/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cookie plugin 3 | * 4 | * Copyright (c) 2006 Klaus Hartl (stilbuero.de) 5 | * Dual licensed under the MIT and GPL licenses: 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * http://www.gnu.org/licenses/gpl.html 8 | * 9 | */ 10 | 11 | /** 12 | * Create a cookie with the given name and value and other optional parameters. 13 | * 14 | * @example $.cookie('the_cookie', 'the_value'); 15 | * @desc Set the value of a cookie. 16 | * @example $.cookie('the_cookie', 'the_value', {expires: 7, path: '/', domain: 'jquery.com', secure: true}); 17 | * @desc Create a cookie with all available options. 18 | * @example $.cookie('the_cookie', 'the_value'); 19 | * @desc Create a session cookie. 20 | * @example $.cookie('the_cookie', null); 21 | * @desc Delete a cookie by passing null as value. 22 | * 23 | * @param String name The name of the cookie. 24 | * @param String value The value of the cookie. 25 | * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. 26 | * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. 27 | * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. 28 | * If set to null or omitted, the cookie will be a session cookie and will not be retained 29 | * when the the browser exits. 30 | * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). 31 | * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). 32 | * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will 33 | * require a secure protocol (like HTTPS). 34 | * @type undefined 35 | * 36 | * @name $.cookie 37 | * @cat Plugins/Cookie 38 | * @author Klaus Hartl/klaus.hartl@stilbuero.de 39 | */ 40 | 41 | /** 42 | * Get the value of a cookie with the given name. 43 | * 44 | * @example $.cookie('the_cookie'); 45 | * @desc Get the value of a cookie. 46 | * 47 | * @param String name The name of the cookie. 48 | * @return The value of the cookie. 49 | * @type String 50 | * 51 | * @name $.cookie 52 | * @cat Plugins/Cookie 53 | * @author Klaus Hartl/klaus.hartl@stilbuero.de 54 | */ 55 | jQuery.cookie = function(name, value, options) 56 | { 57 | if (typeof value != 'undefined') 58 | { // name and value given, set cookie 59 | options = options || {}; 60 | if (value === null) 61 | { 62 | value = ''; 63 | options.expires = -1; 64 | } 65 | var expires = ''; 66 | if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) 67 | { 68 | var date; 69 | if (typeof options.expires == 'number') 70 | { 71 | date = new Date(); 72 | date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); 73 | } 74 | else 75 | { 76 | date = options.expires; 77 | } 78 | expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE 79 | } 80 | var path = options.path ? '; path=' + options.path : ''; 81 | var domain = options.domain ? '; domain=' + options.domain : ''; 82 | var secure = options.secure ? '; secure' : ''; 83 | document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); 84 | } 85 | else 86 | { // only name given, get cookie 87 | var cookieValue = null; 88 | if (document.cookie && document.cookie != '') 89 | { 90 | var cookies = document.cookie.split(';'); 91 | for (var i = 0; i < cookies.length; i++) 92 | { 93 | var cookie = jQuery.trim(cookies[i]); 94 | // Does this cookie string begin with the name we want? 95 | if (cookie.substring(0, name.length + 1) == (name + '=')) 96 | { 97 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 98 | break; 99 | } 100 | } 101 | } 102 | return cookieValue; 103 | } 104 | }; -------------------------------------------------------------------------------- /docs/classes.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | G 11 | 12 | cluster_Global 13 | 14 | \ 15 | 16 | cluster_\stojg 17 | 18 | stojg 19 | 20 | cluster_\stojg\crop 21 | 22 | crop 23 | 24 | 25 | \\stojg\\crop\\CropFace 26 | 27 | CropFace 28 | 29 | 30 | \\stojg\\crop\\CropEntropy 31 | 32 | CropEntropy 33 | 34 | 35 | \\stojg\\crop\\CropFace->\\stojg\\crop\\CropEntropy 36 | 37 | 38 | 39 | 40 | \\stojg\\crop\\CropCenter 41 | 42 | CropCenter 43 | 44 | 45 | \\stojg\\crop\\Crop 46 | 47 | «abstract» 48 | Crop 49 | 50 | 51 | \\stojg\\crop\\CropCenter->\\stojg\\crop\\Crop 52 | 53 | 54 | 55 | 56 | \\stojg\\crop\\CropBalanced 57 | 58 | CropBalanced 59 | 60 | 61 | \\stojg\\crop\\CropBalanced->\\stojg\\crop\\Crop 62 | 63 | 64 | 65 | 66 | \\stojg\\crop\\CropEntropy->\\stojg\\crop\\Crop 67 | 68 | 69 | 70 | 71 | \\ClassEntropyTest 72 | 73 | ClassEntropyTest 74 | 75 | 76 | \\PHPUnit_Framework_TestCase 77 | 78 | \PHPUnit_Framework_TestCase 79 | 80 | 81 | \\ClassEntropyTest->\\PHPUnit_Framework_TestCase 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /docs/deprecated.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | » Deprecated elements 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 90 | 91 |
92 | 97 | 98 | 99 |
100 | 101 |
102 | 105 |
106 | 107 |
108 | 112 | 113 |
114 |
No deprecated elements have been found in this project.
115 |
116 |
117 |
118 |
119 | 120 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /docs/graph_class.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | API Documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 89 | 90 |
91 | 96 | 97 | 98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | 106 | 107 | 119 |
120 | 121 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/stojg/crop/Crop.php: -------------------------------------------------------------------------------- 1 | setImage(new \Imagick($imagePath)); 62 | } 63 | } 64 | 65 | /** 66 | * Sets the object Image to be croped 67 | * 68 | * @param \Imagick $image 69 | * @return null 70 | */ 71 | public function setImage(\Imagick $image) 72 | { 73 | $this->originalImage = $image; 74 | 75 | // set base image dimensions 76 | $this->setBaseDimensions( 77 | $this->originalImage->getImageWidth(), 78 | $this->originalImage->getImageHeight() 79 | ); 80 | } 81 | 82 | /** 83 | * Get the area in pixels for this image 84 | * 85 | * @param \Imagick $image 86 | * @return int 87 | */ 88 | protected function area(\Imagick $image) 89 | { 90 | $size = $image->getImageGeometry(); 91 | 92 | return $size['height'] * $size['width']; 93 | } 94 | 95 | /** 96 | * Resize and crop the image so it dimensions matches $targetWidth and $targetHeight 97 | * 98 | * @param int $targetWidth 99 | * @param int $targetHeight 100 | * @return boolean|\Imagick 101 | */ 102 | public function resizeAndCrop($targetWidth, $targetHeight) 103 | { 104 | // First get the size that we can use to safely trim down the image without cropping any sides 105 | $crop = $this->getSafeResizeOffset($this->originalImage, $targetWidth, $targetHeight); 106 | // Resize the image 107 | $this->originalImage->resizeImage($crop['width'], $crop['height'], \Imagick::FILTER_CUBIC, .5); 108 | // Get the offset for cropping the image further 109 | $offset = $this->getSpecialOffset($this->originalImage, $targetWidth, $targetHeight); 110 | // Crop the image 111 | $this->originalImage->cropImage($targetWidth, $targetHeight, $offset['x'], $offset['y']); 112 | 113 | return $this->originalImage; 114 | } 115 | 116 | /** 117 | * Returns width and height for resizing the image, keeping the aspect ratio 118 | * and allow the image to be larger than either the width or height 119 | * 120 | * @param \Imagick $image 121 | * @param int $targetWidth 122 | * @param int $targetHeight 123 | * @return array 124 | */ 125 | protected function getSafeResizeOffset(\Imagick $image, $targetWidth, $targetHeight) 126 | { 127 | $source = $image->getImageGeometry(); 128 | if (($source['width'] / $source['height']) < ($targetWidth / $targetHeight)) { 129 | $scale = $source['width'] / $targetWidth; 130 | } else { 131 | $scale = $source['height'] / $targetHeight; 132 | } 133 | 134 | return array('width' => (int) ($source['width'] / $scale), 'height' => (int) ($source['height'] / $scale)); 135 | } 136 | 137 | /** 138 | * Returns a YUV weighted greyscale value 139 | * 140 | * @param int $r 141 | * @param int $g 142 | * @param int $b 143 | * @return int 144 | * @see http://en.wikipedia.org/wiki/YUV 145 | */ 146 | protected function rgb2bw($r, $g, $b) 147 | { 148 | return ($r*0.299)+($g*0.587)+($b*0.114); 149 | } 150 | 151 | /** 152 | * 153 | * @param array $histogram - a value[count] array 154 | * @param int $area 155 | * @return float 156 | */ 157 | protected function getEntropy($histogram, $area) 158 | { 159 | $value = 0.0; 160 | 161 | $colors = count($histogram); 162 | for ($idx = 0; $idx < $colors; $idx++) { 163 | // calculates the percentage of pixels having this color value 164 | $p = $histogram[$idx]->getColorCount() / $area; 165 | // A common way of representing entropy in scalar 166 | $value = $value + $p * log($p, 2); 167 | } 168 | // $value is always 0.0 or negative, so transform into positive scalar value 169 | return -$value; 170 | } 171 | 172 | /** 173 | * setBaseDimensions 174 | * 175 | * @param int $width 176 | * @param int $height 177 | * @access protected 178 | * @return $this 179 | */ 180 | protected function setBaseDimensions($width, $height) 181 | { 182 | $this->baseDimension = ['width' => $width, 'height' => $height]; 183 | 184 | return $this; 185 | } 186 | 187 | /** 188 | * getBaseDimension 189 | * 190 | * @param string $key width|height 191 | * @access protected 192 | * @return int 193 | */ 194 | protected function getBaseDimension($key) 195 | { 196 | if (isset($this->baseDimension)) { 197 | return $this->baseDimension[$key]; 198 | } elseif ($key == 'width') { 199 | return $this->originalImage->getImageWidth(); 200 | } else { 201 | return $this->originalImage->getImageHeight(); 202 | } 203 | } 204 | 205 | /** 206 | * get special offset for class 207 | * 208 | * @param \Imagick $original 209 | * @param int $targetWidth 210 | * @param int $targetHeight 211 | * @return array 212 | */ 213 | abstract protected function getSpecialOffset(\Imagick $original, $targetWidth, $targetHeight); 214 | } 215 | -------------------------------------------------------------------------------- /src/stojg/crop/CropBalanced.php: -------------------------------------------------------------------------------- 1 | getRandomEdgeOffset($original, $targetWidth, $targetHeight); 30 | } 31 | 32 | /** 33 | * 34 | * @param \Imagick $original 35 | * @param int $targetWidth 36 | * @param int $targetHeight 37 | * @return array 38 | */ 39 | protected function getRandomEdgeOffset(\Imagick $original, $targetWidth, $targetHeight) 40 | { 41 | $measureImage = clone($original); 42 | // Enhance edges with radius 1 43 | $measureImage->edgeimage(1); 44 | // Turn image into a grayscale 45 | $measureImage->modulateImage(100, 0, 100); 46 | // Turn everything darker than this to pitch black 47 | $measureImage->blackThresholdImage("#101010"); 48 | // Get the calculated offset for cropping 49 | return $this->getOffsetBalanced($targetWidth, $targetHeight); 50 | } 51 | 52 | /** 53 | * 54 | * @param int $targetWidth 55 | * @param int $targetHeight 56 | * @return array 57 | * @todo refactor so it follows DRY 58 | */ 59 | public function getOffsetBalanced($targetWidth, $targetHeight) 60 | { 61 | 62 | $size = $this->originalImage->getImageGeometry(); 63 | 64 | $points = array(); 65 | 66 | $halfWidth = ceil($size['width']/2); 67 | $halfHeight = ceil($size['height']/2); 68 | 69 | // First quadrant 70 | $clone = clone($this->originalImage); 71 | $clone->cropimage($halfWidth, $halfHeight, 0, 0); 72 | $point = $this->getHighestEnergyPoint($clone); 73 | $points[] = array('x' => $point['x'], 'y' => $point['y'], 'sum' => $point['sum']); 74 | 75 | // Second quadrant 76 | $clone = clone($this->originalImage); 77 | $clone->cropimage($halfWidth, $halfHeight, $halfWidth, 0); 78 | $point = $this->getHighestEnergyPoint($clone); 79 | $points[] = array('x' => $point['x']+$halfWidth, 'y' => $point['y'], 'sum' => $point['sum']); 80 | 81 | // Third quadrant 82 | $clone = clone($this->originalImage); 83 | $clone->cropimage($halfWidth, $halfHeight, 0, $halfHeight); 84 | $point = $this->getHighestEnergyPoint($clone); 85 | $points[] = array('x' => $point['x'], 'y' => $point['y']+$halfHeight, 'sum' => $point['sum']); 86 | 87 | // Fourth quadrant 88 | $clone = clone($this->originalImage); 89 | $clone->cropimage($halfWidth, $halfHeight, $halfWidth, $halfHeight); 90 | $point = $point = $this->getHighestEnergyPoint($clone); 91 | $points[] = array('x' => $point['x']+$halfWidth, 'y' => $point['y']+$halfHeight, 'sum' => $point['sum']); 92 | 93 | // get the totalt sum value so we can find out a mean center point 94 | $totalWeight = array_reduce( 95 | $points, 96 | function ($result, $array) { 97 | return $result + $array['sum']; 98 | } 99 | ); 100 | 101 | $centerX = 0; 102 | $centerY = 0; 103 | 104 | // Calulate the mean weighted center x and y 105 | $totalPoints = count($points); 106 | for ($idx=0; $idx < $totalPoints; $idx++) { 107 | $centerX += $points[$idx]['x'] * ($points[$idx]['sum'] / $totalWeight); 108 | $centerY += $points[$idx]['y'] * ($points[$idx]['sum'] / $totalWeight); 109 | } 110 | 111 | // From the weighted center point to the topleft corner of the crop would be 112 | $topleftX = max(0, ($centerX - $targetWidth / 2)); 113 | $topleftY = max(0, ($centerY - $targetHeight / 2)); 114 | 115 | // If we don't have enough width for the crop, back up $topleftX until 116 | // we can make the image meet $targetWidth 117 | if ($topleftX + $targetWidth > $size['width']) { 118 | $topleftX -= ($topleftX+$targetWidth) - $size['width']; 119 | } 120 | // If we don't have enough height for the crop, back up $topleftY until 121 | // we can make the image meet $targetHeight 122 | if ($topleftY+$targetHeight > $size['height']) { 123 | $topleftY -= ($topleftY+$targetHeight) - $size['height']; 124 | } 125 | 126 | return array('x'=>$topleftX, 'y'=>$topleftY); 127 | } 128 | 129 | /** 130 | * By doing random sampling from the image, find the most energetic point on the passed in 131 | * image 132 | * 133 | * @param \Imagick $image 134 | * @return array 135 | */ 136 | protected function getHighestEnergyPoint(\Imagick $image) 137 | { 138 | $size = $image->getImageGeometry(); 139 | // It's more performant doing random pixel uplook via GD 140 | $tmpFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'image' . rand(); 141 | $image->writeimage($tmpFile); 142 | $im = imagecreatefromjpeg($tmpFile); 143 | $xcenter = 0; 144 | $ycenter = 0; 145 | $sum = 0; 146 | // Sample only sample 1/50 of of all the pixels in the image 147 | $sampleSize = round($size['height']*$size['width'])/50; 148 | 149 | for ($k=0; $k<$sampleSize; $k++) { 150 | $i = mt_rand(0, $size['width']-1); 151 | $j = mt_rand(0, $size['height']-1); 152 | 153 | $rgb = imagecolorat($im, $i, $j); 154 | $r = ($rgb >> 16) & 0xFF; 155 | $g = ($rgb >> 8) & 0xFF; 156 | $b = $rgb & 0xFF; 157 | 158 | $val = $this->rgb2bw($r, $g, $b); 159 | $sum += $val; 160 | $xcenter += ($i+1)*$val; 161 | $ycenter += ($j+1)*$val; 162 | } 163 | 164 | if ($sum) { 165 | $xcenter /= $sum; 166 | $ycenter /= $sum; 167 | } 168 | 169 | $point = array('x' => $xcenter, 'y' => $ycenter, 'sum' => $sum/round($size['height']*$size['width'])); 170 | 171 | return $point; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /docs/markers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | » Markers 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 90 | 91 |
92 | 97 | 98 | 99 |
100 |
101 | 104 |
105 | 106 |
107 | 108 | 112 | 113 |
No markers have been found in this project.
114 | 115 |
116 |
117 |
118 |
119 |
120 | 121 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /docs/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | 2 | .hidden{display:none;visibility:hidden;} 3 | @media (max-width:480px){.nav-collapse{-webkit-transform:translate3d(0, 0, 0);} .page-header h1 small{display:block;line-height:18px;} input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;} .input-prepend input[class*="span"],.input-append input[class*="span"]{width:auto;} input[type="checkbox"],input[type="radio"]{border:1px solid #ccc;} .form-horizontal .control-group>label{float:none;width:auto;padding-top:0;text-align:left;} .form-horizontal .controls{margin-left:0;} .form-horizontal .control-list{padding-top:0;} .form-horizontal .form-actions{padding-left:10px;padding-right:10px;} .modal{position:absolute;top:10px;left:10px;right:10px;width:auto;margin:0;}.modal.fade.in{top:auto;} .modal-header .close{padding:10px;margin:-10px;} .carousel-caption{position:static;}}@media (max-width:768px){.container{width:auto;padding:0 20px;} .row-fluid{width:100%;} .row{margin-left:0;} .row>[class*="span"],.row-fluid>[class*="span"]{float:none;display:block;width:auto;margin:0;}}@media (min-width:768px) and (max-width:980px){.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";} .row:after{clear:both;} [class*="span"]{float:left;margin-left:20px;} .span1{width:42px;} .span2{width:104px;} .span3{width:166px;} .span4{width:228px;} .span5{width:290px;} .span6{width:352px;} .span7{width:414px;} .span8{width:476px;} .span9{width:538px;} .span10{width:600px;} .span11{width:662px;} .span12,.container{width:724px;} .offset1{margin-left:82px;} .offset2{margin-left:144px;} .offset3{margin-left:206px;} .offset4{margin-left:268px;} .offset5{margin-left:330px;} .offset6{margin-left:392px;} .offset7{margin-left:454px;} .offset8{margin-left:516px;} .offset9{margin-left:578px;} .offset10{margin-left:640px;} .offset11{margin-left:702px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";} .row-fluid:after{clear:both;} .row-fluid>[class*="span"]{float:left;margin-left:2.762430939%;} .row-fluid>[class*="span"]:first-child{margin-left:0;} .row-fluid .span1{width:5.801104972%;} .row-fluid .span2{width:14.364640883%;} .row-fluid .span3{width:22.928176794%;} .row-fluid .span4{width:31.491712705%;} .row-fluid .span5{width:40.055248616%;} .row-fluid .span6{width:48.618784527%;} .row-fluid .span7{width:57.182320438000005%;} .row-fluid .span8{width:65.74585634900001%;} .row-fluid .span9{width:74.30939226%;} .row-fluid .span10{width:82.87292817100001%;} .row-fluid .span11{width:91.436464082%;} .row-fluid .span12{width:99.999999993%;} input.span1,textarea.span1,.uneditable-input.span1{width:32px;} input.span2,textarea.span2,.uneditable-input.span2{width:94px;} input.span3,textarea.span3,.uneditable-input.span3{width:156px;} input.span4,textarea.span4,.uneditable-input.span4{width:218px;} input.span5,textarea.span5,.uneditable-input.span5{width:280px;} input.span6,textarea.span6,.uneditable-input.span6{width:342px;} input.span7,textarea.span7,.uneditable-input.span7{width:404px;} input.span8,textarea.span8,.uneditable-input.span8{width:466px;} input.span9,textarea.span9,.uneditable-input.span9{width:528px;} input.span10,textarea.span10,.uneditable-input.span10{width:590px;} input.span11,textarea.span11,.uneditable-input.span11{width:652px;} input.span12,textarea.span12,.uneditable-input.span12{width:714px;}}@media (max-width:980px){body{padding-top:0;} .navbar-fixed-top{position:static;margin-bottom:18px;} .navbar-fixed-top .navbar-inner{padding:5px;} .navbar .container{width:auto;padding:0;} .navbar .brand{padding-left:10px;padding-right:10px;margin:0 0 0 -5px;} .navbar .nav-collapse{clear:left;} .navbar .nav{float:none;margin:0 0 9px;} .navbar .nav>li{float:none;} .navbar .nav>li>a{margin-bottom:2px;} .navbar .nav>.divider-vertical{display:none;} .navbar .nav>li>a,.navbar .dropdown-menu a{padding:6px 15px;font-weight:bold;color:#999999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} .navbar .dropdown-menu li+li a{margin-bottom:2px;} .navbar .nav>li>a:hover,.navbar .dropdown-menu a:hover{background-color:#222222;} .navbar .dropdown-menu{position:static;top:auto;left:auto;float:none;display:block;max-width:none;margin:0 15px;padding:0;background-color:transparent;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} .navbar .dropdown-menu:before,.navbar .dropdown-menu:after{display:none;} .navbar .dropdown-menu .divider{display:none;} .navbar-form,.navbar-search{float:none;padding:9px 15px;margin:9px 0;border-top:1px solid #222222;border-bottom:1px solid #222222;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.1);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.1);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.1);} .navbar .nav.pull-right{float:none;margin-left:0;} .navbar-static .navbar-inner{padding-left:10px;padding-right:10px;} .btn-navbar{display:block;} .nav-collapse{overflow:hidden;height:0;}}@media (min-width:980px){.nav-collapse.collapse{height:auto !important;}}@media (min-width:1200px){.row{margin-left:-30px;*zoom:1;}.row:before,.row:after{display:table;content:"";} .row:after{clear:both;} [class*="span"]{float:left;margin-left:30px;} .span1{width:70px;} .span2{width:170px;} .span3{width:270px;} .span4{width:370px;} .span5{width:470px;} .span6{width:570px;} .span7{width:670px;} .span8{width:770px;} .span9{width:870px;} .span10{width:970px;} .span11{width:1070px;} .span12,.container{width:1170px;} .offset1{margin-left:130px;} .offset2{margin-left:230px;} .offset3{margin-left:330px;} .offset4{margin-left:430px;} .offset5{margin-left:530px;} .offset6{margin-left:630px;} .offset7{margin-left:730px;} .offset8{margin-left:830px;} .offset9{margin-left:930px;} .offset10{margin-left:1030px;} .offset11{margin-left:1130px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";} .row-fluid:after{clear:both;} .row-fluid>[class*="span"]{float:left;margin-left:2.564102564%;} .row-fluid>[class*="span"]:first-child{margin-left:0;} .row-fluid .span1{width:5.982905983%;} .row-fluid .span2{width:14.529914530000001%;} .row-fluid .span3{width:23.076923077%;} .row-fluid .span4{width:31.623931624%;} .row-fluid .span5{width:40.170940171000005%;} .row-fluid .span6{width:48.717948718%;} .row-fluid .span7{width:57.264957265%;} .row-fluid .span8{width:65.81196581200001%;} .row-fluid .span9{width:74.358974359%;} .row-fluid .span10{width:82.905982906%;} .row-fluid .span11{width:91.45299145300001%;} .row-fluid .span12{width:100%;} input.span1,textarea.span1,.uneditable-input.span1{width:60px;} input.span2,textarea.span2,.uneditable-input.span2{width:160px;} input.span3,textarea.span3,.uneditable-input.span3{width:260px;} input.span4,textarea.span4,.uneditable-input.span4{width:360px;} input.span5,textarea.span5,.uneditable-input.span5{width:460px;} input.span6,textarea.span6,.uneditable-input.span6{width:560px;} input.span7,textarea.span7,.uneditable-input.span7{width:660px;} input.span8,textarea.span8,.uneditable-input.span8{width:760px;} input.span9,textarea.span9,.uneditable-input.span9{width:860px;} input.span10,textarea.span10,.uneditable-input.span10{width:960px;} input.span11,textarea.span11,.uneditable-input.span11{width:1060px;} input.span12,textarea.span12,.uneditable-input.span12{width:1160px;} .thumbnails{margin-left:-30px;} .thumbnails>li{margin-left:30px;}} 4 | -------------------------------------------------------------------------------- /docs/js/SVGPan.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SVGPan library 1.2 - phpDocumentor1 3 | * ==================== 4 | * 5 | * Given an unique existing element with id "viewport", including the 6 | * the library into any SVG adds the following capabilities: 7 | * 8 | * - Mouse panning 9 | * - Mouse zooming (using the wheel) 10 | * - Object dargging 11 | * 12 | * Known issues: 13 | * 14 | * - Zooming (while panning) on Safari has still some issues 15 | * 16 | * Releases: 17 | * 18 | * 1.2 - phpDocumentor1, Fri Apr 08 19:19:00 CET 2011, Mike van Riel 19 | * Increased zoom speed with 20% 20 | * Disabled element moving functionality 21 | * 22 | * 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui 23 | * Fixed a bug with browser mouse handler interaction 24 | * 25 | * 1.1, Wed Feb 3 17:39:33 GMT 2010, Zeng Xiaohui 26 | * Updated the zoom code to support the mouse wheel on Safari/Chrome 27 | * 28 | * 1.0, Andrea Leofreddi 29 | * First release 30 | * 31 | * This code is licensed under the following BSD license: 32 | * 33 | * Copyright 2009-2010 Andrea Leofreddi . All rights reserved. 34 | * 35 | * Redistribution and use in source and binary forms, with or without modification, are 36 | * permitted provided that the following conditions are met: 37 | * 38 | * 1. Redistributions of source code must retain the above copyright notice, this list of 39 | * conditions and the following disclaimer. 40 | * 41 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list 42 | * of conditions and the following disclaimer in the documentation and/or other materials 43 | * provided with the distribution. 44 | * 45 | * THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ``AS IS'' AND ANY EXPRESS OR IMPLIED 46 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 47 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR 48 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 49 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 50 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 51 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 52 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 53 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 54 | * 55 | * The views and conclusions contained in the software and documentation are those of the 56 | * authors and should not be interpreted as representing official policies, either expressed 57 | * or implied, of Andrea Leofreddi. 58 | */ 59 | 60 | var root = document.documentElement; 61 | 62 | var state = 'none', stateTarget, stateOrigin, stateTf; 63 | 64 | setupHandlers(root); 65 | 66 | /** 67 | * Register handlers 68 | */ 69 | function setupHandlers(root){ 70 | setAttributes(root, { 71 | "onmouseup" : "add(evt)", 72 | "onmousedown" : "handleMouseDown(evt)", 73 | "onmousemove" : "handleMouseMove(evt)", 74 | "onmouseup" : "handleMouseUp(evt)", 75 | // "onmouseout" : "handleMouseUp(evt)" // Decomment this to stop the pan functionality when dragging out of the SVG element 76 | }); 77 | 78 | if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0) 79 | window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari 80 | else 81 | window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others 82 | } 83 | 84 | /** 85 | * Instance an SVGPoint object with given event coordinates. 86 | */ 87 | function getEventPoint(evt) { 88 | var p = root.createSVGPoint(); 89 | 90 | p.x = evt.clientX; 91 | p.y = evt.clientY; 92 | 93 | return p; 94 | } 95 | 96 | /** 97 | * Sets the current transform matrix of an element. 98 | */ 99 | function setCTM(element, matrix) { 100 | var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")"; 101 | 102 | element.setAttribute("transform", s); 103 | } 104 | 105 | /** 106 | * Dumps a matrix to a string (useful for debug). 107 | */ 108 | function dumpMatrix(matrix) { 109 | var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n 0, 0, 1 ]"; 110 | 111 | return s; 112 | } 113 | 114 | /** 115 | * Sets attributes of an element. 116 | */ 117 | function setAttributes(element, attributes){ 118 | for (i in attributes) 119 | element.setAttributeNS(null, i, attributes[i]); 120 | } 121 | 122 | /** 123 | * Handle mouse move event. 124 | */ 125 | function handleMouseWheel(evt) { 126 | if(evt.preventDefault) 127 | evt.preventDefault(); 128 | 129 | evt.returnValue = false; 130 | 131 | var svgDoc = evt.target.ownerDocument; 132 | 133 | var delta; 134 | 135 | if(evt.wheelDelta) 136 | delta = evt.wheelDelta / 3600; // Chrome/Safari 137 | else 138 | delta = evt.detail / -90; // Mozilla 139 | 140 | var z = 1 + (delta * 1.2); // Zoom factor: 0.9/1.1 141 | 142 | var g = svgDoc.getElementById("viewport"); 143 | 144 | var p = getEventPoint(evt); 145 | 146 | p = p.matrixTransform(g.getCTM().inverse()); 147 | 148 | // Compute new scale matrix in current mouse position 149 | var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y); 150 | 151 | setCTM(g, g.getCTM().multiply(k)); 152 | 153 | stateTf = stateTf.multiply(k.inverse()); 154 | } 155 | 156 | /** 157 | * Handle mouse move event. 158 | */ 159 | function handleMouseMove(evt) { 160 | if(evt.preventDefault) 161 | evt.preventDefault(); 162 | 163 | evt.returnValue = false; 164 | 165 | var svgDoc = evt.target.ownerDocument; 166 | 167 | var g = svgDoc.getElementById("viewport"); 168 | 169 | if(state == 'pan') { 170 | // Pan mode 171 | var p = getEventPoint(evt).matrixTransform(stateTf); 172 | 173 | setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y)); 174 | } else if(state == 'move') { 175 | // Move mode 176 | var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse()); 177 | 178 | setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM())); 179 | 180 | stateOrigin = p; 181 | } 182 | } 183 | 184 | /** 185 | * Handle click event. 186 | */ 187 | function handleMouseDown(evt) { 188 | if(evt.preventDefault) 189 | evt.preventDefault(); 190 | 191 | evt.returnValue = false; 192 | 193 | var svgDoc = evt.target.ownerDocument; 194 | 195 | var g = svgDoc.getElementById("viewport"); 196 | 197 | // if(evt.target.tagName == "svg") { 198 | // Pan mode 199 | state = 'pan'; 200 | 201 | stateTf = g.getCTM().inverse(); 202 | 203 | stateOrigin = getEventPoint(evt).matrixTransform(stateTf); 204 | // } else { 205 | // Move mode 206 | // state = 'move'; 207 | // 208 | // stateTarget = evt.target; 209 | // 210 | // stateTf = g.getCTM().inverse(); 211 | // 212 | // stateOrigin = getEventPoint(evt).matrixTransform(stateTf); 213 | // } 214 | } 215 | 216 | /** 217 | * Handle mouse button release event. 218 | */ 219 | function handleMouseUp(evt) { 220 | if(evt.preventDefault) 221 | evt.preventDefault(); 222 | 223 | evt.returnValue = false; 224 | 225 | var svgDoc = evt.target.ownerDocument; 226 | 227 | if(state == 'pan' || state == 'move') { 228 | // Quit pan mode 229 | state = ''; 230 | } 231 | } 232 | 233 | -------------------------------------------------------------------------------- /docs/js/template.js: -------------------------------------------------------------------------------- 1 | $.browser.chrome = /chrome/.test(navigator.userAgent.toLowerCase()); 2 | $.browser.ipad = /ipad/.test(navigator.userAgent.toLowerCase()); 3 | 4 | /** 5 | * Initializes page contents for progressive enhancement. 6 | */ 7 | function initializeContents() 8 | { 9 | // hide all more buttons because they are not needed with JS 10 | $(".element a.more").hide(); 11 | 12 | $(".clickable.class,.clickable.interface").click(function() { 13 | document.location = $("a.more", this).attr('href'); 14 | }); 15 | 16 | // change the cursor to a pointer to make it more explicit that this it clickable 17 | // do a background color change on hover to emphasize the clickability eveb more 18 | // we do not use CSS for this because when JS is disabled this behaviour does not 19 | // apply and we do not want the hover 20 | $(".element.method,.element.function,.element.class.clickable,.element.interface.clickable") 21 | .css("cursor", "pointer") 22 | .hover(function() { 23 | $(this).css('backgroundColor', '#F8FDF6') 24 | }, function(){ 25 | $(this).css('backgroundColor', 'white')} 26 | ); 27 | 28 | // do not show tooltips on iPad; it will cause the user having to click twice 29 | if (!$.browser.ipad) { 30 | $('.btn-group.visibility,.btn-group.view,.btn-group.type-filter') 31 | .tooltip({'placement':'bottom'}); 32 | } 33 | 34 | $('.btn-group.visibility,.btn-group.view,.btn-group.type-filter') 35 | .show() 36 | .find('button') 37 | .find('i').click(function(){ $(this).parent().click(); }); 38 | 39 | // set the events for the visibility buttons and enable by default. 40 | $('.visibility button.public').click(function(){ 41 | $('.element.public,.side-nav li.public').toggle($(this).hasClass('active')); 42 | }).click(); 43 | $('.visibility button.protected').click(function(){ 44 | $('.element.protected,.side-nav li.protected').toggle($(this).hasClass('active')); 45 | }).click(); 46 | $('.visibility button.private').click(function(){ 47 | $('.element.private,.side-nav li.private').toggle($(this).hasClass('active')); 48 | }).click(); 49 | $('.visibility button.inherited').click(function(){ 50 | $('.element.inherited,.side-nav li.inherited').toggle($(this).hasClass('active')); 51 | }).click(); 52 | 53 | $('.type-filter button.critical').click(function(){ 54 | $('tr.critical').toggle($(this).hasClass('active')); 55 | }); 56 | $('.type-filter button.error').click(function(){ 57 | $('tr.error').toggle($(this).hasClass('active')); 58 | }); 59 | $('.type-filter button.notice').click(function(){ 60 | $('tr.notice').toggle($(this).hasClass('active')); 61 | }); 62 | 63 | $('.view button.details').click(function(){ 64 | $('.side-nav li.view-simple').removeClass('view-simple'); 65 | }).button('toggle').click(); 66 | 67 | $('.view button.details').click(function(){ 68 | $('.side-nav li.view-simple').removeClass('view-simple'); 69 | }).button('toggle').click(); 70 | $('.view button.simple').click(function(){ 71 | $('.side-nav li').addClass('view-simple'); 72 | }); 73 | 74 | // sorting example 75 | // $('ol li').sort( 76 | // function(a, b) { return a.innerHTML.toLowerCase() > b.innerHTML.toLowerCase() ? 1 : -1; } 77 | // ).appendTo('ol'); 78 | } 79 | 80 | $(document).ready(function() { 81 | prettyPrint(); 82 | 83 | initializeContents(); 84 | 85 | // do not show tooltips on iPad; it will cause the user having to click twice 86 | if(!$.browser.ipad) { 87 | $(".side-nav a").tooltip({'placement': 'top'}); 88 | } 89 | 90 | // chrome cannot deal with certain situations; warn the user about reduced features 91 | if ($.browser.chrome && (window.location.protocol == 'file:')) { 92 | $("body > .container").prepend( 93 | '
×' + 94 | 'You are using Google Chrome in a local environment; AJAX interaction has been ' + 95 | 'disabled because Chrome cannot ' + 96 | 'retrieve files using Ajax.
' 97 | ); 98 | } 99 | 100 | $('ul.nav-namespaces li a, ul.nav-packages li a').click(function(){ 101 | // Google Chrome does not do Ajax locally 102 | if ($.browser.chrome && (window.location.protocol == 'file:')) 103 | { 104 | return true; 105 | } 106 | 107 | $(this).parents('.side-nav').find('.active').removeClass('active'); 108 | $(this).parent().addClass('active'); 109 | $('div.namespace-contents').load( 110 | this.href + ' div.namespace-contents', function(){ 111 | initializeContents(); 112 | $(window).scrollTop($('div.namespace-contents').position().top); 113 | } 114 | ); 115 | $('div.package-contents').load( 116 | this.href + ' div.package-contents', function(){ 117 | initializeContents(); 118 | $(window).scrollTop($('div.package-contents').position().top); 119 | } 120 | ); 121 | 122 | return false; 123 | }); 124 | 125 | function filterPath(string) 126 | { 127 | return string 128 | .replace(/^\//, '') 129 | .replace(/(index|default).[a-zA-Z]{3,4}$/, '') 130 | .replace(/\/$/, ''); 131 | } 132 | 133 | var locationPath = filterPath(location.pathname); 134 | 135 | // the ipad already smoothly scrolls and does not detect the scrollable 136 | // element if top=0; as such we disable this behaviour for the iPad 137 | if (!$.browser.ipad) { 138 | $('a[href*=#]').each(function () 139 | { 140 | var thisPath = filterPath(this.pathname) || locationPath; 141 | if (locationPath == thisPath && (location.hostname == this.hostname || !this.hostname) && this.hash.replace(/#/, '')) 142 | { 143 | var target = decodeURIComponent(this.hash.replace(/#/,'')); 144 | // note: I'm using attribute selector, because id selector can't match elements with '$' 145 | var $target = $('[id="'+target+'"]'); 146 | 147 | if ($target.length > 0) 148 | { 149 | $(this).click(function (event) 150 | { 151 | var scrollElem = scrollableElement('html', 'body'); 152 | var targetOffset = $target.offset().top; 153 | 154 | event.preventDefault(); 155 | $(scrollElem).animate({scrollTop:targetOffset}, 400, function () 156 | { 157 | location.hash = target; 158 | }); 159 | }); 160 | } 161 | } 162 | }); 163 | } 164 | 165 | // use the first element that is "scrollable" 166 | function scrollableElement(els) 167 | { 168 | for (var i = 0, argLength = arguments.length; i < argLength; i++) 169 | { 170 | var el = arguments[i], $scrollElement = $(el); 171 | if ($scrollElement.scrollTop() > 0) 172 | { 173 | return el; 174 | } 175 | else 176 | { 177 | $scrollElement.scrollTop(1); 178 | var isScrollable = $scrollElement.scrollTop() > 0; 179 | $scrollElement.scrollTop(0); 180 | if (isScrollable) 181 | { 182 | return el; 183 | } 184 | } 185 | } 186 | return []; 187 | } 188 | 189 | // Hide API Documentation menu if it's empty 190 | $('.nav .dropdown a[href=#api]').next().filter(function(el) { 191 | if ($(el).children().length == 0) { 192 | return true; 193 | } 194 | }).parent().hide(); 195 | }); 196 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | API Documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 89 | 90 |
91 | 96 | 97 |
98 |

API Documentation

99 |

Documentation

100 |
101 | 102 |
103 |
104 |
105 | 110 |
111 | 112 |
113 | 117 |
118 | 119 |
120 |
121 |
122 | 126 |
127 |
128 | 143 |
144 |
145 |
146 |
147 | 148 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /docs/js/jquery.treeview.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Treeview 1.5pre - jQuery plugin to hide and show branches of a tree 3 | * 4 | * http://bassistance.de/jquery-plugins/jquery-plugin-treeview/ 5 | * http://docs.jquery.com/Plugins/Treeview 6 | * 7 | * Copyright (c) 2007 Jörn Zaefferer 8 | * 9 | * Dual licensed under the MIT and GPL licenses: 10 | * http://www.opensource.org/licenses/mit-license.php 11 | * http://www.gnu.org/licenses/gpl.html 12 | * 13 | * Revision: $Id: jquery.treeview.js 5759 2008-07-01 07:50:28Z joern.zaefferer $ 14 | * 15 | */ 16 | 17 | ;(function($) { 18 | 19 | // TODO rewrite as a widget, removing all the extra plugins 20 | $.extend($.fn, { 21 | swapClass: function(c1, c2) { 22 | var c1Elements = this.filter('.' + c1); 23 | this.filter('.' + c2).removeClass(c2).addClass(c1); 24 | c1Elements.removeClass(c1).addClass(c2); 25 | return this; 26 | }, 27 | replaceClass: function(c1, c2) { 28 | return this.filter('.' + c1).removeClass(c1).addClass(c2).end(); 29 | }, 30 | hoverClass: function(className) { 31 | className = className || "hover"; 32 | return this.hover(function() { 33 | $(this).addClass(className); 34 | }, function() { 35 | $(this).removeClass(className); 36 | }); 37 | }, 38 | heightToggle: function(animated, callback) { 39 | animated ? 40 | this.animate({ height: "toggle" }, animated, callback) : 41 | this.each(function(){ 42 | jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ](); 43 | if(callback) 44 | callback.apply(this, arguments); 45 | }); 46 | }, 47 | heightHide: function(animated, callback) { 48 | if (animated) { 49 | this.animate({ height: "hide" }, animated, callback); 50 | } else { 51 | this.hide(); 52 | if (callback) 53 | this.each(callback); 54 | } 55 | }, 56 | prepareBranches: function(settings) { 57 | if (!settings.prerendered) { 58 | // mark last tree items 59 | this.filter(":last-child:not(ul)").addClass(CLASSES.last); 60 | // collapse whole tree, or only those marked as closed, anyway except those marked as open 61 | this.filter((settings.collapsed ? "" : "." + CLASSES.closed) + ":not(." + CLASSES.open + ")").find(">ul").hide(); 62 | } 63 | // return all items with sublists 64 | return this.filter(":has(>ul)"); 65 | }, 66 | applyClasses: function(settings, toggler) { 67 | // TODO use event delegation 68 | this.filter(":has(>ul):not(:has(>a))").find(">span").unbind("click.treeview").bind("click.treeview", function(event) { 69 | // don't handle click events on children, eg. checkboxes 70 | if ( this == event.target ) 71 | toggler.apply($(this).next()); 72 | }).add( $("a", this) ).hoverClass(); 73 | 74 | if (!settings.prerendered) { 75 | // handle closed ones first 76 | this.filter(":has(>ul:hidden)") 77 | .addClass(CLASSES.expandable) 78 | .replaceClass(CLASSES.last, CLASSES.lastExpandable); 79 | 80 | // handle open ones 81 | this.not(":has(>ul:hidden)") 82 | .addClass(CLASSES.collapsable) 83 | .replaceClass(CLASSES.last, CLASSES.lastCollapsable); 84 | 85 | // create hitarea if not present 86 | var hitarea = this.find("div." + CLASSES.hitarea); 87 | if (!hitarea.length) 88 | hitarea = this.prepend("
").find("div." + CLASSES.hitarea); 89 | hitarea.removeClass().addClass(CLASSES.hitarea).each(function() { 90 | var classes = ""; 91 | $.each($(this).parent().attr("class").split(" "), function() { 92 | classes += this + "-hitarea "; 93 | }); 94 | $(this).addClass( classes ); 95 | }) 96 | } 97 | 98 | // apply event to hitarea 99 | this.find("div." + CLASSES.hitarea).click( toggler ); 100 | }, 101 | treeview: function(settings) { 102 | 103 | settings = $.extend({ 104 | cookieId: "treeview" 105 | }, settings); 106 | 107 | if ( settings.toggle ) { 108 | var callback = settings.toggle; 109 | settings.toggle = function() { 110 | return callback.apply($(this).parent()[0], arguments); 111 | }; 112 | } 113 | 114 | // factory for treecontroller 115 | function treeController(tree, control) { 116 | // factory for click handlers 117 | function handler(filter) { 118 | return function() { 119 | // reuse toggle event handler, applying the elements to toggle 120 | // start searching for all hitareas 121 | toggler.apply( $("div." + CLASSES.hitarea, tree).filter(function() { 122 | // for plain toggle, no filter is provided, otherwise we need to check the parent element 123 | return filter ? $(this).parent("." + filter).length : true; 124 | }) ); 125 | return false; 126 | }; 127 | } 128 | // click on first element to collapse tree 129 | $("a:eq(0)", control).click( handler(CLASSES.collapsable) ); 130 | // click on second to expand tree 131 | $("a:eq(1)", control).click( handler(CLASSES.expandable) ); 132 | // click on third to toggle tree 133 | $("a:eq(2)", control).click( handler() ); 134 | } 135 | 136 | // handle toggle event 137 | function toggler() { 138 | $(this) 139 | .parent() 140 | // swap classes for hitarea 141 | .find(">.hitarea") 142 | .swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea ) 143 | .swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea ) 144 | .end() 145 | // swap classes for parent li 146 | .swapClass( CLASSES.collapsable, CLASSES.expandable ) 147 | .swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable ) 148 | // find child lists 149 | .find( ">ul" ) 150 | // toggle them 151 | .heightToggle( settings.animated, settings.toggle ); 152 | if ( settings.unique ) { 153 | $(this).parent() 154 | .siblings() 155 | // swap classes for hitarea 156 | .find(">.hitarea") 157 | .replaceClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea ) 158 | .replaceClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea ) 159 | .end() 160 | .replaceClass( CLASSES.collapsable, CLASSES.expandable ) 161 | .replaceClass( CLASSES.lastCollapsable, CLASSES.lastExpandable ) 162 | .find( ">ul" ) 163 | .heightHide( settings.animated, settings.toggle ); 164 | } 165 | } 166 | this.data("toggler", toggler); 167 | 168 | function serialize() { 169 | function binary(arg) { 170 | return arg ? 1 : 0; 171 | } 172 | var data = []; 173 | branches.each(function(i, e) { 174 | data[i] = $(e).is(":has(>ul:visible)") ? 1 : 0; 175 | }); 176 | $.cookie(settings.cookieId, data.join(""), settings.cookieOptions ); 177 | } 178 | 179 | function deserialize() { 180 | var stored = $.cookie(settings.cookieId); 181 | if ( stored ) { 182 | var data = stored.split(""); 183 | branches.each(function(i, e) { 184 | $(e).find(">ul")[ parseInt(data[i]) ? "show" : "hide" ](); 185 | }); 186 | } 187 | } 188 | 189 | // add treeview class to activate styles 190 | this.addClass("treeview"); 191 | 192 | // prepare branches and find all tree items with child lists 193 | var branches = this.find("li").prepareBranches(settings); 194 | 195 | switch(settings.persist) { 196 | case "cookie": 197 | var toggleCallback = settings.toggle; 198 | settings.toggle = function() { 199 | serialize(); 200 | if (toggleCallback) { 201 | toggleCallback.apply(this, arguments); 202 | } 203 | }; 204 | deserialize(); 205 | break; 206 | case "location": 207 | var current = this.find("a").filter(function() { 208 | return this.href.toLowerCase() == location.href.toLowerCase(); 209 | }); 210 | if ( current.length ) { 211 | // TODO update the open/closed classes 212 | var items = current.addClass("selected").parents("ul, li").add( current.next() ).show(); 213 | if (settings.prerendered) { 214 | // if prerendered is on, replicate the basic class swapping 215 | items.filter("li") 216 | .swapClass( CLASSES.collapsable, CLASSES.expandable ) 217 | .swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable ) 218 | .find(">.hitarea") 219 | .swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea ) 220 | .swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea ); 221 | } 222 | } 223 | break; 224 | } 225 | 226 | branches.applyClasses(settings, toggler); 227 | 228 | // if control option is set, create the treecontroller and show it 229 | if ( settings.control ) { 230 | treeController(this, settings.control); 231 | $(settings.control).show(); 232 | } 233 | 234 | return this; 235 | } 236 | }); 237 | 238 | // classes used by the plugin 239 | // need to be styled via external stylesheet, see first example 240 | $.treeview = {}; 241 | var CLASSES = ($.treeview.classes = { 242 | open: "open", 243 | closed: "closed", 244 | expandable: "expandable", 245 | expandableHitarea: "expandable-hitarea", 246 | lastExpandableHitarea: "lastExpandable-hitarea", 247 | collapsable: "collapsable", 248 | collapsableHitarea: "collapsable-hitarea", 249 | lastCollapsableHitarea: "lastCollapsable-hitarea", 250 | lastCollapsable: "lastCollapsable", 251 | lastExpandable: "lastExpandable", 252 | last: "last", 253 | hitarea: "hitarea" 254 | }); 255 | 256 | })(jQuery); -------------------------------------------------------------------------------- /docs/phpdoc-cache-51/phpdoc-cache-file_28dd9fff2c7f32cbf7c5a9d47b836f35.dat: -------------------------------------------------------------------------------- 1 | O:39:"phpDocumentor\Descriptor\FileDescriptor":20:{s:7:"*hash";s:32:"c7ec93a76641ed658c940d5fe3f7bc90";s:9:"*source";s:1123:"getCenterOffset($original, $targetWidth, $targetHeight); 28 | } 29 | 30 | /** 31 | * Get the cropping offset for the image based on the center of the image 32 | * 33 | * @param \Imagick $image 34 | * @param int $targetWidth 35 | * @param int $targetHeight 36 | * @return array 37 | */ 38 | protected function getCenterOffset(\Imagick $image, $targetWidth, $targetHeight) 39 | { 40 | $size = $image->getImageGeometry(); 41 | $originalWidth = $size['width']; 42 | $originalHeight = $size['height']; 43 | $goalX = (int)(($originalWidth-$targetWidth)/2); 44 | $goalY = (int)(($originalHeight-$targetHeight)/2); 45 | return array('x' => $goalX, 'y' => $goalY); 46 | } 47 | } 48 | ";s:20:"*namespace_aliases";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:11:"*includes";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:12:"*constants";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:12:"*functions";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:10:"*classes";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:1:{s:10:"CropCenter";O:40:"phpDocumentor\Descriptor\ClassDescriptor":17:{s:9:"*parent";s:16:"\stojg\crop\Crop";s:13:"*implements";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:11:"*abstract";b:0;s:8:"*final";b:0;s:12:"*constants";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:13:"*properties";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:10:"*methods";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:2:{s:16:"getSpecialOffset";O:41:"phpDocumentor\Descriptor\MethodDescriptor":16:{s:9:"*parent";r:14;s:11:"*abstract";b:0;s:8:"*final";b:0;s:9:"*static";b:0;s:13:"*visibility";s:9:"protected";s:12:"*arguments";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:3:{s:9:"$original";O:43:"phpDocumentor\Descriptor\ArgumentDescriptor":13:{s:8:"*types";a:1:{i:0;s:8:"\Imagick";}s:10:"*default";N;s:14:"*byReference";b:0;s:8:"*fqsen";s:0:"";s:7:"*name";s:9:"$original";s:12:"*namespace";N;s:10:"*package";N;s:10:"*summary";s:0:"";s:14:"*description";s:0:"";s:7:"*path";s:0:"";s:7:"*line";i:0;s:7:"*tags";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:9:"*errors";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}}s:12:"$targetWidth";O:43:"phpDocumentor\Descriptor\ArgumentDescriptor":13:{s:8:"*types";a:1:{i:0;s:3:"int";}s:10:"*default";N;s:14:"*byReference";b:0;s:8:"*fqsen";s:0:"";s:7:"*name";s:12:"$targetWidth";s:12:"*namespace";N;s:10:"*package";N;s:10:"*summary";s:0:"";s:14:"*description";s:0:"";s:7:"*path";s:0:"";s:7:"*line";i:0;s:7:"*tags";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:9:"*errors";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}}s:13:"$targetHeight";O:43:"phpDocumentor\Descriptor\ArgumentDescriptor":13:{s:8:"*types";a:1:{i:0;s:3:"int";}s:10:"*default";N;s:14:"*byReference";b:0;s:8:"*fqsen";s:0:"";s:7:"*name";s:13:"$targetHeight";s:12:"*namespace";N;s:10:"*package";N;s:10:"*summary";s:0:"";s:14:"*description";s:0:"";s:7:"*path";s:0:"";s:7:"*line";i:0;s:7:"*tags";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:9:"*errors";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}}}}s:8:"*fqsen";s:42:"\stojg\crop\CropCenter::getSpecialOffset()";s:7:"*name";s:16:"getSpecialOffset";s:12:"*namespace";N;s:10:"*package";s:0:"";s:10:"*summary";s:28:"get special offset for class";s:14:"*description";s:0:"";s:7:"*path";s:0:"";s:7:"*line";i:25;s:7:"*tags";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:3:{s:5:"param";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:3:{i:0;O:44:"phpDocumentor\Descriptor\Tag\ParamDescriptor":4:{s:15:"*variableName";s:9:"$original";s:8:"*types";a:1:{i:0;s:8:"\Imagick";}s:7:"*name";s:5:"param";s:14:"*description";s:0:"";}i:1;O:44:"phpDocumentor\Descriptor\Tag\ParamDescriptor":4:{s:15:"*variableName";s:12:"$targetWidth";s:8:"*types";a:1:{i:0;s:3:"int";}s:7:"*name";s:5:"param";s:14:"*description";s:0:"";}i:2;O:44:"phpDocumentor\Descriptor\Tag\ParamDescriptor":4:{s:15:"*variableName";s:13:"$targetHeight";s:8:"*types";a:1:{i:0;s:3:"int";}s:7:"*name";s:5:"param";s:14:"*description";s:0:"";}}}s:6:"return";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:1:{i:0;O:45:"phpDocumentor\Descriptor\Tag\ReturnDescriptor":3:{s:8:"*types";a:1:{i:0;s:5:"array";}s:7:"*name";s:6:"return";s:14:"*description";s:0:"";}}}s:8:"internal";N;}}s:9:"*errors";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}}s:15:"getCenterOffset";O:41:"phpDocumentor\Descriptor\MethodDescriptor":16:{s:9:"*parent";r:14;s:11:"*abstract";b:0;s:8:"*final";b:0;s:9:"*static";b:0;s:13:"*visibility";s:9:"protected";s:12:"*arguments";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:3:{s:6:"$image";O:43:"phpDocumentor\Descriptor\ArgumentDescriptor":13:{s:8:"*types";a:1:{i:0;s:8:"\Imagick";}s:10:"*default";N;s:14:"*byReference";b:0;s:8:"*fqsen";s:0:"";s:7:"*name";s:6:"$image";s:12:"*namespace";N;s:10:"*package";N;s:10:"*summary";s:0:"";s:14:"*description";s:0:"";s:7:"*path";s:0:"";s:7:"*line";i:0;s:7:"*tags";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:9:"*errors";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}}s:12:"$targetWidth";O:43:"phpDocumentor\Descriptor\ArgumentDescriptor":13:{s:8:"*types";a:1:{i:0;s:3:"int";}s:10:"*default";N;s:14:"*byReference";b:0;s:8:"*fqsen";s:0:"";s:7:"*name";s:12:"$targetWidth";s:12:"*namespace";N;s:10:"*package";N;s:10:"*summary";s:0:"";s:14:"*description";s:0:"";s:7:"*path";s:0:"";s:7:"*line";i:0;s:7:"*tags";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:9:"*errors";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}}s:13:"$targetHeight";O:43:"phpDocumentor\Descriptor\ArgumentDescriptor":13:{s:8:"*types";a:1:{i:0;s:3:"int";}s:10:"*default";N;s:14:"*byReference";b:0;s:8:"*fqsen";s:0:"";s:7:"*name";s:13:"$targetHeight";s:12:"*namespace";N;s:10:"*package";N;s:10:"*summary";s:0:"";s:14:"*description";s:0:"";s:7:"*path";s:0:"";s:7:"*line";i:0;s:7:"*tags";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:9:"*errors";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}}}}s:8:"*fqsen";s:41:"\stojg\crop\CropCenter::getCenterOffset()";s:7:"*name";s:15:"getCenterOffset";s:12:"*namespace";N;s:10:"*package";s:0:"";s:10:"*summary";s:70:"Get the cropping offset for the image based on the center of the image";s:14:"*description";s:0:"";s:7:"*path";s:0:"";s:7:"*line";i:38;s:7:"*tags";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:3:{s:5:"param";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:3:{i:0;O:44:"phpDocumentor\Descriptor\Tag\ParamDescriptor":4:{s:15:"*variableName";s:6:"$image";s:8:"*types";a:1:{i:0;s:8:"\Imagick";}s:7:"*name";s:5:"param";s:14:"*description";s:0:"";}i:1;O:44:"phpDocumentor\Descriptor\Tag\ParamDescriptor":4:{s:15:"*variableName";s:12:"$targetWidth";s:8:"*types";a:1:{i:0;s:3:"int";}s:7:"*name";s:5:"param";s:14:"*description";s:0:"";}i:2;O:44:"phpDocumentor\Descriptor\Tag\ParamDescriptor":4:{s:15:"*variableName";s:13:"$targetHeight";s:8:"*types";a:1:{i:0;s:3:"int";}s:7:"*name";s:5:"param";s:14:"*description";s:0:"";}}}s:6:"return";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:1:{i:0;O:45:"phpDocumentor\Descriptor\Tag\ReturnDescriptor":3:{s:8:"*types";a:1:{i:0;s:5:"array";}s:7:"*name";s:6:"return";s:14:"*description";s:0:"";}}}s:8:"internal";N;}}s:9:"*errors";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}}}}s:8:"*fqsen";s:22:"\stojg\crop\CropCenter";s:7:"*name";s:10:"CropCenter";s:12:"*namespace";s:11:"\stojg\crop";s:10:"*package";s:0:"";s:10:"*summary";s:10:"CropCenter";s:14:"*description";s:148:"The most basic of cropping techniques: 49 | 50 | 1. Find the exact center of the image 51 | 2. Trim any edges that is bigger than the targetWidth and targetHeight";s:7:"*path";r:1;s:7:"*line";i:14;s:7:"*tags";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:9:"*errors";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}}}}s:13:"*interfaces";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:9:"*traits";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:10:"*markers";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:8:"*fqsen";s:0:"";s:7:"*name";s:14:"CropCenter.php";s:12:"*namespace";N;s:10:"*package";s:0:"";s:10:"*summary";s:0:"";s:14:"*description";s:0:"";s:7:"*path";s:29:"src/stojg/crop/CropCenter.php";s:7:"*line";i:0;s:7:"*tags";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}s:9:"*errors";O:35:"phpDocumentor\Descriptor\Collection":1:{s:8:"*items";a:0:{}}} -------------------------------------------------------------------------------- /src/stojg/crop/CropEntropy.php: -------------------------------------------------------------------------------- 1 | getEntropyOffsets($original, $targetWidth, $targetHeight); 33 | } 34 | 35 | 36 | /** 37 | * Get the topleftX and topleftY that will can be passed to a cropping method. 38 | * 39 | * @param \Imagick $original 40 | * @param int $targetWidth 41 | * @param int $targetHeight 42 | * @return array 43 | */ 44 | protected function getEntropyOffsets(\Imagick $original, $targetWidth, $targetHeight) 45 | { 46 | $measureImage = clone($original); 47 | // Enhance edges 48 | $measureImage->edgeimage(1); 49 | // Turn image into a grayscale 50 | $measureImage->modulateImage(100, 0, 100); 51 | // Turn everything darker than this to pitch black 52 | $measureImage->blackThresholdImage("#070707"); 53 | // Get the calculated offset for cropping 54 | return $this->getOffsetFromEntropy($measureImage, $targetWidth, $targetHeight); 55 | } 56 | 57 | /** 58 | * Get the offset of where the crop should start 59 | * 60 | * @param \Imagick $image 61 | * @param int $targetHeight 62 | * @param int $targetHeight 63 | * @param int $sliceSize 64 | * @return array 65 | */ 66 | protected function getOffsetFromEntropy(\Imagick $originalImage, $targetWidth, $targetHeight) 67 | { 68 | // The entropy works better on a blured image 69 | $image = clone $originalImage; 70 | $image->blurImage(3, 2); 71 | 72 | $size = $image->getImageGeometry(); 73 | 74 | $originalWidth = $size['width']; 75 | $originalHeight = $size['height']; 76 | 77 | $leftX = $this->slice($image, $originalWidth, $targetWidth, 'h'); 78 | $topY = $this->slice($image, $originalHeight, $targetHeight, 'v'); 79 | 80 | return array('x' => $leftX, 'y' => $topY); 81 | } 82 | 83 | 84 | /** 85 | * slice 86 | * 87 | * @param mixed $image 88 | * @param mixed $originalSize 89 | * @param mixed $targetSize 90 | * @param mixed $axis h=horizontal, v = vertical 91 | * @access protected 92 | * @return void 93 | */ 94 | protected function slice($image, $originalSize, $targetSize, $axis) 95 | { 96 | $aSlice = null; 97 | $bSlice = null; 98 | 99 | // Just an arbitrary size of slice size 100 | $sliceSize = ceil(($originalSize - $targetSize) / 25); 101 | 102 | $aBottom = $originalSize; 103 | $aTop = 0; 104 | 105 | // while there still are uninvestigated slices of the image 106 | while ($aBottom - $aTop > $targetSize) { 107 | // Make sure that we don't try to slice outside the picture 108 | $sliceSize = min($aBottom - $aTop - $targetSize, $sliceSize); 109 | 110 | // Make a top slice image 111 | if (!$aSlice) { 112 | $aSlice = clone $image; 113 | if ($axis === 'h') { 114 | $aSlice->cropImage($originalSize, $sliceSize, $aTop, 0); 115 | } else { 116 | $aSlice->cropImage($originalSize, $sliceSize, 0, $aTop); 117 | } 118 | } 119 | 120 | // Make a bottom slice image 121 | if (!$bSlice) { 122 | $bSlice = clone $image; 123 | if ($axis === 'h') { 124 | $bSlice->cropImage($originalSize, $sliceSize, $aBottom - $sliceSize, 0); 125 | } else { 126 | $bSlice->cropImage($originalSize, $sliceSize, 0, $aBottom - $sliceSize); 127 | } 128 | } 129 | 130 | // calculate slices potential 131 | $aPosition = ($axis === 'h' ? 'left' : 'top'); 132 | $bPosition = ($axis === 'h' ? 'right' : 'bottom'); 133 | 134 | $aPot = $this->getPotential($aPosition, $aTop, $sliceSize); 135 | $bPot = $this->getPotential($bPosition, $aBottom, $sliceSize); 136 | 137 | $canCutA = ($aPot <= 0); 138 | $canCutB = ($bPot <= 0); 139 | 140 | // if no slices are "cutable", we force if a slice has a lot of potential 141 | if (!$canCutA && !$canCutB) { 142 | if ($aPot * self::POTENTIAL_RATIO < $bPot) { 143 | $canCutA = true; 144 | } elseif ($aPot > $bPot * self::POTENTIAL_RATIO) { 145 | $canCutB = true; 146 | } 147 | } 148 | 149 | // if we can only cut on one side 150 | if ($canCutA xor $canCutB) { 151 | if ($canCutA) { 152 | $aTop += $sliceSize; 153 | $aSlice = null; 154 | } else { 155 | $aBottom -= $sliceSize; 156 | $bSlice = null; 157 | } 158 | } elseif ($this->grayscaleEntropy($aSlice) < $this->grayscaleEntropy($bSlice)) { 159 | // bSlice has more entropy, so remove aSlice and bump aTop down 160 | $aTop += $sliceSize; 161 | $aSlice = null; 162 | } else { 163 | $aBottom -= $sliceSize; 164 | $bSlice = null; 165 | } 166 | } 167 | 168 | return $aTop; 169 | } 170 | 171 | /** 172 | * getSafeZoneList 173 | * 174 | * @access protected 175 | * @return array 176 | */ 177 | protected function getSafeZoneList() 178 | { 179 | return array(); 180 | } 181 | 182 | /** 183 | * getPotential 184 | * 185 | * @param mixed $position 186 | * @param mixed $top 187 | * @param mixed $sliceSize 188 | * @access protected 189 | * @return void 190 | */ 191 | protected function getPotential($position, $top, $sliceSize) 192 | { 193 | $safeZoneList = $this->getSafeZoneList(); 194 | 195 | $safeRatio = 0; 196 | 197 | if ($position == 'top' || $position == 'left') { 198 | $start = $top; 199 | $end = $top + $sliceSize; 200 | } else { 201 | $start = $top - $sliceSize; 202 | $end = $top; 203 | } 204 | 205 | for ($i = $start; $i < $end; $i++) { 206 | foreach ($safeZoneList as $safeZone) { 207 | if ($position == 'top' || $position == 'bottom') { 208 | if ($safeZone['top'] <= $i && $safeZone['bottom'] >= $i) { 209 | $safeRatio = max($safeRatio, ($safeZone['right'] - $safeZone['left'])); 210 | } 211 | } else { 212 | if ($safeZone['left'] <= $i && $safeZone['right'] >= $i) { 213 | $safeRatio = max($safeRatio, ($safeZone['bottom'] - $safeZone['top'])); 214 | } 215 | } 216 | } 217 | } 218 | 219 | return $safeRatio; 220 | } 221 | 222 | /** 223 | * Calculate the entropy for this image. 224 | * 225 | * A higher value of entropy means more noise / liveliness / color / business 226 | * 227 | * @param \Imagick $image 228 | * @return float 229 | * 230 | * @see http://brainacle.com/calculating-image-entropy-with-python-how-and-why.html 231 | * @see http://www.mathworks.com/help/toolbox/images/ref/entropy.html 232 | */ 233 | protected function grayscaleEntropy(\Imagick $image) 234 | { 235 | // The histogram consists of a list of 0-254 and the number of pixels that has that value 236 | $histogram = $image->getImageHistogram(); 237 | 238 | return $this->getEntropy($histogram, $this->area($image)); 239 | } 240 | 241 | /** 242 | * Find out the entropy for a color image 243 | * 244 | * If the source image is in color we need to transform RGB into a grayscale image 245 | * so we can calculate the entropy more performant. 246 | * 247 | * @param \Imagick $image 248 | * @return float 249 | */ 250 | protected function colorEntropy(\Imagick $image) 251 | { 252 | $histogram = $image->getImageHistogram(); 253 | $newHistogram = array(); 254 | 255 | // Translates a color histogram into a bw histogram 256 | $colors = count($histogram); 257 | for ($idx = 0; $idx < $colors; $idx++) { 258 | $colors = $histogram[$idx]->getColor(); 259 | $grey = $this->rgb2bw($colors['r'], $colors['g'], $colors['b']); 260 | if (!isset($newHistogram[$grey])) { 261 | $newHistogram[$grey] = $histogram[$idx]->getColorCount(); 262 | } else { 263 | $newHistogram[$grey] += $histogram[$idx]->getColorCount(); 264 | } 265 | } 266 | 267 | return $this->getEntropy($newHistogram, $this->area($image)); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /docs/namespaces/stojg.crop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | » \stojg\crop 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 90 | 91 |
92 | 97 | 98 | 99 | 100 |
101 | 102 |
103 |
104 | 108 |
109 | 116 |
117 | 118 |
119 | 120 | 121 | 136 | 137 | 138 | 139 |
140 |

Classes, interfaces and traits

141 | 142 | 143 |
144 |

CropFace

145 |

CropFace

146 |
This class will try to find the most interesting point in the image by trying to find a face and 147 | center the crop on that
148 | « More »
149 | 150 |
151 |

CropCenter

152 |

CropCenter

153 |
The most basic of cropping techniques: 154 | 155 | 1. Find the exact center of the image 156 | 2. Trim any edges that is bigger than the targetWidth and targetHeight
157 | « More »
158 | 159 |
160 |

Crop

161 |

Base class for all Croppers

162 |
163 | « More »
164 | 165 |
166 |

CropBalanced

167 |

CropBalanced

168 |
This class calculates the most interesting point in the image by: 169 | 170 | 1. Dividing the image into four equally squares 171 | 2. Find the most energetic point per square 172 | 3. Finding the images weighted mean interest point
173 | « More »
174 | 175 |
176 |

CropEntropy

177 |

CropEntropy

178 |
This class finds the a position in the picture with the most energy in it. 179 | 180 | Energy is in this case calculated by this 181 | 182 | 1. Take the image and turn it into black and white 183 | 2. Run a edge filter so that we're left with only edges. 184 | 3. Find a piece in the picture that has the highest entropy (i.e. most edges) 185 | 4. Return coordinates that makes sure that this piece of the picture is not cropped 'away'
186 | « More »
187 | 188 |
189 | 190 | 191 |
192 |
193 |
194 | 195 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /docs/packages/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | » 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 90 | 91 |
92 | 97 | 98 | 99 | 100 |
101 | 102 |
103 |
104 | 108 |
109 | 116 |
117 | 118 |
119 | 120 | 121 | 130 | 131 | 132 | 133 |
134 |

Classes, interfaces and traits

135 | 136 | 137 |
138 |

CropFace

139 |

CropFace

140 |
This class will try to find the most interesting point in the image by trying to find a face and 141 | center the crop on that
142 | « More » 143 |
144 | 145 |
146 |

CropCenter

147 |

CropCenter

148 |
The most basic of cropping techniques: 149 | 150 | 1. Find the exact center of the image 151 | 2. Trim any edges that is bigger than the targetWidth and targetHeight
152 | « More » 153 |
154 | 155 |
156 |

Crop

157 |

Base class for all Croppers

158 |
159 | « More » 160 |
161 | 162 |
163 |

CropBalanced

164 |

CropBalanced

165 |
This class calculates the most interesting point in the image by: 166 | 167 | 1. Dividing the image into four equally squares 168 | 2. Find the most energetic point per square 169 | 3. Finding the images weighted mean interest point
170 | « More » 171 |
172 | 173 |
174 |

CropEntropy

175 |

CropEntropy

176 |
This class finds the a position in the picture with the most energy in it. 177 | 178 | Energy is in this case calculated by this 179 | 180 | 1. Take the image and turn it into black and white 181 | 2. Run a edge filter so that we're left with only edges. 182 | 3. Find a piece in the picture that has the highest entropy (i.e. most edges) 183 | 4. Return coordinates that makes sure that this piece of the picture is not cropped 'away'
184 | « More » 185 |
186 | 187 |
188 |

ClassEntropyTest

189 |

190 |
191 | « More » 192 |
193 | 194 |
195 | 196 | 197 |
198 |
199 |
200 | 201 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /docs/namespaces/stojg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | » \stojg 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 90 | 91 |
92 | 97 | 98 | 99 | 100 |
101 | 102 |
103 |
104 | 108 |
109 | 125 |
126 | 127 |
128 | 129 | 130 | 131 | 132 | 147 | 148 | 149 | 150 |
151 |

Classes, interfaces and traits

152 | 153 | 154 |
155 |

CropFace

156 |

CropFace

157 |
This class will try to find the most interesting point in the image by trying to find a face and 158 | center the crop on that
159 | « More »
160 | 161 |
162 |

CropCenter

163 |

CropCenter

164 |
The most basic of cropping techniques: 165 | 166 | 1. Find the exact center of the image 167 | 2. Trim any edges that is bigger than the targetWidth and targetHeight
168 | « More »
169 | 170 |
171 |

Crop

172 |

Base class for all Croppers

173 |
174 | « More »
175 | 176 |
177 |

CropBalanced

178 |

CropBalanced

179 |
This class calculates the most interesting point in the image by: 180 | 181 | 1. Dividing the image into four equally squares 182 | 2. Find the most energetic point per square 183 | 3. Finding the images weighted mean interest point
184 | « More »
185 | 186 |
187 |

CropEntropy

188 |

CropEntropy

189 |
This class finds the a position in the picture with the most energy in it. 190 | 191 | Energy is in this case calculated by this 192 | 193 | 1. Take the image and turn it into black and white 194 | 2. Run a edge filter so that we're left with only edges. 195 | 3. Find a piece in the picture that has the highest entropy (i.e. most edges) 196 | 4. Return coordinates that makes sure that this piece of the picture is not cropped 'away'
197 | « More »
198 | 199 |
200 | 201 | 202 | 203 |
204 |
205 |
206 | 207 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /docs/namespaces/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | » \ 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 90 | 91 |
92 | 97 | 98 | 99 | 100 |
101 | 102 |
103 |
104 | 108 |
109 | 134 |
135 | 136 |
137 | 138 | 139 | 148 | 149 | 150 | 151 |
152 |

Classes, interfaces and traits

153 | 154 | 155 |
156 |

ClassEntropyTest

157 |

158 |
159 | « More »
160 | 161 |
162 | 163 | 164 | 165 | 166 | 167 | 182 | 183 | 184 | 185 |
186 |

Classes, interfaces and traits

187 | 188 | 189 |
190 |

CropFace

191 |

CropFace

192 |
This class will try to find the most interesting point in the image by trying to find a face and 193 | center the crop on that
194 | « More »
195 | 196 |
197 |

CropCenter

198 |

CropCenter

199 |
The most basic of cropping techniques: 200 | 201 | 1. Find the exact center of the image 202 | 2. Trim any edges that is bigger than the targetWidth and targetHeight
203 | « More »
204 | 205 |
206 |

Crop

207 |

Base class for all Croppers

208 |
209 | « More »
210 | 211 |
212 |

CropBalanced

213 |

CropBalanced

214 |
This class calculates the most interesting point in the image by: 215 | 216 | 1. Dividing the image into four equally squares 217 | 2. Find the most energetic point per square 218 | 3. Finding the images weighted mean interest point
219 | « More »
220 | 221 |
222 |

CropEntropy

223 |

CropEntropy

224 |
This class finds the a position in the picture with the most energy in it. 225 | 226 | Energy is in this case calculated by this 227 | 228 | 1. Take the image and turn it into black and white 229 | 2. Run a edge filter so that we're left with only edges. 230 | 3. Find a piece in the picture that has the highest entropy (i.e. most edges) 231 | 4. Return coordinates that makes sure that this piece of the picture is not cropped 'away'
232 | « More »
233 | 234 |
235 | 236 | 237 | 238 | 239 |
240 |
241 |
242 | 243 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /docs/css/template.css: -------------------------------------------------------------------------------- 1 | @import url(bootstrap.min.css); 2 | @import url(bootstrap-responsive.css); 3 | @import url(prettify.css); 4 | @import url(jquery.iviewer.css); 5 | @import url(http://fonts.googleapis.com/css?family=Crimson+Text|Philosopher|Forum); 6 | 7 | body 8 | { 9 | padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */ 10 | background: #f9f9f9; 11 | color: #444; 12 | } 13 | 14 | a 15 | { 16 | color: #55A72F; 17 | } 18 | 19 | td p:last-of-type { 20 | margin: 0; 21 | } 22 | 23 | li.l0, li.l1, li.l2, li.l3, li.l5, li.l6, li.l7, li.l8 24 | { 25 | list-style-type: decimal; 26 | } 27 | 28 | a.brand, h2, .hero-unit h1 29 | { 30 | font-family: 'Forum', "Helvetica Neue", Helvetica, Arial, sans-serif; 31 | } 32 | 33 | .element .span4 34 | { 35 | width: 275px; 36 | } 37 | 38 | .namespace-contents hr, .package-contents hr 39 | { 40 | border-top: 3px dotted silver; 41 | } 42 | 43 | .namespace-indent, .package-indent 44 | { 45 | padding-left: 10px; border-left: 1px dashed #f0f0f0; 46 | } 47 | 48 | .element h3 i, .namespace-contents h3 i, .package-contents h3 i 49 | { 50 | margin-top: 2px; 51 | margin-right: 5px; 52 | } 53 | 54 | .element h3, .namespace-contents h3, .package-contents h3 55 | { 56 | margin-top: 25px; 57 | margin-bottom: 20px; 58 | border-bottom: 1px solid silver; 59 | } 60 | 61 | .element h3:first-of-type, .namespace-contents h3:first-of-type, 62 | .package-contents h3:first-of-type 63 | { 64 | margin-top: 30px; 65 | } 66 | 67 | .element h2 68 | { 69 | font-family: inherit; 70 | font-size: 1.2em; 71 | color: black; 72 | } 73 | 74 | .element .type 75 | { 76 | font-weight: bold; 77 | } 78 | 79 | #search-query 80 | { 81 | height: auto; 82 | } 83 | 84 | .hero-unit, div.element, .well 85 | { 86 | border: 1px solid #e0e0e0; 87 | background: white; 88 | } 89 | 90 | .dropdown-menu a{ 91 | overflow: hidden; 92 | text-overflow: ellipsis; 93 | } 94 | h2 95 | { 96 | border-bottom: 1px dashed #55A72F; 97 | margin-bottom: 10px; 98 | padding-bottom: 0; 99 | padding-left: 5px; 100 | color: #e9e9e9; 101 | font-weight: normal; 102 | margin-top: 40px; 103 | } 104 | 105 | h2:first-of-type 106 | { 107 | margin-top: 0; 108 | } 109 | 110 | .hero-unit 111 | { 112 | background: #75a70d; /* Old browsers */ 113 | background: -moz-radial-gradient(center, ellipse cover, #bfd255 0%, #8eb92a 72%, #72aa00 96%, #9ecb2d 100%); /* FF3.6+ */ 114 | background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,#bfd255), color-stop(72%,#8eb92a), color-stop(96%,#72aa00), color-stop(100%,#9ecb2d)); /* Chrome,Safari4+ */ 115 | background: -webkit-radial-gradient(center, ellipse cover, #bfd255 0%,#8eb92a 72%,#72aa00 96%,#9ecb2d 100%); /* Chrome10+,Safari5.1+ */ 116 | background: -o-radial-gradient(center, ellipse cover, #bfd255 0%,#8eb92a 72%,#72aa00 96%,#9ecb2d 100%); /* Opera 12+ */ 117 | background: -ms-radial-gradient(center, ellipse cover, #bfd255 0%,#8eb92a 72%,#72aa00 96%,#9ecb2d 100%); /* IE10+ */ 118 | background: radial-gradient(center, ellipse cover, #bfd255 0%,#8eb92a 72%,#72aa00 96%,#9ecb2d 100%); /* W3C */ 119 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#bfd255', endColorstr='#9ecb2d',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ 120 | 121 | padding: 40px 0 15px 0; 122 | box-shadow: inset 0 0 10px gray; 123 | } 124 | 125 | .hero-unit h1 126 | { 127 | text-align: center; 128 | font-weight: normal; 129 | text-align: center; 130 | color: white; 131 | text-shadow: black 0px 0px 15px; 132 | } 133 | 134 | .hero-unit h2 135 | { 136 | border: none; 137 | color: white; 138 | background: rgba(48, 48, 48, 0.5); 139 | padding: 0; 140 | margin: 0; 141 | margin-top: 15px; 142 | text-align: center; 143 | } 144 | 145 | .namespace-contents h2, .package-contents h2 146 | { 147 | padding-left: 44px; 148 | background: transparent url('../img/icons/icon-th-big.png') no-repeat 3px center; 149 | } 150 | 151 | .package-contents h2 152 | { 153 | background-image: url('../img/icons/icon-folder-open-big.png'); 154 | } 155 | 156 | .namespace-contents .element h2, .package-contents .element h2 157 | { 158 | padding-left: 0; 159 | background: none; 160 | } 161 | 162 | div.element 163 | { 164 | border-left: 10px solid #55A72F; 165 | border-radius: 5px; 166 | padding: 7px 7px 2px 7px; 167 | margin-bottom: 15px; 168 | margin-left: 0; 169 | } 170 | 171 | div.element.protected 172 | { 173 | border-left-color: orange; 174 | } 175 | 176 | div.element.private 177 | { 178 | border-left-color: red; 179 | } 180 | 181 | div.element.class, div.element.interface 182 | { 183 | border-left-color: #e0e0e0; 184 | } 185 | 186 | div.element.class.abstract h1, div.element.interface.abstract h1 187 | { 188 | font-style: italic; 189 | } 190 | 191 | div.element h1 192 | { 193 | font-size: 1.2em; 194 | line-height: 1.5em; 195 | margin-bottom: 10px; 196 | padding-left: 22px; 197 | background: transparent no-repeat left 2px; 198 | word-wrap: break-word; 199 | } 200 | 201 | div.element h1 a 202 | { 203 | color: transparent; 204 | margin-left: 10px; 205 | } 206 | 207 | div.element h1:hover a 208 | { 209 | color: silver; 210 | } 211 | 212 | div.element h1 a:hover 213 | { 214 | color: navy; 215 | } 216 | 217 | div.element a.more:hover 218 | { 219 | background: #f0f0f0; 220 | color: #444; 221 | text-decoration: none; 222 | } 223 | 224 | div.element a.more 225 | { 226 | font-weight: bold; 227 | text-align: center; 228 | color: gray; 229 | border-top: 1px dashed silver; 230 | display: block; 231 | margin-top: 5px; 232 | padding: 5px 0; 233 | border-bottom-left-radius: 5px; 234 | border-bottom-right-radius: 5px; 235 | } 236 | 237 | div.element p 238 | { 239 | font-size: 0.9em; 240 | } 241 | 242 | div.element .table 243 | { 244 | font-size: 0.9em; 245 | } 246 | 247 | div.element .table th 248 | { 249 | text-transform: capitalize; 250 | } 251 | 252 | div.detail-description 253 | { 254 | padding-left: 30px; 255 | } 256 | 257 | div.detail-description table th { 258 | vertical-align: top; 259 | } 260 | 261 | body.invert 262 | { 263 | background: white; 264 | } 265 | 266 | body.invert div.element 267 | { 268 | background: #f9f9f9; 269 | } 270 | 271 | ul.side-nav 272 | { 273 | clear: both; 274 | } 275 | 276 | ul.side-nav li 277 | { 278 | word-wrap: break-word; 279 | padding-left: 10px; 280 | text-indent: -10px; 281 | } 282 | 283 | ul.side-nav li a 284 | { 285 | background: transparent no-repeat 5px 3px; 286 | padding-bottom: 10px; 287 | font-style: italic; 288 | } 289 | 290 | ul.side-nav li pre 291 | { 292 | font-size: 0.8em; 293 | margin: 5px 15px 0 15px; 294 | padding: 2px 5px; 295 | background-color: #f8f8f8; 296 | color: gray; 297 | font-style: normal; 298 | word-wrap: break-word; 299 | text-indent: 0; 300 | } 301 | 302 | ul.side-nav li.view-simple span.description 303 | { 304 | display: none; 305 | } 306 | 307 | ul.side-nav li.view-simple pre 308 | { 309 | font-size: inherit; 310 | margin: inherit; 311 | padding: inherit; 312 | background-color: inherit; 313 | border: none; 314 | color: inherit; 315 | font-family: inherit; 316 | font-style: inherit; 317 | padding-bottom: 0; 318 | padding-left: 5px; 319 | } 320 | 321 | ul.side-nav li.view-simple a 322 | { 323 | padding-bottom: 0px; 324 | } 325 | 326 | i.icon-custom 327 | { 328 | width: 16px; 329 | height: 16px; 330 | background-position: 0; 331 | } 332 | 333 | .table.markers 334 | { 335 | background: white; 336 | } 337 | 338 | /* JS only functionality; disable by default */ 339 | .btn-group.visibility, .btn-group.view, .btn-group.type-filter 340 | { 341 | display: none; 342 | } 343 | 344 | .btn-group.view 345 | { 346 | margin-left: 20px; 347 | margin-bottom: 20px; 348 | } 349 | 350 | .visibility button 351 | { 352 | height: 24px; 353 | } 354 | 355 | div.element.constant h1, 356 | i.icon-constant { background-image: url('../img/icons/constant.png'); } 357 | 358 | div.element.function h1, 359 | i.icon-function { background-image: url('../img/icons/function.png'); } 360 | 361 | div.element.method h1, 362 | i.icon-method { background-image: url('../img/icons/method.png'); } 363 | 364 | div.element.class h1, 365 | i.icon-class { background-image: url('../img/icons/class.png'); } 366 | 367 | div.element.interface h1, 368 | i.icon-interface { background-image: url('../img/icons/interface.png'); } 369 | 370 | div.element.property h1, 371 | i.icon-property { background-image: url('../img/icons/property.png'); } 372 | 373 | i.icon-show-public { background-image: url('../img/icons/visibility_public.png'); } 374 | i.icon-show-protected { background-image: url('../img/icons/visibility_protected.png'); } 375 | i.icon-show-private { background-image: url('../img/icons/visibility_private.png'); } 376 | 377 | span.empty-namespace 378 | { 379 | color: silver; 380 | } 381 | 382 | footer 383 | { 384 | text-align: right; 385 | font-size: 0.8em; 386 | opacity: 0.5; 387 | } 388 | 389 | #mapHolder 390 | { 391 | border: 4px solid #555; 392 | padding: 0 !important; 393 | overflow: hidden 394 | } 395 | 396 | div.element div.subelement 397 | { 398 | margin-left: 10px; 399 | padding-bottom: 5px; 400 | clear: both; 401 | } 402 | 403 | pre code 404 | { 405 | border: none; 406 | } 407 | 408 | div.element div.subelement > code 409 | { 410 | font-size: 0.8em; 411 | float: left; 412 | margin-right: 10px; 413 | padding: 0 5px; 414 | line-height: 16px; 415 | } 416 | 417 | div.element div.subelement > p 418 | { 419 | margin-left: 20px; 420 | margin-right: 50px; 421 | } 422 | 423 | div.element div.subelement h4 424 | { 425 | color: #666; 426 | margin-bottom: 5px; 427 | } 428 | 429 | div.element div.subelement.response 430 | { 431 | padding-bottom: 15px; 432 | margin-right: 50px; 433 | } 434 | 435 | div.labels 436 | { 437 | text-align: right; 438 | } 439 | 440 | .nav-list .nav-header 441 | { 442 | font-size: 13px; 443 | } 444 | 445 | .detail-description code { 446 | white-space: pre; 447 | display: inline-block; 448 | padding: 10px; 449 | } 450 | 451 | .go_to_top 452 | { 453 | float: right; 454 | margin-right: 20px; 455 | background: #2C2C2C; 456 | color: #999; 457 | padding: 3px 10px; 458 | border-bottom-right-radius: 5px; 459 | border-bottom-left-radius: 5px; 460 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 461 | line-height: 19px; 462 | } 463 | 464 | .visibility .btn { 465 | text-transform: uppercase; 466 | font-size: 0.7em; 467 | font-weight: bold; 468 | } 469 | 470 | .iviewer_common 471 | { 472 | z-index: 100; 473 | } 474 | 475 | @media (min-width: 980px) 476 | { 477 | a[name] 478 | { 479 | margin-top: -50px; 480 | position: absolute; 481 | } 482 | } 483 | 484 | @media (min-width: 1200px) 485 | { 486 | .method .span4 487 | { 488 | width: 345px; 489 | } 490 | } 491 | 492 | /* redefined because twitter bootstrap assumes that bootstrap-responsive.css */ 493 | @media (max-width: 980px) 494 | { 495 | body 496 | { 497 | padding-top: 0; 498 | } 499 | 500 | .go_to_top 501 | { 502 | display: none; 503 | } 504 | 505 | .btn-group.visibility 506 | { 507 | font-size: 0.80em; 508 | margin-bottom: 7px; 509 | display: block; 510 | float: right; 511 | } 512 | } 513 | 514 | @media (max-width: 768px) 515 | { 516 | .hero-unit h1 { 517 | font-size: 30px; 518 | } 519 | .hero-unit h2 { 520 | font-size: 19px; 521 | } 522 | 523 | } 524 | @media (min-width: 768px) and (max-width: 980px) 525 | { 526 | .method .span4 527 | { 528 | width: 203px; 529 | } 530 | } 531 | -------------------------------------------------------------------------------- /docs/js/jquery.splitter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery.splitter.js - two-pane splitter window plugin 3 | * 4 | * version 1.51 (2009/01/09) 5 | * 6 | * Dual licensed under the MIT and GPL licenses: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.gnu.org/licenses/gpl.html 9 | */ 10 | 11 | /** 12 | * The splitter() plugin implements a two-pane resizable splitter window. 13 | * The selected elements in the jQuery object are converted to a splitter; 14 | * each selected element should have two child elements, used for the panes 15 | * of the splitter. The plugin adds a third child element for the splitbar. 16 | * 17 | * For more details see: http://methvin.com/splitter/ 18 | * 19 | * 20 | * @example $('#MySplitter').splitter(); 21 | * @desc Create a vertical splitter with default settings 22 | * 23 | * @example $('#MySplitter').splitter({type: 'h', accessKey: 'M'}); 24 | * @desc Create a horizontal splitter resizable via Alt+Shift+M 25 | * 26 | * @name splitter 27 | * @type jQuery 28 | * @param Object options Options for the splitter (not required) 29 | * @cat Plugins/Splitter 30 | * @return jQuery 31 | * @author Dave Methvin (dave.methvin@gmail.com) 32 | */ 33 | ; 34 | (function($) { 35 | 36 | $.fn.splitter = function(args) { 37 | args = args || {}; 38 | return this.each(function() { 39 | var zombie; // left-behind splitbar for outline resizes 40 | function startSplitMouse(evt) { 41 | if (opts.outline) 42 | zombie = zombie || bar.clone(false).insertAfter(A); 43 | panes.css("-webkit-user-select", "none"); // Safari selects A/B text on a move 44 | bar.addClass(opts.activeClass); 45 | $('
').insertAfter(bar); 46 | A._posSplit = A[0][opts.pxSplit] - evt[opts.eventPos]; 47 | $(document) 48 | .bind("mousemove", doSplitMouse) 49 | .bind("mouseup", endSplitMouse); 50 | } 51 | 52 | function doSplitMouse(evt) { 53 | var newPos = A._posSplit + evt[opts.eventPos]; 54 | if (opts.outline) { 55 | newPos = Math.max(0, Math.min(newPos, splitter._DA - bar._DA)); 56 | bar.css(opts.origin, newPos); 57 | } else 58 | resplit(newPos); 59 | } 60 | 61 | function endSplitMouse(evt) { 62 | $('div.splitterMask').remove(); 63 | bar.removeClass(opts.activeClass); 64 | var newPos = A._posSplit + evt[opts.eventPos]; 65 | if (opts.outline) { 66 | zombie.remove(); 67 | zombie = null; 68 | resplit(newPos); 69 | } 70 | panes.css("-webkit-user-select", "text"); // let Safari select text again 71 | $(document) 72 | .unbind("mousemove", doSplitMouse) 73 | .unbind("mouseup", endSplitMouse); 74 | } 75 | 76 | function resplit(newPos) { 77 | // Constrain new splitbar position to fit pane size limits 78 | newPos = Math.max(A._min, splitter._DA - B._max, 79 | Math.min(newPos, A._max, splitter._DA - bar._DA - B._min)); 80 | // Resize/position the two panes 81 | bar._DA = bar[0][opts.pxSplit]; // bar size may change during dock 82 | bar.css(opts.origin, newPos).css(opts.fixed, splitter._DF); 83 | A.css(opts.origin, 0).css(opts.split, newPos).css(opts.fixed, splitter._DF); 84 | B.css(opts.origin, newPos + bar._DA) 85 | .css(opts.split, splitter._DA - bar._DA - newPos).css(opts.fixed, splitter._DF); 86 | // IE fires resize for us; all others pay cash 87 | if (!$.browser.msie) 88 | panes.trigger("resize"); 89 | } 90 | 91 | function dimSum(jq, dims) { 92 | // Opera returns -1 for missing min/max width, turn into 0 93 | var sum = 0; 94 | for (var i = 1; i < arguments.length; i++) 95 | sum += Math.max(parseInt(jq.css(arguments[i])) || 0, 0); 96 | return sum; 97 | } 98 | 99 | // Determine settings based on incoming opts, element classes, and defaults 100 | var vh = (args.splitHorizontal ? 'h' : args.splitVertical ? 'v' : args.type) || 'v'; 101 | var opts = $.extend({ 102 | activeClass: 'active', // class name for active splitter 103 | pxPerKey: 8, // splitter px moved per keypress 104 | tabIndex: 0, // tab order indicator 105 | accessKey: '' // accessKey for splitbar 106 | }, { 107 | v: { // Vertical splitters: 108 | keyLeft: 39, keyRight: 37, cursor: "e-resize", 109 | splitbarClass: "vsplitbar", outlineClass: "voutline", 110 | type: 'v', eventPos: "pageX", origin: "left", 111 | split: "width", pxSplit: "offsetWidth", side1: "Left", side2: "Right", 112 | fixed: "height", pxFixed: "offsetHeight", side3: "Top", side4: "Bottom" 113 | }, 114 | h: { // Horizontal splitters: 115 | keyTop: 40, keyBottom: 38, cursor: "n-resize", 116 | splitbarClass: "hsplitbar", outlineClass: "houtline", 117 | type: 'h', eventPos: "pageY", origin: "top", 118 | split: "height", pxSplit: "offsetHeight", side1: "Top", side2: "Bottom", 119 | fixed: "width", pxFixed: "offsetWidth", side3: "Left", side4: "Right" 120 | } 121 | }[vh], args); 122 | 123 | // Create jQuery object closures for splitter and both panes 124 | var splitter = $(this).css({position: "relative"}); 125 | var panes = $(">*", splitter[0]).css({ 126 | position: "absolute", // positioned inside splitter container 127 | "z-index": "1", // splitbar is positioned above 128 | "-moz-outline-style": "none" // don't show dotted outline 129 | }); 130 | var A = $(panes[0]); // left or top 131 | var B = $(panes[1]); // right or bottom 132 | 133 | // Focuser element, provides keyboard support; title is shown by Opera accessKeys 134 | var focuser = $('') 135 | .attr({accessKey: opts.accessKey, tabIndex: opts.tabIndex, title: opts.splitbarClass}) 136 | .bind($.browser.opera ? "click" : "focus", function() { 137 | this.focus(); 138 | bar.addClass(opts.activeClass) 139 | }) 140 | .bind("keydown", function(e) { 141 | var key = e.which || e.keyCode; 142 | var dir = key == opts["key" + opts.side1] ? 1 : key == opts["key" + opts.side2] ? -1 : 0; 143 | if (dir) 144 | resplit(A[0][opts.pxSplit] + dir * opts.pxPerKey, false); 145 | }) 146 | .bind("blur", function() { 147 | bar.removeClass(opts.activeClass) 148 | }); 149 | 150 | // Splitbar element, can be already in the doc or we create one 151 | var bar = $(panes[2] || '
') 152 | .insertAfter(A).css("z-index", "100").append(focuser) 153 | .attr({"class": opts.splitbarClass, unselectable: "on"}) 154 | .css({position: "absolute", "user-select": "none", "-webkit-user-select": "none", 155 | "-khtml-user-select": "none", "-moz-user-select": "none", "top": "0px"}) 156 | .bind("mousedown", startSplitMouse); 157 | // Use our cursor unless the style specifies a non-default cursor 158 | if (/^(auto|default|)$/.test(bar.css("cursor"))) 159 | bar.css("cursor", opts.cursor); 160 | 161 | // Cache several dimensions for speed, rather than re-querying constantly 162 | bar._DA = bar[0][opts.pxSplit]; 163 | splitter._PBF = $.boxModel ? dimSum(splitter, "border" + opts.side3 + "Width", "border" + opts.side4 + "Width") : 0; 164 | splitter._PBA = $.boxModel ? dimSum(splitter, "border" + opts.side1 + "Width", "border" + opts.side2 + "Width") : 0; 165 | A._pane = opts.side1; 166 | B._pane = opts.side2; 167 | $.each([A,B], function() { 168 | this._min = opts["min" + this._pane] || dimSum(this, "min-" + opts.split); 169 | this._max = opts["max" + this._pane] || dimSum(this, "max-" + opts.split) || 9999; 170 | this._init = opts["size" + this._pane] === true ? 171 | parseInt($.curCSS(this[0], opts.split)) : opts["size" + this._pane]; 172 | }); 173 | 174 | // Determine initial position, get from cookie if specified 175 | var initPos = A._init; 176 | if (!isNaN(B._init)) // recalc initial B size as an offset from the top or left side 177 | initPos = splitter[0][opts.pxSplit] - splitter._PBA - B._init - bar._DA; 178 | if (opts.cookie) { 179 | if (!$.cookie) 180 | alert('jQuery.splitter(): jQuery cookie plugin required'); 181 | var ckpos = parseInt($.cookie(opts.cookie)); 182 | if (!isNaN(ckpos)) 183 | initPos = ckpos; 184 | $(window).bind("unload", function() { 185 | var state = String(bar.css(opts.origin)); // current location of splitbar 186 | $.cookie(opts.cookie, state, {expires: opts.cookieExpires || 365, 187 | path: opts.cookiePath || document.location.pathname}); 188 | }); 189 | } 190 | if (isNaN(initPos)) // King Solomon's algorithm 191 | initPos = Math.round((splitter[0][opts.pxSplit] - splitter._PBA - bar._DA) / 2); 192 | 193 | // Resize event propagation and splitter sizing 194 | if (opts.anchorToWindow) { 195 | // Account for margin or border on the splitter container and enforce min height 196 | splitter._hadjust = dimSum(splitter, "borderTopWidth", "borderBottomWidth", "marginBottom"); 197 | splitter._hmin = Math.max(dimSum(splitter, "minHeight"), 20); 198 | $(window).bind("resize", 199 | function() { 200 | var top = splitter.offset().top; 201 | var wh = $(window).height(); 202 | splitter.css("height", Math.max(wh - top - splitter._hadjust, splitter._hmin) + "px"); 203 | if (!$.browser.msie) splitter.trigger("resize"); 204 | }).trigger("resize"); 205 | } 206 | else if (opts.resizeToWidth && !$.browser.msie) 207 | $(window).bind("resize", function() { 208 | splitter.trigger("resize"); 209 | }); 210 | 211 | // Resize event handler; triggered immediately to set initial position 212 | splitter.bind("resize", 213 | function(e, size) { 214 | // Custom events bubble in jQuery 1.3; don't Yo Dawg 215 | if (e.target != this) return; 216 | // Determine new width/height of splitter container 217 | splitter._DF = splitter[0][opts.pxFixed] - splitter._PBF; 218 | splitter._DA = splitter[0][opts.pxSplit] - splitter._PBA; 219 | // Bail if splitter isn't visible or content isn't there yet 220 | if (splitter._DF <= 0 || splitter._DA <= 0) return; 221 | // Re-divvy the adjustable dimension; maintain size of the preferred pane 222 | resplit(!isNaN(size) ? size : (!(opts.sizeRight || opts.sizeBottom) ? A[0][opts.pxSplit] : 223 | splitter._DA - B[0][opts.pxSplit] - bar._DA)); 224 | }).trigger("resize", [initPos]); 225 | }); 226 | }; 227 | 228 | })(jQuery); --------------------------------------------------------------------------------