I just finished putting together a simple proof-of-concept utility that automatically fades image borders and applies a drop shadow. It's written in Python and it uses Cairo, PIL, and GTK. I also include in the utility a very simplistic graphical interface that is useful for experimenting with various settings, particularly the size of the faded regions. To try it out, just launch the script from the command line and give it a png image as a parameter: python edge_fader.py screenshot.png I would definitely like to see more automation used for creating and maintaining screenshots, and I'm willing to help develop an end-to-end solution that integrates these features and simplifies the process of capturing screenshots. I have attached the source code as well as a sample output image. I'm not entirely convinced yet that faded borders are the best possible solution to user confusion regarding screenshots, but I think it is a reasonably effective approach, and it is very easy to automate. I think that when SVG support improves in Gecko, we will probably be able to do some very impressive things inside of Yelp without having to do any sort of external processing. Some day, we may be able to use SVG and Javascript to automatically generate translucent gradients for border fading. For now, I think my script will work. For those that are interested, I have written a blog entry with additional technical details about my script: http://www.cixar.com/~segphault/blog/entry/programming/python/edge_fader.blog#new_utility As always, questions, comments, and criticism are greatly appreciated. -- Ryan On Thu, 2006-08-03 at 19:51 +0100, Joachim Noreiko wrote: > --- karderio <karderio gmail com> wrote: > > > Hi :o) > > > > On Sun, 2006-07-30 at 12:56 -0700, Ryan Paul wrote: > > > I can do the screenshots, including the > > cropping/fading. Are there > > > specific GTK/Metacity themes that I should use? > > > > > > If there is sufficient interest, I am also willing > > to make a utility to > > > help automate the screenshot/crop/fade process. > > Last year I was writing > > > articles with lots of screenshots, and I started > > to get really > > > frustrated with the limitations of the GNOME > > screenshot utility. I > > > eventually made my own with Ruby and Glade. It > > does a lot of things that > > > the GNOME screenshot utility doesn't do. For > > instance, it can capture a > > > specific window or a specified screen region. It > > probably wouldn't be > > > that difficult to extend my utility so that it can > > optionally perform > > > the border fade operation. If enough doc writers > > are interested in using > > > something like that, I am willing to rewrite it in > > Python so that people > > > don't have to wrestle with the Ruby GNOME > > dependencies. > > > > Interested I am. This is on my todo list also. I > > looked into this a > > while back. The plan was to use DogTail to automate > > the process of > > taking the shots in each language[1], by running a > > script in the help > > directory. The problem was that dogtail didn't run > > properly on my system > > yet. > > How much can DogTail automate? > > Can everything about how a screenshot should be made > be codified into a script? -- eg window size, document > contents, menus, dialog boxes, pop-up elements etc? > > If so, then I can imagine the following scenario: > > The DocBook file contains a tag for the screenshot. > This tag contains somewhere within it meta-information > destined for DogTail. > The documentation writer copies this to the terminal, > dogtail opens a few windows, does its stuff, and > shazamm!!! the screenshot has been updated based on > the software on the writer's system. (Because the > meta-information also says where to save the file, of > course!) > An eventual GUI documentation editor would just have a > nice big button: "Update Screenshot". > > It's nice to dream, huh? :) > > > > ___________________________________________________________ > Does your mail provider give you FREE antivirus protection? > Get Yahoo! Mail http://uk.mail.yahoo.com
#!/usr/bin/env python """ GDP Image Edge Fader Proof of Concept -------------------------------------- SegPhault (Ryan Paul) - 08/01/2006 This utility automatically blurs the edges of images and applies a drop shadow. The graphical interface was implemented to facilitate dynamic alteration of the attributes used by the edge fading proccess. It will make it easy to compare various configurations and determine exactly which values should be used by default for documentation screenshots. There are three values exposed to the interface: o border - specifies the size of the faded regions o filled - specifies how far from the edge the fades should end o offset - specifies the the drop shadow offset Dependencies ------------ o Python bindings for Cairo o Python bindings for GTK o PIL Issues ------ The edge fading is implemented in Cairo, which does not support gaussian blur filtering yet. The shadow can't be implemented without that, so I ended doing that part with PIL. Unfortunately, version 1.0.2 of PyCairo doesn't have any mechanism for outputting raw image data that PIL can read. The CVS version has a nifty surface method called to_rgba that does what I want, but for now I have to save the image from the Cairo surface to disk and then load it back in with PIL. Resources --------- Python Cookbook recipe for guassian blur drop shadows with PIL: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/474116 Example of how to access Cairo data in PIL: http://webcvs.cairographics.org/pycairo/test/to_rgba.py?view=markup Loading PIL data into a GTK Pixbuf: http://www.daa.com.au/pipermail/pygtk/2003-June/005268.html """ import sys, cairo, gtk, StringIO from PIL import Image, ImageFilter TEMP_FILE = "/tmp/junk.png" def pil_to_gdk(img): file = StringIO.StringIO() img.save(file, 'ppm') contents = file.getvalue() file.close() loader = gtk.gdk.PixbufLoader('pnm') loader.write (contents, len(contents)) pixbuf = loader.get_pixbuf() loader.close() return pixbuf def fade_edges(img_file, border = 30, filled = 1, offset = 6, show_shadow = True, fade_edges = True): img = cairo.ImageSurface.create_from_png(img_file) width, height = img.get_width(), img.get_height() c = cairo.Context(img) ops = ((0, filled, 0, border), # top (filled, 0, border, 0), # left (0, height - filled, 0, height - border), # bottom (width - filled, 0, width - border, 0)) # right if fade_edges: for op in ops: p = cairo.LinearGradient(*op) p.add_color_stop_rgba(0,1,1,1,1) p.add_color_stop_rgba(1,1,1,1,0) c.rectangle(0,0, width, height) c.set_source(p); c.fill_preserve() img.write_to_png(TEMP_FILE) image = Image.open(TEMP_FILE) if not show_shadow: return image back = Image.new(image.mode, (width + offset * 3, height + offset * 3), "rgb(255,255,255)") back.paste("rgb(68,68,68)", [ offset, offset * 2, offset + width, offset + height]) for x in range(3): back = back.filter(ImageFilter.BLUR) back.paste(image, (0, 0)) return back def save_to_disk(pil_img, target_file): pil_to_gdk(pil_img).save(target_file, "png") ## The rest of this script contains a user interface for testing purposes ## class EdgeFadeExperiment(gtk.Window): def __init__(self, img_file, **args): gtk.Window.__init__(self) self.connect("destroy", self.on_close) self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white")) layout = gtk.VBox(False); self.add(layout) self.image = gtk.Image(); layout.add(self.image) self.image_file = img_file self.sliders = dict([(n, v) for n, v in self.add_sliders(**args)]) for n, v in self.sliders.items(): v.set_digits(0) hb = gtk.HBox(False) hb.pack_start(gtk.Label(n.capitalize() + ":"), False, False, 10) hb.add(v); layout.add(hb) self.optInstant = gtk.CheckButton("Instant _apply", True) self.optShadow = gtk.CheckButton("Render _shadow", True) self.optFade = gtk.CheckButton("Fade _edges", True) self.optShadow.set_active(True); self.optFade.set_active(True) btnUpdate = gtk.Button("_Update"); btnSave = gtk.Button("_Save") btnUpdate.connect("pressed", lambda *w: self.render_image(True)) btnSave.connect("pressed", self.on_save) hb = gtk.HBox(False); layout.add(hb) hb.add(self.optInstant); hb.add(self.optShadow); hb.add(self.optFade) hb.pack_end(btnUpdate, False, False); hb.pack_end(btnSave, False, False) def add_sliders(self, **args): for n, v in args.items(): adj = gtk.Adjustment(v, 0, 100, 1) adj.connect("value-changed", lambda *a: self.render_image()) yield n, gtk.HScale(adj) def render_image(self, update = False): if self.optInstant.get_active() or update: args = dict([(n, int(v.get_adjustment().value)) for n, v in self.sliders.items()]) args["show_shadow"] = self.optShadow.get_active() args["fade_edges"] = self.optFade.get_active() self.image.set_from_pixbuf(pil_to_gdk(fade_edges(self.image_file, **args))) def on_save(self, *args): buttons = (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK) d = gtk.FileChooserDialog("Save file", self, gtk.FILE_CHOOSER_ACTION_SAVE, buttons) if d.run() == gtk.RESPONSE_OK: self.image.get_pixbuf().save(d.get_filename(), "png") d.destroy() def on_close(self, *args): gtk.main_quit() if __name__ == '__main__': w = EdgeFadeExperiment(sys.argv[1], border = 30, filled = 1, offset = 6) w.render_image(True) w.show_all() gtk.main()
Attachment:
faded.png
Description: PNG image