Hi Martin Thanks for having a look at my patches.
On 17.07.2010 12:25, Martin Renold wrote: > It looks good to me and as soon as > http://jens.triq.net/thumbnail-spec/modifications.html > is also implemented this can go into master. > Both options (key/value pairs), "Thumb::MTime" (to check for modification time) and "Thumb::URI" get saved now. > Below are only some minor points. > > - One of the commit messages has a too long summary (just add two newlines). > Reverted to master and reworked the functionality anway, so attached will be only a single patch this time. > - I think the guideline asks us to check for a 256x256 thumbnail before we > start to generate a 128x128 one. Would be nice to have this. > It will now check for a 128px² thumbnail (as that is the preview size) first; if it doesn't exist it will check for a 256px² thumbnail. If that, too, doesn't exist, it will simply try to load the file (if it is an image) directly. (save_freedesktop_thumbnail will get called in either case. Hm, now that I think of it, calling the method "get_freedesktop_thumbnail" might be a bit misleading, as it does not only get but also create thumbnails.) > - As I said on IRC, no point to safe only .ora thumbnails I think. > Everything gtk/gdk can handle plus .ora files will be thumbnailed now. >> + else: >> + pass > > Ideally we would set some "broken thumbnail" image, or just all-blank. > Yes, you'd just have to tell me where to include/place a no-preview-available.png (I see there is a theme search path in application.py for the icons etc?). Till
>From 2f8f556de20641cc1b74f90932002867102f988f Mon Sep 17 00:00:00 2001 From: Till Hartmann <[email protected]> Date: Sun, 25 Jul 2010 00:13:17 +0200 Subject: [PATCH] Thumbnail storage and retrieval now follow FDO specs. See http://jens.triq.net/thumbnail-spec/introduction.html for the complete freedesktop.org thumbnail specification. --- gui/filehandling.py | 31 +++++------------- lib/helpers.py | 87 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 78 insertions(+), 40 deletions(-) diff --git a/gui/filehandling.py b/gui/filehandling.py index 871f071..336727b 100644 --- a/gui/filehandling.py +++ b/gui/filehandling.py @@ -17,7 +17,6 @@ from gettext import ngettext from lib import document, helpers import drawwindow -import zipfile import mimetypes SAVE_FORMAT_ANY = 0 @@ -253,30 +252,16 @@ class FileHandler(object): def update_preview_cb(self, file_chooser, preview): filename = file_chooser.get_preview_filename() - pixbuf = self.get_preview_image(filename) - preview.set_from_pixbuf(pixbuf) - file_chooser.set_preview_widget_active(pixbuf != None) - - def get_preview_image(self, filename): if filename: - if os.path.splitext(filename)[1].lower() == ".ora": - ora = zipfile.ZipFile(file(filename)) - try: - data = ora.read("Thumbnails/thumbnail.png") - except KeyError: - return None - loader = gtk.gdk.PixbufLoader("png") - loader.write(data) - loader.close() - pixbuf = loader.get_pixbuf() - return pixbuf + pixbuf = helpers.get_freedesktop_thumbnail(filename) + if pixbuf: + # if pixbuf is smaller than 128px in width, copy it onto a transparent 128x128 pixbuf + pixbuf = helpers.pixbuf_thumbnail(pixbuf, 128, 128, True) + preview.set_from_pixbuf(pixbuf) + file_chooser.set_preview_widget_active(True) else: - try: - #TODO do not scale images smaller than 256x256 up. - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 256, 256) - return pixbuf - except: - pass + #TODO display "no preview available" image + pass def open_cb(self, action): if not self.confirm_destructive_action(): diff --git a/lib/helpers.py b/lib/helpers.py index f6ca5af..335c5f6 100644 --- a/lib/helpers.py +++ b/lib/helpers.py @@ -12,6 +12,10 @@ import colorsys, urllib, gc from gtk import gdk # for gdk_pixbuf stuff import mypaintlib +import hashlib +import os +import zipfile + try: from json import dumps as json_dumps, loads as json_loads print "builtin python 2.6 json support" @@ -111,26 +115,75 @@ def gdkpixbuf2numpy(pixbuf): arr = pixbuf.get_pixels_array() return mypaintlib.gdkpixbuf_numeric2numpy(arr) -def pixbuf_thumbnail(src, w, h): +def get_freedesktop_thumbnail(filename): """ - Creates a centered thumbnail of a gdk.pixbuf. + Tries to fetch a thumbnail from ~/.thumbnails. + If there is no thumbnail for the specified filename, + a new thumbnail will be generated and stored according to the FDO spec. + A thumbnail will also get regenerated if the MTimes (as in "modified") + of thumbnail and original image do not match. """ - src_w = src.get_width() - src_h = src.get_height() - - w2, h2 = src_w, src_h - if w2 > w: - h2 = h2*w/w2 - w2 = w - if h2 > h: - w2 = w2*h/h2 - h2 = h - assert w2 <= w and h2 <= h - src2 = src.scale_simple(w2, h2, gdk.INTERP_BILINEAR) - - dst = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, w, h) - dst.fill(0xffffffff) # white background + file_hash = hashlib.md5('file://'+filename).hexdigest() + tb_filename_normal = os.path.join(os.path.expanduser('~/.thumbnails/normal'), file_hash) + '.png' + tb_filename_large = os.path.join(os.path.expanduser('~/.thumbnails/large'), file_hash) + '.png' + if os.path.isfile(tb_filename_normal): + pixbuf = gdk.pixbuf_new_from_file(tb_filename_normal) + elif os.path.isfile(tb_filename_large): + pixbuf = gdk.pixbuf_new_from_file(tb_filename_large) + else: + pixbuf = get_pixbuf(filename) + if pixbuf: + save_freedesktop_thumbnail(pixbuf, filename) # save thumbnail or regenerate if MTimes do not match + return pixbuf + +def save_freedesktop_thumbnail(pixbuf, filename): + """ + Saves a thumbnail according to the FDO spec. + """ + file_hash = hashlib.md5('file://'+filename).hexdigest() + tb_filename_normal = os.path.join(os.path.expanduser('~/.thumbnails/normal'), file_hash) + '.png' + if (not os.path.isfile(tb_filename_normal)) or (str(os.stat(filename).st_mtime) != pixbuf.get_option("tEXt::Thumb::MTime")): + pixbuf = scale_proportionally(pixbuf, 128,128) + #print ("recreated thumbnail for "+filename+" modified: " + str(os.stat(filename).st_mtime)) + pixbuf.save(tb_filename_normal, 'png', {"tEXt::Thumb::MTime" : str(os.stat(filename).st_mtime), "tEXt::Thumb::URI" : ('file://'+filename)}) +def get_pixbuf(filename): + try: + if os.path.splitext(filename)[1].lower() == ".ora": + ora = zipfile.ZipFile(file(filename)) + data = ora.read("Thumbnails/thumbnail.png") + loader = gdk.PixbufLoader("png") + loader.write(data) + loader.close() + pixbuf = loader.get_pixbuf() + return pixbuf + else: + pixbuf = gdk.pixbuf_new_from_file(filename) + return pixbuf; + except: + pass + +def scale_proportionally(pixbuf, w, h, shrink_only=True): + width, height = pixbuf.get_width(), pixbuf.get_height() + scale = min(w / float(width), h / float(height)) + if shrink_only and scale >= 1: + return pixbuf + new_width, new_height = int(width * scale), int(height * scale) + if new_width > 0 and new_height > 0: + pixbuf = pixbuf.scale_simple(new_width, new_height, gdk.INTERP_BILINEAR) + return pixbuf + +def pixbuf_thumbnail(src, w, h, alpha=False): + """ + Creates a centered thumbnail of a gdk.pixbuf. + """ + src2 = scale_proportionally(src, w, h) + w2, h2 = src2.get_width(), src2.get_height() + dst = gdk.Pixbuf(gdk.COLORSPACE_RGB, alpha, 8, w, h) + if alpha: + dst.fill(0xffffff00) # transparent background + else: + dst.fill(0xffffffff) # white background src2.copy_area(0, 0, w2, h2, dst, (w-w2)/2, (h-h2)/2) return dst -- 1.7.0.4
_______________________________________________ Mypaint-discuss mailing list [email protected] https://mail.gna.org/listinfo/mypaint-discuss
