Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/port/pixbuf.py
blob: ba6d0f51daf51def7079b3c2dd1edc9c4601d4c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

"""gtk.gdk.Pixbuf extensions"""

import re
import os
import cStringIO
import gtk
import rsvg
import cairo
import logging

from sugar.graphics.xocolor import XoColor
from sugar.util import LRU

def to_file(pixbuf):
    """Convert pixbuf object to file object"""

    def push(pixbuf, buffer):
        buffer.write(pixbuf)

    buffer = cStringIO.StringIO()
    pixbuf.save_to_callback(push, 'png', user_data=buffer)
    buffer.seek(0)

    return buffer

def to_str(pixbuf):
    """Convert pixbuf object to string"""
    return to_file(pixbuf).getvalue()

def from_str(str):
    """Convert string to pixbuf object"""

    loader = gtk.gdk.pixbuf_loader_new_with_mime_type('image/png')
    loader.write(str)
    loader.close()

    return loader.get_pixbuf()

def from_svg_at_size(filename=None, width=None, height=None, handle=None,
        keep_ratio=True):
    """Scale and load SVG into pixbuf"""

    if not handle:
        handle = rsvg.Handle(filename)

    dimensions = handle.get_dimension_data()
    icon_width = dimensions[0]
    icon_height = dimensions[1]

    if icon_width != width or icon_height != height:
        ratio_width = float(width) / icon_width
        ratio_height = float(height) / icon_height

        if keep_ratio:
            ratio = min(ratio_width, ratio_height)
            if ratio_width != ratio:
                ratio_width = ratio
                width = int(icon_width * ratio)
            elif ratio_height != ratio:
                ratio_height = ratio
                height = int(icon_height * ratio)
    else:
        ratio_width = 1
        ratio_height = 1

    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
    context = cairo.Context(surface)
    context.scale(ratio_width, ratio_height)
    handle.render_cairo(context)

    loader = gtk.gdk.pixbuf_loader_new_with_mime_type('image/png')
    surface.write_to_png(loader)
    loader.close()

    return loader.get_pixbuf()

def sugar_icon(file_name=None, icon_name=None,
               width=None, height=None,
               color=None,
               insensitive_widget=None):
    """Load sugar icon into pixbuf

    NOTE: Function can load all image formats but makes sense only for SVG
          (due to color argument, see load_svg())

    NOTE: Function caches results

    Arguments:
        file_name           path to filename with image
                            (mutually exclusive for icon_name)
        icon_name           name of icon
                            (mutually exclusive for icon_name)
        width               width of final image
        height              height of final image
        color               defines stroke and fill colors for final SVG image
                            in string notion, could be:
                            * tuple of (stroke_color, fill_color)
                            * XoColor
                            * scalar value for stroke and fill colors
        insensitive_widget  render icon in insensitive mode
    """
    def load_svg():
        entities = {}
        if fill_color:
            entities['fill_color'] = fill_color
        if stroke_color:
            entities['stroke_color'] = stroke_color

        f = open(icon_filename, 'r')
        icon = f.read()
        f.close()

        for entity, value in entities.items():
            xml = '<!ENTITY %s "%s">' % (entity, value)
            icon = re.sub('<!ENTITY %s .*>' % entity, xml, icon)

        return rsvg.Handle(data=icon)

    def get_insensitive_pixbuf():
        if not (insensitive_widget and insensitive_widget.style):
            return pixbuf

        icon_source = gtk.IconSource()
        # Special size meaning "don't touch"
        icon_source.set_size(-1)
        icon_source.set_pixbuf(pixbuf)
        icon_source.set_state(gtk.STATE_INSENSITIVE)
        icon_source.set_direction_wildcarded(False)
        icon_source.set_size_wildcarded(False)

        # Please note that the pixbuf returned by this function is leaked
        # with current stable versions of pygtk. The relevant bug is
        # http://bugzilla.gnome.org/show_bug.cgi?id=502871
        #   -- 2007-12-14 Benjamin Berg
        pixbuf = insensitive_widget.style.render_icon(icon_source,
                insensitive_widget.get_direction(), gtk.STATE_INSENSITIVE, -1,
                insensitive_widget, "sugar-icon")

        return pixbuf

    def get_cache_key():
        return (icon_filename, fill_color, stroke_color, width, height,
                insensitive_widget is None)

    if isinstance(color, XoColor):
        stroke_color = color.get_stroke_color()
        fill_color = color.get_fill_color()
    elif isinstance(color, tuple):
        stroke_color = color[0]
        fill_color = color[1]
    else:
        stroke_color = color
        fill_color = color

    if file_name:
        icon_filename = file_name
    elif icon_name:
        theme = gtk.icon_theme_get_default()
        info = theme.lookup_icon(icon_name, width or 50, 0)
        if info:
            icon_filename = info.get_filename()
            del info
        else:
            logging.warning('No icon with the name %s '
                            'was found in the theme.' % icon_name)
    else:
        return None

    cache_key = get_cache_key()
    if cache_key in _sugar_icon_cache:
        return _sugar_icon_cache[cache_key]

    logging.debug('sugar_icon: file_name=%s icon_name=%s width=%s height=%s ' \
                  'color=%s' % (file_name, icon_name, width, height, color))

    is_svg = icon_filename.endswith('.svg')

    if is_svg:
        handle = load_svg()
        if width and height:
            pixbuf = from_svg_at_size(handle=handle, width=width, height=height,
                    keep_ratio=True)
        else:
            pixbuf = handle.get_pixbuf()
    else:
        if width and height:
            pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(icon_filename,
                    width, height)
        else:
            pixbuf = gtk.gdk.pixbuf_new_from_file(icon_filename)

    if insensitive_widget:
        pixbuf = get_insensitive_pixbuf()

    _sugar_icon_cache[cache_key] = pixbuf

    return pixbuf

_sugar_icon_cache = LRU(50)