API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPImage.j
Go to the documentation of this file.
1 /*
2  * CPImage.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 
32 
33 CPImageDidLoadNotification = @"CPImageDidLoadNotification";
34 
35 // Image Names
36 CPImageNameColorPanel = @"CPImageNameColorPanel";
37 CPImageNameColorPanelHighlighted = @"CPImageNameColorPanelHighlighted";
38 
39 var imagesForNames = { },
41  ImageDescriptionFormat = "%s {\n filename: \"%s\",\n size: { width:%f, height:%f }\n}";
42 
43 AppKitImageForNames[CPImageNameColorPanel] = CGSizeMake(26.0, 29.0);
45 
58 function CPImageInBundle()
59 {
60  var filename = arguments[0],
61  size = nil,
62  bundle = nil;
63 
64  if (typeof(arguments[1]) === "number")
65  {
66  if (arguments[1] !== nil && arguments[1] !== undefined)
67  size = CGSizeMake(arguments[1], arguments[2]);
68 
69  bundle = arguments[3];
70  }
71  else if (typeof(arguments[1]) === "object")
72  {
73  size = arguments[1];
74  bundle = arguments[2];
75  }
76 
77  if (!bundle)
78  bundle = [CPBundle mainBundle];
79 
80  if (size)
81  return [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:filename] size:size];
82 
83  return [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:filename]];
84 }
85 
86 function CPAppKitImage(aFilename, aSize)
87 {
88  return CPImageInBundle(aFilename, aSize, [CPBundle bundleForClass:[CPView class]]);
89 }
90 
112 @implementation CPImage : CPObject
113 {
114  CGSize _size;
115  CPString _filename;
116  CPString _name;
117 
118  id _delegate;
119  unsigned _loadStatus;
120 
121  Image _image;
122 }
123 
124 - (id)init
125 {
126  return [self initByReferencingFile:@"" size:CGSizeMake(-1, -1)];
127 }
128 
138 - (id)initByReferencingFile:(CPString)aFilename size:(CGSize)aSize
139 {
140  // Quietly return nil like in Cocoa, rather than crashing later.
141  if (aFilename === undefined || aFilename === nil)
142  return nil;
143 
144  self = [super init];
145 
146  if (self)
147  {
148  _size = CGSizeMakeCopy(aSize);
149  _filename = aFilename;
150  _loadStatus = CPImageLoadStatusInitialized;
151  }
152 
153  return self;
154 }
155 
162 - (id)initWithContentsOfFile:(CPString)aFilename size:(CGSize)aSize
163 {
164  self = [self initByReferencingFile:aFilename size:aSize];
165 
166  if (self)
167  [self load];
168 
169  return self;
170 }
171 
178 - (id)initWithContentsOfFile:(CPString)aFilename
179 {
180  self = [self initByReferencingFile:aFilename size:CGSizeMake(-1, -1)];
181 
182  if (self)
183  [self load];
184 
185  return self;
186 }
187 
193 - (id)initWithData:(CPData)someData
194 {
195  var base64 = [someData base64],
196  type = [base64 hasPrefix:@"/9j/4AAQSkZJRgABAQEASABIAAD/"] ? @"jpg" : @"png",
197  dataURL = "data:image/" + type + ";base64," + base64;
198 
199  return [self initWithContentsOfFile:dataURL];
200 }
201 
205 - (CPString)filename
206 {
207  return _filename;
208 }
209 
214 - (CPData)data
215 {
216 #if PLATFORM(DOM)
217  var dataURL;
218 
219  if ([_filename hasPrefix:@"data:image"])
220  dataURL = _filename;
222  {
223  var canvas = document.createElement("canvas"),
224  ctx = canvas.getContext("2d");
225 
226  canvas.width = _image.width,
227  canvas.height = _image.height;
228 
229  ctx.drawImage(_image, 0, 0);
230 
231  dataURL = canvas.toDataURL("image/png");
232  }
233  else
234  return nil;
235 
236  var base64 = dataURL.replace(/^data:image\/png;base64,/, "");
237  return [CPData dataWithBase64:base64];
238 #endif
239 }
240 
245 - (void)setSize:(CGSize)aSize
246 {
247  _size = CGSizeMakeCopy(aSize);
248 }
249 
253 - (CGSize)size
254 {
255  return _size;
256 }
257 
258 + (id)imageNamed:(CPString)aName
259 {
260  var image = imagesForNames[aName];
261 
262  if (image)
263  return image;
264 
265  var imageOrSize = AppKitImageForNames[aName];
266 
267  if (!imageOrSize)
268  return nil;
269 
270  if (!imageOrSize.isa)
271  {
272  imageOrSize = CPAppKitImage("CPImage/" + aName + ".png", imageOrSize);
273 
274  [imageOrSize setName:aName];
275 
276  AppKitImageForNames[aName] = imageOrSize;
277  }
278 
279  return imageOrSize;
280 }
281 
282 - (BOOL)setName:(CPString)aName
283 {
284  if (_name === aName)
285  return YES;
286 
287  if (imagesForNames[aName])
288  return NO;
289 
290  _name = aName;
291 
292  imagesForNames[aName] = self;
293 
294  return YES;
295 }
296 
298 {
299  return _name;
300 }
301 
305 - (Image)image
306 {
307  return _image;
308 }
309 
314 - (void)setDelegate:(id)aDelegate
315 {
316  _delegate = aDelegate;
317 }
318 
322 - (id)delegate
323 {
324  return _delegate;
325 }
326 
330 - (unsigned)loadStatus
331 {
332  return _loadStatus;
333 }
334 
341 - (void)load
342 {
343  if (_loadStatus == CPImageLoadStatusLoading || _loadStatus == CPImageLoadStatusCompleted)
344  return;
345 
346  _loadStatus = CPImageLoadStatusLoading;
347 
348 #if PLATFORM(DOM)
349  _image = new Image();
350 
351  var isSynchronous = YES;
352 
353  // FIXME: We need a better/performance way of doing this.
354  _image.onload = function ()
355  {
356  if (isSynchronous)
357  window.setTimeout(function() { [self _imageDidLoad]; }, 0);
358  else
359  {
360  [self _imageDidLoad];
361  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
362  }
363  [self _derefFromImage];
364  };
365 
366  _image.onerror = function ()
367  {
368  if (isSynchronous)
369  window.setTimeout(function() { [self _imageDidError]; }, 0);
370  else
371  {
372  [self _imageDidError];
373  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
374  }
375  [self _derefFromImage];
376  };
377 
378  _image.onabort = function ()
379  {
380  if (isSynchronous)
381  window.setTimeout(function() { [self _imageDidAbort]; }, 0);
382  else
383  {
384  [self _imageDidAbort];
385  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
386  }
387  [self _derefFromImage];
388  };
389 
390  _image.src = _filename;
391 
392  // onload and friends may fire after this point but BEFORE the end of the run loop,
393  // crazy, I know. So don't set isSynchronous here, rather wait a bit longer.
394  window.setTimeout(function() { isSynchronous = NO; }, 0);
395 #endif
396 }
397 
398 - (BOOL)isSingleImage
399 {
400  return YES;
401 }
402 
403 - (BOOL)isThreePartImage
404 {
405  return NO;
406 }
407 
408 - (BOOL)isNinePartImage
409 {
410  return NO;
411 }
412 
413 - (CPString)description
414 {
415  var filename = [self filename],
416  size = [self size];
417 
418  if (filename.indexOf("data:") === 0)
419  {
420  var index = filename.indexOf(",");
421 
422  if (index > 0)
423  filename = [CPString stringWithFormat:@"%s,%s...%s", filename.substr(0, index), filename.substr(index + 1, 10), filename.substr(filename.length - 10)];
424  else
425  filename = "data:<unknown type>";
426  }
427 
428  return [CPString stringWithFormat:ImageDescriptionFormat, [super description], filename, size.width, size.height];
429 }
430 
431 /* @ignore */
432 - (void)_derefFromImage
433 {
434  _image.onload = null;
435  _image.onerror = null;
436  _image.onabort = null;
437 }
438 
439 /* @ignore */
440 - (void)_imageDidLoad
441 {
442  _loadStatus = CPImageLoadStatusCompleted;
443 
444  // FIXME: IE is wrong on image sizes????
445  if (!_size || (_size.width == -1 && _size.height == -1))
446  _size = CGSizeMake(_image.width, _image.height);
447 
449  postNotificationName:CPImageDidLoadNotification
450  object:self];
451 
452  if ([_delegate respondsToSelector:@selector(imageDidLoad:)])
453  [_delegate imageDidLoad:self];
454 }
455 
456 /* @ignore */
457 - (void)_imageDidError
458 {
459  _loadStatus = CPImageLoadStatusReadError;
460 
461  if ([_delegate respondsToSelector:@selector(imageDidError:)])
462  [_delegate imageDidError:self];
463 }
464 
465 /* @ignore */
466 - (void)_imageDidAbort
467 {
468  _loadStatus = CPImageLoadStatusCancelled;
469 
470  if ([_delegate respondsToSelector:@selector(imageDidAbort:)])
471  [_delegate imageDidAbort:self];
472 }
473 
474 @end
475 
476 @implementation CPImage (CPCoding)
477 
483 - (id)initWithCoder:(CPCoder)aCoder
484 {
485  return [self initWithContentsOfFile:[aCoder decodeObjectForKey:@"CPFilename"] size:[aCoder decodeSizeForKey:@"CPSize"]];
486 }
487 
492 - (void)encodeWithCoder:(CPCoder)aCoder
493 {
494  [aCoder encodeObject:_filename forKey:@"CPFilename"];
495  [aCoder encodeSize:_size forKey:@"CPSize"];
496 }
497 
498 @end
499 
500 @implementation CPThreePartImage : CPObject
501 {
502  CPArray _imageSlices;
503  BOOL _isVertical;
504 }
505 
506 - (id)initWithImageSlices:(CPArray)imageSlices isVertical:(BOOL)isVertical
507 {
508  self = [super init];
509 
510  if (self)
511  {
512  _imageSlices = imageSlices;
513  _isVertical = isVertical;
514  }
515 
516  return self;
517 }
518 
519 - (CPString)filename
520 {
521  return @"";
522 }
523 
524 - (CPArray)imageSlices
525 {
526  return _imageSlices;
527 }
528 
529 - (BOOL)isVertical
530 {
531  return _isVertical;
532 }
533 
534 - (BOOL)isSingleImage
535 {
536  return NO;
537 }
538 
539 - (BOOL)isThreePartImage
540 {
541  return YES;
542 }
543 
544 - (BOOL)isNinePartImage
545 {
546  return NO;
547 }
548 
549 @end
550 
551 var CPThreePartImageImageSlicesKey = @"CPThreePartImageImageSlicesKey",
552  CPThreePartImageIsVerticalKey = @"CPThreePartImageIsVerticalKey";
553 
555 
556 - (id)initWithCoder:(CPCoder)aCoder
557 {
558  self = [super init];
559 
560  if (self)
561  {
562  _imageSlices = [aCoder decodeObjectForKey:CPThreePartImageImageSlicesKey];
563  _isVertical = [aCoder decodeBoolForKey:CPThreePartImageIsVerticalKey];
564  }
565 
566  return self;
567 }
568 
569 - (void)encodeWithCoder:(CPCoder)aCoder
570 {
571  [aCoder encodeObject:_imageSlices forKey:CPThreePartImageImageSlicesKey];
572  [aCoder encodeBool:_isVertical forKey:CPThreePartImageIsVerticalKey];
573 }
574 
575 @end
576 
577 
578 @implementation CPNinePartImage : CPObject
579 {
580  CPArray _imageSlices;
581 }
582 
583 - (id)initWithImageSlices:(CPArray)imageSlices
584 {
585  self = [super init];
586 
587  if (self)
588  _imageSlices = imageSlices;
589 
590  return self;
591 }
592 
593 - (CPString)filename
594 {
595  return @"";
596 }
597 
598 - (CPArray)imageSlices
599 {
600  return _imageSlices;
601 }
602 
603 - (BOOL)isSingleImage
604 {
605  return NO;
606 }
607 
608 - (BOOL)isThreePartImage
609 {
610  return NO;
611 }
612 
613 - (BOOL)isNinePartImage
614 {
615  return YES;
616 }
617 
618 @end
619 
620 var CPNinePartImageImageSlicesKey = @"CPNinePartImageImageSlicesKey";
621 
623 
624 - (id)initWithCoder:(CPCoder)aCoder
625 {
626  self = [super init];
627 
628  if (self)
629  _imageSlices = [aCoder decodeObjectForKey:CPNinePartImageImageSlicesKey];
630 
631  return self;
632 }
633 
634 - (void)encodeWithCoder:(CPCoder)aCoder
635 {
636  [aCoder encodeObject:_imageSlices forKey:CPNinePartImageImageSlicesKey];
637 }
638 
639 @end