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