| | 90 | #: Enumeration value to scale the area of the pixbuf to draw and put |
|---|
| | 91 | #: the result in cache. This is the slowest draw method as the whole |
|---|
| | 92 | #: area to be drawn must be rescaled. It is mostly used when no part of |
|---|
| | 93 | #: `PixbufDrawCache`:s cache is valid. |
|---|
| | 94 | #: |
|---|
| | 95 | #: :see: `PixbufDrawCache.get_method()` |
|---|
| | 96 | #: :see: `PixbufDrawCache.invalidate()` |
|---|
| | 97 | DRAW_METHOD_SCALE = 0 |
|---|
| | 98 | #: Enumeration value to get the area of the pixbuf to draw from the |
|---|
| | 99 | #: cache and not update the cache afterwards. |
|---|
| | 100 | #: |
|---|
| | 101 | #: :see: `PixbufDrawCache.get_method()` |
|---|
| | 102 | DRAW_METHOD_CONTAINS = 1 |
|---|
| | 103 | #: Enumeration value to partially use the cache and scale the region |
|---|
| | 104 | #: not cached. The cache is updated with the result. |
|---|
| | 105 | #: |
|---|
| | 106 | #: :see: `PixbufDrawCache.get_method()` |
|---|
| | 107 | DRAW_METHOD_SCROLL = 2 |
|---|
| | 108 | |
|---|
| | 109 | class PixbufDrawOpts: |
|---|
| | 110 | ''' |
|---|
| | 111 | Container class which holds options for how the pixbuf should be |
|---|
| | 112 | drawn. Options include such things like the source rectangle in |
|---|
| | 113 | the pixbuf to draw, where to draw it, which zoom to use and so on. |
|---|
| | 114 | |
|---|
| | 115 | :see: `PixbufDrawCache.draw()` |
|---|
| | 116 | ''' |
|---|
| | 117 | def __init__(self, zoom, zoom_rect, widget_x, widget_y, interp, pixbuf, check_color1, check_color2): |
|---|
| | 118 | self.zoom = zoom |
|---|
| | 119 | '''Zoom factor.''' |
|---|
| | 120 | self.zoom_rect = zoom_rect |
|---|
| | 121 | '''Rectangle in zoom space coordinates of the area to draw.''' |
|---|
| | 122 | self.widget_x = widget_x |
|---|
| | 123 | '''X-coordinate of the draw location on the widget.''' |
|---|
| | 124 | self.widget_y = widget_y |
|---|
| | 125 | '''Y-coordinate of the draw location on the widget.''' |
|---|
| | 126 | self.interp = interp |
|---|
| | 127 | '''Which ``gtk.gdk.InterpType`` to use.''' |
|---|
| | 128 | self.pixbuf = pixbuf |
|---|
| | 129 | '''Pixbuf to draw from.''' |
|---|
| | 130 | self.check_color1 = check_color1 |
|---|
| | 131 | '''The first color to use for drawing transparent parts.''' |
|---|
| | 132 | self.check_color2 = check_color2 |
|---|
| | 133 | '''The second color to use for drawing transparent parts.''' |
|---|
| | 134 | |
|---|
| | 135 | class PixbufDrawCache: |
|---|
| | 136 | ''' |
|---|
| | 137 | Cache that ensures fast redraws by storing the last draw |
|---|
| | 138 | operation. For example, when resizing an `ImageView`, it will |
|---|
| | 139 | receive an expose event and must redraw the damaged region. Unless |
|---|
| | 140 | fitting is ``True``, most of the pixels it should draw are |
|---|
| | 141 | indentical to the ones drawn the previous time. Redrawing them is |
|---|
| | 142 | wasteful because scaling and especially bilinear scaling is very |
|---|
| | 143 | slow. Therefore, PixbufDrawCache objectifies the drawing process |
|---|
| | 144 | and adds a cache with the last draw from which pixels can be |
|---|
| | 145 | fetched. |
|---|
| | 146 | |
|---|
| | 147 | This class is present purely to ensure optimal speed. An |
|---|
| | 148 | `IImageTool` that is asked to redraw a part of the image view |
|---|
| | 149 | widget could either do it manually with something like this: |
|---|
| | 150 | |
|---|
| | 151 | .. python:: |
|---|
| | 152 | |
|---|
| | 153 | def paint_image(self, draw_opts, drawable): |
|---|
| | 154 | zoom_rect = draw_opts.zoom_rect |
|---|
| | 155 | scaled = draw_opts.pixbuf.scale(0, 0, |
|---|
| | 156 | zoom_rect.width, zoom_rect.height, |
|---|
| | 157 | -zoom_rect.x, -zoom_rect.y, |
|---|
| | 158 | draw_opts.zoom, |
|---|
| | 159 | draw_opts.interp, |
|---|
| | 160 | zoom_rect.x, zoom_rect.y, |
|---|
| | 161 | 16, |
|---|
| | 162 | draw_opts.check_color1, |
|---|
| | 163 | draw_opts.check_color2) |
|---|
| | 164 | drawable.draw_pixbuf(NULL, scaled, |
|---|
| | 165 | 0, 0, |
|---|
| | 166 | draw_opts.widget_x, draw_opts.widget_y, |
|---|
| | 167 | zoom_rect.width, zoom_rect.height, |
|---|
| | 168 | gtk.gdk.RGB_DITHER_MAX, |
|---|
| | 169 | draw_opts.widget_x, draw_opts.widget_y) |
|---|
| | 170 | |
|---|
| | 171 | ''' |
|---|
| | 172 | |
|---|
| | 173 | |
|---|
| | 174 | def __init__(self): |
|---|
| | 175 | ''' |
|---|
| | 176 | Create a new pixbuf draw cache. |
|---|
| | 177 | ''' |
|---|
| | 178 | |
|---|
| | 179 | def invalidate(self): |
|---|
| | 180 | ''' |
|---|
| | 181 | Force the draw cache to scale the pixbuf at the next draw. |
|---|
| | 182 | |
|---|
| | 183 | `PixbufDrawCache` tries to minimize the number of scale |
|---|
| | 184 | operations needed by caching the last drawn pixbuf. It would |
|---|
| | 185 | be inefficient to check the individual pixels inside the |
|---|
| | 186 | pixbuf so it assumes that if the memory address of the pixbuf |
|---|
| | 187 | has not changed, then the cache is good to use. |
|---|
| | 188 | |
|---|
| | 189 | However, when the image data is modified, this assumtion |
|---|
| | 190 | breaks, which is why this method must be used to tell draw |
|---|
| | 191 | cache about it. |
|---|
| | 192 | |
|---|
| | 193 | :see: `DRAW_METHOD_SCALE` |
|---|
| | 194 | :see: `draw()` |
|---|
| | 195 | :see: `ImageView.damage_pixels()` |
|---|
| | 196 | ''' |
|---|
| | 197 | |
|---|
| | 198 | def draw(self, draw_opts, drawable): |
|---|
| | 199 | ''' |
|---|
| | 200 | Draw on the drawable using the specified draw options. |
|---|
| | 201 | ''' |
|---|
| | 202 | |
|---|
| | 203 | @classmethod |
|---|
| | 204 | def get_method(cls, last_opts, new_opts): |
|---|
| | 205 | ''' |
|---|
| | 206 | Get the fastest method to draw the specified draw options. |
|---|
| | 207 | `last_opts` is assumed to be the last `PixbufDrawOpts` used |
|---|
| | 208 | and `new_opts` is the one to use this time. |
|---|
| | 209 | |
|---|
| | 210 | This function returns one of the three constants |
|---|
| | 211 | `DRAW_METHOD_CONTAINS`, `DRAW_METHOD_SCROLL` or |
|---|
| | 212 | `DRAW_METHOD_SCALE`. |
|---|
| | 213 | |
|---|
| | 214 | :param last_opts: the last draw options used |
|---|
| | 215 | :param new_opts: the current draw options |
|---|
| | 216 | :return: the best draw method to use to draw |
|---|
| | 217 | ''' |
|---|
| | 218 | |
|---|
| 104 | | def pixbuf_changed(self, reset_fit): |
|---|
| 105 | | ''' |
|---|
| 106 | | This method is called by the view whenever its pixbuf or its |
|---|
| 107 | | tool changes. That is, when any of the methods |
|---|
| 108 | | `ImageView.set_pixbuf()` or `ImageView.set_tool()` is invoked. |
|---|
| 109 | | If the `reset_fit` parameter is ``True``, then the tool is |
|---|
| 110 | | free to treat the pixbuf as completely new. See also |
|---|
| 111 | | `ImageView.sig_pixbuf_changed`. |
|---|
| 112 | | |
|---|
| | 248 | def pixbuf_changed(self, reset_fit, rect): |
|---|
| | 249 | ''' |
|---|
| | 250 | Indicate to the tool that either a part of, or the whole |
|---|
| | 251 | pixbuf that the image view shows has changed. This method is |
|---|
| | 252 | called by the view whenever its pixbuf or its tool changes. |
|---|
| | 253 | That is, when any of the following method are used: |
|---|
| | 254 | |
|---|
| | 255 | * `ImageView.set_pixbuf()` |
|---|
| | 256 | * `ImageView.set_tool()` |
|---|
| | 257 | * `ImageView.damage_pixels()` |
|---|
| | 258 | |
|---|
| | 259 | If the `reset_fit` parameter is ``True``, it means that a new |
|---|
| | 260 | pixbuf has been loaded into the view. `rect` is a rectangle in |
|---|
| | 261 | image space coordinates that indicates which region of the |
|---|
| | 262 | pixbufs pixels that is modified. `rect` can be ``None`` which |
|---|
| | 263 | means that all image data in the pixbuf has been changed. |
|---|
| | 264 | |
|---|
| | 265 | :see: `ImageView.sig_pixbuf_changed` |
|---|
| | 711 | ''' |
|---|
| | 712 | |
|---|
| | 713 | def damage_pixels(self, rect): |
|---|
| | 714 | ''' |
|---|
| | 715 | Mark the pixels in the rectangle as damaged. That the pixels |
|---|
| | 716 | are damaged, means that they have been modified and that the |
|---|
| | 717 | view must redraw them to ensure that the visible part of the |
|---|
| | 718 | image corresponds to the pixels in that image. |
|---|
| | 719 | |
|---|
| | 720 | Damaging first causes the `IImageTool.pixbuf_changed()` to be |
|---|
| | 721 | called which allows the tool to, for example, update its cache |
|---|
| | 722 | of the pixbuf if it has one. |
|---|
| | 723 | |
|---|
| | 724 | The signal `sig_pixbuf_changed` is emitted which enables |
|---|
| | 725 | interested listeners to update their view of the pixbuf. And |
|---|
| | 726 | finally is the visible part of the damaged rectangle queued |
|---|
| | 727 | for redraw. |
|---|
| | 728 | |
|---|
| | 729 | :param rect: the ``gtk.gdk.Rectangle`` in image space |
|---|
| | 730 | coordinates to mark as dirty. |
|---|
| | 731 | :see: `set_pixbuf()` |
|---|
| | 732 | :see: `sig_pixbuf_changed` |
|---|
| | 733 | ''' |
|---|
| | 734 | |
|---|
| | 735 | def image_to_widget_rect(self, rect): |
|---|
| | 736 | ''' |
|---|
| | 737 | Converts a rectangle in image space coordinates to widget |
|---|
| | 738 | space coordinates. If the view is not realized, or if it |
|---|
| | 739 | contains no pixbuf, then the conversion cannot be done and |
|---|
| | 740 | ``None`` is returned. |
|---|
| | 741 | |
|---|
| | 742 | Note that this method may return a rectangle that is not |
|---|
| | 743 | visible on the widget. |
|---|
| | 744 | |
|---|
| | 745 | A use for this method would be if you for example is |
|---|
| | 746 | implementing an `IImageTool` and want to return a special |
|---|
| | 747 | ``gtk.gdk.Cursor`` if the pointer is over a hotspot in the |
|---|
| | 748 | image: |
|---|
| | 749 | |
|---|
| | 750 | .. python:: |
|---|
| | 751 | |
|---|
| | 752 | def cursor_at_point(self, x, y): |
|---|
| | 753 | wid_rect = self.view.image_to_widget_rect(self.hotspot) |
|---|
| | 754 | # Use the default cursor if the the view isn't realized |
|---|
| | 755 | # or if the pointer isn't over the hotspot. |
|---|
| | 756 | if not wid_rect: |
|---|
| | 757 | return None |
|---|
| | 758 | if not (x >= wid_rect.x and x < wid_rect.x + wid_rect.width and |
|---|
| | 759 | y >= wid_rect.y and y < wid_rect.y + wid_rect.height): |
|---|
| | 760 | return None |
|---|
| | 761 | # Use the hotspot cursor if the pointer is over the hotspot. |
|---|
| | 762 | return self.hotspot_cursor |
|---|
| | 763 | |
|---|
| | 764 | :param rect: a ``gtk.gdk.Rectangle`` in image space |
|---|
| | 765 | coordinates. |
|---|
| | 766 | :return: a ``gtk.gdk.Rectangle`` of the widget space |
|---|
| | 767 | coordinates or ``None`` |
|---|