support embedding blocks into PNG metadata

snap8
Jens Mönig 2022-04-22 16:11:15 +02:00
rodzic 789e257c0b
commit 1e18b5b1ad
5 zmienionych plików z 133 dodań i 16 usunięć

Wyświetl plik

@ -41,6 +41,9 @@
* **Translation Updates:**
* German
### 2022-04-22
* morphic, objects, gui: support embedding blocks into PNG metadata
### 2022-04-20
* threads: terminate all threads waiting to display a question on ASKing a falsy value
* threads: clear "answer" on ASK nothing/falsy

Wyświetl plik

@ -13,14 +13,14 @@
<meta name="apple-mobile-web-app-title" content="Snap!">
<meta name="msapplication-TileImage" content="img/snap-icon-144.png">
<meta name="msapplication-TileColor" content="#FFFFFF">
<script src="src/morphic.js?version=2022-01-28"></script>
<script src="src/morphic.js?version=2022-04-22"></script>
<script src="src/symbols.js?version=2021-03-03"></script>
<script src="src/widgets.js?version=2021-17-09"></script>
<script src="src/blocks.js?version=2022-04-20"></script>
<script src="src/threads.js?version=2022-04-20"></script>
<script src="src/objects.js?version=2022-04-20"></script>
<script src="src/objects.js?version=2022-04-22"></script>
<script src="src/scenes.js?version=2022-03-03"></script>
<script src="src/gui.js?version=2022-04-06"></script>
<script src="src/gui.js?version=2022-04-22"></script>
<script src="src/paint.js?version=2021-07-05"></script>
<script src="src/lists.js?version=2022-02-07"></script>
<script src="src/byob.js?version=2022-04-20"></script>

Wyświetl plik

@ -86,7 +86,7 @@ BlockVisibilityDialogMorph, ThreadManager*/
// Global stuff ////////////////////////////////////////////////////////
modules.gui = '2022-April-20';
modules.gui = '2022-April-22';
// Declarations
@ -2471,7 +2471,7 @@ IDE_Morph.prototype.endBulkDrop = function () {
this.bulkDropInProgress = false;
};
IDE_Morph.prototype.droppedImage = function (aCanvas, name) {
IDE_Morph.prototype.droppedImage = function (aCanvas, name, embeddedCode) {
var costume = new Costume(
aCanvas,
this.currentSprite.newCostumeName(
@ -2491,6 +2491,7 @@ IDE_Morph.prototype.droppedImage = function (aCanvas, name) {
return;
}
costume.code = embeddedCode || null;
this.currentSprite.addCostume(costume);
this.currentSprite.wearCostume(costume);
this.spriteBar.tabBar.tabTo('costumes');
@ -10063,6 +10064,9 @@ CostumeIconMorph.prototype.exportCostume = function () {
if (this.object instanceof SVG_Costume) {
// don't show SVG costumes in a new tab (shows text)
ide.saveFileAs(this.object.contents.src, 'text/svg', this.object.name);
} else if (this.object.code) {
// embed blocks code inside the PNG image data
ide.saveFileAs(this.object.pngData(), 'image/png', this.object.name);
} else { // rasterized Costume
ide.saveCanvasAs(this.object.contents, this.object.name);
}

Wyświetl plik

@ -642,7 +642,7 @@
Drops of image elements from outside the world canvas are dispatched as
droppedImage(aCanvas, name)
droppedImage(aCanvas, name, embeddedCode)
droppedSVG(anImage, name)
events to interested Morphs at the mouse pointer. If you want your Morph
@ -663,8 +663,22 @@
droppedImage() event with a canvas containing a rasterized version of the
SVG.
The same applies to drops of audio or text files from outside the world
canvas.
Note that PNG images provide for embedded text comments, which can be used
to include code inside the image. Such a payload has to be identified by
an agreed-upon marker. The default tag is stored in MorphicPreferences and
can be overriden by apps wishing to make use of this feature. If such an
embedded text-payload is found inside a PNG it is passed as the optional
third "embeddedCode" parameter to the "droppedImage()" event. embedded text
only applies to PNGs. You can embed a string into the PNG metadata of a PNG
by calling
embedMetadataPNG(aCanvas, aString)
with a raster image represented by a canvas and a string that is to be
embedded into the PNG's metadata.
The same event mechanism applies to drops of audio or text files from
outside the world canvas.
Those are dispatched as
@ -1276,6 +1290,8 @@
Jason N (@cyderize) contributed native copy & paste for text editing.
Bartosz Leper contributed retina display support.
Zhenlei Jia and Dariusz Dorożalski pioneered IME text editing.
Dariusz Dorożalski and Jesus Villalobos contributed embedding blocks
into image metadata.
Bernat Romagosa contributed to text editing and to the core design.
Michael Ball found and fixed a longstanding scrolling bug.
Brian Harvey contributed to the design and implementation of submenus.
@ -1289,9 +1305,9 @@
/*global window, HTMLCanvasElement, FileReader, Audio, FileList, Map*/
/*jshint esversion: 6*/
/*jshint esversion: 11, bitwise: false*/
var morphicVersion = '2022-January-28';
var morphicVersion = '2022-April-22';
var modules = {}; // keep track of additional loaded modules
var useBlurredShadows = true;
@ -1303,6 +1319,7 @@ const CLEAR = new Color(0, 0, 0, 0);
Object.freeze(ZERO);
Object.freeze(BLACK);
Object.freeze(WHITE);
Object.freeze(CLEAR);
var standardSettings = {
minimumFontHeight: getMinimumFontHeight(), // browser settings
@ -1318,6 +1335,7 @@ var standardSettings = {
mouseScrollAmount: 40,
useSliderForInput: false,
isTouchDevice: false, // turned on by touch events, don't set
pngPayloadMarker: 'Data\tPayload\tEmbedded',
rasterizeSVGs: false,
isFlat: false,
grabThreshold: 5,
@ -1338,6 +1356,7 @@ var touchScreenSettings = {
mouseScrollAmount: 40,
useSliderForInput: false,
isTouchDevice: true,
pngPayloadMarker: 'Data\tPayload\tEmbedded',
rasterizeSVGs: false,
isFlat: false,
grabThreshold: 5,
@ -1569,6 +1588,65 @@ function copy(target) {
return c;
}
function escapeString(str) {
var len, R = '', k = 0, S, chr, ord,
ascii = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +
'0123456789@*_+-./,';
str = str.toString();
len = str.length;
while(k < len) {
chr = str[k];
if (ascii.indexOf(chr) != -1) {
S = chr;
} else {
ord = str.charCodeAt(k);
if (ord < 256) {
S = '%' + ("00" + ord.toString(16)).toUpperCase().slice(-2);
} else {
S = '%u' + ("0000" + ord.toString(16)).toUpperCase().slice(-4);
}
}
R += S;
k++;
}
return R;
}
function embedMetadataPNG(aCanvas, aString) {
var embedTag = MorphicPreferences.pngPayloadMarker,
crc32 = (str, crc) => {
let table = [...Array(256).keys()].map(it =>
[...Array(8)].reduce((cc) =>
(cc & 1) ? (0xedb88320 ^ (cc >>> 1)) : (cc >>> 1), it)
);
crc = [...str].reduce(
(crc, ch) => (crc >>> 8) ^ table[(crc ^ ch.charCodeAt(0)) & 0xff],
(crc ? crc = 0 : crc) ^ (-1) // (crc ||= 0) ^ (-1)
);
return ( crc ^ (-1) ) >>> 0;
},
arr2Str = (arr) =>
arr.reduce((res, byte) => res + String.fromCharCode(byte), ''),
int2BStr = (val) =>
arr2Str(Array.from(new Uint8Array(new Uint32Array( [val] ).buffer)).reverse()),
buildChunk = (data) => {
let res = "iTXt" + data;
return int2BStr(data.length) + res + int2BStr(crc32(res));
},
parts = aCanvas.toDataURL("image/png").split(","),
bPart = atob(parts[1]).split(""),
newChunk = buildChunk(
"Snap!_SRC\0\0\0\0\0" +
embedTag +
aString +
embedTag
);
bPart.splice(-12, 0, ...newChunk);
parts[1] = btoa(bPart.join(""));
return parts.join(',');
}
// Retina Display Support //////////////////////////////////////////////
/*
@ -11643,7 +11721,7 @@ HandMorph.prototype.processDrop = function (event) {
onto the world canvas, turn it into an offscreen canvas or audio
element and dispatch the
droppedImage(canvas, name)
droppedImage(canvas, name, embeddedCode)
droppedSVG(image, name)
droppedAudio(audio, name)
droppedText(text, name, type)
@ -11692,16 +11770,41 @@ HandMorph.prototype.processDrop = function (event) {
function readImage(aFile) {
var pic = new Image(),
frd = new FileReader(),
trg = target;
url = event.dataTransfer?.getData( "text/uri-list"),
file = event.dataTransfer?.files?.[0],
trg = target,
embedTag = MorphicPreferences.pngPayloadMarker;
while (!trg.droppedImage) {
trg = trg.parent;
}
pic.onload = () => {
canvas = newCanvas(new Point(pic.width, pic.height), true);
canvas.getContext('2d').drawImage(pic, 0, 0);
trg.droppedImage(canvas, aFile.name);
bulkDrop();
(async () => {
if (!file && url) {
// alternative: "https://api.allorigins.win/raw?url=" + url
file = await fetch(url);
}
// extract embedded data (e.g. blocks)
// from the image's meta data if present.
let buff = new Uint8Array(await file?.arrayBuffer()),
strBuff = buff.reduce((acc, b) =>
acc + String.fromCharCode(b), ""),
embedded = strBuff.includes(embedTag) ?
decodeURIComponent(
escapeString((strBuff)?.split(embedTag)[1])
)
: null;
trg.droppedImage(canvas, aFile.name, embedded);
bulkDrop();
})();
};
frd = new FileReader();
frd.onloadend = (e) => pic.src = e.target.result;
frd.readAsDataURL(aFile);

Wyświetl plik

@ -89,11 +89,12 @@ SpeechBubbleMorph, InputSlotMorph, isNil, FileReader, TableDialogMorph, String,
BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, BooleanSlotMorph,
localize, TableMorph, TableFrameMorph, normalizeCanvas, VectorPaintEditorMorph,
AlignmentMorph, Process, WorldMap, copyCanvas, useBlurredShadows, BLACK,
BlockVisibilityDialogMorph, CostumeIconMorph, SoundIconMorph, MenuItemMorph*/
BlockVisibilityDialogMorph, CostumeIconMorph, SoundIconMorph, MenuItemMorph,
embedMetadataPNG*/
/*jshint esversion: 6*/
modules.objects = '2022-April-20';
modules.objects = '2022-April-22';
var SpriteMorph;
var StageMorph;
@ -10776,6 +10777,12 @@ Costume.prototype.isTainted = function () {
return false;
};
// Costume storing blocks code in PNG exports
Costume.prototype.pngData = function () {
return embedMetadataPNG(this.contents, this.code);
};
// SVG_Costume /////////////////////////////////////////////////////////////
/*