This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Bomb Bloke's profile picture

GIF API

Started by Bomb Bloke, 26 April 2015 - 10:39 PM
Bomb Bloke #1
Posted 27 April 2015 - 12:39 AM
pastebin get 5uk9uRjC GIF

Requires BBPack:

pastebin get cUYTGbpb bbpack

So yeah, a GIF decoder (and encoder). You hand it a GIF and it draws it. If it's an animation, it can handle that too. Fairly straight-forward.

Videohttp://www.youtube.com/watch?v=Db-UdqSQRL8
Although it currently tends to stall, skyTerm (also demonstrated in the video) can be found here.

CC 1.74+ gets a better rendering rate, though updated versions of the API run at about full speed on much older ComputerCraft builds.

For reference, a regular computer display has 51x19 characters available for rendering. A full-sized monitor, with a text scale of 0.5, has 164x81 characters. If you want some suitable images, try a web search, or perhaps this site. The API can resize loaded images to fit your display, but the process is processor-intensive compared to using an external tool.

UsageGIF.loadGIF(string targetFile [, string palette file]) => table imageData

Returns a table filled with numerically indexed subtables, one for each image in the GIF:

Loaded GIF structure
{
	["backgroundCol"] = (number) colour. When calling GIF.animateGIF(), used for transparent pixels. Calling GIF.drawGIF() leaves the background undisturbed
	["backgroundCol2"] = (string) colour. Paintutils representation; eg colours.red would be "e"
	["width"] = (number) image width
	["height"] = (number) image height
	["pal"] = (table) sub-tables containg RGB values for each colour in the image, stored per the pattern {{R,G,B},{R,G,B},...}. Only present if a palette was specified during image loading.
	
	-- First frame in GIF (non-animations only have a single frame):
	[1] = {
		["xstart"] = (number) columns. Start rendering this frame at the GIF's position, plus this many columns
		["ystart"] = (number) rows. Start rendering this frame at the GIF's position, plus this many rows
		["xend"] = (number) frame width
		["yend"] = (number) frame height
		["delay"] = (number) seconds. Sleep this duration after rendering the frame, before drawing the next
		["disposal"] = (number) undraw method. 1 = Leave alone, 2 = After the delay, clear the frame's area with the image's background colour immediately before drawing the next frame
		
		First row of the frame's pixel data:
		[1] = {(number) skip / (string) blit, (number) skip / (string) blit, (number) skip / (string) blit, etc...}
		When rendering the data in a given row's table, if a number is encountered, add that to the column position of the cursor. If a string is encountered, term.blit() with it as the text background. Continue until the end of the row's table
		[2] = Second row
		...

		If a palette was specified during image loading, the strings are replaced with sub-tables, containing numbers indexing into image.pal.
	}
	[2] = Second frame
	...
}

If specified, "palette file" must point to a file containing a serialised table, containing sub-tables with RGB values. Other elements can be present - see this example. The image will be re-mapped to use this palette. The result cannot be drawn / animated / converted to paintutils format by this API; omit the palette string to get images compatible with ComputerCraft terminals.

GIF.drawGIF(table imageData [, number xPos] [, number yPos] [, table terminal])

Draws a single image from a GIF that has been loaded by GIF.loadGIF(). You can either pass in a certain frame (eg myImage[4] for the fourth frame), or just pass in the whole GIF object to render just the first frame (eg myImage). In the case of any GIF that doesn't animate (ei it only contains a single image), just go with the latter.

If not specified, xPos and yPos default to 1, and "terminal" defaults to term.

GIF.animateGIF(table imageData [, number xPos] [, number yPos] [, table terminal])

Pretty much exactly the same as GIF.drawGIF(), but you can't hand it an individual frame - it requires the whole imageData table, and draws the contained frames in a loop. Doesn't stop on its own, so you'll need to rig up something via eg the parallel API if you only want to run it for a while.

GIF.saveGIF(table imageData, string fileName)

Saves the image to the specified file. As with GIF.drawGIF(), you can either hand in the whole GIF object or just a single frame (depending on whether you want to create an animating GIF or a static one - single-frame images are saved as static regardless). Also handles images loaded using paintutils.loadImage().

GIF.flattenGIF(table imageData [, boolean optimise]) => table imageData

Think of an animated GIF as being like an image with many invisible layers - at the beginning you can only see the first, but as each frame / layer's timer expires, the next is revealed on top of it, progressing the animation. Frames tend to only contain the part of the overall image they're affecting, but this function flattens each frame so that they each contain ALL the image data that should be visible when they're rendered. Rendering will be slower, but it's handy if you wish to eg bounce an animation around your screen.

If the "optimise" flag is set to true, it does the reverse, chopping flattened layers down to only contain the bare minimum content they need. GIFs are not automatically "optimised" in this way when loaded / resized, and the process can be time-intensive, so consider using GIF.saveGIF() after optimising so that you don't need to repeat it.

GIF.toPaintutils(table imageData) => table paintutils image

Returns a table suitable for use with paintutils.drawImage(), as though the frame had been paintutils.loadImage()'d. As with most other commands in the API, you can either hand in the whole GIF object or just a single frame. In the former case, the first frame of the image will be the one returned.

GIF.buildGIF(table paintutils image, table paintutils image, …) => table imageData

Takes any number of paintutils.loadImage()'d images and returns them as a GIF object, as if they were a GIF that'd been GIF.loadGIF()'d. Each frame will have a default delay of 0.1 set.

GIF.setBackgroundColour(table imageData, number / string colour)

Alters the GIF's background colour (used for transparent pixels when animating). Can either use a suitable number (eg colours.red) or a suitable paintutils string(eg, "e" for red).

When loading a GIF, the API checks to see which colours it doesn't make use of, and defaults to the first of these as the transparent colour. If a GIF uses all 16 of ComputerCraft's colours then black is the default.

Doesn't return anything, but rather just alters the original object. Yes, you can spell it wrong, if you must.

GIF.resizeGIF(table imageData [, number width] [, number height]) => table imageData

Returns a scaled version of a loaded GIF. Width or height can be omitted (set to nil, in the case of width), but not both - if one isn't specified, the other will be scaled to match.

For eg, if you wanted to alter a GIF to best match your display resolution, you might do:

Resize example
os.loadAPI("GIF")
local image = GIF.loadGIF("someImage.gif")

local mon = peripheral.find("monitor")
mon.setTextScale(0.5)
term.redirect(mon)

local x, y = term.getSize()

if x < image.width or y < image.height then
	if x < math.floor(y / image.height * image.width) then
		image = GIF.resizeGIF(image, x)
	else
		image = GIF.resizeGIF(image, nil, y)
	end
end

GIF.drawGIF(image)

Consider GIF.saveGIF()'ing the result so you need not repeat this process. Using an external image editor can be much faster, of course.

Here's the sample script used in the video:

Example
os.loadAPI("GIF")

local mon = peripheral.find("monitor")

local gifs = fs.find("*.gif")  -- Note: fs.find() is case-sensitive, even if your file system isn't.

mon.setTextScale(0.5)
local x, y = mon.getSize()

while true do for i = 1, #gifs do
	local image = GIF.loadGIF(gifs[i])
	mon.setBackgroundColour(image[1].transparentCol or image.backgroundCol)
	mon.clear()
	
	parallel.waitForAny(
		function()
			GIF.animateGIF(image, math.floor((x - image.width) / 2) + 1, math.floor((y - image.height) / 2) + 1, mon)
		end,
		
		function()
			sleep(10)
		end
	)
end end

Version History2015/04/27
1.0.0
Initial release.

1.0.1 - 1.0.3
Attempts to fix the jump between the final frame of animations back to the first. 1.0.3 seems to've nailed it.

2015/05/02
1.0.4
Some animating GIFs fail to set a frame rate; for those, a default is now set at 10fps.

2015/05/04
1.1.0
Added GIF.saveGIF() and GIF.toPaintutils().

2015/05/13
1.1.1
Flicker reduction.
Under older ComputerCraft builds, rendering is now faster and can no longer trigger yield protection.

2015/05/30
1.2.0
Further speed enhancements and bug fixes. Loading's slower, rendering's faster.
Backwards compatibility now extends to at least ComputerCraft 1.5, and probably further (previously stopped at 1.6).
Added GIF.flattenGIF(), GIF.resizeGIF(), GIF.setBackgroundColour() and GIF.buildGIF().
Individual image frames no longer have "transparentCol" keys; main image's "backgroundCol" key is used instead.

2015/09/26
1.2.1
Loading's faster (skimps on the optimising), rendering's pretty much the same speed anyway.
Can now pass additional filenames to GIF.loadGIF(), containing colour info - it'll map the loaded GIF to a palette defined within them.

2015/10/15
1.2.2
Fixes for GIF.buildGIF().

2015/12/21
1.2.3
First row of images produced with GIF.toPaintutils() will now always be the length of the original row width.
Fixes for GIF.flattenGIF().

2016/02/16
1.2.4
Fixes a floating-point issue affecting encoding under older CC builds.

2016/03/27
1.2.5
Fixes a crash bug affecting files with entirely clear frames.
Edited on 09 June 2017 - 12:18 PM
Antelux #2
Posted 27 April 2015 - 01:32 AM
This is pretty cool. I can imagine using this to make a game of some sort.

… Hope I didn't just give anyone ideas. :P/>
SquidDev #3
Posted 28 April 2015 - 03:08 PM
Wow, oh wow. This is pretty awesome. I'm very impressed that the code is that small - though I guess you've already got LZW implemented. All we need now is PNG and JPEG parsers and then we have the complete suite! I'm also impressed how close the colours were, I don't remember that with my bitmap parser. Who says we need more colours? :P/>.
Bomb Bloke #4
Posted 29 April 2015 - 12:18 AM
It probably looks like there's more colours than there are, at least in the video, where you've got compression artefacts muddying the waters.

That said, I reckon the effect's pretty good, even in-game. Especially with animations, where the mind has a hard time spotting odd colours before they're gone. :)/>
Spoiler
Edited on 10 February 2016 - 10:15 PM
Lyqyd #5
Posted 29 April 2015 - 12:56 AM
Very nice! Palettizing the gif is impressively well done! Does it also handle individually paletted frames?
Bomb Bloke #6
Posted 29 April 2015 - 12:57 AM
Yes, it does.
Bomb Bloke #7
Posted 04 May 2015 - 01:20 PM
I'd been considering encoding functionality, but didn't think there to be much use for it. Then CrazedProgrammer went and posted a script that contained an animation represented via a single +30mb string, and I just had to try converting it…

Totally go watch the video he made out of that, by the way. It's pretty cool.

So yeah, added GIF.saveGIF(), and while I was at it, GIF.toPaintutils().
Edited on 04 May 2015 - 11:21 AM
Bomb Bloke #8
Posted 30 May 2015 - 01:39 PM
Another update. I've tweaked things around such that rendering speeds are now quite reasonable under older versions of ComputerCraft; even testing under CC 1.5, most of the example GIFs from my original video play at full speed.

There've been a few other functions added, the most interestingish probably being GIF.resizeGIF(), which is useful for the purposes of getting a specific aspect ratio or simply forcing an image to fit a given display. It's slow as a dog, mind you, but handy if an external editor isn't immediately available.

I've also fleshed out the "usage" documentation somewhat.
Pyuu #9
Posted 30 May 2015 - 04:33 PM
This is amazing, I haven't looked through your code to find out how you decode gif files but it looks like you spent a lot of quality time working on this.
DaKodiMaster #10
Posted 30 May 2015 - 10:03 PM
This looks amazing. I want to try this, but I dont have any gif files to use.
Bomb Bloke #11
Posted 31 May 2015 - 04:59 AM
This is amazing, I haven't looked through your code to find out how you decode gif files but it looks like you spent a lot of quality time working on this.

In short, I do it the long way.

This looks amazing. I want to try this, but I dont have any gif files to use.

Figured I might as well put together a demo which doesn't require a massive monitor:

pastebin get cUYTGbpb bbpack
pastebin get 5uk9uRjC GIF
bbpack get wZfpq9h8
slideshow
Edited on 20 July 2017 - 11:19 PM
minecraftwarlock #12
Posted 07 June 2015 - 03:34 AM
Is there any way to get the original RGB values from the image?
Bomb Bloke #13
Posted 07 June 2015 - 03:59 AM
The API doesn't offer any such method, but could be modified to provide one. I'm not certain why or how such a feature would be of any use, however.
minecraftwarlock #14
Posted 20 June 2015 - 11:22 PM
Where in the code could the RGB values be found?
Bomb Bloke #15
Posted 21 June 2015 - 01:07 AM
Well, in the current build (1.2), the global palette (if the file has one) is read in by lines 377-382, and local palettes (for individual frames within the files, if they have one) are read in by line 451.
uaBArt #16
Posted 23 February 2016 - 07:33 PM
I tried to play this GIF
wget http://www.webdevelo...files/neko2.gif someImage.gif

But I get

GIF:509: attempt to get length of boolean

I tried more and I had some fun
Edited on 23 February 2016 - 09:18 PM
Bomb Bloke #17
Posted 24 February 2016 - 01:13 AM
Note my bug, sorry; wget isn't suitable for downloading anything other than text files. Truth be told, ComputerCraft simply doesn't offer a method of getting any other files directly from the web, full stop.

Personally if I want to get them into a server environment I first place them onto the drive of a SSP CC system using my real computer's file manager, and then use Package to upload them to Pastebin in a format that can be downloaded within an SMP world.
uaBArt #18
Posted 24 February 2016 - 11:08 AM
I tried to get maximum from what we can output on CC huge monitor, so I have picture in 164 symbols width and I used all symbols that might to be
Original Image:
Spoiler

What I get
Spoiler

What we can get without symbols
Spoiler
or

P.S. But rendering of this took one night for creating 6x9 png for each symbol in each color combination (57033 unique files), and after close to 4 hours to compile that in mosaic using AndreaMosaic with custom config.
P.P.S. If someone need all images that I generated, you can take them on my OneDrive

And do we have something like format for images like in my picture above? For saving char and two colors for each pixel.
If we do, I'll probably upload my image in compatible format.
Bomb Bloke #19
Posted 25 February 2016 - 01:32 AM
Figured I'd see what I could do in-game:

Low-Res
os.loadAPI("GIF")

local mon = peripheral.find("monitor")
mon.setTextScale(0.5)

local gif = "ssmall.gif"  -- Image saved pre-scaled to 144x81

local x, y = mon.getSize()

local image = GIF.loadGIF(gif)
mon.setBackgroundColour(image[1].transparentCol or image.backgroundCol)
mon.clear()

GIF.drawGIF(image[1], math.floor((x - image.width) / 2) + 1, math.floor((y - image.height) / 2) + 1, mon)


High-Res (Prescaled)
os.loadAPI("GIF")
os.loadAPI("blittle")

local mon = peripheral.find("monitor")
mon.setTextScale(0.5)
mon = blittle.createWindow(mon)

local gif = "sbig.gif"  -- Image saved pre-scaled to 328x184

local x, y = mon.getSize()

local image = GIF.loadGIF(gif)
mon.setBackgroundColour(image[1].transparentCol or image.backgroundCol)
mon.clear()

GIF.drawGIF(image[1], math.floor((x - image.width) / 2) + 1, math.floor((y - image.height) / 2) + 1, mon)


High-Res (In-game scaling)
os.loadAPI("GIF")
os.loadAPI("blittle")

local mon = peripheral.find("monitor")
mon.setTextScale(0.5)
mon = blittle.createWindow(mon)

local gif = "shuge.gif"  -- Image saved unscaled

local x, y = mon.getSize()

local image = GIF.loadGIF(gif)

image = GIF.resizeGIF(image, 328, 184)

mon.setBackgroundColour(image[1].transparentCol or image.backgroundCol)
mon.clear()

GIF.drawGIF(image[1], math.floor((x - image.width) / 2) + 1, math.floor((y - image.height) / 2) + 1, mon)


The low res version dumps straight to the screen, while the others make use of Blittle. The difference between each of the latter two is that when I allow my code to handle the resizing (as opposed to having an external editor do it), the colour depth is reduced beforehand.

My code can do these translations within a minute (even when loading in the full-sized image at maximum GIF colour depth), but doing them using the whole CC character set would indeed take a lot longer - especially since AndreaMosaic isn't simply averaging out the colour depth within each character space (and it'd do it with no resizing prior to picking the characters, too). If you look at the pig's back for eg you can see it's chosen characters which are best shaped to match how the top of the characters should veer towards yellow.

There's a function in the BLittle API which could load and display the sort of images AndreaMosaic produces, if they were saved appropriately. It's a pretty simple affair so it shouldn't be hard to mimic (assuming you can get AndreaMosaic to give you some sort of text dump representing its output).
Chelebi #20
Posted 18 September 2016 - 10:32 AM
Hi everybody.

i'm trying to get a .gif running but im really lost :P/> can someone explain step by step what i need to do, to get a .gif running on a monitor please ?
i would really appreciate the help :)/>
Bomb Bloke #21
Posted 18 September 2016 - 11:15 AM
In the first post is an example script - copy / paste that onto your computer, along with a few GIF files, then simply run that.

If you're still stuck, elaborate on the bit you're having trouble with. I have no way of knowing what you've already managed to set up so far.
Chelebi #22
Posted 18 September 2016 - 12:52 PM
so i loaded GIF and package via pastebin and copy / paste your example.

when i run you example it finds the .gif but it does not fit on the screen its zoomes realy faar in.

im trying to get this .gif running maybe this will give an idea.


i don't really have experience with computercraft but i found your Video and wanted to use this to play this GIF on our server spawn. i hope you can help me with that :)/>
Edited on 18 September 2016 - 08:02 PM
Bomb Bloke #23
Posted 19 September 2016 - 09:57 AM
ComputerCraft doesn't offer control over "pixels", as such - you can draw text characters to a display, each of a different colour (and perhaps with a different colour again behind them).

So most CC "art" involves writing coloured spaces. Since the average CC display is 51x19 characters, there isn't room to fully represent a 209x300 image like the one you've got there - even a full-sized external monitor has a maximum resolution of 164x81 characters.

BLittle can help with this somewhat, assuming you're on Minecraft 1.8 or later:

Spoiler
os.loadAPI("GIF")
os.loadAPI("blittle")

local mon = peripheral.find("monitor")

local image = GIF.loadGIF("yourGIF.gif")  --**** Set your filename here!

mon.setBackgroundColour(image.backgroundCol)
image = blittle.shrinkGIF(image, image.backgroundCol)

mon.setTextScale(0.5)
mon.clear()

local x, y = mon.getSize()
x, y = math.floor((x - image.width) / 2) + 1, math.floor((y - image.height) / 2) + 1

while true do
        for i = 1, #image do
                blittle.draw(image[i], x, y, mon)
                sleep(image[i].delay)
        end
end

That'll still only get you 328x243 though. For more you'd need multiple monitors via eg Stitch, but you'll start to interfere with your server tick rate by that stage.

Really you'd be better off shrinking that image. 139x200 would work well with BLittle, whereas you'd need to go down to something like 56x81 without.
Chelebi #24
Posted 19 September 2016 - 10:50 AM
ok im going to try that :)/> Thanks for your Help.
LDDestroier #25
Posted 24 December 2016 - 03:28 AM
Hey Bomb Bloke!

I have a specific problem with figuring out how to use this API on the Project Beta server. It involves using 44 computers to store a 39 MB GIF file of the trailer to the Rocky Horror Picture Show and animate it onto a monitor, but it errors, and the error is so long, that it gets another 'too long without yield' error just rendering it.

…help?
Bomb Bloke #26
Posted 24 December 2016 - 01:09 PM
I logged into the server, but even there I found nothing which would lead me to anything I could use to even begin to troubleshoot that. Provide more information, such as the code you're running, the last of your lines to execute before the error, the image file used to generate the error, etc…