About

This page has some technical information that you may or may not be too interested in. If you just want to make GIFs, you should return to the homepage.

Behind the scenes

gifblocks was inspired by the Save project video feature in Scratch, which I recently discovered. I found that feature wonderful, and I do not think it gets enough appreciation.

I feel that the concept of converting a Scratch program to a GIF is extremely powerful for a variety of reasons. Most importantly, it resonates well with the demographics that Scratch aims for: young children. Animated GIFs can be found across the Internet, and on social media. In fact, many Scratch users use animated GIFs as their profile pictures or forum signatures. The ability to create animated profile pictures programmatically would both raise the ceiling and widen the walls of Scratch.

Of course, I do not mean to suggest that gifblocks is the ideal user interface for this kind of creation. The snapshot block is just one of several potential interfaces, and far more research is required before we can expect this kind of feature to be included in Scratch. Nevertheless, I am pleased with how easy it was to implement this, and hopeful for the future.

If you are interested in chatting further about these ideas, you can find my Scratch profile here.

Credits

Even though I only added one block, I think gifblocks is technically a derivative work of Scratch. This means, I have to do several things.

  1. Not call it Scratch—done!
  2. Release gifblocks under GPLv2 or later. I am releasing gifblocks under GPLv3, whose text can be found here.
  3. Release the source code. Now, I feel ridiculous publishing a massive online repository for the very minimal changes I made. So, instead, I have included the changes as a patch that you can find attached below. The original source, of course, can be found on Github.
  4. Remind you guys that gifblocks is based on Scratch from the MIT Media Laboratory. This one is the easiest! :-) If you aren't familiar with Scratch, I would strongly encourage you to check out the website here before you begin tinkering with gifblocks.
Finally, I used gif.js by Johan Nordberg and swfobject by the swfobject team.

Appendix: A patchfile

(Intended to be applied to commit c041fd002be3679759e128b8746ddcf0aa251c6c.)
diff --git a/src/Scratch.as b/src/Scratch.as
index 7b3ccb8..b9814b7 100644
--- a/src/Scratch.as
+++ b/src/Scratch.as
@@ -209,7 +209,8 @@ public class Scratch extends Sprite {
 		stage.addEventListener(Event.ENTER_FRAME, step);
 		stage.addEventListener(Event.RESIZE, onResize);
 
-		setEditMode(startInEditMode());
+		// setEditMode(startInEditMode());
+        setEditMode(true);
 
 		// install project before calling fixLayout()
 		if (editMode) runtime.installNewProject();
diff --git a/src/Specs.as b/src/Specs.as
index 9576619..1e918db 100644
--- a/src/Specs.as
+++ b/src/Specs.as
@@ -394,6 +394,8 @@ public class Specs {
 		["hide all sprites",					" ", 99, "hideAll"],
 		["user id",								"r", 99, "getUserId"],
 
+        ["snapshot", " ", 5, "snapshot"],
+
 	];
 
 	public static var extensionSpecs:Array = ["when %m.booleanSensor", "when %m.sensor %m.lessMore %n", "sensor %m.booleanSensor?", "%m.sensor sensor value", "turn %m.motor on for %n secs", "turn %m.motor on", "turn %m.motor off", "set %m.motor power to %n", "set %m.motor2 direction to %m.motorDirection", "when distance %m.lessMore %n", "when tilt %m.eNe %n", "distance", "tilt"];
diff --git a/src/primitives/Primitives.as b/src/primitives/Primitives.as
index 142abb6..41c97cf 100644
--- a/src/primitives/Primitives.as
+++ b/src/primitives/Primitives.as
@@ -30,6 +30,22 @@ package primitives {
 	import scratch.ScratchSprite;
 	import translation.Translator;
 
+    // I didn't have the patience to figure out which imports were needed, so I
+    // imported them all. Sorry.
+    import flash.display.*;        
+    import flash.events.*;         
+    import flash.geom.Matrix;      
+    import flash.geom.Point;       
+    import flash.geom.Rectangle;   
+    import flash.media.*;          
+    import flash.net.*;            
+    import flash.system.System;    
+    import flash.text.TextField;   
+    import flash.utils.*;          
+    import util.*;
+    import by.blooddy.crypto.image.PNG24Encoder;            
+    import by.blooddy.crypto.image.PNGFilter;               
+
 public class Primitives {
 
 	private const MaxCloneCount:int = 300;
@@ -78,12 +94,28 @@ public class Primitives {
 		primTable["INCR_COUNT"]			= function(b:*):* { counter++ };
 		primTable["CLR_COUNT"]			= function(b:*):* { counter = 0 };
 
+        // Whoo
+        primTable["snapshot"] = snapshot;
+
 		new LooksPrims(app, interp).addPrimsTo(primTable);
 		new MotionAndPenPrims(app, interp).addPrimsTo(primTable);
 		new SoundPrims(app, interp).addPrimsTo(primTable);
 		new VideoMotionPrims(app, interp).addPrimsTo(primTable);
 		addOtherPrims(primTable);
 	}
+    public function fromArray(array:ByteArray):String {
+        var s:String = "";
+        for (var i:uint=0;i<array.length;i++) {
+            s+=("0"+array[i].toString(16)).substr(-2,2);
+        }
+        return s;
+    }
+    function snapshot(b:Block):void {
+        app.stagePane.commitPenStrokes();
+        var d:BitmapData = app.stagePane.saveScreenData();
+        var pngData:ByteArray = PNG24Encoder.encode(d, PNGFilter.PAETH);
+        app.externalCall('ping', null, fromArray(pngData));
+    }
 
 	protected function addOtherPrims(primTable:Dictionary):void {
 		new SensingPrims(app, interp).addPrimsTo(primTable);
diff --git a/src/scratch/ScratchRuntime.as b/src/scratch/ScratchRuntime.as
index 8b4fd6f..e072bdf 100644
--- a/src/scratch/ScratchRuntime.as
+++ b/src/scratch/ScratchRuntime.as
@@ -92,7 +92,7 @@ public class ScratchRuntime {
 	//------------------------------
 
 	public function stepRuntime():void {
-		if (projectToInstall != null && (app.isOffline || app.isExtensionDevMode)) {
+		if (projectToInstall != null /* && (app.isOffline || app.isExtensionDevMode) */) {
 			installProject(projectToInstall);
 			if (saveAfterInstall) app.setSaveNeeded(true);
 			projectToInstall = null;
@@ -844,7 +844,7 @@ public class ScratchRuntime {
 
 	public function projectLoadFailed(ignore:* = null):void {
 		app.removeLoadProgressBox();
-		//DialogBox.notify('Error!', 'Project did not load.', app.stage);
+		DialogBox.notify('Error!', 'Project did not load.', app.stage);
 		app.loadProjectFailed();
 	}