Image simple image drawing and utility class



This class does use underneath the Cocoa Bridge by jan Trützschler and is basically a wrapper around the NSImage class. So Mac OS X Only.


Image.new(path) -  new Image from a file or an URL located at path


path can be either an absolute file path, a relative file path (relative to the current document's path), or an URL beginning with file:///, http:// or ftp://

returns nil and send a warning if the file does not exits.

The initializer will always try to create a bitmap representation of the image.


caution: 

- use the right extension for your image type. If you try to load a jpeg file with a .png extension instead, it will crash supercollider (sending you the beachball of pain)...

- test your URL, bad or nonexistant url may lead also to unknown behaviour.



Image.newClear(size) - new empty Image with an initial size of size


size might be a Rect (it will take only the with and height values) or a Size or a Point (wich allows you to use the 200@200 syntax). (info: On return the newly allocated image will contain a NSCachedImageRep).


Image.releaseAll - will release all current instances, freeing memory.


Image.numberOfImages - the number of Image instances currently allocated


autoTransform - if true the instance will set the appropriate matrix to draw itself correctly in supercollider coordinates whenever a drawXXX method is called. is true by default. set it to false to perform you own transformation


path

width

height

height

bounds

release - caution: you should always release the image when done with it !

(bitsPerSample)

(bitsPerPixel)

(isOpaque)

(hasAlpha)


drawAtPoint(aPoint, fraction, operation) - draw the image. fraction = 0 means draw it totally transparent, 1 = fully opaque, is 1.0 by default. operation is the Compositing operation to apply, Compositing.copy is default:


Compositing {

*clear {^0;}

*copy {^1;}

*sourceIn {^2;}

*sourceOver {^3;}

*sourceOut {^4;}

*sourceATop {^5;}

*destinationOver {^6;}

*destinationIn {^7;}

*destinationOut {^8;}

*destinationATop {^9;}

*xor {^10;}

*darker {^11;}

*highlight {^12;}

*lightPlus {^13;}

}


drawRegionAtPoint(aPoint, aRegion, fraction, operation) - draw a portion of the image. see examples for more info


drawStringAtPoint(string, point, font, color) - see example for particular need case for this method. This method always flip the coordinate system.


flipped - returns a newly allocated Image wich is the flipped (upside down) copy of the receiver.


saveAs(argPath, type) - save it :-). argPath should be an absolute file path and type should be a valid ImageType.


ImageType {

*tiff {^0;}

*bmp {^1;}

*gif {^2;}

*jpeg {^3;}

*png {^4;}

*jpeg2000 {^5;} // carefull: supported only in 10.4

}


lockFocus, unlockFocus - All Pen commands between those two call will draw inside the Image. Caution: Normally, it will use by default the Quartz coordinate system. See examples on how to setup correctly the image to draw inside of it with the supercollider coordinate system




Notes:

- currently it is not possible to retrieve the bitmap data fo the image.

- you cannot set/retrieve individual pixel value. (draw inside the image using lockFocus to set pixels)

- creating an image with newClear will create a totally transparent image. But once you have drawn opaque or semi-opaque pixels inside of it, it is not currently possible to re-make it totally transparent/clear it (since Pen does not provide a method to clear a context, using Pen.fillColor_(Color.clear) won't work), you'll have to create a new one for that !



/*

--------------------- Examples ---------------------

Please make sure you have the 'resources' folder in the same folder as the current script otherwise you won't be able to load the example images !


Example 1:

Transparency Example:

How to draw transparency of an image that contains an alpha channel.

+ alpha control of the image

+ show different compositing options

+ kind of multi state Button with different images (record button)

*/


(

var 

resources=nil,

icon=nil,

recordButton=nil,

customIcon=nil,

recordButtonState=0,

background, imageAlpha=1.0, imageComposition=Compositing.sourceIn

;


resources = Dictionary[

'bg' -> Image("resources/gradient.jpg"),

'icon' -> Image("resources/Swamp.png"),

'recButton' -> [

Image("resources/TrBtn_record_off.tiff"),

Image("resources/TrBtn_record_off_p.tiff"),

Image("resources/TrBtn_record_on.tiff"),

Image("resources/TrBtn_record_on_p.tiff")

]

];


w = SCWindow("Drawing Options", Rect(310, 500, 500, 300)).front;

w.view.background_(Color.white);

w.onClose = {

resources.do{

|img| 

if(img.isKindOf(Image), {img.release});

if(img.isKindOf(Array), {img.do {|item| item.release}});

};

//Image.releaseAll; // you can also use this to release all image instances 

};


background = SCUserView(w, Rect(0, 0, w.bounds.width, w.bounds.height)).relativeOrigin_(false).canFocus_(false)

.drawFunc_({resources.at('bg').drawAtPoint(0@0)})

;


icon = SCUserView(w, Rect(20, 20, resources.at('icon').width, resources.at('icon').height))

.relativeOrigin_(false)

.drawFunc_({resources.at('icon').drawAtPoint(icon.bounds.left@icon.bounds.top, imageAlpha, imageComposition)})

;

recordButton = SCUserView(w, Rect(icon.bounds.left + icon.bounds.width + 10, icon.bounds.top, resources.at('recButton')[0].width, resources.at('recButton')[0].height))

.relativeOrigin_(false)

.canFocus_(false)

.mouseDownAction_({|me| recordButtonState=recordButtonState+1; me.refresh;})

.mouseUpAction_({|me| 

recordButtonState=recordButtonState+1; 

if(recordButtonState > (resources.at('recButton').size-1), {recordButtonState=0}); me.refresh;

})

.drawFunc_({

var img = resources.at('recButton')[recordButtonState];

img.drawAtPoint(recordButton.bounds.left@recordButton.bounds.top, 1, Compositing.sourceIn);

})

;


p = SCSlider(w, Rect(10, icon.bounds.top + icon.bounds.height + 10, icon.bounds.width, 20))

.action_({

imageAlpha = p.value;

icon.refresh;

})

.value_(1.0);


m = SCPopUpMenu(w, Rect(p.bounds.left, p.bounds.top + p.bounds.height + 5, p.bounds.width + 20, 20));

m.items = 

[

"Compositing.clear", 

"Compositing.copy", 

"Compositing.sourceIn", 

"Compositing.sourceOver",

"Compositing.sourceOut",

"Compositing.sourceATop",

"Compositing.destinationOver",

"Compositing.destinationIn",

"Compositing.destinationOut",

"Compositing.destinationATop",

"Compositing.xor",

"Compositing.darker",

"Compositing.highlight",

"Compositing.lightPlus"

];

m.value_(2);

m.action_({

|v|

imageComposition = v.value;

icon.refresh;

});


)



/*

Example 2:

Kind of multi graphic state button made with one image

containing the different states.

Shows how to draw a part of an image

*/

(

var state = 0,

stateArray;

stateArray = [

0@60, // normal state

0@15, // normal state pressed

0@45, // second state

0@0 // second state pressed ...ect...

];


w = SCWindow("Drawing Options", Rect(300, 500, 50, 50)).front;

w.view.background_(Color.new255(192, 192, 192));

b = Image("resources/TrackMute.tiff");


v = SCUserView(w, Rect(10, 10, 15, 15))

.relativeOrigin_(false)

.drawFunc_({

var coords = stateArray[state];

//("drawing state: "+state+" = "+coords.asString).postln;

b.drawRegionAtPoint(10@10, Rect(coords.x, coords.y, 15, 15));

})

.canFocus_(false);

v.mouseDownAction = {|me| state=state+1; me.refresh;};

v.mouseUpAction = {|me| state=state+1; if(state > (stateArray.size-1), {state=0}); me.refresh;};

w.onClose = {

b.release;

};

w.refresh;

)


/*

Example 3:

Navigation through an Image 

*/

(

var image, left=0, top=0, width=0, height=0;

image = Image("resources/earth.jpg");

width = image.width;

height = image.height;



w = SCWindow("Drawing Options", Rect(310, 500, 300, 200)).front;

w.view.background_(Color.clear);

y = SCUserView(w, Rect(10, 10, 150, 150)).relativeOrigin_(false);

y.drawFunc = {

image.drawRegionAtPoint(10@10, Rect(left, top, 150, 150));

};


v = SC2DSlider(w, Rect(150 + 20, 10, 100, 150));

v.x_(0); v.y_(0);

v.action_({

left = v.x * (width - 150);

top = v.y * (height - 150);

y.refresh;

//[left, top].postln;

});


w.onClose = {

image.release;

};

w.refresh;

)



/*

Example 4:

Scaling Example

*/

(

var image, left=0, top=0, width=0, height=0, scalex = 1.0, scaley = 1.0;

image = Image("resources/earth.jpg");

width = image.width;

height = image.height;



w = SCWindow("Drawing Options", Rect(310, 500, 400, 400)).front;

w.view.background_(Color.clear);

w.drawHook = {

Pen.use {

Pen.translate(20, 20);

Pen.scale(scalex, scaley);

image.drawAtPoint(0@0);

}

};


v = SC2DSlider(w, Rect(20, image.height + 30, 100, 30));

v.x_(0); v.y_(0);

v.action = {

|m|

scalex = 1.0 + v.x;

scaley = 1.0 + v.y;

w.refresh;

};


w.onClose = {

image.release;

};

w.refresh;

)


/*

Drawing inside an image:

Very basic example

*/

(

var image=nil, image2=nil, width, height;

image = Image.newClear(150@150);

width = image.width;

height = image.height;



/* 

the setFlipped method is the easy way to setup the image before drawing.

But this won't draw the image flipped. It is just a hint for the cache buffer when rendering inside of it.

*/

image.setFlipped(true);

image.autoTransform = false; // set autoTransform to false we use setFlipped

image.lockFocus; // begins drawing

Pen.use {

30.do {

Pen.fillColor_(Color.new(1.0.rand,1.0.rand,1.0.rand,1.0.rand));

Pen.fillOval(Rect(50.rand, 50.rand, 100.rand, 100.rand));

};

// Drawing text require a specific command

image.drawStringAtPoint("Hello Image", 5@20); // draw text in the right sense

//"Hello".drawAtPoint(5@20); // uncomment to see otherwise...

};

image.unlockFocus; // do not forget to call it when done

image.recache; // in case


w = SCWindow("Drawing Scale", Rect(310, 500, 400, 400)).front;

w.view.background_(Color.clear);

w.drawHook = {

Pen.use {

image.drawAtPoint(10@10, operation:Compositing.sourceIn);

}

};


v = SCButton(w, Rect(0, image.height + 30, 80, 20)).states_([["Save Me", Color.black, Color.white]]);

v.action_({

var result;

var savePath = Document.current.path.dirname ++ "/SavedImage.png";

image2 = image.flipped; // here this is a particular case since we created 

// a flipped cached image so we need to flip it to save it after

("flipping image and save at: "++savePath).postln;

CocoaDialog.savePanel(

{

arg path;

result = image2.saveAs(path++".png", ImageType.png); // to keep alpha channel

("saving result: " + result.class).postln;

image2.release; // release after done

}, {}

);

});


w.onClose = {

image.release;

};

w.refresh;

)


Image.releaseAll; // ensure all Image intances are cleared