Activity › Forums › Adobe After Effects Expressions › how can i center the anchor point based on opacity of a comp’s contents?
-
how can i center the anchor point based on opacity of a comp’s contents?
Posted by Seth Olson on December 21, 2022 at 11:16 pmi have a comp with transparency. i want to center the anchor point of the comp to the area of opacity. I tried this on the comp:
// [CENTER, CENTER]
l = thisLayer.sourceRectAtTime();
[l.left + l.width/2, l.top + l.height/2];but it seems to measure the entire comp, not the opaque areas. Suggestions?
Lui Mauricio Moraes Nogueira replied 2 years ago 4 Members · 8 Replies -
8 Replies
-
Dan Ebberts
December 21, 2022 at 11:26 pmThe only thing I can think of is the old brute-force sampleImage() method:
h = height/2;
w = width/2;
for (i = 0; i < width; i++){
if (sampleImage([i,h],[.5,h])[3]>0) break;
}
left = i;
for (i = width-1; i >= 0; i--){
if (sampleImage([i,h],[.5,h])[3]>0) break;
}
right = i;
for (i = 0; i < height; i++){
if (sampleImage([w,i],[w,.5])[3]>0) break;
}
top = i;
for (i = height-1; i >= 0; i--){
if (sampleImage([w,i],[w,.5])[3]>0) break;
}
bottom = i;
[(left+right)/2,(top+bottom)/2] -
Dan Ebberts
December 22, 2022 at 7:06 pmI think this is actually a little (half a pixel) more precise:
h = height/2;
w = width/2;
for (i = 0.5; i < width; i++){
if (sampleImage([i,h],[.5,h])[3]>0) break;
}
left = i;
for (i = width-0.5; i > 0; i--){
if (sampleImage([i,h],[.5,h])[3]>0) break;
}
right = i;
for (i = 0.5; i < height; i++){
if (sampleImage([w,i],[w,.5])[3]>0) break;
}
top = i;
for (i = height-0.5; i > 0; i--){
if (sampleImage([w,i],[w,.5])[3]>0) break;
}
bottom = i;
[(left+right)/2,(top+bottom)/2] -
Seth Olson
December 22, 2022 at 8:06 pmOk, this works very well, but also very slowly. It requires several seconds to process each frame.
Is there a way to get the property using sourceRectAtTime() on the first layer inside the comp?
something like: sc=comp(“1”).layer(0).sourceRectAtTime();
but then I get an error that “index 0 is out of range”
maybe something made up like: thisLayer.firstLayer.sourceRectAtTime();
Thank you for your help!
-
Dan Ebberts
December 22, 2022 at 9:53 pmsourceRectAtTime() won’t detect the zero-alpha areas of precomp layers.
You could always convert the anchor point expression to keyframes. That might help.
-
Filip Vandueren
January 12, 2023 at 10:20 amHere’s a version that uses binary search so is exponentially faster:
Note that it works best in 16 or 32 bpc for images that have a very small amount of opaque pixels ( < 0,4%)
// posterizeTime(0); // uncomment for still images
includeEffects = false;
l=thisLayer;
w=l.source.width;
h=l.source.height;
threshold = 0;
width_bits = Math.ceil(Math.log(w)/Math.log(2));
height_bits = Math.ceil(Math.log(h)/Math.log(2));
left = 0; right = 0;
for (b=width_bits-1; b>=0; b--) {
slice_width = 2**b;
if (l.sampleImage([left + slice_width/2, h/2], [slice_width/2, h/2],includeEffects)[3]<=threshold) {
left+=slice_width;
}
if (l.sampleImage([w - (right + slice_width/2), h/2], [slice_width/2, h/2],includeEffects)[3]<=threshold) {
right+=slice_width;
}
}
right= w-right;
top = 0; bottom = 0;
for (b=height_bits-1; b>=0; b--) {
slice_height = 2**b;
if (l.sampleImage([w/2, top + slice_height/2], [w/2, slice_height/2],includeEffects)[3]<=threshold) {
top+=slice_height;
}
if (l.sampleImage([w/2, h - (bottom + slice_height/2)], [w/2, slice_height/2],includeEffects)[3]<=threshold) {
bottom+=slice_height;
}
}
bottom = h-bottom;
[left+right, top+bottom]/2 -
Lui Mauricio Moraes Nogueira
April 24, 2024 at 12:49 amHello Filip, this is amazing!!
Do you know if there is a way to create a bounding box based on the alpha of a layer? Maybe through a shape layer, so I can access its position and size in space.
-
Filip Vandueren
April 24, 2024 at 8:29 amCertainly, all the calculations for the bounding-box are already done, we just need a different final line.
For a rectangle shape, we need two 2-dimensional results: the center of the rectangle, and the width and height of the rectangle.
You could rewrite the two expressions like this:
Size:
// posterizeTime(0); // uncomment for still images
includeEffects = false;
l=thisComp.layer("transparent PNG");
w=l.source.width;
h=l.source.height;
threshold = 0;
width_bits = Math.ceil(Math.log(w)/Math.log(2));
height_bits = Math.ceil(Math.log(h)/Math.log(2));
left = 0; right = 0;
for (b=width_bits-1; b>=0; b--) {
slice_width = 2**b;
if (l.sampleImage([left + slice_width/2, h/2], [slice_width/2, h/2],includeEffects)[3]<=threshold) {
left+=slice_width;
}
if (l.sampleImage([w - (right + slice_width/2), h/2], [slice_width/2, h/2],includeEffects)[3]<=threshold) {
right+=slice_width;
}
}
right= w-right;
top = 0; bottom = 0;
for (b=height_bits-1; b>=0; b--) {
slice_height = 2**b;
if (l.sampleImage([w/2, top + slice_height/2], [w/2, slice_height/2],includeEffects)[3]<=threshold) {
top+=slice_height;
}
if (l.sampleImage([w/2, h - (bottom + slice_height/2)], [w/2, slice_height/2],includeEffects)[3]<=threshold) {
bottom+=slice_height;
}
}
bottom = h-bottom;
[right-left, bottom-top];Position:
// posterizeTime(0); // uncomment for still images
includeEffects = false;
l=thisComp.layer("transparent PNG");
w=l.source.width;
h=l.source.height;
threshold = 0;
width_bits = Math.ceil(Math.log(w)/Math.log(2));
height_bits = Math.ceil(Math.log(h)/Math.log(2));
left = 0; right = 0;
for (b=width_bits-1; b>=0; b--) {
slice_width = 2**b;
if (l.sampleImage([left + slice_width/2, h/2], [slice_width/2, h/2],includeEffects)[3]<=threshold) {
left+=slice_width;
}
if (l.sampleImage([w - (right + slice_width/2), h/2], [slice_width/2, h/2],includeEffects)[3]<=threshold) {
right+=slice_width;
}
}
right= w-right;
top = 0; bottom = 0;
for (b=height_bits-1; b>=0; b--) {
slice_height = 2**b;
if (l.sampleImage([w/2, top + slice_height/2], [w/2, slice_height/2],includeEffects)[3]<=threshold) {
top+=slice_height;
}
if (l.sampleImage([w/2, h - (bottom + slice_height/2)], [w/2, slice_height/2],includeEffects)[3]<=threshold) {
bottom+=slice_height;
}
}
bottom = h-bottom;
fromComp([(right+left)/2, (bottom+top)/2]);Note that in the 2nd expression, Only the final line is different. And that in both “l” is no longer thisLayer, but is explicitly set to the layer with alpha we want to examine.
This approach with 2 expressions on two properties means that we have to do the entire calculation with sampleImages twice, and while the binary search has made it pretty efficient, it does take twice as long.
Whenever complex expressions need to be done in multiple places, it could be better to do the calculation just once, and then have both size and position reference the same result to get the value.
In the case of a single value that needs to be reused, this can be done in an expression control like a slider for 1 dimension values, or a controlPoint for more dimensions, but there are about 4 or more variables resulting from the expression we might want: left, right, top, bottom, ctr, height, width,…
A neat way to communicate complexer results is with text-layers.
Something like this:
A hidden text-layer named “sourceRect” gets this expression for its sourceText property:
sourceText:
// posterizeTime(0); // uncomment for still images
includeEffects = false;
l=thisComp.layer("transparent PNG");
w=l.source.width;
h=l.source.height;
threshold = 0;
width_bits = Math.ceil(Math.log(w)/Math.log(2));
height_bits = Math.ceil(Math.log(h)/Math.log(2));
left = 0; right = 0;
for (b=width_bits-1; b>=0; b--) {
slice_width = 2**b;
if (l.sampleImage([left + slice_width/2, h/2], [slice_width/2, h/2],includeEffects)[3]<=threshold) {
left+=slice_width;
}
if (l.sampleImage([w - (right + slice_width/2), h/2], [slice_width/2, h/2],includeEffects)[3]<=threshold) {
right+=slice_width;
}
}
right= w-right;
top = 0; bottom = 0;
for (b=height_bits-1; b>=0; b--) {
slice_height = 2**b;
if (l.sampleImage([w/2, top + slice_height/2], [w/2, slice_height/2],includeEffects)[3]<=threshold) {
top+=slice_height;
}
if (l.sampleImage([w/2, h - (bottom + slice_height/2)], [w/2, slice_height/2],includeEffects)[3]<=threshold) {
bottom+=slice_height;
}
}
bottom = h-bottom;
sourceRect = {left: left, right: right, top: top, bottom: bottom, width: right-left, height: bottom-top, ctr: [(left+right)/2,(top+bottom)/2]};
JSON.stringify(sourceRect);if you look at the txt-layer its output will be something like:
{
"left": 715,
"right": 1163,
"top": 304,
"bottom": 450,
"width": 448,
"height": 146,
"ctr": [
939,
377
]
}This has all the data we need (even more than a regular sourceRectAtTime would give) in JSON format. We can read/parse anywhere we need it in a different property’s expression like this:
rectangle’s shape Size:
sr = JSON.parse(thisComp.layer("sourceRect").text.sourceText);
[sr.width, sr.height];rectangle’s shape position:
sr = JSON.parse(thisComp.layer("sourceRect").text.sourceText);
fromComp(sr.ctr);You can add more shapes, like a little circle on the anchor-point, or crosshairs etc. by reading out the values in the text-layers, instead of having to copy (and execute) the entire long calculation expression in each place you need the results.
Finally a caveat:
If you plan on scaling, rotating or moving the alpha-layer anywhere but at its default and want the rectangle shape to keep lining up:
It’s easiest to parent the shape-layer to the alpha layer,
Set the shape layer at scale 100% and position 0,0. (again: the position of the layer, not the position of the shape)
And then don’t do the fromComp() in the rectangle’s shape position:
Rectangle’s shape position:
sr = JSON.parse(thisComp.layer("sourceRect").text.sourceText);
sr.ctr; -
Lui Mauricio Moraes Nogueira
April 24, 2024 at 2:20 pmFilip, this is so amazing!!!! Thank you VERY much for this, it will help me a lot, and I bet this will help a lot of people too.
Reply to this Discussion! Login or Sign Up