View difference between Paste ID: HkxvJcSF and ujchRSnU
SHOW: | | - or go back to the newest paste.
1
-- +--------------------------------------------------------+
2
-- |                                                        |
3
-- |                        BLittle                         |
4
-- |                                                        |
5
-- +--------------------------------------------------------+
6
7
local version = "Version 1.1.6beta"
8
9
-- By Jeffrey Alexander, aka Bomb Bloke.
10
-- Convenience functions to make use of ComputerCraft 1.76's new "drawing" characters.
11
-- http://www.computercraft.info/forums2/index.php?/topic/25354-cc-176-blittle-api/
12
13
-------------------------------------------------------------
14
15
if shell then
16
	local arg = {...}
17
	
18
	if #arg == 0 then
19
		print("Usage:")
20
		print("blittle <scriptName> [args]")
21
		return
22
	end
23
	
24
	if not blittle then os.loadAPI(shell.getRunningProgram()) end
25
	local oldTerm = term.redirect(blittle.createWindow())
26
	shell.run(unpack(arg))
27
	term.redirect(oldTerm)
28
	
29
	return
30
end
31
32
local relations = {[0] = {8, 4, 3, 6, 5}, {4, 14, 8, 7}, {6, 10, 8, 7}, {9, 11, 8, 0}, {1, 14, 8, 0}, {13, 12, 8, 0}, {2, 10, 8, 0}, {15, 8, 10, 11, 12, 14},
33
		{0, 7, 1, 9, 2, 13}, {3, 11, 8, 7}, {2, 6, 7, 15}, {9, 3, 7, 15}, {13, 5, 7, 15}, {5, 12, 8, 7}, {1, 4, 7, 15}, {7, 10, 11, 12, 14}}
34
35
local colourNum, exponents, colourChar = {}, {}, {}
36
for i = 0, 15 do exponents[2^i] = i end
37
do
38
	local hex = "0123456789abcdef"
39
	for i = 1, 16 do
40
		colourNum[hex:sub(i, i)] = i - 1
41
		colourNum[i - 1] = hex:sub(i, i)
42
		colourChar[hex:sub(i, i)] = 2 ^ (i - 1)
43
		colourChar[2 ^ (i - 1)] = hex:sub(i, i)
44
		
45
		local thisRel = relations[i - 1]
46
		for i = 1, #thisRel do thisRel[i] = 2 ^ thisRel[i] end
47
	end
48
end
49
50
local function getBestColourMatch(usage)
51
	local lastCol = relations[exponents[usage[#usage][1]]]
52
53
	for j = 1, #lastCol do
54
		local thisRelation = lastCol[j]
55
		for i = 1, #usage - 1 do if usage[i][1] == thisRelation then return i end end
56
	end
57
	
58
	return 1
59
end
60
61
local function colsToChar(pattern, totals)
62
	if not totals then
63
		local newPattern = {}
64
		totals = {}
65
		for i = 1, 6 do
66
			local thisVal = pattern[i]
67
			local thisTot = totals[thisVal]
68
			totals[thisVal], newPattern[i] = thisTot and (thisTot + 1) or 1, thisVal
69
		end
70
		pattern = newPattern
71
	end
72
	
73
	local usage = {}
74
	for key, value in pairs(totals) do usage[#usage + 1] = {key, value} end
75
	
76
	if #usage > 1 then
77
		-- Reduce the chunk to two colours:
78
		while #usage > 2 do
79
			table.sort(usage, function (a, b) return a[2] > b[2] end)
80
			local matchToInd, usageLen = getBestColourMatch(usage), #usage
81
			local matchFrom, matchTo = usage[usageLen][1], usage[matchToInd][1]
82
			for i = 1, 6 do if pattern[i] == matchFrom then
83
				pattern[i] = matchTo
84
				usage[matchToInd][2] = usage[matchToInd][2] + 1
85
			end end
86
			usage[usageLen] = nil
87
		end
88
89
		-- Convert to character. Adapted from oli414's function:
90
		-- http://www.computercraft.info/forums2/index.php?/topic/25340-cc-176-easy-drawing-characters/
91
		local data = 128
92
		for i = 1, #pattern - 1 do if pattern[i] ~= pattern[6] then data = data + 2^(i-1) end end
93
		return string.char(data), colourChar[usage[1][1] == pattern[6] and usage[2][1] or usage[1][1]], colourChar[pattern[6]]
94
	else
95
		-- Solid colour character:
96
		return "\128", colourChar[pattern[1]], colourChar[pattern[1]]
97
	end
98
end
99
100
local function snooze()
101
	local myEvent = tostring({})
102
	os.queueEvent(myEvent)
103
	os.pullEvent(myEvent)
104
end
105
106
function shrink(image, bgCol)
107
	local results, width, height, bgCol = {{}, {}, {}}, 0, #image + #image % 3, bgCol or colours.black
108
	for i = 1, #image do if #image[i] > width then width = #image[i] end end
109
	
110
	for y = 0, height - 1, 3 do
111
		local cRow, tRow, bRow, counter = {}, {}, {}, 1
112
		
113
		for x = 0, width - 1, 2 do
114
			-- Grab a 2x3 chunk:
115
			local pattern, totals = {}, {}
116
			
117
			for yy = 1, 3 do for xx = 1, 2 do
118
				pattern[#pattern + 1] = (image[y + yy] and image[y + yy][x + xx]) and (image[y + yy][x + xx] == 0 and bgCol or image[y + yy][x + xx]) or bgCol
119
				totals[pattern[#pattern]] = totals[pattern[#pattern]] and (totals[pattern[#pattern]] + 1) or 1
120
			end end
121
			
122
			cRow[counter], tRow[counter], bRow[counter] = colsToChar(pattern, totals)
123
			counter = counter + 1
124
		end
125
		
126
		results[1][#results[1] + 1], results[2][#results[2] + 1], results[3][#results[3] + 1] = table.concat(cRow), table.concat(tRow), table.concat(bRow)
127
	end
128
	
129
	results.width, results.height = #results[1][1], #results[1]
130
	
131
	return results
132
end
133
134
function shrinkGIF(image, bgCol)
135
	if not GIF and not os.loadAPI("GIF") then error("blittle.shrinkGIF: Load GIF API first.", 2) end
136
	
137
	image = GIF.flattenGIF(image)
138
	snooze()
139
	
140
	local prev = GIF.toPaintutils(image[1])
141
	snooze()
142
	
143
	prev = blittle.shrink(prev, bgCol)
144
	prev.delay = image[1].delay
145
	image[1] = prev
146
	snooze()
147
	
148
	image.width, image.height = prev.width, prev.height
149
	
150
	for i = 2, #image do
151
		local temp = GIF.toPaintutils(image[i])
152
		snooze()
153
		
154
		temp = blittle.shrink(temp, bgCol)
155
		snooze()
156
		
157
		local newImage = {{}, {}, {}, ["delay"] = image[i].delay, ["width"] = temp.width, ["height"] = 0}
158
		
159
		local a, b, c, pa, pb, pc = temp[1], temp[2], temp[3], prev[1], prev[2], prev[3]
160
		for i = 1, temp.height do
161
			local a1, b1, c1, pa1, pb1, pc1 = a[i], b[i], c[i], pa[i], pb[i], pc[i]
162
			
163
			if a1 ~= pa1 or b1 ~= pb1 or c1 ~= pc1 then
164
				local min, max = 1, #a1
165
				local a2, b2, c2, pa2, pb2, pc2 = {a1:byte(1, max)}, {b1:byte(1, max)}, {c1:byte(1, max)}, {pa1:byte(1, max)}, {pb1:byte(1, max)}, {pc1:byte(1, max)}
166
				
167
				for j = 1, max do if a2[j] ~= pa2[j] or b2[j] ~= pb2[j] or c2[j] ~= pc2[j] then
168
					min = j
169
					break
170
				end end
171
				
172
				for j = max, min, -1 do if a2[j] ~= pa2[j] or b2[j] ~= pb2[j] or c2[j] ~= pc2[j] then
173
					max = j
174
					break
175
				end end
176
				
177
				newImage[1][i], newImage[2][i], newImage[3][i], newImage.height = min > 1 and {min - 1, a1:sub(min, max)} or a1:sub(min, max), b1:sub(min, max), c1:sub(min, max), i
178
			end
179
			
180
			snooze()
181
		end
182
		
183
		image[i], prev = newImage, temp
184
		
185
		for j = 1, i - 1 do
186
			local oldImage = image[j]
187
			
188
			if type(oldImage[1]) == "table" and oldImage.height == newImage.height then
189
				local same = true
190
				
191
				for k = 1, oldImage.height do
192
					local comp1, comp2 = oldImage[1][k], newImage[1][k]
193
					
194
					if type(comp1) ~= type(comp2) or
195
						(type(comp1) == "string" and comp1 ~= comp2) or
196
						(type(comp1) == "table" and (comp1[1] ~= comp2[1] or comp1[2] ~= comp2[2])) or
197
						oldImage[2][k] ~= newImage[2][k] or
198
						oldImage[3][k] ~= newImage[3][k] then
199
							same = false
200
							break
201
					end
202
				end
203
				
204
				if same then
205
					newImage[1], newImage[2], newImage[3] = j
206
					break
207
				end
208
			end
209
			
210
			snooze()
211
		end
212
	end
213
	
214
	return image
215
end
216
217
local function newLine(width, bCol)
218
	local line = {}
219
	for i = 1, width do line[i] = {bCol, bCol, bCol, bCol, bCol, bCol} end
220
	return line
221
end
222
223
function createWindow(parent, x, y, width, height, visible)
224
	if parent == term or not parent then
225
		parent = term.current()
226
	elseif type(parent) ~= "table" or not parent.write then
227
		error("blittle.newWindow: \"parent\" does not appear to be a terminal object.", 2)
228
	end
229
	
230
	local workBuffer, backBuffer, frontBuffer, window, tCol, bCol, curX, curY, blink, cWidth, cHeight, pal = {}, {}, {}, {}, colours.white, colours.black, 1, 1, false
231
	if type(visible) ~= "boolean" then visible = true end
232
	x, y = x and math.floor(x) or 1, y and math.floor(y) or 1
233
	
234
	do
235
		local xSize, ySize = parent.getSize()
236
		cWidth, cHeight = (width or xSize), (height or ySize)
237
		width, height = cWidth * 2, cHeight * 3
238
	end
239
	
240
	if parent.setPaletteColour then
241
		pal = {}
242
		
243
		local counter = 1
244
		for i = 1, 16 do
245
			pal[counter] = {parent.getPaletteColour(counter)}
246
			counter = counter * 2
247
		end
248
		
249
		window.getPaletteColour = function(colour)
250
			return unpack(pal[colour])
251
		end
252
		
253
		window.setPaletteColour = function(colour, r, g, b)
254
			pal[colour] = {r, g, b}
255
			if visible then return parent.setPaletteColour(colour, r, g, b) end
256
		end
257
		
258
		window.getPaletteColor, window.setPaletteColor = window.getPaletteColour, window.setPaletteColour
259
	end
260
	
261
	window.blit = function(_, _, bC)
262
		local bClen = #bC
263
		if curX > width or curX + bClen < 2 or curY < 1 or curY > height then
264
			curX = curX + bClen
265
			return
266
		end
267
		
268
		if curX < 1 then
269
			bC = bC:sub(2 - curX)
270
			curX, bClen = 1, #bC
271
		end
272
		
273
		if curX + bClen - 1 > width then bC, bClen = bC:sub(1, width - curX + 1), width - curX + 1 end
274
275
		local colNum, rowNum, thisX, yBump = math.floor((curX - 1) / 2) + 1, math.floor((curY - 1) / 3) + 1, (curX - 1) % 2, ((curY - 1) % 3) * 2
276
		local firstColNum, lastColNum, thisRow = colNum, math.floor((curX + bClen) / 2), backBuffer[rowNum]
277
		local thisChar = thisRow[colNum]
278
		
279
		for i = 1, bClen do
280
			thisChar[thisX + yBump + 1] = colourChar[bC:sub(i, i)]
281
			
282
			if thisX == 1 then
283
				thisX, colNum = 0, colNum + 1
284
				thisChar = thisRow[colNum]
285
				if not thisChar then break end
286
			else thisX = 1 end
287
		end
288
		
289
		if visible then
290
			local chars1, chars2, chars3, count = {}, {}, {}, 1
291
			
292
			for i = firstColNum, lastColNum do
293
				chars1[count], chars2[count], chars3[count] = colsToChar(thisRow[i])
294
				count = count + 1
295
			end
296
			
297
			chars1, chars2, chars3 = table.concat(chars1), table.concat(chars2), table.concat(chars3)
298
			parent.setCursorPos(x + math.floor((curX - 1) / 2), y + math.floor((curY - 1) / 3))
299
			parent.blit(chars1, chars2, chars3)
300
			local thisRow = frontBuffer[rowNum]
301
			frontBuffer[rowNum] = {thisRow[1]:sub(1, firstColNum - 1) .. chars1 .. thisRow[1]:sub(lastColNum + 1), thisRow[2]:sub(1, firstColNum - 1) .. chars2 .. thisRow[2]:sub(lastColNum + 1), thisRow[3]:sub(1, firstColNum - 1) .. chars3 .. thisRow[3]:sub(lastColNum + 1)}
302
		else
303
			local thisRow = workBuffer[rowNum]
304
			
305
			if (not thisRow[firstColNum]) or thisRow[firstColNum] < lastColNum then
306
				local x, newLastColNum = 1, lastColNum
307
				
308
				while x <= lastColNum + 1 do
309
					local thisSpot = thisRow[x]
310
					
311
					if thisSpot then
312
						if thisSpot >= firstColNum - 1 then
313
							if x < firstColNum then firstColNum = x else thisRow[x] = nil end
314
							if thisSpot > newLastColNum then newLastColNum = thisSpot end
315
						end
316
						x = thisSpot + 1
317
					else x = x + 1 end
318
				end
319
				
320
				thisRow[firstColNum] = newLastColNum
321
				if thisRow.max <= newLastColNum then thisRow.max = firstColNum end
322
			end
323
		end
324
		
325
		curX = curX + bClen
326
	end
327
	
328
	window.write = function(text)
329
		window.blit(nil, nil, string.rep(colourChar[bCol], #tostring(text)))
330
	end
331
	
332
	window.clearLine = function()
333
		local oldX = curX
334
		curX = 1
335
		window.blit(nil, nil, string.rep(colourChar[bCol], width))
336
		curX = oldX
337
	end
338
	
339
	window.clear = function()
340
		local t, fC, bC = string.rep("\128", cWidth), string.rep(colourChar[tCol], cWidth), string.rep(colourChar[bCol], cWidth)
341
		for y = 1, cHeight do workBuffer[y], backBuffer[y], frontBuffer[y] = {["max"] = 0}, newLine(cWidth, bCol), {t, fC, bC} end
342
		window.redraw()
343
	end
344
	
345
	window.getCursorPos = function()
346
		return curX, curY
347
	end
348
	
349
	window.setCursorPos = function(newX, newY)
350
		curX, curY = math.floor(newX), math.floor(newY)
351
		if visible and blink then window.restoreCursor() end
352
	end
353
	
354
	window.restoreCursor = function() end
355
	window.setCursorBlink = window.restoreCursor
356
	
357
	window.isColour = function()
358
		return parent.isColour()
359
	end
360
	window.isColor = window.isColour
361
	
362
	window.getSize = function()
363
		return width, height
364
	end
365
	
366
	window.scroll = function(lines)
367
		lines = math.floor(lines)
368
		
369
		if lines ~= 0 then
370
			if lines % 3 == 0 then
371
				local newWB, newBB, newFB, line1, line2, line3 = {}, {}, {}, string.rep("\128", cWidth), string.rep(colourChar[tCol], cWidth), string.rep(colourChar[bCol], cWidth)
372
				for y = 1, cHeight do newWB[y], newBB[y], newFB[y] = workBuffer[y + lines] or {["max"] = 0}, backBuffer[y + lines] or newLine(cWidth, bCol), frontBuffer[y + lines] or {line1, line2, line3} end
373
				workBuffer, backBuffer, frontBuffer = newWB, newBB, newFB
374
			else
375
				local newBB, tRowNum, tBump, sRowNum, sBump = {}, 1, 0, math.floor(lines / 3) + 1, (lines % 3) * 2
376
				local sRow, tRow = backBuffer[sRowNum], {}
377
				for x = 1, cWidth do tRow[x] = {} end
378
				
379
				for y = 1, height do
380
					if sRow then
381
						for x = 1, cWidth do
382
							local tChar, sChar = tRow[x], sRow[x]
383
							tChar[tBump + 1], tChar[tBump + 2] = sChar[sBump + 1], sChar[sBump + 2]
384
						end
385
					else
386
						for x = 1, cWidth do
387
							local tChar = tRow[x]
388
							tChar[tBump + 1], tChar[tBump + 2] = bCol, bCol
389
						end
390
					end
391
					
392
					tBump, sBump = tBump + 2, sBump + 2
393
					
394
					if tBump > 4 then
395
						tBump, newBB[tRowNum] = 0, tRow
396
						tRowNum, tRow = tRowNum + 1, {}
397
						for x = 1, cWidth do tRow[x] = {} end
398
					end
399
					
400
					if sBump > 4 then
401
						sRowNum, sBump = sRowNum + 1, 0
402
						sRow = backBuffer[sRowNum]
403
					end
404
				end
405
				
406
				for y = 1, cHeight do workBuffer[y] = {["max"] = 1, cWidth} end
407
				
408
				backBuffer = newBB
409
			end
410
			
411
			window.redraw()
412
		end
413
	end
414
	
415
	window.setTextColour = function(newCol)
416
		tCol = newCol
417
	end
418
	window.setTextColor = window.setTextColour
419
	
420
	window.setBackgroundColour = function(newCol)
421
		bCol = newCol
422
	end
423
	window.setBackgroundColor = window.setBackgroundColour
424
	
425
	window.getTextColour = function()
426
		return tCol
427
	end
428
	window.getTextColor = window.getTextColour
429
	
430
	window.getBackgroundColour = function()
431
		return bCol
432
	end
433
	window.getBackgroundColor = window.getBackgroundColour
434
	
435
	window.redraw = function()
436
		if visible then
437
			for i = 1, cHeight do
438
				local work, front = workBuffer[i], frontBuffer[i]
439
				local front1, front2, front3 = front[1], front[2], front[3]
440
441
				if work.max > 0 then
442
					local line1, line2, line3, lineLen, skip, back, count = {}, {}, {}, 1, 0, backBuffer[i], 1
443
444
					while count <= work.max do if work[count] then
445
						if skip > 0 then
446
							line1[lineLen], line2[lineLen], line3[lineLen] = front1:sub(count - skip, count - 1), front2:sub(count - skip, count - 1), front3:sub(count - skip, count - 1)
447
							skip, lineLen = 0, lineLen + 1
448
						end
449
450
						for i = count, work[count] do
451
							line1[lineLen], line2[lineLen], line3[lineLen] = colsToChar(back[i])
452
							lineLen = lineLen + 1
453
						end
454
455
						count = work[count] + 1
456
					else skip, count = skip + 1, count + 1 end end
457
458
					if count < cWidth + 1 then line1[lineLen], line2[lineLen], line3[lineLen] = front1:sub(count), front2:sub(count), front3:sub(count) end
459
460
					front1, front2, front3 = table.concat(line1), table.concat(line2), table.concat(line3)
461
					frontBuffer[i], workBuffer[i] = {front1, front2, front3}, {["max"] = 0}
462
				end
463
464
				parent.setCursorPos(x, y + i - 1)
465
				parent.blit(front1, front2, front3)
466
			end
467
			
468
			if pal then
469
				local counter = 1
470
				for i = 1, 16 do
471
					parent.setPaletteColour(counter, unpack(pal[counter]))
472
					counter = counter * 2
473
				end
474
			end
475
		end
476
	end
477
	
478
	window.setVisible = function(newVis)
479
		newVis = newVis and true or false
480
		
481
		if newVis and not visible then
482
			visible = true
483
			window.redraw()
484
		else visible = newVis end
485
	end
486
	
487
	window.getPosition = function()
488
		return x, y
489
	end
490
	
491
	window.reposition = function(newX, newY, newWidth, newHeight)
492
		x, y = type(newX) == "number" and math.floor(newX) or x, type(newY) == "number" and math.floor(newY) or y
493
		
494
		if type(newWidth) == "number" then
495
			newWidth = math.floor(newWidth)
496
			if newWidth > cWidth then
497
				local line1, line2, line3 = string.rep("\128", newWidth - cWidth), string.rep(colourChar[tCol], newWidth - cWidth), string.rep(colourChar[bCol], newWidth - cWidth)
498
				for y = 1, cHeight do
499
					local bRow, fRow = backBuffer[y], frontBuffer[y]
500
					for x = cWidth + 1, newWidth do bRow[x] = {bCol, bCol, bCol, bCol, bCol, bCol} end
501
					frontBuffer[y] = {fRow[1] .. line3, fRow[2] .. line2, fRow[3] .. line3}
502
				end
503
			elseif newWidth < cWidth then
504
				for y = 1, cHeight do
505
					local wRow, bRow, fRow = workBuffer[y], backBuffer[y], frontBuffer[y]
506
					for x = newWidth + 1, cWidth do bRow[x] = nil end
507
					frontBuffer[y] = {fRow[1]:sub(1, newWidth), fRow[2]:sub(1, newWidth), fRow[3]:sub(1, newWidth)}
508
					
509
					while wRow[wRow.max] and wRow[wRow.max] > newWidth do
510
						wRow[wRow.max] = nil
511
						wRow.max = table.maxn(wRow)
512
					end
513
				end
514
			end
515
			width, cWidth = newWidth * 2, newWidth
516
		end
517
		
518
		if type(newHeight) == "number" then
519
			newHeight = math.floor(newHeight)
520
			if newHeight > cHeight then
521
				local line1, line2, line3 = string.rep("\128", cWidth), string.rep(colourChar[tCol], cWidth), string.rep(colourChar[bCol], cWidth)
522
				for y = cHeight + 1, newHeight do workBuffer[y], backBuffer[y], frontBuffer[y] = {["max"] = 0}, newLine(cWidth, bCol), {line1, line2, line3} end
523
			elseif newHeight < cHeight then
524
				for y = newHeight + 1, cHeight do workBuffer[y], backBuffer[y], frontBuffer[y] = nil, nil, nil end
525
			end
526
			height, cHeight = newHeight * 3, newHeight
527
		end
528
		
529
		window.redraw()
530
	end
531
	
532
	window.clear()
533
	return window
534
end
535
536
function draw(image, x, y, terminal)
537
	local t, tC, bC = image[1], image[2], image[3]
538
	x, y, terminal = x or 1, y or 1, terminal or term.current()
539
	
540
	for i = 1, image.height do
541
		local tI = t[i]
542
		if type(tI) == "string" then
543
			terminal.setCursorPos(x, y + i - 1)
544
			terminal.blit(tI, tC[i], bC[i])
545
		elseif type(tI) == "table" then
546
			terminal.setCursorPos(x + tI[1], y + i - 1)
547
			terminal.blit(tI[2], tC[i], bC[i])
548
		end
549
	end
550
end
551
552
function save(image, filename)
553
	local output = fs.open(filename, "wb")
554
	if not output then error("Can't open "..filename.." for output.") end
555
	
556
	local writeByte = output.write
557
558
	local function writeInt(num)
559
		writeByte(bit.band(num, 255))
560
		writeByte(bit.brshift(num, 8))
561
	end
562
563
	writeByte(66)  -- B
564
	writeByte(76)  -- L
565
	writeByte(84)  -- T
566
	
567
	local animated = image[1].delay ~= nil
568
	writeByte(animated and 1 or 0)
569
	
570
	if animated then
571
		writeInt(#image)
572
	else
573
		local tempImage = {image[1], image[2], image[3]}
574
		image[1], image[2], image[3] = tempImage, nil, nil
575
	end
576
	
577
	local width, height = image.width, image.height
578
	
579
	writeInt(width)
580
	writeInt(height)
581
	
582
	for k = 1, #image do
583
		local thisImage = image[k]
584
		
585
		if type(thisImage[1]) == "number" then
586
			writeByte(3)
587
			writeInt(thisImage[1])
588
		else
589
			for i = 1, height do
590
				if thisImage[1][i] then
591
					local rowType, len, thisRow = type(thisImage[1][i])
592
593
					if rowType == "string" then
594
						writeByte(1)
595
						len = #thisImage[1][i]
596
						writeInt(len)
597
						thisRow = {thisImage[1][i]:byte(1, len)}
598
					elseif rowType == "table" then
599
						writeByte(2)
600
						len = #thisImage[1][i][2]
601
						writeInt(len)
602
						writeInt(thisImage[1][i][1])
603
						thisRow = {thisImage[1][i][2]:byte(1, len)}
604
					else
605
						error("Malformed row record #"..i.." in frame #"..k.." when attempting to save \""..filename.."\", type is "..rowType..".")
606
					end
607
608
					for x = 1, len do writeByte(thisRow[x]) end
609
610
					local txt, bg = thisImage[2][i], thisImage[3][i]
611
					for x = 1, len do writeByte(colourNum[txt:sub(x, x)] + colourNum[bg:sub(x, x)] * 16) end
612
				else writeByte(0) end
613
			end
614
		end
615
		
616
		if animated then writeInt(thisImage.delay * 20) end
617
		
618
		snooze()
619
	end
620
	
621
	if image.pal then
622
		writeByte(#image.pal)
623
		for i = 0, #image.pal do for j = 1, 3 do writeByte(image.pal[i][j]) end end
624
	end
625
	
626
	if not animated then
627
		image[2], image[3] = image[1][2], image[1][3]
628
		image[1] = image[1][1]
629
	end
630
	
631
	output.close()
632
end
633
634
function load(filename)
635
	local input = fs.open(filename, "rb")
636
	if not input then error("Can't open "..filename.." for input.") end
637
	
638
	local read = input.read
639
	
640
	local function readInt()
641
		local result = read()
642
		return result + bit.blshift(read(), 8)
643
	end
644
	
645
	if string.char(read(), read(), read()) ~= "BLT" then
646
		-- Assume legacy format.
647
		input.close()
648
		input = fs.open(filename, "rb")
649
		
650
		read = input.read
651
		
652
		function readInt()
653
			local result = input.read()
654
			return result + bit.blshift(input.read(), 8)
655
		end
656
657
		local image = {}
658
		image.width, image.height = readInt(), readInt()
659
660
		for i = 1, 3 do
661
			local thisSet = {}
662
			for y = 1, image.height do
663
				local thisRow = {}
664
				for x = 1, image.width do thisRow[x] = string.char(input.read()) end
665
				thisSet[y] = table.concat(thisRow)
666
			end
667
			image[i] = thisSet
668
		end
669
670
		input.close()
671
672
		return image
673
	end
674
	
675
	local image, animated, frames = {}, read() == 1
676
	if animated then frames = readInt() else frames = 1 end
677
	
678
	local width, height = readInt(), readInt()
679
	image.width, image.height = width, height
680
	
681
	for k = 1, frames do
682
		local thisImage = {["width"] = width, ["height"] = 0}
683
		local chr, txt, bg = {}, {}, {}
684
		
685
		for i = 1, height do
686
			local lineType = read()
687
			
688
			if lineType == 3 then
689
				chr, txt, bg = readInt()
690
				break
691
			elseif lineType > 0 then
692
				local l1, l2, len, bump = {}, {}, readInt()
693
				if lineType == 2 then bump = readInt() end
694
								
695
				for x = 1, len do l1[x] = read() end
696
				chr[i] = string.char(unpack(l1))
697
				if lineType == 2 then chr[i] = {bump, chr[i]} end
698
699
				for x = 1, len do
700
					local thisVal = read()
701
					l1[x], l2[x] = colourNum[bit.band(thisVal, 15)], colourNum[bit.brshift(thisVal, 4)]
702
				end
703
704
				txt[i], bg[i], thisImage.height = table.concat(l1), table.concat(l2), i
705
			end
706
		end
707
		
708
		if animated then thisImage["delay"] = readInt() / 20 end
709
		thisImage[1], thisImage[2], thisImage[3] = chr, txt, bg
710
		image[k] = thisImage
711
		
712
		snooze()
713
	end
714
	
715
	local palLength = read()
716
	if palLength and palLength > 0 then
717
		image.pal = {}
718
		for i = 0, palLength do image.pal[i] = {read(), read(), read()} end
719
	end
720
	
721
	if not animated then
722
		image[2], image[3] = image[1][2], image[1][3]
723
		image[1] = image[1][1]
724
	end
725
	
726
	input.close()
727
	
728
	return image
729
end
730
731
if term.setPaletteColour then
732
	function applyPalette(image, terminal)
733
		terminal = terminal or term
734
735
		local col, pal = 1, image.pal
736
737
		for i = 0, #pal do
738
			local thisCol = pal[i]
739
			terminal.setPaletteColour(col, thisCol[1] / 255, thisCol[2] / 255, thisCol[3] / 255)
740
			col = col * 2
741
		end
742
	end
743
end