├── .npmignore ├── .gitignore ├── demo ├── images │ ├── 1.jpg │ ├── 2.jpg │ └── 3.png ├── demo.html └── colorpicker │ ├── spectrum.css │ └── spectrum.js ├── i18n └── jquery.memegenerator.pl.js ├── gulpfile.js ├── package.json ├── dist ├── jquery.memegenerator.min.css └── jquery.memegenerator.min.js ├── src ├── less │ └── jquery.memegenerator.less ├── css │ └── jquery.memegenerator.css └── js │ └── jquery.memegenerator.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | demo 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .idea 3 | .kdev4 4 | -------------------------------------------------------------------------------- /demo/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakG10/jquery-meme-generator/HEAD/demo/images/1.jpg -------------------------------------------------------------------------------- /demo/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakG10/jquery-meme-generator/HEAD/demo/images/2.jpg -------------------------------------------------------------------------------- /demo/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MakG10/jquery-meme-generator/HEAD/demo/images/3.png -------------------------------------------------------------------------------- /i18n/jquery.memegenerator.pl.js: -------------------------------------------------------------------------------- 1 | $.fn.memeGenerator("i18n", "pl", { 2 | topTextPlaceholder: "GÓRNY TEKST", 3 | bottomTextPlaceholder: "DOLNY TEKST", 4 | 5 | addTextbox: "Dodaj pole tekstowe", 6 | advancedSettings: "Zaawansowane opcje", 7 | toolbox: "Rysowanie", 8 | 9 | optionCapitalLetters: "Używaj tylko wielkich liter", 10 | optionDragResize: "Włącz interaktywne przemieszczanie i skalowanie pól tekstowych", 11 | optionDrawingAboveText: "Pokazuj rysunek ponad tekstem", 12 | 13 | drawingStart: "Rysuj", 14 | drawingStop: "Przestań rysować", 15 | drawingErase: "Wyczyść", 16 | }); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var uglify = require('gulp-uglify'); 3 | var less = require('gulp-less'); 4 | var cleanCSS = require('gulp-clean-css'); 5 | var gutil = require('gulp-util'); 6 | var rename = require('gulp-rename'); 7 | 8 | gulp.task('default', ['build']); 9 | 10 | gulp.task('build', ['styles', 'scripts']); 11 | 12 | gulp.task('styles', function() { 13 | gulp.src('./src/less/jquery.memegenerator.less') 14 | .pipe(less()) 15 | .pipe(gulp.dest('./src/css')); 16 | 17 | gulp.src('src/css/jquery.memegenerator.css') 18 | .pipe(cleanCSS().on('error', gutil.log)) 19 | .pipe(rename({suffix: '.min'})) 20 | .pipe(gulp.dest('./dist/')); 21 | }); 22 | 23 | gulp.task('scripts', function() { 24 | gulp.src('src/js/jquery.memegenerator.js') 25 | .pipe(uglify({preserveComments: 'license'}).on('error', gutil.log)) 26 | .pipe(rename({suffix: '.min'})) 27 | .pipe(gulp.dest('./dist/')); 28 | }); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-meme-generator", 3 | "version": "1.0.4", 4 | "description": "Meme Generator is a jQuery plugin allowing easily creating images with captions (memes) using interactive editor.", 5 | "main": "src/js/jquery.memegenerator.js", 6 | "scripts": {}, 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/MakG10/jquery-meme-generator.git" 10 | }, 11 | "keywords": [ 12 | "jquery", 13 | "plugin", 14 | "meme", 15 | "generator", 16 | "image", 17 | "caption" 18 | ], 19 | "author": "Maciej Gierej (http://maciej.gierej.pl)", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/MakG10/jquery-meme-generator/issues" 23 | }, 24 | "homepage": "http://maciej.gierej.pl/en/jquery-meme-generator", 25 | "dependencies": { 26 | "jquery": ">=1.7.2", 27 | "jquery-ui": ">=1.7.2" 28 | }, 29 | "devDependencies": { 30 | "gulp": "^3.9.1", 31 | "gulp-clean-css": "^2.0.13", 32 | "gulp-less": "^3.2.0", 33 | "gulp-rename": "^1.2.2", 34 | "gulp-uglify": "^2.0.0", 35 | "gulp-util": "^3.0.7" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Meme Generator 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 51 | 52 | 53 | 54 | 55 |
56 |

Default meme generator without any custom options

57 | 58 |
59 | 60 |
61 |

Using bootstrap styling and fixed width (image is scaled)

62 | 63 |
64 | 65 |
66 |

Horizontal layout and CSS preview

67 | 68 |
69 | 70 |
71 |

Custom save button

72 | 73 | 74 | 75 |
76 | 77 |
78 |
79 |
80 | 81 |
82 | 83 | 84 | 85 |
86 |
87 |
88 |
89 | 90 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /dist/jquery.memegenerator.min.css: -------------------------------------------------------------------------------- 1 | .mg-wrapper{position:relative;text-align:center}.mg-wrapper.mg-horizontal-layout .mg-image{float:left;width:55%;margin-right:5%}.mg-wrapper.mg-horizontal-layout .mg-controls{float:left;width:40%}.mg-wrapper .mg-image{position:relative;display:inline-block;margin:0 auto 10px;width:auto;overflow:hidden;text-align:left}.mg-wrapper .mg-image>img{max-width:100%;width:100%}.mg-wrapper .mg-image:hover .mg-helpers{visibility:visible}.mg-wrapper .mg-canvas{position:absolute;z-index:3;top:0;bottom:0;left:0;right:0}.mg-wrapper .mg-canvas canvas{position:absolute;top:0;bottom:0;left:0;right:0;z-index:3}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox{margin-bottom:10px;text-align:right}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox:last-child{margin:0}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox:after{content:' ';height:0;display:block;clear:both}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox input{box-sizing:border-box;padding:0 5px;max-width:100%;height:30px;font-size:16px;border-top:none;border-right:none;border-left:none;border-bottom:2px solid #ddd}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox .colorpickerContainer,.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox input:not(:last-child){float:left;margin-right:2%}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox .mg-textbox-text{width:40%}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox .mg-textbox-border-width,.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox .mg-textbox-size{width:13%}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox .colorpickerContainer{width:13%}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox .colorpickerReplacer{width:100%}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-add-textbox,.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-advanced-settings-toggle,.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox-toggle{display:block;padding:5px 0;text-align:center;font-size:20px}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-add-textbox.active,.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-advanced-settings-toggle.active,.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox-toggle.active{font-weight:700}.mg-wrapper:not(.usingBootstrap) .mg-controls .option input{margin-right:5%}.mg-wrapper:not(.usingBootstrap) .mg-controls .option label{display:inline}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox .mg-toolbox-item{float:left;width:21.25%;margin-right:5%;text-align:center}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox .mg-toolbox-item .colorpickerReplacer,.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox .mg-toolbox-item button,.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox .mg-toolbox-item input{box-sizing:border-box;width:100%;height:30px}.mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox .mg-toolbox-item:last-child{margin-right:0}.mg-wrapper.usingBootstrap .mg-controls .row:not(:last-child){margin-bottom:10px}.mg-wrapper.usingBootstrap .mg-controls .colorpickerReplacer,.mg-wrapper.usingBootstrap .mg-controls input{width:100%}.mg-wrapper.usingBootstrap .mg-controls .col-md-1{margin-bottom:5px}.mg-wrapper.usingBootstrap .mg-controls .col-md-2{margin-bottom:5px}.mg-wrapper.usingBootstrap .mg-controls .col-md-3{margin-bottom:5px}.mg-wrapper.usingBootstrap .mg-controls .col-md-4{margin-bottom:5px}.mg-wrapper.usingBootstrap .mg-controls .col-md-5{margin-bottom:5px}.mg-wrapper.usingBootstrap .mg-controls .col-md-6{margin-bottom:5px}.mg-wrapper.usingBootstrap .mg-controls .col-md-7{margin-bottom:5px}.mg-wrapper.usingBootstrap .mg-controls .col-md-8{margin-bottom:5px}.mg-wrapper.usingBootstrap .mg-controls .col-md-9{margin-bottom:5px}.mg-wrapper.usingBootstrap .mg-controls .col-md-10{margin-bottom:5px}.mg-wrapper.usingBootstrap .mg-controls .col-md-11{margin-bottom:5px}.mg-wrapper.usingBootstrap .mg-controls .col-md-12{margin-bottom:5px}.mg-wrapper .mg-controls{text-align:left}.mg-wrapper .mg-controls .mg-textbox{margin-bottom:10px}.mg-wrapper .mg-controls .mg-textbox>div{text-align:center}.mg-wrapper .mg-controls .option :first-child{text-align:center}.mg-wrapper .mg-controls .sp-preview{margin:0;width:80%}.mg-wrapper .mg-controls .sp-dd{width:20%;text-align:right}.mg-wrapper .mg-helpers{visibility:hidden;position:absolute;top:0;bottom:0;left:0;right:0}.mg-wrapper .mg-helpers .draggable{box-sizing:border-box;position:absolute;top:0;z-index:4;width:100%;border:2px dashed #222}.mg-wrapper .mg-helpers .draggable:hover:before{border-color:#fff}.mg-wrapper .mg-helpers .draggable:before{display:block;content:' ';position:absolute;top:0;bottom:0;left:0;right:0;border:2px dashed #ccc}.mg-wrapper .mg-css-preview{position:absolute;top:0;bottom:0;left:0;right:0;z-index:2}.mg-wrapper .mg-css-preview div{position:absolute}.mg-wrapper .mg-drawing{display:none;position:absolute;z-index:5;top:0;bottom:0;left:0;right:0}.mg-wrapper:after{content:' ';height:0;display:block;clear:both} -------------------------------------------------------------------------------- /src/less/jquery.memegenerator.less: -------------------------------------------------------------------------------- 1 | .mg-wrapper { 2 | position: relative; 3 | text-align: center; 4 | 5 | &.mg-horizontal-layout { 6 | .mg-image { 7 | float: left; 8 | width: 55%; 9 | margin-right: 5%; 10 | } 11 | 12 | .mg-controls { 13 | float: left; 14 | width: 40%; 15 | } 16 | } 17 | 18 | .mg-image { 19 | position: relative; 20 | display: inline-block; 21 | margin: 0 auto 10px; 22 | width: auto; 23 | overflow: hidden; 24 | text-align: left; 25 | 26 | & > img { 27 | max-width: 100%; 28 | width: 100%; 29 | } 30 | 31 | &:hover .mg-helpers { 32 | visibility: visible; 33 | } 34 | } 35 | 36 | .mg-canvas { 37 | position: absolute; 38 | z-index: 3; 39 | .sides(0px); 40 | 41 | canvas { 42 | position: absolute; 43 | .sides(0px); 44 | z-index: 3; 45 | } 46 | } 47 | 48 | &:not(.usingBootstrap) { 49 | .mg-controls { 50 | .mg-textbox { 51 | margin-bottom: 10px; 52 | text-align: right; 53 | 54 | &:last-child { 55 | margin: 0; 56 | } 57 | 58 | &:after { 59 | .clear(); 60 | } 61 | 62 | input { 63 | box-sizing: border-box; 64 | padding: 0 5px; 65 | max-width: 100%; 66 | height: 30px; 67 | 68 | font-size: 16px; 69 | 70 | border-top: none; 71 | border-right: none; 72 | border-left: none; 73 | border-bottom: 2px solid #DDD; 74 | } 75 | 76 | input:not(:last-child), .colorpickerContainer { 77 | float: left; 78 | margin-right: 2%; 79 | } 80 | 81 | .mg-textbox-text { 82 | width: 40%; 83 | } 84 | 85 | .mg-textbox-size, .mg-textbox-border-width { 86 | width: 13%; 87 | } 88 | 89 | .colorpickerContainer { 90 | width: 13%; 91 | } 92 | 93 | .colorpickerReplacer { 94 | width: 100%; 95 | } 96 | } 97 | 98 | .mg-add-textbox, .mg-advanced-settings-toggle, .mg-toolbox-toggle { 99 | display: block; 100 | padding: 5px 0; 101 | 102 | text-align: center; 103 | font-size: 20px; 104 | 105 | &.active { 106 | font-weight: bold; 107 | } 108 | } 109 | 110 | .option { 111 | input { 112 | margin-right: 5%; 113 | } 114 | 115 | label { 116 | display: inline; 117 | } 118 | } 119 | 120 | .mg-toolbox { 121 | .mg-toolbox-item { 122 | float: left; 123 | width: 21.25%; 124 | margin-right: 5%; 125 | 126 | text-align: center; 127 | 128 | button, input, .colorpickerReplacer { 129 | box-sizing: border-box; 130 | width: 100%; 131 | height: 30px; 132 | } 133 | 134 | &:last-child { 135 | margin-right: 0; 136 | } 137 | } 138 | } 139 | } 140 | } 141 | 142 | &.usingBootstrap { 143 | .mg-controls { 144 | .row:not(:last-child) { 145 | margin-bottom: 10px; 146 | } 147 | 148 | input, .colorpickerReplacer { 149 | width: 100%; 150 | } 151 | 152 | .set-col-margins(12); 153 | } 154 | } 155 | 156 | .mg-controls { 157 | text-align: left; 158 | 159 | .mg-textbox { 160 | margin-bottom: 10px; 161 | 162 | & > div { 163 | text-align: center; 164 | } 165 | } 166 | 167 | .option :first-child { 168 | text-align: center; 169 | } 170 | 171 | 172 | /* Adjustments for default color picker */ 173 | 174 | .sp-preview { 175 | margin: 0; 176 | width: 80%; 177 | } 178 | 179 | .sp-dd { 180 | width: 20%; 181 | text-align: right; 182 | } 183 | } 184 | 185 | .mg-helpers { 186 | visibility: hidden; 187 | position: absolute; 188 | .sides(0px); 189 | 190 | .draggable { 191 | box-sizing: border-box; 192 | position: absolute; 193 | top: 0; 194 | z-index: 4; 195 | 196 | width: 100%; 197 | 198 | border: 2px dashed #222; 199 | 200 | &:hover { 201 | &:before { 202 | border-color: #FFF; 203 | } 204 | } 205 | 206 | &:before { 207 | display: block; 208 | content: ' '; 209 | 210 | position: absolute; 211 | .sides(0px); 212 | 213 | border: 2px dashed #CCC; 214 | } 215 | } 216 | } 217 | 218 | .mg-css-preview { 219 | position: absolute; 220 | .sides(0px); 221 | z-index: 2; 222 | 223 | div { 224 | position: absolute; 225 | // z-index: 3; 226 | } 227 | } 228 | 229 | .mg-drawing { 230 | display: none; 231 | position: absolute; 232 | z-index: 5; 233 | .sides(0px); 234 | } 235 | 236 | &:after { 237 | .clear(); 238 | } 239 | } 240 | 241 | .set-col-margins(@n, @i: 1) when (@i =< @n) { 242 | .col-md-@{i} { 243 | margin-bottom: 5px 244 | } 245 | 246 | .set-col-margins(@n, (@i + 1)); 247 | } 248 | 249 | .sides(@length) { 250 | top: @length; 251 | bottom: @length; 252 | left: @length; 253 | right: @length; 254 | } 255 | 256 | .clear() { 257 | content: ' '; 258 | height: 0; 259 | display: block; 260 | clear: both; 261 | } -------------------------------------------------------------------------------- /src/css/jquery.memegenerator.css: -------------------------------------------------------------------------------- 1 | .mg-wrapper { 2 | position: relative; 3 | text-align: center; 4 | } 5 | .mg-wrapper.mg-horizontal-layout .mg-image { 6 | float: left; 7 | width: 55%; 8 | margin-right: 5%; 9 | } 10 | .mg-wrapper.mg-horizontal-layout .mg-controls { 11 | float: left; 12 | width: 40%; 13 | } 14 | .mg-wrapper .mg-image { 15 | position: relative; 16 | display: inline-block; 17 | margin: 0 auto 10px; 18 | width: auto; 19 | overflow: hidden; 20 | text-align: left; 21 | } 22 | .mg-wrapper .mg-image > img { 23 | max-width: 100%; 24 | width: 100%; 25 | } 26 | .mg-wrapper .mg-image:hover .mg-helpers { 27 | visibility: visible; 28 | } 29 | .mg-wrapper .mg-canvas { 30 | position: absolute; 31 | z-index: 3; 32 | top: 0px; 33 | bottom: 0px; 34 | left: 0px; 35 | right: 0px; 36 | } 37 | .mg-wrapper .mg-canvas canvas { 38 | position: absolute; 39 | top: 0px; 40 | bottom: 0px; 41 | left: 0px; 42 | right: 0px; 43 | z-index: 3; 44 | } 45 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox { 46 | margin-bottom: 10px; 47 | text-align: right; 48 | } 49 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox:last-child { 50 | margin: 0; 51 | } 52 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox:after { 53 | content: ' '; 54 | height: 0; 55 | display: block; 56 | clear: both; 57 | } 58 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox input { 59 | box-sizing: border-box; 60 | padding: 0 5px; 61 | max-width: 100%; 62 | height: 30px; 63 | font-size: 16px; 64 | border-top: none; 65 | border-right: none; 66 | border-left: none; 67 | border-bottom: 2px solid #DDD; 68 | } 69 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox input:not(:last-child), 70 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox .colorpickerContainer { 71 | float: left; 72 | margin-right: 2%; 73 | } 74 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox .mg-textbox-text { 75 | width: 40%; 76 | } 77 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox .mg-textbox-size, 78 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox .mg-textbox-border-width { 79 | width: 13%; 80 | } 81 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox .colorpickerContainer { 82 | width: 13%; 83 | } 84 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-textbox .colorpickerReplacer { 85 | width: 100%; 86 | } 87 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-add-textbox, 88 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-advanced-settings-toggle, 89 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox-toggle { 90 | display: block; 91 | padding: 5px 0; 92 | text-align: center; 93 | font-size: 20px; 94 | } 95 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-add-textbox.active, 96 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-advanced-settings-toggle.active, 97 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox-toggle.active { 98 | font-weight: bold; 99 | } 100 | .mg-wrapper:not(.usingBootstrap) .mg-controls .option input { 101 | margin-right: 5%; 102 | } 103 | .mg-wrapper:not(.usingBootstrap) .mg-controls .option label { 104 | display: inline; 105 | } 106 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox .mg-toolbox-item { 107 | float: left; 108 | width: 21.25%; 109 | margin-right: 5%; 110 | text-align: center; 111 | } 112 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox .mg-toolbox-item button, 113 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox .mg-toolbox-item input, 114 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox .mg-toolbox-item .colorpickerReplacer { 115 | box-sizing: border-box; 116 | width: 100%; 117 | height: 30px; 118 | } 119 | .mg-wrapper:not(.usingBootstrap) .mg-controls .mg-toolbox .mg-toolbox-item:last-child { 120 | margin-right: 0; 121 | } 122 | .mg-wrapper.usingBootstrap .mg-controls .row:not(:last-child) { 123 | margin-bottom: 10px; 124 | } 125 | .mg-wrapper.usingBootstrap .mg-controls input, 126 | .mg-wrapper.usingBootstrap .mg-controls .colorpickerReplacer { 127 | width: 100%; 128 | } 129 | .mg-wrapper.usingBootstrap .mg-controls .col-md-1 { 130 | margin-bottom: 5px; 131 | } 132 | .mg-wrapper.usingBootstrap .mg-controls .col-md-2 { 133 | margin-bottom: 5px; 134 | } 135 | .mg-wrapper.usingBootstrap .mg-controls .col-md-3 { 136 | margin-bottom: 5px; 137 | } 138 | .mg-wrapper.usingBootstrap .mg-controls .col-md-4 { 139 | margin-bottom: 5px; 140 | } 141 | .mg-wrapper.usingBootstrap .mg-controls .col-md-5 { 142 | margin-bottom: 5px; 143 | } 144 | .mg-wrapper.usingBootstrap .mg-controls .col-md-6 { 145 | margin-bottom: 5px; 146 | } 147 | .mg-wrapper.usingBootstrap .mg-controls .col-md-7 { 148 | margin-bottom: 5px; 149 | } 150 | .mg-wrapper.usingBootstrap .mg-controls .col-md-8 { 151 | margin-bottom: 5px; 152 | } 153 | .mg-wrapper.usingBootstrap .mg-controls .col-md-9 { 154 | margin-bottom: 5px; 155 | } 156 | .mg-wrapper.usingBootstrap .mg-controls .col-md-10 { 157 | margin-bottom: 5px; 158 | } 159 | .mg-wrapper.usingBootstrap .mg-controls .col-md-11 { 160 | margin-bottom: 5px; 161 | } 162 | .mg-wrapper.usingBootstrap .mg-controls .col-md-12 { 163 | margin-bottom: 5px; 164 | } 165 | .mg-wrapper .mg-controls { 166 | text-align: left; 167 | /* Adjustments for default color picker */ 168 | } 169 | .mg-wrapper .mg-controls .mg-textbox { 170 | margin-bottom: 10px; 171 | } 172 | .mg-wrapper .mg-controls .mg-textbox > div { 173 | text-align: center; 174 | } 175 | .mg-wrapper .mg-controls .option :first-child { 176 | text-align: center; 177 | } 178 | .mg-wrapper .mg-controls .sp-preview { 179 | margin: 0; 180 | width: 80%; 181 | } 182 | .mg-wrapper .mg-controls .sp-dd { 183 | width: 20%; 184 | text-align: right; 185 | } 186 | .mg-wrapper .mg-helpers { 187 | visibility: hidden; 188 | position: absolute; 189 | top: 0px; 190 | bottom: 0px; 191 | left: 0px; 192 | right: 0px; 193 | } 194 | .mg-wrapper .mg-helpers .draggable { 195 | box-sizing: border-box; 196 | position: absolute; 197 | top: 0; 198 | z-index: 4; 199 | width: 100%; 200 | border: 2px dashed #222; 201 | } 202 | .mg-wrapper .mg-helpers .draggable:hover:before { 203 | border-color: #FFF; 204 | } 205 | .mg-wrapper .mg-helpers .draggable:before { 206 | display: block; 207 | content: ' '; 208 | position: absolute; 209 | top: 0px; 210 | bottom: 0px; 211 | left: 0px; 212 | right: 0px; 213 | border: 2px dashed #CCC; 214 | } 215 | .mg-wrapper .mg-css-preview { 216 | position: absolute; 217 | top: 0px; 218 | bottom: 0px; 219 | left: 0px; 220 | right: 0px; 221 | z-index: 2; 222 | } 223 | .mg-wrapper .mg-css-preview div { 224 | position: absolute; 225 | } 226 | .mg-wrapper .mg-drawing { 227 | display: none; 228 | position: absolute; 229 | z-index: 5; 230 | top: 0px; 231 | bottom: 0px; 232 | left: 0px; 233 | right: 0px; 234 | } 235 | .mg-wrapper:after { 236 | content: ' '; 237 | height: 0; 238 | display: block; 239 | clear: both; 240 | } 241 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery Meme Generator 2 | - [About](#about) 3 | - [Features](#features) 4 | - [Demo](#demo) 5 | - [Requirements](#requirements) 6 | - [Installation](#installation) 7 | - [Configuration](#configuration) 8 | - [Internationalization](#internationalization) 9 | - [Saving an image on server side](#saving-an-image-on-server-side) 10 | - [PHP](#php) 11 | - [Python w/ Django](#python-w-django) 12 | - [Changelog](#changelog) 13 | - [License](#license) 14 | 15 | ## About 16 | Meme Generator is a jQuery plugin allowing easily creating images with captions (memes). 17 | 18 | Written by Maciej Gierej - http://makg.eu 19 | 20 | ## Features 21 | - Seperate styling for each text box 22 | - Drawing tool with customizable color and size 23 | - Two preview modes - canvas and CSS - latter being more suitable for slow machines 24 | - Saving image as data url or canvas 25 | - Predefining captions 26 | 27 | ## Demo 28 | http://makg10.github.io/jquery-meme-generator/ 29 | 30 | ## Requirements 31 | - jQuery 1.11+ 32 | - jQuery UI 1.11+ (draggable and resizeable plugins) 33 | - Spectrum (optional) 34 | 35 | ## Installation 36 | 37 | ### (optional) Installing npm package 38 | You can install jQuery Meme Generator using npm: 39 | ``` 40 | npm install --save jquery-meme-generator 41 | ``` 42 | 43 | ### Step 1 - Include required files 44 | ```html 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 | ### Step 2 - Markup 69 | Meme Generator doesn't require any special markup - you can attach the plugin to any `` tag. 70 | 71 | ```html 72 | 73 | ``` 74 | 75 | ### Step 3 - Attach Meme Generator plugin 76 | Call Meme Generator plugin after you create a target `` element or call it when the DOM is ready. 77 | 78 | ```javascript 79 | $(document).ready(function(){ 80 | $("#meme").memeGenerator(); 81 | }); 82 | ``` 83 | 84 | ## Configuration 85 | 86 | ### Options 87 | Property |Default |Description 88 | ---------------------|------------|----------- 89 | defaultTextStyle |{
color: "#FFFFFF",
size: 42,
lineHeight: 1.2,
font: "Impact, Arial",
style: "normal",
forceUppercase: true,
borderColor: "#000000",
borderWidth: 2,
}|Default text style properties for all captions. Each text box can be styled seperately by user. 90 | defaultDrawingStyle |{
color: "#FF0000",
lineWidth: 10,
}|Default drawing style properties. 91 | minFontSize |1 |Minimum font size the user can set. 92 | maxFontSize |128 |Maximum font size the user can set. 93 | minBorderWidth |0 |Minimum border (text stroke) size the user can set. 94 | maxBorderWidth |10 |Maximum border (text stroke) size the user can set. 95 | fontSizeStep |1 |Step size for spinner related to the font size. 96 | borderWidthStep |1 |Step size for spinner related to the text stroke size. 97 | captions |[] |Default captions that will show up after plugin initialisation. The first element of the array is the top text, the second is the bottom text and every array element after that is an additional text box positioned in the middle. 98 | previewMode |canvas |Preview mode (rendering method). Available options: "canvas", "css". CSS mode is faster on slow machines, but isn't 100% accurate with rendered canvas layers. 99 | outputFormat |image/png |Output format of saved canvas. Can be any format supported by HTML5 Canvas. 100 | editingEnabled |true |Enables/disables editing. If set to false, the controls don't show at all. You can use it to just present captions defined by you. 101 | dragResizeEnabled |true |Enables/disables dragging and resizing text boxes. 102 | drawingAboveText |true |Determines whether the drawing should be render above captions (true) or below (false). 103 | showAdvancedSettings |true |Enables/disables editing advanced settings by user. 104 | colorPicker |null |Color picker callback, if you want to use a different color picker. Set to *false* if you want to disable color picker entirely. See an example in the "Custom Color Picker" section below. 105 | wrapperClass |mg-wrapper |Class name of the div wrapping the image and all additional elements created by this plugin. 106 | toolboxSelector |null |Selector or jQuery object of toolbox container. Use it if you want to have the drawing UI in a different place on the page. 107 | layout |vertical |Layout of the meme generator. Available options: "vertical", "horizontal". 108 | useBootstrap |false |Set to *true* if you want to automatically style meme generator with Bootstrap3. The Bootstrap3 CSS file needs to be included on the page. 109 | 110 | ### Callbacks 111 | Property |Default |Description 112 | ---------------------|------------|----------- 113 | `onLayerChange` |null |It fires when any layer is changed (re-rendered). 114 | `onNewTextBox` |null |It fires when the user creates new text box. 115 | `onInit` |null |It fires when the meme generator has been initialized (the source images has been loaded and UI has been created) 116 | 117 | #### Example 118 | ```javascript 119 | $("img").memeGenerator({ 120 | onInit: function(){ 121 | this.deserialize('json to deserialize...'); 122 | } 123 | }); 124 | ``` 125 | 126 | ### Methods 127 | Method name |Description 128 | ---------------------|----------- 129 | save |Returns the image with captions as data url string.
**Usage:**`$("selector").memeGenerator("save");` 130 | saveCanvas |Returns the image with captions as a single canvas element.
**Usage:**`$("selector").memeGenerator("saveCanvas");` 131 | download |Generates the image and automatically initiates a download.
**Parameters:** filename (optional)
__Note:__ file name should have an extension appropriate to outputFormat option (default image/png). Default file name is "image.png".
**Usage:** `$("selector").memeGenerator("download", "image.png");` 132 | serialize |Returns JSON string with all the layers which can be stored and restored later with deserialize method.
__Note:__ Currently only text layers are supported, drawings aren't getting serialized.
**Usage:** `var json = $("selector").memeGenerator("serialize");` 133 | deserialize |Restores the layers from the JSON string. If there is missing data from the layers (i.e. no font size), the default values will be used.
__Note:__ As of now, you have to make sure to call `deserialize` after the meme generator has been initialized (image has been loaded and controls created). You can use `onInit` event.
**Usage:** `$("selector").memeGenerator("deserialize", '[{"type":"text","name":"layer1","text":"TEXT1","x":"0","y":"0","maxWidth":"555","fontSize":"60","lineHeight":1.2,"font":"Impact, Arial","color":"#69aae7","borderColor":"#000000","borderWidth":"6"},{"type":"text","name":"layer2","text":"TEXT2","x":"0","y":"454","maxWidth":"555","fontSize":"42","lineHeight":1.2,"font":"Impact, Arial","color":"#00ff6c","borderColor":"#ff0000","borderWidth":"2"}]');` 134 | destroy |Destroys the meme generator leaving the source tag intact. 135 | 136 | ### Custom Color Picker - Example 137 | By default, Meme Generator plugin is using Spectrum as a color picker, if it's included on the page, otherwise it falls back to a simple text input. 138 | 139 | You can use a different color picker with "colorPicker" option. It takes a callback function which executes when creating text boxes. Example: 140 | 141 | ```javascript 142 | $("img").memeGenerator({ 143 | colorPicker: function(mg, selector){ 144 | selector.colorPicker(); 145 | // Other stuff required for your color picker to work 146 | // selector is a jQuery object with input element(s) 147 | } 148 | }); 149 | ``` 150 | 151 | ## Internationalization 152 | To translate UI messages, simply include a proper i18n file after Meme Generator JS file. 153 | 154 | ```html 155 | 156 | ``` 157 | 158 | If you want to create a new translation, you can see an example in `i18n/jquery.memegenerator.pl.js`. 159 | 160 | ```javascript 161 | $.fn.memeGenerator("i18n", "pl", { 162 | topTextPlaceholder: "GÓRNY TEKST", 163 | bottomTextPlaceholder: "DOLNY TEKST", 164 | 165 | addTextbox: "Dodaj pole tekstowe", 166 | advancedSettings: "Zaawansowane opcje", 167 | toolbox: "Rysowanie", 168 | 169 | optionCapitalLetters: "Używaj tylko wielkich liter", 170 | optionDragResize: "Włącz interaktywne przemieszczanie i skalowanie pól tekstowych", 171 | optionDrawingAboveText: "Pokazuj rysunek ponad tekstem", 172 | 173 | drawingStart: "Rysuj", 174 | drawingStop: "Przestań rysować", 175 | drawingErase: "Wyczyść", 176 | }); 177 | ``` 178 | 179 | ## Saving an image on server side 180 | You can easily send an image using AJAX request with generated image data URL as a parameter. 181 | 182 | ```javascript 183 | 184 | 185 | 204 | ``` 205 | 206 | ### PHP 207 | ```php 208 | '/scripts/images/' . $filename)); 222 | } else { 223 | throw new Exception('Could not save the file.'); 224 | } 225 | } else { 226 | throw new Exception('Invalid data URL.'); 227 | } 228 | ``` 229 | 230 | ### Python w/ Django 231 | ```python 232 | import hashlib 233 | import json 234 | import re 235 | from binascii import a2b_base64 236 | from django.http import JsonResponse 237 | 238 | def save_img(request): 239 | if request.method == 'POST': 240 | data = request.POST.get('image', '') 241 | 242 | matches = re.match(r'data:image\/(gif|jpeg|png);base64,(.*)', data) 243 | 244 | binary_data = a2b_base64(matches.group(2)) 245 | filename = hashlib.md5(binary_data).hexdigest() + '.png'; 246 | 247 | fd = open('static/images/' + filename, 'wb') 248 | fd.write(binary_data) 249 | fd.close() 250 | 251 | return JsonResponse({'filename': request.build_absolute_uri('/static/images/' + filename)}) 252 | ``` 253 | 254 | ## Changelog 255 | 256 | ### Version 1.0.4 257 | - New "destroy" method 258 | - Handling dynamic changes of "src" attribute 259 | - Fixed jQuery 3 compatibility issue 260 | 261 | ### Version 1.0.3 262 | - New serialize and deserialize methods 263 | - Experimental: changing text directly on image 264 | - Filename argument for the public "download" method 265 | - New onInit event 266 | 267 | ### Version 1.0.2 268 | - New lineHeight param 269 | - Fix: forceUppercase causing text selection problems (issue #3) 270 | - New useWordpressStyle option 271 | 272 | ### Version 1.0.1 273 | - Added gulp tasks 274 | 275 | ### Version 1.0 276 | Initial release 277 | 278 | ## License 279 | Released under the MIT license - http://opensource.org/licenses/MIT 280 | -------------------------------------------------------------------------------- /demo/colorpicker/spectrum.css: -------------------------------------------------------------------------------- 1 | /*** 2 | Spectrum Colorpicker v1.7.1 3 | https://github.com/bgrins/spectrum 4 | Author: Brian Grinstead 5 | License: MIT 6 | ***/ 7 | 8 | .sp-container { 9 | position:absolute; 10 | top:0; 11 | left:0; 12 | display:inline-block; 13 | *display: inline; 14 | *zoom: 1; 15 | /* https://github.com/bgrins/spectrum/issues/40 */ 16 | z-index: 9999994; 17 | overflow: hidden; 18 | } 19 | .sp-container.sp-flat { 20 | position: relative; 21 | } 22 | 23 | /* Fix for * { box-sizing: border-box; } */ 24 | .sp-container, 25 | .sp-container * { 26 | -webkit-box-sizing: content-box; 27 | -moz-box-sizing: content-box; 28 | box-sizing: content-box; 29 | } 30 | 31 | /* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */ 32 | .sp-top { 33 | position:relative; 34 | width: 100%; 35 | display:inline-block; 36 | } 37 | .sp-top-inner { 38 | position:absolute; 39 | top:0; 40 | left:0; 41 | bottom:0; 42 | right:0; 43 | } 44 | .sp-color { 45 | position: absolute; 46 | top:0; 47 | left:0; 48 | bottom:0; 49 | right:20%; 50 | } 51 | .sp-hue { 52 | position: absolute; 53 | top:0; 54 | right:0; 55 | bottom:0; 56 | left:84%; 57 | height: 100%; 58 | } 59 | 60 | .sp-clear-enabled .sp-hue { 61 | top:33px; 62 | height: 77.5%; 63 | } 64 | 65 | .sp-fill { 66 | padding-top: 80%; 67 | } 68 | .sp-sat, .sp-val { 69 | position: absolute; 70 | top:0; 71 | left:0; 72 | right:0; 73 | bottom:0; 74 | } 75 | 76 | .sp-alpha-enabled .sp-top { 77 | margin-bottom: 18px; 78 | } 79 | .sp-alpha-enabled .sp-alpha { 80 | display: block; 81 | } 82 | .sp-alpha-handle { 83 | position:absolute; 84 | top:-4px; 85 | bottom: -4px; 86 | width: 6px; 87 | left: 50%; 88 | cursor: pointer; 89 | border: 1px solid black; 90 | background: white; 91 | opacity: .8; 92 | } 93 | .sp-alpha { 94 | display: none; 95 | position: absolute; 96 | bottom: -14px; 97 | right: 0; 98 | left: 0; 99 | height: 8px; 100 | } 101 | .sp-alpha-inner { 102 | border: solid 1px #333; 103 | } 104 | 105 | .sp-clear { 106 | display: none; 107 | } 108 | 109 | .sp-clear.sp-clear-display { 110 | background-position: center; 111 | } 112 | 113 | .sp-clear-enabled .sp-clear { 114 | display: block; 115 | position:absolute; 116 | top:0px; 117 | right:0; 118 | bottom:0; 119 | left:84%; 120 | height: 28px; 121 | } 122 | 123 | /* Don't allow text selection */ 124 | .sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button { 125 | -webkit-user-select:none; 126 | -moz-user-select: -moz-none; 127 | -o-user-select:none; 128 | user-select: none; 129 | } 130 | 131 | .sp-container.sp-input-disabled .sp-input-container { 132 | display: none; 133 | } 134 | .sp-container.sp-buttons-disabled .sp-button-container { 135 | display: none; 136 | } 137 | .sp-container.sp-palette-buttons-disabled .sp-palette-button-container { 138 | display: none; 139 | } 140 | .sp-palette-only .sp-picker-container { 141 | display: none; 142 | } 143 | .sp-palette-disabled .sp-palette-container { 144 | display: none; 145 | } 146 | 147 | .sp-initial-disabled .sp-initial { 148 | display: none; 149 | } 150 | 151 | 152 | /* Gradients for hue, saturation and value instead of images. Not pretty... but it works */ 153 | .sp-sat { 154 | background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0))); 155 | background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); 156 | background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); 157 | background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); 158 | background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); 159 | background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0)); 160 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)"; 161 | filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81'); 162 | } 163 | .sp-val { 164 | background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0))); 165 | background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); 166 | background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); 167 | background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); 168 | background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); 169 | background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0)); 170 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)"; 171 | filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000'); 172 | } 173 | 174 | .sp-hue { 175 | background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 176 | background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 177 | background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 178 | background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000)); 179 | background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 180 | background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 181 | } 182 | 183 | /* IE filters do not support multiple color stops. 184 | Generate 6 divs, line them up, and do two color gradients for each. 185 | Yes, really. 186 | */ 187 | .sp-1 { 188 | height:17%; 189 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00'); 190 | } 191 | .sp-2 { 192 | height:16%; 193 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00'); 194 | } 195 | .sp-3 { 196 | height:17%; 197 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff'); 198 | } 199 | .sp-4 { 200 | height:17%; 201 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff'); 202 | } 203 | .sp-5 { 204 | height:16%; 205 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff'); 206 | } 207 | .sp-6 { 208 | height:17%; 209 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000'); 210 | } 211 | 212 | .sp-hidden { 213 | display: none !important; 214 | } 215 | 216 | /* Clearfix hack */ 217 | .sp-cf:before, .sp-cf:after { content: ""; display: table; } 218 | .sp-cf:after { clear: both; } 219 | .sp-cf { *zoom: 1; } 220 | 221 | /* Mobile devices, make hue slider bigger so it is easier to slide */ 222 | @media (max-device-width: 480px) { 223 | .sp-color { right: 40%; } 224 | .sp-hue { left: 63%; } 225 | .sp-fill { padding-top: 60%; } 226 | } 227 | .sp-dragger { 228 | border-radius: 5px; 229 | height: 5px; 230 | width: 5px; 231 | border: 1px solid #fff; 232 | background: #000; 233 | cursor: pointer; 234 | position:absolute; 235 | top:0; 236 | left: 0; 237 | } 238 | .sp-slider { 239 | position: absolute; 240 | top:0; 241 | cursor:pointer; 242 | height: 3px; 243 | left: -1px; 244 | right: -1px; 245 | border: 1px solid #000; 246 | background: white; 247 | opacity: .8; 248 | } 249 | 250 | /* 251 | Theme authors: 252 | Here are the basic themeable display options (colors, fonts, global widths). 253 | See http://bgrins.github.io/spectrum/themes/ for instructions. 254 | */ 255 | 256 | .sp-container { 257 | border-radius: 0; 258 | background-color: #ECECEC; 259 | border: solid 1px #f0c49B; 260 | padding: 0; 261 | } 262 | .sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear { 263 | font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; 264 | -webkit-box-sizing: border-box; 265 | -moz-box-sizing: border-box; 266 | -ms-box-sizing: border-box; 267 | box-sizing: border-box; 268 | } 269 | .sp-top { 270 | margin-bottom: 3px; 271 | } 272 | .sp-color, .sp-hue, .sp-clear { 273 | border: solid 1px #666; 274 | } 275 | 276 | /* Input */ 277 | .sp-input-container { 278 | float:right; 279 | width: 100px; 280 | margin-bottom: 4px; 281 | } 282 | .sp-initial-disabled .sp-input-container { 283 | width: 100%; 284 | } 285 | .sp-input { 286 | font-size: 12px !important; 287 | border: 1px inset; 288 | padding: 4px 5px; 289 | margin: 0; 290 | width: 100%; 291 | background:transparent; 292 | border-radius: 3px; 293 | color: #222; 294 | } 295 | .sp-input:focus { 296 | border: 1px solid orange; 297 | } 298 | .sp-input.sp-validation-error { 299 | border: 1px solid red; 300 | background: #fdd; 301 | } 302 | .sp-picker-container , .sp-palette-container { 303 | float:left; 304 | position: relative; 305 | padding: 10px; 306 | padding-bottom: 300px; 307 | margin-bottom: -290px; 308 | } 309 | .sp-picker-container { 310 | width: 172px; 311 | border-left: solid 1px #fff; 312 | } 313 | 314 | /* Palettes */ 315 | .sp-palette-container { 316 | border-right: solid 1px #ccc; 317 | } 318 | 319 | .sp-palette-only .sp-palette-container { 320 | border: 0; 321 | } 322 | 323 | .sp-palette .sp-thumb-el { 324 | display: block; 325 | position:relative; 326 | float:left; 327 | width: 24px; 328 | height: 15px; 329 | margin: 3px; 330 | cursor: pointer; 331 | border:solid 2px transparent; 332 | } 333 | .sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active { 334 | border-color: orange; 335 | } 336 | .sp-thumb-el { 337 | position:relative; 338 | } 339 | 340 | /* Initial */ 341 | .sp-initial { 342 | float: left; 343 | border: solid 1px #333; 344 | } 345 | .sp-initial span { 346 | width: 30px; 347 | height: 25px; 348 | border:none; 349 | display:block; 350 | float:left; 351 | margin:0; 352 | } 353 | 354 | .sp-initial .sp-clear-display { 355 | background-position: center; 356 | } 357 | 358 | /* Buttons */ 359 | .sp-palette-button-container, 360 | .sp-button-container { 361 | float: right; 362 | } 363 | 364 | /* Replacer (the little preview div that shows up instead of the ) */ 365 | .sp-replacer { 366 | margin:0; 367 | overflow:hidden; 368 | cursor:pointer; 369 | padding: 4px; 370 | display:inline-block; 371 | *zoom: 1; 372 | *display: inline; 373 | border: solid 1px #91765d; 374 | background: #eee; 375 | color: #333; 376 | vertical-align: middle; 377 | } 378 | .sp-replacer:hover, .sp-replacer.sp-active { 379 | border-color: #F0C49B; 380 | color: #111; 381 | } 382 | .sp-replacer.sp-disabled { 383 | cursor:default; 384 | border-color: silver; 385 | color: silver; 386 | } 387 | .sp-dd { 388 | padding: 2px 0; 389 | height: 16px; 390 | line-height: 16px; 391 | float:left; 392 | font-size:10px; 393 | } 394 | .sp-preview { 395 | position:relative; 396 | width:25px; 397 | height: 20px; 398 | border: solid 1px #222; 399 | margin-right: 5px; 400 | float:left; 401 | z-index: 0; 402 | } 403 | 404 | .sp-palette { 405 | *width: 220px; 406 | max-width: 220px; 407 | } 408 | .sp-palette .sp-thumb-el { 409 | width:16px; 410 | height: 16px; 411 | margin:2px 1px; 412 | border: solid 1px #d0d0d0; 413 | } 414 | 415 | .sp-container { 416 | padding-bottom:0; 417 | } 418 | 419 | 420 | /* Buttons: http://hellohappy.org/css3-buttons/ */ 421 | .sp-container button { 422 | background-color: #eeeeee; 423 | background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc); 424 | background-image: -moz-linear-gradient(top, #eeeeee, #cccccc); 425 | background-image: -ms-linear-gradient(top, #eeeeee, #cccccc); 426 | background-image: -o-linear-gradient(top, #eeeeee, #cccccc); 427 | background-image: linear-gradient(to bottom, #eeeeee, #cccccc); 428 | border: 1px solid #ccc; 429 | border-bottom: 1px solid #bbb; 430 | border-radius: 3px; 431 | color: #333; 432 | font-size: 14px; 433 | line-height: 1; 434 | padding: 5px 4px; 435 | text-align: center; 436 | text-shadow: 0 1px 0 #eee; 437 | vertical-align: middle; 438 | } 439 | .sp-container button:hover { 440 | background-color: #dddddd; 441 | background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb); 442 | background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb); 443 | background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb); 444 | background-image: -o-linear-gradient(top, #dddddd, #bbbbbb); 445 | background-image: linear-gradient(to bottom, #dddddd, #bbbbbb); 446 | border: 1px solid #bbb; 447 | border-bottom: 1px solid #999; 448 | cursor: pointer; 449 | text-shadow: 0 1px 0 #ddd; 450 | } 451 | .sp-container button:active { 452 | border: 1px solid #aaa; 453 | border-bottom: 1px solid #888; 454 | -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 455 | -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 456 | -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 457 | -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 458 | box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 459 | } 460 | .sp-cancel { 461 | font-size: 11px; 462 | color: #d93f3f !important; 463 | margin:0; 464 | padding:2px; 465 | margin-right: 5px; 466 | vertical-align: middle; 467 | text-decoration:none; 468 | 469 | } 470 | .sp-cancel:hover { 471 | color: #d93f3f !important; 472 | text-decoration: underline; 473 | } 474 | 475 | 476 | .sp-palette span:hover, .sp-palette span.sp-thumb-active { 477 | border-color: #000; 478 | } 479 | 480 | .sp-preview, .sp-alpha, .sp-thumb-el { 481 | position:relative; 482 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==); 483 | } 484 | .sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner { 485 | display:block; 486 | position:absolute; 487 | top:0;left:0;bottom:0;right:0; 488 | } 489 | 490 | .sp-palette .sp-thumb-inner { 491 | background-position: 50% 50%; 492 | background-repeat: no-repeat; 493 | } 494 | 495 | .sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner { 496 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=); 497 | } 498 | 499 | .sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner { 500 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=); 501 | } 502 | 503 | .sp-clear-display { 504 | background-repeat:no-repeat; 505 | background-position: center; 506 | background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==); 507 | } 508 | -------------------------------------------------------------------------------- /dist/jquery.memegenerator.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Maciej Gierej 3 | * https://github.com/MakG10/jquery-memegenerator 4 | */ 5 | !function(e){var t={topTextPlaceholder:"TOP TEXT",bottomTextPlaceholder:"BOTTOM TEXT",addTextbox:"Add Textbox",advancedSettings:"Advanced Settings",toolbox:"Toolbox",optionCapitalLetters:"Use only capital letters",optionDragResize:"Enable dragging and resizing textboxes",optionDrawingAboveText:"Show drawing above text",drawingStart:"Draw",drawingStop:"Stop drawing",drawingErase:"Erase"},a=function(a,n){var i=this;this.settings=e.extend(!0,{},{defaultTextStyle:{color:"#FFFFFF",size:42,lineHeight:1.2,font:"Impact, Arial",style:"normal",forceUppercase:!0,borderColor:"#000000",borderWidth:2},defaultDrawingStyle:{color:"#FF0000",lineWidth:10},minFontSize:1,maxFontSize:128,minBorderWidth:0,maxBorderWidth:10,fontSizeStep:1,borderWidthStep:1,captions:[],previewMode:"canvas",outputFormat:"image/png",editingEnabled:!0,dragResizeEnabled:!0,drawingAboveText:!0,showAdvancedSettings:!0,colorPicker:null,wrapperClass:"mg-wrapper",toolboxSelector:null,layout:"vertical",useBootstrap:!1,useWordpressStyle:!1,onLayerChange:function(e){return!0},onNewTextBox:function(e){return!0},onInit:function(){return!0}},n),this.userSettings={forceUppercase:i.settings.defaultTextStyle.forceUppercase,dragResizeEnabled:i.settings.dragResizeEnabled,drawingAboveText:i.settings.drawingAboveText},this.element=a,this.wrapper=null,this.canvasContainer=null,this.helpersContainer=null,this.canvasLayers=[],this.originalSize=[],this.scale=1,this.initialized=!1,this.save=function(){return i.canvas.save().toDataURL(i.settings.outputFormat)},this.saveCanvas=function(){return i.canvas.save()},this.download=function(t){void 0===t&&(t="image.png");var a=e("").attr("href",this.save()).attr("download",t).appendTo(i.wrapper);a[0].click(),a.remove()},this.serialize=function(){return JSON.stringify(i.getLayersData())},this.deserialize=function(e){var t=JSON.parse(e),a=[];i.canvasLayers.forEach(function(e,t){var n=i.wrapper.find("[data-layer='"+e+"']");if(i.getLayerType(n)){n.remove();return void i.wrapper.find("[data-target-layer='"+e+"']").remove()}a.push(e)}),i.canvasLayers=a,t.reverse().forEach(function(e){"text"==e.type&&i.ui.createTextBox("",null,e).prependTo(i.wrapper.find(".mg-controls"))}),i.events.onLayerChange()},this.destroy=function(){this.element.insertAfter(this.wrapper),this.element.off("load.mg"),this.wrapper.remove()},this.init=function(){a.wrap('
'),i.wrapper=a.parent().parent(),i.canvasContainer=e('
').appendTo(i.wrapper.find("> .mg-image")),i.helpersContainer=e('
').appendTo(i.wrapper.find("> .mg-image")),"horizontal"==i.settings.layout&&i.wrapper.addClass("mg-horizontal-layout"),"css"==i.settings.previewMode&&i.cssPreview.enable(),e("").attr("src",a.attr("src")).on("load",function(){i.originalSize[0]=this.width,i.originalSize[1]=this.height,i.scale=a.width()/i.originalSize[0],i.settings.editingEnabled&&i.wrapper.append(i.ui.createControls()),i.settings.captions.forEach(function(e,t){var a=i.wrapper.find(".mg-textbox-text").eq(t);0==a.length&&(a=i.ui.createTextBox("","center center").insertAfter(i.wrapper.find(".mg-controls .mg-textbox").last()).find(".mg-textbox-text")),i.userSettings.forceUppercase&&(e=i.ui._strtoupper(e)),a.attr("value",e),a.attr("data-text",e),a.trigger("change")}),i.settings.useBootstrap?i.ui.bootstrapify():i.settings.useWordpressStyle&&i.ui.wordpressify(),i.initialized=!0,i.settings.onInit.call(i)}),e(window).on("resize",function(){i.scale=a.width()/i.originalSize[0],i.events.onLayerChange()}),a.on("load.mg",function(){i.initialized&&(i.originalSize[0]=this.naturalWidth,i.originalSize[1]=this.naturalHeight,i.scale=a.width()/i.originalSize[0],i.ui.resizeHelpers(),i.events.onLayerChange())})},this.ui={createControls:function(){var a=e('
');a.append(i.ui.createTextBox(t.topTextPlaceholder,"top center")),a.append(i.ui.createTextBox(t.bottomTextPlaceholder,"bottom center"));var n=e(''+t.addTextbox+"").appendTo(a);if(e(n).click(function(e){e.preventDefault();var t=i.ui.createTextBox("","center center").insertAfter(a.find(".mg-textbox").last());i.settings.useBootstrap?i.ui.bootstrapify():i.settings.useWordpressStyle&&i.ui.wordpressify(),t.find("input[type=text]").first().focus(),i.settings.onNewTextBox.call(i,t)}),i.settings.showAdvancedSettings){var r=e(''+t.advancedSettings+"").appendTo(a),s=e('
').appendTo(a).hide();s.append(e('
').append(e('')).append(e(""))),a.find(".mg-option-uppercase").prop("checked",i.userSettings.forceUppercase),a.find(".mg-option-uppercase").change(function(t){t.preventDefault(),i.userSettings.forceUppercase=e(this).is(":checked"),i.userSettings.forceUppercase?(a.find(".mg-textbox-text").css("textTransform","uppercase"),a.find(".mg-textbox-text").each(function(){e(this).val(i.ui._strtoupper(e(this).val()))}),i.events.onLayerChange()):a.find(".mg-textbox-text").css("textTransform","none")}),s.append(e('
').append(e('')).append(e(""))),a.find(".mg-option-dragresize").prop("checked",i.userSettings.dragResizeEnabled),a.find(".mg-option-dragresize").change(function(t){t.preventDefault(),i.userSettings.dragResizeEnabled=e(this).is(":checked"),i.ui.destroyPositionHelpers(),i.userSettings.dragResizeEnabled&&a.find(".mg-textbox").each(function(){i.ui.createPositionHelper(e(this))})}),s.append(e('
').append(e('')).append(e(""))),a.find(".mg-option-drawing-above-text").prop("checked",i.userSettings.dragResizeEnabled),a.find(".mg-option-drawing-above-text").change(function(t){t.preventDefault(),i.userSettings.drawingAboveText=e(this).is(":checked"),i.userSettings.drawingAboveText?(i.wrapper.find(".mg-canvas canvas, .mg-css-preview").css("zIndex",3),i.wrapper.find(".mg-canvas, .mg-canvas .mg-drawing-layer").css("zIndex",4)):(i.wrapper.find(".mg-canvas canvas, .mg-css-preview").css("zIndex",4),i.wrapper.find(".mg-canvas, .mg-canvas .mg-drawing-layer").css("zIndex",3))}),e(r).click(function(e){e.preventDefault(),s.slideToggle(200),r.toggleClass("active")})}var o;if(null==i.settings.toolboxSelector){var d=e(''+t.toolbox+"").appendTo(a),o=e('
').appendTo(a).hide();e(d).click(function(e){e.preventDefault(),o.slideToggle(200),d.toggleClass("active")})}else o=i.settings.toolboxSelector instanceof jQuery?i.settings.toolboxSelector:e(i.settings.toolboxSelector);return o.append('
"),o.append('
'),o.append('
'),o.append('
"),o.find(".mg-drawing-toggle").click(function(){e(this).toggleClass("active"),e(this).hasClass("active")?(e(this).html(t.drawingStop),i.drawing.enable()):(e(this).html(t.drawingStart),i.drawing.disable())}),o.find(".mg-drawing-line-width").on("change keyup",function(){i.drawing.lineWidth=parseInt(e(this).val(),10)}),o.find(".mg-drawing-erase").click(function(){i.drawing.erase()}),o.find(".colorpicker").change(function(){i.drawing.color=e(this).attr("value")}),i.ui._bindColorpicker(o.find(".colorpicker")),a},createTextBox:function(t,a,n){void 0===n&&(n={}),n=e.extend({layerName:"layer"+(i.canvasLayers.length+1),text:"",x:0,y:0,maxWidth:i.originalSize[0],fontSize:i.settings.defaultTextStyle.size,color:i.settings.defaultTextStyle.color,borderColor:i.settings.defaultTextStyle.borderColor,borderWidth:i.settings.defaultTextStyle.borderWidth},n),n.height=Math.round(i.settings.defaultTextStyle.lineHeight*n.fontSize);var r=n.layerName,s=n.maxWidth,o=n.height,d=null===a?[n.x,n.y]:i.ui._getBoxCoordinates(a,n.maxWidth,n.height),l=e('
');return l.append(e('')),l.append(e('')),i.userSettings.forceUppercase&&l.find(".mg-textbox-text").css("textTransform","uppercase"),0!=i.settings.colorPicker&&(l.append(''),l.append(''),l.find(".colorpicker").wrap('
'),i.ui._bindColorpicker(l.find(".colorpicker"))),l.append(e('')),i.canvasLayers.push(r),l.find(".mg-textbox-text, .mg-textbox-size, .mg-textbox-border-width").on("change keyup",function(){var t=e(this).val();i.userSettings.forceUppercase&&(t=i.ui._strtoupper(t)),e(this).attr("data-text",t),i.events.onLayerChange(r)}),i.userSettings.dragResizeEnabled&&i.ui.createPositionHelper(l),l},createPositionHelper:function(t){var a=t.data("layer"),n=e('
').appendTo(i.helpersContainer);n.css({left:t.attr("data-x")*i.scale,top:t.attr("data-y")*i.scale,width:t.attr("data-width")*i.scale,height:t.attr("data-height")*i.scale}),n.draggable({containment:i.wrapper.find("> .mg-image > img"),drag:function(e,t){var n=i.wrapper.find("[data-layer='"+a+"']");n.attr("data-x",t.position.left*(1/i.scale)),n.attr("data-y",t.position.top*(1/i.scale)),i.events.onLayerChange()}}),n.resizable({containment:i.wrapper.find("> .mg-image > img"),resize:function(e,t){var n=i.wrapper.find("[data-layer='"+a+"']");n.attr("data-width",t.size.width*(1/i.scale)),n.attr("data-height",t.size.height*(1/i.scale)),i.events.onLayerChange()}})},resizeHelpers:function(){i.helpersContainer.find("> div").each(function(){var t,a,n,r=i.wrapper.find("[data-layer='"+e(this).attr("data-target-layer")+"']"),s=null;if(r.attr("data-width")&&(t=parseInt(r.attr("data-width"),10)*i.scale,t>i.helpersContainer.width()&&(t=i.helpersContainer.width()),e(this).css("width",t),r.attr("data-width",parseInt(t*(1/i.scale),10))),r.attr("data-height")&&(a=parseInt(r.attr("data-height"),10)*i.scale,e(this).css("height",a),e(this).resizable("option","minHeight",a),e(this).resizable("option","maxHeight",a)),r.attr("data-x")&&r.attr("data-y")&&(n=r.attr("data-x")*i.scale,n+t>i.helpersContainer.width()&&(n=i.helpersContainer.width()-t),n<0&&(n=0),s=r.attr("data-y")*i.scale,s+a>i.helpersContainer.height()&&(s=i.helpersContainer.height()-a),s<0&&(s=0),e(this).css({left:n,top:s}),r.attr("data-x",parseInt(n*(1/i.scale),10)),r.attr("data-y",parseInt(s*(1/i.scale),10))),parseInt(e(this).css("top"),10)+e(this).outerHeight()>i.helpersContainer.height()){var o=i.helpersContainer.outerHeight()-e(this).outerHeight();e(this).css("top",o),r.attr("data-y",o*(1/i.scale))}})},destroyPositionHelpers:function(){i.helpersContainer.find("> div").remove()},bootstrapify:function(){var t=i.wrapper.find(".mg-controls");i.wrapper.hasClass("usingBootstrap")||t.wrapInner('
'),i.wrapper.addClass("usingBootstrap"),t.find(".mg-textbox").each(function(){e(this).hasClass("row")||(e(this).addClass("row"),e(this).find(".mg-textbox-text").addClass("form-control").wrap(e("
").addClass("col-md-4")),e(this).find(".mg-textbox-size").addClass("form-control").wrap(e("
").addClass("col-md-2")),e(this).find(".mg-textbox-border-width").addClass("form-control").wrap(e("
").addClass("col-md-2")),e(this).find(".colorpicker").addClass("form-control"),e(this).find(".colorpickerContainer").addClass("col-md-2"),e(this).find(".colorpickerReplacer").addClass("btn btn-default"))}),t.find(".mg-add-textbox").hasClass("btn")||t.find(".mg-add-textbox").addClass("btn btn-default btn-block").wrap(e("
").addClass("row")),t.find(".mg-advanced-settings-toggle").hasClass("btn")||(t.find(".mg-advanced-settings-toggle, .mg-toolbox-toggle").addClass("btn btn-primary btn-block").wrap(e("
").addClass("row")),t.find(".mg-advanced-settings, .mg-toolbox").addClass("row"),t.find(".mg-advanced-settings .option input").wrap('
'),t.find(".mg-advanced-settings .option label").wrap('
')),t.find(".mg-drawing-toggle").hasClass("btn")||(t.find(".mg-drawing-toggle, .mg-drawing-erase").addClass("btn btn-default btn-block"),t.find(".mg-toolbox-item").addClass("col-md-3"))},wordpressify:function(){var e=i.wrapper.find(".mg-controls");i.wrapper.addClass("usingWordpress"),e.find(".mg-add-textbox").hasClass("button")||e.find(".mg-add-textbox").addClass("button button-secondary"),e.find(".mg-advanced-settings-toggle").hasClass("button")||e.find(".mg-advanced-settings-toggle, .mg-toolbox-toggle").addClass("button button-primary"),e.find(".mg-drawing-toggle").hasClass("button")||e.find(".mg-drawing-toggle, .mg-drawing-erase").addClass("button button-secondary")},_bindColorpicker:function(t){if(null==i.settings.colorPicker&&e.isFunction(e.fn.spectrum)){var a=function(t){e(this).val(t.toHexString()),e(this).attr("value",t.toHexString()),e(this).trigger("change"),i.events.onLayerChange(e(this).parent().data("layer"))};t.spectrum({replacerClassName:"colorpickerReplacer",change:a,move:a})}else null==i.settings.colorPicker?t.on("change keyup",function(){e(this).attr("value",e(this).val()),i.events.onLayerChange(e(this).parent().data("layer"))}):i.settings.colorPicker.call(this,i,t)},_getBoxCoordinates:function(e,t,a){var n=e.split(" "),r=[];if(2==n.length){switch(n[0]){case"center":r[1]=parseInt(i.originalSize[1]/2,10);break;case"bottom":r[1]=i.originalSize[1]-a;break;case"top":default:r[1]=0}switch(n[1]){case"center":r[0]=parseInt(i.originalSize[0]/2,10)-parseInt(t/2,10);break;case"right":r[0]=i.originalSize[0]-t;break;case"left":default:r[0]=0}}else r[0]=r[1]=0;return r},_normalizePosition:function(e){return e*(2-i.scale)},_strtoupper:function(e){return e.toUpperCase()}},this.getLayersData=function(){var e=[];return i.canvasLayers.forEach(function(t){var a=i.wrapper.find("[data-layer='"+t+"']"),n=i.getLayerType(a);switch(n){case"text":return void e.push({type:n,name:t,text:a.find(".mg-textbox-text").attr("data-text"),x:a.attr("data-x"),y:a.attr("data-y"),maxWidth:a.attr("data-width"),fontSize:a.find(".mg-textbox-size").val(),lineHeight:i.settings.defaultTextStyle.lineHeight,font:i.settings.defaultTextStyle.font,style:i.settings.defaultTextStyle.style,color:a.find(".mg-textbox-text-color").val(),borderColor:a.find(".mg-textbox-border-color").attr("value"),borderWidth:a.find(".mg-textbox-border-width").val()});case"drawing":return void e.push({type:n});default:return}}),e},this.getLayerType=function(e){return e.hasClass("mg-textbox")?"text":e.hasClass("mg-drawing-layer")?"drawing":null},this.canvas={drawLayers:function(e){void 0===e&&(e=i.scale),i.canvasContainer.find("canvas:not(.mg-drawing-layer)").remove(""),i.getLayersData().forEach(function(t){if(layerElement=i.wrapper.find("[data-layer='"+t.name+"']"),"text"==t.type){var a=i.canvas.drawText(t.text,t.x*e,t.y*e,t.maxWidth*e,t.fontSize*e,t.lineHeight,t.font,t.style,t.color,t.borderColor,t.borderWidth*e,e);i.canvasContainer.append(a);var n=a.attr("data-text-lines")*t.fontSize;layerElement.attr("data-height",Math.round(i.settings.defaultTextStyle.lineHeight*n))}}),i.userSettings.drawingAboveText?i.canvasContainer.find("canvas.mg-drawing-layer").remove().insertAfter(i.canvasContainer.find(":last")):i.canvasContainer.find("canvas.mg-drawing-layer").remove().insertBefore(i.canvasContainer.find(":first"))},drawText:function(t,a,n,r,s,o,d,l,c,g,p,h){void 0===h&&(h=1);var f=e("").attr("width",i.originalSize[0]*h).attr("height",i.originalSize[1]*h),u=f[0].getContext("2d");u.font=l+" "+s+"px "+d,u.textAlign="center",u.fillStyle=c,u.strokeStyle=g,u.lineWidth=p;var v=parseInt(a,10)+parseInt(r,10)/2,w=parseInt(n,10)+parseInt(s,10),o=s*parseFloat(o),m=i.canvas._wrapText(u,t,r);return m.forEach(function(e,t){u.fillText(e,v,w-p+(o-s)/2+t*o),p>0&&u.strokeText(e,v,w-p+(o-s)/2+t*o)}),f.attr("data-text-lines",m.length),f},save:function(){var t=e("").attr("width",i.originalSize[0]).attr("height",i.originalSize[1])[0],n=t.getContext("2d");return n.drawImage(a[0],0,0),i.canvas.drawLayers(1),i.canvasContainer.find("canvas").each(function(){n.drawImage(this,0,0,i.originalSize[0],i.originalSize[1])}),"canvas"==i.settings.previewMode?i.canvas.drawLayers():i.canvas.clear(),t},clear:function(){i.canvasContainer.find("canvas:not(.mg-drawing-layer)").remove("")},_wrapText:function(e,t,a){for(var n=t.split(" "),i=[],r=n[0],s=1;s .mg-image").append('
')},disable:function(){i.wrapper.find("div.mg-css-preview").remove()},drawLayers:function(){var e=i.wrapper.find(".mg-css-preview");e.find("div").remove(),i.getLayersData().forEach(function(t){if(layerElement=i.wrapper.find("[data-layer='"+t.name+"']"),"text"==t.type){var a=i.cssPreview.drawText(t.text,t.x*i.scale,t.y*i.scale,t.maxWidth*i.scale,t.fontSize*i.scale,t.lineHeight*i.scale,t.font,t.style,t.color,t.borderColor,t.borderWidth*i.scale);e.append(a),layerElement.attr("data-height",Math.round(a.height()))}})},drawText:function(t,a,n,i,r,s,o,d,l,c,g){var p=e("
").html(t);return p.css({left:a,top:n,width:i,minHeight:r,fontSize:r,fontFamily:o,fontWeight:-1==d.indexOf("bold")?"normal":"bold",fontStyle:-1==d.indexOf("italic")?"normal":"italic",color:l,textAlign:"center",lineHeight:parseFloat(s),textShadow:function(e,t){var a="";return[[-1,-1],[-1,1],[1,-1],[1,1]].forEach(function(n,i){a+=n[0]*e+"px "+n[1]*e+"px 0 "+t,3!=i&&(a+=",")}),a}(g,c)}),p}},this.drawing={container:null,canvas:null,enabled:!1,flag:!1,prevX:0,currX:0,prevY:0,currY:0,color:i.settings.defaultDrawingStyle.color,lineWidth:i.settings.defaultDrawingStyle.lineWidth,enable:function(){if(null==i.drawing.canvas){i.drawing.container=e('
').appendTo(i.wrapper.find("> .mg-image"));var t=e('').attr("width",i.drawing.container.width()).attr("height",i.drawing.container.height()).appendTo(i.canvasContainer);i.drawing.canvas=t[0],i.drawing.initEvents()}i.drawing.enabled=!0,i.drawing.container.show()},disable:function(){i.drawing.enabled=!1,i.drawing.container.hide()},initEvents:function(){i.drawing.container[0].addEventListener("mousemove",function(e){i.drawing.handleAction("move",e)},!1),i.drawing.container[0].addEventListener("mousedown",function(e){i.drawing.handleAction("down",e)},!1),i.drawing.container[0].addEventListener("mouseup",function(e){i.drawing.handleAction("up",e)},!1),i.drawing.container[0].addEventListener("mouseout",function(e){i.drawing.handleAction("out",e)},!1)},draw:function(){var e=i.drawing.canvas.getContext("2d");e.beginPath(),e.moveTo(i.drawing.prevX,i.drawing.prevY),e.lineTo(i.drawing.currX,i.drawing.currY),e.strokeStyle=i.drawing.color,e.lineWidth=i.drawing.lineWidth,e.stroke(),e.closePath()},erase:function(){i.drawing.canvas.getContext("2d").clearRect(0,0,i.drawing.canvas.width,i.drawing.canvas.height)},handleAction:function(e,t){if(i.drawing.enabled){var a=i.drawing.canvas.getContext("2d");"down"==e?(i.drawing.prevX=i.drawing.currX,i.drawing.prevY=i.drawing.currY,i.drawing.currX=t.clientX-i.drawing.container.offset().left,i.drawing.currY=t.clientY-i.drawing.container.offset().top,i.drawing.flag=!0,a.beginPath(),a.fillStyle=i.drawing.color,a.fillRect(i.drawing.currX,i.drawing.currY,2,2),a.closePath()):"up"==e||"out"==e?i.drawing.flag=!1:"move"==e&&i.drawing.flag&&(i.drawing.prevX=i.drawing.currX,i.drawing.prevY=i.drawing.currY,i.drawing.currX=t.clientX-i.drawing.container.offset().left,i.drawing.currY=t.clientY-i.drawing.container.offset().top,i.drawing.draw())}}},this.events={onLayerChange:function(e){"canvas"==i.settings.previewMode?i.canvas.drawLayers():"css"==i.settings.previewMode&&i.cssPreview.drawLayers(),i.ui.resizeHelpers(),i.settings.onLayerChange.call(i,e)}}};e.fn.memeGenerator=function(n){var i="string"==typeof n?n:void 0;if(i){var r=[],s=arguments.length>1?Array.prototype.slice.call(arguments,1):void 0,o=[];return"i18n"==i?void e.extend(t,s[1]):(this.each(function(){var t=e(this),a=t.data("memeGenerator");r.push(a)}),this.each(function(e){var t=r[e];if(!t)return void o.push(void 0);if("function"==typeof t[i]){var a=t[i].apply(t,s);o.push(a)}else console.warn('$.fn.memeGenerator: Undefined method "'+i+'"')}),o.length>1?o:o[0])}var d="object"==typeof n?n:void 0;return this.each(function(){var t=e(this),n=new a(t,d);n.init(),t.data("memeGenerator",n)})}}(jQuery); -------------------------------------------------------------------------------- /src/js/jquery.memegenerator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Maciej Gierej 3 | * https://github.com/MakG10/jquery-memegenerator 4 | */ 5 | (function($){ 6 | 7 | var i18n = { 8 | topTextPlaceholder: "TOP TEXT", 9 | bottomTextPlaceholder: "BOTTOM TEXT", 10 | 11 | addTextbox: "Add Textbox", 12 | advancedSettings: "Advanced Settings", 13 | toolbox: "Toolbox", 14 | 15 | optionCapitalLetters: "Use only capital letters", 16 | optionDragResize: "Enable dragging and resizing textboxes", 17 | optionDrawingAboveText: "Show drawing above text", 18 | 19 | drawingStart: "Draw", 20 | drawingStop: "Stop drawing", 21 | drawingErase: "Erase", 22 | }; 23 | 24 | var MemeGenerator = function(element, options){ 25 | var MG = this; 26 | 27 | this.settings = $.extend(true, {}, { 28 | defaultTextStyle: { 29 | color: "#FFFFFF", 30 | size: 42, 31 | lineHeight: 1.2, 32 | font: "Impact, Arial", 33 | style: "normal", 34 | forceUppercase: true, 35 | 36 | borderColor: "#000000", 37 | borderWidth: 2, 38 | }, 39 | 40 | defaultDrawingStyle: { 41 | color: "#FF0000", 42 | lineWidth: 10, 43 | }, 44 | 45 | minFontSize: 1, 46 | maxFontSize: 128, 47 | minBorderWidth: 0, 48 | maxBorderWidth: 10, 49 | 50 | fontSizeStep: 1, 51 | borderWidthStep: 1, 52 | 53 | captions: [], 54 | 55 | previewMode: "canvas", // css, canvas 56 | outputFormat: "image/png", 57 | 58 | editingEnabled: true, 59 | dragResizeEnabled: true, 60 | drawingAboveText: true, 61 | showAdvancedSettings: true, 62 | colorPicker: null, 63 | 64 | wrapperClass: "mg-wrapper", 65 | toolboxSelector: null, 66 | layout: "vertical", 67 | useBootstrap: false, 68 | useWordpressStyle: false, 69 | 70 | // Events 71 | onLayerChange: function(layerName){ return true; }, 72 | onNewTextBox: function(textBox){ return true; }, 73 | onInit: function(){ return true; } 74 | }, options); 75 | 76 | // Options possible to be overwritten by user 77 | this.userSettings = { 78 | forceUppercase: MG.settings.defaultTextStyle.forceUppercase, 79 | dragResizeEnabled: MG.settings.dragResizeEnabled, 80 | drawingAboveText: MG.settings.drawingAboveText, 81 | }; 82 | 83 | this.element = element; 84 | this.wrapper = null; 85 | this.canvasContainer = null; 86 | this.helpersContainer = null; 87 | this.canvasLayers = []; 88 | 89 | this.originalSize = []; 90 | this.scale = 1.0; 91 | 92 | this.initialized = false; 93 | 94 | // =============== 95 | // PUBLIC METHODS 96 | // =============== 97 | 98 | this.save = function(){ 99 | return MG.canvas.save().toDataURL(MG.settings.outputFormat); 100 | }; 101 | 102 | this.saveCanvas = function(){ 103 | return MG.canvas.save(); 104 | }; 105 | 106 | this.download = function(filename){ 107 | if(typeof filename === 'undefined') filename = 'image.png'; 108 | 109 | var downloadLink = $("").attr("href", this.save()).attr("download", filename).appendTo(MG.wrapper); 110 | downloadLink[0].click(); 111 | 112 | downloadLink.remove(); 113 | }; 114 | 115 | this.serialize = function(){ 116 | return JSON.stringify(MG.getLayersData()); 117 | }; 118 | 119 | this.deserialize = function(json){ 120 | var layers = JSON.parse(json); 121 | 122 | var newLayers = []; 123 | MG.canvasLayers.forEach(function(layerName, index) { 124 | var layer = MG.wrapper.find("[data-layer='" + layerName + "']"); 125 | 126 | if(MG.getLayerType(layer)) 127 | { 128 | layer.remove(); 129 | 130 | var helper = MG.wrapper.find("[data-target-layer='" + layerName + "']"); 131 | helper.remove(); 132 | 133 | return; 134 | } 135 | 136 | newLayers.push(layerName); 137 | }); 138 | 139 | MG.canvasLayers = newLayers; 140 | 141 | layers.reverse().forEach(function(layer){ 142 | if(layer.type == 'text') 143 | { 144 | MG.ui.createTextBox("", null, layer).prependTo(MG.wrapper.find('.mg-controls')); 145 | } 146 | }); 147 | 148 | MG.events.onLayerChange(); 149 | }; 150 | 151 | this.destroy = function(){ 152 | this.element.insertAfter(this.wrapper); 153 | this.element.off('load.mg'); 154 | 155 | this.wrapper.remove(); 156 | }; 157 | 158 | 159 | // =============== 160 | // PRIVATE METHODS 161 | // =============== 162 | 163 | this.init = function(){ 164 | element.wrap('
'); 165 | 166 | MG.wrapper = element.parent().parent(); 167 | MG.canvasContainer = $('
').appendTo(MG.wrapper.find("> .mg-image")); 168 | MG.helpersContainer = $('
').appendTo(MG.wrapper.find("> .mg-image")); 169 | 170 | if(MG.settings.layout == "horizontal") MG.wrapper.addClass("mg-horizontal-layout"); 171 | 172 | if(MG.settings.previewMode == "css") MG.cssPreview.enable(); 173 | 174 | $("") 175 | .attr("src", element.attr("src")) 176 | .on('load', function(){ 177 | MG.originalSize[0] = this.width; 178 | MG.originalSize[1] = this.height; 179 | 180 | MG.scale = element.width() / MG.originalSize[0]; 181 | 182 | if(MG.settings.editingEnabled) 183 | { 184 | MG.wrapper.append(MG.ui.createControls()); 185 | } 186 | 187 | // Set default captions if any 188 | MG.settings.captions.forEach(function(caption, index){ 189 | var textbox = MG.wrapper.find(".mg-textbox-text").eq(index); 190 | 191 | if(textbox.length == 0) 192 | { 193 | textbox = MG.ui.createTextBox("", "center center").insertAfter(MG.wrapper.find(".mg-controls .mg-textbox").last()).find(".mg-textbox-text"); 194 | } 195 | 196 | if(MG.userSettings.forceUppercase) 197 | { 198 | caption = MG.ui._strtoupper(caption); 199 | } 200 | 201 | textbox.attr("value", caption); 202 | textbox.attr("data-text", caption); 203 | textbox.trigger("change"); 204 | }); 205 | 206 | if(MG.settings.useBootstrap) 207 | { 208 | MG.ui.bootstrapify(); 209 | } 210 | else if(MG.settings.useWordpressStyle) 211 | { 212 | MG.ui.wordpressify(); 213 | } 214 | 215 | MG.initialized = true; 216 | MG.settings.onInit.call(MG); 217 | }); 218 | 219 | $(window).on("resize", function(){ 220 | MG.scale = element.width() / MG.originalSize[0]; 221 | 222 | MG.events.onLayerChange(); 223 | }); 224 | 225 | // Handling change of the src attribute 226 | element.on('load.mg', function(){ 227 | if(!MG.initialized) return; 228 | 229 | MG.originalSize[0] = this.naturalWidth; 230 | MG.originalSize[1] = this.naturalHeight; 231 | 232 | MG.scale = element.width() / MG.originalSize[0]; 233 | 234 | MG.ui.resizeHelpers(); 235 | MG.events.onLayerChange(); 236 | }); 237 | }; 238 | 239 | this.ui = { 240 | createControls: function(){ 241 | var controls = $('
'); 242 | controls.append(MG.ui.createTextBox(i18n.topTextPlaceholder, "top center")); 243 | controls.append(MG.ui.createTextBox(i18n.bottomTextPlaceholder, "bottom center")); 244 | 245 | // Add extra textbox button 246 | var addTextboxButton = $('' + i18n.addTextbox + '').appendTo(controls); 247 | $(addTextboxButton).click(function(e){ 248 | e.preventDefault(); 249 | 250 | var newTextbox = MG.ui.createTextBox("", "center center").insertAfter(controls.find(".mg-textbox").last()); 251 | 252 | if(MG.settings.useBootstrap) 253 | { 254 | MG.ui.bootstrapify(); 255 | } 256 | else if(MG.settings.useWordpressStyle) 257 | { 258 | MG.ui.wordpressify(); 259 | } 260 | 261 | newTextbox.find("input[type=text]").first().focus(); 262 | 263 | MG.settings.onNewTextBox.call(MG, newTextbox); 264 | }); 265 | 266 | // Advanced settings 267 | if(MG.settings.showAdvancedSettings) 268 | { 269 | var advancedSettingsButton = $('' + i18n.advancedSettings + '').appendTo(controls); 270 | var advancedSettings = $('
').appendTo(controls).hide(); 271 | 272 | // -- Force uppercase option 273 | advancedSettings.append( 274 | $('
') 275 | .append($('')) 276 | .append($('')) 277 | ); 278 | controls.find(".mg-option-uppercase").prop("checked", MG.userSettings.forceUppercase); 279 | 280 | controls.find(".mg-option-uppercase").change(function(e){ 281 | e.preventDefault(); 282 | 283 | MG.userSettings.forceUppercase = $(this).is(":checked"); 284 | 285 | if(MG.userSettings.forceUppercase) 286 | { 287 | controls.find(".mg-textbox-text").css("textTransform", "uppercase"); 288 | 289 | controls.find(".mg-textbox-text").each(function(){ 290 | $(this).val(MG.ui._strtoupper($(this).val())); 291 | }); 292 | 293 | MG.events.onLayerChange(); 294 | } else { 295 | controls.find(".mg-textbox-text").css("textTransform", "none"); 296 | } 297 | }); 298 | 299 | // -- Drag & Resize option 300 | advancedSettings.append( 301 | $('
') 302 | .append($('')) 303 | .append($('')) 304 | ); 305 | controls.find(".mg-option-dragresize").prop("checked", MG.userSettings.dragResizeEnabled); 306 | 307 | controls.find(".mg-option-dragresize").change(function(e){ 308 | e.preventDefault(); 309 | 310 | MG.userSettings.dragResizeEnabled = $(this).is(":checked"); 311 | 312 | MG.ui.destroyPositionHelpers(); 313 | if(MG.userSettings.dragResizeEnabled) 314 | { 315 | controls.find(".mg-textbox").each(function(){ 316 | MG.ui.createPositionHelper($(this)); 317 | }); 318 | } 319 | }); 320 | 321 | // -- Drawing and text - layers order 322 | advancedSettings.append( 323 | $('
') 324 | .append($('')) 325 | .append($('')) 326 | ); 327 | controls.find(".mg-option-drawing-above-text").prop("checked", MG.userSettings.dragResizeEnabled); 328 | 329 | controls.find(".mg-option-drawing-above-text").change(function(e){ 330 | e.preventDefault(); 331 | 332 | MG.userSettings.drawingAboveText = $(this).is(":checked"); 333 | 334 | if(MG.userSettings.drawingAboveText) 335 | { 336 | MG.wrapper.find(".mg-canvas canvas, .mg-css-preview").css("zIndex", 3); 337 | MG.wrapper.find(".mg-canvas, .mg-canvas .mg-drawing-layer").css("zIndex", 4); 338 | } else { 339 | MG.wrapper.find(".mg-canvas canvas, .mg-css-preview").css("zIndex", 4); 340 | MG.wrapper.find(".mg-canvas, .mg-canvas .mg-drawing-layer").css("zIndex", 3); 341 | } 342 | }); 343 | 344 | $(advancedSettingsButton).click(function(e){ 345 | e.preventDefault(); 346 | 347 | advancedSettings.slideToggle(200); 348 | advancedSettingsButton.toggleClass("active"); 349 | }); 350 | } 351 | 352 | // Toolbox 353 | var toolbox; 354 | 355 | if(MG.settings.toolboxSelector == null) 356 | { 357 | var toolboxButton = $('' + i18n.toolbox + '').appendTo(controls); 358 | var toolbox = $('
').appendTo(controls).hide(); 359 | 360 | $(toolboxButton).click(function(e){ 361 | e.preventDefault(); 362 | 363 | toolbox.slideToggle(200); 364 | toolboxButton.toggleClass("active"); 365 | }); 366 | } else { 367 | if(MG.settings.toolboxSelector instanceof jQuery) 368 | { 369 | toolbox = MG.settings.toolboxSelector; 370 | } else { 371 | toolbox = $(MG.settings.toolboxSelector); 372 | } 373 | } 374 | 375 | toolbox.append('
'); 376 | toolbox.append('
'); 377 | toolbox.append('
'); 378 | toolbox.append('
'); 379 | 380 | toolbox.find(".mg-drawing-toggle").click(function(){ 381 | $(this).toggleClass("active"); 382 | 383 | if($(this).hasClass("active")) 384 | { 385 | $(this).html(i18n.drawingStop); 386 | 387 | MG.drawing.enable(); 388 | } else { 389 | $(this).html(i18n.drawingStart); 390 | 391 | MG.drawing.disable(); 392 | } 393 | }); 394 | 395 | toolbox.find(".mg-drawing-line-width").on("change keyup", function(){ 396 | MG.drawing.lineWidth = parseInt($(this).val(), 10); 397 | }); 398 | 399 | toolbox.find(".mg-drawing-erase").click(function(){ 400 | MG.drawing.erase(); 401 | }); 402 | 403 | toolbox.find(".colorpicker").change(function(){ 404 | MG.drawing.color = $(this).attr("value"); 405 | }); 406 | 407 | MG.ui._bindColorpicker(toolbox.find(".colorpicker")); 408 | 409 | return controls; 410 | }, 411 | 412 | createTextBox: function(placeholder, position, params){ 413 | if(typeof params === 'undefined') params = {}; 414 | 415 | params = $.extend({ 416 | layerName: "layer" + (MG.canvasLayers.length + 1), 417 | text: "", 418 | x: 0, 419 | y: 0, 420 | maxWidth: MG.originalSize[0], 421 | fontSize: MG.settings.defaultTextStyle.size, 422 | color: MG.settings.defaultTextStyle.color, 423 | borderColor: MG.settings.defaultTextStyle.borderColor, 424 | borderWidth: MG.settings.defaultTextStyle.borderWidth 425 | }, params); 426 | params.height = Math.round(MG.settings.defaultTextStyle.lineHeight * params.fontSize); 427 | 428 | var layerName = params.layerName; 429 | 430 | var boxWidth = params.maxWidth; 431 | var boxHeight = params.height; 432 | var boxPosition = position === null ? [params.x, params.y] 433 | : MG.ui._getBoxCoordinates(position, params.maxWidth, params.height); 434 | 435 | var box = $('
'); 436 | box.append($('')); 437 | box.append($('')); 438 | 439 | if(MG.userSettings.forceUppercase) 440 | { 441 | box.find(".mg-textbox-text").css("textTransform", "uppercase"); 442 | } 443 | 444 | if(MG.settings.colorPicker != false) 445 | { 446 | box.append(''); 447 | box.append(''); 448 | box.find(".colorpicker").wrap('
'); 449 | 450 | MG.ui._bindColorpicker(box.find(".colorpicker")); 451 | } 452 | 453 | box.append($('')); 454 | 455 | // Canvas Layer 456 | MG.canvasLayers.push(layerName); 457 | 458 | // Bind events 459 | box.find(".mg-textbox-text, .mg-textbox-size, .mg-textbox-border-width").on("change keyup", function(){ 460 | var text = $(this).val(); 461 | if(MG.userSettings.forceUppercase) text = MG.ui._strtoupper(text); 462 | 463 | $(this).attr("data-text", text); 464 | 465 | MG.events.onLayerChange(layerName); 466 | }); 467 | 468 | // Position helper 469 | if(MG.userSettings.dragResizeEnabled) 470 | { 471 | MG.ui.createPositionHelper(box); 472 | } 473 | 474 | return box; 475 | }, 476 | 477 | createPositionHelper: function(box){ 478 | var layerName = box.data('layer'); 479 | var helper = $('
').appendTo(MG.helpersContainer); 480 | helper.css({left: box.attr("data-x") * MG.scale, top: box.attr("data-y") * MG.scale, width: box.attr("data-width") * MG.scale, height: box.attr("data-height") * MG.scale}); 481 | 482 | helper.draggable({ 483 | containment: MG.wrapper.find("> .mg-image > img"), 484 | drag: function(event, ui){ 485 | var layer = MG.wrapper.find("[data-layer='" + layerName + "']"); 486 | layer.attr("data-x", ui.position.left * (1 / MG.scale)); 487 | layer.attr("data-y", ui.position.top * (1 / MG.scale)); 488 | 489 | MG.events.onLayerChange(); 490 | } 491 | }); 492 | 493 | helper.resizable({ 494 | containment: MG.wrapper.find("> .mg-image > img"), 495 | resize: function(event, ui){ 496 | var layer = MG.wrapper.find("[data-layer='" + layerName + "']"); 497 | layer.attr("data-width", ui.size.width * (1 / MG.scale)); 498 | layer.attr("data-height", ui.size.height * (1 / MG.scale)); 499 | 500 | MG.events.onLayerChange(); 501 | } 502 | }); 503 | 504 | //helper.on('click', function(e){ 505 | // var layer = MG.wrapper.find("[data-layer='" + layerName + "']"); 506 | // var input = layer.find('.mg-textbox-text'); 507 | // var length = input.val().length; 508 | // 509 | // input.select(); 510 | // input[0].setSelectionRange(length, length); 511 | //}); 512 | }, 513 | 514 | resizeHelpers: function(){ 515 | MG.helpersContainer.find("> div").each(function(){ 516 | var layer = MG.wrapper.find("[data-layer='" + $(this).attr("data-target-layer") + "']"); 517 | var width, height, x, y = null; 518 | 519 | // WIDTH 520 | if(layer.attr("data-width")) 521 | { 522 | width = parseInt(layer.attr("data-width"), 10) * MG.scale; 523 | if(width > MG.helpersContainer.width()) width = MG.helpersContainer.width(); 524 | 525 | $(this).css("width", width); 526 | 527 | layer.attr("data-width", parseInt(width * (1 / MG.scale), 10)); 528 | } 529 | 530 | // HEIGHT 531 | if(layer.attr("data-height")) 532 | { 533 | height = parseInt(layer.attr("data-height"), 10) * MG.scale; 534 | 535 | $(this).css("height", height); 536 | $(this).resizable("option", "minHeight", height); 537 | $(this).resizable("option", "maxHeight", height); 538 | } 539 | 540 | // X & Y coordinates 541 | if(layer.attr("data-x") && layer.attr("data-y")) 542 | { 543 | x = layer.attr("data-x") * MG.scale; 544 | if(x + width > MG.helpersContainer.width()) x = MG.helpersContainer.width() - width; 545 | if(x < 0) x = 0; 546 | 547 | y = layer.attr("data-y") * MG.scale; 548 | if(y + height > MG.helpersContainer.height()) y = MG.helpersContainer.height() - height; 549 | if(y < 0) y = 0; 550 | 551 | $(this).css({left: x, top: y}); 552 | 553 | layer.attr('data-x', parseInt(x * (1 / MG.scale), 10)); 554 | layer.attr('data-y', parseInt(y * (1 / MG.scale), 10)); 555 | } 556 | 557 | if(parseInt($(this).css("top"), 10) + $(this).outerHeight() > MG.helpersContainer.height()) 558 | { 559 | var newTop = MG.helpersContainer.outerHeight() - $(this).outerHeight(); 560 | 561 | $(this).css("top", newTop); 562 | layer.attr("data-y", newTop * (1 / MG.scale)); 563 | } 564 | }); 565 | }, 566 | 567 | destroyPositionHelpers: function(){ 568 | MG.helpersContainer.find("> div").remove(); 569 | }, 570 | 571 | bootstrapify: function(){ 572 | var controls = MG.wrapper.find(".mg-controls"); 573 | 574 | if(!MG.wrapper.hasClass("usingBootstrap")) 575 | { 576 | controls.wrapInner('
'); 577 | } 578 | 579 | MG.wrapper.addClass("usingBootstrap"); 580 | 581 | controls.find(".mg-textbox").each(function(){ 582 | if(!$(this).hasClass("row")) 583 | { 584 | $(this).addClass("row"); 585 | $(this).find(".mg-textbox-text").addClass("form-control").wrap($("
").addClass("col-md-4")); 586 | $(this).find(".mg-textbox-size").addClass("form-control").wrap($("
").addClass("col-md-2")); 587 | $(this).find(".mg-textbox-border-width").addClass("form-control").wrap($("
").addClass("col-md-2")); 588 | $(this).find(".colorpicker").addClass("form-control") 589 | $(this).find(".colorpickerContainer").addClass("col-md-2"); 590 | $(this).find(".colorpickerReplacer").addClass("btn btn-default"); 591 | } 592 | }); 593 | 594 | if(!controls.find(".mg-add-textbox").hasClass("btn")) 595 | { 596 | controls.find(".mg-add-textbox").addClass("btn btn-default btn-block").wrap($("
").addClass("row")); 597 | } 598 | 599 | if(!controls.find(".mg-advanced-settings-toggle").hasClass("btn")) 600 | { 601 | controls.find(".mg-advanced-settings-toggle, .mg-toolbox-toggle").addClass("btn btn-primary btn-block").wrap($("
").addClass("row")); 602 | controls.find(".mg-advanced-settings, .mg-toolbox").addClass("row"); 603 | // controls.find(".mg-advanced-settings .option").addClass("row"); 604 | controls.find(".mg-advanced-settings .option input").wrap('
'); 605 | controls.find(".mg-advanced-settings .option label").wrap('
'); 606 | } 607 | 608 | if(!controls.find(".mg-drawing-toggle").hasClass("btn")) 609 | { 610 | controls.find(".mg-drawing-toggle, .mg-drawing-erase").addClass("btn btn-default btn-block"); 611 | controls.find(".mg-toolbox-item").addClass("col-md-3"); 612 | 613 | } 614 | }, 615 | 616 | wordpressify: function(){ 617 | var controls = MG.wrapper.find(".mg-controls"); 618 | 619 | MG.wrapper.addClass("usingWordpress"); 620 | 621 | if(!controls.find(".mg-add-textbox").hasClass("button")) 622 | { 623 | controls.find(".mg-add-textbox").addClass("button button-secondary"); 624 | } 625 | 626 | if(!controls.find(".mg-advanced-settings-toggle").hasClass("button")) 627 | { 628 | controls.find(".mg-advanced-settings-toggle, .mg-toolbox-toggle").addClass("button button-primary"); 629 | } 630 | 631 | if(!controls.find(".mg-drawing-toggle").hasClass("button")) 632 | { 633 | controls.find(".mg-drawing-toggle, .mg-drawing-erase").addClass("button button-secondary"); 634 | 635 | } 636 | }, 637 | 638 | _bindColorpicker: function(selector){ 639 | if(MG.settings.colorPicker == null && $.isFunction($.fn.spectrum)) 640 | { 641 | var changeEvent = function(color){ 642 | $(this).val(color.toHexString()); 643 | $(this).attr("value", color.toHexString()); 644 | $(this).trigger("change"); 645 | 646 | MG.events.onLayerChange($(this).parent().data("layer")); 647 | }; 648 | 649 | selector.spectrum({ 650 | replacerClassName: "colorpickerReplacer", 651 | change: changeEvent, 652 | move: changeEvent 653 | }); 654 | } 655 | else if(MG.settings.colorPicker == null) 656 | { 657 | selector.on("change keyup", function(){ 658 | $(this).attr("value", $(this).val()); 659 | MG.events.onLayerChange($(this).parent().data("layer")); 660 | }); 661 | } else { 662 | MG.settings.colorPicker.call(this, MG, selector); 663 | } 664 | }, 665 | 666 | _getBoxCoordinates: function(position, boxWidth, boxHeight){ 667 | var posDesc = position.split(" "); 668 | 669 | var coordinates = []; 670 | if(posDesc.length == 2) 671 | { 672 | switch(posDesc[0]) 673 | { 674 | case "center": 675 | coordinates[1] = parseInt(MG.originalSize[1] / 2, 10); 676 | break; 677 | 678 | case "bottom": 679 | coordinates[1] = MG.originalSize[1] - boxHeight; 680 | break; 681 | 682 | case "top": 683 | default: 684 | coordinates[1] = 0; 685 | break; 686 | } 687 | 688 | switch(posDesc[1]) 689 | { 690 | case "center": 691 | coordinates[0] = parseInt(MG.originalSize[0] / 2, 10) - parseInt(boxWidth / 2, 10); 692 | break; 693 | 694 | case "right": 695 | coordinates[0] = MG.originalSize[0] - boxWidth; 696 | break; 697 | 698 | case "left": 699 | default: 700 | coordinates[0] = 0; 701 | break; 702 | } 703 | } else { 704 | coordinates[0] = coordinates[1] = 0; 705 | } 706 | 707 | return coordinates; 708 | }, 709 | 710 | _normalizePosition: function(value){ 711 | return value * (1 + 1 - MG.scale); 712 | }, 713 | 714 | _strtoupper: function(text){ 715 | return text.toUpperCase(); 716 | } 717 | }; 718 | 719 | this.getLayersData = function(){ 720 | var layers = []; 721 | 722 | MG.canvasLayers.forEach(function(layerName){ 723 | var layer = MG.wrapper.find("[data-layer='" + layerName + "']"); 724 | var type = MG.getLayerType(layer); 725 | 726 | switch(type) 727 | { 728 | case 'text': 729 | layers.push({ 730 | type: type, 731 | name: layerName, 732 | text: layer.find(".mg-textbox-text").attr("data-text"), 733 | x: layer.attr("data-x"), 734 | y: layer.attr("data-y"), 735 | maxWidth: layer.attr("data-width"), 736 | fontSize: layer.find(".mg-textbox-size").val(), 737 | lineHeight: MG.settings.defaultTextStyle.lineHeight, 738 | font: MG.settings.defaultTextStyle.font, 739 | style: MG.settings.defaultTextStyle.style, 740 | color: layer.find(".mg-textbox-text-color").val(), 741 | borderColor: layer.find(".mg-textbox-border-color").attr("value"), 742 | borderWidth: layer.find(".mg-textbox-border-width").val() 743 | }); 744 | return; 745 | 746 | // TODO: serialization of drawing? 747 | case 'drawing': 748 | layers.push({ 749 | type: type 750 | }); 751 | return; 752 | 753 | default: 754 | return; 755 | } 756 | }); 757 | 758 | return layers; 759 | }; 760 | 761 | this.getLayerType = function(layer) { 762 | if(layer.hasClass('mg-textbox')) 763 | { 764 | return 'text'; 765 | } 766 | 767 | if(layer.hasClass('mg-drawing-layer')) 768 | { 769 | return 'drawing'; 770 | } 771 | 772 | return null; 773 | }; 774 | 775 | this.canvas = { 776 | drawLayers: function(canvasScale){ 777 | if(typeof canvasScale == "undefined") canvasScale = MG.scale; 778 | 779 | MG.canvasContainer.find("canvas:not(.mg-drawing-layer)").remove(""); 780 | 781 | MG.getLayersData().forEach(function(layer){ 782 | layerElement = MG.wrapper.find("[data-layer='" + layer.name + "']"); 783 | 784 | if(layer.type == 'text') 785 | { 786 | var textCanvas = MG.canvas.drawText( 787 | layer.text, 788 | layer.x * canvasScale, 789 | layer.y * canvasScale, 790 | layer.maxWidth * canvasScale, 791 | layer.fontSize * canvasScale, 792 | layer.lineHeight, 793 | layer.font, 794 | layer.style, 795 | layer.color, 796 | layer.borderColor, 797 | layer.borderWidth * canvasScale, 798 | canvasScale 799 | ); 800 | MG.canvasContainer.append(textCanvas); 801 | 802 | var textHeight = textCanvas.attr("data-text-lines") * layer.fontSize; 803 | layerElement.attr("data-height", Math.round(MG.settings.defaultTextStyle.lineHeight * textHeight)); 804 | } 805 | }); 806 | 807 | if(MG.userSettings.drawingAboveText) 808 | { 809 | MG.canvasContainer.find("canvas.mg-drawing-layer").remove().insertAfter(MG.canvasContainer.find(":last")); 810 | } else { 811 | MG.canvasContainer.find("canvas.mg-drawing-layer").remove().insertBefore(MG.canvasContainer.find(":first")); 812 | } 813 | }, 814 | 815 | drawText: function(text, x, y, maxWidth, fontSize, lineHeight, font, style, color, borderColor, borderWidth, scale){ 816 | if(typeof scale == "undefined") scale = 1.0; 817 | 818 | var canvasElement = $('').attr("width", MG.originalSize[0] * scale).attr("height", MG.originalSize[1] * scale); 819 | var canvasContext = canvasElement[0].getContext("2d"); 820 | 821 | // Font settings 822 | canvasContext.font = style + " " + fontSize + "px " + font; 823 | canvasContext.textAlign = "center"; 824 | canvasContext.fillStyle = color; 825 | canvasContext.strokeStyle = borderColor; 826 | canvasContext.lineWidth = borderWidth; 827 | 828 | var posX = parseInt(x, 10) + parseInt(maxWidth, 10) / 2; 829 | var posY = parseInt(y, 10) + parseInt(fontSize, 10); 830 | var lineHeight = fontSize * parseFloat(lineHeight); 831 | 832 | var lines = MG.canvas._wrapText(canvasContext, text, maxWidth); 833 | lines.forEach(function(line, index){ 834 | canvasContext.fillText(line, posX, posY - borderWidth + (lineHeight - fontSize) / 2 + index * lineHeight); 835 | 836 | if(borderWidth > 0) 837 | canvasContext.strokeText(line, posX, posY - borderWidth + (lineHeight - fontSize) / 2 + index * lineHeight); 838 | }); 839 | 840 | canvasElement.attr("data-text-lines", lines.length); 841 | 842 | return canvasElement; 843 | }, 844 | 845 | save: function(){ 846 | var image = $('').attr("width", MG.originalSize[0]).attr("height", MG.originalSize[1])[0]; 847 | var imageContext = image.getContext("2d"); 848 | 849 | imageContext.drawImage(element[0], 0, 0); 850 | 851 | MG.canvas.drawLayers(1.0); 852 | 853 | MG.canvasContainer.find("canvas").each(function(){ 854 | imageContext.drawImage(this, 0, 0, MG.originalSize[0], MG.originalSize[1]); 855 | }); 856 | 857 | // Restore scaled layers in preview box 858 | if(MG.settings.previewMode == "canvas") 859 | { 860 | MG.canvas.drawLayers(); 861 | } else { 862 | MG.canvas.clear(); 863 | } 864 | 865 | return image; 866 | }, 867 | 868 | clear: function(){ 869 | MG.canvasContainer.find("canvas:not(.mg-drawing-layer)").remove(""); 870 | }, 871 | 872 | _wrapText: function(ctx, text, maxWidth){ 873 | var words = text.split(" "); 874 | var lines = []; 875 | var currentLine = words[0]; 876 | 877 | for(var i = 1; i < words.length; i++) { 878 | var word = words[i]; 879 | var width = ctx.measureText(currentLine + " " + word).width; 880 | 881 | if(width < maxWidth) 882 | { 883 | currentLine += " " + word; 884 | } else { 885 | lines.push(currentLine); 886 | currentLine = word; 887 | } 888 | } 889 | 890 | lines.push(currentLine); 891 | return lines; 892 | } 893 | }; 894 | 895 | this.cssPreview = { 896 | enable: function(){ 897 | MG.wrapper.find("> .mg-image").append('
'); 898 | }, 899 | 900 | disable: function(){ 901 | MG.wrapper.find("div.mg-css-preview").remove(); 902 | }, 903 | 904 | drawLayers: function(){ 905 | var cssPreviewContainer = MG.wrapper.find(".mg-css-preview"); 906 | cssPreviewContainer.find("div").remove(); 907 | 908 | MG.getLayersData().forEach(function(layer){ 909 | layerElement = MG.wrapper.find("[data-layer='" + layer.name + "']"); 910 | 911 | if(layer.type == 'text') 912 | { 913 | var textElement = MG.cssPreview.drawText( 914 | layer.text, 915 | layer.x * MG.scale, 916 | layer.y * MG.scale, 917 | layer.maxWidth * MG.scale, 918 | layer.fontSize * MG.scale, 919 | layer.lineHeight * MG.scale, 920 | layer.font, 921 | layer.style, 922 | layer.color, 923 | layer.borderColor, 924 | layer.borderWidth * MG.scale 925 | ); 926 | cssPreviewContainer.append(textElement); 927 | 928 | layerElement.attr("data-height", Math.round(textElement.height())); 929 | } 930 | }); 931 | }, 932 | 933 | drawText: function(text, x, y, maxWidth, fontSize, lineHeight, font, style, color, borderColor, borderWidth){ 934 | var textElement = $("
").html(text); 935 | textElement.css({ 936 | left: x, 937 | top: y, 938 | width: maxWidth, 939 | minHeight: fontSize, 940 | fontSize: fontSize, 941 | fontFamily: font, 942 | fontWeight: style.indexOf('bold') == -1 ? 'normal' : 'bold', 943 | fontStyle: style.indexOf('italic') == -1 ? 'normal' : 'italic', 944 | color: color, 945 | textAlign: "center", 946 | lineHeight: parseFloat(lineHeight), 947 | textShadow: (function(borderWidth, borderColor){ 948 | var textShadow = ""; 949 | [[-1, -1], [-1, 1], [1, -1], [1, 1]].forEach(function(direction, index){ 950 | textShadow += direction[0] * borderWidth + "px " + direction[1] * borderWidth + "px 0 " + borderColor; 951 | if(index != 3) textShadow += ","; 952 | }); 953 | 954 | return textShadow; 955 | }(borderWidth, borderColor)) 956 | }); 957 | 958 | return textElement; 959 | } 960 | }; 961 | 962 | this.drawing = { 963 | container: null, 964 | canvas: null, 965 | enabled: false, 966 | flag: false, 967 | prevX: 0, 968 | currX: 0, 969 | prevY: 0, 970 | currY: 0, 971 | 972 | color: MG.settings.defaultDrawingStyle.color, 973 | lineWidth: MG.settings.defaultDrawingStyle.lineWidth, 974 | 975 | enable: function(){ 976 | if(MG.drawing.canvas == null) 977 | { 978 | MG.drawing.container = $('
').appendTo(MG.wrapper.find("> .mg-image")); 979 | 980 | var drawingCanvas = $('').attr("width", MG.drawing.container.width()).attr("height", MG.drawing.container.height()).appendTo(MG.canvasContainer); 981 | MG.drawing.canvas = drawingCanvas[0]; 982 | MG.drawing.initEvents(); 983 | } 984 | 985 | MG.drawing.enabled = true; 986 | MG.drawing.container.show(); 987 | }, 988 | 989 | disable: function(){ 990 | MG.drawing.enabled = false; 991 | MG.drawing.container.hide(); 992 | }, 993 | 994 | initEvents: function(){ 995 | MG.drawing.container[0].addEventListener("mousemove", function (e) { 996 | MG.drawing.handleAction("move", e) 997 | }, false); 998 | 999 | MG.drawing.container[0].addEventListener("mousedown", function (e) { 1000 | MG.drawing.handleAction("down", e) 1001 | }, false); 1002 | 1003 | MG.drawing.container[0].addEventListener("mouseup", function (e) { 1004 | MG.drawing.handleAction("up", e) 1005 | }, false); 1006 | 1007 | MG.drawing.container[0].addEventListener("mouseout", function (e) { 1008 | MG.drawing.handleAction("out", e) 1009 | }, false); 1010 | }, 1011 | 1012 | draw: function(){ 1013 | var context = MG.drawing.canvas.getContext("2d"); 1014 | 1015 | context.beginPath(); 1016 | context.moveTo(MG.drawing.prevX, MG.drawing.prevY); 1017 | context.lineTo(MG.drawing.currX, MG.drawing.currY); 1018 | context.strokeStyle = MG.drawing.color; 1019 | context.lineWidth = MG.drawing.lineWidth; 1020 | context.stroke(); 1021 | context.closePath(); 1022 | }, 1023 | 1024 | erase: function(){ 1025 | var context = MG.drawing.canvas.getContext("2d"); 1026 | 1027 | context.clearRect(0, 0, MG.drawing.canvas.width, MG.drawing.canvas.height); 1028 | }, 1029 | 1030 | handleAction: function(action, event){ 1031 | if(MG.drawing.enabled) 1032 | { 1033 | var context = MG.drawing.canvas.getContext("2d"); 1034 | 1035 | if(action == "down") 1036 | { 1037 | MG.drawing.prevX = MG.drawing.currX; 1038 | MG.drawing.prevY = MG.drawing.currY; 1039 | MG.drawing.currX = event.clientX - MG.drawing.container.offset().left; 1040 | MG.drawing.currY = event.clientY - MG.drawing.container.offset().top; 1041 | 1042 | MG.drawing.flag = true; 1043 | 1044 | context.beginPath(); 1045 | context.fillStyle = MG.drawing.color; 1046 | context.fillRect(MG.drawing.currX, MG.drawing.currY, 2, 2); 1047 | context.closePath(); 1048 | } 1049 | 1050 | else if(action == "up" || action == "out") 1051 | { 1052 | MG.drawing.flag = false; 1053 | } 1054 | 1055 | else if(action == "move") 1056 | { 1057 | if(MG.drawing.flag) 1058 | { 1059 | MG.drawing.prevX = MG.drawing.currX; 1060 | MG.drawing.prevY = MG.drawing.currY; 1061 | MG.drawing.currX = event.clientX - MG.drawing.container.offset().left; 1062 | MG.drawing.currY = event.clientY - MG.drawing.container.offset().top; 1063 | MG.drawing.draw(); 1064 | } 1065 | } 1066 | } 1067 | } 1068 | }; 1069 | 1070 | this.events = { 1071 | onLayerChange: function(layerName){ 1072 | if(MG.settings.previewMode == "canvas") 1073 | { 1074 | MG.canvas.drawLayers(); 1075 | } 1076 | else if(MG.settings.previewMode == "css") 1077 | { 1078 | MG.cssPreview.drawLayers(); 1079 | } 1080 | 1081 | MG.ui.resizeHelpers(); 1082 | 1083 | MG.settings.onLayerChange.call(MG, layerName); 1084 | }, 1085 | }; 1086 | }; 1087 | 1088 | 1089 | $.fn.memeGenerator = function(methodOrOptions){ 1090 | 1091 | var method = (typeof methodOrOptions === 'string') ? methodOrOptions : undefined; 1092 | 1093 | if(method) 1094 | { 1095 | var memeGeneratorPlugins = []; 1096 | var args = (arguments.length > 1) ? Array.prototype.slice.call(arguments, 1) : undefined; 1097 | var results = []; 1098 | 1099 | if(method == "i18n") 1100 | { 1101 | $.extend(i18n, args[1]); 1102 | return; 1103 | } 1104 | 1105 | this.each(function(){ 1106 | var $el = $(this); 1107 | var memeGeneratorPlugin = $el.data('memeGenerator'); 1108 | 1109 | memeGeneratorPlugins.push(memeGeneratorPlugin); 1110 | }); 1111 | 1112 | this.each(function(index){ 1113 | var memeGeneratorPlugin = memeGeneratorPlugins[index]; 1114 | 1115 | if(!memeGeneratorPlugin) 1116 | { 1117 | results.push(undefined); 1118 | return; 1119 | } 1120 | 1121 | if(typeof memeGeneratorPlugin[method] === 'function') 1122 | { 1123 | var result = memeGeneratorPlugin[method].apply(memeGeneratorPlugin, args); 1124 | results.push(result); 1125 | } else { 1126 | console.warn('$.fn.memeGenerator: Undefined method "' + method + '"'); 1127 | } 1128 | }); 1129 | 1130 | return (results.length > 1) ? results : results[0]; 1131 | } else { 1132 | var options = (typeof methodOrOptions === 'object') ? methodOrOptions : undefined; 1133 | 1134 | return this.each(function(){ 1135 | var $el = $(this); 1136 | var memeGeneratorPlugin = new MemeGenerator($el, options); 1137 | memeGeneratorPlugin.init(); 1138 | 1139 | $el.data('memeGenerator', memeGeneratorPlugin); 1140 | }); 1141 | } 1142 | 1143 | }; 1144 | }(jQuery)); 1145 | -------------------------------------------------------------------------------- /demo/colorpicker/spectrum.js: -------------------------------------------------------------------------------- 1 | // Spectrum Colorpicker v1.7.1 2 | // https://github.com/bgrins/spectrum 3 | // Author: Brian Grinstead 4 | // License: MIT 5 | 6 | (function (factory) { 7 | "use strict"; 8 | 9 | if (typeof define === 'function' && define.amd) { // AMD 10 | define(['jquery'], factory); 11 | } 12 | else if (typeof exports == "object" && typeof module == "object") { // CommonJS 13 | module.exports = factory; 14 | } 15 | else { // Browser 16 | factory(jQuery); 17 | } 18 | })(function($, undefined) { 19 | "use strict"; 20 | 21 | var defaultOpts = { 22 | 23 | // Callbacks 24 | beforeShow: noop, 25 | move: noop, 26 | change: noop, 27 | show: noop, 28 | hide: noop, 29 | 30 | // Options 31 | color: false, 32 | flat: false, 33 | showInput: false, 34 | allowEmpty: false, 35 | showButtons: true, 36 | clickoutFiresChange: true, 37 | showInitial: false, 38 | showPalette: false, 39 | showPaletteOnly: false, 40 | hideAfterPaletteSelect: false, 41 | togglePaletteOnly: false, 42 | showSelectionPalette: true, 43 | localStorageKey: false, 44 | appendTo: "body", 45 | maxSelectionSize: 7, 46 | cancelText: "cancel", 47 | chooseText: "choose", 48 | togglePaletteMoreText: "more", 49 | togglePaletteLessText: "less", 50 | clearText: "Clear Color Selection", 51 | noColorSelectedText: "No Color Selected", 52 | preferredFormat: false, 53 | className: "", // Deprecated - use containerClassName and replacerClassName instead. 54 | containerClassName: "", 55 | replacerClassName: "", 56 | showAlpha: false, 57 | theme: "sp-light", 58 | palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]], 59 | selectionPalette: [], 60 | disabled: false, 61 | offset: null 62 | }, 63 | spectrums = [], 64 | IE = !!/msie/i.exec( window.navigator.userAgent ), 65 | rgbaSupport = (function() { 66 | function contains( str, substr ) { 67 | return !!~('' + str).indexOf(substr); 68 | } 69 | 70 | var elem = document.createElement('div'); 71 | var style = elem.style; 72 | style.cssText = 'background-color:rgba(0,0,0,.5)'; 73 | return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla'); 74 | })(), 75 | replaceInput = [ 76 | "
", 77 | "
", 78 | "
", 79 | "
" 80 | ].join(''), 81 | markup = (function () { 82 | 83 | // IE does not support gradients with multiple stops, so we need to simulate 84 | // that for the rainbow slider with 8 divs that each have a single gradient 85 | var gradientFix = ""; 86 | if (IE) { 87 | for (var i = 1; i <= 6; i++) { 88 | gradientFix += "
"; 89 | } 90 | } 91 | 92 | return [ 93 | "
", 94 | "
", 95 | "
", 96 | "
", 97 | "", 98 | "
", 99 | "
", 100 | "
", 101 | "
", 102 | "
", 103 | "
", 104 | "
", 105 | "
", 106 | "
", 107 | "
", 108 | "
", 109 | "
", 110 | "
", 111 | "
", 112 | "
", 113 | "
", 114 | "
", 115 | gradientFix, 116 | "
", 117 | "
", 118 | "
", 119 | "
", 120 | "
", 121 | "", 122 | "
", 123 | "
", 124 | "
", 125 | "", 126 | "", 127 | "
", 128 | "
", 129 | "
" 130 | ].join(""); 131 | })(); 132 | 133 | function paletteTemplate (p, color, className, opts) { 134 | var html = []; 135 | for (var i = 0; i < p.length; i++) { 136 | var current = p[i]; 137 | if(current) { 138 | var tiny = tinycolor(current); 139 | var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light"; 140 | c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : ""; 141 | var formattedString = tiny.toString(opts.preferredFormat || "rgb"); 142 | var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter(); 143 | html.push(''); 144 | } else { 145 | var cls = 'sp-clear-display'; 146 | html.push($('
') 147 | .append($('') 148 | .attr('title', opts.noColorSelectedText) 149 | ) 150 | .html() 151 | ); 152 | } 153 | } 154 | return "
" + html.join('') + "
"; 155 | } 156 | 157 | function hideAll() { 158 | for (var i = 0; i < spectrums.length; i++) { 159 | if (spectrums[i]) { 160 | spectrums[i].hide(); 161 | } 162 | } 163 | } 164 | 165 | function instanceOptions(o, callbackContext) { 166 | var opts = $.extend({}, defaultOpts, o); 167 | opts.callbacks = { 168 | 'move': bind(opts.move, callbackContext), 169 | 'change': bind(opts.change, callbackContext), 170 | 'show': bind(opts.show, callbackContext), 171 | 'hide': bind(opts.hide, callbackContext), 172 | 'beforeShow': bind(opts.beforeShow, callbackContext) 173 | }; 174 | 175 | return opts; 176 | } 177 | 178 | function spectrum(element, o) { 179 | 180 | var opts = instanceOptions(o, element), 181 | flat = opts.flat, 182 | showSelectionPalette = opts.showSelectionPalette, 183 | localStorageKey = opts.localStorageKey, 184 | theme = opts.theme, 185 | callbacks = opts.callbacks, 186 | resize = throttle(reflow, 10), 187 | visible = false, 188 | isDragging = false, 189 | dragWidth = 0, 190 | dragHeight = 0, 191 | dragHelperHeight = 0, 192 | slideHeight = 0, 193 | slideWidth = 0, 194 | alphaWidth = 0, 195 | alphaSlideHelperWidth = 0, 196 | slideHelperHeight = 0, 197 | currentHue = 0, 198 | currentSaturation = 0, 199 | currentValue = 0, 200 | currentAlpha = 1, 201 | palette = [], 202 | paletteArray = [], 203 | paletteLookup = {}, 204 | selectionPalette = opts.selectionPalette.slice(0), 205 | maxSelectionSize = opts.maxSelectionSize, 206 | draggingClass = "sp-dragging", 207 | shiftMovementDirection = null; 208 | 209 | var doc = element.ownerDocument, 210 | body = doc.body, 211 | boundElement = $(element), 212 | disabled = false, 213 | container = $(markup, doc).addClass(theme), 214 | pickerContainer = container.find(".sp-picker-container"), 215 | dragger = container.find(".sp-color"), 216 | dragHelper = container.find(".sp-dragger"), 217 | slider = container.find(".sp-hue"), 218 | slideHelper = container.find(".sp-slider"), 219 | alphaSliderInner = container.find(".sp-alpha-inner"), 220 | alphaSlider = container.find(".sp-alpha"), 221 | alphaSlideHelper = container.find(".sp-alpha-handle"), 222 | textInput = container.find(".sp-input"), 223 | paletteContainer = container.find(".sp-palette"), 224 | initialColorContainer = container.find(".sp-initial"), 225 | cancelButton = container.find(".sp-cancel"), 226 | clearButton = container.find(".sp-clear"), 227 | chooseButton = container.find(".sp-choose"), 228 | toggleButton = container.find(".sp-palette-toggle"), 229 | isInput = boundElement.is("input"), 230 | isInputTypeColor = isInput && boundElement.attr("type") === "color" && inputTypeColorSupport(), 231 | shouldReplace = isInput && !flat, 232 | replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]), 233 | offsetElement = (shouldReplace) ? replacer : boundElement, 234 | previewElement = replacer.find(".sp-preview-inner"), 235 | initialColor = opts.color || (isInput && boundElement.val()), 236 | colorOnShow = false, 237 | preferredFormat = opts.preferredFormat, 238 | currentPreferredFormat = preferredFormat, 239 | clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange, 240 | isEmpty = !initialColor, 241 | allowEmpty = opts.allowEmpty && !isInputTypeColor; 242 | 243 | function applyOptions() { 244 | 245 | if (opts.showPaletteOnly) { 246 | opts.showPalette = true; 247 | } 248 | 249 | toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText); 250 | 251 | if (opts.palette) { 252 | palette = opts.palette.slice(0); 253 | paletteArray = $.isArray(palette[0]) ? palette : [palette]; 254 | paletteLookup = {}; 255 | for (var i = 0; i < paletteArray.length; i++) { 256 | for (var j = 0; j < paletteArray[i].length; j++) { 257 | var rgb = tinycolor(paletteArray[i][j]).toRgbString(); 258 | paletteLookup[rgb] = true; 259 | } 260 | } 261 | } 262 | 263 | container.toggleClass("sp-flat", flat); 264 | container.toggleClass("sp-input-disabled", !opts.showInput); 265 | container.toggleClass("sp-alpha-enabled", opts.showAlpha); 266 | container.toggleClass("sp-clear-enabled", allowEmpty); 267 | container.toggleClass("sp-buttons-disabled", !opts.showButtons); 268 | container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly); 269 | container.toggleClass("sp-palette-disabled", !opts.showPalette); 270 | container.toggleClass("sp-palette-only", opts.showPaletteOnly); 271 | container.toggleClass("sp-initial-disabled", !opts.showInitial); 272 | container.addClass(opts.className).addClass(opts.containerClassName); 273 | 274 | reflow(); 275 | } 276 | 277 | function initialize() { 278 | 279 | if (IE) { 280 | container.find("*:not(input)").attr("unselectable", "on"); 281 | } 282 | 283 | applyOptions(); 284 | 285 | if (shouldReplace) { 286 | boundElement.after(replacer).hide(); 287 | } 288 | 289 | if (!allowEmpty) { 290 | clearButton.hide(); 291 | } 292 | 293 | if (flat) { 294 | boundElement.after(container).hide(); 295 | } 296 | else { 297 | 298 | var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo); 299 | if (appendTo.length !== 1) { 300 | appendTo = $("body"); 301 | } 302 | 303 | appendTo.append(container); 304 | } 305 | 306 | updateSelectionPaletteFromStorage(); 307 | 308 | offsetElement.bind("click.spectrum touchstart.spectrum", function (e) { 309 | if (!disabled) { 310 | toggle(); 311 | } 312 | 313 | e.stopPropagation(); 314 | 315 | if (!$(e.target).is("input")) { 316 | e.preventDefault(); 317 | } 318 | }); 319 | 320 | if(boundElement.is(":disabled") || (opts.disabled === true)) { 321 | disable(); 322 | } 323 | 324 | // Prevent clicks from bubbling up to document. This would cause it to be hidden. 325 | container.click(stopPropagation); 326 | 327 | // Handle user typed input 328 | textInput.change(setFromTextInput); 329 | textInput.bind("paste", function () { 330 | setTimeout(setFromTextInput, 1); 331 | }); 332 | textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } }); 333 | 334 | cancelButton.text(opts.cancelText); 335 | cancelButton.bind("click.spectrum", function (e) { 336 | e.stopPropagation(); 337 | e.preventDefault(); 338 | revert(); 339 | hide(); 340 | }); 341 | 342 | clearButton.attr("title", opts.clearText); 343 | clearButton.bind("click.spectrum", function (e) { 344 | e.stopPropagation(); 345 | e.preventDefault(); 346 | isEmpty = true; 347 | move(); 348 | 349 | if(flat) { 350 | //for the flat style, this is a change event 351 | updateOriginalInput(true); 352 | } 353 | }); 354 | 355 | chooseButton.text(opts.chooseText); 356 | chooseButton.bind("click.spectrum", function (e) { 357 | e.stopPropagation(); 358 | e.preventDefault(); 359 | 360 | if (IE && textInput.is(":focus")) { 361 | textInput.trigger('change'); 362 | } 363 | 364 | if (isValid()) { 365 | updateOriginalInput(true); 366 | hide(); 367 | } 368 | }); 369 | 370 | toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText); 371 | toggleButton.bind("click.spectrum", function (e) { 372 | e.stopPropagation(); 373 | e.preventDefault(); 374 | 375 | opts.showPaletteOnly = !opts.showPaletteOnly; 376 | 377 | // To make sure the Picker area is drawn on the right, next to the 378 | // Palette area (and not below the palette), first move the Palette 379 | // to the left to make space for the picker, plus 5px extra. 380 | // The 'applyOptions' function puts the whole container back into place 381 | // and takes care of the button-text and the sp-palette-only CSS class. 382 | if (!opts.showPaletteOnly && !flat) { 383 | container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5)); 384 | } 385 | applyOptions(); 386 | }); 387 | 388 | draggable(alphaSlider, function (dragX, dragY, e) { 389 | currentAlpha = (dragX / alphaWidth); 390 | isEmpty = false; 391 | if (e.shiftKey) { 392 | currentAlpha = Math.round(currentAlpha * 10) / 10; 393 | } 394 | 395 | move(); 396 | }, dragStart, dragStop); 397 | 398 | draggable(slider, function (dragX, dragY) { 399 | currentHue = parseFloat(dragY / slideHeight); 400 | isEmpty = false; 401 | if (!opts.showAlpha) { 402 | currentAlpha = 1; 403 | } 404 | move(); 405 | }, dragStart, dragStop); 406 | 407 | draggable(dragger, function (dragX, dragY, e) { 408 | 409 | // shift+drag should snap the movement to either the x or y axis. 410 | if (!e.shiftKey) { 411 | shiftMovementDirection = null; 412 | } 413 | else if (!shiftMovementDirection) { 414 | var oldDragX = currentSaturation * dragWidth; 415 | var oldDragY = dragHeight - (currentValue * dragHeight); 416 | var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY); 417 | 418 | shiftMovementDirection = furtherFromX ? "x" : "y"; 419 | } 420 | 421 | var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x"; 422 | var setValue = !shiftMovementDirection || shiftMovementDirection === "y"; 423 | 424 | if (setSaturation) { 425 | currentSaturation = parseFloat(dragX / dragWidth); 426 | } 427 | if (setValue) { 428 | currentValue = parseFloat((dragHeight - dragY) / dragHeight); 429 | } 430 | 431 | isEmpty = false; 432 | if (!opts.showAlpha) { 433 | currentAlpha = 1; 434 | } 435 | 436 | move(); 437 | 438 | }, dragStart, dragStop); 439 | 440 | if (!!initialColor) { 441 | set(initialColor); 442 | 443 | // In case color was black - update the preview UI and set the format 444 | // since the set function will not run (default color is black). 445 | updateUI(); 446 | currentPreferredFormat = preferredFormat || tinycolor(initialColor).format; 447 | 448 | addColorToSelectionPalette(initialColor); 449 | } 450 | else { 451 | updateUI(); 452 | } 453 | 454 | if (flat) { 455 | show(); 456 | } 457 | 458 | function paletteElementClick(e) { 459 | if (e.data && e.data.ignore) { 460 | set($(e.target).closest(".sp-thumb-el").data("color")); 461 | move(); 462 | } 463 | else { 464 | set($(e.target).closest(".sp-thumb-el").data("color")); 465 | move(); 466 | updateOriginalInput(true); 467 | if (opts.hideAfterPaletteSelect) { 468 | hide(); 469 | } 470 | } 471 | 472 | return false; 473 | } 474 | 475 | var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum"; 476 | paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick); 477 | initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick); 478 | } 479 | 480 | function updateSelectionPaletteFromStorage() { 481 | 482 | if (localStorageKey && window.localStorage) { 483 | 484 | // Migrate old palettes over to new format. May want to remove this eventually. 485 | try { 486 | var oldPalette = window.localStorage[localStorageKey].split(",#"); 487 | if (oldPalette.length > 1) { 488 | delete window.localStorage[localStorageKey]; 489 | $.each(oldPalette, function(i, c) { 490 | addColorToSelectionPalette(c); 491 | }); 492 | } 493 | } 494 | catch(e) { } 495 | 496 | try { 497 | selectionPalette = window.localStorage[localStorageKey].split(";"); 498 | } 499 | catch (e) { } 500 | } 501 | } 502 | 503 | function addColorToSelectionPalette(color) { 504 | if (showSelectionPalette) { 505 | var rgb = tinycolor(color).toRgbString(); 506 | if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) { 507 | selectionPalette.push(rgb); 508 | while(selectionPalette.length > maxSelectionSize) { 509 | selectionPalette.shift(); 510 | } 511 | } 512 | 513 | if (localStorageKey && window.localStorage) { 514 | try { 515 | window.localStorage[localStorageKey] = selectionPalette.join(";"); 516 | } 517 | catch(e) { } 518 | } 519 | } 520 | } 521 | 522 | function getUniqueSelectionPalette() { 523 | var unique = []; 524 | if (opts.showPalette) { 525 | for (var i = 0; i < selectionPalette.length; i++) { 526 | var rgb = tinycolor(selectionPalette[i]).toRgbString(); 527 | 528 | if (!paletteLookup[rgb]) { 529 | unique.push(selectionPalette[i]); 530 | } 531 | } 532 | } 533 | 534 | return unique.reverse().slice(0, opts.maxSelectionSize); 535 | } 536 | 537 | function drawPalette() { 538 | 539 | var currentColor = get(); 540 | 541 | var html = $.map(paletteArray, function (palette, i) { 542 | return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts); 543 | }); 544 | 545 | updateSelectionPaletteFromStorage(); 546 | 547 | if (selectionPalette) { 548 | html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts)); 549 | } 550 | 551 | paletteContainer.html(html.join("")); 552 | } 553 | 554 | function drawInitial() { 555 | if (opts.showInitial) { 556 | var initial = colorOnShow; 557 | var current = get(); 558 | initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts)); 559 | } 560 | } 561 | 562 | function dragStart() { 563 | if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0) { 564 | reflow(); 565 | } 566 | isDragging = true; 567 | container.addClass(draggingClass); 568 | shiftMovementDirection = null; 569 | boundElement.trigger('dragstart.spectrum', [ get() ]); 570 | } 571 | 572 | function dragStop() { 573 | isDragging = false; 574 | container.removeClass(draggingClass); 575 | boundElement.trigger('dragstop.spectrum', [ get() ]); 576 | } 577 | 578 | function setFromTextInput() { 579 | 580 | var value = textInput.val(); 581 | 582 | if ((value === null || value === "") && allowEmpty) { 583 | set(null); 584 | updateOriginalInput(true); 585 | } 586 | else { 587 | var tiny = tinycolor(value); 588 | if (tiny.isValid()) { 589 | set(tiny); 590 | updateOriginalInput(true); 591 | } 592 | else { 593 | textInput.addClass("sp-validation-error"); 594 | } 595 | } 596 | } 597 | 598 | function toggle() { 599 | if (visible) { 600 | hide(); 601 | } 602 | else { 603 | show(); 604 | } 605 | } 606 | 607 | function show() { 608 | var event = $.Event('beforeShow.spectrum'); 609 | 610 | if (visible) { 611 | reflow(); 612 | return; 613 | } 614 | 615 | boundElement.trigger(event, [ get() ]); 616 | 617 | if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) { 618 | return; 619 | } 620 | 621 | hideAll(); 622 | visible = true; 623 | 624 | $(doc).bind("keydown.spectrum", onkeydown); 625 | $(doc).bind("click.spectrum", clickout); 626 | $(window).bind("resize.spectrum", resize); 627 | replacer.addClass("sp-active"); 628 | container.removeClass("sp-hidden"); 629 | 630 | reflow(); 631 | updateUI(); 632 | 633 | colorOnShow = get(); 634 | 635 | drawInitial(); 636 | callbacks.show(colorOnShow); 637 | boundElement.trigger('show.spectrum', [ colorOnShow ]); 638 | } 639 | 640 | function onkeydown(e) { 641 | // Close on ESC 642 | if (e.keyCode === 27) { 643 | hide(); 644 | } 645 | } 646 | 647 | function clickout(e) { 648 | // Return on right click. 649 | if (e.button == 2) { return; } 650 | 651 | // If a drag event was happening during the mouseup, don't hide 652 | // on click. 653 | if (isDragging) { return; } 654 | 655 | if (clickoutFiresChange) { 656 | updateOriginalInput(true); 657 | } 658 | else { 659 | revert(); 660 | } 661 | hide(); 662 | } 663 | 664 | function hide() { 665 | // Return if hiding is unnecessary 666 | if (!visible || flat) { return; } 667 | visible = false; 668 | 669 | $(doc).unbind("keydown.spectrum", onkeydown); 670 | $(doc).unbind("click.spectrum", clickout); 671 | $(window).unbind("resize.spectrum", resize); 672 | 673 | replacer.removeClass("sp-active"); 674 | container.addClass("sp-hidden"); 675 | 676 | callbacks.hide(get()); 677 | boundElement.trigger('hide.spectrum', [ get() ]); 678 | } 679 | 680 | function revert() { 681 | set(colorOnShow, true); 682 | } 683 | 684 | function set(color, ignoreFormatChange) { 685 | if (tinycolor.equals(color, get())) { 686 | // Update UI just in case a validation error needs 687 | // to be cleared. 688 | updateUI(); 689 | return; 690 | } 691 | 692 | var newColor, newHsv; 693 | if (!color && allowEmpty) { 694 | isEmpty = true; 695 | } else { 696 | isEmpty = false; 697 | newColor = tinycolor(color); 698 | newHsv = newColor.toHsv(); 699 | 700 | currentHue = (newHsv.h % 360) / 360; 701 | currentSaturation = newHsv.s; 702 | currentValue = newHsv.v; 703 | currentAlpha = newHsv.a; 704 | } 705 | updateUI(); 706 | 707 | if (newColor && newColor.isValid() && !ignoreFormatChange) { 708 | currentPreferredFormat = preferredFormat || newColor.getFormat(); 709 | } 710 | } 711 | 712 | function get(opts) { 713 | opts = opts || { }; 714 | 715 | if (allowEmpty && isEmpty) { 716 | return null; 717 | } 718 | 719 | return tinycolor.fromRatio({ 720 | h: currentHue, 721 | s: currentSaturation, 722 | v: currentValue, 723 | a: Math.round(currentAlpha * 100) / 100 724 | }, { format: opts.format || currentPreferredFormat }); 725 | } 726 | 727 | function isValid() { 728 | return !textInput.hasClass("sp-validation-error"); 729 | } 730 | 731 | function move() { 732 | updateUI(); 733 | 734 | callbacks.move(get()); 735 | boundElement.trigger('move.spectrum', [ get() ]); 736 | } 737 | 738 | function updateUI() { 739 | 740 | textInput.removeClass("sp-validation-error"); 741 | 742 | updateHelperLocations(); 743 | 744 | // Update dragger background color (gradients take care of saturation and value). 745 | var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 }); 746 | dragger.css("background-color", flatColor.toHexString()); 747 | 748 | // Get a format that alpha will be included in (hex and names ignore alpha) 749 | var format = currentPreferredFormat; 750 | if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) { 751 | if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") { 752 | format = "rgb"; 753 | } 754 | } 755 | 756 | var realColor = get({ format: format }), 757 | displayColor = ''; 758 | 759 | //reset background info for preview element 760 | previewElement.removeClass("sp-clear-display"); 761 | previewElement.css('background-color', 'transparent'); 762 | 763 | if (!realColor && allowEmpty) { 764 | // Update the replaced elements background with icon indicating no color selection 765 | previewElement.addClass("sp-clear-display"); 766 | } 767 | else { 768 | var realHex = realColor.toHexString(), 769 | realRgb = realColor.toRgbString(); 770 | 771 | // Update the replaced elements background color (with actual selected color) 772 | if (rgbaSupport || realColor.alpha === 1) { 773 | previewElement.css("background-color", realRgb); 774 | } 775 | else { 776 | previewElement.css("background-color", "transparent"); 777 | previewElement.css("filter", realColor.toFilter()); 778 | } 779 | 780 | if (opts.showAlpha) { 781 | var rgb = realColor.toRgb(); 782 | rgb.a = 0; 783 | var realAlpha = tinycolor(rgb).toRgbString(); 784 | var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")"; 785 | 786 | if (IE) { 787 | alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex)); 788 | } 789 | else { 790 | alphaSliderInner.css("background", "-webkit-" + gradient); 791 | alphaSliderInner.css("background", "-moz-" + gradient); 792 | alphaSliderInner.css("background", "-ms-" + gradient); 793 | // Use current syntax gradient on unprefixed property. 794 | alphaSliderInner.css("background", 795 | "linear-gradient(to right, " + realAlpha + ", " + realHex + ")"); 796 | } 797 | } 798 | 799 | displayColor = realColor.toString(format); 800 | } 801 | 802 | // Update the text entry input as it changes happen 803 | if (opts.showInput) { 804 | textInput.val(displayColor); 805 | } 806 | 807 | if (opts.showPalette) { 808 | drawPalette(); 809 | } 810 | 811 | drawInitial(); 812 | } 813 | 814 | function updateHelperLocations() { 815 | var s = currentSaturation; 816 | var v = currentValue; 817 | 818 | if(allowEmpty && isEmpty) { 819 | //if selected color is empty, hide the helpers 820 | alphaSlideHelper.hide(); 821 | slideHelper.hide(); 822 | dragHelper.hide(); 823 | } 824 | else { 825 | //make sure helpers are visible 826 | alphaSlideHelper.show(); 827 | slideHelper.show(); 828 | dragHelper.show(); 829 | 830 | // Where to show the little circle in that displays your current selected color 831 | var dragX = s * dragWidth; 832 | var dragY = dragHeight - (v * dragHeight); 833 | dragX = Math.max( 834 | -dragHelperHeight, 835 | Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight) 836 | ); 837 | dragY = Math.max( 838 | -dragHelperHeight, 839 | Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight) 840 | ); 841 | dragHelper.css({ 842 | "top": dragY + "px", 843 | "left": dragX + "px" 844 | }); 845 | 846 | var alphaX = currentAlpha * alphaWidth; 847 | alphaSlideHelper.css({ 848 | "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px" 849 | }); 850 | 851 | // Where to show the bar that displays your current selected hue 852 | var slideY = (currentHue) * slideHeight; 853 | slideHelper.css({ 854 | "top": (slideY - slideHelperHeight) + "px" 855 | }); 856 | } 857 | } 858 | 859 | function updateOriginalInput(fireCallback) { 860 | var color = get(), 861 | displayColor = '', 862 | hasChanged = !tinycolor.equals(color, colorOnShow); 863 | 864 | if (color) { 865 | displayColor = color.toString(currentPreferredFormat); 866 | // Update the selection palette with the current color 867 | addColorToSelectionPalette(color); 868 | } 869 | 870 | if (isInput) { 871 | boundElement.val(displayColor); 872 | } 873 | 874 | if (fireCallback && hasChanged) { 875 | callbacks.change(color); 876 | boundElement.trigger('change', [ color ]); 877 | } 878 | } 879 | 880 | function reflow() { 881 | dragWidth = dragger.width(); 882 | dragHeight = dragger.height(); 883 | dragHelperHeight = dragHelper.height(); 884 | slideWidth = slider.width(); 885 | slideHeight = slider.height(); 886 | slideHelperHeight = slideHelper.height(); 887 | alphaWidth = alphaSlider.width(); 888 | alphaSlideHelperWidth = alphaSlideHelper.width(); 889 | 890 | if (!flat) { 891 | container.css("position", "absolute"); 892 | if (opts.offset) { 893 | container.offset(opts.offset); 894 | } else { 895 | container.offset(getOffset(container, offsetElement)); 896 | } 897 | } 898 | 899 | updateHelperLocations(); 900 | 901 | if (opts.showPalette) { 902 | drawPalette(); 903 | } 904 | 905 | boundElement.trigger('reflow.spectrum'); 906 | } 907 | 908 | function destroy() { 909 | boundElement.show(); 910 | offsetElement.unbind("click.spectrum touchstart.spectrum"); 911 | container.remove(); 912 | replacer.remove(); 913 | spectrums[spect.id] = null; 914 | } 915 | 916 | function option(optionName, optionValue) { 917 | if (optionName === undefined) { 918 | return $.extend({}, opts); 919 | } 920 | if (optionValue === undefined) { 921 | return opts[optionName]; 922 | } 923 | 924 | opts[optionName] = optionValue; 925 | applyOptions(); 926 | } 927 | 928 | function enable() { 929 | disabled = false; 930 | boundElement.attr("disabled", false); 931 | offsetElement.removeClass("sp-disabled"); 932 | } 933 | 934 | function disable() { 935 | hide(); 936 | disabled = true; 937 | boundElement.attr("disabled", true); 938 | offsetElement.addClass("sp-disabled"); 939 | } 940 | 941 | function setOffset(coord) { 942 | opts.offset = coord; 943 | reflow(); 944 | } 945 | 946 | initialize(); 947 | 948 | var spect = { 949 | show: show, 950 | hide: hide, 951 | toggle: toggle, 952 | reflow: reflow, 953 | option: option, 954 | enable: enable, 955 | disable: disable, 956 | offset: setOffset, 957 | set: function (c) { 958 | set(c); 959 | updateOriginalInput(); 960 | }, 961 | get: get, 962 | destroy: destroy, 963 | container: container 964 | }; 965 | 966 | spect.id = spectrums.push(spect) - 1; 967 | 968 | return spect; 969 | } 970 | 971 | /** 972 | * checkOffset - get the offset below/above and left/right element depending on screen position 973 | * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js 974 | */ 975 | function getOffset(picker, input) { 976 | var extraY = 0; 977 | var dpWidth = picker.outerWidth(); 978 | var dpHeight = picker.outerHeight(); 979 | var inputHeight = input.outerHeight(); 980 | var doc = picker[0].ownerDocument; 981 | var docElem = doc.documentElement; 982 | var viewWidth = docElem.clientWidth + $(doc).scrollLeft(); 983 | var viewHeight = docElem.clientHeight + $(doc).scrollTop(); 984 | var offset = input.offset(); 985 | offset.top += inputHeight; 986 | 987 | offset.left -= 988 | Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? 989 | Math.abs(offset.left + dpWidth - viewWidth) : 0); 990 | 991 | offset.top -= 992 | Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? 993 | Math.abs(dpHeight + inputHeight - extraY) : extraY)); 994 | 995 | return offset; 996 | } 997 | 998 | /** 999 | * noop - do nothing 1000 | */ 1001 | function noop() { 1002 | 1003 | } 1004 | 1005 | /** 1006 | * stopPropagation - makes the code only doing this a little easier to read in line 1007 | */ 1008 | function stopPropagation(e) { 1009 | e.stopPropagation(); 1010 | } 1011 | 1012 | /** 1013 | * Create a function bound to a given object 1014 | * Thanks to underscore.js 1015 | */ 1016 | function bind(func, obj) { 1017 | var slice = Array.prototype.slice; 1018 | var args = slice.call(arguments, 2); 1019 | return function () { 1020 | return func.apply(obj, args.concat(slice.call(arguments))); 1021 | }; 1022 | } 1023 | 1024 | /** 1025 | * Lightweight drag helper. Handles containment within the element, so that 1026 | * when dragging, the x is within [0,element.width] and y is within [0,element.height] 1027 | */ 1028 | function draggable(element, onmove, onstart, onstop) { 1029 | onmove = onmove || function () { }; 1030 | onstart = onstart || function () { }; 1031 | onstop = onstop || function () { }; 1032 | var doc = document; 1033 | var dragging = false; 1034 | var offset = {}; 1035 | var maxHeight = 0; 1036 | var maxWidth = 0; 1037 | var hasTouch = ('ontouchstart' in window); 1038 | 1039 | var duringDragEvents = {}; 1040 | duringDragEvents["selectstart"] = prevent; 1041 | duringDragEvents["dragstart"] = prevent; 1042 | duringDragEvents["touchmove mousemove"] = move; 1043 | duringDragEvents["touchend mouseup"] = stop; 1044 | 1045 | function prevent(e) { 1046 | if (e.stopPropagation) { 1047 | e.stopPropagation(); 1048 | } 1049 | if (e.preventDefault) { 1050 | e.preventDefault(); 1051 | } 1052 | e.returnValue = false; 1053 | } 1054 | 1055 | function move(e) { 1056 | if (dragging) { 1057 | // Mouseup happened outside of window 1058 | if (IE && doc.documentMode < 9 && !e.button) { 1059 | return stop(); 1060 | } 1061 | 1062 | var t0 = e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0]; 1063 | var pageX = t0 && t0.pageX || e.pageX; 1064 | var pageY = t0 && t0.pageY || e.pageY; 1065 | 1066 | var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth)); 1067 | var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight)); 1068 | 1069 | if (hasTouch) { 1070 | // Stop scrolling in iOS 1071 | prevent(e); 1072 | } 1073 | 1074 | onmove.apply(element, [dragX, dragY, e]); 1075 | } 1076 | } 1077 | 1078 | function start(e) { 1079 | var rightclick = (e.which) ? (e.which == 3) : (e.button == 2); 1080 | 1081 | if (!rightclick && !dragging) { 1082 | if (onstart.apply(element, arguments) !== false) { 1083 | dragging = true; 1084 | maxHeight = $(element).height(); 1085 | maxWidth = $(element).width(); 1086 | offset = $(element).offset(); 1087 | 1088 | $(doc).bind(duringDragEvents); 1089 | $(doc.body).addClass("sp-dragging"); 1090 | 1091 | move(e); 1092 | 1093 | prevent(e); 1094 | } 1095 | } 1096 | } 1097 | 1098 | function stop() { 1099 | if (dragging) { 1100 | $(doc).unbind(duringDragEvents); 1101 | $(doc.body).removeClass("sp-dragging"); 1102 | 1103 | // Wait a tick before notifying observers to allow the click event 1104 | // to fire in Chrome. 1105 | setTimeout(function() { 1106 | onstop.apply(element, arguments); 1107 | }, 0); 1108 | } 1109 | dragging = false; 1110 | } 1111 | 1112 | $(element).bind("touchstart mousedown", start); 1113 | } 1114 | 1115 | function throttle(func, wait, debounce) { 1116 | var timeout; 1117 | return function () { 1118 | var context = this, args = arguments; 1119 | var throttler = function () { 1120 | timeout = null; 1121 | func.apply(context, args); 1122 | }; 1123 | if (debounce) clearTimeout(timeout); 1124 | if (debounce || !timeout) timeout = setTimeout(throttler, wait); 1125 | }; 1126 | } 1127 | 1128 | function inputTypeColorSupport() { 1129 | return $.fn.spectrum.inputTypeColorSupport(); 1130 | } 1131 | 1132 | /** 1133 | * Define a jQuery plugin 1134 | */ 1135 | var dataID = "spectrum.id"; 1136 | $.fn.spectrum = function (opts, extra) { 1137 | 1138 | if (typeof opts == "string") { 1139 | 1140 | var returnValue = this; 1141 | var args = Array.prototype.slice.call( arguments, 1 ); 1142 | 1143 | this.each(function () { 1144 | var spect = spectrums[$(this).data(dataID)]; 1145 | if (spect) { 1146 | var method = spect[opts]; 1147 | if (!method) { 1148 | throw new Error( "Spectrum: no such method: '" + opts + "'" ); 1149 | } 1150 | 1151 | if (opts == "get") { 1152 | returnValue = spect.get(); 1153 | } 1154 | else if (opts == "container") { 1155 | returnValue = spect.container; 1156 | } 1157 | else if (opts == "option") { 1158 | returnValue = spect.option.apply(spect, args); 1159 | } 1160 | else if (opts == "destroy") { 1161 | spect.destroy(); 1162 | $(this).removeData(dataID); 1163 | } 1164 | else { 1165 | method.apply(spect, args); 1166 | } 1167 | } 1168 | }); 1169 | 1170 | return returnValue; 1171 | } 1172 | 1173 | // Initializing a new instance of spectrum 1174 | return this.spectrum("destroy").each(function () { 1175 | var options = $.extend({}, opts, $(this).data()); 1176 | var spect = spectrum(this, options); 1177 | $(this).data(dataID, spect.id); 1178 | }); 1179 | }; 1180 | 1181 | $.fn.spectrum.load = true; 1182 | $.fn.spectrum.loadOpts = {}; 1183 | $.fn.spectrum.draggable = draggable; 1184 | $.fn.spectrum.defaults = defaultOpts; 1185 | $.fn.spectrum.inputTypeColorSupport = function inputTypeColorSupport() { 1186 | if (typeof inputTypeColorSupport._cachedResult === "undefined") { 1187 | var colorInput = $("")[0]; // if color element is supported, value will default to not null 1188 | inputTypeColorSupport._cachedResult = colorInput.type === "color" && colorInput.value !== ""; 1189 | } 1190 | return inputTypeColorSupport._cachedResult; 1191 | }; 1192 | 1193 | $.spectrum = { }; 1194 | $.spectrum.localization = { }; 1195 | $.spectrum.palettes = { }; 1196 | 1197 | $.fn.spectrum.processNativeColorInputs = function () { 1198 | var colorInputs = $("input[type=color]"); 1199 | if (colorInputs.length && !inputTypeColorSupport()) { 1200 | colorInputs.spectrum({ 1201 | preferredFormat: "hex6" 1202 | }); 1203 | } 1204 | }; 1205 | 1206 | // TinyColor v1.1.2 1207 | // https://github.com/bgrins/TinyColor 1208 | // Brian Grinstead, MIT License 1209 | 1210 | (function() { 1211 | 1212 | var trimLeft = /^[\s,#]+/, 1213 | trimRight = /\s+$/, 1214 | tinyCounter = 0, 1215 | math = Math, 1216 | mathRound = math.round, 1217 | mathMin = math.min, 1218 | mathMax = math.max, 1219 | mathRandom = math.random; 1220 | 1221 | var tinycolor = function(color, opts) { 1222 | 1223 | color = (color) ? color : ''; 1224 | opts = opts || { }; 1225 | 1226 | // If input is already a tinycolor, return itself 1227 | if (color instanceof tinycolor) { 1228 | return color; 1229 | } 1230 | // If we are called as a function, call using new instead 1231 | if (!(this instanceof tinycolor)) { 1232 | return new tinycolor(color, opts); 1233 | } 1234 | 1235 | var rgb = inputToRGB(color); 1236 | this._originalInput = color, 1237 | this._r = rgb.r, 1238 | this._g = rgb.g, 1239 | this._b = rgb.b, 1240 | this._a = rgb.a, 1241 | this._roundA = mathRound(100*this._a) / 100, 1242 | this._format = opts.format || rgb.format; 1243 | this._gradientType = opts.gradientType; 1244 | 1245 | // Don't let the range of [0,255] come back in [0,1]. 1246 | // Potentially lose a little bit of precision here, but will fix issues where 1247 | // .5 gets interpreted as half of the total, instead of half of 1 1248 | // If it was supposed to be 128, this was already taken care of by `inputToRgb` 1249 | if (this._r < 1) { this._r = mathRound(this._r); } 1250 | if (this._g < 1) { this._g = mathRound(this._g); } 1251 | if (this._b < 1) { this._b = mathRound(this._b); } 1252 | 1253 | this._ok = rgb.ok; 1254 | this._tc_id = tinyCounter++; 1255 | }; 1256 | 1257 | tinycolor.prototype = { 1258 | isDark: function() { 1259 | return this.getBrightness() < 128; 1260 | }, 1261 | isLight: function() { 1262 | return !this.isDark(); 1263 | }, 1264 | isValid: function() { 1265 | return this._ok; 1266 | }, 1267 | getOriginalInput: function() { 1268 | return this._originalInput; 1269 | }, 1270 | getFormat: function() { 1271 | return this._format; 1272 | }, 1273 | getAlpha: function() { 1274 | return this._a; 1275 | }, 1276 | getBrightness: function() { 1277 | var rgb = this.toRgb(); 1278 | return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; 1279 | }, 1280 | setAlpha: function(value) { 1281 | this._a = boundAlpha(value); 1282 | this._roundA = mathRound(100*this._a) / 100; 1283 | return this; 1284 | }, 1285 | toHsv: function() { 1286 | var hsv = rgbToHsv(this._r, this._g, this._b); 1287 | return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a }; 1288 | }, 1289 | toHsvString: function() { 1290 | var hsv = rgbToHsv(this._r, this._g, this._b); 1291 | var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100); 1292 | return (this._a == 1) ? 1293 | "hsv(" + h + ", " + s + "%, " + v + "%)" : 1294 | "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")"; 1295 | }, 1296 | toHsl: function() { 1297 | var hsl = rgbToHsl(this._r, this._g, this._b); 1298 | return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a }; 1299 | }, 1300 | toHslString: function() { 1301 | var hsl = rgbToHsl(this._r, this._g, this._b); 1302 | var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100); 1303 | return (this._a == 1) ? 1304 | "hsl(" + h + ", " + s + "%, " + l + "%)" : 1305 | "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")"; 1306 | }, 1307 | toHex: function(allow3Char) { 1308 | return rgbToHex(this._r, this._g, this._b, allow3Char); 1309 | }, 1310 | toHexString: function(allow3Char) { 1311 | return '#' + this.toHex(allow3Char); 1312 | }, 1313 | toHex8: function() { 1314 | return rgbaToHex(this._r, this._g, this._b, this._a); 1315 | }, 1316 | toHex8String: function() { 1317 | return '#' + this.toHex8(); 1318 | }, 1319 | toRgb: function() { 1320 | return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a }; 1321 | }, 1322 | toRgbString: function() { 1323 | return (this._a == 1) ? 1324 | "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" : 1325 | "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")"; 1326 | }, 1327 | toPercentageRgb: function() { 1328 | return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a }; 1329 | }, 1330 | toPercentageRgbString: function() { 1331 | return (this._a == 1) ? 1332 | "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" : 1333 | "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")"; 1334 | }, 1335 | toName: function() { 1336 | if (this._a === 0) { 1337 | return "transparent"; 1338 | } 1339 | 1340 | if (this._a < 1) { 1341 | return false; 1342 | } 1343 | 1344 | return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false; 1345 | }, 1346 | toFilter: function(secondColor) { 1347 | var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a); 1348 | var secondHex8String = hex8String; 1349 | var gradientType = this._gradientType ? "GradientType = 1, " : ""; 1350 | 1351 | if (secondColor) { 1352 | var s = tinycolor(secondColor); 1353 | secondHex8String = s.toHex8String(); 1354 | } 1355 | 1356 | return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")"; 1357 | }, 1358 | toString: function(format) { 1359 | var formatSet = !!format; 1360 | format = format || this._format; 1361 | 1362 | var formattedString = false; 1363 | var hasAlpha = this._a < 1 && this._a >= 0; 1364 | var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name"); 1365 | 1366 | if (needsAlphaFormat) { 1367 | // Special case for "transparent", all other non-alpha formats 1368 | // will return rgba when there is transparency. 1369 | if (format === "name" && this._a === 0) { 1370 | return this.toName(); 1371 | } 1372 | return this.toRgbString(); 1373 | } 1374 | if (format === "rgb") { 1375 | formattedString = this.toRgbString(); 1376 | } 1377 | if (format === "prgb") { 1378 | formattedString = this.toPercentageRgbString(); 1379 | } 1380 | if (format === "hex" || format === "hex6") { 1381 | formattedString = this.toHexString(); 1382 | } 1383 | if (format === "hex3") { 1384 | formattedString = this.toHexString(true); 1385 | } 1386 | if (format === "hex8") { 1387 | formattedString = this.toHex8String(); 1388 | } 1389 | if (format === "name") { 1390 | formattedString = this.toName(); 1391 | } 1392 | if (format === "hsl") { 1393 | formattedString = this.toHslString(); 1394 | } 1395 | if (format === "hsv") { 1396 | formattedString = this.toHsvString(); 1397 | } 1398 | 1399 | return formattedString || this.toHexString(); 1400 | }, 1401 | 1402 | _applyModification: function(fn, args) { 1403 | var color = fn.apply(null, [this].concat([].slice.call(args))); 1404 | this._r = color._r; 1405 | this._g = color._g; 1406 | this._b = color._b; 1407 | this.setAlpha(color._a); 1408 | return this; 1409 | }, 1410 | lighten: function() { 1411 | return this._applyModification(lighten, arguments); 1412 | }, 1413 | brighten: function() { 1414 | return this._applyModification(brighten, arguments); 1415 | }, 1416 | darken: function() { 1417 | return this._applyModification(darken, arguments); 1418 | }, 1419 | desaturate: function() { 1420 | return this._applyModification(desaturate, arguments); 1421 | }, 1422 | saturate: function() { 1423 | return this._applyModification(saturate, arguments); 1424 | }, 1425 | greyscale: function() { 1426 | return this._applyModification(greyscale, arguments); 1427 | }, 1428 | spin: function() { 1429 | return this._applyModification(spin, arguments); 1430 | }, 1431 | 1432 | _applyCombination: function(fn, args) { 1433 | return fn.apply(null, [this].concat([].slice.call(args))); 1434 | }, 1435 | analogous: function() { 1436 | return this._applyCombination(analogous, arguments); 1437 | }, 1438 | complement: function() { 1439 | return this._applyCombination(complement, arguments); 1440 | }, 1441 | monochromatic: function() { 1442 | return this._applyCombination(monochromatic, arguments); 1443 | }, 1444 | splitcomplement: function() { 1445 | return this._applyCombination(splitcomplement, arguments); 1446 | }, 1447 | triad: function() { 1448 | return this._applyCombination(triad, arguments); 1449 | }, 1450 | tetrad: function() { 1451 | return this._applyCombination(tetrad, arguments); 1452 | } 1453 | }; 1454 | 1455 | // If input is an object, force 1 into "1.0" to handle ratios properly 1456 | // String input requires "1.0" as input, so 1 will be treated as 1 1457 | tinycolor.fromRatio = function(color, opts) { 1458 | if (typeof color == "object") { 1459 | var newColor = {}; 1460 | for (var i in color) { 1461 | if (color.hasOwnProperty(i)) { 1462 | if (i === "a") { 1463 | newColor[i] = color[i]; 1464 | } 1465 | else { 1466 | newColor[i] = convertToPercentage(color[i]); 1467 | } 1468 | } 1469 | } 1470 | color = newColor; 1471 | } 1472 | 1473 | return tinycolor(color, opts); 1474 | }; 1475 | 1476 | // Given a string or object, convert that input to RGB 1477 | // Possible string inputs: 1478 | // 1479 | // "red" 1480 | // "#f00" or "f00" 1481 | // "#ff0000" or "ff0000" 1482 | // "#ff000000" or "ff000000" 1483 | // "rgb 255 0 0" or "rgb (255, 0, 0)" 1484 | // "rgb 1.0 0 0" or "rgb (1, 0, 0)" 1485 | // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" 1486 | // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" 1487 | // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" 1488 | // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" 1489 | // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" 1490 | // 1491 | function inputToRGB(color) { 1492 | 1493 | var rgb = { r: 0, g: 0, b: 0 }; 1494 | var a = 1; 1495 | var ok = false; 1496 | var format = false; 1497 | 1498 | if (typeof color == "string") { 1499 | color = stringInputToObject(color); 1500 | } 1501 | 1502 | if (typeof color == "object") { 1503 | if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) { 1504 | rgb = rgbToRgb(color.r, color.g, color.b); 1505 | ok = true; 1506 | format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb"; 1507 | } 1508 | else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) { 1509 | color.s = convertToPercentage(color.s); 1510 | color.v = convertToPercentage(color.v); 1511 | rgb = hsvToRgb(color.h, color.s, color.v); 1512 | ok = true; 1513 | format = "hsv"; 1514 | } 1515 | else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) { 1516 | color.s = convertToPercentage(color.s); 1517 | color.l = convertToPercentage(color.l); 1518 | rgb = hslToRgb(color.h, color.s, color.l); 1519 | ok = true; 1520 | format = "hsl"; 1521 | } 1522 | 1523 | if (color.hasOwnProperty("a")) { 1524 | a = color.a; 1525 | } 1526 | } 1527 | 1528 | a = boundAlpha(a); 1529 | 1530 | return { 1531 | ok: ok, 1532 | format: color.format || format, 1533 | r: mathMin(255, mathMax(rgb.r, 0)), 1534 | g: mathMin(255, mathMax(rgb.g, 0)), 1535 | b: mathMin(255, mathMax(rgb.b, 0)), 1536 | a: a 1537 | }; 1538 | } 1539 | 1540 | 1541 | // Conversion Functions 1542 | // -------------------- 1543 | 1544 | // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: 1545 | // 1546 | 1547 | // `rgbToRgb` 1548 | // Handle bounds / percentage checking to conform to CSS color spec 1549 | // 1550 | // *Assumes:* r, g, b in [0, 255] or [0, 1] 1551 | // *Returns:* { r, g, b } in [0, 255] 1552 | function rgbToRgb(r, g, b){ 1553 | return { 1554 | r: bound01(r, 255) * 255, 1555 | g: bound01(g, 255) * 255, 1556 | b: bound01(b, 255) * 255 1557 | }; 1558 | } 1559 | 1560 | // `rgbToHsl` 1561 | // Converts an RGB color value to HSL. 1562 | // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1] 1563 | // *Returns:* { h, s, l } in [0,1] 1564 | function rgbToHsl(r, g, b) { 1565 | 1566 | r = bound01(r, 255); 1567 | g = bound01(g, 255); 1568 | b = bound01(b, 255); 1569 | 1570 | var max = mathMax(r, g, b), min = mathMin(r, g, b); 1571 | var h, s, l = (max + min) / 2; 1572 | 1573 | if(max == min) { 1574 | h = s = 0; // achromatic 1575 | } 1576 | else { 1577 | var d = max - min; 1578 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 1579 | switch(max) { 1580 | case r: h = (g - b) / d + (g < b ? 6 : 0); break; 1581 | case g: h = (b - r) / d + 2; break; 1582 | case b: h = (r - g) / d + 4; break; 1583 | } 1584 | 1585 | h /= 6; 1586 | } 1587 | 1588 | return { h: h, s: s, l: l }; 1589 | } 1590 | 1591 | // `hslToRgb` 1592 | // Converts an HSL color value to RGB. 1593 | // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] 1594 | // *Returns:* { r, g, b } in the set [0, 255] 1595 | function hslToRgb(h, s, l) { 1596 | var r, g, b; 1597 | 1598 | h = bound01(h, 360); 1599 | s = bound01(s, 100); 1600 | l = bound01(l, 100); 1601 | 1602 | function hue2rgb(p, q, t) { 1603 | if(t < 0) t += 1; 1604 | if(t > 1) t -= 1; 1605 | if(t < 1/6) return p + (q - p) * 6 * t; 1606 | if(t < 1/2) return q; 1607 | if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; 1608 | return p; 1609 | } 1610 | 1611 | if(s === 0) { 1612 | r = g = b = l; // achromatic 1613 | } 1614 | else { 1615 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 1616 | var p = 2 * l - q; 1617 | r = hue2rgb(p, q, h + 1/3); 1618 | g = hue2rgb(p, q, h); 1619 | b = hue2rgb(p, q, h - 1/3); 1620 | } 1621 | 1622 | return { r: r * 255, g: g * 255, b: b * 255 }; 1623 | } 1624 | 1625 | // `rgbToHsv` 1626 | // Converts an RGB color value to HSV 1627 | // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] 1628 | // *Returns:* { h, s, v } in [0,1] 1629 | function rgbToHsv(r, g, b) { 1630 | 1631 | r = bound01(r, 255); 1632 | g = bound01(g, 255); 1633 | b = bound01(b, 255); 1634 | 1635 | var max = mathMax(r, g, b), min = mathMin(r, g, b); 1636 | var h, s, v = max; 1637 | 1638 | var d = max - min; 1639 | s = max === 0 ? 0 : d / max; 1640 | 1641 | if(max == min) { 1642 | h = 0; // achromatic 1643 | } 1644 | else { 1645 | switch(max) { 1646 | case r: h = (g - b) / d + (g < b ? 6 : 0); break; 1647 | case g: h = (b - r) / d + 2; break; 1648 | case b: h = (r - g) / d + 4; break; 1649 | } 1650 | h /= 6; 1651 | } 1652 | return { h: h, s: s, v: v }; 1653 | } 1654 | 1655 | // `hsvToRgb` 1656 | // Converts an HSV color value to RGB. 1657 | // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] 1658 | // *Returns:* { r, g, b } in the set [0, 255] 1659 | function hsvToRgb(h, s, v) { 1660 | 1661 | h = bound01(h, 360) * 6; 1662 | s = bound01(s, 100); 1663 | v = bound01(v, 100); 1664 | 1665 | var i = math.floor(h), 1666 | f = h - i, 1667 | p = v * (1 - s), 1668 | q = v * (1 - f * s), 1669 | t = v * (1 - (1 - f) * s), 1670 | mod = i % 6, 1671 | r = [v, q, p, p, t, v][mod], 1672 | g = [t, v, v, q, p, p][mod], 1673 | b = [p, p, t, v, v, q][mod]; 1674 | 1675 | return { r: r * 255, g: g * 255, b: b * 255 }; 1676 | } 1677 | 1678 | // `rgbToHex` 1679 | // Converts an RGB color to hex 1680 | // Assumes r, g, and b are contained in the set [0, 255] 1681 | // Returns a 3 or 6 character hex 1682 | function rgbToHex(r, g, b, allow3Char) { 1683 | 1684 | var hex = [ 1685 | pad2(mathRound(r).toString(16)), 1686 | pad2(mathRound(g).toString(16)), 1687 | pad2(mathRound(b).toString(16)) 1688 | ]; 1689 | 1690 | // Return a 3 character hex if possible 1691 | if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) { 1692 | return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); 1693 | } 1694 | 1695 | return hex.join(""); 1696 | } 1697 | // `rgbaToHex` 1698 | // Converts an RGBA color plus alpha transparency to hex 1699 | // Assumes r, g, b and a are contained in the set [0, 255] 1700 | // Returns an 8 character hex 1701 | function rgbaToHex(r, g, b, a) { 1702 | 1703 | var hex = [ 1704 | pad2(convertDecimalToHex(a)), 1705 | pad2(mathRound(r).toString(16)), 1706 | pad2(mathRound(g).toString(16)), 1707 | pad2(mathRound(b).toString(16)) 1708 | ]; 1709 | 1710 | return hex.join(""); 1711 | } 1712 | 1713 | // `equals` 1714 | // Can be called with any tinycolor input 1715 | tinycolor.equals = function (color1, color2) { 1716 | if (!color1 || !color2) { return false; } 1717 | return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString(); 1718 | }; 1719 | tinycolor.random = function() { 1720 | return tinycolor.fromRatio({ 1721 | r: mathRandom(), 1722 | g: mathRandom(), 1723 | b: mathRandom() 1724 | }); 1725 | }; 1726 | 1727 | 1728 | // Modification Functions 1729 | // ---------------------- 1730 | // Thanks to less.js for some of the basics here 1731 | // 1732 | 1733 | function desaturate(color, amount) { 1734 | amount = (amount === 0) ? 0 : (amount || 10); 1735 | var hsl = tinycolor(color).toHsl(); 1736 | hsl.s -= amount / 100; 1737 | hsl.s = clamp01(hsl.s); 1738 | return tinycolor(hsl); 1739 | } 1740 | 1741 | function saturate(color, amount) { 1742 | amount = (amount === 0) ? 0 : (amount || 10); 1743 | var hsl = tinycolor(color).toHsl(); 1744 | hsl.s += amount / 100; 1745 | hsl.s = clamp01(hsl.s); 1746 | return tinycolor(hsl); 1747 | } 1748 | 1749 | function greyscale(color) { 1750 | return tinycolor(color).desaturate(100); 1751 | } 1752 | 1753 | function lighten (color, amount) { 1754 | amount = (amount === 0) ? 0 : (amount || 10); 1755 | var hsl = tinycolor(color).toHsl(); 1756 | hsl.l += amount / 100; 1757 | hsl.l = clamp01(hsl.l); 1758 | return tinycolor(hsl); 1759 | } 1760 | 1761 | function brighten(color, amount) { 1762 | amount = (amount === 0) ? 0 : (amount || 10); 1763 | var rgb = tinycolor(color).toRgb(); 1764 | rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100)))); 1765 | rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100)))); 1766 | rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100)))); 1767 | return tinycolor(rgb); 1768 | } 1769 | 1770 | function darken (color, amount) { 1771 | amount = (amount === 0) ? 0 : (amount || 10); 1772 | var hsl = tinycolor(color).toHsl(); 1773 | hsl.l -= amount / 100; 1774 | hsl.l = clamp01(hsl.l); 1775 | return tinycolor(hsl); 1776 | } 1777 | 1778 | // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue. 1779 | // Values outside of this range will be wrapped into this range. 1780 | function spin(color, amount) { 1781 | var hsl = tinycolor(color).toHsl(); 1782 | var hue = (mathRound(hsl.h) + amount) % 360; 1783 | hsl.h = hue < 0 ? 360 + hue : hue; 1784 | return tinycolor(hsl); 1785 | } 1786 | 1787 | // Combination Functions 1788 | // --------------------- 1789 | // Thanks to jQuery xColor for some of the ideas behind these 1790 | // 1791 | 1792 | function complement(color) { 1793 | var hsl = tinycolor(color).toHsl(); 1794 | hsl.h = (hsl.h + 180) % 360; 1795 | return tinycolor(hsl); 1796 | } 1797 | 1798 | function triad(color) { 1799 | var hsl = tinycolor(color).toHsl(); 1800 | var h = hsl.h; 1801 | return [ 1802 | tinycolor(color), 1803 | tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }), 1804 | tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l }) 1805 | ]; 1806 | } 1807 | 1808 | function tetrad(color) { 1809 | var hsl = tinycolor(color).toHsl(); 1810 | var h = hsl.h; 1811 | return [ 1812 | tinycolor(color), 1813 | tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }), 1814 | tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }), 1815 | tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l }) 1816 | ]; 1817 | } 1818 | 1819 | function splitcomplement(color) { 1820 | var hsl = tinycolor(color).toHsl(); 1821 | var h = hsl.h; 1822 | return [ 1823 | tinycolor(color), 1824 | tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}), 1825 | tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l}) 1826 | ]; 1827 | } 1828 | 1829 | function analogous(color, results, slices) { 1830 | results = results || 6; 1831 | slices = slices || 30; 1832 | 1833 | var hsl = tinycolor(color).toHsl(); 1834 | var part = 360 / slices; 1835 | var ret = [tinycolor(color)]; 1836 | 1837 | for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) { 1838 | hsl.h = (hsl.h + part) % 360; 1839 | ret.push(tinycolor(hsl)); 1840 | } 1841 | return ret; 1842 | } 1843 | 1844 | function monochromatic(color, results) { 1845 | results = results || 6; 1846 | var hsv = tinycolor(color).toHsv(); 1847 | var h = hsv.h, s = hsv.s, v = hsv.v; 1848 | var ret = []; 1849 | var modification = 1 / results; 1850 | 1851 | while (results--) { 1852 | ret.push(tinycolor({ h: h, s: s, v: v})); 1853 | v = (v + modification) % 1; 1854 | } 1855 | 1856 | return ret; 1857 | } 1858 | 1859 | // Utility Functions 1860 | // --------------------- 1861 | 1862 | tinycolor.mix = function(color1, color2, amount) { 1863 | amount = (amount === 0) ? 0 : (amount || 50); 1864 | 1865 | var rgb1 = tinycolor(color1).toRgb(); 1866 | var rgb2 = tinycolor(color2).toRgb(); 1867 | 1868 | var p = amount / 100; 1869 | var w = p * 2 - 1; 1870 | var a = rgb2.a - rgb1.a; 1871 | 1872 | var w1; 1873 | 1874 | if (w * a == -1) { 1875 | w1 = w; 1876 | } else { 1877 | w1 = (w + a) / (1 + w * a); 1878 | } 1879 | 1880 | w1 = (w1 + 1) / 2; 1881 | 1882 | var w2 = 1 - w1; 1883 | 1884 | var rgba = { 1885 | r: rgb2.r * w1 + rgb1.r * w2, 1886 | g: rgb2.g * w1 + rgb1.g * w2, 1887 | b: rgb2.b * w1 + rgb1.b * w2, 1888 | a: rgb2.a * p + rgb1.a * (1 - p) 1889 | }; 1890 | 1891 | return tinycolor(rgba); 1892 | }; 1893 | 1894 | 1895 | // Readability Functions 1896 | // --------------------- 1897 | // 1898 | 1899 | // `readability` 1900 | // Analyze the 2 colors and returns an object with the following properties: 1901 | // `brightness`: difference in brightness between the two colors 1902 | // `color`: difference in color/hue between the two colors 1903 | tinycolor.readability = function(color1, color2) { 1904 | var c1 = tinycolor(color1); 1905 | var c2 = tinycolor(color2); 1906 | var rgb1 = c1.toRgb(); 1907 | var rgb2 = c2.toRgb(); 1908 | var brightnessA = c1.getBrightness(); 1909 | var brightnessB = c2.getBrightness(); 1910 | var colorDiff = ( 1911 | Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) + 1912 | Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) + 1913 | Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b) 1914 | ); 1915 | 1916 | return { 1917 | brightness: Math.abs(brightnessA - brightnessB), 1918 | color: colorDiff 1919 | }; 1920 | }; 1921 | 1922 | // `readable` 1923 | // http://www.w3.org/TR/AERT#color-contrast 1924 | // Ensure that foreground and background color combinations provide sufficient contrast. 1925 | // *Example* 1926 | // tinycolor.isReadable("#000", "#111") => false 1927 | tinycolor.isReadable = function(color1, color2) { 1928 | var readability = tinycolor.readability(color1, color2); 1929 | return readability.brightness > 125 && readability.color > 500; 1930 | }; 1931 | 1932 | // `mostReadable` 1933 | // Given a base color and a list of possible foreground or background 1934 | // colors for that base, returns the most readable color. 1935 | // *Example* 1936 | // tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000" 1937 | tinycolor.mostReadable = function(baseColor, colorList) { 1938 | var bestColor = null; 1939 | var bestScore = 0; 1940 | var bestIsReadable = false; 1941 | for (var i=0; i < colorList.length; i++) { 1942 | 1943 | // We normalize both around the "acceptable" breaking point, 1944 | // but rank brightness constrast higher than hue. 1945 | 1946 | var readability = tinycolor.readability(baseColor, colorList[i]); 1947 | var readable = readability.brightness > 125 && readability.color > 500; 1948 | var score = 3 * (readability.brightness / 125) + (readability.color / 500); 1949 | 1950 | if ((readable && ! bestIsReadable) || 1951 | (readable && bestIsReadable && score > bestScore) || 1952 | ((! readable) && (! bestIsReadable) && score > bestScore)) { 1953 | bestIsReadable = readable; 1954 | bestScore = score; 1955 | bestColor = tinycolor(colorList[i]); 1956 | } 1957 | } 1958 | return bestColor; 1959 | }; 1960 | 1961 | 1962 | // Big List of Colors 1963 | // ------------------ 1964 | // 1965 | var names = tinycolor.names = { 1966 | aliceblue: "f0f8ff", 1967 | antiquewhite: "faebd7", 1968 | aqua: "0ff", 1969 | aquamarine: "7fffd4", 1970 | azure: "f0ffff", 1971 | beige: "f5f5dc", 1972 | bisque: "ffe4c4", 1973 | black: "000", 1974 | blanchedalmond: "ffebcd", 1975 | blue: "00f", 1976 | blueviolet: "8a2be2", 1977 | brown: "a52a2a", 1978 | burlywood: "deb887", 1979 | burntsienna: "ea7e5d", 1980 | cadetblue: "5f9ea0", 1981 | chartreuse: "7fff00", 1982 | chocolate: "d2691e", 1983 | coral: "ff7f50", 1984 | cornflowerblue: "6495ed", 1985 | cornsilk: "fff8dc", 1986 | crimson: "dc143c", 1987 | cyan: "0ff", 1988 | darkblue: "00008b", 1989 | darkcyan: "008b8b", 1990 | darkgoldenrod: "b8860b", 1991 | darkgray: "a9a9a9", 1992 | darkgreen: "006400", 1993 | darkgrey: "a9a9a9", 1994 | darkkhaki: "bdb76b", 1995 | darkmagenta: "8b008b", 1996 | darkolivegreen: "556b2f", 1997 | darkorange: "ff8c00", 1998 | darkorchid: "9932cc", 1999 | darkred: "8b0000", 2000 | darksalmon: "e9967a", 2001 | darkseagreen: "8fbc8f", 2002 | darkslateblue: "483d8b", 2003 | darkslategray: "2f4f4f", 2004 | darkslategrey: "2f4f4f", 2005 | darkturquoise: "00ced1", 2006 | darkviolet: "9400d3", 2007 | deeppink: "ff1493", 2008 | deepskyblue: "00bfff", 2009 | dimgray: "696969", 2010 | dimgrey: "696969", 2011 | dodgerblue: "1e90ff", 2012 | firebrick: "b22222", 2013 | floralwhite: "fffaf0", 2014 | forestgreen: "228b22", 2015 | fuchsia: "f0f", 2016 | gainsboro: "dcdcdc", 2017 | ghostwhite: "f8f8ff", 2018 | gold: "ffd700", 2019 | goldenrod: "daa520", 2020 | gray: "808080", 2021 | green: "008000", 2022 | greenyellow: "adff2f", 2023 | grey: "808080", 2024 | honeydew: "f0fff0", 2025 | hotpink: "ff69b4", 2026 | indianred: "cd5c5c", 2027 | indigo: "4b0082", 2028 | ivory: "fffff0", 2029 | khaki: "f0e68c", 2030 | lavender: "e6e6fa", 2031 | lavenderblush: "fff0f5", 2032 | lawngreen: "7cfc00", 2033 | lemonchiffon: "fffacd", 2034 | lightblue: "add8e6", 2035 | lightcoral: "f08080", 2036 | lightcyan: "e0ffff", 2037 | lightgoldenrodyellow: "fafad2", 2038 | lightgray: "d3d3d3", 2039 | lightgreen: "90ee90", 2040 | lightgrey: "d3d3d3", 2041 | lightpink: "ffb6c1", 2042 | lightsalmon: "ffa07a", 2043 | lightseagreen: "20b2aa", 2044 | lightskyblue: "87cefa", 2045 | lightslategray: "789", 2046 | lightslategrey: "789", 2047 | lightsteelblue: "b0c4de", 2048 | lightyellow: "ffffe0", 2049 | lime: "0f0", 2050 | limegreen: "32cd32", 2051 | linen: "faf0e6", 2052 | magenta: "f0f", 2053 | maroon: "800000", 2054 | mediumaquamarine: "66cdaa", 2055 | mediumblue: "0000cd", 2056 | mediumorchid: "ba55d3", 2057 | mediumpurple: "9370db", 2058 | mediumseagreen: "3cb371", 2059 | mediumslateblue: "7b68ee", 2060 | mediumspringgreen: "00fa9a", 2061 | mediumturquoise: "48d1cc", 2062 | mediumvioletred: "c71585", 2063 | midnightblue: "191970", 2064 | mintcream: "f5fffa", 2065 | mistyrose: "ffe4e1", 2066 | moccasin: "ffe4b5", 2067 | navajowhite: "ffdead", 2068 | navy: "000080", 2069 | oldlace: "fdf5e6", 2070 | olive: "808000", 2071 | olivedrab: "6b8e23", 2072 | orange: "ffa500", 2073 | orangered: "ff4500", 2074 | orchid: "da70d6", 2075 | palegoldenrod: "eee8aa", 2076 | palegreen: "98fb98", 2077 | paleturquoise: "afeeee", 2078 | palevioletred: "db7093", 2079 | papayawhip: "ffefd5", 2080 | peachpuff: "ffdab9", 2081 | peru: "cd853f", 2082 | pink: "ffc0cb", 2083 | plum: "dda0dd", 2084 | powderblue: "b0e0e6", 2085 | purple: "800080", 2086 | rebeccapurple: "663399", 2087 | red: "f00", 2088 | rosybrown: "bc8f8f", 2089 | royalblue: "4169e1", 2090 | saddlebrown: "8b4513", 2091 | salmon: "fa8072", 2092 | sandybrown: "f4a460", 2093 | seagreen: "2e8b57", 2094 | seashell: "fff5ee", 2095 | sienna: "a0522d", 2096 | silver: "c0c0c0", 2097 | skyblue: "87ceeb", 2098 | slateblue: "6a5acd", 2099 | slategray: "708090", 2100 | slategrey: "708090", 2101 | snow: "fffafa", 2102 | springgreen: "00ff7f", 2103 | steelblue: "4682b4", 2104 | tan: "d2b48c", 2105 | teal: "008080", 2106 | thistle: "d8bfd8", 2107 | tomato: "ff6347", 2108 | turquoise: "40e0d0", 2109 | violet: "ee82ee", 2110 | wheat: "f5deb3", 2111 | white: "fff", 2112 | whitesmoke: "f5f5f5", 2113 | yellow: "ff0", 2114 | yellowgreen: "9acd32" 2115 | }; 2116 | 2117 | // Make it easy to access colors via `hexNames[hex]` 2118 | var hexNames = tinycolor.hexNames = flip(names); 2119 | 2120 | 2121 | // Utilities 2122 | // --------- 2123 | 2124 | // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }` 2125 | function flip(o) { 2126 | var flipped = { }; 2127 | for (var i in o) { 2128 | if (o.hasOwnProperty(i)) { 2129 | flipped[o[i]] = i; 2130 | } 2131 | } 2132 | return flipped; 2133 | } 2134 | 2135 | // Return a valid alpha value [0,1] with all invalid values being set to 1 2136 | function boundAlpha(a) { 2137 | a = parseFloat(a); 2138 | 2139 | if (isNaN(a) || a < 0 || a > 1) { 2140 | a = 1; 2141 | } 2142 | 2143 | return a; 2144 | } 2145 | 2146 | // Take input from [0, n] and return it as [0, 1] 2147 | function bound01(n, max) { 2148 | if (isOnePointZero(n)) { n = "100%"; } 2149 | 2150 | var processPercent = isPercentage(n); 2151 | n = mathMin(max, mathMax(0, parseFloat(n))); 2152 | 2153 | // Automatically convert percentage into number 2154 | if (processPercent) { 2155 | n = parseInt(n * max, 10) / 100; 2156 | } 2157 | 2158 | // Handle floating point rounding errors 2159 | if ((math.abs(n - max) < 0.000001)) { 2160 | return 1; 2161 | } 2162 | 2163 | // Convert into [0, 1] range if it isn't already 2164 | return (n % max) / parseFloat(max); 2165 | } 2166 | 2167 | // Force a number between 0 and 1 2168 | function clamp01(val) { 2169 | return mathMin(1, mathMax(0, val)); 2170 | } 2171 | 2172 | // Parse a base-16 hex value into a base-10 integer 2173 | function parseIntFromHex(val) { 2174 | return parseInt(val, 16); 2175 | } 2176 | 2177 | // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 2178 | // 2179 | function isOnePointZero(n) { 2180 | return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; 2181 | } 2182 | 2183 | // Check to see if string passed in is a percentage 2184 | function isPercentage(n) { 2185 | return typeof n === "string" && n.indexOf('%') != -1; 2186 | } 2187 | 2188 | // Force a hex value to have 2 characters 2189 | function pad2(c) { 2190 | return c.length == 1 ? '0' + c : '' + c; 2191 | } 2192 | 2193 | // Replace a decimal with it's percentage value 2194 | function convertToPercentage(n) { 2195 | if (n <= 1) { 2196 | n = (n * 100) + "%"; 2197 | } 2198 | 2199 | return n; 2200 | } 2201 | 2202 | // Converts a decimal to a hex value 2203 | function convertDecimalToHex(d) { 2204 | return Math.round(parseFloat(d) * 255).toString(16); 2205 | } 2206 | // Converts a hex value to a decimal 2207 | function convertHexToDecimal(h) { 2208 | return (parseIntFromHex(h) / 255); 2209 | } 2210 | 2211 | var matchers = (function() { 2212 | 2213 | // 2214 | var CSS_INTEGER = "[-\\+]?\\d+%?"; 2215 | 2216 | // 2217 | var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; 2218 | 2219 | // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. 2220 | var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; 2221 | 2222 | // Actual matching. 2223 | // Parentheses and commas are optional, but not required. 2224 | // Whitespace can take the place of commas or opening paren 2225 | var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; 2226 | var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; 2227 | 2228 | return { 2229 | rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), 2230 | rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), 2231 | hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), 2232 | hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), 2233 | hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), 2234 | hsva: new RegExp("hsva" + PERMISSIVE_MATCH4), 2235 | hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, 2236 | hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, 2237 | hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ 2238 | }; 2239 | })(); 2240 | 2241 | // `stringInputToObject` 2242 | // Permissive string parsing. Take in a number of formats, and output an object 2243 | // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` 2244 | function stringInputToObject(color) { 2245 | 2246 | color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase(); 2247 | var named = false; 2248 | if (names[color]) { 2249 | color = names[color]; 2250 | named = true; 2251 | } 2252 | else if (color == 'transparent') { 2253 | return { r: 0, g: 0, b: 0, a: 0, format: "name" }; 2254 | } 2255 | 2256 | // Try to match string input using regular expressions. 2257 | // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] 2258 | // Just return an object and let the conversion functions handle that. 2259 | // This way the result will be the same whether the tinycolor is initialized with string or object. 2260 | var match; 2261 | if ((match = matchers.rgb.exec(color))) { 2262 | return { r: match[1], g: match[2], b: match[3] }; 2263 | } 2264 | if ((match = matchers.rgba.exec(color))) { 2265 | return { r: match[1], g: match[2], b: match[3], a: match[4] }; 2266 | } 2267 | if ((match = matchers.hsl.exec(color))) { 2268 | return { h: match[1], s: match[2], l: match[3] }; 2269 | } 2270 | if ((match = matchers.hsla.exec(color))) { 2271 | return { h: match[1], s: match[2], l: match[3], a: match[4] }; 2272 | } 2273 | if ((match = matchers.hsv.exec(color))) { 2274 | return { h: match[1], s: match[2], v: match[3] }; 2275 | } 2276 | if ((match = matchers.hsva.exec(color))) { 2277 | return { h: match[1], s: match[2], v: match[3], a: match[4] }; 2278 | } 2279 | if ((match = matchers.hex8.exec(color))) { 2280 | return { 2281 | a: convertHexToDecimal(match[1]), 2282 | r: parseIntFromHex(match[2]), 2283 | g: parseIntFromHex(match[3]), 2284 | b: parseIntFromHex(match[4]), 2285 | format: named ? "name" : "hex8" 2286 | }; 2287 | } 2288 | if ((match = matchers.hex6.exec(color))) { 2289 | return { 2290 | r: parseIntFromHex(match[1]), 2291 | g: parseIntFromHex(match[2]), 2292 | b: parseIntFromHex(match[3]), 2293 | format: named ? "name" : "hex" 2294 | }; 2295 | } 2296 | if ((match = matchers.hex3.exec(color))) { 2297 | return { 2298 | r: parseIntFromHex(match[1] + '' + match[1]), 2299 | g: parseIntFromHex(match[2] + '' + match[2]), 2300 | b: parseIntFromHex(match[3] + '' + match[3]), 2301 | format: named ? "name" : "hex" 2302 | }; 2303 | } 2304 | 2305 | return false; 2306 | } 2307 | 2308 | window.tinycolor = tinycolor; 2309 | })(); 2310 | 2311 | $(function () { 2312 | if ($.fn.spectrum.load) { 2313 | $.fn.spectrum.processNativeColorInputs(); 2314 | } 2315 | }); 2316 | 2317 | }); 2318 | --------------------------------------------------------------------------------