├── .gitattributes
├── index.html
├── .gitignore
├── css
└── styles.css
└── js
└── app.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.doc diff=astextplain
2 | *.DOC diff=astextplain
3 | *.docx diff=astextplain
4 | *.DOCX diff=astextplain
5 | *.dot diff=astextplain
6 | *.DOT diff=astextplain
7 | *.pdf diff=astextplain
8 | *.PDF diff=astextplain
9 | *.rtf diff=astextplain
10 | *.RTF diff=astextplain
11 |
12 | *.jpg binary
13 | *.png binary
14 | *.gif binary
15 |
16 | *.cs text=auto diff=csharp
17 | *.vb text=auto
18 | *.c text=auto
19 | *.cpp text=auto
20 | *.cxx text=auto
21 | *.h text=auto
22 | *.hxx text=auto
23 | *.py text=auto
24 | *.rb text=auto
25 | *.java text=auto
26 | *.html text=auto
27 | *.htm text=auto
28 | *.css text=auto
29 | *.scss text=auto
30 | *.sass text=auto
31 | *.less text=auto
32 | *.js text=auto
33 | *.lisp text=auto
34 | *.clj text=auto
35 | *.sql text=auto
36 | *.php text=auto
37 | *.lua text=auto
38 | *.m text=auto
39 | *.asm text=auto
40 | *.erl text=auto
41 | *.fs text=auto
42 | *.fsx text=auto
43 | *.hs text=auto
44 |
45 | *.csproj text=auto merge=union
46 | *.vbproj text=auto merge=union
47 | *.fsproj text=auto merge=union
48 | *.dbproj text=auto merge=union
49 | *.sln text=auto eol=crlf merge=union
50 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | koolaj
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | no-frills collage maker
13 | koolaj
14 |
15 |
16 | drag and drop your pictures here ▶
17 | or click this to browse
18 |
19 |
20 |
21 |
22 |
27 |
32 |
randomize
33 |
save
34 |
clear
35 |
36 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
![Image]()
47 |
48 |
Back
49 |
50 |
51 | It looks like your browser has trouble viewing koolaj.
52 | How about trying this on Chrome or Firefox, or the new IE 10?
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | #################
3 | ## Eclipse
4 | #################
5 |
6 | *.pydevproject
7 | .project
8 | .metadata
9 | bin/**
10 | tmp/**
11 | tmp/**/*
12 | *.tmp
13 | *.bak
14 | *.swp
15 | *~.nib
16 | local.properties
17 | .classpath
18 | .settings/
19 | .loadpath
20 |
21 | # External tool builders
22 | .externalToolBuilders/
23 |
24 | # Locally stored "Eclipse launch configurations"
25 | *.launch
26 |
27 | # CDT-specific
28 | .cproject
29 |
30 | # PDT-specific
31 | .buildpath
32 |
33 |
34 | #################
35 | ## Visual Studio
36 | #################
37 |
38 | ## Ignore Visual Studio temporary files, build results, and
39 | ## files generated by popular Visual Studio add-ons.
40 |
41 | # User-specific files
42 | *.suo
43 | *.user
44 | *.sln.docstates
45 |
46 | # Build results
47 | **/[Dd]ebug/
48 | **/[Rr]elease/
49 | *_i.c
50 | *_p.c
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.vspscc
65 | .builds
66 | **/*.dotCover
67 |
68 | ## TODO: If you have NuGet Package Restore enabled, uncomment this
69 | #**/packages/
70 |
71 | # Visual C++ cache files
72 | ipch/
73 | *.aps
74 | *.ncb
75 | *.opensdf
76 | *.sdf
77 |
78 | # Visual Studio profiler
79 | *.psess
80 | *.vsp
81 |
82 | # ReSharper is a .NET coding add-in
83 | _ReSharper*
84 |
85 | # Installshield output folder
86 | [Ee]xpress
87 |
88 | # DocProject is a documentation generator add-in
89 | DocProject/buildhelp/
90 | DocProject/Help/*.HxT
91 | DocProject/Help/*.HxC
92 | DocProject/Help/*.hhc
93 | DocProject/Help/*.hhk
94 | DocProject/Help/*.hhp
95 | DocProject/Help/Html2
96 | DocProject/Help/html
97 |
98 | # Click-Once directory
99 | publish
100 |
101 | # Others
102 | [Bb]in
103 | [Oo]bj
104 | sql
105 | TestResults
106 | *.Cache
107 | ClientBin
108 | stylecop.*
109 | ~$*
110 | *.dbmdl
111 | Generated_Code #added for RIA/Silverlight projects
112 |
113 | # Backup & report files from converting an old project file to a newer
114 | # Visual Studio version. Backup files are not needed, because we have git ;-)
115 | _UpgradeReport_Files/
116 | Backup*/
117 | UpgradeLog*.XML
118 |
119 |
120 |
121 | ############
122 | ## Windows
123 | ############
124 |
125 | # Windows image file caches
126 | Thumbs.db
127 |
128 | # Folder config file
129 | Desktop.ini
130 |
131 |
132 | #############
133 | ## Python
134 | #############
135 |
136 | *.py[co]
137 |
138 | # Packages
139 | *.egg
140 | *.egg-info
141 | dist
142 | build
143 | eggs
144 | parts
145 | bin
146 | var
147 | sdist
148 | develop-eggs
149 | .installed.cfg
150 |
151 | # Installer logs
152 | pip-log.txt
153 |
154 | # Unit test / coverage reports
155 | .coverage
156 | .tox
157 |
158 | #Translations
159 | *.mo
160 |
161 | #Mr Developer
162 | .mr.developer.cfg
163 |
164 | # Mac crap
165 | .DS_Store
166 |
--------------------------------------------------------------------------------
/css/styles.css:
--------------------------------------------------------------------------------
1 | html,body {
2 | width: 100%;
3 | height: 100%;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | body {
9 | background-color: #eee;
10 |
11 | /* Firefox */
12 | display:-moz-box;
13 | -moz-box-orient:horizontal;
14 | -moz-box-pack:center;
15 | -moz-box-align:center;
16 |
17 | /* Safari and Chrome */
18 | display:-webkit-box;
19 | -webkit-box-orient:horizontal;
20 | -webkit-box-pack:center;
21 | -webkit-box-align:center;
22 |
23 | /* IE 10 */
24 | display:-ms-flexbox;
25 | -ms-flex-direction:row;
26 | -ms-flex-pack:center;
27 | -ms-flex-align:center;
28 |
29 | /* W3C */
30 | display:box;
31 | box-orient:horizontal;
32 | box-pack:center;
33 | box-align:center;
34 | }
35 |
36 | canvas {
37 | width: 768px;
38 | height: 768px;
39 | margin: 10px 10px 10px 0px;
40 |
41 | border: dashed 1px #bbb;
42 | border-radius: 5px;
43 |
44 | background-color: white;
45 |
46 | -webkit-transition: width 0.5s, height 0.5s, box-shadow 0.5s;
47 | -o-transition: width 0.5s, height 0.5s, box-shadow 0.5s;
48 | -moz-transition: width 0.5s, height 0.5s, box-shadow 0.5s;
49 | -ms-transition: width 0.5s, height 0.5s, box-shadow 0.5s;
50 | transition: width 0.5s, height 0.5s, box-shadow 0.5s;
51 | }
52 |
53 | a {
54 | color: inherit;
55 | font-family: inherit;
56 | font-size: inherit;
57 | }
58 |
59 | h1, h5 {
60 | font-family: "Lobster", cursive;
61 | }
62 |
63 | h1 {
64 | font-size: 3.5em;
65 | margin-top: 0px;
66 | }
67 |
68 | h5 {
69 | margin-bottom: 0px;
70 | }
71 |
72 | #controls_container {
73 | margin: 10px 0px 10px 10px;
74 | width: 158px;
75 |
76 | font-family: "Cabin", Arial;
77 | text-align: right;
78 | word-wrap: normal;
79 |
80 | color: #555;
81 | cursor: default;
82 | }
83 |
84 | #controls_container header, #controls_container .controls, #controls_container footer {
85 | margin-right: 20px;
86 | }
87 |
88 | #controls_container footer {
89 | font-size: smaller;
90 | }
91 |
92 | #controls_container #browse {
93 | padding: 5px 10px 5px 5px;
94 | border: dashed 1px #bbb;
95 | border-right: none;
96 | border-radius: 5px 0px 0px 5px;
97 |
98 | font-family: "Lobster", cursive;
99 | color: white;
100 | background-color: #4169E1;
101 | box-shadow: 0px 0px 0px 2px #4169E1, 0px 0px 2px 4px #ccc;
102 | cursor: pointer;
103 | }
104 |
105 | #controls_container footer a {
106 | font-family: "Lobster", cursive;
107 | text-decoration: none;
108 | color: inherit;
109 | }
110 |
111 | #controls_container footer a:hover {
112 | text-decoration: underline;
113 | }
114 |
115 | .controls {
116 | padding: 6px 6px 0px 6px;
117 |
118 | border: dashed 1px #bbb;
119 | border-radius: 5px;
120 |
121 | background-color: #fafafa;
122 | box-shadow: 0px 0px 0px 2px #eee, 0px 0px 2px 4px #bbb, 0px 0px 5px 5px #eee inset;
123 | }
124 |
125 | .controls a {
126 | display: block;
127 | padding: 5px;
128 | margin-top: 0px;
129 | margin-bottom: 6px;
130 |
131 | border: solid 1px #555;
132 | border-radius: 5px;
133 |
134 | font-family: inherit;
135 | font-size: inherit;
136 | text-decoration: none;
137 | text-align: center;
138 |
139 | color: inherit;
140 | background-color: #ddd;
141 | }
142 |
143 | .controls select {
144 | width: 100%;
145 | padding: 5px;
146 | margin-top: 0px;
147 | margin-bottom: 6px;
148 |
149 | border: solid 1px #555;
150 | border-radius: 5px;
151 | outline: none;
152 |
153 | font-family: inherit;
154 | font-size: inherit;
155 |
156 | color: inherit;
157 | background-color: #ddd;
158 | }
159 |
160 | .controls a, .controls select {
161 | -webkit-transition: background-color 0.5s;
162 | -o-transition: background-color 0.5s;
163 | -moz-transition: background-color 0.5s;
164 | -ms-transition: background-color 0.5s;
165 | transition: background-color 0.5s;
166 | }
167 |
168 | .controls a:hover, .controls select:hover {
169 | background-color: white;
170 | }
171 |
172 | .inset-shadow {
173 | box-shadow: 0px 0px 0px 5px #f7f7f7, 0px 0px 2px 7px #bbb, 0px 0px 5px 5px #f7f7f7 inset;
174 | }
175 |
176 | .outset-shadow {
177 | box-shadow: 0px 0px 0px 5px #f7f7f7, 0px 0px 5px 10px #bbb, 0px 0px 2px 2px #f7f7f7 inset;
178 | }
179 |
180 | .move-cursor {
181 | cursor: move;
182 | }
183 |
184 | #image_container {
185 | margin: 0;
186 | padding: 10px;
187 |
188 | border: dashed 1px #bbb;
189 | border-radius: 5px;
190 |
191 | background-color: #fafafa;
192 | box-shadow: 0px 0px 0px 2px #eee, 0px 0px 2px 4px #bbb, 0px 0px 5px 5px #eee inset;
193 |
194 | font-family: "Lobster", cursive;
195 | color: #555;
196 | }
197 |
198 | #image_container footer {
199 | margin-top: 5px;
200 | text-align: center;
201 | }
202 |
203 | #image_container #back {
204 | position: absolute;
205 | padding: 5px;
206 | top: 10%;
207 | left: -66px; /* 50(width) + 5*2(padding) + 1(border) + 2*2(box-shadow) + 1(border of image_container) */
208 | width: 50px;
209 |
210 | border: dashed 1px #bbb;
211 | border-right: none;
212 | border-radius: 5px 0px 0px 5px;
213 |
214 | background-color: #E3675C;
215 | box-shadow: 0px 0px 0px 2px #E3675C, 0px 0px 2px 4px #ccc;
216 |
217 | text-align: center;
218 | z-index: 0;
219 | cursor: pointer;
220 | }
221 |
222 | #controls_container, #canvas_container, #image_container {
223 | -webkit-transition: opacity 0.5s;
224 | -o-transition: opacity 0.5s;
225 | -moz-transition: opacity 0.5s;
226 | -ms-transition: opacity 0.5s;
227 | transition: opacity 0.5s;
228 | }
229 |
230 | .hidden {
231 | position: absolute;
232 | width: 0;
233 | height: 0;
234 | overflow: hidden;
235 | opacity: 0;
236 | visibility: hidden;
237 | }
238 |
239 | .shown {
240 | opacity: 1;
241 | visibility: visible;
242 | }
243 |
244 | .relative {
245 | position: relative;
246 | }
247 |
248 | #unsupported {
249 | position: fixed;
250 | top: 0;
251 | left: 0;
252 | width: 100%;
253 | padding: 5px;
254 |
255 | font-family: "Cabin", Arial;
256 | font-weight: bold;
257 | text-align: center;
258 |
259 | color: #fff;
260 | background-color: #E3675C;
261 | box-shadow: 0px 0px 5px 5px #aaa;
262 | }
--------------------------------------------------------------------------------
/js/app.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | // Board is in charge of the board that contains tiles. No drawing is done here, just the creation of the board and the tiles.
3 | var Board = function(width,height,tileSize) {
4 | var _width = width || 6;
5 | var _height = height || 6;
6 | var _tileSize = tileSize || 128;
7 |
8 | var _map = []; // represents the board, size is specified by _width and _height.
9 | var _mapNextIndex = { x : 0, y : 0 }; // holds the next index in the map where the new tile will be added.
10 | this.tiles = []; // holds the formed tiles in the board.
11 |
12 | var _self = this;
13 |
14 | // constants
15 | var _tileIndex = {
16 | TILE_2_2 : 0,
17 | TILE_2_1 : 1,
18 | TILE_1_1 : 2,
19 | TILE_1_2 : 3
20 | };
21 |
22 | var _tileTypes = [
23 | { h_factor : 2, w_factor : 2 },
24 | { h_factor : 2, w_factor : 1 },
25 | { h_factor : 1, w_factor : 1 },
26 | { h_factor : 1, w_factor : 2 }
27 | ];
28 |
29 | // clears the map and tiles.
30 | var clear = function() {
31 | // set map to 0
32 | for (var y = 0; y < _height; y++)
33 | {
34 | _map[y] = _map[y] || [];
35 | for (var x = 0; x < _width; x++)
36 | {
37 | _map[y][x] = 0;
38 | }
39 | }
40 | _mapNextIndex.x = 0;
41 | _mapNextIndex.y = 0;
42 | // reset tiles
43 | _self.tiles = [];
44 | };
45 |
46 | // returns a random whole number from 0 to n-1.
47 | var random = function(n) {
48 | n = n || 0;
49 | return Math.floor(Math.random()*n);
50 | };
51 |
52 | // returns a random _tileIndex constant.
53 | var getRandomTileIndex = function() {
54 | var tile;
55 |
56 | var x = _mapNextIndex.x,
57 | y = _mapNextIndex.y;
58 | var is_x_free = x+1 < _width && _map[y][x+1] === 0,
59 | is_y_free = y+1 < _height && _map[y+1][x] === 0;
60 |
61 | if (is_x_free && is_y_free)
62 | tile = random(4);
63 | else if (is_x_free)
64 | tile = random(2) + 2;
65 | else if (is_y_free)
66 | tile = random(2) + 1;
67 | else
68 | tile = _tileIndex.TILE_1_1;
69 |
70 | return tile;
71 | };
72 |
73 | // adds a tile with its computed coords and size given the tileIndex to _tiles.
74 | var addTile = function(tileIndex) {
75 | var tile = {
76 | x : _tileSize * _mapNextIndex.x,
77 | y : _tileSize * _mapNextIndex.y,
78 | w : _tileSize * _tileTypes[tileIndex].w_factor,
79 | h : _tileSize * _tileTypes[tileIndex].h_factor
80 | };
81 |
82 | // save
83 | _self.tiles.push(tile);
84 | };
85 |
86 | // sets next index in the map (_mapNextIndex) given the tile that has just been added.
87 | var setMapNextIndex = function(tileIndex) {
88 | var x = _mapNextIndex.x,
89 | y = _mapNextIndex.y;
90 |
91 | switch (tileIndex)
92 | {
93 | case _tileIndex.TILE_2_2:
94 | _map[y][x] = 1;
95 | _map[y][x+1] = 1;
96 | _map[y+1][x] = 1;
97 | _map[y+1][x+1] = 1;
98 | x += 2;
99 | break;
100 | case _tileIndex.TILE_2_1:
101 | _map[y][x] = 1;
102 | _map[y+1][x] = 1;
103 | x++;
104 | break;
105 | case _tileIndex.TILE_1_2:
106 | _map[y][x] = 1;
107 | _map[y][x+1] = 1;
108 | x += 2;
109 | break;
110 | case _tileIndex.TILE_1_1:
111 | _map[y][x] = 1;
112 | x++;
113 | break;
114 | }
115 |
116 | if (x === _width)
117 | x--;
118 |
119 | while (y < _height && _map[y][x] === 1)
120 | {
121 | x++;
122 | if (x === _width)
123 | {
124 | x = 0;
125 | y++;
126 | }
127 | }
128 |
129 | _mapNextIndex.x = x;
130 | _mapNextIndex.y = y;
131 | };
132 |
133 | // main interface of this class. this will fill up the board with randomly created tiles.
134 | this.formTiles = function() {
135 | clear();
136 | while (_mapNextIndex.y < _height)
137 | {
138 | var tileIndex = getRandomTileIndex();
139 | addTile(tileIndex);
140 | setMapNextIndex(tileIndex);
141 | }
142 | };
143 |
144 | // sets the size of the board and the tile.
145 | this.setSize = function(width,height,tileSize) {
146 | _width = width;
147 | _height = height;
148 | _tileSize = tileSize;
149 | };
150 | };
151 |
152 | // BoardDrawer is in charge of the rendering of the board into the canvas.
153 | var BoardDrawer = function() {
154 | /* assume these are defined outside:
155 | cnv - canvas
156 | ctx - canvas context
157 | _board - Board object
158 | */
159 | var _images = []; // holds the images that have been added into the board.
160 |
161 | this.clearBoard = function() {
162 | cnv.width = cnv.width;
163 | cnv.height = cnv.height;
164 | };
165 |
166 | this.redrawBoard = function() {
167 | this.clearBoard();
168 | this.drawTiles();
169 | this.drawImages();
170 | };
171 |
172 | /* ****************************************************************************
173 | Interfaces for scrolling and scaling of image within a tile on the board.
174 | */
175 | var _anchorPoint = null;
176 |
177 | // saves the position of the mouse relative to the clicked image. used in scrolling.
178 | this.saveAnchorPoint = function(image,mousePos) {
179 | if (image && image.slice)
180 | {
181 | _anchorPoint = {
182 | x : image.slice.x + mousePos.x,
183 | y : image.slice.y + mousePos.y
184 | };
185 | }
186 | };
187 |
188 | // scrolls the given image.
189 | this.scrollImage = function(image,mousePos) {
190 | if (!_anchorPoint || !image.slice)
191 | return;
192 |
193 | var newX = _anchorPoint.x - mousePos.x,
194 | newY = _anchorPoint.y - mousePos.y;
195 |
196 | newX = Math.max(newX,0);
197 | newX = Math.min(newX,Math.abs(image.width-image.slice.w));
198 |
199 | newY = Math.max(newY,0);
200 | newY = Math.min(newY,Math.abs(image.height-image.slice.h));
201 |
202 | image.slice.x = newX;
203 | image.slice.y = newY;
204 |
205 | ctx.drawImage(image,
206 | image.slice.x, image.slice.y,
207 | image.slice.w, image.slice.h,
208 | image.slice.dx, image.slice.dy,
209 | image.slice.dw, image.slice.dh);
210 | };
211 |
212 | // scales the image at where the mouse is.
213 | this.scaleImageAt = function(mousePos,isScaleUp) {
214 | var image = this.getImageAt(mousePos);
215 | if (image && image.slice)
216 | {
217 | var newW, newH;
218 | if (image.slice.dw > image.slice.dh)
219 | {
220 | newW = isScaleUp ? image.slice.w + 10 : image.slice.w - 10;
221 | newW = Math.max(newW,image.slice.dw);
222 | newW = Math.min(newW,image.width);
223 | newH = Math.floor(newW*image.slice.dh/image.slice.dw);
224 |
225 | if (newH > image.height)
226 | {
227 | newH = image.height;
228 | newW = Math.floor(newH*image.slice.dw/image.slice.dh);
229 | }
230 | }
231 | else
232 | {
233 | newH = isScaleUp ? image.slice.h + 10 : image.slice.h - 10;
234 | newH = Math.max(newH,image.slice.dh);
235 | newH = Math.min(newH,image.height);
236 | newW = Math.floor(newH*image.slice.dw/image.slice.dh);
237 |
238 | if (newW > image.width)
239 | {
240 | newW = image.width;
241 | newH = Math.floor(newW*image.slice.dh/image.slice.dw);
242 | }
243 | }
244 |
245 | image.slice.w = newW;
246 | image.slice.h = newH;
247 | // update x and y
248 | image.slice.x = Math.min(image.slice.x,Math.abs(image.width-image.slice.w));
249 | image.slice.y = Math.min(image.slice.y,Math.abs(image.height-image.slice.h));
250 |
251 | ctx.drawImage(image,
252 | image.slice.x, image.slice.y,
253 | image.slice.w, image.slice.h,
254 | image.slice.dx, image.slice.dy,
255 | image.slice.dw, image.slice.dh);
256 | }
257 | };
258 |
259 | // removes the slice of the given image. A slice contains the coords and dimension of the image for slicing.
260 | this.removeSlice = function(image) {
261 | if (image)
262 | image.slice = null;
263 | _anchorPoint = null;
264 | };
265 |
266 | // removes the slices of all the images.
267 | this.removeAllSlices = function() {
268 | for (var i = 0; i < _board.tiles.length; i++)
269 | this.removeSlice(_images[i]);
270 | };
271 |
272 | /* ****************************************************************************
273 | Interfaces for tiles and highlighting of tiles.
274 | */
275 | var _highlightedTile = -1;
276 |
277 | // returns true if tileIndex is a valid index of _board.tiles
278 | var validateTile = function(tileIndex) {
279 | return (tileIndex > -1 && tileIndex < _board.tiles.length);
280 | };
281 |
282 | // returns the index of the tile where the mousePos is.
283 | this.getTileAt = function(mousePos) {
284 | var i;
285 | for (i = 0; i < _board.tiles.length; i++)
286 | {
287 | var tile = _board.tiles[i];
288 | if (mousePos.x >= tile.x && mousePos.x <= tile.x + tile.w &&
289 | mousePos.y >= tile.y && mousePos.y <= tile.y + tile.h)
290 | break;
291 | }
292 | return i;
293 | };
294 |
295 | // draws the tiles.
296 | this.drawTiles = function() {
297 | ctx.strokeStyle = "#eee";
298 |
299 | for (var i = 0; i < _board.tiles.length; i++)
300 | {
301 | var tile = _board.tiles[i];
302 | ctx.strokeRect(tile.x,tile.y,tile.w,tile.h);
303 | }
304 | };
305 |
306 | // draws a highlighted tile.
307 | var drawHighlightedTile = function() {
308 | if (validateTile(_highlightedTile))
309 | {
310 | ctx.fillStyle = "#fafafa";
311 | var tile = _board.tiles[_highlightedTile];
312 | ctx.fillRect(tile.x,tile.y,tile.w,tile.h);
313 | }
314 | };
315 |
316 | // highlights the tile at mousePos. highlighting of tiles occurs only when the mouse is over the canvas while dragging a picture onto it.
317 | this.highlightTileAt = function(mousePos) {
318 | var i = this.getTileAt(mousePos);
319 |
320 | if (i !== _board.tiles.length && i !== _highlightedTile)
321 | {
322 | _highlightedTile = i;
323 |
324 | this.redrawBoard();
325 | drawHighlightedTile();
326 | }
327 | };
328 |
329 | // removes the highlighted tile.
330 | this.removeHighlight = function() {
331 | _highlightedTile = -1;
332 | this.redrawBoard();
333 | };
334 |
335 | /* ****************************************************************************
336 | Interfaces for adding and drawing of images.
337 | */
338 |
339 | this.getImageAt = function(mousePos) {
340 | var i = this.getTileAt(mousePos);
341 | if (_images[i])
342 | return _images[i];
343 | return null;
344 | };
345 |
346 | this.addImageAt = function(tileIndex, image) {
347 | if (image && validateTile(tileIndex))
348 | {
349 | _images[tileIndex] = image;
350 | }
351 | }
352 |
353 | this.removeImageAt = function(tileIndex) {
354 | if (validateTile(tileIndex))
355 | {
356 | _images[tileIndex] = null;
357 | }
358 | };
359 |
360 | this.drawImage = function(tileIndex,img) {
361 | if (!validateTile(tileIndex))
362 | return false;
363 |
364 | var image;
365 |
366 | if (img)
367 | {
368 | image = img;
369 | }
370 | else
371 | {
372 | if (!_images[tileIndex])
373 | return true;
374 |
375 | image = _images[tileIndex];
376 | }
377 |
378 | var tile = _board.tiles[tileIndex];
379 | var sw,sh,sx,sy;
380 |
381 | if (image.slice)
382 | {
383 | sx = image.slice.x;
384 | sy = image.slice.y;
385 | sw = image.slice.w;
386 | sh = image.slice.h;
387 | }
388 | else
389 | {
390 | var compute_sw_then_sh = function() {
391 | var mult = Math.floor(image.width/tile.w);
392 | sw = mult === 0 ? image.width : tile.w * mult;
393 | sh = Math.floor(sw*tile.h/tile.w);
394 | };
395 |
396 | var compute_sh_then_sw = function() {
397 | var mult = Math.floor(image.height/tile.h);
398 | sh = mult === 0 ? image.height : tile.h * mult;
399 | sw = Math.floor(sh*tile.w/tile.h);
400 | };
401 |
402 | if (tile.w > tile.h)
403 | {
404 | compute_sw_then_sh();
405 | if (sh > image.height)
406 | compute_sh_then_sw();
407 | }
408 | else
409 | {
410 | compute_sh_then_sw();
411 | if (sw > image.width)
412 | compute_sw_then_sh();
413 | }
414 |
415 | sx = Math.floor((image.width-sw)/2);
416 | sy = Math.floor((image.height-sh)/2);
417 |
418 | // save coords for scrolling
419 | image.slice = {
420 | x : sx,
421 | y : sy,
422 | w : sw,
423 | h : sh,
424 | dx : tile.x,
425 | dy : tile.y,
426 | dw : tile.w,
427 | dh : tile.h
428 | };
429 | }
430 |
431 | ctx.drawImage(image,sx,sy,sw,sh,tile.x,tile.y,tile.w,tile.h);
432 |
433 | return true;
434 | };
435 |
436 | this.addAndDrawImage = function(image) {
437 | if (validateTile(_highlightedTile))
438 | {
439 | _images[_highlightedTile] = image;
440 | this.drawImage(_highlightedTile);
441 | _highlightedTile = -1;
442 | }
443 | else
444 | {
445 | for (var i = 0; i < _board.tiles.length; i++)
446 | {
447 | if (!_images[i])
448 | {
449 | _images[i] = image;
450 | this.drawImage(i);
451 | break;
452 | }
453 | }
454 | }
455 | };
456 |
457 | this.drawImages = function() {
458 | for (var i = 0; i < _board.tiles.length && this.drawImage(i); i++);
459 | };
460 |
461 | this.clearImages = function() {
462 | _images = [];
463 | };
464 | };
465 |
466 | function readFiles(files) {
467 | for (var i = 0; i < files.length; i++)
468 | {
469 | // Only process image files
470 | var imageType = /image.*/;
471 | if (!files[i].type.match(imageType))
472 | continue;
473 |
474 | var reader = new FileReader();
475 |
476 | reader.onerror = function(e) {
477 | alert("Error code: " + e.target.error.code);
478 | };
479 |
480 | reader.onload = function(e) {
481 | var img = new Image();
482 | img.onload = function() {
483 | _boardDrawer.addAndDrawImage(img);
484 | };
485 | img.src = e.target.result;
486 | };
487 |
488 | reader.readAsDataURL(files[i]);
489 | }
490 | }
491 |
492 | function getMousePos(evt) {
493 | var mousePos = {
494 | x : evt.clientX - cnv.offsetLeft,
495 | y : evt.clientY - cnv.offsetTop
496 | };
497 | return mousePos;
498 | }
499 |
500 | function handleDragOver(evt) {
501 | evt.stopPropagation();
502 | evt.preventDefault();
503 | cnv.className = "outset-shadow";
504 |
505 | _boardDrawer.highlightTileAt(getMousePos(evt));
506 | }
507 |
508 | function handleDragLeave(evt) {
509 | evt.stopPropagation();
510 | evt.preventDefault();
511 | cnv.className = "inset-shadow";
512 |
513 | _boardDrawer.removeHighlight();
514 | }
515 |
516 | function handleDrop(evt) {
517 | evt.stopPropagation();
518 | evt.preventDefault();
519 | cnv.className = "inset-shadow";
520 |
521 | readFiles(evt.dataTransfer.files);
522 | }
523 |
524 | function handleMouseDown(evt) {
525 | evt.preventDefault();
526 | var mousePos = getMousePos(evt);
527 | var image = _boardDrawer.getImageAt(mousePos);
528 | if (image !== null)
529 | {
530 | isMouseDown = true;
531 | selectedImage = image;
532 | selectedTile = _boardDrawer.getTileAt(mousePos);
533 | currentTile = selectedTile;
534 | _boardDrawer.removeImageAt(selectedTile);
535 |
536 | // change cursor
537 | cnv.className = "outset-shadow move-cursor";
538 |
539 | // save mousePos as anchor point
540 | _boardDrawer.saveAnchorPoint(image,mousePos);
541 | }
542 | }
543 |
544 | function handleMouseMove(evt) {
545 | evt.preventDefault();
546 | if (isMouseDown)
547 | {
548 | var mousePos = getMousePos(evt);
549 | var newTile = _boardDrawer.getTileAt(mousePos);
550 | if (currentTile !== newTile)
551 | {
552 | currentTile = newTile;
553 |
554 | _boardDrawer.removeSlice(selectedImage);
555 | _boardDrawer.redrawBoard();
556 | _boardDrawer.drawImage(currentTile,selectedImage);
557 | }
558 | else
559 | {
560 | // scroll current image
561 | _boardDrawer.scrollImage(selectedImage,mousePos);
562 | }
563 | }
564 | }
565 |
566 | function handleMouseOut(evt) {
567 | if (isMouseDown)
568 | {
569 | _boardDrawer.removeSlice(selectedImage);
570 | _boardDrawer.addImageAt(selectedTile,selectedImage);
571 | _boardDrawer.redrawBoard();
572 |
573 | selectedImage = null;
574 | selectedTile = -1;
575 | currentTile = -1;
576 | isMouseDown = false;
577 |
578 | cnv.className = "inset-shadow";
579 | }
580 | }
581 |
582 | function handleMouseUp(evt) {
583 | if (isMouseDown)
584 | {
585 | var newTile = _boardDrawer.getTileAt(getMousePos(evt));
586 | _boardDrawer.addImageAt(newTile,selectedImage);
587 | //_boardDrawer.drawImage(newTile);
588 |
589 | selectedImage = null;
590 | selectedTile = -1;
591 | currentTile = -1;
592 | isMouseDown = false;
593 |
594 | cnv.className = "inset-shadow";
595 | }
596 | }
597 |
598 | function handleMouseWheel(evt) {
599 | evt.preventDefault();
600 |
601 | var isScaleUp;
602 |
603 | if (evt.wheelDelta)
604 | isScaleUp = evt.wheelDelta < 0;
605 | else if (evt.detail)
606 | isScaleUp = evt.detail > 0;
607 |
608 | _boardDrawer.scaleImageAt(getMousePos(evt),isScaleUp);
609 | }
610 |
611 | function csize_changed(evt) {
612 | switch (evt.target.value)
613 | {
614 | case "small":
615 | baseTileSize = 100;
616 | cnv.width = baseTileSize * 6;
617 | cnv.height = baseTileSize * 6;
618 | break;
619 | case "large":
620 | baseTileSize = 128;
621 | cnv.width = baseTileSize * 6;
622 | cnv.height = baseTileSize * 6;
623 | break;
624 | case "wide":
625 | baseTileSize = 128;
626 | cnv.width = baseTileSize * 12;
627 | cnv.height = baseTileSize * 6;
628 | break;
629 | default:
630 | baseTileSize = 128;
631 | cnv.width = baseTileSize * 6;
632 | cnv.height = baseTileSize * 6;
633 | break;
634 | }
635 |
636 | cnv.style.width = cnv.width + "px";
637 | cnv.style.height = cnv.height + "px";
638 |
639 | tsize_changed();
640 | }
641 |
642 | function tsize_changed(evt) {
643 | var mult = evt ? parseInt(evt.target.value,10) : parseInt(document.getElementById("tsize").value,10);
644 | if (!mult || mult === 0)
645 | mult = 1;
646 | var tileSize = baseTileSize * mult;
647 | var width = Math.floor(cnv.width/tileSize);
648 | var height = Math.floor(cnv.height/tileSize);
649 |
650 | _board.setSize(width,height,tileSize);
651 | _board.formTiles();
652 |
653 | _boardDrawer.removeAllSlices();
654 | _boardDrawer.redrawBoard();
655 | }
656 |
657 | var baseTileSize = 128;
658 | var isMouseDown = false;
659 | var selectedImage = null;
660 | var selectedTile = -1;
661 | var currentTile = -1;
662 |
663 | // setup canvas
664 | var cnv = document.getElementById("c");
665 | var ctx = cnv.getContext("2d");
666 |
667 | // setup board
668 | var _board = new Board();
669 |
670 | // setup board drawer
671 | var _boardDrawer = new BoardDrawer();
672 |
673 | // add drag and drop to canvas
674 | cnv.addEventListener('dragover',handleDragOver,false);
675 | cnv.addEventListener('dragleave',handleDragLeave,false);
676 | cnv.addEventListener('drop',handleDrop,false);
677 |
678 | cnv.addEventListener('mousedown',handleMouseDown,false);
679 | cnv.addEventListener('mousemove',handleMouseMove,false);
680 | cnv.addEventListener('mouseout',handleMouseOut,false);
681 | cnv.addEventListener('mouseup',handleMouseUp,false);
682 | cnv.addEventListener('mousewheel',handleMouseWheel,false);
683 | // for firefox
684 | cnv.addEventListener('DOMMouseScroll',handleMouseWheel,false);
685 |
686 | var evt = { target : { value : 0 } };
687 | if (window.innerWidth < 1024 || window.innerHeight < 768)
688 | evt.target.value = "small";
689 | else if (window.innerWidth > 1720)
690 | evt.target.value = "wide";
691 | else
692 | evt.target.value = "large";
693 |
694 | var csize = document.getElementById("csize");
695 | csize.value = evt.target.value; // set default value
696 | csize.addEventListener('change',csize_changed,false);
697 |
698 | var tsize = document.getElementById("tsize");
699 | tsize.value = "1"; // set default value
700 | tsize.addEventListener('change',tsize_changed,false);
701 |
702 | // change default canvas size according to screen size
703 | csize_changed(evt);
704 |
705 | var browse = document.getElementById("browse");
706 | browse.addEventListener('click',function(){
707 | var file = document.getElementById("file");
708 | file.click();
709 | },false);
710 |
711 | var file = document.getElementById("file");
712 | file.addEventListener('change',function(evt){
713 | readFiles(evt.target.files);
714 | },false);
715 |
716 | var randomize = document.getElementById("randomize");
717 | randomize.addEventListener('click',function(evt){
718 | evt.preventDefault();
719 |
720 | _board.formTiles();
721 | _boardDrawer.removeAllSlices();
722 | _boardDrawer.redrawBoard();
723 | },false);
724 |
725 | var controls_container = document.getElementById("controls_container");
726 | var canvas_container = document.getElementById("canvas_container");
727 | var image_container = document.getElementById("image_container");
728 |
729 | var save = document.getElementById("save");
730 | save.addEventListener('click',function(evt){
731 | evt.preventDefault();
732 |
733 | var image = document.getElementById("image");
734 | image.src = cnv.toDataURL("image/jpeg",0.9);
735 | image.width = cnv.width;
736 | image.height = cnv.height;
737 |
738 | image_container.className = "shown relative";
739 | controls_container.className = "hidden";
740 | canvas_container.className = "hidden";
741 |
742 | },false);
743 |
744 | var clear = document.getElementById("clear");
745 | clear.addEventListener('click',function(evt){
746 | evt.preventDefault();
747 |
748 | _boardDrawer.clearBoard();
749 | _boardDrawer.clearImages();
750 | _boardDrawer.drawTiles();
751 | },false);
752 |
753 | var back = document.getElementById("back");
754 | back.addEventListener('click',function(){
755 | image_container.className = "hidden";
756 | controls_container.className = "shown";
757 | canvas_container.className = "shown";
758 | });
759 |
760 | // detect browser. only allow chrome, firefox, ie10
761 | var allowed = /(Chrome|Firefox|MSIE 10.0)/i;
762 | if (!allowed.test(navigator.userAgent))
763 | {
764 | document.getElementById("unsupported").style.display = "block";
765 | }
766 |
767 | })();
--------------------------------------------------------------------------------