Re: automated screenshots (was Re: more doc work, Re: completed gataxx doc)



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



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]