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) {