diff --git a/HISTORY.md b/HISTORY.md
index ddca8285..d57bdc56 100755
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -20,6 +20,7 @@
* new "play sound at sample rate" command
* accept lists and lists of lists as inputs to all sound primitives
* new "play frequency" commands in the Sounds category
+ * pixel access primitives for bitmap and vector (!) graphics
* added "neg" selector to monadic function reporter in "Operators" category
* added "log2" selector to monadic function reporter in "Operators" category
* added "^" reporter (power of) in the Operators category
@@ -61,6 +62,9 @@
* German
* French
+### 2019-04-09
+* Blocks, Objects, Threads: new "getImageAttribute" reporter primitive
+
### 2019-04-08
* Blocks, Objects, Threads: new "getSoundAttribute" reporter primitive
* Blocks, Objects, Threads: new "play sound at sample rate" command primitive
diff --git a/snap.html b/snap.html
index 10b2260f..4da466d5 100755
--- a/snap.html
+++ b/snap.html
@@ -6,9 +6,9 @@
-
-
-
+
+
+
diff --git a/src/blocks.js b/src/blocks.js
index 12286768..9b76b14f 100644
--- a/src/blocks.js
+++ b/src/blocks.js
@@ -148,7 +148,7 @@ CustomCommandBlockMorph, SymbolMorph, ToggleButtonMorph, DialMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.blocks = '2019-April-08';
+modules.blocks = '2019-April-09';
var SyntaxElementMorph;
var BlockMorph;
@@ -1010,6 +1010,19 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
true // read-only
);
break;
+ case '%img': // image attributes
+ part = new InputSlotMorph(
+ null, // text
+ false, // numeric?
+ {
+ 'name' : ['name'],
+ 'width' : ['width'],
+ 'height' : ['height'],
+ 'pixels' : ['pixels']
+ },
+ true // read-only
+ );
+ break;
case '%rate':
part = new InputSlotMorph(
null,
diff --git a/src/objects.js b/src/objects.js
index a856a46f..3c535eeb 100644
--- a/src/objects.js
+++ b/src/objects.js
@@ -84,7 +84,7 @@ BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize,
TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph, HandleMorph,
AlignmentMorph, Process, XML_Element, VectorPaintEditorMorph*/
-modules.objects = '2019-April-08';
+modules.objects = '2019-April-09';
var SpriteMorph;
var StageMorph;
@@ -304,6 +304,12 @@ SpriteMorph.prototype.initBlocks = function () {
category: 'looks',
spec: 'costume #'
},
+ reportGetImageAttribute: {
+ type: 'reporter',
+ category: 'looks',
+ spec: '%img of costume %cst',
+ defaults: [['width']]
+ },
doSayFor: {
only: SpriteMorph,
type: 'command',
@@ -1994,6 +2000,8 @@ SpriteMorph.prototype.blockTemplates = function (category) {
blocks.push(watcherToggle('getCostumeIdx'));
blocks.push(block('getCostumeIdx', this.inheritsAttribute('costume #')));
blocks.push('-');
+ blocks.push(block('reportGetImageAttribute'));
+ blocks.push('-');
blocks.push(block('doSayFor'));
blocks.push(block('bubble'));
blocks.push(block('doThinkFor'));
@@ -7492,6 +7500,8 @@ StageMorph.prototype.blockTemplates = function (category) {
blocks.push(watcherToggle('getCostumeIdx'));
blocks.push(block('getCostumeIdx'));
blocks.push('-');
+ blocks.push(block('reportGetImageAttribute'));
+ blocks.push('-');
blocks.push(block('changeEffect'));
blocks.push(block('setEffect'));
blocks.push(block('clearEffects'));
@@ -8893,6 +8903,32 @@ Costume.prototype.thumbnail = function (extentPoint) {
return trg;
};
+// Costume pixel access
+
+Costume.prototype.rasterized = function () {
+ return this;
+};
+
+Costume.prototype.pixels = function () {
+ var i,
+ pixels = [],
+ src = this.contents.getContext('2d').getImageData(
+ 0,
+ 0,
+ this.contents.width,
+ this.contents.height
+ );
+ for (i = 0; i < src.data.length; i += 4) {
+ pixels.push(new List([
+ src.data[i],
+ src.data[i + 1],
+ src.data[i + 2],
+ src.data[i + 3]
+ ]));
+ }
+ return new List(pixels);
+};
+
// Costume catching "tainted" canvases
Costume.prototype.isTainted = function () {
@@ -9022,6 +9058,22 @@ SVG_Costume.prototype.edit = function (
);
};
+// SVG_Costume pixel access
+
+SVG_Costume.prototype.rasterized = function () {
+ var canvas = newCanvas(this.extent(), true),
+ ctx = canvas.getContext('2d'),
+ rasterized;
+
+ ctx.drawImage(this.contents, 0, 0);
+ rasterized = new Costume(
+ canvas,
+ this.name,
+ this.rotationCenter.copy()
+ );
+ return rasterized;
+};
+
// CostumeEditorMorph ////////////////////////////////////////////////////////
// CostumeEditorMorph inherits from Morph:
diff --git a/src/threads.js b/src/threads.js
index 5c579b13..ff8a0686 100644
--- a/src/threads.js
+++ b/src/threads.js
@@ -62,7 +62,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy,
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph, Color,
TableFrameMorph, ColorSlotMorph, isSnapObject, Map*/
-modules.threads = '2019-April-08';
+modules.threads = '2019-April-09';
var ThreadManager;
var Process;
@@ -2291,7 +2291,7 @@ Process.prototype.doPlaySoundAtRate = function (name, rate) {
}
source.pause = source.stop;
source.ended = false;
- source.onended = function () {this.ended = true; }
+ source.onended = function () {this.ended = true; };
source.start();
rcvr.parentThatIsA(StageMorph).activeSounds.push(source);
return source;
@@ -2420,7 +2420,7 @@ Process.prototype.encodeSound = function (samples, rate) {
}
source = ctx.createBufferSource();
source.buffer = arrayBuffer;
- source.audioBuffer = source.buffer; // +++
+ source.audioBuffer = source.buffer;
return source;
};
@@ -4335,6 +4335,33 @@ Process.prototype.doSetInstrument = function (num) {
}
};
+// Process image processing primitives
+
+Process.prototype.reportGetImageAttribute = function (choice, name) {
+ var cst = name instanceof Costume ? name
+ : (typeof name === 'number' ?
+ this.blockReceiver().costumes.at(name)
+ : detect(
+ this.blockReceiver().costumes.asArray(),
+ function (c) {return c.name === name.toString(); }
+ )
+ ),
+ option = this.inputOption(choice);
+
+ switch (option) {
+ case 'name':
+ return cst.name;
+ case 'width':
+ return cst.width();
+ case 'height':
+ return cst.height();
+ case 'pixels':
+ return cst.rasterized().pixels();
+ default:
+ return 0;
+ }
+};
+
// Process constant input options
Process.prototype.inputOption = function (dta) {