Skip to content

Instantly share code, notes, and snippets.

@dermotbalson
Created May 20, 2013 01:37
Show Gist options
  • Save dermotbalson/5609902 to your computer and use it in GitHub Desktop.
Save dermotbalson/5609902 to your computer and use it in GitHub Desktop.
--# Notes
--# ReadMe
--[[
First, don't be put off by all this text. This utility is extremely simple to use.
It creates code that reproduces images so you don't need to include them separately with your projects. It uses RLE, a well known compression technique. Essentially, it runs through all the pixels, and every time the color changes, it stores the last color and the number of pixels it applied to.
FUNCTIONALITY
It can turn any image(with reason) into code strings, and reassemble the image from them.
It can take images from the libraries, or Spritely code, and convert to compressed code.
The practical limits on image size and complexity are processing speed and storage, because the greater the number of colors and pixels, the more space we have to allow for each item.
FUNCTIONS
The Coder:Encode function takes the image as input and prints the code to recreate it. You copy this code to your project. The Test tab shows how it looks. Note, you will also need to copy the Decoder tab to your project.
Usage: Coder:Encode(img) --then copy and paste the printed code
NB As well as printing the code, the Coder function also returns the two strings required to decode the image. This is only for testing purposes, so the Main tab can show how it works - in practice, you will just copy the printed code and not worry about the return values.
The Decoder:Decode function takes two strings as input and returns an image. Normally, you don't have to worry about this because the printed code calls the function correctly for you, returning the image.
The Main function carries out several tests
It reads in an image from the standard library, codes and then decodes it back to an image
It takes an image previously created by this code, and encodes/decodes it back to an image
USING IT
Only the ImageCoder tab is needed to encode images
Only the Decoder tab needs to be included in any project where you want to decode RLE images.
COMPRESSION
The level of compression depends on many factors, but the code generated by this project seems to e not much greater than the original image size.
Dermot (user:Ignatz)
--]]
--# Main
--# Main
-- Compress
function setup()
--Read image from library
--You can pick any image you like, but don't make it too big or complex
img1 = readImage("Space Art:Part Red Hull 1")
saveImage("Dropbox:Cute",img1)
--Normally, you only need to run the single line of code below, which prints the code you need
--The function does return two strings, but you can ignore them (they are just for testing),
--just copy what is printed out in the Output screen, and and paste it into your own project
--So normally, you'll just run Coder:Encode(img1)
--The Test tab shows how it looks when pasted in, and that is the third example below
local strCol,str=Coder:Encode(img1)
--The rest of the code below is just to prove it works
--check we can reproduce the original image we loaded above, using the compressed code
img_new1=Decoder:Decode(img1.width,img1.height,strCol,str)
--print(string.len(strCol..str)) --uncomment this to see the size of the printed code
img2=Test:Decode()
end
-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(208, 208, 214, 255)
-- This sets the line thickness
strokeWidth(5)
-- Do your drawing here
text( "This utility converts images to code you can embed in your project", 290, 550 )
text( "Library original", 100, 500 )
sprite(img1,100,400)
text( "Copy from code", 100, 250 )
sprite(img_new1,100,150)
if img~=nil then sprite(img2,300,150) end
end
--# Coder
--# Coder
Coder = class()
--This function encodes an image in two strings
--the first string makes a list of unique color settings (r,g,b,a), encoded as an 8 char hex string
--the second string runs through the image, through rows then columns, and every time the color changes,
--it records the color setting and number of cells that had that color
--NB it doesn't store the color setting as an 8 char hex string, imstead it stores the position of
--this color in the first string. This saves a lot of storage where colors are used over and over again
Codes="!#&~*+,)/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ]_`abcdefghijklmnopqrstuvwxyz"
-- 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
-- 1 2 3 4 5 6 7 8
function Coder:Encode(img)
local rows=img.height
local cols=img.width
local colors={} --hash table of unique colors, gives us their sequence in the unique list
local strCol="" --unique list of color settings, each one is 8 chars hex, for r,g,b,a
local str="" -- each item consists of a color setting (or rather, the position in strCol), and the number of
--cells to be colored
--for example, if the image starts with 56 cells with r=1,g=2,b=5,a=6, we will add the hex value of this,
--which is 001002005006, to strCol, and set colors[001002005006]=1, (1 because this is the first color)
--to str, we add the hex values of the string position, ie 1, and the number of cells, ie 56, so we will
--add 001038 to str
local colorCount=0
local prevColor=-1 --to tell us when the color changes
local count=0 --number of cells with current color
local r,g,b,a,x
local tbl={}
local tblCol={}
--loop through image
for i=1,cols do
for j=1,rows do
r,g,b,a=img:get(i,j)
x=r..","..g..","..b..","..a
if x==prevColor then
count=count+1
else --color has changed, store details of last color
if count>0 then --this will only be zero for the very first cell
y=colors[prevColor] --this looks up the position of the color, in the color list
if y==nil then --add the color to the list if not there
colorCount = colorCount + 1
y=colorCount
colors[prevColor]=colorCount
--if colorCount>1 then
--table.insert(tblCol,","..prevColor)
--else
table.insert(tblCol,prevColor)
--end
end
table.insert(tbl,y..","..count)
end
prevColor=x
count=1
end
if errMessage~=nil then break end
end
if errMessage~=nil then break end
end
--we've finished, but we may have some left over chars that need to be stored
if count>0 then
y=colors[prevColor]
if y==nil then
colorCount = colorCount + 1
colors[prevColor]=colorCount
y=colorCount
end
table.insert(tblCol,y)
table.insert(tbl,count)
end
str=table.concat(tbl,",")
strCol=table.concat(tblCol,",")
--create code for user
if errMessage~=nil then
print(errMessage)
return errMessage,errMessage
else
str1=Coder:Compress(strCol)
str2=Coder:Compress(str)
Coder:PrintCode(cols,rows,str1,str2)
return str1,str2
end
end
function Coder:Compress(d)
local a={}
local m=0
local y,z
local x=string.gsub(d,"8","82")
x=string.gsub(x,"7","81")
x=string.gsub(x,"9","83")
x=string.gsub(x,",","7")
for i=1,#x,2 do
y=string.sub(x,i,i)*9
if i<#x then y=y+string.sub(x,i+1,i+1) else y=y+7 end
y=y+1
z=string.sub(Codes,y,y)
table.insert(a,z)
end
return table.concat(a)
end
function Coder:PrintCode(c,r,s1,s2)
print("img=Decoder:Decode("..c..","..r..",'"..s1.."','"..s2.."')")
end
--# Decoder
Decoder=class()
Codes="!#&~*+,)/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ]_`abcdefghijklmnopqrstuvwxyz"
-- 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
-- 1 2 3 4 5 6 7 8
function Decoder:Decode(cols,rows,cc,dd)
local img=image(cols,rows)
local n=0
local m=4
local arrCols={}
strCol=Decoder:Decompress(cc)
strDat=Decoder:Decompress(dd)
--unpack color descriptions
for q in string.gmatch(strCol,"[^,]+") do
m = m + 1
if m==5 then
m=1
n = n + 1
arrCols[n]={}
end
arrCols[n][m]=q
end
--if m~=4 then print("faulty column codes, m=",m) end
--unpack RLE content
local col=1
local row=0
local ind=1
n=0
for q in string.gmatch(strDat, "[^,]+") do
if ind==1 then
colIndex=tonumber(q)
else
colCount=tonumber(q)
n = n + 1
for u=1,colCount do
row = row + 1
if row>rows then col=col+1 row=1 end
img:set(col,row,
color(arrCols[colIndex][1],arrCols[colIndex][2],arrCols[colIndex][3],arrCols[colIndex][4]))
end
end
ind=3-ind
end
return img
end
function Decoder:Decompress(a)
local s={}
local d1,d2,y,x
for i=1,#a do
y=string.find(Codes,string.sub(a,i,i))-1
d2=y%9
d1=(y-d2)/9
d2=d2
table.insert(s,d1)
table.insert(s,d2)
end
if s[#s]==7 then table.remove(s,#s) end
str=table.concat(s)
x=string.gsub(str,"7",",")
x=string.gsub(x,"81","7")
x=string.gsub(x,"83","9")
x=string.gsub(x,"82","8")
return x
end
--# Test
--# Test
Test=class()
function Test:Decode()
--code produced by
img=Decoder:Decode(48,76,'))))ck7:j1lR1j7`j#lIEjsg4smumukYqAIAIAI5gulljg5un@VkHjDmROk=jJ@Qmg=)=q@tmI_I8I8I0I4]S@S@>IKjI3q0js@_7_7>]2@Km)8Bl@0j)Zl70j)Yj>m7Lk/@2um@MjugtukukujYjVn)Tk?jJ7Pm]<]2mmk@3]Pm]<@67WnI=)37NmI;I3ROmR<I6nnkuj;m)Kju7@))R4RS@S@>)27Km)8BoI:k71)4IS7S7:R4@S7S7=g1@J7J78GkiinjPmtmtkVjs)Zng>I81ngZkXj<m)Kk#jOmtmtkUlsj@2o]6)WnI<tjA@Mm@8Gq?ktktjTj5ltltk#jOmsmsk5j_IVn@<sjDmINk?jDmROkAI4mmk]ttkukujXj:luluju7`k)9j+nI8787uIsR=kR2uj:m)KjuI47Qmg:74@S7S7=tj/IHlg8BjA7Mm@:)sR`q>kYq=q1qJI>]t@hIug=uj=m7Lk~oRXq5juuj?m@MjuRutl@Djs7sI_ItR>@stn7h@8Fj87JIJI9R4@ssq878DkJ@<tkJ@>]tIs)utkXjVnIWk=o)SIhI8GjKn@Yk>k/@8J78J7<7sgcqA@<)5]1)1)8Fq1o)tR:ujejVjVk>k#jtg8?k>otngsukGqBmto)8Djug8Hjug>]`n)s)8CjbnRYkQq3ngssk/7tIs)uskVoRVq4k6j/7SI_79)stogu@<tq3nsq8I:)47bo@8JI5tnRZkNj`j9j9jusk4k*k*kGoungt)=)tsmuo@8CjTnRXk8Is)_It@:R4ItItI8Cq8@eqBkFo)SIs)8J73RTn];)2sm@MjuR5@Tn);sq<q0qJ@>I2@Km)9)Gj71o@6gYn]=g6IYn]=sjcn]ZkS76RYng=tjPmtmtk8747Qmg=R0)ElI84n78787tujcnRXkMjBmINk9j>m7Lk87]770jKmgQkMjCmINjutq87>k]3R9ool]3)NmI;72@Km)8J@47S7S7=gA@uqISINjR4q5j;m)KjuujS@S@S@;)0IFlR88I)))7sI=kR2tj<m)Kju7XjtjtqFj*lRFjt73gPm]9]6RXnR=R2umINk872sm@Mk:j)II8@2RLm78DjBmINk?j?m@Mk9jWn)TkA73@NmI9)4gS@S@>Rt)?kg3ujA@Mm@:R4@S7S7=slR1j7_I47S7S7=]4IS7S7=uqJIElI82j5ltltk!jJ7Pm]<ujiikksqIumsmR4jRssj','7sskjljjq3mjnlojjktq74jkunkq@7ujnkj)71jjkI2jnjg3jjR77?nkj]I6jnljsjjk)8@7]78I75jR9jnlk777=nkj]];jkI7]778I=jnjk]75jskg7A77]@A@7AI77:nkj]gBjnjjjtl77]7Djj]:lI7]IFjjjunkj]gBjnjjjtnkj]=l]7Hjnklsjltjjjg]@5ol)7]7787JI7]7Kjj]?m77MjnkmI7OjjjR]@5ol)7]776ktjnjm]75l)Qjnlmsjjj@]@5ol)7]775mtjnjmujj]En)7Ujnkn@7WjjqI]@5ol)7]774nR7]7Qjj]J7Yjnlng77snkj]gBjnjnsjI_@7]7_I75m)`jo77]@bjl777R]@5ol)7]5oI75k7dko]7fjj]8@Kjosjnkl@77I]@5mRdkotjo]75k7hI7]7s)77@]@5mRdkq1jo]7s@75k7]7sI77@]@5m@sR7s]7dlq6jo]75k7]7sI77@]@5m)ssjq5loRIstjo]7sujj]9njq3jjknkj]J@Ljq9jq5moRRejq:jj]9njq3jjknkj]J7t@7s]gdmq<jo]75k)]7sI77@tR@t]Hq5q@dmq?jo]7tsjj]8I]7sI77@ttkq5mRdno]@5junjq3jjkqA@@s]OoR]tujo]7u)75jtnjq3jjkqA@@s]OoRgejqCjj]8@]7sI77@ttkq5mRdoo]7u@75jtnjq3jjkqA@@s]OoR]uI7ejqFjj]8@]7sI77@ttkq5mRdnqGjo]75junjq3jjkqA@@s]OoR]ejqHjj]8I]7sI77@]@5lgusjq5q7dmqAI7ejqJ@75junjq3jjknkj]J@uujq5noRRekj]9njq3jjknkj]JIKjj!jq5loRI077ejj&jj]9njq3jjknkj]Lj~jq5koRI0R7ejj]:njq3jjknkj]Nj+joRIejj,jj]:njq3jjknkj]OoR@0sjo]70tjj]:njq3jjknkj]g0ujjuj]1)75k7dkj1jo]75jum771@7]@1I77@]@5ol)7]6j4jj]9j5kj6jj8775jsj8@71ujnkj9jjmnkj]gBjnjjjR]@5lul)7]I2777g]@5ol)7]775nkj]Gj;jj<jnkj=jq777tnkj]gBjnjjj]2]7]72g75l@2sjnljA@771nkj]gBjnjjjg2ujnjjBjj]AI377]@3@73I773nkj]gBjnjjjsjFjnjjGjj]>jHjj?jnkjJ773tjjj]]@5ol)7]778@3ujnjjKjj];l)7]I47778@]@5ol)7]778I4@7]74I75jtjOjjPjnkjQjjS7779nkj]R4tjnkjSI779jTjnjjUjj]5jVjjWjnkjXjjkI]@5jjOjjYjnkjZjj_777;j_@7]87ajjtjjk]5ujnmj`jjJ@77>jajjbjR4@76I77A76R7]73jjejjS@')
--end of code
return img
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment