pixbuf = gdk.pixbuf_new_from_file(filename)
self.tdw.neutral_background_pixbuf = backgroundsurface.Background(helpers.gdkpixbuf2numpy(pixbuf))
- self.zoomlevel_values = [2.0/11, 0.25, 1.0/3, 0.50, 2.0/3, 1.0, 1.5, 2.0, 3.0, 4.0, 5.5, 8.0]
+ self.zoomlevel_values = [1.0/8, 2.0/11, 0.25, 1.0/3, 0.50, 2.0/3, 1.0, 1.5, 2.0, 3.0, 4.0, 5.5, 8.0]
self.zoomlevel = self.zoomlevel_values.index(1.0)
self.tdw.zoom_min = min(self.zoomlevel_values)
self.tdw.zoom_max = max(self.zoomlevel_values)
import gtk, cairo, random
gdk = gtk.gdk
-from math import floor, ceil, pi
+from math import floor, ceil, pi, log
from lib import helpers, tiledsurface, pixbufsurface
import cursor
# bye bye device coordinates
self.get_model_coordinates_cairo_context(cr)
+ # choose best mipmap
+ mipmap_level = max(0, int(ceil(log(1/self.scale,2))))
+ mipmap_level = min(mipmap_level, tiledsurface.MAX_MIPMAP_LEVEL)
+ cr.scale(2**mipmap_level, 2**mipmap_level)
+
translation_only = self.is_translation_only()
# calculate the final model bbox with all the clipping above
dst = surface.get_tile_memory(tx, ty)
- self.doc.blit_tile_into(dst, tx, ty, layers, background)
+ self.doc.blit_tile_into(dst, tx, ty, mipmap_level, layers, background)
if translation_only:
# not sure why, but using gdk directly is notably faster than the same via cairo
import numpy
import mypaintlib, helpers
-from tiledsurface import N
+from tiledsurface import N, MAX_MIPMAP_LEVEL
class Background:
- def __init__(self, obj):
+ def __init__(self, obj, mipmap_level=0):
try:
obj = helpers.gdkpixbuf2numpy(obj)
except:
tile[:,:,:] = obj[N*ty:N*(ty+1), N*tx:N*(tx+1), :]
self.tiles[tx, ty] = tile
- def blit_tile_into(self, dst, tx, ty):
+ # generate mipmap
+ self.mipmap_level = mipmap_level
+ if mipmap_level < MAX_MIPMAP_LEVEL:
+ mipmap_obj = numpy.zeros((self.th*N, self.tw*N, 3), dtype='uint8')
+ for ty in range(self.th):
+ for tx in range(self.tw):
+ mypaintlib.tile_downscale_rgb8(self.tiles[tx, ty], mipmap_obj, tx*N/2, ty*N/2, True)
+ self.mipmap = Background(mipmap_obj, mipmap_level+1)
+
+ def blit_tile_into(self, dst, tx, ty, mipmap_level=0):
+ if self.mipmap_level < mipmap_level:
+ return self.mipmap.blit_tile_into(dst, tx, ty, mipmap_level)
rgb = self.tiles[tx%self.tw, ty%self.th]
# render solid or tiled background
#dst[:] = rgb # 13 times slower than below, with some bursts having the same speed as below (huh?)
res.expandToIncludeRect(bbox)
return res
- def blit_tile_into(self, dst, tx, ty, layers=None, background=None):
+ def blit_tile_into(self, dst, tx, ty, mipmap=1, layers=None, background=None):
if layers is None:
layers = self.layers
if background is None:
background = self.background
- background.blit_tile_into(dst, tx, ty)
+ background.blit_tile_into(dst, tx, ty, mipmap)
for layer in layers:
surface = layer.surface
- surface.composite_tile_over(dst, tx, ty, layer.opacity)
+ surface.composite_tile_over(dst, tx, ty, mipmap, layer.opacity)
def add_layer(self, insert_idx):
self.do(command.AddLayer(self, insert_idx))
* (at your option) any later version.
*/
+// downscale a tile to half its size using bilinear interpolation
+// used mainly for generating background mipmaps
+void tile_downscale_rgb8(PyObject *src, PyObject *dst, int dst_x, int dst_y, bool repeat) {
+ /* disabled as optimization
+ assert(PyArray_DIM(src, 0) == TILE_SIZE);
+ assert(PyArray_DIM(src, 1) == TILE_SIZE);
+ assert(PyArray_TYPE(src) == NPY_UINT8);
+ assert(PyArray_ISCARRAY(src));
+
+ assert(PyArray_TYPE(dst) == NPY_UINT8);
+ assert(PyArray_ISCARRAY(dst));
+ */
+
+ PyArrayObject* src_arr = ((PyArrayObject*)src);
+ PyArrayObject* dst_arr = ((PyArrayObject*)dst);
+
+ for (int y=0; y<TILE_SIZE/2; y++) {
+ uint8_t * src_p = (uint8_t*)(src_arr->data + (2*y)*src_arr->strides[0]);
+ uint8_t * dst_p = (uint8_t*)(dst_arr->data + (y+dst_y)*dst_arr->strides[0]);
+ dst_p += 3*dst_x;
+ for(int x=0; x<TILE_SIZE/2; x++) {
+ dst_p[0] = src_p[0]/4 + (src_p+3)[0]/4 + (src_p+3*TILE_SIZE)[0]/4 + (src_p+3*TILE_SIZE+3)[0]/4;
+ dst_p[1] = src_p[1]/4 + (src_p+3)[1]/4 + (src_p+3*TILE_SIZE)[1]/4 + (src_p+3*TILE_SIZE+3)[1]/4;
+ dst_p[2] = src_p[2]/4 + (src_p+3)[2]/4 + (src_p+3*TILE_SIZE)[2]/4 + (src_p+3*TILE_SIZE+3)[2]/4;
+ src_p += 6;
+ dst_p += 3;
+ }
+ if(repeat) {
+ uint8_t *p1 = (uint8_t*)(dst_arr->data + (y+dst_y)*dst_arr->strides[0] + 3*dst_x);
+ uint8_t *p2 = p1 + 3 * dst_arr->dimensions[1] / 2;
+ uint8_t *p3 = p1 + dst_arr->strides[0] * dst_arr->dimensions[0] / 2;
+ uint8_t *p4 = p3 + 3 * dst_arr->dimensions[1] / 2;
+ memcpy(p2, p1, 3*TILE_SIZE/2);
+ memcpy(p3, p1, 3*TILE_SIZE/2);
+ memcpy(p4, p1, 3*TILE_SIZE/2);
+ }
+ }
+}
+// downscale a tile to half its size using bilinear interpolation
+// used mainly for generating tiledsurface mipmaps
+void tile_downscale_rgba16(PyObject *src, PyObject *dst, int dst_x, int dst_y) {
+ /* disabled as optimization
+ assert(PyArray_DIM(src, 0) == TILE_SIZE);
+ assert(PyArray_DIM(src, 1) == TILE_SIZE);
+ assert(PyArray_TYPE(src) == NPY_UINT16);
+ assert(PyArray_ISCARRAY(src));
+
+ assert(PyArray_DIM(dst, 0) == TILE_SIZE);
+ assert(PyArray_DIM(dst, 1) == TILE_SIZE);
+ assert(PyArray_TYPE(dst) == NPY_UINT16);
+ assert(PyArray_ISCARRAY(dst));
+ */
+
+ PyArrayObject* src_arr = ((PyArrayObject*)src);
+ PyArrayObject* dst_arr = ((PyArrayObject*)dst);
+
+ for (int y=0; y<TILE_SIZE/2; y++) {
+ uint16_t * src_p = (uint16_t*)(src_arr->data + (2*y)*src_arr->strides[0]);
+ uint16_t * dst_p = (uint16_t*)(dst_arr->data + (y+dst_y)*dst_arr->strides[0]);
+ dst_p += 4*dst_x;
+ for(int x=0; x<TILE_SIZE/2; x++) {
+ dst_p[0] = src_p[0]/4 + (src_p+4)[0]/4 + (src_p+4*TILE_SIZE)[0]/4 + (src_p+4*TILE_SIZE+4)[0]/4;
+ dst_p[1] = src_p[1]/4 + (src_p+4)[1]/4 + (src_p+4*TILE_SIZE)[1]/4 + (src_p+4*TILE_SIZE+4)[1]/4;
+ dst_p[2] = src_p[2]/4 + (src_p+4)[2]/4 + (src_p+4*TILE_SIZE)[2]/4 + (src_p+4*TILE_SIZE+4)[2]/4;
+ dst_p[3] = src_p[3]/4 + (src_p+4)[3]/4 + (src_p+4*TILE_SIZE)[3]/4 + (src_p+4*TILE_SIZE+4)[3]/4;
+ src_p += 8;
+ dst_p += 4;
+ }
+ }
+}
void tile_composite_rgba16_over_rgb8(PyObject * src, PyObject * dst, float alpha) {
/* disabled as optimization
*/
#define TILE_SIZE 64
+#define MAX_MIPMAP_LEVEL 3
class TiledSurface : public Surface {
// the Python half of this class is in tiledsurface.py
import mypaintlib, helpers
tilesize = N = mypaintlib.TILE_SIZE
+MAX_MIPMAP_LEVEL = mypaintlib.MAX_MIPMAP_LEVEL
import pixbufsurface
-
class Tile:
def __init__(self, copy_from=None):
# note: pixels are stored with premultiplied alpha
else:
self.rgba = copy_from.rgba.copy()
self.readonly = False
+ self.mipmap_dirty = False
def copy(self):
return Tile(copy_from=self)
class Surface(mypaintlib.TiledSurface):
# the C++ half of this class is in tiledsurface.hpp
- def __init__(self):
+ def __init__(self, mipmap_level=0):
mypaintlib.TiledSurface.__init__(self, self)
self.tiledict = {}
self.observers = []
+ self.mipmap_level = mipmap_level
+ self.mipmap = None
+ self.parent = None
+
+ if mipmap_level < MAX_MIPMAP_LEVEL:
+ self.mipmap = Surface(mipmap_level+1)
+ self.mipmap.parent = self
+
def notify_observers(self, *args):
for f in self.observers:
f(*args)
tiles = self.tiledict.keys()
self.tiledict = {}
self.notify_observers(*get_tiles_bbox(tiles))
+ if self.mipmap: self.mipmap.clear()
def get_tile_memory(self, tx, ty, readonly):
# copy-on-write for readonly tiles
else:
t = Tile()
self.tiledict[(tx, ty)] = t
+ if t.mipmap_dirty:
+ # regenerate mipmap
+ if self.mipmap_level > 0:
+ for x in xrange(2):
+ for y in xrange(2):
+ src = self.parent.get_tile_memory(tx*2 + x, ty*2 + y, True)
+ mypaintlib.tile_downscale_rgba16(src, t.rgba, x*N/2, y*N/2)
+ t.mipmap_dirty = False
if t.readonly and not readonly:
# OPTIMIZE: we could do the copying in save_snapshot() instead, this might reduce the latency while drawing
# (eg. tile.valid_copy = some_other_tile_instance; and valid_copy = None here)
# before doing this, measure the worst-case time of the call below; same thing with new tiles
t = t.copy()
self.tiledict[(tx, ty)] = t
+ if not readonly:
+ self.mark_mipmap_dirty(tx, ty, t)
return t.rgba
+ def mark_mipmap_dirty(self, tx, ty, t=None):
+ if t is None:
+ t = self.tiledict.get((tx,ty))
+ if t is None:
+ t = Tile()
+ self.tiledict[(tx, ty)] = t
+ if not t.mipmap_dirty:
+ t.mipmap_dirty = True
+ if self.mipmap:
+ self.mipmap.mark_mipmap_dirty(tx/2, ty/2)
+
def blit_tile_into(self, dst, tx, ty):
# used mainly for saving (transparent PNG)
assert dst.shape[2] == 4
tmp = self.get_tile_memory(tx, ty, readonly=True)
return mypaintlib.tile_convert_rgba16_to_rgba8(tmp, dst)
- def composite_tile_over(self, dst, tx, ty, opac):
+ def composite_tile_over(self, dst, tx, ty, mipmap_level=0, opac=1.0):
"""
composite one tile of this surface over the array dst, modifying only dst
"""
- tile = self.tiledict.get((tx, ty))
- if tile is None:
+
+ if self.mipmap_level < mipmap_level:
+ return self.mipmap.composite_tile_over(dst, tx, ty, mipmap_level, opac)
+ if not (tx,ty) in self.tiledict:
return
+ src = self.get_tile_memory(tx, ty, True)
if dst.shape[2] == 3 and dst.dtype == 'uint8':
- mypaintlib.tile_composite_rgba16_over_rgb8(tile.rgba, dst, opac)
+ mypaintlib.tile_composite_rgba16_over_rgb8(src, dst, opac)
elif dst.shape[2] == 4 and dst.dtype == 'uint16':
# rarely used (only for merging layers)
# src (premultiplied) OVER dst (premultiplied)
# dstColor = srcColor + (1.0 - srcAlpha) * dstColor
- src = tile.rgba
one_minus_srcAlpha = (1<<15) - (opac * src[:,:,3:4]).astype('uint32')
dst[:,:,:] = opac * src[:,:,:] + ((one_minus_srcAlpha * dst[:,:,:]) >> 15).astype('uint16')
self.tiledict = sshot.tiledict.copy()
new = set(self.tiledict.items())
dirty = old.symmetric_difference(new)
+ for pos, tile in dirty:
+ self.mark_mipmap_dirty(*pos)
bbox = get_tiles_bbox([pos for (pos, tile) in dirty])
if not bbox.empty():
self.notify_observers(*bbox)