Filipe answered me in another post and it’s working like a charm, I’ll leave his answer here in case someone is looking for the same thing:
Filip answer:
Certainly, 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;