API  0.9.6
 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 
46 function CPImageInBundle(aFilename, aSize, aBundle)
47 {
48  if (!aBundle)
49  aBundle = [CPBundle mainBundle];
50 
51  if (aSize)
52  return [[CPImage alloc] initWithContentsOfFile:[aBundle pathForResource:aFilename] size:aSize];
53 
54  return [[CPImage alloc] initWithContentsOfFile:[aBundle pathForResource:aFilename]];
55 }
56 
57 function CPAppKitImage(aFilename, aSize)
58 {
59  return CPImageInBundle(aFilename, aSize, [CPBundle bundleForClass:[CPView class]]);
60 }
61 
83 @implementation CPImage : CPObject
84 {
85  CGSize _size;
86  CPString _filename;
87  CPString _name;
88 
89  id _delegate;
90  unsigned _loadStatus;
91 
92  Image _image;
93 }
94 
95 - (id)init
96 {
97  return [self initByReferencingFile:@"" size:CGSizeMake(-1, -1)];
98 }
99 
108 - (id)initByReferencingFile:(CPString)aFilename size:(CGSize)aSize
109 {
110  self = [super init];
111 
112  if (self)
113  {
114  _size = CPSizeCreateCopy(aSize);
115  _filename = aFilename;
116  _loadStatus = CPImageLoadStatusInitialized;
117  }
118 
119  return self;
120 }
121 
128 - (id)initWithContentsOfFile:(CPString)aFilename size:(CGSize)aSize
129 {
130  self = [self initByReferencingFile:aFilename size:aSize];
131 
132  if (self)
133  [self load];
134 
135  return self;
136 }
137 
144 - (id)initWithContentsOfFile:(CPString)aFilename
145 {
146  self = [self initByReferencingFile:aFilename size:CGSizeMake(-1, -1)];
147 
148  if (self)
149  [self load];
150 
151  return self;
152 }
153 
159 - (id)initWithData:(CPData)someData
160 {
161  var base64 = [someData base64],
162  type = [base64 hasPrefix:@"/9j/4AAQSkZJRgABAQEASABIAAD/"] ? @"jpg" : @"png",
163  dataURL = "data:image/" + type + ";base64," + base64;
164 
165  return [self initWithContentsOfFile:dataURL];
166 }
167 
171 - (CPString)filename
172 {
173  return _filename;
174 }
175 
180 - (CPData)data
181 {
182 #if PLATFORM(DOM)
183  var dataURL;
184 
185  if ([_filename hasPrefix:@"data:image"])
186  dataURL = _filename;
188  {
189  var canvas = document.createElement("canvas"),
190  ctx = canvas.getContext("2d");
191 
192  canvas.width = _image.width,
193  canvas.height = _image.height;
194 
195  ctx.drawImage(_image, 0, 0);
196 
197  dataURL = canvas.toDataURL("image/png");
198  }
199  else
200  return nil;
201 
202  var base64 = dataURL.replace(/^data:image\/png;base64,/, "");
203  return [CPData dataWithBase64:base64];
204 #endif
205 }
206 
211 - (void)setSize:(CGSize)aSize
212 {
213  _size = CGSizeMakeCopy(aSize);
214 }
215 
219 - (CGSize)size
220 {
221  return _size;
222 }
223 
224 + (id)imageNamed:(CPString)aName
225 {
226  var image = imagesForNames[aName];
227 
228  if (image)
229  return image;
230 
231  var imageOrSize = AppKitImageForNames[aName];
232 
233  if (!imageOrSize)
234  return nil;
235 
236  if (!imageOrSize.isa)
237  {
238  imageOrSize = CPAppKitImage("CPImage/" + aName + ".png", imageOrSize);
239 
240  [imageOrSize setName:aName];
241 
242  AppKitImageForNames[aName] = imageOrSize;
243  }
244 
245  return imageOrSize;
246 }
247 
248 - (BOOL)setName:(CPString)aName
249 {
250  if (_name === aName)
251  return YES;
252 
253  if (imagesForNames[aName])
254  return NO;
255 
256  _name = aName;
257 
258  imagesForNames[aName] = self;
259 
260  return YES;
261 }
262 
263 - (CPString)name
264 {
265  return _name;
266 }
267 
272 - (void)setDelegate:(id)aDelegate
273 {
274  _delegate = aDelegate;
275 }
276 
280 - (id)delegate
281 {
282  return _delegate;
283 }
284 
288 - (unsigned)loadStatus
289 {
290  return _loadStatus;
291 }
292 
299 - (void)load
300 {
301  if (_loadStatus == CPImageLoadStatusLoading || _loadStatus == CPImageLoadStatusCompleted)
302  return;
303 
304  _loadStatus = CPImageLoadStatusLoading;
305 
306 #if PLATFORM(DOM)
307  _image = new Image();
308 
309  var isSynchronous = YES;
310 
311  // FIXME: We need a better/performance way of doing this.
312  _image.onload = function ()
313  {
314  if (isSynchronous)
315  window.setTimeout(function() { [self _imageDidLoad]; }, 0);
316  else
317  {
318  [self _imageDidLoad];
319  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
320  }
321  [self _derefFromImage];
322  };
323 
324  _image.onerror = function ()
325  {
326  if (isSynchronous)
327  window.setTimeout(function() { [self _imageDidError]; }, 0);
328  else
329  {
330  [self _imageDidError];
331  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
332  }
333  [self _derefFromImage];
334  };
335 
336  _image.onabort = function ()
337  {
338  if (isSynchronous)
339  window.setTimeout(function() { [self _imageDidAbort]; }, 0);
340  else
341  {
342  [self _imageDidAbort];
343  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
344  }
345  [self _derefFromImage];
346  };
347 
348  _image.src = _filename;
349 
350  // onload and friends may fire after this point but BEFORE the end of the run loop,
351  // crazy, I know. So don't set isSynchronous here, rather wait a bit longer.
352  window.setTimeout(function() { isSynchronous = NO; }, 0);
353 #endif
354 }
355 
356 - (BOOL)isThreePartImage
357 {
358  return NO;
359 }
360 
361 - (BOOL)isNinePartImage
362 {
363  return NO;
364 }
365 
366 - (CPString)description
367 {
368  var filename = [self filename],
369  size = [self size];
370 
371  if (filename.indexOf("data:") === 0)
372  {
373  var index = filename.indexOf(",");
374 
375  if (index > 0)
376  filename = [CPString stringWithFormat:@"%s,%s...%s", filename.substr(0, index), filename.substr(index + 1, 10), filename.substr(filename.length - 10)];
377  else
378  filename = "data:<unknown type>";
379  }
380 
381  return [CPString stringWithFormat:ImageDescriptionFormat, [super description], filename, size.width, size.height];
382 }
383 
384 /* @ignore */
385 - (void)_derefFromImage
386 {
387  _image.onload = null;
388  _image.onerror = null;
389  _image.onabort = null;
390 }
391 
392 /* @ignore */
393 - (void)_imageDidLoad
394 {
395  _loadStatus = CPImageLoadStatusCompleted;
396 
397  // FIXME: IE is wrong on image sizes????
398  if (!_size || (_size.width == -1 && _size.height == -1))
399  _size = CGSizeMake(_image.width, _image.height);
400 
402  postNotificationName:CPImageDidLoadNotification
403  object:self];
404 
405  if ([_delegate respondsToSelector:@selector(imageDidLoad:)])
406  [_delegate imageDidLoad:self];
407 }
408 
409 /* @ignore */
410 - (void)_imageDidError
411 {
412  _loadStatus = CPImageLoadStatusReadError;
413 
414  if ([_delegate respondsToSelector:@selector(imageDidError:)])
415  [_delegate imageDidError:self];
416 }
417 
418 /* @ignore */
419 - (void)_imageDidAbort
420 {
421  _loadStatus = CPImageLoadStatusCancelled;
422 
423  if ([_delegate respondsToSelector:@selector(imageDidAbort:)])
424  [_delegate imageDidAbort:self];
425 }
426 
427 @end
428 
429 @implementation CPImage (CPCoding)
430 
436 - (id)initWithCoder:(CPCoder)aCoder
437 {
438  return [self initWithContentsOfFile:[aCoder decodeObjectForKey:@"CPFilename"] size:[aCoder decodeSizeForKey:@"CPSize"]];
439 }
440 
445 - (void)encodeWithCoder:(CPCoder)aCoder
446 {
447  [aCoder encodeObject:_filename forKey:@"CPFilename"];
448  [aCoder encodeSize:_size forKey:@"CPSize"];
449 }
450 
451 @end
452 
453 @implementation CPThreePartImage : CPObject
454 {
455  CPArray _imageSlices;
456  BOOL _isVertical;
457 }
458 
459 - (id)initWithImageSlices:(CPArray)imageSlices isVertical:(BOOL)isVertical
460 {
461  self = [super init];
462 
463  if (self)
464  {
465  _imageSlices = imageSlices;
466  _isVertical = isVertical;
467  }
468 
469  return self;
470 }
471 
472 - (CPString)filename
473 {
474  return @"";
475 }
476 
477 - (CPArray)imageSlices
478 {
479  return _imageSlices;
480 }
481 
482 - (BOOL)isVertical
483 {
484  return _isVertical;
485 }
486 
487 - (BOOL)isThreePartImage
488 {
489  return YES;
490 }
491 
492 - (BOOL)isNinePartImage
493 {
494  return NO;
495 }
496 
497 @end
498 
499 var CPThreePartImageImageSlicesKey = @"CPThreePartImageImageSlicesKey",
500  CPThreePartImageIsVerticalKey = @"CPThreePartImageIsVerticalKey";
501 
503 
504 - (id)initWithCoder:(CPCoder)aCoder
505 {
506  self = [super init];
507 
508  if (self)
509  {
510  _imageSlices = [aCoder decodeObjectForKey:CPThreePartImageImageSlicesKey];
511  _isVertical = [aCoder decodeBoolForKey:CPThreePartImageIsVerticalKey];
512  }
513 
514  return self;
515 }
516 
517 - (void)encodeWithCoder:(CPCoder)aCoder
518 {
519  [aCoder encodeObject:_imageSlices forKey:CPThreePartImageImageSlicesKey];
520  [aCoder encodeBool:_isVertical forKey:CPThreePartImageIsVerticalKey];
521 }
522 
523 @end
524 
525 
526 @implementation CPNinePartImage : CPObject
527 {
528  CPArray _imageSlices;
529 }
530 
531 - (id)initWithImageSlices:(CPArray)imageSlices
532 {
533  self = [super init];
534 
535  if (self)
536  _imageSlices = imageSlices;
537 
538  return self;
539 }
540 
541 - (CPString)filename
542 {
543  return @"";
544 }
545 
546 - (CPArray)imageSlices
547 {
548  return _imageSlices;
549 }
550 
551 - (BOOL)isThreePartImage
552 {
553  return NO;
554 }
555 
556 - (BOOL)isNinePartImage
557 {
558  return YES;
559 }
560 
561 @end
562 
563 var CPNinePartImageImageSlicesKey = @"CPNinePartImageImageSlicesKey";
564 
566 
567 - (id)initWithCoder:(CPCoder)aCoder
568 {
569  self = [super init];
570 
571  if (self)
572  _imageSlices = [aCoder decodeObjectForKey:CPNinePartImageImageSlicesKey];
573 
574  return self;
575 }
576 
577 - (void)encodeWithCoder:(CPCoder)aCoder
578 {
579  [aCoder encodeObject:_imageSlices forKey:CPNinePartImageImageSlicesKey];
580 }
581 
582 @end