Programming and Scripting :: XPM Icon Viewer



Quote
I suppose we could ask Murga if it is a murgaLua issue or if it's an FLTK bug. It might be as simple as rebuilding FLTK using a different xpm version.
At first glance, it seems to be coming from Fltk (just looking at the func. call), but I haven't looked at the actual source.  But your suggestion might be valid.

Quote
I had no idea how I would address the issue through script, and it looks like your solution is definitely useable in some situations. What happens, though, if the icon is not writeable by the user? I think I would prefer to check the file as you suggested, but then just not load it if it's going to cause a crash.
The script addons I posted does the following:
- check if the xpm has leading whitespace on each line
- if the above is true: make a valid copy of the xpm and display that, but editing will still point to the original file.  It's only used for the display.

If you just wanted it to ignore ones that would crash, you could just wrap the code in the for loop around a if-conditional on checkxpm.

Quote
lso an issue with checking every file is that it will probably slow things down considerably on a large number of files.
I didn't notice with the few files I had :P Mind running a test for the difference in time?

Quote
make a valid copy of the xpm and display that, but editing will still point to the original file
Oh. I didn't study your code closely enough. So editing is still done on the original. That's much better.

Here's what I did as a test last night. I used your idea of checking each file for leading spaces, condensing it as much as I could, and simply didn't display the image if space is found:
Code Sample
function find_stupid_space(file)
 local data=io.open(file)
 if data then
   for line in data:lines() do
     if string.find(line,"^%s") then return 1 end
   end
 data:close()
 end
end
[...]
--Just before building the buttons:
if not find_stupid_space(dir..images[i]) and icon[i]:w()==xpm_size and icon[i]:h()==xpm_size then

I still don't really like the idea of parsing every line of every file, though. In a directory with 714 xpm files, 280 of them 32x32, and 2 of those corrupted, the app loads in about 1 second without the check. With the check, load time is about 3-4 seconds, regardless of whether or not the loop is broken as soon as a space is found.

I think I may have found a partial solution.

The count() method apparently doesn't count these spaced lines. Since count *should* be 32 + the number of colors + 1 for a 32x32 xpm, a file with a count less than this number would be corrupted.
The problem is finding the number of colors. It shouldn't be too difficult, but I would like to make the parsing as terse as possible and haven't even attempted it yet. At this time I'm checking only xpm_size+1, which would fail if the number of colors was greater than 32 but the spaces didn't occur until later into the image data (was that clear at all?)

So I removed the text parsing entirely, and changed the final check to this:
Code Sample
if icon[i]:count()>=xpm_size+1 and icon[i]:w()==xpm_size and icon[i]:h()==xpm_size then

Later I will look into grabbing the number of colors

EDIT: It's a lot harder than I thought. So far I haven't succeeded in doing anything better than simply searching for leading spaces, since finding the number of colors still requires loading at least part of every file. This loading is what is causing the slowdown.  That most recent idea is not reliable unless I know how many lines of data (not including any comments) are in the file. A test showed that adding just one space near the end of the file will cause it to pass the test and segfault.

The best I've found so far is similar to what I had last night, except the check is done after 32x32 images have been separated instead of on all xpm files (only slightly faster). I have a feeling that unless there is a way to run a check on the loaded image data instead of on the file, I'll need to accept either the delay or the occasional segfaults.

I did add a few debug lines that make it easier to find corrupted files, though:
Code Sample
if arg[1]=="debug" then
 print(images[i])
 butt[xpm_count]:image(icon[i]:copy())
else
 butt[xpm_count]:image(icon[i])
end

The copy method immediately crashes with a corrupt file, and the last filename printed is the bad one.

Here's a compromise that I like best so far. I can't see there being a lot of people with more than a few hundred 32x32 xpm files in a single directory, so I've decided the delay of a couple of seconds isn't a huge problem. The compromise is that the script no longer just ignores the "bad" files as I had originally planned, but also does not pretend there's nothing wrong with the files as ^thehatsrule^ had coded. The script now contains a "broken image" icon that appears in place of the original icon, to let the user know something's not right with it, and the file itself can still be opened with the "edit" button.

Code Sample
#!/bin/murgaLua

-- XPM icon viewer
-- mikshaw 2007

dir="/usr/share/dfm/icons"
editor="xpaint"
xpm_size=32

broken_xpm_data=[[
/* XPM */
static char *broken_image[] = {
/* columns rows colors chars-per-pixel */
"14 16 16 1",
"  c black",
". c #800000",
"X c #008000",
"o c #808000",
"O c navy",
"+ c #800080",
"@ c #008080",
"# c #C0C0C0",
"$ c #808080",
"% c red",
"& c green",
"* c None",
"= c blue",
"- c magenta",
"; c cyan",
": c gray100",
/* pixels */
"          $***",
" :::::::::$$**",
" :########$:$*",
" :###XX###$::$",
" :##X&X ##    ",
" :##XXX ####: ",
" :###  #####: ",
" :######=== :*",
" :#%####=;= :*",
" :#-%###==O***",
" :#--%##  **: ",
" :#---%#***#: ",
" :#   *****#: ",
" :##*****###: ",
" ::*****::::: ",
"  ******      "
};
]]
xpm_tempfile=os.tmpname()
xpm_write=io.open(xpm_tempfile,"w")
if xpm_write then
 xpm_write:write(broken_xpm_data)
 xpm_write:close()
 broken_xpm=fltk:Fl_XPM_Image(xpm_tempfile)
 os.remove(xpm_tempfile)
end

edvar=os.getenv("XPM_EDITOR")
if edvar then editor=edvar end

function get_image_data(self)
 local old_data=display:image()
 if old_data then old_data:uncache() end
 local image_data=self:image()
 if image_data then
   display:image(image_data:copy(128,128)) --show it bigger!
 else
   display:image(broken_xpm)
 end
 current_file=dir..self:tooltip() -- for editor
 display:label("\n"..self:tooltip().."\n"..lfs.attributes(current_file).size.." bytes")
 display:redraw()
end

function menu_cb()
 if i_menu:text()=="&Edit" and current_file then os.execute(editor.." "..current_file.." &")
 elseif
i_menu:text()=="&Directory" then chdir()
 elseif i_menu:text()=="&Refresh" then refresh()
 elseif i_menu:text()=="&Quit" then os.exit(0)
 end
end

function find_stupid_space(file)
 local data=io.open(file)
 if data then
   for line in data:lines() do
     if string.find(line,"^%s") then return 1 end
   end
 data:close()
 end
end

function build_list()
 if not string.find(dir,"/$") then dir=dir.."/" end
 scroll=fltk:Fl_Scroll(5,bh,sw,sh)
 w:add(scroll)
 pack=fltk:Fl_Pack(5,bh,bw,sh)
 pack:spacing(2)
 scroll:add(pack)
 -- get all xpm files
 for file in lfs.dir(dir) do
   if string.find(file,"%.xpm$") then
     table.insert(images,file)
   end
 end
 table.sort(images)
 -- make buttons for "xpm_size" images only
 xpm_count=0
 filesize=0
 for i=1,table.getn(images) do
   icon[i]=fltk:Fl_XPM_Image(dir..images[i])
   if icon[i]:w()==xpm_size and icon[i]:h()==xpm_size then
       xpm_count=1+xpm_count
       filesize=filesize+lfs.attributes(dir..images[i]).size
       butt[xpm_count]=fltk:Fl_Button(0,0,bw,bw)
       butt[xpm_count]:box(15) --shadow
     if not find_stupid_space(dir..images[i]) then
       butt[xpm_count]:image(icon[i])
     else butt[xpm_count]:label("bad\nimage")
     end
       butt[xpm_count]:callback(get_image_data)
       butt[xpm_count]:tooltip(images[i])
       pack:add(butt[xpm_count])
     else icon[i]:uncache()
   end
 end
 if butt[1] then display:show(); butt[1]:do_callback() end -- show first image
 display2:label(xpm_count.." files | "..math.ceil(filesize/1024).." kb")
end

function refresh()
 local old_image=display:image()
 if old_image then old_image:uncache(); display:hide() end
 -- clear all tables and remove buttons
 for i=1,table.getn(images) do table.remove(images) end
 for i=1,table.getn(icon) do icon[i]:uncache() end
 for i=1,table.getn(icon) do table.remove(icon) end
 for i=1,table.getn(butt) do table.remove(butt) end
 w:remove(scroll)
 Fl:delete_widget(scroll)
 scroll=nil
 build_list()
end

function chdir()
 local dirname=fltk.fl_dir_chooser("pick a dir...",dir)
 if dirname then
   dir=dirname
   refresh()
 end
end

-- candy
Fl:set_boxtype(fltk.FL_UP_BOX,fltk.FL_THIN_UP_BOX)
Fl:set_boxtype(fltk.FL_DOWN_BOX,fltk.FL_THIN_DOWN_BOX)
fltk.fl_define_FL_SHADOW_BOX()
Fl:set_color(fltk.FL_DARK3,150,150,150) -- shadow
Fl:set_color(fltk.FL_GRAY0,128,128,128) -- frame
--Fl_Tooltip:disable()

-- some sizes and positions are determined by xpm_size
bw=xpm_size+20 -- button width
bh=30; sw=bw+20; sh=250 -- menu, scroll size
ww=sw+sh+10
wh=bh+sh+10
w=fltk:Fl_Window(ww,wh,"XPM Icons")

menu_items={"&Edit","&Directory","&Refresh","&Quit"}
i_menu=fltk:Fl_Menu_Button(0,0,sw+5,bh,"&Menu")
i_menu:align(20)
i_menu:callback(menu_cb)
i_menu:selection_color(fltk.FL_WHITE)
for i,v in ipairs(menu_items) do i_menu:add(v) end

display=fltk:Fl_Box(sw+5,0,sh,sh) -- big image
display2=fltk:Fl_Box(sw+5,sh,sh,bh) -- dir info

xpm_count=0 -- set global variables
filesize=0
images={}; icon={}; butt={}

build_list()
w:show()
Fl:run()

That compromise is fine by me, although I am still hoping for a fix on the binary engine side of things.  I like the use of the broken image though.

Some comments (on latest):

Quote
function find_stupid_space(file)
local data=io.open(file)
...
    if string.find(line,"^%s") then return 1 end
...
I'm not sure how it handles files, but shouldn't you explicity call close on data here as well? (Although I'm guessing it could be automatically taken care of when the local data var is destroyed)

- would adding an extra button to optionally "fix" a broken image be a good idea?
- does the xpm format actually allow spaces in front?  If it does, then technically it's not a "broken image"...

Next Page...
original here.