├── .svg ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── bower.json ├── index.coffee ├── jaggy ├── package.json ├── public ├── chrono_trigger.JPG ├── index.html ├── jaggy.min.js ├── jaggy.min.js.map ├── moon.png ├── uma.gif ├── uma2.gif └── yuno.png ├── src ├── classes.coffee ├── index.coffee ├── jaggy.angular.coffee ├── jaggy.browser.coffee └── jaggy.coffee └── test ├── classess.spec.coffee ├── jaggy.angular.spec.coffee ├── jaggy.browser.spec.coffee └── jaggy.spec.coffee /.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # quick boot options 2 | sudo: false 3 | cache: 4 | directories: 5 | - node_modules 6 | 7 | # language options 8 | before_install: 9 | - npm install coffee-script --global 10 | language: node_js 11 | node_js: 12 | - '0.10' 13 | 14 | # coveralls.io options 15 | env: 16 | global: 17 | - secure: Ntsmce9vaWJWoAD6aFE9PbufcENR+bM49LvLjjSM+kZ8f92DVngBEwKA9tPh37KNCr935h4MxSJqe+OHQ50ObuJ5wiMJRsXc+4lvC4MLOo2ntIZd2qdol2SR+89LTT9YAiCX3JGnc/Us1KaUBh/hJ9A/OyxUF8rJLy6dDGldnW4= 18 | - secure: TsnPeT+sp3VGyHnnEJtsgjX20plmyR0CZv+dqzWlo5839FjA6BlSn6tHvL9O4agifjTarwcj3fRlkHjlvKSGqWiDttmCk2+83ilfHpDLncJ8086k1cQhkGu0nox3cuTHcfNJ0YRJeC/n/fFelL92KV/PSSHjQ/Q5DuzeG5+7a8Y= 19 | 20 | # deploy options 21 | deploy: 22 | provider: npm 23 | email: i59naga@icloud.com 24 | api_key: 25 | secure: ASq8p+8Kmu36TGLVLWBulUqXvfxDI8BAQopYhEbO/TN7mlxBq0MB5r9Ex5EgFqZimu+D7bWjakZQd1YGGrZSmsu8xgXtPBD/i2kaGZyjZaNNtfNMaoIzOAaSxdECzGbXWkkOt7nwqjTJzLYiZ8z8b+EzGdFt0voM+ar2qtk0D5o= 26 | on: 27 | repo: 59naga/jaggy 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | v0.1.17-rc.3 / Apr 27 2015 2 | ========================= 3 | * [`4b8a6ee`][13] :bug: Fix [#6][13A] 4 | * [`4b8a6ee`][13] :bug: Fix [#7][13B] 5 | * [`37aaf03`][14] :lipstick: Fix [#8][14A] Add `jagged` for jaggy directive 6 | * [`3b94494`][15] :bug: Fix [#9][15A] 7 | * [`unknown`][16] :bug: Fix [#10][16A] 8 | 9 | [13]: https://github.com/59naga/jaggy/commit/4b8a6ee577f8625ddfd2e492a30e36b9a8d244e1 10 | [13A]: https://github.com/59naga/jaggy/issues/6 11 | [13B]: https://github.com/59naga/jaggy/issues/7 12 | [14]: https://github.com/59naga/jaggy/commit/37aaf03a58170f9153f4a11565186df4f21466a3 13 | [14A]: https://github.com/59naga/jaggy/issues/8 14 | [15]: https://github.com/59naga/jaggy/commit/3b9449490f9b1323845f8f595e2126e67734b07d 15 | [15A]: https://github.com/59naga/jaggy/issues/9 16 | [15]: https://github.com/59naga/jaggy/commit/ 17 | [16A]: https://github.com/59naga/jaggy/issues/10 18 | 19 | v0.1.17 / Apr 15 2015 20 | ========================= 21 | * [`5078a94`][12] :racehorse: Add Jaggy.queues for Jaggy.createSVG 22 | * [`5078a94`][12] :lipstick: Move Jaggy.createSVG to Jaggy._createSVG 23 | * [`5078a94`][12] :racehorse: Add Jaggy.options.timeout for Jaggy._createSVG 24 | * [`5078a94`][12] :racehorse: Add lz-string for caching(setCache/getCache) 25 | 26 | [12]: https://github.com/59naga/jaggy/commit/5078a9470f3026702a0fdf01a1e7a0d749d29dd5 27 | 28 | v0.1.15 / Apr 12 2015 29 | ========================= 30 | * [`20c58d2`][9] :bulb: Add jaggy.pixelLimit for angular.js 31 | * [`bbc4132`][10] :fire: Deprecated `window.jaggy`. Move to `window.jaggy.createSVG` 32 | * [`bbc4132`][10] :lipstick: Rename for angular.js 33 | * jaggyConfig to `jaggy` 34 | * jaggyConfig.useCache to `jaggy.cache` 35 | * jaggyConfig.useEmptyImage to `jaggy.emptyImage` 36 | * [`bbc4132`][10] :bug: Fix duplicate uuid for animation 37 | * [`2fb60d9`][11] :bug: Fix InvalidCharacterError: DOM Exception 5 on safari 38 | 39 | [9]: https://github.com/59naga/jaggy/commit/20c58d2ea152ce4481a634f35562ea7e2334e9fe 40 | [10]: https://github.com/59naga/jaggy/commit/bbc413299f362e5e26d270b04237ddda61c21927 41 | [11]: https://github.com/59naga/jaggy/commit/2fb60d9db8df447ac222385ae6274225c14747af 42 | 43 | v0.1.13 / Apr 10 2015 44 | ========================= 45 | * [`8ea1129`][7] :bug: fix `Cannot read property 'indexOf' of undefined` by angular-jaggy 46 | * [`65b72fb`][8] :bulb: Add `jaggyConfig.glitch` 47 | 48 | [7]: https://github.com/59naga/jaggy/commit/8ea1129a91043d569ef63ad3c1d46cd0eb07a8b0 49 | [8]: https://github.com/59naga/jaggy/commit/65b72fbd4b8f16823bf6bddf46ee5c2b1b4b853b 50 | 51 | v0.1.11 / Apr 8 2015 52 | ========================= 53 | * [`0265a98`][6] :bug: Hotfix [#3][6A] 54 | 55 | [6A]: https://github.com/59naga/jaggy/issues/3 56 | [6]: https://github.com/59naga/jaggy/commit/0265a98fd8f6d5270b7eaef60c559511335aeb38 57 | 58 | v0.1.9 / Apr 7 2015 59 | ========================= 60 | * [`d36c425`][5] :lipstick: Add ng-annotate for uglifyjs 61 | 62 | [5]: https://github.com/59naga/jaggy/commit/d36c425846abff547f719c43dc2ecf67097079e8 63 | 64 | v0.1.8 / Mar 30 2015 65 | ========================= 66 | Add Angular options by constant `jaggyConfig` 67 | 68 | * [`a907a0a`][2] :bulb: empty image instead of Error by `jaggyConfig.useEmptyImage` 69 | * [`a907a0a`][2] :bulb: caching a converted svg by `jaggyConfig.useCache` 70 | 71 | * [`unknown`][3] :bug: Fix [#1](https://github.com/59naga/jaggy/issues/1) 72 | * [`unknown`][4] :bug: Fix [#2](https://github.com/59naga/jaggy/issues/2) 73 | 74 | [2]: https://github.com/59naga/jaggy/commit/a907a0a5da621d26fb5c01fceb49a882b6f97a71 75 | [3]: https://github.com/59naga/jaggy/commit/d4cd748d68f2fd27b17af54cc768bc1cbb196d3d 76 | [4]: https://github.com/59naga/jaggy/commit/4cb8d40a9ae223a97249f4d07fae390f3435c183 77 | 78 | v0.1.4 / Mar 29 2015 79 | ========================= 80 | * [`21fb96a`][1] :bug: Fix `` 82 | 83 | [1]: https://github.com/59naga/jaggy/commit/21fb96a22352c84f4802c50f6a35f7500cee9254 84 | 85 | v0.1.3 / Mar 03 2015 86 | ========================= 87 | * [`1084961c`][0] Release v0.1.3 88 | 89 | [0]: https://github.com/59naga/jaggy/commits/master -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![jaggy][.svg] Jaggy [![NPM version][npm-image]][npm] [![Bower version][bower-image]][bower] [![Build Status][travis-image]][travis] [![Coverage Status][coveralls-image]][coveralls] 2 | 3 | ## for gulp 4 | ```bash 5 | $ npm install gulp jaggy 6 | ``` 7 | 8 | gulpfile.js 9 | 10 | ```js 11 | var jaggy,gulp; 12 | jaggy= require('jaggy'); 13 | gulp= require('gulp'); 14 | gulp.task('default',function(){ 15 | gulp.src(['*.png','*.gif','*.jpg']) 16 | .pipe(jaggy()) 17 | .pipe(gulp.dest('./')) 18 | ; 19 | }); 20 | ``` 21 | 22 | ```bash 23 | $ gulp # Create the .svg 24 | ``` 25 | 26 | ## for CLI 27 | Can use jaggy command to folder or file. 28 | Create the sameName.svg by [.gif, .jpg, .png] 29 | 30 | Example: 31 | 32 | ```bash 33 | $ npm install gulp jaggy --global 34 | $ jaggy public_html --recursive 35 | ``` 36 | 37 | ## for browser 38 | ```bash 39 | $ bower install jaggy 40 | ``` 41 | 42 | ```html 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ``` 52 | 53 | * Add `jaggy.browser.js` for ``. 54 | * Set `jaggy` class for ``. 55 | * Converting after `DOMContentLoaded`. 56 | 57 | ***Doesn't work [Cross-origin][1]*** 58 | 59 | [1]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS 60 | 61 | ## for angular.js 1.* 62 | 63 | ```html 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | ``` 73 | 74 | Can use `jaggy` directive. 75 | 76 | ## Why? 77 | Doesn't work [`image-rendering:crisp-edges`](http://caniuse.com/#feat=css-crisp-edges). 78 | However, Can work on the [``](http://caniuse.com/#feat=svg). 79 | Gotcha, save the jaggy. 80 | 81 | ## Browser options 82 | ### for browser 83 | ```js 84 | 97 | ``` 98 | ### for angular.js 1.* 99 | ```html 100 | 116 | ``` 117 | 118 | * `.cache` 119 | Caching a converted svg by localStorage. 120 | 121 | * `.emptyImage` 122 | Replace empty image instead of Error. e.g. `` 123 | 124 | * `.pixelLimit` 125 | Skip a converting if over set value. 126 | 127 | ```html 128 | 129 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 147 | 148 | ``` 149 | 150 | Default: 262144 (= width 256 * height 256 * channel 4 * frame 1) 151 | 152 | * `.glitch` 153 | Change `Frame.putImageData` logic by increment channel value. 154 | 155 | ## Known issue 156 | * Svg conversion of animated gif is experimental. It will take the high the CPU usage to play. 157 | * Uncaught QuotaExceededError: Failed to execute 'setItem' on 'Storage': Setting the value of `jaggy:url` exceeded the quota. due to Huge Animationed gif 158 | 159 | ## TODO 160 | * TEST for jaggy.browser.coffee 161 | * TEST for jaggy.angular.coffee 162 | 163 | License 164 | ========================= 165 | [MIT][License] by 59naga 166 | 167 | [License]: http://59naga.mit-license.org/ 168 | 169 | [.svg]: https://cdn.rawgit.com/59naga/jaggy/master/.svg? 170 | 171 | [npm-image]: https://badge.fury.io/js/jaggy.svg 172 | [npm]: https://npmjs.org/package/jaggy 173 | [bower-image]: https://badge.fury.io/bo/jaggy.svg 174 | [bower]: http://badge.fury.io/bo/jaggy 175 | [travis-image]: https://travis-ci.org/59naga/jaggy.svg?branch=master 176 | [travis]: https://travis-ci.org/59naga/jaggy 177 | [coveralls-image]: https://coveralls.io/repos/59naga/jaggy/badge.svg?branch=master 178 | [coveralls]: https://coveralls.io/r/59naga/jaggy?branch=master 179 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jaggy", 3 | "main": "public/jaggy.min.js", 4 | "description": " to ", 5 | "homepage": "https://github.com/59naga/jaggy", 6 | 7 | "authors": [ 8 | "59naga " 9 | ], 10 | 11 | "keywords": [ 12 | "pixel", 13 | "pixel-art", 14 | "get-pixels", 15 | "gif", 16 | "svg", 17 | "browserify", 18 | "angularjs" 19 | ], 20 | 21 | "license": "MIT", 22 | "ignore": [ 23 | "**/.*", 24 | "node_modules", 25 | "bower_components" 26 | ], 27 | "devDependencies": { 28 | "angular": "~1.3.13" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /index.coffee: -------------------------------------------------------------------------------- 1 | module.exports= 2 | if __filename.slice(-7) is '.coffee' 3 | require './src' 4 | else 5 | require './lib' 6 | -------------------------------------------------------------------------------- /jaggy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./').cli(process.argv); 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jaggy", 3 | "main": "jaggy", 4 | "bin": "jaggy", 5 | "description": "is Converting to SVG by pixels", 6 | "version": "0.2.0-alpha.1", 7 | 8 | "main": "lib", 9 | "files": [ 10 | "jaggy", 11 | "lib", 12 | "index.js" 13 | ], 14 | "scripts": { 15 | "start": "jasminetea --watch --timeout 20000", 16 | "test": "jasminetea --cover --report --timeout 20000", 17 | 18 | "prelocalhost": "onefile -D inclusive --output public/pkgs", 19 | "localhost": "cd public && open http://localhost:8000 && python -m SimpleHTTPServer", 20 | 21 | "watch": "abigail build-dev PKG", 22 | "build-dev": "browserify src/jaggy.browser.coffee -t coffeeify --extension .coffee > $(opc bower main)", 23 | 24 | "prebuild": "browserify src/jaggy.browser.coffee --debug -t coffeeify --extension .coffee -t [ browserify-ngannotate --x .coffee ] | exorcist public/jaggy.js.map -b . > public/jaggy.js", 25 | "build": "uglifyjs public/jaggy.js --in-source-map public/jaggy.js.map --compres --mangle --source-map public/jaggy.min.js.map --source-map-url jaggy.min.js.map > public/jaggy.min.js", 26 | "postbuild": "rm public/jaggy.js* && wc -c $(opc bower main)", 27 | 28 | "update": "git tag v$(opc package version) && git push --tags", 29 | 30 | "prepublish": "node -e \"if(process.env.TRAVIS){}else{process.exit(1)}\" && npm run compile || echo skip prepublish", 31 | "compile": "coffee --output lib --bare --compile src", 32 | "postcompile": "coffee --bare --compile index.coffee" 33 | }, 34 | 35 | "dependencies": { 36 | "commander": "^2.6.0", 37 | "dom-lite": "^0.5.0", 38 | "get-pixels": "^3.2.3", 39 | "gify-parse": "^1.0.4", 40 | "gulp-util": "^3.0.3", 41 | "lz-string": "^1.4.1", 42 | "mime": "^1.2.11", 43 | "through2": "^2.0.0" 44 | }, 45 | "devDependencies": { 46 | "browserify": "^11.2.0", 47 | "coffee-script": "^1.10.0", 48 | "abigail": "^0.1.1", 49 | "browserify": "^11.2.0", 50 | "browserify-ngannotate": "^1.0.1", 51 | "coffeeify": "^1.0.0", 52 | "exorcist": "^0.4.0", 53 | "gulp": "^3.9.0", 54 | "jasminetea": "^0.2.1", 55 | "ng-annotate": "^1.0.2", 56 | "onefile": "^0.3.3", 57 | "uglify-js": "^2.4.19", 58 | "object-parser-cli": "0.0.1" 59 | }, 60 | 61 | "keywords": [ 62 | "pixelart", 63 | "get-pixels", 64 | "cross-platform", 65 | "gulpplugin", 66 | "gif" 67 | ], 68 | "repository": { 69 | "type": "git", 70 | "url": "https://github.com/59naga/jaggy.git" 71 | }, 72 | "bugs": { 73 | "url": "https://github.com/59naga/jaggy/issues" 74 | }, 75 | "homepage": "https://github.com/59naga/jaggy", 76 | "author": "59naga (http://berabou.me/)", 77 | "license": "MIT" 78 | } 79 | -------------------------------------------------------------------------------- /public/chrono_trigger.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/59naga/jaggy/907c9b93b7964881be5d63b5574e2375d98d3fbd/public/chrono_trigger.JPG -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 11 | 12 | 44 | 47 | 48 | 49 |
50 |

angular.js

51 |

toggle glitch

52 | for Angular.js 53 | for Angular.js 54 | for Angular.js 55 | for Angular.js 56 | for Angular.js 57 | for Angular.js 58 | for Angular.js 59 | 60 | 61 |
62 | 63 |
64 |

standalone

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 | 76 | 77 | -------------------------------------------------------------------------------- /public/moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/59naga/jaggy/907c9b93b7964881be5d63b5574e2375d98d3fbd/public/moon.png -------------------------------------------------------------------------------- /public/uma.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/59naga/jaggy/907c9b93b7964881be5d63b5574e2375d98d3fbd/public/uma.gif -------------------------------------------------------------------------------- /public/uma2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/59naga/jaggy/907c9b93b7964881be5d63b5574e2375d98d3fbd/public/uma2.gif -------------------------------------------------------------------------------- /public/yuno.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/59naga/jaggy/907c9b93b7964881be5d63b5574e2375d98d3fbd/public/yuno.png -------------------------------------------------------------------------------- /src/classes.coffee: -------------------------------------------------------------------------------- 1 | # Shim dependencies 2 | document= window?.document 3 | if not window? 4 | document= require('dom-lite').document 5 | document.createElementNS= (ns,name)-> 6 | element= document.createElement name 7 | element.setAttributeNS?= (ns,key,value)-> this.setAttribute key,value 8 | element 9 | 10 | class Frames 11 | constructor:(image,options={})-> 12 | @attrs= 13 | 'version': '1.1' 14 | 'xmlns': 'http://www.w3.org/2000/svg' 15 | 'xmlns:xlink': 'http://www.w3.org/1999/xlink' 16 | 17 | 'shape-rendering': 'crispEdges' 18 | 'width': image.width 19 | 'height': image.height 20 | 'viewBox': "0 0 #{image.width} #{image.height}" 21 | 22 | @frame_size= image.width*image.height*image.channel 23 | @frames= [] 24 | @delays= image.anime || [] 25 | 26 | i= 0 27 | while image.data[i*@frame_size] isnt undefined 28 | frame= new Frame 29 | frame.delay= image.anime.delays[i] if image.anime? 30 | frame.disposal= image.anime.disposals[i] if image.anime? 31 | frame.putImageData i*@frame_size,i*@frame_size+@frame_size,image,options 32 | @frames.push frame 33 | 34 | i++ 35 | 36 | toSVG:-> 37 | svg= document.createElementNS 'http://www.w3.org/2000/svg','svg' 38 | for key,value of @attrs 39 | svg.setAttribute key,value if key.indexOf('xmlns') is 0 40 | svg.setAttributeNS null,key,value if key.indexOf('xmlns') isnt 0 41 | if @frames.length is 1 42 | svg.appendChild @frames[0].toG() 43 | else 44 | svg.setAttribute 'id','A'+@uuid() 45 | svg.appendChild @createAnime() 46 | svg.appendChild @createScript(svg.id) 47 | svg 48 | 49 | uuid:-> 50 | # via http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript 51 | S4=-> (((1+Math.random())*0x10000)|0).toString(16).substring(1) 52 | S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4() 53 | 54 | createAnime:-> 55 | g= document.createElementNS 'http://www.w3.org/2000/svg','g' 56 | g.setAttribute 'style','display:none' 57 | g.appendChild frame.toG() for frame in @frames 58 | g 59 | 60 | createScript:(id)-> 61 | # Can not uglifyjs(The below code toString() by coffee-repl) 62 | ### 63 | animation= (id)-> 64 | i= 0 65 | frames= [].slice.call window.document.querySelectorAll '#'+id+'>g>g' 66 | display= null 67 | 68 | setTimeout -> nextFrame() 69 | nextFrame=-> 70 | frame= frames[i] 71 | frame= frames[i= 0] if frame is undefined 72 | frame_id= frame.getAttribute 'id' 73 | if frame_id is null 74 | frame_id= id+'_'+('0000'+i).slice(-5) 75 | frame.setAttribute 'id',frame_id 76 | 77 | if i is 0 or frame.getAttribute('disposal') is '2' 78 | uses= window.document.querySelectorAll '#'+id+'>use' 79 | use.parentNode.removeChild use for use in uses 80 | 81 | i++ 82 | createDisplay frame_id 83 | setTimeout nextFrame,frame.getAttribute 'delay' 84 | 85 | createDisplay=(frame_id)-> 86 | display= window.document.createElementNS 'http://www.w3.org/2000/svg','use' 87 | display.setAttributeNS 'http://www.w3.org/1999/xlink','href','#'+frame_id if frame_id 88 | 89 | anime= window.document.querySelector '#'+id 90 | anime.insertBefore display,window.document.querySelector '#'+id+'>g' if anime? 91 | ### 92 | animation= "function (id) {\n var createDisplay, display, frames, i, nextFrame;\n i = 0;\n frames = [].slice.call(window.document.querySelectorAll(\'#\' + id + \'>g>g\'));\n display = null;\n setTimeout(function() {\n return nextFrame();\n });\n nextFrame = function() {\n var frame, frame_id, j, len, use, uses;\n frame = frames[i];\n if (frame === void 0) {\n frame = frames[i = 0];\n }\n frame_id = frame.getAttribute(\'id\');\n if (frame_id === null) {\n frame_id = id + \'_\' + (\'0000\' + i).slice(-5);\n frame.setAttribute(\'id\', frame_id);\n }\n if (i === 0 || frame.getAttribute(\'disposal\') === \'2\') {\n uses = window.document.querySelectorAll(\'#\' + id + \'>use\');\n for (j = 0, len = uses.length; j < len; j++) {\n use = uses[j];\n use.parentNode.removeChild(use);\n }\n }\n i++;\n createDisplay(frame_id);\n return setTimeout(nextFrame, frame.getAttribute(\'delay\'));\n };\n return createDisplay = function(frame_id) {\n var anime;\n display = window.document.createElementNS(\'http://www.w3.org/2000/svg\', \'use\');\n if (frame_id) {\n display.setAttributeNS(\'http://www.w3.org/1999/xlink\', \'href\', \'#\' + frame_id);\n }\n anime = window.document.querySelector(\'#\' + id);\n if (anime != null) {\n return anime.insertBefore(display, window.document.querySelector(\'#\' + id + \'>g\'));\n }\n };\n}" 93 | 94 | script= document.createElement 'script' 95 | script.appendChild document.createTextNode "(#{animation})('#{id}');" 96 | script 97 | 98 | class Frame# has many Color 99 | constructor:-> 100 | putImageData:(begin,end,image,options={})-> 101 | for key,value of this 102 | delete this[key] if this.hasOwnProperty key and key isnt 'attrs' 103 | 104 | return if not image.data? 105 | 106 | i= 0 107 | increment= if options.glitch? then options.glitch else 4 108 | while (begin+i) <= end 109 | opacity= image.data[begin+i+3] > 0 and image.data[begin+i+0]? 110 | if opacity 111 | values= [] 112 | values.push image.data[begin+i+0] 113 | values.push image.data[begin+i+1] 114 | values.push image.data[begin+i+2] 115 | values.push (image.data[begin+i+3]/255).toFixed(2) 116 | 117 | x= (i/4)% image.width 118 | y= ~~((i/4)/image.width) 119 | 120 | rgba= 'rgba('+values.join(',')+')' 121 | @[rgba]= new Color if not @[rgba]? 122 | @[rgba].put (new Point x,y) 123 | 124 | i= if typeof increment is 'function' then increment(i) else i+increment 125 | 126 | toG:-> 127 | g= document.createElementNS 'http://www.w3.org/2000/svg','g' 128 | for key,value of this 129 | g.setAttribute key,value if typeof value is 'number' 130 | g.appendChild value.toPath key if value instanceof Color 131 | g 132 | 133 | class Color# has many Rect in @points 134 | constructor:(@points=[])-> 135 | put:(point)-> @points.push point 136 | 137 | toPath:(fill='black')-> 138 | path= document.createElementNS 'http://www.w3.org/2000/svg','path' 139 | path.setAttributeNS null,'fill',fill if fill.length 140 | path.setAttributeNS null, 'd',@getRects().map((rect)->rect.toD()).join '' if @points.length 141 | path 142 | 143 | getRects:-> 144 | rects= [] 145 | 146 | # Merge @points for horizontal 147 | rect_index= {}# Merge to rect of over if equal x and width 148 | i= 0 149 | while i < @points.length 150 | point= @points[i++] 151 | 152 | left= rects[rects.length-1]|| {} 153 | is_left= left.y is point.y and (left.x+left.width) is point.x 154 | if is_left 155 | left.width++ 156 | continue 157 | 158 | rect_index[left.x]?= {} 159 | rect_index[left.x][left.y]= left if left.width 160 | left_over= rect_index[left.x][left.y-1] || {} 161 | is_same_length= left.width is left_over.width and left_over.x isnt undefined 162 | if is_same_length 163 | left_over.height++# Glitch point... 164 | rect_index[left.x][left.y]= left_over 165 | rects.pop() 166 | i-- 167 | continue 168 | 169 | rects.push point.toRect() 170 | 171 | rects 172 | 173 | class Rect 174 | constructor:(point)-> 175 | @x= point.x 176 | @y= point.y 177 | @width= 1 178 | @height= 1 179 | 180 | toD: -> 181 | # 1pixel = 182 | 'M'+@x+','+@y+'h'+@width+'v'+@height+'h-'+@width+'Z'; 183 | 184 | class Point 185 | constructor:(@x,@y)-> 186 | toRect:-> new Rect this 187 | 188 | module.exports= {Frames, Frame, Color, Rect, Point} -------------------------------------------------------------------------------- /src/index.coffee: -------------------------------------------------------------------------------- 1 | module.exports= require './jaggy' 2 | -------------------------------------------------------------------------------- /src/jaggy.angular.coffee: -------------------------------------------------------------------------------- 1 | Jaggy= require './jaggy' 2 | 3 | service= angular.module 'jaggy',[] 4 | service.constant 'jaggy',window.jaggy.options 5 | 6 | service.config (jaggy)-> 7 | key= 'jaggy:version' 8 | value= '0.1.17' 9 | localStorage.clear() if localStorage.getItem(key) isnt value 10 | localStorage.setItem key,value 11 | 12 | service.directive 'jaggy',( 13 | jaggy 14 | $compile 15 | )-> 16 | scope:{ 17 | jagged:'=' 18 | } 19 | link:(scope,element,attrs)-> 20 | element.css 'display','none' 21 | 22 | scope.config= jaggy 23 | scope.$watch 'config',(-> createSVG()),yes 24 | 25 | createSVG= -> 26 | # fix 27 | url= attrs.src 28 | url?= attrs.ngSrc 29 | if not url? or url.length is 0 30 | if jaggy.emptySVG 31 | element.replaceWith Jaggy.emptySVG() 32 | return 33 | 34 | options= angular.copy jaggy 35 | if attrs.jaggy 36 | for param in attrs.jaggy.split ';' 37 | [key,value]= param.split ':' 38 | options[key]= value 39 | 40 | Jaggy.createSVG url,options,(error,svg)-> 41 | svg= Jaggy.regenerateUUID svg if svg? 42 | svgElement= angular.element svg if svg? 43 | if error 44 | return element.css 'display',null if error is true # over pixelLimit 45 | throw error if not jaggy.emptySVG 46 | svgElement= angular.element Jaggy.emptySVG() 47 | angularElement= $compile(svgElement) scope 48 | element.replaceWith angularElement 49 | element= angularElement 50 | 51 | # fix animatedGif caching 52 | script= element.find 'script' 53 | eval script.html() if script? 54 | 55 | scope.jagged scope,element,attrs if typeof scope.jagged is 'function' -------------------------------------------------------------------------------- /src/jaggy.browser.coffee: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | Jaggy= require './jaggy' 3 | Jaggy.options= 4 | cache: yes 5 | cacheScript: 'base64' 6 | emptySVG: yes 7 | pixelLimit: 0 8 | timeout: 0 9 | glitch: 4 10 | debug: off 11 | 12 | Frames= (require './classes').Frames 13 | 14 | getPixels= require 'get-pixels' 15 | gifyParse= require 'gify-parse' 16 | LZString= require 'lz-string' 17 | 18 | # Methods for browser 19 | Jaggy.createSVG= (img,args...,callback)-> 20 | beginQueue= not Jaggy.queues? 21 | requestAnimationFrame -> 22 | Jaggy.nextQueue() if beginQueue 23 | 24 | if not Jaggy.queues? 25 | Jaggy.queues= [] 26 | Jaggy.begin= Date.now() 27 | console.log 'jaggy:createSVG','start' if Jaggy.options.debug 28 | 29 | queues= Jaggy.queues 30 | queues.push arguments 31 | Jaggy.nextQueue= -> 32 | queue= Jaggy.queues.shift() 33 | if not queue? 34 | console.log 'jaggy:createSVG','successfully',(Date.now()- Jaggy.begin).toLocaleString(),'msec' if Jaggy.options.debug 35 | return Jaggy.queues= null 36 | 37 | [img,args...,callback]= queue 38 | options= args[0] or {} 39 | Jaggy._createSVG img,options,-> 40 | callback arguments... 41 | 42 | Jaggy.nextQueue() 43 | 44 | Jaggy._createSVG= (img,args...,callback)-> 45 | url= img.getAttribute 'src' if img.getAttribute? 46 | url?= img 47 | options= args[0] or {} 48 | cacheUrl= url+options.glitch 49 | 50 | if options.cache 51 | begin= Date.now() 52 | cache= Jaggy.getCache cacheUrl,options 53 | console.log 'jaggy:cached',cacheUrl,cache.innerHTML.length.toLocaleString(),'decompressed',Date.now()-begin,'msec' if Jaggy.options.debug and cache? 54 | return callback null,cache if cache? and options.cache 55 | 56 | type= '.GIF' if url.split('?')[0].match /.gif$/i 57 | getPixels url,type,(error,pixels)-> 58 | return callback error,null if error? 59 | return callback true,null if options.pixelLimit>0 and pixels.data.length> options.pixelLimit 60 | 61 | if pixels.shape.length is 3 62 | begin= Date.now() 63 | Jaggy.convertToSVG pixels,options,(error,svg)-> 64 | Jaggy.setCache cacheUrl,svg,options if options.cache 65 | console.log 'jaggy:converted',cacheUrl,Date.now()-begin,'msec' if Jaggy.options.debug 66 | console.log 'jaggy:rendered',cacheUrl,svg.innerHTML.length.toLocaleString() if Jaggy.options.debug 67 | callback error,svg 68 | 69 | if pixels.shape.length is 4 70 | xhr= new XMLHttpRequest 71 | xhr.open 'GET',url,true 72 | xhr.timeout= Jaggy.options.timeout 73 | xhr.responseType= 'arraybuffer' 74 | xhr.send() 75 | xhr.onerror= -> callback xhr.statusText,null 76 | xhr.onload= -> 77 | anime= gifyParse.getInfo xhr.response 78 | anime.delays= anime.images.map (image)-> image.delay 79 | anime.disposals= anime.images.map (image)-> image.disposal 80 | pixels.anime= anime 81 | 82 | begin= Date.now() 83 | Jaggy.convertToSVG pixels,options,(error,svg)-> 84 | Jaggy.setCache cacheUrl,svg,options if options.cache 85 | console.log 'jaggy:converted',cacheUrl,Date.now()-begin,'msec' if Jaggy.options.debug 86 | console.log 'jaggy:rendered',cacheUrl,svg?.innerHTML.length.toLocaleString() if Jaggy.options.debug 87 | callback error,svg 88 | 89 | Jaggy.flush= ()-> 90 | for key in Object.keys localStorage 91 | localStorage.removeItem key if key.indexOf 'jaggy' is 0 92 | 'flushed' 93 | 94 | Jaggy.getCache= (url,options={})-> 95 | div= document.createElement 'div' 96 | div.innerHTML= LZString.decompressFromBase64 localStorage.getItem 'jaggy:'+url 97 | 98 | # Decoding via localStorage 99 | if options.cacheScript is 'base64' 100 | script= div.querySelector 'script' 101 | if script 102 | enableScript= document.createElement 'script' 103 | enableScript.innerHTML= atob div.innerHTML.match('')[1] 104 | script.parentNode.replaceChild enableScript,script 105 | 106 | div.querySelector 'svg' 107 | 108 | Jaggy.setCache= (url,element,options={})-> 109 | div= document.createElement 'div' 110 | div.appendChild element.cloneNode true 111 | if options.cacheScript is 'base64' 112 | script= div.querySelector 'script' 113 | script.innerHTML= btoa script.innerHTML if script 114 | 115 | cache= div.innerHTML 116 | 117 | # Maybe error due to capacity 10MB 118 | try 119 | compressed= LZString.compressToBase64 cache 120 | if Jaggy.options.debug 121 | console.log 'jaggy:compressed',url,~~((cache.length-compressed.length)/cache.length*100)+'%',cache.length.toLocaleString(),'->',compressed.length.toLocaleString() 122 | localStorage.setItem 'jaggy:'+url,compressed 123 | catch error 124 | localStorage.removeItem 'jaggy:'+url 125 | 126 | console.error 'jaggy:'+url,cache.length,error 127 | 128 | Jaggy.replaceByClass= -> 129 | imgs= document.querySelectorAll '.jaggy' 130 | 131 | queues= [] 132 | for img,i in imgs 133 | do (img,i)-> 134 | next= -> 135 | queues[i+1]() if queues[i+1]? 136 | delete queues[i+1] 137 | queues.push -> 138 | Jaggy.createSVG img,Jaggy.options,(error,svg)-> 139 | return if error is yes # pixelLimit 140 | # console.error error if error? 141 | 142 | svg= Jaggy.regenerateUUID svg if not error? 143 | svg= Jaggy.emptySVG() if error? and Jaggy.options.emptySVG 144 | img.parentNode.replaceChild svg,img 145 | next() 146 | 147 | queues[0]() if queues[0]? 148 | 149 | Jaggy.regenerateUUID= (svg)-> 150 | script= svg.querySelector 'script' 151 | if script? 152 | uuid= 'A'+Frames::uuid() 153 | 154 | id= svg.getAttribute 'id' 155 | svg.setAttribute 'id',uuid 156 | 157 | script.innerHTML= script.innerHTML.replace id,uuid if script 158 | 159 | svg 160 | 161 | Jaggy.emptySVG= -> 162 | div= document.createElement 'div' 163 | div.innerHTML= '' 164 | div.querySelector 'svg' 165 | 166 | window.jaggy= Jaggy 167 | window.addEventListener 'DOMContentLoaded',-> 168 | Jaggy.replaceByClass() 169 | 170 | require './jaggy.angular' if window.angular? -------------------------------------------------------------------------------- /src/jaggy.coffee: -------------------------------------------------------------------------------- 1 | version= (require '../package.json').version 2 | Frames= (require './classes').Frames 3 | 4 | # Dependencies for node 5 | if not window? 6 | gutil= require 'gulp-util' 7 | through2= require 'through2' 8 | Command= (require 'commander').Command 9 | 10 | path= require 'path' 11 | gulp= require 'gulp' 12 | 13 | mime= require 'mime' 14 | 15 | getPixels= require 'get-pixels' 16 | gifyParse= require 'gify-parse' 17 | 18 | # Methods for node 19 | jaggy= (options={})-> 20 | through2.obj (file,encode,next)-> 21 | return @emit 'error',new gutil.PluginError 'jaggy','Streaming not supported' if file.isStream() 22 | 23 | jaggy.readImageData file,(error,pixels)=> 24 | return @emit 'error',new gutil.PluginError 'jaggy',error if error? 25 | 26 | jaggy.convertToSVG pixels,options,(error,svg)=> 27 | return @emit 'error',new gutil.PluginError 'jaggy',error if error? 28 | 29 | file.path= gutil.replaceExtension file.path,'.svg' 30 | file.contents= new Buffer svg 31 | @push file 32 | 33 | next() 34 | 35 | jaggy.convertToSVG= (pixels,args...,callback)-> 36 | options= args[0] or {} 37 | options.glitch= +options.glitch if typeof options.glitch is 'string' 38 | return callback new Error('glitch is 0') if options.glitch is 0 39 | 40 | frames= jaggy.convert pixels,options 41 | svg= frames.toSVG options 42 | 43 | # fix to domlite 44 | if not window? 45 | svg= svg.outerHTML.replace ' viewbox=',' viewBox=' 46 | svg= svg.replace />/g,'>' 47 | 48 | callback null,svg 49 | 50 | jaggy.readImageData= (file,callback)-> 51 | return callback 'file is not object' if typeof file isnt 'object' 52 | 53 | buffer= new Buffer file.contents 54 | mimeType= mime.lookup file.path 55 | 56 | getPixels buffer,mimeType,(error,pixels)-> 57 | return callback error if error? 58 | 59 | if pixels.shape.length is 4 60 | anime= gifyParse.getInfo buffer 61 | anime.delays= anime.images.map (image)-> image.delay 62 | anime.disposals= anime.images.map (image)-> image.disposal 63 | pixels.anime= anime 64 | 65 | callback null,pixels 66 | 67 | jaggy.convert= (pixels,options={})-> 68 | throw new Error 'Not supported File' if pixels?.shape?.length is undefined 69 | 70 | [width,height,channel]= pixels.shape if pixels.shape.length is 3 71 | [frame,width,height,channel]= pixels.shape if pixels.shape.length is 4 72 | pixels.frame= frame ? 1 73 | pixels.width= width 74 | pixels.height= height 75 | pixels.channel= channel 76 | 77 | new Frames pixels,options 78 | 79 | jaggy.cli= (argv)-> 80 | cli= new Command 81 | cli 82 | .version version 83 | .usage 'file/directory [options...]' 84 | .option '-r, --recursive','Convert pixelarts in recursive directory' 85 | .option '-o, --output ','Output directory <./>','.' 86 | .option '-g, --glitch ','Glitch color palettes <4>',4 87 | .parse argv 88 | cli.help() if cli.args.length is 0 89 | 90 | globs= [] 91 | for arg in cli.args 92 | if arg.match(/(\.gif|\.jpg|\.png)$/) 93 | glob= path.resolve arg 94 | else 95 | glob= path.resolve "#{arg}/*.+(gif|png|jpg)" 96 | glob= path.resolve "#{arg}/**/*.+(gif|png|jpg)" if cli.recursive 97 | globs.push glob 98 | 99 | # Ignore unsupport extension 100 | globs.push '!**/*.!(*gif|*png|*jpg)' 101 | 102 | gulp.src globs,{base:process.cwd(),nocase:yes} 103 | .pipe jaggy glitch:cli.glitch 104 | .pipe gulp.dest path.resolve cli.output 105 | .on 'data',(file)-> 106 | from= path.relative process.cwd(),file.path 107 | to= gutil.replaceExtension from,'.svg' 108 | 109 | console.log 'Convert',to 110 | .on 'end',-> 111 | process.exit 0 112 | 113 | module.exports= jaggy 114 | -------------------------------------------------------------------------------- /test/classess.spec.coffee: -------------------------------------------------------------------------------- 1 | {Frames, Frame, Color, Rect, Point}= require '../src/classes' 2 | 3 | describe 'Classes',-> 4 | it 'Convert to by Frame',-> 5 | frame= new Frame 6 | expect(frame.toG().outerHTML).toEqual('') 7 | 8 | it 'Convert to by Color',-> 9 | color= new Color 10 | expect(color.toPath('').outerHTML).toEqual('') 11 | 12 | it 'Color has points',-> 13 | color= new Color 14 | expect(JSON.stringify color).toEqual('{"points":[]}') 15 | 16 | it 'Point is {x,y}',-> 17 | point= new Point 0,0 18 | expect(JSON.stringify point).toEqual('{"x":0,"y":0}') 19 | 20 | it 'Rect like a Point ',-> 21 | point= new Point 0,0 22 | rect= new Rect point 23 | expect(JSON.stringify rect).toMatch((JSON.stringify point).slice(-1)) 24 | 25 | it 'Rect is M0,0h1v1h-1Z',-> 26 | rect= new Rect x:0,y:0 27 | expect(rect.toD()).toEqual('M0,0h1v1h-1Z') 28 | -------------------------------------------------------------------------------- /test/jaggy.angular.spec.coffee: -------------------------------------------------------------------------------- 1 | describe 'jaggy.angular',-> 2 | -------------------------------------------------------------------------------- /test/jaggy.browser.spec.coffee: -------------------------------------------------------------------------------- 1 | describe 'jaggy.browser',-> 2 | -------------------------------------------------------------------------------- /test/jaggy.spec.coffee: -------------------------------------------------------------------------------- 1 | jaggy= require '../src' 2 | 3 | gulp= require 'gulp' 4 | fs= require 'fs' 5 | 6 | gutil= require 'gulp-util' 7 | path= require 'path' 8 | fs= require 'fs' 9 | 10 | describe 'jaggy',-> 11 | describe 'Usage for gulp',-> 12 | it 'Convert to by .gif',(done)-> 13 | glob= 'public/*.gif' 14 | 15 | files= [] 16 | gulp.src glob,{nocase:yes} 17 | .pipe jaggy() 18 | .on 'data',(file)-> files.push file 19 | .on 'end',-> 20 | file= files[0] 21 | filePath= file?.path ? '' 22 | try 23 | svg= fs.readFileSync(filePath).toString() 24 | 25 | svg?= '' 26 | 27 | expect(files.length).toBe 2 28 | expect(svg.indexOf("rgba(,,,NaN)")).toEqual -1 29 | done() 30 | 31 | it 'Convert to by .png',(done)-> 32 | glob= 'public/*.png' 33 | 34 | files= [] 35 | gulp.src glob,{nocase:yes} 36 | .pipe jaggy() 37 | .on 'data',(file)-> files.push file 38 | .on 'end',-> 39 | file= files[0] 40 | filePath= file?.path ? '' 41 | try 42 | svg= fs.readFileSync(filePath).toString() 43 | 44 | svg?= '' 45 | 46 | expect(files.length).toBe 2 47 | expect(svg.indexOf("rgba(,,,NaN)")).toEqual -1 48 | done() 49 | 50 | it 'Convert to by .jpg',(done)-> 51 | glob= 'public/*.jpg' 52 | 53 | files= [] 54 | gulp.src glob,{nocase:yes} 55 | .pipe jaggy() 56 | .on 'data',(file)-> files.push file 57 | .on 'end',-> 58 | file= files[0] 59 | filePath= file?.path ? '' 60 | try 61 | svg= fs.readFileSync(filePath).toString() 62 | 63 | svg?= '' 64 | 65 | expect(files.length).toBe 1 66 | expect(svg.indexOf("rgba(,,,NaN)")).toEqual -1 67 | done() 68 | 69 | it 'Glitch to by .png',(done)-> 70 | glob= 'public/yuno.PNG' 71 | 72 | files= [] 73 | gulp.src glob,{nocase:yes} 74 | .pipe jaggy {glitch:3} 75 | .on 'data',(file)-> files.push file 76 | .on 'end',-> 77 | file= files[0] 78 | filePath= file?.path ? '' 79 | try 80 | svg= fs.readFileSync(filePath).toString() 81 | 82 | svg?= '' 83 | 84 | expect(files.length).toBe 1 85 | expect(svg.indexOf("rgba(,,,NaN)")).toEqual -1 86 | done() 87 | 88 | it 'Convert to pixels by gutil.File',(done)-> 89 | file= new gutil.File 90 | cwd: process.cwd() 91 | base: "#{process.cwd()}/public/" 92 | path: "#{process.cwd()}/public/moon.png" 93 | contents: fs.readFileSync "#{process.cwd()}/public/moon.png" 94 | 95 | jaggy.readImageData file,(error,pixels)-> 96 | expect(error).toEqual(null) 97 | expect(pixels.data.length).toEqual(96*192*4) 98 | expect(JSON.stringify pixels.shape).toEqual('[96,192,4]') 99 | 100 | done() 101 | --------------------------------------------------------------------------------