Skip to content

Commit cef3d91

Browse files
committed
"Objectifying" Image and encapsulating filename creation in it as well
- Make Image singular, following other classes pattern; - Move mkdir to `grunt prepare`;
1 parent aed3097 commit cef3d91

File tree

5 files changed

+255
-224
lines changed

5 files changed

+255
-224
lines changed

grunt.js

+3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ function setup( callback ) {
4747
if ( !fs.existsSync( "tmp" ) ) {
4848
grunt.file.mkdir( "tmp" );
4949
}
50+
if ( !fs.existsSync( "tmp/cache" ) ) {
51+
grunt.file.mkdir( "tmp/cache" );
52+
}
5053
callback();
5154
}
5255

lib/themeroller.image.js

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
var async = require( "async" ),
2+
fs = require( "fs" ),
3+
im = require( "gm" ).subClass({ imageMagick: true }),
4+
path = require( "path" );
5+
6+
function expandColor( color ) {
7+
if ( color.length === 3 ) {
8+
return [ 0, 0, 1, 1, 2, 2 ].map(function( i ) {
9+
return color[i];
10+
}).join( "" );
11+
}
12+
return color;
13+
}
14+
15+
function hashColor( color ) {
16+
if ( ( color.length === 3 || color.length === 6 ) && /^[0-9a-f]+$/i.test( color ) ) {
17+
color = "#" + color;
18+
}
19+
return color;
20+
}
21+
22+
// I don't know if there's a better solution, but without the below conversion to Buffer we're not able to use it.
23+
function stream2Buffer( callback ) {
24+
return function( err, data ) {
25+
if ( err ) {
26+
return callback( err );
27+
}
28+
var chunks = [],
29+
dataLen = 0;
30+
31+
data.on( "data", function( chunk ) {
32+
chunks.push( chunk );
33+
dataLen += chunk.length;
34+
});
35+
36+
data.on( "end", function() {
37+
var i = 0,
38+
buffer = new Buffer( dataLen );
39+
chunks.forEach(function ( chunk ) {
40+
chunk.copy( buffer, i, 0, chunk.length );
41+
i += chunk.length;
42+
});
43+
callback( null, buffer );
44+
});
45+
46+
data.on( "error", function( err ) {
47+
callback( err );
48+
});
49+
};
50+
}
51+
52+
var generateIcon, generateImage, generateTexture,
53+
cacheDirectory = __dirname + "/../tmp/cache",
54+
concurrentQueues = 4,
55+
imageQueue = async.queue( function( task, callback ) {
56+
task( callback );
57+
}, concurrentQueues );
58+
59+
if ( !fs.existsSync( cacheDirectory ) ) {
60+
throw new Error( "Missing " + cacheDirectory + " folder. Run `grunt prepare` first." );
61+
}
62+
63+
generateImage = function( params, callback ) {
64+
if ( params.icon ) {
65+
generateIcon( params.icon, callback );
66+
} else {
67+
generateTexture( params.texture, callback );
68+
}
69+
};
70+
71+
generateIcon = function( params, callback ) {
72+
var color;
73+
74+
// Add '#' in the beginning of the colors if needed
75+
color = hashColor( params.color );
76+
77+
// http://www.imagemagick.org/Usage/masking/#shapes
78+
// $ convert <icons_mask_filename> -background <color> -alpha shape output.png
79+
80+
imageQueue.push(function( innerCallback ) {
81+
im( __dirname + "/../template/themeroller/icon/mask.png" )
82+
.background( color )
83+
.out( "-alpha", "shape" )
84+
.stream( "png", stream2Buffer( innerCallback ) );
85+
}, callback );
86+
};
87+
88+
generateTexture = function( params, callback ) {
89+
var color, filename;
90+
91+
// Add '#' in the beginning of the colors if needed
92+
color = hashColor( params.color );
93+
94+
filename = params.type.replace( /-/g, "_" ).replace( /$/, ".png" );
95+
96+
// http://www.imagemagick.org/Usage/compose/#dissolve
97+
// $ convert -size <width>x<height> 'xc:<color>' <texture_filename> -compose dissolve -define compose:args=<opacity>,100 -composite output.png
98+
99+
imageQueue.push(function( innerCallback ) {
100+
im( params.width, params.height, color )
101+
.out( __dirname + "/../template/themeroller/texture/" + filename, "-compose", "dissolve", "-define", "compose:args=" + params.opacity + ",100", "-composite" )
102+
.stream( "png", stream2Buffer( innerCallback ) );
103+
}, callback );
104+
};
105+
106+
107+
/**
108+
* Image
109+
*/
110+
function Image( params ) {
111+
var missingParams, requiredParams;
112+
113+
if ( typeof params === "string" ) {
114+
params = this._parse( params );
115+
}
116+
117+
params = params || {};
118+
119+
if ( params.icon ) {
120+
params.icon = params.icon || {};
121+
122+
// Validate Icon
123+
if ( !params.icon.color ) {
124+
throw new Error( "missing color" );
125+
}
126+
127+
} else if ( params.texture ) {
128+
params.texture = params.texture || {};
129+
130+
// Validate Texture
131+
requiredParams = [ "color", "height", "opacity", "type", "width" ];
132+
133+
missingParams = requiredParams.filter(function( param ) {
134+
return !params.texture[ param ];
135+
});
136+
137+
if ( missingParams.length ) {
138+
throw new Error( "missing \"" + missingParams.join( "\", \"" ) + "\"" );
139+
}
140+
141+
} else {
142+
throw new Error( "invalid parameters ", JSON.stringify( params ) );
143+
}
144+
145+
this.params = params;
146+
}
147+
148+
Image.prototype = {
149+
_parse: function( filename ) {
150+
var match, params;
151+
152+
if ( /^ui-icons/i.test( filename ) ) {
153+
154+
// ui-icons_<color>_256x240.png
155+
match = filename.match( /^ui-icons_(\w+)_256x240.png$/i );
156+
if ( match == null ) {
157+
throw new Error( "Invalid format: " + filename );
158+
}
159+
params = {
160+
icon: { color: match[ 1 ] }
161+
};
162+
163+
} else {
164+
165+
// ui-bg_<type>_<opacity>_<color>_<width>x<height>.png
166+
match = filename.match( /^ui-bg_([a-z0-9\-]+)_(\w+)_(\w+)_(\d+)x(\d+).png$/i );
167+
if ( match == null ) {
168+
throw new Error( "Invalid format: " + filename );
169+
}
170+
params = {
171+
texture: {
172+
type: match[ 1 ],
173+
opacity: match[ 2 ],
174+
color: match[ 3 ],
175+
width: match[ 4 ],
176+
height: match[ 5 ]
177+
}
178+
};
179+
}
180+
181+
return params;
182+
},
183+
184+
filename: function() {
185+
var color, params;
186+
if ( !this._filename ) {
187+
if ( this.params.icon ) {
188+
params = this.params.icon;
189+
color = expandColor( params.color ).replace( /^#/, "" );
190+
191+
// ui-icons_<color>_256x240.png
192+
this._filename = "ui-icons_" + color + "_256x240.png";
193+
194+
} else {
195+
params = this.params.texture;
196+
color = expandColor( params.color ).replace( /^#/, "" );
197+
198+
// ui-bg_<type>_<opacity>_<color>_<width>x<height>.png
199+
this._filename = "ui-bg_" + params.type.replace( /_/g, "-" ) + "_" + params.opacity + "_" + color + "_" + params.width + "x" + params.height + ".png";
200+
}
201+
}
202+
return this._filename;
203+
},
204+
205+
get: function( callback ) {
206+
var cacheFile = cacheDirectory + "/" + this.filename(),
207+
filename = this.filename(),
208+
params = this.params;
209+
210+
fs.readFile( cacheFile, function( err, data ) {
211+
if ( err ) {
212+
generateImage( params, function( err, data ) {
213+
if ( !err ) {
214+
// Write file asynchronously while sending data to callback.
215+
fs.writeFile( cacheFile, data );
216+
}
217+
callback( err, filename, data );
218+
});
219+
} else {
220+
callback( null, filename, data );
221+
}
222+
});
223+
}
224+
};
225+
226+
module.exports = Image;

lib/themeroller.images.js

-144
This file was deleted.

0 commit comments

Comments
 (0)