├── .gitignore
├── .gitmodules
├── LICENSE
├── Procfile
├── README.md
├── TODO.md
├── css
├── gradient-example.png
└── gradient-scanner.css
├── index.html
├── js
├── color-stops.js
├── edit-gradient.js
├── example-image.js
├── flow-section.js
├── gradient-scanner.js
├── image-data-utils.js
├── image-loader.js
├── line-selector.js
├── line-utils.js
└── sensitivity.js
├── lib
└── colorpicker
│ ├── css
│ └── colorpicker.css
│ ├── images
│ ├── colorpicker_background.png
│ ├── colorpicker_hex.png
│ ├── colorpicker_hsb_b.png
│ ├── colorpicker_hsb_h.png
│ ├── colorpicker_hsb_s.png
│ ├── colorpicker_indic.gif
│ ├── colorpicker_overlay.png
│ ├── colorpicker_rgb_b.png
│ ├── colorpicker_rgb_g.png
│ ├── colorpicker_rgb_r.png
│ └── colorpicker_select.gif
│ └── js
│ └── colorpicker.js
├── package.json
├── server.js
└── test
├── color-stops.js
├── css-gradient-dropdown-menu.gif
├── index.html
├── line-utils.js
└── testData.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.tmproj
2 | npm-debug.log
3 | node_modules
4 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/jquery-tmpl"]
2 | path = lib/jquery-tmpl
3 | url = https://github.com/jquery/jquery-tmpl.git
4 | [submodule "lib/pixastic"]
5 | path = lib/pixastic
6 | url = https://github.com/kpdecker/pixastic.git
7 | [submodule "test/qunit"]
8 | path = test/qunit
9 | url = https://github.com/jquery/qunit.git
10 | [submodule "lib/user-image-cache"]
11 | path = lib/user-image-cache
12 | url = https://github.com/kpdecker/user-image-cache.git
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011, Kevin Decker, http://www.incaseofstairs.com/
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | - Neither the name of the Kevin Decker nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9 |
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: node server.js
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gradient-scanner
2 | Utility to generate CSS3 gradients from source raster files like mock images.
3 |
4 | ## Features
5 | - Loading for mockup files via URL or local file access (File API)
6 | - Scanning of a user selected section of text for possible gradients
7 | - User adjustment of the gradient estimations with live preview
8 | - Cross-browser gradient CSS generation
9 |
10 | ## Setup
11 | ### Checkout
12 | $ git clone git@github.com:kpdecker/gradient-scanner.git
13 | $ cd gradient-scanner
14 | $ git submodule update --init
15 |
16 | ### Development Requirements
17 | Development of the app must be done over a HTTP connection (or connection other than file://) due to the canvas [security model](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#security-with-canvas-elements).
18 |
19 | ### Node Server Setup
20 | In order to access files that are on URLs outside of the current server, we must proxy images due to the security model.
21 |
22 | $ npm install express
23 |
24 | ### Node Server Execution
25 |
26 | $ cd ./server
27 | $ node main.js
28 |
29 | ## Development Plan
30 | Development currently mid-phase 1.
31 |
32 | ### Phase 1: Lowest Common Denominator
33 | Implement support for only the features that are supported by both the latest WebKit and Mozilla implementations.
34 |
35 | This amounts to:
36 |
37 | - Linear gradients
38 | - Radial gradients with a single center point
39 | - Arbitrary number of color stops
40 |
41 | ### Phase 2: Single Feature Mode
42 | Support the features that are specific to a single browser, attempting to best match the display for the other browser.
43 |
44 | This will add support for radial gradients with multiple center points and repeating gradients.
45 |
46 | ### Phase 3: Automated Feature Detection
47 | Implement automatic feature detection in addition to the user-input based system.
48 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 | - CSS generation
3 | - Cross browser
4 | - Implement the help UI
5 | - Investigate the effects that blurring the image can have on the final result
6 | - Preview UI
7 | - Radial support
8 | - Repeating support
9 |
--------------------------------------------------------------------------------
/css/gradient-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/css/gradient-example.png
--------------------------------------------------------------------------------
/css/gradient-scanner.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | /* General Page UI */
6 | body {
7 | color: white;
8 | background-color: #83ADC8;
9 | font-size: 9pt;
10 | font-family: trebuchet ms,arial,helvetica,sans-serif;
11 | margin: 0;
12 | }
13 | .wrapper {
14 | margin: 0px auto;
15 | padding: 0;
16 | background-color: #131F27;
17 |
18 | width: 1000px;
19 | }
20 | .header {
21 | background-color: black;
22 | margin: 20px 0 0;
23 | }
24 |
25 | .wrapper,
26 | .wrapper > .header {
27 | -moz-border-radius: 10px 10px 0px 0px;
28 | -webkit-border-radius: 10px 10px 0px 0px;
29 | border-radius: 10px 10px 0px 0px;
30 | }
31 |
32 | .header > h1 {
33 | display: inline-block;
34 |
35 | margin: 0;
36 | padding: 5px 15px;
37 |
38 | font-size: 15pt;
39 | font-weight: normal;
40 | letter-spacing: -2px;
41 | }
42 |
43 | .content {
44 | margin: 5px 15px;
45 | overflow: hidden;
46 | }
47 |
48 | #errorMsg {
49 | color: red;
50 | }
51 |
52 | .footer {
53 | background-color: black;
54 | padding: 5px 10px;
55 | clear: both;
56 | overflow: auto;
57 | }
58 | .footer-link {
59 | text-align: center;
60 | }
61 | .header > a,
62 | .footer > div > a {
63 | color: #6598B8;
64 | }
65 |
66 | .help-text {
67 | padding: 0 0 15px 15px;
68 | }
69 |
70 | /* Flow */
71 | .flow-section {
72 | display: none;
73 | }
74 | .flow-section.active {
75 | display: block;
76 | }
77 | .flow-prev {
78 | visibility: hidden;
79 | float: left;
80 | }
81 | .flow-next {
82 | visibility: hidden;
83 | float: right;
84 | }
85 | .flow-prev.active,
86 | .flow-next.active {
87 | visibility: visible;
88 | }
89 |
90 | /* File Input Click Handler: See http://www.quirksmode.org/dom/inputfile.html */
91 | .no-local .localDependent {
92 | display: none;
93 | }
94 |
95 | label {
96 | display: block;
97 | }
98 | .info-marker {
99 | position: relative;
100 | display: inline-block;
101 | color: yellow;
102 | font-weight: bold;
103 | }
104 | .info-marker > .info {
105 | position: absolute;
106 | top: 100%;
107 | left: 0;
108 | min-width: 300px;
109 | z-index: 1000;
110 | display: none;
111 |
112 | background-color: #83ADC8;
113 | color: black;
114 | font-weight: normal;
115 |
116 | padding: 8px;
117 |
118 | -webkit-border-radius: 15px;
119 | -webkit-border-top-left-radius: 0;
120 | -moz-border-radius: 15px;
121 | -moz-border-radius-topleft: 0;
122 | border-radius: 15px;
123 | border-top-left-radius: 0;
124 | }
125 | .info-marker:hover > .info {
126 | display: block;
127 | }
128 |
129 | #pathToImage {
130 | width: 250px;
131 | margin-left: 0.6em;
132 | }
133 | .fileInput {
134 | position: relative;
135 | }
136 |
137 | .fileInput > input[type="button"] {
138 | position: absolute;
139 | top: 0px;
140 | left: 0px;
141 | z-index: 1;
142 | }
143 |
144 | .fileInput > input[type='file'] {
145 | position: relative;
146 | text-align: right;
147 | opacity: 0;
148 | z-index: 2;
149 | }
150 |
151 | /* Line Overlay Components */
152 | .image-display {
153 | position: relative;
154 | display: inline-block;
155 |
156 | margin: 10px 0;
157 | cursor: crosshair;
158 | }
159 | #lineOverlay {
160 | position: absolute;
161 | background: rgba(0, 0, 0, 0.1);
162 | height: 5px;
163 | border: dashed yellow 1px;
164 |
165 | /* linked to both the height and the border. Needs to be adjusted if those are changed */
166 | -moz-transform-origin: 2px 2px;
167 | -o-transform-origin: 2px 2px;
168 | -webkit-transform-origin: 2px 2px;
169 |
170 | /* Default to hidden */
171 | top: -1000px;
172 | left: -1000px;
173 | width: 0;
174 | }
175 |
176 | /* Focus Line Preview Components */
177 | .gradient-preview {
178 | display: inline-block;
179 | width: 400px;
180 | }
181 |
182 | .preview-component {
183 | display: block;
184 | width: 100%;
185 | height: 50px;
186 | }
187 |
188 | .display-gradient {
189 | float: left;
190 | margin-bottom: 15px;
191 | }
192 | .stop-editor {
193 | display: none;
194 | float: right;
195 | margin-bottom: 15px;
196 | }
197 | .stop-editor.active {
198 | display: block;
199 | }
200 |
201 | .delta-e-slider,
202 | .stop-position-slider {
203 | margin: 5px 0;
204 | width: 400px;
205 | }
206 |
207 | /* Stop List */
208 | .stops-list {
209 | width: 400px;
210 | height: 200px;
211 | overflow: auto;
212 |
213 | border: 1px solid #333;
214 | }
215 | .color-stop {
216 | padding: 5px 5px 0;
217 | line-height: 30px;
218 | }
219 | .color-stop.disabled {
220 | opacity: 0.5;
221 | text-decoration: line-through;
222 | }
223 | .color-stop.editing,
224 | .color-stop:hover {
225 | background-color: #0AE;
226 | cursor: pointer;
227 | }
228 | .color-preview {
229 | float: left;
230 | width: 25px;
231 | height: 25px;
232 | margin-right: 5px;
233 | }
234 | .stop-editor-field {
235 | display: block;
236 | }
237 |
238 | .generated-css {
239 | padding: 15px;
240 | overflow: auto;
241 | clear: both;
242 |
243 | border: 1px solid #333;
244 | }
245 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 | gradient-scanner
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
Select Image
24 |
25 | Open an image that contains a gradient and then draw a line over the major components of the gradient.
26 |
27 |
44 |
45 |
49 |
![]()
50 |
51 |
52 |
Adjust Color Sensitivity
53 |
54 | Use the slider to adjust the sensitivity of the color matcher.
55 |
56 |
57 |
58 |
![]()
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
Edit Gradient
69 |
70 | Manually adjust the color stops as necessary.
71 |
72 |
73 |
74 |
![]()
75 |
76 |
77 |
78 |
79 |
80 |
81 | Color:
82 |
83 |
87 |
91 |
92 |
93 |
94 |
95 |
96 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/js/color-stops.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | var ColorStops = {};
6 |
7 | (function() {
8 | ColorStops.JND = 2.39;
9 |
10 | function slopeChanged(oldDeriv, newDeriv) {
11 | if (!oldDeriv[0] && !oldDeriv[1] && !oldDeriv[2] && !oldDeriv[3]) {
12 | return false;
13 | }
14 |
15 | if (!newDeriv[0] && !newDeriv[1] && !newDeriv[2] && !newDeriv[3]) {
16 | return true;
17 | }
18 |
19 | return (newDeriv[0] && (oldDeriv[0]<0 !== newDeriv[0]<0))
20 | || (newDeriv[1] && (oldDeriv[1]<0 !== newDeriv[1]<0))
21 | || (newDeriv[2] && (oldDeriv[2]<0 !== newDeriv[2]<0))
22 | || (newDeriv[3] && (oldDeriv[3]<0 !== newDeriv[3]<0));
23 | }
24 | function updateDeriv(oldDeriv, newDeriv) {
25 | return [
26 | newDeriv[0] || oldDeriv[0],
27 | newDeriv[1] || oldDeriv[1],
28 | newDeriv[2] || oldDeriv[2],
29 | newDeriv[3] || oldDeriv[3]
30 | ];
31 | }
32 |
33 | function RGBtoXYZ(color) {
34 | // Algorithm from http://www.brucelindbloom.com/Eqn_RGB_to_XYZ.html
35 | var R = color[0] / 255,
36 | G = color[1] / 255,
37 | B = color[2] / 255;
38 |
39 | // Inverse Gamma Compounding, using Adobe RGB (1998) color
40 | // http://www.brucelindbloom.com/WorkingSpaceInfo.html#Specifications
41 | var r = Math.pow(R, 2.2),
42 | g = Math.pow(G, 2.2),
43 | b = Math.pow(B, 2.2);
44 |
45 | // Constants here are constructed from the M value for Adobe RGB (1998) defined
46 | // here: http://www.brucelindbloom.com/Eqn_RGB_XYZ_Matrix.html
47 | return [
48 | (0.5767309*r + 0.1855540*b + 0.1881852*g),
49 | (0.2973769*r + 0.6273491*b + 0.0752741*g),
50 | (0.0270343*r + 0.0706872*b + 0.9911085*g)
51 | ];
52 | }
53 | function XYZtoLAB(color) {
54 | // Algorithm from: http://www.brucelindbloom.com/Eqn_XYZ_to_Lab.html
55 | const X_r=0.95047, Y_r=1.00, Z_r=1.08883,
56 | EPSILON = 0.008856,
57 | KAPPA = 903.3;
58 |
59 | var x = color[0]/X_r,
60 | y = color[1]/X_r,
61 | z = color[2]/X_r;
62 |
63 | function f_(a) {
64 | if (a > EPSILON) {
65 | return Math.pow(a, 1/3);
66 | } else {
67 | return (KAPPA*a+16)/116;
68 | }
69 | }
70 | var f_x = f_(x),
71 | f_y = f_(y),
72 | f_z = f_(z);
73 |
74 | return [
75 | 116*f_y-16,
76 | 500*(f_x-f_z),
77 | 200*(f_y-f_z)
78 | ];
79 | }
80 | function deltaE(color1, color2) {
81 | color1 = XYZtoLAB(RGBtoXYZ(color1));
82 | color2 = XYZtoLAB(RGBtoXYZ(color2));
83 |
84 | return Math.sqrt(Math.pow(color2[0]-color1[0], 2)+Math.pow(color2[1]-color1[1], 2)+Math.pow(color2[2]-color1[2], 2));
85 | }
86 | function colorsApproxEqual(color1, color2, dELimit) {
87 | const ALPHA_EPSILON = 256*0.1;
88 |
89 | var dE = deltaE(color1, color2);
90 | return dE <= dELimit
91 | && Math.abs(color1[3]-color2[3]) <= ALPHA_EPSILON;
92 | }
93 | function stopsLinear(prev, cur, next, dELimit) {
94 | var deltaX = next.position-prev.position,
95 | slope = [
96 | (next.color[0]-prev.color[0])/deltaX,
97 | (next.color[1]-prev.color[1])/deltaX,
98 | (next.color[2]-prev.color[2])/deltaX,
99 | (next.color[3]-prev.color[3])/deltaX
100 | ],
101 | curDeltaX = cur.position-prev.position,
102 | curEstimate = [
103 | prev.color[0]+slope[0]*curDeltaX | 0,
104 | prev.color[1]+slope[1]*curDeltaX | 0,
105 | prev.color[2]+slope[2]*curDeltaX | 0,
106 | prev.color[3]+slope[3]*curDeltaX | 0
107 | ];
108 |
109 | return colorsApproxEqual(cur.color, curEstimate, dELimit);
110 | }
111 | function cullDuplicates(stops, dELimit) {
112 | const EDGE_DISTANCE = 0.01;
113 |
114 | dELimit = dELimit || ColorStops.JND;
115 | if (stops.length < 2) {
116 | return;
117 | }
118 |
119 | // Special case for the first to remove any repeating sections
120 | if (stops[1].position-stops[0].position > EDGE_DISTANCE
121 | && colorsApproxEqual(stops[0].color, stops[1].color, dELimit)) {
122 | stops.splice(0, 1);
123 | }
124 |
125 | var len = stops.length;
126 | for (var i = 1; i < len-1; i++) {
127 | var prev = stops[i-1],
128 | cur = stops[i],
129 | next = stops[i+1];
130 | // Check to see if the current element is on the same line as the previous and next. If so remove.
131 | if (stopsLinear(prev, cur, next, dELimit)) {
132 | stops.splice(i, 1);
133 | i--; len--;
134 | }
135 | }
136 |
137 | // Another special case for the last to remove any repeating sections
138 | if (len >= 2
139 | && stops[len-1].position-stops[len-2].position > EDGE_DISTANCE
140 | && colorsApproxEqual(stops[len-2].color, stops[len-1].color, dELimit)) {
141 | stops.splice(len-1, 1);
142 | }
143 | }
144 |
145 | ColorStops.extractColorStops = function(lineData, dELimit) {
146 | var len = lineData.length,
147 | last = [lineData[0], lineData[1], lineData[2], lineData[3]],
148 | deriv = [0, 0, 0, 0],
149 | secDeriv = [0, 0, 0, 0],
150 | ret = [];
151 |
152 | // We always have a color stop on the first pixel
153 | ret.push({
154 | position: 0.0,
155 | color: last
156 | });
157 |
158 | // Scan through the remaining pixels looking for any of our possible triggers, err on the side of false
159 | // positives (which may be culled in the next step)
160 | var totalDelta = 0,
161 | runCount = 0;
162 | for (var i = 4; i < len; i += 4) {
163 | var cur = [lineData[i], lineData[i+1], lineData[i+2], lineData[i+3]],
164 | newDeriv = [cur[0]-last[0], cur[1]-last[1], cur[2]-last[2], cur[3]-last[3]],
165 | newSecDeriv = [newDeriv[0]-deriv[0], newDeriv[1]-deriv[1], newDeriv[2]-deriv[2], newDeriv[3]-deriv[3]],
166 | delta = Math.abs(newDeriv[0]) + Math.abs(newDeriv[1]) + Math.abs(newDeriv[2]) + Math.abs(newDeriv[3]);
167 |
168 | // Check to see if this is a drastic change in derivative from the previous trend
169 | var prevAverage = totalDelta/runCount,
170 | deltaVariance = Math.abs(prevAverage-delta);
171 | if (runCount && prevAverage < 0.75*deltaVariance) {
172 | ret.push({
173 | position: i / len,
174 | color: last
175 | });
176 | ret.push({
177 | position: i / len,
178 | color: cur
179 | });
180 | totalDelta = 0;
181 | runCount = 0;
182 | } else {
183 | totalDelta += delta;
184 | runCount++;
185 | }
186 |
187 | // Check for a zero crossing in the first or second dervative
188 | if (slopeChanged(deriv, newDeriv) || slopeChanged(secDeriv, newSecDeriv)) {
189 | ret.push({
190 | position: i / len,
191 | color: cur
192 | });
193 | deriv = newDeriv;
194 | secDeriv = newSecDeriv;
195 | }
196 |
197 | last = cur;
198 | deriv = updateDeriv(deriv, newDeriv);
199 | secDeriv = updateDeriv(secDeriv, newSecDeriv);
200 | }
201 |
202 | ret.push({
203 | position: 1.0,
204 | color: last
205 | });
206 |
207 | // Clear out any stops that do not provide additional data (per configurable dELimit)
208 | cullDuplicates(ret, dELimit);
209 |
210 | // Make the position values nice and clean
211 | ret.forEach(function(stop) {
212 | stop.position = Math.floor(stop.position*1000)/1000;
213 | });
214 |
215 | return ret;
216 | };
217 |
218 | ColorStops.getColorValue = function(color) {
219 | color = color.map(Math.floor);
220 | if (color[3] === 255) {
221 | color.pop();
222 |
223 | var hex = color.map(function(color) {
224 | var ret = color.toString(16);
225 | if (ret.length < 2) {
226 | ret = "0" + ret;
227 | }
228 | return ret;
229 | }),
230 | reduce = hex.every(function(color) {
231 | return color[0] === color[1];
232 | });
233 |
234 | if (reduce) {
235 | hex = hex.map(function(color) {
236 | return color[0];
237 | });
238 | }
239 |
240 | return "#" + hex.join("");
241 | } else {
242 | return "rgba(" + color.join(", ") + ")";
243 | }
244 | };
245 |
246 | function generateContainer(dragStart, dragEnd) {
247 | if (LineUtils.getUnit(dragStart.x) === "%" || LineUtils.getUnit(dragStart.y) === "%"
248 | || LineUtils.getUnit(dragEnd.x) === "%" || LineUtils.getUnit(dragEnd.y) === "%") {
249 | return {x:0,y:0, width:"100%",height:"100%"};
250 | } else {
251 | return {
252 | x:0,
253 | y:0,
254 | width:Math.max(parseFloat(dragStart.x), parseFloat(dragEnd.x)) + (LineUtils.getUnit(dragStart.x) || LineUtils.getUnit(dragEnd.x) || 0),
255 | height:Math.max(parseFloat(dragStart.y), parseFloat(dragEnd.y)) + (LineUtils.getUnit(dragStart.y) || LineUtils.getUnit(dragEnd.y) || 0)
256 | };
257 | }
258 | }
259 |
260 | function newGenerator(prefix, type, dragStart, dragEnd, colorStops, container) {
261 | function formatUnit(value) {
262 | return parseFloat(value) + (LineUtils.getUnit(value) || "");
263 | }
264 |
265 | container = container || generateContainer(dragStart, dragEnd);
266 |
267 | var gradientPoints = LineUtils.gradientPoints(dragStart, dragEnd, container),
268 | stopCSS = colorStops.map(function(stop, index) {
269 | var prev = colorStops[index-1],
270 | next = colorStops[index+1],
271 | position = " " + Math.floor((gradientPoints.startOff + stop.position*gradientPoints.scale) * 1000)/10 + "%";
272 | if (prev && next) {
273 | // Check to see if these are equally spaced and should have their values removed
274 | var avgPos = prev.position + (next.position-prev.position)/2;
275 | if (avgPos === stop.position) {
276 | // We are on the same line, no need to display the position value
277 | position = "";
278 | }
279 | } else if ((!index && position === " 0%") || (!next && position === " 100%")) {
280 | position = "";
281 | }
282 | return ColorStops.getColorValue(stop.color) + position;
283 | }).join(", "),
284 |
285 | angle = 360-LineUtils.radsToDegrees(LineUtils.slopeInRads(dragStart, dragEnd)),
286 | point = !LineUtils.isOnEdge(dragStart, container) ? formatUnit(dragStart.x) + " " + formatUnit(dragStart.y) : "";
287 |
288 | // Generate the position component if necessary
289 | var unprefixedAngle = Math.abs(angle - 450) % 360,
290 | position = angle !== 270 ? angle + 'deg, ' : '',
291 | unprefixedPosition = unprefixedAngle !== 180 ? unprefixedAngle + 'deg, ' : '';
292 |
293 | if (type === "linear") {
294 | return (prefix ? '-' + prefix + '-' : '')
295 | + 'linear-gradient('
296 | + (prefix ? position : unprefixedPosition)
297 | + stopCSS
298 | + ')';
299 | } else if (type === "radial") {
300 | }
301 | }
302 | var cssGenerators = {
303 | webkitOld: function(type, dragStart, dragEnd, colorStops) {
304 | function formatUnit(value) {
305 | if (LineUtils.getUnit(value) === "%") {
306 | return parseFloat(value) + "%";
307 | } else {
308 | return parseFloat(value);
309 | }
310 | }
311 |
312 | var stopCSS = colorStops.map(function(stop) {
313 | if (!stop.position) {
314 | return "from(" + ColorStops.getColorValue(stop.color) + ")";
315 | } else if (stop.position === 1.0) {
316 | return "to(" + ColorStops.getColorValue(stop.color) + ")";
317 | } else {
318 | return "color-stop(" + stop.position + ", " + ColorStops.getColorValue(stop.color) + ")";
319 | }
320 | }).join(", ");
321 |
322 | var points = formatUnit(dragStart.x) + " " + formatUnit(dragStart.y) + ", " + formatUnit(dragEnd.x) + " " + formatUnit(dragEnd.y);
323 |
324 | return "-webkit-gradient(" + type + ", " + points + ", " + stopCSS + ")";
325 | },
326 | webkitNew: function(type, dragStart, dragEnd, colorStops, container) {
327 | return newGenerator("webkit", type, dragStart, dragEnd, colorStops, container);
328 | },
329 | mozilla: function(type, dragStart, dragEnd, colorStops, container) {
330 | return newGenerator("moz", type, dragStart, dragEnd, colorStops, container);
331 | },
332 | opera: function(type, dragStart, dragEnd, colorStops, container) {
333 | return newGenerator("o", type, dragStart, dragEnd, colorStops, container);
334 | },
335 | standard: function(type, dragStart, dragEnd, colorStops, container) {
336 | return newGenerator('', type, dragStart, dragEnd, colorStops, container);
337 | }
338 | };
339 |
340 | ColorStops.generateCSS = function(type, dragStart, dragEnd, colorStops, container) {
341 | colorStops = colorStops.filter(function(stop) {
342 | return !stop.disabled;
343 | });
344 |
345 | var ret = [];
346 | for (var name in cssGenerators) {
347 | if (cssGenerators.hasOwnProperty(name)) {
348 | ret.push(cssGenerators[name](type, dragStart, dragEnd, colorStops, container));
349 | }
350 | }
351 |
352 | return ret;
353 | };
354 |
355 | ColorStops.applyBackground = function(el, type, dragStart, dragEnd, colorStops) {
356 | var css = ColorStops.generateCSS(type, dragStart, dragEnd, colorStops),
357 | len = css.length;
358 | for (var i = 0; i < len; i++) {
359 | el.css("background-image", css[i]);
360 | }
361 | };
362 | })();
363 |
--------------------------------------------------------------------------------
/js/edit-gradient.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | /*global $, jQuery, ColorStops, GradientScanner */
6 |
7 | $(document).ready(function() {
8 | $.template(
9 | "colorStopTemplate",
10 | ""
11 | + "
"
12 | + "${colorCss} ${position}%"
13 | + "
");
14 |
15 | var colorStopsEl = $("#colorStops");
16 |
17 | var colorStops, editStop, deltaE = ColorStops.JND;
18 |
19 | function renderStop(stop, index) {
20 | var stopEl = $.tmpl("colorStopTemplate", {
21 | position: Math.floor(stop.position*1000)/10,
22 | colorCss: ColorStops.getColorValue(stop.color),
23 | disabled: stop.disabled ? "disabled" : ""
24 | });
25 | stopEl.data("stopIndex", index);
26 |
27 | return stopEl;
28 | }
29 | function outputGradient() {
30 | $(document).trigger(new jQuery.Event("gradientUpdated"));
31 | }
32 | function updateGradient() {
33 | colorStops = GradientScanner.colorStops;
34 |
35 | colorStopsEl.html("");
36 | $(".stop-editor").removeClass("active");
37 |
38 | colorStops.forEach(function(stop, index) {
39 | colorStopsEl.append(renderStop(stop, index));
40 | });
41 |
42 | outputGradient();
43 | }
44 |
45 | $(".stop-position-slider").slider({
46 | step: 0.001,
47 | min: 0,
48 | max: 1,
49 | slide: function(event, ui) {
50 | var growing = editStop.position < ui.value,
51 |
52 | editingEl = $(".color-stop.editing"),
53 | curIndex = editingEl.data("stopIndex"),
54 |
55 | toUpdate = colorStopsEl.children().filter(function() {
56 | var el = $(this),
57 | index = el.data("stopIndex"),
58 | stop = colorStops[index];
59 | if (growing) {
60 | return index > curIndex && stop.position < ui.value;
61 | } else {
62 | return index < curIndex && stop.position > ui.value;
63 | }
64 | });
65 |
66 | // Update the stop index for all of the elements that are between this location and
67 | // the destination
68 | toUpdate.each(function() {
69 | var el = $(this);
70 | el.data("stopIndex", el.data("stopIndex") + (growing?-1:1));
71 | });
72 |
73 | // Update the data model
74 | curIndex += (growing?1:-1)*toUpdate.length;
75 | editStop.position = ui.value;
76 | colorStops.sort(function(a, b) { return a.position-b.position; });
77 |
78 | // Rerender the element for the updated state
79 | var stopEl = renderStop(editStop, curIndex);
80 | stopEl.addClass("editing");
81 | if (!toUpdate.length) {
82 | editingEl.after(stopEl);
83 | } else if (growing) {
84 | toUpdate.last().after(stopEl);
85 | } else {
86 | toUpdate.first().before(stopEl);
87 | }
88 | editingEl.remove();
89 |
90 | // Make sure that the element is visible
91 | // TODO : Cache this height value
92 | colorStopsEl.scrollTop(stopEl.height()*(curIndex-1));
93 |
94 | // Update the rest of the app
95 | outputGradient();
96 | }
97 | });
98 |
99 | $("#disableCheck").click(function() {
100 | var el = $(this);
101 | editStop.disabled = this.checked;
102 | $(".color-stop.editing").toggleClass("disabled", editStop.disabled);
103 |
104 | // Update the rest of the app
105 | outputGradient();
106 | });
107 |
108 | $(".color-sel").ColorPicker({
109 | flat: true,
110 | onChange: function(hsb, hex, rgb) {
111 | editStop.color[0] = rgb.r;
112 | editStop.color[1] = rgb.g;
113 | editStop.color[2] = rgb.b;
114 |
115 | var editingEl = $(".color-stop.editing");
116 |
117 | // Rerender the element for the updated state
118 | var stopEl = renderStop(editStop, editingEl.data("stopIndex"));
119 | stopEl.addClass("editing");
120 | editingEl.replaceWith(stopEl);
121 |
122 | // Update the rest of the app
123 | outputGradient();
124 | }
125 | });
126 |
127 | colorStopsEl.delegate(".color-stop", "click", function(event) {
128 | var el = $(this);
129 |
130 | editStop = colorStops[el.data("stopIndex")];
131 |
132 | if (!el.hasClass("editing")) {
133 | $(".stop-position-slider").slider("option", "value", editStop.position);
134 | $("#disableCheck").attr("checked", editStop.disabled ? "checked" : "");
135 | $(".color-sel").ColorPickerSetColor({r:editStop.color[0], g:editStop.color[1], b:editStop.color[2]});
136 |
137 | $(".color-stop.editing").removeClass("editing");
138 | $(".stop-editor").addClass("active");
139 | } else {
140 | $(".stop-editor").removeClass("active");
141 | }
142 | el.toggleClass("editing");
143 |
144 | outputGradient();
145 | });
146 |
147 | $(document).bind("deltaEUpdated", updateGradient);
148 | });
149 |
--------------------------------------------------------------------------------
/js/example-image.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 | var img = document.createElement("img");
4 | $(img).load(function() {
5 | GradientScanner.loadImage(img, "example image");
6 | });
7 | img.src = "css/gradient-example.png";
8 |
9 | $(document).bind("imageLoaded", function lineSeed() {
10 | $(this).unbind("imageLoaded", lineSeed);
11 |
12 | var offset = $("#imageDisplay").offset();
13 |
14 | var mousedown = jQuery.Event("mousedown");
15 | mousedown.which = 1;
16 |
17 | mousedown.pageX = 300;
18 | mousedown.pageY = 1;
19 |
20 | mousedown.pageX += offset.left;
21 | mousedown.pageY += offset.top;
22 |
23 | var mousemove = jQuery.Event("mousemove");
24 | mousemove.which = 1;
25 |
26 | mousemove.pageX = 300;
27 | mousemove.pageY = 89;
28 |
29 | mousemove.pageX += offset.left;
30 | mousemove.pageY += offset.top;
31 |
32 | $("#imageDisplay")
33 | .trigger(mousedown)
34 | .trigger(mousemove)
35 | .trigger(jQuery.Event("mouseup"));
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/js/flow-section.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | (function ($) {
6 | var FlowSection = {
7 | init: function(options) {
8 | var flowSection = this;
9 |
10 | this.first().addClass("active");
11 | $(".flow-prev").click(function() {
12 | var prev = FlowSection.getPrev.call(flowSection);
13 | if (prev) {
14 | flowSection.filter(".active").removeClass("active");
15 |
16 | prev.el.addClass("active");
17 |
18 | // Update the button states
19 | $(".flow-next").addClass("active");
20 | }
21 |
22 | // Update the prev button state
23 | if (!prev || prev.index <= 0) {
24 | $(".flow-prev").removeClass("active");
25 | } else {
26 | $(".flow-prev").addClass("active");
27 | }
28 | });
29 | $(".flow-next").addClass("active").click(function() {
30 | var next = FlowSection.getNext.call(flowSection);
31 |
32 | if (next) {
33 | flowSection.filter(".active").removeClass("active");
34 | next.el.addClass("active");
35 |
36 | // Update the button states
37 | $(".flow-prev").addClass("active");
38 | }
39 |
40 | // Update the next button state
41 | if (!next || next.index === flowSection.length-1) {
42 | $(".flow-next").removeClass("active");
43 | } else {
44 | $(".flow-next").addClass("active");
45 | }
46 | });
47 |
48 | return this;
49 | },
50 |
51 | getPrev: function() {
52 | var last,
53 | ret;
54 | this.each(function(index) {
55 | var el = $(this);
56 | if (el.hasClass("active")) {
57 | ret = {
58 | el: last,
59 | index: index-1
60 | };
61 |
62 | return false;
63 | }
64 | last = el;
65 | });
66 |
67 | return ret;
68 | },
69 | getNext: function() {
70 | var foundActive,
71 | ret;
72 | this.each(function(index) {
73 | var el = $(this);
74 | if (el.hasClass("active")) {
75 | foundActive = true;
76 | } else if (foundActive) {
77 | ret = {
78 | el: el,
79 | index: index
80 | };
81 | return false;
82 | }
83 | });
84 | return ret;
85 | },
86 |
87 | enablePrev: function() {
88 | // Enable the prev button if possible
89 | var prev = FlowSection.getPrev.call(this);
90 | if (prev && prev.index > 0) {
91 | $(".flow-prev").addClass("active");
92 | }
93 | },
94 | enableNext: function() {
95 | // Enable the next button if possible
96 | var prev = FlowSection.getNext.call(this);
97 | if (prev && prev.index < this.length-1) {
98 | $(".flow-next").addClass("active");
99 | }
100 | },
101 | disable: function(next) {
102 | // Disable the next/prev button
103 | $(next ? ".flow-next" : ".flow-prev").removeClass("active");
104 | }
105 | }
106 |
107 | $.fn.flowSection = function(method, options) {
108 | if (method) {
109 | return FlowSection[method].call(this, options);
110 | } else {
111 | return FlowSection.init.call(this, options);
112 | }
113 | };
114 | })(jQuery);
--------------------------------------------------------------------------------
/js/gradient-scanner.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | /*global $, ColorStops, LineUtils */
6 |
7 | // Global namespace
8 | var GradientScanner = {};
9 |
10 | $(document).ready(function() {
11 | var linePreview = $(".line-preview");
12 |
13 | GradientScanner.resetLinePreview = function() {
14 | // Provide a 1px transparent image for the preview images
15 | linePreview.attr("src", "data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==");
16 | };
17 | GradientScanner.resetLinePreview();
18 |
19 | var line, relLine, gradientType = "linear";
20 |
21 | $(document).bind("gradientUpdated", function(event) {
22 | line = GradientScanner.line;
23 |
24 | // Clip the coords to their containing boxes.
25 | var relContaining = LineUtils.containingRect(line.start, line.end);
26 | relLine = {
27 | start: LineUtils.relativeCoords(line.start, relContaining),
28 | end: LineUtils.relativeCoords(line.end, relContaining)
29 | };
30 |
31 | var css = ColorStops.generateCSS(gradientType, relLine.start, relLine.end, GradientScanner.colorStops);
32 | $(".generated-css").text("background-image: " + css.join(";\nbackground-image: "));
33 | });
34 |
35 | $(document).bind("imageLoaded", function(event) {
36 | $(".generated-css").text("");
37 |
38 | $(".flow-section").flowSection("disable", true);
39 | });
40 |
41 | $(".flow-section").flowSection();
42 | });
43 |
--------------------------------------------------------------------------------
/js/image-data-utils.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | var ImageDataUtils;
6 |
7 | (function() {
8 | const DEFAULT_SNAP_SCAN_RANGE = 10;
9 |
10 | function sign(a, b) {
11 | return (a > b) ? 1 : (a < b) ? -1 : 0;
12 | }
13 |
14 | function getPixelDelta(coord, endOffset, imageData) {
15 | var testOffset = ImageDataUtils.getOffset(coord, imageData),
16 | data = imageData.data;
17 |
18 | return Math.abs(data[endOffset]-data[testOffset])
19 | + Math.abs(data[endOffset+1]-data[testOffset+1])
20 | + Math.abs(data[endOffset+2]-data[testOffset+2])
21 | + Math.abs(data[endOffset+3]-data[testOffset+3]);
22 | }
23 |
24 | ImageDataUtils = {
25 | getCoords: function(offset, imageData) {
26 | var binWidth = imageData.width * 4;
27 | return {
28 | x: offset % binWidth,
29 | y: offset / binWidth | 0
30 | };
31 | },
32 | getOffset: function(coords, imageData) {
33 | return (coords.x + coords.y*imageData.width)*4;
34 | },
35 | getPixel: function(context, coord) {
36 | return context.getImageData(coord.x, coord.y, 1, 1).data;
37 | },
38 | getWindow: function getSnapToWindow(context, coord, windowSize) {
39 | var firstPixel = {x: Math.max(coord.x-windowSize, 0), y: Math.max(coord.y-windowSize, 0)};
40 | return {
41 | firstPixel: firstPixel,
42 | focusPixel: {x: coord.x-firstPixel.x, y: coord.y-firstPixel.y},
43 | imageData: context.getImageData(
44 | firstPixel.x,
45 | firstPixel.y,
46 | Math.min(2*windowSize, context.canvas.width-firstPixel.x),
47 | Math.min(2*windowSize, context.canvas.height-firstPixel.y))
48 | };
49 | },
50 | getLinePixels: function(context, coordStart, coordEnd) {
51 | // Extract the rectangle that contains our data
52 | var containingRect = LineUtils.containingRect(coordStart, coordEnd),
53 | image = context.getImageData(
54 | containingRect.x,
55 | containingRect.y,
56 | Math.min(containingRect.width+1, context.canvas.width-containingRect.x), // Need to limit to image coords for Mozilla
57 | Math.min(containingRect.height+1, context.canvas.height-containingRect.y)),
58 | imageData = image.data;
59 |
60 | // Determine the properties of our line
61 | var len = LineUtils.distance(coordStart, coordEnd)|0;
62 | if (!len) {
63 | return;
64 | }
65 |
66 | // Create our destination image
67 | var line = context.createImageData(len, 1),
68 | lineData = line.data;
69 |
70 | LineUtils.walkLine(coordStart, coordEnd, function(t, coords) {
71 | var firstCoord = {x: Math.floor(coords.x), y: Math.floor(coords.y)},
72 | nextCoord = {x: Math.floor(coords.x), y: Math.ceil(coords.y)};
73 |
74 | var firstOffset = ImageDataUtils.getOffset(firstCoord, image),
75 | nextOffset = ImageDataUtils.getOffset(nextCoord, image);
76 |
77 | var xPercentage = 1-(coords.x-firstCoord.x),
78 | yPercentage = 1-(coords.y-firstCoord.y);
79 | function calcComponent(offset1, offset2) {
80 | return imageData[offset1 ]*xPercentage*yPercentage
81 | + (imageData[offset1+4]||0)*(1-xPercentage)*yPercentage
82 | + (imageData[offset2 ]||0)*xPercentage*(1-yPercentage)
83 | + (imageData[offset2+4]||0)*(1-xPercentage)*(1-yPercentage);
84 | }
85 |
86 | var lineOffset = t*4;
87 | lineData[lineOffset] = calcComponent(firstOffset, nextOffset);
88 | lineData[lineOffset+1] = calcComponent(firstOffset+1, nextOffset+1);
89 | lineData[lineOffset+2] = calcComponent(firstOffset+2, nextOffset+2);
90 | lineData[lineOffset+3] = calcComponent(firstOffset+3, nextOffset+3);
91 | });
92 |
93 | return line;
94 | },
95 | getInitialSnapToTarget: function(edgeContext, coordStart, scanRange) {
96 | // Load the window we are concerned with right now
97 | var snapWindow = ImageDataUtils.getWindow(edgeContext, coordStart, scanRange || DEFAULT_SNAP_SCAN_RANGE),
98 | focusPixel = snapWindow.focusPixel,
99 | imageData = snapWindow.imageData,
100 | data = snapWindow.imageData.data,
101 | endOffset = ImageDataUtils.getOffset(focusPixel, snapWindow.imageData);
102 |
103 | // Setup our data tracker
104 | var maximum = focusPixel;
105 | maximum.distance = Infinity;
106 | maximum.delta = 0;
107 |
108 | function checkPixel(coord) {
109 | var delta = getPixelDelta(coord, endOffset, imageData);
110 |
111 | if (delta > 128) {
112 | coord.distance = Math.max(Math.abs(coord.x-focusPixel.x), Math.abs(coord.y-focusPixel.y));
113 | coord.delta = delta;
114 | if (coord.distance && (maximum.distance-coord.distance || coord.delta-maximum.delta) > 0) {
115 | maximum = coord;
116 | }
117 | }
118 | }
119 |
120 | // Scan the window for any pixels that are dratically different
121 | var y = imageData.height;
122 | while (y--) {
123 | checkPixel({x: focusPixel.x, y: y});
124 | }
125 |
126 | var x = imageData.width;
127 | while (x--) {
128 | checkPixel({x: x, y: focusPixel.y});
129 | }
130 |
131 | // Move the return one pixel further along the path
132 | maximum.x = maximum.x+-1*sign(maximum.x, focusPixel.x)+snapWindow.firstPixel.x;
133 | maximum.y = maximum.y+-1*sign(maximum.y, focusPixel.y)+snapWindow.firstPixel.y;
134 |
135 | return maximum;
136 | },
137 | getSnapToTarget: function(edgeContext, coordStart, coordEnd, scanRange) {
138 | // Load the window we are concerned with right now
139 | var snapWindow = ImageDataUtils.getWindow(edgeContext, coordEnd, scanRange || DEFAULT_SNAP_SCAN_RANGE),
140 | imageData = snapWindow.imageData,
141 | data = imageData.data,
142 | endOffset = ImageDataUtils.getOffset(snapWindow.focusPixel, imageData),
143 |
144 | angle = LineUtils.slopeInRads(coordStart, coordEnd),
145 | shiftedStart = {x: coordStart.x-snapWindow.firstPixel.x, y: coordStart.y-snapWindow.firstPixel.y};
146 |
147 | var maximum = snapWindow.focusPixel;
148 | maximum.slopeDelta = 0;
149 | maximum.distance = LineUtils.distance(shiftedStart, maximum);
150 | maximum.delta = 0;
151 |
152 | // Scan the window for any pixels that are dratically different
153 | var y = imageData.height;
154 | while (y--) {
155 | var x = imageData.width;
156 | while (x--) {
157 | var coord = {x: x, y: y},
158 | delta = getPixelDelta(coord, endOffset, imageData);
159 |
160 | if (delta > 128) {
161 | coord.distance = LineUtils.distance(shiftedStart, coord);
162 | coord.delta = delta;
163 | coord.slopeDelta = Math.abs(LineUtils.slopeInRads(shiftedStart, coord)-angle);
164 | if ((maximum.slopeDelta-coord.slopeDelta || coord.delta-maximum.delta || coord.distance-maximum.distance) > 0) {
165 | maximum = coord;
166 | }
167 | }
168 | }
169 | }
170 |
171 | maximum.x += snapWindow.firstPixel.x;
172 | maximum.y += snapWindow.firstPixel.y;
173 |
174 | return maximum;
175 | },
176 | getEdgeContext: function(el) {
177 | var loadOptions = {};
178 | Pixastic.process(ImageDataUtils.cloneCanvas(el), "edges", loadOptions);
179 | return loadOptions.resultCanvas.getContext("2d");
180 | },
181 | cloneCanvas: function(el) {
182 | var clone = document.createElement("canvas");
183 | clone.width = el.naturalWidth || el.width;
184 | clone.height = el.naturalHeight || el.height;
185 | clone.getContext("2d").drawImage(el, 0, 0);
186 | return clone;
187 | },
188 | createCanvasFromImageData: function(imageData) {
189 | var canvas = document.createElement("canvas");
190 | canvas.setAttribute("width", imageData.width);
191 | canvas.setAttribute("height", imageData.height);
192 |
193 | var context = canvas.getContext("2d");
194 | context.putImageData(imageData, 0, 0);
195 |
196 | return canvas;
197 | },
198 | };
199 | })();
200 |
--------------------------------------------------------------------------------
/js/image-loader.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | /*global $,jQuery,LineUtils,ImageDataUtils,UserImageCache,GradientScanner */
6 | $(document).ready(function() {
7 | var canvas = $("#imageDisplay"),
8 | context = canvas[0].getContext("2d"),
9 | pathToImage = $("#pathToImage");
10 |
11 | function errorHandler(code) {
12 | $("#errorMsg").html("*** " + "Failed to load image. Error code: " + JSON.stringify(code));
13 | }
14 |
15 | // Generic loader used by the UI and the seeder
16 | GradientScanner.loadImage = function(image, name) {
17 | canvas.attr({
18 | width: image.width,
19 | height: image.height
20 | });
21 |
22 | context.drawImage(image, 0, 0);
23 |
24 | pathToImage.val(name);
25 |
26 | GradientScanner.edgeContext = ImageDataUtils.getEdgeContext(context.canvas);
27 | $(document).trigger(new jQuery.Event("imageLoaded"));
28 | };
29 |
30 | // User Image Cache loader
31 | var loader = new Image();
32 | $(loader).load(function() {
33 | GradientScanner.loadImage(this, UserImageCache.getDisplayName());
34 | })
35 | .error(function() { errorHandler(); });
36 | UserImageCache.setImageEl(loader);
37 | UserImageCache.setRemoteProxy("http://" + location.host + "/proxy?href=");
38 |
39 | // User Input Setup
40 | pathToImage.change(function(event) {
41 | $("#errorMsg").html("");
42 | UserImageCache.load(pathToImage.val(), errorHandler);
43 | });
44 |
45 | // File API
46 | if (UserImageCache.isLocalSupported()) {
47 | $("#localImage").bind("change", function(event) {
48 | var file = this.files[0];
49 |
50 | $("#errorMsg").html("");
51 | UserImageCache.load(file, errorHandler);
52 | });
53 |
54 | // Unhide the browse button if the current browser supports local files
55 | $(".no-local").removeClass("no-local");
56 | }
57 | });
58 |
--------------------------------------------------------------------------------
/js/line-selector.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | /*global $,jQuery,LineUtils,ImageDataUtils,GradientScanner */
6 | $(document).ready(function() {
7 | const SNAP_TO_PX = 10;
8 |
9 | var canvas = $("#imageDisplay"),
10 | context = canvas[0].getContext("2d"),
11 |
12 | linePreview = $(".line-preview");
13 |
14 | var dragging, dragStart, dragEnd, imageData;
15 |
16 | function resetLineOverlay() {
17 | $("#lineOverlay").css({
18 | width: "",
19 | top: "",
20 | left: ""
21 | });
22 | }
23 |
24 | canvas.parent().mousedown(function(event) {
25 | // Only activate this if the event is due to a left click
26 | if (event.which !== 1) {
27 | return;
28 | }
29 |
30 | dragging = true;
31 | var canvasOffset = canvas.offset();
32 | dragStart = {x: event.pageX-canvasOffset.left, y: event.pageY-canvasOffset.top};
33 |
34 | var edgeSnaps = ImageDataUtils.getInitialSnapToTarget(GradientScanner.edgeContext, dragStart);
35 | dragStart = edgeSnaps || dragStart;
36 | // Init the line overlay
37 | $("#lineOverlay").css("width", "0px")
38 | .css("left", dragStart.x+"px")
39 | .css("top", dragStart.y+"px");
40 |
41 | // Reset any existing data
42 | dragEnd = undefined;
43 | imageData = undefined;
44 |
45 | GradientScanner.line = {};
46 | GradientScanner.resetLinePreview();
47 | $(".flow-section").flowSection("disable", true);
48 |
49 | event.preventDefault();
50 | }).mousemove(function(event) {
51 | if (dragging) {
52 | var canvasOffset = canvas.offset();
53 | dragEnd = {x: event.pageX-canvasOffset.left, y: event.pageY-canvasOffset.top};
54 |
55 | // Check for snapto
56 | if (Math.abs(dragEnd.y-dragStart.y) < SNAP_TO_PX) {
57 | dragEnd.y = dragStart.y;
58 | } else if (Math.abs(dragEnd.x-dragStart.x) < SNAP_TO_PX) {
59 | dragEnd.x = dragStart.x;
60 | }
61 |
62 | // Check for edge snapto
63 | var edgeSnap = ImageDataUtils.getSnapToTarget(GradientScanner.edgeContext, dragStart, dragEnd);
64 | dragEnd = edgeSnap || dragEnd;
65 |
66 | // Collect the line data while the user is dragging
67 | imageData = ImageDataUtils.getLinePixels(context, dragStart, dragEnd);
68 | if (!imageData) {
69 | GradientScanner.resetLinePreview();
70 | return;
71 | }
72 |
73 | // Display the line preview
74 | var stretcher = ImageDataUtils.createCanvasFromImageData(imageData);
75 | linePreview.attr("src", stretcher.toDataURL());
76 |
77 | // Move the line indicator
78 | var distance = LineUtils.distance(dragStart, dragEnd),
79 | rotate = "rotate(" + LineUtils.slopeInRads(dragStart, dragEnd) + "rad)";
80 | $("#lineOverlay").css("width", distance)
81 | .css("-moz-transform", rotate)
82 | .css("-o-transform", rotate)
83 | .css("-webkit-transform", rotate);
84 | }
85 | }).mouseup(function(event) {
86 | dragging = false;
87 |
88 | // If they didn't move the mouse then there isn't much we can do
89 | if (!dragEnd || !LineUtils.distance(dragStart, dragEnd)) {
90 | resetLineOverlay();
91 | return;
92 | }
93 |
94 | GradientScanner.line = {
95 | start: dragStart,
96 | end: dragEnd,
97 | imageData: imageData
98 | };
99 |
100 | $(document).trigger(new jQuery.Event("lineUpdated"));
101 | $(".flow-section").flowSection("enableNext");
102 | });
103 |
104 | $(document).bind("imageLoaded", function(event) {
105 | GradientScanner.line = {};
106 | GradientScanner.resetLinePreview();
107 |
108 | resetLineOverlay();
109 | });
110 | });
111 |
--------------------------------------------------------------------------------
/js/line-utils.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | var LineUtils;
6 |
7 | (function() {
8 |
9 | function getUnit(value) {
10 | if (typeof value === "number") {
11 | return value ? "px" : 0;
12 | }
13 |
14 | if (parseFloat(value) === 0) {
15 | return 0;
16 | }
17 | return (/\d+(?:\.\d+)?(.*)/).exec(value)[1] || "px";
18 | }
19 | function checkUnit(unit, value) {
20 | var newUnit = getUnit(value);
21 | if (unit === 0 || unit === newUnit) {
22 | return newUnit;
23 | } else if (newUnit === 0) {
24 | return unit;
25 | } else {
26 | return false;
27 | }
28 | }
29 | function combineUnit(magnitude, unit) {
30 | if (!magnitude || unit === "px") {
31 | return magnitude;
32 | } else {
33 | return magnitude + unit;
34 | }
35 | }
36 |
37 | LineUtils = {
38 | /**
39 | * Retrieves the unit from a given value (defaulting to pixels if undefined)
40 | */
41 | getUnit: getUnit,
42 |
43 | /**
44 | * Determines the minimum rectangle that will cover the line segment with the given endpoints and optionally width
45 | */
46 | containingRect: function(start, end, width) {
47 | var unit = 0;
48 | unit = checkUnit(unit, start.x);
49 | unit = checkUnit(unit, start.y);
50 | unit = checkUnit(unit, end.x);
51 | unit = checkUnit(unit, end.y);
52 | if (width) {
53 | unit = checkUnit(unit, width);
54 | }
55 | if (unit === false) {
56 | return NaN;
57 | }
58 |
59 | // Remove any units, we'll restore them later
60 | start = {x: parseFloat(start.x), y: parseFloat(start.y)};
61 | end = {x: parseFloat(end.x), y: parseFloat(end.y)};
62 | width = parseFloat(width);
63 |
64 | var topLeft = {x: Math.min(start.x, end.x), y: Math.min(start.y, end.y)},
65 | bottomRight = {x: Math.max(start.x, end.x), y: Math.max(start.y, end.y)};
66 |
67 | if (width) {
68 | var angle = LineUtils.slopeInRads(start, end),
69 | horz = Math.floor(width*Math.abs(Math.sin(angle))/2),
70 | vert = Math.floor(width*Math.abs(Math.cos(angle))/2);
71 | topLeft.x -= horz;
72 | topLeft.y -= vert;
73 | bottomRight.x += horz;
74 | bottomRight.y += vert;
75 | }
76 |
77 | return {
78 | x: combineUnit(topLeft.x, unit),
79 | y: combineUnit(topLeft.y, unit),
80 | width: combineUnit(Math.max(bottomRight.x-topLeft.x, 1), unit),
81 | height: combineUnit(Math.max(bottomRight.y-topLeft.y, 1), unit)
82 | };
83 | },
84 |
85 | /**
86 | * Returns the relative position for a given coord, relative to a specific origin.
87 | */
88 | relativeCoords: function(coord, origin) {
89 | var unit = 0;
90 | unit = checkUnit(unit, coord.x);
91 | unit = checkUnit(unit, coord.y);
92 | unit = checkUnit(unit, origin.x);
93 | unit = checkUnit(unit, origin.y);
94 | if (unit === false) {
95 | return NaN;
96 | }
97 |
98 | return {
99 | x: combineUnit(parseFloat(coord.x)-parseFloat(origin.x), unit),
100 | y: combineUnit(parseFloat(coord.y)-parseFloat(origin.y), unit)
101 | };
102 | },
103 |
104 | /**
105 | * Determines the distance between two points. The units of this value are the same as those input. For the percentage case,
106 | * distance is distance covered within the plane of a square with demensions of 100% x 100%. This can not be mapped back
107 | * to a fixed unit space directly.
108 | */
109 | distance: function(start, end) {
110 | // Check that the units match
111 | var unit = 0;
112 | unit = checkUnit(unit, start.x);
113 | unit = checkUnit(unit, start.y);
114 | unit = checkUnit(unit, end.x);
115 | unit = checkUnit(unit, end.y);
116 | if (unit === false) {
117 | return NaN;
118 | }
119 |
120 | return Math.sqrt(Math.pow(parseFloat(end.y)-parseFloat(start.y),2) + Math.pow(parseFloat(end.x)-parseFloat(start.x), 2));
121 | },
122 |
123 | /**
124 | * Calculates the percentage of the ray length that a line segment covers, when the ray is constrained by the given container.
125 | * @return percentage value [0, 1]
126 | */
127 | percentageOfRay: function(start, end, container) {
128 | // Check that the units match
129 | var unit = 0;
130 | unit = checkUnit(unit, start.x);
131 | unit = checkUnit(unit, start.y);
132 | unit = checkUnit(unit, end.x);
133 | unit = checkUnit(unit, end.y);
134 | unit = checkUnit(unit, container.x);
135 | unit = checkUnit(unit, container.y);
136 | unit = checkUnit(unit, container.width);
137 | unit = checkUnit(unit, container.height);
138 | if (unit === false) {
139 | return NaN;
140 | }
141 |
142 | var intercepts = LineUtils.lineIntercepts(start, end, container, true);
143 | if (!intercepts.length) {
144 | return 0;
145 | }
146 |
147 | var rayDistance = LineUtils.distance(start, intercepts[intercepts.length-1]),
148 | segmentDistance = LineUtils.distance(start, end);
149 | return segmentDistance/rayDistance;
150 | },
151 |
152 | isOnEdge: function(point, container) {
153 | // Check that the units match
154 | var unit = 0;
155 | unit = checkUnit(unit, point.x);
156 | unit = checkUnit(unit, point.y);
157 | unit = checkUnit(unit, container.x);
158 | unit = checkUnit(unit, container.y);
159 | unit = checkUnit(unit, container.width);
160 | unit = checkUnit(unit, container.height);
161 | if (unit === false) {
162 | return NaN;
163 | }
164 |
165 | point = {x: parseFloat(point.x), y: parseFloat(point.y)};
166 | container = {
167 | x: parseFloat(container.x), y: parseFloat(container.y),
168 | width: parseFloat(container.width), height: parseFloat(container.height)
169 | };
170 |
171 | if (point.x === container.x || point.x === container.x+container.width) {
172 | return container.y <= point.y && point.y <= container.y + container.height;
173 | } else if (point.y === container.y || point.y === container.y+container.height) {
174 | return container.x <= point.x && point.x <= container.x + container.width;
175 | } else {
176 | return false;
177 | }
178 | },
179 |
180 | /**
181 | * Calculates the gradient start and end points as defined by http://dev.w3.org/csswg/css3-images/#linear-gradients
182 | * as well as the color stop adjustment values to fix the color stops to an centroid angle gradient.
183 | */
184 | gradientPoints: function(start, end, container) {
185 | // Check that the units match
186 | var unit = 0;
187 | unit = checkUnit(unit, start.x);
188 | unit = checkUnit(unit, start.y);
189 | unit = checkUnit(unit, end.x);
190 | unit = checkUnit(unit, end.y);
191 | unit = checkUnit(unit, container.x);
192 | unit = checkUnit(unit, container.y);
193 | unit = checkUnit(unit, container.width);
194 | unit = checkUnit(unit, container.height);
195 | if (unit === false) {
196 | return NaN;
197 | }
198 |
199 | start = {x: parseFloat(start.x), y: parseFloat(start.y)};
200 | end = {x: parseFloat(end.x), y: parseFloat(end.y)};
201 | container = {
202 | x: parseFloat(container.x), y: parseFloat(container.y),
203 | width: parseFloat(container.width), height: parseFloat(container.height)
204 | };
205 |
206 | var rise = end.y-start.y,
207 | run = end.x-start.x,
208 | slope = rise/run,
209 | perpendicularSlope = -1/slope,
210 |
211 | top = container.y,
212 | bottom = container.y + container.height,
213 | left = container.x,
214 | right = container.x + container.width,
215 | center = {x: container.x + container.width/2, y: container.y + container.height/2},
216 |
217 | startCorner, endCorner, passedCornerIntercept, totalDist;
218 |
219 | function findLineIntersect(point1, slope1, point2, slope2) {
220 | var retX = (point1.y - point2.y + slope2*point2.x - slope1*point1.x)/(slope2 - slope1);
221 | return {
222 | x: retX,
223 | y: slope1*(retX - point1.x) + point1.y
224 | };
225 | }
226 |
227 | // Special case the vertical and horizontal as they or their perpendicular have the lil divide by zero concern
228 | if (!run) {
229 | startCorner = {x: center.x, y: end.ystart.y?bottom:top};
231 | passedCornerIntercept = {x: start.x, y: startCorner.y};
232 | } else if (!rise) {
233 | // Corners are the vertical intercepts
234 | startCorner = {x: end.xstart.x?right:left, y: center.y};
236 | passedCornerIntercept = {x: startCorner.x, y: start.y};
237 | } else if (slope < 0) {
238 | // The corners are (0,h) and (w,0)
239 | var topRight = findLineIntersect({x:right, y:top}, perpendicularSlope, center, slope),
240 | bottomLeft = findLineIntersect({x:left, y:bottom}, perpendicularSlope, center, slope);
241 |
242 | startCorner = run < 0 ? topRight : bottomLeft;
243 | endCorner = run < 0 ? bottomLeft : topRight;
244 | passedCornerIntercept = findLineIntersect(start, slope, startCorner, perpendicularSlope);
245 | } else {
246 | // The corners are (0,0) and bottom (w,h)
247 | var topLeft = findLineIntersect({x:left, y:top}, perpendicularSlope, center, slope),
248 | bottomRight = findLineIntersect({x:right, y:bottom}, perpendicularSlope, center, slope);
249 |
250 | startCorner = run < 0 ? bottomRight : topLeft;
251 | endCorner = run < 0 ? topLeft : bottomRight;
252 | passedCornerIntercept = findLineIntersect(start, slope, startCorner, perpendicularSlope);
253 | }
254 |
255 | totalDist = LineUtils.distance(startCorner, endCorner);
256 | return {
257 | start: {x: combineUnit(startCorner.x, unit), y: combineUnit(startCorner.y, unit)},
258 | startOff: LineUtils.distance(passedCornerIntercept, start)/totalDist,
259 | end: {x: combineUnit(endCorner.x, unit), y: combineUnit(endCorner.y, unit)},
260 | scale: LineUtils.distance(start, end)/totalDist
261 | };
262 | },
263 |
264 | /**
265 | * Determines the two points at which the line connecting start and end intersects with the container
266 | * boundaries.
267 | */
268 | lineIntercepts: function(start, end, container, ray) {
269 | // Check that the units match
270 | var unit = 0;
271 | unit = checkUnit(unit, start.x);
272 | unit = checkUnit(unit, start.y);
273 | unit = checkUnit(unit, end.x);
274 | unit = checkUnit(unit, end.y);
275 | unit = checkUnit(unit, container.x);
276 | unit = checkUnit(unit, container.y);
277 | unit = checkUnit(unit, container.width);
278 | unit = checkUnit(unit, container.height);
279 | if (unit === false) {
280 | return NaN;
281 | }
282 |
283 | start = {x: parseFloat(start.x), y: parseFloat(start.y)};
284 | end = {x: parseFloat(end.x), y: parseFloat(end.y)};
285 | container = {
286 | x: parseFloat(container.x), y: parseFloat(container.y),
287 | width: parseFloat(container.width), height: parseFloat(container.height)
288 | };
289 |
290 | var rise = end.y-start.y,
291 | run = end.x-start.x,
292 | slope = rise/run,
293 | b = (start.y*end.x - end.y*start.x)/run,
294 |
295 | intercepts = [],
296 |
297 | x, y,
298 | len, check;
299 |
300 | // Special case the vertical
301 | if (!run) {
302 | if (container.x <= end.x && end.x <= container.x+container.width) {
303 | intercepts = [{x: end.x, y: container.y+(end.ystart.y?container.height:0)}];
304 |
305 | len = intercepts.length;
306 | while (len--) {
307 | check = intercepts[len];
308 | if (ray && (rise>0 ? check.ystart.y)) {
309 | intercepts.splice(len, 1);
310 | }
311 |
312 | check.x = combineUnit(check.x, unit);
313 | check.y = combineUnit(check.y, unit);
314 | }
315 |
316 | return intercepts;
317 | } else {
318 | return [];
319 | }
320 | }
321 |
322 | // 4 possible intercepts (of which two will be selected)
323 | // 1) y = container.y
324 | x = (container.y - b)/slope;
325 | if (container.x <= x && x <= container.x+container.width) {
326 | intercepts.push({x: x, y: container.y});
327 | }
328 |
329 | // 2) x === container.x
330 | y = slope*container.x + b;
331 | if (container.y <= y && y <= container.y+container.height) {
332 | intercepts.push({x: container.x, y: y});
333 | }
334 |
335 | // 3) y === container.y+container.height
336 | x = (container.y+container.height - b)/slope;
337 | if (container.x <= x && x <= container.x+container.width) {
338 | intercepts.push({x: x, y: container.y+container.height});
339 | }
340 |
341 | // 4) x === container.x+container.width
342 | y = slope*(container.x+container.width) + b;
343 | if (container.y <= y && y <= container.y+container.height) {
344 | intercepts.push({x: container.x+container.width, y: y});
345 | }
346 |
347 | // Order the elements so the first is the entering intercept and the later is the leaving
348 | intercepts.sort(function(a, b) { return run>0? a.x-b.x : b.x-a.x; });
349 |
350 | // Remove any duplicates or entries before the ray start and restore the units
351 | len = intercepts.length;
352 | x = undefined;
353 | while (len--) {
354 | check = intercepts[len];
355 | if (check.x === x || (ray && (run>0 ? check.xstart.x))) {
356 | intercepts.splice(len, 1);
357 | }
358 | x = check.x;
359 |
360 | check.x = combineUnit(check.x, unit);
361 | check.y = combineUnit(check.y, unit);
362 | }
363 |
364 | return intercepts;
365 | },
366 |
367 | /**
368 | * Determines the angle, in radians of the line connected by these two points.
369 | * Range: [0, 2PI]
370 | */
371 | slopeInRads: function(start, end) {
372 | // Check that the units match
373 | var unit = 0;
374 | unit = checkUnit(unit, start.x);
375 | unit = checkUnit(unit, start.y);
376 | unit = checkUnit(unit, end.x);
377 | unit = checkUnit(unit, end.y);
378 | if (unit === false) {
379 | return NaN;
380 | }
381 |
382 | var rise = parseFloat(end.y)-parseFloat(start.y),
383 | run = parseFloat(end.x)-parseFloat(start.x);
384 | return (run<0 ? Math.PI : (rise<0 ? 2*Math.PI : 0)) + (run ? Math.atan(rise/run) : (rise<0?-1:1)*Math.PI/2);
385 | },
386 | radsToDegrees: function(rads) {
387 | return 180*rads/Math.PI;
388 | },
389 | walkLine: function(start, end, callback) {
390 | // Determine the properties of our line
391 | var rise = end.y-start.y,
392 | run = end.x-start.x,
393 | xIntercept = start.x-Math.min(start.x, end.x),
394 | yIntercept = start.y-Math.min(start.y, end.y),
395 | distance = LineUtils.distance(start, end),
396 |
397 | len = distance|0;
398 | if (!len) {
399 | return len;
400 | }
401 |
402 | // Scale the run and rise for each integer step
403 | run /= len;
404 | rise /= len;
405 |
406 | // Walk the parameterized line
407 | for (var t = 0; t < len; t++) {
408 | callback(t, {
409 | x: (run*t + xIntercept),
410 | y: (rise*t + yIntercept)
411 | });
412 | }
413 | return len;
414 | }
415 | };
416 |
417 | })();
418 |
--------------------------------------------------------------------------------
/js/sensitivity.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | /*global $, jQuery, ColorStops, GradientScanner */
6 |
7 | $(document).ready(function() {
8 | var colorStops, deltaE = ColorStops.JND;
9 |
10 | function outputGradient() {
11 | ColorStops.applyBackground($(".gradient-preview"), "linear", {x: 0, y: 0}, {x: "100%", y: 0}, colorStops);
12 | $(".stop-count").text("Count: " + colorStops.filter(function(stop) { return !stop.disabled; }).length + " deltaE: " + deltaE);
13 | }
14 | function updateGradient() {
15 | GradientScanner.colorStops = colorStops = ColorStops.extractColorStops(GradientScanner.line.imageData.data, deltaE);
16 |
17 | $(document).trigger(new jQuery.Event("deltaEUpdated"));
18 | }
19 |
20 | $(".delta-e-slider").slider({
21 | value: deltaE,
22 | step: 0.5,
23 | min: 1,
24 | max: 15,
25 | slide: function(event, ui) {
26 | deltaE = ui.value;
27 |
28 | updateGradient();
29 | }
30 | });
31 |
32 | $(document).bind("imageLoaded", function(event) {
33 | $(".gradient-preview").css("background", "none");
34 | $(".stop-count").text('');
35 | });
36 | $(document).bind("lineUpdated", updateGradient);
37 | $(document).bind("deltaEUpdated", outputGradient);
38 | $(document).bind("gradientUpdated", outputGradient);
39 | });
40 |
--------------------------------------------------------------------------------
/lib/colorpicker/css/colorpicker.css:
--------------------------------------------------------------------------------
1 | .colorpicker {
2 | width: 356px;
3 | height: 176px;
4 | overflow: hidden;
5 | position: absolute;
6 | background: url(../images/colorpicker_background.png);
7 | font-family: Arial, Helvetica, sans-serif;
8 | display: none;
9 | }
10 | .colorpicker_color {
11 | width: 150px;
12 | height: 150px;
13 | left: 14px;
14 | top: 13px;
15 | position: absolute;
16 | background: #f00;
17 | overflow: hidden;
18 | cursor: crosshair;
19 | }
20 | .colorpicker_color div {
21 | position: absolute;
22 | top: 0;
23 | left: 0;
24 | width: 150px;
25 | height: 150px;
26 | background: url(../images/colorpicker_overlay.png);
27 | }
28 | .colorpicker_color div div {
29 | position: absolute;
30 | top: 0;
31 | left: 0;
32 | width: 11px;
33 | height: 11px;
34 | overflow: hidden;
35 | background: url(../images/colorpicker_select.gif);
36 | margin: -5px 0 0 -5px;
37 | }
38 | .colorpicker_hue {
39 | position: absolute;
40 | top: 13px;
41 | left: 171px;
42 | width: 35px;
43 | height: 150px;
44 | cursor: n-resize;
45 | }
46 | .colorpicker_hue div {
47 | position: absolute;
48 | width: 35px;
49 | height: 9px;
50 | overflow: hidden;
51 | background: url(../images/colorpicker_indic.gif) left top;
52 | margin: -4px 0 0 0;
53 | left: 0px;
54 | }
55 | .colorpicker_new_color {
56 | position: absolute;
57 | width: 60px;
58 | height: 30px;
59 | left: 213px;
60 | top: 13px;
61 | background: #f00;
62 | }
63 | .colorpicker_current_color {
64 | position: absolute;
65 | width: 60px;
66 | height: 30px;
67 | left: 283px;
68 | top: 13px;
69 | background: #f00;
70 | }
71 | .colorpicker input {
72 | background-color: transparent;
73 | border: 1px solid transparent;
74 | position: absolute;
75 | font-size: 10px;
76 | font-family: Arial, Helvetica, sans-serif;
77 | color: #898989;
78 | top: 4px;
79 | right: 11px;
80 | text-align: right;
81 | margin: 0;
82 | padding: 0;
83 | height: 11px;
84 | }
85 | .colorpicker_hex {
86 | position: absolute;
87 | width: 72px;
88 | height: 22px;
89 | background: url(../images/colorpicker_hex.png) top;
90 | left: 212px;
91 | top: 142px;
92 | }
93 | .colorpicker_hex input {
94 | right: 6px;
95 | }
96 | .colorpicker_field {
97 | height: 22px;
98 | width: 62px;
99 | background-position: top;
100 | position: absolute;
101 | }
102 | .colorpicker_field span {
103 | position: absolute;
104 | width: 12px;
105 | height: 22px;
106 | overflow: hidden;
107 | top: 0;
108 | right: 0;
109 | cursor: n-resize;
110 | }
111 | .colorpicker_rgb_r {
112 | background-image: url(../images/colorpicker_rgb_r.png);
113 | top: 52px;
114 | left: 212px;
115 | }
116 | .colorpicker_rgb_g {
117 | background-image: url(../images/colorpicker_rgb_g.png);
118 | top: 82px;
119 | left: 212px;
120 | }
121 | .colorpicker_rgb_b {
122 | background-image: url(../images/colorpicker_rgb_b.png);
123 | top: 112px;
124 | left: 212px;
125 | }
126 | .colorpicker_hsb_h {
127 | background-image: url(../images/colorpicker_hsb_h.png);
128 | top: 52px;
129 | left: 282px;
130 | }
131 | .colorpicker_hsb_s {
132 | background-image: url(../images/colorpicker_hsb_s.png);
133 | top: 82px;
134 | left: 282px;
135 | }
136 | .colorpicker_hsb_b {
137 | background-image: url(../images/colorpicker_hsb_b.png);
138 | top: 112px;
139 | left: 282px;
140 | }
141 | .colorpicker_focus {
142 | background-position: center;
143 | }
144 | .colorpicker_hex.colorpicker_focus {
145 | background-position: bottom;
146 | }
147 | .colorpicker_slider {
148 | background-position: bottom;
149 | }
150 |
--------------------------------------------------------------------------------
/lib/colorpicker/images/colorpicker_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/lib/colorpicker/images/colorpicker_background.png
--------------------------------------------------------------------------------
/lib/colorpicker/images/colorpicker_hex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/lib/colorpicker/images/colorpicker_hex.png
--------------------------------------------------------------------------------
/lib/colorpicker/images/colorpicker_hsb_b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/lib/colorpicker/images/colorpicker_hsb_b.png
--------------------------------------------------------------------------------
/lib/colorpicker/images/colorpicker_hsb_h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/lib/colorpicker/images/colorpicker_hsb_h.png
--------------------------------------------------------------------------------
/lib/colorpicker/images/colorpicker_hsb_s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/lib/colorpicker/images/colorpicker_hsb_s.png
--------------------------------------------------------------------------------
/lib/colorpicker/images/colorpicker_indic.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/lib/colorpicker/images/colorpicker_indic.gif
--------------------------------------------------------------------------------
/lib/colorpicker/images/colorpicker_overlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/lib/colorpicker/images/colorpicker_overlay.png
--------------------------------------------------------------------------------
/lib/colorpicker/images/colorpicker_rgb_b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/lib/colorpicker/images/colorpicker_rgb_b.png
--------------------------------------------------------------------------------
/lib/colorpicker/images/colorpicker_rgb_g.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/lib/colorpicker/images/colorpicker_rgb_g.png
--------------------------------------------------------------------------------
/lib/colorpicker/images/colorpicker_rgb_r.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/lib/colorpicker/images/colorpicker_rgb_r.png
--------------------------------------------------------------------------------
/lib/colorpicker/images/colorpicker_select.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/lib/colorpicker/images/colorpicker_select.gif
--------------------------------------------------------------------------------
/lib/colorpicker/js/colorpicker.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Color picker
4 | * Author: Stefan Petre www.eyecon.ro
5 | *
6 | * Dual licensed under the MIT and GPL licenses
7 | *
8 | */
9 | (function ($) {
10 | var ColorPicker = function () {
11 | var
12 | ids = {},
13 | inAction,
14 | charMin = 65,
15 | visible,
16 | tpl = '',
17 | defaults = {
18 | eventName: 'click',
19 | onShow: function () {},
20 | onBeforeShow: function(){},
21 | onHide: function () {},
22 | onChange: function () {},
23 | onSubmit: function () {},
24 | color: 'ff0000',
25 | livePreview: true,
26 | flat: false
27 | },
28 | fillRGBFields = function (hsb, cal) {
29 | var rgb = HSBToRGB(hsb);
30 | $(cal).data('colorpicker').fields
31 | .eq(1).val(rgb.r).end()
32 | .eq(2).val(rgb.g).end()
33 | .eq(3).val(rgb.b).end();
34 | },
35 | fillHSBFields = function (hsb, cal) {
36 | $(cal).data('colorpicker').fields
37 | .eq(4).val(hsb.h).end()
38 | .eq(5).val(hsb.s).end()
39 | .eq(6).val(hsb.b).end();
40 | },
41 | fillHexFields = function (hsb, cal) {
42 | $(cal).data('colorpicker').fields
43 | .eq(0).val(HSBToHex(hsb)).end();
44 | },
45 | setSelector = function (hsb, cal) {
46 | $(cal).data('colorpicker').selector.css('backgroundColor', '#' + HSBToHex({h: hsb.h, s: 100, b: 100}));
47 | $(cal).data('colorpicker').selectorIndic.css({
48 | left: parseInt(150 * hsb.s/100, 10),
49 | top: parseInt(150 * (100-hsb.b)/100, 10)
50 | });
51 | },
52 | setHue = function (hsb, cal) {
53 | $(cal).data('colorpicker').hue.css('top', parseInt(150 - 150 * hsb.h/360, 10));
54 | },
55 | setCurrentColor = function (hsb, cal) {
56 | $(cal).data('colorpicker').currentColor.css('backgroundColor', '#' + HSBToHex(hsb));
57 | },
58 | setNewColor = function (hsb, cal) {
59 | $(cal).data('colorpicker').newColor.css('backgroundColor', '#' + HSBToHex(hsb));
60 | },
61 | keyDown = function (ev) {
62 | var pressedKey = ev.charCode || ev.keyCode || -1;
63 | if ((pressedKey > charMin && pressedKey <= 90) || pressedKey == 32) {
64 | return false;
65 | }
66 | var cal = $(this).parent().parent();
67 | if (cal.data('colorpicker').livePreview === true) {
68 | change.apply(this);
69 | }
70 | },
71 | change = function (ev) {
72 | var cal = $(this).parent().parent(), col;
73 | if (this.parentNode.className.indexOf('_hex') > 0) {
74 | cal.data('colorpicker').color = col = HexToHSB(fixHex(this.value));
75 | } else if (this.parentNode.className.indexOf('_hsb') > 0) {
76 | cal.data('colorpicker').color = col = fixHSB({
77 | h: parseInt(cal.data('colorpicker').fields.eq(4).val(), 10),
78 | s: parseInt(cal.data('colorpicker').fields.eq(5).val(), 10),
79 | b: parseInt(cal.data('colorpicker').fields.eq(6).val(), 10)
80 | });
81 | } else {
82 | cal.data('colorpicker').color = col = RGBToHSB(fixRGB({
83 | r: parseInt(cal.data('colorpicker').fields.eq(1).val(), 10),
84 | g: parseInt(cal.data('colorpicker').fields.eq(2).val(), 10),
85 | b: parseInt(cal.data('colorpicker').fields.eq(3).val(), 10)
86 | }));
87 | }
88 | if (ev) {
89 | fillRGBFields(col, cal.get(0));
90 | fillHexFields(col, cal.get(0));
91 | fillHSBFields(col, cal.get(0));
92 | }
93 | setSelector(col, cal.get(0));
94 | setHue(col, cal.get(0));
95 | setNewColor(col, cal.get(0));
96 | cal.data('colorpicker').onChange.apply(cal, [col, HSBToHex(col), HSBToRGB(col)]);
97 | },
98 | blur = function (ev) {
99 | var cal = $(this).parent().parent();
100 | cal.data('colorpicker').fields.parent().removeClass('colorpicker_focus');
101 | },
102 | focus = function () {
103 | charMin = this.parentNode.className.indexOf('_hex') > 0 ? 70 : 65;
104 | $(this).parent().parent().data('colorpicker').fields.parent().removeClass('colorpicker_focus');
105 | $(this).parent().addClass('colorpicker_focus');
106 | },
107 | downIncrement = function (ev) {
108 | var field = $(this).parent().find('input').focus();
109 | var current = {
110 | el: $(this).parent().addClass('colorpicker_slider'),
111 | max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255),
112 | y: ev.pageY,
113 | field: field,
114 | val: parseInt(field.val(), 10),
115 | preview: $(this).parent().parent().data('colorpicker').livePreview
116 | };
117 | $(document).bind('mouseup', current, upIncrement);
118 | $(document).bind('mousemove', current, moveIncrement);
119 | },
120 | moveIncrement = function (ev) {
121 | ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val + ev.pageY - ev.data.y, 10))));
122 | if (ev.data.preview) {
123 | change.apply(ev.data.field.get(0), [true]);
124 | }
125 | return false;
126 | },
127 | upIncrement = function (ev) {
128 | change.apply(ev.data.field.get(0), [true]);
129 | ev.data.el.removeClass('colorpicker_slider').find('input').focus();
130 | $(document).unbind('mouseup', upIncrement);
131 | $(document).unbind('mousemove', moveIncrement);
132 | return false;
133 | },
134 | downHue = function (ev) {
135 | var current = {
136 | cal: $(this).parent(),
137 | y: $(this).offset().top
138 | };
139 | current.preview = current.cal.data('colorpicker').livePreview;
140 | $(document).bind('mouseup', current, upHue);
141 | $(document).bind('mousemove', current, moveHue);
142 | },
143 | moveHue = function (ev) {
144 | change.apply(
145 | ev.data.cal.data('colorpicker')
146 | .fields
147 | .eq(4)
148 | .val(parseInt(360*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.y))))/150, 10))
149 | .get(0),
150 | [ev.data.preview]
151 | );
152 | return false;
153 | },
154 | upHue = function (ev) {
155 | fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
156 | fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
157 | $(document).unbind('mouseup', upHue);
158 | $(document).unbind('mousemove', moveHue);
159 | return false;
160 | },
161 | downSelector = function (ev) {
162 | var current = {
163 | cal: $(this).parent(),
164 | pos: $(this).offset()
165 | };
166 | current.preview = current.cal.data('colorpicker').livePreview;
167 | $(document).bind('mouseup', current, upSelector);
168 | $(document).bind('mousemove', current, moveSelector);
169 | },
170 | moveSelector = function (ev) {
171 | change.apply(
172 | ev.data.cal.data('colorpicker')
173 | .fields
174 | .eq(6)
175 | .val(parseInt(100*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.pos.top))))/150, 10))
176 | .end()
177 | .eq(5)
178 | .val(parseInt(100*(Math.max(0,Math.min(150,(ev.pageX - ev.data.pos.left))))/150, 10))
179 | .get(0),
180 | [ev.data.preview]
181 | );
182 | return false;
183 | },
184 | upSelector = function (ev) {
185 | fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
186 | fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
187 | $(document).unbind('mouseup', upSelector);
188 | $(document).unbind('mousemove', moveSelector);
189 | return false;
190 | },
191 | enterSubmit = function (ev) {
192 | $(this).addClass('colorpicker_focus');
193 | },
194 | leaveSubmit = function (ev) {
195 | $(this).removeClass('colorpicker_focus');
196 | },
197 | clickSubmit = function (ev) {
198 | var cal = $(this).parent();
199 | var col = cal.data('colorpicker').color;
200 | cal.data('colorpicker').origColor = col;
201 | setCurrentColor(col, cal.get(0));
202 | cal.data('colorpicker').onSubmit(col, HSBToHex(col), HSBToRGB(col), cal.data('colorpicker').el);
203 | },
204 | show = function (ev) {
205 | var cal = $('#' + $(this).data('colorpickerId'));
206 | cal.data('colorpicker').onBeforeShow.apply(this, [cal.get(0)]);
207 | var pos = $(this).offset();
208 | var viewPort = getViewport();
209 | var top = pos.top + this.offsetHeight;
210 | var left = pos.left;
211 | if (top + 176 > viewPort.t + viewPort.h) {
212 | top -= this.offsetHeight + 176;
213 | }
214 | if (left + 356 > viewPort.l + viewPort.w) {
215 | left -= 356;
216 | }
217 | cal.css({left: left + 'px', top: top + 'px'});
218 | if (cal.data('colorpicker').onShow.apply(this, [cal.get(0)]) != false) {
219 | cal.show();
220 | }
221 | $(document).bind('mousedown', {cal: cal}, hide);
222 | return false;
223 | },
224 | hide = function (ev) {
225 | if (!isChildOf(ev.data.cal.get(0), ev.target, ev.data.cal.get(0))) {
226 | if (ev.data.cal.data('colorpicker').onHide.apply(this, [ev.data.cal.get(0)]) != false) {
227 | ev.data.cal.hide();
228 | }
229 | $(document).unbind('mousedown', hide);
230 | }
231 | },
232 | isChildOf = function(parentEl, el, container) {
233 | if (parentEl == el) {
234 | return true;
235 | }
236 | if (parentEl.contains) {
237 | return parentEl.contains(el);
238 | }
239 | if ( parentEl.compareDocumentPosition ) {
240 | return !!(parentEl.compareDocumentPosition(el) & 16);
241 | }
242 | var prEl = el.parentNode;
243 | while(prEl && prEl != container) {
244 | if (prEl == parentEl)
245 | return true;
246 | prEl = prEl.parentNode;
247 | }
248 | return false;
249 | },
250 | getViewport = function () {
251 | var m = document.compatMode == 'CSS1Compat';
252 | return {
253 | l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft),
254 | t : window.pageYOffset || (m ? document.documentElement.scrollTop : document.body.scrollTop),
255 | w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth),
256 | h : window.innerHeight || (m ? document.documentElement.clientHeight : document.body.clientHeight)
257 | };
258 | },
259 | fixHSB = function (hsb) {
260 | return {
261 | h: Math.min(360, Math.max(0, hsb.h)),
262 | s: Math.min(100, Math.max(0, hsb.s)),
263 | b: Math.min(100, Math.max(0, hsb.b))
264 | };
265 | },
266 | fixRGB = function (rgb) {
267 | return {
268 | r: Math.min(255, Math.max(0, rgb.r)),
269 | g: Math.min(255, Math.max(0, rgb.g)),
270 | b: Math.min(255, Math.max(0, rgb.b))
271 | };
272 | },
273 | fixHex = function (hex) {
274 | var len = 6 - hex.length;
275 | if (len > 0) {
276 | var o = [];
277 | for (var i=0; i -1) ? hex.substring(1) : hex), 16);
287 | return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)};
288 | },
289 | HexToHSB = function (hex) {
290 | return RGBToHSB(HexToRGB(hex));
291 | },
292 | RGBToHSB = function (rgb) {
293 | var hsb = {
294 | h: 0,
295 | s: 0,
296 | b: 0
297 | };
298 | var min = Math.min(rgb.r, rgb.g, rgb.b);
299 | var max = Math.max(rgb.r, rgb.g, rgb.b);
300 | var delta = max - min;
301 | hsb.b = max;
302 | if (max != 0) {
303 |
304 | }
305 | hsb.s = max != 0 ? 255 * delta / max : 0;
306 | if (hsb.s != 0) {
307 | if (rgb.r == max) {
308 | hsb.h = (rgb.g - rgb.b) / delta;
309 | } else if (rgb.g == max) {
310 | hsb.h = 2 + (rgb.b - rgb.r) / delta;
311 | } else {
312 | hsb.h = 4 + (rgb.r - rgb.g) / delta;
313 | }
314 | } else {
315 | hsb.h = -1;
316 | }
317 | hsb.h *= 60;
318 | if (hsb.h < 0) {
319 | hsb.h += 360;
320 | }
321 | hsb.s *= 100/255;
322 | hsb.b *= 100/255;
323 | return hsb;
324 | },
325 | HSBToRGB = function (hsb) {
326 | var rgb = {};
327 | var h = Math.round(hsb.h);
328 | var s = Math.round(hsb.s*255/100);
329 | var v = Math.round(hsb.b*255/100);
330 | if(s == 0) {
331 | rgb.r = rgb.g = rgb.b = v;
332 | } else {
333 | var t1 = v;
334 | var t2 = (255-s)*v/255;
335 | var t3 = (t1-t2)*(h%60)/60;
336 | if(h==360) h = 0;
337 | if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3}
338 | else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3}
339 | else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3}
340 | else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3}
341 | else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3}
342 | else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3}
343 | else {rgb.r=0; rgb.g=0; rgb.b=0}
344 | }
345 | return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)};
346 | },
347 | RGBToHex = function (rgb) {
348 | var hex = [
349 | rgb.r.toString(16),
350 | rgb.g.toString(16),
351 | rgb.b.toString(16)
352 | ];
353 | $.each(hex, function (nr, val) {
354 | if (val.length == 1) {
355 | hex[nr] = '0' + val;
356 | }
357 | });
358 | return hex.join('');
359 | },
360 | HSBToHex = function (hsb) {
361 | return RGBToHex(HSBToRGB(hsb));
362 | },
363 | restoreOriginal = function () {
364 | var cal = $(this).parent();
365 | var col = cal.data('colorpicker').origColor;
366 | cal.data('colorpicker').color = col;
367 | fillRGBFields(col, cal.get(0));
368 | fillHexFields(col, cal.get(0));
369 | fillHSBFields(col, cal.get(0));
370 | setSelector(col, cal.get(0));
371 | setHue(col, cal.get(0));
372 | setNewColor(col, cal.get(0));
373 | };
374 | return {
375 | init: function (opt) {
376 | opt = $.extend({}, defaults, opt||{});
377 | if (typeof opt.color == 'string') {
378 | opt.color = HexToHSB(opt.color);
379 | } else if (opt.color.r != undefined && opt.color.g != undefined && opt.color.b != undefined) {
380 | opt.color = RGBToHSB(opt.color);
381 | } else if (opt.color.h != undefined && opt.color.s != undefined && opt.color.b != undefined) {
382 | opt.color = fixHSB(opt.color);
383 | } else {
384 | return this;
385 | }
386 | return this.each(function () {
387 | if (!$(this).data('colorpickerId')) {
388 | var options = $.extend({}, opt);
389 | options.origColor = opt.color;
390 | var id = 'collorpicker_' + parseInt(Math.random() * 1000);
391 | $(this).data('colorpickerId', id);
392 | var cal = $(tpl).attr('id', id);
393 | if (options.flat) {
394 | cal.appendTo(this).show();
395 | } else {
396 | cal.appendTo(document.body);
397 | }
398 | options.fields = cal
399 | .find('input')
400 | .bind('keyup', keyDown)
401 | .bind('change', change)
402 | .bind('blur', blur)
403 | .bind('focus', focus);
404 | cal
405 | .find('span').bind('mousedown', downIncrement).end()
406 | .find('>div.colorpicker_current_color').bind('click', restoreOriginal);
407 | options.selector = cal.find('div.colorpicker_color').bind('mousedown', downSelector);
408 | options.selectorIndic = options.selector.find('div div');
409 | options.el = this;
410 | options.hue = cal.find('div.colorpicker_hue div');
411 | cal.find('div.colorpicker_hue').bind('mousedown', downHue);
412 | options.newColor = cal.find('div.colorpicker_new_color');
413 | options.currentColor = cal.find('div.colorpicker_current_color');
414 | cal.data('colorpicker', options);
415 | cal.find('div.colorpicker_submit')
416 | .bind('mouseenter', enterSubmit)
417 | .bind('mouseleave', leaveSubmit)
418 | .bind('click', clickSubmit);
419 | fillRGBFields(options.color, cal.get(0));
420 | fillHSBFields(options.color, cal.get(0));
421 | fillHexFields(options.color, cal.get(0));
422 | setHue(options.color, cal.get(0));
423 | setSelector(options.color, cal.get(0));
424 | setCurrentColor(options.color, cal.get(0));
425 | setNewColor(options.color, cal.get(0));
426 | if (options.flat) {
427 | cal.css({
428 | position: 'relative',
429 | display: 'block'
430 | });
431 | } else {
432 | $(this).bind(options.eventName, show);
433 | }
434 | }
435 | });
436 | },
437 | showPicker: function() {
438 | return this.each( function () {
439 | if ($(this).data('colorpickerId')) {
440 | show.apply(this);
441 | }
442 | });
443 | },
444 | hidePicker: function() {
445 | return this.each( function () {
446 | if ($(this).data('colorpickerId')) {
447 | $('#' + $(this).data('colorpickerId')).hide();
448 | }
449 | });
450 | },
451 | setColor: function(col) {
452 | if (typeof col == 'string') {
453 | col = HexToHSB(col);
454 | } else if (col.r != undefined && col.g != undefined && col.b != undefined) {
455 | col = RGBToHSB(col);
456 | } else if (col.h != undefined && col.s != undefined && col.b != undefined) {
457 | col = fixHSB(col);
458 | } else {
459 | return this;
460 | }
461 | return this.each(function(){
462 | if ($(this).data('colorpickerId')) {
463 | var cal = $('#' + $(this).data('colorpickerId'));
464 | cal.data('colorpicker').color = col;
465 | cal.data('colorpicker').origColor = col;
466 | fillRGBFields(col, cal.get(0));
467 | fillHSBFields(col, cal.get(0));
468 | fillHexFields(col, cal.get(0));
469 | setHue(col, cal.get(0));
470 | setSelector(col, cal.get(0));
471 | setCurrentColor(col, cal.get(0));
472 | setNewColor(col, cal.get(0));
473 | }
474 | });
475 | }
476 | };
477 | }();
478 | $.fn.extend({
479 | ColorPicker: ColorPicker.init,
480 | ColorPickerHide: ColorPicker.hidePicker,
481 | ColorPickerShow: ColorPicker.showPicker,
482 | ColorPickerSetColor: ColorPicker.setColor
483 | });
484 | })(jQuery)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Kevin Decker (http://incaseofstairs.com)",
3 | "name": "gradient-scanner",
4 | "version": "0.2.0",
5 | "homepage": "http://gradient-scanner.com",
6 | "repository": {
7 | "type": "git",
8 | "url": "git://github.com/kpdecker/gradient-scanner.git"
9 | },
10 | "engines": {
11 | "node": "~0.6"
12 | },
13 | "dependencies": {
14 | "express": "*"
15 | },
16 | "devDependencies": {}
17 | }
18 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var express = require("express"),
2 | http = require("http"),
3 | url = require("url");
4 |
5 | var app = express.createServer(),
6 | port = process.env.PORT || 3000;
7 |
8 | app.configure(function(){
9 | app.use(app.router);
10 | app.use(function(req, res, next) {
11 | // Filter out non-content. Ideally this would be in a subdir
12 | // but didn't want to move as the focus is the static client content.
13 | if ('/' === req.url
14 | || '/index.html' === req.url
15 | || '/index.htm' === req.url
16 | || /^\/css\//.test(req.url)
17 | || /^\/lib\//.test(req.url)
18 | || /^\/js\//.test(req.url)) {
19 | next();
20 | } else {
21 | res.send(404);
22 | }
23 | });
24 | app.use(express.static(__dirname));
25 | });
26 |
27 | app.get('/proxy', function(req, res){
28 | var hrefParam = req.param("href"),
29 | href = url.parse(hrefParam),
30 | options = {
31 | host: href.hostname,
32 | port: href.port || 80,
33 | path: (href.pathname || "") + (href.search || "")
34 | },
35 | len = 0;
36 |
37 | http.get(options, function(sourceRes) {
38 | sourceRes.on("data", function(chunk) {
39 | res.write(chunk);
40 | len += chunk.length;
41 | });
42 | sourceRes.on("end", function() {
43 | console.info("proxied url", hrefParam, "length", len, "status", sourceRes.statusCode);
44 | res.end();
45 | });
46 |
47 | res.writeHead(sourceRes.statusCode, sourceRes.headers);
48 | }).on("error", function(e) {
49 | console.info("error", e);
50 | res.send(502);
51 | });
52 | });
53 |
54 | console.log("Starting gradient scanner server on port", port);
55 | app.listen(port);
56 |
--------------------------------------------------------------------------------
/test/color-stops.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | $(document).ready(function(){
6 | module("ColorStops");
7 |
8 | test("getColorValue", function() {
9 | expect(12);
10 | equal(ColorStops.getColorValue([255, 0, 0, 255]), "#f00", "getColorValue(255, 0, 0, 255)");
11 | equal(ColorStops.getColorValue([0, 255, 0, 255]), "#0f0", "getColorValue(0, 255, 0, 255)");
12 | equal(ColorStops.getColorValue([0, 0, 255, 255]), "#00f", "getColorValue(0, 0, 255, 255)");
13 | equal(ColorStops.getColorValue([254, 0, 0, 255]), "#fe0000", "getColorValue(255, 0, 0, 255)");
14 | equal(ColorStops.getColorValue([0, 254, 0, 255]), "#00fe00", "getColorValue(0, 255, 0, 255)");
15 | equal(ColorStops.getColorValue([0, 0, 254, 255]), "#0000fe", "getColorValue(0, 0, 255, 255)");
16 | equal(ColorStops.getColorValue([0, 0, 0, 255]), "#000", "getColorValue(0, 0, 0, 255)");
17 |
18 | equal(ColorStops.getColorValue([255, 0, 0, 0]), "rgba(255, 0, 0, 0)", "getColorValue(255, 0, 0, 0)");
19 | equal(ColorStops.getColorValue([0, 255, 0, 0]), "rgba(0, 255, 0, 0)", "getColorValue(0, 255, 0, 0)");
20 | equal(ColorStops.getColorValue([0, 0, 255, 0]), "rgba(0, 0, 255, 0)", "getColorValue(0, 0, 255, 0)");
21 | equal(ColorStops.getColorValue([0, 0, 0, 0]), "rgba(0, 0, 0, 0)", "getColorValue(0, 0, 0, 0)");
22 |
23 | equal(ColorStops.getColorValue([128.5, 128.5, 128.5, 128.5]), "rgba(128, 128, 128, 128)", "getColorValue(128.5, 128.5, 128.5, 128.5)");
24 | });
25 |
26 | test("generateCSS", function() {
27 | expect(24);
28 | var redToBlue = [
29 | {position: 0, color: [255, 0, 0, 255]},
30 | {position: 0.5, color: [255, 0, 255, 255], disabled: true},
31 | {position: 1, color: [0, 0, 255, 255]}
32 | ],
33 | steppedRedToBlue = [
34 | {position: 0.25, color: [255, 0, 0, 255]},
35 | {position: 0.5, color: [255, 0, 255, 255], disabled: true},
36 | {position: 0.75, color: [0, 0, 255, 255]}
37 | ];
38 |
39 | // Colorstop Generation Tests
40 | deepEqual(ColorStops.generateCSS("linear", {x:0, y:0}, {x:0, y:"100%"}, redToBlue), [
41 | "-webkit-gradient(linear, 0 0, 0 100%, from(#f00), to(#00f))",
42 | "-webkit-linear-gradient(#f00, #00f)",
43 | "-moz-linear-gradient(#f00, #00f)",
44 | '-o-linear-gradient(#f00, #00f)',
45 | 'linear-gradient(#f00, #00f)'
46 | ], "generateCSS(linear, {0,0}, {0,100%}, redToBlue)");
47 |
48 | deepEqual(ColorStops.generateCSS("linear", {x:0, y:0}, {x:0, y:"100%"}, steppedRedToBlue), [
49 | "-webkit-gradient(linear, 0 0, 0 100%, color-stop(0.25, #f00), color-stop(0.75, #00f))",
50 | "-webkit-linear-gradient(#f00 25%, #00f 75%)",
51 | "-moz-linear-gradient(#f00 25%, #00f 75%)",
52 | '-o-linear-gradient(#f00 25%, #00f 75%)',
53 | 'linear-gradient(#f00 25%, #00f 75%)'
54 | ], "generateCSS(linear, {0,0}, {0,100%}, steppedRedToBlue)");
55 |
56 | // Position and angle Tests
57 | deepEqual(ColorStops.generateCSS("linear", {x:0, y:"100%"}, {x:0, y:0}, redToBlue), [
58 | "-webkit-gradient(linear, 0 100%, 0 0, from(#f00), to(#00f))",
59 | "-webkit-linear-gradient(90deg, #f00, #00f)",
60 | "-moz-linear-gradient(90deg, #f00, #00f)",
61 | '-o-linear-gradient(90deg, #f00, #00f)',
62 | 'linear-gradient(90deg, #f00, #00f)'
63 | ], "generateCSS(linear, {0,100%}, {0,0}, redToBlue)");
64 |
65 | deepEqual(ColorStops.generateCSS("linear", {x:0, y:0}, {x:"100%", y:0}, redToBlue), [
66 | "-webkit-gradient(linear, 0 0, 100% 0, from(#f00), to(#00f))",
67 | "-webkit-linear-gradient(360deg, #f00, #00f)",
68 | "-moz-linear-gradient(360deg, #f00, #00f)",
69 | '-o-linear-gradient(360deg, #f00, #00f)',
70 | 'linear-gradient(360deg, #f00, #00f)'
71 | ], "generateCSS(linear, {0,0}, {100%, 0}, redToBlue)");
72 | deepEqual(ColorStops.generateCSS("linear", {x:"100%", y:0}, {x:0, y:0}, redToBlue), [
73 | "-webkit-gradient(linear, 100% 0, 0 0, from(#f00), to(#00f))",
74 | "-webkit-linear-gradient(180deg, #f00, #00f)",
75 | "-moz-linear-gradient(180deg, #f00, #00f)",
76 | '-o-linear-gradient(180deg, #f00, #00f)',
77 | 'linear-gradient(180deg, #f00, #00f)'
78 | ], "generateCSS(linear, {100%,0}, {0, 0}, redToBlue)");
79 |
80 | deepEqual(ColorStops.generateCSS("linear", {x:0, y:0}, {x:"100%", y:"100%"}, redToBlue), [
81 | "-webkit-gradient(linear, 0 0, 100% 100%, from(#f00), to(#00f))",
82 | "-webkit-linear-gradient(315deg, #f00, #00f)",
83 | "-moz-linear-gradient(315deg, #f00, #00f)",
84 | '-o-linear-gradient(315deg, #f00, #00f)',
85 | 'linear-gradient(315deg, #f00, #00f)'
86 | ], "generateCSS(linear, {0,0}, {100%, 100%}, redToBlue)");
87 | deepEqual(ColorStops.generateCSS("linear", {x:"100%", y:"100%"}, {x:0, y:0}, redToBlue), [
88 | "-webkit-gradient(linear, 100% 100%, 0 0, from(#f00), to(#00f))",
89 | "-webkit-linear-gradient(135deg, #f00, #00f)",
90 | "-moz-linear-gradient(135deg, #f00, #00f)",
91 | '-o-linear-gradient(135deg, #f00, #00f)',
92 | 'linear-gradient(135deg, #f00, #00f)'
93 | ], "generateCSS(linear, {100%, 100%}, {0,0}, redToBlue)");
94 |
95 | // Positions that do not fill the entire region
96 | deepEqual(ColorStops.generateCSS("linear", {x:0, y:"25%"}, {x:0, y:"50%"}, redToBlue), [
97 | "-webkit-gradient(linear, 0 25%, 0 50%, from(#f00), to(#00f))",
98 | "-webkit-linear-gradient(#f00 25%, #00f 50%)",
99 | "-moz-linear-gradient(#f00 25%, #00f 50%)",
100 | '-o-linear-gradient(#f00 25%, #00f 50%)',
101 | 'linear-gradient(#f00 25%, #00f 50%)'
102 | ], "generateCSS(linear, {0,25%}, {0,50%}, redToBlue)");
103 | deepEqual(ColorStops.generateCSS("linear", {x:0, y:"50%"}, {x:0, y:"25%"}, redToBlue), [
104 | "-webkit-gradient(linear, 0 50%, 0 25%, from(#f00), to(#00f))",
105 | "-webkit-linear-gradient(90deg, #f00 50%, #00f 75%)",
106 | "-moz-linear-gradient(90deg, #f00 50%, #00f 75%)",
107 | '-o-linear-gradient(90deg, #f00 50%, #00f 75%)',
108 | 'linear-gradient(90deg, #f00 50%, #00f 75%)'
109 | ], "generateCSS(linear, {0,50%}, {0,25%}, redToBlue)");
110 |
111 | deepEqual(ColorStops.generateCSS("linear", {x:"25%", y:0}, {x:"50%", y:0}, redToBlue), [
112 | "-webkit-gradient(linear, 25% 0, 50% 0, from(#f00), to(#00f))",
113 | "-webkit-linear-gradient(360deg, #f00 25%, #00f 50%)",
114 | "-moz-linear-gradient(360deg, #f00 25%, #00f 50%)",
115 | '-o-linear-gradient(360deg, #f00 25%, #00f 50%)',
116 | 'linear-gradient(360deg, #f00 25%, #00f 50%)'
117 | ], "generateCSS(linear, {25%,0}, {50%,0}, redToBlue)");
118 | deepEqual(ColorStops.generateCSS("linear", {x:"50%", y:0}, {x:"25%", y:0}, redToBlue), [
119 | "-webkit-gradient(linear, 50% 0, 25% 0, from(#f00), to(#00f))",
120 | "-webkit-linear-gradient(180deg, #f00 50%, #00f 75%)",
121 | "-moz-linear-gradient(180deg, #f00 50%, #00f 75%)",
122 | '-o-linear-gradient(180deg, #f00 50%, #00f 75%)',
123 | 'linear-gradient(180deg, #f00 50%, #00f 75%)'
124 | ], "generateCSS(linear, {50%,0}, {25%,0}, redToBlue)");
125 |
126 | deepEqual(ColorStops.generateCSS("linear", {x:"25%", y:"25%"}, {x:"50%", y:"50%"}, redToBlue), [
127 | "-webkit-gradient(linear, 25% 25%, 50% 50%, from(#f00), to(#00f))",
128 | "-webkit-linear-gradient(315deg, #f00 25%, #00f 50%)",
129 | "-moz-linear-gradient(315deg, #f00 25%, #00f 50%)",
130 | '-o-linear-gradient(315deg, #f00 25%, #00f 50%)',
131 | 'linear-gradient(315deg, #f00 25%, #00f 50%)'
132 | ], "generateCSS(linear, {25%,25%}, {50%,50%}, redToBlue)");
133 | deepEqual(ColorStops.generateCSS("linear", {x:"50%", y:"50%"}, {x:"25%", y:"25%"}, redToBlue), [
134 | "-webkit-gradient(linear, 50% 50%, 25% 25%, from(#f00), to(#00f))",
135 | "-webkit-linear-gradient(135deg, #f00 50%, #00f 75%)",
136 | "-moz-linear-gradient(135deg, #f00 50%, #00f 75%)",
137 | '-o-linear-gradient(135deg, #f00 50%, #00f 75%)',
138 | 'linear-gradient(135deg, #f00 50%, #00f 75%)'
139 | ], "generateCSS(linear, {50%,50%}, {25%,25%}, redToBlue)");
140 |
141 | // Shifted offset
142 | deepEqual(ColorStops.generateCSS("linear", {x:"25%", y:"50%"}, {x:"50%", y:"75%"}, redToBlue), [
143 | "-webkit-gradient(linear, 25% 50%, 50% 75%, from(#f00), to(#00f))",
144 | "-webkit-linear-gradient(315deg, #f00 37.5%, #00f 62.5%)",
145 | "-moz-linear-gradient(315deg, #f00 37.5%, #00f 62.5%)",
146 | '-o-linear-gradient(315deg, #f00 37.5%, #00f 62.5%)',
147 | 'linear-gradient(315deg, #f00 37.5%, #00f 62.5%)'
148 | ], "generateCSS(linear, {25%,50%}, {50%,75%}, redToBlue)");
149 |
150 | // Stepped partial
151 | deepEqual(ColorStops.generateCSS("linear", {x:0, y:"25%"}, {x:0, y:"50%"}, steppedRedToBlue), [
152 | "-webkit-gradient(linear, 0 25%, 0 50%, color-stop(0.25, #f00), color-stop(0.75, #00f))",
153 | "-webkit-linear-gradient(#f00 31.2%, #00f 43.7%)",
154 | "-moz-linear-gradient(#f00 31.2%, #00f 43.7%)",
155 | '-o-linear-gradient(#f00 31.2%, #00f 43.7%)',
156 | 'linear-gradient(#f00 31.2%, #00f 43.7%)'
157 | ], "generateCSS(linear, {0,25%}, {0,50%}, steppedRedToBlue)");
158 |
159 | // Non-percentage positions
160 | deepEqual(ColorStops.generateCSS("linear", {x:25, y:0}, {x:50, y:0}, redToBlue), [
161 | "-webkit-gradient(linear, 25 0, 50 0, from(#f00), to(#00f))",
162 | "-webkit-linear-gradient(360deg, #f00 50%, #00f)",
163 | "-moz-linear-gradient(360deg, #f00 50%, #00f)",
164 | '-o-linear-gradient(360deg, #f00 50%, #00f)',
165 | 'linear-gradient(360deg, #f00 50%, #00f)'
166 | ], "generateCSS(linear, {25,0}, {50,0}, redToBlue)");
167 | deepEqual(ColorStops.generateCSS("linear", {x:0, y:25}, {x:0, y:50}, redToBlue), [
168 | "-webkit-gradient(linear, 0 25, 0 50, from(#f00), to(#00f))",
169 | "-webkit-linear-gradient(#f00 50%, #00f)",
170 | "-moz-linear-gradient(#f00 50%, #00f)",
171 | '-o-linear-gradient(#f00 50%, #00f)',
172 | 'linear-gradient(#f00 50%, #00f)'
173 | ], "generateCSS(linear, {0,25}, {0,50}, redToBlue)");
174 | deepEqual(ColorStops.generateCSS("linear", {x:25, y:25}, {x:50, y:50}, redToBlue), [
175 | "-webkit-gradient(linear, 25 25, 50 50, from(#f00), to(#00f))",
176 | "-webkit-linear-gradient(315deg, #f00 50%, #00f)",
177 | "-moz-linear-gradient(315deg, #f00 50%, #00f)",
178 | '-o-linear-gradient(315deg, #f00 50%, #00f)',
179 | 'linear-gradient(315deg, #f00 50%, #00f)'
180 | ], "generateCSS(linear, {25,25}, {50,50}, redToBlue)");
181 | deepEqual(ColorStops.generateCSS("linear", {x:50, y:50}, {x:25, y:25}, redToBlue), [
182 | "-webkit-gradient(linear, 50 50, 25 25, from(#f00), to(#00f))",
183 | "-webkit-linear-gradient(135deg, #f00, #00f 50%)",
184 | "-moz-linear-gradient(135deg, #f00, #00f 50%)",
185 | '-o-linear-gradient(135deg, #f00, #00f 50%)',
186 | 'linear-gradient(135deg, #f00, #00f 50%)'
187 | ], "generateCSS(linear, {50,50}, {25,25}, redToBlue)");
188 | deepEqual(ColorStops.generateCSS("linear", {x:25, y:25}, {x:50, y:50}, redToBlue, {x:0,y:0, width:100,height:100}), [
189 | "-webkit-gradient(linear, 25 25, 50 50, from(#f00), to(#00f))",
190 | "-webkit-linear-gradient(315deg, #f00 25%, #00f 50%)",
191 | "-moz-linear-gradient(315deg, #f00 25%, #00f 50%)",
192 | '-o-linear-gradient(315deg, #f00 25%, #00f 50%)',
193 | 'linear-gradient(315deg, #f00 25%, #00f 50%)'
194 | ], "generateCSS(linear, {25,25}, {50,50}, redToBlue, {0,0,100,100})");
195 | deepEqual(ColorStops.generateCSS("linear", {x:50, y:50}, {x:25, y:25}, redToBlue, {x:0,y:0, width:100,height:100}), [
196 | "-webkit-gradient(linear, 50 50, 25 25, from(#f00), to(#00f))",
197 | "-webkit-linear-gradient(135deg, #f00 50%, #00f 75%)",
198 | "-moz-linear-gradient(135deg, #f00 50%, #00f 75%)",
199 | '-o-linear-gradient(135deg, #f00 50%, #00f 75%)',
200 | 'linear-gradient(135deg, #f00 50%, #00f 75%)'
201 | ], "generateCSS(linear, {50,50}, {25,25}, redToBlue, {0,0,100,100})");
202 |
203 | deepEqual(ColorStops.generateCSS("linear", {x:25.25, y:0}, {x:50.5, y:0}, redToBlue), [
204 | "-webkit-gradient(linear, 25.25 0, 50.5 0, from(#f00), to(#00f))",
205 | "-webkit-linear-gradient(360deg, #f00 50%, #00f)",
206 | "-moz-linear-gradient(360deg, #f00 50%, #00f)",
207 | '-o-linear-gradient(360deg, #f00 50%, #00f)',
208 | 'linear-gradient(360deg, #f00 50%, #00f)'
209 | ], "generateCSS(linear, {25.25,0}, {50.5,0}, redToBlue)");
210 |
211 | // Position spacing
212 | var equallySpaced = [
213 | {position: 0, color: [255, 0, 0, 255]},
214 | {position: 0.5, color: [255, 0, 255, 255]},
215 | {position: 1, color: [0, 0, 255, 255]}
216 | ],
217 | notEquallySpaced = [
218 | {position: 0, color: [255, 0, 0, 255]},
219 | {position: 0.5, color: [255, 0, 255, 255]},
220 | {position: 0.75, color: [0, 0, 255, 255]}
221 | ];
222 | deepEqual(ColorStops.generateCSS("linear", {x:0, y:0}, {x:0, y:"100%"}, equallySpaced), [
223 | "-webkit-gradient(linear, 0 0, 0 100%, from(#f00), color-stop(0.5, #f0f), to(#00f))",
224 | "-webkit-linear-gradient(#f00, #f0f, #00f)",
225 | "-moz-linear-gradient(#f00, #f0f, #00f)",
226 | '-o-linear-gradient(#f00, #f0f, #00f)',
227 | 'linear-gradient(#f00, #f0f, #00f)'
228 | ], "generateCSS(linear, {0,0}, {0,100%}, equallySpaced)");
229 | deepEqual(ColorStops.generateCSS("linear", {x:0, y:0}, {x:0, y:"100%"}, notEquallySpaced), [
230 | "-webkit-gradient(linear, 0 0, 0 100%, from(#f00), color-stop(0.5, #f0f), color-stop(0.75, #00f))",
231 | "-webkit-linear-gradient(#f00, #f0f 50%, #00f 75%)",
232 | "-moz-linear-gradient(#f00, #f0f 50%, #00f 75%)",
233 | '-o-linear-gradient(#f00, #f0f 50%, #00f 75%)',
234 | 'linear-gradient(#f00, #f0f 50%, #00f 75%)'
235 | ], "generateCSS(linear, {0,0}, {0,100%}, notEquallySpaced)");
236 | });
237 | });
238 |
--------------------------------------------------------------------------------
/test/css-gradient-dropdown-menu.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpdecker/gradient-scanner/33136b64375667523d7b6f2338870617e1222d69/test/css-gradient-dropdown-menu.gif
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 | gradient-scanner tests
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/test/line-utils.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | $(document).ready(function(){
6 | module("LineUtils");
7 |
8 | test("getUnit", function() {
9 | expect(16);
10 | equals(LineUtils.getUnit(0), 0, "getUnit(0)");
11 | equals(LineUtils.getUnit("0"), 0, "getUnit('0')");
12 | equals(LineUtils.getUnit("0px"), 0, "getUnit(0px)");
13 | equals(LineUtils.getUnit("0%"), 0, "getUnit(0%)");
14 |
15 | equals(LineUtils.getUnit(1), "px", "getUnit(1)");
16 | equals(LineUtils.getUnit("1"), "px", "getUnit('1')");
17 | equals(LineUtils.getUnit("1px"), "px", "getUnit(1px)");
18 | equals(LineUtils.getUnit("1%"), "%", "getUnit(1%)");
19 |
20 | equals(LineUtils.getUnit(0.5), "px", "getUnit(0.5)");
21 | equals(LineUtils.getUnit("0.5"), "px", "getUnit('0.5')");
22 | equals(LineUtils.getUnit("0.5px"), "px", "getUnit(0.5px)");
23 | equals(LineUtils.getUnit("0.5%"), "%", "getUnit(0.5%)");
24 |
25 | equals(LineUtils.getUnit(1.5), "px", "getUnit(1.5)");
26 | equals(LineUtils.getUnit("1.5"), "px", "getUnit('1.5')");
27 | equals(LineUtils.getUnit("1.5px"), "px", "getUnit(1.5px)");
28 | equals(LineUtils.getUnit("1.5%"), "%", "getUnit(1.5%)");
29 | });
30 |
31 | test("containingRect", function() {
32 | expect(27);
33 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:0, y:1}), {x:0,y:0, width:1,height:1}, "containingRect({0,0}, {0,1})");
34 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:1, y:0}), {x:0,y:0, width:1,height:1}, "containingRect({0,0}, {1,0})");
35 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:1, y:1}), {x:0,y:0, width:1,height:1}, "containingRect({0,0}, {1,1})");
36 | deepEqual(LineUtils.containingRect({x:1, y:1}, {x:0, y:0}), {x:0,y:0, width:1,height:1}, "containingRect({1,1}, {0,0})");
37 | deepEqual(LineUtils.containingRect({x:1, y:0}, {x:0, y:1}), {x:0,y:0, width:1,height:1}, "containingRect({1,0}, {0,1})");
38 | deepEqual(LineUtils.containingRect({x:0, y:1}, {x:1, y:0}), {x:0,y:0, width:1,height:1}, "containingRect({0,1}, {1,0})");
39 |
40 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:0, y:10}, 10), {x:-5,y:0, width:10,height:10}, "containingRect({0,0}, {0,10}, 10)");
41 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:10, y:0}, 10), {x:0,y:-5, width:10,height:10}, "containingRect({0,0}, {10,0}, 10)");
42 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:10, y:10}, 10), {x:-3,y:-3, width:16,height:16}, "containingRect({0,0}, {10,10}, 10)");
43 | deepEqual(LineUtils.containingRect({x:10, y:10}, {x:0, y:0}, 10), {x:-3,y:-3, width:16,height:16}, "containingRect({10,10}, {0,0}, 10)");
44 | deepEqual(LineUtils.containingRect({x:10, y:0}, {x:0, y:10}, 10), {x:-3,y:-3, width:16,height:16}, "containingRect({10,0}, {0,10}, 10)");
45 | deepEqual(LineUtils.containingRect({x:0, y:10}, {x:10, y:0}, 10), {x:-3,y:-3, width:16,height:16}, "containingRect({0,10}, {10,0}, 10)");
46 |
47 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:0, y:"1%"}), {x:0,y:0, width:"1%",height:"1%"}, "containingRect({0,0}, {0,1%})");
48 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:"1%", y:0}), {x:0,y:0, width:"1%",height:"1%"}, "containingRect({0,0}, {1%,0})");
49 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:"1%", y:"1%"}), {x:0,y:0, width:"1%",height:"1%"}, "containingRect({0,0}, {1%,1%})");
50 | deepEqual(LineUtils.containingRect({x:"1%", y:"1%"}, {x:0, y:0}), {x:0,y:0, width:"1%",height:"1%"}, "containingRect({1%,1%}, {0,0})");
51 | deepEqual(LineUtils.containingRect({x:"1%", y:0}, {x:0, y:"1%"}), {x:0,y:0, width:"1%",height:"1%"}, "containingRect({1%,0}, {0,1%})");
52 | deepEqual(LineUtils.containingRect({x:0, y:"1%"}, {x:"1%", y:0}), {x:0,y:0, width:"1%",height:"1%"}, "containingRect({0,1%}, {1%,0})");
53 |
54 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:0, y:"10%"}, "10%"), {x:"-5%",y:0, width:"10%",height:"10%"}, "containingRect({0,0}, {0,10%}, 10%)");
55 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:"10%", y:0}, "10%"), {x:0,y:"-5%", width:"10%",height:"10%"}, "containingRect({0,0}, {10%,0}, 10%)");
56 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:"10%", y:"10%"}, "10%"), {x:"-3%",y:"-3%", width:"16%",height:"16%"}, "containingRect({0,0}, {10%,10%}, 10%)");
57 | deepEqual(LineUtils.containingRect({x:"10%", y:"10%"}, {x:0, y:0}, "10%"), {x:"-3%",y:"-3%", width:"16%",height:"16%"}, "containingRect({10%,10%}, {0,0}, 10%)");
58 | deepEqual(LineUtils.containingRect({x:"10%", y:0}, {x:0, y:"10%"}, "10%"), {x:"-3%",y:"-3%", width:"16%",height:"16%"}, "containingRect({10%,0}, {0,10%}, 10%)");
59 | deepEqual(LineUtils.containingRect({x:0, y:"10%"}, {x:"10%", y:0}, "10%"), {x:"-3%",y:"-3%", width:"16%",height:"16%"}, "containingRect({0,10%}, {10%,0}, 10%)");
60 |
61 | deepEqual(LineUtils.containingRect({x:0, y:0}, {x:0, y:1.5}), {x:0,y:0, width:1,height:1.5}, "containingRect({0,0}, {0,1.5})");
62 |
63 | deepEqual(LineUtils.containingRect({x:"1%", y:0}, {x:0, y:1}), NaN, "containingRect({1%,0}, {0,1})");
64 | deepEqual(LineUtils.containingRect({x:"1%", y:0}, {x:0, y:"1%"}, 1), NaN, "containingRect({1%,0}, {0,1%}, 1)");
65 | });
66 |
67 | test("relativeCoords", function() {
68 | expect(7);
69 | deepEqual(LineUtils.relativeCoords({x:0, y:0}, {x:0, y:0}), {x:0,y:0}, "relativeCoords({0,0}, {0,0})");
70 | deepEqual(LineUtils.relativeCoords({x:0, y:0}, {x:1, y:1}), {x:-1,y:-1}, "relativeCoords({0,0}, {1,1})");
71 | deepEqual(LineUtils.relativeCoords({x:1, y:1}, {x:0, y:0}), {x:1,y:1}, "relativeCoords({1,1}, {0,0})");
72 |
73 | deepEqual(LineUtils.relativeCoords({x:1, y:1}, {x:0.5, y:0.5}), {x:0.5,y:0.5}, "relativeCoords({1,1}, {0.5,0.5})");
74 |
75 | deepEqual(LineUtils.relativeCoords({x:0, y:0}, {x:"1%", y:"1%"}), {x:"-1%",y:"-1%"}, "relativeCoords({0,0}, {1%,1%})");
76 | deepEqual(LineUtils.relativeCoords({x:"1%", y:"1%"}, {x:0, y:0}), {x:"1%",y:"1%"}, "relativeCoords({1%,1%}, {0,0})");
77 |
78 | deepEqual(LineUtils.relativeCoords({x:"1%", y:0}, {x:0, y:1}), NaN, "relativeCoords({1%,0}, {0,1})");
79 | });
80 |
81 | test("distance", function() {
82 | // Testing conditional logic only, assuming that the math is correct
83 | expect(14);
84 | equals(LineUtils.distance({x:0, y:0}, {x:2, y:0}), 2, "distance({0,0}, {2,0})");
85 | equals(LineUtils.distance({x:2, y:0}, {x:0, y:0}), 2, "distance({2,0}, {0,0})");
86 |
87 | equals(LineUtils.distance({x:0, y:0}, {x:0, y:2}), 2, "distance({0,0}, {0,2})");
88 | equals(LineUtils.distance({x:0, y:2}, {x:0, y:0}), 2, "distance({0,2}, {0,0})");
89 |
90 | equals(LineUtils.distance({x:0, y:0}, {x:3, y:4}), 5, "distance({0,0}, {3,4})");
91 | equals(LineUtils.distance({x:3, y:4}, {x:0, y:0}), 5, "distance({3,4}, {0,0})");
92 |
93 | equals(LineUtils.distance({x:0, y:0}, {x:4, y:3}), 5, "distance({0,0}, {4,3})");
94 | equals(LineUtils.distance({x:4, y:3}, {x:0, y:0}), 5, "distance({4,3}, {0,0})");
95 |
96 | equals(LineUtils.distance({x:0, y:0}, {x:"1%", y:0}), 1, "distance({0,0}, {1%,0})");
97 | equals(LineUtils.distance({x:"1%", y:0}, {x:0, y:0}), 1, "distance({1%,0}, {0,0})");
98 |
99 | equals(LineUtils.distance({x:0, y:0}, {x:0, y:"1%"}), 1, "distance({0,0}, {0,1%})");
100 | equals(LineUtils.distance({x:0, y:"1%"}, {x:0, y:0}), 1, "distance({0,1%}, {0,0})");
101 |
102 | equals(LineUtils.distance({x:0, y:2.5}, {x:0, y:0}), 2.5, "distance({0,2.5}, {0,0})");
103 |
104 | deepEqual(LineUtils.distance({x:"1%", y:"1%"}, {x:1, y:0}), NaN, "distance({1%,1%}, {1,0})");
105 | });
106 |
107 | test("percentageOfRay", function() {
108 | expect(17);
109 |
110 | equals(LineUtils.percentageOfRay({x:0, y:0}, {x:0, y:1}, {x:0,y:0, width:1,height:1}), 1, "percentageOfRay({0,0}, {0,1}, {0,0,1,1})");
111 | equals(LineUtils.percentageOfRay({x:0, y:0}, {x:1, y:0}, {x:0,y:0, width:1,height:1}), 1, "percentageOfRay({0,0}, {1,0}, {0,0,1,1})");
112 | equals(LineUtils.percentageOfRay({x:0, y:0}, {x:1, y:1}, {x:0,y:0, width:1,height:1}), 1, "percentageOfRay({0,0}, {1,1}, {0,0,1,1})");
113 | equals(LineUtils.percentageOfRay({x:1, y:1}, {x:0, y:0}, {x:0,y:0, width:1,height:1}), 1, "percentageOfRay({1,1}, {0,0}, {0,0,1,1})");
114 | equals(LineUtils.percentageOfRay({x:1, y:0}, {x:0, y:1}, {x:0,y:0, width:1,height:1}), 1, "percentageOfRay({1,0}, {0,1}, {0,0,1,1})");
115 | equals(LineUtils.percentageOfRay({x:0, y:1}, {x:1, y:0}, {x:0,y:0, width:1,height:1}), 1, "percentageOfRay({0,1}, {1,0}, {0,0,1,1})");
116 |
117 | // No intercepts
118 | equals(LineUtils.percentageOfRay({x:0, y:10}, {x:10, y:10}, {x:0,y:0, width:1,height:1}), 0, "percentageOfRay({0,10}, {10,10}, {0,0,1,1})");
119 | equals(LineUtils.percentageOfRay({x:10, y:0}, {x:10, y:10}, {x:0,y:0, width:1,height:1}), 0, "percentageOfRay({10,0}, {10,10}, {0,0,1,1})");
120 |
121 | // Not connected to the edge cases
122 | equals(LineUtils.percentageOfRay({x:1, y:2}, {x:2, y:3}, {x:0,y:0, width:10,height:10}), 0.125, "percentageOfRay({1,2}, {2,3}, {0,0,10,10})");
123 | equals(LineUtils.percentageOfRay({x:2, y:3}, {x:1, y:2}, {x:0,y:0, width:10,height:10}), 0.5, "percentageOfRay({2,3}, {1,2}, {0,0,10,10})");
124 |
125 | equals(LineUtils.percentageOfRay({x:8, y:2}, {x:6, y:4}, {x:0,y:0, width:10,height:10}), 0.25, "percentageOfRay({8,2}, {6,4}, {0,0,10,10})");
126 | equals(LineUtils.percentageOfRay({x:8, y:2}, {x:9, y:1}, {x:0,y:0, width:10,height:10}), 0.5, "percentageOfRay({2,8}, {9,1}, {0,0,10,10})");
127 |
128 | equals(LineUtils.percentageOfRay({x:8, y:2}, {x:6, y:4}, {x:-1,y:-1, width:11,height:11}), 0.25, "percentageOfRay({8,2}, {6,4}, {-1,-1,11,11})");
129 | equals(LineUtils.percentageOfRay({x:8, y:2}, {x:9, y:1}, {x:-1,y:-1, width:11,height:11}), 0.5, "percentageOfRay({2,8}, {9,1}, {-1,-1,11,11})");
130 |
131 | // Units
132 | equals(LineUtils.percentageOfRay({x:0, y:0}, {x:0, y:"1%"}, {x:0,y:0, width:"1%",height:"1%"}), 1, "percentageOfRay({0,0}, {0,1%}, {0,0,1%,1%})");
133 | equals(LineUtils.percentageOfRay({x:0, y:0}, {x:"1%", y:"1%"}, {x:0,y:0, width:"1%",height:"1%"}), 1, "percentageOfRay({0,0}, {1%,1%}, {0,0,1%,1%})");
134 | deepEqual(LineUtils.percentageOfRay({x:0, y:0}, {x:0, y:"1%"}, {x:0,y:0, width:1,height:1}), NaN, "percentageOfRay({0,0}, {0,1%}, {0,0,1,1})");
135 | });
136 |
137 |
138 | test("isOnEdge", function() {
139 | expect(29);
140 |
141 | equals(LineUtils.isOnEdge({x:0, y:0}, {x:0,y:0, width:10,height:10}), true, "isOnEdge({0,0}, {10,10})");
142 | equals(LineUtils.isOnEdge({x:1, y:1}, {x:0,y:0, width:10,height:10}), false, "isOnEdge({1,1}, {10,10})");
143 | equals(LineUtils.isOnEdge({x:1, y:0}, {x:0,y:0, width:10,height:10}), true, "isOnEdge({1,0}, {10,10})");
144 | equals(LineUtils.isOnEdge({x:0, y:1}, {x:0,y:0, width:10,height:10}), true, "isOnEdge({0,1}, {10,10})");
145 | equals(LineUtils.isOnEdge({x:10, y:10}, {x:0,y:0, width:10,height:10}), true, "isOnEdge({10,10}, {10,10})");
146 | equals(LineUtils.isOnEdge({x:10, y:0}, {x:0,y:0, width:10,height:10}), true, "isOnEdge({10,0}, {10,10})");
147 | equals(LineUtils.isOnEdge({x:0, y:10}, {x:0,y:0, width:10,height:10}), true, "isOnEdge({0,10}, {10,10})");
148 | equals(LineUtils.isOnEdge({x:-10, y:-10}, {x:0,y:0, width:10,height:10}), false, "isOnEdge({-10,-10}, {10,10})");
149 | equals(LineUtils.isOnEdge({x:-10, y:0}, {x:0,y:0, width:10,height:10}), false, "isOnEdge({-10,0}, {10,10})");
150 | equals(LineUtils.isOnEdge({x:0, y:-10}, {x:0,y:0, width:10,height:10}), false, "isOnEdge({0,-10}, {10,10})");
151 | equals(LineUtils.isOnEdge({x:20, y:20}, {x:0,y:0, width:10,height:10}), false, "isOnEdge({20,20}, {10,10})");
152 | equals(LineUtils.isOnEdge({x:20, y:0}, {x:0,y:0, width:10,height:10}), false, "isOnEdge({20,0}, {10,10})");
153 | equals(LineUtils.isOnEdge({x:0, y:20}, {x:0,y:0, width:10,height:10}), false, "isOnEdge({0,20}, {10,10})");
154 |
155 | equals(LineUtils.isOnEdge({x:0, y:0}, {x:0,y:0, width:"10%",height:"10%"}), true, "isOnEdge({0,0}, {10%,10%})");
156 | equals(LineUtils.isOnEdge({x:"1%", y:"1%"}, {x:0,y:0, width:"10%",height:"10%"}), false, "isOnEdge({1%,1%}, {10%,10%})");
157 | equals(LineUtils.isOnEdge({x:"1%", y:0}, {x:0,y:0, width:"10%",height:"10%"}), true, "isOnEdge({1%,0}, {10%,10%})");
158 | equals(LineUtils.isOnEdge({x:0, y:"1%"}, {x:0,y:0, width:"10%",height:"10%"}), true, "isOnEdge({0,1%}, {10%,10%})");
159 | equals(LineUtils.isOnEdge({x:"10%", y:"10%"}, {x:0,y:0, width:"10%",height:"10%"}), true, "isOnEdge({10%,1%0%}, {10%,10%})");
160 | equals(LineUtils.isOnEdge({x:"10%", y:0}, {x:0,y:0, width:"10%",height:"10%"}), true, "isOnEdge({10%,0}, {10%,10%})");
161 | equals(LineUtils.isOnEdge({x:0, y:"10%"}, {x:0,y:0, width:"10%",height:"10%"}), true, "isOnEdge({0,10%}, {10%,10%})");
162 | equals(LineUtils.isOnEdge({x:"-10%", y:"-10%"}, {x:0,y:0, width:"10%",height:"10%"}), false, "isOnEdge({-10%,-10%}, {10%,10%})");
163 | equals(LineUtils.isOnEdge({x:"-10%", y:0}, {x:0,y:0, width:"10%",height:"10%"}), false, "isOnEdge({-10%,0}, {10%,10%})");
164 | equals(LineUtils.isOnEdge({x:0, y:"-10%"}, {x:0,y:0, width:"10%",height:"10%"}), false, "isOnEdge({0,-10%}, {10%,10%})");
165 | equals(LineUtils.isOnEdge({x:"20%", y:"20%"}, {x:0,y:0, width:"10%",height:"10%"}), false, "isOnEdge({20%,20%}, {10%,10%})");
166 | equals(LineUtils.isOnEdge({x:"20%", y:0}, {x:0,y:0, width:"10%",height:"10%"}), false, "isOnEdge({20%,0}, {10%,10%})");
167 | equals(LineUtils.isOnEdge({x:0, y:"20%"}, {x:0,y:0, width:"10%",height:"10%"}), false, "isOnEdge({0,20%}, {10%,10%})");
168 |
169 | equals(LineUtils.isOnEdge({x:10.5, y:10.5}, {x:0,y:0, width:10.5,height:10.5}), true, "isOnEdge({10.5,10.5}, {10.5,10.5})");
170 | equals(LineUtils.isOnEdge({x:1.5, y:1.5}, {x:0,y:0, width:10.5,height:10.5}), false, "isOnEdge({1.5,1.5}, {10.5,10.5})");
171 |
172 | deepEqual(LineUtils.isOnEdge({x:"1%", y:0}, {x:0, y:1, width:10,height:10}), NaN, "isOnEdge({1%,0}, {0,1})");
173 | });
174 |
175 | test("gradientPoints", function() {
176 | expect(17);
177 |
178 | deepEqual(
179 | LineUtils.gradientPoints({x:0, y:0}, {x:0, y:1}, {x:0,y:0, width:1,height:1}),
180 | {start: {x:0.5, y:0}, startOff: 0, end: {x:0.5, y:1}, scale: 1},
181 | "gradientPoints({0,0}, {0,1}, {0,0,1,1})");
182 | deepEqual(
183 | LineUtils.gradientPoints({x:0, y:0}, {x:1, y:0}, {x:0,y:0, width:1,height:1}),
184 | {start: {x:0, y:0.5}, startOff: 0, end: {x:1, y:0.5}, scale: 1},
185 | "gradientPoints({0,0}, {1,0}, {0,0,1,1})");
186 | deepEqual(
187 | LineUtils.gradientPoints({x:0, y:0}, {x:1, y:1}, {x:0,y:0, width:1,height:1}),
188 | {start: {x:0, y:0}, startOff: 0, end: {x:1, y:1}, scale: 1},
189 | "gradientPoints({0,0}, {1,1}, {0,0,1,1})");
190 | deepEqual(
191 | LineUtils.gradientPoints({x:1, y:1}, {x:0, y:0}, {x:0,y:0, width:1,height:1}),
192 | {start: {x:1, y:1}, startOff: 0, end: {x:0, y:0}, scale: 1},
193 | "gradientPoints({1,1}, {0,0}, {0,0,1,1})");
194 | deepEqual(
195 | LineUtils.gradientPoints({x:1, y:0}, {x:0, y:1}, {x:0,y:0, width:1,height:1}),
196 | {start: {x:1, y:0}, startOff: 0, end: {x:0, y:1}, scale: 1},
197 | "gradientPoints({1,0}, {0,1}, {0,0,1,1})");
198 | deepEqual(
199 | LineUtils.gradientPoints({x:0, y:1}, {x:1, y:0}, {x:0,y:0, width:1,height:1}),
200 | {start: {x:0, y:1}, startOff: 0, end: {x:1, y:0}, scale: 1},
201 | "gradientPoints({0,1}, {1,0}, {0,0,1,1})");
202 |
203 | // No intercepts
204 | deepEqual(
205 | LineUtils.gradientPoints({x:0, y:10}, {x:10, y:10}, {x:0,y:0, width:1,height:1}),
206 | {start: {x:0,y:0.5}, startOff: 0, end: {x:1,y:0.5}, scale: 10},
207 | "gradientPoints({0,10}, {10,10}, {0,0,1,1})");
208 | deepEqual(
209 | LineUtils.gradientPoints({x:10, y:0}, {x:10, y:10}, {x:0,y:0, width:1,height:1}),
210 | {start: {x:0.5,y:0}, startOff: 0, end: {x:0.5,y:1}, scale: 10},
211 | "gradientPoints({10,0}, {10,10}, {0,0,1,1})");
212 |
213 | // Not connected to the edge cases
214 | deepEqual(
215 | LineUtils.gradientPoints({x:1, y:2}, {x:2, y:3}, {x:0,y:0, width:10,height:10}),
216 | {start: {x:0, y:0}, startOff: 0.15, end: {x:10, y:10}, scale: 0.1},
217 | "gradientPoints({1,2}, {2,3}, {0,0,10,10})");
218 | deepEqual(
219 | LineUtils.gradientPoints({x:2, y:3}, {x:1, y:2}, {x:0,y:0, width:10,height:10}),
220 | {start: {x:10, y:10}, startOff: 0.75, end: {x:0, y:0}, scale: 0.1},
221 | "gradientPoints({2,3}, {1,2}, {0,0,10,10})");
222 |
223 | deepEqual(
224 | LineUtils.gradientPoints({x:9, y:1}, {x:8, y:2}, {x:0,y:0, width:10,height:10}),
225 | {start: {x:10, y:0}, startOff: 0.1, end: {x:0, y:10}, scale: 0.1},
226 | "gradientPoints({9,1}, {8,2}, {0,0,10,10})");
227 | deepEqual(
228 | LineUtils.gradientPoints({x:8, y:2}, {x:9, y:1}, {x:0,y:0, width:10,height:10}),
229 | {start: {x:0, y:10}, startOff: 0.8, end: {x:10, y:0}, scale: 0.1},
230 | "gradientPoints({2,8}, {9,1}, {0,0,10,10})");
231 |
232 | deepEqual(
233 | LineUtils.gradientPoints({x:9, y:1}, {x:8, y:2}, {x:-1,y:-1, width:11,height:11}),
234 | {start: {x:10, y:-1}, startOff: 0.13636363636363635, end: {x:-1, y:10}, scale: 0.09090909090909091},
235 | "gradientPoints({9,1}, {8,2}, {-1,-1,11,11})");
236 | deepEqual(
237 | LineUtils.gradientPoints({x:8, y:2}, {x:9, y:1}, {x:-1,y:-1, width:11,height:11}),
238 | {start: {x:-1, y:10}, startOff: 0.7727272727272727, end: {x:10, y:-1}, scale: 0.09090909090909091},
239 | "gradientPoints({2,8}, {9,1}, {-1,-1,11,11})");
240 |
241 | // Units
242 | deepEqual(
243 | LineUtils.gradientPoints({x:0, y:0}, {x:0, y:"1%"}, {x:0,y:0, width:"1%",height:"1%"}),
244 | {start: {x:"0.5%", y:0}, startOff: 0, end: {x:"0.5%", y:"1%"}, scale: 1},
245 | "gradientPoints({0,0}, {0,1%}, {0,0,1%,1%})");
246 | deepEqual(
247 | LineUtils.gradientPoints({x:0, y:0}, {x:"1%", y:"1%"}, {x:0,y:0, width:"1%",height:"1%"}),
248 | {start: {x:0, y:0}, startOff: 0, end: {x:"1%", y:"1%"}, scale: 1},
249 | "gradientPoints({0,0}, {1%,1%}, {0,0,1%,1%})");
250 | deepEqual(
251 | LineUtils.gradientPoints({x:0, y:0}, {x:0, y:"1%"}, {x:0,y:0, width:1,height:1}),
252 | NaN,
253 | "gradientPoints({0,0}, {0,1%}, {0,0,1,1})");
254 |
255 | // TODO : Angles that resolve to something other than a direct corner
256 | });
257 |
258 | test("lineIntercepts", function() {
259 | expect(29);
260 |
261 | deepEqual(LineUtils.lineIntercepts({x:0, y:0}, {x:0, y:1}, {x:0,y:0, width:1,height:1}), [{x:0, y:0}, {x:0, y:1}], "lineIntercepts({0,0}, {0,1}, {0,0,1,1})");
262 | deepEqual(LineUtils.lineIntercepts({x:0, y:0}, {x:1, y:0}, {x:0,y:0, width:1,height:1}), [{x:0, y:0}, {x:1, y:0}], "lineIntercepts({0,0}, {1,0}, {0,0,1,1})");
263 | deepEqual(LineUtils.lineIntercepts({x:0, y:0}, {x:1, y:1}, {x:0,y:0, width:1,height:1}), [{x:0, y:0}, {x:1, y:1}], "lineIntercepts({0,0}, {1,1}, {0,0,1,1})");
264 | deepEqual(LineUtils.lineIntercepts({x:1, y:1}, {x:0, y:0}, {x:0,y:0, width:1,height:1}), [{x:1, y:1}, {x:0, y:0}], "lineIntercepts({1,1}, {0,0}, {0,0,1,1})");
265 | deepEqual(LineUtils.lineIntercepts({x:1, y:0}, {x:0, y:1}, {x:0,y:0, width:1,height:1}), [{x:1, y:0}, {x:0, y:1}], "lineIntercepts({1,0}, {0,1}, {0,0,1,1})");
266 | deepEqual(LineUtils.lineIntercepts({x:0, y:1}, {x:1, y:0}, {x:0,y:0, width:1,height:1}), [{x:0, y:1}, {x:1, y:0}], "lineIntercepts({0,1}, {1,0}, {0,0,1,1})");
267 |
268 | // No intercepts
269 | deepEqual(LineUtils.lineIntercepts({x:0, y:10}, {x:10, y:10}, {x:0,y:0, width:1,height:1}), [], "lineIntercepts({0,10}, {10,10}, {0,0,1,1})");
270 | deepEqual(LineUtils.lineIntercepts({x:10, y:0}, {x:10, y:10}, {x:0,y:0, width:1,height:1}), [], "lineIntercepts({10,0}, {10,10}, {0,0,1,1})");
271 |
272 | // Not connected to the edge cases
273 | deepEqual(LineUtils.lineIntercepts({x:1, y:2}, {x:2, y:3}, {x:0,y:0, width:10,height:10}), [{x:0, y:1}, {x:9, y:10}], "lineIntercepts({1,2}, {2,3}, {0,0,10,10})");
274 | deepEqual(LineUtils.lineIntercepts({x:2, y:3}, {x:1, y:2}, {x:0,y:0, width:10,height:10}), [{x:9, y:10}, {x:0, y:1}], "lineIntercepts({2,3}, {1,2}, {0,0,10,10})");
275 |
276 | deepEqual(LineUtils.lineIntercepts({x:9, y:1}, {x:8, y:2}, {x:0,y:0, width:10,height:10}), [{x:10, y:0}, {x:0, y:10}], "lineIntercepts({9,1}, {8,2}, {0,0,10,10})");
277 | deepEqual(LineUtils.lineIntercepts({x:8, y:2}, {x:9, y:1}, {x:0,y:0, width:10,height:10}), [{x:0, y:10}, {x:10, y:0}], "lineIntercepts({2,8}, {9,1}, {0,0,10,10})");
278 |
279 | deepEqual(LineUtils.lineIntercepts({x:9, y:1}, {x:8, y:2}, {x:-1,y:-1, width:11,height:11}), [{x:10, y:0}, {x:0, y:10}], "lineIntercepts({9,1}, {8,2}, {-1,-1,11,11})");
280 | deepEqual(LineUtils.lineIntercepts({x:8, y:2}, {x:9, y:1}, {x:-1,y:-1, width:11,height:11}), [{x:0, y:10}, {x:10, y:0}], "lineIntercepts({2,8}, {9,1}, {-1,-1,11,11})");
281 |
282 | // Ray mode
283 | deepEqual(LineUtils.lineIntercepts({x:0, y:0}, {x:0, y:1}, {x:0,y:0, width:1,height:1}, true), [{x:0, y:0}, {x:0, y:1}], "lineIntercepts({0,0}, {0,1}, {0,0,1,1}, true)");
284 | deepEqual(LineUtils.lineIntercepts({x:0, y:0}, {x:1, y:0}, {x:0,y:0, width:1,height:1}, true), [{x:0, y:0}, {x:1, y:0}], "lineIntercepts({0,0}, {1,0}, {0,0,1,1}, true)");
285 | deepEqual(LineUtils.lineIntercepts({x:0, y:0}, {x:1, y:1}, {x:0,y:0, width:1,height:1}, true), [{x:0, y:0}, {x:1, y:1}], "lineIntercepts({0,0}, {1,1}, {0,0,1,1}, true)");
286 | deepEqual(LineUtils.lineIntercepts({x:1, y:1}, {x:0, y:0}, {x:0,y:0, width:1,height:1}, true), [{x:1, y:1}, {x:0, y:0}], "lineIntercepts({1,1}, {0,0}, {0,0,1,1}, true)");
287 | deepEqual(LineUtils.lineIntercepts({x:1, y:0}, {x:0, y:1}, {x:0,y:0, width:1,height:1}, true), [{x:1, y:0}, {x:0, y:1}], "lineIntercepts({1,0}, {0,1}, {0,0,1,1}, true)");
288 | deepEqual(LineUtils.lineIntercepts({x:0, y:1}, {x:1, y:0}, {x:0,y:0, width:1,height:1}, true), [{x:0, y:1}, {x:1, y:0}], "lineIntercepts({0,1}, {1,0}, {0,0,1,1}, true)");
289 |
290 | deepEqual(LineUtils.lineIntercepts({x:1, y:2}, {x:2, y:3}, {x:0,y:0, width:10,height:10}, true), [{x:9, y:10}], "lineIntercepts({1,2}, {2,3}, {0,0,10,10}, true)");
291 | deepEqual(LineUtils.lineIntercepts({x:2, y:3}, {x:1, y:2}, {x:0,y:0, width:10,height:10}, true), [{x:0, y:1}], "lineIntercepts({2,3}, {1,2}, {0,0,10,10}, true)");
292 |
293 | deepEqual(LineUtils.lineIntercepts({x:9, y:1}, {x:8, y:2}, {x:0,y:0, width:10,height:10}, true), [{x:0, y:10}], "lineIntercepts({9,1}, {8,2}, {0,0,10,10}, true)");
294 | deepEqual(LineUtils.lineIntercepts({x:8, y:2}, {x:9, y:1}, {x:0,y:0, width:10,height:10}, true), [{x:10, y:0}], "lineIntercepts({2,8}, {9,1}, {0,0,10,10}, true)");
295 |
296 | // Percents
297 | deepEqual(LineUtils.lineIntercepts({x:0, y:0}, {x:0, y:"1.5%"}, {x:0,y:0, width:"1.5%",height:"1.5%"}), [{x:0, y:0}, {x:0, y:"1.5%"}], "lineIntercepts({0,0}, {0,1.5%}, {0,0,1.5%,1.5%})");
298 | deepEqual(LineUtils.lineIntercepts({x:0, y:0}, {x:"1.5%", y:"1.5%"}, {x:0,y:0, width:"1.5%",height:"1.5%"}), [{x:0, y:0}, {x:"1.5%", y:"1.5%"}], "lineIntercepts({0,0}, {1.5%,1.5%}, {0,0,1.5%,1.5%})");
299 |
300 | // Units
301 | deepEqual(LineUtils.lineIntercepts({x:0, y:0}, {x:0, y:"1%"}, {x:0,y:0, width:"1%",height:"1%"}), [{x:0, y:0}, {x:0, y:"1%"}], "lineIntercepts({0,0}, {0,1%}, {0,0,1%,1%})");
302 | deepEqual(LineUtils.lineIntercepts({x:0, y:0}, {x:"1%", y:"1%"}, {x:0,y:0, width:"1%",height:"1%"}), [{x:0, y:0}, {x:"1%", y:"1%"}], "lineIntercepts({0,0}, {1%,1%}, {0,0,1%,1%})");
303 | deepEqual(LineUtils.lineIntercepts({x:0, y:0}, {x:0, y:"1%"}, {x:0,y:0, width:1,height:1}), NaN, "lineIntercepts({0,0}, {0,1%}, {0,0,1,1})");
304 | });
305 |
306 | test("slopeInRads", function() {
307 | // Testing conditional logic only, assuming that the math is correct
308 | expect(19);
309 | equals(LineUtils.slopeInRads({x:0, y:0}, {x:1, y:0}), 0, "slopeInRads({0,0}, {1,0})");
310 | equals(LineUtils.slopeInRads({x:1, y:0}, {x:0, y:0}), Math.PI, "slopeInRads({1,0}, {0,0})");
311 |
312 | equals(LineUtils.slopeInRads({x:0, y:0}, {x:0, y:1}), Math.PI/2, "slopeInRads({0,0}, {0,1})");
313 | equals(LineUtils.slopeInRads({x:0, y:1}, {x:0, y:0}), 3*Math.PI/2, "slopeInRads({0,1}, {0,0})");
314 |
315 | equals(LineUtils.slopeInRads({x:0, y:0}, {x:1, y:1}), Math.PI/4, "slopeInRads({0,0}, {1,1})");
316 | equals(LineUtils.slopeInRads({x:1, y:1}, {x:0, y:0}), 5*Math.PI/4, "slopeInRads({1,1}, {0,0})");
317 |
318 | equals(LineUtils.slopeInRads({x:0, y:0}, {x:"1%", y:0}), 0, "slopeInRads({0,0}, {1%,0})");
319 | equals(LineUtils.slopeInRads({x:"1%", y:0}, {x:0, y:0}), Math.PI, "slopeInRads({1%,0}, {0,0})");
320 |
321 | equals(LineUtils.slopeInRads({x:0, y:0}, {x:0, y:"1%"}), Math.PI/2, "slopeInRads({0,0}, {0,1%})");
322 | equals(LineUtils.slopeInRads({x:0, y:"1%"}, {x:0, y:0}), 3*Math.PI/2, "slopeInRads({0,1%}, {0,0})");
323 |
324 | equals(LineUtils.slopeInRads({x:0, y:0}, {x:"1%", y:"1%"}), Math.PI/4, "slopeInRads({0,0}, {1%,1%})");
325 | equals(LineUtils.slopeInRads({x:"1%", y:"1%"}, {x:0, y:0}), 5*Math.PI/4, "slopeInRads({1%,1%}, {0,0})");
326 |
327 | equals(LineUtils.slopeInRads({x:0, y:0}, {x:1.5, y:0}), 0, "slopeInRads({0,0}, {1.5,0})");
328 | equals(LineUtils.slopeInRads({x:1.5, y:0}, {x:0, y:0}), Math.PI, "slopeInRads({1.5,0}, {0,0})");
329 |
330 | equals(LineUtils.slopeInRads({x:0, y:0}, {x:0, y:1.5}), Math.PI/2, "slopeInRads({0,0}, {0,1.5})");
331 | equals(LineUtils.slopeInRads({x:0, y:1.5}, {x:0, y:0}), 3*Math.PI/2, "slopeInRads({0,1.5}, {0,0})");
332 |
333 | equals(LineUtils.slopeInRads({x:0, y:0}, {x:1.5, y:1.5}), Math.PI/4, "slopeInRads({0,0}, {1.5,1.5})");
334 | equals(LineUtils.slopeInRads({x:1.5, y:1.5}, {x:0, y:0}), 5*Math.PI/4, "slopeInRads({1.5,1.5}, {0,0})");
335 |
336 | deepEqual(LineUtils.slopeInRads({x:"1%", y:"1%"}, {x:1, y:0}), NaN, "slopeInRads({1%,1%}, {1,0})");
337 | });
338 | });
339 |
--------------------------------------------------------------------------------
/test/testData.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Kevin Decker (http://www.incaseofstairs.com/)
3 | * See LICENSE for license information
4 | */
5 | $(document).ready(function() {
6 | function drawGradient(x, y, r1, r2) {
7 | var radialGradient = context.createRadialGradient(x, y, r1, x, y, r2);
8 | radialGradient.addColorStop(0, "red");
9 | radialGradient.addColorStop(1, "green");
10 | context.fillStyle = radialGradient;
11 |
12 | context.beginPath();
13 | context.arc(x, y, r2, 0, 360, false);
14 | context.fill();
15 | context.closePath();
16 | }
17 | var canvas = document.createElement("canvas"),
18 | context = canvas.getContext("2d");
19 | canvas.width = canvas.height = 600;
20 |
21 | context.fillRect(0,0,50,50);
22 |
23 | var linearGradient = context.createLinearGradient(100, 100, 100, 600);
24 | linearGradient.addColorStop(0.0, "black");
25 | linearGradient.addColorStop(0.5, "red");
26 | linearGradient.addColorStop(1.0, "blue");
27 | context.fillStyle = linearGradient;
28 | context.fillRect(100,100,500,600);
29 |
30 | drawGradient(75, 75, 25, 55);
31 | drawGradient(225, 75, 5, 55);
32 |
33 | var img = document.createElement("img");
34 | $(img).load(function() {
35 | context.drawImage(img, 100, 100);
36 |
37 | GradientScanner.loadImage(canvas, "example image");
38 | });
39 |
40 | img.src = "test/css-gradient-dropdown-menu.gif";
41 |
42 | $(document).bind("imageLoaded", function lineSeed() {
43 | $(this).unbind("imageLoaded", lineSeed);
44 |
45 | var offset = $("#imageDisplay").offset();
46 |
47 | var mousedown = jQuery.Event("mousedown");
48 | mousedown.which = 1;
49 |
50 | mousedown.pageX = 189;
51 | mousedown.pageY = 503;
52 |
53 | mousedown.pageX = 232;
54 | mousedown.pageY = 81;
55 |
56 | mousedown.pageX = 366;
57 | mousedown.pageY = 100;
58 | /*
59 | mousedown.pageX = 225;
60 | mousedown.pageY = 73;
61 | /*
62 | mousedown.pageX = 586;
63 | mousedown.pageY = 100;
64 |
65 | mousedown.pageX = 386;
66 | mousedown.pageY = 406;
67 | */
68 | mousedown.pageX += offset.left;
69 | mousedown.pageY += offset.top;
70 |
71 | var mousemove = jQuery.Event("mousemove");
72 | mousemove.which = 1;
73 |
74 | mousemove.pageX = 189;
75 | mousemove.pageY = 142;
76 |
77 | mousemove.pageX = 79;
78 | mousemove.pageY = 81;
79 |
80 | mousemove.pageX = 366;
81 | mousemove.pageY = 600;
82 | /*
83 | mousemove.pageX = 67;
84 | mousemove.pageY = 73;
85 | /*
86 | mousemove.pageX = 586;
87 | mousemove.pageY = 600;
88 |
89 | mousemove.pageX = 224;
90 | mousemove.pageY = 257;
91 | */
92 | mousemove.pageX += offset.left;
93 | mousemove.pageY += offset.top;
94 |
95 | $("#imageDisplay")
96 | .trigger(mousedown)
97 | .trigger(mousemove)
98 | .trigger(jQuery.Event("mouseup"));
99 | });
100 | });
101 |
--------------------------------------------------------------------------------