Skip to content

Instantly share code, notes, and snippets.

@dermotbalson
Created September 8, 2013 02:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dermotbalson/6481363 to your computer and use it in GitHub Desktop.
Save dermotbalson/6481363 to your computer and use it in GitHub Desktop.
Compress4
--# 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, 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 saves the code in the ImageCode tab to recreate it. You copy this code to your project.
Important - the Imagecode tab will look very strange because the Codea editor can't display very long lines of code. Just copy the whole tab anyway.
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 between not much greater than the original image size for simpler (few colour) images, up to about double, for complex images.
Dermot (user:Ignatz)
--]]
--# 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("Small World:Icon")
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("Alongside is the original image and the result after encoding and decoding it")
print("The total size of the code is about ",#strCol+#str.." chars")
if img then print("On the right is the image whose code is stored in the Imagecode tab. It may not be the same image as on the left, if you've just changed that image") end
end
function draw()
background(208, 208, 214, 255)
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 we've previously saved an image to code, decode and show it
if img then
text("Image from Tab",300,250)
sprite(img,300,150)
end
end
--# 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)
saveProjectTab("ImageCode","--image code\nimg=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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment