Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / SCClassLibrary / Common / GUI / osx / scide_scapp / SCImage.sc
blobabe950da2d48ba47ea68f326c719f5ad403cda8c
1 // blackrain at realizedsound dot net and charles picasso - thelych at gmail dot com
2 // 09-2008
4 SCImage {
5         classvar <formats, <compositingOperations, <interpolations, <allPlotWindows;
6         var dataptr, <width, <height, <background, <>name, <url, <>autoMode=true, <filters, prCache, prFinalizer;
8         *initClass {
10                 formats = [
11                         'tiff', // 0
12                         'bmp',  // 1
13                         'gif',  // 2
14                         'jpeg', // 3
15                         'png'   // 4
16                 ];
18                 interpolations = [
19                         'default',      // 0
20                         'none',                 // 1
21                         'low',          // 2
22                         'high'          // 3
23                 ];
25                 compositingOperations = [
26                         'clear',                        // 0
27                         'copy',                 // 1
28                         'sourceOver',           // 2
29                         'sourceIn',             // 3
30                         'sourceOut',            // 4
31                         'sourceATop',           // 5
32                         'destinationOver',      // 6
33                         'destinationIn',        // 7
34                         'destinationOut',       // 8
35                         'destinationATop',      // 9
36                         'xor',                  // 10
37                         'plusDarker',           // 11
38                         'highlight',            // 12
39                         'plusLighter'           // 13
40                 ];
42         }
43         *new { arg multiple, height=nil;
45                 if(multiple.isKindOf(Point), {
46                         ^super.new.init(multiple.x, multiple.y);
47                 });
49                 if(multiple.isKindOf(Number), {
50                         ^super.new.init(multiple, height ? multiple);
51                 });
53                 if(multiple.isKindOf(String), {
55                         if ( multiple.beginsWith("http://").not
56                                 and:{ multiple.beginsWith("file://").not }
57                                 and:{ multiple.beginsWith("ftp://").not  }) {
59                                 ^this.open(multiple);
61                         };
63                         ^this.openURL( multiple );
64                 });
65                 ^nil;
66         }
67         *color { arg ... args;
68                 var newone, color, filter;
69                 newone = SCImage.new(*args);
70                 color = args.last;
71                 if(color.isKindOf(Color).not, {color = Color.black});
72                 filter = SCImageFilter(\CIConstantColorGenerator);
73                 filter.color_(color);
74                 newone.applyFilters(filter);
75                 ^newone;
76         }
77         *open { arg path;
78                 path = path.standardizePath;
79                 if ( File.exists(path) ) {
80                         ^super.new.initFromURL("file://" +/+ path.replace(" ", "%20"));
81                 }{
82                         format("SCImage: % not found.", path).error;
83                         ^nil
84                 }
85         }
86         *openURL { arg url;
87                 ^super.new.initFromURL(url.replace(" ", "%20"));
88         }
89         *fromName { arg imageNamed;
90                 // return a system Image or a previously allocated instance of an Image named ...
91                 // tbi
92         }
93         *fromImage { arg scimage;
94                 if(scimage.isKindOf(this), {
95                         ^scimage.copy;
96                 });
97                 "SCImage: invalid instance to copy from.".error;
98                 ^nil
99         }
100         *fromWindow {arg window, rect;
101                 if(window.isKindOf(SCWindow).not, {
102                         "SCImage: fromWindowRect window argument is not instance of SCWindow.".error;
103                 });
104                 rect = rect ? window.view.bounds.moveTo(0,0);
105                 if(rect.isKindOf(Rect).not, {
106                         "SCImage: fromWindowRect rect argument is not instance of Rect.".error;
107                 });
108                 ^this.prFromWindowRect(window, rect);
109         }
111         /**
112          *      Converts a Color instance into
113          *      a pixel datatype suitable for SCImage.
114          *      This is a 32bit packed Integer in
115          *      the RGBA format.
116          */
117         *colorToPixel { arg col;
118                 ^Integer.fromRGBA(
119                         (col.red * 255 ).asInteger,
120                         (col.green * 255).asInteger,
121                         (col.blue * 255 ).asInteger,
122                         (col.alpha * 255 ).asInteger);
123         }
125         *prFromWindowRect {arg window, rect;
126                 _SCImage_fromWindowRect
127                 ^this.primitiveFailed;
128         }
130         init { arg width, height;
131                 this.prInit(width, height);
132                 filters = [];
133         }
134         /* not used any more
135         initFromFile { arg path;
136                 url = "file://" ++ path.standardizePath;
137                 this.prInitFromFile(path);
138                 all = all.add(this);
139                 filters = [];
140         }
141         */
142         initFromURL { arg newURL;
143                 url = newURL;
144                 this.prInitFromURL(url);
145                 filters = [];
146         }
147         free {
148                 this.clearCache;
149                 this.prFree;
150         }
151         copy {
152                 var new;
153                 new = this.class.new(width, height);
154                 new.name_(this.name);
155                 new.url_(this.url);
156                 new.autoMode_(this.autoMode);
157                 new.lockFocus;
159                         this.drawAtPoint(0@0,nil,1,1.0);
161                 new.unlockFocus;
162                 ^new
163         }
164         isValid {
165                 ^dataptr.notNil
166         }
167         bounds { // helper
168                 ^Rect(0,0,width?0,height?0)
169         }
170         write { arg path, format; // ok
171                 if (this.isValid) {
172                         path = path.standardizePath;
173                         format = format ?? { path.basename.splitext.at(1).asSymbol };
174                         format = formats.indexOf(format) ? 0;
175                         ^this.prWriteToFile(path, format);
176                 };
177                 format("SCImage:write invalid instance.").error;
178         }
179         url_ { arg new_url;
180                 if(new_url.isKindOf(String), {
181                         url = new_url.standardizePath.replace(" ", "%20");
182                 }, {url=""});
183         }
184         crop {|aRect|
185                 var filter, transform;
186                 if(aRect.isKindOf(Rect).not, {
187                         "SCImage: bad argument for cropping image !".warn;
188                         ^this;
189                 });
190                 aRect.top = this.height - aRect.top - aRect.height;
191                 if(autoMode, {this.accelerated_(true)});
192                 filter = SCImageFilter(\CICrop);
193                 filter.rectangle_(aRect);
194                 transform = SCImageFilter(\CIAffineTransform);
195                 transform.transform_([aRect.left.neg, aRect.top.neg]);
196                 this.applyFilters([filter, transform]);
197                 ^this;
198         }
199         invert {
200                 if(autoMode, {this.accelerated_(true)});
201                 this.applyFilters(SCImageFilter(\CIColorInvert));
202                 ^this;
203         }
204         interpolation {
205                 var index;
206                 index = this.prGetInterpolation;
207                 ^this.class.interpolations[index];
208         }
209         interpolation_ {|mode|
210                 var index;
211                 if(mode.isKindOf(Integer), {
212                         this.prSetInterpolation(mode.clip(0, this.class.interpolations.size - 1));
213                 }, {
214                         index = this.class.interpolations.indexOf(mode.asSymbol);
215                         if(index.notNil, {
216                                 this.prSetInterpolation(index);
217                         }, {
218                                 "SCImage: bad interpolation value as argument !".error;
219                         });
220                 });
221         }
223         // pixel manipulation
224         setPixel {|rgbaInteger, x, y|
225                 ^this.prSetPixel(rgbaInteger, x, y);
226         }
227         getPixel {|x,y|
228                 ^this.prGetPixel(x,y);
229         }
230         setColor {|color, x, y|
231                 ^this.prSetColor(color, x, y);
232         }
233         getColor {|x, y|
234                 ^this.prGetColor(x, y);
235         }
236         pixels {
237                 var pixelArray;
238                 if(autoMode, {this.accelerated_(false)});
239                 if(width <= 0 or:{height <= 0}, {^nil});
240                 pixelArray = Int32Array.newClear(width*height);
241                 this.prLoadPixels(pixelArray);
242                 ^pixelArray;
243         }
244         loadPixels {arg array, region=nil, start=0;
245                 if(autoMode, {this.accelerated_(false)});
246                 if(array.isKindOf(Int32Array).not, {
247                         "SCImage: array should be an Int32Array".warn;
248                         ^nil;
249                 });
250                 this.prLoadPixels(array, region, start);
251                 ^this;
252         }
253         pixels_ {|array|
254                 this.setPixels(array);
255         }
256         setPixels {|array, region=nil, start=0|
257                 if(autoMode, {this.accelerated_(false)});
258                 if(region.isNil, {
259                         this.prUpdatePixels(array, start);
260                 }, {
261                         this.prUpdatePixelsInRect(array, region, start);
262                 });
263         }
264         // this method should not be called directly
265         // unless you know exactly what you want to do
266         // use autoMode_(true) to let the class choose the best rep internally
267         // or autoMode_(false) with accelerated_ method to manually manage your representations
268         // a bad management can lead to : worse performance when manipulating images
269         // bad syncing issue between representations !
270         accelerated_ {|aBool| // if yes ensure to use CoreImages
271                 _SCImage_setAccelerated
272                 ^this.primitiveFailed;
273         }
275         accelerated {
276                 _SCImage_isAccelerated
277                 ^this.primitiveFailed;
278         }
280         // filters
281         addFilter {|filter|
282                 if(filter.isKindOf(SCImageFilter), {
283                         filters = filters.add(filter);
284                 });
285         }
286         removeFilter {|filter|
287                 // remove last occurence of a filter in the array
288                 filters.reverseDo {|object, i|
289                         if(object == filter, {
290                                 filters.removeAt(filter.size - i);
291                                 ^this;
292                         });
293                 };
294         }
295         removeAllFilters {
296                 filters = [];
297         }
298         createCache { // only for Filters
299                 prCache = this.filteredWith(filters);
300         }
301         /*
302                 flatten the receiver with all filters added to it
303         */
304         flatten {
305                 this.clearCache;
306                 this.accelerated_(true); // force acceleration just in case
307                 this.applyFilters(filters);
308                 filters = []; // clear all filters
309                 if(autoMode, {this.accelerated_(false)}); // ensure bitmap representation
310         }
312         applyFilters {|filters, crop=0, region|
313                 if(filters.isNil, {^this});
314                 // passing nil to crop says use the result extent of the filter
315                 // may return huge extent so check and crop image if needed !
316                 if(crop == 0, {crop = this.bounds});
317                 if(crop.isKindOf(Rect).not and:{crop.isNil.not}, {
318                         "SCImage: crop should be a Rect, 0, or Nil".warn;
319                         ^this;
320                 });
321                 if(filters.isKindOf(SCImageFilter), {
322                         filters = [filters];
323                 });
324                 if(autoMode, {this.accelerated_(true)});
325                 ^this.prApplyFilters(filters, true, region, crop);
326         }
328         // returns a copy of the receiver filtered with filter
329         filteredWith {|filters, crop=0|
330                 if(filters.isNil, {^this});
331                 if(filters.isKindOf(SCImageFilter), {
332                         filters = [filters];
333                 });
334                 if(crop == 0, {crop = this.bounds});
335                 if(crop.isKindOf(Rect).not and:{crop.isNil.not}, {
336                         "SCImage: crop should be a Rect, 0, or Nil".warn;
337                         ^this;
338                 });
339                 if(autoMode, {this.accelerated_(true)});
340                 ^this.prApplyFilters(filters, false, nil, crop);
341         }
343         clearCache {
344                 if(prCache.notNil, {prCache.free; prCache=nil});
345         }
347         // still experimental
348         applyKernel {|kernel, crop|
349                 if(kernel.isKindOf(SCImageKernel).not, {
350                         "SCImage: aKernel should be a SCImageKernel !".warn;
351                         ^this;
352                 });
353                 if(kernel.isValid.not, {
354                         "SCImage: kernel does not seem to be valid !".warn;
355                         ^this;
356                 });
357                 ^this.prApplyKernel(kernel, crop, true);
358         }
360         // drawing the image
361         lockFocus {
362                 if(autoMode, {this.accelerated_(false)});
363                 this.prLockFocus;
364         }
365         unlockFocus {
366                 if(autoMode, {this.accelerated_(false)});
367                 this.prUnlockFocus;
368         }
369         drawAtPoint { arg point, fromRect, operation='sourceOver', fraction=1.0;
370                 if(filters.size == 0, {
371                         operation = compositingOperations.indexOf(operation) ? 2;
372                         this.prDrawAtPoint(point, fromRect, operation, fraction);
373                 }, {
374                         this.clearCache;
375                         this.accelerated_(true); // we have to force acceleration
376                         this.createCache;
377                         prCache.drawAtPoint(point, fromRect, operation, fraction);
378                 });
379         }
380         drawInRect { arg rect, fromRect, operation='sourceOver', fraction=1.0;
381                 if(filters.size == 0, {
382                         operation = compositingOperations.indexOf(operation) ? 2;
383                         this.prDrawInRect(rect, fromRect, operation, fraction);
384                 }, {
385                         this.clearCache;
386                         this.accelerated_(true); // we have to force acceleration
387                         this.createCache;
388                         prCache.drawInRect(rect, fromRect, operation, fraction);
389                 });
390         }
391         tileInRect { arg rect, fromRect, operation='sourceOver', fraction=1.0;          if(filters.size == 0, {
392                         this.prTileInRect(rect, fromRect ? this.bounds,
393                                 compositingOperations.indexOf(operation) ? 2, fraction);
394                 }, {
395                         this.clearCache;
396                         this.accelerated_(true); // we have to force acceleration
397                         this.createCache;
398                         prCache.tileInRect(rect, fromRect, operation, fraction);
399                 })
400         }
401         draw {|aFunction|
402                 this.lockFocus;
403                 aFunction.value(this);
404                 this.unlockFocus;
405         }
407         // string drawing support
408         drawStringAtPoint { arg string, point, font, color;
409                 var strbounds;
410                 if (string.notNil) {
411                         strbounds = string.bounds(font);
412                         SCPen.use { // for now
413                                 SCPen.translate(0, this.height);
414                                 SCPen.scale(1,-1);
415                                 point.y = this.height - point.y - strbounds.height;
416                                 string.drawAtPoint(point, font, color);
417                         }
418                 }
419         }
421         // simple convenient function
422         plot { arg name, bounds, freeOnClose=false, background=nil, showInfo=true;
423                 var uview, window, nw, nh, ratio = width / height, info="";
424                 nw = width.min(600).max(200);
425                 nh = nw / ratio;
426                 window = SCWindow.new(name ? "plot", bounds ? Rect(400,400,nw,nh)/*, textured: false*/);
427                 allPlotWindows = allPlotWindows.add(window);
429                 if(background.notNil, {
430                         window.view.background_(background);
431                 });
432                 window.acceptsMouseOver = true;
434                 uview = SCUserView(window, window.view.bounds)
435                         .resize_(5)
436                         .focusColor_(Color.clear);
438                 window.onClose_({
439                         allPlotWindows.remove(window);
440                         if(freeOnClose, {
441                                 this.free
442                         });
443                 });
444                 uview.drawFunc_({
446                         SCPen.use {
447                                 this.drawInRect(window.view.bounds, this.bounds, 2, 1.0);
448                         };
450                         if(showInfo, {
451                                 SCPen.use {
452                                         SCPen.width_(0.5);
453                                         Color.black.alpha_(0.4).setFill;
454                                         Color.white.setStroke;
455                                         SCPen.fillRect(Rect(5.5,5.5,100,20));
456                                         SCPen.strokeRect(Rect(5.5,5.5,100,20));
457                                         info.drawAtPoint(10@10, Font.default, Color.white);
458                                 }
459                         });
460                 });
461                 uview.mouseOverAction_({|v, x, y|
462                         if(showInfo, {
463                                 if (this.isValid) {
464                                         info = format("X: %, Y: %",
465                                         ((x / window.view.bounds.width) * this.width).floor.min(width-1),
466                                         ((y / window.view.bounds.height) * this.height).floor.min(height-1) );
467                                 }{
468                                         info = "invalid image";
469                                 };
470                                 window.view.refreshInRect(Rect(5.5,5.5,100,20));
471                         });
472                 });
473                 ^window.front;
474         }
475         *closeAllPlotWindows {
476                 allPlotWindows.do(_.close);
477         }
479         storeOn { arg stream;
480                 stream << this.class.name << ".openURL(" << url.asCompileString <<")"
481         }
483         archiveAsCompileString { ^true }
485         // cocoa bridge additions
486         asNSObject {
487                 ^dataptr.asNSReturn
488         }
491         scalesWhenResized {
492                 _SCImage_scalesWhenResized
493                 ^this.primitiveFailed
494         }
495         scalesWhenResized_ { arg flag; // to test
496                 _SCImage_setScalesWhenResized
497                 ^this.primitiveFailed
498         }
500         width_ { arg w;
501                 this.setSize(w, height);
502         }
503         height_ { arg h;
504                 this.setSize(width, h);
505         }
506         setSize { arg width, height; // to test
507                 _SCImage_setSize
508                 ^this.primitiveFailed
509         }
511 // primtives
512         // pixel manipulation private
513         prSetPixel {|rgbaInteger, x, y|
514                 _SCImage_setPixelAt
515                 ^this.primitiveFailed
516         }
517         prGetPixel {|x,y|
518                 _SCImage_getPixelAt
519                 ^this.primitiveFailed
520         }
521         prSetColor {|color, x, y|
522                 _SCImage_setColorAt
523                 ^this.primitiveFailed
524         }
525         prGetColor {|x, y|
526                 _SCImage_getColorAt
527                 ^this.primitiveFailed
528         }
529         prApplyFilters {|filterArray, inPlaceBolean, region, maxSize|
530                 _SCImageFilter_ApplyMultiple
531                 ^this.primitiveFailed;
532         }
533         prApplyKernel {|kernel, crop, inPlace |
534                 _SCImageFilter_ApplyKernel
535                 ^this.primitiveFailed;
536         }
537         prGetInterpolation {
538                 _SCImage_interpolation
539                 ^this.primitiveFailed;
540         }
542         prSetInterpolation {|index|
543                 _SCImage_setInterpolation
544                 ^this.primitiveFailed;
545         }
547         prDrawAtPoint { arg point, fromRect, operation, fraction;
548                 _SCImage_DrawAtPoint
549                 ^this.primitiveFailed
550         }
552         prDrawInRect { arg rect, fromRect, operation, fraction;
553                 _SCImage_DrawInRect
554                 ^this.primitiveFailed
555         }
557         prSetName { arg newName; // currently does nothing
558                 _SCImage_setName
559                 ^this.primitiveFailed
560         }
562         prSetBackground { arg color; // currently does nothing
563                 _SCImage_setBackgroundColor
564                 ^this.primitiveFailed
565         }
567         prSync {        // should never be used -- be provided in case
568                 _SCImage_sync
569                 ^this.primitiveFailed
570         }
572         //
573         prInit { arg width, height;
574                 _SCImage_New
575                 ^this.primitiveFailed
576         }
577         /* not used any more
578         prInitFromFile { arg path;
579                 _SCImage_NewFromFile
580                 ^this.primitiveFailed
581         }
582         */
583         prInitFromURL { arg url;
584                 _SCImage_NewFromURL
585                 ^this.primitiveFailed
586         }
587         prLockFocus {
588                 _SCImage_lockFocus
589                 ^this.primitiveFailed
590         }
591         prUnlockFocus {
592                 _SCImage_unlockFocus
593                 ^this.primitiveFailed
594         }
595         prFree {
596                 _SCImage_Free
597                 ^this.primitiveFailed
598         }
599         *prFreeAll {
600                 _SCImage_FreeAll
601                 ^this.primitiveFailed
602         }
603         prLoadPixels {arg array, region, startIndex;
604                 _SCImage_loadPixels
605                 ^this.primitiveFailed
606         }
607         prUpdatePixels {arg array, startIndex;
608                 _SCImage_updatePixels
609                 ^this.primitiveFailed
610         }
611         prUpdatePixelsInRect {arg array, rect, startIndex;
612                 _SCImage_updatePixelsInRect
613                 ^this.primitiveFailed
614         }
615         prTileInRect { arg rect, fromRect, operation, fraction;
616                 _SCImage_tileInRect
617                 ^this.primitiveFailed
618         }
619         prWriteToFile { arg path, format;
620                 _SCImage_WriteToFile
621                 ^this.primitiveFailed
622         }
625 SCImageFilter {
626         classvar categories;
627         var <name, <attributes, <values, <>enable=true;
629         *filterCategories {
630                 ^categories;
631         }
633         *initClass {
634                 var categoryNames = [
635                         \CICategoryDistortionEffect,
636                         \CICategoryGeometryAdjustment,
637                         \CICategoryCompositeOperation,
638                         \CICategoryHalftoneEffect,
639                         \CICategoryColorAdjustment,
640                         \CICategoryColorEffect,
641                         \CICategoryTransition,
642                         \CICategoryTileEffect,
643                         \CICategoryGenerator,
644                         \CICategoryGradient,
645                         \CICategoryStylize,
646                         \CICategorySharpen,
647                         \CICategoryBlur,
648                         \CICategoryVideo,
649                         \CICategoryStillImage,
650                         \CICategoryInterlaced,
651                         \CICategoryNonSquarePixels,
652                         \CICategoryHighDynamicRange,
653                         \CICategoryDistortionEffect,
654                         \CICategoryBuiltIn
655                 ];
657                 categories = IdentityDictionary.new;
659                 Platform.when(#[\_SCImageFilter_NamesInCategory], {
661                         categoryNames.do {|key|
662                                 categories.add(key -> this.getFilterNames(key));
663                         };
665                         //"SCImage filter categories done !".postln;
666                 });
668         }
670         *new {|filterName, args|
671                 ^super.newCopyArgs(filterName.asSymbol).initSCImageFilter(args);
672         }
674         defaults {
675                 values = Array.new;
676         }
678         initSCImageFilter {|arguments|
679                 attributes = this.class.getFilterAttributes(name);
680                 values = Array.new;
681                 this.attributes_(arguments);
682         }
684         *translateObject {|object|
685                 if(object.isKindOf(Boolean), {
686                         ^ if(object == true, {1}, {0});
687                 });
689                 if(object.isKindOf(Rect), {
690                         ^ [object.left, object.top, object.width, object.height];
691                 });
693                 ^ object; // no translation
694         }
696         doesNotUnderstand { arg selector ... args;
697                 var key, index;
698                 if(selector.isSetter && attributes.includesKey(selector.asGetter), {
699                         key = selector.asGetter;
700                         args[0] = this.class.translateObject(args[0]);
701                         if(args[0].isKindOf(attributes.at(key)),
702                         {
703                                 index = values.indexOf(key);
704                                 if(index.notNil, {
705                                         values[index+1] = args[0];
706                                 }, {
707                                         values = values.add(key);
708                                         values = values.add(args[0]);
709                                 });
710                         }, {
711                                 ("SCImageFilter: invalid value for filter attribute: "+key+"(a"+args[0].class.asString++") -> argument should be of class:"+attributes.at(key)).error;
712                         });
713                 }, {
714                         ^attributes.at(selector);
715                 });
716         }
718         *getFilterNames {|category|
719                 ^this.prGetFilterNames(category, Array.newClear(64));
720         }
722         *getFilterAttributes {|filterName|
723                 var key, class;
724                 var array, result;
725                 array = this.prGetFilterAttributes(filterName);
726                 result = IdentityDictionary.new;
728                 (array.size >> 1).do {|i|
729                         i = i << 1;
730                         key = array[i];
731                         class = array[i+1];
733                         switch(class,
734                                 \NSNumber,      {class = \Number},
735                                 \CIVector,      {class = \Array},
736                                 \CIImage,       {class = \SCImage},
737                                 \CIColor,       {class = \Color},
738                                 \NSAffineTransform, {class = \Array},
739                                 \NSPoint,               {class = \Point}
740                         );
742                         result.put(key.asSymbol, class.asClass);
743                 };
745                 ^result;
746         }
748         attributeRange { |attributeName|
749                 var result = [nil, nil, nil];
750                 this.prAttributeRange(attributeName.asSymbol, result);
751                 ^result;
752         }
754         // new
755         attributes_ {|array|
756                 var method, value, max;
757                 if(array.isNil, {^this});
758                 max = array.size.asInteger >> 1;
759                 max.do {|i|
760                         method = array[i << 1];
761                         value = array[(i << 1) + 1];
762                         this.perform(method.asSymbol.asSetter, value);
763                 };
764         }
766         set {arg ... values;
767                 this.attributes_(values);
768         }
770         prAttributeRange { |attr|
771                 _SCImageFilter_GetAttributeMinMax
772                 ^this.primitiveFailed
773         }
775         *prGetFilterNames {|cat, array|
776                 _SCImageFilter_NamesInCategory
777                 ^this.primitiveFailed
778         }
780         // direct primitive call - should not be used !!!
781         *prFilterSet{ |filterName, filterArguments|
782                 _SCImageFilter_Set
783                 ^this.primitiveFailed
784         }
786         *prGetFilterAttributes {|filterName|
787                 _SCImageFilter_Attributes
788                 ^this.primitiveFailed
789         }
792 SCImageKernel {
793         var <>shader, <>values, <>bounds, <>enabled, dataptr, finalizer;
794         *new {|shader, values, bounds|
795                 if(values.isKindOf(Array).not and:{values.isNil.not}, {
796                         "SCImageKernel values should be an Array !".warn;
797                         ^nil;
798                 });
799                 if(shader.isKindOf(String).not and:{shader.isNil.not}, {
800                         "SCImageKernel shader should be a String !".warn;
801                         ^nil;
802                 });
803                 if(bounds.isKindOf(Rect).not and:{bounds.isNil.not}, {
804                         "SCImageKernel shader should be a Rect !".warn;
805                         ^nil;
806                 });
808                 ^super.newCopyArgs(shader, values, bounds, true);
809         }
811         isValid {
812                 ^(shader.notNil.or(shader.size > 0).and(values.notNil.or(values.size <= 0)));
813         }
815         compile {
816                 _SCImageKernel_Compile
817                 ^this.primitiveFailed
818         }
822 // integer additions to retrieve 8-bit pixel component from RGBA packed data
824 +Integer {
825         *fromRGBA {|r, g=0, b=0, a=255|
826                 ^(
827                         ((r.asInteger & 16r000000FF) << 24) | ((g.asInteger & 16r000000FF) << 16) | ((b.asInteger & 16r000000FF) << 8) | (a.asInteger & 16r000000FF)
828                 );
829         }
831         *fromColor {|color|
832                 ^this.fromRGBA(
833                         (color.red * 255).asInteger,
834                         (color.green * 255).asInteger,
835                         (color.blue * 255).asInteger,
836                         (color.alpha * 255).asInteger
837                 )
838         }
840         asColor {
841                 ^Color.new255(this.red, this.green, this.blue, this.alpha);
842         }
844         rgbaArray {
845                 ^[this.red, this.green, this.blue, this.alpha];
846         }
848         red {
849                 ^((this >> 24) & 16r000000FF);
850         }
851         green {
852                 ^((this >> 16) & 16r000000FF);
853         }
854         blue {
855                 ^((this >> 8) & 16r000000FF);
856         }
857         alpha {
858                 ^(this & 16r000000FF);
859         }
862 +Array {
863         asRGBA {
864                 ^Integer.fromRGBA(this.at(0)?0,this.at(1)?0, this.at(2)?0, this.at(3)?0);
865         }
868 +Color {
869         asInteger {
870                 ^Integer.fromRGBA(
871                         (this.red * 255).asInteger,
872                         (this.green * 255).asInteger,
873                         (this.blue * 255).asInteger,
874                         (this.alpha * 255).asInteger
875                 );
876         }
880         SCView:backgroundImage
882         modes :
883         1 - fixed to left, fixed to top
884         2 - horizontally tile, fixed to top
885         3 - fixed to right, fixed to top
886         4 - fixed to left, vertically tile
887         5 - horizontally tile, vertically tile
888         6 - fixed to right, vertically tile
889         7 - fixed to left, fixed to bottom
890         8 - horizontally tile, fixed to bottom
891         9 - fixed to right, fixed to bottom
892         10 - fit
893         11 - center, center (scale)
894         12 - center , fixed to top
895         13 - center , fixed to bottom
896         14 - fixed to left, center
897         15 - fixed to right, center
898         16 - center, center (no scale)
901 +SCView {
902         backgroundImage_ { arg image, tileMode=1, alpha=1.0, fromRect;
903                 this.setProperty(\backgroundImage, [image, tileMode, alpha, fromRect])
904         }
905         refreshInRect {|b|
906                 _SCView_RefreshInRect
907                 ^this.primitiveFailed
908         }
911 +Rect {
912         outsetBy {arg h, v;
913                 if(v.isNil, {v=h});
914                 ^this.class.new(left - h, top - v, width + h + h, height + v + v);
915         }