There exists a block in Snap!, the “aspect” block (coined by the selector name):
![]()
But also, it supports hyper ops:
However, its slow! The normal block takes around 20 or so milliseconds, so this function takes around 100. It would be nice if it was faster (and don’t worry, I know how it can be fixed)
How...
In threads.js…
Process.prototype.reportAspect = function (aspect, location) {
// sense colors and sprites anywhere,
// use sprites to read/write data encoded in colors.
//
// usage:
// ------
// left input selects color/saturation/brightness/transparency or "sprites".
// right input selects "mouse-pointer", "myself" or name of another sprite.
// you can also embed a a reporter with a reference to a sprite itself
// or a list of two items representing x- and y- coordinates.
//
// what you'll get:
// ----------------
// left input (aspect):
//
// 'color' - a COLOR object
// 'hue' - hsv HUE on a scale of 0 - 100
// 'saturation' - hsv SATURATION on a scale of 0 - 100
// 'brightness' - hsv BRIGHTNESS on a scale of 0 - 100
// 'transparency' - rgba ALPHA on a reversed (!) scale of 0 - 100
// 'r-g-b-a' - list of rgba values on a scale of 0 - 255 each
// 'sprites' - a list of sprites at the location, empty if none
//
// right input (location):
//
// 'mouse-pointer' - color/sprites at mouse-pointer anywhere in Snap
// 'myself' - sprites at or color UNDERNEATH the rotation center
// sprite-name - sprites at or color UNDERNEATH sprites's rot-ctr.
// two-item-list - color/sprites at x-/y- coordinates on the Stage
//
// what does "underneath" mean?
// ----------------------------
// the not-fully-transparent color of the top-layered sprite at the given
// location excluding the receiver sprite's own layer and all layers above
// it gets reported.
//
// color-aspect "underneath" a sprite means that the sprite's layer is
// relevant for what gets reported. Sprites can only sense colors in layers
// below themselves, not their own color and not colors in sprites above
// their own layer.
if (this.enableHyperOps) {
if (location instanceof List && !this.isCoordinate(location)) {
return location.map(each => this.reportAspect(aspect, each));
}
}
var choice = this.inputOption(aspect),
target = this.inputOption(location),
options = ['hue', 'saturation', 'brightness', 'transparency'],
idx = options.indexOf(choice),
thisObj = this.blockReceiver(),
thatObj,
stage = thisObj.parentThatIsA(StageMorph),
world = thisObj.world(),
point,
clr;
if (target === 'myself') {
if (choice === 'sprites') {
if (thisObj instanceof StageMorph) {
point = thisObj.center();
} else {
point = thisObj.rotationCenter();
}
return this.spritesAtPoint(point, stage);
} else {
clr = this.colorAtSprite(thisObj);
}
} else if (target === 'mouse-pointer') {
if (choice === 'sprites') {
return this.spritesAtPoint(world.hand.position(), stage);
} else {
clr = world.getGlobalPixelColor(world.hand.position());
}
} else if (target instanceof List) {
point = new Point(
target.at(1) * stage.scale + stage.center().x,
stage.center().y - (target.at(2) * stage.scale)
);
if (choice === 'sprites') {
return this.spritesAtPoint(point, stage);
} else {
clr = world.getGlobalPixelColor(point);
}
} else {
if (!target) {return; }
thatObj = this.getOtherObject(target, thisObj, stage);
if (thatObj) {
if (choice === 'sprites') {
point = thatObj instanceof SpriteMorph ?
thatObj.rotationCenter() : thatObj.center();
return this.spritesAtPoint(point, stage);
} else {
clr = this.colorAtSprite(thatObj);
}
} else {
return;
}
}
if (choice === 'color') {
return clr.copy();
}
if (choice === 'r-g-b-a') {
return new List([clr.r, clr.g, clr.b, Math.round(clr.a * 255)]);
}
if (idx < 0 || idx > 3) {
return;
}
if (idx === 3) {
return (1 - clr.a) * 100;
}
return clr[SpriteMorph.prototype.penColorModel]()[idx] * 100;
};
As you can see, if hyper-ops are enabled and the location is a list (with coords), it maps it with the same reportAspect function. Now, note the multiple uses of getGlobalPixelColor, how about we check the code for that? (in morphic.js)
WorldMorph.prototype.getGlobalPixelColor = function (point) {
// answer the color at the given point.
// first, create a new temporary canvas representing the fullImage
// and sample that one instead of the actual world canvas
// this slows things down but keeps Chrome from crashing
// in v119 in the Fall of 2023
var dta = Morph.prototype.fullImage.call(this)
.getContext('2d')
.getImageData(point.x, point.y, 1, 1)
.data;
return new Color(dta[0], dta[1], dta[2]);
};
Notice the problem here? For every coordinate in the location list, it renders everything on the world over again! The fix would be, in the reportAspect function–instead of calling itself over and over– do something a bit more complicated but faster. If the block should be a hyperop, instead run its own code that will get the rendered world canvas and use that to get the coordinate colors. Here’s the (hopefully) fixed code!
if (this.enableHyperOps) {
if (location instanceof List && !this.isCoordinate(location)) {
// old code: return location.map(each => this.reportAspect(aspect, each));
var thisObj = this.blockReceiver(),
world = thisObj.world(),
ctx = Morph.prototype.fullImage.call(world)
.getContext('2d');
return location.map(each => {
var data = ctx.getImageData(each.at(1), each.at(2), 1, 1)
.data;
return new Color(data[0], data[1], data[2]);
});
}
}
Hope this gets fixed!
