├── .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 | 50 | 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 | })(); --------------------------------------------------------------------------------