Beispiel #1
0
def flood_fill(src, x, y, color, bbox, tolerance, dst):
    """Fills connected areas of one surface into another

    :param src: Source surface-like object
    :type src: Anything supporting readonly tile_request()
    :param x: Starting point X coordinate
    :param y: Starting point Y coordinate
    :param color: an RGB color
    :type color: tuple
    :param bbox: Bounding box: limits the fill
    :type bbox: lib.helpers.Rect or equivalent 4-tuple
    :param tolerance: how much filled pixels are permitted to vary
    :type tolerance: float [0.0, 1.0]
    :param dst: Target surface
    :type dst: lib.tiledsurface.MyPaintSurface

    See also `lib.layer.Layer.flood_fill()`.
    """
    # Color to fill with
    fill_r, fill_g, fill_b = color

    # Limits
    tolerance = helpers.clamp(tolerance, 0.0, 1.0)

    # Maximum area to fill: tile and in-tile pixel extents
    bbx, bby, bbw, bbh = bbox
    if bbh <= 0 or bbw <= 0:
        return
    bbbrx = bbx + bbw - 1
    bbbry = bby + bbh - 1
    min_tx = int(bbx // N)
    min_ty = int(bby // N)
    max_tx = int(bbbrx // N)
    max_ty = int(bbbry // N)
    min_px = int(bbx % N)
    min_py = int(bby % N)
    max_px = int(bbbrx % N)
    max_py = int(bbbry % N)

    # Tile and pixel addressing for the seed point
    tx, ty = int(x // N), int(y // N)
    px, py = int(x % N), int(y % N)

    # Sample the pixel color there to obtain the target color
    with src.tile_request(tx, ty, readonly=True) as start:
        targ_r, targ_g, targ_b, targ_a = [int(c) for c in start[py][px]]
    if targ_a == 0:
        targ_r = 0
        targ_g = 0
        targ_b = 0
        targ_a = 0

    # Flood-fill loop
    filled = {}
    tileq = [
        ((tx, ty),
         [(px, py)])
    ]
    while len(tileq) > 0:
        (tx, ty), seeds = tileq.pop(0)
        # Bbox-derived limits
        if tx > max_tx or ty > max_ty:
            continue
        if tx < min_tx or ty < min_ty:
            continue
        # Pixel limits within this tile...
        min_x = 0
        min_y = 0
        max_x = N-1
        max_y = N-1
        # ... vary at the edges
        if tx == min_tx:
            min_x = min_px
        if ty == min_ty:
            min_y = min_py
        if tx == max_tx:
            max_x = max_px
        if ty == max_ty:
            max_y = max_py
        # Flood-fill one tile
        with src.tile_request(tx, ty, readonly=True) as src_tile:
            dst_tile = filled.get((tx, ty), None)
            if dst_tile is None:
                dst_tile = numpy.zeros((N, N, 4), 'uint16')
                filled[(tx, ty)] = dst_tile
            overflows = mypaintlib.tile_flood_fill(
                src_tile, dst_tile, seeds,
                targ_r, targ_g, targ_b, targ_a,
                fill_r, fill_g, fill_b,
                min_x, min_y, max_x, max_y,
                tolerance
            )
            seeds_n, seeds_e, seeds_s, seeds_w = overflows
        # Enqueue overflows in each cardinal direction
        if seeds_n and ty > min_ty:
            tpos = (tx, ty-1)
            tileq.append((tpos, seeds_n))
        if seeds_w and tx > min_tx:
            tpos = (tx-1, ty)
            tileq.append((tpos, seeds_w))
        if seeds_s and ty < max_ty:
            tpos = (tx, ty+1)
            tileq.append((tpos, seeds_s))
        if seeds_e and tx < max_tx:
            tpos = (tx+1, ty)
            tileq.append((tpos, seeds_e))

    # Composite filled tiles into the destination surface
    mode = mypaintlib.CombineNormal
    for (tx, ty), src_tile in filled.iteritems():
        with dst.tile_request(tx, ty, readonly=False) as dst_tile:
            mypaintlib.tile_combine(mode, src_tile, dst_tile, True, 1.0)
        dst._mark_mipmap_dirty(tx, ty)
    bbox = lib.surface.get_tiles_bbox(filled)
    dst.notify_observers(*bbox)
Beispiel #2
0
def flood_fill(src, x, y, color, bbox, tolerance, dst):
    """Fills connected areas of one surface into another

    :param src: Source surface-like object
    :type src: Anything supporting readonly tile_request()
    :param x: Starting point X coordinate
    :param y: Starting point Y coordinate
    :param color: an RGB color
    :type color: tuple
    :param bbox: Bounding box: limits the fill
    :type bbox: lib.helpers.Rect or equivalent 4-tuple
    :param tolerance: how much filled pixels are permitted to vary
    :type tolerance: float [0.0, 1.0]
    :param dst: Target surface
    :type dst: lib.tiledsurface.MyPaintSurface

    See also `lib.layer.Layer.flood_fill()`.
    """
    # Color to fill with
    fill_r, fill_g, fill_b = color

    # Limits
    tolerance = helpers.clamp(tolerance, 0.0, 1.0)

    # Maximum area to fill: tile and in-tile pixel extents
    bbx, bby, bbw, bbh = bbox
    if bbh <= 0 or bbw <= 0:
        return
    bbbrx = bbx + bbw - 1
    bbbry = bby + bbh - 1
    min_tx = int(bbx // N)
    min_ty = int(bby // N)
    max_tx = int(bbbrx // N)
    max_ty = int(bbbry // N)
    min_px = int(bbx % N)
    min_py = int(bby % N)
    max_px = int(bbbrx % N)
    max_py = int(bbbry % N)

    # Tile and pixel addressing for the seed point
    tx, ty = int(x // N), int(y // N)
    px, py = int(x % N), int(y % N)

    # Sample the pixel color there to obtain the target color
    with src.tile_request(tx, ty, readonly=True) as start:
        targ_r, targ_g, targ_b, targ_a = [int(c) for c in start[py][px]]
    if targ_a == 0:
        targ_r = 0
        targ_g = 0
        targ_b = 0
        targ_a = 0

    # Flood-fill loop
    filled = {}
    tileq = [((tx, ty), [(px, py)])]
    while len(tileq) > 0:
        (tx, ty), seeds = tileq.pop(0)
        # Bbox-derived limits
        if tx > max_tx or ty > max_ty:
            continue
        if tx < min_tx or ty < min_ty:
            continue
        # Pixel limits within this tile...
        min_x = 0
        min_y = 0
        max_x = N - 1
        max_y = N - 1
        # ... vary at the edges
        if tx == min_tx:
            min_x = min_px
        if ty == min_ty:
            min_y = min_py
        if tx == max_tx:
            max_x = max_px
        if ty == max_ty:
            max_y = max_py
        # Flood-fill one tile
        with src.tile_request(tx, ty, readonly=True) as src_tile:
            dst_tile = filled.get((tx, ty), None)
            if dst_tile is None:
                dst_tile = numpy.zeros((N, N, 4), 'uint16')
                filled[(tx, ty)] = dst_tile
            overflows = mypaintlib.tile_flood_fill(src_tile, dst_tile, seeds,
                                                   targ_r, targ_g, targ_b,
                                                   targ_a, fill_r, fill_g,
                                                   fill_b, min_x, min_y, max_x,
                                                   max_y, tolerance)
            seeds_n, seeds_e, seeds_s, seeds_w = overflows
        # Enqueue overflows in each cardinal direction
        if seeds_n and ty > min_ty:
            tpos = (tx, ty - 1)
            tileq.append((tpos, seeds_n))
        if seeds_w and tx > min_tx:
            tpos = (tx - 1, ty)
            tileq.append((tpos, seeds_w))
        if seeds_s and ty < max_ty:
            tpos = (tx, ty + 1)
            tileq.append((tpos, seeds_s))
        if seeds_e and tx < max_tx:
            tpos = (tx + 1, ty)
            tileq.append((tpos, seeds_e))

    # Composite filled tiles into the destination surface
    mode = mypaintlib.CombineNormal
    for (tx, ty), src_tile in filled.iteritems():
        with dst.tile_request(tx, ty, readonly=False) as dst_tile:
            mypaintlib.tile_combine(mode, src_tile, dst_tile, True, 1.0)
        dst._mark_mipmap_dirty(tx, ty)
    bbox = lib.surface.get_tiles_bbox(filled)
    dst.notify_observers(*bbox)