[adwaita-icon-theme] Add shadow generation and a make-script for W32 cursors



commit feb88a1e0b0930469c9ffcd97c83d24f6c1356f9
Author: Руслан Ижбулатов <lrn1986 gmail com>
Date:   Mon May 18 20:55:24 2015 +0000

    Add shadow generation and a make-script for W32 cursors
    
    * anicursorgen.py now can generate shadows similar to those that W32 WM
      generates for system cursors. This is enabled by the -s option and
      disabled (overriding previous -s occurrences) by the -n option.
      Options -r, -d, -b and -c are available to customize the shadow generator.
    
      This will generate shadows even for parts of cursors that normally have
      no shadows in Adwaita (the sub-icons attached to dnd and left_ptr variants).
      However, this is consistent with how W32 WM draws shadows (i.e. IDC_HELP).
    
    * New make-w32.sh, which is just like make.sh, except that it generates
      W32 cursors. Any arguments given to make-w32.sh will be passed along
      to anicursorgen.py that it runs. It knows enough to pass an extra -n
      when generating a 'tcross' cursor.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=749559

 src/cursors/anicursorgen.py  |  104 ++++++++++++++++++++++++++++++++++--------
 src/cursors/pngs/make-w32.sh |   65 ++++++++++++++++++++++++++
 2 files changed, 149 insertions(+), 20 deletions(-)
---
diff --git a/src/cursors/anicursorgen.py b/src/cursors/anicursorgen.py
index ad13bff..7c4c31a 100755
--- a/src/cursors/anicursorgen.py
+++ b/src/cursors/anicursorgen.py
@@ -24,7 +24,9 @@ import argparse
 import shlex
 import io
 import struct
+import math
 from PIL import Image
+from PIL import ImageFilter
 
 p = struct.pack
 
@@ -40,6 +42,21 @@ def main ():
                        help='Display the usage message and exit.')
   parser.add_argument ('-p', '--prefix', metavar='dir', default=None,
                        help='Find cursor images in the directory specified by dir. If not specified, the 
current directory is used.')
+  parser.add_argument ('-s', '--add-shadows', action='store_true', dest='add_shadows', default=False,
+                       help='Generate shadows for cursors (disabled by default).')
+  parser.add_argument ('-n', '--no-shadows', action='store_false', dest='add_shadows', default=False,
+                       help='Do not generate shadows for cursors (put after --add-shadows to cancel its 
effect).')
+
+  shadows = parser.add_argument_group (title='Shadow generation', description='Only relevant when 
--add-shadows is given')
+
+  shadows.add_argument ('-r', '--right-shift', metavar='%', type=float, default=9.375,
+                       help='Shift shadow right by this percentage of the canvas size (default is 9.375).')
+  shadows.add_argument ('-d', '--down-shift', metavar='%', type=float, default=3.125,
+                       help='Shift shadow down by this percentage of the canvas size (default is 3.125).')
+  shadows.add_argument ('-b', '--blur', metavar='%', type=float, default=3.125,
+                       help='Blur radius, in percentage of the canvas size (default is 3.125, set to 0 to 
disable blurring).')
+  shadows.add_argument ('-c', '--color', metavar='%', default='0x80808080',
+                       help='Shadow color in 0xRRGGBBAA form (default is 0x80808080).')
 
   parser.add_argument ('input_config', default='-', metavar='input-config [output-file]', nargs='?',
                        help='Input config file (stdin by default).')
@@ -48,6 +65,15 @@ def main ():
 
   args = parser.parse_args ()
 
+  try:
+    if args.color[0] != '0' or args.color[1] not in ['x', 'X'] or len (args.color) != 10:
+      raise ValueError
+    args.color = (int (args.color[2:4], 16), int (args.color[4:6], 16), int (args.color[6:8], 16), int 
(args.color[8:10], 16))
+  except:
+    print ("Can't parse the color '{}'".format (args.color), file=sys.stderr)
+    parser.print_help ()
+    return 1
+
   if args.prefix is None:
     args.prefix = os.getcwd ()
 
@@ -61,22 +87,22 @@ def main ():
   else:
     output_file = open (args.output_file, 'wb')
 
-  result = make_cursor_from (input_config, output_file, args.prefix)
+  result = make_cursor_from (input_config, output_file, args)
 
   input_config.close ()
   output_file.close ()
 
   return result
 
-def make_cursor_from (inp, out, prefix):
-  frames = parse_config_from (inp, prefix)
+def make_cursor_from (inp, out, args):
+  frames = parse_config_from (inp, args.prefix)
 
   animated = frames_have_animation (frames)
 
   if animated:
-    result = make_ani (frames, out)
+    result = make_ani (frames, out, args)
   else:
-    buf = make_cur (frames)
+    buf = make_cur (frames, args)
     copy_to (out, buf)
     result = 0
 
@@ -102,7 +128,7 @@ def frames_have_animation (frames):
 
   return False
 
-def make_cur (frames):
+def make_cur (frames, args):
   buf = io.BytesIO ()
   buf.write (p ('<HHH', 0, 2, len (frames)))
   frame_offsets = []
@@ -120,12 +146,22 @@ def make_cur (frames):
     frame_offset = buf.seek (0, io.SEEK_CUR)
     frame_offsets[i].append (frame_offset)
 
+    frame_png = Image.open (frame[3])
+
+    if args.add_shadows:
+      succeeded, shadowed = create_shadow (frame_png, args)
+      if succeeded == 0:
+        frame_png.close ()
+        frame_png = shadowed
+
     compressed = frame[0] > 48
 
     if compressed:
-      write_png (buf, frame)
+      write_png (buf, frame, frame_png)
     else:
-      write_cur (buf, frame)
+      write_cur (buf, frame, frame_png)
+
+    frame_png.close ()
 
     frame_end = buf.seek (0, io.SEEK_CUR)
     frame_offsets[i].append (frame_end - frame_offset)
@@ -172,7 +208,7 @@ def make_framesets (frames):
 
   return framesets
 
-def make_ani (frames, out):
+def make_ani (frames, out, args):
   framesets = make_framesets (frames)
   if framesets is None:
     return 1
@@ -207,7 +243,7 @@ def make_ani (frames, out):
 
   for frameset in framesets:
     buf.write (b'icon')
-    cur = make_cur (frameset)
+    cur = make_cur (frameset, args)
     cur_size = cur.seek (0, io.SEEK_END)
     aligned_cur_size = cur_size
     #if cur_size % 4 != 0:
@@ -228,17 +264,11 @@ def make_ani (frames, out):
 
   return 0
 
-def write_png (out, frame):
-  with open (frame[3], 'rb') as src:
-    while True:
-      buf = src.read (1024)
-      if buf is None or len (buf) == 0:
-        break
-      out.write (buf)
+def write_png (out, frame, frame_png):
+  frame_png.save (out, "png", optimize=True)
 
-def write_cur (out, frame):
-  img = Image.open (frame[3])
-  pixels = img.load ()
+def write_cur (out, frame, frame_png):
+  pixels = frame_png.load ()
 
   out.write (p ('<I II HH IIIIII', 40, frame[0], frame[0] * 2, 1, 32, 0, 0, 0, 0, 0, 0))
 
@@ -293,5 +323,39 @@ def parse_config_from (inp, prefix):
 
   return frames
 
+def create_shadow (orig, args):
+  blur_px = orig.size[0] / 100.0 * args.blur
+  right_px = int (orig.size[0] / 100.0 * args.right_shift)
+  down_px = int (orig.size[1] / 100.0 * args.down_shift)
+
+  shadow = Image.new ('RGBA', orig.size, (0, 0, 0, 0))
+  shadowize (shadow, orig, args.color)
+  shadow.load ()
+
+  if args.blur > 0:
+    crop = (int (math.floor (-blur_px)), int (math.floor (-blur_px)), orig.size[0] + int (math.ceil 
(blur_px)), orig.size[1] + int (math.ceil (blur_px)))
+    right_px += int (math.floor (-blur_px))
+    down_px += int (math.floor (-blur_px))
+    shadow = shadow.crop (crop)
+    flt = ImageFilter.GaussianBlur (blur_px)
+    shadow = shadow.filter (flt)
+  shadow.load ()
+
+  shadowed = Image.new ('RGBA', orig.size, (0, 0, 0, 0))
+  shadowed.paste (shadow, (right_px, down_px))
+  shadowed.crop ((0, 0, orig.size[0], orig.size[1]))
+  shadowed = Image.alpha_composite (shadowed, orig)
+
+  return 0, shadowed
+
+def shadowize (shadow, orig, color):
+  o_pxs = orig.load ()
+  s_pxs = shadow.load ()
+  for y in range (orig.size[1]):
+    for x in range (orig.size[0]):
+      o_px = o_pxs[x, y]
+      if o_px[3] > 0:
+        s_pxs[x, y] = (color[0], color[1], color[2], int (color[3] * (o_px[3] / 255.0)))
+
 if __name__ == '__main__':
   sys.exit (main ())
diff --git a/src/cursors/pngs/make-w32.sh b/src/cursors/pngs/make-w32.sh
new file mode 100644
index 0000000..9268e90
--- /dev/null
+++ b/src/cursors/pngs/make-w32.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+# $@ is for the caller to be able to supply arguments to anicursorgen (-s, in particular)
+GEN=../anicursorgen.py\ "$@"
+for theme in Adwaita Adwaita-Large Adwaita-ExtraLarge
+do
+  mkdir -p ../../../$theme/cursors
+  ${GEN} left_ptr_watch.in ../../../$theme/cursors/left_ptr_watch.ani
+#  ${GEN} hand1.in ../../../$theme/cursors/hand1.cur
+  ${GEN} hand2.in ../../../$theme/cursors/hand2.cur
+  ${GEN} left_ptr.in ../../../$theme/cursors/left_ptr.cur
+#  ${GEN} center_ptr.in ../../../$theme/cursors/center_ptr.cur
+  ${GEN} xterm.in ../../../$theme/cursors/xterm.cur
+  ${GEN} crossed_circle.in ../../../$theme/cursors/crossed_circle.cur
+  ${GEN} right_ptr.in ../../../$theme/cursors/right_ptr.cur
+  ${GEN} copy.in ../../../$theme/cursors/copy.cur
+  ${GEN} move.in ../../../$theme/cursors/move.cur
+  ${GEN} pointer-move.in ../../../$theme/cursors/pointer-move.cur
+  ${GEN} link.in ../../../$theme/cursors/link.cur
+  ${GEN} circle.in ../../../$theme/cursors/circle.cur
+  ${GEN} sb_h_double_arrow.in ../../../$theme/cursors/sb_h_double_arrow.cur
+  ${GEN} sb_v_double_arrow.in ../../../$theme/cursors/sb_v_double_arrow.cur
+  ${GEN} top_left_corner.in ../../../$theme/cursors/top_left_corner.cur
+  ${GEN} top_right_corner.in ../../../$theme/cursors/top_right_corner.cur
+  ${GEN} bottom_left_corner.in ../../../$theme/cursors/bottom_left_corner.cur
+  ${GEN} bottom_right_corner.in ../../../$theme/cursors/bottom_right_corner.cur
+  ${GEN} fd_double_arrow.in ../../../$theme/cursors/fd_double_arrow.cur
+  ${GEN} bd_double_arrow.in ../../../$theme/cursors/bd_double_arrow.cur
+  ${GEN} watch.in ../../../$theme/cursors/watch.ani
+  ${GEN} sb_left_arrow.in ../../../$theme/cursors/sb_left_arrow.cur
+  ${GEN} sb_right_arrow.in ../../../$theme/cursors/sb_right_arrow.cur
+  ${GEN} sb_up_arrow.in ../../../$theme/cursors/sb_up_arrow.cur
+  ${GEN} sb_down_arrow.in ../../../$theme/cursors/sb_down_arrow.cur
+#  ${GEN} based_arrow_down.in ../../../$theme/cursors/based_arrow_down.cur
+#  ${GEN} based_arrow_up.in ../../../$theme/cursors/based_arrow_up.cur
+  ${GEN} bottom_side.in ../../../$theme/cursors/bottom_side.cur
+  ${GEN} top_side.in ../../../$theme/cursors/top_side.cur
+  ${GEN} left_side.in ../../../$theme/cursors/left_side.cur
+  ${GEN} right_side.in ../../../$theme/cursors/right_side.cur
+  ${GEN} grabbing.in ../../../$theme/cursors/grabbing.cur
+  ${GEN} question_arrow.in ../../../$theme/cursors/question_arrow.cur
+  ${GEN} top_tee.in ../../../$theme/cursors/top_tee.cur
+  ${GEN} bottom_tee.in ../../../$theme/cursors/bottom_tee.cur
+  ${GEN} left_tee.in ../../../$theme/cursors/left_tee.cur
+  ${GEN} right_tee.in ../../../$theme/cursors/right_tee.cur
+  ${GEN} ul_angle.in ../../../$theme/cursors/ul_angle.cur
+  ${GEN} ll_angle.in ../../../$theme/cursors/ll_angle.cur
+  ${GEN} ur_angle.in ../../../$theme/cursors/ur_angle.cur
+  ${GEN} lr_angle.in ../../../$theme/cursors/lr_angle.cur
+  ${GEN} X_cursor.in ../../../$theme/cursors/X_cursor.cur
+  ${GEN} crosshair.in ../../../$theme/cursors/crosshair.cur
+  ${GEN} cross.in ../../../$theme/cursors/cross.cur
+  ${GEN} --no-shadows tcross.in ../../../$theme/cursors/tcross.cur
+  ${GEN} dotbox.in ../../../$theme/cursors/dotbox.cur
+  ${GEN} plus.in ../../../$theme/cursors/plus.cur
+  ${GEN} pencil.in ../../../$theme/cursors/pencil.cur
+  ${GEN} dnd-none.in ../../../$theme/cursors/dnd-none.cur
+  ${GEN} dnd-copy.in ../../../$theme/cursors/dnd-copy.cur
+  ${GEN} dnd-link.in ../../../$theme/cursors/dnd-link.cur
+  ${GEN} dnd-move.in ../../../$theme/cursors/dnd-move.cur
+  ${GEN} dnd-ask.in ../../../$theme/cursors/dnd-ask.cur
+  ${GEN} zoom-in.in ../../../$theme/cursors/zoom-in.cur
+  ${GEN} zoom-out.in ../../../$theme/cursors/zoom-out.cur
+  ${GEN} all-scroll.in ../../../$theme/cursors/all-scroll.cur
+  ${GEN} vertical-text.in ../../../$theme/cursors/vertical-text.cur
+done


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