API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPColor.j
Go to the documentation of this file.
1 /*
2  * CPColor.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 
26 
31 
36 
38 
39 var _redComponent = 0,
40  _greenComponent = 1,
41  _blueComponent = 2,
42  _alphaCompnent = 3;
43 
44 var _hueComponent = 0,
45  _saturationComponent = 1,
46  _brightnessComponent = 2;
47 
48 var cachedBlackColor,
49  cachedRedColor,
50  cachedGreenColor,
51  cachedBlueColor,
52  cachedYellowColor,
53  cachedGrayColor,
54  cachedLightGrayColor,
55  cachedDarkGrayColor,
56  cachedWhiteColor,
57  cachedBrownColor,
58  cachedCyanColor,
59  cachedMagentaColor,
60  cachedOrangeColor,
61  cachedPurpleColor,
62  cachedShadowColor,
63  cachedClearColor;
64 
66 
76 @implementation CPColor : CPObject
77 {
78  CPArray _components;
79 
80  CPImage _patternImage;
81  CPString _cssString;
82 }
83 
97 + (CPColor)colorWithRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
98 {
99  return [[CPColor alloc] _initWithRGBA:[MAX(0.0, MIN(1.0, red)), MAX(0.0, MIN(1.0, green)), MAX(0.0, MIN(1.0, blue)), MAX(0.0, MIN(1.0, alpha))]];
100 }
101 
117 + (CPColor)colorWithCalibratedRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
118 {
119  return [self colorWithRed:red green:green blue:blue alpha:alpha];
120 }
121 
122 
132 + (CPColor)colorWithWhite:(float)white alpha:(float)alpha
133 {
134  return [[CPColor alloc] _initWithRGBA:[white, white, white, alpha]];
135 }
136 
148 + (CPColor)colorWithCalibratedWhite:(float)white alpha:(float)alpha
149 {
150  return [self colorWithWhite:white alpha:alpha];
151 }
152 
166 + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness
167 {
168  return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:1.0];
169 }
170 
176 + (CPColor)colorWithCalibratedHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha
177 {
178  return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha];
179 }
180 
195 + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha
196 {
197  // Clamp values.
198  hue = MAX(MIN(hue, 1.0), 0.0);
199  saturation = MAX(MIN(saturation, 1.0), 0.0);
200  brightness = MAX(MIN(brightness, 1.0), 0.0);
201 
202  if (saturation === 0.0)
203  return [CPColor colorWithCalibratedWhite:brightness alpha:alpha];
204 
205  var f = (hue * 360) % 60,
206  p = (brightness * (1 - saturation)),
207  q = (brightness * (60 - saturation * f)) / 60,
208  t = (brightness * (60 - saturation * (60 - f))) / 60,
209  b = brightness;
210 
211  switch (FLOOR(hue * 6))
212  {
213  case 0:
214  case 6:
215  return [CPColor colorWithCalibratedRed:b green:t blue:p alpha:alpha];
216  case 1:
217  return [CPColor colorWithCalibratedRed:q green:b blue:p alpha:alpha];
218  case 2:
219  return [CPColor colorWithCalibratedRed:p green:b blue:t alpha:alpha];
220  case 3:
221  return [CPColor colorWithCalibratedRed:p green:q blue:b alpha:alpha];
222  case 4:
223  return [CPColor colorWithCalibratedRed:t green:p blue:b alpha:alpha];
224  case 5:
225  return [CPColor colorWithCalibratedRed:b green:p blue:q alpha:alpha];
226  }
227 }
228 
239 + (CPColor)colorWithHexString:(string)hex
240 {
241  var rgba = hexToRGB(hex);
242  return rgba ? [[CPColor alloc] _initWithRGBA: rgba] : null;
243 }
244 
249 + (CPColor)colorWithSRGBRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
250 {
251  // TODO If Cappuccino is ported to a colorspace aware platform, this color should be in
252  // sRGBColorSpace.
253  return [self colorWithRed:red green:green blue:blue alpha:alpha];
254 }
255 
259 + (CPColor)blackColor
260 {
261  if (!cachedBlackColor)
262  cachedBlackColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0]];
263 
264  return cachedBlackColor;
265 }
266 
270 + (CPColor)blueColor
271 {
272  if (!cachedBlueColor)
273  cachedBlueColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 1.0, 1.0]];
274 
275  return cachedBlueColor;
276 }
277 
281 + (CPColor)darkGrayColor
282 {
283  if (!cachedDarkGrayColor)
284  cachedDarkGrayColor = [CPColor colorWithCalibratedWhite:1.0 / 3.0 alpha:1.0];
285 
286  return cachedDarkGrayColor;
287 }
288 
292 + (CPColor)grayColor
293 {
294  if (!cachedGrayColor)
295  cachedGrayColor = [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0];
296 
297  return cachedGrayColor;
298 }
299 
303 + (CPColor)greenColor
304 {
305  if (!cachedGreenColor)
306  cachedGreenColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 0.0, 1.0]];
307 
308  return cachedGreenColor;
309 }
310 
314 + (CPColor)lightGrayColor
315 {
316  if (!cachedLightGrayColor)
317  cachedLightGrayColor = [CPColor colorWithCalibratedWhite:2.0 / 3.0 alpha:1.0];
318 
319  return cachedLightGrayColor;
320 }
321 
325 + (CPColor)redColor
326 {
327  if (!cachedRedColor)
328  cachedRedColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 0.0, 1.0]];
329 
330  return cachedRedColor;
331 }
332 
336 + (CPColor)whiteColor
337 {
338  if (!cachedWhiteColor)
339  cachedWhiteColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 1.0, 1.0]];
340 
341  return cachedWhiteColor;
342 }
343 
347 + (CPColor)yellowColor
348 {
349  if (!cachedYellowColor)
350  cachedYellowColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 0.0, 1.0]];
351 
352  return cachedYellowColor;
353 }
354 
358 + (CPColor)brownColor
359 {
360  if (!cachedBrownColor)
361  cachedBrownColor = [[CPColor alloc] _initWithRGBA:[0.6, 0.4, 0.2, 1.0]];
362 
363  return cachedBrownColor;
364 }
365 
369 + (CPColor)cyanColor
370 {
371  if (!cachedCyanColor)
372  cachedCyanColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 1.0, 1.0]];
373 
374  return cachedCyanColor;
375 }
376 
380 + (CPColor)magentaColor
381 {
382  if (!cachedMagentaColor)
383  cachedMagentaColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 1.0, 1.0]];
384 
385  return cachedMagentaColor;
386 }
387 
391 + (CPColor)orangeColor
392 {
393  if (!cachedOrangeColor)
394  cachedOrangeColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.5, 0.0, 1.0]];
395 
396  return cachedOrangeColor;
397 }
398 
402 + (CPColor)purpleColor
403 {
404  if (!cachedPurpleColor)
405  cachedPurpleColor = [[CPColor alloc] _initWithRGBA:[0.5, 0.0, 0.5, 1.0]];
406 
407  return cachedPurpleColor;
408 }
409 
414 + (CPColor)shadowColor
415 {
416  if (!cachedShadowColor)
417  cachedShadowColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0 / 3.0]];
418 
419  return cachedShadowColor;
420 }
421 
426 + (CPColor)clearColor
427 {
428  if (!cachedClearColor)
429  cachedClearColor = [self colorWithCalibratedWhite:0.0 alpha:0.0];
430 
431  return cachedClearColor;
432 }
433 
434 + (CPColor)alternateSelectedControlColor
435 {
436  return [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]];
437 }
438 
439 + (CPColor)secondarySelectedControlColor
440 {
441  return [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]];
442 }
443 
449 + (CPColor)colorWithPatternImage:(CPImage)anImage
450 {
451  return [[CPColor alloc] _initWithPatternImage:anImage];
452 }
453 
460 + (CPColor)colorWithCSSString:(CPString)aString
461 {
462  return [[CPColor alloc] _initWithCSSString: aString];
463 }
464 
465 /* @ignore */
466 - (id)_initWithCSSString:(CPString)aString
467 {
468  if (aString.indexOf("rgb") == CPNotFound)
469  return nil;
470 
471  self = [super init];
472 
473  var startingIndex = aString.indexOf("("),
474  parts = aString.substring(startingIndex + 1).split(',');
475 
476  _components = [
477  parseInt(parts[0], 10) / 255.0,
478  parseInt(parts[1], 10) / 255.0,
479  parseInt(parts[2], 10) / 255.0,
480  parts[3] ? parseFloat(parts[3], 10) : 1.0
481  ];
482 
483  // We can't reuse aString as _cssString because the browser might not support the `rgba` syntax, and aString might
484  // use it (issue #1413.)
485  [self _initCSSStringFromComponents];
486 
487  return self;
488 }
489 
490 /* @ignore */
491 - (id)_initWithRGBA:(CPArray)components
492 {
493  self = [super init];
494 
495  if (self)
496  {
497  _components = components;
498 
499  [self _initCSSStringFromComponents];
500  }
501 
502  return self;
503 }
504 
505 - (void)_initCSSStringFromComponents
506 {
507  var hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
508 
509  _cssString = (hasAlpha ? "rgba(" : "rgb(") +
510  parseInt(_components[0] * 255.0) + ", " +
511  parseInt(_components[1] * 255.0) + ", " +
512  parseInt(_components[2] * 255.0) +
513  (hasAlpha ? (", " + _components[3]) : "") + ")";
514 }
515 
516 /* @ignore */
517 - (id)_initWithPatternImage:(CPImage)anImage
518 {
519  self = [super init];
520 
521  if (self)
522  {
523  _patternImage = anImage;
524  _cssString = "url(\"" + [_patternImage filename] + "\")";
525  _components = [0.0, 0.0, 0.0, 1.0];
526  }
527 
528  return self;
529 }
530 
534 - (CPImage)patternImage
535 {
536  return _patternImage;
537 }
538 
542 - (float)alphaComponent
543 {
544  return _components[3];
545 }
546 
550 - (float)blueComponent
551 {
552  return _components[2];
553 }
554 
558 - (float)greenComponent
559 {
560  return _components[1];
561 }
562 
566 - (float)redComponent
567 {
568  return _components[0];
569 }
570 
582 - (CPArray)components
583 {
584  return _components;
585 }
586 
594 - (CPColor)colorWithAlphaComponent:(float)anAlphaComponent
595 {
596  var components = _components.slice();
597 
598  components[components.length - 1] = anAlphaComponent;
599 
600  return [[[self class] alloc] _initWithRGBA:components];
601 }
602 
606 - (CPColor)colorUsingColorSpaceName:(id)aColorSpaceName
607 {
608  return self;
609 }
610 
624 - (CPArray)hsbComponents
625 {
626  var red = ROUND(_components[_redComponent] * 255.0),
627  green = ROUND(_components[_greenComponent] * 255.0),
628  blue = ROUND(_components[_blueComponent] * 255.0);
629 
630  var max = MAX(red, green, blue),
631  min = MIN(red, green, blue),
632  delta = max - min;
633 
634  var brightness = max / 255.0,
635  saturation = (max != 0) ? delta / max : 0;
636 
637  var hue;
638 
639  if (saturation == 0)
640  {
641  hue = 0;
642  }
643  else
644  {
645  var rr = (max - red) / delta,
646  gr = (max - green) / delta,
647  br = (max - blue) / delta;
648 
649  if (red == max)
650  hue = br - gr;
651  else if (green == max)
652  hue = 2 + rr - br;
653  else
654  hue = 4 + gr - rr;
655 
656  hue /= 6;
657  if (hue < 0)
658  hue++;
659  }
660 
661  return [
662  hue,
663  saturation,
664  brightness
665  ];
666 }
667 
671 - (float)hueComponent
672 {
673  return [self hsbComponents][0];
674 }
675 
679 - (float)saturationComponent
680 {
681  return [self hsbComponents][1];
682 }
683 
687 - (float)brightnessComponent
688 {
689  return [self hsbComponents][2];
690 }
691 
701 - (CPString)cssString
702 {
703  return _cssString;
704 }
705 
709 - (CPString)hexString
710 {
711  return rgbToHex([self redComponent], [self greenComponent], [self blueComponent]);
712 }
713 
714 - (BOOL)isEqual:(CPColor)aColor
715 {
716  if (!aColor)
717  return NO;
718 
719  if (aColor === self)
720  return YES;
721 
722  if (![aColor isKindOfClass:CPColor])
723  return NO;
724 
725  if (_patternImage || [aColor patternImage])
726  return [_patternImage isEqual:[aColor patternImage]];
727 
728  // We don't require the components to be equal beyond 8 bits since otherwise
729  // simple rounding errors will make two colours which are exactly the same on
730  // screen compare unequal.
731  return ROUND([self redComponent] * 255.0) == ROUND([aColor redComponent] * 255.0) &&
732  ROUND([self greenComponent] * 255.0) == ROUND([aColor greenComponent] * 255.0) &&
733  ROUND([self blueComponent] * 255.0) == ROUND([aColor blueComponent] * 255.0) &&
734  [self alphaComponent] == [aColor alphaComponent];
735 }
736 
737 - (CPString)description
738 {
739  var description = [super description],
740  patternImage = [self patternImage];
741 
742  if (!patternImage)
743  return description + " " + [self cssString];
744 
745  description += " {\n";
746 
747  if ([patternImage isThreePartImage] || [patternImage isNinePartImage])
748  {
749  var slices = [patternImage imageSlices];
750 
751  if ([patternImage isThreePartImage])
752  description += " orientation: " + ([patternImage isVertical] ? "vertical" : "horizontal") + ",\n";
753 
754  description += " patternImage (" + slices.length + " part): [\n";
755 
756  for (var i = 0; i < slices.length; ++i)
757  {
758  var imgDescription = [slices[i] description] || "nil";
759 
760  description += imgDescription.replace(/^/mg, " ") + ",\n";
761  }
762 
763  description = description.substr(0, description.length - 2) + "\n ]\n}";
764  }
765  else
766  description += ([patternImage description] || "nil").replace(/^/mg, " ") + "\n}";
767 
768  return description;
769 }
770 
771 @end
772 
774 
778 - (void)set
779 {
780  [self setFill];
781  [self setStroke];
782 }
783 
787 - (void)setFill
788 {
790  CGContextSetFillColor(ctx, self);
791 }
792 
796 - (void)setStroke
797 {
799  CGContextSetStrokeColor(ctx, self);
800 }
801 
802 @end
803 
804 @implementation CPColor (Debugging)
805 
806 + (CPColor)randomColor
807 {
808  return [CPColor colorWithRed:RAND() green:RAND() blue:RAND() alpha:1.0];
809 }
810 
811 @end
812 
814 var CPColorComponentsKey = @"CPColorComponentsKey",
815  CPColorPatternImageKey = @"CPColorPatternImageKey";
817 
818 @implementation CPColor (CPCoding)
819 
824 - (id)initWithCoder:(CPCoder)aCoder
825 {
826  if ([aCoder containsValueForKey:CPColorPatternImageKey])
827  return [self _initWithPatternImage:[aCoder decodeObjectForKey:CPColorPatternImageKey]];
828 
829  return [self _initWithRGBA:[aCoder decodeObjectForKey:CPColorComponentsKey]];
830 }
831 
836 - (void)encodeWithCoder:(CPCoder)aCoder
837 {
838  if (_patternImage)
839  [aCoder encodeObject:_patternImage forKey:CPColorPatternImageKey];
840  else
841  [aCoder encodeObject:_components forKey:CPColorComponentsKey];
842 }
843 
844 @end
845 
846 
848 var hexCharacters = "0123456789ABCDEF";
849 
850 /*
851  Used for the CPColor +colorWithHexString: implementation.
852  Returns an array of rgb components.
853 */
854 var hexToRGB = function(hex)
855 {
856  if (hex.length == 3)
857  hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
858 
859  if (hex.length != 6)
860  return null;
861 
862  hex = hex.toUpperCase();
863 
864  for (var i = 0; i < hex.length; i++)
865  if (hexCharacters.indexOf(hex.charAt(i)) == -1)
866  return null;
867 
868  var red = (hexCharacters.indexOf(hex.charAt(0)) * 16 + hexCharacters.indexOf(hex.charAt(1))) / 255.0,
869  green = (hexCharacters.indexOf(hex.charAt(2)) * 16 + hexCharacters.indexOf(hex.charAt(3))) / 255.0,
870  blue = (hexCharacters.indexOf(hex.charAt(4)) * 16 + hexCharacters.indexOf(hex.charAt(5))) / 255.0;
871 
872  return [red, green, blue, 1.0];
873 };
874 
875 var rgbToHex = function(r,g,b)
876 {
877  return byteToHex(r) + byteToHex(g) + byteToHex(b);
878 };
879 
880 var byteToHex = function(n)
881 {
882  if (!n || isNaN(n))
883  return "00";
884 
885  n = FLOOR(MIN(255, MAX(0, 256 * n)));
886 
887  return hexCharacters.charAt((n - n % 16) / 16) +
888  hexCharacters.charAt(n % 16);
889 };
890 
1110 function CPColorWithImages()
1111 {
1112  var slices = nil,
1113  numParts = 0,
1114  isVertical = false,
1115  imageFactory = CPImageInBundle,
1116  args = Array.prototype.slice.apply(arguments);
1117 
1118  if (typeof(args[args.length - 1]) === "function")
1119  imageFactory = args.pop();
1120 
1121  switch (args.length)
1122  {
1123  case 1:
1124  return imageFromSlices(args[0], isVertical, imageFactory);
1125 
1126  case 2:
1127  // New-style 3-part and 9-part images
1128  if (typeof(args[0]) === "string")
1129  return patternColorsFromPattern.call(this, args[0], args[1], imageFactory);
1130 
1131  return imageFromSlices(args[0], args[1], imageFactory);
1132 
1133  case 3:
1134  case 4:
1135  return [CPColor colorWithPatternImage:imageFactory(args[0], args[1], args[2], args[3])];
1136 
1137  default:
1138  throw("ERROR: Invalid argument count: " + args.length);
1139  }
1140 }
1141 
1142 var imageFromSlices = function(slices, isVertical, imageFactory)
1143 {
1144  var imageSlices = [];
1145 
1146  for (var i = 0; i < slices.length; ++i)
1147  {
1148  var slice = slices[i];
1149 
1150  imageSlices.push(slice ? imageFactory(slice[0], slice[1], slice[2], slice[3]) : nil);
1151  }
1152 
1153  switch (slices.length)
1154  {
1155  case 3:
1156  return [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:imageSlices isVertical:isVertical]];
1157 
1158  case 9:
1159  return [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:imageSlices]];
1160 
1161  default:
1162  throw("ERROR: Invalid number of image slices: " + slices.length);
1163  }
1164 };
1165 
1166 var patternColorsFromPattern = function(pattern, attributes, imageFactory)
1167 {
1168  if (pattern.match(/^.*\{[^}]+\}/))
1169  {
1170  var width = attributes["width"],
1171  height = attributes["height"],
1172  separator = attributes["separator"] || "-",
1173  orientation = attributes["orientation"],
1174  rightWidth,
1175  bottomHeight,
1176  centerWidthHeight,
1177  centerIsNil,
1178  numParts;
1179 
1180  // positions are mandatory
1181  if (pattern.indexOf("{position}") < 0)
1182  throw("ERROR: Pattern strings must have a {position} placeholder (\"" + pattern + "\")");
1183 
1184  if (orientation === undefined)
1185  {
1186  numParts = 9;
1187 
1188  if (attributes["centerIsNil"] !== undefined)
1189  centerIsNil = attributes["centerIsNil"];
1190  }
1191  else
1192  {
1193  numParts = 3;
1194  isVertical = orientation === PatternIsVertical;
1195 
1196  if (isVertical)
1197  {
1198  if (attributes["centerHeight"])
1199  centerWidthHeight = attributes["centerHeight"];
1200  }
1201  else
1202  {
1203  if (attributes["centerWidth"])
1204  centerWidthHeight = attributes["centerWidth"];
1205  }
1206  }
1207 
1208  if (attributes["rightWidth"])
1209  rightWidth = attributes["rightWidth"];
1210 
1211  if (attributes["bottomHeight"])
1212  bottomHeight = attributes["bottomHeight"];
1213 
1214  var positions = attributes["positions"] || "@",
1215  states = nil,
1216  styles = nil;
1217 
1218  if (numParts === 3)
1219  {
1220  if (positions === "@")
1221  {
1222  if (isVertical)
1223  positions = ["top", "center", "bottom"];
1224  else
1225  positions = ["left", "center", "right"];
1226  }
1227  else if (positions === "#")
1228  positions = ["0", "1", "2"];
1229  else
1230  throw("ERROR: Invalid positions: " + positions)
1231  }
1232  else // numParts === 9
1233  {
1234  if (positions === "@" || positions === "abbrev")
1235  positions = ["top-left", "top", "top-right", "left", "center", "right", "bottom-left", "bottom", "bottom-right"];
1236  else if (positions === "full")
1237  positions = ["top-left", "top-center", "top-right", "center-left", "center-center", "center-right", "bottom-left", "bottom-center", "bottom-right"];
1238  else if (positions === "#")
1239  positions = ["0", "1", "2", "3", "4", "5", "6", "7", "8"];
1240  else
1241  throw("ERROR: Invalid positions: " + positions)
1242  }
1243 
1244  // states
1245  if (pattern.indexOf("{state}") >= 0)
1246  {
1247  states = attributes["states"];
1248 
1249  if (!states)
1250  throw("ERROR: {state} placeholder in the pattern (\"" + pattern + "\") but no states item in the attributes");
1251  }
1252 
1253  // styles
1254  if (pattern.indexOf("{style}") >= 0)
1255  {
1256  styles = attributes["styles"];
1257 
1258  if (!styles)
1259  throw("ERROR: {style} placeholder in the pattern (\"" + pattern + "\") but no styles item in the attributes");
1260  }
1261 
1262  // Now assemble the hierarchy
1263  var placeholder = "{position}",
1264  pos = pattern.indexOf(placeholder),
1265  i;
1266 
1267  for (i = 0; i < positions.length; ++i)
1268  positions[i] = pattern.replace(placeholder, pos === 0 ? positions[i] + separator : separator + positions[i]);
1269 
1270  var slices = positions,
1271  object = slices,
1272  key,
1273  sep;
1274 
1275  if (states)
1276  {
1277  placeholder = "{state}";
1278  pos = pattern.indexOf(placeholder);
1279  object = {};
1280 
1281  for (i = 0; i < states.length; ++i)
1282  {
1283  var state = states[i];
1284  key = state || "@";
1285  sep = state ? separator : "";
1286 
1287  object[key] = slices.slice(0);
1288  replacePlaceholderInArray(object[key], placeholder, pos === 0 ? state + sep : sep + state);
1289  }
1290  }
1291 
1292  if (styles)
1293  {
1294  placeholder = "{style}";
1295  pos = pattern.indexOf(placeholder);
1296 
1297  var styleObject = {};
1298 
1299  for (i = 0; i < styles.length; ++i)
1300  {
1301  var style = styles[i];
1302  key = style || "@";
1303  sep = style ? separator : "";
1304 
1305  if (states)
1306  {
1307  styleObject[key] = cloneObject(object);
1308  replacePlaceholderInObject(styleObject[key], placeholder, pos === 0 ? style + sep : sep + style);
1309  }
1310  else
1311  {
1312  styleObject[key] = slices.slice(0);
1313  replacePlaceholderInArray(styleObject[key], placeholder, pos === 0 ? style + sep : sep + style);
1314  }
1315  }
1316 
1317  object = styleObject;
1318  }
1319 
1320  if (styles || states)
1321  {
1322  if (numParts === 3)
1323  makeThreePartSlicesFromObject(object, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
1324  else
1325  makeNinePartSlicesFromObject(object, width, height, rightWidth, bottomHeight, centerIsNil);
1326 
1327  makeImagesFromObject(object, isVertical, imageFactory);
1328  return object;
1329  }
1330  else
1331  {
1332  if (numParts === 3)
1333  makeThreePartSlicesFromArray(object, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
1334  else
1335  makeNinePartSlicesFromArray(object, width, height, rightWidth, bottomHeight, centerIsNil);
1336 
1337  return imageFromSlices(object, isVertical, imageFactory);
1338  }
1339  }
1340  else
1341  throw("ERROR: No placeholders in slice pattern (\"" + pattern + "\")");
1342 };
1343 
1344 var replacePlaceholderInArray = function(array, find, replacement)
1345 {
1346  for (var i = 0; i < array.length; ++i)
1347  array[i] = array[i].replace(find, replacement);
1348 };
1349 
1350 var replacePlaceholderInObject = function(object, find, replacement)
1351 {
1352  for (var key in object)
1353  if (object.hasOwnProperty(key))
1354  if (object[key].constructor === Array)
1355  replacePlaceholderInArray(object[key], find, replacement);
1356  else
1357  replacePlaceholderInObject(object[key], find, replacement);
1358 };
1359 
1360 var cloneObject = function(object)
1361 {
1362  var clone = {};
1363 
1364  for (var key in object)
1365  if (object.hasOwnProperty(key))
1366  if (object[key].constructor === Array)
1367  clone[key] = object[key].slice(0);
1368  else if (typeof(object[key]) === "object")
1369  clone[key] = cloneObject(object[key]);
1370  else
1371  clone[key] = object[key];
1372 
1373  return clone;
1374 };
1375 
1376 var makeThreePartSlicesFromObject = function(object, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical)
1377 {
1378  for (var key in object)
1379  if (object.hasOwnProperty(key))
1380  if (object[key].constructor === Array)
1381  makeThreePartSlicesFromArray(object[key], width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
1382  else // object
1383  makeThreePartSlicesFromObject(object[key], width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
1384 };
1385 
1386 var makeThreePartSlicesFromArray = function(array, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical)
1387 {
1388  array[0] = [array[0], width, height];
1389 
1390  if (isVertical)
1391  {
1392  array[1] = [array[1], width, centerWidthHeight ? centerWidthHeight : 1.0];
1393  array[2] = [array[2], width, bottomHeight ? bottomHeight : height];
1394  }
1395  else
1396  {
1397  array[1] = [array[1], centerWidthHeight ? centerWidthHeight : 1.0, height];
1398  array[2] = [array[2], rightWidth ? rightWidth : width, height];
1399  }
1400 };
1401 
1402 var makeNinePartSlicesFromObject = function(object, width, height, rightWidth, bottomHeight, centerIsNil)
1403 {
1404  for (var key in object)
1405  if (object.hasOwnProperty(key))
1406  if (object[key].constructor === Array)
1407  makeNinePartSlicesFromArray(object[key], width, height, rightWidth, bottomHeight, centerIsNil);
1408  else // object
1409  makeNinePartSlicesFromObject(object[key], width, height, rightWidth, bottomHeight, centerIsNil);
1410 };
1411 
1412 var makeNinePartSlicesFromArray = function(array, width, height, rightWidth, bottomHeight, centerIsNil)
1413 {
1414  rightWidth = rightWidth ? rightWidth : width;
1415  bottomHeight = bottomHeight ? bottomHeight : height;
1416 
1417  array[0] = [array[0], width, height]; // top-left
1418  array[1] = [array[1], 1.0, height]; // top
1419  array[2] = [array[2], rightWidth, height]; // top-right
1420  array[3] = [array[3], width, 1.0]; // left
1421  array[4] = centerIsNil ? nil : [array[4], 1.0, 1.0]; // center
1422  array[5] = [array[5], rightWidth, 1.0]; // right
1423  array[6] = [array[6], width, bottomHeight]; // bottom-left
1424  array[7] = [array[7], 1.0, bottomHeight]; // bottom
1425  array[8] = [array[8], rightWidth, bottomHeight]; // bottom-right
1426 };
1427 
1428 var makeImagesFromObject = function(object, isVertical, imageFactory)
1429 {
1430  for (var key in object)
1431  if (object.hasOwnProperty(key))
1432  if (object[key].constructor === Array)
1433  object[key] = imageFromSlices(object[key], isVertical, imageFactory);
1434  else // object
1435  makeImagesFromObject(object[key], isVertical, imageFactory);
1436 };
1437